@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
@@ -1,84 +1,76 @@
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 { CloudArrowUpIcon } from '@heroicons/react/24/outline';
4
+ import { AsteriskIcon } from 'lucide-react';
5
+ import { useFormContext } from 'react-hook-form';
6
6
 
7
- import { Label } from '@twintag/twintag-form/components';
8
- import { type CommonProps } from './__props';
9
- import { fileStyle } from './__styles';
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';
10
16
 
11
- type Props = CommonProps<VariantProps<typeof fileStyle>['variant']>;
12
-
13
- const File: FC<Props> = ({ label, bind, options }) => {
17
+ export const File: FC<Props> = ({ label, bind, options }) => {
14
18
  const { control } = useFormContext();
15
19
 
16
- const {
17
- field: { ref, value, onChange },
18
- } = useController({
19
- control,
20
- name: bind,
21
- defaultValue: null,
22
- rules: {
23
- required: options?.optionalField ? false : `${label} is required`,
24
- },
25
- });
26
-
27
- const { container, span, input } = fileStyle({ variant: options?.variant });
20
+ const isOptional = options?.optionalField;
28
21
 
29
22
  return (
30
- <label className={container()}>
31
- {label ? (
32
- <Label className={span()} required={!options?.optionalField}>
33
- {label}
34
- </Label>
35
- ) : (
36
- <></>
37
- )}
38
-
39
- <input
40
- ref={ref}
41
- type='file'
42
- multiple
43
- capture='environment'
44
- onChange={(e) => {
45
- if (e.target.files) {
46
- onChange(e.target.files);
47
- }
48
- }}
49
- className='peer sr-only'
50
- />
51
-
52
- <span
53
- className={`${input()} ${
54
- value && value.length > 0
55
- ? 'border-primary text-input'
56
- : 'text-placeholder border-slate-700/10'
57
- }`}
58
- >
59
- <span className='grid'>
60
- {value && value.length > 0 ? (
61
- Array.from(value as unknown as FileList).map((item, index) => {
62
- return (
63
- <span
64
- key={index}
65
- className='block max-w-full overflow-hidden text-ellipsis whitespace-nowrap'
66
- >
67
- {item.name}
23
+ <FormField
24
+ defaultValue=''
25
+ control={control}
26
+ name={bind}
27
+ rules={{
28
+ required: isOptional ? false : options?.error ?? true,
29
+ }}
30
+ render={({ field, formState }) => (
31
+ <FormItem>
32
+ <div className='space-y-0.5'>
33
+ <FormLabel htmlFor={bind}>
34
+ {label}
35
+ {!isOptional ? (
36
+ <span className='ml-0.5 inline-block'>
37
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
68
38
  </span>
69
- );
70
- })
71
- ) : (
72
- <span className='block max-w-full overflow-hidden text-ellipsis whitespace-nowrap'>
73
- {options?.placeholder || 'No file selected'}
74
- </span>
75
- )}
76
- </span>
39
+ ) : (
40
+ <></>
41
+ )}
42
+ </FormLabel>
43
+ {options?.description ? (
44
+ <FormDescription>{options.description}</FormDescription>
45
+ ) : (
46
+ <></>
47
+ )}
48
+ </div>
49
+ <FormControl>
50
+ <Input
51
+ ref={(el) => {
52
+ field.ref(el);
77
53
 
78
- <CloudArrowUpIcon className='h-6 w-6 min-w-[1.5rem]' />
79
- </span>
80
- </label>
54
+ if (el && formState.isSubmitSuccessful) {
55
+ el.value = '';
56
+ }
57
+ }}
58
+ id={bind}
59
+ type='file'
60
+ name={field.name}
61
+ capture='environment'
62
+ multiple
63
+ onBlur={field.onBlur}
64
+ onChange={(e) => {
65
+ if (e.target.files) {
66
+ field.onChange(e.target.files);
67
+ }
68
+ }}
69
+ />
70
+ </FormControl>
71
+ <FormMessage />
72
+ </FormItem>
73
+ )}
74
+ />
81
75
  );
82
76
  };
83
-
84
- export default File;
@@ -1,19 +1,21 @@
1
- import type { FC } from 'react';
1
+ import type { ElementType, FC } from 'react';
2
2
 
3
- import Simple from './simple';
4
- import Textarea from './textarea';
5
- import Toggle from './toggle';
6
- import File from './file';
7
- import Submit from './submit';
8
- import Checkbox from './checkbox';
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';
9
10
 
10
- const Inputs = new Map()
11
+ const Inputs = new Map<string, ElementType>()
11
12
  .set('simple', Simple)
12
13
  .set('textarea', Textarea)
13
14
  .set('toggle', Toggle)
14
15
  .set('file', File)
15
16
  .set('submit', Submit)
16
- .set('checkbox', Checkbox);
17
+ .set('checkbox', Checkbox)
18
+ .set('radiogroup', RadioGroup);
17
19
 
18
20
  type Props = {
19
21
  subtype?: string;
@@ -21,12 +23,10 @@ type Props = {
21
23
  bind?: string;
22
24
  };
23
25
 
24
- const Input: FC<Props> = ({ subtype = 'simple', ...rest }) => {
25
- const InputComponent = Inputs.get(subtype);
26
+ export const Input: FC<Props> = ({ subtype = 'simple', ...rest }) => {
27
+ const Comp = Inputs.get(subtype);
26
28
 
27
- if (!InputComponent) return <></>;
29
+ if (!Comp) return <></>;
28
30
 
29
- return <InputComponent {...rest} />;
31
+ return <Comp {...rest} />;
30
32
  };
31
-
32
- export default Input;
@@ -0,0 +1,84 @@
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 {
16
+ RadioGroup as CommonRadioGroup,
17
+ RadioGroupItem,
18
+ } from '@twintag/twintag-form/common/radiogroup';
19
+
20
+ export const RadioGroup: FC<Props> = ({ label, bind, items, options }) => {
21
+ const { control } = useFormContext();
22
+
23
+ const isOptional = options?.optionalField;
24
+
25
+ return (
26
+ <FormField
27
+ control={control}
28
+ name={bind}
29
+ defaultValue=''
30
+ rules={{
31
+ required: isOptional ? false : options?.error ?? true,
32
+ }}
33
+ render={({ field }) => (
34
+ <FormItem className='space-y-3'>
35
+ <div className='space-y-0.5'>
36
+ <FormLabel>
37
+ {label}
38
+ {!isOptional ? (
39
+ <span className='ml-0.5 inline-block'>
40
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
41
+ </span>
42
+ ) : (
43
+ <></>
44
+ )}
45
+ </FormLabel>
46
+ {options?.description ? (
47
+ <FormDescription>{options.description}</FormDescription>
48
+ ) : (
49
+ <></>
50
+ )}
51
+ </div>
52
+ <FormControl>
53
+ <CommonRadioGroup
54
+ ref={field.ref}
55
+ value={field.value as string}
56
+ onValueChange={field.onChange}
57
+ >
58
+ {items?.map((item) => {
59
+ if (item.type === 'item') {
60
+ return (
61
+ <FormItem
62
+ key={item.id}
63
+ className='flex items-center space-x-3 space-y-0'
64
+ >
65
+ <FormControl>
66
+ <RadioGroupItem value={item.bind} />
67
+ </FormControl>
68
+ <FormLabel className='font-normal'>
69
+ {item.label}
70
+ </FormLabel>
71
+ </FormItem>
72
+ );
73
+ }
74
+
75
+ return <></>;
76
+ })}
77
+ </CommonRadioGroup>
78
+ </FormControl>
79
+ <FormMessage />
80
+ </FormItem>
81
+ )}
82
+ />
83
+ );
84
+ };
@@ -1,39 +1,61 @@
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 { AsteriskIcon } from 'lucide-react';
4
5
  import { useFormContext } from 'react-hook-form';
5
6
 
6
- import { Label } from '@twintag/twintag-form/components';
7
- import { type CommonProps } from './__props';
8
- import { simpleStyle } from './__styles';
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';
9
16
 
10
- type Props = CommonProps<VariantProps<typeof simpleStyle>['variant']>;
17
+ export const Simple: FC<Props> = ({ label, bind, options }) => {
18
+ const { control } = useFormContext();
11
19
 
12
- const Simple: FC<Props> = ({ label, bind, options }) => {
13
- const { container, span, input } = simpleStyle({ variant: options?.variant });
14
-
15
- const { register } = useFormContext();
20
+ const isOptional = options?.optionalField;
16
21
 
17
22
  return (
18
- <label className={container()}>
19
- {label ? (
20
- <Label className={span()} required={!options?.optionalField}>
21
- {label}
22
- </Label>
23
- ) : (
24
- <></>
23
+ <FormField
24
+ control={control}
25
+ name={bind}
26
+ defaultValue=''
27
+ rules={{
28
+ required: isOptional ? false : options?.error ?? true,
29
+ }}
30
+ render={({ field }) => (
31
+ <FormItem>
32
+ <div className='space-y-0.5'>
33
+ <FormLabel>
34
+ {label}
35
+ {!isOptional ? (
36
+ <span className='ml-0.5 inline-block'>
37
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
38
+ </span>
39
+ ) : (
40
+ <></>
41
+ )}
42
+ </FormLabel>
43
+ {options?.description ? (
44
+ <FormDescription>{options.description}</FormDescription>
45
+ ) : (
46
+ <></>
47
+ )}
48
+ </div>
49
+ <FormControl>
50
+ <Input
51
+ type='text'
52
+ placeholder={options?.placeholder ?? 'Write here...'}
53
+ {...field}
54
+ />
55
+ </FormControl>
56
+ <FormMessage />
57
+ </FormItem>
25
58
  )}
26
-
27
- <input
28
- type='text'
29
- placeholder={options?.placeholder || 'Write here...'}
30
- className={input()}
31
- {...register(bind, {
32
- required: options?.optionalField ? false : `${label} is required`,
33
- })}
34
- />
35
- </label>
59
+ />
36
60
  );
37
61
  };
38
-
39
- export default Simple;
@@ -1,21 +1,12 @@
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 { type CommonProps } from './__props';
5
- import { submitStyle } from './__styles';
6
-
7
- type Props = CommonProps<VariantProps<typeof submitStyle>['variant']>;
8
-
9
- const Submit: FC<Props> = ({ label, options }) => {
10
- const style = submitStyle({ variant: options?.variant });
4
+ import { Button } from '@twintag/twintag-form/common/button';
11
5
 
6
+ export const Submit: FC<Props> = ({ label }) => {
12
7
  return (
13
8
  <div className='flex w-full justify-center'>
14
- <button type='submit' className={style}>
15
- {label}
16
- </button>
9
+ <Button>{label}</Button>
17
10
  </div>
18
11
  );
19
12
  };
20
-
21
- export default Submit;
@@ -1,38 +1,60 @@
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 { AsteriskIcon } from 'lucide-react';
4
5
  import { useFormContext } from 'react-hook-form';
5
6
 
6
- import { Label } from '@twintag/twintag-form/components';
7
- import { type CommonProps } from './__props';
8
- import { simpleStyle } from './__styles';
7
+ import {
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from '@twintag/twintag-form/common/form';
15
+ import { Textarea as CommonTextarea } from '@twintag/twintag-form/common/textarea';
9
16
 
10
- type Props = CommonProps<VariantProps<typeof simpleStyle>['variant']>;
17
+ export const Textarea: FC<Props> = ({ label, bind, options }) => {
18
+ const { control } = useFormContext();
11
19
 
12
- const Textarea: FC<Props> = ({ label, bind, options }) => {
13
- const { register } = useFormContext();
14
-
15
- const { container, span, input } = simpleStyle({ variant: options?.variant });
20
+ const isOptional = options?.optionalField;
16
21
 
17
22
  return (
18
- <label className={container()}>
19
- {label ? (
20
- <Label className={span()} required={!options?.optionalField}>
21
- {label}
22
- </Label>
23
- ) : (
24
- <></>
23
+ <FormField
24
+ control={control}
25
+ name={bind}
26
+ defaultValue=''
27
+ rules={{
28
+ required: isOptional ? false : options?.error ?? true,
29
+ }}
30
+ render={({ field }) => (
31
+ <FormItem>
32
+ <div className='space-y-0.5'>
33
+ <FormLabel>
34
+ {label}
35
+ {!isOptional ? (
36
+ <span className='ml-0.5 inline-block'>
37
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
38
+ </span>
39
+ ) : (
40
+ <></>
41
+ )}
42
+ </FormLabel>
43
+ {options?.description ? (
44
+ <FormDescription>{options.description}</FormDescription>
45
+ ) : (
46
+ <></>
47
+ )}
48
+ </div>
49
+ <FormControl>
50
+ <CommonTextarea
51
+ {...field}
52
+ placeholder={options?.placeholder ?? 'Write here...'}
53
+ />
54
+ </FormControl>
55
+ <FormMessage />
56
+ </FormItem>
25
57
  )}
26
-
27
- <textarea
28
- className={input()}
29
- placeholder={options?.placeholder || 'Write here...'}
30
- {...register(bind, {
31
- required: options?.optionalField ? false : `${label} is required`,
32
- })}
33
- />
34
- </label>
58
+ />
35
59
  );
36
60
  };
37
-
38
- export default Textarea;
@@ -1,56 +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 * as Switch from '@radix-ui/react-switch';
4
+ import { AsteriskIcon } from 'lucide-react';
5
+ import { useFormContext } from 'react-hook-form';
6
6
 
7
- import { Label } from '@twintag/twintag-form/components';
8
- import { type CommonProps } from './__props';
9
- import { toggleStyle } from './__styles';
7
+ import {
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from '@twintag/twintag-form/common/form';
15
+ import { Switch } from '@twintag/twintag-form/common/switch';
10
16
 
11
- type Props = CommonProps<VariantProps<typeof toggleStyle>['variant']>;
12
-
13
- const Toggle: FC<Props> = ({ label, bind, options }) => {
17
+ export const Toggle: FC<Props> = ({ label, bind, options }) => {
14
18
  const { control } = useFormContext();
15
19
 
16
- const {
17
- field: { ref, value, onChange },
18
- } = useController({
19
- control,
20
- name: bind,
21
- defaultValue: false,
22
- rules: {
23
- required: options?.optionalField ? false : `${label} is required`,
24
- },
25
- });
26
-
27
- const { container, span, input, thumb } = toggleStyle({
28
- variant: options?.variant,
29
- });
20
+ const isOptional = options?.optionalField;
30
21
 
31
22
  return (
32
- <label className={container()}>
33
- {label ? (
34
- <Label className={span()} required={!options?.optionalField}>
35
- {label}
36
- </Label>
37
- ) : (
38
- <></>
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>
32
+ <div className='flex flex-row items-center justify-between'>
33
+ <div className='space-y-0.5'>
34
+ <FormLabel htmlFor={bind}>
35
+ {label}
36
+ {!isOptional ? (
37
+ <span className='ml-0.5 inline-block'>
38
+ <AsteriskIcon className='h-3 w-3 text-destructive' />
39
+ </span>
40
+ ) : (
41
+ <></>
42
+ )}
43
+ </FormLabel>
44
+ {options?.description ? (
45
+ <FormDescription>{options.description}</FormDescription>
46
+ ) : (
47
+ <></>
48
+ )}
49
+ </div>
50
+ <FormControl>
51
+ <Switch
52
+ id={bind}
53
+ ref={field.ref}
54
+ checked={field.value as boolean}
55
+ onCheckedChange={field.onChange}
56
+ className='!my-0'
57
+ />
58
+ </FormControl>
59
+ </div>
60
+ <FormMessage />
61
+ </FormItem>
39
62
  )}
40
-
41
- <Switch.Root
42
- id={bind}
43
- ref={ref}
44
- checked={value}
45
- className={input()}
46
- onCheckedChange={(state) => {
47
- onChange(state);
48
- }}
49
- >
50
- <Switch.Thumb className={thumb()} />
51
- </Switch.Root>
52
- </label>
63
+ />
53
64
  );
54
65
  };
55
-
56
- export default Toggle;
@@ -0,0 +1,30 @@
1
+ import type { FC } from 'react';
2
+ import type { ParsedJsonComponent } from '@twintag/twintag-form/types';
3
+
4
+ type Props = {
5
+ config: ParsedJsonComponent;
6
+ };
7
+
8
+ export const Renderer: FC<Props> = ({ config }) => {
9
+ const { id, type, items, comp, ...rest } = config;
10
+
11
+ if (!type || !comp) return <></>;
12
+
13
+ const Component = comp;
14
+
15
+ if (items && Array.isArray(items) && items.length > 0) {
16
+ if (type === 'category') {
17
+ return (
18
+ <Component key={id} {...rest}>
19
+ {items.map((item) => (
20
+ <Renderer key={item.id} config={{ ...item }} />
21
+ ))}
22
+ </Component>
23
+ );
24
+ }
25
+
26
+ return <Component key={id} items={items} {...rest} />;
27
+ }
28
+
29
+ return <Component key={id} {...rest} />;
30
+ };