@inspirare/design-system 0.0.22 → 0.0.24
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 +82 -61
- package/dist/book-logo-filled.svg +55 -0
- package/dist/components/ui/accessibility-improvements.js +26 -23
- package/dist/components/ui/accordion.js +34 -24
- package/dist/components/ui/alert-dialog.js +91 -57
- package/dist/components/ui/alert.js +38 -26
- package/dist/components/ui/aspect-ratio.js +11 -3
- package/dist/components/ui/avatar.js +30 -8
- package/dist/components/ui/badge.js +20 -14
- package/dist/components/ui/breadcrumb.js +69 -56
- package/dist/components/ui/button-group.js +17 -13
- package/dist/components/ui/button.js +19 -15
- package/dist/components/ui/calendar.js +63 -59
- package/dist/components/ui/card.js +11 -9
- package/dist/components/ui/carousel.js +70 -75
- package/dist/components/ui/chart.js +75 -72
- package/dist/components/ui/checkbox.js +4 -5
- package/dist/components/ui/collapsible.js +12 -14
- package/dist/components/ui/color-picker.js +60 -80
- package/dist/components/ui/combobox.js +153 -33
- package/dist/components/ui/command.js +50 -48
- package/dist/components/ui/context-menu.js +129 -68
- package/dist/components/ui/dialog.js +65 -60
- package/dist/components/ui/drawer.js +81 -40
- package/dist/components/ui/dropdown-menu.js +100 -87
- package/dist/components/ui/empty.js +7 -6
- package/dist/components/ui/field.js +44 -47
- package/dist/components/ui/form.js +22 -23
- package/dist/components/ui/hover-card.js +14 -11
- package/dist/components/ui/input-group.js +16 -13
- package/dist/components/ui/input-otp.js +37 -32
- package/dist/components/ui/input.js +9 -7
- package/dist/components/ui/item.js +42 -36
- package/dist/components/ui/kbd.js +2 -1
- package/dist/components/ui/label.js +6 -7
- package/dist/components/ui/menubar.js +124 -93
- package/dist/components/ui/navigation-menu.js +79 -54
- package/dist/components/ui/pagination.js +78 -58
- package/dist/components/ui/popover.js +35 -16
- package/dist/components/ui/progress.js +37 -13
- package/dist/components/ui/radio-group.js +14 -14
- package/dist/components/ui/resizable.js +24 -15
- package/dist/components/ui/scroll-area.js +29 -11
- package/dist/components/ui/select.js +98 -56
- package/dist/components/ui/separator.js +5 -7
- package/dist/components/ui/sheet.js +47 -42
- package/dist/components/ui/sidebar.js +279 -248
- package/dist/components/ui/skeleton.js +4 -40
- package/dist/components/ui/slider.js +27 -29
- package/dist/components/ui/sonner.js +27 -10
- package/dist/components/ui/spinner.js +2 -0
- package/dist/components/ui/switch.js +3 -4
- package/dist/components/ui/table.js +6 -6
- package/dist/components/ui/tabs.js +45 -22
- package/dist/components/ui/textarea.js +3 -2
- package/dist/components/ui/theme-toggle.js +38 -41
- package/dist/components/ui/toggle-group.js +30 -21
- package/dist/components/ui/toggle.js +8 -9
- package/dist/components/ui/tooltip.js +28 -23
- package/dist/index.css +1 -1
- package/dist/index.d.ts +446 -468
- package/dist/index.js +58 -58
- package/package.json +42 -41
- package/src/App.tsx +28 -34
- package/src/components/branding/assets/book-logo-filled.svg +55 -0
- package/src/components/branding/logo/logo.tsx +56 -0
- package/src/components/ui/accessibility-improvements.stories.tsx +18 -0
- package/src/components/ui/accessibility-improvements.test.tsx +13 -0
- package/src/components/ui/accessibility-improvements.tsx +16 -6
- package/src/components/ui/accordion.stories.tsx +19 -0
- package/src/components/ui/accordion.test.tsx +14 -0
- package/src/components/ui/accordion.tsx +28 -20
- package/src/components/ui/alert-dialog.stories.tsx +19 -0
- package/src/components/ui/alert-dialog.test.tsx +14 -0
- package/src/components/ui/alert-dialog.tsx +159 -123
- package/src/components/ui/alert.stories.tsx +19 -0
- package/src/components/ui/alert.test.tsx +14 -0
- package/src/components/ui/alert.tsx +60 -41
- package/src/components/ui/aspect-ratio.stories.tsx +19 -0
- package/src/components/ui/aspect-ratio.test.tsx +14 -0
- package/src/components/ui/aspect-ratio.tsx +19 -2
- package/src/components/ui/attachment.stories.tsx +19 -0
- package/src/components/ui/attachment.test.tsx +14 -0
- package/src/components/ui/attachment.tsx +209 -0
- package/src/components/ui/avatar.stories.tsx +19 -0
- package/src/components/ui/avatar.test.tsx +14 -0
- package/src/components/ui/avatar.tsx +73 -17
- package/src/components/ui/badge.stories.tsx +19 -0
- package/src/components/ui/badge.test.tsx +14 -0
- package/src/components/ui/badge.tsx +33 -25
- package/src/components/ui/breadcrumb.stories.tsx +19 -0
- package/src/components/ui/breadcrumb.test.tsx +14 -0
- package/src/components/ui/breadcrumb.tsx +98 -86
- package/src/components/ui/bubble.stories.tsx +19 -0
- package/src/components/ui/bubble.test.tsx +14 -0
- package/src/components/ui/bubble.tsx +130 -0
- package/src/components/ui/button-group.stories.tsx +19 -0
- package/src/components/ui/button-group.test.tsx +14 -0
- package/src/components/ui/button-group.tsx +26 -20
- package/src/components/ui/button.stories.tsx +19 -0
- package/src/components/ui/button.test.tsx +14 -0
- package/src/components/ui/button.tsx +30 -31
- package/src/components/ui/calendar.stories.tsx +19 -0
- package/src/components/ui/calendar.test.tsx +14 -0
- package/src/components/ui/calendar.tsx +28 -26
- package/src/components/ui/card.stories.tsx +19 -0
- package/src/components/ui/card.test.tsx +14 -0
- package/src/components/ui/card.tsx +30 -17
- package/src/components/ui/carousel.stories.tsx +19 -0
- package/src/components/ui/carousel.test.tsx +14 -0
- package/src/components/ui/carousel.tsx +115 -135
- package/src/components/ui/chart.stories.tsx +18 -0
- package/src/components/ui/chart.test.tsx +13 -0
- package/src/components/ui/chart.tsx +220 -216
- package/src/components/ui/checkbox.stories.tsx +19 -0
- package/src/components/ui/checkbox.test.tsx +14 -0
- package/src/components/ui/checkbox.tsx +7 -10
- package/src/components/ui/collapsible.stories.tsx +19 -0
- package/src/components/ui/collapsible.test.tsx +14 -0
- package/src/components/ui/collapsible.tsx +11 -26
- package/src/components/ui/color-picker.stories.tsx +19 -0
- package/src/components/ui/color-picker.test.tsx +14 -0
- package/src/components/ui/color-picker.tsx +36 -26
- package/src/components/ui/combobox.stories.tsx +19 -0
- package/src/components/ui/combobox.test.tsx +14 -0
- package/src/components/ui/combobox.tsx +281 -96
- package/src/components/ui/command.stories.tsx +19 -0
- package/src/components/ui/command.test.tsx +14 -0
- package/src/components/ui/command.tsx +59 -47
- package/src/components/ui/context-menu.stories.tsx +19 -0
- package/src/components/ui/context-menu.test.tsx +14 -0
- package/src/components/ui/context-menu.tsx +231 -160
- package/src/components/ui/dialog.stories.tsx +19 -0
- package/src/components/ui/dialog.test.tsx +14 -0
- package/src/components/ui/dialog.tsx +81 -72
- package/src/components/ui/direction.stories.tsx +19 -0
- package/src/components/ui/direction.test.tsx +14 -0
- package/src/components/ui/direction.tsx +6 -0
- package/src/components/ui/drawer.stories.tsx +19 -0
- package/src/components/ui/drawer.test.tsx +14 -0
- package/src/components/ui/drawer.tsx +143 -64
- package/src/components/ui/dropdown-menu.stories.tsx +19 -0
- package/src/components/ui/dropdown-menu.test.tsx +14 -0
- package/src/components/ui/dropdown-menu.tsx +199 -153
- package/src/components/ui/empty.stories.tsx +18 -0
- package/src/components/ui/empty.test.tsx +13 -0
- package/src/components/ui/empty.tsx +11 -9
- package/src/components/ui/field.stories.tsx +18 -0
- package/src/components/ui/field.test.tsx +13 -0
- package/src/components/ui/field.tsx +26 -32
- package/src/components/ui/form.stories.tsx +22 -0
- package/src/components/ui/form.test.tsx +15 -0
- package/src/components/ui/form.tsx +1 -2
- package/src/components/ui/hover-card.stories.tsx +19 -0
- package/src/components/ui/hover-card.test.tsx +14 -0
- package/src/components/ui/hover-card.tsx +34 -32
- package/src/components/ui/input-group.stories.tsx +18 -0
- package/src/components/ui/input-group.test.tsx +13 -0
- package/src/components/ui/input-group.tsx +21 -28
- package/src/components/ui/input-otp.stories.tsx +19 -0
- package/src/components/ui/input-otp.test.tsx +14 -0
- package/src/components/ui/input-otp.tsx +58 -42
- package/src/components/ui/input.stories.tsx +19 -0
- package/src/components/ui/input.test.tsx +14 -0
- package/src/components/ui/input.tsx +9 -8
- package/src/components/ui/item.stories.tsx +18 -0
- package/src/components/ui/item.test.tsx +13 -0
- package/src/components/ui/item.tsx +38 -28
- package/src/components/ui/kbd.stories.tsx +18 -0
- package/src/components/ui/kbd.test.tsx +13 -0
- package/src/components/ui/kbd.tsx +3 -3
- package/src/components/ui/label.stories.tsx +18 -0
- package/src/components/ui/label.test.tsx +13 -0
- package/src/components/ui/label.tsx +7 -11
- package/src/components/ui/marker.stories.tsx +19 -0
- package/src/components/ui/marker.test.tsx +14 -0
- package/src/components/ui/marker.tsx +73 -0
- package/src/components/ui/menubar.stories.tsx +19 -0
- package/src/components/ui/menubar.test.tsx +14 -0
- package/src/components/ui/menubar.tsx +226 -202
- package/src/components/ui/message-scroller.stories.tsx +19 -0
- package/src/components/ui/message-scroller.test.tsx +14 -0
- package/src/components/ui/message-scroller.tsx +131 -0
- package/src/components/ui/message.stories.tsx +19 -0
- package/src/components/ui/message.test.tsx +14 -0
- package/src/components/ui/message.tsx +94 -0
- package/src/components/ui/native-select.stories.tsx +18 -0
- package/src/components/ui/native-select.test.tsx +13 -0
- package/src/components/ui/native-select.tsx +63 -0
- package/src/components/ui/navigation-menu.stories.tsx +19 -0
- package/src/components/ui/navigation-menu.test.tsx +14 -0
- package/src/components/ui/navigation-menu.tsx +145 -103
- package/src/components/ui/pagination.stories.tsx +19 -0
- package/src/components/ui/pagination.test.tsx +14 -0
- package/src/components/ui/pagination.tsx +114 -83
- package/src/components/ui/popover.stories.tsx +19 -0
- package/src/components/ui/popover.test.tsx +14 -0
- package/src/components/ui/popover.tsx +68 -33
- package/src/components/ui/progress.stories.tsx +19 -0
- package/src/components/ui/progress.test.tsx +14 -0
- package/src/components/ui/progress.tsx +67 -15
- package/src/components/ui/questionnaire.stories.tsx +19 -0
- package/src/components/ui/questionnaire.test.tsx +14 -0
- package/src/components/ui/questionnaire.tsx +324 -0
- package/src/components/ui/radio-group.stories.tsx +19 -0
- package/src/components/ui/radio-group.test.tsx +14 -0
- package/src/components/ui/radio-group.tsx +15 -20
- package/src/components/ui/resizable.stories.tsx +19 -0
- package/src/components/ui/resizable.test.tsx +14 -0
- package/src/components/ui/resizable.tsx +37 -30
- package/src/components/ui/scroll-area.stories.tsx +19 -0
- package/src/components/ui/scroll-area.test.tsx +14 -0
- package/src/components/ui/scroll-area.tsx +41 -19
- package/src/components/ui/select.stories.tsx +19 -0
- package/src/components/ui/select.test.tsx +14 -0
- package/src/components/ui/select.tsx +145 -78
- package/src/components/ui/separator.stories.tsx +19 -0
- package/src/components/ui/separator.test.tsx +14 -0
- package/src/components/ui/separator.tsx +8 -11
- package/src/components/ui/sheet.stories.tsx +19 -0
- package/src/components/ui/sheet.test.tsx +14 -0
- package/src/components/ui/sheet.tsx +56 -71
- package/src/components/ui/sidebar.stories.tsx +18 -0
- package/src/components/ui/sidebar.test.tsx +13 -0
- package/src/components/ui/sidebar.tsx +469 -513
- package/src/components/ui/skeleton.stories.tsx +19 -0
- package/src/components/ui/skeleton.test.tsx +14 -0
- package/src/components/ui/skeleton.tsx +8 -114
- package/src/components/ui/slider.stories.tsx +19 -0
- package/src/components/ui/slider.test.tsx +14 -0
- package/src/components/ui/slider.tsx +31 -40
- package/src/components/ui/sonner.stories.tsx +19 -0
- package/src/components/ui/sonner.test.tsx +14 -0
- package/src/components/ui/sonner.tsx +39 -7
- package/src/components/ui/spinner.stories.tsx +18 -0
- package/src/components/ui/spinner.test.tsx +13 -0
- package/src/components/ui/spinner.tsx +3 -7
- package/src/components/ui/switch.stories.tsx +19 -0
- package/src/components/ui/switch.test.tsx +14 -0
- package/src/components/ui/switch.tsx +4 -7
- package/src/components/ui/table.stories.tsx +19 -0
- package/src/components/ui/table.test.tsx +14 -0
- package/src/components/ui/table.tsx +17 -17
- package/src/components/ui/tabs.stories.tsx +19 -0
- package/src/components/ui/tabs.test.tsx +14 -0
- package/src/components/ui/tabs.tsx +71 -31
- package/src/components/ui/textarea.stories.tsx +19 -0
- package/src/components/ui/textarea.test.tsx +14 -0
- package/src/components/ui/textarea.tsx +7 -5
- package/src/components/ui/theme-toggle.stories.tsx +18 -0
- package/src/components/ui/theme-toggle.test.tsx +13 -0
- package/src/components/ui/theme-toggle.tsx +35 -37
- package/src/components/ui/toggle-group.stories.tsx +19 -0
- package/src/components/ui/toggle-group.test.tsx +14 -0
- package/src/components/ui/toggle-group.tsx +41 -25
- package/src/components/ui/toggle.stories.tsx +19 -0
- package/src/components/ui/toggle.test.tsx +14 -0
- package/src/components/ui/toggle.tsx +17 -19
- package/src/components/ui/tooltip.stories.tsx +19 -0
- package/src/components/ui/tooltip.test.tsx +14 -0
- package/src/components/ui/tooltip.tsx +53 -44
- package/src/demo/AIDemo.tsx +50 -0
- package/src/demo/ButtonsDemo.tsx +41 -18
- package/src/demo/CalendarDemo.tsx +1 -1
- package/src/demo/FeedbackDemo.tsx +4 -6
- package/src/demo/FormsDemo.tsx +34 -11
- package/src/demo/NavigationDemo.tsx +38 -13
- package/src/demo/OverlaysDemo.tsx +22 -36
- package/src/demo/update-demos.js +82 -0
- package/src/index.css +66 -46
- package/src/lib/utils.ts +3 -3
- package/src/stories/Button.stories.ts +54 -0
- package/src/stories/Button.tsx +36 -0
- package/src/stories/Configure.mdx +388 -0
- package/src/stories/Header.stories.ts +34 -0
- package/src/stories/Header.tsx +55 -0
- package/src/stories/Page.stories.ts +33 -0
- package/src/stories/Page.tsx +73 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +68 -0
|
@@ -12,8 +12,7 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
|
12
12
|
<fieldset
|
|
13
13
|
data-slot="field-set"
|
|
14
14
|
className={cn(
|
|
15
|
-
"flex flex-col gap-
|
|
16
|
-
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
15
|
+
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
17
16
|
className
|
|
18
17
|
)}
|
|
19
18
|
{...props}
|
|
@@ -31,9 +30,7 @@ function FieldLegend({
|
|
|
31
30
|
data-slot="field-legend"
|
|
32
31
|
data-variant={variant}
|
|
33
32
|
className={cn(
|
|
34
|
-
"mb-
|
|
35
|
-
"data-[variant=legend]:text-base",
|
|
36
|
-
"data-[variant=label]:text-sm",
|
|
33
|
+
"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
|
37
34
|
className
|
|
38
35
|
)}
|
|
39
36
|
{...props}
|
|
@@ -46,7 +43,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
46
43
|
<div
|
|
47
44
|
data-slot="field-group"
|
|
48
45
|
className={cn(
|
|
49
|
-
"group/field-group @container/field-group flex w-full flex-col gap-
|
|
46
|
+
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
|
50
47
|
className
|
|
51
48
|
)}
|
|
52
49
|
{...props}
|
|
@@ -55,21 +52,15 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
const fieldVariants = cva(
|
|
58
|
-
"group/field data-[invalid=true]:text-destructive
|
|
55
|
+
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
|
|
59
56
|
{
|
|
60
57
|
variants: {
|
|
61
58
|
orientation: {
|
|
62
|
-
vertical:
|
|
63
|
-
horizontal:
|
|
64
|
-
"flex-row items-center",
|
|
65
|
-
|
|
66
|
-
"has-[>[data-slot=field-content]]:[
|
|
67
|
-
],
|
|
68
|
-
responsive: [
|
|
69
|
-
"@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
|
|
70
|
-
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
71
|
-
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
72
|
-
],
|
|
59
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
60
|
+
horizontal:
|
|
61
|
+
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
62
|
+
responsive:
|
|
63
|
+
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
73
64
|
},
|
|
74
65
|
},
|
|
75
66
|
defaultVariants: {
|
|
@@ -99,7 +90,7 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
99
90
|
<div
|
|
100
91
|
data-slot="field-content"
|
|
101
92
|
className={cn(
|
|
102
|
-
"group/field-content flex flex-1 flex-col gap-
|
|
93
|
+
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
|
103
94
|
className
|
|
104
95
|
)}
|
|
105
96
|
{...props}
|
|
@@ -115,9 +106,8 @@ function FieldLabel({
|
|
|
115
106
|
<Label
|
|
116
107
|
data-slot="field-label"
|
|
117
108
|
className={cn(
|
|
118
|
-
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
|
119
|
-
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col
|
|
120
|
-
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
|
|
109
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
|
110
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
|
121
111
|
className
|
|
122
112
|
)}
|
|
123
113
|
{...props}
|
|
@@ -130,7 +120,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
130
120
|
<div
|
|
131
121
|
data-slot="field-label"
|
|
132
122
|
className={cn(
|
|
133
|
-
"flex w-fit items-center gap-2 text-sm font-medium
|
|
123
|
+
"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
|
|
134
124
|
className
|
|
135
125
|
)}
|
|
136
126
|
{...props}
|
|
@@ -143,9 +133,9 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
143
133
|
<p
|
|
144
134
|
data-slot="field-description"
|
|
145
135
|
className={cn(
|
|
146
|
-
"text-
|
|
147
|
-
"
|
|
148
|
-
"[&>a
|
|
136
|
+
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
137
|
+
"last:mt-0 nth-last-2:-mt-1",
|
|
138
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
149
139
|
className
|
|
150
140
|
)}
|
|
151
141
|
{...props}
|
|
@@ -173,7 +163,7 @@ function FieldSeparator({
|
|
|
173
163
|
<Separator className="absolute inset-0 top-1/2" />
|
|
174
164
|
{children && (
|
|
175
165
|
<span
|
|
176
|
-
className="
|
|
166
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
177
167
|
data-slot="field-separator-content"
|
|
178
168
|
>
|
|
179
169
|
{children}
|
|
@@ -196,17 +186,21 @@ function FieldError({
|
|
|
196
186
|
return children
|
|
197
187
|
}
|
|
198
188
|
|
|
199
|
-
if (!errors) {
|
|
189
|
+
if (!errors?.length) {
|
|
200
190
|
return null
|
|
201
191
|
}
|
|
202
192
|
|
|
203
|
-
|
|
204
|
-
|
|
193
|
+
const uniqueErrors = [
|
|
194
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
if (uniqueErrors?.length == 1) {
|
|
198
|
+
return uniqueErrors[0]?.message
|
|
205
199
|
}
|
|
206
200
|
|
|
207
201
|
return (
|
|
208
202
|
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
209
|
-
{
|
|
203
|
+
{uniqueErrors.map(
|
|
210
204
|
(error, index) =>
|
|
211
205
|
error?.message && <li key={index}>{error.message}</li>
|
|
212
206
|
)}
|
|
@@ -222,7 +216,7 @@ function FieldError({
|
|
|
222
216
|
<div
|
|
223
217
|
role="alert"
|
|
224
218
|
data-slot="field-error"
|
|
225
|
-
className={cn("text-
|
|
219
|
+
className={cn("text-sm font-normal text-destructive", className)}
|
|
226
220
|
{...props}
|
|
227
221
|
>
|
|
228
222
|
{content}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "./form"; import { useForm } from "react-hook-form"; import { Input } from "./input";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Form',
|
|
6
|
+
parameters: { layout: 'centered' },
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
} satisfies Meta;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => {
|
|
15
|
+
const form = useForm({ defaultValues: { username: "" }});
|
|
16
|
+
return (
|
|
17
|
+
<div className="p-4 w-full h-full flex items-center justify-center">
|
|
18
|
+
<Form {...form}><form className="space-y-8"><FormField control={form.control} name="username" render={({ field }) => (<FormItem><FormLabel>Username</FormLabel><FormControl><Input placeholder="shadcn" {...field} /></FormControl><FormDescription>This is your public display name.</FormDescription><FormMessage /></FormItem>)} /></form></Form>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { axe } from 'jest-axe';
|
|
3
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "./form"; import { useForm } from "react-hook-form"; import { Input } from "./input";
|
|
4
|
+
|
|
5
|
+
describe('Form Accessibility', () => {
|
|
6
|
+
it('should have no accessibility violations in basic render', async () => {
|
|
7
|
+
const FormWrapper = () => {
|
|
8
|
+
const form = useForm({ defaultValues: { username: "" }});
|
|
9
|
+
return (<Form {...form}><form className="space-y-8"><FormField control={form.control} name="username" render={({ field }) => (<FormItem><FormLabel>Username</FormLabel><FormControl><Input placeholder="shadcn" {...field} /></FormControl><FormDescription>This is your public display name.</FormDescription><FormMessage /></FormItem>)} /></form></Form>);
|
|
10
|
+
};
|
|
11
|
+
const { container } = render(<FormWrapper />);
|
|
12
|
+
const results = await axe(container);
|
|
13
|
+
expect(results).toHaveNoViolations();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
5
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
6
5
|
import {
|
|
7
6
|
Controller,
|
|
@@ -90,7 +89,7 @@ function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
90
89
|
function FormLabel({
|
|
91
90
|
className,
|
|
92
91
|
...props
|
|
93
|
-
}: React.ComponentProps<
|
|
92
|
+
}: React.ComponentProps<"label">) {
|
|
94
93
|
const { error, formItemId } = useFormField();
|
|
95
94
|
|
|
96
95
|
return (
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { HoverCard, HoverCardTrigger, HoverCardContent } from "./hover-card";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/HoverCard',
|
|
6
|
+
parameters: { layout: 'centered' },
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
} satisfies Meta;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<div className="p-4 w-full h-full flex items-center justify-center">
|
|
16
|
+
<HoverCard><HoverCardTrigger className="underline cursor-pointer">@nextjs</HoverCardTrigger><HoverCardContent>The React Framework – created and maintained by @vercel.</HoverCardContent></HoverCard>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { axe } from 'jest-axe';
|
|
3
|
+
import { HoverCard, HoverCardTrigger, HoverCardContent } from "./hover-card";
|
|
4
|
+
|
|
5
|
+
describe('HoverCard Accessibility', () => {
|
|
6
|
+
it('should have no accessibility violations in basic render', async () => {
|
|
7
|
+
const Wrapper = () => (
|
|
8
|
+
<HoverCard><HoverCardTrigger className="underline cursor-pointer">@nextjs</HoverCardTrigger><HoverCardContent>The React Framework – created and maintained by @vercel.</HoverCardContent></HoverCard>
|
|
9
|
+
);
|
|
10
|
+
const { container } = render(<Wrapper />);
|
|
11
|
+
const results = await axe(container);
|
|
12
|
+
expect(results).toHaveNoViolations();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
|
3
|
+
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
|
|
5
4
|
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
7
6
|
|
|
8
|
-
function HoverCard({
|
|
9
|
-
...props
|
|
10
|
-
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
|
11
|
-
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
|
|
7
|
+
function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
|
|
8
|
+
return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
function HoverCardTrigger({
|
|
15
|
-
className,
|
|
16
|
-
...props
|
|
17
|
-
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
|
11
|
+
function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
|
|
18
12
|
return (
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
className={cn("cursor-pointer", className)}
|
|
22
|
-
{...props}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
13
|
+
<PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
|
14
|
+
)
|
|
25
15
|
}
|
|
26
16
|
|
|
27
17
|
function HoverCardContent({
|
|
28
18
|
className,
|
|
29
|
-
|
|
19
|
+
side = "bottom",
|
|
30
20
|
sideOffset = 4,
|
|
21
|
+
align = "center",
|
|
22
|
+
alignOffset = 4,
|
|
31
23
|
...props
|
|
32
|
-
}:
|
|
24
|
+
}: PreviewCardPrimitive.Popup.Props &
|
|
25
|
+
Pick<
|
|
26
|
+
PreviewCardPrimitive.Positioner.Props,
|
|
27
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
28
|
+
>) {
|
|
33
29
|
return (
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
data-slot="hover-card-content"
|
|
30
|
+
<PreviewCardPrimitive.Portal data-slot="hover-card-portal">
|
|
31
|
+
<PreviewCardPrimitive.Positioner
|
|
37
32
|
align={align}
|
|
33
|
+
alignOffset={alignOffset}
|
|
34
|
+
side={side}
|
|
38
35
|
sideOffset={sideOffset}
|
|
39
|
-
className=
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
className="isolate z-50"
|
|
37
|
+
>
|
|
38
|
+
<PreviewCardPrimitive.Popup
|
|
39
|
+
data-slot="hover-card-content"
|
|
40
|
+
className={cn(
|
|
41
|
+
"z-50 w-64 origin-(--transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
</PreviewCardPrimitive.Positioner>
|
|
47
|
+
</PreviewCardPrimitive.Portal>
|
|
48
|
+
)
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
|
51
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'Design System/InputGroup',
|
|
5
|
+
parameters: { layout: 'centered' },
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
} satisfies Meta;
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
render: () => (
|
|
14
|
+
<div className="p-4 w-full h-full flex items-center justify-center">
|
|
15
|
+
<div className="p-4 border rounded bg-muted/50 text-muted-foreground text-sm">InputGroup — needs manual template</div>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { axe } from 'jest-axe';
|
|
3
|
+
|
|
4
|
+
describe('InputGroup Accessibility', () => {
|
|
5
|
+
it('should have no accessibility violations in basic render', async () => {
|
|
6
|
+
const Wrapper = () => (
|
|
7
|
+
<div className="p-4 border rounded bg-muted/50 text-muted-foreground text-sm">InputGroup — needs manual template</div>
|
|
8
|
+
);
|
|
9
|
+
const { container } = render(<Wrapper />);
|
|
10
|
+
const results = await axe(container);
|
|
11
|
+
expect(results).toHaveNoViolations();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -14,21 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
14
14
|
data-slot="input-group"
|
|
15
15
|
role="group"
|
|
16
16
|
className={cn(
|
|
17
|
-
"group/input-group border-input
|
|
18
|
-
"h-9 has-[>textarea]:h-auto",
|
|
19
|
-
|
|
20
|
-
// Variants based on alignment.
|
|
21
|
-
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
22
|
-
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
23
|
-
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
24
|
-
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
25
|
-
|
|
26
|
-
// Focus state.
|
|
27
|
-
"has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
|
|
28
|
-
|
|
29
|
-
// Error state.
|
|
30
|
-
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
31
|
-
|
|
17
|
+
"group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
|
|
32
18
|
className
|
|
33
19
|
)}
|
|
34
20
|
{...props}
|
|
@@ -37,18 +23,18 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
37
23
|
}
|
|
38
24
|
|
|
39
25
|
const inputGroupAddonVariants = cva(
|
|
40
|
-
"
|
|
26
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
41
27
|
{
|
|
42
28
|
variants: {
|
|
43
29
|
align: {
|
|
44
30
|
"inline-start":
|
|
45
|
-
"order-first pl-
|
|
31
|
+
"order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
|
|
46
32
|
"inline-end":
|
|
47
|
-
"order-last pr-
|
|
33
|
+
"order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
|
|
48
34
|
"block-start":
|
|
49
|
-
"
|
|
35
|
+
"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
|
|
50
36
|
"block-end":
|
|
51
|
-
"
|
|
37
|
+
"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
|
|
52
38
|
},
|
|
53
39
|
},
|
|
54
40
|
defaultVariants: {
|
|
@@ -74,6 +60,11 @@ function InputGroupAddon({
|
|
|
74
60
|
}
|
|
75
61
|
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
76
62
|
}}
|
|
63
|
+
onKeyDown={(e) => {
|
|
64
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
65
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
77
68
|
{...props}
|
|
78
69
|
/>
|
|
79
70
|
)
|
|
@@ -84,10 +75,10 @@ const inputGroupButtonVariants = cva(
|
|
|
84
75
|
{
|
|
85
76
|
variants: {
|
|
86
77
|
size: {
|
|
87
|
-
xs: "h-6 gap-1 rounded-[calc(var(--radius)-
|
|
88
|
-
sm: "
|
|
78
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
|
79
|
+
sm: "",
|
|
89
80
|
"icon-xs":
|
|
90
|
-
"size-6 rounded-[calc(var(--radius)-
|
|
81
|
+
"size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
|
|
91
82
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
92
83
|
},
|
|
93
84
|
},
|
|
@@ -103,8 +94,10 @@ function InputGroupButton({
|
|
|
103
94
|
variant = "ghost",
|
|
104
95
|
size = "xs",
|
|
105
96
|
...props
|
|
106
|
-
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
|
107
|
-
VariantProps<typeof inputGroupButtonVariants>
|
|
97
|
+
}: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
|
|
98
|
+
VariantProps<typeof inputGroupButtonVariants> & {
|
|
99
|
+
type?: "button" | "submit" | "reset"
|
|
100
|
+
}) {
|
|
108
101
|
return (
|
|
109
102
|
<Button
|
|
110
103
|
type={type}
|
|
@@ -120,7 +113,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
|
120
113
|
return (
|
|
121
114
|
<span
|
|
122
115
|
className={cn(
|
|
123
|
-
"
|
|
116
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
124
117
|
className
|
|
125
118
|
)}
|
|
126
119
|
{...props}
|
|
@@ -136,7 +129,7 @@ function InputGroupInput({
|
|
|
136
129
|
<Input
|
|
137
130
|
data-slot="input-group-control"
|
|
138
131
|
className={cn(
|
|
139
|
-
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
132
|
+
"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
|
|
140
133
|
className
|
|
141
134
|
)}
|
|
142
135
|
{...props}
|
|
@@ -152,7 +145,7 @@ function InputGroupTextarea({
|
|
|
152
145
|
<Textarea
|
|
153
146
|
data-slot="input-group-control"
|
|
154
147
|
className={cn(
|
|
155
|
-
"flex-1 resize-none rounded-none border-0 bg-transparent py-
|
|
148
|
+
"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
|
|
156
149
|
className
|
|
157
150
|
)}
|
|
158
151
|
{...props}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "./input-otp";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/InputOtp',
|
|
6
|
+
parameters: { layout: 'centered' },
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
} satisfies Meta;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<div className="p-4 w-full h-full flex items-center justify-center">
|
|
16
|
+
<div><label htmlFor="otp-input">Verification Code</label><InputOTP id="otp-input" maxLength={6}><InputOTPGroup><InputOTPSlot index={0} /><InputOTPSlot index={1} /><InputOTPSlot index={2} /></InputOTPGroup><InputOTPSeparator /><InputOTPGroup><InputOTPSlot index={3} /><InputOTPSlot index={4} /><InputOTPSlot index={5} /></InputOTPGroup></InputOTP></div>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { axe } from 'jest-axe';
|
|
3
|
+
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "./input-otp";
|
|
4
|
+
|
|
5
|
+
describe('InputOtp Accessibility', () => {
|
|
6
|
+
it('should have no accessibility violations in basic render', async () => {
|
|
7
|
+
const Wrapper = () => (
|
|
8
|
+
<div><label htmlFor="otp-input">Verification Code</label><InputOTP id="otp-input" maxLength={6}><InputOTPGroup><InputOTPSlot index={0} /><InputOTPSlot index={1} /><InputOTPSlot index={2} /></InputOTPGroup><InputOTPSeparator /><InputOTPGroup><InputOTPSlot index={3} /><InputOTPSlot index={4} /><InputOTPSlot index={5} /></InputOTPGroup></InputOTP></div>
|
|
9
|
+
);
|
|
10
|
+
const { container } = render(<Wrapper />);
|
|
11
|
+
const results = await axe(container);
|
|
12
|
+
expect(results).toHaveNoViolations();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -2,47 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { OTPInput, OTPInputContext } from "input-otp"
|
|
5
|
-
import { Dot } from "lucide-react"
|
|
6
5
|
|
|
7
6
|
import { cn } from "@/lib/utils"
|
|
7
|
+
import { MinusIcon } from "lucide-react"
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
|
|
9
|
+
function InputOTP({
|
|
10
|
+
className,
|
|
11
|
+
containerClassName,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof OTPInput> & {
|
|
14
|
+
containerClassName?: string
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<OTPInput
|
|
18
|
+
data-slot="input-otp"
|
|
19
|
+
containerClassName={cn(
|
|
20
|
+
"cn-input-otp flex items-center has-disabled:opacity-50",
|
|
21
|
+
containerClassName
|
|
22
|
+
)}
|
|
23
|
+
spellCheck={false}
|
|
24
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-slot="input-otp-group"
|
|
34
|
+
className={cn(
|
|
35
|
+
"flex items-center rounded-lg has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
function InputOTPSlot({
|
|
44
|
+
index,
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<"div"> & {
|
|
48
|
+
index: number
|
|
49
|
+
}) {
|
|
37
50
|
const inputOTPContext = React.useContext(OTPInputContext)
|
|
38
|
-
const { char, hasFakeCaret, isActive } = inputOTPContext
|
|
51
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
|
39
52
|
|
|
40
53
|
return (
|
|
41
54
|
<div
|
|
42
|
-
|
|
55
|
+
data-slot="input-otp-slot"
|
|
56
|
+
data-active={isActive}
|
|
43
57
|
className={cn(
|
|
44
|
-
"relative flex
|
|
45
|
-
isActive && "z-10 ring-2 ring-ring ring-offset-background",
|
|
58
|
+
"relative flex size-8 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-lg first:border-l last:rounded-r-lg aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-3 data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",
|
|
46
59
|
className
|
|
47
60
|
)}
|
|
48
61
|
{...props}
|
|
@@ -55,17 +68,20 @@ const InputOTPSlot = React.forwardRef<
|
|
|
55
68
|
)}
|
|
56
69
|
</div>
|
|
57
70
|
)
|
|
58
|
-
}
|
|
59
|
-
InputOTPSlot.displayName = "InputOTPSlot"
|
|
71
|
+
}
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="input-otp-separator"
|
|
77
|
+
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
|
|
78
|
+
role="separator"
|
|
79
|
+
{...props}
|
|
80
|
+
>
|
|
81
|
+
<MinusIcon
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
70
86
|
|
|
71
87
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Input } from "./input";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Design System/Input',
|
|
6
|
+
parameters: { layout: 'centered' },
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
} satisfies Meta;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<div className="p-4 w-full h-full flex items-center justify-center">
|
|
16
|
+
<Input type="email" placeholder="Email" />
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
};
|