@twintag/twintag-form 1.0.1 → 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.
Files changed (69) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +61 -3
  4. package/dev/index.css +2 -0
  5. package/dev/main.tsx +50 -27
  6. package/dist/common/accordion.d.ts +7 -0
  7. package/dist/common/button.d.ts +28 -0
  8. package/dist/common/calendar.d.ts +9 -0
  9. package/dist/common/form.d.ts +1 -1
  10. package/dist/common/icon.d.ts +7 -0
  11. package/dist/common/popover.d.ts +6 -0
  12. package/dist/common/scroll-area.d.ts +5 -0
  13. package/dist/common/select.d.ts +13 -0
  14. package/dist/components/config.d.ts +4 -0
  15. package/dist/components/containers/accordion.d.ts +8 -0
  16. package/dist/components/fields/date-picker.d.ts +3 -0
  17. package/dist/components/{Input/simple.d.ts → fields/input.d.ts} +1 -1
  18. package/dist/components/form.d.ts +3 -1
  19. package/dist/components/index.d.ts +1 -2
  20. package/dist/index.d.ts +2 -0
  21. package/dist/twintag-form.react.js +19844 -6458
  22. package/dist/twintag-form.webcomponent.js +314 -299
  23. package/dist/types/index.d.ts +6 -5
  24. package/dist/utils/data.d.ts +2 -0
  25. package/dist/utils/string.d.ts +3 -0
  26. package/dist/utils/style.d.ts +2 -0
  27. package/index.html +3 -1
  28. package/lib/common/accordion.tsx +77 -0
  29. package/lib/common/button.tsx +3 -0
  30. package/lib/common/calendar.tsx +110 -0
  31. package/lib/common/form.tsx +14 -1
  32. package/lib/common/icon.tsx +35 -0
  33. package/lib/common/input.tsx +2 -1
  34. package/lib/common/popover.tsx +30 -0
  35. package/lib/common/scroll-area.tsx +46 -0
  36. package/lib/common/select.tsx +159 -0
  37. package/lib/common/textarea.tsx +1 -1
  38. package/lib/components/Render/index.tsx +4 -2
  39. package/lib/components/config.tsx +44 -0
  40. package/lib/components/containers/accordion.tsx +105 -0
  41. package/lib/components/containers/category.tsx +48 -0
  42. package/lib/components/fields/date-picker.tsx +117 -0
  43. package/lib/components/{Input → fields}/file.tsx +1 -1
  44. package/lib/components/{Input/simple.tsx → fields/input.tsx} +5 -5
  45. package/lib/components/{Input → fields}/textarea.tsx +2 -2
  46. package/lib/components/form.tsx +19 -3
  47. package/lib/components/index.tsx +0 -3
  48. package/lib/index.tsx +7 -8
  49. package/lib/types/index.ts +13 -11
  50. package/lib/utils/data.ts +18 -0
  51. package/lib/utils/string.ts +12 -0
  52. package/lib/utils/style.ts +8 -0
  53. package/package.json +13 -5
  54. package/tailwind.config.ts +19 -1
  55. package/.turbo/turbo-lint.log +0 -4
  56. package/dist/components/Input/index.d.ts +0 -8
  57. package/lib/components/Input/index.tsx +0 -32
  58. package/lib/components/category.tsx +0 -31
  59. /package/dist/components/{category.d.ts → containers/category.d.ts} +0 -0
  60. /package/dist/components/{Input → fields}/checkbox.d.ts +0 -0
  61. /package/dist/components/{Input → fields}/file.d.ts +0 -0
  62. /package/dist/components/{Input → fields}/radiogroup.d.ts +0 -0
  63. /package/dist/components/{Input → fields}/submit.d.ts +0 -0
  64. /package/dist/components/{Input → fields}/textarea.d.ts +0 -0
  65. /package/dist/components/{Input → fields}/toggle.d.ts +0 -0
  66. /package/lib/components/{Input → fields}/checkbox.tsx +0 -0
  67. /package/lib/components/{Input → fields}/radiogroup.tsx +0 -0
  68. /package/lib/components/{Input → fields}/submit.tsx +0 -0
  69. /package/lib/components/{Input → fields}/toggle.tsx +0 -0
@@ -7,6 +7,7 @@ type RawProps = Partial<{
7
7
  bind: string;
8
8
  subtype: string;
9
9
  label: string;
10
+ icon: string;
10
11
  options: Partial<{
11
12
  description: string;
12
13
  placeholder: string;
@@ -14,20 +15,20 @@ type RawProps = Partial<{
14
15
  optionalField: boolean;
15
16
  }>;
16
17
  }>;
17
- export type RawJsonComponent = RawProps & {
18
+ export type RawJsonComponent = RawProps & Record<string, unknown> & {
18
19
  type: string;
19
20
  children?: RawJsonComponent[];
20
21
  } & {
21
22
  props?: RawProps;
22
23
  };
23
- export type Props = Omit<RawProps, 'subtype'> & {
24
- bind: string;
25
- items?: ParsedJsonComponent[];
26
- };
27
24
  export type ParsedJsonComponent = Props & {
28
25
  id: string;
29
26
  type: string;
30
27
  subtype?: string;
31
28
  comp?: ElementType;
32
29
  };
30
+ export type Props = Omit<RawProps, 'subtype'> & {
31
+ bind: string;
32
+ items?: ParsedJsonComponent[];
33
+ };
33
34
  export {};
@@ -0,0 +1,2 @@
1
+ import type { Props } from '../types';
2
+ export declare const getBindSetFromItems: (items: Props[]) => Set<string>;
@@ -0,0 +1,3 @@
1
+ export declare const camelize: (str: string) => string;
2
+ export declare const camelizeKebab: (s: string) => string;
3
+ export declare const capitalize: (s: string) => string;
@@ -0,0 +1,2 @@
1
+ import type { ClassValue } from 'clsx';
2
+ export declare function cn(...inputs: ClassValue[]): string;
package/index.html CHANGED
@@ -6,7 +6,9 @@
6
6
  <title>Twintag Form</title>
7
7
  </head>
8
8
  <body>
9
- <div id="root"></div>
9
+ <div style="margin-inline: auto; max-width: 32rem; background: hsl(var(--tf-background)); padding: 2rem">
10
+ <twintag-form></twintag-form>
11
+ </div>
10
12
  <script type="module" src="./dev/main.tsx"></script>
11
13
  </body>
12
14
  </html>
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import * as AccordionPrimitive from '@radix-ui/react-accordion';
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ const Accordion = AccordionPrimitive.Root;
7
+
8
+ const accordionVariants = tv({
9
+ base: 'border-b',
10
+ });
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={accordionVariants({ className })}
18
+ {...props}
19
+ />
20
+ ));
21
+ AccordionItem.displayName = 'AccordionItem';
22
+
23
+ const accordionTriggerVariants = tv({
24
+ slots: {
25
+ header: 'flex',
26
+ trigger:
27
+ 'flex flex-1 items-center justify-between py-4 font-medium hover:underline [&[data-state=open]>svg]:rotate-180',
28
+ icon: 'h-4 w-4 shrink-0 text-current transition-transform duration-200',
29
+ },
30
+ });
31
+ const AccordionTrigger = React.forwardRef<
32
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
33
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
34
+ >(({ className, children, ...props }, ref) => {
35
+ const { header, trigger, icon } = accordionTriggerVariants();
36
+ return (
37
+ <AccordionPrimitive.Header className={header()} asChild>
38
+ <div>
39
+ <AccordionPrimitive.Trigger
40
+ ref={ref}
41
+ className={trigger({
42
+ className,
43
+ })}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <ChevronDown className={icon()} />
48
+ </AccordionPrimitive.Trigger>
49
+ </div>
50
+ </AccordionPrimitive.Header>
51
+ );
52
+ });
53
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
54
+
55
+ const accordionContentVariants = tv({
56
+ slots: {
57
+ content:
58
+ 'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
59
+ wrapper: 'pb-4 pt-0',
60
+ },
61
+ });
62
+ const AccordionContent = React.forwardRef<
63
+ React.ElementRef<typeof AccordionPrimitive.Content>,
64
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
65
+ >(({ className, children, ...props }, ref) => {
66
+ const { content, wrapper } = accordionContentVariants();
67
+
68
+ return (
69
+ <AccordionPrimitive.Content ref={ref} className={content()} {...props}>
70
+ <div className={wrapper({ className })}>{children}</div>
71
+ </AccordionPrimitive.Content>
72
+ );
73
+ });
74
+
75
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName;
76
+
77
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -14,6 +14,9 @@ const buttonVariants = tv({
14
14
  variant: {
15
15
  default:
16
16
  'bg-primary-foreground text-primary shadow hover:bg-primary-foreground/90',
17
+ outline:
18
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
19
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
17
20
  },
18
21
  size: {
19
22
  default: 'h-9 px-4 py-2',
@@ -0,0 +1,110 @@
1
+ import * as React from 'react';
2
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
3
+ import { DayPicker } from 'react-day-picker';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ import { buttonVariants } from '@twintag/twintag-form/common/button';
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9
+
10
+ const calendarVariants = tv({
11
+ slots: {
12
+ picker: 'p-3',
13
+ months: 'flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0',
14
+ month: 'space-y-4',
15
+ caption: 'relative flex items-center justify-center pt-1',
16
+ caption_dropdowns: '',
17
+ caption_label: 'text-sm font-semibold',
18
+ nav: 'flex items-center space-x-1',
19
+ nav_button: buttonVariants({
20
+ variant: 'outline',
21
+ className: 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
22
+ }),
23
+ nav_button_previous: 'absolute left-1',
24
+ nav_button_next: 'absolute right-1',
25
+ table: 'w-full border-collapse space-y-1',
26
+ head_row: 'flex',
27
+ head_cell: 'w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground',
28
+ row: 'mt-2 flex w-full',
29
+ cell: 'relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-transparent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-muted/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
30
+ day: buttonVariants({
31
+ variant: 'ghost',
32
+ className: 'h-9 w-9 p-0 font-normal aria-selected:opacity-100',
33
+ }),
34
+ day_range_end: 'day-range-end',
35
+ day_selected:
36
+ 'bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
37
+ day_today: 'bg-muted text-muted-foreground',
38
+ day_outside:
39
+ 'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/30 aria-selected:text-accent-foreground',
40
+ day_disabled: 'text-muted-foreground opacity-50',
41
+ day_range_middle:
42
+ 'aria-selected:bg-accent aria-selected:text-accent-foreground',
43
+ day_hidden: 'invisible',
44
+ },
45
+ variants: {
46
+ variant: {
47
+ dropdown: {
48
+ caption_label: 'hidden',
49
+ caption_dropdowns: [
50
+ 'flex w-4/6 justify-center gap-1',
51
+ '[&>*>span]:hidden [&>*]:flex-1 [&>.rdp-vhidden]:hidden ',
52
+ '[&>*>select]:h-7 [&>*>select]:min-w-[3.6rem] [&>*>select]:rounded-md [&>*>select]:border [&>*>select]:border-input [&>*>select]:bg-background [&>*>select]:px-1 [&>*>select]:text-sm',
53
+ '[&>*>select]:ring-offset-background [&>*>select]:focus-visible:outline-none [&>*>select]:focus-visible:ring-1 [&>*>select]:focus-visible:ring-ring',
54
+ ],
55
+ },
56
+ },
57
+ },
58
+ });
59
+
60
+ function Calendar({
61
+ className,
62
+ classNames,
63
+ showOutsideDays = true,
64
+ ...props
65
+ }: CalendarProps) {
66
+ const slots = calendarVariants({
67
+ variant:
68
+ props.captionLayout === 'dropdown-buttons' ? 'dropdown' : undefined,
69
+ });
70
+
71
+ return (
72
+ <DayPicker
73
+ showOutsideDays={showOutsideDays}
74
+ className={slots.picker({ className })}
75
+ classNames={{
76
+ months: slots.months(),
77
+ month: slots.month(),
78
+ caption: slots.caption(),
79
+ caption_label: slots.caption_label(),
80
+ caption_dropdowns: slots.caption_dropdowns(),
81
+ nav: slots.nav(),
82
+ nav_button: slots.nav_button(),
83
+ nav_button_previous: slots.nav_button_previous(),
84
+ nav_button_next: slots.nav_button_next(),
85
+ table: slots.table(),
86
+ head_row: slots.head_row(),
87
+ head_cell: slots.head_cell(),
88
+ row: slots.row(),
89
+ cell: slots.cell(),
90
+ day: slots.day(),
91
+ day_range_end: slots.day_range_end(),
92
+ day_selected: slots.day_selected(),
93
+ day_today: slots.day_today(),
94
+ day_outside: slots.day_outside(),
95
+ day_disabled: slots.day_disabled(),
96
+ day_range_middle: slots.day_range_middle(),
97
+ day_hidden: slots.day_hidden(),
98
+ ...classNames,
99
+ }}
100
+ components={{
101
+ IconLeft: () => <ChevronLeft className='h-4 w-4' />,
102
+ IconRight: () => <ChevronRight className='h-4 w-4' />,
103
+ }}
104
+ {...props}
105
+ />
106
+ );
107
+ }
108
+ Calendar.displayName = 'Calendar';
109
+
110
+ export { Calendar };
@@ -6,6 +6,7 @@ import { Slot } from '@radix-ui/react-slot';
6
6
  import { Controller, FormProvider, useFormContext } from 'react-hook-form';
7
7
  import { tv } from 'tailwind-variants';
8
8
 
9
+ import { useTwintagForm } from '../provider';
9
10
  import { Label } from './label';
10
11
 
11
12
  const Form = FormProvider;
@@ -67,6 +68,12 @@ const FormItemContext = React.createContext<FormItemContextValue>(
67
68
 
68
69
  const formItemVariants = tv({
69
70
  base: 'space-y-2',
71
+ variants: {
72
+ variant: {
73
+ default: '',
74
+ beta: 'space-y-1',
75
+ },
76
+ },
70
77
  });
71
78
  const FormItem = React.forwardRef<
72
79
  HTMLDivElement,
@@ -74,9 +81,15 @@ const FormItem = React.forwardRef<
74
81
  >(({ className, ...props }, ref) => {
75
82
  const id = React.useId();
76
83
 
84
+ const { variant } = useTwintagForm();
85
+
77
86
  return (
78
87
  <FormItemContext.Provider value={{ id }}>
79
- <div ref={ref} className={formItemVariants({ className })} {...props} />
88
+ <div
89
+ ref={ref}
90
+ className={formItemVariants({ variant, className })}
91
+ {...props}
92
+ />
80
93
  </FormItemContext.Provider>
81
94
  );
82
95
  });
@@ -0,0 +1,35 @@
1
+ import type { FC } from 'react';
2
+
3
+ import { useMemo } from 'react';
4
+ import * as icons from '@heroicons/react/24/outline';
5
+ import { tv } from 'tailwind-variants';
6
+
7
+ import { camelizeKebab, capitalize } from '../utils/string';
8
+
9
+ type Props = {
10
+ name: string;
11
+ className?: string;
12
+ };
13
+ const iconVariants = tv({
14
+ base: 'h-5 w-5',
15
+ });
16
+ export const Icon: FC<Props> = ({ name, className, ...props }) => {
17
+ const camel = useMemo(
18
+ () => `${capitalize(camelizeKebab(name))}Icon` as keyof typeof icons,
19
+ [name],
20
+ );
21
+
22
+ const Comp = icons[camel];
23
+
24
+ if (!Comp) return <></>;
25
+
26
+ return (
27
+ <Comp
28
+ aria-hidden='true'
29
+ className={iconVariants({
30
+ className,
31
+ })}
32
+ {...props}
33
+ />
34
+ );
35
+ };
@@ -21,7 +21,7 @@ const inputVariants = tv({
21
21
  'rounded-md border px-3 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
22
22
  },
23
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',
24
+ root: 'after:absolute after:left-0 after:top-[calc(100%_+_1px)] after:w-full after:rounded-full after:content-[""] focus-within:after:h-0.5 focus-within:after:bg-ring',
25
25
  input: 'border-b px-1',
26
26
  },
27
27
  },
@@ -30,6 +30,7 @@ const inputVariants = tv({
30
30
  variant: 'default',
31
31
  },
32
32
  });
33
+
33
34
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
34
35
  ({ className, type, ...props }, ref) => {
35
36
  const { variant } = useTwintagForm();
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
3
+ import { tv } from 'tailwind-variants';
4
+
5
+ const Popover = PopoverPrimitive.Root;
6
+
7
+ const PopoverTrigger = PopoverPrimitive.Trigger;
8
+
9
+ const popoverContentVariants = tv({
10
+ base: 'z-50 w-72 rounded-md border bg-background p-4 text-foreground shadow-md outline-none',
11
+ });
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = 'center', sideOffset = 2, ...props }, ref) => {
16
+ return (
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={popoverContentVariants({
22
+ className,
23
+ })}
24
+ {...props}
25
+ />
26
+ );
27
+ });
28
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
29
+
30
+ export { Popover, PopoverTrigger, PopoverContent };
@@ -0,0 +1,46 @@
1
+ import * as React from 'react';
2
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
3
+
4
+ import { cn } from '@twintag/twintag-form/utils/style';
5
+
6
+ const ScrollArea = React.forwardRef<
7
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9
+ >(({ className, children, ...props }, ref) => (
10
+ <ScrollAreaPrimitive.Root
11
+ ref={ref}
12
+ className={cn('relative overflow-hidden', className)}
13
+ {...props}
14
+ >
15
+ <ScrollAreaPrimitive.Viewport className='h-full w-full rounded-[inherit]'>
16
+ {children}
17
+ </ScrollAreaPrimitive.Viewport>
18
+ <ScrollBar />
19
+ <ScrollAreaPrimitive.Corner />
20
+ </ScrollAreaPrimitive.Root>
21
+ ));
22
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
23
+
24
+ const ScrollBar = React.forwardRef<
25
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27
+ >(({ className, orientation = 'vertical', ...props }, ref) => (
28
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
29
+ ref={ref}
30
+ orientation={orientation}
31
+ className={cn(
32
+ 'flex touch-none select-none transition-colors',
33
+ orientation === 'vertical' &&
34
+ 'h-full w-2.5 border-l border-l-transparent p-[1px]',
35
+ orientation === 'horizontal' &&
36
+ 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
37
+ className,
38
+ )}
39
+ {...props}
40
+ >
41
+ <ScrollAreaPrimitive.ScrollAreaThumb className='relative flex-1 rounded-full bg-input' />
42
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
43
+ ));
44
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
45
+
46
+ export { ScrollArea, ScrollBar };
@@ -0,0 +1,159 @@
1
+ import * as React from 'react';
2
+ import { CaretSortIcon } from '@radix-ui/react-icons';
3
+ import * as SelectPrimitive from '@radix-ui/react-select';
4
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react';
5
+
6
+ import { cn } from '../utils/style';
7
+
8
+ const Select = SelectPrimitive.Root;
9
+
10
+ const SelectGroup = SelectPrimitive.Group;
11
+
12
+ const SelectValue = SelectPrimitive.Value;
13
+
14
+ const SelectTrigger = React.forwardRef<
15
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
16
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
17
+ >(({ className, children, ...props }, ref) => (
18
+ <SelectPrimitive.Trigger
19
+ ref={ref}
20
+ className={cn(
21
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
22
+ className,
23
+ )}
24
+ {...props}
25
+ >
26
+ {children}
27
+ <SelectPrimitive.Icon asChild>
28
+ <CaretSortIcon className='h-4 w-4 opacity-50' />
29
+ </SelectPrimitive.Icon>
30
+ </SelectPrimitive.Trigger>
31
+ ));
32
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
33
+
34
+ const SelectScrollUpButton = React.forwardRef<
35
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
36
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
37
+ >(({ className, ...props }, ref) => (
38
+ <SelectPrimitive.ScrollUpButton
39
+ ref={ref}
40
+ className={cn(
41
+ 'flex cursor-default items-center justify-center py-1',
42
+ className,
43
+ )}
44
+ {...props}
45
+ >
46
+ <ChevronUp className='h-4 w-4' />
47
+ </SelectPrimitive.ScrollUpButton>
48
+ ));
49
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
50
+
51
+ const SelectScrollDownButton = React.forwardRef<
52
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
53
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
54
+ >(({ className, ...props }, ref) => (
55
+ <SelectPrimitive.ScrollDownButton
56
+ ref={ref}
57
+ className={cn(
58
+ 'flex cursor-default items-center justify-center py-1',
59
+ className,
60
+ )}
61
+ {...props}
62
+ >
63
+ <ChevronDown className='h-4 w-4' />
64
+ </SelectPrimitive.ScrollDownButton>
65
+ ));
66
+ SelectScrollDownButton.displayName =
67
+ SelectPrimitive.ScrollDownButton.displayName;
68
+
69
+ const SelectContent = React.forwardRef<
70
+ React.ElementRef<typeof SelectPrimitive.Content>,
71
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
72
+ >(({ className, children, position = 'popper', ...props }, ref) => {
73
+ return (
74
+ <SelectPrimitive.Content
75
+ ref={ref}
76
+ className={cn(
77
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-background text-foreground shadow-md',
78
+ position === 'popper' &&
79
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
80
+ className,
81
+ )}
82
+ position={position}
83
+ {...props}
84
+ >
85
+ <SelectScrollUpButton />
86
+ <SelectPrimitive.Viewport
87
+ className={cn(
88
+ 'p-1',
89
+ position === 'popper' &&
90
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
91
+ )}
92
+ >
93
+ {children}
94
+ </SelectPrimitive.Viewport>
95
+ <SelectScrollDownButton />
96
+ </SelectPrimitive.Content>
97
+ );
98
+ });
99
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
100
+
101
+ const SelectLabel = React.forwardRef<
102
+ React.ElementRef<typeof SelectPrimitive.Label>,
103
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
104
+ >(({ className, ...props }, ref) => (
105
+ <SelectPrimitive.Label
106
+ ref={ref}
107
+ className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
108
+ {...props}
109
+ />
110
+ ));
111
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
112
+
113
+ const SelectItem = React.forwardRef<
114
+ React.ElementRef<typeof SelectPrimitive.Item>,
115
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
116
+ >(({ className, children, ...props }, ref) => (
117
+ <SelectPrimitive.Item
118
+ ref={ref}
119
+ className={cn(
120
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
121
+ className,
122
+ )}
123
+ {...props}
124
+ >
125
+ <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>
126
+ <SelectPrimitive.ItemIndicator>
127
+ <Check className='h-4 w-4' />
128
+ </SelectPrimitive.ItemIndicator>
129
+ </span>
130
+
131
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
132
+ </SelectPrimitive.Item>
133
+ ));
134
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
135
+
136
+ const SelectSeparator = React.forwardRef<
137
+ React.ElementRef<typeof SelectPrimitive.Separator>,
138
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
139
+ >(({ className, ...props }, ref) => (
140
+ <SelectPrimitive.Separator
141
+ ref={ref}
142
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
143
+ {...props}
144
+ />
145
+ ));
146
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
147
+
148
+ export {
149
+ Select,
150
+ SelectGroup,
151
+ SelectValue,
152
+ SelectTrigger,
153
+ SelectContent,
154
+ SelectLabel,
155
+ SelectItem,
156
+ SelectSeparator,
157
+ SelectScrollUpButton,
158
+ SelectScrollDownButton,
159
+ };
@@ -20,7 +20,7 @@ const textareaVariants = tv({
20
20
  'rounded-md border px-3 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
21
21
  },
22
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',
23
+ root: 'after:absolute after:left-0 after:top-[calc(100%_+_1px)] after:w-full after:rounded-full after:content-[""] focus-within:after:h-0.5 focus-within:after:bg-ring',
24
24
  textarea: 'border-b px-1',
25
25
  },
26
26
  },
@@ -1,6 +1,8 @@
1
1
  import type { FC } from 'react';
2
2
  import type { ParsedJsonComponent } from '@twintag/twintag-form/types';
3
3
 
4
+ import { Containers } from '@twintag/twintag-form/components/config';
5
+
4
6
  type Props = {
5
7
  config: ParsedJsonComponent;
6
8
  };
@@ -13,9 +15,9 @@ export const Renderer: FC<Props> = ({ config }) => {
13
15
  const Component = comp;
14
16
 
15
17
  if (items && Array.isArray(items) && items.length > 0) {
16
- if (type === 'category') {
18
+ if (Containers.has(type)) {
17
19
  return (
18
- <Component key={id} {...rest}>
20
+ <Component key={id} items={items} {...rest}>
19
21
  {items.map((item) => (
20
22
  <Renderer key={item.id} config={{ ...item }} />
21
23
  ))}
@@ -0,0 +1,44 @@
1
+ import type { ElementType, FC } from 'react';
2
+
3
+ import { Accordion } from './containers/accordion';
4
+ import { Category } from './containers/category';
5
+ import { Checkbox } from './fields/checkbox';
6
+ import { DatePicker } from './fields/date-picker';
7
+ import { File } from './fields/file';
8
+ import { Input as InputField } from './fields/input';
9
+ import { RadioGroup } from './fields/radiogroup';
10
+ import { Submit } from './fields/submit';
11
+ import { Textarea } from './fields/textarea';
12
+ import { Toggle } from './fields/toggle';
13
+
14
+ export const Inputs = new Map<string, ElementType>()
15
+ .set('simple', InputField)
16
+ .set('textarea', Textarea)
17
+ .set('toggle', Toggle)
18
+ .set('file', File)
19
+ .set('submit', Submit)
20
+ .set('checkbox', Checkbox)
21
+ .set('radiogroup', RadioGroup)
22
+ .set('datepicker', DatePicker);
23
+
24
+ type InputProps = {
25
+ subtype?: string;
26
+ label?: string;
27
+ bind?: string;
28
+ };
29
+ const Input: FC<InputProps> = ({ subtype = 'simple', ...rest }) => {
30
+ const Comp = Inputs.get(subtype);
31
+
32
+ if (!Comp) return <></>;
33
+
34
+ return <Comp {...rest} />;
35
+ };
36
+
37
+ export const Containers = new Map<string, ElementType>()
38
+ .set('category', Category)
39
+ .set('accordion', Accordion);
40
+
41
+ export const components = new Map<string, ElementType>(Containers).set(
42
+ 'input',
43
+ Input,
44
+ );