### 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
| @@ -13,6 +13,7 @@ | |||
| "@hookform/resolvers": "^3.9.1", | |||
| "@js-preview/excel": "^1.7.8", | |||
| "@monaco-editor/react": "^4.6.0", | |||
| "@radix-ui/react-avatar": "^1.1.1", | |||
| "@radix-ui/react-checkbox": "^1.1.2", | |||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | |||
| "@radix-ui/react-icons": "^1.3.1", | |||
| @@ -22,6 +23,7 @@ | |||
| "@radix-ui/react-separator": "^1.1.0", | |||
| "@radix-ui/react-slot": "^1.1.0", | |||
| "@radix-ui/react-switch": "^1.1.1", | |||
| "@radix-ui/react-tabs": "^1.1.1", | |||
| "@radix-ui/react-toast": "^1.2.2", | |||
| "@tanstack/react-query": "^5.40.0", | |||
| "@tanstack/react-query-devtools": "^5.51.5", | |||
| @@ -4083,6 +4085,31 @@ | |||
| } | |||
| } | |||
| }, | |||
| "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": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", | |||
| @@ -4702,6 +4729,35 @@ | |||
| } | |||
| } | |||
| }, | |||
| "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": { | |||
| "version": "1.2.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", | |||
| @@ -24,6 +24,7 @@ | |||
| "@hookform/resolvers": "^3.9.1", | |||
| "@js-preview/excel": "^1.7.8", | |||
| "@monaco-editor/react": "^4.6.0", | |||
| "@radix-ui/react-avatar": "^1.1.1", | |||
| "@radix-ui/react-checkbox": "^1.1.2", | |||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | |||
| "@radix-ui/react-icons": "^1.3.1", | |||
| @@ -33,6 +34,7 @@ | |||
| "@radix-ui/react-separator": "^1.1.0", | |||
| "@radix-ui/react-slot": "^1.1.0", | |||
| "@radix-ui/react-switch": "^1.1.1", | |||
| "@radix-ui/react-tabs": "^1.1.1", | |||
| "@radix-ui/react-toast": "^1.2.2", | |||
| "@tanstack/react-query": "^5.40.0", | |||
| "@tanstack/react-query-devtools": "^5.51.5", | |||
| @@ -0,0 +1,50 @@ | |||
| '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 }; | |||
| @@ -0,0 +1,19 @@ | |||
| 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> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| 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> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| '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 }; | |||
| @@ -0,0 +1,39 @@ | |||
| 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> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| 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> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| import { CardWithForm } from './card'; | |||
| import { HomeHeader } from './header'; | |||
| const Home = () => { | |||
| return ( | |||
| <div> | |||
| <HomeHeader></HomeHeader> | |||
| <section> | |||
| <CardWithForm></CardWithForm> | |||
| </section> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default Home; | |||
| @@ -126,6 +126,11 @@ const routes = [ | |||
| component: '@/pages/demo', | |||
| layout: false, | |||
| }, | |||
| { | |||
| path: '/home', | |||
| layout: false, | |||
| component: '@/pages/home', | |||
| }, | |||
| ]; | |||
| export default routes; | |||
| @@ -3,7 +3,7 @@ const { fontFamily } = require('tailwindcss/defaultTheme'); | |||
| /** @type {import('tailwindcss').Config} */ | |||
| module.exports = { | |||
| darkMode: ['class'], | |||
| darkMode: ['selector'], | |||
| content: [ | |||
| './src/pages/**/*.tsx', | |||
| './src/components/**/*.tsx', | |||
| @@ -52,6 +52,10 @@ module.exports = { | |||
| DEFAULT: 'hsl(var(--card))', | |||
| foreground: 'hsl(var(--card-foreground))', | |||
| }, | |||
| backgroundInverseStandard: { | |||
| DEFAULT: 'var(--background-inverse-standard)', | |||
| foreground: 'var(--background-inverse-standard-foreground)', | |||
| }, | |||
| }, | |||
| borderRadius: { | |||
| lg: `var(--radius)`, | |||
| @@ -34,6 +34,9 @@ | |||
| --ring: 215 20.2% 65.1%; | |||
| --radius: 0.5rem; | |||
| --background-inverse-standard: rgba(58, 56, 65, 0.15); | |||
| --background-inverse-standard-foreground: rgb(92, 81, 81); | |||
| } | |||
| .dark { | |||
| @@ -67,6 +70,9 @@ | |||
| --ring: 216 34% 17%; | |||
| --radius: 0.5rem; | |||
| --background-inverse-standard: rgba(230, 227, 246, 0.15); | |||
| --background-inverse-standard-foreground: rgba(255, 255, 255, 1); | |||
| } | |||
| } | |||