@twintag/twintag-form 1.4.0-beta.1 → 1.4.0-beta.3

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 (34) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +8 -4
  4. package/dev/main.tsx +453 -233
  5. package/dist/components/config.d.ts +2 -2
  6. package/dist/components/containers/accordion.d.ts +2 -4
  7. package/dist/components/fields/checkbox.d.ts +1 -1
  8. package/dist/components/fields/date-picker.d.ts +1 -1
  9. package/dist/components/fields/file.d.ts +1 -1
  10. package/dist/components/fields/input.d.ts +1 -1
  11. package/dist/components/fields/radiogroup.d.ts +3 -1
  12. package/dist/components/fields/textarea.d.ts +1 -1
  13. package/dist/components/fields/toggle.d.ts +1 -1
  14. package/dist/components/fields/tri-toggle.d.ts +1 -1
  15. package/dist/components/provider.d.ts +3 -3
  16. package/dist/twintag-form.react.js +1637 -1624
  17. package/dist/types/index.d.ts +37 -22
  18. package/dist/utils/data.d.ts +5 -1
  19. package/index.html +1 -0
  20. package/lib/components/config.tsx +2 -2
  21. package/lib/components/containers/accordion.tsx +33 -14
  22. package/lib/components/fields/checkbox.tsx +1 -1
  23. package/lib/components/fields/date-picker.tsx +5 -1
  24. package/lib/components/fields/file.tsx +83 -83
  25. package/lib/components/fields/input.tsx +1 -1
  26. package/lib/components/fields/radiogroup.tsx +5 -1
  27. package/lib/components/fields/textarea.tsx +1 -1
  28. package/lib/components/fields/toggle.tsx +1 -1
  29. package/lib/components/fields/tri-toggle.tsx +1 -1
  30. package/lib/components/provider.tsx +163 -159
  31. package/lib/index.tsx +41 -41
  32. package/lib/types/index.ts +82 -49
  33. package/lib/utils/data.ts +5 -2
  34. package/package.json +1 -1
@@ -1,41 +1,56 @@
1
1
  import { ElementType } from 'react';
2
2
 
3
- export type ComponentConfig = {
3
+ export type InputCompConfig = {
4
4
  defaultValue?: unknown;
5
5
  component: ElementType;
6
6
  };
7
- export type ParsedJsonComponent = Props & {
8
- id: string;
9
- type: string;
10
- subtype?: string;
11
- comp?: ElementType;
12
- };
13
- export type RawJsonComponent = RawProps & Record<string, unknown> & {
14
- type: string;
15
- children?: RawJsonComponent[];
16
- } & {
17
- props?: RawProps;
18
- };
19
- type RawProps = Partial<{
7
+ type CompType = 'input' | 'item' | 'accordion' | 'category';
8
+ type StringLiteral<T> = T extends string ? string extends T ? never : T : never;
9
+ type RawProps<TType extends string = string, TSubtype extends string = string> = TType extends 'input' | 'item' ? {
10
+ type: TType;
20
11
  bind: string;
21
- subtype: string;
22
- label: string;
23
- icon: string;
24
- options: Partial<{
12
+ label?: string;
13
+ subtype?: string;
14
+ options?: Partial<{
25
15
  description: string;
26
16
  placeholder: string;
27
17
  error: string;
28
18
  optionalField: boolean;
29
19
  }>;
30
- }>;
31
- export type Props = Omit<RawProps, 'subtype'> & {
32
- bind: string;
20
+ } : TType extends 'accordion' ? TSubtype extends 'item' ? {
21
+ type: TType;
22
+ subtype: TSubtype;
23
+ value?: string;
24
+ label: string;
25
+ icon: string;
26
+ } : TSubtype extends 'multiple' | 'single' ? {
27
+ type: TType;
28
+ subtype: TSubtype;
29
+ defaultValue?: string;
30
+ } : never : {
31
+ type: TType;
32
+ label?: string;
33
+ subtype?: string;
34
+ };
35
+ export type RawJsonComponent = CompType extends infer T ? T extends CompType ? {
36
+ type: StringLiteral<T>;
37
+ props?: Omit<RawProps<StringLiteral<T>>, 'type'>;
38
+ children?: RawJsonComponent[];
39
+ } : never : never;
40
+ export type ParsedJsonComponent<TType extends string = string> = RawProps<TType> & {
41
+ id: string;
42
+ type: string;
43
+ subtype?: string;
44
+ comp?: ElementType;
33
45
  items?: ParsedJsonComponent[];
34
46
  };
47
+ export type Props<TType extends string = string, TSubtype extends string = string> = Omit<RawProps<TType, TSubtype>, 'type'> & {
48
+ items?: (Props<'input'> & ParsedJsonComponent)[];
49
+ };
35
50
  export type TwintagFormContext = {
36
51
  variant?: 'default' | 'beta';
37
52
  size?: 'base' | 'sm';
38
- inputs: Map<string, ComponentConfig>;
53
+ inputs: Map<string, InputCompConfig>;
39
54
  components: Map<string, ElementType>;
40
55
  config: ParsedJsonComponent[] | ParsedJsonComponent;
41
56
  resetValues: Record<string, unknown>;
@@ -1,3 +1,7 @@
1
1
  import { Props } from '../types';
2
2
 
3
- export declare const getBindSetFromItems: (items: Props[]) => Set<string>;
3
+ type Items = (Props<'input'> & {
4
+ items?: Items[];
5
+ })[];
6
+ export declare const getBindSetFromItems: (items: Items) => Set<string>;
7
+ export {};
package/index.html CHANGED
@@ -16,6 +16,7 @@
16
16
  "
17
17
  >
18
18
  <div>
19
+ <button id="set-config">Set config</button>
19
20
  <button id="get-values">Get Values</button>
20
21
  <button id="submit">Submit</button>
21
22
  </div>
@@ -1,5 +1,5 @@
1
1
  import type { ElementType, FC } from 'react';
2
- import type { ComponentConfig } from '../types';
2
+ import type { InputCompConfig } from '../types';
3
3
 
4
4
  import { Accordion } from './containers/accordion';
5
5
  import { Category } from './containers/category';
@@ -13,7 +13,7 @@ import { DEFAULT_TEXTAREA_VALUE, Textarea } from './fields/textarea';
13
13
  import { DEFAULT_TOGGLE_VALUE, Toggle } from './fields/toggle';
14
14
  import { DEFAULT_TRI_TOGGLE_VALUE, TriToggle } from './fields/tri-toggle';
15
15
 
16
- export const Inputs = new Map<string, ComponentConfig>()
16
+ export const Inputs = new Map<string, InputCompConfig>()
17
17
  .set('simple', {
18
18
  defaultValue: DEFAULT_INPUT_VALUE,
19
19
  component: InputField,
@@ -15,7 +15,8 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
15
15
  import { camelize } from '@twintag/twintag-form/utils/string';
16
16
 
17
17
  type AccordionRootProps = {
18
- multiple?: boolean;
18
+ type: 'single' | 'multiple';
19
+ defaultValue?: string;
19
20
  } & WithChildren;
20
21
 
21
22
  const accordionRootVariants = tv({
@@ -31,16 +32,22 @@ const accordionRootVariants = tv({
31
32
  },
32
33
  });
33
34
  const AccordionRoot: FC<AccordionRootProps> = ({
34
- multiple = true,
35
+ type,
36
+ defaultValue,
35
37
  children,
36
38
  }) => {
37
39
  const { variant } = useTwintagForm();
38
40
 
41
+ if (type === 'single') {
42
+ return (
43
+ <AccordionPrimitive type='single' defaultValue={defaultValue} collapsible>
44
+ <div className={accordionRootVariants({ variant })}>{children}</div>
45
+ </AccordionPrimitive>
46
+ );
47
+ }
48
+
39
49
  return (
40
- <AccordionPrimitive
41
- type={multiple ? 'multiple' : 'single'}
42
- collapsible={multiple ? undefined : true}
43
- >
50
+ <AccordionPrimitive type='multiple'>
44
51
  <div className={accordionRootVariants({ variant })}>{children}</div>
45
52
  </AccordionPrimitive>
46
53
  );
@@ -80,11 +87,15 @@ const accordionItemVariants = tv({
80
87
  },
81
88
  });
82
89
 
83
- type AccordionItemProps = Omit<Props, 'bind'> & WithChildren;
90
+ type AccordionItemProps = Omit<Props<'accordion', 'item'>, 'subtype'> & {
91
+ value?: string;
92
+ } & WithChildren;
84
93
  const AccordionItem: FC<AccordionItemProps> = ({
85
94
  label = '',
95
+ value = '',
86
96
  icon,
87
97
  children,
98
+ ...rest
88
99
  }) => {
89
100
  const { variant, size } = useTwintagForm();
90
101
 
@@ -96,7 +107,11 @@ const AccordionItem: FC<AccordionItemProps> = ({
96
107
  } = accordionItemVariants({ size, variant });
97
108
 
98
109
  return (
99
- <AccordionItemPrimitive value={camelize(label)} className={item()}>
110
+ <AccordionItemPrimitive
111
+ value={value ? value : camelize(label)}
112
+ className={item()}
113
+ {...rest}
114
+ >
100
115
  <AccordionTrigger className='text-category'>
101
116
  <span className={trigger()}>
102
117
  {icon ? <Icon name={icon} className={iconVariants()} /> : <></>}
@@ -109,14 +124,18 @@ const AccordionItem: FC<AccordionItemProps> = ({
109
124
  };
110
125
  AccordionItem.displayName = 'AccordionItem';
111
126
 
112
- type AccordionProps = {
113
- subtype?: 'item' | 'multiple' | 'single';
114
- label?: string;
115
- } & WithChildren;
127
+ type AccordionProps = Props<'accordion', 'item' | 'multiple' | 'single'> &
128
+ WithChildren;
116
129
  const Accordion: FC<AccordionProps> = ({ subtype = 'single', ...rest }) => {
117
- if (subtype === 'item') return <AccordionItem {...rest} />;
130
+ if (subtype === 'item') {
131
+ return (
132
+ <AccordionItem
133
+ {...(rest as Omit<Props<'accordion', 'item'>, 'subtype'>)}
134
+ />
135
+ );
136
+ }
118
137
 
119
- return <AccordionRoot {...rest} />;
138
+ return <AccordionRoot type={subtype} {...rest} />;
120
139
  };
121
140
 
122
141
  export { Accordion };
@@ -17,7 +17,7 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
17
 
18
18
  export const DEFAULT_CHECKBOX_VALUE = false;
19
19
 
20
- export const Checkbox: FC<Props> = ({ label, bind, options }) => {
20
+ export const Checkbox: FC<Props<'input'>> = ({ label, bind, options }) => {
21
21
  const { control } = useFormContext();
22
22
  const { variant, size } = useTwintagForm();
23
23
 
@@ -65,7 +65,11 @@ const datePickerTriggerVariants = tv({
65
65
 
66
66
  export const DEFAULT_DATE_PICKER_VALUE = null;
67
67
 
68
- export const DatePicker: React.FC<Props> = ({ label, bind, options }) => {
68
+ export const DatePicker: React.FC<Props<'input'>> = ({
69
+ label,
70
+ bind,
71
+ options,
72
+ }) => {
69
73
  const { variant, size } = useTwintagForm();
70
74
  const { control } = useFormContext();
71
75
 
@@ -1,83 +1,83 @@
1
- import type { FC } from 'react';
2
- import type { Props } from '@twintag/twintag-form/types';
3
-
4
- import { AsteriskIcon } from 'lucide-react';
5
- import { useFormContext } from 'react-hook-form';
6
-
7
- import {
8
- FormControl,
9
- FormDescription,
10
- FormField,
11
- FormItem,
12
- FormLabel,
13
- FormMessage,
14
- } from '@twintag/twintag-form/common/form';
15
- import { Input } from '@twintag/twintag-form/common/input';
16
- import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
-
18
- export const DEFAULT_FILE_VALUE = '';
19
-
20
- export const File: FC<Props> = ({ label, bind, options }) => {
21
- const { control } = useFormContext();
22
- const { variant, size } = useTwintagForm();
23
-
24
- const isOptional = options?.optionalField;
25
-
26
- return (
27
- <FormField
28
- defaultValue={DEFAULT_FILE_VALUE}
29
- control={control}
30
- name={bind}
31
- rules={{
32
- required: isOptional ? false : options?.error ?? true,
33
- }}
34
- render={({ field, formState }) => (
35
- <FormItem variant={variant}>
36
- <div className='space-y-1'>
37
- <FormLabel htmlFor={bind} size={size}>
38
- {label}
39
- {!isOptional ? (
40
- <span className='ml-0.5 inline-block'>
41
- <AsteriskIcon className='h-3 w-3 text-destructive' />
42
- </span>
43
- ) : (
44
- <></>
45
- )}
46
- </FormLabel>
47
- {options?.description ? (
48
- <FormDescription size={size}>
49
- {options.description}
50
- </FormDescription>
51
- ) : (
52
- <></>
53
- )}
54
- </div>
55
- <FormControl>
56
- <Input
57
- ref={(el) => {
58
- field.ref(el);
59
-
60
- if (el && formState.isSubmitSuccessful) {
61
- el.value = '';
62
- }
63
- }}
64
- id={bind}
65
- type='file'
66
- name={field.name}
67
- size={size}
68
- variant={variant}
69
- multiple
70
- onBlur={field.onBlur}
71
- onChange={(e) => {
72
- if (e.target.files) {
73
- field.onChange(e.target.files);
74
- }
75
- }}
76
- />
77
- </FormControl>
78
- <FormMessage size={size} className='pt-1.5' />
79
- </FormItem>
80
- )}
81
- />
82
- );
83
- };
1
+ import type { FC } from 'react';
2
+ import type { Props } from '@twintag/twintag-form/types';
3
+
4
+ import { AsteriskIcon } from 'lucide-react';
5
+ import { useFormContext } from 'react-hook-form';
6
+
7
+ import {
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from '@twintag/twintag-form/common/form';
15
+ import { Input } from '@twintag/twintag-form/common/input';
16
+ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
+
18
+ export const DEFAULT_FILE_VALUE = '';
19
+
20
+ export const File: FC<Props<'input'>> = ({ label, bind, options }) => {
21
+ const { control } = useFormContext();
22
+ const { variant, size } = useTwintagForm();
23
+
24
+ const isOptional = options?.optionalField;
25
+
26
+ return (
27
+ <FormField
28
+ defaultValue={DEFAULT_FILE_VALUE}
29
+ control={control}
30
+ name={bind}
31
+ rules={{
32
+ required: isOptional ? false : options?.error ?? true,
33
+ }}
34
+ render={({ field, formState }) => (
35
+ <FormItem variant={variant}>
36
+ <div className='space-y-1'>
37
+ <FormLabel htmlFor={bind} size={size}>
38
+ {label}
39
+ {!isOptional ? (
40
+ <span className='ml-0.5 inline-block'>
41
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
42
+ </span>
43
+ ) : (
44
+ <></>
45
+ )}
46
+ </FormLabel>
47
+ {options?.description ? (
48
+ <FormDescription size={size}>
49
+ {options.description}
50
+ </FormDescription>
51
+ ) : (
52
+ <></>
53
+ )}
54
+ </div>
55
+ <FormControl>
56
+ <Input
57
+ ref={(el) => {
58
+ field.ref(el);
59
+
60
+ if (el && formState.isSubmitSuccessful) {
61
+ el.value = '';
62
+ }
63
+ }}
64
+ id={bind}
65
+ type='file'
66
+ name={field.name}
67
+ size={size}
68
+ variant={variant}
69
+ multiple
70
+ onBlur={field.onBlur}
71
+ onChange={(e) => {
72
+ if (e.target.files) {
73
+ field.onChange(e.target.files);
74
+ }
75
+ }}
76
+ />
77
+ </FormControl>
78
+ <FormMessage size={size} className='pt-1.5' />
79
+ </FormItem>
80
+ )}
81
+ />
82
+ );
83
+ };
@@ -17,7 +17,7 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
17
 
18
18
  export const DEFAULT_INPUT_VALUE = '';
19
19
 
20
- export const Input: FC<Props> = ({ label, bind, options }) => {
20
+ export const Input: FC<Props<'input'>> = ({ label, bind, options }) => {
21
21
  const { control } = useFormContext();
22
22
 
23
23
  const { variant, size } = useTwintagForm();
@@ -20,7 +20,11 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
20
20
 
21
21
  export const DEFAULT_RADIOGROUP_VALUE = '';
22
22
 
23
- export const RadioGroup: FC<Props> = ({ label, bind, items, options }) => {
23
+ export const RadioGroup: FC<
24
+ Props<'input'> & {
25
+ items: Props<'input'>[];
26
+ }
27
+ > = ({ label, bind, items, options }) => {
24
28
  const { control } = useFormContext();
25
29
  const { size } = useTwintagForm();
26
30
 
@@ -17,7 +17,7 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
17
 
18
18
  export const DEFAULT_TEXTAREA_VALUE = '';
19
19
 
20
- export const Textarea: FC<Props> = ({ label, bind, options }) => {
20
+ export const Textarea: FC<Props<'input'>> = ({ label, bind, options }) => {
21
21
  const { control } = useFormContext();
22
22
  const { variant, size } = useTwintagForm();
23
23
 
@@ -17,7 +17,7 @@ import { useTwintagForm } from '@twintag/twintag-form/components/provider';
17
17
 
18
18
  export const DEFAULT_TOGGLE_VALUE = false;
19
19
 
20
- export const Toggle: FC<Props> = ({ label, bind, options }) => {
20
+ export const Toggle: FC<Props<'input'>> = ({ label, bind, options }) => {
21
21
  const { control } = useFormContext();
22
22
  const { variant, size } = useTwintagForm();
23
23
 
@@ -88,7 +88,7 @@ Slider.displayName = SliderPrimitive.Root.displayName;
88
88
 
89
89
  export const DEFAULT_TRI_TOGGLE_VALUE = null;
90
90
 
91
- export const TriToggle: FC<Props> = ({ label, bind, options }) => {
91
+ export const TriToggle: FC<Props<'input'>> = ({ label, bind, options }) => {
92
92
  const { control } = useFormContext();
93
93
  const { variant, size } = useTwintagForm();
94
94