From c7fe61667fd386b4b5d1b43d4f75df8f72e6ca67 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Sat, 23 Nov 2024 19:43:15 +0100 Subject: [PATCH 01/12] fix: ring --- templates/plate-playground-template/src/app/globals.css | 2 +- templates/plate-template/src/app/globals.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/plate-playground-template/src/app/globals.css b/templates/plate-playground-template/src/app/globals.css index 09c15d81a3..0214326097 100644 --- a/templates/plate-playground-template/src/app/globals.css +++ b/templates/plate-playground-template/src/app/globals.css @@ -32,7 +32,7 @@ body { --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --ring: 215 20.2% 65.1%; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; diff --git a/templates/plate-template/src/app/globals.css b/templates/plate-template/src/app/globals.css index 09c15d81a3..c3b7c675bb 100644 --- a/templates/plate-template/src/app/globals.css +++ b/templates/plate-template/src/app/globals.css @@ -85,7 +85,7 @@ body { --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --ring: 215 20.2% 65.1%; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; From 71427f45fa325b002de32b909d59c8f3a494b451 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Sat, 23 Nov 2024 19:44:01 +0100 Subject: [PATCH 02/12] docs --- apps/www/package.json | 8 +- apps/www/public/r/index.json | 150 +++++++++- apps/www/public/r/styles/default/avatar.json | 5 +- .../www/public/r/styles/default/calendar.json | 5 +- .../www/public/r/styles/default/checkbox.json | 5 +- apps/www/public/r/styles/default/command.json | 9 +- apps/www/public/r/styles/default/dialog.json | 5 +- .../r/styles/default/dropdown-menu.json | 5 +- .../r/styles/default/editable-voids-demo.json | 2 +- apps/www/public/r/styles/default/editor.json | 2 +- apps/www/public/r/styles/default/form.json | 27 ++ apps/www/public/r/styles/default/input.json | 5 +- apps/www/public/r/styles/default/label.json | 22 ++ apps/www/public/r/styles/default/popover.json | 7 +- .../r/styles/default/select-editor-demo.json | 21 ++ .../default/select-editor-form-demo.json | 21 ++ .../r/styles/default/select-editor.json | 30 ++ .../public/r/styles/default/separator.json | 5 +- .../public/r/styles/default/tag-element.json | 27 ++ apps/www/public/r/styles/default/tooltip.json | 5 +- apps/www/src/__registry__/index.tsx | 60 ++++ .../(app)/_components/installation-tab.tsx | 2 +- .../src/components/plugins-tab-content.tsx | 2 +- apps/www/src/components/setting-checkbox.tsx | 2 +- apps/www/src/components/theme-customizer.tsx | 2 +- apps/www/src/config/docs-examples.ts | 2 +- apps/www/src/config/docs-icons.tsx | 4 + .../default/example/editable-voids-demo.tsx | 2 +- .../default/example/select-editor-demo.tsx | 129 +++++++++ .../src/registry/default/plate-ui/command.tsx | 23 +- .../src/registry/default/plate-ui/editor.tsx | 5 + .../src/registry/default/plate-ui/form.tsx | 179 ++++++++++++ .../default/plate-ui}/label.tsx | 0 .../src/registry/default/plate-ui/popover.tsx | 41 ++- .../default/plate-ui/select-editor.tsx | 271 ++++++++++++++++++ .../registry/default/plate-ui/tag-element.tsx | 51 ++++ apps/www/src/registry/registry-examples.ts | 10 + apps/www/src/registry/registry-ui.ts | 91 +++++- 38 files changed, 1189 insertions(+), 53 deletions(-) create mode 100644 apps/www/public/r/styles/default/form.json create mode 100644 apps/www/public/r/styles/default/label.json create mode 100644 apps/www/public/r/styles/default/select-editor-demo.json create mode 100644 apps/www/public/r/styles/default/select-editor-form-demo.json create mode 100644 apps/www/public/r/styles/default/select-editor.json create mode 100644 apps/www/public/r/styles/default/tag-element.json create mode 100644 apps/www/src/registry/default/example/select-editor-demo.tsx create mode 100644 apps/www/src/registry/default/plate-ui/form.tsx rename apps/www/src/{components/ui => registry/default/plate-ui}/label.tsx (100%) create mode 100644 apps/www/src/registry/default/plate-ui/select-editor.tsx create mode 100644 apps/www/src/registry/default/plate-ui/tag-element.tsx diff --git a/apps/www/package.json b/apps/www/package.json index 5a8612e7ca..f62eda6e62 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -15,7 +15,8 @@ "lint:fix": "yarn lint --fix", "preview": "next build && next start", "start": "next start", - "typecheck": "yarn prebuild && tsc --noEmit" + "typecheck": "yarn prebuild && tsc --noEmit", + "typecheck:watch": "yarn typecheck --watch" }, "browserslist": { "production": [ @@ -37,6 +38,7 @@ "@ai-sdk/openai": "^0.0.67", "@ariakit/react": "0.4.11", "@faker-js/faker": "^9.0.2", + "@hookform/resolvers": "3.9.1", "@next/third-parties": "15.0.3", "@radix-ui/colors": "3.0.0", "@radix-ui/react-accessible-icon": "^1.1.0", @@ -69,6 +71,7 @@ "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toolbar": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", + "@udecode/cmdk": "workspace:^", "@udecode/cn": "workspace:^", "@udecode/plate": "workspace:^", "@udecode/plate-ai": "workspace:^", @@ -121,6 +124,7 @@ "@udecode/plate-suggestion": "workspace:^", "@udecode/plate-tabbable": "workspace:^", "@udecode/plate-table": "workspace:^", + "@udecode/plate-tag": "workspace:^", "@udecode/plate-test-utils": "workspace:^", "@udecode/plate-toggle": "workspace:^", "@udecode/plate-trailing-block": "workspace:^", @@ -139,6 +143,7 @@ "contentlayer2": "^0.4.6", "date-fns": "^3.6.0", "framer-motion": "^11.5.4", + "fzf": "0.5.2", "lodash.template": "^4.5.0", "lucide-react": "0.460.0", "match-sorter": "6.3.4", @@ -152,6 +157,7 @@ "react-dnd": "16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", + "react-hook-form": "7.53.2", "react-lite-youtube-embed": "^2.4.0", "react-markdown": "9.0.1", "react-player": "2.16.0", diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 388a7e090b..48bfe1038c 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -1020,6 +1020,31 @@ ], "type": "registry:ui" }, + { + "dependencies": [], + "doc": { + "description": "A tag element component with selection states and styling.", + "docs": [ + { + "route": "/docs/tag" + } + ], + "examples": [ + "select-editor-demo" + ] + }, + "files": [ + { + "path": "plate-ui/tag-element.tsx", + "type": "registry:ui" + } + ], + "name": "tag-element", + "registryDependencies": [ + "plate-element" + ], + "type": "registry:ui" + }, { "dependencies": [ "@udecode/plate-heading" @@ -1134,7 +1159,10 @@ "@radix-ui/react-avatar" ], "doc": { - "description": "An image element with a fallback for representing the user." + "description": "An image element with a fallback for representing the user.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/avatar" + } }, "files": [ { @@ -1171,7 +1199,10 @@ "react-day-picker@8.10.1" ], "doc": { - "description": "A date field component that allows users to enter and edit date." + "description": "A date field component that allows users to enter and edit date.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/calendar" + } }, "files": [ { @@ -1190,7 +1221,10 @@ "@radix-ui/react-checkbox" ], "doc": { - "description": "A control that allows the user to toggle between checked and not checked." + "description": "A control that allows the user to toggle between checked and not checked.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/checkbox" + } }, "files": [ { @@ -1205,10 +1239,13 @@ { "dependencies": [ "@radix-ui/react-dialog", - "cmdk" + "@udecode/cmdk" ], "doc": { - "description": "Fast, composable, unstyled command menu for React." + "description": "Fast, composable, unstyled command menu for React.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/command" + } }, "files": [ { @@ -1248,7 +1285,10 @@ "@radix-ui/react-dialog" ], "doc": { - "description": "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert." + "description": "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/dialog" + } }, "files": [ { @@ -1265,7 +1305,10 @@ "@radix-ui/react-dropdown-menu" ], "doc": { - "description": "Displays a menu to the user — such as a set of actions or functions — triggered by a button." + "description": "Displays a menu to the user — such as a set of actions or functions — triggered by a button.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/dropdown-menu" + } }, "files": [ { @@ -1277,10 +1320,38 @@ "registryDependencies": [], "type": "registry:ui" }, + { + "dependencies": [ + "react-hook-form", + "@hookform/resolvers/zod", + "@radix-ui/react-label", + "@radix-ui/react-slot" + ], + "doc": { + "description": "Building forms with React Hook Form and Zod.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/form" + } + }, + "files": [ + { + "path": "plate-ui/form.tsx", + "type": "registry:ui" + } + ], + "name": "form", + "registryDependencies": [ + "label" + ], + "type": "registry:ui" + }, { "dependencies": [], "doc": { - "description": "Displays a form input field or a component that looks like an input field." + "description": "Displays a form input field or a component that looks like an input field.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/input" + } }, "files": [ { @@ -1292,12 +1363,35 @@ "registryDependencies": [], "type": "registry:ui" }, + { + "dependencies": [ + "@radix-ui/react-label" + ], + "doc": { + "description": "Renders an accessible label associated with controls.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/label" + } + }, + "files": [ + { + "path": "plate-ui/label.tsx", + "type": "registry:ui" + } + ], + "name": "label", + "registryDependencies": [], + "type": "registry:ui" + }, { "dependencies": [ "@radix-ui/react-popover" ], "doc": { - "description": "Displays rich content in a portal, triggered by a button." + "description": "Displays rich content in a portal, triggered by a button.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/popover" + } }, "files": [ { @@ -1314,7 +1408,10 @@ "@radix-ui/react-separator" ], "doc": { - "description": "Visually or semantically separates content." + "description": "Visually or semantically separates content.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/separator" + } }, "files": [ { @@ -1351,7 +1448,10 @@ "@radix-ui/react-tooltip" ], "doc": { - "description": "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it." + "description": "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/tooltip" + } }, "files": [ { @@ -1838,6 +1938,34 @@ "registryDependencies": [], "type": "registry:ui" }, + { + "dependencies": [ + "fzf@0.5.2", + "@udecode/plate-tag", + "@udecode/cmdk" + ], + "doc": { + "description": "An editor to select tags.", + "examples": [ + "select-editor-demo" + ], + "label": "New" + }, + "files": [ + { + "path": "plate-ui/select-editor.tsx", + "type": "registry:ui" + } + ], + "name": "select-editor", + "registryDependencies": [ + "editor", + "command", + "popover", + "tag-element" + ], + "type": "registry:ui" + }, { "dependencies": [ "@udecode/plate-emoji", diff --git a/apps/www/public/r/styles/default/avatar.json b/apps/www/public/r/styles/default/avatar.json index b36e454462..a4135855c2 100644 --- a/apps/www/public/r/styles/default/avatar.json +++ b/apps/www/public/r/styles/default/avatar.json @@ -3,7 +3,10 @@ "@radix-ui/react-avatar" ], "doc": { - "description": "An image element with a fallback for representing the user." + "description": "An image element with a fallback for representing the user.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/avatar" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/calendar.json b/apps/www/public/r/styles/default/calendar.json index 7e2a9c2361..4679f88697 100644 --- a/apps/www/public/r/styles/default/calendar.json +++ b/apps/www/public/r/styles/default/calendar.json @@ -3,7 +3,10 @@ "react-day-picker@8.10.1" ], "doc": { - "description": "A date field component that allows users to enter and edit date." + "description": "A date field component that allows users to enter and edit date.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/calendar" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/checkbox.json b/apps/www/public/r/styles/default/checkbox.json index 2aec122cc5..162a35e70e 100644 --- a/apps/www/public/r/styles/default/checkbox.json +++ b/apps/www/public/r/styles/default/checkbox.json @@ -3,7 +3,10 @@ "@radix-ui/react-checkbox" ], "doc": { - "description": "A control that allows the user to toggle between checked and not checked." + "description": "A control that allows the user to toggle between checked and not checked.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/checkbox" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/command.json b/apps/www/public/r/styles/default/command.json index 65450d2bf5..79a1de577b 100644 --- a/apps/www/public/r/styles/default/command.json +++ b/apps/www/public/r/styles/default/command.json @@ -1,14 +1,17 @@ { "dependencies": [ "@radix-ui/react-dialog", - "cmdk" + "@udecode/cmdk" ], "doc": { - "description": "Fast, composable, unstyled command menu for React." + "description": "Fast, composable, unstyled command menu for React.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/command" + } }, "files": [ { - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DialogProps } from '@radix-ui/react-dialog';\n\nimport {\n cn,\n createPrimitiveElement,\n withCn,\n withRef,\n withVariants,\n} from '@udecode/cn';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { Search } from 'lucide-react';\n\nimport { Dialog, DialogContent, DialogTitle } from './dialog';\nimport { inputVariants } from './input';\n\nexport const Command = withCn(\n CommandPrimitive,\n 'flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground'\n);\n\nexport function CommandDialog({ children, ...props }: DialogProps) {\n return (\n \n \n Command Dialog\n \n {children}\n \n \n \n );\n}\n\nexport const CommandInput = withRef(\n ({ className, ...props }, ref) => (\n
\n \n \n
\n )\n);\n\nexport const InputCommand = withVariants(\n CommandPrimitive.Input,\n inputVariants,\n ['variant']\n);\n\nexport const CommandList = withCn(\n CommandPrimitive.List,\n 'max-h-[500px] overflow-y-auto overflow-x-hidden'\n);\n\nexport const CommandEmpty = withCn(\n CommandPrimitive.Empty,\n 'py-6 text-center text-sm'\n);\n\nexport const CommandGroup = withCn(\n CommandPrimitive.Group,\n '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'\n);\n\nexport const CommandSeparator = withCn(\n CommandPrimitive.Separator,\n '-mx-1 h-px bg-border'\n);\n\nexport const CommandItem = withCn(\n CommandPrimitive.Item,\n 'relative flex cursor-default select-none items-center gap-2 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'\n);\n\nexport const CommandShortcut = withCn(\n createPrimitiveElement('span'),\n 'ml-auto text-xs tracking-widest text-muted-foreground'\n);\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DialogProps } from '@radix-ui/react-dialog';\n\nimport { Command as CommandPrimitive } from '@udecode/cmdk';\nimport {\n cn,\n createPrimitiveElement,\n withCn,\n withRef,\n withVariants,\n} from '@udecode/cn';\nimport { cva } from 'class-variance-authority';\nimport { Search } from 'lucide-react';\n\nimport { Dialog, DialogContent, DialogTitle } from './dialog';\nimport { inputVariants } from './input';\n\nconst commandVariants = cva(\n 'flex size-full flex-col rounded-md bg-popover text-popover-foreground',\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n combobox: 'overflow-visible bg-transparent has-[[data-readonly]]:w-fit',\n default: 'overflow-hidden',\n },\n },\n }\n);\n\nexport const Command = withVariants(CommandPrimitive, commandVariants, [\n 'variant',\n]);\n\nexport function CommandDialog({ children, ...props }: DialogProps) {\n return (\n \n \n Command Dialog\n \n {children}\n \n \n \n );\n}\n\nexport const CommandInput = withRef(\n ({ className, ...props }, ref) => (\n
\n \n \n
\n )\n);\n\nexport const InputCommand = withVariants(\n CommandPrimitive.Input,\n inputVariants,\n ['variant']\n);\n\nexport const CommandList = withCn(\n CommandPrimitive.List,\n 'max-h-[500px] overflow-y-auto overflow-x-hidden'\n);\n\nexport const CommandEmpty = withCn(\n CommandPrimitive.Empty,\n 'py-6 text-center text-sm'\n);\n\nexport const CommandGroup = withCn(\n CommandPrimitive.Group,\n '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'\n);\n\nexport const CommandSeparator = withCn(\n CommandPrimitive.Separator,\n '-mx-1 h-px bg-border'\n);\n\nexport const CommandItem = withCn(\n CommandPrimitive.Item,\n 'relative flex cursor-default select-none items-center gap-2 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'\n);\n\nexport const CommandShortcut = withCn(\n createPrimitiveElement('span'),\n 'ml-auto text-xs tracking-widest text-muted-foreground'\n);\n", "path": "plate-ui/command.tsx", "target": "components/plate-ui/command.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/dialog.json b/apps/www/public/r/styles/default/dialog.json index 0e80ee5d2c..977810047b 100644 --- a/apps/www/public/r/styles/default/dialog.json +++ b/apps/www/public/r/styles/default/dialog.json @@ -3,7 +3,10 @@ "@radix-ui/react-dialog" ], "doc": { - "description": "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert." + "description": "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/dialog" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/dropdown-menu.json b/apps/www/public/r/styles/default/dropdown-menu.json index 186f6a0350..d7750f6221 100644 --- a/apps/www/public/r/styles/default/dropdown-menu.json +++ b/apps/www/public/r/styles/default/dropdown-menu.json @@ -3,7 +3,10 @@ "@radix-ui/react-dropdown-menu" ], "doc": { - "description": "Displays a menu to the user — such as a set of actions or functions — triggered by a button." + "description": "Displays a menu to the user — such as a set of actions or functions — triggered by a button.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/dropdown-menu" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/editable-voids-demo.json b/apps/www/public/r/styles/default/editable-voids-demo.json index dfd3ad21ac..248520c9cc 100644 --- a/apps/www/public/r/styles/default/editable-voids-demo.json +++ b/apps/www/public/r/styles/default/editable-voids-demo.json @@ -1,7 +1,7 @@ { "files": [ { - "content": "'use client';\n\nimport React, { useState } from 'react';\n\nimport type { PlateRenderElementProps } from '@udecode/plate-common/react';\n\nimport { Plate, createPlatePlugin } from '@udecode/plate-common/react';\n\nimport { Label } from '@/components/ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\nimport { editorPlugins } from '@/components/editor/plugins/editor-plugins';\nimport { useCreateEditor } from '@/components/editor/use-create-editor';\nimport { editableVoidsValue } from '@/registry/default/example/values/editable-voids-value';\nimport { Editor, EditorContainer } from '@/components/plate-ui/editor';\nimport { Input } from '@/components/plate-ui/input';\n\nexport const EditableVoidPlugin = createPlatePlugin({\n key: 'editable-void',\n node: {\n component: EditableVoidElement,\n isElement: true,\n isVoid: true,\n },\n});\n\nexport function EditableVoidElement({\n attributes,\n children,\n}: PlateRenderElementProps) {\n const [inputValue, setInputValue] = useState('');\n\n const editor = useCreateEditor({\n plugins: editorPlugins,\n });\n\n return (\n // Need contentEditable=false or Firefox has issues with certain input types.\n
\n
\n {\n setInputValue(e.target.value);\n }}\n placeholder=\"Name\"\n type=\"text\"\n />\n\n
\n \n\n \n
\n \n \n
\n
\n \n \n
\n
\n
\n\n
\n \n\n \n \n \n \n \n
\n
\n {children}\n
\n );\n}\n\nexport default function EditableVoidsDemo() {\n const editor = useCreateEditor({\n plugins: [...editorPlugins, EditableVoidPlugin],\n value: editableVoidsValue,\n });\n\n return (\n \n \n \n \n \n );\n}\n", + "content": "'use client';\n\nimport React, { useState } from 'react';\n\nimport type { PlateRenderElementProps } from '@udecode/plate-common/react';\n\nimport { Plate, createPlatePlugin } from '@udecode/plate-common/react';\n\nimport { Label } from '@/components/plate-ui/label';\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\nimport { editorPlugins } from '@/components/editor/plugins/editor-plugins';\nimport { useCreateEditor } from '@/components/editor/use-create-editor';\nimport { editableVoidsValue } from '@/registry/default/example/values/editable-voids-value';\nimport { Editor, EditorContainer } from '@/components/plate-ui/editor';\nimport { Input } from '@/components/plate-ui/input';\n\nexport const EditableVoidPlugin = createPlatePlugin({\n key: 'editable-void',\n node: {\n component: EditableVoidElement,\n isElement: true,\n isVoid: true,\n },\n});\n\nexport function EditableVoidElement({\n attributes,\n children,\n}: PlateRenderElementProps) {\n const [inputValue, setInputValue] = useState('');\n\n const editor = useCreateEditor({\n plugins: editorPlugins,\n });\n\n return (\n // Need contentEditable=false or Firefox has issues with certain input types.\n
\n
\n {\n setInputValue(e.target.value);\n }}\n placeholder=\"Name\"\n type=\"text\"\n />\n\n
\n \n\n \n
\n \n \n
\n
\n \n \n
\n
\n
\n\n
\n \n\n \n \n \n \n \n
\n
\n {children}\n
\n );\n}\n\nexport default function EditableVoidsDemo() {\n const editor = useCreateEditor({\n plugins: [...editorPlugins, EditableVoidPlugin],\n value: editableVoidsValue,\n });\n\n return (\n \n \n \n \n \n );\n}\n", "path": "example/editable-voids-demo.tsx", "target": "components/editable-voids-demo.tsx", "type": "registry:example" diff --git a/apps/www/public/r/styles/default/editor.json b/apps/www/public/r/styles/default/editor.json index df15e69c48..1103387fd2 100644 --- a/apps/www/public/r/styles/default/editor.json +++ b/apps/www/public/r/styles/default/editor.json @@ -15,7 +15,7 @@ }, "files": [ { - "content": "'use client';\n\nimport React from 'react';\n\nimport type { PlateContentProps } from '@udecode/plate-common/react';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@udecode/cn';\nimport {\n PlateContent,\n useEditorContainerRef,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\nconst editorContainerVariants = cva(\n 'relative w-full cursor-text overflow-y-auto caret-primary selection:bg-brand/25 [&_.slate-selection-area]:border [&_.slate-selection-area]:border-brand/25 [&_.slate-selection-area]:bg-brand/15',\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: 'h-full',\n demo: 'h-[650px]',\n },\n },\n }\n);\n\nexport const EditorContainer = ({\n className,\n variant,\n ...props\n}: React.HTMLAttributes &\n VariantProps) => {\n const editor = useEditorRef();\n const containerRef = useEditorContainerRef();\n\n return (\n \n );\n};\n\nEditorContainer.displayName = 'EditorContainer';\n\nconst editorVariants = cva(\n cn(\n 'group/editor',\n 'relative w-full overflow-x-hidden whitespace-pre-wrap break-words',\n 'rounded-md ring-offset-background placeholder:text-muted-foreground/80 focus-visible:outline-none',\n '[&_[data-slate-placeholder]]:text-muted-foreground/80 [&_[data-slate-placeholder]]:!opacity-100',\n '[&_[data-slate-placeholder]]:top-[auto_!important]',\n '[&_strong]:font-bold'\n ),\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n disabled: {\n true: 'cursor-not-allowed opacity-50',\n },\n focused: {\n true: 'ring-2 ring-ring ring-offset-2',\n },\n variant: {\n ai: 'w-full px-0 text-sm',\n aiChat:\n 'max-h-[min(70vh,320px)] w-full max-w-[700px] overflow-y-auto px-3 py-2 text-sm',\n default:\n 'size-full px-16 pb-72 pt-4 text-base sm:px-[max(64px,calc(50%-350px))]',\n demo: 'size-full px-16 pb-72 pt-4 text-base sm:px-[max(64px,calc(50%-350px))]',\n fullWidth: 'size-full px-16 pb-72 pt-4 text-base sm:px-24',\n none: '',\n },\n },\n }\n);\n\nexport type EditorProps = PlateContentProps &\n VariantProps;\n\nexport const Editor = React.forwardRef(\n ({ className, disabled, focused, variant, ...props }, ref) => {\n return (\n \n );\n }\n);\n\nEditor.displayName = 'Editor';\n", + "content": "'use client';\n\nimport React from 'react';\n\nimport type { PlateContentProps } from '@udecode/plate-common/react';\nimport type { VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@udecode/cn';\nimport {\n PlateContent,\n useEditorContainerRef,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { cva } from 'class-variance-authority';\n\nconst editorContainerVariants = cva(\n 'relative w-full cursor-text overflow-y-auto caret-primary selection:bg-brand/25 [&_.slate-selection-area]:border [&_.slate-selection-area]:border-brand/25 [&_.slate-selection-area]:bg-brand/15',\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n variant: {\n default: 'h-full',\n demo: 'h-[650px]',\n select: cn(\n 'group rounded-md border border-input ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2',\n 'has-[[data-readonly]]:w-fit has-[[data-readonly]]:cursor-default has-[[data-readonly]]:border-transparent has-[[data-readonly]]:focus-within:ring-0'\n ),\n },\n },\n }\n);\n\nexport const EditorContainer = ({\n className,\n variant,\n ...props\n}: React.HTMLAttributes &\n VariantProps) => {\n const editor = useEditorRef();\n const containerRef = useEditorContainerRef();\n\n return (\n \n );\n};\n\nEditorContainer.displayName = 'EditorContainer';\n\nconst editorVariants = cva(\n cn(\n 'group/editor',\n 'relative w-full overflow-x-hidden whitespace-pre-wrap break-words',\n 'rounded-md ring-offset-background placeholder:text-muted-foreground/80 focus-visible:outline-none',\n '[&_[data-slate-placeholder]]:text-muted-foreground/80 [&_[data-slate-placeholder]]:!opacity-100',\n '[&_[data-slate-placeholder]]:top-[auto_!important]',\n '[&_strong]:font-bold'\n ),\n {\n defaultVariants: {\n variant: 'default',\n },\n variants: {\n disabled: {\n true: 'cursor-not-allowed opacity-50',\n },\n focused: {\n true: 'ring-2 ring-ring ring-offset-2',\n },\n variant: {\n ai: 'w-full px-0 text-sm',\n aiChat:\n 'max-h-[min(70vh,320px)] w-full max-w-[700px] overflow-y-auto px-3 py-2 text-sm',\n default:\n 'size-full px-16 pb-72 pt-4 text-base sm:px-[max(64px,calc(50%-350px))]',\n demo: 'size-full px-16 pb-72 pt-4 text-base sm:px-[max(64px,calc(50%-350px))]',\n fullWidth: 'size-full px-16 pb-72 pt-4 text-base sm:px-24',\n none: '',\n select: 'px-3 py-2 text-base data-[readonly]:w-fit',\n },\n },\n }\n);\n\nexport type EditorProps = PlateContentProps &\n VariantProps;\n\nexport const Editor = React.forwardRef(\n ({ className, disabled, focused, variant, ...props }, ref) => {\n return (\n \n );\n }\n);\n\nEditor.displayName = 'Editor';\n", "path": "plate-ui/editor.tsx", "target": "components/plate-ui/editor.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/form.json b/apps/www/public/r/styles/default/form.json new file mode 100644 index 0000000000..6e45630f15 --- /dev/null +++ b/apps/www/public/r/styles/default/form.json @@ -0,0 +1,27 @@ +{ + "dependencies": [ + "react-hook-form", + "@hookform/resolvers/zod", + "@radix-ui/react-label", + "@radix-ui/react-slot" + ], + "doc": { + "description": "Building forms with React Hook Form and Zod.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/form" + } + }, + "files": [ + { + "content": "'use client';\n\nimport * as React from 'react';\nimport {\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n Controller,\n FormProvider,\n useFormContext,\n} from 'react-hook-form';\n\nimport type * as LabelPrimitive from '@radix-ui/react-label';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn } from '@udecode/cn';\n\nimport { Label } from '@/components/plate-ui/label';\n\nconst Form = FormProvider;\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n> = {\n name: TName;\n};\n\nconst FormFieldContext = React.createContext(\n {} as FormFieldContextValue\n);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath = FieldPath,\n>({\n ...props\n}: ControllerProps) => {\n return (\n \n \n \n );\n};\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext);\n const itemContext = React.useContext(FormItemContext);\n const { formState, getFieldState } = useFormContext();\n\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error('useFormField should be used within ');\n }\n\n const { id } = itemContext;\n\n return {\n id,\n formDescriptionId: `${id}-form-item-description`,\n formItemId: `${id}-form-item`,\n formMessageId: `${id}-form-item-message`,\n name: fieldContext.name,\n ...fieldState,\n };\n};\n\ntype FormItemContextValue = {\n id: string;\n};\n\nconst FormItemContext = React.createContext(\n {} as FormItemContextValue\n);\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => {\n const id = React.useId();\n\n return (\n \n
\n \n );\n});\nFormItem.displayName = 'FormItem';\n\nconst FormLabel = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField();\n\n return (\n \n );\n});\nFormLabel.displayName = 'FormLabel';\n\nconst FormControl = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ ...props }, ref) => {\n const { error, formDescriptionId, formItemId, formMessageId } =\n useFormField();\n\n return (\n \n );\n});\nFormControl.displayName = 'FormControl';\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField();\n\n return (\n \n );\n});\nFormDescription.displayName = 'FormDescription';\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ children, className, ...props }, ref) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message) : children;\n\n if (!body) {\n return null;\n }\n\n return (\n \n {body}\n

\n );\n});\nFormMessage.displayName = 'FormMessage';\n\nexport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n useFormField,\n};\n", + "path": "plate-ui/form.tsx", + "target": "components/plate-ui/form.tsx", + "type": "registry:ui" + } + ], + "name": "form", + "registryDependencies": [ + "label" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/input.json b/apps/www/public/r/styles/default/input.json index 3f4858d2ac..8e6478c950 100644 --- a/apps/www/public/r/styles/default/input.json +++ b/apps/www/public/r/styles/default/input.json @@ -1,7 +1,10 @@ { "dependencies": [], "doc": { - "description": "Displays a form input field or a component that looks like an input field." + "description": "Displays a form input field or a component that looks like an input field.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/input" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/label.json b/apps/www/public/r/styles/default/label.json new file mode 100644 index 0000000000..ae56b8610b --- /dev/null +++ b/apps/www/public/r/styles/default/label.json @@ -0,0 +1,22 @@ +{ + "dependencies": [ + "@radix-ui/react-label" + ], + "doc": { + "description": "Renders an accessible label associated with controls.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/label" + } + }, + "files": [ + { + "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { cn } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nconst labelVariants = cva(\n 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'\n);\n\nconst Label = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ className, ...props }, ref) => (\n \n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n", + "path": "plate-ui/label.tsx", + "target": "components/plate-ui/label.tsx", + "type": "registry:ui" + } + ], + "name": "label", + "registryDependencies": [], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/popover.json b/apps/www/public/r/styles/default/popover.json index 6f3d557329..efcaeee73e 100644 --- a/apps/www/public/r/styles/default/popover.json +++ b/apps/www/public/r/styles/default/popover.json @@ -3,11 +3,14 @@ "@radix-ui/react-popover" ], "doc": { - "description": "Displays rich content in a portal, triggered by a button." + "description": "Displays rich content in a portal, triggered by a button.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/popover" + } }, "files": [ { - "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn, withRef } from '@udecode/cn';\nimport { cva } from 'class-variance-authority';\n\nexport const Popover = PopoverPrimitive.Root;\n\nexport const PopoverTrigger = PopoverPrimitive.Trigger;\n\nexport const PopoverAnchor = PopoverPrimitive.Anchor;\n\nexport const popoverVariants = cva(\n 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 print:hidden'\n);\n\nexport const PopoverContent = withRef(\n ({ align = 'center', className, sideOffset = 4, ...props }, ref) => (\n \n \n \n )\n);\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn, withRef } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\n\nexport const Popover = PopoverPrimitive.Root;\n\nexport const PopoverTrigger = PopoverPrimitive.Trigger;\n\nexport const PopoverAnchor = PopoverPrimitive.Anchor;\n\nexport const popoverVariants = cva(\n 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none print:hidden',\n {\n defaultVariants: {\n animate: true,\n },\n variants: {\n animate: {\n true: '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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n },\n },\n }\n);\n\nexport const PopoverContent = withRef<\n typeof PopoverPrimitive.Content,\n VariantProps\n>(({ align = 'center', animate, className, sideOffset = 4, ...props }, ref) => (\n \n \n \n));\n", "path": "plate-ui/popover.tsx", "target": "components/plate-ui/popover.tsx", "type": "registry:ui" diff --git a/apps/www/public/r/styles/default/select-editor-demo.json b/apps/www/public/r/styles/default/select-editor-demo.json new file mode 100644 index 0000000000..31a607b5c0 --- /dev/null +++ b/apps/www/public/r/styles/default/select-editor-demo.json @@ -0,0 +1,21 @@ +{ + "doc": { + "description": "A form with a select editor component for managing labels.", + "title": "Select Editor Form" + }, + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\nimport { useForm, useWatch } from 'react-hook-form';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { CheckIcon, PlusIcon } from 'lucide-react';\nimport * as z from 'zod';\n\nimport { Button } from '@/components/plate-ui/button';\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormMessage,\n} from '@/components/plate-ui/form';\nimport {\n type SelectItem,\n SelectEditor,\n SelectEditorCombobox,\n SelectEditorContent,\n SelectEditorInput,\n} from '@/components/plate-ui/select-editor';\n\nconst LABELS = [\n { url: '/docs/components/editor', value: 'Editor' },\n { url: '/docs/components/select-editor', value: 'Select Editor' },\n { url: '/docs/components/block-selection', value: 'Block Selection' },\n { url: '/docs/components/button', value: 'Button' },\n { url: '/docs/components/command', value: 'Command' },\n { url: '/docs/components/dialog', value: 'Dialog' },\n { url: '/docs/components/form', value: 'Form' },\n { url: '/docs/components/input', value: 'Input' },\n { url: '/docs/components/label', value: 'Label' },\n { url: '/docs/components/plate-element', value: 'Plate Element' },\n { url: '/docs/components/popover', value: 'Popover' },\n { url: '/docs/components/tag-element', value: 'Tag Element' },\n] satisfies (SelectItem & { url: string })[];\n\nconst formSchema = z.object({\n labels: z\n .array(\n z.object({\n value: z.string(),\n })\n )\n .min(1, 'Select at least one label')\n .max(10, 'Select up to 10 labels'),\n});\n\ntype FormValues = z.infer;\n\nexport default function EditorSelectForm() {\n const [readOnly, setReadOnly] = React.useState(false);\n const form = useForm({\n defaultValues: {\n labels: [LABELS[0]],\n },\n resolver: zodResolver(formSchema),\n });\n\n const labels = useWatch({ control: form.control, name: 'labels' });\n\n return (\n
\n
\n
\n (\n \n
\n setReadOnly(!readOnly)}\n type=\"button\"\n >\n {readOnly ? (\n \n ) : (\n \n )}\n \n\n {readOnly && labels.length === 0 ? (\n {\n setReadOnly(false);\n }}\n type=\"button\"\n >\n \n Add labels\n \n ) : (\n \n \n \n \n {!readOnly && }\n \n \n \n )}\n
\n \n
\n )}\n />\n
\n
\n
\n );\n}\n", + "path": "example/select-editor-demo.tsx", + "target": "components/select-editor-demo.tsx", + "type": "registry:example" + } + ], + "name": "select-editor-demo", + "registryDependencies": [ + "form", + "button", + "select-editor" + ], + "type": "registry:example" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/select-editor-form-demo.json b/apps/www/public/r/styles/default/select-editor-form-demo.json new file mode 100644 index 0000000000..398f568e11 --- /dev/null +++ b/apps/www/public/r/styles/default/select-editor-form-demo.json @@ -0,0 +1,21 @@ +{ + "doc": { + "description": "A form with a select editor component for managing labels.", + "title": "Editor Select Form" + }, + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { CheckIcon, PlusIcon } from 'lucide-react';\nimport * as z from 'zod';\n\nimport { Button } from '@/components/plate-ui/button';\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormMessage,\n} from '@/components/plate-ui/form';\nimport {\n type SelectItem,\n SelectEditor,\n SelectEditorCombobox,\n SelectEditorContent,\n SelectEditorInput,\n} from '@/components/plate-ui/select-editor';\n\nconst LABELS = [\n { value: 'Bug' },\n { value: 'Feature' },\n { value: 'Enhancement' },\n { value: 'Breaking Change' },\n { value: 'Documentation' },\n { value: 'Design' },\n { value: 'Performance' },\n { value: 'Security' },\n { value: 'Technical Debt' },\n { value: 'Dependencies' },\n { value: 'High Priority' },\n { value: 'Low Priority' },\n] satisfies SelectItem[];\n\nconst formSchema = z.object({\n labels: z\n .array(\n z.object({\n value: z.string(),\n })\n )\n .min(1, 'Select at least one label')\n .max(10, 'Select up to 10 labels'),\n});\n\ntype FormValues = z.infer;\n\nexport function EditorSelectForm() {\n const [readOnly, setReadOnly] = React.useState(false);\n const form = useForm({\n defaultValues: {\n labels: [],\n },\n resolver: zodResolver(formSchema),\n });\n\n return (\n
\n
\n
\n (\n \n
\n \n \n \n \n {!readOnly && }\n \n \n \n\n setReadOnly(!readOnly)}\n type=\"button\"\n >\n {readOnly ? (\n \n ) : (\n \n )}\n \n
\n \n
\n )}\n />\n
\n
\n
\n );\n}\n", + "path": "example/select-editor-form.tsx", + "target": "components/select-editor-form.tsx", + "type": "registry:example" + } + ], + "name": "select-editor-form-demo", + "registryDependencies": [ + "form", + "button", + "select-editor" + ], + "type": "registry:example" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/select-editor.json b/apps/www/public/r/styles/default/select-editor.json new file mode 100644 index 0000000000..6ed4699218 --- /dev/null +++ b/apps/www/public/r/styles/default/select-editor.json @@ -0,0 +1,30 @@ +{ + "dependencies": [ + "fzf@0.5.2", + "@udecode/plate-tag", + "@udecode/cmdk" + ], + "doc": { + "description": "An editor to select tags.", + "examples": [ + "select-editor-demo" + ], + "label": "New" + }, + "files": [ + { + "content": "'use client';\n\nimport React from 'react';\n\nimport { useCommandActions } from '@udecode/cmdk';\nimport {\n getEditorString,\n isHotkey,\n moveSelection,\n removeEditorText,\n replaceNodeChildren,\n someNode,\n} from '@udecode/plate-common';\nimport {\n Plate,\n useEditorContainerRef,\n useEditorRef,\n usePlateEditor,\n} from '@udecode/plate-common/react';\nimport { isEqualTags } from '@udecode/plate-tag';\nimport {\n TagPlugin,\n useSelectEditorCombobox,\n useSelectableItems,\n} from '@udecode/plate-tag/react';\nimport { Fzf } from 'fzf';\nimport { PlusIcon } from 'lucide-react';\n\nimport {\n Command,\n CommandGroup,\n CommandItem,\n CommandList,\n} from '@/components/plate-ui/command';\nimport { Editor, EditorContainer } from '@/components/plate-ui/editor';\nimport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n} from '@/components/plate-ui/popover';\nimport { TagElement } from '@/components/plate-ui/tag-element';\n\nexport type SelectItem = {\n value: string;\n isNew?: boolean;\n};\n\ntype SelectEditorContextValue = {\n items: SelectItem[];\n open: boolean;\n setOpen: (open: boolean) => void;\n defaultValue?: SelectItem[];\n value?: SelectItem[];\n onValueChange?: (items: SelectItem[]) => void;\n};\n\nconst SelectEditorContext = React.createContext<\n SelectEditorContextValue | undefined\n>(undefined);\n\nconst useSelectEditorContext = () => {\n const context = React.useContext(SelectEditorContext);\n\n if (!context) {\n throw new Error('useSelectEditor must be used within SelectEditor');\n }\n\n return context;\n};\n\nexport function SelectEditor({\n children,\n defaultValue,\n items = [],\n value,\n onValueChange,\n}: {\n children: React.ReactNode;\n defaultValue?: SelectItem[];\n items?: SelectItem[];\n value?: SelectItem[];\n onValueChange?: (items: SelectItem[]) => void;\n}) {\n const [open, setOpen] = React.useState(false);\n const [internalValue] = React.useState(defaultValue);\n\n return (\n \n \n {children}\n \n \n );\n}\n\nexport function SelectEditorContent({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const { value } = useSelectEditorContext();\n const { setSearch } = useCommandActions();\n\n const editor = usePlateEditor(\n {\n plugins: [\n TagPlugin.configure({\n node: {\n component: TagElement,\n },\n }),\n ],\n value: createEditorValue(value),\n },\n []\n );\n\n React.useEffect(() => {\n if (!isEqualTags(editor, value)) {\n replaceNodeChildren(editor, {\n at: [],\n nodes: createEditorValue(value),\n });\n }\n }, [editor, value]);\n\n return (\n {\n setSearch(getEditorString(editor, []));\n }}\n editor={editor}\n >\n {children}\n \n );\n}\n\nexport const SelectEditorInput = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef\n>((props, ref) => {\n const editor = useEditorRef();\n const { setOpen } = useSelectEditorContext();\n const { selectCurrentItem, selectFirstItem } = useCommandActions();\n\n return (\n setOpen(false)}\n onFocusCapture={() => {\n setOpen(true);\n selectFirstItem();\n }}\n onKeyDown={(e) => {\n if (isHotkey('enter', e)) {\n e.preventDefault();\n selectCurrentItem();\n removeEditorText(editor);\n\n if (someNode(editor, { match: { type: TagPlugin.key } })) {\n moveSelection(editor);\n }\n }\n if (isHotkey('escape', e) || isHotkey('mod+enter', e)) {\n e.preventDefault();\n e.currentTarget.blur();\n }\n }}\n autoFocusOnEditable\n {...props}\n />\n );\n});\n\nexport function SelectEditorCombobox() {\n const editor = useEditorRef();\n const containerRef = useEditorContainerRef();\n const { items, open, onValueChange } = useSelectEditorContext();\n const selectableItems = useSelectableItems({\n filter: fzfFilter,\n items,\n });\n const { selectFirstItem } = useCommandActions();\n\n useSelectEditorCombobox({ open, selectFirstItem, onValueChange });\n\n if (!open || selectableItems.length === 0) return null;\n\n return (\n \n \n e.preventDefault()}\n onOpenAutoFocus={(e) => e.preventDefault()}\n align=\"start\"\n alignOffset={-4}\n animate={false}\n sideOffset={8}\n >\n \n \n {selectableItems.map((item) => (\n e.preventDefault()}\n onSelect={() => {\n editor.getTransforms(TagPlugin).insert.option(item);\n }}\n >\n {item.isNew ? (\n
\n \n Create new label:\n \"{item.value}\"\n
\n ) : (\n item.value\n )}\n \n ))}\n
\n
\n \n
\n );\n}\n\nconst createEditorValue = (value?: SelectItem[]) => [\n {\n children: [\n { text: '' },\n ...(value?.flatMap((item) => [\n {\n children: [{ text: '' }],\n type: TagPlugin.key,\n ...item,\n },\n {\n text: '',\n },\n ]) ?? []),\n ],\n type: 'p',\n },\n];\n\nconst fzfFilter = (value: string, search: string): boolean => {\n if (!search) return true;\n\n const fzf = new Fzf([value], {\n casing: 'case-insensitive',\n selector: (v: string) => v,\n });\n\n return fzf.find(search).length > 0;\n};\n", + "path": "plate-ui/select-editor.tsx", + "target": "components/plate-ui/select-editor.tsx", + "type": "registry:ui" + } + ], + "name": "select-editor", + "registryDependencies": [ + "editor", + "command", + "popover", + "tag-element" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/separator.json b/apps/www/public/r/styles/default/separator.json index c8db3dc91d..bf987dba87 100644 --- a/apps/www/public/r/styles/default/separator.json +++ b/apps/www/public/r/styles/default/separator.json @@ -3,7 +3,10 @@ "@radix-ui/react-separator" ], "doc": { - "description": "Visually or semantically separates content." + "description": "Visually or semantically separates content.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/separator" + } }, "files": [ { diff --git a/apps/www/public/r/styles/default/tag-element.json b/apps/www/public/r/styles/default/tag-element.json new file mode 100644 index 0000000000..cc9c300905 --- /dev/null +++ b/apps/www/public/r/styles/default/tag-element.json @@ -0,0 +1,27 @@ +{ + "dependencies": [], + "doc": { + "description": "A tag element component with selection states and styling.", + "docs": [ + { + "route": "/docs/tag" + } + ], + "examples": [ + "select-editor-demo" + ] + }, + "files": [ + { + "content": "'use client';\n\nimport { cn, withRef } from '@udecode/cn';\nimport { PlateElement } from '@udecode/plate-common/react';\nimport Link from 'next/link';\nimport { useFocused, useReadOnly, useSelected } from 'slate-react';\n\nexport const TagElement = withRef(\n ({ children, className, ...props }, ref) => {\n const { element } = props;\n const selected = useSelected();\n const focused = useFocused();\n const readOnly = useReadOnly();\n\n console.log(element);\n\n const badge = (\n \n {element.value as string}\n
\n );\n\n const content =\n readOnly && element.url ? (\n {badge}\n ) : (\n badge\n );\n\n return (\n \n {content}\n {children}\n \n );\n }\n);\n", + "path": "plate-ui/tag-element.tsx", + "target": "components/plate-ui/tag-element.tsx", + "type": "registry:ui" + } + ], + "name": "tag-element", + "registryDependencies": [ + "plate-element" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/tooltip.json b/apps/www/public/r/styles/default/tooltip.json index ace75be031..174a0ad235 100644 --- a/apps/www/public/r/styles/default/tooltip.json +++ b/apps/www/public/r/styles/default/tooltip.json @@ -3,7 +3,10 @@ "@radix-ui/react-tooltip" ], "doc": { - "description": "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it." + "description": "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.", + "links": { + "doc": "https://ui.shadcn.com/docs/components/tooltip" + } }, "files": [ { diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx index 4e081dfbf9..71963e21de 100644 --- a/apps/www/src/__registry__/index.tsx +++ b/apps/www/src/__registry__/index.tsx @@ -401,6 +401,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "tag-element": { + name: "tag-element", + description: "", + type: "registry:ui", + registryDependencies: ["plate-element"], + files: ["registry/default/plate-ui/tag-element.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/tag-element.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "toc-element": { name: "toc-element", description: "", @@ -545,6 +557,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "form": { + name: "form", + description: "", + type: "registry:ui", + registryDependencies: ["label"], + files: ["registry/default/plate-ui/form.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/form.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "input": { name: "input", description: "", @@ -557,6 +581,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "label": { + name: "label", + description: "", + type: "registry:ui", + registryDependencies: [], + files: ["registry/default/plate-ui/label.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/label.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "popover": { name: "popover", description: "", @@ -761,6 +797,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "select-editor": { + name: "select-editor", + description: "", + type: "registry:ui", + registryDependencies: ["editor","command","popover","tag-element"], + files: ["registry/default/plate-ui/select-editor.tsx"], + component: React.lazy(() => import("@/registry/default/plate-ui/select-editor.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "emoji-dropdown-menu": { name: "emoji-dropdown-menu", description: "", @@ -1949,6 +1997,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "select-editor-demo": { + name: "select-editor-demo", + description: "", + type: "registry:example", + registryDependencies: ["form","button","select-editor"], + files: ["registry/default/example/select-editor-demo.tsx"], + component: React.lazy(() => import("@/registry/default/example/select-editor-demo.tsx")), + source: "", + category: "", + subcategory: "", + chunks: [] + }, "single-line-demo": { name: "single-line-demo", description: "", diff --git a/apps/www/src/app/(app)/_components/installation-tab.tsx b/apps/www/src/app/(app)/_components/installation-tab.tsx index 1f0e34d0bb..f4e22a5853 100644 --- a/apps/www/src/app/(app)/_components/installation-tab.tsx +++ b/apps/www/src/app/(app)/_components/installation-tab.tsx @@ -19,7 +19,6 @@ import { AccordionItem, AccordionTrigger, } from '@/components/ui/accordion'; -import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { allPlugins, @@ -27,6 +26,7 @@ import { orderedPluginKeys, } from '@/config/customizer-items'; import { useMounted } from '@/registry/default/hooks/use-mounted'; +import { Label } from '@/registry/default/plate-ui/label'; import { InstallationCode } from './installation-code'; diff --git a/apps/www/src/components/plugins-tab-content.tsx b/apps/www/src/components/plugins-tab-content.tsx index 6a1b3ee9b7..6c01ded263 100644 --- a/apps/www/src/components/plugins-tab-content.tsx +++ b/apps/www/src/components/plugins-tab-content.tsx @@ -20,7 +20,7 @@ import { AccordionItem, AccordionTrigger, } from './ui/accordion'; -import { Label } from './ui/label'; +import { Label } from '../registry/default/plate-ui/label'; export function SettingsEffect() { const checkedPluginsNext = settingsStore.use.checkedPluginsNext(); diff --git a/apps/www/src/components/setting-checkbox.tsx b/apps/www/src/components/setting-checkbox.tsx index 2fff6dce3d..2e7801707c 100644 --- a/apps/www/src/components/setting-checkbox.tsx +++ b/apps/www/src/components/setting-checkbox.tsx @@ -17,7 +17,7 @@ import { settingsStore } from './context/settings-store'; import { Icons } from './icons'; import { TreeIcon } from './tree-icon'; import { Badge } from './ui/badge'; -import { Label } from './ui/label'; +import { Label } from '../registry/default/plate-ui/label'; export function SettingCheckbox({ id, diff --git a/apps/www/src/components/theme-customizer.tsx b/apps/www/src/components/theme-customizer.tsx index 1020bf5a3b..4821c81881 100644 --- a/apps/www/src/components/theme-customizer.tsx +++ b/apps/www/src/components/theme-customizer.tsx @@ -25,7 +25,7 @@ import { Separator } from '@/registry/default/plate-ui/separator'; import { CopyCodeButton, getThemeCode } from './copy-code-button'; import { ThemesSwitcher } from './themes-selector-mini'; -import { Label } from './ui/label'; +import { Label } from '../registry/default/plate-ui/label'; import { Skeleton } from './ui/skeleton'; export function ThemeCustomizer() { diff --git a/apps/www/src/config/docs-examples.ts b/apps/www/src/config/docs-examples.ts index 805d8a966b..101866dfaa 100644 --- a/apps/www/src/config/docs-examples.ts +++ b/apps/www/src/config/docs-examples.ts @@ -48,7 +48,7 @@ export const docsExamples: SidebarNavItem[] = [ ) ).map((item) => ({ ...item, - title: item.title + ' Demo', + title: item.title + (item.title?.includes(' Form') ? '' : ' Demo'), })), ]; diff --git a/apps/www/src/config/docs-icons.tsx b/apps/www/src/config/docs-icons.tsx index 868f4dba97..1c3862aa2b 100644 --- a/apps/www/src/config/docs-icons.tsx +++ b/apps/www/src/config/docs-icons.tsx @@ -89,6 +89,8 @@ import { SquareStackIcon, TableIcon, TableOfContentsIcon, + TagIcon, + TagsIcon, TextCursorIcon, TextCursorInputIcon, TextSearchIcon, @@ -234,6 +236,7 @@ export const DocIcons = { resizable: ProportionsIcon, 'search-highlight-leaf': HighlighterIcon, select: KeyboardIcon, + 'select-editor': TagsIcon, separator: SeparatorHorizontalIcon, 'server-side': ServerIcon, 'single-line': RectangleHorizontalIcon, @@ -247,6 +250,7 @@ export const DocIcons = { 'table-dropdown-menu': TableIcon, 'table-element': GridIcon, 'table-row-element': RowsIcon, + 'tag-element': TagIcon, toc: TableOfContentsIcon, 'toc-element': TableOfContentsIcon, 'todo-list-element': ListTodoIcon, diff --git a/apps/www/src/registry/default/example/editable-voids-demo.tsx b/apps/www/src/registry/default/example/editable-voids-demo.tsx index c5c7320d79..399b1f81b2 100644 --- a/apps/www/src/registry/default/example/editable-voids-demo.tsx +++ b/apps/www/src/registry/default/example/editable-voids-demo.tsx @@ -6,7 +6,7 @@ import type { PlateRenderElementProps } from '@udecode/plate-common/react'; import { Plate, createPlatePlugin } from '@udecode/plate-common/react'; -import { Label } from '@/components/ui/label'; +import { Label } from '@/registry/default/plate-ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { editorPlugins } from '@/registry/default/components/editor/plugins/editor-plugins'; import { useCreateEditor } from '@/registry/default/components/editor/use-create-editor'; diff --git a/apps/www/src/registry/default/example/select-editor-demo.tsx b/apps/www/src/registry/default/example/select-editor-demo.tsx new file mode 100644 index 0000000000..3fcde3726e --- /dev/null +++ b/apps/www/src/registry/default/example/select-editor-demo.tsx @@ -0,0 +1,129 @@ +'use client'; + +import React from 'react'; +import { useForm, useWatch } from 'react-hook-form'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { CheckIcon, PlusIcon } from 'lucide-react'; +import * as z from 'zod'; + +import { Button } from '@/registry/default/plate-ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/registry/default/plate-ui/form'; +import { + type SelectItem, + SelectEditor, + SelectEditorCombobox, + SelectEditorContent, + SelectEditorInput, +} from '@/registry/default/plate-ui/select-editor'; + +const LABELS = [ + { url: '/docs/components/editor', value: 'Editor' }, + { url: '/docs/components/select-editor', value: 'Select Editor' }, + { url: '/docs/components/block-selection', value: 'Block Selection' }, + { url: '/docs/components/button', value: 'Button' }, + { url: '/docs/components/command', value: 'Command' }, + { url: '/docs/components/dialog', value: 'Dialog' }, + { url: '/docs/components/form', value: 'Form' }, + { url: '/docs/components/input', value: 'Input' }, + { url: '/docs/components/label', value: 'Label' }, + { url: '/docs/components/plate-element', value: 'Plate Element' }, + { url: '/docs/components/popover', value: 'Popover' }, + { url: '/docs/components/tag-element', value: 'Tag Element' }, +] satisfies (SelectItem & { url: string })[]; + +const formSchema = z.object({ + labels: z + .array( + z.object({ + value: z.string(), + }) + ) + .min(1, 'Select at least one label') + .max(10, 'Select up to 10 labels'), +}); + +type FormValues = z.infer; + +export default function EditorSelectForm() { + const [readOnly, setReadOnly] = React.useState(false); + const form = useForm({ + defaultValues: { + labels: [LABELS[0]], + }, + resolver: zodResolver(formSchema), + }); + + const labels = useWatch({ control: form.control, name: 'labels' }); + + return ( +
+
+
+ ( + +
+ + + {readOnly && labels.length === 0 ? ( + + ) : ( + + + + + {!readOnly && } + + + + )} +
+ +
+ )} + /> +
+
+
+ ); +} diff --git a/apps/www/src/registry/default/plate-ui/command.tsx b/apps/www/src/registry/default/plate-ui/command.tsx index 0a4b9dc120..e4ae513588 100644 --- a/apps/www/src/registry/default/plate-ui/command.tsx +++ b/apps/www/src/registry/default/plate-ui/command.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import type { DialogProps } from '@radix-ui/react-dialog'; +import { Command as CommandPrimitive } from '@udecode/cmdk'; import { cn, createPrimitiveElement, @@ -11,17 +12,31 @@ import { withRef, withVariants, } from '@udecode/cn'; -import { Command as CommandPrimitive } from 'cmdk'; +import { cva } from 'class-variance-authority'; import { Search } from 'lucide-react'; import { Dialog, DialogContent, DialogTitle } from './dialog'; import { inputVariants } from './input'; -export const Command = withCn( - CommandPrimitive, - 'flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground' +const commandVariants = cva( + 'flex size-full flex-col rounded-md bg-popover text-popover-foreground', + { + defaultVariants: { + variant: 'default', + }, + variants: { + variant: { + combobox: 'overflow-visible bg-transparent has-[[data-readonly]]:w-fit', + default: 'overflow-hidden', + }, + }, + } ); +export const Command = withVariants(CommandPrimitive, commandVariants, [ + 'variant', +]); + export function CommandDialog({ children, ...props }: DialogProps) { return ( diff --git a/apps/www/src/registry/default/plate-ui/editor.tsx b/apps/www/src/registry/default/plate-ui/editor.tsx index 6810bb171c..d19de25749 100644 --- a/apps/www/src/registry/default/plate-ui/editor.tsx +++ b/apps/www/src/registry/default/plate-ui/editor.tsx @@ -23,6 +23,10 @@ const editorContainerVariants = cva( variant: { default: 'h-full', demo: 'h-[650px]', + select: cn( + 'group rounded-md border border-input ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2', + 'has-[[data-readonly]]:w-fit has-[[data-readonly]]:cursor-default has-[[data-readonly]]:border-transparent has-[[data-readonly]]:focus-within:ring-0' + ), }, }, } @@ -83,6 +87,7 @@ const editorVariants = cva( demo: 'size-full px-16 pb-72 pt-4 text-base sm:px-[max(64px,calc(50%-350px))]', fullWidth: 'size-full px-16 pb-72 pt-4 text-base sm:px-24', none: '', + select: 'px-3 py-2 text-base data-[readonly]:w-fit', }, }, } diff --git a/apps/www/src/registry/default/plate-ui/form.tsx b/apps/www/src/registry/default/plate-ui/form.tsx new file mode 100644 index 0000000000..944be3ed1f --- /dev/null +++ b/apps/www/src/registry/default/plate-ui/form.tsx @@ -0,0 +1,179 @@ +'use client'; + +import * as React from 'react'; +import { + type ControllerProps, + type FieldPath, + type FieldValues, + Controller, + FormProvider, + useFormContext, +} from 'react-hook-form'; + +import type * as LabelPrimitive from '@radix-ui/react-label'; + +import { Slot } from '@radix-ui/react-slot'; +import { cn } from '@udecode/cn'; + +import { Label } from '@/registry/default/plate-ui/label'; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { formState, getFieldState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } + + const { id } = itemContext; + + return { + id, + formDescriptionId: `${id}-form-item-description`, + formItemId: `${id}-form-item`, + formMessageId: `${id}-form-item-message`, + name: fieldContext.name, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = 'FormItem'; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +