@inui/design-system 0.0.0-stage → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -2
- package/dist/components/ui/accordion.d.ts +44 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +86 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/ai-avatar.d.ts +46 -0
- package/dist/components/ui/ai-avatar.d.ts.map +1 -0
- package/dist/components/ui/ai-avatar.js +43 -0
- package/dist/components/ui/ai-avatar.js.map +1 -0
- package/dist/components/ui/ai-label.d.ts +118 -0
- package/dist/components/ui/ai-label.d.ts.map +1 -0
- package/dist/components/ui/ai-label.js +184 -0
- package/dist/components/ui/ai-label.js.map +1 -0
- package/dist/components/ui/ai-sparkle.d.ts +91 -0
- package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
- package/dist/components/ui/ai-sparkle.js +200 -0
- package/dist/components/ui/ai-sparkle.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +79 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +147 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/alert.d.ts +71 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +108 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/aspect-ratio.d.ts +30 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +40 -0
- package/dist/components/ui/aspect-ratio.js.map +1 -0
- package/dist/components/ui/attachment.d.ts +74 -0
- package/dist/components/ui/attachment.d.ts.map +1 -0
- package/dist/components/ui/attachment.js +142 -0
- package/dist/components/ui/attachment.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +82 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +123 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +130 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +226 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +43 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +98 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/browser-tabs.d.ts +218 -0
- package/dist/components/ui/browser-tabs.d.ts.map +1 -0
- package/dist/components/ui/browser-tabs.js +528 -0
- package/dist/components/ui/browser-tabs.js.map +1 -0
- package/dist/components/ui/bubble.d.ts +80 -0
- package/dist/components/ui/bubble.d.ts.map +1 -0
- package/dist/components/ui/bubble.js +235 -0
- package/dist/components/ui/bubble.js.map +1 -0
- package/dist/components/ui/button-group.d.ts +60 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +89 -0
- package/dist/components/ui/button-group.js.map +1 -0
- package/dist/components/ui/button.d.ts +157 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +144 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +57 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +163 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +96 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +131 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/carousel.d.ts +86 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +207 -0
- package/dist/components/ui/carousel.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +54 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +78 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +45 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +65 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +111 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +264 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +71 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +133 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +70 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +163 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/count-badge.d.ts +40 -0
- package/dist/components/ui/count-badge.d.ts.map +1 -0
- package/dist/components/ui/count-badge.js +43 -0
- package/dist/components/ui/count-badge.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +1026 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +3423 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +73 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +145 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/direction.d.ts +35 -0
- package/dist/components/ui/direction.d.ts.map +1 -0
- package/dist/components/ui/direction.js +35 -0
- package/dist/components/ui/direction.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +53 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +115 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +75 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +173 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/dropzone.d.ts +141 -0
- package/dist/components/ui/dropzone.d.ts.map +1 -0
- package/dist/components/ui/dropzone.js +257 -0
- package/dist/components/ui/dropzone.js.map +1 -0
- package/dist/components/ui/empty.d.ts +94 -0
- package/dist/components/ui/empty.d.ts.map +1 -0
- package/dist/components/ui/empty.js +149 -0
- package/dist/components/ui/empty.js.map +1 -0
- package/dist/components/ui/field.d.ts +122 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/components/ui/field.js +254 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +51 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +101 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +62 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +115 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-otp.d.ts +52 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +89 -0
- package/dist/components/ui/input-otp.js.map +1 -0
- package/dist/components/ui/input.d.ts +47 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +47 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/item.d.ts +74 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +176 -0
- package/dist/components/ui/item.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +41 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +58 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-value-pair.d.ts +56 -0
- package/dist/components/ui/key-value-pair.d.ts.map +1 -0
- package/dist/components/ui/key-value-pair.js +110 -0
- package/dist/components/ui/key-value-pair.js.map +1 -0
- package/dist/components/ui/label.d.ts +83 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +85 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/marker.d.ts +54 -0
- package/dist/components/ui/marker.d.ts.map +1 -0
- package/dist/components/ui/marker.js +79 -0
- package/dist/components/ui/marker.js.map +1 -0
- package/dist/components/ui/menubar.d.ts +72 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +174 -0
- package/dist/components/ui/menubar.js.map +1 -0
- package/dist/components/ui/message-scroller.d.ts +53 -0
- package/dist/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller.js +95 -0
- package/dist/components/ui/message-scroller.js.map +1 -0
- package/dist/components/ui/message.d.ts +63 -0
- package/dist/components/ui/message.d.ts.map +1 -0
- package/dist/components/ui/message.js +92 -0
- package/dist/components/ui/message.js.map +1 -0
- package/dist/components/ui/meta-list.d.ts +55 -0
- package/dist/components/ui/meta-list.d.ts.map +1 -0
- package/dist/components/ui/meta-list.js +75 -0
- package/dist/components/ui/meta-list.js.map +1 -0
- package/dist/components/ui/native-select.d.ts +51 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +75 -0
- package/dist/components/ui/native-select.js.map +1 -0
- package/dist/components/ui/navigation-menu.d.ts +56 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +116 -0
- package/dist/components/ui/navigation-menu.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +223 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +233 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +68 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +145 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +61 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +144 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +35 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +46 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/questionnaire.d.ts +92 -0
- package/dist/components/ui/questionnaire.d.ts.map +1 -0
- package/dist/components/ui/questionnaire.js +277 -0
- package/dist/components/ui/questionnaire.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +50 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +44 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +73 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +55 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +72 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +274 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +609 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +31 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +38 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +59 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +128 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +166 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +512 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +35 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +46 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +39 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +75 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +56 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +96 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +37 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +51 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +84 -0
- package/dist/components/ui/stepper.d.ts.map +1 -0
- package/dist/components/ui/stepper.js +145 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/switch.d.ts +36 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +44 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +80 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +130 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +87 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +128 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +35 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +38 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +58 -0
- package/dist/components/ui/time-picker.d.ts.map +1 -0
- package/dist/components/ui/time-picker.js +188 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +52 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +94 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +50 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +70 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +51 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +78 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/hooks/use-dark-mode.d.ts +2 -0
- package/dist/hooks/use-dark-mode.d.ts.map +1 -0
- package/dist/hooks/use-dark-mode.js +36 -0
- package/dist/hooks/use-dark-mode.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +20 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/lib/scope.d.ts +64 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +74 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/theme-tokens.css +537 -0
- package/dist/theme.css +417 -0
- package/package.json +103 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropzone.js","names":[],"sources":["../../../src/components/ui/dropzone.tsx"],"sourcesContent":["/**\n * Dropzone — click-or-drag target that hands files to the caller.\n *\n * A composite over Attachment, not a new primitive: it's Attachment's\n * `state=\"idle\"` dashed-border look wired up with a native hidden file\n * input plus drag-and-drop handlers. Selected files themselves aren't\n * rendered here — map them to a stack of Attachment rows below the\n * dropzone (state=\"uploading\"/\"error\"/\"done\" per file, AttachmentAction\n * for retry/remove) the same way any other Attachment list works.\n * FileTypeIcon (below) gives each row a colored icon circle per file type,\n * as an AttachmentMedia composition.\n */\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport {\n ArrowDownToLineIcon,\n FileAudioIcon,\n FileIcon,\n FileTextIcon,\n FileVideoIcon,\n ImageIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Attachment, AttachmentMedia } from \"@/components/ui/attachment\"\n\ntype FileTypeFamily = \"video\" | \"image\" | \"document\" | \"audio\" | \"other\"\n\n/** Derives a file-type family from a File or a plain MIME-type string. */\nfunction getFileTypeFamily(file: File | string): FileTypeFamily {\n const type = typeof file === \"string\" ? file : file.type\n if (type.startsWith(\"video/\")) return \"video\"\n if (type.startsWith(\"image/\")) return \"image\"\n if (type.startsWith(\"audio/\")) return \"audio\"\n if (type.startsWith(\"application/\")) return \"document\"\n return \"other\"\n}\n\n/** Whether a dragged item's MIME type matches an `accept` list (comma-separated, wildcards like \"image/*\" supported). */\nfunction matchesAccept(type: string, accept: string): boolean {\n if (!type) return true\n return accept\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n .some((entry) =>\n entry.endsWith(\"/*\")\n ? type.startsWith(entry.slice(0, -1))\n : type === entry\n )\n}\n\nconst fileTypeIcons: Record<\n FileTypeFamily,\n React.ComponentType<React.ComponentProps<\"svg\">>\n> = {\n video: FileVideoIcon,\n image: ImageIcon,\n document: FileTextIcon,\n audio: FileAudioIcon,\n other: FileIcon,\n}\n\n// Decorative, non-semantic category color — the chart-1..8 ramp is the only\n// sanctioned one (design-mind genome/tokens.md), so each family gets a\n// distinct chart hue: a 15%-opacity tint for the bg, the solid hue for the\n// icon. The ramp has no red/green, so image/other substitute the closest\n// distinguishable hues (pink, lime) rather than the raw destructive/success\n// colors those would otherwise imply.\n//\n// `!` (important): AttachmentMedia's own `group-data-[state=error]/attachment:\n// bg-destructive/10` unconditionally repaints the icon slot on an errored\n// Attachment, which otherwise wins the specificity fight against these plain\n// classes — silently replacing the type color with a translucent destructive\n// tint on EVERY family, not just the one that resembles it, where it's\n// hardest to notice. The type color is meant to survive every Attachment\n// state; only the description text and retry action should carry the error\n// signal.\nconst fileTypeAccents: Record<FileTypeFamily, string> = {\n video: \"!bg-chart-2/15 !text-chart-2\",\n image: \"!bg-chart-6/15 !text-chart-6\",\n document: \"!bg-chart-3/15 !text-chart-3\",\n audio: \"!bg-chart-1/15 !text-chart-1\",\n other: \"!bg-chart-4/15 !text-chart-4\",\n}\n\n/**\n * FileTypeIcon — colored icon-circle, per file type.\n *\n * A thin AttachmentMedia composition, not a new primitive: swaps its default\n * square muted-bg look for a `rounded-full` circle tinted per file-type\n * family (derived from `file`'s MIME type, or pass `family` directly).\n */\nfunction FileTypeIcon({\n file,\n family,\n className,\n ...props\n}: Omit<\n React.ComponentProps<typeof AttachmentMedia>,\n \"variant\" | \"children\"\n> & {\n file?: File | string\n family?: FileTypeFamily\n}) {\n const resolvedFamily = family ?? (file ? getFileTypeFamily(file) : \"other\")\n const Icon = fileTypeIcons[resolvedFamily]\n\n return (\n <AttachmentMedia\n variant=\"icon\"\n className={cn(\"rounded-full\", fileTypeAccents[resolvedFamily], className)}\n {...props}\n >\n <Icon />\n </AttachmentMedia>\n )\n}\n\nconst dropzoneVariants = cva(\n \"group/dropzone w-full cursor-pointer items-center justify-center transition-colors\",\n {\n variants: {\n size: {\n default:\n \"flex-col gap-1.5 p-6 text-center [&_svg:not([class*='size-'])]:size-8\",\n // Row, icon on the left — same treatment as xs below, just with the icon kept.\n sm: \"flex-row gap-3 p-4 text-left [&_svg:not([class*='size-'])]:size-6\",\n // Row, no icon at all — same as every other xs UI state (Empty, etc), which\n // drops the icon entirely rather than just shrinking it.\n xs: \"flex-row gap-2 p-2.5 text-left [&_svg:not([class*='size-'])]:size-5\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\nconst dropzoneFeedbackIconVariants = cva(\n \"flex shrink-0 items-center justify-center [&_svg]:pointer-events-none\",\n {\n variants: {\n size: {\n default: \"size-10 [&_svg:not([class*='size-'])]:size-5\",\n sm: \"size-9 [&_svg:not([class*='size-'])]:size-4\",\n xs: \"size-7 [&_svg:not([class*='size-'])]:size-4\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\n/** Default \"active\" (valid drag-over) feedback — a filled icon plus a short instruction, replacing `children` entirely while dragging. No icon at xs, same as everywhere else in this file. */\nfunction DropzoneActiveContent({ size }: { size?: \"default\" | \"sm\" | \"xs\" }) {\n return (\n <>\n {size !== \"xs\" && (\n <div\n className={cn(\n dropzoneFeedbackIconVariants({ size }),\n \"rounded-lg bg-primary text-primary-foreground\"\n )}\n >\n <ArrowDownToLineIcon />\n </div>\n )}\n <p className=\"font-medium text-primary\">Drag your files here</p>\n </>\n )\n}\n\n/** Default \"error\" (rejected file type on drag-over) feedback — a filled \"!\" plus a short reason, replacing `children` entirely while dragging. No icon at xs, same as everywhere else in this file. */\nfunction DropzoneErrorContent({ size }: { size?: \"default\" | \"sm\" | \"xs\" }) {\n return (\n <>\n {size !== \"xs\" && (\n <div\n className={cn(\n dropzoneFeedbackIconVariants({ size }),\n \"rounded-full bg-destructive text-destructive-foreground\"\n )}\n >\n <span className=\"text-base leading-none font-bold\">!</span>\n </div>\n )}\n <p className=\"font-medium text-destructive\">File format not accepted</p>\n </>\n )\n}\n\nfunction Dropzone({\n className,\n onFilesSelected,\n accept,\n multiple = true,\n disabled,\n size = \"default\",\n state,\n activeContent,\n errorContent,\n children,\n id,\n \"aria-describedby\": ariaDescribedby,\n \"aria-invalid\": ariaInvalid,\n inputRef,\n ...props\n}: Omit<\n React.ComponentProps<\"label\">,\n \"onDrop\" | \"onDragOver\" | \"onDragLeave\"\n> & {\n onFilesSelected: (files: File[]) => void\n accept?: string\n multiple?: boolean\n disabled?: boolean\n size?: \"default\" | \"sm\" | \"xs\"\n /** Overrides the drag-derived visual state — mainly for demos; leave unset for real drag-and-drop. */\n state?: \"default\" | \"active\" | \"error\"\n /** Overrides the built-in \"active\" feedback content (icon + \"Drag your files here\"). */\n activeContent?: React.ReactNode\n /** Overrides the built-in \"error\" feedback content (icon + \"File format not accepted\"). */\n errorContent?: React.ReactNode\n /** Ref to the underlying (visually-hidden) `<input type=\"file\">` — the actual control `id`/`aria-describedby`/`aria-invalid` land on, not the outer `<label>`. */\n inputRef?: React.Ref<HTMLInputElement>\n}) {\n const [dragState, setDragState] = React.useState<\n \"default\" | \"active\" | \"error\"\n >(\"default\")\n const resolvedState = state ?? dragState\n // `id`/`aria-describedby`/`aria-invalid` describe the actual file control,\n // not the label wrapping it — a `<label>` only passes its *name* through\n // native label association, not these ARIA relationships. Generate a\n // fallback id so a consumer can still target the input even without\n // passing one explicitly.\n const generatedId = React.useId()\n const inputId = id ?? generatedId\n\n return (\n <label\n data-slot=\"dropzone\"\n data-state={resolvedState}\n className={cn(\"block\", disabled && \"cursor-not-allowed\")}\n {...props}\n >\n <input\n ref={inputRef}\n type=\"file\"\n id={inputId}\n data-slot=\"dropzone-input\"\n className=\"sr-only\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n aria-describedby={ariaDescribedby}\n aria-invalid={ariaInvalid}\n onChange={(e) => {\n const files = Array.from(e.target.files ?? [])\n e.target.value = \"\"\n if (files.length) onFilesSelected(files)\n }}\n />\n <Attachment\n data-slot=\"dropzone-target\"\n state=\"idle\"\n orientation=\"vertical\"\n onDragOver={(e) => {\n e.preventDefault()\n if (disabled) return\n const items = Array.from(e.dataTransfer.items ?? [])\n const allAccepted =\n !accept || items.every((item) => matchesAccept(item.type, accept))\n setDragState(allAccepted ? \"active\" : \"error\")\n }}\n onDragLeave={() => setDragState(\"default\")}\n onDrop={(e) => {\n e.preventDefault()\n setDragState(\"default\")\n if (disabled) return\n const files = Array.from(e.dataTransfer.files ?? [])\n if (files.length) onFilesSelected(files)\n }}\n className={cn(\n dropzoneVariants({ size }),\n resolvedState === \"active\" && \"border-primary bg-primary/10\",\n resolvedState === \"error\" && \"border-destructive bg-destructive/10\",\n disabled && \"pointer-events-none opacity-50\",\n className\n )}\n >\n {resolvedState === \"default\" && children}\n {resolvedState === \"active\" &&\n (activeContent ?? <DropzoneActiveContent size={size} />)}\n {resolvedState === \"error\" &&\n (errorContent ?? <DropzoneErrorContent size={size} />)}\n </Attachment>\n </label>\n )\n}\n\nexport { Dropzone, FileTypeIcon, getFileTypeFamily }\n\n/**\n * WHEN TO USE\n * - A drag-and-drop or click-to-browse target for picking one or more files\n * - Paired with a list of Attachment rows below it, one per selected file,\n * for per-file progress/error/remove — this is Dropzone's job, not the\n * list's; there's no dedicated list component, just Attachment rows\n * - FileTypeIcon on each row's AttachmentMedia slot for colored, per-type\n * icon circles (video/image/document/audio/other)\n *\n * NOT WHEN\n * - A plain, unstyled file input with no drop target or preview — use Input type=\"file\"\n * - Files are already attached and just need to be displayed — use Attachment/AttachmentGroup directly\n *\n * KEY RULES\n * - `onFilesSelected(files: File[])` fires from both the native picker (click) and drag-and-drop —\n * handle it once, not twice\n * - `size`: \"default\" | \"sm\" | \"xs\" — controls LAYOUT (Dropzone owns this via `dropzoneVariants`,\n * not just padding/icon-size): \"default\" stacks the icon above the text, \"sm\" puts the icon on\n * the left instead (row, not stacked — the format/size hint stays, it's not optional info, just\n * repositioned), \"xs\" drops the icon entirely down to one compact text line — same rule every\n * other xs UI state in this library follows (see Empty). The children themselves (icon, hint\n * text) are still fully custom — Dropzone only dictates the flex direction and whether there's\n * room to show a leading icon; get the actual \"which size shows what\" split from a real example\n * like the Sizes story, don't just eyeball it from this list.\n * - `state` is normally left unset — Dropzone derives \"active\" (valid drag-over) and \"error\"\n * (drag-over with a file type `accept` would reject) from real drag events by inspecting\n * `dataTransfer.items[].type`, which browsers expose pre-drop. Pass `state` explicitly only\n * to force a look for a demo; a real Dropzone should let this stay derived.\n * - Unlike the idle look, \"active\"/\"error\" REPLACE `children` with built-in feedback (a filled\n * icon + a short line — \"Drag your files here\" / \"File format not accepted\", no icon at\n * `size=\"xs\"` same as everywhere else) — override via `activeContent`/`errorContent` if the\n * defaults don't fit; this is deliberate, not something to fight with children-based tricks.\n * - The drag-over \"error\" look is a file-type warning ONLY, and it does not block the drop —\n * the file still reaches `onFilesSelected`. File size can't be checked before drop (browsers\n * don't expose it until then), so \"file too large\" is always a file-list-level error — render\n * that file's Attachment with `state=\"error\"` and the reason in AttachmentDescription, not\n * as a Dropzone state.\n * - The dropzone renders as a `label` wrapping a visually-hidden `<input type=\"file\">`, so\n * clicking anywhere in it opens the OS file picker with zero extra JS\n * - `accept`/`multiple`/`disabled` map straight to the underlying input's attributes\n * - `id`/`aria-describedby`/`aria-invalid` land on the underlying `<input type=\"file\">`, not the\n * outer `<label>` — a label only passes its accessible *name* through, not these relationships —\n * so wire hint/error text (e.g. a rejection reason) through `aria-describedby`/`aria-invalid`\n * rather than assuming label-wrapping is enough. `id` falls back to a generated one if unset.\n * `inputRef` reaches the same underlying input if you need the ref itself.\n * - Idle-state content (icon, \"Drag files here or browse\", format/size hints, an optional sample-\n * file link) is fully custom via children — nothing is hardcoded, this component only owns the\n * interaction wiring, the idle-state look, and the active/error feedback swap above\n * - FileTypeIcon derives its color+icon from `file`'s MIME type (or pass `family` directly) — it's\n * an AttachmentMedia composition (`rounded-full` + a per-family tint), not a new component tree\n * - Attachment defaults to `w-fit` (sized to its own content, right for a horizontal chip strip) —\n * for a vertical file list where every row should line up at the same width, pass\n * `className=\"w-full\"` on each Attachment row\n * - AttachmentAction covers both retry and remove — render two of them (retry then remove) on an\n * error row, one (remove) otherwise\n *\n * USAGE\n * const [files, setFiles] = React.useState<Array<{ file: File; state: \"uploading\" | \"error\" | \"done\" }>>([])\n * <div className=\"grid gap-3\">\n * <Dropzone\n * accept=\"image/png,image/jpeg\"\n * onFilesSelected={(picked) =>\n * setFiles((prev) => [...prev, ...picked.map((file) => ({ file, state: \"uploading\" as const }))])\n * }\n * >\n * <UploadIcon className=\"text-muted-foreground\" />\n * <p className=\"text-sm font-medium\">Drag your files here or <span className=\"text-primary\">browse</span></p>\n * <p className=\"text-xs text-muted-foreground\">PNG or JPEG, up to 25MB</p>\n * </Dropzone>\n *\n * {files.map(({ file, state }) => (\n * <Attachment key={file.name} state={state} className=\"w-full\">\n * <FileTypeIcon file={file} />\n * <AttachmentContent>\n * <AttachmentTitle>{file.name}</AttachmentTitle>\n * <AttachmentDescription>\n * {state === \"error\" ? \"File exceeds the 25MB limit\" : `${(file.size / 1024).toFixed(1)} KB`}\n * </AttachmentDescription>\n * </AttachmentContent>\n * <AttachmentActions>\n * {state === \"error\" && (\n * <AttachmentAction aria-label={`Retry ${file.name}`} onClick={() => retry(file)}>\n * <RefreshCwIcon />\n * </AttachmentAction>\n * )}\n * <AttachmentAction\n * aria-label={`Remove ${file.name}`}\n * onClick={() => setFiles((prev) => prev.filter((f) => f.file !== file))}\n * >\n * <XIcon />\n * </AttachmentAction>\n * </AttachmentActions>\n * </Attachment>\n * ))}\n * </div>\n */\n"],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty — composed block for "nothing to show" states (no data, no results).
|
|
3
|
+
*
|
|
4
|
+
* Plain divs composed via data-slot attributes, with class-variance-authority
|
|
5
|
+
* (cva) on Empty's own `size` and on EmptyMedia's `variant`. Structure is
|
|
6
|
+
* Empty > EmptyHeader > EmptyMedia + EmptyTitle + EmptyDescription, plus a
|
|
7
|
+
* sibling EmptyContent.
|
|
8
|
+
*
|
|
9
|
+
* `size` is driven by the CONTAINER TYPE Empty renders inside, not measured
|
|
10
|
+
* space — see each option below and the doc comment's KEY RULES for the
|
|
11
|
+
* container-to-size mapping. Sub-components react to it via
|
|
12
|
+
* `group-data-[size=x]/empty`, the same prop-drilling-free pattern
|
|
13
|
+
* Attachment already uses for its own size variants, so EmptyHeader/
|
|
14
|
+
* EmptyMedia/etc. don't need `size` passed to each of them individually.
|
|
15
|
+
*/
|
|
16
|
+
import { type VariantProps } from "class-variance-authority";
|
|
17
|
+
declare const emptyVariants: (props?: ({
|
|
18
|
+
size?: "sm" | "default" | "xs" | null | undefined;
|
|
19
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
20
|
+
declare function Empty({ className, size, ...props }: React.ComponentProps<"div"> & VariantProps<typeof emptyVariants>): import("react").JSX.Element;
|
|
21
|
+
declare function EmptyHeader({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
|
|
22
|
+
declare const emptyMediaVariants: (props?: ({
|
|
23
|
+
variant?: "default" | "icon" | null | undefined;
|
|
24
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
25
|
+
declare function EmptyMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>): import("react").JSX.Element;
|
|
26
|
+
declare function EmptyTitle({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
|
|
27
|
+
declare function EmptyDescription({ className, ...props }: React.ComponentProps<"p">): import("react").JSX.Element;
|
|
28
|
+
declare function EmptyContent({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
|
|
29
|
+
export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia, };
|
|
30
|
+
/**
|
|
31
|
+
* WHEN TO USE
|
|
32
|
+
* - A list, table, or panel has nothing to show yet (e.g. "No messages").
|
|
33
|
+
* - A filtered or search view returns zero results — pair with an action to reset filters.
|
|
34
|
+
* - A load failed and there's no existing content on screen to preserve — a fetch
|
|
35
|
+
* error, a 404, a permission issue — with a retry/resolve action in EmptyContent.
|
|
36
|
+
* See "Patterns/UI States" for the full loading/empty/error catalog.
|
|
37
|
+
*
|
|
38
|
+
* NOT WHEN
|
|
39
|
+
* - A load failed but existing content is already on screen (e.g. a same-page
|
|
40
|
+
* action lost connectivity mid-request) — use Alert instead, so the content
|
|
41
|
+
* doesn't get torn down over what may be a transient failure.
|
|
42
|
+
* - The data is still loading — use Skeleton or Spinner instead.
|
|
43
|
+
*
|
|
44
|
+
* KEY RULES
|
|
45
|
+
* - EmptyMedia's `variant` is "default" (transparent, for arbitrary media like an avatar) or "icon" (muted rounded-lg badge sized for a single icon) — defaults to "default".
|
|
46
|
+
* - Structure is EmptyHeader (wraps EmptyMedia + EmptyTitle + EmptyDescription) followed by an optional sibling EmptyContent for actions.
|
|
47
|
+
* - EmptyDescription is a plain description slot, not required, but pairs with EmptyTitle for context on why the state is empty.
|
|
48
|
+
* - `size` picks the density by which CONTAINER TYPE Empty renders inside —
|
|
49
|
+
* not a measured/responsive breakpoint, a deliberate choice per context.
|
|
50
|
+
* Same "default" | "sm" | "xs" vocabulary as Button/Item/Attachment/Select,
|
|
51
|
+
* not component-specific naming:
|
|
52
|
+
* "default" full page, a large Dialog/Sheet — icon, title, description, action, all stacked and centered
|
|
53
|
+
* "sm" a Card, a medium Sheet/Popover — same anatomy as default, just tighter gaps/padding
|
|
54
|
+
* "xs" a small Popover, inline widget,
|
|
55
|
+
* table cell, toast-sized space — EmptyMedia and EmptyDescription both disappear; EmptyHeader
|
|
56
|
+
* and EmptyContent become a single row (title left, truncating;
|
|
57
|
+
* action right) instead of a stacked, centered block
|
|
58
|
+
* - The icon only ever appears at "default" and "sm" — "xs" has no room for
|
|
59
|
+
* one and drops straight to a single line of title text.
|
|
60
|
+
* - Set `size` once on Empty; EmptyHeader/EmptyMedia/EmptyTitle/EmptyDescription/
|
|
61
|
+
* EmptyContent all react to it automatically (`group-data-[size=x]/empty`) —
|
|
62
|
+
* don't pass size to each of them individually, it isn't a prop on those.
|
|
63
|
+
*
|
|
64
|
+
* USAGE
|
|
65
|
+
* <Empty>
|
|
66
|
+
* <EmptyHeader>
|
|
67
|
+
* <EmptyMedia variant="icon"><InboxIcon /></EmptyMedia>
|
|
68
|
+
* <EmptyTitle>No messages</EmptyTitle>
|
|
69
|
+
* <EmptyDescription>When someone sends you a message it will appear here.</EmptyDescription>
|
|
70
|
+
* </EmptyHeader>
|
|
71
|
+
* </Empty>
|
|
72
|
+
*
|
|
73
|
+
* <Empty>
|
|
74
|
+
* <EmptyHeader>
|
|
75
|
+
* <EmptyMedia variant="icon"><SearchIcon /></EmptyMedia>
|
|
76
|
+
* <EmptyTitle>No results found</EmptyTitle>
|
|
77
|
+
* <EmptyDescription>Try adjusting your filters or search for something else.</EmptyDescription>
|
|
78
|
+
* </EmptyHeader>
|
|
79
|
+
* <EmptyContent>
|
|
80
|
+
* <Button variant="outline">Clear filters</Button>
|
|
81
|
+
* </EmptyContent>
|
|
82
|
+
* </Empty>
|
|
83
|
+
*
|
|
84
|
+
* // Inside a small Popover/table cell — no icon, single truncating line
|
|
85
|
+
* <Empty size="xs">
|
|
86
|
+
* <EmptyHeader>
|
|
87
|
+
* <EmptyTitle>No results</EmptyTitle>
|
|
88
|
+
* </EmptyHeader>
|
|
89
|
+
* <EmptyContent>
|
|
90
|
+
* <Button variant="outline" size="xs">Clear</Button>
|
|
91
|
+
* </EmptyContent>
|
|
92
|
+
* </Empty>
|
|
93
|
+
*/
|
|
94
|
+
//# sourceMappingURL=empty.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty.d.ts","sourceRoot":"","sources":["../../../src/components/ui/empty.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,aAAa;;8EAclB,CAAA;AAED,iBAAS,KAAK,CAAC,EACb,SAAS,EACT,IAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,+BASlE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAaxE;AAED,QAAA,MAAM,kBAAkB;;8EAavB,CAAA;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,kBAAkB,CAAC,+BASvE;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAYvE;AAED,iBAAS,gBAAgB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,+BAY3E;AAED,iBAAS,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAazE;AAED,OAAO,EACL,KAAK,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,GACX,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
//#region src/components/ui/empty.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Empty — composed block for "nothing to show" states (no data, no results).
|
|
7
|
+
*
|
|
8
|
+
* Plain divs composed via data-slot attributes, with class-variance-authority
|
|
9
|
+
* (cva) on Empty's own `size` and on EmptyMedia's `variant`. Structure is
|
|
10
|
+
* Empty > EmptyHeader > EmptyMedia + EmptyTitle + EmptyDescription, plus a
|
|
11
|
+
* sibling EmptyContent.
|
|
12
|
+
*
|
|
13
|
+
* `size` is driven by the CONTAINER TYPE Empty renders inside, not measured
|
|
14
|
+
* space — see each option below and the doc comment's KEY RULES for the
|
|
15
|
+
* container-to-size mapping. Sub-components react to it via
|
|
16
|
+
* `group-data-[size=x]/empty`, the same prop-drilling-free pattern
|
|
17
|
+
* Attachment already uses for its own size variants, so EmptyHeader/
|
|
18
|
+
* EmptyMedia/etc. don't need `size` passed to each of them individually.
|
|
19
|
+
*/
|
|
20
|
+
var emptyVariants = cva("group/empty flex w-full min-w-0 flex-1 rounded-xl border-dashed text-balance", {
|
|
21
|
+
variants: { size: {
|
|
22
|
+
default: "flex-col items-center justify-center gap-4 p-6 text-center",
|
|
23
|
+
sm: "flex-col items-center justify-center gap-3 p-4 text-center",
|
|
24
|
+
xs: "flex-row items-center justify-between gap-2 p-2 text-left"
|
|
25
|
+
} },
|
|
26
|
+
defaultVariants: { size: "default" }
|
|
27
|
+
});
|
|
28
|
+
function Empty({ className, size = "default", ...props }) {
|
|
29
|
+
return /* @__PURE__ */ jsx("div", {
|
|
30
|
+
"data-slot": "empty",
|
|
31
|
+
"data-size": size,
|
|
32
|
+
className: cn(emptyVariants({ size }), className),
|
|
33
|
+
...props
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function EmptyHeader({ className, ...props }) {
|
|
37
|
+
return /* @__PURE__ */ jsx("div", {
|
|
38
|
+
"data-slot": "empty-header",
|
|
39
|
+
className: cn("flex max-w-sm min-w-0 flex-col items-center gap-2", "group-data-[size=sm]/empty:max-w-xs group-data-[size=sm]/empty:gap-1.5", "group-data-[size=xs]/empty:max-w-none group-data-[size=xs]/empty:min-w-0 group-data-[size=xs]/empty:flex-1 group-data-[size=xs]/empty:flex-row group-data-[size=xs]/empty:items-center group-data-[size=xs]/empty:gap-1.5", className),
|
|
40
|
+
...props
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
var emptyMediaVariants = cva("mb-2 flex shrink-0 items-center justify-center group-data-[size=xs]/empty:mb-0 group-data-[size=xs]/empty:hidden [&_svg]:pointer-events-none [&_svg]:shrink-0", {
|
|
44
|
+
variants: { variant: {
|
|
45
|
+
default: "bg-transparent",
|
|
46
|
+
icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4"
|
|
47
|
+
} },
|
|
48
|
+
defaultVariants: { variant: "default" }
|
|
49
|
+
});
|
|
50
|
+
function EmptyMedia({ className, variant = "default", ...props }) {
|
|
51
|
+
return /* @__PURE__ */ jsx("div", {
|
|
52
|
+
"data-slot": "empty-icon",
|
|
53
|
+
"data-variant": variant,
|
|
54
|
+
className: cn(emptyMediaVariants({
|
|
55
|
+
variant,
|
|
56
|
+
className
|
|
57
|
+
})),
|
|
58
|
+
...props
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function EmptyTitle({ className, ...props }) {
|
|
62
|
+
return /* @__PURE__ */ jsx("div", {
|
|
63
|
+
"data-slot": "empty-title",
|
|
64
|
+
className: cn("text-sm font-medium tracking-tight", "group-data-[size=xs]/empty:min-w-0 group-data-[size=xs]/empty:truncate group-data-[size=xs]/empty:text-xs", className),
|
|
65
|
+
...props
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function EmptyDescription({ className, ...props }) {
|
|
69
|
+
return /* @__PURE__ */ jsx("div", {
|
|
70
|
+
"data-slot": "empty-description",
|
|
71
|
+
className: cn("text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", "group-data-[size=xs]/empty:hidden", className),
|
|
72
|
+
...props
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function EmptyContent({ className, ...props }) {
|
|
76
|
+
return /* @__PURE__ */ jsx("div", {
|
|
77
|
+
"data-slot": "empty-content",
|
|
78
|
+
className: cn("flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance", "group-data-[size=sm]/empty:max-w-xs group-data-[size=sm]/empty:gap-2", "group-data-[size=xs]/empty:w-auto group-data-[size=xs]/empty:max-w-none group-data-[size=xs]/empty:shrink-0 group-data-[size=xs]/empty:flex-row group-data-[size=xs]/empty:gap-2", className),
|
|
79
|
+
...props
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* WHEN TO USE
|
|
84
|
+
* - A list, table, or panel has nothing to show yet (e.g. "No messages").
|
|
85
|
+
* - A filtered or search view returns zero results — pair with an action to reset filters.
|
|
86
|
+
* - A load failed and there's no existing content on screen to preserve — a fetch
|
|
87
|
+
* error, a 404, a permission issue — with a retry/resolve action in EmptyContent.
|
|
88
|
+
* See "Patterns/UI States" for the full loading/empty/error catalog.
|
|
89
|
+
*
|
|
90
|
+
* NOT WHEN
|
|
91
|
+
* - A load failed but existing content is already on screen (e.g. a same-page
|
|
92
|
+
* action lost connectivity mid-request) — use Alert instead, so the content
|
|
93
|
+
* doesn't get torn down over what may be a transient failure.
|
|
94
|
+
* - The data is still loading — use Skeleton or Spinner instead.
|
|
95
|
+
*
|
|
96
|
+
* KEY RULES
|
|
97
|
+
* - EmptyMedia's `variant` is "default" (transparent, for arbitrary media like an avatar) or "icon" (muted rounded-lg badge sized for a single icon) — defaults to "default".
|
|
98
|
+
* - Structure is EmptyHeader (wraps EmptyMedia + EmptyTitle + EmptyDescription) followed by an optional sibling EmptyContent for actions.
|
|
99
|
+
* - EmptyDescription is a plain description slot, not required, but pairs with EmptyTitle for context on why the state is empty.
|
|
100
|
+
* - `size` picks the density by which CONTAINER TYPE Empty renders inside —
|
|
101
|
+
* not a measured/responsive breakpoint, a deliberate choice per context.
|
|
102
|
+
* Same "default" | "sm" | "xs" vocabulary as Button/Item/Attachment/Select,
|
|
103
|
+
* not component-specific naming:
|
|
104
|
+
* "default" full page, a large Dialog/Sheet — icon, title, description, action, all stacked and centered
|
|
105
|
+
* "sm" a Card, a medium Sheet/Popover — same anatomy as default, just tighter gaps/padding
|
|
106
|
+
* "xs" a small Popover, inline widget,
|
|
107
|
+
* table cell, toast-sized space — EmptyMedia and EmptyDescription both disappear; EmptyHeader
|
|
108
|
+
* and EmptyContent become a single row (title left, truncating;
|
|
109
|
+
* action right) instead of a stacked, centered block
|
|
110
|
+
* - The icon only ever appears at "default" and "sm" — "xs" has no room for
|
|
111
|
+
* one and drops straight to a single line of title text.
|
|
112
|
+
* - Set `size` once on Empty; EmptyHeader/EmptyMedia/EmptyTitle/EmptyDescription/
|
|
113
|
+
* EmptyContent all react to it automatically (`group-data-[size=x]/empty`) —
|
|
114
|
+
* don't pass size to each of them individually, it isn't a prop on those.
|
|
115
|
+
*
|
|
116
|
+
* USAGE
|
|
117
|
+
* <Empty>
|
|
118
|
+
* <EmptyHeader>
|
|
119
|
+
* <EmptyMedia variant="icon"><InboxIcon /></EmptyMedia>
|
|
120
|
+
* <EmptyTitle>No messages</EmptyTitle>
|
|
121
|
+
* <EmptyDescription>When someone sends you a message it will appear here.</EmptyDescription>
|
|
122
|
+
* </EmptyHeader>
|
|
123
|
+
* </Empty>
|
|
124
|
+
*
|
|
125
|
+
* <Empty>
|
|
126
|
+
* <EmptyHeader>
|
|
127
|
+
* <EmptyMedia variant="icon"><SearchIcon /></EmptyMedia>
|
|
128
|
+
* <EmptyTitle>No results found</EmptyTitle>
|
|
129
|
+
* <EmptyDescription>Try adjusting your filters or search for something else.</EmptyDescription>
|
|
130
|
+
* </EmptyHeader>
|
|
131
|
+
* <EmptyContent>
|
|
132
|
+
* <Button variant="outline">Clear filters</Button>
|
|
133
|
+
* </EmptyContent>
|
|
134
|
+
* </Empty>
|
|
135
|
+
*
|
|
136
|
+
* // Inside a small Popover/table cell — no icon, single truncating line
|
|
137
|
+
* <Empty size="xs">
|
|
138
|
+
* <EmptyHeader>
|
|
139
|
+
* <EmptyTitle>No results</EmptyTitle>
|
|
140
|
+
* </EmptyHeader>
|
|
141
|
+
* <EmptyContent>
|
|
142
|
+
* <Button variant="outline" size="xs">Clear</Button>
|
|
143
|
+
* </EmptyContent>
|
|
144
|
+
* </Empty>
|
|
145
|
+
*/
|
|
146
|
+
//#endregion
|
|
147
|
+
export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=empty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty.js","names":[],"sources":["../../../src/components/ui/empty.tsx"],"sourcesContent":["/**\n * Empty — composed block for \"nothing to show\" states (no data, no results).\n *\n * Plain divs composed via data-slot attributes, with class-variance-authority\n * (cva) on Empty's own `size` and on EmptyMedia's `variant`. Structure is\n * Empty > EmptyHeader > EmptyMedia + EmptyTitle + EmptyDescription, plus a\n * sibling EmptyContent.\n *\n * `size` is driven by the CONTAINER TYPE Empty renders inside, not measured\n * space — see each option below and the doc comment's KEY RULES for the\n * container-to-size mapping. Sub-components react to it via\n * `group-data-[size=x]/empty`, the same prop-drilling-free pattern\n * Attachment already uses for its own size variants, so EmptyHeader/\n * EmptyMedia/etc. don't need `size` passed to each of them individually.\n */\n\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst emptyVariants = cva(\n \"group/empty flex w-full min-w-0 flex-1 rounded-xl border-dashed text-balance\",\n {\n variants: {\n size: {\n default: \"flex-col items-center justify-center gap-4 p-6 text-center\",\n sm: \"flex-col items-center justify-center gap-3 p-4 text-center\",\n xs: \"flex-row items-center justify-between gap-2 p-2 text-left\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\nfunction Empty({\n className,\n size = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyVariants>) {\n return (\n <div\n data-slot=\"empty\"\n data-size={size}\n className={cn(emptyVariants({ size }), className)}\n {...props}\n />\n )\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-header\"\n className={cn(\n \"flex max-w-sm min-w-0 flex-col items-center gap-2\",\n \"group-data-[size=sm]/empty:max-w-xs group-data-[size=sm]/empty:gap-1.5\",\n \"group-data-[size=xs]/empty:max-w-none group-data-[size=xs]/empty:min-w-0 group-data-[size=xs]/empty:flex-1 group-data-[size=xs]/empty:flex-row group-data-[size=xs]/empty:items-center group-data-[size=xs]/empty:gap-1.5\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst emptyMediaVariants = cva(\n \"mb-2 flex shrink-0 items-center justify-center group-data-[size=xs]/empty:mb-0 group-data-[size=xs]/empty:hidden [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n icon: \"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction EmptyMedia({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n return (\n <div\n data-slot=\"empty-icon\"\n data-variant={variant}\n className={cn(emptyMediaVariants({ variant, className }))}\n {...props}\n />\n )\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-title\"\n className={cn(\n \"text-sm font-medium tracking-tight\",\n \"group-data-[size=xs]/empty:min-w-0 group-data-[size=xs]/empty:truncate group-data-[size=xs]/empty:text-xs\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <div\n data-slot=\"empty-description\"\n className={cn(\n \"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n \"group-data-[size=xs]/empty:hidden\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-content\"\n className={cn(\n \"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance\",\n \"group-data-[size=sm]/empty:max-w-xs group-data-[size=sm]/empty:gap-2\",\n \"group-data-[size=xs]/empty:w-auto group-data-[size=xs]/empty:max-w-none group-data-[size=xs]/empty:shrink-0 group-data-[size=xs]/empty:flex-row group-data-[size=xs]/empty:gap-2\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n Empty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n}\n\n/**\n * WHEN TO USE\n * - A list, table, or panel has nothing to show yet (e.g. \"No messages\").\n * - A filtered or search view returns zero results — pair with an action to reset filters.\n * - A load failed and there's no existing content on screen to preserve — a fetch\n * error, a 404, a permission issue — with a retry/resolve action in EmptyContent.\n * See \"Patterns/UI States\" for the full loading/empty/error catalog.\n *\n * NOT WHEN\n * - A load failed but existing content is already on screen (e.g. a same-page\n * action lost connectivity mid-request) — use Alert instead, so the content\n * doesn't get torn down over what may be a transient failure.\n * - The data is still loading — use Skeleton or Spinner instead.\n *\n * KEY RULES\n * - EmptyMedia's `variant` is \"default\" (transparent, for arbitrary media like an avatar) or \"icon\" (muted rounded-lg badge sized for a single icon) — defaults to \"default\".\n * - Structure is EmptyHeader (wraps EmptyMedia + EmptyTitle + EmptyDescription) followed by an optional sibling EmptyContent for actions.\n * - EmptyDescription is a plain description slot, not required, but pairs with EmptyTitle for context on why the state is empty.\n * - `size` picks the density by which CONTAINER TYPE Empty renders inside —\n * not a measured/responsive breakpoint, a deliberate choice per context.\n * Same \"default\" | \"sm\" | \"xs\" vocabulary as Button/Item/Attachment/Select,\n * not component-specific naming:\n * \"default\" full page, a large Dialog/Sheet — icon, title, description, action, all stacked and centered\n * \"sm\" a Card, a medium Sheet/Popover — same anatomy as default, just tighter gaps/padding\n * \"xs\" a small Popover, inline widget,\n * table cell, toast-sized space — EmptyMedia and EmptyDescription both disappear; EmptyHeader\n * and EmptyContent become a single row (title left, truncating;\n * action right) instead of a stacked, centered block\n * - The icon only ever appears at \"default\" and \"sm\" — \"xs\" has no room for\n * one and drops straight to a single line of title text.\n * - Set `size` once on Empty; EmptyHeader/EmptyMedia/EmptyTitle/EmptyDescription/\n * EmptyContent all react to it automatically (`group-data-[size=x]/empty`) —\n * don't pass size to each of them individually, it isn't a prop on those.\n *\n * USAGE\n * <Empty>\n * <EmptyHeader>\n * <EmptyMedia variant=\"icon\"><InboxIcon /></EmptyMedia>\n * <EmptyTitle>No messages</EmptyTitle>\n * <EmptyDescription>When someone sends you a message it will appear here.</EmptyDescription>\n * </EmptyHeader>\n * </Empty>\n *\n * <Empty>\n * <EmptyHeader>\n * <EmptyMedia variant=\"icon\"><SearchIcon /></EmptyMedia>\n * <EmptyTitle>No results found</EmptyTitle>\n * <EmptyDescription>Try adjusting your filters or search for something else.</EmptyDescription>\n * </EmptyHeader>\n * <EmptyContent>\n * <Button variant=\"outline\">Clear filters</Button>\n * </EmptyContent>\n * </Empty>\n *\n * // Inside a small Popover/table cell — no icon, single truncating line\n * <Empty size=\"xs\">\n * <EmptyHeader>\n * <EmptyTitle>No results</EmptyTitle>\n * </EmptyHeader>\n * <EmptyContent>\n * <Button variant=\"outline\" size=\"xs\">Clear</Button>\n * </EmptyContent>\n * </Empty>\n */\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,IAAM,gBAAgB,IACpB,gFACA;CACE,UAAU,EACR,MAAM;EACJ,SAAS;EACT,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EACf,MAAM,UACR;AACF,CACF;AAEA,SAAS,MAAM,EACb,WACA,OAAO,WACP,GAAG,SACgE;CACnE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,SAAS;EAChD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAsC;CACzE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,qDACA,0EACA,6NACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAM,qBAAqB,IACzB,iKACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,MAAM;CACR,EACF;CACA,iBAAiB,EACf,SAAS,UACX;AACF,CACF;AAEA,SAAS,WAAW,EAClB,WACA,UAAU,WACV,GAAG,SACqE;CACxE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,gBAAc;EACd,WAAW,GAAG,mBAAmB;GAAE;GAAS;EAAU,CAAC,CAAC;EACxD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAsC;CACxE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,sCACA,6GACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,SAAoC;CAC5E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,2GACA,qCACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,mFACA,wEACA,oLACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field — form field wrapper (label + control + description + error).
|
|
3
|
+
*
|
|
4
|
+
* A composite family, not a single component: Field lays out one control
|
|
5
|
+
* with its label/description/error slots; FieldSet+FieldLegend group
|
|
6
|
+
* several Fields for assistive tech; FieldGroup stacks Fields together;
|
|
7
|
+
* FieldSeparator divides sections. Invalid state is driven by
|
|
8
|
+
* `data-invalid` on Field plus `aria-invalid` on the control.
|
|
9
|
+
*/
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import { type VariantProps } from "class-variance-authority";
|
|
12
|
+
import { Label } from "../../components/ui/label";
|
|
13
|
+
declare const FieldIdContext: React.Context<{
|
|
14
|
+
titleId: string | null;
|
|
15
|
+
setTitleId: (id: string | null) => void;
|
|
16
|
+
descriptionId: string | null;
|
|
17
|
+
setDescriptionId: (id: string | null) => void;
|
|
18
|
+
errorId: string | null;
|
|
19
|
+
setErrorId: (id: string | null) => void;
|
|
20
|
+
} | null>;
|
|
21
|
+
declare function FieldSet({ className, "aria-labelledby": ariaLabelledby, ...props }: React.ComponentProps<"fieldset">): React.JSX.Element;
|
|
22
|
+
declare function FieldLegend({ className, id, variant, required, optional, children, aiLabel, aiLabelAlign, ...props }: React.ComponentProps<"legend"> & {
|
|
23
|
+
variant?: "legend" | "label";
|
|
24
|
+
/** Red dot after the legend — the group (checkboxes, radios) is mandatory. Visual only: put `required`/`aria-required` on the group's control(s) too. */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
/** Muted "(optional)" after the legend. Mutually exclusive with `required`. */
|
|
27
|
+
optional?: boolean;
|
|
28
|
+
/** An `AILabel` rendered in the same fixed-height row as the legend — a sibling, never nested inside `<legend>` (AILabelTrigger is a button; a button inside a legend/label is invalid HTML). */
|
|
29
|
+
aiLabel?: React.ReactNode;
|
|
30
|
+
/** "inline" (default) places `aiLabel` immediately after the legend text — the usual placement for a per-group AI marker (Checkbox/RadioGroup's own AI-flavored stories). "end" pushes it to the row's far end instead. */
|
|
31
|
+
aiLabelAlign?: "inline" | "end";
|
|
32
|
+
}): React.JSX.Element;
|
|
33
|
+
declare function FieldGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
34
|
+
declare const fieldVariants: (props?: ({
|
|
35
|
+
orientation?: "horizontal" | "vertical" | "responsive" | null | undefined;
|
|
36
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
37
|
+
declare function Field({ className, orientation, ...props }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>): React.JSX.Element;
|
|
38
|
+
declare function FieldContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
39
|
+
declare function FieldLabel({ className, aiLabel, aiLabelAlign, ...props }: React.ComponentProps<typeof Label> & {
|
|
40
|
+
/** An `AILabel` rendered in the same row as the label — a sibling, never
|
|
41
|
+
* nested inside `<label>` (AILabelTrigger is a button; a button inside a
|
|
42
|
+
* label is invalid HTML). Passing this switches FieldLabel to a fixed
|
|
43
|
+
* `h-6` (24px) row, so a grid mixing AI and non-AI fields still aligns —
|
|
44
|
+
* plain FieldLabel (no `aiLabel`) keeps its normal auto height, since
|
|
45
|
+
* forcing every label to a fixed row would also force the unrelated
|
|
46
|
+
* "FieldLabel wrapping a nested Field" option-card pattern (see
|
|
47
|
+
* `has-[>[data-slot=field]]` below) into that same fixed height, which
|
|
48
|
+
* needs to stay auto/flex-col instead. */
|
|
49
|
+
aiLabel?: React.ReactNode;
|
|
50
|
+
/** "end" (default) pushes `aiLabel` to the row's far end — Input/Textarea/Select's own AI-flavored stories. "inline" places it immediately after the label text instead. */
|
|
51
|
+
aiLabelAlign?: "inline" | "end";
|
|
52
|
+
}): React.JSX.Element;
|
|
53
|
+
declare function FieldTitle({ className, id, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
54
|
+
declare function FieldDescription({ className, id, ...props }: React.ComponentProps<"p">): React.JSX.Element;
|
|
55
|
+
declare function FieldSeparator({ children, className, ...props }: React.ComponentProps<"div"> & {
|
|
56
|
+
children?: React.ReactNode;
|
|
57
|
+
}): React.JSX.Element;
|
|
58
|
+
declare function FieldError({ className, children, errors, id, ...props }: React.ComponentProps<"div"> & {
|
|
59
|
+
errors?: Array<{
|
|
60
|
+
message?: string;
|
|
61
|
+
} | undefined>;
|
|
62
|
+
}): React.JSX.Element | null;
|
|
63
|
+
export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle, FieldIdContext, };
|
|
64
|
+
/**
|
|
65
|
+
* WHEN TO USE
|
|
66
|
+
* - Any labelled form field that needs help text or validation messaging — wraps any input primitive (Input, Textarea, Checkbox, ...)
|
|
67
|
+
* - `orientation="horizontal"` for label-beside-control rows (e.g. a Checkbox with title/description via FieldContent + FieldTitle)
|
|
68
|
+
* - FieldSet + FieldLegend to group related Fields for assistive tech; FieldGroup to stack them with consistent spacing
|
|
69
|
+
* - FieldSeparator to divide sections within a FieldGroup, optionally with a label (e.g. "OR")
|
|
70
|
+
*
|
|
71
|
+
* NOT WHEN
|
|
72
|
+
* - A bare label with no description or error text — use Label directly, it's lighter
|
|
73
|
+
* - Display-only key/value pairs with no input — this is a form-control wrapper, not a data display
|
|
74
|
+
*
|
|
75
|
+
* KEY RULES
|
|
76
|
+
* - orientation: vertical (default), horizontal, responsive (stacks on narrow, horizontal at @md container width)
|
|
77
|
+
* - Required/optional are marked on the label, nowhere else: `<FieldLabel required>` (red dot) or `<FieldLabel optional>` (muted "(optional)"), and the same two props on FieldLegend for a whole group. Visual only — the control still carries `required`/`aria-required` for assistive tech. Pick one convention per form: mark the required fields or the optional ones, not both.
|
|
78
|
+
* - Mark invalid state with `data-invalid` on Field AND `aria-invalid` on the control — Field's red text styling depends on `data-invalid`, the control's border/ring styling depends on `aria-invalid`
|
|
79
|
+
* - FieldError accepts either `children` (a fixed message) or an `errors` array of `{ message }` objects (react-hook-form shape) — duplicates are deduped by message, renders a single line for one error or a bulleted list for multiple; renders nothing if there's no content
|
|
80
|
+
* - FieldDescription/FieldError generate an id and register it on Field's context; Checkbox reads that context and defaults its own `aria-describedby`/`aria-labelledby` (to FieldTitle's id) whenever the consumer hasn't already set one — for every other control, still give each element an explicit `id` and set the control's `aria-describedby` to match (see USAGE below), or a screen reader user who tabs to the control never hears the hint/error
|
|
81
|
+
* - FieldLabel wraps Label — use it (not FieldTitle) when the row has an actual form control; use FieldTitle for the horizontal FieldContent pattern where the "label" is really a heading beside a standalone control. FieldTitle is a plain heading-styled `<div>`, not a `<label>` — for controls other than Checkbox, give the control its own `aria-label`/`aria-labelledby` matching FieldTitle's text (see USAGE below) or it ships with no accessible name at all.
|
|
82
|
+
*
|
|
83
|
+
* USAGE
|
|
84
|
+
* <Field>
|
|
85
|
+
* <FieldLabel htmlFor="email">Email</FieldLabel>
|
|
86
|
+
* <Input id="email" type="email" aria-describedby="email-hint" />
|
|
87
|
+
* <FieldDescription id="email-hint">We'll never share your address.</FieldDescription>
|
|
88
|
+
* </Field>
|
|
89
|
+
*
|
|
90
|
+
* // With validation — aria-describedby lists both ids so an invalid field
|
|
91
|
+
* // still announces its hint alongside the error, not just one or the other
|
|
92
|
+
* <Field data-invalid={!!error}>
|
|
93
|
+
* <FieldLabel htmlFor="email">Email</FieldLabel>
|
|
94
|
+
* <Input id="email" type="email" aria-invalid={!!error} aria-describedby="email-hint email-error" />
|
|
95
|
+
* <FieldDescription id="email-hint">We'll never share your address.</FieldDescription>
|
|
96
|
+
* <FieldError id="email-error">{error}</FieldError>
|
|
97
|
+
* </Field>
|
|
98
|
+
*
|
|
99
|
+
* <Field data-invalid orientation="horizontal">
|
|
100
|
+
* <FieldContent>
|
|
101
|
+
* <FieldTitle>Marketing emails</FieldTitle>
|
|
102
|
+
* <FieldDescription>Receive product news and updates.</FieldDescription>
|
|
103
|
+
* </FieldContent>
|
|
104
|
+
* <Checkbox />
|
|
105
|
+
* </Field>
|
|
106
|
+
* // Checkbox picks up FieldTitle as its aria-labelledby and
|
|
107
|
+
* // FieldDescription/FieldError as its aria-describedby automatically —
|
|
108
|
+
* // pass an explicit aria-label/aria-labelledby/aria-describedby yourself
|
|
109
|
+
* // only to override that default. Any other control in this position
|
|
110
|
+
* // (there's no `<label>` wrapping it) still needs its own aria-label/
|
|
111
|
+
* // aria-labelledby matching FieldTitle's text.
|
|
112
|
+
*
|
|
113
|
+
* <FieldSet>
|
|
114
|
+
* <FieldLegend>Project details</FieldLegend>
|
|
115
|
+
* <FieldGroup>
|
|
116
|
+
* <Field>...</Field>
|
|
117
|
+
* <FieldSeparator />
|
|
118
|
+
* <Field>...</Field>
|
|
119
|
+
* </FieldGroup>
|
|
120
|
+
* </FieldSet>
|
|
121
|
+
*/
|
|
122
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/components/ui/field.tsx"],"names":[],"mappings":"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"}
|