@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,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import "react";
|
|
4
|
+
import { Tabs as Tabs$1 } from "radix-ui";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
//#region src/components/ui/tabs.tsx
|
|
8
|
+
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
9
|
+
return /* @__PURE__ */ jsx(Tabs$1.Root, {
|
|
10
|
+
"data-slot": "tabs",
|
|
11
|
+
"data-orientation": orientation,
|
|
12
|
+
className: cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className),
|
|
13
|
+
...props
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
var tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none", {
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
default: "bg-secondary",
|
|
20
|
+
line: "gap-1 bg-transparent"
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default: "group-data-[orientation=horizontal]/tabs:h-8",
|
|
24
|
+
sm: "group-data-[orientation=horizontal]/tabs:h-7"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: "default",
|
|
29
|
+
size: "default"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
function TabsList({ className, variant = "default", size = "default", ...props }) {
|
|
33
|
+
return /* @__PURE__ */ jsx(Tabs$1.List, {
|
|
34
|
+
"data-slot": "tabs-list",
|
|
35
|
+
"data-variant": variant,
|
|
36
|
+
"data-size": size,
|
|
37
|
+
className: cn(tabsListVariants({
|
|
38
|
+
variant,
|
|
39
|
+
size
|
|
40
|
+
}), className),
|
|
41
|
+
...props
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function TabsTrigger({ className, ...props }) {
|
|
45
|
+
return /* @__PURE__ */ jsx(Tabs$1.Trigger, {
|
|
46
|
+
"data-slot": "tabs-trigger",
|
|
47
|
+
className: cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[size=sm]/tabs-list:gap-1 group-data-[size=sm]/tabs-list:px-1 group-data-[size=sm]/tabs-list:text-xs", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:text-primary dark:group-data-[variant=line]/tabs-list:data-[state=active]:hover:text-primary-hover", "group-data-[variant=line]/tabs-list:hover:after:bg-muted-foreground group-data-[variant=line]/tabs-list:hover:after:opacity-100 group-data-[variant=line]/tabs-list:data-[state=active]:hover:after:bg-primary-hover dark:group-data-[variant=line]/tabs-list:data-[state=active]:hover:after:bg-primary-hover", "group-data-[variant=default]/tabs-list:hover:bg-muted group-data-[variant=default]/tabs-list:hover:text-foreground dark:group-data-[variant=default]/tabs-list:hover:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:hover:bg-primary/10 group-data-[variant=default]/tabs-list:data-[state=active]:hover:text-primary-hover", "data-[state=active]:bg-primary/10 data-[state=active]:text-primary data-[state=active]:hover:text-primary-hover dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", "after:absolute after:bg-primary after:opacity-0 after:transition group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className),
|
|
48
|
+
...props
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function TabsContent({ className, ...props }) {
|
|
52
|
+
return /* @__PURE__ */ jsx(Tabs$1.Content, {
|
|
53
|
+
"data-slot": "tabs-content",
|
|
54
|
+
className: cn("flex-1 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring", className),
|
|
55
|
+
...props
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* WHEN TO USE
|
|
60
|
+
* - Switching between 2–6 named views of the same entity (e.g. a record's
|
|
61
|
+
* Overview / History / Notes)
|
|
62
|
+
* - orientation="vertical" for a side-by-side settings-style layout
|
|
63
|
+
*
|
|
64
|
+
* NOT WHEN
|
|
65
|
+
* - Top-level app navigation — use NavigationMenu
|
|
66
|
+
* - An ordered sequence where each step has its own pending/done state —
|
|
67
|
+
* use Stepper, which is Tabs plus a step-circle icon
|
|
68
|
+
* - A one-question-at-a-time form flow with its own Previous/Next/Submit —
|
|
69
|
+
* use Questionnaire
|
|
70
|
+
* - Showing hierarchical position — use Breadcrumb
|
|
71
|
+
*
|
|
72
|
+
* KEY RULES
|
|
73
|
+
* - TabsList variant: "default" (transparent track, default) | "line"
|
|
74
|
+
* (underline, also transparent) — set once on TabsList, not per trigger
|
|
75
|
+
* - Both variants read the active tab as primary, not neutral: "default" gets a
|
|
76
|
+
* subtle bg-primary/10 fill + text-primary (translucent, so it blends on any
|
|
77
|
+
* surface with no per-container overrides needed); "line" gets text-primary
|
|
78
|
+
* plus a bg-primary underline, no fill (there's no track to fill)
|
|
79
|
+
* - Hover, inactive tab: "default" gets a bg-muted fill + darker text; "line"
|
|
80
|
+
* doesn't change text at all — it reveals a bg-border underline instead,
|
|
81
|
+
* a plain hover affordance, not a color change
|
|
82
|
+
* - Hover, active tab: "default"'s fill doesn't change, only its text darkens
|
|
83
|
+
* to text-primary-hover; "line" darkens BOTH its text and its underline to
|
|
84
|
+
* text-primary-hover/bg-primary-hover — hovering the current tab still
|
|
85
|
+
* reads as "this one, more so," not a neutral hover state
|
|
86
|
+
* - A leading icon on a trigger uses the same `data-icon="inline-start"` slot
|
|
87
|
+
* convention as Button/Toggle/SelectTrigger — no separate icon prop; the
|
|
88
|
+
* trigger's own padding adjusts automatically once that attribute is present
|
|
89
|
+
* - Individual TabsTrigger can be `disabled`
|
|
90
|
+
* - orientation defaults to "horizontal"; vertical stacks TabsList in a column
|
|
91
|
+
* - TabsList size: "default" (h-8 track, text-sm triggers) | "sm" (h-7,
|
|
92
|
+
* text-xs, tighter gap/padding) — same default/sm vocabulary as Button/
|
|
93
|
+
* Item/Attachment/Select, not Tabs-specific naming. Set once on TabsList;
|
|
94
|
+
* TabsTrigger reacts to it automatically.
|
|
95
|
+
* - A count next to a label (e.g. "Inbox 12") is a plain `Badge` child, same
|
|
96
|
+
* `gap-1.5` slot the leading-icon convention above already uses — no
|
|
97
|
+
* dedicated count prop. Badge's own static color reads fine against both
|
|
98
|
+
* the active and inactive tab state as-is.
|
|
99
|
+
*
|
|
100
|
+
* USAGE
|
|
101
|
+
* <Tabs defaultValue="overview">
|
|
102
|
+
* <TabsList>
|
|
103
|
+
* <TabsTrigger value="overview">Overview</TabsTrigger>
|
|
104
|
+
* <TabsTrigger value="history">History</TabsTrigger>
|
|
105
|
+
* </TabsList>
|
|
106
|
+
* <TabsContent value="overview">...</TabsContent>
|
|
107
|
+
* <TabsContent value="history">...</TabsContent>
|
|
108
|
+
* </Tabs>
|
|
109
|
+
*
|
|
110
|
+
* <TabsList size="sm">
|
|
111
|
+
* <TabsTrigger value="a">A</TabsTrigger>
|
|
112
|
+
* <TabsTrigger value="b">B</TabsTrigger>
|
|
113
|
+
* </TabsList>
|
|
114
|
+
*
|
|
115
|
+
* <TabsList variant="line">
|
|
116
|
+
* <TabsTrigger value="a">A</TabsTrigger>
|
|
117
|
+
* <TabsTrigger value="b" disabled>B</TabsTrigger>
|
|
118
|
+
* </TabsList>
|
|
119
|
+
*
|
|
120
|
+
* <TabsTrigger value="overview">
|
|
121
|
+
* <LayoutGridIcon data-icon="inline-start" />
|
|
122
|
+
* Overview
|
|
123
|
+
* </TabsTrigger>
|
|
124
|
+
*/
|
|
125
|
+
//#endregion
|
|
126
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","names":[],"sources":["../../../src/components/ui/tabs.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * Tabs — switches between panels of the same content area.\n *\n * Radix Tabs primitive with two TabsList visual variants: \"default\" (pill\n * background, active tab gets a raised background) and \"line\" (transparent\n * background, active tab gets an underline).\n */\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Tabs as TabsPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Tabs({\n className,\n orientation = \"horizontal\",\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n return (\n <TabsPrimitive.Root\n data-slot=\"tabs\"\n data-orientation={orientation}\n className={cn(\n \"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst tabsListVariants = cva(\n \"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none\",\n {\n variants: {\n variant: {\n default: \"bg-secondary\",\n line: \"gap-1 bg-transparent\",\n },\n size: {\n default: \"group-data-[orientation=horizontal]/tabs:h-8\",\n sm: \"group-data-[orientation=horizontal]/tabs:h-7\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction TabsList({\n className,\n variant = \"default\",\n size = \"default\",\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.List> &\n VariantProps<typeof tabsListVariants>) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n data-variant={variant}\n data-size={size}\n className={cn(tabsListVariants({ variant, size }), className)}\n {...props}\n />\n )\n}\n\nfunction TabsTrigger({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n return (\n <TabsPrimitive.Trigger\n data-slot=\"tabs-trigger\"\n className={cn(\n \"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n \"group-data-[size=sm]/tabs-list:gap-1 group-data-[size=sm]/tabs-list:px-1 group-data-[size=sm]/tabs-list:text-xs\",\n \"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:text-primary dark:group-data-[variant=line]/tabs-list:data-[state=active]:hover:text-primary-hover\",\n \"group-data-[variant=line]/tabs-list:hover:after:bg-muted-foreground group-data-[variant=line]/tabs-list:hover:after:opacity-100 group-data-[variant=line]/tabs-list:data-[state=active]:hover:after:bg-primary-hover dark:group-data-[variant=line]/tabs-list:data-[state=active]:hover:after:bg-primary-hover\",\n \"group-data-[variant=default]/tabs-list:hover:bg-muted group-data-[variant=default]/tabs-list:hover:text-foreground dark:group-data-[variant=default]/tabs-list:hover:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:hover:bg-primary/10 group-data-[variant=default]/tabs-list:data-[state=active]:hover:text-primary-hover\",\n \"data-[state=active]:bg-primary/10 data-[state=active]:text-primary data-[state=active]:hover:text-primary-hover dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground\",\n \"after:absolute after:bg-primary after:opacity-0 after:transition group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction TabsContent({\n className,\n ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n return (\n <TabsPrimitive.Content\n data-slot=\"tabs-content\"\n className={cn(\n \"flex-1 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }\n\n/**\n * WHEN TO USE\n * - Switching between 2–6 named views of the same entity (e.g. a record's\n * Overview / History / Notes)\n * - orientation=\"vertical\" for a side-by-side settings-style layout\n *\n * NOT WHEN\n * - Top-level app navigation — use NavigationMenu\n * - An ordered sequence where each step has its own pending/done state —\n * use Stepper, which is Tabs plus a step-circle icon\n * - A one-question-at-a-time form flow with its own Previous/Next/Submit —\n * use Questionnaire\n * - Showing hierarchical position — use Breadcrumb\n *\n * KEY RULES\n * - TabsList variant: \"default\" (transparent track, default) | \"line\"\n * (underline, also transparent) — set once on TabsList, not per trigger\n * - Both variants read the active tab as primary, not neutral: \"default\" gets a\n * subtle bg-primary/10 fill + text-primary (translucent, so it blends on any\n * surface with no per-container overrides needed); \"line\" gets text-primary\n * plus a bg-primary underline, no fill (there's no track to fill)\n * - Hover, inactive tab: \"default\" gets a bg-muted fill + darker text; \"line\"\n * doesn't change text at all — it reveals a bg-border underline instead,\n * a plain hover affordance, not a color change\n * - Hover, active tab: \"default\"'s fill doesn't change, only its text darkens\n * to text-primary-hover; \"line\" darkens BOTH its text and its underline to\n * text-primary-hover/bg-primary-hover — hovering the current tab still\n * reads as \"this one, more so,\" not a neutral hover state\n * - A leading icon on a trigger uses the same `data-icon=\"inline-start\"` slot\n * convention as Button/Toggle/SelectTrigger — no separate icon prop; the\n * trigger's own padding adjusts automatically once that attribute is present\n * - Individual TabsTrigger can be `disabled`\n * - orientation defaults to \"horizontal\"; vertical stacks TabsList in a column\n * - TabsList size: \"default\" (h-8 track, text-sm triggers) | \"sm\" (h-7,\n * text-xs, tighter gap/padding) — same default/sm vocabulary as Button/\n * Item/Attachment/Select, not Tabs-specific naming. Set once on TabsList;\n * TabsTrigger reacts to it automatically.\n * - A count next to a label (e.g. \"Inbox 12\") is a plain `Badge` child, same\n * `gap-1.5` slot the leading-icon convention above already uses — no\n * dedicated count prop. Badge's own static color reads fine against both\n * the active and inactive tab state as-is.\n *\n * USAGE\n * <Tabs defaultValue=\"overview\">\n * <TabsList>\n * <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n * <TabsTrigger value=\"history\">History</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"overview\">...</TabsContent>\n * <TabsContent value=\"history\">...</TabsContent>\n * </Tabs>\n *\n * <TabsList size=\"sm\">\n * <TabsTrigger value=\"a\">A</TabsTrigger>\n * <TabsTrigger value=\"b\">B</TabsTrigger>\n * </TabsList>\n *\n * <TabsList variant=\"line\">\n * <TabsTrigger value=\"a\">A</TabsTrigger>\n * <TabsTrigger value=\"b\" disabled>B</TabsTrigger>\n * </TabsList>\n *\n * <TabsTrigger value=\"overview\">\n * <LayoutGridIcon data-icon=\"inline-start\" />\n * Overview\n * </TabsTrigger>\n */\n"],"mappings":";;;;;;;AAgBA,SAAS,KAAK,EACZ,WACA,cAAc,cACd,GAAG,SAC+C;CAClD,OACE,oBAAC,OAAc,MAAf;EACE,aAAU;EACV,oBAAkB;EAClB,WAAW,GACT,gEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAM,mBAAmB,IACvB,wOACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,MAAM;EACR;EACA,MAAM;GACJ,SAAS;GACT,IAAI;EACN;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAEA,SAAS,SAAS,EAChB,WACA,UAAU,WACV,OAAO,WACP,GAAG,SAEoC;CACvC,OACE,oBAAC,OAAc,MAAf;EACE,aAAU;EACV,gBAAc;EACd,aAAW;EACX,WAAW,GAAG,iBAAiB;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAC5D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EACnB,WACA,GAAG,SACkD;CACrD,OACE,oBAAC,OAAc,SAAf;EACE,aAAU;EACV,WAAW,GACT,goBACA,mHACA,ybACA,kTACA,2VACA,uOACA,ydACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EACnB,WACA,GAAG,SACkD;CACrD,OACE,oBAAC,OAAc,SAAf;EACE,aAAU;EACV,WAAW,GACT,sIACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Textarea — multi-line text field.
|
|
3
|
+
*
|
|
4
|
+
* A plain native `<textarea>` with MMDS border/focus/error styling.
|
|
5
|
+
* Height auto-grows to content (`field-sizing-content`, min-h-16) instead of
|
|
6
|
+
* a fixed rows count, and error state is driven off `aria-invalid` rather
|
|
7
|
+
* than a prop.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
|
|
11
|
+
export { Textarea };
|
|
12
|
+
/**
|
|
13
|
+
* WHEN TO USE
|
|
14
|
+
* - Notes, descriptions, messages — any free text that can span multiple lines
|
|
15
|
+
*
|
|
16
|
+
* NOT WHEN
|
|
17
|
+
* - A short single-line value — use Input
|
|
18
|
+
* - Rich text or markdown editing — Textarea is plain text only
|
|
19
|
+
*
|
|
20
|
+
* KEY RULES
|
|
21
|
+
* - Height grows with content (`field-sizing-content`); min-height is 4rem, no fixed `rows` styling
|
|
22
|
+
* - Error state is driven entirely by `aria-invalid` — there is no `error` prop
|
|
23
|
+
* - An AI-drafted value is a plain composition, not a dedicated prop, and the same shape as
|
|
24
|
+
* Input's: put `AILabel` in the label row as a sibling of FieldLabel (never inside it —
|
|
25
|
+
* AILabelTrigger is a button). The Textarea itself stays plain: no `shadow-ai-glow` or
|
|
26
|
+
* gradient border — that boundary glow is reserved for card-like AI surfaces (Card, Dialog),
|
|
27
|
+
* never form controls, where it fights the field's own focus/invalid rings. Pass AILabel
|
|
28
|
+
* `edited={value !== draft}` and an `onRevert` that restores it, so once the user edits the
|
|
29
|
+
* marker becomes a ghost undo button (AILabel's own override/undo morph). See AI/Textarea.
|
|
30
|
+
*
|
|
31
|
+
* USAGE
|
|
32
|
+
* <Textarea placeholder="Add a description..." />
|
|
33
|
+
* <Textarea aria-invalid defaultValue="..." />
|
|
34
|
+
*/
|
|
35
|
+
//# sourceMappingURL=textarea.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/ui/textarea.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,iBAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,qBAW1E;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA;AAEnB;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/ui/textarea.tsx
|
|
5
|
+
function Textarea({ className, ...props }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("textarea", {
|
|
7
|
+
"data-slot": "textarea",
|
|
8
|
+
className: cn("flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
|
|
9
|
+
...props
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* WHEN TO USE
|
|
14
|
+
* - Notes, descriptions, messages — any free text that can span multiple lines
|
|
15
|
+
*
|
|
16
|
+
* NOT WHEN
|
|
17
|
+
* - A short single-line value — use Input
|
|
18
|
+
* - Rich text or markdown editing — Textarea is plain text only
|
|
19
|
+
*
|
|
20
|
+
* KEY RULES
|
|
21
|
+
* - Height grows with content (`field-sizing-content`); min-height is 4rem, no fixed `rows` styling
|
|
22
|
+
* - Error state is driven entirely by `aria-invalid` — there is no `error` prop
|
|
23
|
+
* - An AI-drafted value is a plain composition, not a dedicated prop, and the same shape as
|
|
24
|
+
* Input's: put `AILabel` in the label row as a sibling of FieldLabel (never inside it —
|
|
25
|
+
* AILabelTrigger is a button). The Textarea itself stays plain: no `shadow-ai-glow` or
|
|
26
|
+
* gradient border — that boundary glow is reserved for card-like AI surfaces (Card, Dialog),
|
|
27
|
+
* never form controls, where it fights the field's own focus/invalid rings. Pass AILabel
|
|
28
|
+
* `edited={value !== draft}` and an `onRevert` that restores it, so once the user edits the
|
|
29
|
+
* marker becomes a ghost undo button (AILabel's own override/undo morph). See AI/Textarea.
|
|
30
|
+
*
|
|
31
|
+
* USAGE
|
|
32
|
+
* <Textarea placeholder="Add a description..." />
|
|
33
|
+
* <Textarea aria-invalid defaultValue="..." />
|
|
34
|
+
*/
|
|
35
|
+
//#endregion
|
|
36
|
+
export { Textarea };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.js","names":[],"sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["/**\n * Textarea — multi-line text field.\n *\n * A plain native `<textarea>` with MMDS border/focus/error styling.\n * Height auto-grows to content (`field-sizing-content`, min-h-16) instead of\n * a fixed rows count, and error state is driven off `aria-invalid` rather\n * than a prop.\n */\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport { Textarea }\n\n/**\n * WHEN TO USE\n * - Notes, descriptions, messages — any free text that can span multiple lines\n *\n * NOT WHEN\n * - A short single-line value — use Input\n * - Rich text or markdown editing — Textarea is plain text only\n *\n * KEY RULES\n * - Height grows with content (`field-sizing-content`); min-height is 4rem, no fixed `rows` styling\n * - Error state is driven entirely by `aria-invalid` — there is no `error` prop\n * - An AI-drafted value is a plain composition, not a dedicated prop, and the same shape as\n * Input's: put `AILabel` in the label row as a sibling of FieldLabel (never inside it —\n * AILabelTrigger is a button). The Textarea itself stays plain: no `shadow-ai-glow` or\n * gradient border — that boundary glow is reserved for card-like AI surfaces (Card, Dialog),\n * never form controls, where it fights the field's own focus/invalid rings. Pass AILabel\n * `edited={value !== draft}` and an `onRevert` that restores it, so once the user edits the\n * marker becomes a ghost undo button (AILabel's own override/undo morph). See AI/Textarea.\n *\n * USAGE\n * <Textarea placeholder=\"Add a description...\" />\n * <Textarea aria-invalid defaultValue=\"...\" />\n */\n"],"mappings":";;;;AAaA,SAAS,SAAS,EAAE,WAAW,GAAG,SAA2C;CAC3E,OACE,oBAAC,YAAD;EACE,aAAU;EACV,WAAW,GACT,0hBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimePicker — masked text input for typing a 12-hour time (HH:MM AM/PM).
|
|
3
|
+
*
|
|
4
|
+
* Not a native `<input type="time">` (that renders OS/browser chrome, not
|
|
5
|
+
* a plain masked text field) and not a dropdown/list picker (compose
|
|
6
|
+
* Select or Combobox for that shape instead) — this is purely a typing
|
|
7
|
+
* experience, driven entirely through onKeyDown (not onChange) so the
|
|
8
|
+
* displayed value can never drift out of sync with what was actually
|
|
9
|
+
* typed.
|
|
10
|
+
*
|
|
11
|
+
* The mask is always fully visible — "--:-- AM" from the very first
|
|
12
|
+
* render, never a placeholder that vanishes on the first keystroke. Each
|
|
13
|
+
* digit replaces one "-" in place; the meridiem always shows a real
|
|
14
|
+
* "AM"/"PM" (defaults "AM" on mount), and its "M" never changes — only the
|
|
15
|
+
* leading "A"/"P" toggles. Uncontrolled only: `defaultValue` seeds it,
|
|
16
|
+
* `onValueChange` notifies of every change, there is no `value` prop.
|
|
17
|
+
*/
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import { Input } from "../../components/ui/input";
|
|
20
|
+
declare function TimePicker({ defaultValue, onValueChange, className, ...props }: Omit<React.ComponentProps<typeof Input>, "value" | "defaultValue" | "onChange" | "type"> & {
|
|
21
|
+
defaultValue?: string;
|
|
22
|
+
onValueChange?: (value: string) => void;
|
|
23
|
+
}): React.JSX.Element;
|
|
24
|
+
export { TimePicker };
|
|
25
|
+
/**
|
|
26
|
+
* WHEN TO USE
|
|
27
|
+
* - Typing a time directly is faster than scrolling/clicking through a list — appointment
|
|
28
|
+
* forms, scheduling fields, anywhere the exact time is usually already known.
|
|
29
|
+
*
|
|
30
|
+
* NOT WHEN
|
|
31
|
+
* - Picking from a fixed set of bookable slots (e.g. every 15 minutes) — compose Select or
|
|
32
|
+
* Combobox with those options instead; this component has no dropdown at all.
|
|
33
|
+
* - Full date + time together — pair this with Calendar inside the same Popover; see Calendar's
|
|
34
|
+
* doc comment for the date-picker composition pattern.
|
|
35
|
+
*
|
|
36
|
+
* KEY RULES
|
|
37
|
+
* - Uncontrolled only — `defaultValue` seeds it, `onValueChange` fires on every change. There is
|
|
38
|
+
* no `value` prop; this component owns its own text the same way a plain `<input>` would.
|
|
39
|
+
* - `onValueChange` reports exactly what's on screen, mask characters included (e.g. "09:-- AM"
|
|
40
|
+
* while the minute is still unfilled) — check for "-" to know whether entry is complete.
|
|
41
|
+
* - The mask never disappears: it's the value itself, not a `placeholder` (which only shows on an
|
|
42
|
+
* empty value — this input is never actually empty). Each keystroke replaces one "-" in place.
|
|
43
|
+
* - The meridiem always shows a real "AM"/"PM" (starts "AM") — "a"/"p" toggles it at any time,
|
|
44
|
+
* independent of how much of the hour/minute has been typed; there's nothing to erase there.
|
|
45
|
+
* A trailing AM/PM button mirrors this toggle for touch/virtual-keyboard users, since
|
|
46
|
+
* `inputMode="numeric"` hides letter keys on those keyboards.
|
|
47
|
+
* - Digits fill hour then minute, each clamped live (hour rejects >12 or "00", minute rejects
|
|
48
|
+
* >59). Backspace removes the most recently-typed digit (minute first, then hour). Pasting a
|
|
49
|
+
* full time string (masked or clean) is also handled.
|
|
50
|
+
* - Error state is driven entirely by `aria-invalid` (spreads through to Input) — there is no
|
|
51
|
+
* separate `error` prop, consistent with every other input in this library.
|
|
52
|
+
* - This wraps Input purely for the visual shell — don't reach for Input directly and rebuild
|
|
53
|
+
* this masking logic; that's the one thing this component exists to own.
|
|
54
|
+
*
|
|
55
|
+
* USAGE
|
|
56
|
+
* <TimePicker defaultValue="09:30 AM" onValueChange={(value) => console.log(value)} />
|
|
57
|
+
*/
|
|
58
|
+
//# sourceMappingURL=time-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../../../src/components/ui/time-picker.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AA0D7C,iBAAS,UAAU,CAAC,EAClB,YAAY,EACZ,aAAa,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CACL,KAAK,CAAC,cAAc,CAAC,OAAO,KAAK,CAAC,EAClC,OAAO,GAAG,cAAc,GAAG,UAAU,GAAG,MAAM,CAC/C,GAAG;IACF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,qBAyGA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/ui/time-picker.tsx
|
|
7
|
+
/**
|
|
8
|
+
* TimePicker — masked text input for typing a 12-hour time (HH:MM AM/PM).
|
|
9
|
+
*
|
|
10
|
+
* Not a native `<input type="time">` (that renders OS/browser chrome, not
|
|
11
|
+
* a plain masked text field) and not a dropdown/list picker (compose
|
|
12
|
+
* Select or Combobox for that shape instead) — this is purely a typing
|
|
13
|
+
* experience, driven entirely through onKeyDown (not onChange) so the
|
|
14
|
+
* displayed value can never drift out of sync with what was actually
|
|
15
|
+
* typed.
|
|
16
|
+
*
|
|
17
|
+
* The mask is always fully visible — "--:-- AM" from the very first
|
|
18
|
+
* render, never a placeholder that vanishes on the first keystroke. Each
|
|
19
|
+
* digit replaces one "-" in place; the meridiem always shows a real
|
|
20
|
+
* "AM"/"PM" (defaults "AM" on mount), and its "M" never changes — only the
|
|
21
|
+
* leading "A"/"P" toggles. Uncontrolled only: `defaultValue` seeds it,
|
|
22
|
+
* `onValueChange` notifies of every change, there is no `value` prop.
|
|
23
|
+
*/
|
|
24
|
+
var EMPTY_SEGMENTS = {
|
|
25
|
+
hour: "",
|
|
26
|
+
minute: "",
|
|
27
|
+
meridiem: "AM"
|
|
28
|
+
};
|
|
29
|
+
function maskChar(digits, index) {
|
|
30
|
+
return digits[index] ?? "-";
|
|
31
|
+
}
|
|
32
|
+
/** The always-visible mask string — e.g. "09:-- AM" while the minute is still unfilled. */
|
|
33
|
+
function formatSegments({ hour, minute, meridiem }) {
|
|
34
|
+
return `${maskChar(hour, 0)}${maskChar(hour, 1)}:${maskChar(minute, 0)}${maskChar(minute, 1)} ${meridiem}`;
|
|
35
|
+
}
|
|
36
|
+
/** Clamps/normalizes a segment set from an external source (defaultValue, paste) — rejects an out-of-range hour/minute instead of displaying it, and zero-pads a single digit to match the two-digit look typing always produces. */
|
|
37
|
+
function clampSegments({ hour, minute, meridiem }) {
|
|
38
|
+
const validHour = hour !== "" && Number(hour) >= 1 && Number(hour) <= 12;
|
|
39
|
+
const validMinute = minute !== "" && Number(minute) <= 59;
|
|
40
|
+
return {
|
|
41
|
+
hour: validHour ? hour.padStart(2, "0") : "",
|
|
42
|
+
minute: validMinute ? minute.padStart(2, "0") : "",
|
|
43
|
+
meridiem
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Accepts either the clean "9:30 AM" shape or the masked "09:-- AM" shape (dashes are just treated as unfilled). */
|
|
47
|
+
function parseValue(value) {
|
|
48
|
+
const match = value.trim().match(/^([\d-]{1,2})?:?([\d-]{1,2})?\s*(AM|PM)?$/i);
|
|
49
|
+
if (!match) return EMPTY_SEGMENTS;
|
|
50
|
+
const [, hourRaw = "", minuteRaw = "", meridiemRaw = ""] = match;
|
|
51
|
+
return clampSegments({
|
|
52
|
+
hour: hourRaw.replace(/-/g, ""),
|
|
53
|
+
minute: minuteRaw.replace(/-/g, ""),
|
|
54
|
+
meridiem: meridiemRaw.toUpperCase() || "AM"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
var NAVIGATION_KEYS = /* @__PURE__ */ new Set([
|
|
58
|
+
"Tab",
|
|
59
|
+
"ArrowLeft",
|
|
60
|
+
"ArrowRight",
|
|
61
|
+
"Home",
|
|
62
|
+
"End",
|
|
63
|
+
"Shift",
|
|
64
|
+
"Delete"
|
|
65
|
+
]);
|
|
66
|
+
function TimePicker({ defaultValue, onValueChange, className, ...props }) {
|
|
67
|
+
const [segments, setSegments] = React.useState(() => parseValue(defaultValue ?? ""));
|
|
68
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
69
|
+
const commit = (next) => {
|
|
70
|
+
setSegments(next);
|
|
71
|
+
setAnnouncement(formatSegments(next));
|
|
72
|
+
onValueChange?.(formatSegments(next));
|
|
73
|
+
};
|
|
74
|
+
const handleKeyDown = (e) => {
|
|
75
|
+
if (e.key === "Backspace") {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
if (segments.minute) return commit({
|
|
78
|
+
...segments,
|
|
79
|
+
minute: segments.minute.slice(0, -1)
|
|
80
|
+
});
|
|
81
|
+
if (segments.hour) return commit({
|
|
82
|
+
...segments,
|
|
83
|
+
hour: segments.hour.slice(0, -1)
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (/^[ap]$/i.test(e.key)) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
commit({
|
|
90
|
+
...segments,
|
|
91
|
+
meridiem: e.key.toLowerCase() === "a" ? "AM" : "PM"
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (/^[0-9]$/.test(e.key)) {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
if (segments.hour.length < 2) {
|
|
98
|
+
const nextHour = segments.hour + e.key;
|
|
99
|
+
if (Number(nextHour) > 12 || nextHour === "00") return;
|
|
100
|
+
commit({
|
|
101
|
+
...segments,
|
|
102
|
+
hour: nextHour
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (segments.minute.length < 2) {
|
|
107
|
+
const nextMinute = segments.minute + e.key;
|
|
108
|
+
if (Number(nextMinute) > 59) return;
|
|
109
|
+
commit({
|
|
110
|
+
...segments,
|
|
111
|
+
minute: nextMinute
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!NAVIGATION_KEYS.has(e.key) && !e.metaKey && !e.ctrlKey) e.preventDefault();
|
|
117
|
+
};
|
|
118
|
+
return /* @__PURE__ */ jsxs(InputGroup, { children: [
|
|
119
|
+
/* @__PURE__ */ jsx(InputGroupInput, {
|
|
120
|
+
type: "text",
|
|
121
|
+
inputMode: "numeric",
|
|
122
|
+
autoComplete: "off",
|
|
123
|
+
value: formatSegments(segments),
|
|
124
|
+
onChange: () => {},
|
|
125
|
+
onKeyDown: handleKeyDown,
|
|
126
|
+
onPaste: (e) => {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
commit(parseValue(e.clipboardData.getData("text")));
|
|
129
|
+
},
|
|
130
|
+
className: cn(className),
|
|
131
|
+
...props
|
|
132
|
+
}),
|
|
133
|
+
/* @__PURE__ */ jsx(InputGroupAddon, {
|
|
134
|
+
align: "inline-end",
|
|
135
|
+
children: /* @__PURE__ */ jsx(InputGroupButton, {
|
|
136
|
+
type: "button",
|
|
137
|
+
"aria-label": segments.meridiem === "AM" ? "Switch to PM" : "Switch to AM",
|
|
138
|
+
onClick: () => commit({
|
|
139
|
+
...segments,
|
|
140
|
+
meridiem: segments.meridiem === "AM" ? "PM" : "AM"
|
|
141
|
+
}),
|
|
142
|
+
children: segments.meridiem
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ jsx("span", {
|
|
146
|
+
role: "status",
|
|
147
|
+
className: "sr-only",
|
|
148
|
+
children: announcement
|
|
149
|
+
})
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* WHEN TO USE
|
|
154
|
+
* - Typing a time directly is faster than scrolling/clicking through a list — appointment
|
|
155
|
+
* forms, scheduling fields, anywhere the exact time is usually already known.
|
|
156
|
+
*
|
|
157
|
+
* NOT WHEN
|
|
158
|
+
* - Picking from a fixed set of bookable slots (e.g. every 15 minutes) — compose Select or
|
|
159
|
+
* Combobox with those options instead; this component has no dropdown at all.
|
|
160
|
+
* - Full date + time together — pair this with Calendar inside the same Popover; see Calendar's
|
|
161
|
+
* doc comment for the date-picker composition pattern.
|
|
162
|
+
*
|
|
163
|
+
* KEY RULES
|
|
164
|
+
* - Uncontrolled only — `defaultValue` seeds it, `onValueChange` fires on every change. There is
|
|
165
|
+
* no `value` prop; this component owns its own text the same way a plain `<input>` would.
|
|
166
|
+
* - `onValueChange` reports exactly what's on screen, mask characters included (e.g. "09:-- AM"
|
|
167
|
+
* while the minute is still unfilled) — check for "-" to know whether entry is complete.
|
|
168
|
+
* - The mask never disappears: it's the value itself, not a `placeholder` (which only shows on an
|
|
169
|
+
* empty value — this input is never actually empty). Each keystroke replaces one "-" in place.
|
|
170
|
+
* - The meridiem always shows a real "AM"/"PM" (starts "AM") — "a"/"p" toggles it at any time,
|
|
171
|
+
* independent of how much of the hour/minute has been typed; there's nothing to erase there.
|
|
172
|
+
* A trailing AM/PM button mirrors this toggle for touch/virtual-keyboard users, since
|
|
173
|
+
* `inputMode="numeric"` hides letter keys on those keyboards.
|
|
174
|
+
* - Digits fill hour then minute, each clamped live (hour rejects >12 or "00", minute rejects
|
|
175
|
+
* >59). Backspace removes the most recently-typed digit (minute first, then hour). Pasting a
|
|
176
|
+
* full time string (masked or clean) is also handled.
|
|
177
|
+
* - Error state is driven entirely by `aria-invalid` (spreads through to Input) — there is no
|
|
178
|
+
* separate `error` prop, consistent with every other input in this library.
|
|
179
|
+
* - This wraps Input purely for the visual shell — don't reach for Input directly and rebuild
|
|
180
|
+
* this masking logic; that's the one thing this component exists to own.
|
|
181
|
+
*
|
|
182
|
+
* USAGE
|
|
183
|
+
* <TimePicker defaultValue="09:30 AM" onValueChange={(value) => console.log(value)} />
|
|
184
|
+
*/
|
|
185
|
+
//#endregion
|
|
186
|
+
export { TimePicker };
|
|
187
|
+
|
|
188
|
+
//# sourceMappingURL=time-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-picker.js","names":[],"sources":["../../../src/components/ui/time-picker.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * TimePicker — masked text input for typing a 12-hour time (HH:MM AM/PM).\n *\n * Not a native `<input type=\"time\">` (that renders OS/browser chrome, not\n * a plain masked text field) and not a dropdown/list picker (compose\n * Select or Combobox for that shape instead) — this is purely a typing\n * experience, driven entirely through onKeyDown (not onChange) so the\n * displayed value can never drift out of sync with what was actually\n * typed.\n *\n * The mask is always fully visible — \"--:-- AM\" from the very first\n * render, never a placeholder that vanishes on the first keystroke. Each\n * digit replaces one \"-\" in place; the meridiem always shows a real\n * \"AM\"/\"PM\" (defaults \"AM\" on mount), and its \"M\" never changes — only the\n * leading \"A\"/\"P\" toggles. Uncontrolled only: `defaultValue` seeds it,\n * `onValueChange` notifies of every change, there is no `value` prop.\n */\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupInput,\n} from \"@/components/ui/input-group\"\n\ntype TimeSegments = {\n hour: string // 0-2 digit chars, unfilled positions render as \"-\"\n minute: string // 0-2 digit chars, unfilled positions render as \"-\"\n meridiem: \"AM\" | \"PM\" // always has a real value — never unset\n}\n\nconst EMPTY_SEGMENTS: TimeSegments = { hour: \"\", minute: \"\", meridiem: \"AM\" }\n\nfunction maskChar(digits: string, index: number): string {\n return digits[index] ?? \"-\"\n}\n\n/** The always-visible mask string — e.g. \"09:-- AM\" while the minute is still unfilled. */\nfunction formatSegments({ hour, minute, meridiem }: TimeSegments): string {\n return `${maskChar(hour, 0)}${maskChar(hour, 1)}:${maskChar(minute, 0)}${maskChar(minute, 1)} ${meridiem}`\n}\n\n/** Clamps/normalizes a segment set from an external source (defaultValue, paste) — rejects an out-of-range hour/minute instead of displaying it, and zero-pads a single digit to match the two-digit look typing always produces. */\nfunction clampSegments({ hour, minute, meridiem }: TimeSegments): TimeSegments {\n const validHour = hour !== \"\" && Number(hour) >= 1 && Number(hour) <= 12\n const validMinute = minute !== \"\" && Number(minute) <= 59\n return {\n hour: validHour ? hour.padStart(2, \"0\") : \"\",\n minute: validMinute ? minute.padStart(2, \"0\") : \"\",\n meridiem,\n }\n}\n\n/** Accepts either the clean \"9:30 AM\" shape or the masked \"09:-- AM\" shape (dashes are just treated as unfilled). */\nfunction parseValue(value: string): TimeSegments {\n const match = value.trim().match(/^([\\d-]{1,2})?:?([\\d-]{1,2})?\\s*(AM|PM)?$/i)\n if (!match) return EMPTY_SEGMENTS\n const [, hourRaw = \"\", minuteRaw = \"\", meridiemRaw = \"\"] = match\n return clampSegments({\n hour: hourRaw.replace(/-/g, \"\"),\n minute: minuteRaw.replace(/-/g, \"\"),\n meridiem: (meridiemRaw.toUpperCase() as \"AM\" | \"PM\") || \"AM\",\n })\n}\n\nconst NAVIGATION_KEYS = new Set([\n \"Tab\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n \"Shift\",\n \"Delete\",\n])\n\nfunction TimePicker({\n defaultValue,\n onValueChange,\n className,\n ...props\n}: Omit<\n React.ComponentProps<typeof Input>,\n \"value\" | \"defaultValue\" | \"onChange\" | \"type\"\n> & {\n defaultValue?: string\n onValueChange?: (value: string) => void\n}) {\n const [segments, setSegments] = React.useState<TimeSegments>(() =>\n parseValue(defaultValue ?? \"\")\n )\n // Every accepted keystroke is intercepted in onKeyDown and only ever\n // reaches the screen through React re-rendering `value` — never through\n // native text insertion — so most screen readers' typing-echo has\n // nothing to announce. This mirrors the resulting mask into a live\n // region so each accepted digit/backspace/meridiem change is still\n // spoken.\n const [announcement, setAnnouncement] = React.useState(\"\")\n\n const commit = (next: TimeSegments) => {\n setSegments(next)\n setAnnouncement(formatSegments(next))\n onValueChange?.(formatSegments(next))\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Backspace\") {\n e.preventDefault()\n if (segments.minute)\n return commit({ ...segments, minute: segments.minute.slice(0, -1) })\n if (segments.hour)\n return commit({ ...segments, hour: segments.hour.slice(0, -1) })\n return\n }\n\n if (/^[ap]$/i.test(e.key)) {\n e.preventDefault()\n commit({\n ...segments,\n meridiem: e.key.toLowerCase() === \"a\" ? \"AM\" : \"PM\",\n })\n return\n }\n\n if (/^[0-9]$/.test(e.key)) {\n e.preventDefault()\n if (segments.hour.length < 2) {\n const nextHour = segments.hour + e.key\n if (Number(nextHour) > 12 || nextHour === \"00\") return\n commit({ ...segments, hour: nextHour })\n return\n }\n if (segments.minute.length < 2) {\n const nextMinute = segments.minute + e.key\n if (Number(nextMinute) > 59) return\n commit({ ...segments, minute: nextMinute })\n }\n return\n }\n\n // Block anything else from reaching the native input — we render a\n // fully-controlled value ourselves, so an uncaught keystroke would\n // otherwise desync what's displayed from what these segments hold.\n if (!NAVIGATION_KEYS.has(e.key) && !e.metaKey && !e.ctrlKey) {\n e.preventDefault()\n }\n }\n\n return (\n <InputGroup>\n <InputGroupInput\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n value={formatSegments(segments)}\n onChange={() => {}}\n onKeyDown={handleKeyDown}\n onPaste={(e) => {\n e.preventDefault()\n commit(parseValue(e.clipboardData.getData(\"text\")))\n }}\n className={cn(className)}\n {...props}\n />\n <InputGroupAddon align=\"inline-end\">\n {/*\n \"a\"/\"p\" only reaches this component through a physical keyboard —\n `inputMode=\"numeric\"` above tells on-screen/virtual keyboards to\n show digits only, so touch users on those devices have no letter\n key to press at all. This button is the modality-independent\n fallback; the \"a\"/\"p\" shortcut still works for physical keyboards.\n */}\n <InputGroupButton\n type=\"button\"\n aria-label={\n segments.meridiem === \"AM\" ? \"Switch to PM\" : \"Switch to AM\"\n }\n onClick={() =>\n commit({\n ...segments,\n meridiem: segments.meridiem === \"AM\" ? \"PM\" : \"AM\",\n })\n }\n >\n {segments.meridiem}\n </InputGroupButton>\n </InputGroupAddon>\n <span role=\"status\" className=\"sr-only\">\n {announcement}\n </span>\n </InputGroup>\n )\n}\n\nexport { TimePicker }\n\n/**\n * WHEN TO USE\n * - Typing a time directly is faster than scrolling/clicking through a list — appointment\n * forms, scheduling fields, anywhere the exact time is usually already known.\n *\n * NOT WHEN\n * - Picking from a fixed set of bookable slots (e.g. every 15 minutes) — compose Select or\n * Combobox with those options instead; this component has no dropdown at all.\n * - Full date + time together — pair this with Calendar inside the same Popover; see Calendar's\n * doc comment for the date-picker composition pattern.\n *\n * KEY RULES\n * - Uncontrolled only — `defaultValue` seeds it, `onValueChange` fires on every change. There is\n * no `value` prop; this component owns its own text the same way a plain `<input>` would.\n * - `onValueChange` reports exactly what's on screen, mask characters included (e.g. \"09:-- AM\"\n * while the minute is still unfilled) — check for \"-\" to know whether entry is complete.\n * - The mask never disappears: it's the value itself, not a `placeholder` (which only shows on an\n * empty value — this input is never actually empty). Each keystroke replaces one \"-\" in place.\n * - The meridiem always shows a real \"AM\"/\"PM\" (starts \"AM\") — \"a\"/\"p\" toggles it at any time,\n * independent of how much of the hour/minute has been typed; there's nothing to erase there.\n * A trailing AM/PM button mirrors this toggle for touch/virtual-keyboard users, since\n * `inputMode=\"numeric\"` hides letter keys on those keyboards.\n * - Digits fill hour then minute, each clamped live (hour rejects >12 or \"00\", minute rejects\n * >59). Backspace removes the most recently-typed digit (minute first, then hour). Pasting a\n * full time string (masked or clean) is also handled.\n * - Error state is driven entirely by `aria-invalid` (spreads through to Input) — there is no\n * separate `error` prop, consistent with every other input in this library.\n * - This wraps Input purely for the visual shell — don't reach for Input directly and rebuild\n * this masking logic; that's the one thing this component exists to own.\n *\n * USAGE\n * <TimePicker defaultValue=\"09:30 AM\" onValueChange={(value) => console.log(value)} />\n */\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAM,iBAA+B;CAAE,MAAM;CAAI,QAAQ;CAAI,UAAU;AAAK;AAE5E,SAAS,SAAS,QAAgB,OAAuB;CACvD,OAAO,OAAO,UAAU;AAC1B;;AAGA,SAAS,eAAe,EAAE,MAAM,QAAQ,YAAkC;CACxE,OAAO,GAAG,SAAS,MAAM,CAAC,IAAI,SAAS,MAAM,CAAC,EAAE,GAAG,SAAS,QAAQ,CAAC,IAAI,SAAS,QAAQ,CAAC,EAAE,GAAG;AAClG;;AAGA,SAAS,cAAc,EAAE,MAAM,QAAQ,YAAwC;CAC7E,MAAM,YAAY,SAAS,MAAM,OAAO,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK;CACtE,MAAM,cAAc,WAAW,MAAM,OAAO,MAAM,KAAK;CACvD,OAAO;EACL,MAAM,YAAY,KAAK,SAAS,GAAG,GAAG,IAAI;EAC1C,QAAQ,cAAc,OAAO,SAAS,GAAG,GAAG,IAAI;EAChD;CACF;AACF;;AAGA,SAAS,WAAW,OAA6B;CAC/C,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,4CAA4C;CAC7E,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,GAAG,UAAU,IAAI,YAAY,IAAI,cAAc,MAAM;CAC3D,OAAO,cAAc;EACnB,MAAM,QAAQ,QAAQ,MAAM,EAAE;EAC9B,QAAQ,UAAU,QAAQ,MAAM,EAAE;EAClC,UAAW,YAAY,YAAY,KAAqB;CAC1D,CAAC;AACH;AAEA,IAAM,kCAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,WAAW,EAClB,cACA,eACA,WACA,GAAG,SAOF;CACD,MAAM,CAAC,UAAU,eAAe,MAAM,eACpC,WAAW,gBAAgB,EAAE,CAC/B;CAOA,MAAM,CAAC,cAAc,mBAAmB,MAAM,SAAS,EAAE;CAEzD,MAAM,UAAU,SAAuB;EACrC,YAAY,IAAI;EAChB,gBAAgB,eAAe,IAAI,CAAC;EACpC,gBAAgB,eAAe,IAAI,CAAC;CACtC;CAEA,MAAM,iBAAiB,MAA6C;EAClE,IAAI,EAAE,QAAQ,aAAa;GACzB,EAAE,eAAe;GACjB,IAAI,SAAS,QACX,OAAO,OAAO;IAAE,GAAG;IAAU,QAAQ,SAAS,OAAO,MAAM,GAAG,EAAE;GAAE,CAAC;GACrE,IAAI,SAAS,MACX,OAAO,OAAO;IAAE,GAAG;IAAU,MAAM,SAAS,KAAK,MAAM,GAAG,EAAE;GAAE,CAAC;GACjE;EACF;EAEA,IAAI,UAAU,KAAK,EAAE,GAAG,GAAG;GACzB,EAAE,eAAe;GACjB,OAAO;IACL,GAAG;IACH,UAAU,EAAE,IAAI,YAAY,MAAM,MAAM,OAAO;GACjD,CAAC;GACD;EACF;EAEA,IAAI,UAAU,KAAK,EAAE,GAAG,GAAG;GACzB,EAAE,eAAe;GACjB,IAAI,SAAS,KAAK,SAAS,GAAG;IAC5B,MAAM,WAAW,SAAS,OAAO,EAAE;IACnC,IAAI,OAAO,QAAQ,IAAI,MAAM,aAAa,MAAM;IAChD,OAAO;KAAE,GAAG;KAAU,MAAM;IAAS,CAAC;IACtC;GACF;GACA,IAAI,SAAS,OAAO,SAAS,GAAG;IAC9B,MAAM,aAAa,SAAS,SAAS,EAAE;IACvC,IAAI,OAAO,UAAU,IAAI,IAAI;IAC7B,OAAO;KAAE,GAAG;KAAU,QAAQ;IAAW,CAAC;GAC5C;GACA;EACF;EAKA,IAAI,CAAC,gBAAgB,IAAI,EAAE,GAAG,KAAK,CAAC,EAAE,WAAW,CAAC,EAAE,SAClD,EAAE,eAAe;CAErB;CAEA,OACE,qBAAC,YAAD,EAAA,UAAA;EACE,oBAAC,iBAAD;GACE,MAAK;GACL,WAAU;GACV,cAAa;GACb,OAAO,eAAe,QAAQ;GAC9B,gBAAgB,CAAC;GACjB,WAAW;GACX,UAAU,MAAM;IACd,EAAE,eAAe;IACjB,OAAO,WAAW,EAAE,cAAc,QAAQ,MAAM,CAAC,CAAC;GACpD;GACA,WAAW,GAAG,SAAS;GACvB,GAAI;EACL,CAAA;EACD,oBAAC,iBAAD;GAAiB,OAAM;GAQrB,UAAA,oBAAC,kBAAD;IACE,MAAK;IACL,cACE,SAAS,aAAa,OAAO,iBAAiB;IAEhD,eACE,OAAO;KACL,GAAG;KACH,UAAU,SAAS,aAAa,OAAO,OAAO;IAChD,CAAC;IAGF,UAAA,SAAS;GACM,CAAA;EACH,CAAA;EACjB,oBAAC,QAAD;GAAM,MAAK;GAAS,WAAU;GAC3B,UAAA;EACG,CAAA;CACI,EAAA,CAAA;AAEhB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleGroup — set of related Toggles sharing one selection state.
|
|
3
|
+
*
|
|
4
|
+
* `type` (required) is "single" for radio-like exclusive selection or
|
|
5
|
+
* "multiple" for any-number-pressed. variant/size set on the group cascade to
|
|
6
|
+
* every ToggleGroupItem via context, so items don't need to repeat them.
|
|
7
|
+
* Built on the Radix ToggleGroup primitive. Adds `orientation` (vertical
|
|
8
|
+
* stacking) and `spacing` (gap between items — spacing=0 collapses items
|
|
9
|
+
* into one seamless bar with shared borders, like ButtonGroup).
|
|
10
|
+
*/
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import { type VariantProps } from "class-variance-authority";
|
|
13
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
|
|
14
|
+
import { toggleVariants } from "../../components/ui/toggle";
|
|
15
|
+
declare function ToggleGroup({ className, variant, size, spacing, orientation, children, ...props }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof toggleVariants> & {
|
|
16
|
+
spacing?: number;
|
|
17
|
+
orientation?: "horizontal" | "vertical";
|
|
18
|
+
}): React.JSX.Element;
|
|
19
|
+
declare function ToggleGroupItem({ className, children, variant, size, ...props }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>): React.JSX.Element;
|
|
20
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
21
|
+
/**
|
|
22
|
+
* WHEN TO USE
|
|
23
|
+
* - Text alignment pickers, view density switchers, multi-select formatting toolbars
|
|
24
|
+
* - Any set of related Toggles that should share pressed state, variant, and size
|
|
25
|
+
*
|
|
26
|
+
* NOT WHEN
|
|
27
|
+
* - Switching between views/pages of content — use Tabs
|
|
28
|
+
* - A form field that needs a single mutually-exclusive value — use RadioGroup
|
|
29
|
+
* - Actions that don't hold pressed state — use ButtonGroup
|
|
30
|
+
*
|
|
31
|
+
* KEY RULES
|
|
32
|
+
* - type is required: "single" (only one item pressed at a time, like radio) or "multiple" (any number pressed)
|
|
33
|
+
* - variant/size set on ToggleGroup cascade to every ToggleGroupItem and take priority over a variant/size
|
|
34
|
+
* passed directly on an item
|
|
35
|
+
* - spacing: default 2 (gap between items); spacing={0} collapses items into a seamless segmented
|
|
36
|
+
* control with shared borders and collapsed inner radii
|
|
37
|
+
* - orientation: horizontal (default) | vertical
|
|
38
|
+
* - Each ToggleGroupItem needs its own aria-label (or visible text) when icon-only — none is added automatically
|
|
39
|
+
*
|
|
40
|
+
* USAGE
|
|
41
|
+
* <ToggleGroup type="single" defaultValue="center" variant="outline">
|
|
42
|
+
* <ToggleGroupItem value="left" aria-label="Align left"><AlignLeftIcon /></ToggleGroupItem>
|
|
43
|
+
* <ToggleGroupItem value="center" aria-label="Align center"><AlignCenterIcon /></ToggleGroupItem>
|
|
44
|
+
* <ToggleGroupItem value="right" aria-label="Align right"><AlignRightIcon /></ToggleGroupItem>
|
|
45
|
+
* </ToggleGroup>
|
|
46
|
+
*
|
|
47
|
+
* <ToggleGroup type="multiple" defaultValue={["bold"]}>
|
|
48
|
+
* <ToggleGroupItem value="bold" aria-label="Bold"><BoldIcon /></ToggleGroupItem>
|
|
49
|
+
* <ToggleGroupItem value="italic" aria-label="Italic"><ItalicIcon /></ToggleGroupItem>
|
|
50
|
+
* </ToggleGroup>
|
|
51
|
+
*/
|
|
52
|
+
//# sourceMappingURL=toggle-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle-group.d.ts","sourceRoot":"","sources":["../../../src/components/ui/toggle-group.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAG9D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAcvD,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,OAAO,EACP,IAAI,EACJ,OAAW,EACX,WAA0B,EAC1B,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,IAAI,CAAC,GACvD,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;CACxC,qBAsBF;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,QAAQ,EACR,OAAmB,EACnB,IAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,IAAI,CAAC,GACvD,YAAY,CAAC,OAAO,cAAc,CAAC,qBAsBpC;AAED,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG"}
|