@twintag/twintag-form 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +6 -0
- package/README.md +57 -0
- package/dev/index.css +32 -30
- package/dev/main.tsx +50 -27
- package/dist/common/accordion.d.ts +7 -0
- package/dist/common/button.d.ts +28 -0
- package/dist/common/calendar.d.ts +9 -0
- package/dist/common/form.d.ts +1 -1
- package/dist/common/icon.d.ts +7 -0
- package/dist/common/popover.d.ts +6 -0
- package/dist/common/scroll-area.d.ts +5 -0
- package/dist/common/select.d.ts +13 -0
- package/dist/components/config.d.ts +4 -0
- package/dist/components/containers/accordion.d.ts +8 -0
- package/dist/components/fields/date-picker.d.ts +3 -0
- package/dist/components/{Input/simple.d.ts → fields/input.d.ts} +1 -1
- package/dist/components/form.d.ts +3 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/index.d.ts +2 -0
- package/dist/twintag-form.react.js +19844 -6458
- package/dist/twintag-form.webcomponent.js +314 -299
- package/dist/types/index.d.ts +6 -5
- package/dist/utils/data.d.ts +2 -0
- package/dist/utils/string.d.ts +3 -0
- package/dist/utils/style.d.ts +2 -0
- package/index.html +15 -13
- package/lib/common/accordion.tsx +77 -0
- package/lib/common/button.tsx +3 -0
- package/lib/common/calendar.tsx +110 -0
- package/lib/common/form.tsx +14 -1
- package/lib/common/icon.tsx +35 -0
- package/lib/common/input.tsx +2 -1
- package/lib/common/popover.tsx +30 -0
- package/lib/common/scroll-area.tsx +46 -0
- package/lib/common/select.tsx +159 -0
- package/lib/common/textarea.tsx +1 -1
- package/lib/components/Render/index.tsx +4 -2
- package/lib/components/config.tsx +44 -0
- package/lib/components/containers/accordion.tsx +105 -0
- package/lib/components/containers/category.tsx +48 -0
- package/lib/components/fields/date-picker.tsx +117 -0
- package/lib/components/{Input → fields}/file.tsx +1 -1
- package/lib/components/{Input/simple.tsx → fields/input.tsx} +5 -5
- package/lib/components/{Input → fields}/textarea.tsx +2 -2
- package/lib/components/form.tsx +19 -3
- package/lib/components/index.tsx +0 -3
- package/lib/index.tsx +7 -8
- package/lib/types/index.ts +13 -11
- package/lib/utils/data.ts +18 -0
- package/lib/utils/string.ts +12 -0
- package/lib/utils/style.ts +8 -0
- package/package.json +11 -3
- package/tailwind.config.ts +19 -1
- package/dist/components/Input/index.d.ts +0 -8
- package/lib/components/Input/index.tsx +0 -32
- package/lib/components/category.tsx +0 -31
- /package/dist/components/{category.d.ts → containers/category.d.ts} +0 -0
- /package/dist/components/{Input → fields}/checkbox.d.ts +0 -0
- /package/dist/components/{Input → fields}/file.d.ts +0 -0
- /package/dist/components/{Input → fields}/radiogroup.d.ts +0 -0
- /package/dist/components/{Input → fields}/submit.d.ts +0 -0
- /package/dist/components/{Input → fields}/textarea.d.ts +0 -0
- /package/dist/components/{Input → fields}/toggle.d.ts +0 -0
- /package/lib/components/{Input → fields}/checkbox.tsx +0 -0
- /package/lib/components/{Input → fields}/radiogroup.tsx +0 -0
- /package/lib/components/{Input → fields}/submit.tsx +0 -0
- /package/lib/components/{Input → fields}/toggle.tsx +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Props } from '@twintag/twintag-form/types';
|
|
2
|
+
import type { WithChildren } from 'twintag-types/dom';
|
|
3
|
+
|
|
4
|
+
import { type FC } from 'react';
|
|
5
|
+
import { tv } from 'tailwind-variants';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AccordionContent,
|
|
9
|
+
AccordionItem as AccordionItemPrimitive,
|
|
10
|
+
Accordion as AccordionPrimitive,
|
|
11
|
+
AccordionTrigger,
|
|
12
|
+
} from '@twintag/twintag-form/common/accordion';
|
|
13
|
+
import { Icon } from '@twintag/twintag-form/common/icon';
|
|
14
|
+
import { useTwintagForm } from '@twintag/twintag-form/provider';
|
|
15
|
+
import { camelize } from '@twintag/twintag-form/utils/string';
|
|
16
|
+
|
|
17
|
+
type AccordionRootProps = {
|
|
18
|
+
multiple?: boolean;
|
|
19
|
+
} & WithChildren;
|
|
20
|
+
|
|
21
|
+
const accordionRootVariants = tv({
|
|
22
|
+
base: 'border',
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
default: 'rounded-md',
|
|
26
|
+
beta: 'rounded-none',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: 'default',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
const AccordionRoot: FC<AccordionRootProps> = ({
|
|
34
|
+
multiple = true,
|
|
35
|
+
children,
|
|
36
|
+
}) => {
|
|
37
|
+
const { variant } = useTwintagForm();
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<AccordionPrimitive
|
|
41
|
+
type={multiple ? 'multiple' : 'single'}
|
|
42
|
+
collapsible={multiple ? undefined : true}
|
|
43
|
+
>
|
|
44
|
+
<div className={accordionRootVariants({ variant })}>{children}</div>
|
|
45
|
+
</AccordionPrimitive>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
AccordionRoot.displayName = 'AccordionRoot';
|
|
49
|
+
|
|
50
|
+
const accordionItemVariants = tv({
|
|
51
|
+
slots: {
|
|
52
|
+
item: 'px-5 last:border-none',
|
|
53
|
+
trigger: 'flex flex-1 items-center gap-2.5 font-tf-font',
|
|
54
|
+
content: 'space-y-4',
|
|
55
|
+
},
|
|
56
|
+
variants: {
|
|
57
|
+
variant: {
|
|
58
|
+
default: {
|
|
59
|
+
trigger: 'text-base font-medium',
|
|
60
|
+
},
|
|
61
|
+
beta: {
|
|
62
|
+
trigger: 'text-sm',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
variant: 'default',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
type AccordionItemProps = Omit<Props, 'bind'> & WithChildren;
|
|
72
|
+
const AccordionItem: FC<AccordionItemProps> = ({
|
|
73
|
+
label = '',
|
|
74
|
+
icon,
|
|
75
|
+
children,
|
|
76
|
+
}) => {
|
|
77
|
+
const { variant } = useTwintagForm();
|
|
78
|
+
|
|
79
|
+
const { item, trigger, content } = accordionItemVariants({ variant });
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<AccordionItemPrimitive value={camelize(label)} className={item()}>
|
|
83
|
+
<AccordionTrigger className='text-category'>
|
|
84
|
+
<span className={trigger()}>
|
|
85
|
+
{icon ? <Icon name={icon} className='shrink-0' /> : <></>}
|
|
86
|
+
{label}
|
|
87
|
+
</span>
|
|
88
|
+
</AccordionTrigger>
|
|
89
|
+
<AccordionContent className={content()}>{children}</AccordionContent>
|
|
90
|
+
</AccordionItemPrimitive>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
AccordionItem.displayName = 'AccordionItem';
|
|
94
|
+
|
|
95
|
+
type AccordionProps = {
|
|
96
|
+
subtype?: 'item' | 'multiple' | 'single';
|
|
97
|
+
label?: string;
|
|
98
|
+
} & WithChildren;
|
|
99
|
+
const Accordion: FC<AccordionProps> = ({ subtype = 'single', ...rest }) => {
|
|
100
|
+
if (subtype === 'item') return <AccordionItem {...rest} />;
|
|
101
|
+
|
|
102
|
+
return <AccordionRoot {...rest} />;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export { Accordion };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { WithChildren } from 'twintag-types/dom';
|
|
3
|
+
|
|
4
|
+
import { tv } from 'tailwind-variants';
|
|
5
|
+
|
|
6
|
+
import { useTwintagForm } from '@twintag/twintag-form/provider';
|
|
7
|
+
|
|
8
|
+
const categoryStyle = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
container: 'relative w-full space-y-4',
|
|
11
|
+
heading: 'font-tf-font text-category',
|
|
12
|
+
content: 'grid gap-4',
|
|
13
|
+
},
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: {
|
|
17
|
+
heading: 'text-base font-medium',
|
|
18
|
+
},
|
|
19
|
+
beta: {
|
|
20
|
+
heading: 'text-sm font-normal',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: 'default',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
type Props = {
|
|
30
|
+
label?: string;
|
|
31
|
+
} & WithChildren;
|
|
32
|
+
|
|
33
|
+
const Category: FC<Props> = ({ label, children }) => {
|
|
34
|
+
const { variant } = useTwintagForm();
|
|
35
|
+
|
|
36
|
+
const { container, heading, content } = categoryStyle({ variant });
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<section className={container()}>
|
|
40
|
+
{label ? <h3 className={heading()}>{label}</h3> : <></>}
|
|
41
|
+
|
|
42
|
+
<div className={content()}>{children}</div>
|
|
43
|
+
</section>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
Category.displayName = 'Category';
|
|
47
|
+
|
|
48
|
+
export { Category };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Props } from '@twintag/twintag-form/types';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { format } from 'date-fns';
|
|
5
|
+
import { AsteriskIcon, Calendar as CalendarIcon } from 'lucide-react';
|
|
6
|
+
import { useFormContext } from 'react-hook-form';
|
|
7
|
+
import { tv } from 'tailwind-variants';
|
|
8
|
+
|
|
9
|
+
import { Button } from '@twintag/twintag-form/common/button';
|
|
10
|
+
import { Calendar } from '@twintag/twintag-form/common/calendar';
|
|
11
|
+
import {
|
|
12
|
+
FormControl,
|
|
13
|
+
FormDescription,
|
|
14
|
+
FormField,
|
|
15
|
+
FormItem,
|
|
16
|
+
FormLabel,
|
|
17
|
+
FormMessage,
|
|
18
|
+
} from '@twintag/twintag-form/common/form';
|
|
19
|
+
import {
|
|
20
|
+
Popover,
|
|
21
|
+
PopoverContent,
|
|
22
|
+
PopoverTrigger,
|
|
23
|
+
} from '@twintag/twintag-form/common/popover';
|
|
24
|
+
import { useTwintagForm } from '@twintag/twintag-form/provider';
|
|
25
|
+
|
|
26
|
+
const triggerVariants = tv({
|
|
27
|
+
base: 'w-full justify-start text-left font-normal',
|
|
28
|
+
variants: {
|
|
29
|
+
variant: {
|
|
30
|
+
default: 'px-3',
|
|
31
|
+
beta: 'rounded-none border-x-transparent border-t-transparent px-1 transition-none hover:bg-white hover:text-muted-foreground',
|
|
32
|
+
},
|
|
33
|
+
withValue: {
|
|
34
|
+
true: 'text-muted-foreground',
|
|
35
|
+
false: '',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const DatePicker: React.FC<Props> = ({ label, bind, options }) => {
|
|
41
|
+
const { variant } = useTwintagForm();
|
|
42
|
+
|
|
43
|
+
const { control } = useFormContext();
|
|
44
|
+
|
|
45
|
+
const isOptional = options?.optionalField;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<FormField
|
|
49
|
+
control={control}
|
|
50
|
+
name={bind}
|
|
51
|
+
rules={{
|
|
52
|
+
required: isOptional ? false : options?.error ?? true,
|
|
53
|
+
}}
|
|
54
|
+
render={({ field }) => (
|
|
55
|
+
<FormItem className='flex flex-col [&>div[data-radix-popper-content-wrapper]]:!z-50'>
|
|
56
|
+
<div className='space-y-0.5'>
|
|
57
|
+
<FormLabel>
|
|
58
|
+
{label}
|
|
59
|
+
{!isOptional ? (
|
|
60
|
+
<span className='ml-0.5 inline-block'>
|
|
61
|
+
<AsteriskIcon className='h-3 w-3 text-destructive' />
|
|
62
|
+
</span>
|
|
63
|
+
) : (
|
|
64
|
+
<></>
|
|
65
|
+
)}
|
|
66
|
+
</FormLabel>
|
|
67
|
+
{options?.description ? (
|
|
68
|
+
<FormDescription>{options.description}</FormDescription>
|
|
69
|
+
) : (
|
|
70
|
+
<></>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
<Popover>
|
|
74
|
+
<PopoverTrigger asChild>
|
|
75
|
+
<FormControl>
|
|
76
|
+
<Button
|
|
77
|
+
variant='outline'
|
|
78
|
+
className={triggerVariants({
|
|
79
|
+
variant,
|
|
80
|
+
withValue: !field.value,
|
|
81
|
+
className: 'h-10 gap-3',
|
|
82
|
+
})}
|
|
83
|
+
>
|
|
84
|
+
{field.value ? (
|
|
85
|
+
format(field.value, 'PPP')
|
|
86
|
+
) : (
|
|
87
|
+
<span>{options?.placeholder ?? 'Select a date'}</span>
|
|
88
|
+
)}
|
|
89
|
+
<CalendarIcon className='ml-auto h-4 w-4 opacity-50' />
|
|
90
|
+
</Button>
|
|
91
|
+
</FormControl>
|
|
92
|
+
</PopoverTrigger>
|
|
93
|
+
<PopoverContent className='w-auto p-0' align='start'>
|
|
94
|
+
<Calendar
|
|
95
|
+
fromYear={1970}
|
|
96
|
+
toYear={2050}
|
|
97
|
+
captionLayout='dropdown-buttons'
|
|
98
|
+
mode='single'
|
|
99
|
+
selected={
|
|
100
|
+
!isNaN(new Date(field.value as Date).getTime())
|
|
101
|
+
? new Date(field.value as Date)
|
|
102
|
+
: undefined
|
|
103
|
+
}
|
|
104
|
+
onSelect={field.onChange}
|
|
105
|
+
disabled={(date) =>
|
|
106
|
+
date > new Date() || date < new Date('1900-01-01')
|
|
107
|
+
}
|
|
108
|
+
initialFocus
|
|
109
|
+
/>
|
|
110
|
+
</PopoverContent>
|
|
111
|
+
</Popover>
|
|
112
|
+
<FormMessage className='pt-1' />
|
|
113
|
+
</FormItem>
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -12,18 +12,18 @@ import {
|
|
|
12
12
|
FormLabel,
|
|
13
13
|
FormMessage,
|
|
14
14
|
} from '@twintag/twintag-form/common/form';
|
|
15
|
-
import { Input } from '@twintag/twintag-form/common/input';
|
|
15
|
+
import { Input as InputPrimitive } from '@twintag/twintag-form/common/input';
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const Input: FC<Props> = ({ label, bind, options }) => {
|
|
18
18
|
const { control } = useFormContext();
|
|
19
19
|
|
|
20
20
|
const isOptional = options?.optionalField;
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<FormField
|
|
24
|
+
defaultValue=''
|
|
24
25
|
control={control}
|
|
25
26
|
name={bind}
|
|
26
|
-
defaultValue=''
|
|
27
27
|
rules={{
|
|
28
28
|
required: isOptional ? false : options?.error ?? true,
|
|
29
29
|
}}
|
|
@@ -47,13 +47,13 @@ export const Simple: FC<Props> = ({ label, bind, options }) => {
|
|
|
47
47
|
)}
|
|
48
48
|
</div>
|
|
49
49
|
<FormControl>
|
|
50
|
-
<
|
|
50
|
+
<InputPrimitive
|
|
51
51
|
type='text'
|
|
52
52
|
placeholder={options?.placeholder ?? 'Write here...'}
|
|
53
53
|
{...field}
|
|
54
54
|
/>
|
|
55
55
|
</FormControl>
|
|
56
|
-
<FormMessage />
|
|
56
|
+
<FormMessage className='pt-1' />
|
|
57
57
|
</FormItem>
|
|
58
58
|
)}
|
|
59
59
|
/>
|
|
@@ -21,9 +21,9 @@ export const Textarea: FC<Props> = ({ label, bind, options }) => {
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<FormField
|
|
24
|
+
defaultValue=''
|
|
24
25
|
control={control}
|
|
25
26
|
name={bind}
|
|
26
|
-
defaultValue=''
|
|
27
27
|
rules={{
|
|
28
28
|
required: isOptional ? false : options?.error ?? true,
|
|
29
29
|
}}
|
|
@@ -52,7 +52,7 @@ export const Textarea: FC<Props> = ({ label, bind, options }) => {
|
|
|
52
52
|
placeholder={options?.placeholder ?? 'Write here...'}
|
|
53
53
|
/>
|
|
54
54
|
</FormControl>
|
|
55
|
-
<FormMessage />
|
|
55
|
+
<FormMessage className='pt-1' />
|
|
56
56
|
</FormItem>
|
|
57
57
|
)}
|
|
58
58
|
/>
|
package/lib/components/form.tsx
CHANGED
|
@@ -7,21 +7,36 @@ import { useForm } from 'react-hook-form';
|
|
|
7
7
|
import { Form } from '@twintag/twintag-form/common/form';
|
|
8
8
|
|
|
9
9
|
type Props = {
|
|
10
|
+
defaultValues?: FieldValues;
|
|
10
11
|
resetOnSubmit?: boolean;
|
|
11
12
|
} & WithChildren;
|
|
12
13
|
|
|
13
|
-
export const TwintagForm = ({
|
|
14
|
+
export const TwintagForm = ({
|
|
15
|
+
defaultValues,
|
|
16
|
+
resetOnSubmit,
|
|
17
|
+
children,
|
|
18
|
+
}: Props) => {
|
|
14
19
|
const ref = useRef<HTMLFormElement>(null);
|
|
15
20
|
|
|
16
|
-
const form = useForm(
|
|
21
|
+
const form = useForm({
|
|
22
|
+
defaultValues,
|
|
23
|
+
});
|
|
17
24
|
|
|
18
25
|
useEffect(() => {
|
|
19
26
|
if (form.formState.isSubmitSuccessful && resetOnSubmit) {
|
|
20
|
-
|
|
27
|
+
if (defaultValues) {
|
|
28
|
+
form.reset(
|
|
29
|
+
Object.keys(defaultValues).reduce(
|
|
30
|
+
(acc, key) => ({ ...acc, [key]: '' }),
|
|
31
|
+
{},
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
}, [form.formState]);
|
|
23
37
|
|
|
24
38
|
const onValid = (data: FieldValues) => {
|
|
39
|
+
console.log('form', data);
|
|
25
40
|
const customSubmit = new CustomEvent('twintag-form-valid', {
|
|
26
41
|
detail: data,
|
|
27
42
|
bubbles: true,
|
|
@@ -64,6 +79,7 @@ export const TwintagForm = ({ resetOnSubmit, children }: Props) => {
|
|
|
64
79
|
return (
|
|
65
80
|
<Form {...form}>
|
|
66
81
|
<form
|
|
82
|
+
id='twintag-form'
|
|
67
83
|
ref={ref}
|
|
68
84
|
className='relative space-y-4 font-tf-font'
|
|
69
85
|
onSubmit={form.handleSubmit(onValid, onInvalid)}
|
package/lib/components/index.tsx
CHANGED
|
@@ -3,9 +3,6 @@ import type { RawJsonComponent } from '@twintag/twintag-form/types';
|
|
|
3
3
|
import { Renderer } from '@twintag/twintag-form/components/Render';
|
|
4
4
|
import { useJson } from '@twintag/twintag-form/components/Render/useJson';
|
|
5
5
|
|
|
6
|
-
export { Input } from './Input';
|
|
7
|
-
export { Category } from './category';
|
|
8
|
-
|
|
9
6
|
type Props = {
|
|
10
7
|
config: RawJsonComponent | RawJsonComponent[];
|
|
11
8
|
};
|
package/lib/index.tsx
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import type { ComponentProps
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
2
|
import type { RawJsonComponent } from '@twintag/twintag-form/types';
|
|
3
3
|
|
|
4
4
|
import { TwintagJsonRenderer } from '@twintag/twintag-form/components';
|
|
5
|
-
import {
|
|
5
|
+
import { components } from '@twintag/twintag-form/components/config';
|
|
6
6
|
import { TwintagForm } from '@twintag/twintag-form/components/form';
|
|
7
|
-
import { Input } from '@twintag/twintag-form/components/Input';
|
|
8
7
|
import { TwintagFormProvider } from '@twintag/twintag-form/provider';
|
|
9
8
|
|
|
10
|
-
const components = new Map<string, ElementType>()
|
|
11
|
-
.set('input', Input)
|
|
12
|
-
.set('category', Category);
|
|
13
|
-
|
|
14
9
|
type Props = {
|
|
15
10
|
config: RawJsonComponent[];
|
|
16
11
|
options?: Partial<{
|
|
12
|
+
defaultValues?: ComponentProps<typeof TwintagForm>['defaultValues'];
|
|
17
13
|
variant?: ComponentProps<typeof TwintagFormProvider>['variant'];
|
|
18
14
|
resetOnSubmit?: boolean;
|
|
19
15
|
}>;
|
|
@@ -22,7 +18,10 @@ type Props = {
|
|
|
22
18
|
export const Form = ({ config, options }: Props) => {
|
|
23
19
|
return (
|
|
24
20
|
<TwintagFormProvider variant={options?.variant} components={components}>
|
|
25
|
-
<TwintagForm
|
|
21
|
+
<TwintagForm
|
|
22
|
+
defaultValues={options?.defaultValues}
|
|
23
|
+
resetOnSubmit={options?.resetOnSubmit}
|
|
24
|
+
>
|
|
26
25
|
{config.map((item, idx) => (
|
|
27
26
|
<TwintagJsonRenderer key={idx} config={item} />
|
|
28
27
|
))}
|
package/lib/types/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ type RawProps = Partial<{
|
|
|
9
9
|
bind: string;
|
|
10
10
|
subtype: string;
|
|
11
11
|
label: string;
|
|
12
|
+
icon: string;
|
|
12
13
|
options: Partial<{
|
|
13
14
|
description: string;
|
|
14
15
|
placeholder: string;
|
|
@@ -17,17 +18,13 @@ type RawProps = Partial<{
|
|
|
17
18
|
}>;
|
|
18
19
|
}>;
|
|
19
20
|
|
|
20
|
-
export type RawJsonComponent = RawProps &
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export type Props = Omit<RawProps, 'subtype'> & {
|
|
28
|
-
bind: string;
|
|
29
|
-
items?: ParsedJsonComponent[];
|
|
30
|
-
};
|
|
21
|
+
export type RawJsonComponent = RawProps &
|
|
22
|
+
Record<string, unknown> & {
|
|
23
|
+
type: string;
|
|
24
|
+
children?: RawJsonComponent[];
|
|
25
|
+
} & {
|
|
26
|
+
props?: RawProps;
|
|
27
|
+
};
|
|
31
28
|
|
|
32
29
|
export type ParsedJsonComponent = Props & {
|
|
33
30
|
id: string;
|
|
@@ -35,3 +32,8 @@ export type ParsedJsonComponent = Props & {
|
|
|
35
32
|
subtype?: string;
|
|
36
33
|
comp?: ElementType;
|
|
37
34
|
};
|
|
35
|
+
|
|
36
|
+
export type Props = Omit<RawProps, 'subtype'> & {
|
|
37
|
+
bind: string;
|
|
38
|
+
items?: ParsedJsonComponent[];
|
|
39
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Props } from '@twintag/twintag-form/types';
|
|
2
|
+
|
|
3
|
+
export const getBindSetFromItems = (items: Props[]): Set<string> => {
|
|
4
|
+
const binds = new Set<string>();
|
|
5
|
+
|
|
6
|
+
items.forEach((item) => {
|
|
7
|
+
if (item.bind) {
|
|
8
|
+
binds.add(item.bind);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (item.items) {
|
|
12
|
+
const subBinds = getBindSetFromItems(item.items);
|
|
13
|
+
subBinds.forEach((bind) => binds.add(bind));
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return binds;
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const camelize = (str: string) => {
|
|
2
|
+
return str
|
|
3
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
|
|
4
|
+
return index === 0 ? word.toLowerCase() : word.toUpperCase();
|
|
5
|
+
})
|
|
6
|
+
.replace(/\s+/g, '');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const camelizeKebab = (s: string) =>
|
|
10
|
+
s.replace(/-./g, (x) => x[1].toUpperCase());
|
|
11
|
+
|
|
12
|
+
export const capitalize = (s: string) => s && s[0].toUpperCase() + s.slice(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twintag/twintag-form",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "Twintag",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,12 +14,16 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@heroicons/react": "^2.
|
|
17
|
+
"@heroicons/react": "^2.1.1",
|
|
18
18
|
"@r2wc/react-to-web-component": "^2.0.3",
|
|
19
|
+
"@radix-ui/react-accordion": "^1.1.2",
|
|
19
20
|
"@radix-ui/react-checkbox": "^1.0.4",
|
|
20
21
|
"@radix-ui/react-icons": "^1.3.0",
|
|
21
22
|
"@radix-ui/react-label": "^2.0.2",
|
|
23
|
+
"@radix-ui/react-popover": "^1.0.7",
|
|
22
24
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
25
|
+
"@radix-ui/react-scroll-area": "^1.0.5",
|
|
26
|
+
"@radix-ui/react-select": "^2.0.0",
|
|
23
27
|
"@radix-ui/react-slot": "^1.0.2",
|
|
24
28
|
"@radix-ui/react-switch": "^1.0.2",
|
|
25
29
|
"@storybook/react": "7.0.2",
|
|
@@ -27,10 +31,14 @@
|
|
|
27
31
|
"@twind/preset-autoprefix": "^1.0.7",
|
|
28
32
|
"@twind/preset-tailwind": "^1.1.4",
|
|
29
33
|
"@twind/with-web-components": "^1.1.3",
|
|
34
|
+
"clsx": "^2.1.0",
|
|
35
|
+
"date-fns": "^3.3.1",
|
|
30
36
|
"lucide-react": "^0.269.0",
|
|
31
37
|
"react": "^18.2.0",
|
|
38
|
+
"react-day-picker": "^8.10.0",
|
|
32
39
|
"react-dom": "^18.2.0",
|
|
33
|
-
"react-hook-form": "^7.
|
|
40
|
+
"react-hook-form": "^7.50.1",
|
|
41
|
+
"tailwind-merge": "^2.2.1",
|
|
34
42
|
"uuid": "^9.0.1"
|
|
35
43
|
},
|
|
36
44
|
"devDependencies": {
|
package/tailwind.config.ts
CHANGED
|
@@ -3,13 +3,16 @@ import type { Config } from 'tailwindcss';
|
|
|
3
3
|
import { fontFamily } from 'tailwindcss/defaultTheme';
|
|
4
4
|
|
|
5
5
|
const config = {
|
|
6
|
-
content: ['./
|
|
6
|
+
content: ['./lib/**/*.{ts,tsx}'],
|
|
7
7
|
theme: {
|
|
8
8
|
extend: {
|
|
9
9
|
colors: {
|
|
10
10
|
category: 'hsl(var(--tf-category) / <alpha-value>)',
|
|
11
11
|
label: 'hsl(var(--tf-label) / <alpha-value>)',
|
|
12
|
+
|
|
12
13
|
background: 'hsl(var(--tf-background) / <alpha-value>)',
|
|
14
|
+
foreground: 'hsl(var(--tf-foreground) / <alpha-value>)',
|
|
15
|
+
|
|
13
16
|
primary: {
|
|
14
17
|
DEFAULT: 'hsl(var(--tf-primary) / <alpha-value>)',
|
|
15
18
|
foreground: 'hsl(var(--tf-primary-foreground) / <alpha-value>)',
|
|
@@ -26,6 +29,7 @@ const config = {
|
|
|
26
29
|
DEFAULT: 'hsl(var(--tf-muted) / <alpha-value>)',
|
|
27
30
|
foreground: 'hsl(var(--tf-muted-foreground) / <alpha-value>)',
|
|
28
31
|
},
|
|
32
|
+
|
|
29
33
|
input: 'hsl(var(--tf-input) / <alpha-value>)',
|
|
30
34
|
ring: 'hsl(var(--tf-ring) / <alpha-value>)',
|
|
31
35
|
},
|
|
@@ -37,6 +41,20 @@ const config = {
|
|
|
37
41
|
fontFamily: {
|
|
38
42
|
'tf-font': ['var(--tf-font)', ...fontFamily.sans],
|
|
39
43
|
},
|
|
44
|
+
keyframes: {
|
|
45
|
+
'accordion-down': {
|
|
46
|
+
from: { height: '0' },
|
|
47
|
+
to: { height: 'var(--radix-accordion-content-height)' },
|
|
48
|
+
},
|
|
49
|
+
'accordion-up': {
|
|
50
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
51
|
+
to: { height: '0' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
animation: {
|
|
55
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
56
|
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
57
|
+
},
|
|
40
58
|
},
|
|
41
59
|
},
|
|
42
60
|
} satisfies Config;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ElementType, FC } from 'react';
|
|
2
|
-
|
|
3
|
-
import { Checkbox } from './checkbox';
|
|
4
|
-
import { File } from './file';
|
|
5
|
-
import { RadioGroup } from './radiogroup';
|
|
6
|
-
import { Simple } from './simple';
|
|
7
|
-
import { Submit } from './submit';
|
|
8
|
-
import { Textarea } from './textarea';
|
|
9
|
-
import { Toggle } from './toggle';
|
|
10
|
-
|
|
11
|
-
const Inputs = new Map<string, ElementType>()
|
|
12
|
-
.set('simple', Simple)
|
|
13
|
-
.set('textarea', Textarea)
|
|
14
|
-
.set('toggle', Toggle)
|
|
15
|
-
.set('file', File)
|
|
16
|
-
.set('submit', Submit)
|
|
17
|
-
.set('checkbox', Checkbox)
|
|
18
|
-
.set('radiogroup', RadioGroup);
|
|
19
|
-
|
|
20
|
-
type Props = {
|
|
21
|
-
subtype?: string;
|
|
22
|
-
label?: string;
|
|
23
|
-
bind?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const Input: FC<Props> = ({ subtype = 'simple', ...rest }) => {
|
|
27
|
-
const Comp = Inputs.get(subtype);
|
|
28
|
-
|
|
29
|
-
if (!Comp) return <></>;
|
|
30
|
-
|
|
31
|
-
return <Comp {...rest} />;
|
|
32
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { FC } from 'react';
|
|
2
|
-
import type { WithChildren } from 'twintag-types/dom';
|
|
3
|
-
|
|
4
|
-
import { tv } from 'tailwind-variants';
|
|
5
|
-
|
|
6
|
-
const categoryStyle = tv({
|
|
7
|
-
slots: {
|
|
8
|
-
container: 'w-full space-y-4',
|
|
9
|
-
heading: 'font-tf-font text-sm text-category',
|
|
10
|
-
items: 'grid gap-4',
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
type Props = {
|
|
15
|
-
label?: string;
|
|
16
|
-
} & WithChildren;
|
|
17
|
-
|
|
18
|
-
const Category: FC<Props> = ({ label, children }) => {
|
|
19
|
-
const { container, heading, items } = categoryStyle();
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<section className={container()}>
|
|
23
|
-
{label ? <h3 className={heading()}>{label}</h3> : <></>}
|
|
24
|
-
|
|
25
|
-
<div className={items()}>{children}</div>
|
|
26
|
-
</section>
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
Category.displayName = 'Category';
|
|
30
|
-
|
|
31
|
-
export { Category };
|
|
File without changes
|