@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,41 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { SelectInput } from "../primitives/select-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/select-field.tsx
8
+ function SelectField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, options, loadOptions, multiple, clearable, maxSelections, emptyMessage }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(SelectInput, {
22
+ id: name,
23
+ value: field.value ?? (multiple ? [] : null),
24
+ onChange: field.onChange,
25
+ options,
26
+ loadOptions,
27
+ multiple,
28
+ clearable,
29
+ maxSelections,
30
+ emptyMessage,
31
+ placeholder,
32
+ disabled,
33
+ "aria-invalid": !!fieldState.error,
34
+ className
35
+ })
36
+ })
37
+ });
38
+ }
39
+
40
+ //#endregion
41
+ export { SelectField };
@@ -0,0 +1,34 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ToggleInput } from "../primitives/toggle-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/switch-field.tsx
8
+ function SwitchField({ name, label, description, required, disabled, localized, locale, control, className }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(ToggleInput, {
22
+ id: name,
23
+ value: !!field.value,
24
+ onChange: field.onChange,
25
+ disabled,
26
+ "aria-invalid": !!fieldState.error,
27
+ className
28
+ })
29
+ })
30
+ });
31
+ }
32
+
33
+ //#endregion
34
+ export { SwitchField };
@@ -0,0 +1,38 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { TextInput } from "../primitives/text-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/text-field.tsx
8
+ function TextField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, type = "text", maxLength, autoComplete }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(TextInput, {
22
+ id: name,
23
+ value: field.value ?? "",
24
+ onChange: field.onChange,
25
+ type,
26
+ placeholder,
27
+ disabled,
28
+ maxLength,
29
+ autoComplete,
30
+ "aria-invalid": !!fieldState.error,
31
+ className
32
+ })
33
+ })
34
+ });
35
+ }
36
+
37
+ //#endregion
38
+ export { TextField };
@@ -0,0 +1,38 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { TextareaInput } from "../primitives/textarea-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/textarea-field.tsx
8
+ function TextareaField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, rows, maxLength, autoResize }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(TextareaInput, {
22
+ id: name,
23
+ value: field.value ?? "",
24
+ onChange: field.onChange,
25
+ placeholder,
26
+ disabled,
27
+ rows,
28
+ maxLength,
29
+ autoResize,
30
+ "aria-invalid": !!fieldState.error,
31
+ className
32
+ })
33
+ })
34
+ });
35
+ }
36
+
37
+ //#endregion
38
+ export { TextareaField };
@@ -0,0 +1,59 @@
1
+ import { AdminApp } from "./admin-app";
2
+
3
+ export * from "../views"
4
+
5
+ export * from "./fields"
6
+
7
+ export * from "./ui/accordion"
8
+
9
+ export * from "./ui/avatar"
10
+
11
+ export * from "./ui/badge"
12
+
13
+ export * from "./ui/button"
14
+
15
+ export * from "./ui/card"
16
+
17
+ export * from "./ui/checkbox"
18
+
19
+ export * from "./ui/combobox"
20
+
21
+ export * from "./ui/dialog"
22
+
23
+ export * from "./ui/dropdown-menu"
24
+
25
+ export * from "./ui/field"
26
+
27
+ export * from "./ui/input"
28
+
29
+ export * from "./ui/input-group"
30
+
31
+ export * from "./ui/label"
32
+
33
+ export * from "./ui/popover"
34
+
35
+ export * from "./ui/scroll-area"
36
+
37
+ export * from "./ui/select"
38
+
39
+ export * from "./ui/separator"
40
+
41
+ export * from "./ui/sheet"
42
+
43
+ export * from "./ui/sidebar"
44
+
45
+ export * from "./ui/skeleton"
46
+
47
+ export * from "./ui/spinner"
48
+
49
+ export * from "./ui/switch"
50
+
51
+ export * from "./ui/table"
52
+
53
+ export * from "./ui/tabs"
54
+
55
+ export * from "./ui/textarea"
56
+
57
+ export * from "./ui/tooltip"
58
+
59
+ export { AdminApp };
@@ -0,0 +1,127 @@
1
+ "use client";
2
+
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Checkbox } from "../ui/checkbox";
5
+ import { cn } from "../../lib/utils";
6
+
7
+ //#region src/components/primitives/checkbox-input.tsx
8
+ /**
9
+ * Checkbox Input Primitive
10
+ *
11
+ * A single checkbox with value/onChange pattern.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <CheckboxInput
16
+ * value={isAccepted}
17
+ * onChange={setIsAccepted}
18
+ * />
19
+ * ```
20
+ */
21
+ function CheckboxInput({ value, onChange, disabled, className, id, "aria-invalid": ariaInvalid }) {
22
+ return /* @__PURE__ */ jsx(Checkbox, {
23
+ id,
24
+ checked: value,
25
+ onCheckedChange: (checked) => onChange(checked === true),
26
+ disabled,
27
+ "aria-invalid": ariaInvalid,
28
+ className: cn(className)
29
+ });
30
+ }
31
+ /**
32
+ * Checkbox Group Primitive
33
+ *
34
+ * A group of checkboxes for multi-selection.
35
+ * Returns an array of selected values.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * <CheckboxGroup
40
+ * value={selectedCategories}
41
+ * onChange={setSelectedCategories}
42
+ * options={[
43
+ * { value: "news", label: "News" },
44
+ * { value: "blog", label: "Blog" },
45
+ * { value: "tutorial", label: "Tutorial" },
46
+ * ]}
47
+ * orientation="vertical"
48
+ * />
49
+ * ```
50
+ */
51
+ function CheckboxGroup({ value, onChange, options, orientation = "vertical", disabled, className, id, "aria-invalid": ariaInvalid }) {
52
+ const handleChange = (optionValue, checked) => {
53
+ if (checked) onChange([...value, optionValue]);
54
+ else onChange(value.filter((v) => v !== optionValue));
55
+ };
56
+ return /* @__PURE__ */ jsx("div", {
57
+ id,
58
+ role: "group",
59
+ "aria-invalid": ariaInvalid,
60
+ className: cn("flex gap-3", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap", className),
61
+ children: options.map((option) => {
62
+ const optionId = `${id}-${String(option.value)}`;
63
+ return /* @__PURE__ */ jsxs("div", {
64
+ className: cn("flex items-center gap-2", option.disabled || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
65
+ children: [/* @__PURE__ */ jsx(CheckboxInput, {
66
+ id: optionId,
67
+ value: value.includes(option.value),
68
+ onChange: (checked) => handleChange(option.value, checked),
69
+ disabled: option.disabled || disabled
70
+ }), /* @__PURE__ */ jsx("label", {
71
+ htmlFor: optionId,
72
+ className: "text-sm cursor-pointer",
73
+ children: option.label
74
+ })]
75
+ }, String(option.value));
76
+ })
77
+ });
78
+ }
79
+ /**
80
+ * Radio Group Primitive
81
+ *
82
+ * A group of radio buttons for single selection.
83
+ * Returns the selected value or null.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * <RadioGroup
88
+ * value={selectedPriority}
89
+ * onChange={setSelectedPriority}
90
+ * options={[
91
+ * { value: "low", label: "Low" },
92
+ * { value: "medium", label: "Medium" },
93
+ * { value: "high", label: "High" },
94
+ * ]}
95
+ * />
96
+ * ```
97
+ */
98
+ function RadioGroup({ value, onChange, options, orientation = "vertical", disabled, className, id, "aria-invalid": ariaInvalid }) {
99
+ return /* @__PURE__ */ jsx("div", {
100
+ id,
101
+ role: "radiogroup",
102
+ "aria-invalid": ariaInvalid,
103
+ className: cn("flex gap-3", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap", className),
104
+ children: options.map((option) => {
105
+ const optionId = `${id}-${String(option.value)}`;
106
+ return /* @__PURE__ */ jsxs("div", {
107
+ className: cn("flex items-center gap-2", option.disabled || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
108
+ children: [/* @__PURE__ */ jsx("input", {
109
+ type: "radio",
110
+ id: optionId,
111
+ name: id,
112
+ checked: value === option.value,
113
+ onChange: () => onChange(option.value),
114
+ disabled: option.disabled || disabled,
115
+ className: cn("size-4 shrink-0 accent-primary", "disabled:cursor-not-allowed disabled:opacity-50")
116
+ }), /* @__PURE__ */ jsx("label", {
117
+ htmlFor: optionId,
118
+ className: "text-sm cursor-pointer",
119
+ children: option.label
120
+ })]
121
+ }, String(option.value));
122
+ })
123
+ });
124
+ }
125
+
126
+ //#endregion
127
+ export { CheckboxGroup, CheckboxInput, RadioGroup };
@@ -0,0 +1,303 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
6
+ import { cn } from "../../lib/utils";
7
+ import { DayPicker } from "react-day-picker";
8
+ import { format } from "date-fns";
9
+ import { CalendarBlank, X } from "@phosphor-icons/react";
10
+
11
+ //#region src/components/primitives/date-input.tsx
12
+ /**
13
+ * Date Input Primitive
14
+ *
15
+ * A date picker with popover calendar.
16
+ * Uses react-day-picker under the hood.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * <DateInput
21
+ * value={selectedDate}
22
+ * onChange={setSelectedDate}
23
+ * placeholder="Select date"
24
+ * />
25
+ * ```
26
+ */
27
+ function DateInput({ value, onChange, minDate, maxDate, format: dateFormat = "PP", placeholder = "Select date", disabled, className, id, "aria-invalid": ariaInvalid }) {
28
+ const [open, setOpen] = useState(false);
29
+ const handleSelect = (date) => {
30
+ onChange(date ?? null);
31
+ setOpen(false);
32
+ };
33
+ const handleClear = (e) => {
34
+ e.stopPropagation();
35
+ onChange(null);
36
+ };
37
+ return /* @__PURE__ */ jsxs(Popover, {
38
+ open,
39
+ onOpenChange: setOpen,
40
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
41
+ id,
42
+ disabled,
43
+ "aria-invalid": ariaInvalid,
44
+ className: cn("flex h-9 w-full items-center justify-start gap-2 border border-input bg-background px-3 py-2 text-sm", "hover:bg-accent hover:text-accent-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", !value && "text-muted-foreground", className),
45
+ children: [
46
+ /* @__PURE__ */ jsx(CalendarBlank, { className: "size-4" }),
47
+ /* @__PURE__ */ jsx("span", {
48
+ className: "flex-1 text-left",
49
+ children: value ? format(value, dateFormat) : placeholder
50
+ }),
51
+ value && !disabled && /* @__PURE__ */ jsx(X, {
52
+ className: "size-4 opacity-50 hover:opacity-100",
53
+ onClick: handleClear
54
+ })
55
+ ]
56
+ }), /* @__PURE__ */ jsx(PopoverContent, {
57
+ className: "w-auto p-0",
58
+ align: "start",
59
+ children: /* @__PURE__ */ jsx(DayPicker, {
60
+ mode: "single",
61
+ selected: value ?? void 0,
62
+ onSelect: handleSelect,
63
+ disabled: (date) => {
64
+ if (minDate && date < minDate) return true;
65
+ if (maxDate && date > maxDate) return true;
66
+ return false;
67
+ },
68
+ className: "p-3",
69
+ classNames: {
70
+ months: "flex flex-col sm:flex-row gap-2",
71
+ month: "flex flex-col gap-4",
72
+ month_caption: "flex justify-center pt-1 relative items-center w-full",
73
+ caption_label: "text-sm font-medium",
74
+ nav: "flex items-center gap-1",
75
+ button_previous: "absolute left-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
76
+ button_next: "absolute right-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
77
+ month_grid: "w-full border-collapse",
78
+ weekdays: "flex",
79
+ weekday: "text-muted-foreground w-8 font-normal text-[0.8rem]",
80
+ week: "flex w-full mt-2",
81
+ day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50",
82
+ day_button: cn("size-8 p-0 font-normal", "hover:bg-accent hover:text-accent-foreground", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"),
83
+ selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
84
+ today: "bg-accent text-accent-foreground",
85
+ outside: "text-muted-foreground opacity-50",
86
+ disabled: "text-muted-foreground opacity-50",
87
+ hidden: "invisible"
88
+ }
89
+ })
90
+ })]
91
+ });
92
+ }
93
+ /**
94
+ * DateTime Input Primitive
95
+ *
96
+ * A date and time picker.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * <DateTimeInput
101
+ * value={scheduledAt}
102
+ * onChange={setScheduledAt}
103
+ * precision="minute"
104
+ * />
105
+ * ```
106
+ */
107
+ function DateTimeInput({ value, onChange, minDate, maxDate, format: dateFormat = "PPp", precision = "minute", placeholder = "Select date and time", disabled, className, id, "aria-invalid": ariaInvalid }) {
108
+ const [open, setOpen] = useState(false);
109
+ const [timeValue, setTimeValue] = useState(() => {
110
+ if (!value) return "";
111
+ return precision === "second" ? format(value, "HH:mm:ss") : format(value, "HH:mm");
112
+ });
113
+ const handleDateSelect = (date) => {
114
+ if (!date) {
115
+ onChange(null);
116
+ return;
117
+ }
118
+ if (timeValue) {
119
+ const [hours, minutes, seconds] = timeValue.split(":").map(Number);
120
+ date.setHours(hours || 0, minutes || 0, seconds || 0);
121
+ }
122
+ onChange(date);
123
+ };
124
+ const handleTimeChange = (e) => {
125
+ const time = e.target.value;
126
+ setTimeValue(time);
127
+ if (value && time) {
128
+ const [hours, minutes, seconds] = time.split(":").map(Number);
129
+ const newDate = new Date(value);
130
+ newDate.setHours(hours || 0, minutes || 0, seconds || 0);
131
+ onChange(newDate);
132
+ }
133
+ };
134
+ const handleClear = (e) => {
135
+ e.stopPropagation();
136
+ onChange(null);
137
+ setTimeValue("");
138
+ };
139
+ return /* @__PURE__ */ jsxs(Popover, {
140
+ open,
141
+ onOpenChange: setOpen,
142
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
143
+ id,
144
+ disabled,
145
+ "aria-invalid": ariaInvalid,
146
+ className: cn("flex h-9 w-full items-center justify-start gap-2 border border-input bg-background px-3 py-2 text-sm", "hover:bg-accent hover:text-accent-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", !value && "text-muted-foreground", className),
147
+ children: [
148
+ /* @__PURE__ */ jsx(CalendarBlank, { className: "size-4" }),
149
+ /* @__PURE__ */ jsx("span", {
150
+ className: "flex-1 text-left",
151
+ children: value ? format(value, dateFormat) : placeholder
152
+ }),
153
+ value && !disabled && /* @__PURE__ */ jsx(X, {
154
+ className: "size-4 opacity-50 hover:opacity-100",
155
+ onClick: handleClear
156
+ })
157
+ ]
158
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
159
+ className: "w-auto p-0",
160
+ align: "start",
161
+ children: [/* @__PURE__ */ jsx(DayPicker, {
162
+ mode: "single",
163
+ selected: value ?? void 0,
164
+ onSelect: handleDateSelect,
165
+ disabled: (date) => {
166
+ if (minDate && date < minDate) return true;
167
+ if (maxDate && date > maxDate) return true;
168
+ return false;
169
+ },
170
+ className: "p-3",
171
+ classNames: {
172
+ months: "flex flex-col sm:flex-row gap-2",
173
+ month: "flex flex-col gap-4",
174
+ month_caption: "flex justify-center pt-1 relative items-center w-full",
175
+ caption_label: "text-sm font-medium",
176
+ nav: "flex items-center gap-1",
177
+ button_previous: "absolute left-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
178
+ button_next: "absolute right-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
179
+ month_grid: "w-full border-collapse",
180
+ weekdays: "flex",
181
+ weekday: "text-muted-foreground w-8 font-normal text-[0.8rem]",
182
+ week: "flex w-full mt-2",
183
+ day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50",
184
+ day_button: cn("size-8 p-0 font-normal", "hover:bg-accent hover:text-accent-foreground", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"),
185
+ selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
186
+ today: "bg-accent text-accent-foreground",
187
+ outside: "text-muted-foreground opacity-50",
188
+ disabled: "text-muted-foreground opacity-50",
189
+ hidden: "invisible"
190
+ }
191
+ }), /* @__PURE__ */ jsx("div", {
192
+ className: "border-t border-border p-3",
193
+ children: /* @__PURE__ */ jsx("input", {
194
+ type: "time",
195
+ step: precision === "second" ? 1 : 60,
196
+ value: timeValue,
197
+ onChange: handleTimeChange,
198
+ className: cn("flex h-9 w-full border border-input bg-background px-3 py-2 text-sm", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2")
199
+ })
200
+ })]
201
+ })]
202
+ });
203
+ }
204
+ /**
205
+ * Date Range Input Primitive
206
+ *
207
+ * A date range picker for selecting start and end dates.
208
+ *
209
+ * @example
210
+ * ```tsx
211
+ * <DateRangeInput
212
+ * value={{ start: startDate, end: endDate }}
213
+ * onChange={setDateRange}
214
+ * />
215
+ * ```
216
+ */
217
+ function DateRangeInput({ value, onChange, minDate, maxDate, placeholder = "Select date range", disabled, className, id, "aria-invalid": ariaInvalid }) {
218
+ const [open, setOpen] = useState(false);
219
+ const handleSelect = (range) => {
220
+ onChange({
221
+ start: range?.from ?? null,
222
+ end: range?.to ?? null
223
+ });
224
+ };
225
+ const handleClear = (e) => {
226
+ e.stopPropagation();
227
+ onChange({
228
+ start: null,
229
+ end: null
230
+ });
231
+ };
232
+ const displayValue = () => {
233
+ if (!value.start && !value.end) return placeholder;
234
+ if (value.start && value.end) return `${format(value.start, "PP")} - ${format(value.end, "PP")}`;
235
+ if (value.start) return `${format(value.start, "PP")} - ...`;
236
+ return placeholder;
237
+ };
238
+ return /* @__PURE__ */ jsxs(Popover, {
239
+ open,
240
+ onOpenChange: setOpen,
241
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
242
+ id,
243
+ disabled,
244
+ "aria-invalid": ariaInvalid,
245
+ className: cn("flex h-9 w-full items-center justify-start gap-2 border border-input bg-background px-3 py-2 text-sm", "hover:bg-accent hover:text-accent-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", !value.start && !value.end && "text-muted-foreground", className),
246
+ children: [
247
+ /* @__PURE__ */ jsx(CalendarBlank, { className: "size-4" }),
248
+ /* @__PURE__ */ jsx("span", {
249
+ className: "flex-1 text-left",
250
+ children: displayValue()
251
+ }),
252
+ (value.start || value.end) && !disabled && /* @__PURE__ */ jsx(X, {
253
+ className: "size-4 opacity-50 hover:opacity-100",
254
+ onClick: handleClear
255
+ })
256
+ ]
257
+ }), /* @__PURE__ */ jsx(PopoverContent, {
258
+ className: "w-auto p-0",
259
+ align: "start",
260
+ children: /* @__PURE__ */ jsx(DayPicker, {
261
+ mode: "range",
262
+ selected: value.start || value.end ? {
263
+ from: value.start ?? void 0,
264
+ to: value.end ?? void 0
265
+ } : void 0,
266
+ onSelect: handleSelect,
267
+ numberOfMonths: 2,
268
+ disabled: (date) => {
269
+ if (minDate && date < minDate) return true;
270
+ if (maxDate && date > maxDate) return true;
271
+ return false;
272
+ },
273
+ className: "p-3",
274
+ classNames: {
275
+ months: "flex flex-col sm:flex-row gap-2",
276
+ month: "flex flex-col gap-4",
277
+ month_caption: "flex justify-center pt-1 relative items-center w-full",
278
+ caption_label: "text-sm font-medium",
279
+ nav: "flex items-center gap-1",
280
+ button_previous: "absolute left-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
281
+ button_next: "absolute right-1 size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
282
+ month_grid: "w-full border-collapse",
283
+ weekdays: "flex",
284
+ weekday: "text-muted-foreground w-8 font-normal text-[0.8rem]",
285
+ week: "flex w-full mt-2",
286
+ day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50",
287
+ day_button: cn("size-8 p-0 font-normal", "hover:bg-accent hover:text-accent-foreground", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"),
288
+ selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
289
+ range_start: "rounded-l-md",
290
+ range_end: "rounded-r-md",
291
+ range_middle: "bg-accent",
292
+ today: "bg-accent text-accent-foreground",
293
+ outside: "text-muted-foreground opacity-50",
294
+ disabled: "text-muted-foreground opacity-50",
295
+ hidden: "invisible"
296
+ }
297
+ })
298
+ })]
299
+ });
300
+ }
301
+
302
+ //#endregion
303
+ export { DateInput, DateRangeInput, DateTimeInput };
@@ -0,0 +1,12 @@
1
+ import { TextInput } from "./text-input";
2
+ import { NumberInput } from "./number-input";
3
+ import { TextareaInput } from "./textarea-input";
4
+ import { SelectInput } from "./select-input";
5
+ import { ToggleInput } from "./toggle-input";
6
+ import { CheckboxGroup, CheckboxInput, RadioGroup } from "./checkbox-input";
7
+ import { DateInput, DateRangeInput, DateTimeInput } from "./date-input";
8
+ import { TagInput } from "./tag-input";
9
+
10
+ export * from "./types"
11
+
12
+ export { CheckboxGroup, CheckboxInput, DateInput, DateRangeInput, DateTimeInput, NumberInput, RadioGroup, SelectInput, TagInput, TextInput, TextareaInput, ToggleInput };