@luanthnh/cntt-ui 0.1.7 → 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.
- package/package.json +5 -1
- package/.storybook/globals.d.ts +0 -1
- package/.storybook/main.ts +0 -29
- package/.storybook/preview.ts +0 -32
- package/assets/fonts/Montserrat-Black.eot +0 -0
- package/assets/fonts/Montserrat-Black.ttf +0 -0
- package/assets/fonts/Montserrat-Black.woff +0 -0
- package/assets/fonts/Montserrat-Black.woff2 +0 -0
- package/assets/fonts/Montserrat-BlackItalic.eot +0 -0
- package/assets/fonts/Montserrat-BlackItalic.ttf +0 -0
- package/assets/fonts/Montserrat-BlackItalic.woff +0 -0
- package/assets/fonts/Montserrat-BlackItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Bold.eot +0 -0
- package/assets/fonts/Montserrat-Bold.ttf +0 -0
- package/assets/fonts/Montserrat-Bold.woff +0 -0
- package/assets/fonts/Montserrat-Bold.woff2 +0 -0
- package/assets/fonts/Montserrat-BoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-BoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-BoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-BoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraBold.eot +0 -0
- package/assets/fonts/Montserrat-ExtraBold.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraBold.woff +0 -0
- package/assets/fonts/Montserrat-ExtraBold.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-ExtraBoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraLight.eot +0 -0
- package/assets/fonts/Montserrat-ExtraLight.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraLight.woff +0 -0
- package/assets/fonts/Montserrat-ExtraLight.woff2 +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.eot +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.woff +0 -0
- package/assets/fonts/Montserrat-ExtraLightItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Italic.eot +0 -0
- package/assets/fonts/Montserrat-Italic.ttf +0 -0
- package/assets/fonts/Montserrat-Italic.woff +0 -0
- package/assets/fonts/Montserrat-Italic.woff2 +0 -0
- package/assets/fonts/Montserrat-Light.eot +0 -0
- package/assets/fonts/Montserrat-Light.ttf +0 -0
- package/assets/fonts/Montserrat-Light.woff +0 -0
- package/assets/fonts/Montserrat-Light.woff2 +0 -0
- package/assets/fonts/Montserrat-LightItalic.eot +0 -0
- package/assets/fonts/Montserrat-LightItalic.ttf +0 -0
- package/assets/fonts/Montserrat-LightItalic.woff +0 -0
- package/assets/fonts/Montserrat-LightItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Medium.eot +0 -0
- package/assets/fonts/Montserrat-Medium.ttf +0 -0
- package/assets/fonts/Montserrat-Medium.woff +0 -0
- package/assets/fonts/Montserrat-Medium.woff2 +0 -0
- package/assets/fonts/Montserrat-MediumItalic.eot +0 -0
- package/assets/fonts/Montserrat-MediumItalic.ttf +0 -0
- package/assets/fonts/Montserrat-MediumItalic.woff +0 -0
- package/assets/fonts/Montserrat-MediumItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Regular.eot +0 -0
- package/assets/fonts/Montserrat-Regular.ttf +0 -0
- package/assets/fonts/Montserrat-Regular.woff +0 -0
- package/assets/fonts/Montserrat-Regular.woff2 +0 -0
- package/assets/fonts/Montserrat-SemiBold.eot +0 -0
- package/assets/fonts/Montserrat-SemiBold.ttf +0 -0
- package/assets/fonts/Montserrat-SemiBold.woff +0 -0
- package/assets/fonts/Montserrat-SemiBold.woff2 +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.eot +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.ttf +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.woff +0 -0
- package/assets/fonts/Montserrat-SemiBoldItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Thin.eot +0 -0
- package/assets/fonts/Montserrat-Thin.ttf +0 -0
- package/assets/fonts/Montserrat-Thin.woff +0 -0
- package/assets/fonts/Montserrat-Thin.woff2 +0 -0
- package/assets/fonts/Montserrat-ThinItalic.eot +0 -0
- package/assets/fonts/Montserrat-ThinItalic.ttf +0 -0
- package/assets/fonts/Montserrat-ThinItalic.woff +0 -0
- package/assets/fonts/Montserrat-ThinItalic.woff2 +0 -0
- package/assets/fonts/Montserrat-Variable.eot +0 -0
- package/assets/fonts/Montserrat-Variable.ttf +0 -0
- package/assets/fonts/Montserrat-Variable.woff +0 -0
- package/assets/fonts/Montserrat-Variable.woff2 +0 -0
- package/assets/fonts/Montserrat-VariableItalic.eot +0 -0
- package/assets/fonts/Montserrat-VariableItalic.ttf +0 -0
- package/assets/fonts/Montserrat-VariableItalic.woff +0 -0
- package/assets/fonts/Montserrat-VariableItalic.woff2 +0 -0
- package/assets/icons/arrow-left.svg +0 -1
- package/assets/icons/file.svg +0 -1
- package/assets/icons/globe.svg +0 -1
- package/assets/icons/logo-line.svg +0 -1
- package/assets/icons/next.svg +0 -1
- package/assets/icons/panel-left-expand.svg +0 -1
- package/assets/icons/placeholder.svg +0 -57
- package/assets/icons/vercel.svg +0 -1
- package/assets/icons/window.svg +0 -1
- package/assets/lotties/error-404.json +0 -19642
- package/assets/lotties/error.json +0 -2414
- package/assets/lotties/loader.json +0 -305
- package/components/Welcome.mdx +0 -74
- package/components/lenis/index.tsx +0 -48
- package/components/motion/auto-height.tsx +0 -56
- package/components/motion/cursor.tsx +0 -108
- package/components/motion/highlight.tsx +0 -605
- package/components/motion/number-ticker.tsx +0 -55
- package/components/motion/slot.tsx +0 -106
- package/components/motion/waves.tsx +0 -417
- package/components/primitives/tabs.tsx +0 -174
- package/components/ui/Accordion/index.stories.tsx +0 -39
- package/components/ui/Accordion/index.tsx +0 -170
- package/components/ui/Alert/index.stories.tsx +0 -39
- package/components/ui/Alert/index.tsx +0 -60
- package/components/ui/AlertDialog/index.stories.tsx +0 -47
- package/components/ui/AlertDialog/index.tsx +0 -172
- package/components/ui/AspectRatio/index.stories.tsx +0 -40
- package/components/ui/AspectRatio/index.tsx +0 -9
- package/components/ui/Avatar/index.stories.tsx +0 -39
- package/components/ui/Avatar/index.tsx +0 -44
- package/components/ui/Badge/index.stories.tsx +0 -64
- package/components/ui/Badge/index.tsx +0 -46
- package/components/ui/Breadcrumb/index.stories.tsx +0 -64
- package/components/ui/Breadcrumb/index.tsx +0 -102
- package/components/ui/Button/index.stories.tsx +0 -232
- package/components/ui/Button/index.tsx +0 -114
- package/components/ui/Calendar/index.stories.tsx +0 -20
- package/components/ui/Calendar/index.tsx +0 -149
- package/components/ui/Card/index.stories.tsx +0 -39
- package/components/ui/Card/index.tsx +0 -65
- package/components/ui/Carousel/index.stories.tsx +0 -37
- package/components/ui/Carousel/index.tsx +0 -242
- package/components/ui/Chart/index.stories.tsx +0 -53
- package/components/ui/Chart/index.tsx +0 -322
- package/components/ui/Checkbox/index.stories.tsx +0 -56
- package/components/ui/Checkbox/index.tsx +0 -167
- package/components/ui/CircleProcess/index.stories.tsx +0 -29
- package/components/ui/CircleProcess/index.tsx +0 -50
- package/components/ui/Collapsible/index.stories.tsx +0 -33
- package/components/ui/Collapsible/index.tsx +0 -124
- package/components/ui/Command/index.stories.tsx +0 -65
- package/components/ui/Command/index.tsx +0 -161
- package/components/ui/Container/index.stories.tsx +0 -22
- package/components/ui/Container/index.tsx +0 -30
- package/components/ui/ContextMenu/index.stories.tsx +0 -51
- package/components/ui/ContextMenu/index.tsx +0 -224
- package/components/ui/Dialog/index.stories.tsx +0 -44
- package/components/ui/Dialog/index.tsx +0 -156
- package/components/ui/Drawer/index.stories.tsx +0 -54
- package/components/ui/Drawer/index.tsx +0 -124
- package/components/ui/DropdownMenu/index.stories.tsx +0 -83
- package/components/ui/DropdownMenu/index.tsx +0 -231
- package/components/ui/Dropzone/index.stories.tsx +0 -18
- package/components/ui/Dropzone/index.tsx +0 -47
- package/components/ui/Form/date-field.tsx +0 -77
- package/components/ui/Form/index.stories.tsx +0 -67
- package/components/ui/Form/index.tsx +0 -188
- package/components/ui/Form/select-field.tsx +0 -55
- package/components/ui/Form/text-area-field.tsx +0 -37
- package/components/ui/Form/text-field.tsx +0 -72
- package/components/ui/HStack/index.stories.tsx +0 -48
- package/components/ui/HStack/index.tsx +0 -73
- package/components/ui/HoverCard/index.stories.tsx +0 -38
- package/components/ui/HoverCard/index.tsx +0 -38
- package/components/ui/Icons/index.stories.tsx +0 -27
- package/components/ui/Icons/index.tsx +0 -33
- package/components/ui/ImageWithFallback/index.stories.tsx +0 -32
- package/components/ui/ImageWithFallback/index.tsx +0 -34
- package/components/ui/Input/index.stories.tsx +0 -47
- package/components/ui/Input/index.tsx +0 -21
- package/components/ui/InputOtp/index.stories.tsx +0 -35
- package/components/ui/InputOtp/index.tsx +0 -70
- package/components/ui/Label/index.stories.tsx +0 -18
- package/components/ui/Label/index.tsx +0 -21
- package/components/ui/Marquee/index.stories.tsx +0 -71
- package/components/ui/Marquee/index.tsx +0 -65
- package/components/ui/Menubar/index.stories.tsx +0 -116
- package/components/ui/Menubar/index.tsx +0 -252
- package/components/ui/NavigationMenu/index.stories.tsx +0 -112
- package/components/ui/NavigationMenu/index.tsx +0 -185
- package/components/ui/NoData/index.stories.tsx +0 -24
- package/components/ui/NoData/index.tsx +0 -19
- package/components/ui/Pagination/index.stories.tsx +0 -53
- package/components/ui/Pagination/index.tsx +0 -114
- package/components/ui/Popover/index.stories.tsx +0 -31
- package/components/ui/Popover/index.tsx +0 -42
- package/components/ui/Progress/index.stories.tsx +0 -35
- package/components/ui/Progress/index.tsx +0 -28
- package/components/ui/RadioGroup/index.stories.tsx +0 -28
- package/components/ui/RadioGroup/index.tsx +0 -45
- package/components/ui/Resizable/index.stories.tsx +0 -44
- package/components/ui/Resizable/index.tsx +0 -54
- package/components/ui/ScrollArea/index.stories.tsx +0 -31
- package/components/ui/ScrollArea/index.tsx +0 -56
- package/components/ui/Select/index.stories.tsx +0 -64
- package/components/ui/Select/index.tsx +0 -170
- package/components/ui/Separator/index.stories.tsx +0 -31
- package/components/ui/Separator/index.tsx +0 -28
- package/components/ui/Sheet/index.stories.tsx +0 -45
- package/components/ui/Sheet/index.tsx +0 -130
- package/components/ui/Sidebar/index.stories.tsx +0 -82
- package/components/ui/Sidebar/index.tsx +0 -676
- package/components/ui/Skeleton/index.stories.tsx +0 -36
- package/components/ui/Skeleton/index.tsx +0 -13
- package/components/ui/Slider/index.stories.tsx +0 -48
- package/components/ui/Slider/index.tsx +0 -82
- package/components/ui/Slot/index.stories.tsx +0 -29
- package/components/ui/Slot/index.tsx +0 -106
- package/components/ui/Sonner/index.stories.tsx +0 -36
- package/components/ui/Sonner/index.tsx +0 -31
- package/components/ui/Switch/index.stories.tsx +0 -33
- package/components/ui/Switch/index.tsx +0 -28
- package/components/ui/Table/index.stories.tsx +0 -74
- package/components/ui/Table/index.tsx +0 -95
- package/components/ui/Tabs/index.stories.tsx +0 -38
- package/components/ui/Tabs/index.tsx +0 -78
- package/components/ui/Text/index.stories.tsx +0 -53
- package/components/ui/Text/index.tsx +0 -138
- package/components/ui/Textarea/index.stories.tsx +0 -25
- package/components/ui/Textarea/index.tsx +0 -18
- package/components/ui/Toggle/index.stories.tsx +0 -52
- package/components/ui/Toggle/index.tsx +0 -46
- package/components/ui/ToggleGroup/index.stories.tsx +0 -52
- package/components/ui/ToggleGroup/index.tsx +0 -69
- package/components/ui/Tooltip/index.stories.tsx +0 -29
- package/components/ui/Tooltip/index.tsx +0 -35
- package/components/ui/VStack/index.stories.tsx +0 -45
- package/components/ui/VStack/index.tsx +0 -69
- package/components/ui/colors.stories.tsx +0 -148
- package/eslint.config.js +0 -10
- package/hooks/index.ts +0 -3
- package/hooks/use-auto-height.tsx +0 -99
- package/hooks/use-controlled-state.tsx +0 -32
- package/hooks/use-mobile.ts +0 -19
- package/index.ts +0 -58
- package/lib/get-strict-context.ts +0 -15
- package/lib/utils.ts +0 -10
- package/scripts/generate-exports.ts +0 -32
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
- package/tsup.config.ts +0 -11
- package/types/svg.d.ts +0 -10
- 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,188 +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
|
-
};
|
|
@@ -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 };
|