### What problem does this PR solve? feat: Add custom background color #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.14.0
| "@hookform/resolvers": "^3.9.1", | "@hookform/resolvers": "^3.9.1", | ||||
| "@js-preview/excel": "^1.7.8", | "@js-preview/excel": "^1.7.8", | ||||
| "@monaco-editor/react": "^4.6.0", | "@monaco-editor/react": "^4.6.0", | ||||
| "@radix-ui/react-avatar": "^1.1.1", | |||||
| "@radix-ui/react-checkbox": "^1.1.2", | "@radix-ui/react-checkbox": "^1.1.2", | ||||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | "@radix-ui/react-dropdown-menu": "^2.1.2", | ||||
| "@radix-ui/react-icons": "^1.3.1", | "@radix-ui/react-icons": "^1.3.1", | ||||
| "@radix-ui/react-separator": "^1.1.0", | "@radix-ui/react-separator": "^1.1.0", | ||||
| "@radix-ui/react-slot": "^1.1.0", | "@radix-ui/react-slot": "^1.1.0", | ||||
| "@radix-ui/react-switch": "^1.1.1", | "@radix-ui/react-switch": "^1.1.1", | ||||
| "@radix-ui/react-tabs": "^1.1.1", | |||||
| "@radix-ui/react-toast": "^1.2.2", | "@radix-ui/react-toast": "^1.2.2", | ||||
| "@tanstack/react-query": "^5.40.0", | "@tanstack/react-query": "^5.40.0", | ||||
| "@tanstack/react-query-devtools": "^5.51.5", | "@tanstack/react-query-devtools": "^5.51.5", | ||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@radix-ui/react-avatar": { | |||||
| "version": "1.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.1.tgz", | |||||
| "integrity": "sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==", | |||||
| "dependencies": { | |||||
| "@radix-ui/react-context": "1.1.1", | |||||
| "@radix-ui/react-primitive": "2.0.0", | |||||
| "@radix-ui/react-use-callback-ref": "1.1.0", | |||||
| "@radix-ui/react-use-layout-effect": "1.1.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": "*", | |||||
| "@types/react-dom": "*", | |||||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "@types/react-dom": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@radix-ui/react-checkbox": { | "node_modules/@radix-ui/react-checkbox": { | ||||
| "version": "1.1.2", | "version": "1.1.2", | ||||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", | "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", | ||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@radix-ui/react-tabs": { | |||||
| "version": "1.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", | |||||
| "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", | |||||
| "dependencies": { | |||||
| "@radix-ui/primitive": "1.1.0", | |||||
| "@radix-ui/react-context": "1.1.1", | |||||
| "@radix-ui/react-direction": "1.1.0", | |||||
| "@radix-ui/react-id": "1.1.0", | |||||
| "@radix-ui/react-presence": "1.1.1", | |||||
| "@radix-ui/react-primitive": "2.0.0", | |||||
| "@radix-ui/react-roving-focus": "1.1.0", | |||||
| "@radix-ui/react-use-controllable-state": "1.1.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": "*", | |||||
| "@types/react-dom": "*", | |||||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||||
| "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "@types/react-dom": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@radix-ui/react-toast": { | "node_modules/@radix-ui/react-toast": { | ||||
| "version": "1.2.2", | "version": "1.2.2", | ||||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", | "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", |
| "@hookform/resolvers": "^3.9.1", | "@hookform/resolvers": "^3.9.1", | ||||
| "@js-preview/excel": "^1.7.8", | "@js-preview/excel": "^1.7.8", | ||||
| "@monaco-editor/react": "^4.6.0", | "@monaco-editor/react": "^4.6.0", | ||||
| "@radix-ui/react-avatar": "^1.1.1", | |||||
| "@radix-ui/react-checkbox": "^1.1.2", | "@radix-ui/react-checkbox": "^1.1.2", | ||||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | "@radix-ui/react-dropdown-menu": "^2.1.2", | ||||
| "@radix-ui/react-icons": "^1.3.1", | "@radix-ui/react-icons": "^1.3.1", | ||||
| "@radix-ui/react-separator": "^1.1.0", | "@radix-ui/react-separator": "^1.1.0", | ||||
| "@radix-ui/react-slot": "^1.1.0", | "@radix-ui/react-slot": "^1.1.0", | ||||
| "@radix-ui/react-switch": "^1.1.1", | "@radix-ui/react-switch": "^1.1.1", | ||||
| "@radix-ui/react-tabs": "^1.1.1", | |||||
| "@radix-ui/react-toast": "^1.2.2", | "@radix-ui/react-toast": "^1.2.2", | ||||
| "@tanstack/react-query": "^5.40.0", | "@tanstack/react-query": "^5.40.0", | ||||
| "@tanstack/react-query-devtools": "^5.51.5", | "@tanstack/react-query-devtools": "^5.51.5", |
| 'use client'; | |||||
| import * as AvatarPrimitive from '@radix-ui/react-avatar'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Avatar = React.forwardRef< | |||||
| React.ElementRef<typeof AvatarPrimitive.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <AvatarPrimitive.Root | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| Avatar.displayName = AvatarPrimitive.Root.displayName; | |||||
| const AvatarImage = React.forwardRef< | |||||
| React.ElementRef<typeof AvatarPrimitive.Image>, | |||||
| React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <AvatarPrimitive.Image | |||||
| ref={ref} | |||||
| className={cn('aspect-square h-full w-full', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| AvatarImage.displayName = AvatarPrimitive.Image.displayName; | |||||
| const AvatarFallback = React.forwardRef< | |||||
| React.ElementRef<typeof AvatarPrimitive.Fallback>, | |||||
| React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <AvatarPrimitive.Fallback | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'flex h-full w-full items-center justify-center rounded-full bg-muted', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; | |||||
| export { Avatar, AvatarFallback, AvatarImage }; |
| import { cn } from '@/lib/utils'; | |||||
| export function Container({ | |||||
| children, | |||||
| className, | |||||
| ...props | |||||
| }: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) { | |||||
| return ( | |||||
| <div | |||||
| className={cn( | |||||
| 'px-2 py-1 bg-backgroundInverseStandard text-backgroundInverseStandard-foreground inline-flex items-center rounded-sm gap-2', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| {children} | |||||
| </div> | |||||
| ); | |||||
| } |
| import { cn } from '@/lib/utils'; | |||||
| import * as React from 'react'; | |||||
| export declare type SegmentedValue = string | number; | |||||
| export declare type SegmentedRawOption = SegmentedValue; | |||||
| export interface SegmentedLabeledOption { | |||||
| className?: string; | |||||
| disabled?: boolean; | |||||
| label: React.ReactNode; | |||||
| value: SegmentedRawOption; | |||||
| /** | |||||
| * html `title` property for label | |||||
| */ | |||||
| title?: string; | |||||
| } | |||||
| declare type SegmentedOptions = (SegmentedRawOption | SegmentedLabeledOption)[]; | |||||
| export interface SegmentedProps | |||||
| extends Omit<React.HTMLProps<HTMLDivElement>, 'onChange'> { | |||||
| options: SegmentedOptions; | |||||
| defaultValue?: SegmentedValue; | |||||
| value?: SegmentedValue; | |||||
| onChange?: (value: SegmentedValue) => void; | |||||
| disabled?: boolean; | |||||
| prefixCls?: string; | |||||
| direction?: 'ltr' | 'rtl'; | |||||
| motionName?: string; | |||||
| } | |||||
| export function Segmented({ options, value, onChange }: SegmentedProps) { | |||||
| return ( | |||||
| <div className="flex items-center rounded-sm p-1 gap-2 bg-zinc-200"> | |||||
| {options.map((option) => { | |||||
| const isObject = typeof option === 'object'; | |||||
| const actualValue = isObject ? option.value : option; | |||||
| return ( | |||||
| <div | |||||
| key={actualValue} | |||||
| className={cn( | |||||
| 'inline-flex items-center px-2 py-1 text-sm font-medium rounded-sm cursor-pointer', | |||||
| { 'bg-indigo-400': value === actualValue }, | |||||
| )} | |||||
| onClick={() => onChange?.(actualValue)} | |||||
| > | |||||
| {isObject ? option.label : option} | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| ); | |||||
| } |
| 'use client'; | |||||
| import * as TabsPrimitive from '@radix-ui/react-tabs'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Tabs = TabsPrimitive.Root; | |||||
| const TabsList = React.forwardRef< | |||||
| React.ElementRef<typeof TabsPrimitive.List>, | |||||
| React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <TabsPrimitive.List | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| TabsList.displayName = TabsPrimitive.List.displayName; | |||||
| const TabsTrigger = React.forwardRef< | |||||
| React.ElementRef<typeof TabsPrimitive.Trigger>, | |||||
| React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <TabsPrimitive.Trigger | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; | |||||
| const TabsContent = React.forwardRef< | |||||
| React.ElementRef<typeof TabsPrimitive.Content>, | |||||
| React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <TabsPrimitive.Content | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| TabsContent.displayName = TabsPrimitive.Content.displayName; | |||||
| export { Tabs, TabsContent, TabsList, TabsTrigger }; |
| import { Button } from '@/components/ui/button'; | |||||
| import { | |||||
| Card, | |||||
| CardContent, | |||||
| CardDescription, | |||||
| CardFooter, | |||||
| CardHeader, | |||||
| CardTitle, | |||||
| } from '@/components/ui/card'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { Label } from '@/components/ui/label'; | |||||
| export function CardWithForm() { | |||||
| return ( | |||||
| <Card className="w-[350px]"> | |||||
| <CardHeader> | |||||
| <CardTitle>Create project</CardTitle> | |||||
| <CardDescription>Deploy your new project in one-click.</CardDescription> | |||||
| </CardHeader> | |||||
| <CardContent> | |||||
| <form> | |||||
| <div className="grid w-full items-center gap-4"> | |||||
| <div className="flex flex-col space-y-1.5"> | |||||
| <Label htmlFor="name">Name</Label> | |||||
| <Input id="name" placeholder="Name of your project" /> | |||||
| </div> | |||||
| <div className="flex flex-col space-y-1.5"> | |||||
| <Label htmlFor="framework">Framework</Label> | |||||
| </div> | |||||
| </div> | |||||
| </form> | |||||
| </CardContent> | |||||
| <CardFooter className="flex justify-between"> | |||||
| <Button variant="outline">Cancel</Button> | |||||
| <Button>Deploy</Button> | |||||
| </CardFooter> | |||||
| </Card> | |||||
| ); | |||||
| } |
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { Container } from '@/components/ui/container'; | |||||
| import { Segmented, SegmentedValue } from '@/components/ui/segmented '; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { useNavigateWithFromState } from '@/hooks/route-hook'; | |||||
| import { | |||||
| Cpu, | |||||
| Github, | |||||
| Library, | |||||
| MessageSquareText, | |||||
| Search, | |||||
| Star, | |||||
| Zap, | |||||
| } from 'lucide-react'; | |||||
| import { useCallback, useMemo, useState } from 'react'; | |||||
| import { useLocation } from 'umi'; | |||||
| export function HomeHeader() { | |||||
| const { t } = useTranslate('header'); | |||||
| const { pathname } = useLocation(); | |||||
| const navigate = useNavigateWithFromState(); | |||||
| const [currentPath, setCurrentPath] = useState('/home'); | |||||
| const tagsData = useMemo( | |||||
| () => [ | |||||
| { path: '/home', name: t('knowledgeBase'), icon: Library }, | |||||
| { path: '/chat', name: t('chat'), icon: MessageSquareText }, | |||||
| { path: '/search', name: t('search'), icon: Search }, | |||||
| { path: '/flow', name: t('flow'), icon: Cpu }, | |||||
| // { path: '/file', name: t('fileManager'), icon: FileIcon }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const options = useMemo(() => { | |||||
| return tagsData.map((tag) => { | |||||
| const HeaderIcon = tag.icon; | |||||
| return { | |||||
| label: ( | |||||
| <div className="flex items-center gap-1"> | |||||
| <HeaderIcon className="size-5"></HeaderIcon> | |||||
| <span>{tag.name}</span> | |||||
| </div> | |||||
| ), | |||||
| value: tag.path, | |||||
| }; | |||||
| }); | |||||
| }, [tagsData]); | |||||
| // const currentPath = useMemo(() => { | |||||
| // return tagsData.find((x) => pathname.startsWith(x.path))?.name || 'home'; | |||||
| // }, [pathname, tagsData]); | |||||
| const handleChange = (path: SegmentedValue) => { | |||||
| // navigate(path as string); | |||||
| setCurrentPath(path as string); | |||||
| }; | |||||
| const handleLogoClick = useCallback(() => { | |||||
| navigate('/'); | |||||
| }, [navigate]); | |||||
| return ( | |||||
| <section className="px-[60px] py-[12px] flex justify-between"> | |||||
| <div className="flex items-center gap-4"> | |||||
| <img | |||||
| src={'/logo.svg'} | |||||
| alt="logo" | |||||
| className="w-[100] h-[100] mr-[12]" | |||||
| onClick={handleLogoClick} | |||||
| /> | |||||
| <Button variant="secondary"> | |||||
| <Github /> | |||||
| 21.5k stars | |||||
| <Star /> | |||||
| </Button> | |||||
| </div> | |||||
| <div> | |||||
| <Segmented | |||||
| options={options} | |||||
| value={currentPath} | |||||
| onChange={handleChange} | |||||
| ></Segmented> | |||||
| </div> | |||||
| <div className="flex items-center gap-4"> | |||||
| <Button variant="secondary">V 0.13.0</Button> | |||||
| <Container> | |||||
| <Avatar className="w-[30px] h-[30px]"> | |||||
| <AvatarImage src="https://github.com/shadcn.png" /> | |||||
| <AvatarFallback>CN</AvatarFallback> | |||||
| </Avatar> | |||||
| yifanwu92@gmail.com | |||||
| <Button | |||||
| variant="destructive" | |||||
| className="py-[2px] px-[8px] h-[23px] rounded-[4px]" | |||||
| > | |||||
| <Zap /> | |||||
| Pro | |||||
| </Button> | |||||
| </Container> | |||||
| </div> | |||||
| </section> | |||||
| ); | |||||
| } |
| import { CardWithForm } from './card'; | |||||
| import { HomeHeader } from './header'; | |||||
| const Home = () => { | |||||
| return ( | |||||
| <div> | |||||
| <HomeHeader></HomeHeader> | |||||
| <section> | |||||
| <CardWithForm></CardWithForm> | |||||
| </section> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Home; |
| component: '@/pages/demo', | component: '@/pages/demo', | ||||
| layout: false, | layout: false, | ||||
| }, | }, | ||||
| { | |||||
| path: '/home', | |||||
| layout: false, | |||||
| component: '@/pages/home', | |||||
| }, | |||||
| ]; | ]; | ||||
| export default routes; | export default routes; |
| /** @type {import('tailwindcss').Config} */ | /** @type {import('tailwindcss').Config} */ | ||||
| module.exports = { | module.exports = { | ||||
| darkMode: ['class'], | |||||
| darkMode: ['selector'], | |||||
| content: [ | content: [ | ||||
| './src/pages/**/*.tsx', | './src/pages/**/*.tsx', | ||||
| './src/components/**/*.tsx', | './src/components/**/*.tsx', | ||||
| DEFAULT: 'hsl(var(--card))', | DEFAULT: 'hsl(var(--card))', | ||||
| foreground: 'hsl(var(--card-foreground))', | foreground: 'hsl(var(--card-foreground))', | ||||
| }, | }, | ||||
| backgroundInverseStandard: { | |||||
| DEFAULT: 'var(--background-inverse-standard)', | |||||
| foreground: 'var(--background-inverse-standard-foreground)', | |||||
| }, | |||||
| }, | }, | ||||
| borderRadius: { | borderRadius: { | ||||
| lg: `var(--radius)`, | lg: `var(--radius)`, |
| --ring: 215 20.2% 65.1%; | --ring: 215 20.2% 65.1%; | ||||
| --radius: 0.5rem; | --radius: 0.5rem; | ||||
| --background-inverse-standard: rgba(58, 56, 65, 0.15); | |||||
| --background-inverse-standard-foreground: rgb(92, 81, 81); | |||||
| } | } | ||||
| .dark { | .dark { | ||||
| --ring: 216 34% 17%; | --ring: 216 34% 17%; | ||||
| --radius: 0.5rem; | --radius: 0.5rem; | ||||
| --background-inverse-standard: rgba(230, 227, 246, 0.15); | |||||
| --background-inverse-standard-foreground: rgba(255, 255, 255, 1); | |||||
| } | } | ||||
| } | } | ||||