@questpie/admin 0.0.1 → 1.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 (250) hide show
  1. package/README.md +439 -424
  2. package/dist/auth-layout-M8K8_q5R.mjs +181 -0
  3. package/dist/auth-layout-M8K8_q5R.mjs.map +1 -0
  4. package/dist/bulk-upload-dialog-D7w7W1Hl.mjs +273 -0
  5. package/dist/bulk-upload-dialog-D7w7W1Hl.mjs.map +1 -0
  6. package/dist/{components/ui/card.mjs → card-BKHjBQfw.mjs} +8 -8
  7. package/dist/card-BKHjBQfw.mjs.map +1 -0
  8. package/dist/client/styles/index.css +434 -0
  9. package/dist/client-DbpZKSgH.d.mts +13585 -0
  10. package/dist/client-DbpZKSgH.d.mts.map +1 -0
  11. package/dist/client-njX1rZmi.mjs +22612 -0
  12. package/dist/client-njX1rZmi.mjs.map +1 -0
  13. package/dist/client.d.mts +3 -0
  14. package/dist/client.mjs +13 -0
  15. package/dist/content-locales-provider-BXvuIgfg.mjs +1650 -0
  16. package/dist/content-locales-provider-BXvuIgfg.mjs.map +1 -0
  17. package/dist/dashboard-page-B4PGEdc2.mjs +2500 -0
  18. package/dist/dashboard-page-B4PGEdc2.mjs.map +1 -0
  19. package/dist/dashboard-page-mCY0pgZv.mjs +3 -0
  20. package/dist/dropzone-Do3awXKd.mjs +634 -0
  21. package/dist/dropzone-Do3awXKd.mjs.map +1 -0
  22. package/dist/{views/auth/forgot-password-form.mjs → forgot-password-page-Bcp-An4Y.mjs} +87 -14
  23. package/dist/forgot-password-page-Bcp-An4Y.mjs.map +1 -0
  24. package/dist/forgot-password-page-CEwsdLwn.mjs +3 -0
  25. package/dist/index-B9Xwk4hi.d.mts +2753 -0
  26. package/dist/index-B9Xwk4hi.d.mts.map +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +13 -0
  29. package/dist/login-page-BUnpCbCa.mjs +3 -0
  30. package/dist/login-page-CP4gA-dl.mjs +298 -0
  31. package/dist/login-page-CP4gA-dl.mjs.map +1 -0
  32. package/dist/preview-utils-BKQ9-TMa.mjs +65 -0
  33. package/dist/preview-utils-BKQ9-TMa.mjs.map +1 -0
  34. package/dist/{views/auth/reset-password-form.mjs → reset-password-page-BqfDmLxA.mjs} +111 -14
  35. package/dist/reset-password-page-BqfDmLxA.mjs.map +1 -0
  36. package/dist/reset-password-page-CufHz3h3.mjs +3 -0
  37. package/dist/runtime-6VZM878K.mjs +69 -0
  38. package/dist/runtime-6VZM878K.mjs.map +1 -0
  39. package/dist/saved-views.types-BMsz5mCy.d.mts +42 -0
  40. package/dist/saved-views.types-BMsz5mCy.d.mts.map +1 -0
  41. package/dist/server.d.mts +250 -0
  42. package/dist/server.d.mts.map +1 -0
  43. package/dist/server.mjs +832 -0
  44. package/dist/server.mjs.map +1 -0
  45. package/dist/setup-page-BNNzt_Z6.mjs +3 -0
  46. package/dist/setup-page-YAP_fzqh.mjs +264 -0
  47. package/dist/setup-page-YAP_fzqh.mjs.map +1 -0
  48. package/dist/shared.d.mts +57 -0
  49. package/dist/shared.d.mts.map +1 -0
  50. package/dist/shared.mjs +3 -0
  51. package/dist/{hooks/use-auth.mjs → use-auth-BoLmWtmU.mjs} +42 -30
  52. package/dist/use-auth-BoLmWtmU.mjs.map +1 -0
  53. package/package.json +48 -198
  54. package/.turbo/turbo-build.log +0 -108
  55. package/CHANGELOG.md +0 -10
  56. package/STATUS.md +0 -917
  57. package/VALIDATION.md +0 -602
  58. package/components.json +0 -24
  59. package/dist/__tests__/setup.mjs +0 -38
  60. package/dist/__tests__/test-utils.mjs +0 -45
  61. package/dist/__tests__/vitest.d.mjs +0 -3
  62. package/dist/components/admin-app.mjs +0 -69
  63. package/dist/components/fields/array-field.mjs +0 -190
  64. package/dist/components/fields/checkbox-field.mjs +0 -34
  65. package/dist/components/fields/custom-field.mjs +0 -32
  66. package/dist/components/fields/date-field.mjs +0 -41
  67. package/dist/components/fields/datetime-field.mjs +0 -42
  68. package/dist/components/fields/email-field.mjs +0 -37
  69. package/dist/components/fields/embedded-collection.mjs +0 -253
  70. package/dist/components/fields/field-types.mjs +0 -1
  71. package/dist/components/fields/field-utils.mjs +0 -10
  72. package/dist/components/fields/field-wrapper.mjs +0 -34
  73. package/dist/components/fields/index.mjs +0 -23
  74. package/dist/components/fields/json-field.mjs +0 -243
  75. package/dist/components/fields/locale-badge.mjs +0 -16
  76. package/dist/components/fields/number-field.mjs +0 -39
  77. package/dist/components/fields/password-field.mjs +0 -37
  78. package/dist/components/fields/relation-field.mjs +0 -104
  79. package/dist/components/fields/relation-picker.mjs +0 -229
  80. package/dist/components/fields/relation-select.mjs +0 -188
  81. package/dist/components/fields/rich-text-editor/index.mjs +0 -897
  82. package/dist/components/fields/select-field.mjs +0 -41
  83. package/dist/components/fields/switch-field.mjs +0 -34
  84. package/dist/components/fields/text-field.mjs +0 -38
  85. package/dist/components/fields/textarea-field.mjs +0 -38
  86. package/dist/components/index.mjs +0 -59
  87. package/dist/components/primitives/checkbox-input.mjs +0 -127
  88. package/dist/components/primitives/date-input.mjs +0 -303
  89. package/dist/components/primitives/index.mjs +0 -12
  90. package/dist/components/primitives/number-input.mjs +0 -104
  91. package/dist/components/primitives/select-input.mjs +0 -177
  92. package/dist/components/primitives/tag-input.mjs +0 -135
  93. package/dist/components/primitives/text-input.mjs +0 -39
  94. package/dist/components/primitives/textarea-input.mjs +0 -37
  95. package/dist/components/primitives/toggle-input.mjs +0 -31
  96. package/dist/components/primitives/types.mjs +0 -12
  97. package/dist/components/ui/accordion.mjs +0 -55
  98. package/dist/components/ui/avatar.mjs +0 -54
  99. package/dist/components/ui/badge.mjs +0 -34
  100. package/dist/components/ui/button.mjs +0 -48
  101. package/dist/components/ui/checkbox.mjs +0 -21
  102. package/dist/components/ui/combobox.mjs +0 -163
  103. package/dist/components/ui/dialog.mjs +0 -95
  104. package/dist/components/ui/dropdown-menu.mjs +0 -138
  105. package/dist/components/ui/field.mjs +0 -113
  106. package/dist/components/ui/input-group.mjs +0 -82
  107. package/dist/components/ui/input.mjs +0 -17
  108. package/dist/components/ui/label.mjs +0 -15
  109. package/dist/components/ui/popover.mjs +0 -56
  110. package/dist/components/ui/scroll-area.mjs +0 -38
  111. package/dist/components/ui/select.mjs +0 -100
  112. package/dist/components/ui/separator.mjs +0 -16
  113. package/dist/components/ui/sheet.mjs +0 -90
  114. package/dist/components/ui/sidebar.mjs +0 -387
  115. package/dist/components/ui/skeleton.mjs +0 -14
  116. package/dist/components/ui/spinner.mjs +0 -16
  117. package/dist/components/ui/switch.mjs +0 -22
  118. package/dist/components/ui/table.mjs +0 -68
  119. package/dist/components/ui/tabs.mjs +0 -48
  120. package/dist/components/ui/textarea.mjs +0 -15
  121. package/dist/components/ui/tooltip.mjs +0 -44
  122. package/dist/config/component-registry.mjs +0 -38
  123. package/dist/config/index.mjs +0 -129
  124. package/dist/hooks/admin-provider.mjs +0 -70
  125. package/dist/hooks/index.mjs +0 -7
  126. package/dist/hooks/store.mjs +0 -178
  127. package/dist/hooks/use-collection-db.mjs +0 -146
  128. package/dist/hooks/use-collection.mjs +0 -112
  129. package/dist/hooks/use-global.mjs +0 -46
  130. package/dist/hooks/use-mobile.mjs +0 -20
  131. package/dist/lib/utils.mjs +0 -10
  132. package/dist/styles/index.css +0 -336
  133. package/dist/styles/index.mjs +0 -1
  134. package/dist/utils/index.mjs +0 -9
  135. package/dist/views/auth/auth-layout.mjs +0 -52
  136. package/dist/views/auth/index.mjs +0 -6
  137. package/dist/views/auth/login-form.mjs +0 -156
  138. package/dist/views/collection/auto-form-fields.mjs +0 -525
  139. package/dist/views/collection/collection-form.mjs +0 -91
  140. package/dist/views/collection/collection-list.mjs +0 -76
  141. package/dist/views/collection/form-field.mjs +0 -42
  142. package/dist/views/collection/index.mjs +0 -6
  143. package/dist/views/common/index.mjs +0 -4
  144. package/dist/views/common/locale-switcher.mjs +0 -39
  145. package/dist/views/common/version-history.mjs +0 -272
  146. package/dist/views/index.mjs +0 -9
  147. package/dist/views/layout/admin-layout.mjs +0 -40
  148. package/dist/views/layout/admin-router.mjs +0 -95
  149. package/dist/views/layout/admin-sidebar.mjs +0 -63
  150. package/dist/views/layout/index.mjs +0 -5
  151. package/src/__tests__/setup.ts +0 -44
  152. package/src/__tests__/test-utils.tsx +0 -49
  153. package/src/__tests__/vitest.d.ts +0 -9
  154. package/src/components/admin-app.tsx +0 -221
  155. package/src/components/fields/array-field.tsx +0 -237
  156. package/src/components/fields/checkbox-field.tsx +0 -47
  157. package/src/components/fields/custom-field.tsx +0 -50
  158. package/src/components/fields/date-field.tsx +0 -65
  159. package/src/components/fields/datetime-field.tsx +0 -67
  160. package/src/components/fields/email-field.tsx +0 -51
  161. package/src/components/fields/embedded-collection.tsx +0 -315
  162. package/src/components/fields/field-types.ts +0 -162
  163. package/src/components/fields/field-utils.ts +0 -6
  164. package/src/components/fields/field-wrapper.tsx +0 -52
  165. package/src/components/fields/index.ts +0 -66
  166. package/src/components/fields/json-field.tsx +0 -440
  167. package/src/components/fields/locale-badge.tsx +0 -15
  168. package/src/components/fields/number-field.tsx +0 -57
  169. package/src/components/fields/password-field.tsx +0 -51
  170. package/src/components/fields/relation-field.tsx +0 -243
  171. package/src/components/fields/relation-picker.tsx +0 -402
  172. package/src/components/fields/relation-select.tsx +0 -327
  173. package/src/components/fields/rich-text-editor/index.tsx +0 -1337
  174. package/src/components/fields/select-field.tsx +0 -61
  175. package/src/components/fields/switch-field.tsx +0 -47
  176. package/src/components/fields/text-field.tsx +0 -55
  177. package/src/components/fields/textarea-field.tsx +0 -55
  178. package/src/components/index.ts +0 -40
  179. package/src/components/primitives/checkbox-input.tsx +0 -193
  180. package/src/components/primitives/date-input.tsx +0 -401
  181. package/src/components/primitives/index.ts +0 -24
  182. package/src/components/primitives/number-input.tsx +0 -132
  183. package/src/components/primitives/select-input.tsx +0 -296
  184. package/src/components/primitives/tag-input.tsx +0 -200
  185. package/src/components/primitives/text-input.tsx +0 -49
  186. package/src/components/primitives/textarea-input.tsx +0 -46
  187. package/src/components/primitives/toggle-input.tsx +0 -36
  188. package/src/components/primitives/types.ts +0 -235
  189. package/src/components/ui/accordion.tsx +0 -72
  190. package/src/components/ui/avatar.tsx +0 -106
  191. package/src/components/ui/badge.tsx +0 -48
  192. package/src/components/ui/button.tsx +0 -53
  193. package/src/components/ui/card.tsx +0 -94
  194. package/src/components/ui/checkbox.tsx +0 -27
  195. package/src/components/ui/combobox.tsx +0 -290
  196. package/src/components/ui/dialog.tsx +0 -151
  197. package/src/components/ui/dropdown-menu.tsx +0 -254
  198. package/src/components/ui/field.tsx +0 -227
  199. package/src/components/ui/input-group.tsx +0 -149
  200. package/src/components/ui/input.tsx +0 -20
  201. package/src/components/ui/label.tsx +0 -18
  202. package/src/components/ui/popover.tsx +0 -88
  203. package/src/components/ui/scroll-area.tsx +0 -53
  204. package/src/components/ui/select.tsx +0 -192
  205. package/src/components/ui/separator.tsx +0 -23
  206. package/src/components/ui/sheet.tsx +0 -127
  207. package/src/components/ui/sidebar.tsx +0 -723
  208. package/src/components/ui/skeleton.tsx +0 -13
  209. package/src/components/ui/spinner.tsx +0 -10
  210. package/src/components/ui/switch.tsx +0 -32
  211. package/src/components/ui/table.tsx +0 -99
  212. package/src/components/ui/tabs.tsx +0 -82
  213. package/src/components/ui/textarea.tsx +0 -18
  214. package/src/components/ui/tooltip.tsx +0 -70
  215. package/src/config/component-registry.ts +0 -190
  216. package/src/config/index.ts +0 -1099
  217. package/src/hooks/README.md +0 -269
  218. package/src/hooks/admin-provider.tsx +0 -110
  219. package/src/hooks/index.ts +0 -41
  220. package/src/hooks/store.ts +0 -248
  221. package/src/hooks/use-auth.ts +0 -168
  222. package/src/hooks/use-collection-db.ts +0 -209
  223. package/src/hooks/use-collection.ts +0 -156
  224. package/src/hooks/use-global.ts +0 -69
  225. package/src/hooks/use-mobile.ts +0 -21
  226. package/src/lib/utils.ts +0 -6
  227. package/src/styles/index.css +0 -340
  228. package/src/utils/index.ts +0 -6
  229. package/src/views/auth/auth-layout.tsx +0 -77
  230. package/src/views/auth/forgot-password-form.tsx +0 -192
  231. package/src/views/auth/index.ts +0 -21
  232. package/src/views/auth/login-form.tsx +0 -229
  233. package/src/views/auth/reset-password-form.tsx +0 -232
  234. package/src/views/collection/auto-form-fields.tsx +0 -982
  235. package/src/views/collection/collection-form.tsx +0 -186
  236. package/src/views/collection/collection-list.tsx +0 -223
  237. package/src/views/collection/form-field.tsx +0 -52
  238. package/src/views/collection/index.ts +0 -15
  239. package/src/views/common/index.ts +0 -8
  240. package/src/views/common/locale-switcher.tsx +0 -45
  241. package/src/views/common/version-history.tsx +0 -406
  242. package/src/views/index.ts +0 -25
  243. package/src/views/layout/admin-layout.tsx +0 -117
  244. package/src/views/layout/admin-router.tsx +0 -206
  245. package/src/views/layout/admin-sidebar.tsx +0 -185
  246. package/src/views/layout/index.ts +0 -12
  247. package/tsconfig.json +0 -13
  248. package/tsconfig.tsbuildinfo +0 -1
  249. package/tsdown.config.ts +0 -13
  250. package/vitest.config.ts +0 -29
@@ -1,296 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useCallback, useMemo, useEffect, useRef } from "react";
4
- import { Check, CircleNotch } from "@phosphor-icons/react";
5
- import {
6
- Select,
7
- SelectContent,
8
- SelectItem,
9
- SelectTrigger,
10
- SelectValue,
11
- } from "../ui/select";
12
- import {
13
- Combobox,
14
- ComboboxInput,
15
- ComboboxContent,
16
- ComboboxList,
17
- ComboboxItem,
18
- ComboboxEmpty,
19
- ComboboxChips,
20
- ComboboxChip,
21
- ComboboxChipsInput,
22
- useComboboxAnchor,
23
- } from "../ui/combobox";
24
- import { cn } from "../../lib/utils";
25
- import type { SelectInputProps, SelectOption } from "./types";
26
- import { flattenOptions } from "./types";
27
-
28
- /**
29
- * Select Input Primitive
30
- *
31
- * A unified select component that supports:
32
- * - Single select (dropdown)
33
- * - Multi-select (chips)
34
- * - Static options
35
- * - Dynamic loading via loadOptions
36
- * - Search/filtering
37
- *
38
- * @example
39
- * ```tsx
40
- * // Single select with static options
41
- * <SelectInput
42
- * value={status}
43
- * onChange={setStatus}
44
- * options={[
45
- * { value: "active", label: "Active" },
46
- * { value: "inactive", label: "Inactive" },
47
- * ]}
48
- * />
49
- *
50
- * // Multi-select with dynamic loading
51
- * <SelectInput
52
- * value={selectedTags}
53
- * onChange={setSelectedTags}
54
- * multiple
55
- * loadOptions={async (search) => {
56
- * const res = await fetch(`/api/tags?search=${search}`)
57
- * return res.json()
58
- * }}
59
- * />
60
- * ```
61
- */
62
- export function SelectInput<TValue extends string = string>({
63
- value,
64
- onChange,
65
- options: staticOptions = [],
66
- loadOptions,
67
- multiple = false,
68
- clearable = true,
69
- maxSelections,
70
- debounceMs = 300,
71
- loading: externalLoading = false,
72
- emptyMessage = "No options found",
73
- placeholder = "Select...",
74
- disabled,
75
- className,
76
- id,
77
- "aria-invalid": ariaInvalid,
78
- }: SelectInputProps<TValue>) {
79
- const [search, setSearch] = useState("");
80
- const [dynamicOptions, setDynamicOptions] = useState<SelectOption<TValue>[]>(
81
- [],
82
- );
83
- const [isLoading, setIsLoading] = useState(false);
84
- const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);
85
- const anchorRef = useComboboxAnchor();
86
-
87
- // Flatten static options
88
- const flatStaticOptions = useMemo(
89
- () => flattenOptions(staticOptions),
90
- [staticOptions],
91
- );
92
-
93
- // Merge static and dynamic options
94
- const allOptions = useMemo(() => {
95
- if (loadOptions) {
96
- return dynamicOptions;
97
- }
98
- return flatStaticOptions;
99
- }, [loadOptions, dynamicOptions, flatStaticOptions]);
100
-
101
- // Filter options by search (for static options)
102
- const filteredOptions = useMemo(() => {
103
- if (loadOptions) {
104
- return allOptions; // Dynamic options are already filtered by server
105
- }
106
- if (!search) {
107
- return allOptions;
108
- }
109
- return allOptions.filter((opt) =>
110
- opt.label.toLowerCase().includes(search.toLowerCase()),
111
- );
112
- }, [allOptions, search, loadOptions]);
113
-
114
- // Load dynamic options
115
- useEffect(() => {
116
- if (!loadOptions) return;
117
-
118
- if (debounceRef.current) {
119
- clearTimeout(debounceRef.current);
120
- }
121
-
122
- debounceRef.current = setTimeout(async () => {
123
- setIsLoading(true);
124
- try {
125
- const results = await loadOptions(search);
126
- setDynamicOptions(results);
127
- } catch (error) {
128
- console.error("Failed to load options:", error);
129
- setDynamicOptions([]);
130
- } finally {
131
- setIsLoading(false);
132
- }
133
- }, debounceMs);
134
-
135
- return () => {
136
- if (debounceRef.current) {
137
- clearTimeout(debounceRef.current);
138
- }
139
- };
140
- }, [search, loadOptions, debounceMs]);
141
-
142
- // Get label for a value
143
- const getLabel = useCallback(
144
- (val: TValue): string => {
145
- const option = allOptions.find((opt) => opt.value === val);
146
- return option?.label ?? String(val);
147
- },
148
- [allOptions],
149
- );
150
-
151
- // Normalize value to array for internal handling
152
- const selectedValues = useMemo((): TValue[] => {
153
- if (value === null || value === undefined) return [];
154
- return Array.isArray(value) ? value : [value];
155
- }, [value]);
156
-
157
- // Handle selection for multi-select
158
- const handleMultiSelect = useCallback(
159
- (selectedValue: TValue) => {
160
- const currentValues = selectedValues;
161
- const isSelected = currentValues.includes(selectedValue);
162
-
163
- if (isSelected) {
164
- // Remove
165
- const newValues = currentValues.filter((v) => v !== selectedValue);
166
- onChange(newValues as TValue & (TValue | TValue[] | null));
167
- } else {
168
- // Add (check max)
169
- if (maxSelections && currentValues.length >= maxSelections) return;
170
- const newValues = [...currentValues, selectedValue];
171
- onChange(newValues as TValue & (TValue | TValue[] | null));
172
- }
173
- },
174
- [selectedValues, onChange, maxSelections],
175
- );
176
-
177
- // Handle removal (for multi-select chips)
178
- const handleRemove = useCallback(
179
- (removedValue: TValue) => {
180
- const newValues = selectedValues.filter((v) => v !== removedValue);
181
- onChange(newValues as TValue & (TValue | TValue[] | null));
182
- },
183
- [selectedValues, onChange],
184
- );
185
-
186
- const showLoading = isLoading || externalLoading;
187
-
188
- // ==========================================================================
189
- // Single Select (using shadcn Select) - only for static options
190
- // ==========================================================================
191
- if (!multiple && !loadOptions) {
192
- return (
193
- <Select
194
- value={selectedValues[0] ?? ""}
195
- onValueChange={(val) =>
196
- onChange(val as TValue & (TValue | TValue[] | null))
197
- }
198
- disabled={disabled}
199
- >
200
- <SelectTrigger
201
- id={id}
202
- className={cn("w-full", className)}
203
- aria-invalid={ariaInvalid}
204
- >
205
- <SelectValue>
206
- {selectedValues[0] ? getLabel(selectedValues[0]) : placeholder}
207
- </SelectValue>
208
- </SelectTrigger>
209
- <SelectContent>
210
- {filteredOptions.map((option) => (
211
- <SelectItem
212
- key={String(option.value)}
213
- value={String(option.value)}
214
- disabled={option.disabled}
215
- >
216
- {option.icon}
217
- {option.label}
218
- </SelectItem>
219
- ))}
220
- </SelectContent>
221
- </Select>
222
- );
223
- }
224
-
225
- // ==========================================================================
226
- // Multi-Select or Dynamic Loading (using Combobox)
227
- // ==========================================================================
228
- return (
229
- <Combobox
230
- value={multiple ? undefined : (selectedValues[0] ?? null)}
231
- onValueChange={(val) => {
232
- if (val !== null && val !== undefined) {
233
- if (multiple) {
234
- handleMultiSelect(val as TValue);
235
- } else {
236
- onChange(val as TValue & (TValue | TValue[] | null));
237
- }
238
- }
239
- }}
240
- disabled={disabled}
241
- >
242
- {multiple ? (
243
- // Multi-select with chips
244
- <ComboboxChips
245
- ref={anchorRef}
246
- className={cn(className)}
247
- aria-invalid={ariaInvalid}
248
- >
249
- {selectedValues.map((val) => (
250
- <ComboboxChip key={String(val)}>{getLabel(val)}</ComboboxChip>
251
- ))}
252
- <ComboboxChipsInput
253
- placeholder={selectedValues.length === 0 ? placeholder : undefined}
254
- value={search}
255
- onChange={(e) => setSearch(e.target.value)}
256
- />
257
- </ComboboxChips>
258
- ) : (
259
- // Single select with search
260
- <ComboboxInput
261
- id={id}
262
- placeholder={placeholder}
263
- value={search}
264
- onChange={(e) => setSearch(e.target.value)}
265
- className={cn(className)}
266
- aria-invalid={ariaInvalid}
267
- showClear={clearable && selectedValues.length > 0}
268
- />
269
- )}
270
-
271
- <ComboboxContent anchor={multiple ? anchorRef : undefined}>
272
- {showLoading && (
273
- <div className="flex items-center justify-center py-4">
274
- <CircleNotch className="size-4 animate-spin text-muted-foreground" />
275
- </div>
276
- )}
277
- <ComboboxList>
278
- {filteredOptions.map((option) => (
279
- <ComboboxItem
280
- key={String(option.value)}
281
- value={option.value}
282
- disabled={option.disabled}
283
- >
284
- {option.icon}
285
- {option.label}
286
- {multiple && selectedValues.includes(option.value) && (
287
- <Check className="ml-auto size-3.5" />
288
- )}
289
- </ComboboxItem>
290
- ))}
291
- </ComboboxList>
292
- <ComboboxEmpty>{emptyMessage}</ComboboxEmpty>
293
- </ComboboxContent>
294
- </Combobox>
295
- );
296
- }
@@ -1,200 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useCallback, KeyboardEvent } from "react";
4
- import { X } from "@phosphor-icons/react";
5
- import { Badge } from "../ui/badge";
6
- import { cn } from "../../lib/utils";
7
- import type { TagInputProps } from "./types";
8
-
9
- /**
10
- * Tag Input Primitive
11
- *
12
- * An input for creating and managing tags/chips.
13
- * Supports validation patterns, max tags, and autocomplete suggestions.
14
- *
15
- * @example
16
- * ```tsx
17
- * // Basic usage
18
- * <TagInput
19
- * value={tags}
20
- * onChange={setTags}
21
- * placeholder="Add tags..."
22
- * />
23
- *
24
- * // With suggestions and validation
25
- * <TagInput
26
- * value={emails}
27
- * onChange={setEmails}
28
- * suggestions={["user@example.com", "admin@example.com"]}
29
- * pattern={/^[^\s@]+@[^\s@]+\.[^\s@]+$/}
30
- * maxTags={5}
31
- * />
32
- * ```
33
- */
34
- export function TagInput({
35
- value,
36
- onChange,
37
- suggestions = [],
38
- maxTags,
39
- allowDuplicates = false,
40
- pattern,
41
- placeholder = "Type and press Enter...",
42
- disabled,
43
- className,
44
- id,
45
- "aria-invalid": ariaInvalid,
46
- }: TagInputProps) {
47
- const [inputValue, setInputValue] = useState("");
48
- const [showSuggestions, setShowSuggestions] = useState(false);
49
-
50
- const addTag = useCallback(
51
- (tag: string) => {
52
- const trimmed = tag.trim();
53
- if (!trimmed) return;
54
-
55
- // Check max tags
56
- if (maxTags && value.length >= maxTags) return;
57
-
58
- // Check duplicates
59
- if (!allowDuplicates && value.includes(trimmed)) return;
60
-
61
- // Check pattern
62
- if (pattern && !pattern.test(trimmed)) return;
63
-
64
- onChange([...value, trimmed]);
65
- setInputValue("");
66
- setShowSuggestions(false);
67
- },
68
- [value, onChange, maxTags, allowDuplicates, pattern],
69
- );
70
-
71
- const removeTag = useCallback(
72
- (index: number) => {
73
- if (disabled) return;
74
- const newTags = value.filter((_, i) => i !== index);
75
- onChange(newTags);
76
- },
77
- [value, onChange, disabled],
78
- );
79
-
80
- const handleKeyDown = useCallback(
81
- (e: KeyboardEvent<HTMLInputElement>) => {
82
- if (e.key === "Enter") {
83
- e.preventDefault();
84
- addTag(inputValue);
85
- } else if (e.key === "Backspace" && !inputValue && value.length > 0) {
86
- removeTag(value.length - 1);
87
- } else if (e.key === "Escape") {
88
- setShowSuggestions(false);
89
- }
90
- },
91
- [inputValue, value, addTag, removeTag],
92
- );
93
-
94
- const handleInputChange = useCallback(
95
- (e: React.ChangeEvent<HTMLInputElement>) => {
96
- const newValue = e.target.value;
97
- setInputValue(newValue);
98
- setShowSuggestions(newValue.length > 0 && suggestions.length > 0);
99
- },
100
- [suggestions],
101
- );
102
-
103
- const filteredSuggestions = suggestions.filter(
104
- (suggestion) =>
105
- suggestion.toLowerCase().includes(inputValue.toLowerCase()) &&
106
- (allowDuplicates || !value.includes(suggestion)),
107
- );
108
-
109
- const canAddMore = !maxTags || value.length < maxTags;
110
-
111
- return (
112
- <div className={cn("relative", className)}>
113
- <div
114
- className={cn(
115
- "flex flex-wrap gap-1.5 min-h-9 w-full border border-input bg-background px-3 py-2",
116
- "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
117
- ariaInvalid && "border-destructive ring-destructive/20",
118
- disabled && "cursor-not-allowed opacity-50",
119
- )}
120
- >
121
- {value.map((tag, index) => (
122
- <Badge
123
- key={`${tag}-${index}`}
124
- variant="secondary"
125
- className="gap-1 pr-1"
126
- >
127
- {tag}
128
- {!disabled && (
129
- <button
130
- type="button"
131
- onClick={() => removeTag(index)}
132
- className="ml-1 rounded-full p-0.5 hover:bg-muted-foreground/20"
133
- aria-label={`Remove ${tag}`}
134
- >
135
- <X className="size-3" />
136
- </button>
137
- )}
138
- </Badge>
139
- ))}
140
- {canAddMore && (
141
- <input
142
- id={id}
143
- type="text"
144
- value={inputValue}
145
- onChange={handleInputChange}
146
- onKeyDown={handleKeyDown}
147
- onFocus={() =>
148
- setShowSuggestions(
149
- inputValue.length > 0 && suggestions.length > 0,
150
- )
151
- }
152
- onBlur={() => {
153
- // Delay to allow click on suggestion
154
- setTimeout(() => setShowSuggestions(false), 150);
155
- }}
156
- placeholder={value.length === 0 ? placeholder : undefined}
157
- disabled={disabled}
158
- aria-invalid={ariaInvalid}
159
- className={cn(
160
- "flex-1 min-w-[120px] bg-transparent text-sm outline-none placeholder:text-muted-foreground",
161
- "disabled:cursor-not-allowed",
162
- )}
163
- />
164
- )}
165
- </div>
166
-
167
- {/* Suggestions dropdown */}
168
- {showSuggestions && filteredSuggestions.length > 0 && (
169
- <div
170
- className={cn(
171
- "absolute z-50 mt-1 w-full border border-input bg-popover shadow-md",
172
- "max-h-[200px] overflow-auto",
173
- )}
174
- >
175
- {filteredSuggestions.map((suggestion, index) => (
176
- <button
177
- key={suggestion}
178
- type="button"
179
- onClick={() => addTag(suggestion)}
180
- className={cn(
181
- "w-full px-3 py-2 text-left text-sm",
182
- "hover:bg-accent hover:text-accent-foreground",
183
- "focus:bg-accent focus:text-accent-foreground focus:outline-none",
184
- )}
185
- >
186
- {suggestion}
187
- </button>
188
- ))}
189
- </div>
190
- )}
191
-
192
- {/* Helper text for max tags */}
193
- {maxTags && (
194
- <p className="mt-1 text-xs text-muted-foreground">
195
- {value.length} / {maxTags} tags
196
- </p>
197
- )}
198
- </div>
199
- );
200
- }
@@ -1,49 +0,0 @@
1
- import { Input } from "../ui/input";
2
- import { cn } from "../../lib/utils";
3
- import type { TextInputProps } from "./types";
4
-
5
- /**
6
- * Text Input Primitive
7
- *
8
- * A basic text input with value/onChange pattern.
9
- * Supports different input types: text, email, password, url, tel, search.
10
- *
11
- * @example
12
- * ```tsx
13
- * <TextInput
14
- * value={email}
15
- * onChange={setEmail}
16
- * type="email"
17
- * placeholder="Enter email"
18
- * />
19
- * ```
20
- */
21
- export function TextInput({
22
- value,
23
- onChange,
24
- type = "text",
25
- placeholder,
26
- disabled,
27
- readOnly,
28
- maxLength,
29
- autoComplete,
30
- className,
31
- id,
32
- "aria-invalid": ariaInvalid,
33
- }: TextInputProps) {
34
- return (
35
- <Input
36
- id={id}
37
- type={type}
38
- value={value}
39
- onChange={(e) => onChange(e.target.value)}
40
- placeholder={placeholder}
41
- disabled={disabled}
42
- readOnly={readOnly}
43
- maxLength={maxLength}
44
- autoComplete={autoComplete}
45
- aria-invalid={ariaInvalid}
46
- className={cn(className)}
47
- />
48
- );
49
- }
@@ -1,46 +0,0 @@
1
- import { Textarea } from "../ui/textarea";
2
- import { cn } from "../../lib/utils";
3
- import type { TextareaInputProps } from "./types";
4
-
5
- /**
6
- * Textarea Input Primitive
7
- *
8
- * A multi-line text input.
9
- *
10
- * @example
11
- * ```tsx
12
- * <TextareaInput
13
- * value={description}
14
- * onChange={setDescription}
15
- * rows={4}
16
- * placeholder="Enter description..."
17
- * />
18
- * ```
19
- */
20
- export function TextareaInput({
21
- value,
22
- onChange,
23
- rows = 3,
24
- maxLength,
25
- placeholder,
26
- disabled,
27
- readOnly,
28
- className,
29
- id,
30
- "aria-invalid": ariaInvalid,
31
- }: TextareaInputProps) {
32
- return (
33
- <Textarea
34
- id={id}
35
- value={value}
36
- onChange={(e) => onChange(e.target.value)}
37
- rows={rows}
38
- maxLength={maxLength}
39
- placeholder={placeholder}
40
- disabled={disabled}
41
- readOnly={readOnly}
42
- aria-invalid={ariaInvalid}
43
- className={cn(className)}
44
- />
45
- );
46
- }
@@ -1,36 +0,0 @@
1
- import { Switch } from "../ui/switch";
2
- import { cn } from "../../lib/utils";
3
- import type { ToggleInputProps } from "./types";
4
-
5
- /**
6
- * Toggle Input Primitive
7
- *
8
- * A switch/toggle for boolean values.
9
- *
10
- * @example
11
- * ```tsx
12
- * <ToggleInput
13
- * value={isActive}
14
- * onChange={setIsActive}
15
- * />
16
- * ```
17
- */
18
- export function ToggleInput({
19
- value,
20
- onChange,
21
- disabled,
22
- className,
23
- id,
24
- "aria-invalid": ariaInvalid,
25
- }: ToggleInputProps) {
26
- return (
27
- <Switch
28
- id={id}
29
- checked={value}
30
- onCheckedChange={onChange}
31
- disabled={disabled}
32
- aria-invalid={ariaInvalid}
33
- className={cn(className)}
34
- />
35
- );
36
- }