@luanthnh/cntt-ui 0.1.8 → 0.1.9

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 (238) hide show
  1. package/package.json +5 -1
  2. package/.storybook/globals.d.ts +0 -1
  3. package/.storybook/main.ts +0 -29
  4. package/.storybook/preview.ts +0 -32
  5. package/assets/fonts/Montserrat-Black.eot +0 -0
  6. package/assets/fonts/Montserrat-Black.ttf +0 -0
  7. package/assets/fonts/Montserrat-Black.woff +0 -0
  8. package/assets/fonts/Montserrat-Black.woff2 +0 -0
  9. package/assets/fonts/Montserrat-BlackItalic.eot +0 -0
  10. package/assets/fonts/Montserrat-BlackItalic.ttf +0 -0
  11. package/assets/fonts/Montserrat-BlackItalic.woff +0 -0
  12. package/assets/fonts/Montserrat-BlackItalic.woff2 +0 -0
  13. package/assets/fonts/Montserrat-Bold.eot +0 -0
  14. package/assets/fonts/Montserrat-Bold.ttf +0 -0
  15. package/assets/fonts/Montserrat-Bold.woff +0 -0
  16. package/assets/fonts/Montserrat-Bold.woff2 +0 -0
  17. package/assets/fonts/Montserrat-BoldItalic.eot +0 -0
  18. package/assets/fonts/Montserrat-BoldItalic.ttf +0 -0
  19. package/assets/fonts/Montserrat-BoldItalic.woff +0 -0
  20. package/assets/fonts/Montserrat-BoldItalic.woff2 +0 -0
  21. package/assets/fonts/Montserrat-ExtraBold.eot +0 -0
  22. package/assets/fonts/Montserrat-ExtraBold.ttf +0 -0
  23. package/assets/fonts/Montserrat-ExtraBold.woff +0 -0
  24. package/assets/fonts/Montserrat-ExtraBold.woff2 +0 -0
  25. package/assets/fonts/Montserrat-ExtraBoldItalic.eot +0 -0
  26. package/assets/fonts/Montserrat-ExtraBoldItalic.ttf +0 -0
  27. package/assets/fonts/Montserrat-ExtraBoldItalic.woff +0 -0
  28. package/assets/fonts/Montserrat-ExtraBoldItalic.woff2 +0 -0
  29. package/assets/fonts/Montserrat-ExtraLight.eot +0 -0
  30. package/assets/fonts/Montserrat-ExtraLight.ttf +0 -0
  31. package/assets/fonts/Montserrat-ExtraLight.woff +0 -0
  32. package/assets/fonts/Montserrat-ExtraLight.woff2 +0 -0
  33. package/assets/fonts/Montserrat-ExtraLightItalic.eot +0 -0
  34. package/assets/fonts/Montserrat-ExtraLightItalic.ttf +0 -0
  35. package/assets/fonts/Montserrat-ExtraLightItalic.woff +0 -0
  36. package/assets/fonts/Montserrat-ExtraLightItalic.woff2 +0 -0
  37. package/assets/fonts/Montserrat-Italic.eot +0 -0
  38. package/assets/fonts/Montserrat-Italic.ttf +0 -0
  39. package/assets/fonts/Montserrat-Italic.woff +0 -0
  40. package/assets/fonts/Montserrat-Italic.woff2 +0 -0
  41. package/assets/fonts/Montserrat-Light.eot +0 -0
  42. package/assets/fonts/Montserrat-Light.ttf +0 -0
  43. package/assets/fonts/Montserrat-Light.woff +0 -0
  44. package/assets/fonts/Montserrat-Light.woff2 +0 -0
  45. package/assets/fonts/Montserrat-LightItalic.eot +0 -0
  46. package/assets/fonts/Montserrat-LightItalic.ttf +0 -0
  47. package/assets/fonts/Montserrat-LightItalic.woff +0 -0
  48. package/assets/fonts/Montserrat-LightItalic.woff2 +0 -0
  49. package/assets/fonts/Montserrat-Medium.eot +0 -0
  50. package/assets/fonts/Montserrat-Medium.ttf +0 -0
  51. package/assets/fonts/Montserrat-Medium.woff +0 -0
  52. package/assets/fonts/Montserrat-Medium.woff2 +0 -0
  53. package/assets/fonts/Montserrat-MediumItalic.eot +0 -0
  54. package/assets/fonts/Montserrat-MediumItalic.ttf +0 -0
  55. package/assets/fonts/Montserrat-MediumItalic.woff +0 -0
  56. package/assets/fonts/Montserrat-MediumItalic.woff2 +0 -0
  57. package/assets/fonts/Montserrat-Regular.eot +0 -0
  58. package/assets/fonts/Montserrat-Regular.ttf +0 -0
  59. package/assets/fonts/Montserrat-Regular.woff +0 -0
  60. package/assets/fonts/Montserrat-Regular.woff2 +0 -0
  61. package/assets/fonts/Montserrat-SemiBold.eot +0 -0
  62. package/assets/fonts/Montserrat-SemiBold.ttf +0 -0
  63. package/assets/fonts/Montserrat-SemiBold.woff +0 -0
  64. package/assets/fonts/Montserrat-SemiBold.woff2 +0 -0
  65. package/assets/fonts/Montserrat-SemiBoldItalic.eot +0 -0
  66. package/assets/fonts/Montserrat-SemiBoldItalic.ttf +0 -0
  67. package/assets/fonts/Montserrat-SemiBoldItalic.woff +0 -0
  68. package/assets/fonts/Montserrat-SemiBoldItalic.woff2 +0 -0
  69. package/assets/fonts/Montserrat-Thin.eot +0 -0
  70. package/assets/fonts/Montserrat-Thin.ttf +0 -0
  71. package/assets/fonts/Montserrat-Thin.woff +0 -0
  72. package/assets/fonts/Montserrat-Thin.woff2 +0 -0
  73. package/assets/fonts/Montserrat-ThinItalic.eot +0 -0
  74. package/assets/fonts/Montserrat-ThinItalic.ttf +0 -0
  75. package/assets/fonts/Montserrat-ThinItalic.woff +0 -0
  76. package/assets/fonts/Montserrat-ThinItalic.woff2 +0 -0
  77. package/assets/fonts/Montserrat-Variable.eot +0 -0
  78. package/assets/fonts/Montserrat-Variable.ttf +0 -0
  79. package/assets/fonts/Montserrat-Variable.woff +0 -0
  80. package/assets/fonts/Montserrat-Variable.woff2 +0 -0
  81. package/assets/fonts/Montserrat-VariableItalic.eot +0 -0
  82. package/assets/fonts/Montserrat-VariableItalic.ttf +0 -0
  83. package/assets/fonts/Montserrat-VariableItalic.woff +0 -0
  84. package/assets/fonts/Montserrat-VariableItalic.woff2 +0 -0
  85. package/assets/icons/arrow-left.svg +0 -1
  86. package/assets/icons/file.svg +0 -1
  87. package/assets/icons/globe.svg +0 -1
  88. package/assets/icons/logo-line.svg +0 -1
  89. package/assets/icons/next.svg +0 -1
  90. package/assets/icons/panel-left-expand.svg +0 -1
  91. package/assets/icons/placeholder.svg +0 -57
  92. package/assets/icons/vercel.svg +0 -1
  93. package/assets/icons/window.svg +0 -1
  94. package/assets/lotties/error-404.json +0 -19642
  95. package/assets/lotties/error.json +0 -2414
  96. package/assets/lotties/loader.json +0 -305
  97. package/components/Welcome.mdx +0 -74
  98. package/components/lenis/index.tsx +0 -48
  99. package/components/motion/auto-height.tsx +0 -56
  100. package/components/motion/cursor.tsx +0 -108
  101. package/components/motion/highlight.tsx +0 -605
  102. package/components/motion/number-ticker.tsx +0 -55
  103. package/components/motion/slot.tsx +0 -106
  104. package/components/motion/waves.tsx +0 -417
  105. package/components/primitives/tabs.tsx +0 -174
  106. package/components/ui/Accordion/index.stories.tsx +0 -39
  107. package/components/ui/Accordion/index.tsx +0 -170
  108. package/components/ui/Alert/index.stories.tsx +0 -39
  109. package/components/ui/Alert/index.tsx +0 -60
  110. package/components/ui/AlertDialog/index.stories.tsx +0 -47
  111. package/components/ui/AlertDialog/index.tsx +0 -172
  112. package/components/ui/AspectRatio/index.stories.tsx +0 -40
  113. package/components/ui/AspectRatio/index.tsx +0 -9
  114. package/components/ui/Avatar/index.stories.tsx +0 -39
  115. package/components/ui/Avatar/index.tsx +0 -44
  116. package/components/ui/Badge/index.stories.tsx +0 -64
  117. package/components/ui/Badge/index.tsx +0 -46
  118. package/components/ui/Breadcrumb/index.stories.tsx +0 -64
  119. package/components/ui/Breadcrumb/index.tsx +0 -102
  120. package/components/ui/Button/index.stories.tsx +0 -232
  121. package/components/ui/Button/index.tsx +0 -114
  122. package/components/ui/Calendar/index.stories.tsx +0 -20
  123. package/components/ui/Calendar/index.tsx +0 -149
  124. package/components/ui/Card/index.stories.tsx +0 -39
  125. package/components/ui/Card/index.tsx +0 -65
  126. package/components/ui/Carousel/index.stories.tsx +0 -37
  127. package/components/ui/Carousel/index.tsx +0 -242
  128. package/components/ui/Chart/index.stories.tsx +0 -53
  129. package/components/ui/Chart/index.tsx +0 -322
  130. package/components/ui/Checkbox/index.stories.tsx +0 -56
  131. package/components/ui/Checkbox/index.tsx +0 -167
  132. package/components/ui/CircleProcess/index.stories.tsx +0 -29
  133. package/components/ui/CircleProcess/index.tsx +0 -50
  134. package/components/ui/Collapsible/index.stories.tsx +0 -33
  135. package/components/ui/Collapsible/index.tsx +0 -124
  136. package/components/ui/Command/index.stories.tsx +0 -65
  137. package/components/ui/Command/index.tsx +0 -161
  138. package/components/ui/Container/index.stories.tsx +0 -22
  139. package/components/ui/Container/index.tsx +0 -30
  140. package/components/ui/ContextMenu/index.stories.tsx +0 -51
  141. package/components/ui/ContextMenu/index.tsx +0 -224
  142. package/components/ui/Dialog/index.stories.tsx +0 -44
  143. package/components/ui/Dialog/index.tsx +0 -156
  144. package/components/ui/Drawer/index.stories.tsx +0 -54
  145. package/components/ui/Drawer/index.tsx +0 -124
  146. package/components/ui/DropdownMenu/index.stories.tsx +0 -83
  147. package/components/ui/DropdownMenu/index.tsx +0 -231
  148. package/components/ui/Dropzone/index.stories.tsx +0 -18
  149. package/components/ui/Dropzone/index.tsx +0 -47
  150. package/components/ui/Form/date-field.tsx +0 -77
  151. package/components/ui/Form/index.stories.tsx +0 -67
  152. package/components/ui/Form/index.tsx +0 -193
  153. package/components/ui/Form/select-field.tsx +0 -55
  154. package/components/ui/Form/text-area-field.tsx +0 -37
  155. package/components/ui/Form/text-field.tsx +0 -72
  156. package/components/ui/HStack/index.stories.tsx +0 -48
  157. package/components/ui/HStack/index.tsx +0 -73
  158. package/components/ui/HoverCard/index.stories.tsx +0 -38
  159. package/components/ui/HoverCard/index.tsx +0 -38
  160. package/components/ui/Icons/index.stories.tsx +0 -27
  161. package/components/ui/Icons/index.tsx +0 -33
  162. package/components/ui/ImageWithFallback/index.stories.tsx +0 -32
  163. package/components/ui/ImageWithFallback/index.tsx +0 -34
  164. package/components/ui/Input/index.stories.tsx +0 -47
  165. package/components/ui/Input/index.tsx +0 -21
  166. package/components/ui/InputOtp/index.stories.tsx +0 -35
  167. package/components/ui/InputOtp/index.tsx +0 -70
  168. package/components/ui/Label/index.stories.tsx +0 -18
  169. package/components/ui/Label/index.tsx +0 -21
  170. package/components/ui/Marquee/index.stories.tsx +0 -71
  171. package/components/ui/Marquee/index.tsx +0 -65
  172. package/components/ui/Menubar/index.stories.tsx +0 -116
  173. package/components/ui/Menubar/index.tsx +0 -252
  174. package/components/ui/NavigationMenu/index.stories.tsx +0 -112
  175. package/components/ui/NavigationMenu/index.tsx +0 -185
  176. package/components/ui/NoData/index.stories.tsx +0 -24
  177. package/components/ui/NoData/index.tsx +0 -19
  178. package/components/ui/Pagination/index.stories.tsx +0 -53
  179. package/components/ui/Pagination/index.tsx +0 -114
  180. package/components/ui/Popover/index.stories.tsx +0 -31
  181. package/components/ui/Popover/index.tsx +0 -42
  182. package/components/ui/Progress/index.stories.tsx +0 -35
  183. package/components/ui/Progress/index.tsx +0 -28
  184. package/components/ui/RadioGroup/index.stories.tsx +0 -28
  185. package/components/ui/RadioGroup/index.tsx +0 -45
  186. package/components/ui/Resizable/index.stories.tsx +0 -44
  187. package/components/ui/Resizable/index.tsx +0 -54
  188. package/components/ui/ScrollArea/index.stories.tsx +0 -31
  189. package/components/ui/ScrollArea/index.tsx +0 -56
  190. package/components/ui/Select/index.stories.tsx +0 -64
  191. package/components/ui/Select/index.tsx +0 -170
  192. package/components/ui/Separator/index.stories.tsx +0 -31
  193. package/components/ui/Separator/index.tsx +0 -28
  194. package/components/ui/Sheet/index.stories.tsx +0 -45
  195. package/components/ui/Sheet/index.tsx +0 -130
  196. package/components/ui/Sidebar/index.stories.tsx +0 -82
  197. package/components/ui/Sidebar/index.tsx +0 -676
  198. package/components/ui/Skeleton/index.stories.tsx +0 -36
  199. package/components/ui/Skeleton/index.tsx +0 -13
  200. package/components/ui/Slider/index.stories.tsx +0 -48
  201. package/components/ui/Slider/index.tsx +0 -82
  202. package/components/ui/Slot/index.stories.tsx +0 -29
  203. package/components/ui/Slot/index.tsx +0 -106
  204. package/components/ui/Sonner/index.stories.tsx +0 -36
  205. package/components/ui/Sonner/index.tsx +0 -31
  206. package/components/ui/Switch/index.stories.tsx +0 -33
  207. package/components/ui/Switch/index.tsx +0 -28
  208. package/components/ui/Table/index.stories.tsx +0 -74
  209. package/components/ui/Table/index.tsx +0 -95
  210. package/components/ui/Tabs/index.stories.tsx +0 -38
  211. package/components/ui/Tabs/index.tsx +0 -78
  212. package/components/ui/Text/index.stories.tsx +0 -53
  213. package/components/ui/Text/index.tsx +0 -138
  214. package/components/ui/Textarea/index.stories.tsx +0 -25
  215. package/components/ui/Textarea/index.tsx +0 -18
  216. package/components/ui/Toggle/index.stories.tsx +0 -52
  217. package/components/ui/Toggle/index.tsx +0 -46
  218. package/components/ui/ToggleGroup/index.stories.tsx +0 -52
  219. package/components/ui/ToggleGroup/index.tsx +0 -69
  220. package/components/ui/Tooltip/index.stories.tsx +0 -29
  221. package/components/ui/Tooltip/index.tsx +0 -35
  222. package/components/ui/VStack/index.stories.tsx +0 -45
  223. package/components/ui/VStack/index.tsx +0 -69
  224. package/components/ui/colors.stories.tsx +0 -148
  225. package/eslint.config.js +0 -10
  226. package/hooks/index.ts +0 -3
  227. package/hooks/use-auto-height.tsx +0 -99
  228. package/hooks/use-controlled-state.tsx +0 -32
  229. package/hooks/use-mobile.ts +0 -19
  230. package/index.ts +0 -58
  231. package/lib/get-strict-context.ts +0 -15
  232. package/lib/utils.ts +0 -10
  233. package/scripts/generate-exports.ts +0 -32
  234. package/tsconfig.json +0 -12
  235. package/tsconfig.tsbuildinfo +0 -1
  236. package/tsup.config.ts +0 -11
  237. package/types/svg.d.ts +0 -10
  238. package/vercel.json +0 -5
@@ -1,18 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
-
3
- import { Dropzone } from './index';
4
-
5
- const meta: Meta<typeof Dropzone> = {
6
- title: 'UI/Dropzone',
7
- component: Dropzone,
8
- tags: ['autodocs'],
9
- };
10
-
11
- export default meta;
12
- type Story = StoryObj<typeof Dropzone>;
13
-
14
- export const Default: Story = {
15
- args: {
16
- onFile: (file) => console.log('File dropped:', file),
17
- },
18
- };
@@ -1,47 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback } from 'react';
4
- import { UploadCloud } from 'lucide-react';
5
- import { useDropzone } from 'react-dropzone';
6
-
7
- import { cn } from '@/lib/utils';
8
-
9
- interface Props {
10
- onFile: (file: File) => void;
11
- className?: string;
12
- }
13
-
14
- export function Dropzone({ onFile, className }: Props) {
15
- const onDrop = useCallback(
16
- (acceptedFiles: File[]) => {
17
- if (acceptedFiles[0]) onFile(acceptedFiles[0]);
18
- },
19
- [onFile],
20
- );
21
-
22
- const { getRootProps, getInputProps, isDragActive } = useDropzone({
23
- onDrop,
24
- accept: { 'image/*': [] },
25
- multiple: false,
26
- });
27
-
28
- return (
29
- <div
30
- {...getRootProps()}
31
- className={cn(
32
- 'group border-muted-foreground/25 hover:border-muted-foreground/50 relative grid h-32 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-6 py-4 text-center transition',
33
- isDragActive && 'border-primary',
34
- className,
35
- )}
36
- >
37
- <input {...getInputProps()} />
38
- <div className="text-muted-foreground flex flex-col items-center gap-1 text-sm">
39
- <UploadCloud className="h-6 w-6" />
40
- <span className="font-medium">
41
- {isDragActive ? 'Thả ảnh vào đây' : 'Kéo thả hoặc click để chọn'}
42
- </span>
43
- <span className="text-xs">PNG, JPG, GIF tối đa 5 MB</span>
44
- </div>
45
- </div>
46
- );
47
- }
@@ -1,77 +0,0 @@
1
- 'use client';
2
-
3
- import type * as React from 'react';
4
- import { format } from 'date-fns';
5
- import { CalendarIcon } from 'lucide-react';
6
- import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
7
-
8
- import { cn } from '@/lib/utils';
9
- import { Button } from '@/components/ui/Button';
10
- import { Calendar } from '@/components/ui/Calendar';
11
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover';
12
-
13
- import { FormControl, FormField, FormItem, FormLabel, FormMessage } from './index';
14
-
15
- interface Props<
16
- TFieldValues extends FieldValues = FieldValues,
17
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
18
- > extends Omit<React.ComponentProps<'button'>, 'name' | 'type' | 'defaultValue'> {
19
- name: TName;
20
- control: ControllerProps<TFieldValues, TName>['control'];
21
- label?: string;
22
- isRequired?: boolean;
23
- }
24
-
25
- export function DateField<
26
- TFieldValues extends FieldValues = FieldValues,
27
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
28
- >({ name, control, label, isRequired, ...props }: Props<TFieldValues, TName>) {
29
- return (
30
- <FormField
31
- name={name}
32
- control={control}
33
- render={({ field }) => (
34
- <FormItem className="flex flex-col gap-2">
35
- {label && <FormLabel isRequired={isRequired}>{label}</FormLabel>}
36
- <FormControl>
37
- <Popover>
38
- <PopoverTrigger asChild>
39
- <Button
40
- variant="outline"
41
- className={cn(
42
- 'border-primary-100 h-11 w-full justify-start text-left font-normal text-black shadow-xs transition-[color,box-shadow]',
43
- !field.value && 'text-muted-foreground',
44
- props.className,
45
- )}
46
- {...props}
47
- >
48
- <CalendarIcon className="mr-2 h-4 w-4" />
49
- {field.value ? (
50
- typeof field.value === 'string' ? (
51
- format(new Date(field.value), 'dd/MM/yyyy')
52
- ) : (
53
- format(field.value, 'dd/MM/yyyy')
54
- )
55
- ) : (
56
- <span>Chọn ngày</span>
57
- )}
58
- </Button>
59
- </PopoverTrigger>
60
- <PopoverContent className="w-auto p-0" align="start">
61
- <Calendar
62
- mode="single"
63
- captionLayout="dropdown"
64
- selected={field.value ? new Date(field.value) : undefined}
65
- onSelect={field.onChange}
66
- fromYear={1900}
67
- toYear={new Date().getFullYear() + 10}
68
- />
69
- </PopoverContent>
70
- </Popover>
71
- </FormControl>
72
- <FormMessage />
73
- </FormItem>
74
- )}
75
- />
76
- );
77
- }
@@ -1,67 +0,0 @@
1
- import { zodResolver } from '@hookform/resolvers/zod';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { useForm } from 'react-hook-form';
4
- import { z } from 'zod';
5
-
6
- import { Button } from '@/components/ui/Button';
7
- import { Input } from '@/components/ui/Input';
8
-
9
- import {
10
- Form,
11
- FormControl,
12
- FormDescription,
13
- FormField,
14
- FormItem,
15
- FormLabel,
16
- FormMessage,
17
- } from './index';
18
-
19
- const meta: Meta<typeof Form> = {
20
- title: 'UI/Form',
21
- component: Form,
22
- tags: ['autodocs'],
23
- };
24
-
25
- export default meta;
26
-
27
- const formSchema = z.object({
28
- username: z.string().min(2, {
29
- message: 'Username must be at least 2 characters.',
30
- }),
31
- });
32
-
33
- export const Default: StoryObj = {
34
- render: () => {
35
- // eslint-disable-next-line react-hooks/rules-of-hooks
36
- const form = useForm<z.infer<typeof formSchema>>({
37
- resolver: zodResolver(formSchema),
38
- defaultValues: {
39
- username: '',
40
- },
41
- });
42
-
43
- function onSubmit(values: z.infer<typeof formSchema>) {
44
- alert(JSON.stringify(values, null, 2));
45
- }
46
-
47
- return (
48
- <Form form={form} onSubmit={onSubmit} className="space-y-8">
49
- <FormField
50
- control={form.control}
51
- name="username"
52
- render={({ field }) => (
53
- <FormItem>
54
- <FormLabel>Username</FormLabel>
55
- <FormControl>
56
- <Input placeholder="shadcn" {...field} />
57
- </FormControl>
58
- <FormDescription>This is your public display name.</FormDescription>
59
- <FormMessage />
60
- </FormItem>
61
- )}
62
- />
63
- <Button type="submit">Submit</Button>
64
- </Form>
65
- );
66
- },
67
- };
@@ -1,193 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import * as LabelPrimitive from '@radix-ui/react-label';
5
- import { Slot } from '@radix-ui/react-slot';
6
- import {
7
- Controller,
8
- FormProvider,
9
- SubmitHandler,
10
- useFormContext,
11
- UseFormReturn,
12
- useFormState,
13
- type ControllerProps,
14
- type FieldPath,
15
- type FieldValues,
16
- } from 'react-hook-form';
17
-
18
- import { cn } from '@/lib/utils';
19
- import { Label } from '@/components/ui/Label';
20
-
21
- export interface FormProps<T extends FieldValues> {
22
- form: UseFormReturn<T, unknown>;
23
- onSubmit?: SubmitHandler<T>;
24
- children: React.ReactNode;
25
- id?: string;
26
- className?: string;
27
- }
28
-
29
- const Form = <T extends FieldValues>({
30
- form,
31
- onSubmit,
32
- children,
33
- id = 'form',
34
- className,
35
- }: FormProps<T>) => {
36
- return (
37
- <FormProvider {...form}>
38
- <form
39
- id={id}
40
- className={className}
41
- onSubmit={form.handleSubmit(onSubmit as SubmitHandler<T>)}
42
- noValidate
43
- >
44
- {children}
45
- </form>
46
- </FormProvider>
47
- );
48
- };
49
-
50
- type FormFieldContextValue<
51
- TFieldValues extends FieldValues = FieldValues,
52
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
53
- > = {
54
- name: TName;
55
- };
56
-
57
- const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
58
-
59
- const FormField = <
60
- TFieldValues extends FieldValues = FieldValues,
61
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
62
- >({
63
- ...props
64
- }: ControllerProps<TFieldValues, TName>) => {
65
- return (
66
- <FormFieldContext.Provider value={{ name: props.name }}>
67
- <Controller {...props} />
68
- </FormFieldContext.Provider>
69
- );
70
- };
71
-
72
- const useFormField = () => {
73
- const fieldContext = React.useContext(FormFieldContext);
74
- const itemContext = React.useContext(FormItemContext);
75
- const { getFieldState } = useFormContext();
76
- const formState = useFormState({ name: fieldContext.name });
77
- const fieldState = getFieldState(fieldContext.name, formState);
78
-
79
- if (!fieldContext) {
80
- throw new Error('useFormField should be used within <FormField>');
81
- }
82
-
83
- const { id } = itemContext;
84
-
85
- return {
86
- id,
87
- name: fieldContext.name,
88
- formItemId: `${id}-form-item`,
89
- formDescriptionId: `${id}-form-item-description`,
90
- formMessageId: `${id}-form-item-message`,
91
- ...fieldState,
92
- };
93
- };
94
-
95
- type FormItemContextValue = {
96
- id: string;
97
- };
98
-
99
- const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
100
-
101
- function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
102
- const id = React.useId();
103
-
104
- return (
105
- <FormItemContext.Provider value={{ id }}>
106
- <div data-slot="form-item" className={cn('grid gap-2', className)} {...props} />
107
- </FormItemContext.Provider>
108
- );
109
- }
110
-
111
- function FormLabel({
112
- className,
113
- isRequired,
114
- children,
115
- ...props
116
- }: React.ComponentProps<typeof LabelPrimitive.Root> & { isRequired?: boolean }) {
117
- const { error, formItemId } = useFormField();
118
- return (
119
- <Label
120
- data-slot="form-label"
121
- data-error={!!error}
122
- className={cn('data-[error=true]:text-destructive', className)}
123
- htmlFor={formItemId}
124
- {...props}
125
- >
126
- {children}
127
- {isRequired && <span className="text-destructive">*</span>}
128
- </Label>
129
- );
130
- }
131
-
132
- function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
133
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
134
-
135
- return (
136
- <Slot
137
- data-slot="form-control"
138
- id={formItemId}
139
- aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
140
- aria-invalid={!!error}
141
- {...props}
142
- />
143
- );
144
- }
145
-
146
- function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
147
- const { formDescriptionId } = useFormField();
148
-
149
- return (
150
- <p
151
- data-slot="form-description"
152
- id={formDescriptionId}
153
- className={cn('text-muted-foreground text-sm', className)}
154
- {...props}
155
- />
156
- );
157
- }
158
-
159
- function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
160
- const { error, formMessageId } = useFormField();
161
- const body = error ? String(error?.message ?? '') : props.children;
162
-
163
- if (!body) {
164
- return null;
165
- }
166
-
167
- return (
168
- <p
169
- data-slot="form-message"
170
- id={formMessageId}
171
- className={cn('text-destructive text-sm', className)}
172
- {...props}
173
- >
174
- {body}
175
- </p>
176
- );
177
- }
178
-
179
- export {
180
- useFormField,
181
- Form,
182
- FormItem,
183
- FormLabel,
184
- FormControl,
185
- FormDescription,
186
- FormMessage,
187
- FormField,
188
- };
189
-
190
- export * from './text-field';
191
- export * from './text-area-field';
192
- export * from './select-field';
193
- export * from './date-field';
@@ -1,55 +0,0 @@
1
- import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
2
-
3
- import {
4
- Select,
5
- SelectContent,
6
- SelectItem,
7
- SelectTrigger,
8
- SelectValue,
9
- } from '@/components/ui/Select';
10
-
11
- import { FormControl, FormField, FormItem, FormLabel, FormMessage } from './index';
12
-
13
- export interface SelectFieldProps<
14
- TFieldValues extends FieldValues = FieldValues,
15
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
16
- > {
17
- name: TName;
18
- control: ControllerProps<TFieldValues, TName>['control'];
19
- label?: string;
20
- placeholder?: string;
21
- options: { value: string; label: string }[];
22
- isRequired?: boolean;
23
- }
24
-
25
- export function SelectField<
26
- TFieldValues extends FieldValues = FieldValues,
27
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
28
- >({ label, placeholder, options, isRequired, ...rest }: SelectFieldProps<TFieldValues, TName>) {
29
- return (
30
- <FormField
31
- name={rest.name}
32
- control={rest.control}
33
- render={({ field }) => (
34
- <FormItem>
35
- {label && <FormLabel isRequired={isRequired}>{label}</FormLabel>}
36
- <Select onValueChange={field.onChange} defaultValue={field.value}>
37
- <FormControl>
38
- <SelectTrigger>
39
- <SelectValue placeholder={placeholder} />
40
- </SelectTrigger>
41
- </FormControl>
42
- <SelectContent>
43
- {options.map((o) => (
44
- <SelectItem key={o.value} value={o.value}>
45
- {o.label}
46
- </SelectItem>
47
- ))}
48
- </SelectContent>
49
- </Select>
50
- <FormMessage />
51
- </FormItem>
52
- )}
53
- />
54
- );
55
- }
@@ -1,37 +0,0 @@
1
- import type * as React from 'react';
2
- import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
3
-
4
- import { Textarea } from '@/components/ui/Textarea';
5
-
6
- import { FormControl, FormField, FormItem, FormLabel, FormMessage } from './index';
7
-
8
- export interface TextAreaFieldProps<
9
- TFieldValues extends FieldValues = FieldValues,
10
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
11
- > extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'> {
12
- name: TName;
13
- control: ControllerProps<TFieldValues, TName>['control'];
14
- label?: string;
15
- isRequired?: boolean;
16
- }
17
-
18
- export function TextAreaField<
19
- TFieldValues extends FieldValues = FieldValues,
20
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
21
- >({ label, isRequired, ...textareaProps }: TextAreaFieldProps<TFieldValues, TName>) {
22
- return (
23
- <FormField
24
- name={textareaProps.name}
25
- control={textareaProps.control}
26
- render={({ field }) => (
27
- <FormItem>
28
- {label && <FormLabel isRequired={isRequired}>{label}</FormLabel>}
29
- <FormControl>
30
- <Textarea {...field} {...textareaProps} className="min-h-37.5" />
31
- </FormControl>
32
- <FormMessage />
33
- </FormItem>
34
- )}
35
- />
36
- );
37
- }
@@ -1,72 +0,0 @@
1
- import * as React from 'react';
2
- import { Eye, EyeOff } from 'lucide-react';
3
- import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
4
-
5
- import { Input } from '@/components/ui/Input';
6
-
7
- import { FormField, FormItem, FormLabel, FormMessage } from './index';
8
-
9
- export interface TextFieldProps<
10
- TFieldValues extends FieldValues = FieldValues,
11
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
12
- > extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'> {
13
- name: TName;
14
- control: ControllerProps<TFieldValues, TName>['control'];
15
- label?: string;
16
- isPassword?: boolean;
17
- isRequired?: boolean;
18
- }
19
-
20
- export function TextField<
21
- TFieldValues extends FieldValues = FieldValues,
22
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
23
- >({
24
- label,
25
- type = 'text',
26
- isPassword,
27
- isRequired,
28
- ...inputProps
29
- }: TextFieldProps<TFieldValues, TName>) {
30
- const [show, setShow] = React.useState(false);
31
- const toggle = () => setShow((prev) => !prev);
32
- const isNum = type === 'number';
33
- const renderedType = isPassword ? (show ? 'text' : 'password') : 'text';
34
-
35
- return (
36
- <FormField
37
- name={inputProps.name}
38
- control={inputProps.control}
39
- render={({ field }) => (
40
- <FormItem>
41
- {label && <FormLabel isRequired={isRequired}>{label}</FormLabel>}
42
- <div className="relative">
43
- <FormItem>
44
- <Input
45
- {...field}
46
- {...inputProps}
47
- type={renderedType}
48
- inputMode={isNum ? 'decimal' : undefined}
49
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
50
- let val = e.target.value;
51
- if (isNum) val = val.replace(/[^0-9]/g, '');
52
- field.onChange(val);
53
- }}
54
- />
55
- </FormItem>
56
- {isPassword && (
57
- <button
58
- type="button"
59
- tabIndex={-1}
60
- onClick={toggle}
61
- className="text-muted-foreground hover:text-foreground absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer"
62
- >
63
- {show ? <EyeOff size={16} /> : <Eye size={16} />}
64
- </button>
65
- )}
66
- </div>
67
- <FormMessage />
68
- </FormItem>
69
- )}
70
- />
71
- );
72
- }
@@ -1,48 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
-
3
- import { HStack } from './index';
4
-
5
- const meta: Meta<typeof HStack> = {
6
- title: 'UI/Layout/HStack',
7
- component: HStack,
8
- tags: ['autodocs'],
9
- argTypes: {
10
- spacing: {
11
- control: 'select',
12
- options: [0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 64, 'none'],
13
- },
14
- justify: {
15
- control: 'select',
16
- options: ['left', 'right', 'center', 'between', 'around', 'evenly'],
17
- },
18
- align: {
19
- control: 'select',
20
- options: ['default', 'center', 'start', 'end', 'baseline'],
21
- },
22
- noWrap: {
23
- control: 'boolean',
24
- },
25
- },
26
- };
27
-
28
- export default meta;
29
- type Story = StoryObj<typeof HStack>;
30
-
31
- export const Default: Story = {
32
- args: {
33
- children: (
34
- <>
35
- <div className="bg-primary flex h-10 w-10 items-center justify-center rounded text-white">
36
- 1
37
- </div>
38
- <div className="bg-primary flex h-10 w-10 items-center justify-center rounded text-white">
39
- 2
40
- </div>
41
- <div className="bg-primary flex h-10 w-10 items-center justify-center rounded text-white">
42
- 3
43
- </div>
44
- </>
45
- ),
46
- spacing: 16,
47
- },
48
- };
@@ -1,73 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { cva, type VariantProps } from 'class-variance-authority';
5
- import { motion, type HTMLMotionProps } from 'motion/react';
6
-
7
- import { cn } from '@/lib/utils';
8
-
9
- const hStackVariants = cva('flex flex-wrap items-center', {
10
- variants: {
11
- justify: {
12
- left: 'justify-start',
13
- right: 'justify-end',
14
- center: 'justify-center',
15
- between: 'justify-between',
16
- around: 'justify-around',
17
- evenly: 'justify-evenly',
18
- },
19
- align: {
20
- default: 'items-stretch',
21
- center: 'items-center',
22
- start: 'items-start',
23
- end: 'items-end',
24
- baseline: 'items-baseline',
25
- },
26
- spacing: {
27
- 0: 'gap-0',
28
- 2: 'gap-0.5',
29
- 4: 'gap-1',
30
- 6: 'gap-1.5',
31
- 8: 'gap-2',
32
- 12: 'gap-3',
33
- 16: 'gap-4',
34
- 20: 'gap-5',
35
- 24: 'gap-6',
36
- 32: 'gap-8',
37
- 40: 'gap-10',
38
- 48: 'gap-12',
39
- 64: 'gap-16',
40
- none: 'gap-0',
41
- },
42
- noWrap: {
43
- true: 'flex-nowrap',
44
- },
45
- },
46
- defaultVariants: {
47
- spacing: 16,
48
- justify: 'left',
49
- align: 'default',
50
- },
51
- });
52
-
53
- export interface HStackProps extends VariantProps<typeof hStackVariants>, HTMLMotionProps<'div'> {
54
- className?: string;
55
- }
56
-
57
- const HStack = React.forwardRef<HTMLDivElement, HStackProps>(
58
- ({ className, noWrap, justify, align, spacing, children, ...motionProps }, ref) => {
59
- return (
60
- <motion.div
61
- ref={ref}
62
- className={cn(hStackVariants({ spacing, align, justify, noWrap }), className)}
63
- {...motionProps}
64
- >
65
- {children}
66
- </motion.div>
67
- );
68
- },
69
- );
70
-
71
- HStack.displayName = 'HStack';
72
-
73
- export { HStack, hStackVariants };