pdyform 1.1.0 → 2.0.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 (175) hide show
  1. package/README.md +28 -1
  2. package/example/README.md +36 -0
  3. package/example/react-demo/dist/assets/index-BBU9cJqy.css +1 -0
  4. package/example/react-demo/dist/assets/index-DeJS8UcQ.js +105 -0
  5. package/example/react-demo/dist/index.html +13 -0
  6. package/example/react-demo/index.html +12 -0
  7. package/example/react-demo/node_modules/.bin/browserslist +17 -0
  8. package/example/react-demo/node_modules/.bin/tsc +17 -0
  9. package/example/react-demo/node_modules/.bin/tsserver +17 -0
  10. package/example/react-demo/node_modules/.bin/vite +17 -0
  11. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-checkbox.js +300 -0
  12. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-checkbox.js.map +7 -0
  13. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-label.js +194 -0
  14. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-label.js.map +7 -0
  15. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-radio-group.js +530 -0
  16. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-radio-group.js.map +7 -0
  17. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-select.js +4808 -0
  18. package/example/react-demo/node_modules/.vite/deps/@radix-ui_react-select.js.map +7 -0
  19. package/example/react-demo/node_modules/.vite/deps/_metadata.json +115 -0
  20. package/example/react-demo/node_modules/.vite/deps/chunk-3D5PZ6F6.js +49 -0
  21. package/example/react-demo/node_modules/.vite/deps/chunk-3D5PZ6F6.js.map +7 -0
  22. package/example/react-demo/node_modules/.vite/deps/chunk-5Q2RBQLA.js +127 -0
  23. package/example/react-demo/node_modules/.vite/deps/chunk-5Q2RBQLA.js.map +7 -0
  24. package/example/react-demo/node_modules/.vite/deps/chunk-G3PMV62Z.js +36 -0
  25. package/example/react-demo/node_modules/.vite/deps/chunk-G3PMV62Z.js.map +7 -0
  26. package/example/react-demo/node_modules/.vite/deps/chunk-GX7YZ5KV.js +370 -0
  27. package/example/react-demo/node_modules/.vite/deps/chunk-GX7YZ5KV.js.map +7 -0
  28. package/example/react-demo/node_modules/.vite/deps/chunk-PUFJGYAC.js +928 -0
  29. package/example/react-demo/node_modules/.vite/deps/chunk-PUFJGYAC.js.map +7 -0
  30. package/example/react-demo/node_modules/.vite/deps/chunk-SIU35MPB.js +21 -0
  31. package/example/react-demo/node_modules/.vite/deps/chunk-SIU35MPB.js.map +7 -0
  32. package/example/react-demo/node_modules/.vite/deps/chunk-TOMGVNQP.js +1906 -0
  33. package/example/react-demo/node_modules/.vite/deps/chunk-TOMGVNQP.js.map +7 -0
  34. package/example/react-demo/node_modules/.vite/deps/chunk-YYN6DZAU.js +21628 -0
  35. package/example/react-demo/node_modules/.vite/deps/chunk-YYN6DZAU.js.map +7 -0
  36. package/example/react-demo/node_modules/.vite/deps/chunk-ZE5VSJFE.js +144 -0
  37. package/example/react-demo/node_modules/.vite/deps/chunk-ZE5VSJFE.js.map +7 -0
  38. package/example/react-demo/node_modules/.vite/deps/class-variance-authority.js +51 -0
  39. package/example/react-demo/node_modules/.vite/deps/class-variance-authority.js.map +7 -0
  40. package/example/react-demo/node_modules/.vite/deps/clsx.js +10 -0
  41. package/example/react-demo/node_modules/.vite/deps/clsx.js.map +7 -0
  42. package/example/react-demo/node_modules/.vite/deps/lucide-react.js +29725 -0
  43. package/example/react-demo/node_modules/.vite/deps/lucide-react.js.map +7 -0
  44. package/example/react-demo/node_modules/.vite/deps/package.json +3 -0
  45. package/example/react-demo/node_modules/.vite/deps/react-dom.js +7 -0
  46. package/example/react-demo/node_modules/.vite/deps/react-dom.js.map +7 -0
  47. package/example/react-demo/node_modules/.vite/deps/react-dom_client.js +39 -0
  48. package/example/react-demo/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  49. package/example/react-demo/node_modules/.vite/deps/react.js +6 -0
  50. package/example/react-demo/node_modules/.vite/deps/react.js.map +7 -0
  51. package/example/react-demo/node_modules/.vite/deps/react_jsx-dev-runtime.js +913 -0
  52. package/example/react-demo/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  53. package/example/react-demo/node_modules/.vite/deps/react_jsx-runtime.js +7 -0
  54. package/example/react-demo/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  55. package/example/react-demo/node_modules/.vite/deps/tailwind-merge.js +2534 -0
  56. package/example/react-demo/node_modules/.vite/deps/tailwind-merge.js.map +7 -0
  57. package/example/react-demo/package.json +23 -0
  58. package/example/react-demo/postcss.config.mjs +6 -0
  59. package/example/react-demo/src/App.tsx +64 -0
  60. package/example/react-demo/src/main.tsx +10 -0
  61. package/example/react-demo/src/styles.css +102 -0
  62. package/example/react-demo/tailwind.config.mjs +50 -0
  63. package/example/react-demo/tsconfig.json +16 -0
  64. package/example/react-demo/vite.config.ts +14 -0
  65. package/example/shared/defaultSchema.ts +68 -0
  66. package/example/vue-demo/dist/assets/index-BhWj3D5x.css +1 -0
  67. package/example/vue-demo/dist/assets/index-Bw3THsrD.js +44 -0
  68. package/example/vue-demo/dist/index.html +13 -0
  69. package/example/vue-demo/index.html +12 -0
  70. package/example/vue-demo/node_modules/.bin/tsc +17 -0
  71. package/example/vue-demo/node_modules/.bin/tsserver +17 -0
  72. package/example/vue-demo/node_modules/.bin/vite +17 -0
  73. package/example/vue-demo/node_modules/.vite/deps/_metadata.json +46 -0
  74. package/example/vue-demo/node_modules/.vite/deps/chunk-PZ5AY32C.js +10 -0
  75. package/example/vue-demo/node_modules/.vite/deps/chunk-PZ5AY32C.js.map +7 -0
  76. package/example/vue-demo/node_modules/.vite/deps/chunk-TCXBSQ4M.js +12877 -0
  77. package/example/vue-demo/node_modules/.vite/deps/chunk-TCXBSQ4M.js.map +7 -0
  78. package/example/vue-demo/node_modules/.vite/deps/clsx.js +22 -0
  79. package/example/vue-demo/node_modules/.vite/deps/clsx.js.map +7 -0
  80. package/example/vue-demo/node_modules/.vite/deps/lucide-vue-next.js +29720 -0
  81. package/example/vue-demo/node_modules/.vite/deps/lucide-vue-next.js.map +7 -0
  82. package/example/vue-demo/node_modules/.vite/deps/package.json +3 -0
  83. package/example/vue-demo/node_modules/.vite/deps/radix-vue.js +24321 -0
  84. package/example/vue-demo/node_modules/.vite/deps/radix-vue.js.map +7 -0
  85. package/example/vue-demo/node_modules/.vite/deps/tailwind-merge.js +2534 -0
  86. package/example/vue-demo/node_modules/.vite/deps/tailwind-merge.js.map +7 -0
  87. package/example/vue-demo/node_modules/.vite/deps/vue.js +348 -0
  88. package/example/vue-demo/node_modules/.vite/deps/vue.js.map +7 -0
  89. package/example/vue-demo/package.json +20 -0
  90. package/example/vue-demo/postcss.config.mjs +6 -0
  91. package/example/vue-demo/src/App.vue +61 -0
  92. package/example/vue-demo/src/env.d.ts +1 -0
  93. package/example/vue-demo/src/main.ts +5 -0
  94. package/example/vue-demo/src/style.css +102 -0
  95. package/example/vue-demo/tailwind.config.mjs +50 -0
  96. package/example/vue-demo/tsconfig.json +15 -0
  97. package/example/vue-demo/vite.config.ts +14 -0
  98. package/package.json +10 -2
  99. package/packages/core/dist/{chunk-KQR3LFND.js → chunk-GQASS6PM.js} +20 -0
  100. package/packages/core/dist/index.cjs +20 -0
  101. package/packages/core/dist/index.js +1 -1
  102. package/packages/core/dist/utils.cjs +20 -0
  103. package/packages/core/dist/utils.js +1 -1
  104. package/packages/core/node_modules/.bin/jiti +17 -0
  105. package/packages/core/node_modules/.bin/tsup +2 -2
  106. package/packages/core/node_modules/.bin/tsup-node +2 -2
  107. package/packages/core/node_modules/.vite/vitest/results.json +1 -1
  108. package/packages/core/src/utils.ts +21 -0
  109. package/packages/core/test/utils.test.ts +99 -0
  110. package/packages/react/dist/index.cjs +383 -110
  111. package/packages/react/dist/index.d.cts +80 -4
  112. package/packages/react/dist/index.d.ts +80 -4
  113. package/packages/react/dist/index.js +355 -110
  114. package/packages/react/node_modules/.bin/jiti +17 -0
  115. package/packages/react/node_modules/.bin/tsup +2 -2
  116. package/packages/react/node_modules/.bin/tsup-node +2 -2
  117. package/packages/react/node_modules/.bin/vite +4 -4
  118. package/packages/react/node_modules/.vite/vitest/results.json +1 -1
  119. package/packages/react/package.json +7 -2
  120. package/packages/react/postcss.config.mjs +6 -0
  121. package/packages/react/src/FormFieldRenderer.tsx +32 -112
  122. package/packages/react/src/components/Checkbox.tsx +28 -0
  123. package/packages/react/src/components/CheckboxRenderer.tsx +37 -0
  124. package/packages/react/src/components/Input.tsx +24 -0
  125. package/packages/react/src/components/InputRenderer.tsx +29 -0
  126. package/packages/react/src/components/Label.tsx +24 -0
  127. package/packages/react/src/components/RadioGroup.tsx +42 -0
  128. package/packages/react/src/components/RadioRenderer.tsx +29 -0
  129. package/packages/react/src/components/Select.tsx +93 -0
  130. package/packages/react/src/components/SelectRenderer.tsx +27 -0
  131. package/packages/react/src/components/Textarea.tsx +23 -0
  132. package/packages/react/src/components/TextareaRenderer.tsx +17 -0
  133. package/packages/react/src/components/index.ts +55 -0
  134. package/packages/react/src/components/types.ts +17 -0
  135. package/packages/react/src/index.tsx +1 -0
  136. package/packages/react/src/utils.ts +7 -0
  137. package/packages/react/tailwind.config.mjs +10 -0
  138. package/packages/react/test/FormFieldRenderer.test.tsx +127 -0
  139. package/packages/vue/dist/index.d.ts +33 -0
  140. package/packages/vue/dist/index.js +28 -1
  141. package/packages/vue/dist/index.mjs +6798 -142
  142. package/packages/vue/node_modules/.bin/tsc +2 -2
  143. package/packages/vue/node_modules/.bin/tsserver +2 -2
  144. package/packages/vue/node_modules/.bin/vite +2 -2
  145. package/packages/vue/node_modules/.bin/vitest +2 -2
  146. package/packages/vue/node_modules/.bin/vue-tsc +2 -2
  147. package/packages/vue/node_modules/.vite/vitest/results.json +1 -1
  148. package/packages/vue/package.json +4 -2
  149. package/packages/vue/postcss.config.mjs +6 -0
  150. package/packages/vue/src/FormFieldRenderer.vue +46 -90
  151. package/packages/vue/src/components/Checkbox.vue +28 -0
  152. package/packages/vue/src/components/CheckboxRenderer.vue +35 -0
  153. package/packages/vue/src/components/Input.vue +21 -0
  154. package/packages/vue/src/components/InputRenderer.vue +28 -0
  155. package/packages/vue/src/components/Label.vue +21 -0
  156. package/packages/vue/src/components/RadioGroup.vue +30 -0
  157. package/packages/vue/src/components/RadioGroupItem.vue +26 -0
  158. package/packages/vue/src/components/RadioRenderer.vue +24 -0
  159. package/packages/vue/src/components/Select.vue +40 -0
  160. package/packages/vue/src/components/SelectContent.vue +38 -0
  161. package/packages/vue/src/components/SelectItem.vue +43 -0
  162. package/packages/vue/src/components/SelectRenderer.vue +30 -0
  163. package/packages/vue/src/components/SelectTrigger.vue +27 -0
  164. package/packages/vue/src/components/Textarea.vue +19 -0
  165. package/packages/vue/src/components/TextareaRenderer.vue +18 -0
  166. package/packages/vue/src/components/index.ts +24 -0
  167. package/packages/vue/src/fieldComponentMap.ts +34 -0
  168. package/packages/vue/src/index.ts +2 -0
  169. package/packages/vue/src/utils.ts +6 -0
  170. package/packages/vue/tailwind.config.mjs +10 -0
  171. package/packages/vue/test/FormFieldRenderer.test.ts +133 -0
  172. package/pnpm-workspace.yaml +1 -1
  173. package/packages/core/src/index.test.ts +0 -37
  174. /package/packages/react/{src → test}/DynamicForm.test.tsx +0 -0
  175. /package/packages/vue/{src → test}/DynamicForm.test.ts +0 -0
@@ -6,12 +6,12 @@ case `uname` in
6
6
  esac
7
7
 
8
8
  if [ -z "$NODE_PATH" ]; then
9
- export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules"
9
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules"
10
10
  else
11
- export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
11
+ export NODE_PATH="/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules:/Users/pidan/Work/Learn/dynamic-form/node_modules/.pnpm/node_modules:$NODE_PATH"
12
12
  fi
13
13
  if [ -x "$basedir/node" ]; then
14
- exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/vite.js" "$@"
14
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/vite.js" "$@"
15
15
  else
16
- exec node "$basedir/../../../../node_modules/.pnpm/vite@5.4.21_@types+node@20.19.35/node_modules/vite/bin/vite.js" "$@"
16
+ exec node "$basedir/../../../../node_modules/.pnpm/vite@7.3.1_@types+node@20.19.35_jiti@1.21.7/node_modules/vite/bin/vite.js" "$@"
17
17
  fi
@@ -1 +1 @@
1
- {"version":"1.6.1","results":[[":src/DynamicForm.test.tsx",{"duration":26,"failed":false}]]}
1
+ {"version":"1.6.1","results":[[":test/DynamicForm.test.tsx",{"duration":30,"failed":false}],[":test/FormFieldRenderer.test.tsx",{"duration":129,"failed":false}]]}
@@ -27,10 +27,15 @@
27
27
  "lint": "eslint src/**/*.{ts,tsx} --no-error-on-unmatched-pattern"
28
28
  },
29
29
  "dependencies": {
30
- "clsx": "^2.0.0",
30
+ "@radix-ui/react-checkbox": "^1.3.3",
31
+ "@radix-ui/react-label": "^2.1.8",
32
+ "@radix-ui/react-radio-group": "^1.3.8",
33
+ "@radix-ui/react-select": "^2.2.6",
34
+ "class-variance-authority": "^0.7.1",
35
+ "clsx": "^2.1.1",
31
36
  "lucide-react": "^0.300.0",
32
37
  "pdyform": "workspace:*",
33
- "tailwind-merge": "^2.0.0"
38
+ "tailwind-merge": "^2.6.1"
34
39
  },
35
40
  "peerDependencies": {
36
41
  "react": "^18.0.0",
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -1,130 +1,50 @@
1
1
  import React from 'react';
2
- import { FormField } from 'pdyform/core';
2
+ import type { FormField } from 'pdyform/core';
3
+ import { Label, defaultComponentMap, InputRenderer } from './components';
4
+ import type { FieldComponentMap } from './components';
3
5
 
4
- interface FormFieldRendererProps {
6
+ export interface FormFieldRendererProps {
5
7
  field: FormField;
6
8
  value: any;
7
9
  onChange: (value: any) => void;
8
10
  onBlur?: () => void;
9
11
  error?: string;
12
+ /**
13
+ * Custom component map merged with the default map — external entries win.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * import { defaultComponentMap } from 'pdyform-react';
18
+ * const myMap = { ...defaultComponentMap, text: MyInput, rating: StarRating };
19
+ * <FormFieldRenderer componentMap={myMap} ... />
20
+ * ```
21
+ */
22
+ componentMap?: FieldComponentMap;
10
23
  }
11
24
 
12
- export const FormFieldRenderer: React.FC<FormFieldRendererProps> = ({ field, value, onChange, onBlur, error }) => {
13
- const { type, label, placeholder, options, description, disabled, name } = field;
25
+ export const FormFieldRenderer: React.FC<FormFieldRendererProps> = ({
26
+ field,
27
+ value,
28
+ onChange,
29
+ onBlur,
30
+ error,
31
+ componentMap,
32
+ }) => {
33
+ const { label, description, name, type } = field;
14
34
  const fieldId = `field-${name}`;
15
35
 
16
- const baseInputClasses = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
36
+ const resolvedMap: FieldComponentMap = componentMap
37
+ ? { ...defaultComponentMap, ...componentMap }
38
+ : defaultComponentMap;
17
39
 
18
- const renderInput = () => {
19
- switch (type) {
20
- case 'textarea':
21
- return (
22
- <textarea
23
- id={fieldId}
24
- className={`${baseInputClasses} min-h-[80px]`}
25
- placeholder={placeholder}
26
- value={value || ''}
27
- onChange={(e) => onChange(e.target.value)}
28
- onBlur={onBlur}
29
- disabled={disabled}
30
- name={name}
31
- />
32
- );
33
- case 'select':
34
- return (
35
- <select
36
- id={fieldId}
37
- className={baseInputClasses}
38
- value={value || ''}
39
- onChange={(e) => onChange(e.target.value)}
40
- onBlur={onBlur}
41
- disabled={disabled}
42
- name={name}
43
- >
44
- <option value="" disabled>{placeholder || 'Select an option'}</option>
45
- {options?.map((opt) => (
46
- <option key={opt.value} value={opt.value}>
47
- {opt.label}
48
- </option>
49
- ))}
50
- </select>
51
- );
52
- case 'checkbox':
53
- return (
54
- <div className="flex flex-wrap gap-4">
55
- {options?.map((opt) => (
56
- <label key={opt.value} className="flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
57
- <input
58
- type="checkbox"
59
- className="h-4 w-4 rounded border-primary text-primary focus:ring-primary"
60
- checked={Array.isArray(value) && value.includes(opt.value)}
61
- onChange={(e) => {
62
- const newValue = Array.isArray(value) ? [...value] : [];
63
- if (e.target.checked) {
64
- newValue.push(opt.value);
65
- } else {
66
- const index = newValue.indexOf(opt.value);
67
- if (index > -1) newValue.splice(index, 1);
68
- }
69
- onChange(newValue);
70
- }}
71
- onBlur={onBlur}
72
- disabled={disabled}
73
- />
74
- <span>{opt.label}</span>
75
- </label>
76
- ))}
77
- </div>
78
- );
79
- case 'radio':
80
- return (
81
- <div className="flex flex-wrap gap-4">
82
- {options?.map((opt) => (
83
- <label key={opt.value} className="flex items-center space-x-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
84
- <input
85
- type="radio"
86
- className="h-4 w-4 border-primary text-primary focus:ring-primary"
87
- name={field.name}
88
- checked={value === opt.value}
89
- onChange={() => onChange(opt.value)}
90
- onBlur={onBlur}
91
- disabled={disabled}
92
- />
93
- <span>{opt.label}</span>
94
- </label>
95
- ))}
96
- </div>
97
- );
98
- default:
99
- return (
100
- <input
101
- id={fieldId}
102
- type={type}
103
- className={baseInputClasses}
104
- placeholder={placeholder}
105
- value={value || ''}
106
- onChange={(e) => onChange(e.target.value)}
107
- onBlur={onBlur}
108
- disabled={disabled}
109
- name={name}
110
- />
111
- );
112
- }
113
- };
40
+ const FieldComponent = resolvedMap[type] ?? InputRenderer;
114
41
 
115
42
  return (
116
43
  <div className={`space-y-2 ${field.className || ''}`}>
117
- {label && (
118
- <label
119
- htmlFor={fieldId}
120
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
121
- >
122
- {label}
123
- </label>
124
- )}
125
- {renderInput()}
126
- {description && <p className="text-sm text-muted-foreground">{description}</p>}
127
- {error && <p className="text-sm font-medium text-destructive">{error}</p>}
44
+ {label && <Label htmlFor={fieldId}>{label}</Label>}
45
+ <FieldComponent field={field} value={value} onChange={onChange} onBlur={onBlur} fieldId={fieldId} />
46
+ {description && <p className="text-[0.8rem] text-muted-foreground">{description}</p>}
47
+ {error && <p className="text-[0.8rem] font-medium text-destructive">{error}</p>}
128
48
  </div>
129
49
  );
130
50
  };
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "../utils"
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator
20
+ className={cn("flex items-center justify-center text-current")}
21
+ >
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ))
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
+
28
+ export { Checkbox }
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import type { FieldRenderContext } from './types';
3
+ import { Checkbox } from './Checkbox';
4
+ import { Label } from './Label';
5
+
6
+ const CheckboxRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur }) => (
7
+ <div className="flex flex-wrap gap-4">
8
+ {field.options?.map((opt) => {
9
+ const checked = Array.isArray(value) && value.includes(opt.value);
10
+ return (
11
+ <div key={opt.value} className="flex items-center space-x-2">
12
+ <Checkbox
13
+ id={`checkbox-${field.name}-${opt.value}`}
14
+ checked={checked}
15
+ disabled={field.disabled}
16
+ onCheckedChange={(c) => {
17
+ const next = Array.isArray(value) ? [...value] : [];
18
+ if (c) {
19
+ next.push(opt.value);
20
+ } else {
21
+ const idx = next.indexOf(opt.value);
22
+ if (idx > -1) next.splice(idx, 1);
23
+ }
24
+ onChange(next);
25
+ }}
26
+ onBlur={onBlur}
27
+ />
28
+ <Label htmlFor={`checkbox-${field.name}-${opt.value}`} className="font-normal">
29
+ {opt.label}
30
+ </Label>
31
+ </div>
32
+ );
33
+ })}
34
+ </div>
35
+ );
36
+
37
+ export default CheckboxRenderer;
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import { cn } from "../utils"
3
+
4
+ export interface InputProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
6
+
7
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
8
+ ({ className, type, ...props }, ref) => {
9
+ return (
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
14
+ className
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+ )
22
+ Input.displayName = "Input"
23
+
24
+ export { Input }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type { FieldRenderContext } from './types';
3
+ import { Input } from './Input';
4
+
5
+ const InputRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur, fieldId }) => {
6
+ const handleChange = (nextValue: string) => {
7
+ if (field.type !== 'number') {
8
+ onChange(nextValue);
9
+ return;
10
+ }
11
+
12
+ onChange(nextValue === '' ? '' : Number(nextValue));
13
+ };
14
+
15
+ return (
16
+ <Input
17
+ id={fieldId}
18
+ type={field.type}
19
+ placeholder={field.placeholder}
20
+ value={value ?? ''}
21
+ onChange={(e) => handleChange(e.target.value)}
22
+ onBlur={onBlur}
23
+ disabled={field.disabled}
24
+ name={field.name}
25
+ />
26
+ );
27
+ };
28
+
29
+ export default InputRenderer;
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "../utils"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ))
22
+ Label.displayName = LabelPrimitive.Root.displayName
23
+
24
+ export { Label }
@@ -0,0 +1,42 @@
1
+ import * as React from "react"
2
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3
+ import { Circle } from "lucide-react"
4
+
5
+ import { cn } from "../utils"
6
+
7
+ const RadioGroup = React.forwardRef<
8
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
10
+ >(({ className, ...props }, ref) => {
11
+ return (
12
+ <RadioGroupPrimitive.Root
13
+ className={cn("grid gap-2", className)}
14
+ {...props}
15
+ ref={ref}
16
+ />
17
+ )
18
+ })
19
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
20
+
21
+ const RadioGroupItem = React.forwardRef<
22
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
23
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
24
+ >(({ className, ...props }, ref) => {
25
+ return (
26
+ <RadioGroupPrimitive.Item
27
+ ref={ref}
28
+ className={cn(
29
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
35
+ <Circle className="h-2.5 w-2.5 fill-current text-current" />
36
+ </RadioGroupPrimitive.Indicator>
37
+ </RadioGroupPrimitive.Item>
38
+ )
39
+ })
40
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
41
+
42
+ export { RadioGroup, RadioGroupItem }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type { FieldRenderContext } from './types';
3
+ import { RadioGroup, RadioGroupItem } from './RadioGroup';
4
+ import { Label } from './Label';
5
+
6
+ const RadioRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur }) => (
7
+ <RadioGroup
8
+ value={value != null ? String(value) : ''}
9
+ onValueChange={onChange}
10
+ disabled={field.disabled}
11
+ name={field.name}
12
+ className="flex flex-wrap gap-4"
13
+ >
14
+ {field.options?.map((opt) => (
15
+ <div key={opt.value} className="flex items-center space-x-2">
16
+ <RadioGroupItem
17
+ value={String(opt.value)}
18
+ id={`radio-${field.name}-${opt.value}`}
19
+ onBlur={onBlur}
20
+ />
21
+ <Label htmlFor={`radio-${field.name}-${opt.value}`} className="font-normal">
22
+ {opt.label}
23
+ </Label>
24
+ </div>
25
+ ))}
26
+ </RadioGroup>
27
+ );
28
+
29
+ export default RadioRenderer;
@@ -0,0 +1,93 @@
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+
5
+ import { cn } from "../utils"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef<
14
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
15
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16
+ >(({ className, children, ...props }, ref) => (
17
+ <SelectPrimitive.Trigger
18
+ ref={ref}
19
+ className={cn(
20
+ "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",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ <SelectPrimitive.Icon asChild>
27
+ <ChevronDown className="h-4 w-4 opacity-50" />
28
+ </SelectPrimitive.Icon>
29
+ </SelectPrimitive.Trigger>
30
+ ))
31
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32
+
33
+ const SelectContent = React.forwardRef<
34
+ React.ElementRef<typeof SelectPrimitive.Content>,
35
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
36
+ >(({ className, children, position = "popper", ...props }, ref) => (
37
+ <SelectPrimitive.Portal>
38
+ <SelectPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
42
+ position === "popper" &&
43
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
44
+ className
45
+ )}
46
+ position={position}
47
+ {...props}
48
+ >
49
+ <SelectPrimitive.Viewport
50
+ className={cn(
51
+ "p-1",
52
+ position === "popper" &&
53
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
54
+ )}
55
+ >
56
+ {children}
57
+ </SelectPrimitive.Viewport>
58
+ </SelectPrimitive.Content>
59
+ </SelectPrimitive.Portal>
60
+ ))
61
+ SelectContent.displayName = SelectPrimitive.Content.displayName
62
+
63
+ const SelectItem = React.forwardRef<
64
+ React.ElementRef<typeof SelectPrimitive.Item>,
65
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
66
+ >(({ className, children, ...props }, ref) => (
67
+ <SelectPrimitive.Item
68
+ ref={ref}
69
+ className={cn(
70
+ "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",
71
+ className
72
+ )}
73
+ {...props}
74
+ >
75
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
76
+ <SelectPrimitive.ItemIndicator>
77
+ <Check className="h-4 w-4" />
78
+ </SelectPrimitive.ItemIndicator>
79
+ </span>
80
+
81
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
82
+ </SelectPrimitive.Item>
83
+ ))
84
+ SelectItem.displayName = SelectPrimitive.Item.displayName
85
+
86
+ export {
87
+ Select,
88
+ SelectGroup,
89
+ SelectValue,
90
+ SelectTrigger,
91
+ SelectContent,
92
+ SelectItem,
93
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import type { FieldRenderContext } from './types';
3
+ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from './Select';
4
+
5
+ const SelectRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur, fieldId }) => (
6
+ <Select
7
+ value={value != null ? String(value) : ''}
8
+ onValueChange={onChange}
9
+ disabled={field.disabled}
10
+ name={field.name}
11
+ >
12
+ <SelectTrigger id={fieldId} onBlur={onBlur}>
13
+ <SelectValue placeholder={field.placeholder || 'Select an option'} />
14
+ </SelectTrigger>
15
+ <SelectContent>
16
+ <SelectGroup>
17
+ {field.options?.map((opt) => (
18
+ <SelectItem key={opt.value} value={String(opt.value)}>
19
+ {opt.label}
20
+ </SelectItem>
21
+ ))}
22
+ </SelectGroup>
23
+ </SelectContent>
24
+ </Select>
25
+ );
26
+
27
+ export default SelectRenderer;
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+ import { cn } from "../utils"
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+ )
21
+ Textarea.displayName = "Textarea"
22
+
23
+ export { Textarea }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { FieldRenderContext } from './types';
3
+ import { Textarea } from './Textarea';
4
+
5
+ const TextareaRenderer: React.FC<FieldRenderContext> = ({ field, value, onChange, onBlur, fieldId }) => (
6
+ <Textarea
7
+ id={fieldId}
8
+ placeholder={field.placeholder}
9
+ value={value ?? ''}
10
+ onChange={(e) => onChange(e.target.value)}
11
+ onBlur={onBlur}
12
+ disabled={field.disabled}
13
+ name={field.name}
14
+ />
15
+ );
16
+
17
+ export default TextareaRenderer;
@@ -0,0 +1,55 @@
1
+ // ─── Primitive UI components ─────────────────────────────────────────────────
2
+ export { Input } from './Input';
3
+ export { Textarea } from './Textarea';
4
+ export { Checkbox } from './Checkbox';
5
+ export { RadioGroup, RadioGroupItem } from './RadioGroup';
6
+ export {
7
+ Select,
8
+ SelectGroup,
9
+ SelectValue,
10
+ SelectTrigger,
11
+ SelectContent,
12
+ SelectItem,
13
+ } from './Select';
14
+ export { Label } from './Label';
15
+
16
+ // ─── Field renderer components ────────────────────────────────────────────────
17
+ export { default as InputRenderer } from './InputRenderer';
18
+ export { default as TextareaRenderer } from './TextareaRenderer';
19
+ export { default as SelectRenderer } from './SelectRenderer';
20
+ export { default as CheckboxRenderer } from './CheckboxRenderer';
21
+ export { default as RadioRenderer } from './RadioRenderer';
22
+
23
+ // ─── Types ───────────────────────────────────────────────────────────────────
24
+ export type { FieldRenderContext, FieldRenderer, FieldComponentMap } from './types';
25
+
26
+ // ─── Default component map ───────────────────────────────────────────────────
27
+ import InputRenderer from './InputRenderer';
28
+ import TextareaRenderer from './TextareaRenderer';
29
+ import SelectRenderer from './SelectRenderer';
30
+ import CheckboxRenderer from './CheckboxRenderer';
31
+ import RadioRenderer from './RadioRenderer';
32
+ import type { FieldComponentMap } from './types';
33
+
34
+ /**
35
+ * The default built-in component map.
36
+ * Import and spread this to extend or override individual field types:
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * import { defaultComponentMap } from 'pdyform-react';
41
+ * const myMap = { ...defaultComponentMap, text: MyInput, rating: StarRating };
42
+ * <FormFieldRenderer componentMap={myMap} ... />
43
+ * ```
44
+ */
45
+ export const defaultComponentMap: FieldComponentMap = {
46
+ text: InputRenderer,
47
+ number: InputRenderer,
48
+ password: InputRenderer,
49
+ email: InputRenderer,
50
+ date: InputRenderer,
51
+ textarea: TextareaRenderer,
52
+ select: SelectRenderer,
53
+ checkbox: CheckboxRenderer,
54
+ radio: RadioRenderer,
55
+ };