@auto-engineer/generate-react-client 1.12.1

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +10 -0
  3. package/dist/src/commands/generate-react-client.d.ts +21 -0
  4. package/dist/src/commands/generate-react-client.d.ts.map +1 -0
  5. package/dist/src/commands/generate-react-client.js +62 -0
  6. package/dist/src/commands/generate-react-client.js.map +1 -0
  7. package/dist/src/copy-starter.d.ts +2 -0
  8. package/dist/src/copy-starter.d.ts.map +1 -0
  9. package/dist/src/copy-starter.js +34 -0
  10. package/dist/src/copy-starter.js.map +1 -0
  11. package/dist/src/index.d.ts +10 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/starter/.storybook/main.ts +33 -0
  16. package/dist/starter/.storybook/preview.tsx +35 -0
  17. package/dist/starter/components.json +29 -0
  18. package/dist/starter/index.html +12 -0
  19. package/dist/starter/package.json +60 -0
  20. package/dist/starter/pnpm-lock.yaml +5236 -0
  21. package/dist/starter/public/mockServiceWorker.js +336 -0
  22. package/dist/starter/src/App.tsx +15 -0
  23. package/dist/starter/src/components/.gitkeep +0 -0
  24. package/dist/starter/src/components/ui/Accordion.stories.tsx +47 -0
  25. package/dist/starter/src/components/ui/Accordion.tsx +51 -0
  26. package/dist/starter/src/components/ui/Alert.stories.tsx +27 -0
  27. package/dist/starter/src/components/ui/Alert.tsx +49 -0
  28. package/dist/starter/src/components/ui/AlertDialog.stories.tsx +65 -0
  29. package/dist/starter/src/components/ui/AlertDialog.tsx +163 -0
  30. package/dist/starter/src/components/ui/AspectRatio.stories.tsx +33 -0
  31. package/dist/starter/src/components/ui/AspectRatio.tsx +9 -0
  32. package/dist/starter/src/components/ui/Avatar.stories.tsx +42 -0
  33. package/dist/starter/src/components/ui/Avatar.tsx +87 -0
  34. package/dist/starter/src/components/ui/Badge.stories.tsx +36 -0
  35. package/dist/starter/src/components/ui/Badge.tsx +40 -0
  36. package/dist/starter/src/components/ui/Breadcrumb.stories.tsx +52 -0
  37. package/dist/starter/src/components/ui/Breadcrumb.tsx +92 -0
  38. package/dist/starter/src/components/ui/Button.stories.tsx +92 -0
  39. package/dist/starter/src/components/ui/Button.tsx +62 -0
  40. package/dist/starter/src/components/ui/ButtonGroup.stories.tsx +30 -0
  41. package/dist/starter/src/components/ui/ButtonGroup.tsx +75 -0
  42. package/dist/starter/src/components/ui/Calendar.stories.tsx +38 -0
  43. package/dist/starter/src/components/ui/Calendar.tsx +159 -0
  44. package/dist/starter/src/components/ui/Card.stories.tsx +42 -0
  45. package/dist/starter/src/components/ui/Card.tsx +56 -0
  46. package/dist/starter/src/components/ui/Carousel.stories.tsx +54 -0
  47. package/dist/starter/src/components/ui/Carousel.tsx +216 -0
  48. package/dist/starter/src/components/ui/Chart.stories.tsx +38 -0
  49. package/dist/starter/src/components/ui/Chart.tsx +296 -0
  50. package/dist/starter/src/components/ui/Checkbox.stories.tsx +31 -0
  51. package/dist/starter/src/components/ui/Checkbox.tsx +29 -0
  52. package/dist/starter/src/components/ui/Collapsible.stories.tsx +56 -0
  53. package/dist/starter/src/components/ui/Collapsible.tsx +15 -0
  54. package/dist/starter/src/components/ui/Combobox.stories.tsx +73 -0
  55. package/dist/starter/src/components/ui/Combobox.tsx +267 -0
  56. package/dist/starter/src/components/ui/Command.stories.tsx +69 -0
  57. package/dist/starter/src/components/ui/Command.tsx +137 -0
  58. package/dist/starter/src/components/ui/ContextMenu.stories.tsx +66 -0
  59. package/dist/starter/src/components/ui/ContextMenu.tsx +211 -0
  60. package/dist/starter/src/components/ui/DesignSystem-Colors.mdx +68 -0
  61. package/dist/starter/src/components/ui/DesignSystem-Colors.stories.tsx +116 -0
  62. package/dist/starter/src/components/ui/DesignSystem-Layout.mdx +64 -0
  63. package/dist/starter/src/components/ui/DesignSystem-Layout.stories.tsx +166 -0
  64. package/dist/starter/src/components/ui/DesignSystem-Typography.mdx +31 -0
  65. package/dist/starter/src/components/ui/DesignSystem-Typography.stories.tsx +79 -0
  66. package/dist/starter/src/components/ui/Dialog.stories.tsx +72 -0
  67. package/dist/starter/src/components/ui/Dialog.tsx +136 -0
  68. package/dist/starter/src/components/ui/Direction.stories.tsx +36 -0
  69. package/dist/starter/src/components/ui/Direction.tsx +18 -0
  70. package/dist/starter/src/components/ui/Drawer.stories.tsx +68 -0
  71. package/dist/starter/src/components/ui/Drawer.tsx +106 -0
  72. package/dist/starter/src/components/ui/DropdownMenu.stories.tsx +72 -0
  73. package/dist/starter/src/components/ui/DropdownMenu.tsx +219 -0
  74. package/dist/starter/src/components/ui/Empty.stories.tsx +35 -0
  75. package/dist/starter/src/components/ui/Empty.tsx +85 -0
  76. package/dist/starter/src/components/ui/Field.stories.tsx +47 -0
  77. package/dist/starter/src/components/ui/Field.tsx +226 -0
  78. package/dist/starter/src/components/ui/Form.stories.tsx +44 -0
  79. package/dist/starter/src/components/ui/Form.tsx +136 -0
  80. package/dist/starter/src/components/ui/HoverCard.stories.tsx +47 -0
  81. package/dist/starter/src/components/ui/HoverCard.tsx +36 -0
  82. package/dist/starter/src/components/ui/Input.stories.tsx +38 -0
  83. package/dist/starter/src/components/ui/Input.tsx +21 -0
  84. package/dist/starter/src/components/ui/InputGroup.stories.tsx +50 -0
  85. package/dist/starter/src/components/ui/InputGroup.tsx +147 -0
  86. package/dist/starter/src/components/ui/InputOTP.stories.tsx +40 -0
  87. package/dist/starter/src/components/ui/InputOTP.tsx +68 -0
  88. package/dist/starter/src/components/ui/Item.stories.tsx +61 -0
  89. package/dist/starter/src/components/ui/Item.tsx +158 -0
  90. package/dist/starter/src/components/ui/Kbd.stories.tsx +54 -0
  91. package/dist/starter/src/components/ui/Kbd.tsx +18 -0
  92. package/dist/starter/src/components/ui/Label.stories.tsx +85 -0
  93. package/dist/starter/src/components/ui/Label.tsx +40 -0
  94. package/dist/starter/src/components/ui/Menubar.stories.tsx +76 -0
  95. package/dist/starter/src/components/ui/Menubar.tsx +236 -0
  96. package/dist/starter/src/components/ui/NativeSelect.stories.tsx +42 -0
  97. package/dist/starter/src/components/ui/NativeSelect.tsx +44 -0
  98. package/dist/starter/src/components/ui/NavigationMenu.stories.tsx +78 -0
  99. package/dist/starter/src/components/ui/NavigationMenu.tsx +142 -0
  100. package/dist/starter/src/components/ui/Pagination.stories.tsx +75 -0
  101. package/dist/starter/src/components/ui/Pagination.tsx +100 -0
  102. package/dist/starter/src/components/ui/Popover.stories.tsx +51 -0
  103. package/dist/starter/src/components/ui/Popover.tsx +52 -0
  104. package/dist/starter/src/components/ui/Progress.stories.tsx +28 -0
  105. package/dist/starter/src/components/ui/Progress.tsx +24 -0
  106. package/dist/starter/src/components/ui/RadioGroup.stories.tsx +48 -0
  107. package/dist/starter/src/components/ui/RadioGroup.tsx +31 -0
  108. package/dist/starter/src/components/ui/Resizable.stories.tsx +69 -0
  109. package/dist/starter/src/components/ui/Resizable.tsx +47 -0
  110. package/dist/starter/src/components/ui/ScrollArea.stories.tsx +43 -0
  111. package/dist/starter/src/components/ui/ScrollArea.tsx +46 -0
  112. package/dist/starter/src/components/ui/Select.stories.tsx +57 -0
  113. package/dist/starter/src/components/ui/Select.tsx +162 -0
  114. package/dist/starter/src/components/ui/Separator.stories.tsx +40 -0
  115. package/dist/starter/src/components/ui/Separator.tsx +26 -0
  116. package/dist/starter/src/components/ui/Sheet.stories.tsx +66 -0
  117. package/dist/starter/src/components/ui/Sheet.tsx +107 -0
  118. package/dist/starter/src/components/ui/Sidebar.stories.tsx +94 -0
  119. package/dist/starter/src/components/ui/Sidebar.tsx +675 -0
  120. package/dist/starter/src/components/ui/Skeleton.stories.tsx +38 -0
  121. package/dist/starter/src/components/ui/Skeleton.tsx +7 -0
  122. package/dist/starter/src/components/ui/Slider.stories.tsx +21 -0
  123. package/dist/starter/src/components/ui/Slider.tsx +54 -0
  124. package/dist/starter/src/components/ui/Sonner.stories.tsx +44 -0
  125. package/dist/starter/src/components/ui/Sonner.tsx +34 -0
  126. package/dist/starter/src/components/ui/Spinner.stories.tsx +23 -0
  127. package/dist/starter/src/components/ui/Spinner.tsx +9 -0
  128. package/dist/starter/src/components/ui/Switch.stories.tsx +35 -0
  129. package/dist/starter/src/components/ui/Switch.tsx +33 -0
  130. package/dist/starter/src/components/ui/Table.stories.tsx +65 -0
  131. package/dist/starter/src/components/ui/Table.tsx +75 -0
  132. package/dist/starter/src/components/ui/Tabs.stories.tsx +51 -0
  133. package/dist/starter/src/components/ui/Tabs.tsx +69 -0
  134. package/dist/starter/src/components/ui/Textarea.stories.tsx +24 -0
  135. package/dist/starter/src/components/ui/Textarea.tsx +18 -0
  136. package/dist/starter/src/components/ui/Toast.stories.tsx +112 -0
  137. package/dist/starter/src/components/ui/Toast.tsx +114 -0
  138. package/dist/starter/src/components/ui/Toaster.tsx +28 -0
  139. package/dist/starter/src/components/ui/Toggle.stories.tsx +40 -0
  140. package/dist/starter/src/components/ui/Toggle.tsx +41 -0
  141. package/dist/starter/src/components/ui/ToggleGroup.stories.tsx +58 -0
  142. package/dist/starter/src/components/ui/ToggleGroup.tsx +80 -0
  143. package/dist/starter/src/components/ui/Tooltip.stories.tsx +40 -0
  144. package/dist/starter/src/components/ui/Tooltip.tsx +42 -0
  145. package/dist/starter/src/hooks/use-mobile.ts +19 -0
  146. package/dist/starter/src/hooks/use-toast.ts +186 -0
  147. package/dist/starter/src/index.css +123 -0
  148. package/dist/starter/src/lib/utils.ts +6 -0
  149. package/dist/starter/src/main.tsx +5 -0
  150. package/dist/starter/tsconfig.app.json +25 -0
  151. package/dist/starter/tsconfig.json +4 -0
  152. package/dist/starter/vite.config.ts +16 -0
  153. package/dist/tsconfig.tsbuildinfo +1 -0
  154. package/package.json +37 -0
@@ -0,0 +1,136 @@
1
+ import * as React from 'react';
2
+ import type { Label as LabelPrimitive } from 'radix-ui';
3
+ import { Slot } from 'radix-ui';
4
+ import {
5
+ Controller,
6
+ FormProvider,
7
+ useFormContext,
8
+ useFormState,
9
+ type ControllerProps,
10
+ type FieldPath,
11
+ type FieldValues,
12
+ } from 'react-hook-form';
13
+
14
+ import { cn } from '@/lib/utils';
15
+ import { Label } from '@/components/ui/Label';
16
+
17
+ const Form = FormProvider;
18
+
19
+ type FormFieldContextValue<
20
+ TFieldValues extends FieldValues = FieldValues,
21
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
22
+ > = {
23
+ name: TName;
24
+ };
25
+
26
+ const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
27
+
28
+ const FormField = <
29
+ TFieldValues extends FieldValues = FieldValues,
30
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
31
+ >({
32
+ ...props
33
+ }: ControllerProps<TFieldValues, TName>) => {
34
+ return (
35
+ <FormFieldContext.Provider value={{ name: props.name }}>
36
+ <Controller {...props} />
37
+ </FormFieldContext.Provider>
38
+ );
39
+ };
40
+
41
+ const useFormField = () => {
42
+ const fieldContext = React.useContext(FormFieldContext);
43
+ const itemContext = React.useContext(FormItemContext);
44
+ const { getFieldState } = useFormContext();
45
+ const formState = useFormState({ name: fieldContext.name });
46
+ const fieldState = getFieldState(fieldContext.name, formState);
47
+
48
+ if (!fieldContext) {
49
+ throw new Error('useFormField should be used within <FormField>');
50
+ }
51
+
52
+ const { id } = itemContext;
53
+
54
+ return {
55
+ id,
56
+ name: fieldContext.name,
57
+ formItemId: `${id}-form-item`,
58
+ formDescriptionId: `${id}-form-item-description`,
59
+ formMessageId: `${id}-form-item-message`,
60
+ ...fieldState,
61
+ };
62
+ };
63
+
64
+ type FormItemContextValue = {
65
+ id: string;
66
+ };
67
+
68
+ const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
69
+
70
+ function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
71
+ const id = React.useId();
72
+
73
+ return (
74
+ <FormItemContext.Provider value={{ id }}>
75
+ <div data-slot="form-item" className={cn('grid gap-2', className)} {...props} />
76
+ </FormItemContext.Provider>
77
+ );
78
+ }
79
+
80
+ function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
81
+ const { error, formItemId } = useFormField();
82
+
83
+ return (
84
+ <Label
85
+ data-slot="form-label"
86
+ data-error={!!error}
87
+ className={cn('data-[error=true]:text-destructive', className)}
88
+ htmlFor={formItemId}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {
95
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
96
+
97
+ return (
98
+ <Slot.Root
99
+ data-slot="form-control"
100
+ id={formItemId}
101
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
102
+ aria-invalid={!!error}
103
+ {...props}
104
+ />
105
+ );
106
+ }
107
+
108
+ function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
109
+ const { formDescriptionId } = useFormField();
110
+
111
+ return (
112
+ <p
113
+ data-slot="form-description"
114
+ id={formDescriptionId}
115
+ className={cn('text-muted-foreground text-sm', className)}
116
+ {...props}
117
+ />
118
+ );
119
+ }
120
+
121
+ function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
122
+ const { error, formMessageId } = useFormField();
123
+ const body = error ? String(error?.message ?? '') : props.children;
124
+
125
+ if (!body) {
126
+ return null;
127
+ }
128
+
129
+ return (
130
+ <p data-slot="form-message" id={formMessageId} className={cn('text-destructive text-sm', className)} {...props}>
131
+ {body}
132
+ </p>
133
+ );
134
+ }
135
+
136
+ export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { HoverCard, HoverCardTrigger, HoverCardContent } from '@/components/ui/HoverCard';
3
+ import { CalendarDaysIcon } from 'lucide-react';
4
+
5
+ const meta: Meta<typeof HoverCard> = {
6
+ title: 'HoverCard',
7
+ component: HoverCard,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof HoverCard>;
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <HoverCard>
15
+ <HoverCardTrigger asChild>
16
+ <a href="#" className="text-sm font-medium underline underline-offset-4">
17
+ @nextjs
18
+ </a>
19
+ </HoverCardTrigger>
20
+ <HoverCardContent className="w-80">
21
+ <div className="flex justify-between space-x-4">
22
+ <div className="space-y-1">
23
+ <h4 className="text-sm font-semibold">@nextjs</h4>
24
+ <p className="text-sm">The React Framework for the Web. Created and maintained by @vercel.</p>
25
+ <div className="flex items-center pt-2">
26
+ <CalendarDaysIcon className="text-muted-foreground mr-2 h-4 w-4" />
27
+ <span className="text-muted-foreground text-xs">Joined December 2021</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </HoverCardContent>
32
+ </HoverCard>
33
+ ),
34
+ };
35
+
36
+ export const Simple: Story = {
37
+ render: () => (
38
+ <HoverCard>
39
+ <HoverCardTrigger asChild>
40
+ <span className="cursor-pointer text-sm font-medium underline underline-offset-4">Hover me</span>
41
+ </HoverCardTrigger>
42
+ <HoverCardContent>
43
+ <p className="text-sm">This is a simple hover card with some text content.</p>
44
+ </HoverCardContent>
45
+ </HoverCard>
46
+ ),
47
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { HoverCard as HoverCardPrimitive } from 'radix-ui';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ function HoverCard({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
7
+ return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
8
+ }
9
+
10
+ function HoverCardTrigger({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
11
+ return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />;
12
+ }
13
+
14
+ function HoverCardContent({
15
+ className,
16
+ align = 'center',
17
+ sideOffset = 4,
18
+ ...props
19
+ }: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
20
+ return (
21
+ <HoverCardPrimitive.Portal data-slot="hover-card-portal">
22
+ <HoverCardPrimitive.Content
23
+ data-slot="hover-card-content"
24
+ align={align}
25
+ sideOffset={sideOffset}
26
+ className={cn(
27
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
28
+ className,
29
+ )}
30
+ {...props}
31
+ />
32
+ </HoverCardPrimitive.Portal>
33
+ );
34
+ }
35
+
36
+ export { HoverCard, HoverCardTrigger, HoverCardContent };
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Input } from '@/components/ui/Input';
3
+ import { Label } from '@/components/ui/Label';
4
+
5
+ const meta: Meta<typeof Input> = {
6
+ title: 'Input',
7
+ component: Input,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof Input>;
11
+
12
+ export const Default: Story = {
13
+ args: {},
14
+ };
15
+
16
+ export const WithPlaceholder: Story = {
17
+ args: {
18
+ placeholder: 'Enter your email...',
19
+ type: 'email',
20
+ },
21
+ };
22
+
23
+ export const Disabled: Story = {
24
+ args: {
25
+ placeholder: 'Disabled input',
26
+ disabled: true,
27
+ defaultValue: 'Cannot edit this',
28
+ },
29
+ };
30
+
31
+ export const WithLabel: Story = {
32
+ render: () => (
33
+ <div className="grid w-full max-w-sm items-center gap-1.5">
34
+ <Label htmlFor="email-input">Email</Label>
35
+ <Input type="email" id="email-input" placeholder="Email" />
36
+ </div>
37
+ ),
38
+ };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
12
+ 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
13
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
@@ -0,0 +1,50 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { InputGroup, InputGroupAddon, InputGroupText, InputGroupInput } from '@/components/ui/InputGroup';
3
+ import { MailIcon, SearchIcon } from 'lucide-react';
4
+
5
+ const meta: Meta<typeof InputGroup> = {
6
+ title: 'InputGroup',
7
+ component: InputGroup,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof InputGroup>;
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <InputGroup className="max-w-sm">
15
+ <InputGroupAddon align="inline-start">
16
+ <InputGroupText>
17
+ <MailIcon />
18
+ </InputGroupText>
19
+ </InputGroupAddon>
20
+ <InputGroupInput placeholder="you@example.com" />
21
+ </InputGroup>
22
+ ),
23
+ };
24
+
25
+ export const WithPrefixAndSuffix: Story = {
26
+ render: () => (
27
+ <InputGroup className="max-w-sm">
28
+ <InputGroupAddon align="inline-start">
29
+ <InputGroupText>https://</InputGroupText>
30
+ </InputGroupAddon>
31
+ <InputGroupInput placeholder="example.com" />
32
+ <InputGroupAddon align="inline-end">
33
+ <InputGroupText>.com</InputGroupText>
34
+ </InputGroupAddon>
35
+ </InputGroup>
36
+ ),
37
+ };
38
+
39
+ export const SearchInput: Story = {
40
+ render: () => (
41
+ <InputGroup className="max-w-sm">
42
+ <InputGroupAddon align="inline-start">
43
+ <InputGroupText>
44
+ <SearchIcon />
45
+ </InputGroupText>
46
+ </InputGroupAddon>
47
+ <InputGroupInput placeholder="Search..." />
48
+ </InputGroup>
49
+ ),
50
+ };
@@ -0,0 +1,147 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '@/lib/utils';
5
+ import { Button } from '@/components/ui/Button';
6
+ import { Input } from '@/components/ui/Input';
7
+ import { Textarea } from '@/components/ui/Textarea';
8
+
9
+ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
10
+ return (
11
+ <div
12
+ data-slot="input-group"
13
+ role="group"
14
+ className={cn(
15
+ 'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none',
16
+ 'h-9 min-w-0 has-[>textarea]:h-auto',
17
+
18
+ // Variants based on alignment.
19
+ 'has-[>[data-align=inline-start]]:[&>input]:pl-2',
20
+ 'has-[>[data-align=inline-end]]:[&>input]:pr-2',
21
+ 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
22
+ 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
23
+
24
+ // Focus state.
25
+ 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
26
+
27
+ // Error state.
28
+ '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',
29
+
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ const inputGroupAddonVariants = cva(
38
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
39
+ {
40
+ variants: {
41
+ align: {
42
+ 'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
43
+ 'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',
44
+ 'block-start':
45
+ 'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5',
46
+ 'block-end': 'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5',
47
+ },
48
+ },
49
+ defaultVariants: {
50
+ align: 'inline-start',
51
+ },
52
+ },
53
+ );
54
+
55
+ function InputGroupAddon({
56
+ className,
57
+ align = 'inline-start',
58
+ ...props
59
+ }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
60
+ return (
61
+ <div
62
+ role="group"
63
+ data-slot="input-group-addon"
64
+ data-align={align}
65
+ className={cn(inputGroupAddonVariants({ align }), className)}
66
+ onClick={(e) => {
67
+ if ((e.target as HTMLElement).closest('button')) {
68
+ return;
69
+ }
70
+ e.currentTarget.parentElement?.querySelector('input')?.focus();
71
+ }}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ const inputGroupButtonVariants = cva('text-sm shadow-none flex gap-2 items-center', {
78
+ variants: {
79
+ size: {
80
+ xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
81
+ sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5',
82
+ 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
83
+ 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
84
+ },
85
+ },
86
+ defaultVariants: {
87
+ size: 'xs',
88
+ },
89
+ });
90
+
91
+ function InputGroupButton({
92
+ className,
93
+ type = 'button',
94
+ variant = 'ghost',
95
+ size = 'xs',
96
+ ...props
97
+ }: Omit<React.ComponentProps<typeof Button>, 'size'> & VariantProps<typeof inputGroupButtonVariants>) {
98
+ return (
99
+ <Button
100
+ type={type}
101
+ data-size={size}
102
+ variant={variant}
103
+ className={cn(inputGroupButtonVariants({ size }), className)}
104
+ {...props}
105
+ />
106
+ );
107
+ }
108
+
109
+ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
110
+ return (
111
+ <span
112
+ className={cn(
113
+ "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
114
+ className,
115
+ )}
116
+ {...props}
117
+ />
118
+ );
119
+ }
120
+
121
+ function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
122
+ return (
123
+ <Input
124
+ data-slot="input-group-control"
125
+ className={cn(
126
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
127
+ className,
128
+ )}
129
+ {...props}
130
+ />
131
+ );
132
+ }
133
+
134
+ function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
135
+ return (
136
+ <Textarea
137
+ data-slot="input-group-control"
138
+ className={cn(
139
+ 'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
140
+ className,
141
+ )}
142
+ {...props}
143
+ />
144
+ );
145
+ }
146
+
147
+ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea };
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from '@/components/ui/InputOTP';
3
+
4
+ const meta: Meta<typeof InputOTP> = {
5
+ title: 'InputOTP',
6
+ component: InputOTP,
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof InputOTP>;
10
+
11
+ export const Default: Story = {
12
+ render: () => (
13
+ <InputOTP maxLength={6}>
14
+ <InputOTPGroup>
15
+ <InputOTPSlot index={0} />
16
+ <InputOTPSlot index={1} />
17
+ <InputOTPSlot index={2} />
18
+ </InputOTPGroup>
19
+ <InputOTPSeparator />
20
+ <InputOTPGroup>
21
+ <InputOTPSlot index={3} />
22
+ <InputOTPSlot index={4} />
23
+ <InputOTPSlot index={5} />
24
+ </InputOTPGroup>
25
+ </InputOTP>
26
+ ),
27
+ };
28
+
29
+ export const FourDigits: Story = {
30
+ render: () => (
31
+ <InputOTP maxLength={4}>
32
+ <InputOTPGroup>
33
+ <InputOTPSlot index={0} />
34
+ <InputOTPSlot index={1} />
35
+ <InputOTPSlot index={2} />
36
+ <InputOTPSlot index={3} />
37
+ </InputOTPGroup>
38
+ </InputOTP>
39
+ ),
40
+ };
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { OTPInput, OTPInputContext } from 'input-otp';
5
+ import { MinusIcon } from 'lucide-react';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
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('flex items-center gap-2 has-disabled:opacity-50', containerClassName)}
20
+ className={cn('disabled:cursor-not-allowed', className)}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
27
+ return <div data-slot="input-otp-group" className={cn('flex items-center', className)} {...props} />;
28
+ }
29
+
30
+ function InputOTPSlot({
31
+ index,
32
+ className,
33
+ ...props
34
+ }: React.ComponentProps<'div'> & {
35
+ index: number;
36
+ }) {
37
+ const inputOTPContext = React.useContext(OTPInputContext);
38
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
39
+
40
+ return (
41
+ <div
42
+ data-slot="input-otp-slot"
43
+ data-active={isActive}
44
+ className={cn(
45
+ 'data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]',
46
+ className,
47
+ )}
48
+ {...props}
49
+ >
50
+ {char}
51
+ {hasFakeCaret && (
52
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
53
+ <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
54
+ </div>
55
+ )}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
61
+ return (
62
+ <div data-slot="input-otp-separator" role="separator" {...props}>
63
+ <MinusIcon />
64
+ </div>
65
+ );
66
+ }
67
+
68
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Item, ItemContent, ItemTitle, ItemDescription, ItemActions } from '@/components/ui/Item';
3
+ import { Button } from '@/components/ui/Button';
4
+
5
+ const meta: Meta<typeof Item> = {
6
+ title: 'Item',
7
+ component: Item,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof Item>;
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <Item>
15
+ <ItemContent>
16
+ <ItemTitle>Item Title</ItemTitle>
17
+ <ItemDescription>This is a description of the item with some details.</ItemDescription>
18
+ </ItemContent>
19
+ <ItemActions>
20
+ <Button variant="outline" size="sm">
21
+ Edit
22
+ </Button>
23
+ <Button variant="ghost" size="sm">
24
+ Delete
25
+ </Button>
26
+ </ItemActions>
27
+ </Item>
28
+ ),
29
+ };
30
+
31
+ export const OutlineVariant: Story = {
32
+ render: () => (
33
+ <Item variant="outline">
34
+ <ItemContent>
35
+ <ItemTitle>Outline Item</ItemTitle>
36
+ <ItemDescription>An item displayed with the outline variant and a visible border.</ItemDescription>
37
+ </ItemContent>
38
+ <ItemActions>
39
+ <Button variant="outline" size="sm">
40
+ View
41
+ </Button>
42
+ </ItemActions>
43
+ </Item>
44
+ ),
45
+ };
46
+
47
+ export const SmallSize: Story = {
48
+ render: () => (
49
+ <Item size="sm">
50
+ <ItemContent>
51
+ <ItemTitle>Small Item</ItemTitle>
52
+ <ItemDescription>Compact item with reduced padding.</ItemDescription>
53
+ </ItemContent>
54
+ <ItemActions>
55
+ <Button variant="ghost" size="sm">
56
+ Action
57
+ </Button>
58
+ </ItemActions>
59
+ </Item>
60
+ ),
61
+ };