@twintag/twintag-form 0.6.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.eslintrc.cjs +5 -0
  2. package/.turbo/turbo-build.log +14 -9
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/CHANGELOG.md +12 -0
  5. package/README.md +87 -59
  6. package/dev/index.css +29 -0
  7. package/dev/main.tsx +163 -0
  8. package/dist/common/button.d.ts +137 -0
  9. package/dist/common/checkbox.d.ts +4 -0
  10. package/dist/common/form.d.ts +24 -0
  11. package/dist/common/input.d.ts +4 -0
  12. package/dist/common/label.d.ts +4 -0
  13. package/dist/common/radiogroup.d.ts +5 -0
  14. package/dist/common/switch.d.ts +4 -0
  15. package/dist/common/textarea.d.ts +4 -0
  16. package/dist/components/Input/checkbox.d.ts +2 -6
  17. package/dist/components/Input/file.d.ts +2 -6
  18. package/dist/components/Input/index.d.ts +2 -2
  19. package/dist/components/Input/radiogroup.d.ts +3 -0
  20. package/dist/components/Input/simple.d.ts +2 -6
  21. package/dist/components/Input/submit.d.ts +2 -6
  22. package/dist/components/Input/textarea.d.ts +2 -6
  23. package/dist/components/Input/toggle.d.ts +2 -6
  24. package/dist/components/Render/index.d.ts +7 -0
  25. package/dist/components/Render/useJson.d.ts +2 -0
  26. package/dist/components/{Label/index.d.ts → category.d.ts} +3 -4
  27. package/dist/components/form.d.ts +7 -0
  28. package/dist/components/index.d.ts +8 -4
  29. package/dist/index.d.ts +8 -9
  30. package/dist/provider.d.ts +8 -0
  31. package/dist/twintag-form.react.js +7930 -8119
  32. package/dist/twintag-form.webcomponent.js +2007 -1769
  33. package/dist/types/index.d.ts +33 -0
  34. package/index.html +13 -0
  35. package/lib/common/button.tsx +49 -0
  36. package/lib/common/checkbox.tsx +40 -0
  37. package/lib/common/form.tsx +192 -0
  38. package/lib/common/input.tsx +55 -0
  39. package/lib/common/label.tsx +26 -0
  40. package/lib/common/radiogroup.tsx +56 -0
  41. package/lib/common/switch.tsx +39 -0
  42. package/lib/common/textarea.tsx +53 -0
  43. package/lib/components/Input/checkbox.tsx +55 -42
  44. package/lib/components/Input/file.tsx +64 -72
  45. package/lib/components/Input/index.tsx +15 -15
  46. package/lib/components/Input/radiogroup.tsx +84 -0
  47. package/lib/components/Input/simple.tsx +50 -28
  48. package/lib/components/Input/submit.tsx +4 -13
  49. package/lib/components/Input/textarea.tsx +49 -27
  50. package/lib/components/Input/toggle.tsx +54 -45
  51. package/lib/components/Render/index.tsx +30 -0
  52. package/lib/components/Render/useJson.ts +76 -0
  53. package/lib/components/category.tsx +31 -0
  54. package/lib/components/form.tsx +75 -0
  55. package/lib/components/index.tsx +27 -4
  56. package/lib/index.tsx +24 -48
  57. package/lib/provider.tsx +36 -0
  58. package/lib/types/index.ts +37 -0
  59. package/lib/webcomponent.ts +6 -6
  60. package/package.json +17 -9
  61. package/prettier.config.mjs +26 -0
  62. package/tailwind.config.ts +43 -0
  63. package/tsconfig.json +14 -3
  64. package/twind.config.ts +4 -21
  65. package/vite.config.ts +1 -1
  66. package/dist/components/Category/__styles.d.ts +0 -29
  67. package/dist/components/Category/index.d.ts +0 -8
  68. package/dist/components/Form/index.d.ts +0 -12
  69. package/dist/components/Input/__props.d.ts +0 -10
  70. package/dist/components/Input/__styles.d.ts +0 -460
  71. package/dist/styles.d.ts +0 -487
  72. package/lib/__stories__/Form.stories.tsx +0 -141
  73. package/lib/components/Category/__styles.ts +0 -12
  74. package/lib/components/Category/index.tsx +0 -26
  75. package/lib/components/Form/index.tsx +0 -86
  76. package/lib/components/Input/__props.ts +0 -10
  77. package/lib/components/Input/__styles.ts +0 -132
  78. package/lib/components/Label/index.tsx +0 -24
  79. package/lib/styles.ts +0 -143
  80. package/tailwind.config.cjs +0 -8
@@ -0,0 +1,33 @@
1
+ import type { ElementType } from 'react';
2
+ export type TwintagFormContext = {
3
+ variant?: 'default' | 'beta';
4
+ components: Map<string, ElementType>;
5
+ };
6
+ type RawProps = Partial<{
7
+ bind: string;
8
+ subtype: string;
9
+ label: string;
10
+ options: Partial<{
11
+ description: string;
12
+ placeholder: string;
13
+ error: string;
14
+ optionalField: boolean;
15
+ }>;
16
+ }>;
17
+ export type RawJsonComponent = RawProps & {
18
+ type: string;
19
+ children?: RawJsonComponent[];
20
+ } & {
21
+ props?: RawProps;
22
+ };
23
+ export type Props = Omit<RawProps, 'subtype'> & {
24
+ bind: string;
25
+ items?: ParsedJsonComponent[];
26
+ };
27
+ export type ParsedJsonComponent = Props & {
28
+ id: string;
29
+ type: string;
30
+ subtype?: string;
31
+ comp?: ElementType;
32
+ };
33
+ export {};
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Twintag Form</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./dev/main.tsx"></script>
11
+ </body>
12
+ </html>
13
+
@@ -0,0 +1,49 @@
1
+ import type { VariantProps } from 'tailwind-variants';
2
+
3
+ import * as React from 'react';
4
+ import { Slot } from '@radix-ui/react-slot';
5
+ import { tv } from 'tailwind-variants';
6
+
7
+ const buttonVariants = tv({
8
+ base: [
9
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors',
10
+ 'ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2',
11
+ 'disabled:pointer-events-none disabled:opacity-50',
12
+ ],
13
+ variants: {
14
+ variant: {
15
+ default:
16
+ 'bg-primary-foreground text-primary shadow hover:bg-primary-foreground/90',
17
+ },
18
+ size: {
19
+ default: 'h-9 px-4 py-2',
20
+ sm: 'h-8 rounded-md px-3 text-xs',
21
+ lg: 'h-10 rounded-md px-8',
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: 'default',
26
+ size: 'default',
27
+ },
28
+ });
29
+
30
+ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
31
+ VariantProps<typeof buttonVariants> & {
32
+ asChild?: boolean;
33
+ };
34
+
35
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
36
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
37
+ const Comp = asChild ? Slot : 'button';
38
+ return (
39
+ <Comp
40
+ className={buttonVariants({ variant, size, className })}
41
+ ref={ref}
42
+ {...props}
43
+ />
44
+ );
45
+ },
46
+ );
47
+ Button.displayName = 'Button';
48
+
49
+ export { Button, buttonVariants };
@@ -0,0 +1,40 @@
1
+ import * as React from 'react';
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
3
+ import { CheckIcon } from '@radix-ui/react-icons';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ const checkboxVariants = tv({
7
+ slots: {
8
+ root: [
9
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-accent shadow',
10
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1',
11
+ 'disabled:cursor-not-allowed disabled:opacity-50',
12
+ 'data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground',
13
+ ],
14
+ indicator: 'flex items-center justify-center text-current',
15
+ icon: 'h-4 w-4',
16
+ },
17
+ });
18
+ const Checkbox = React.forwardRef<
19
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
20
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
21
+ >(({ className, ...props }, ref) => {
22
+ const { root, indicator, icon } = checkboxVariants();
23
+
24
+ return (
25
+ <CheckboxPrimitive.Root
26
+ ref={ref}
27
+ className={root({
28
+ className,
29
+ })}
30
+ {...props}
31
+ >
32
+ <CheckboxPrimitive.Indicator className={indicator()}>
33
+ <CheckIcon className={icon()} />
34
+ </CheckboxPrimitive.Indicator>
35
+ </CheckboxPrimitive.Root>
36
+ );
37
+ });
38
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
39
+
40
+ export { Checkbox };
@@ -0,0 +1,192 @@
1
+ import type * as LabelPrimitive from '@radix-ui/react-label';
2
+ import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
3
+
4
+ import * as React from 'react';
5
+ import { Slot } from '@radix-ui/react-slot';
6
+ import { Controller, FormProvider, useFormContext } from 'react-hook-form';
7
+ import { tv } from 'tailwind-variants';
8
+
9
+ import { Label } from './label';
10
+
11
+ const Form = FormProvider;
12
+
13
+ type FormFieldContextValue<
14
+ TFieldValues extends FieldValues = FieldValues,
15
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
16
+ > = {
17
+ name: TName;
18
+ };
19
+
20
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
21
+ {} as FormFieldContextValue,
22
+ );
23
+
24
+ const FormField = <
25
+ TFieldValues extends FieldValues = FieldValues,
26
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
27
+ >({
28
+ ...props
29
+ }: ControllerProps<TFieldValues, TName>) => {
30
+ return (
31
+ <FormFieldContext.Provider value={{ name: props.name }}>
32
+ <Controller {...props} />
33
+ </FormFieldContext.Provider>
34
+ );
35
+ };
36
+
37
+ const useFormField = () => {
38
+ const fieldContext = React.useContext(FormFieldContext);
39
+ const itemContext = React.useContext(FormItemContext);
40
+ const { getFieldState, formState } = useFormContext();
41
+
42
+ const fieldState = getFieldState(fieldContext.name, formState);
43
+
44
+ if (!fieldContext) {
45
+ throw new Error('useFormField should be used within <FormField>');
46
+ }
47
+
48
+ const { id } = itemContext;
49
+
50
+ return {
51
+ id,
52
+ name: fieldContext.name,
53
+ formItemId: `${id}-form-item`,
54
+ formDescriptionId: `${id}-form-item-description`,
55
+ formMessageId: `${id}-form-item-message`,
56
+ ...fieldState,
57
+ };
58
+ };
59
+
60
+ type FormItemContextValue = {
61
+ id: string;
62
+ };
63
+
64
+ const FormItemContext = React.createContext<FormItemContextValue>(
65
+ {} as FormItemContextValue,
66
+ );
67
+
68
+ const formItemVariants = tv({
69
+ base: 'space-y-2',
70
+ });
71
+ const FormItem = React.forwardRef<
72
+ HTMLDivElement,
73
+ React.HTMLAttributes<HTMLDivElement>
74
+ >(({ className, ...props }, ref) => {
75
+ const id = React.useId();
76
+
77
+ return (
78
+ <FormItemContext.Provider value={{ id }}>
79
+ <div ref={ref} className={formItemVariants({ className })} {...props} />
80
+ </FormItemContext.Provider>
81
+ );
82
+ });
83
+ FormItem.displayName = 'FormItem';
84
+
85
+ const formLabelVariants = tv({
86
+ base: '',
87
+ variants: {
88
+ error: {
89
+ true: 'text-destructive',
90
+ },
91
+ },
92
+ });
93
+ const FormLabel = React.forwardRef<
94
+ React.ElementRef<typeof LabelPrimitive.Root>,
95
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
96
+ >(({ className, ...props }, ref) => {
97
+ const { error, formItemId } = useFormField();
98
+
99
+ return (
100
+ <Label
101
+ ref={ref}
102
+ className={formLabelVariants({
103
+ error: !!error,
104
+ className,
105
+ })}
106
+ htmlFor={formItemId}
107
+ {...props}
108
+ />
109
+ );
110
+ });
111
+ FormLabel.displayName = 'FormLabel';
112
+
113
+ const FormControl = React.forwardRef<
114
+ React.ElementRef<typeof Slot>,
115
+ React.ComponentPropsWithoutRef<typeof Slot>
116
+ >(({ ...props }, ref) => {
117
+ const { error, formItemId, formDescriptionId, formMessageId } =
118
+ useFormField();
119
+
120
+ return (
121
+ <Slot
122
+ ref={ref}
123
+ id={formItemId}
124
+ aria-describedby={
125
+ !error
126
+ ? `${formDescriptionId}`
127
+ : `${formDescriptionId} ${formMessageId}`
128
+ }
129
+ aria-invalid={!!error}
130
+ {...props}
131
+ />
132
+ );
133
+ });
134
+ FormControl.displayName = 'FormControl';
135
+
136
+ const formDescriptionVariants = tv({
137
+ base: 'text-[0.8rem] text-muted-foreground',
138
+ });
139
+ const FormDescription = React.forwardRef<
140
+ HTMLParagraphElement,
141
+ React.HTMLAttributes<HTMLParagraphElement>
142
+ >(({ className, ...props }, ref) => {
143
+ const { formDescriptionId } = useFormField();
144
+
145
+ return (
146
+ <p
147
+ ref={ref}
148
+ id={formDescriptionId}
149
+ className={formDescriptionVariants({ className })}
150
+ {...props}
151
+ />
152
+ );
153
+ });
154
+ FormDescription.displayName = 'FormDescription';
155
+
156
+ const formMessageVariants = tv({
157
+ base: 'text-[0.8rem] font-medium text-destructive',
158
+ });
159
+ const FormMessage = React.forwardRef<
160
+ HTMLParagraphElement,
161
+ React.HTMLAttributes<HTMLParagraphElement>
162
+ >(({ className, children, ...props }, ref) => {
163
+ const { error, formMessageId } = useFormField();
164
+ const body = error ? String(error?.message) : children;
165
+
166
+ if (!body) {
167
+ return null;
168
+ }
169
+
170
+ return (
171
+ <p
172
+ ref={ref}
173
+ id={formMessageId}
174
+ className={formMessageVariants({ className })}
175
+ {...props}
176
+ >
177
+ {body}
178
+ </p>
179
+ );
180
+ });
181
+ FormMessage.displayName = 'FormMessage';
182
+
183
+ export {
184
+ useFormField,
185
+ Form,
186
+ FormItem,
187
+ FormLabel,
188
+ FormControl,
189
+ FormDescription,
190
+ FormMessage,
191
+ FormField,
192
+ };
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+ import { tv } from 'tailwind-variants';
3
+
4
+ import { useTwintagForm } from '@twintag/twintag-form/provider';
5
+
6
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
7
+
8
+ const inputVariants = tv({
9
+ slots: {
10
+ root: 'relative',
11
+ input: [
12
+ 'flex h-10 w-full border-input bg-background py-2 text-sm text-primary-foreground placeholder:text-muted-foreground focus-visible:outline-none',
13
+ 'file:border-0 file:bg-transparent file:px-0 file:text-sm file:font-medium file:text-primary-foreground',
14
+ 'disabled:cursor-not-allowed disabled:opacity-50',
15
+ ],
16
+ },
17
+ variants: {
18
+ variant: {
19
+ default: {
20
+ input:
21
+ 'rounded-md border px-3 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
22
+ },
23
+ beta: {
24
+ root: 'after:absolute after:left-0 after:top-[calc(100%_+_2px)] after:w-full after:rounded-full after:content-[""] focus-within:after:h-0.5 focus-within:after:bg-ring',
25
+ input: 'border-b px-1',
26
+ },
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ },
32
+ });
33
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
34
+ ({ className, type, ...props }, ref) => {
35
+ const { variant } = useTwintagForm();
36
+
37
+ const { root, input } = inputVariants({ variant });
38
+
39
+ return (
40
+ <div className={root()}>
41
+ <input
42
+ type={type}
43
+ className={input({
44
+ className,
45
+ })}
46
+ ref={ref}
47
+ {...props}
48
+ />
49
+ </div>
50
+ );
51
+ },
52
+ );
53
+ Input.displayName = 'Input';
54
+
55
+ export { Input };
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+ import * as LabelPrimitive from '@radix-ui/react-label';
3
+ import { tv } from 'tailwind-variants';
4
+
5
+ const labelVariants = tv({
6
+ base: [
7
+ 'text-sm font-medium leading-none text-primary-foreground',
8
+ 'peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
9
+ ],
10
+ });
11
+
12
+ const Label = React.forwardRef<
13
+ React.ElementRef<typeof LabelPrimitive.Root>,
14
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={labelVariants({
19
+ className,
20
+ })}
21
+ {...props}
22
+ />
23
+ ));
24
+ Label.displayName = LabelPrimitive.Root.displayName;
25
+
26
+ export { Label };
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
3
+ import { Circle } from 'lucide-react';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ const radioGroupVariants = tv({
7
+ base: 'grid gap-2',
8
+ });
9
+ const RadioGroup = React.forwardRef<
10
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
12
+ >(({ className, ...props }, ref) => {
13
+ return (
14
+ <RadioGroupPrimitive.Root
15
+ className={radioGroupVariants({ className })}
16
+ {...props}
17
+ ref={ref}
18
+ />
19
+ );
20
+ });
21
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
22
+
23
+ const radioGroupItemVariants = tv({
24
+ slots: {
25
+ item: [
26
+ 'aspect-square h-4 w-4 items-center rounded-full border border-accent text-accent ring-offset-background',
27
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
28
+ 'disabled:cursor-not-allowed disabled:opacity-50',
29
+ ],
30
+ indicator: 'relative block h-full w-full',
31
+ icon: 'absolute left-1/2 top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 transform fill-current text-current',
32
+ },
33
+ });
34
+ const RadioGroupItem = React.forwardRef<
35
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
36
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
37
+ >(({ className, ...props }, ref) => {
38
+ const { item, indicator, icon } = radioGroupItemVariants();
39
+
40
+ return (
41
+ <RadioGroupPrimitive.Item
42
+ ref={ref}
43
+ className={item({
44
+ className,
45
+ })}
46
+ {...props}
47
+ >
48
+ <RadioGroupPrimitive.Indicator className={indicator()}>
49
+ <Circle className={icon()} />
50
+ </RadioGroupPrimitive.Indicator>
51
+ </RadioGroupPrimitive.Item>
52
+ );
53
+ });
54
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
55
+
56
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import * as SwitchPrimitives from '@radix-ui/react-switch';
3
+ import { tv } from 'tailwind-variants';
4
+
5
+ const switchVariants = tv({
6
+ slots: {
7
+ root: [
8
+ 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors',
9
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
10
+ 'disabled:cursor-not-allowed disabled:opacity-50',
11
+ 'data-[state=checked]:bg-accent data-[state=unchecked]:bg-muted',
12
+ ],
13
+ thumb: [
14
+ 'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform',
15
+ 'data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
16
+ ],
17
+ },
18
+ });
19
+ const Switch = React.forwardRef<
20
+ React.ElementRef<typeof SwitchPrimitives.Root>,
21
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
22
+ >(({ className, ...props }, ref) => {
23
+ const { root, thumb } = switchVariants();
24
+
25
+ return (
26
+ <SwitchPrimitives.Root
27
+ className={root({
28
+ className,
29
+ })}
30
+ {...props}
31
+ ref={ref}
32
+ >
33
+ <SwitchPrimitives.Thumb className={thumb()} />
34
+ </SwitchPrimitives.Root>
35
+ );
36
+ });
37
+ Switch.displayName = SwitchPrimitives.Root.displayName;
38
+
39
+ export { Switch };
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { tv } from 'tailwind-variants';
3
+
4
+ import { useTwintagForm } from '@twintag/twintag-form/provider';
5
+
6
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
7
+
8
+ const textareaVariants = tv({
9
+ slots: {
10
+ root: 'relative',
11
+ textarea: [
12
+ 'flex min-h-[60px] w-full border-input bg-background py-2 text-sm text-primary-foreground shadow-sm placeholder:text-muted-foreground focus-visible:outline-none',
13
+ 'disabled:cursor-not-allowed disabled:opacity-50',
14
+ ],
15
+ },
16
+ variants: {
17
+ variant: {
18
+ default: {
19
+ textarea:
20
+ 'rounded-md border px-3 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
21
+ },
22
+ beta: {
23
+ root: 'after:absolute after:left-0 after:top-[calc(100%_+_2px)] after:w-full after:rounded-full after:content-[""] focus-within:after:h-0.5 focus-within:after:bg-ring',
24
+ textarea: 'border-b px-1',
25
+ },
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'default',
30
+ },
31
+ });
32
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
33
+ ({ className, ...props }, ref) => {
34
+ const { variant } = useTwintagForm();
35
+
36
+ const { root, textarea } = textareaVariants({ variant });
37
+
38
+ return (
39
+ <div className={root()}>
40
+ <textarea
41
+ className={textarea({
42
+ className,
43
+ })}
44
+ ref={ref}
45
+ {...props}
46
+ />
47
+ </div>
48
+ );
49
+ },
50
+ );
51
+ Textarea.displayName = 'Textarea';
52
+
53
+ export { Textarea };
@@ -1,52 +1,65 @@
1
1
  import type { FC } from 'react';
2
- import type { VariantProps } from 'tailwind-variants';
2
+ import type { Props } from '@twintag/twintag-form/types';
3
3
 
4
- import { useController, useFormContext } from 'react-hook-form';
5
- import { CheckIcon } from '@heroicons/react/24/outline';
6
- import * as RdxCheckbox from '@radix-ui/react-checkbox';
4
+ import { AsteriskIcon } from 'lucide-react';
5
+ import { useFormContext } from 'react-hook-form';
7
6
 
8
- import { Label } from '@twintag/twintag-form/components';
9
- import { type CommonProps } from './__props';
10
- import { checkboxStyle } from './__styles';
7
+ import { Checkbox as CommonCheckbox } from '@twintag/twintag-form/common/checkbox';
8
+ import {
9
+ FormControl,
10
+ FormDescription,
11
+ FormField,
12
+ FormItem,
13
+ FormLabel,
14
+ FormMessage,
15
+ } from '@twintag/twintag-form/common/form';
11
16
 
12
- type Props = CommonProps<VariantProps<typeof checkboxStyle>['variant']>;
13
-
14
- const Checkbox: FC<Props> = ({ label, bind, options }) => {
17
+ export const Checkbox: FC<Props> = ({ label, bind, options }) => {
15
18
  const { control } = useFormContext();
16
19
 
17
- const {
18
- field: { ref, value, onChange },
19
- } = useController({
20
- control,
21
- name: bind,
22
- defaultValue: false,
23
- rules: {
24
- required: options?.optionalField ? false : `${label} is required`,
25
- },
26
- });
27
-
28
- const { container, checkbox } = checkboxStyle({ variant: options?.variant });
20
+ const isOptional = options?.optionalField;
29
21
 
30
22
  return (
31
- <label className={container()}>
32
- <RdxCheckbox.Root
33
- ref={ref}
34
- className={checkbox()}
35
- defaultChecked={value}
36
- onCheckedChange={(state) => {
37
- onChange(state);
38
- }}
39
- >
40
- <RdxCheckbox.Indicator className='text-input'>
41
- <CheckIcon className='w-4' />
42
- </RdxCheckbox.Indicator>
43
- </RdxCheckbox.Root>
44
-
45
- <Label className='text-label' required={!options?.optionalField}>
46
- {label}
47
- </Label>
48
- </label>
23
+ <FormField
24
+ control={control}
25
+ name={bind}
26
+ defaultValue={false}
27
+ rules={{
28
+ required: isOptional ? false : options?.error ?? true,
29
+ }}
30
+ render={({ field }) => (
31
+ <FormItem className='pt-1.5'>
32
+ <div className='flex space-x-2'>
33
+ <FormControl>
34
+ <CommonCheckbox
35
+ {...field}
36
+ checked={field.value as boolean}
37
+ onCheckedChange={(state) => {
38
+ field.onChange(state);
39
+ }}
40
+ />
41
+ </FormControl>
42
+ <div className='grid gap-1.5'>
43
+ <FormLabel>
44
+ {label}
45
+ {!isOptional ? (
46
+ <span className='ml-0.5 inline-block'>
47
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
48
+ </span>
49
+ ) : (
50
+ <></>
51
+ )}
52
+ </FormLabel>
53
+ {options?.description ? (
54
+ <FormDescription>{options.description}</FormDescription>
55
+ ) : (
56
+ <></>
57
+ )}
58
+ <FormMessage />
59
+ </div>
60
+ </div>
61
+ </FormItem>
62
+ )}
63
+ />
49
64
  );
50
65
  };
51
-
52
- export default Checkbox;