### What problem does this PR solve? Feat: Add MultiSelect #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.15.0
| @@ -16,6 +16,7 @@ | |||
| "@radix-ui/react-aspect-ratio": "^1.1.0", | |||
| "@radix-ui/react-avatar": "^1.1.1", | |||
| "@radix-ui/react-checkbox": "^1.1.2", | |||
| "@radix-ui/react-dialog": "^1.1.4", | |||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | |||
| "@radix-ui/react-icons": "^1.3.1", | |||
| "@radix-ui/react-label": "^2.1.0", | |||
| @@ -40,6 +41,7 @@ | |||
| "class-variance-authority": "^0.7.0", | |||
| "classnames": "^2.5.1", | |||
| "clsx": "^2.1.1", | |||
| "cmdk": "^1.0.4", | |||
| "dayjs": "^1.11.10", | |||
| "dompurify": "^3.1.6", | |||
| "eventsource-parser": "^1.1.2", | |||
| @@ -4240,6 +4242,219 @@ | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-dialog": { | |||
| "version": "1.1.4", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz", | |||
| "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==", | |||
| "dependencies": { | |||
| "@radix-ui/primitive": "1.1.1", | |||
| "@radix-ui/react-compose-refs": "1.1.1", | |||
| "@radix-ui/react-context": "1.1.1", | |||
| "@radix-ui/react-dismissable-layer": "1.1.3", | |||
| "@radix-ui/react-focus-guards": "1.1.1", | |||
| "@radix-ui/react-focus-scope": "1.1.1", | |||
| "@radix-ui/react-id": "1.1.0", | |||
| "@radix-ui/react-portal": "1.1.3", | |||
| "@radix-ui/react-presence": "1.1.2", | |||
| "@radix-ui/react-primitive": "2.0.1", | |||
| "@radix-ui/react-slot": "1.1.1", | |||
| "@radix-ui/react-use-controllable-state": "1.1.0", | |||
| "aria-hidden": "^1.1.1", | |||
| "react-remove-scroll": "^2.6.1" | |||
| }, | |||
| "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-dialog/node_modules/@radix-ui/primitive": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.1.tgz", | |||
| "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" | |||
| }, | |||
| "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", | |||
| "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { | |||
| "version": "1.1.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", | |||
| "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", | |||
| "dependencies": { | |||
| "@radix-ui/primitive": "1.1.1", | |||
| "@radix-ui/react-compose-refs": "1.1.1", | |||
| "@radix-ui/react-primitive": "2.0.1", | |||
| "@radix-ui/react-use-callback-ref": "1.1.0", | |||
| "@radix-ui/react-use-escape-keydown": "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-dialog/node_modules/@radix-ui/react-focus-scope": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", | |||
| "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.1", | |||
| "@radix-ui/react-primitive": "2.0.1", | |||
| "@radix-ui/react-use-callback-ref": "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-dialog/node_modules/@radix-ui/react-portal": { | |||
| "version": "1.1.3", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", | |||
| "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", | |||
| "dependencies": { | |||
| "@radix-ui/react-primitive": "2.0.1", | |||
| "@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-dialog/node_modules/@radix-ui/react-presence": { | |||
| "version": "1.1.2", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", | |||
| "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.1", | |||
| "@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-dialog/node_modules/@radix-ui/react-primitive": { | |||
| "version": "2.0.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", | |||
| "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", | |||
| "dependencies": { | |||
| "@radix-ui/react-slot": "1.1.1" | |||
| }, | |||
| "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-dialog/node_modules/@radix-ui/react-slot": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", | |||
| "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", | |||
| "dependencies": { | |||
| "@radix-ui/react-compose-refs": "1.1.1" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { | |||
| "version": "2.6.1", | |||
| "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.6.1.tgz", | |||
| "integrity": "sha512-jWEvWQidZ/C/FnFlUIB1mDLpY3r7uEb22WZ3uVeKj520caKDiaBsNDEB9J1gHJgpiLo+eTdPl2MVi0JitFTiFg==", | |||
| "dependencies": { | |||
| "react-remove-scroll-bar": "^2.3.7", | |||
| "react-style-singleton": "^2.2.1", | |||
| "tslib": "^2.1.0", | |||
| "use-callback-ref": "^1.3.0", | |||
| "use-sidecar": "^1.1.2" | |||
| }, | |||
| "engines": { | |||
| "node": ">=10" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "*", | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| "optional": true | |||
| } | |||
| } | |||
| }, | |||
| "node_modules/@radix-ui/react-direction": { | |||
| "version": "1.1.0", | |||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", | |||
| @@ -10672,6 +10887,29 @@ | |||
| "node": ">=6" | |||
| } | |||
| }, | |||
| "node_modules/cmdk": { | |||
| "version": "1.0.4", | |||
| "resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.0.4.tgz", | |||
| "integrity": "sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==", | |||
| "dependencies": { | |||
| "@radix-ui/react-dialog": "^1.1.2", | |||
| "@radix-ui/react-id": "^1.1.0", | |||
| "@radix-ui/react-primitive": "^2.0.0", | |||
| "use-sync-external-store": "^1.2.2" | |||
| }, | |||
| "peerDependencies": { | |||
| "react": "^18 || ^19 || ^19.0.0-rc", | |||
| "react-dom": "^18 || ^19 || ^19.0.0-rc" | |||
| } | |||
| }, | |||
| "node_modules/cmdk/node_modules/use-sync-external-store": { | |||
| "version": "1.4.0", | |||
| "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", | |||
| "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", | |||
| "peerDependencies": { | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |||
| } | |||
| }, | |||
| "node_modules/co": { | |||
| "version": "4.6.0", | |||
| "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", | |||
| @@ -25069,19 +25307,19 @@ | |||
| } | |||
| }, | |||
| "node_modules/react-remove-scroll-bar": { | |||
| "version": "2.3.6", | |||
| "resolved": "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", | |||
| "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", | |||
| "version": "2.3.8", | |||
| "resolved": "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", | |||
| "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", | |||
| "dependencies": { | |||
| "react-style-singleton": "^2.2.1", | |||
| "react-style-singleton": "^2.2.2", | |||
| "tslib": "^2.0.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">=10" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0" | |||
| "@types/react": "*", | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| @@ -25229,20 +25467,19 @@ | |||
| } | |||
| }, | |||
| "node_modules/react-style-singleton": { | |||
| "version": "2.2.1", | |||
| "resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz", | |||
| "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", | |||
| "version": "2.2.3", | |||
| "resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz", | |||
| "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", | |||
| "dependencies": { | |||
| "get-nonce": "^1.0.0", | |||
| "invariant": "^2.2.4", | |||
| "tslib": "^2.0.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">=10" | |||
| }, | |||
| "peerDependencies": { | |||
| "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0" | |||
| "@types/react": "*", | |||
| "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
| }, | |||
| "peerDependenciesMeta": { | |||
| "@types/react": { | |||
| @@ -27,6 +27,7 @@ | |||
| "@radix-ui/react-aspect-ratio": "^1.1.0", | |||
| "@radix-ui/react-avatar": "^1.1.1", | |||
| "@radix-ui/react-checkbox": "^1.1.2", | |||
| "@radix-ui/react-dialog": "^1.1.4", | |||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | |||
| "@radix-ui/react-icons": "^1.3.1", | |||
| "@radix-ui/react-label": "^2.1.0", | |||
| @@ -51,6 +52,7 @@ | |||
| "class-variance-authority": "^0.7.0", | |||
| "classnames": "^2.5.1", | |||
| "clsx": "^2.1.1", | |||
| "cmdk": "^1.0.4", | |||
| "dayjs": "^1.11.10", | |||
| "dompurify": "^3.1.6", | |||
| "eventsource-parser": "^1.1.2", | |||
| @@ -0,0 +1,153 @@ | |||
| 'use client'; | |||
| import { type DialogProps } from '@radix-ui/react-dialog'; | |||
| import { Command as CommandPrimitive } from 'cmdk'; | |||
| import { Search } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { Dialog, DialogContent } from '@/components/ui/dialog'; | |||
| import { cn } from '@/lib/utils'; | |||
| const Command = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive> | |||
| >(({ className, ...props }, ref) => ( | |||
| <CommandPrimitive | |||
| ref={ref} | |||
| className={cn( | |||
| 'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| )); | |||
| Command.displayName = CommandPrimitive.displayName; | |||
| const CommandDialog = ({ children, ...props }: DialogProps) => { | |||
| return ( | |||
| <Dialog {...props}> | |||
| <DialogContent className="overflow-hidden p-0 shadow-lg"> | |||
| <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> | |||
| {children} | |||
| </Command> | |||
| </DialogContent> | |||
| </Dialog> | |||
| ); | |||
| }; | |||
| const CommandInput = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.Input>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> | |||
| >(({ className, ...props }, ref) => ( | |||
| <div className="flex items-center border-b px-3" cmdk-input-wrapper=""> | |||
| <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> | |||
| <CommandPrimitive.Input | |||
| ref={ref} | |||
| className={cn( | |||
| 'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| </div> | |||
| )); | |||
| CommandInput.displayName = CommandPrimitive.Input.displayName; | |||
| const CommandList = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.List>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> | |||
| >(({ className, ...props }, ref) => ( | |||
| <CommandPrimitive.List | |||
| ref={ref} | |||
| className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)} | |||
| {...props} | |||
| /> | |||
| )); | |||
| CommandList.displayName = CommandPrimitive.List.displayName; | |||
| const CommandEmpty = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.Empty>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> | |||
| >((props, ref) => ( | |||
| <CommandPrimitive.Empty | |||
| ref={ref} | |||
| className="py-6 text-center text-sm" | |||
| {...props} | |||
| /> | |||
| )); | |||
| CommandEmpty.displayName = CommandPrimitive.Empty.displayName; | |||
| const CommandGroup = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.Group>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> | |||
| >(({ className, ...props }, ref) => ( | |||
| <CommandPrimitive.Group | |||
| ref={ref} | |||
| className={cn( | |||
| 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| )); | |||
| CommandGroup.displayName = CommandPrimitive.Group.displayName; | |||
| const CommandSeparator = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.Separator>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator> | |||
| >(({ className, ...props }, ref) => ( | |||
| <CommandPrimitive.Separator | |||
| ref={ref} | |||
| className={cn('-mx-1 h-px bg-border', className)} | |||
| {...props} | |||
| /> | |||
| )); | |||
| CommandSeparator.displayName = CommandPrimitive.Separator.displayName; | |||
| const CommandItem = React.forwardRef< | |||
| React.ElementRef<typeof CommandPrimitive.Item>, | |||
| React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> | |||
| >(({ className, ...props }, ref) => ( | |||
| <CommandPrimitive.Item | |||
| ref={ref} | |||
| className={cn( | |||
| "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| )); | |||
| CommandItem.displayName = CommandPrimitive.Item.displayName; | |||
| const CommandShortcut = ({ | |||
| className, | |||
| ...props | |||
| }: React.HTMLAttributes<HTMLSpanElement>) => { | |||
| return ( | |||
| <span | |||
| className={cn( | |||
| 'ml-auto text-xs tracking-widest text-muted-foreground', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| ); | |||
| }; | |||
| CommandShortcut.displayName = 'CommandShortcut'; | |||
| export { | |||
| Command, | |||
| CommandDialog, | |||
| CommandEmpty, | |||
| CommandGroup, | |||
| CommandInput, | |||
| CommandItem, | |||
| CommandList, | |||
| CommandSeparator, | |||
| CommandShortcut, | |||
| }; | |||
| @@ -0,0 +1,122 @@ | |||
| 'use client'; | |||
| import * as DialogPrimitive from '@radix-ui/react-dialog'; | |||
| import { X } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { cn } from '@/lib/utils'; | |||
| const Dialog = DialogPrimitive.Root; | |||
| const DialogTrigger = DialogPrimitive.Trigger; | |||
| const DialogPortal = DialogPrimitive.Portal; | |||
| const DialogClose = DialogPrimitive.Close; | |||
| const DialogOverlay = React.forwardRef< | |||
| React.ElementRef<typeof DialogPrimitive.Overlay>, | |||
| React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> | |||
| >(({ className, ...props }, ref) => ( | |||
| <DialogPrimitive.Overlay | |||
| ref={ref} | |||
| className={cn( | |||
| 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| )); | |||
| DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; | |||
| const DialogContent = React.forwardRef< | |||
| React.ElementRef<typeof DialogPrimitive.Content>, | |||
| React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> | |||
| >(({ className, children, ...props }, ref) => ( | |||
| <DialogPortal> | |||
| <DialogOverlay /> | |||
| <DialogPrimitive.Content | |||
| ref={ref} | |||
| className={cn( | |||
| 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', | |||
| className, | |||
| )} | |||
| {...props} | |||
| > | |||
| {children} | |||
| <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> | |||
| <X className="h-4 w-4" /> | |||
| <span className="sr-only">Close</span> | |||
| </DialogPrimitive.Close> | |||
| </DialogPrimitive.Content> | |||
| </DialogPortal> | |||
| )); | |||
| DialogContent.displayName = DialogPrimitive.Content.displayName; | |||
| const DialogHeader = ({ | |||
| className, | |||
| ...props | |||
| }: React.HTMLAttributes<HTMLDivElement>) => ( | |||
| <div | |||
| className={cn( | |||
| 'flex flex-col space-y-1.5 text-center sm:text-left', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| ); | |||
| DialogHeader.displayName = 'DialogHeader'; | |||
| const DialogFooter = ({ | |||
| className, | |||
| ...props | |||
| }: React.HTMLAttributes<HTMLDivElement>) => ( | |||
| <div | |||
| className={cn( | |||
| 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| ); | |||
| DialogFooter.displayName = 'DialogFooter'; | |||
| const DialogTitle = React.forwardRef< | |||
| React.ElementRef<typeof DialogPrimitive.Title>, | |||
| React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> | |||
| >(({ className, ...props }, ref) => ( | |||
| <DialogPrimitive.Title | |||
| ref={ref} | |||
| className={cn( | |||
| 'text-lg font-semibold leading-none tracking-tight', | |||
| className, | |||
| )} | |||
| {...props} | |||
| /> | |||
| )); | |||
| DialogTitle.displayName = DialogPrimitive.Title.displayName; | |||
| const DialogDescription = React.forwardRef< | |||
| React.ElementRef<typeof DialogPrimitive.Description>, | |||
| React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> | |||
| >(({ className, ...props }, ref) => ( | |||
| <DialogPrimitive.Description | |||
| ref={ref} | |||
| className={cn('text-sm text-muted-foreground', className)} | |||
| {...props} | |||
| /> | |||
| )); | |||
| DialogDescription.displayName = DialogPrimitive.Description.displayName; | |||
| export { | |||
| Dialog, | |||
| DialogClose, | |||
| DialogContent, | |||
| DialogDescription, | |||
| DialogFooter, | |||
| DialogHeader, | |||
| DialogOverlay, | |||
| DialogPortal, | |||
| DialogTitle, | |||
| DialogTrigger, | |||
| }; | |||
| @@ -0,0 +1,381 @@ | |||
| // src/components/multi-select.tsx | |||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||
| import { | |||
| CheckIcon, | |||
| ChevronDown, | |||
| WandSparkles, | |||
| XCircle, | |||
| XIcon, | |||
| } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { Badge } from '@/components/ui/badge'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| Command, | |||
| CommandEmpty, | |||
| CommandGroup, | |||
| CommandInput, | |||
| CommandItem, | |||
| CommandList, | |||
| CommandSeparator, | |||
| } from '@/components/ui/command'; | |||
| import { | |||
| Popover, | |||
| PopoverContent, | |||
| PopoverTrigger, | |||
| } from '@/components/ui/popover'; | |||
| import { Separator } from '@/components/ui/separator'; | |||
| import { cn } from '@/lib/utils'; | |||
| /** | |||
| * Variants for the multi-select component to handle different styles. | |||
| * Uses class-variance-authority (cva) to define different styles based on "variant" prop. | |||
| */ | |||
| const multiSelectVariants = cva( | |||
| 'm-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300', | |||
| { | |||
| variants: { | |||
| variant: { | |||
| default: | |||
| 'border-foreground/10 text-foreground bg-card hover:bg-card/80', | |||
| secondary: | |||
| 'border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80', | |||
| destructive: | |||
| 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', | |||
| inverted: 'inverted', | |||
| }, | |||
| }, | |||
| defaultVariants: { | |||
| variant: 'default', | |||
| }, | |||
| }, | |||
| ); | |||
| /** | |||
| * Props for MultiSelect component | |||
| */ | |||
| interface MultiSelectProps | |||
| extends React.ButtonHTMLAttributes<HTMLButtonElement>, | |||
| VariantProps<typeof multiSelectVariants> { | |||
| /** | |||
| * An array of option objects to be displayed in the multi-select component. | |||
| * Each option object has a label, value, and an optional icon. | |||
| */ | |||
| options: { | |||
| /** The text to display for the option. */ | |||
| label: string; | |||
| /** The unique value associated with the option. */ | |||
| value: string; | |||
| /** Optional icon component to display alongside the option. */ | |||
| icon?: React.ComponentType<{ className?: string }>; | |||
| }[]; | |||
| /** | |||
| * Callback function triggered when the selected values change. | |||
| * Receives an array of the new selected values. | |||
| */ | |||
| onValueChange: (value: string[]) => void; | |||
| /** The default selected values when the component mounts. */ | |||
| defaultValue?: string[]; | |||
| /** | |||
| * Placeholder text to be displayed when no values are selected. | |||
| * Optional, defaults to "Select options". | |||
| */ | |||
| placeholder?: string; | |||
| /** | |||
| * Animation duration in seconds for the visual effects (e.g., bouncing badges). | |||
| * Optional, defaults to 0 (no animation). | |||
| */ | |||
| animation?: number; | |||
| /** | |||
| * Maximum number of items to display. Extra selected items will be summarized. | |||
| * Optional, defaults to 3. | |||
| */ | |||
| maxCount?: number; | |||
| /** | |||
| * The modality of the popover. When set to true, interaction with outside elements | |||
| * will be disabled and only popover content will be visible to screen readers. | |||
| * Optional, defaults to false. | |||
| */ | |||
| modalPopover?: boolean; | |||
| /** | |||
| * If true, renders the multi-select component as a child of another component. | |||
| * Optional, defaults to false. | |||
| */ | |||
| asChild?: boolean; | |||
| /** | |||
| * Additional class names to apply custom styles to the multi-select component. | |||
| * Optional, can be used to add custom styles. | |||
| */ | |||
| className?: string; | |||
| } | |||
| export const MultiSelect = React.forwardRef< | |||
| HTMLButtonElement, | |||
| MultiSelectProps | |||
| >( | |||
| ( | |||
| { | |||
| options, | |||
| onValueChange, | |||
| variant, | |||
| defaultValue = [], | |||
| placeholder = 'Select options', | |||
| animation = 0, | |||
| maxCount = 3, | |||
| modalPopover = false, | |||
| asChild = false, | |||
| className, | |||
| ...props | |||
| }, | |||
| ref, | |||
| ) => { | |||
| const [selectedValues, setSelectedValues] = | |||
| React.useState<string[]>(defaultValue); | |||
| const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); | |||
| const [isAnimating, setIsAnimating] = React.useState(false); | |||
| const handleInputKeyDown = ( | |||
| event: React.KeyboardEvent<HTMLInputElement>, | |||
| ) => { | |||
| if (event.key === 'Enter') { | |||
| setIsPopoverOpen(true); | |||
| } else if (event.key === 'Backspace' && !event.currentTarget.value) { | |||
| const newSelectedValues = [...selectedValues]; | |||
| newSelectedValues.pop(); | |||
| setSelectedValues(newSelectedValues); | |||
| onValueChange(newSelectedValues); | |||
| } | |||
| }; | |||
| const toggleOption = (option: string) => { | |||
| const newSelectedValues = selectedValues.includes(option) | |||
| ? selectedValues.filter((value) => value !== option) | |||
| : [...selectedValues, option]; | |||
| setSelectedValues(newSelectedValues); | |||
| onValueChange(newSelectedValues); | |||
| }; | |||
| const handleClear = () => { | |||
| setSelectedValues([]); | |||
| onValueChange([]); | |||
| }; | |||
| const handleTogglePopover = () => { | |||
| setIsPopoverOpen((prev) => !prev); | |||
| }; | |||
| const clearExtraOptions = () => { | |||
| const newSelectedValues = selectedValues.slice(0, maxCount); | |||
| setSelectedValues(newSelectedValues); | |||
| onValueChange(newSelectedValues); | |||
| }; | |||
| const toggleAll = () => { | |||
| if (selectedValues.length === options.length) { | |||
| handleClear(); | |||
| } else { | |||
| const allValues = options.map((option) => option.value); | |||
| setSelectedValues(allValues); | |||
| onValueChange(allValues); | |||
| } | |||
| }; | |||
| return ( | |||
| <Popover | |||
| open={isPopoverOpen} | |||
| onOpenChange={setIsPopoverOpen} | |||
| modal={modalPopover} | |||
| > | |||
| <PopoverTrigger asChild> | |||
| <Button | |||
| ref={ref} | |||
| {...props} | |||
| onClick={handleTogglePopover} | |||
| className={cn( | |||
| 'flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto', | |||
| className, | |||
| )} | |||
| > | |||
| {selectedValues.length > 0 ? ( | |||
| <div className="flex justify-between items-center w-full"> | |||
| <div className="flex flex-wrap items-center"> | |||
| {selectedValues.slice(0, maxCount).map((value) => { | |||
| const option = options.find((o) => o.value === value); | |||
| const IconComponent = option?.icon; | |||
| return ( | |||
| <Badge | |||
| key={value} | |||
| className={cn( | |||
| isAnimating ? 'animate-bounce' : '', | |||
| multiSelectVariants({ variant }), | |||
| )} | |||
| style={{ animationDuration: `${animation}s` }} | |||
| > | |||
| {IconComponent && ( | |||
| <IconComponent className="h-4 w-4 mr-2" /> | |||
| )} | |||
| {option?.label} | |||
| <XCircle | |||
| className="ml-2 h-4 w-4 cursor-pointer" | |||
| onClick={(event) => { | |||
| event.stopPropagation(); | |||
| toggleOption(value); | |||
| }} | |||
| /> | |||
| </Badge> | |||
| ); | |||
| })} | |||
| {selectedValues.length > maxCount && ( | |||
| <Badge | |||
| className={cn( | |||
| 'bg-transparent text-foreground border-foreground/1 hover:bg-transparent', | |||
| isAnimating ? 'animate-bounce' : '', | |||
| multiSelectVariants({ variant }), | |||
| )} | |||
| style={{ animationDuration: `${animation}s` }} | |||
| > | |||
| {`+ ${selectedValues.length - maxCount} more`} | |||
| <XCircle | |||
| className="ml-2 h-4 w-4 cursor-pointer" | |||
| onClick={(event) => { | |||
| event.stopPropagation(); | |||
| clearExtraOptions(); | |||
| }} | |||
| /> | |||
| </Badge> | |||
| )} | |||
| </div> | |||
| <div className="flex items-center justify-between"> | |||
| <XIcon | |||
| className="h-4 mx-2 cursor-pointer text-muted-foreground" | |||
| onClick={(event) => { | |||
| event.stopPropagation(); | |||
| handleClear(); | |||
| }} | |||
| /> | |||
| <Separator | |||
| orientation="vertical" | |||
| className="flex min-h-6 h-full" | |||
| /> | |||
| <ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" /> | |||
| </div> | |||
| </div> | |||
| ) : ( | |||
| <div className="flex items-center justify-between w-full mx-auto"> | |||
| <span className="text-sm text-muted-foreground mx-3"> | |||
| {placeholder} | |||
| </span> | |||
| <ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" /> | |||
| </div> | |||
| )} | |||
| </Button> | |||
| </PopoverTrigger> | |||
| <PopoverContent | |||
| className="w-auto p-0" | |||
| align="start" | |||
| onEscapeKeyDown={() => setIsPopoverOpen(false)} | |||
| > | |||
| <Command> | |||
| <CommandInput | |||
| placeholder="Search..." | |||
| onKeyDown={handleInputKeyDown} | |||
| /> | |||
| <CommandList> | |||
| <CommandEmpty>No results found.</CommandEmpty> | |||
| <CommandGroup> | |||
| <CommandItem | |||
| key="all" | |||
| onSelect={toggleAll} | |||
| className="cursor-pointer" | |||
| > | |||
| <div | |||
| className={cn( | |||
| 'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary', | |||
| selectedValues.length === options.length | |||
| ? 'bg-primary text-primary-foreground' | |||
| : 'opacity-50 [&_svg]:invisible', | |||
| )} | |||
| > | |||
| <CheckIcon className="h-4 w-4" /> | |||
| </div> | |||
| <span>(Select All)</span> | |||
| </CommandItem> | |||
| {options.map((option) => { | |||
| const isSelected = selectedValues.includes(option.value); | |||
| return ( | |||
| <CommandItem | |||
| key={option.value} | |||
| onSelect={() => toggleOption(option.value)} | |||
| className="cursor-pointer" | |||
| > | |||
| <div | |||
| className={cn( | |||
| 'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary', | |||
| isSelected | |||
| ? 'bg-primary text-primary-foreground' | |||
| : 'opacity-50 [&_svg]:invisible', | |||
| )} | |||
| > | |||
| <CheckIcon className="h-4 w-4" /> | |||
| </div> | |||
| {option.icon && ( | |||
| <option.icon className="mr-2 h-4 w-4 text-muted-foreground" /> | |||
| )} | |||
| <span>{option.label}</span> | |||
| </CommandItem> | |||
| ); | |||
| })} | |||
| </CommandGroup> | |||
| <CommandSeparator /> | |||
| <CommandGroup> | |||
| <div className="flex items-center justify-between"> | |||
| {selectedValues.length > 0 && ( | |||
| <> | |||
| <CommandItem | |||
| onSelect={handleClear} | |||
| className="flex-1 justify-center cursor-pointer" | |||
| > | |||
| Clear | |||
| </CommandItem> | |||
| <Separator | |||
| orientation="vertical" | |||
| className="flex min-h-6 h-full" | |||
| /> | |||
| </> | |||
| )} | |||
| <CommandItem | |||
| onSelect={() => setIsPopoverOpen(false)} | |||
| className="flex-1 justify-center cursor-pointer max-w-full" | |||
| > | |||
| Close | |||
| </CommandItem> | |||
| </div> | |||
| </CommandGroup> | |||
| </CommandList> | |||
| </Command> | |||
| </PopoverContent> | |||
| {animation > 0 && selectedValues.length > 0 && ( | |||
| <WandSparkles | |||
| className={cn( | |||
| 'cursor-pointer my-2 text-foreground bg-background w-3 h-3', | |||
| isAnimating ? '' : 'text-muted-foreground', | |||
| )} | |||
| onClick={() => setIsAnimating(!isAnimating)} | |||
| /> | |||
| )} | |||
| </Popover> | |||
| ); | |||
| }, | |||
| ); | |||
| MultiSelect.displayName = 'MultiSelect'; | |||
| @@ -23,9 +23,10 @@ import { | |||
| } from '@/components/ui/select'; | |||
| import { FormSlider } from '@/components/ui/slider'; | |||
| import { Textarea } from '@/components/ui/textarea'; | |||
| import ChunkMethodCard from './chunk-method-card'; | |||
| const formSchema = z.object({ | |||
| username: z.number().min(2, { | |||
| parser_id: z.string().min(1, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| a: z.number().min(2, { | |||
| @@ -46,7 +47,7 @@ export default function AdvancedSettingForm() { | |||
| const form = useForm<z.infer<typeof formSchema>>({ | |||
| resolver: zodResolver(formSchema), | |||
| defaultValues: { | |||
| username: 0, | |||
| parser_id: '', | |||
| }, | |||
| }); | |||
| @@ -59,9 +60,9 @@ export default function AdvancedSettingForm() { | |||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | |||
| <FormField | |||
| control={form.control} | |||
| name="username" | |||
| name="a" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormItem className="w-2/5"> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| @@ -73,11 +74,12 @@ export default function AdvancedSettingForm() { | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <ChunkMethodCard></ChunkMethodCard> | |||
| <FormField | |||
| control={form.control} | |||
| name="a" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormItem className="w-2/5"> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| @@ -93,7 +95,7 @@ export default function AdvancedSettingForm() { | |||
| control={form.control} | |||
| name="b" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormItem className="w-2/5"> | |||
| <FormLabel>Username</FormLabel> | |||
| <Select onValueChange={field.onChange} defaultValue={field.value}> | |||
| <FormControl> | |||
| @@ -118,7 +120,7 @@ export default function AdvancedSettingForm() { | |||
| control={form.control} | |||
| name="c" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormItem className="w-2/5"> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| @@ -134,7 +136,7 @@ export default function AdvancedSettingForm() { | |||
| control={form.control} | |||
| name="d" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormItem className="w-2/5"> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <Textarea | |||
| @@ -153,7 +155,7 @@ export default function AdvancedSettingForm() { | |||
| variant={'tertiary'} | |||
| size={'sm'} | |||
| type="submit" | |||
| className="w-full" | |||
| className="w-2/5" | |||
| > | |||
| Test | |||
| </Button> | |||
| @@ -4,16 +4,16 @@ import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormDescription, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { MultiSelect } from '@/components/ui/multi-select'; | |||
| import { | |||
| Select, | |||
| SelectContent, | |||
| @@ -21,34 +21,48 @@ import { | |||
| SelectTrigger, | |||
| SelectValue, | |||
| } from '@/components/ui/select'; | |||
| import { FormSlider } from '@/components/ui/slider'; | |||
| import { Textarea } from '@/components/ui/textarea'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Cat, Dog, Fish, Rabbit, Turtle } from 'lucide-react'; | |||
| import { useState } from 'react'; | |||
| const formSchema = z.object({ | |||
| username: z.number().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| a: z.number().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| b: z.string().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| c: z.number().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| d: z.string().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| }); | |||
| const frameworksList = [ | |||
| { value: 'react', label: 'React', icon: Turtle }, | |||
| { value: 'angular', label: 'Angular', icon: Cat }, | |||
| { value: 'vue', label: 'Vue', icon: Dog }, | |||
| { value: 'svelte', label: 'Svelte', icon: Rabbit }, | |||
| { value: 'ember', label: 'Ember', icon: Fish }, | |||
| ]; | |||
| export default function BasicSettingForm() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const formSchema = z.object({ | |||
| name: z.string().min(1), | |||
| a: z.number().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| language: z.string().min(1, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| c: z.number().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| d: z.string().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| }); | |||
| const form = useForm<z.infer<typeof formSchema>>({ | |||
| resolver: zodResolver(formSchema), | |||
| defaultValues: { | |||
| username: 0, | |||
| name: '', | |||
| language: 'English', | |||
| }, | |||
| }); | |||
| const [selectedFrameworks, setSelectedFrameworks] = useState<string[]>([ | |||
| 'react', | |||
| 'angular', | |||
| ]); | |||
| function onSubmit(values: z.infer<typeof formSchema>) { | |||
| console.log(values); | |||
| @@ -59,42 +73,42 @@ export default function BasicSettingForm() { | |||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | |||
| <FormField | |||
| control={form.control} | |||
| name="username" | |||
| name="name" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormLabel>{t('name')}</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| <Input | |||
| {...field} | |||
| className="bg-colors-background-inverse-weak" | |||
| ></Input> | |||
| </FormControl> | |||
| <FormDescription> | |||
| This is your public display name. | |||
| </FormDescription> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="a" | |||
| name="d" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| <Input | |||
| {...field} | |||
| className="bg-colors-background-inverse-weak" | |||
| ></Input> | |||
| </FormControl> | |||
| <FormDescription> | |||
| This is your public display name. | |||
| </FormDescription> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="b" | |||
| name="language" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormLabel>{t('language')}</FormLabel> | |||
| <Select onValueChange={field.onChange} defaultValue={field.value}> | |||
| <FormControl> | |||
| <SelectTrigger className="bg-colors-background-inverse-weak"> | |||
| @@ -107,9 +121,6 @@ export default function BasicSettingForm() { | |||
| <SelectItem value="m@support.com">m@support.com</SelectItem> | |||
| </SelectContent> | |||
| </Select> | |||
| <FormDescription> | |||
| This is your public display name. | |||
| </FormDescription> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| @@ -121,42 +132,20 @@ export default function BasicSettingForm() { | |||
| <FormItem> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <FormSlider {...field}></FormSlider> | |||
| </FormControl> | |||
| <FormDescription> | |||
| This is your public display name. | |||
| </FormDescription> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="d" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>Username</FormLabel> | |||
| <FormControl> | |||
| <Textarea | |||
| <MultiSelect | |||
| options={frameworksList} | |||
| onValueChange={setSelectedFrameworks} | |||
| defaultValue={selectedFrameworks} | |||
| placeholder="Select frameworks" | |||
| variant="inverted" | |||
| maxCount={100} | |||
| {...field} | |||
| className="bg-colors-background-inverse-weak" | |||
| ></Textarea> | |||
| /> | |||
| </FormControl> | |||
| <FormDescription> | |||
| This is your public display name. | |||
| </FormDescription> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <Button | |||
| variant={'tertiary'} | |||
| size={'sm'} | |||
| type="submit" | |||
| className="w-full" | |||
| > | |||
| Test | |||
| </Button> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| @@ -0,0 +1,124 @@ | |||
| import SvgIcon from '@/components/svg-icon'; | |||
| import { Card } from '@/components/ui/card'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { | |||
| Select, | |||
| SelectContent, | |||
| SelectItem, | |||
| SelectTrigger, | |||
| SelectValue, | |||
| } from '@/components/ui/select'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useSelectParserList } from '@/hooks/user-setting-hooks'; | |||
| import { Col, Divider, Empty, Row, Typography } from 'antd'; | |||
| import DOMPurify from 'dompurify'; | |||
| import camelCase from 'lodash/camelCase'; | |||
| import { useMemo } from 'react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import styles from './index.less'; | |||
| import { ImageMap } from './utils'; | |||
| const { Title, Text } = Typography; | |||
| const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { | |||
| const parserList = useSelectParserList(); | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const item = useMemo(() => { | |||
| const item = parserList.find((x) => x.value === chunkMethod); | |||
| if (item) { | |||
| return { | |||
| title: item.label, | |||
| description: t(camelCase(item.value)), | |||
| }; | |||
| } | |||
| return { title: '', description: '' }; | |||
| }, [parserList, chunkMethod, t]); | |||
| const imageList = useMemo(() => { | |||
| if (chunkMethod in ImageMap) { | |||
| return ImageMap[chunkMethod as keyof typeof ImageMap]; | |||
| } | |||
| return []; | |||
| }, [chunkMethod]); | |||
| return ( | |||
| <section className={styles.categoryPanelWrapper}> | |||
| {imageList.length > 0 ? ( | |||
| <> | |||
| <Title level={5} className={styles.topTitle}> | |||
| {`"${item.title}" ${t('methodTitle')}`} | |||
| </Title> | |||
| <p | |||
| dangerouslySetInnerHTML={{ | |||
| __html: DOMPurify.sanitize(item.description), | |||
| }} | |||
| ></p> | |||
| <Title level={5}>{`"${item.title}" ${t('methodExamples')}`}</Title> | |||
| <Text>{t('methodExamplesDescription')}</Text> | |||
| <Row gutter={[10, 10]} className={styles.imageRow}> | |||
| {imageList.map((x) => ( | |||
| <Col span={12} key={x}> | |||
| <SvgIcon | |||
| name={x} | |||
| width={'100%'} | |||
| className={styles.image} | |||
| ></SvgIcon> | |||
| </Col> | |||
| ))} | |||
| </Row> | |||
| <Title level={5}> | |||
| {item.title} {t('dialogueExamplesTitle')} | |||
| </Title> | |||
| <Divider></Divider> | |||
| </> | |||
| ) : ( | |||
| <Empty description={''} image={null}> | |||
| <p>{t('methodEmpty')}</p> | |||
| <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon> | |||
| </Empty> | |||
| )} | |||
| </section> | |||
| ); | |||
| }; | |||
| export default function ChunkMethodCard() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const form = useFormContext(); | |||
| return ( | |||
| <Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak flex"> | |||
| <div className="w-2/5"> | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_id" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('chunkMethod')}</FormLabel> | |||
| <Select onValueChange={field.onChange} defaultValue={field.value}> | |||
| <FormControl> | |||
| <SelectTrigger className="bg-colors-background-inverse-weak"> | |||
| <SelectValue placeholder="Select a verified email to display" /> | |||
| </SelectTrigger> | |||
| </FormControl> | |||
| <SelectContent> | |||
| <SelectItem value="m@example.com">m@example.com</SelectItem> | |||
| <SelectItem value="m@google.com">m@google.com</SelectItem> | |||
| <SelectItem value="m@support.com">m@support.com</SelectItem> | |||
| </SelectContent> | |||
| </Select> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </div> | |||
| <CategoryPanel chunkMethod=""></CategoryPanel> | |||
| </Card> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| .tags { | |||
| margin-bottom: 24px; | |||
| } | |||
| .preset { | |||
| display: flex; | |||
| height: 80px; | |||
| background-color: rgba(0, 0, 0, 0.1); | |||
| border-radius: 5px; | |||
| padding: 5px; | |||
| margin-bottom: 24px; | |||
| .left { | |||
| flex: 1; | |||
| } | |||
| .right { | |||
| width: 100px; | |||
| border-left: 1px solid rgba(0, 0, 0, 0.4); | |||
| margin: 10px 0px; | |||
| padding: 5px; | |||
| } | |||
| } | |||
| .configurationWrapper { | |||
| padding: 0 52px; | |||
| .buttonWrapper { | |||
| text-align: right; | |||
| } | |||
| .variableSlider { | |||
| width: 100%; | |||
| } | |||
| } | |||
| .categoryPanelWrapper { | |||
| .topTitle { | |||
| margin-top: 0; | |||
| } | |||
| .imageRow { | |||
| margin-top: 16px; | |||
| } | |||
| .image { | |||
| width: 100%; | |||
| } | |||
| } | |||
| @@ -14,9 +14,7 @@ export default function DatasetSettings() { | |||
| <div className="text-3xl font-bold mb-6">Advanced settings</div> | |||
| <Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak"> | |||
| <div className="w-2/5"> | |||
| <AdvancedSettingForm></AdvancedSettingForm> | |||
| </div> | |||
| <AdvancedSettingForm></AdvancedSettingForm> | |||
| </Card> | |||
| </section> | |||
| ); | |||
| @@ -0,0 +1,19 @@ | |||
| const getImageName = (prefix: string, length: number) => | |||
| new Array(length) | |||
| .fill(0) | |||
| .map((x, idx) => `chunk-method/${prefix}-0${idx + 1}`); | |||
| export const ImageMap = { | |||
| book: getImageName('book', 4), | |||
| laws: getImageName('law', 2), | |||
| manual: getImageName('manual', 4), | |||
| picture: getImageName('media', 2), | |||
| naive: getImageName('naive', 2), | |||
| paper: getImageName('paper', 2), | |||
| presentation: getImageName('presentation', 2), | |||
| qa: getImageName('qa', 2), | |||
| resume: getImageName('resume', 2), | |||
| table: getImageName('table', 2), | |||
| one: getImageName('one', 2), | |||
| knowledge_graph: getImageName('knowledge-graph', 2), | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import { useTheme } from '@/components/theme-provider'; | |||
| import { useIsDarkTheme, useTheme } from '@/components/theme-provider'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Label } from '@/components/ui/label'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| @@ -52,6 +52,7 @@ export function SideBar() { | |||
| const pathName = useSecondPathName(); | |||
| const { handleMenuClick } = useHandleMenuClick(); | |||
| const { setTheme } = useTheme(); | |||
| const isDarkTheme = useIsDarkTheme(); | |||
| const handleThemeChange = useCallback( | |||
| (checked: boolean) => { | |||
| @@ -89,7 +90,11 @@ export function SideBar() { | |||
| <div className="p-6 mt-auto border-t"> | |||
| <div className="flex items-center gap-2 mb-6"> | |||
| <Switch id="dark-mode" onCheckedChange={handleThemeChange} /> | |||
| <Switch | |||
| id="dark-mode" | |||
| onCheckedChange={handleThemeChange} | |||
| checked={isDarkTheme} | |||
| /> | |||
| <Label htmlFor="dark-mode" className="text-sm"> | |||
| Dark | |||
| </Label> | |||