create-auto-app 1.34.0 → 1.36.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.
Files changed (156) hide show
  1. package/package.json +2 -2
  2. package/templates/typical/.gitignore +1 -1
  3. package/templates/typical/auto.config.ts +85 -80
  4. package/templates/typical/client/.gitignore +0 -4
  5. package/templates/typical/client/.storybook/main.ts +0 -66
  6. package/templates/typical/client/.storybook/manager-head.html +0 -154
  7. package/templates/typical/client/.storybook/manager.ts +0 -164
  8. package/templates/typical/client/.storybook/preview-head.html +0 -31
  9. package/templates/typical/client/.storybook/preview.tsx +0 -120
  10. package/templates/typical/client/codegen.ts +0 -17
  11. package/templates/typical/client/components.json +0 -29
  12. package/templates/typical/client/index.html +0 -12
  13. package/templates/typical/client/package.json +0 -69
  14. package/templates/typical/client/pnpm-lock.yaml +0 -7868
  15. package/templates/typical/client/public/blank.svg +0 -1
  16. package/templates/typical/client/public/mockServiceWorker.js +0 -336
  17. package/templates/typical/client/src/App.tsx +0 -29
  18. package/templates/typical/client/src/components/.gitkeep +0 -0
  19. package/templates/typical/client/src/components/ui/Accordion.stories.tsx +0 -49
  20. package/templates/typical/client/src/components/ui/Accordion.tsx +0 -55
  21. package/templates/typical/client/src/components/ui/Alert.stories.tsx +0 -29
  22. package/templates/typical/client/src/components/ui/Alert.tsx +0 -56
  23. package/templates/typical/client/src/components/ui/AlertDialog.stories.tsx +0 -67
  24. package/templates/typical/client/src/components/ui/AlertDialog.tsx +0 -178
  25. package/templates/typical/client/src/components/ui/AspectRatio.stories.tsx +0 -35
  26. package/templates/typical/client/src/components/ui/AspectRatio.tsx +0 -13
  27. package/templates/typical/client/src/components/ui/Avatar.stories.tsx +0 -45
  28. package/templates/typical/client/src/components/ui/Avatar.tsx +0 -98
  29. package/templates/typical/client/src/components/ui/Badge.stories.tsx +0 -41
  30. package/templates/typical/client/src/components/ui/Badge.tsx +0 -45
  31. package/templates/typical/client/src/components/ui/Breadcrumb.stories.tsx +0 -54
  32. package/templates/typical/client/src/components/ui/Breadcrumb.tsx +0 -104
  33. package/templates/typical/client/src/components/ui/Button.stories.tsx +0 -102
  34. package/templates/typical/client/src/components/ui/Button.tsx +0 -67
  35. package/templates/typical/client/src/components/ui/ButtonGroup.stories.tsx +0 -32
  36. package/templates/typical/client/src/components/ui/ButtonGroup.tsx +0 -81
  37. package/templates/typical/client/src/components/ui/Calendar.stories.tsx +0 -40
  38. package/templates/typical/client/src/components/ui/Calendar.tsx +0 -165
  39. package/templates/typical/client/src/components/ui/Card.stories.tsx +0 -44
  40. package/templates/typical/client/src/components/ui/Card.tsx +0 -66
  41. package/templates/typical/client/src/components/ui/Carousel.stories.tsx +0 -56
  42. package/templates/typical/client/src/components/ui/Carousel.tsx +0 -225
  43. package/templates/typical/client/src/components/ui/Chart.stories.tsx +0 -39
  44. package/templates/typical/client/src/components/ui/Chart.tsx +0 -305
  45. package/templates/typical/client/src/components/ui/Checkbox.stories.tsx +0 -35
  46. package/templates/typical/client/src/components/ui/Checkbox.tsx +0 -30
  47. package/templates/typical/client/src/components/ui/Collapsible.stories.tsx +0 -58
  48. package/templates/typical/client/src/components/ui/Collapsible.tsx +0 -18
  49. package/templates/typical/client/src/components/ui/Combobox.stories.tsx +0 -75
  50. package/templates/typical/client/src/components/ui/Combobox.tsx +0 -296
  51. package/templates/typical/client/src/components/ui/Command.stories.tsx +0 -71
  52. package/templates/typical/client/src/components/ui/Command.tsx +0 -157
  53. package/templates/typical/client/src/components/ui/ContextMenu.stories.tsx +0 -68
  54. package/templates/typical/client/src/components/ui/ContextMenu.tsx +0 -231
  55. package/templates/typical/client/src/components/ui/DesignSystem-Colors.mdx +0 -68
  56. package/templates/typical/client/src/components/ui/DesignSystem-Colors.stories.tsx +0 -117
  57. package/templates/typical/client/src/components/ui/DesignSystem-Layout.mdx +0 -64
  58. package/templates/typical/client/src/components/ui/DesignSystem-Layout.stories.tsx +0 -167
  59. package/templates/typical/client/src/components/ui/DesignSystem-Overview.stories.tsx +0 -748
  60. package/templates/typical/client/src/components/ui/DesignSystem-Typography.mdx +0 -31
  61. package/templates/typical/client/src/components/ui/DesignSystem-Typography.stories.tsx +0 -80
  62. package/templates/typical/client/src/components/ui/Dialog.stories.tsx +0 -74
  63. package/templates/typical/client/src/components/ui/Dialog.tsx +0 -154
  64. package/templates/typical/client/src/components/ui/Direction.stories.tsx +0 -38
  65. package/templates/typical/client/src/components/ui/Direction.tsx +0 -24
  66. package/templates/typical/client/src/components/ui/Drawer.stories.tsx +0 -70
  67. package/templates/typical/client/src/components/ui/Drawer.tsx +0 -124
  68. package/templates/typical/client/src/components/ui/DropdownMenu.stories.tsx +0 -74
  69. package/templates/typical/client/src/components/ui/DropdownMenu.tsx +0 -239
  70. package/templates/typical/client/src/components/ui/Empty.stories.tsx +0 -37
  71. package/templates/typical/client/src/components/ui/Empty.tsx +0 -98
  72. package/templates/typical/client/src/components/ui/Field.stories.tsx +0 -50
  73. package/templates/typical/client/src/components/ui/Field.tsx +0 -251
  74. package/templates/typical/client/src/components/ui/Form.stories.tsx +0 -45
  75. package/templates/typical/client/src/components/ui/Form.tsx +0 -148
  76. package/templates/typical/client/src/components/ui/HoverCard.stories.tsx +0 -49
  77. package/templates/typical/client/src/components/ui/HoverCard.tsx +0 -39
  78. package/templates/typical/client/src/components/ui/Input.stories.tsx +0 -42
  79. package/templates/typical/client/src/components/ui/Input.tsx +0 -22
  80. package/templates/typical/client/src/components/ui/InputGroup.stories.tsx +0 -53
  81. package/templates/typical/client/src/components/ui/InputGroup.tsx +0 -153
  82. package/templates/typical/client/src/components/ui/InputOTP.stories.tsx +0 -42
  83. package/templates/typical/client/src/components/ui/InputOTP.tsx +0 -72
  84. package/templates/typical/client/src/components/ui/Item.stories.tsx +0 -64
  85. package/templates/typical/client/src/components/ui/Item.tsx +0 -168
  86. package/templates/typical/client/src/components/ui/Kbd.stories.tsx +0 -59
  87. package/templates/typical/client/src/components/ui/Kbd.tsx +0 -22
  88. package/templates/typical/client/src/components/ui/Label.stories.tsx +0 -90
  89. package/templates/typical/client/src/components/ui/Label.tsx +0 -44
  90. package/templates/typical/client/src/components/ui/Menubar.stories.tsx +0 -78
  91. package/templates/typical/client/src/components/ui/Menubar.tsx +0 -251
  92. package/templates/typical/client/src/components/ui/NativeSelect.stories.tsx +0 -45
  93. package/templates/typical/client/src/components/ui/NativeSelect.tsx +0 -50
  94. package/templates/typical/client/src/components/ui/NavigationMenu.stories.tsx +0 -80
  95. package/templates/typical/client/src/components/ui/NavigationMenu.tsx +0 -152
  96. package/templates/typical/client/src/components/ui/Pagination.stories.tsx +0 -77
  97. package/templates/typical/client/src/components/ui/Pagination.tsx +0 -108
  98. package/templates/typical/client/src/components/ui/Popover.stories.tsx +0 -53
  99. package/templates/typical/client/src/components/ui/Popover.tsx +0 -57
  100. package/templates/typical/client/src/components/ui/Progress.stories.tsx +0 -32
  101. package/templates/typical/client/src/components/ui/Progress.tsx +0 -25
  102. package/templates/typical/client/src/components/ui/RadioGroup.stories.tsx +0 -50
  103. package/templates/typical/client/src/components/ui/RadioGroup.tsx +0 -36
  104. package/templates/typical/client/src/components/ui/Resizable.stories.tsx +0 -72
  105. package/templates/typical/client/src/components/ui/Resizable.tsx +0 -54
  106. package/templates/typical/client/src/components/ui/ScrollArea.stories.tsx +0 -45
  107. package/templates/typical/client/src/components/ui/ScrollArea.tsx +0 -51
  108. package/templates/typical/client/src/components/ui/Select.stories.tsx +0 -59
  109. package/templates/typical/client/src/components/ui/Select.tsx +0 -171
  110. package/templates/typical/client/src/components/ui/Separator.stories.tsx +0 -42
  111. package/templates/typical/client/src/components/ui/Separator.tsx +0 -27
  112. package/templates/typical/client/src/components/ui/Sheet.stories.tsx +0 -68
  113. package/templates/typical/client/src/components/ui/Sheet.tsx +0 -115
  114. package/templates/typical/client/src/components/ui/Sidebar.stories.tsx +0 -96
  115. package/templates/typical/client/src/components/ui/Sidebar.tsx +0 -695
  116. package/templates/typical/client/src/components/ui/Skeleton.stories.tsx +0 -40
  117. package/templates/typical/client/src/components/ui/Skeleton.tsx +0 -11
  118. package/templates/typical/client/src/components/ui/Slider.stories.tsx +0 -24
  119. package/templates/typical/client/src/components/ui/Slider.tsx +0 -55
  120. package/templates/typical/client/src/components/ui/Sonner.stories.tsx +0 -45
  121. package/templates/typical/client/src/components/ui/Sonner.tsx +0 -38
  122. package/templates/typical/client/src/components/ui/Spinner.stories.tsx +0 -26
  123. package/templates/typical/client/src/components/ui/Spinner.tsx +0 -13
  124. package/templates/typical/client/src/components/ui/Switch.stories.tsx +0 -39
  125. package/templates/typical/client/src/components/ui/Switch.tsx +0 -35
  126. package/templates/typical/client/src/components/ui/Table.stories.tsx +0 -67
  127. package/templates/typical/client/src/components/ui/Table.tsx +0 -86
  128. package/templates/typical/client/src/components/ui/Tabs.stories.tsx +0 -53
  129. package/templates/typical/client/src/components/ui/Tabs.tsx +0 -75
  130. package/templates/typical/client/src/components/ui/Textarea.stories.tsx +0 -27
  131. package/templates/typical/client/src/components/ui/Textarea.tsx +0 -22
  132. package/templates/typical/client/src/components/ui/Toast.stories.tsx +0 -116
  133. package/templates/typical/client/src/components/ui/Toast.tsx +0 -123
  134. package/templates/typical/client/src/components/ui/Toaster.tsx +0 -32
  135. package/templates/typical/client/src/components/ui/Toggle.stories.tsx +0 -44
  136. package/templates/typical/client/src/components/ui/Toggle.tsx +0 -42
  137. package/templates/typical/client/src/components/ui/ToggleGroup.stories.tsx +0 -61
  138. package/templates/typical/client/src/components/ui/ToggleGroup.tsx +0 -83
  139. package/templates/typical/client/src/components/ui/Tooltip.stories.tsx +0 -42
  140. package/templates/typical/client/src/components/ui/Tooltip.tsx +0 -48
  141. package/templates/typical/client/src/gql/execute.ts +0 -11
  142. package/templates/typical/client/src/gql/fragment-masking.ts +0 -83
  143. package/templates/typical/client/src/gql/gql.ts +0 -9
  144. package/templates/typical/client/src/gql/graphql.ts +0 -182
  145. package/templates/typical/client/src/gql/index.ts +0 -2
  146. package/templates/typical/client/src/graphql/mutations.ts +0 -0
  147. package/templates/typical/client/src/graphql/queries.ts +0 -0
  148. package/templates/typical/client/src/hooks/.gitkeep +0 -0
  149. package/templates/typical/client/src/hooks/use-mobile.ts +0 -19
  150. package/templates/typical/client/src/hooks/use-toast.ts +0 -186
  151. package/templates/typical/client/src/index.css +0 -121
  152. package/templates/typical/client/src/lib/utils.ts +0 -6
  153. package/templates/typical/client/src/main.tsx +0 -5
  154. package/templates/typical/client/tsconfig.app.json +0 -26
  155. package/templates/typical/client/tsconfig.json +0 -10
  156. package/templates/typical/client/vite.config.ts +0 -50
@@ -1,45 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { useForm } from 'react-hook-form';
3
- import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/Form';
4
- import { Input } from '@/components/ui/Input';
5
- import { Button } from '@/components/ui/Button';
6
-
7
- const meta: Meta<typeof Form> = {
8
- title: 'UI Components/Form',
9
- component: Form,
10
- };
11
- export default meta;
12
- type Story = StoryObj<typeof Form>;
13
-
14
- function FormDemo() {
15
- const form = useForm({
16
- defaultValues: { username: '' },
17
- });
18
-
19
- return (
20
- <Form {...form}>
21
- <form onSubmit={form.handleSubmit(() => {})} className="w-full max-w-sm space-y-6">
22
- <FormField
23
- control={form.control}
24
- name="username"
25
- render={({ field }) => (
26
- <FormItem>
27
- <FormLabel>Username</FormLabel>
28
- <FormControl>
29
- <Input placeholder="Enter username" {...field} />
30
- </FormControl>
31
- <FormDescription>This is your public display name.</FormDescription>
32
- <FormMessage />
33
- </FormItem>
34
- )}
35
- />
36
- <Button type="submit">Submit</Button>
37
- </form>
38
- </Form>
39
- );
40
- }
41
-
42
- /** Demonstrates a complete form field with label, input, description, and validation message wiring. */
43
- export const Default: Story = {
44
- render: () => <FormDemo />,
45
- };
@@ -1,148 +0,0 @@
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
- /**
18
- * Form context provider that wraps react-hook-form's FormProvider.
19
- * Use with FormField, FormItem, FormLabel, FormControl, FormDescription, and FormMessage for accessible form layouts with validation.
20
- */
21
- function Form({ ...props }: React.ComponentProps<typeof FormProvider>) {
22
- return <FormProvider {...props} />;
23
- }
24
-
25
- type FormFieldContextValue<
26
- TFieldValues extends FieldValues = FieldValues,
27
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
28
- > = {
29
- name: TName;
30
- };
31
-
32
- const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
33
-
34
- /** Connects a form input to react-hook-form via Controller, providing field context to child components. */
35
- function FormField<
36
- TFieldValues extends FieldValues = FieldValues,
37
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
38
- >({
39
- ...props
40
- }: ControllerProps<TFieldValues, TName>) {
41
- return (
42
- <FormFieldContext.Provider value={{ name: props.name }}>
43
- <Controller {...props} />
44
- </FormFieldContext.Provider>
45
- );
46
- };
47
-
48
- const useFormField = () => {
49
- const fieldContext = React.useContext(FormFieldContext);
50
- const itemContext = React.useContext(FormItemContext);
51
- const { getFieldState } = useFormContext();
52
- const formState = useFormState({ name: fieldContext.name });
53
- const fieldState = getFieldState(fieldContext.name, formState);
54
-
55
- if (!fieldContext) {
56
- throw new Error('useFormField should be used within <FormField>');
57
- }
58
-
59
- const { id } = itemContext;
60
-
61
- return {
62
- id,
63
- name: fieldContext.name,
64
- formItemId: `${id}-form-item`,
65
- formDescriptionId: `${id}-form-item-description`,
66
- formMessageId: `${id}-form-item-message`,
67
- ...fieldState,
68
- };
69
- };
70
-
71
- type FormItemContextValue = {
72
- id: string;
73
- };
74
-
75
- const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
76
-
77
- /** Container for a single form field that provides ID context for accessibility linking between label, input, description, and error message. */
78
- function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
79
- const id = React.useId();
80
-
81
- return (
82
- <FormItemContext.Provider value={{ id }}>
83
- <div data-slot="form-item" className={cn('grid gap-2', className)} {...props} />
84
- </FormItemContext.Provider>
85
- );
86
- }
87
-
88
- /** Label for a form field that automatically links to its input and displays error styling when validation fails. */
89
- function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
90
- const { error, formItemId } = useFormField();
91
-
92
- return (
93
- <Label
94
- data-slot="form-label"
95
- data-error={!!error}
96
- className={cn('data-[error=true]:text-destructive', className)}
97
- htmlFor={formItemId}
98
- {...props}
99
- />
100
- );
101
- }
102
-
103
- /** Wrapper that merges accessibility attributes (id, aria-describedby, aria-invalid) onto the form input element. */
104
- function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {
105
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
106
-
107
- return (
108
- <Slot.Root
109
- data-slot="form-control"
110
- id={formItemId}
111
- aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
112
- aria-invalid={!!error}
113
- {...props}
114
- />
115
- );
116
- }
117
-
118
- /** Helper text displayed below a form input, linked via aria-describedby for screen readers. */
119
- function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
120
- const { formDescriptionId } = useFormField();
121
-
122
- return (
123
- <p
124
- data-slot="form-description"
125
- id={formDescriptionId}
126
- className={cn('text-muted-foreground text-sm', className)}
127
- {...props}
128
- />
129
- );
130
- }
131
-
132
- /** Displays validation error messages for a form field. Automatically shows the field's error message or custom children. */
133
- function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
134
- const { error, formMessageId } = useFormField();
135
- const body = error ? String(error?.message ?? '') : props.children;
136
-
137
- if (!body) {
138
- return null;
139
- }
140
-
141
- return (
142
- <p data-slot="form-message" id={formMessageId} className={cn('text-destructive text-sm', className)} {...props}>
143
- {body}
144
- </p>
145
- );
146
- }
147
-
148
- export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
@@ -1,49 +0,0 @@
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: 'UI Components/HoverCard',
7
- component: HoverCard,
8
- };
9
- export default meta;
10
- type Story = StoryObj<typeof HoverCard>;
11
-
12
- /** Shows a user profile preview card triggered by hovering over a username link. */
13
- export const Default: Story = {
14
- render: () => (
15
- <HoverCard>
16
- <HoverCardTrigger asChild>
17
- <a href="#" className="text-sm font-medium underline underline-offset-4">
18
- @nextjs
19
- </a>
20
- </HoverCardTrigger>
21
- <HoverCardContent className="w-80">
22
- <div className="flex justify-between space-x-4">
23
- <div className="space-y-1">
24
- <h4 className="text-sm font-semibold">@nextjs</h4>
25
- <p className="text-sm">The React Framework for the Web. Created and maintained by @vercel.</p>
26
- <div className="flex items-center pt-2">
27
- <CalendarDaysIcon className="text-muted-foreground mr-2 h-4 w-4" />
28
- <span className="text-muted-foreground text-xs">Joined December 2021</span>
29
- </div>
30
- </div>
31
- </div>
32
- </HoverCardContent>
33
- </HoverCard>
34
- ),
35
- };
36
-
37
- /** Minimal hover card with just text content. */
38
- export const Simple: Story = {
39
- render: () => (
40
- <HoverCard>
41
- <HoverCardTrigger asChild>
42
- <span className="cursor-pointer text-sm font-medium underline underline-offset-4">Hover me</span>
43
- </HoverCardTrigger>
44
- <HoverCardContent>
45
- <p className="text-sm">This is a simple hover card with some text content.</p>
46
- </HoverCardContent>
47
- </HoverCard>
48
- ),
49
- };
@@ -1,39 +0,0 @@
1
- import * as React from 'react';
2
- import { HoverCard as HoverCardPrimitive } from 'radix-ui';
3
-
4
- import { cn } from '@/lib/utils';
5
-
6
- /** A popup card that appears when hovering over a trigger element. Ideal for previewing content like user profiles or link destinations. */
7
- function HoverCard({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
8
- return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
9
- }
10
-
11
- /** Element that activates the hover card on mouse enter. */
12
- function HoverCardTrigger({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
13
- return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />;
14
- }
15
-
16
- /** The popup content that appears when hovering. Portals to body and animates in/out. */
17
- function HoverCardContent({
18
- className,
19
- align = 'center',
20
- sideOffset = 4,
21
- ...props
22
- }: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
23
- return (
24
- <HoverCardPrimitive.Portal data-slot="hover-card-portal">
25
- <HoverCardPrimitive.Content
26
- data-slot="hover-card-content"
27
- align={align}
28
- sideOffset={sideOffset}
29
- className={cn(
30
- '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',
31
- className,
32
- )}
33
- {...props}
34
- />
35
- </HoverCardPrimitive.Portal>
36
- );
37
- }
38
-
39
- export { HoverCard, HoverCardTrigger, HoverCardContent };
@@ -1,42 +0,0 @@
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: 'UI Components/Input',
7
- component: Input,
8
- };
9
- export default meta;
10
- type Story = StoryObj<typeof Input>;
11
-
12
- /** Basic empty input field. */
13
- export const Default: Story = {
14
- args: {},
15
- };
16
-
17
- /** Email input with placeholder text. */
18
- export const WithPlaceholder: Story = {
19
- args: {
20
- placeholder: 'Enter your email...',
21
- type: 'email',
22
- },
23
- };
24
-
25
- /** Disabled input that cannot be edited. */
26
- export const Disabled: Story = {
27
- args: {
28
- placeholder: 'Disabled input',
29
- disabled: true,
30
- defaultValue: 'Cannot edit this',
31
- },
32
- };
33
-
34
- /** Input paired with a label for accessibility. */
35
- export const WithLabel: Story = {
36
- render: () => (
37
- <div className="grid w-full max-w-sm items-center gap-1.5">
38
- <Label htmlFor="email-input">Email</Label>
39
- <Input type="email" id="email-input" placeholder="Email" />
40
- </div>
41
- ),
42
- };
@@ -1,22 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { cn } from '@/lib/utils';
4
-
5
- /** A styled text input field. Supports all native input types including file uploads. Shows validation error styling via aria-invalid. */
6
- function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
7
- return (
8
- <input
9
- type={type}
10
- data-slot="input"
11
- className={cn(
12
- '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',
13
- 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
14
- 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
15
- className,
16
- )}
17
- {...props}
18
- />
19
- );
20
- }
21
-
22
- export { Input };
@@ -1,53 +0,0 @@
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: 'UI Components/InputGroup',
7
- component: InputGroup,
8
- };
9
- export default meta;
10
- type Story = StoryObj<typeof InputGroup>;
11
-
12
- /** Input with a leading email icon addon. */
13
- export const Default: Story = {
14
- render: () => (
15
- <InputGroup className="max-w-sm">
16
- <InputGroupAddon align="inline-start">
17
- <InputGroupText>
18
- <MailIcon />
19
- </InputGroupText>
20
- </InputGroupAddon>
21
- <InputGroupInput placeholder="you@example.com" />
22
- </InputGroup>
23
- ),
24
- };
25
-
26
- /** URL input with text addons on both sides. */
27
- export const WithPrefixAndSuffix: Story = {
28
- render: () => (
29
- <InputGroup className="max-w-sm">
30
- <InputGroupAddon align="inline-start">
31
- <InputGroupText>https://</InputGroupText>
32
- </InputGroupAddon>
33
- <InputGroupInput placeholder="example.com" />
34
- <InputGroupAddon align="inline-end">
35
- <InputGroupText>.com</InputGroupText>
36
- </InputGroupAddon>
37
- </InputGroup>
38
- ),
39
- };
40
-
41
- /** Search input with a magnifying glass icon. */
42
- export const SearchInput: Story = {
43
- render: () => (
44
- <InputGroup className="max-w-sm">
45
- <InputGroupAddon align="inline-start">
46
- <InputGroupText>
47
- <SearchIcon />
48
- </InputGroupText>
49
- </InputGroupAddon>
50
- <InputGroupInput placeholder="Search..." />
51
- </InputGroup>
52
- ),
53
- };
@@ -1,153 +0,0 @@
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
- /** Container that combines an input with addons like icons, buttons, or text. Supports inline and block-positioned addons. */
10
- function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
11
- return (
12
- <div
13
- data-slot="input-group"
14
- role="group"
15
- className={cn(
16
- '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',
17
- 'h-9 min-w-0 has-[>textarea]:h-auto',
18
-
19
- // Variants based on alignment.
20
- 'has-[>[data-align=inline-start]]:[&>input]:pl-2',
21
- 'has-[>[data-align=inline-end]]:[&>input]:pr-2',
22
- 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
23
- 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
24
-
25
- // Focus state.
26
- '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]',
27
-
28
- // Error state.
29
- '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',
30
-
31
- className,
32
- )}
33
- {...props}
34
- />
35
- );
36
- }
37
-
38
- const inputGroupAddonVariants = cva(
39
- "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",
40
- {
41
- variants: {
42
- align: {
43
- 'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
44
- 'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',
45
- 'block-start':
46
- 'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5',
47
- 'block-end': 'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5',
48
- },
49
- },
50
- defaultVariants: {
51
- align: 'inline-start',
52
- },
53
- },
54
- );
55
-
56
- /** Positioned container for icons, text, or buttons alongside the input. Use align prop to position at inline-start, inline-end, block-start, or block-end. */
57
- function InputGroupAddon({
58
- className,
59
- align = 'inline-start',
60
- ...props
61
- }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
62
- return (
63
- <div
64
- role="group"
65
- data-slot="input-group-addon"
66
- data-align={align}
67
- className={cn(inputGroupAddonVariants({ align }), className)}
68
- onClick={(e) => {
69
- if ((e.target as HTMLElement).closest('button')) {
70
- return;
71
- }
72
- e.currentTarget.parentElement?.querySelector('input')?.focus();
73
- }}
74
- {...props}
75
- />
76
- );
77
- }
78
-
79
- const inputGroupButtonVariants = cva('text-sm shadow-none flex gap-2 items-center', {
80
- variants: {
81
- size: {
82
- xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
83
- sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5',
84
- 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
85
- 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
86
- },
87
- },
88
- defaultVariants: {
89
- size: 'xs',
90
- },
91
- });
92
-
93
- /** A compact button sized for use inside InputGroupAddon. */
94
- function InputGroupButton({
95
- className,
96
- type = 'button',
97
- variant = 'ghost',
98
- size = 'xs',
99
- ...props
100
- }: Omit<React.ComponentProps<typeof Button>, 'size'> & VariantProps<typeof inputGroupButtonVariants>) {
101
- return (
102
- <Button
103
- type={type}
104
- data-size={size}
105
- variant={variant}
106
- className={cn(inputGroupButtonVariants({ size }), className)}
107
- {...props}
108
- />
109
- );
110
- }
111
-
112
- /** Static text or icon displayed in an InputGroupAddon. */
113
- function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
114
- return (
115
- <span
116
- className={cn(
117
- "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
118
- className,
119
- )}
120
- {...props}
121
- />
122
- );
123
- }
124
-
125
- /** Input element styled to integrate seamlessly within an InputGroup. */
126
- function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
127
- return (
128
- <Input
129
- data-slot="input-group-control"
130
- className={cn(
131
- 'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
132
- className,
133
- )}
134
- {...props}
135
- />
136
- );
137
- }
138
-
139
- /** Textarea element styled to integrate seamlessly within an InputGroup. */
140
- function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
141
- return (
142
- <Textarea
143
- data-slot="input-group-control"
144
- className={cn(
145
- 'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
146
- className,
147
- )}
148
- {...props}
149
- />
150
- );
151
- }
152
-
153
- export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea };
@@ -1,42 +0,0 @@
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: 'UI Components/InputOTP',
6
- component: InputOTP,
7
- };
8
- export default meta;
9
- type Story = StoryObj<typeof InputOTP>;
10
-
11
- /** 6-digit OTP input split into two groups of three with a separator. */
12
- export const Default: Story = {
13
- render: () => (
14
- <InputOTP maxLength={6}>
15
- <InputOTPGroup>
16
- <InputOTPSlot index={0} />
17
- <InputOTPSlot index={1} />
18
- <InputOTPSlot index={2} />
19
- </InputOTPGroup>
20
- <InputOTPSeparator />
21
- <InputOTPGroup>
22
- <InputOTPSlot index={3} />
23
- <InputOTPSlot index={4} />
24
- <InputOTPSlot index={5} />
25
- </InputOTPGroup>
26
- </InputOTP>
27
- ),
28
- };
29
-
30
- /** Compact 4-digit PIN input without separator. */
31
- export const FourDigits: Story = {
32
- render: () => (
33
- <InputOTP maxLength={4}>
34
- <InputOTPGroup>
35
- <InputOTPSlot index={0} />
36
- <InputOTPSlot index={1} />
37
- <InputOTPSlot index={2} />
38
- <InputOTPSlot index={3} />
39
- </InputOTPGroup>
40
- </InputOTP>
41
- ),
42
- };
@@ -1,72 +0,0 @@
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
- /** One-time password input with individual digit slots. Handles paste, backspace, and arrow key navigation automatically. */
10
- function InputOTP({
11
- className,
12
- containerClassName,
13
- ...props
14
- }: React.ComponentProps<typeof OTPInput> & {
15
- containerClassName?: string;
16
- }) {
17
- return (
18
- <OTPInput
19
- data-slot="input-otp"
20
- containerClassName={cn('flex items-center gap-2 has-disabled:opacity-50', containerClassName)}
21
- className={cn('disabled:cursor-not-allowed', className)}
22
- {...props}
23
- />
24
- );
25
- }
26
-
27
- /** Groups InputOTPSlot elements together visually. */
28
- function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
29
- return <div data-slot="input-otp-group" className={cn('flex items-center', className)} {...props} />;
30
- }
31
-
32
- /** A single digit slot in the OTP input. Shows a blinking caret when active. */
33
- function InputOTPSlot({
34
- index,
35
- className,
36
- ...props
37
- }: React.ComponentProps<'div'> & {
38
- index: number;
39
- }) {
40
- const inputOTPContext = React.useContext(OTPInputContext);
41
- const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
42
-
43
- return (
44
- <div
45
- data-slot="input-otp-slot"
46
- data-active={isActive}
47
- className={cn(
48
- '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]',
49
- className,
50
- )}
51
- {...props}
52
- >
53
- {char}
54
- {hasFakeCaret && (
55
- <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
56
- <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
57
- </div>
58
- )}
59
- </div>
60
- );
61
- }
62
-
63
- /** Visual separator between groups of OTP slots, typically a dash. */
64
- function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
65
- return (
66
- <div data-slot="input-otp-separator" role="separator" {...props}>
67
- <MinusIcon />
68
- </div>
69
- );
70
- }
71
-
72
- export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };