@questpie/admin 0.0.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 (203) hide show
  1. package/.turbo/turbo-build.log +108 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +556 -0
  4. package/STATUS.md +917 -0
  5. package/VALIDATION.md +602 -0
  6. package/components.json +24 -0
  7. package/dist/__tests__/setup.mjs +38 -0
  8. package/dist/__tests__/test-utils.mjs +45 -0
  9. package/dist/__tests__/vitest.d.mjs +3 -0
  10. package/dist/components/admin-app.mjs +69 -0
  11. package/dist/components/fields/array-field.mjs +190 -0
  12. package/dist/components/fields/checkbox-field.mjs +34 -0
  13. package/dist/components/fields/custom-field.mjs +32 -0
  14. package/dist/components/fields/date-field.mjs +41 -0
  15. package/dist/components/fields/datetime-field.mjs +42 -0
  16. package/dist/components/fields/email-field.mjs +37 -0
  17. package/dist/components/fields/embedded-collection.mjs +253 -0
  18. package/dist/components/fields/field-types.mjs +1 -0
  19. package/dist/components/fields/field-utils.mjs +10 -0
  20. package/dist/components/fields/field-wrapper.mjs +34 -0
  21. package/dist/components/fields/index.mjs +23 -0
  22. package/dist/components/fields/json-field.mjs +243 -0
  23. package/dist/components/fields/locale-badge.mjs +16 -0
  24. package/dist/components/fields/number-field.mjs +39 -0
  25. package/dist/components/fields/password-field.mjs +37 -0
  26. package/dist/components/fields/relation-field.mjs +104 -0
  27. package/dist/components/fields/relation-picker.mjs +229 -0
  28. package/dist/components/fields/relation-select.mjs +188 -0
  29. package/dist/components/fields/rich-text-editor/index.mjs +897 -0
  30. package/dist/components/fields/select-field.mjs +41 -0
  31. package/dist/components/fields/switch-field.mjs +34 -0
  32. package/dist/components/fields/text-field.mjs +38 -0
  33. package/dist/components/fields/textarea-field.mjs +38 -0
  34. package/dist/components/index.mjs +59 -0
  35. package/dist/components/primitives/checkbox-input.mjs +127 -0
  36. package/dist/components/primitives/date-input.mjs +303 -0
  37. package/dist/components/primitives/index.mjs +12 -0
  38. package/dist/components/primitives/number-input.mjs +104 -0
  39. package/dist/components/primitives/select-input.mjs +177 -0
  40. package/dist/components/primitives/tag-input.mjs +135 -0
  41. package/dist/components/primitives/text-input.mjs +39 -0
  42. package/dist/components/primitives/textarea-input.mjs +37 -0
  43. package/dist/components/primitives/toggle-input.mjs +31 -0
  44. package/dist/components/primitives/types.mjs +12 -0
  45. package/dist/components/ui/accordion.mjs +55 -0
  46. package/dist/components/ui/avatar.mjs +54 -0
  47. package/dist/components/ui/badge.mjs +34 -0
  48. package/dist/components/ui/button.mjs +48 -0
  49. package/dist/components/ui/card.mjs +58 -0
  50. package/dist/components/ui/checkbox.mjs +21 -0
  51. package/dist/components/ui/combobox.mjs +163 -0
  52. package/dist/components/ui/dialog.mjs +95 -0
  53. package/dist/components/ui/dropdown-menu.mjs +138 -0
  54. package/dist/components/ui/field.mjs +113 -0
  55. package/dist/components/ui/input-group.mjs +82 -0
  56. package/dist/components/ui/input.mjs +17 -0
  57. package/dist/components/ui/label.mjs +15 -0
  58. package/dist/components/ui/popover.mjs +56 -0
  59. package/dist/components/ui/scroll-area.mjs +38 -0
  60. package/dist/components/ui/select.mjs +100 -0
  61. package/dist/components/ui/separator.mjs +16 -0
  62. package/dist/components/ui/sheet.mjs +90 -0
  63. package/dist/components/ui/sidebar.mjs +387 -0
  64. package/dist/components/ui/skeleton.mjs +14 -0
  65. package/dist/components/ui/spinner.mjs +16 -0
  66. package/dist/components/ui/switch.mjs +22 -0
  67. package/dist/components/ui/table.mjs +68 -0
  68. package/dist/components/ui/tabs.mjs +48 -0
  69. package/dist/components/ui/textarea.mjs +15 -0
  70. package/dist/components/ui/tooltip.mjs +44 -0
  71. package/dist/config/component-registry.mjs +38 -0
  72. package/dist/config/index.mjs +129 -0
  73. package/dist/hooks/admin-provider.mjs +70 -0
  74. package/dist/hooks/index.mjs +7 -0
  75. package/dist/hooks/store.mjs +178 -0
  76. package/dist/hooks/use-auth.mjs +76 -0
  77. package/dist/hooks/use-collection-db.mjs +146 -0
  78. package/dist/hooks/use-collection.mjs +112 -0
  79. package/dist/hooks/use-global.mjs +46 -0
  80. package/dist/hooks/use-mobile.mjs +20 -0
  81. package/dist/lib/utils.mjs +10 -0
  82. package/dist/styles/index.css +336 -0
  83. package/dist/styles/index.mjs +1 -0
  84. package/dist/utils/index.mjs +9 -0
  85. package/dist/views/auth/auth-layout.mjs +52 -0
  86. package/dist/views/auth/forgot-password-form.mjs +148 -0
  87. package/dist/views/auth/index.mjs +6 -0
  88. package/dist/views/auth/login-form.mjs +156 -0
  89. package/dist/views/auth/reset-password-form.mjs +184 -0
  90. package/dist/views/collection/auto-form-fields.mjs +525 -0
  91. package/dist/views/collection/collection-form.mjs +91 -0
  92. package/dist/views/collection/collection-list.mjs +76 -0
  93. package/dist/views/collection/form-field.mjs +42 -0
  94. package/dist/views/collection/index.mjs +6 -0
  95. package/dist/views/common/index.mjs +4 -0
  96. package/dist/views/common/locale-switcher.mjs +39 -0
  97. package/dist/views/common/version-history.mjs +272 -0
  98. package/dist/views/index.mjs +9 -0
  99. package/dist/views/layout/admin-layout.mjs +40 -0
  100. package/dist/views/layout/admin-router.mjs +95 -0
  101. package/dist/views/layout/admin-sidebar.mjs +63 -0
  102. package/dist/views/layout/index.mjs +5 -0
  103. package/package.json +276 -0
  104. package/src/__tests__/setup.ts +44 -0
  105. package/src/__tests__/test-utils.tsx +49 -0
  106. package/src/__tests__/vitest.d.ts +9 -0
  107. package/src/components/admin-app.tsx +221 -0
  108. package/src/components/fields/array-field.tsx +237 -0
  109. package/src/components/fields/checkbox-field.tsx +47 -0
  110. package/src/components/fields/custom-field.tsx +50 -0
  111. package/src/components/fields/date-field.tsx +65 -0
  112. package/src/components/fields/datetime-field.tsx +67 -0
  113. package/src/components/fields/email-field.tsx +51 -0
  114. package/src/components/fields/embedded-collection.tsx +315 -0
  115. package/src/components/fields/field-types.ts +162 -0
  116. package/src/components/fields/field-utils.ts +6 -0
  117. package/src/components/fields/field-wrapper.tsx +52 -0
  118. package/src/components/fields/index.ts +66 -0
  119. package/src/components/fields/json-field.tsx +440 -0
  120. package/src/components/fields/locale-badge.tsx +15 -0
  121. package/src/components/fields/number-field.tsx +57 -0
  122. package/src/components/fields/password-field.tsx +51 -0
  123. package/src/components/fields/relation-field.tsx +243 -0
  124. package/src/components/fields/relation-picker.tsx +402 -0
  125. package/src/components/fields/relation-select.tsx +327 -0
  126. package/src/components/fields/rich-text-editor/index.tsx +1337 -0
  127. package/src/components/fields/select-field.tsx +61 -0
  128. package/src/components/fields/switch-field.tsx +47 -0
  129. package/src/components/fields/text-field.tsx +55 -0
  130. package/src/components/fields/textarea-field.tsx +55 -0
  131. package/src/components/index.ts +40 -0
  132. package/src/components/primitives/checkbox-input.tsx +193 -0
  133. package/src/components/primitives/date-input.tsx +401 -0
  134. package/src/components/primitives/index.ts +24 -0
  135. package/src/components/primitives/number-input.tsx +132 -0
  136. package/src/components/primitives/select-input.tsx +296 -0
  137. package/src/components/primitives/tag-input.tsx +200 -0
  138. package/src/components/primitives/text-input.tsx +49 -0
  139. package/src/components/primitives/textarea-input.tsx +46 -0
  140. package/src/components/primitives/toggle-input.tsx +36 -0
  141. package/src/components/primitives/types.ts +235 -0
  142. package/src/components/ui/accordion.tsx +72 -0
  143. package/src/components/ui/avatar.tsx +106 -0
  144. package/src/components/ui/badge.tsx +48 -0
  145. package/src/components/ui/button.tsx +53 -0
  146. package/src/components/ui/card.tsx +94 -0
  147. package/src/components/ui/checkbox.tsx +27 -0
  148. package/src/components/ui/combobox.tsx +290 -0
  149. package/src/components/ui/dialog.tsx +151 -0
  150. package/src/components/ui/dropdown-menu.tsx +254 -0
  151. package/src/components/ui/field.tsx +227 -0
  152. package/src/components/ui/input-group.tsx +149 -0
  153. package/src/components/ui/input.tsx +20 -0
  154. package/src/components/ui/label.tsx +18 -0
  155. package/src/components/ui/popover.tsx +88 -0
  156. package/src/components/ui/scroll-area.tsx +53 -0
  157. package/src/components/ui/select.tsx +192 -0
  158. package/src/components/ui/separator.tsx +23 -0
  159. package/src/components/ui/sheet.tsx +127 -0
  160. package/src/components/ui/sidebar.tsx +723 -0
  161. package/src/components/ui/skeleton.tsx +13 -0
  162. package/src/components/ui/spinner.tsx +10 -0
  163. package/src/components/ui/switch.tsx +32 -0
  164. package/src/components/ui/table.tsx +99 -0
  165. package/src/components/ui/tabs.tsx +82 -0
  166. package/src/components/ui/textarea.tsx +18 -0
  167. package/src/components/ui/tooltip.tsx +70 -0
  168. package/src/config/component-registry.ts +190 -0
  169. package/src/config/index.ts +1099 -0
  170. package/src/hooks/README.md +269 -0
  171. package/src/hooks/admin-provider.tsx +110 -0
  172. package/src/hooks/index.ts +41 -0
  173. package/src/hooks/store.ts +248 -0
  174. package/src/hooks/use-auth.ts +168 -0
  175. package/src/hooks/use-collection-db.ts +209 -0
  176. package/src/hooks/use-collection.ts +156 -0
  177. package/src/hooks/use-global.ts +69 -0
  178. package/src/hooks/use-mobile.ts +21 -0
  179. package/src/lib/utils.ts +6 -0
  180. package/src/styles/index.css +340 -0
  181. package/src/utils/index.ts +6 -0
  182. package/src/views/auth/auth-layout.tsx +77 -0
  183. package/src/views/auth/forgot-password-form.tsx +192 -0
  184. package/src/views/auth/index.ts +21 -0
  185. package/src/views/auth/login-form.tsx +229 -0
  186. package/src/views/auth/reset-password-form.tsx +232 -0
  187. package/src/views/collection/auto-form-fields.tsx +982 -0
  188. package/src/views/collection/collection-form.tsx +186 -0
  189. package/src/views/collection/collection-list.tsx +223 -0
  190. package/src/views/collection/form-field.tsx +52 -0
  191. package/src/views/collection/index.ts +15 -0
  192. package/src/views/common/index.ts +8 -0
  193. package/src/views/common/locale-switcher.tsx +45 -0
  194. package/src/views/common/version-history.tsx +406 -0
  195. package/src/views/index.ts +25 -0
  196. package/src/views/layout/admin-layout.tsx +117 -0
  197. package/src/views/layout/admin-router.tsx +206 -0
  198. package/src/views/layout/admin-sidebar.tsx +185 -0
  199. package/src/views/layout/index.ts +12 -0
  200. package/tsconfig.json +13 -0
  201. package/tsconfig.tsbuildinfo +1 -0
  202. package/tsdown.config.ts +13 -0
  203. package/vitest.config.ts +29 -0
@@ -0,0 +1,61 @@
1
+ import { Controller } from "react-hook-form";
2
+ import { SelectInput } from "../primitives/select-input";
3
+ import { FieldWrapper } from "./field-wrapper";
4
+ import { useResolvedControl } from "./field-utils";
5
+ import type { SelectFieldProps } from "./field-types";
6
+
7
+ export function SelectField<TValue extends string = string>({
8
+ name,
9
+ label,
10
+ description,
11
+ placeholder,
12
+ required,
13
+ disabled,
14
+ localized,
15
+ locale,
16
+ control,
17
+ className,
18
+ options,
19
+ loadOptions,
20
+ multiple,
21
+ clearable,
22
+ maxSelections,
23
+ emptyMessage,
24
+ }: SelectFieldProps<TValue>) {
25
+ const resolvedControl = useResolvedControl(control);
26
+
27
+ return (
28
+ <Controller
29
+ name={name}
30
+ control={resolvedControl}
31
+ render={({ field, fieldState }) => (
32
+ <FieldWrapper
33
+ name={name}
34
+ label={label}
35
+ description={description}
36
+ required={required}
37
+ disabled={disabled}
38
+ localized={localized}
39
+ locale={locale}
40
+ error={fieldState.error?.message}
41
+ >
42
+ <SelectInput<TValue>
43
+ id={name}
44
+ value={field.value ?? (multiple ? [] : null)}
45
+ onChange={field.onChange}
46
+ options={options}
47
+ loadOptions={loadOptions}
48
+ multiple={multiple}
49
+ clearable={clearable}
50
+ maxSelections={maxSelections}
51
+ emptyMessage={emptyMessage}
52
+ placeholder={placeholder}
53
+ disabled={disabled}
54
+ aria-invalid={!!fieldState.error}
55
+ className={className}
56
+ />
57
+ </FieldWrapper>
58
+ )}
59
+ />
60
+ );
61
+ }
@@ -0,0 +1,47 @@
1
+ import { Controller } from "react-hook-form";
2
+ import { ToggleInput } from "../primitives/toggle-input";
3
+ import { FieldWrapper } from "./field-wrapper";
4
+ import { useResolvedControl } from "./field-utils";
5
+ import type { BooleanFieldProps } from "./field-types";
6
+
7
+ export function SwitchField({
8
+ name,
9
+ label,
10
+ description,
11
+ required,
12
+ disabled,
13
+ localized,
14
+ locale,
15
+ control,
16
+ className,
17
+ }: BooleanFieldProps) {
18
+ const resolvedControl = useResolvedControl(control);
19
+
20
+ return (
21
+ <Controller
22
+ name={name}
23
+ control={resolvedControl}
24
+ render={({ field, fieldState }) => (
25
+ <FieldWrapper
26
+ name={name}
27
+ label={label}
28
+ description={description}
29
+ required={required}
30
+ disabled={disabled}
31
+ localized={localized}
32
+ locale={locale}
33
+ error={fieldState.error?.message}
34
+ >
35
+ <ToggleInput
36
+ id={name}
37
+ value={!!field.value}
38
+ onChange={field.onChange}
39
+ disabled={disabled}
40
+ aria-invalid={!!fieldState.error}
41
+ className={className}
42
+ />
43
+ </FieldWrapper>
44
+ )}
45
+ />
46
+ );
47
+ }
@@ -0,0 +1,55 @@
1
+ import { Controller } from "react-hook-form";
2
+ import { TextInput } from "../primitives/text-input";
3
+ import { FieldWrapper } from "./field-wrapper";
4
+ import { useResolvedControl } from "./field-utils";
5
+ import type { TextFieldProps } from "./field-types";
6
+
7
+ export function TextField({
8
+ name,
9
+ label,
10
+ description,
11
+ placeholder,
12
+ required,
13
+ disabled,
14
+ localized,
15
+ locale,
16
+ control,
17
+ className,
18
+ type = "text",
19
+ maxLength,
20
+ autoComplete,
21
+ }: TextFieldProps) {
22
+ const resolvedControl = useResolvedControl(control);
23
+
24
+ return (
25
+ <Controller
26
+ name={name}
27
+ control={resolvedControl}
28
+ render={({ field, fieldState }) => (
29
+ <FieldWrapper
30
+ name={name}
31
+ label={label}
32
+ description={description}
33
+ required={required}
34
+ disabled={disabled}
35
+ localized={localized}
36
+ locale={locale}
37
+ error={fieldState.error?.message}
38
+ >
39
+ <TextInput
40
+ id={name}
41
+ value={field.value ?? ""}
42
+ onChange={field.onChange}
43
+ type={type}
44
+ placeholder={placeholder}
45
+ disabled={disabled}
46
+ maxLength={maxLength}
47
+ autoComplete={autoComplete}
48
+ aria-invalid={!!fieldState.error}
49
+ className={className}
50
+ />
51
+ </FieldWrapper>
52
+ )}
53
+ />
54
+ );
55
+ }
@@ -0,0 +1,55 @@
1
+ import { Controller } from "react-hook-form";
2
+ import { TextareaInput } from "../primitives/textarea-input";
3
+ import { FieldWrapper } from "./field-wrapper";
4
+ import { useResolvedControl } from "./field-utils";
5
+ import type { TextareaFieldProps } from "./field-types";
6
+
7
+ export function TextareaField({
8
+ name,
9
+ label,
10
+ description,
11
+ placeholder,
12
+ required,
13
+ disabled,
14
+ localized,
15
+ locale,
16
+ control,
17
+ className,
18
+ rows,
19
+ maxLength,
20
+ autoResize,
21
+ }: TextareaFieldProps) {
22
+ const resolvedControl = useResolvedControl(control);
23
+
24
+ return (
25
+ <Controller
26
+ name={name}
27
+ control={resolvedControl}
28
+ render={({ field, fieldState }) => (
29
+ <FieldWrapper
30
+ name={name}
31
+ label={label}
32
+ description={description}
33
+ required={required}
34
+ disabled={disabled}
35
+ localized={localized}
36
+ locale={locale}
37
+ error={fieldState.error?.message}
38
+ >
39
+ <TextareaInput
40
+ id={name}
41
+ value={field.value ?? ""}
42
+ onChange={field.onChange}
43
+ placeholder={placeholder}
44
+ disabled={disabled}
45
+ rows={rows}
46
+ maxLength={maxLength}
47
+ autoResize={autoResize}
48
+ aria-invalid={!!fieldState.error}
49
+ className={className}
50
+ />
51
+ </FieldWrapper>
52
+ )}
53
+ />
54
+ );
55
+ }
@@ -0,0 +1,40 @@
1
+ // Re-export all shadcn UI components
2
+
3
+ // MAIN ENTRY POINT
4
+ export { AdminApp } from "./admin-app";
5
+ export type { AdminAppProps } from "./admin-app";
6
+
7
+ // View components - re-export from new views directory
8
+ // @deprecated Import from '@questpie/admin/views' instead
9
+ export * from "../views";
10
+
11
+ // Field components (RelationSelect, RelationPicker, etc.)
12
+ export * from "./fields";
13
+
14
+ // UI components (only export existing ones)
15
+ export * from "./ui/accordion";
16
+ export * from "./ui/avatar";
17
+ export * from "./ui/badge";
18
+ export * from "./ui/button";
19
+ export * from "./ui/card";
20
+ export * from "./ui/checkbox";
21
+ export * from "./ui/combobox";
22
+ export * from "./ui/dialog";
23
+ export * from "./ui/dropdown-menu";
24
+ export * from "./ui/field";
25
+ export * from "./ui/input";
26
+ export * from "./ui/input-group";
27
+ export * from "./ui/label";
28
+ export * from "./ui/popover";
29
+ export * from "./ui/scroll-area";
30
+ export * from "./ui/select";
31
+ export * from "./ui/separator";
32
+ export * from "./ui/sheet";
33
+ export * from "./ui/sidebar";
34
+ export * from "./ui/skeleton";
35
+ export * from "./ui/spinner";
36
+ export * from "./ui/switch";
37
+ export * from "./ui/table";
38
+ export * from "./ui/tabs";
39
+ export * from "./ui/textarea";
40
+ export * from "./ui/tooltip";
@@ -0,0 +1,193 @@
1
+ "use client";
2
+
3
+ import { Checkbox } from "../ui/checkbox";
4
+ import { cn } from "../../lib/utils";
5
+ import type {
6
+ CheckboxInputProps,
7
+ CheckboxGroupProps,
8
+ RadioGroupProps,
9
+ } from "./types";
10
+
11
+ /**
12
+ * Checkbox Input Primitive
13
+ *
14
+ * A single checkbox with value/onChange pattern.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <CheckboxInput
19
+ * value={isAccepted}
20
+ * onChange={setIsAccepted}
21
+ * />
22
+ * ```
23
+ */
24
+ export function CheckboxInput({
25
+ value,
26
+ onChange,
27
+ disabled,
28
+ className,
29
+ id,
30
+ "aria-invalid": ariaInvalid,
31
+ }: CheckboxInputProps) {
32
+ return (
33
+ <Checkbox
34
+ id={id}
35
+ checked={value}
36
+ onCheckedChange={(checked) => onChange(checked === true)}
37
+ disabled={disabled}
38
+ aria-invalid={ariaInvalid}
39
+ className={cn(className)}
40
+ />
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Checkbox Group Primitive
46
+ *
47
+ * A group of checkboxes for multi-selection.
48
+ * Returns an array of selected values.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * <CheckboxGroup
53
+ * value={selectedCategories}
54
+ * onChange={setSelectedCategories}
55
+ * options={[
56
+ * { value: "news", label: "News" },
57
+ * { value: "blog", label: "Blog" },
58
+ * { value: "tutorial", label: "Tutorial" },
59
+ * ]}
60
+ * orientation="vertical"
61
+ * />
62
+ * ```
63
+ */
64
+ export function CheckboxGroup<TValue extends string = string>({
65
+ value,
66
+ onChange,
67
+ options,
68
+ orientation = "vertical",
69
+ disabled,
70
+ className,
71
+ id,
72
+ "aria-invalid": ariaInvalid,
73
+ }: CheckboxGroupProps<TValue>) {
74
+ const handleChange = (optionValue: TValue, checked: boolean) => {
75
+ if (checked) {
76
+ onChange([...value, optionValue]);
77
+ } else {
78
+ onChange(value.filter((v) => v !== optionValue));
79
+ }
80
+ };
81
+
82
+ return (
83
+ <div
84
+ id={id}
85
+ role="group"
86
+ aria-invalid={ariaInvalid}
87
+ className={cn(
88
+ "flex gap-3",
89
+ orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
90
+ className,
91
+ )}
92
+ >
93
+ {options.map((option) => {
94
+ const optionId = `${id}-${String(option.value)}`;
95
+ return (
96
+ <div
97
+ key={String(option.value)}
98
+ className={cn(
99
+ "flex items-center gap-2",
100
+ option.disabled || disabled
101
+ ? "opacity-50 cursor-not-allowed"
102
+ : "cursor-pointer",
103
+ )}
104
+ >
105
+ <CheckboxInput
106
+ id={optionId}
107
+ value={value.includes(option.value)}
108
+ onChange={(checked) => handleChange(option.value, checked)}
109
+ disabled={option.disabled || disabled}
110
+ />
111
+ <label htmlFor={optionId} className="text-sm cursor-pointer">
112
+ {option.label}
113
+ </label>
114
+ </div>
115
+ );
116
+ })}
117
+ </div>
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Radio Group Primitive
123
+ *
124
+ * A group of radio buttons for single selection.
125
+ * Returns the selected value or null.
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * <RadioGroup
130
+ * value={selectedPriority}
131
+ * onChange={setSelectedPriority}
132
+ * options={[
133
+ * { value: "low", label: "Low" },
134
+ * { value: "medium", label: "Medium" },
135
+ * { value: "high", label: "High" },
136
+ * ]}
137
+ * />
138
+ * ```
139
+ */
140
+ export function RadioGroup<TValue extends string = string>({
141
+ value,
142
+ onChange,
143
+ options,
144
+ orientation = "vertical",
145
+ disabled,
146
+ className,
147
+ id,
148
+ "aria-invalid": ariaInvalid,
149
+ }: RadioGroupProps<TValue>) {
150
+ return (
151
+ <div
152
+ id={id}
153
+ role="radiogroup"
154
+ aria-invalid={ariaInvalid}
155
+ className={cn(
156
+ "flex gap-3",
157
+ orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
158
+ className,
159
+ )}
160
+ >
161
+ {options.map((option) => {
162
+ const optionId = `${id}-${String(option.value)}`;
163
+ return (
164
+ <div
165
+ key={String(option.value)}
166
+ className={cn(
167
+ "flex items-center gap-2",
168
+ option.disabled || disabled
169
+ ? "opacity-50 cursor-not-allowed"
170
+ : "cursor-pointer",
171
+ )}
172
+ >
173
+ <input
174
+ type="radio"
175
+ id={optionId}
176
+ name={id}
177
+ checked={value === option.value}
178
+ onChange={() => onChange(option.value)}
179
+ disabled={option.disabled || disabled}
180
+ className={cn(
181
+ "size-4 shrink-0 accent-primary",
182
+ "disabled:cursor-not-allowed disabled:opacity-50",
183
+ )}
184
+ />
185
+ <label htmlFor={optionId} className="text-sm cursor-pointer">
186
+ {option.label}
187
+ </label>
188
+ </div>
189
+ );
190
+ })}
191
+ </div>
192
+ );
193
+ }