### 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
| "@radix-ui/react-aspect-ratio": "^1.1.0", | "@radix-ui/react-aspect-ratio": "^1.1.0", | ||||
| "@radix-ui/react-avatar": "^1.1.1", | "@radix-ui/react-avatar": "^1.1.1", | ||||
| "@radix-ui/react-checkbox": "^1.1.2", | "@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-dropdown-menu": "^2.1.2", | ||||
| "@radix-ui/react-icons": "^1.3.1", | "@radix-ui/react-icons": "^1.3.1", | ||||
| "@radix-ui/react-label": "^2.1.0", | "@radix-ui/react-label": "^2.1.0", | ||||
| "class-variance-authority": "^0.7.0", | "class-variance-authority": "^0.7.0", | ||||
| "classnames": "^2.5.1", | "classnames": "^2.5.1", | ||||
| "clsx": "^2.1.1", | "clsx": "^2.1.1", | ||||
| "cmdk": "^1.0.4", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "dompurify": "^3.1.6", | "dompurify": "^3.1.6", | ||||
| "eventsource-parser": "^1.1.2", | "eventsource-parser": "^1.1.2", | ||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "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": { | "node_modules/@radix-ui/react-direction": { | ||||
| "version": "1.1.0", | "version": "1.1.0", | ||||
| "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", | "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", | ||||
| "node": ">=6" | "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": { | "node_modules/co": { | ||||
| "version": "4.6.0", | "version": "4.6.0", | ||||
| "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", | "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/react-remove-scroll-bar": { | "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": { | "dependencies": { | ||||
| "react-style-singleton": "^2.2.1", | |||||
| "react-style-singleton": "^2.2.2", | |||||
| "tslib": "^2.0.0" | "tslib": "^2.0.0" | ||||
| }, | }, | ||||
| "engines": { | "engines": { | ||||
| "node": ">=10" | "node": ">=10" | ||||
| }, | }, | ||||
| "peerDependencies": { | "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": { | "peerDependenciesMeta": { | ||||
| "@types/react": { | "@types/react": { | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/react-style-singleton": { | "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": { | "dependencies": { | ||||
| "get-nonce": "^1.0.0", | "get-nonce": "^1.0.0", | ||||
| "invariant": "^2.2.4", | |||||
| "tslib": "^2.0.0" | "tslib": "^2.0.0" | ||||
| }, | }, | ||||
| "engines": { | "engines": { | ||||
| "node": ">=10" | "node": ">=10" | ||||
| }, | }, | ||||
| "peerDependencies": { | "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": { | "peerDependenciesMeta": { | ||||
| "@types/react": { | "@types/react": { |
| "@radix-ui/react-aspect-ratio": "^1.1.0", | "@radix-ui/react-aspect-ratio": "^1.1.0", | ||||
| "@radix-ui/react-avatar": "^1.1.1", | "@radix-ui/react-avatar": "^1.1.1", | ||||
| "@radix-ui/react-checkbox": "^1.1.2", | "@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-dropdown-menu": "^2.1.2", | ||||
| "@radix-ui/react-icons": "^1.3.1", | "@radix-ui/react-icons": "^1.3.1", | ||||
| "@radix-ui/react-label": "^2.1.0", | "@radix-ui/react-label": "^2.1.0", | ||||
| "class-variance-authority": "^0.7.0", | "class-variance-authority": "^0.7.0", | ||||
| "classnames": "^2.5.1", | "classnames": "^2.5.1", | ||||
| "clsx": "^2.1.1", | "clsx": "^2.1.1", | ||||
| "cmdk": "^1.0.4", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "dompurify": "^3.1.6", | "dompurify": "^3.1.6", | ||||
| "eventsource-parser": "^1.1.2", | "eventsource-parser": "^1.1.2", |
| '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, | |||||
| }; |
| '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, | |||||
| }; |
| // 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'; |
| } from '@/components/ui/select'; | } from '@/components/ui/select'; | ||||
| import { FormSlider } from '@/components/ui/slider'; | import { FormSlider } from '@/components/ui/slider'; | ||||
| import { Textarea } from '@/components/ui/textarea'; | import { Textarea } from '@/components/ui/textarea'; | ||||
| import ChunkMethodCard from './chunk-method-card'; | |||||
| const formSchema = z.object({ | const formSchema = z.object({ | ||||
| username: z.number().min(2, { | |||||
| parser_id: z.string().min(1, { | |||||
| message: 'Username must be at least 2 characters.', | message: 'Username must be at least 2 characters.', | ||||
| }), | }), | ||||
| a: z.number().min(2, { | a: z.number().min(2, { | ||||
| const form = useForm<z.infer<typeof formSchema>>({ | const form = useForm<z.infer<typeof formSchema>>({ | ||||
| resolver: zodResolver(formSchema), | resolver: zodResolver(formSchema), | ||||
| defaultValues: { | defaultValues: { | ||||
| username: 0, | |||||
| parser_id: '', | |||||
| }, | }, | ||||
| }); | }); | ||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="username" | |||||
| name="a" | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="w-2/5"> | |||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <FormSlider {...field}></FormSlider> | <FormSlider {...field}></FormSlider> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <ChunkMethodCard></ChunkMethodCard> | |||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="a" | name="a" | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="w-2/5"> | |||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <FormSlider {...field}></FormSlider> | <FormSlider {...field}></FormSlider> | ||||
| control={form.control} | control={form.control} | ||||
| name="b" | name="b" | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="w-2/5"> | |||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <Select onValueChange={field.onChange} defaultValue={field.value}> | <Select onValueChange={field.onChange} defaultValue={field.value}> | ||||
| <FormControl> | <FormControl> | ||||
| control={form.control} | control={form.control} | ||||
| name="c" | name="c" | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="w-2/5"> | |||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <FormSlider {...field}></FormSlider> | <FormSlider {...field}></FormSlider> | ||||
| control={form.control} | control={form.control} | ||||
| name="d" | name="d" | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="w-2/5"> | |||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <Textarea | <Textarea | ||||
| variant={'tertiary'} | variant={'tertiary'} | ||||
| size={'sm'} | size={'sm'} | ||||
| type="submit" | type="submit" | ||||
| className="w-full" | |||||
| className="w-2/5" | |||||
| > | > | ||||
| Test | Test | ||||
| </Button> | </Button> |
| import { useForm } from 'react-hook-form'; | import { useForm } from 'react-hook-form'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { Button } from '@/components/ui/button'; | |||||
| import { | import { | ||||
| Form, | Form, | ||||
| FormControl, | FormControl, | ||||
| FormDescription, | |||||
| FormField, | FormField, | ||||
| FormItem, | FormItem, | ||||
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Input } from '@/components/ui/input'; | |||||
| import { MultiSelect } from '@/components/ui/multi-select'; | |||||
| import { | import { | ||||
| Select, | Select, | ||||
| SelectContent, | SelectContent, | ||||
| SelectTrigger, | SelectTrigger, | ||||
| SelectValue, | SelectValue, | ||||
| } from '@/components/ui/select'; | } 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() { | 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>>({ | const form = useForm<z.infer<typeof formSchema>>({ | ||||
| resolver: zodResolver(formSchema), | resolver: zodResolver(formSchema), | ||||
| defaultValues: { | defaultValues: { | ||||
| username: 0, | |||||
| name: '', | |||||
| language: 'English', | |||||
| }, | }, | ||||
| }); | }); | ||||
| const [selectedFrameworks, setSelectedFrameworks] = useState<string[]>([ | |||||
| 'react', | |||||
| 'angular', | |||||
| ]); | |||||
| function onSubmit(values: z.infer<typeof formSchema>) { | function onSubmit(values: z.infer<typeof formSchema>) { | ||||
| console.log(values); | console.log(values); | ||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="username" | |||||
| name="name" | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel>Username</FormLabel> | |||||
| <FormLabel>{t('name')}</FormLabel> | |||||
| <FormControl> | <FormControl> | ||||
| <FormSlider {...field}></FormSlider> | |||||
| <Input | |||||
| {...field} | |||||
| className="bg-colors-background-inverse-weak" | |||||
| ></Input> | |||||
| </FormControl> | </FormControl> | ||||
| <FormDescription> | |||||
| This is your public display name. | |||||
| </FormDescription> | |||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="a" | |||||
| name="d" | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <FormSlider {...field}></FormSlider> | |||||
| <Input | |||||
| {...field} | |||||
| className="bg-colors-background-inverse-weak" | |||||
| ></Input> | |||||
| </FormControl> | </FormControl> | ||||
| <FormDescription> | |||||
| This is your public display name. | |||||
| </FormDescription> | |||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="b" | |||||
| name="language" | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel>Username</FormLabel> | |||||
| <FormLabel>{t('language')}</FormLabel> | |||||
| <Select onValueChange={field.onChange} defaultValue={field.value}> | <Select onValueChange={field.onChange} defaultValue={field.value}> | ||||
| <FormControl> | <FormControl> | ||||
| <SelectTrigger className="bg-colors-background-inverse-weak"> | <SelectTrigger className="bg-colors-background-inverse-weak"> | ||||
| <SelectItem value="m@support.com">m@support.com</SelectItem> | <SelectItem value="m@support.com">m@support.com</SelectItem> | ||||
| </SelectContent> | </SelectContent> | ||||
| </Select> | </Select> | ||||
| <FormDescription> | |||||
| This is your public display name. | |||||
| </FormDescription> | |||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel>Username</FormLabel> | <FormLabel>Username</FormLabel> | ||||
| <FormControl> | <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} | {...field} | ||||
| className="bg-colors-background-inverse-weak" | |||||
| ></Textarea> | |||||
| /> | |||||
| </FormControl> | </FormControl> | ||||
| <FormDescription> | |||||
| This is your public display name. | |||||
| </FormDescription> | |||||
| <FormMessage /> | <FormMessage /> | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <Button | |||||
| variant={'tertiary'} | |||||
| size={'sm'} | |||||
| type="submit" | |||||
| className="w-full" | |||||
| > | |||||
| Test | |||||
| </Button> | |||||
| </form> | </form> | ||||
| </Form> | </Form> | ||||
| ); | ); |
| 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> | |||||
| ); | |||||
| } |
| .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%; | |||||
| } | |||||
| } |
| <div className="text-3xl font-bold mb-6">Advanced settings</div> | <div className="text-3xl font-bold mb-6">Advanced settings</div> | ||||
| <Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak"> | <Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak"> | ||||
| <div className="w-2/5"> | |||||
| <AdvancedSettingForm></AdvancedSettingForm> | |||||
| </div> | |||||
| <AdvancedSettingForm></AdvancedSettingForm> | |||||
| </Card> | </Card> | ||||
| </section> | </section> | ||||
| ); | ); |
| 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), | |||||
| }; |
| import { useTheme } from '@/components/theme-provider'; | |||||
| import { useIsDarkTheme, useTheme } from '@/components/theme-provider'; | |||||
| import { Button } from '@/components/ui/button'; | import { Button } from '@/components/ui/button'; | ||||
| import { Label } from '@/components/ui/label'; | import { Label } from '@/components/ui/label'; | ||||
| import { Switch } from '@/components/ui/switch'; | import { Switch } from '@/components/ui/switch'; | ||||
| const pathName = useSecondPathName(); | const pathName = useSecondPathName(); | ||||
| const { handleMenuClick } = useHandleMenuClick(); | const { handleMenuClick } = useHandleMenuClick(); | ||||
| const { setTheme } = useTheme(); | const { setTheme } = useTheme(); | ||||
| const isDarkTheme = useIsDarkTheme(); | |||||
| const handleThemeChange = useCallback( | const handleThemeChange = useCallback( | ||||
| (checked: boolean) => { | (checked: boolean) => { | ||||
| <div className="p-6 mt-auto border-t"> | <div className="p-6 mt-auto border-t"> | ||||
| <div className="flex items-center gap-2 mb-6"> | <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"> | <Label htmlFor="dark-mode" className="text-sm"> | ||||
| Dark | Dark | ||||
| </Label> | </Label> |