azamat-ui-kit-cli 0.2.2

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 (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,78 @@
1
+ import * as React from "react"
2
+ import { XIcon } from "lucide-react"
3
+
4
+ import { InputDecorator } from "@/components/inputs/input-decorator"
5
+ import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
6
+
7
+ export type ClearableInputProps = Omit<
8
+ React.ComponentProps<typeof InputDecorator>,
9
+ "value" | "onChange"
10
+ > & {
11
+ value?: string | number | null
12
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
13
+ onValueChange?: (value: string) => void
14
+ onClear?: () => void
15
+ clearable?: boolean
16
+ clearLabel?: string
17
+ leadingIcon?: React.ReactNode
18
+ trailing?: React.ReactNode
19
+ wrapperClassName?: string
20
+ inputClassName?: string
21
+ }
22
+
23
+ const ClearableInput = React.forwardRef<HTMLInputElement, ClearableInputProps>(
24
+ (
25
+ {
26
+ value,
27
+ onChange,
28
+ onValueChange,
29
+ onClear,
30
+ clearable = true,
31
+ clearLabel = "Clear",
32
+ leadingIcon,
33
+ trailing,
34
+ disabled,
35
+ ...props
36
+ },
37
+ ref
38
+ ) => {
39
+ const stringValue = getInputValue(value)
40
+ const canClear = clearable && stringValue.length > 0 && !disabled
41
+ const handleChange = createInputChangeHandler({ onChange, onValueChange })
42
+
43
+ const handleClear = () => {
44
+ onValueChange?.("")
45
+ onClear?.()
46
+ }
47
+
48
+ return (
49
+ <InputDecorator
50
+ data-slot="clearable-input"
51
+ ref={ref}
52
+ value={stringValue}
53
+ disabled={disabled}
54
+ onChange={handleChange}
55
+ leading={leadingIcon}
56
+ trailing={
57
+ <>
58
+ {trailing}
59
+ {canClear && (
60
+ <button
61
+ type="button"
62
+ aria-label={clearLabel}
63
+ className="rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
64
+ onClick={handleClear}
65
+ >
66
+ <XIcon className="size-4" />
67
+ </button>
68
+ )}
69
+ </>
70
+ }
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+ )
76
+ ClearableInput.displayName = "ClearableInput"
77
+
78
+ export { ClearableInput }
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+
3
+ import { Input } from "@/components/ui/input"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type ColorInputProps = Omit<React.ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange"> & {
7
+ value?: string
8
+ defaultValue?: string
9
+ onValueChange?: (value: string) => void
10
+ label?: React.ReactNode
11
+ description?: React.ReactNode
12
+ swatchClassName?: string
13
+ }
14
+
15
+ function ColorInput({ value, defaultValue = "#000000", onValueChange, label, description, swatchClassName, className, disabled, ...props }: ColorInputProps) {
16
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
17
+ const currentValue = value ?? internalValue
18
+
19
+ const updateValue = (nextValue: string) => {
20
+ if (value === undefined) setInternalValue(nextValue)
21
+ onValueChange?.(nextValue)
22
+ }
23
+
24
+ return (
25
+ <div data-slot="color-input" className={cn("grid gap-2", className)}>
26
+ {(label || description) && (
27
+ <div className="grid gap-0.5">
28
+ {label && <label className="text-sm font-medium text-foreground">{label}</label>}
29
+ {description && <div className="text-xs text-muted-foreground">{description}</div>}
30
+ </div>
31
+ )}
32
+ <div className="flex items-center gap-2">
33
+ <input
34
+ type="color"
35
+ value={currentValue}
36
+ disabled={disabled}
37
+ className={cn("size-9 cursor-pointer rounded-md border bg-background p-1 disabled:cursor-not-allowed disabled:opacity-50", swatchClassName)}
38
+ onChange={(event) => updateValue(event.currentTarget.value)}
39
+ {...props}
40
+ />
41
+ <Input value={currentValue} disabled={disabled} onChange={(event) => updateValue(event.currentTarget.value)} className="font-mono" />
42
+ </div>
43
+ </div>
44
+ )
45
+ }
46
+
47
+ export { ColorInput }
@@ -0,0 +1,89 @@
1
+ import * as React from "react"
2
+ import { CheckIcon, ChevronDownIcon, SearchIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { Input } from "@/components/ui/input"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ export type ComboboxOption<TValue extends string = string> = {
9
+ value: TValue
10
+ label: React.ReactNode
11
+ searchValue?: string
12
+ description?: React.ReactNode
13
+ disabled?: boolean
14
+ hidden?: boolean
15
+ }
16
+
17
+ export type ComboboxProps<TValue extends string = string> = React.ComponentProps<"div"> & {
18
+ value?: TValue
19
+ options: ComboboxOption<TValue>[]
20
+ onValueChange?: (value: TValue | undefined, option?: ComboboxOption<TValue>) => void
21
+ placeholder?: React.ReactNode
22
+ searchPlaceholder?: string
23
+ emptyLabel?: React.ReactNode
24
+ disabled?: boolean
25
+ }
26
+
27
+ function Combobox<TValue extends string = string>({
28
+ value,
29
+ options,
30
+ onValueChange,
31
+ placeholder = "Select option",
32
+ searchPlaceholder = "Search...",
33
+ emptyLabel = "No option found",
34
+ disabled = false,
35
+ className,
36
+ ...props
37
+ }: ComboboxProps<TValue>) {
38
+ const [open, setOpen] = React.useState(false)
39
+ const [search, setSearch] = React.useState("")
40
+ const selectedOption = options.find((option) => option.value === value)
41
+ const normalizedSearch = search.trim().toLowerCase()
42
+ const filteredOptions = options.filter((option) => {
43
+ if (option.hidden) return false
44
+ if (!normalizedSearch) return true
45
+ return String(option.searchValue ?? option.label).toLowerCase().includes(normalizedSearch)
46
+ })
47
+
48
+ return (
49
+ <div data-slot="combobox" className={cn("relative grid gap-2", className)} {...props}>
50
+ <Button type="button" variant="outline" disabled={disabled} className="w-full justify-between" onClick={() => setOpen((value) => !value)}>
51
+ <span className={cn("truncate", !selectedOption && "text-muted-foreground")}>{selectedOption?.label ?? placeholder}</span>
52
+ <ChevronDownIcon className="size-4 opacity-60" />
53
+ </Button>
54
+ {open && (
55
+ <div className="absolute top-full z-30 mt-2 w-full rounded-md border bg-popover p-2 text-popover-foreground shadow-md">
56
+ <div className="relative mb-2">
57
+ <SearchIcon className="pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
58
+ <Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder={searchPlaceholder} className="h-9 pl-8" />
59
+ </div>
60
+ <div className="max-h-64 overflow-y-auto">
61
+ {filteredOptions.length ? filteredOptions.map((option) => {
62
+ const active = option.value === value
63
+ return (
64
+ <button
65
+ key={option.value}
66
+ type="button"
67
+ disabled={option.disabled}
68
+ className={cn("flex w-full items-start gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-muted disabled:pointer-events-none disabled:opacity-50", active && "bg-muted")}
69
+ onClick={() => {
70
+ onValueChange?.(option.value, option)
71
+ setOpen(false)
72
+ }}
73
+ >
74
+ <span className="mt-0.5 flex size-4 shrink-0 items-center justify-center">{active && <CheckIcon className="size-4" />}</span>
75
+ <span className="min-w-0">
76
+ <span className="block truncate font-medium">{option.label}</span>
77
+ {option.description && <span className="block text-xs text-muted-foreground">{option.description}</span>}
78
+ </span>
79
+ </button>
80
+ )
81
+ }) : <div className="px-2 py-6 text-center text-sm text-muted-foreground">{emptyLabel}</div>}
82
+ </div>
83
+ </div>
84
+ )}
85
+ </div>
86
+ )
87
+ }
88
+
89
+ export { Combobox }
@@ -0,0 +1,32 @@
1
+ import * as React from "react"
2
+
3
+ import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
4
+ import { Input } from "@/components/ui/input"
5
+
6
+ export type DateInputProps = Omit<
7
+ React.ComponentProps<typeof Input>,
8
+ "type" | "value" | "onChange"
9
+ > & {
10
+ value?: string | null
11
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
12
+ onValueChange?: (value: string) => void
13
+ }
14
+
15
+ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
16
+ ({ value, onChange, onValueChange, ...props }, ref) => {
17
+ const handleChange = createInputChangeHandler({ onChange, onValueChange })
18
+
19
+ return (
20
+ <Input
21
+ ref={ref}
22
+ type="date"
23
+ value={getInputValue(value)}
24
+ onChange={handleChange}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+ )
30
+ DateInput.displayName = "DateInput"
31
+
32
+ export { DateInput }
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+
3
+ import { DateInput, type DateInputProps } from "@/components/inputs/date-input"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type DateRangeValue = {
7
+ from?: string
8
+ to?: string
9
+ }
10
+
11
+ export type DateRangeInputProps = Omit<React.ComponentProps<"div">, "onChange"> & {
12
+ value?: DateRangeValue
13
+ onValueChange?: (value: DateRangeValue) => void
14
+ fromPlaceholder?: string
15
+ toPlaceholder?: string
16
+ fromInputProps?: Omit<DateInputProps, "value" | "onValueChange">
17
+ toInputProps?: Omit<DateInputProps, "value" | "onValueChange">
18
+ separator?: React.ReactNode
19
+ inputClassName?: string
20
+ }
21
+
22
+ function DateRangeInput({
23
+ className,
24
+ value,
25
+ onValueChange,
26
+ fromPlaceholder = "From",
27
+ toPlaceholder = "To",
28
+ fromInputProps,
29
+ toInputProps,
30
+ separator = "—",
31
+ inputClassName,
32
+ ...props
33
+ }: DateRangeInputProps) {
34
+ const nextValue = (patch: DateRangeValue) => {
35
+ onValueChange?.({
36
+ from: value?.from ?? "",
37
+ to: value?.to ?? "",
38
+ ...patch,
39
+ })
40
+ }
41
+
42
+ return (
43
+ <div
44
+ data-slot="date-range-input"
45
+ className={cn("flex w-full flex-col gap-2 sm:flex-row sm:items-center", className)}
46
+ {...props}
47
+ >
48
+ <DateInput
49
+ value={value?.from ?? ""}
50
+ placeholder={fromPlaceholder}
51
+ className={inputClassName}
52
+ onValueChange={(from) => nextValue({ from })}
53
+ {...fromInputProps}
54
+ />
55
+ <span className="hidden text-sm text-muted-foreground sm:inline-flex">{separator}</span>
56
+ <DateInput
57
+ value={value?.to ?? ""}
58
+ placeholder={toPlaceholder}
59
+ className={inputClassName}
60
+ onValueChange={(to) => nextValue({ to })}
61
+ {...toInputProps}
62
+ />
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export { DateRangeInput }
@@ -0,0 +1,19 @@
1
+ export * from './simple-select'
2
+ export * from './async-select'
3
+ export * from './clearable-input'
4
+ export * from './search-input'
5
+ export * from './password-input'
6
+ export * from './number-input'
7
+ export * from './date-input'
8
+ export * from './date-range-input'
9
+ export * from './money-input'
10
+ export * from './quantity-input'
11
+ export * from './masked-input'
12
+ export * from './phone-input'
13
+ export * from './tag-input'
14
+ export * from './combobox'
15
+ export * from './rating'
16
+ export * from './slider'
17
+ export * from './otp-input'
18
+ export * from './color-input'
19
+ export * from './quantity-stepper'
@@ -0,0 +1,37 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ type InputChromeProps = React.ComponentProps<"div"> & {
6
+ start?: React.ReactNode
7
+ end?: React.ReactNode
8
+ startClassName?: string
9
+ endClassName?: string
10
+ }
11
+
12
+ function InputChrome({
13
+ start,
14
+ end,
15
+ startClassName,
16
+ endClassName,
17
+ className,
18
+ children,
19
+ ...props
20
+ }: InputChromeProps) {
21
+ return (
22
+ <div
23
+ data-slot="input-chrome"
24
+ className={cn(
25
+ "flex h-8 w-full min-w-0 items-center rounded-lg border border-input bg-transparent transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50",
26
+ className
27
+ )}
28
+ {...props}
29
+ >
30
+ {start && <span className={cn("shrink-0", startClassName)}>{start}</span>}
31
+ {children}
32
+ {end && <span className={cn("shrink-0", endClassName)}>{end}</span>}
33
+ </div>
34
+ )
35
+ }
36
+
37
+ export { InputChrome }
@@ -0,0 +1,64 @@
1
+ import * as React from "react"
2
+
3
+ import { Input } from "@/components/ui/input"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ type InputDecoratorProps = Omit<React.ComponentProps<typeof Input>, "value"> & {
7
+ value?: string | number | null
8
+ leading?: React.ReactNode
9
+ trailing?: React.ReactNode
10
+ wrapperClassName?: string
11
+ inputClassName?: string
12
+ }
13
+
14
+ const InputDecorator = React.forwardRef<HTMLInputElement, InputDecoratorProps>(
15
+ (
16
+ {
17
+ className,
18
+ value,
19
+ leading,
20
+ trailing,
21
+ wrapperClassName,
22
+ inputClassName,
23
+ ...props
24
+ },
25
+ ref
26
+ ) => {
27
+ const hasLeading = Boolean(leading)
28
+ const hasTrailing = Boolean(trailing)
29
+
30
+ return (
31
+ <div
32
+ data-slot="input-decorator"
33
+ className={cn("relative flex w-full items-center", wrapperClassName)}
34
+ >
35
+ {hasLeading && (
36
+ <span className="pointer-events-none absolute left-2.5 flex text-muted-foreground [&_svg]:size-4">
37
+ {leading}
38
+ </span>
39
+ )}
40
+
41
+ <Input
42
+ ref={ref}
43
+ value={value == null ? "" : String(value)}
44
+ className={cn(
45
+ hasLeading && "pl-8",
46
+ hasTrailing && "pr-9",
47
+ inputClassName,
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+
53
+ {hasTrailing && (
54
+ <span className="absolute right-2 flex items-center gap-1">
55
+ {trailing}
56
+ </span>
57
+ )}
58
+ </div>
59
+ )
60
+ }
61
+ )
62
+ InputDecorator.displayName = "InputDecorator"
63
+
64
+ export { InputDecorator }
@@ -0,0 +1,42 @@
1
+ import * as React from "react"
2
+
3
+ type InputValue = string | number | null | undefined
4
+
5
+ type CreateInputChangeHandlerOptions<TValue> = {
6
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
7
+ onValueChange?: (value: TValue) => void
8
+ mapValue?: (rawValue: string, event: React.ChangeEvent<HTMLInputElement>) => TValue
9
+ }
10
+
11
+ function getInputValue(value: InputValue) {
12
+ return value == null ? "" : String(value)
13
+ }
14
+
15
+ function setInputElementValue(
16
+ event: React.ChangeEvent<HTMLInputElement>,
17
+ value: string
18
+ ) {
19
+ event.target.value = value
20
+ event.currentTarget.value = value
21
+ }
22
+
23
+ function createInputChangeHandler<TValue = string>({
24
+ onChange,
25
+ onValueChange,
26
+ mapValue,
27
+ }: CreateInputChangeHandlerOptions<TValue>) {
28
+ return (event: React.ChangeEvent<HTMLInputElement>) => {
29
+ onChange?.(event)
30
+
31
+ if (!onValueChange) return
32
+
33
+ const rawValue = event.target.value
34
+ const nextValue = mapValue
35
+ ? mapValue(rawValue, event)
36
+ : (rawValue as TValue)
37
+
38
+ onValueChange(nextValue)
39
+ }
40
+ }
41
+
42
+ export { createInputChangeHandler, getInputValue, setInputElementValue }
@@ -0,0 +1,51 @@
1
+ import * as React from "react"
2
+
3
+ import { getInputValue, setInputElementValue } from "@/components/inputs/input-value"
4
+ import { Input } from "@/components/ui/input"
5
+
6
+ export type MaskedInputProps = Omit<
7
+ React.ComponentProps<typeof Input>,
8
+ "value" | "onChange"
9
+ > & {
10
+ value?: string
11
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
12
+ onValueChange?: (value: string, rawValue: string) => void
13
+ mask?: (rawValue: string) => string
14
+ unmask?: (maskedValue: string) => string
15
+ }
16
+
17
+ function defaultMask(value: string) {
18
+ return value
19
+ }
20
+
21
+ function defaultUnmask(value: string) {
22
+ return value
23
+ }
24
+
25
+ const MaskedInput = React.forwardRef<HTMLInputElement, MaskedInputProps>(
26
+ (
27
+ {
28
+ value,
29
+ onChange,
30
+ onValueChange,
31
+ mask = defaultMask,
32
+ unmask = defaultUnmask,
33
+ ...props
34
+ },
35
+ ref
36
+ ) => {
37
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
38
+ const rawValue = unmask(event.target.value)
39
+ const maskedValue = mask(rawValue)
40
+
41
+ setInputElementValue(event, maskedValue)
42
+ onChange?.(event)
43
+ onValueChange?.(maskedValue, rawValue)
44
+ }
45
+
46
+ return <Input ref={ref} value={getInputValue(value)} onChange={handleChange} {...props} />
47
+ }
48
+ )
49
+ MaskedInput.displayName = "MaskedInput"
50
+
51
+ export { MaskedInput }
@@ -0,0 +1,73 @@
1
+ import * as React from "react"
2
+
3
+ import { InputChrome } from "@/components/inputs/input-chrome"
4
+ import { getInputValue } from "@/components/inputs/input-value"
5
+ import { parseMoneyLikeInput } from "@/components/inputs/numeric-value"
6
+ import { Input } from "@/components/ui/input"
7
+ import { cn } from "@/lib/utils"
8
+
9
+ export type MoneyInputProps = Omit<
10
+ React.ComponentProps<"input">,
11
+ "value" | "onChange" | "type"
12
+ > & {
13
+ value?: number | string | null
14
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
15
+ onValueChange?: (value: number | null, rawValue: string) => void
16
+ prefix?: React.ReactNode
17
+ suffix?: React.ReactNode
18
+ wrapperClassName?: string
19
+ inputClassName?: string
20
+ }
21
+
22
+ function parseMoneyInput(value: string) {
23
+ return parseMoneyLikeInput(value)
24
+ }
25
+
26
+ function MoneyInput({
27
+ value,
28
+ onChange,
29
+ onValueChange,
30
+ prefix,
31
+ suffix,
32
+ wrapperClassName,
33
+ inputClassName,
34
+ className,
35
+ inputMode = "decimal",
36
+ ...props
37
+ }: MoneyInputProps) {
38
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
39
+ onChange?.(event)
40
+ onValueChange?.(parseMoneyInput(event.target.value), event.target.value)
41
+ }
42
+
43
+ return (
44
+ <InputChrome
45
+ data-slot="money-input"
46
+ className={cn("text-sm", wrapperClassName)}
47
+ start={
48
+ prefix ? (
49
+ <span className="pl-2.5 text-xs font-medium text-muted-foreground">{prefix}</span>
50
+ ) : null
51
+ }
52
+ end={
53
+ suffix ? (
54
+ <span className="pr-2.5 text-xs font-medium text-muted-foreground">{suffix}</span>
55
+ ) : null
56
+ }
57
+ >
58
+ <Input
59
+ value={getInputValue(value)}
60
+ onChange={handleChange}
61
+ inputMode={inputMode}
62
+ className={cn(
63
+ "h-full border-0 bg-transparent px-2.5 shadow-none focus-visible:border-0 focus-visible:ring-0",
64
+ inputClassName,
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ </InputChrome>
70
+ )
71
+ }
72
+
73
+ export { MoneyInput, parseMoneyInput }
@@ -0,0 +1,87 @@
1
+ import * as React from "react"
2
+
3
+ import { getInputValue } from "@/components/inputs/input-value"
4
+ import { clampNumericValue, parseDecimalInput } from "@/components/inputs/numeric-value"
5
+ import { Input } from "@/components/ui/input"
6
+
7
+ export type NumberInputProps = Omit<
8
+ React.ComponentProps<typeof Input>,
9
+ "type" | "value" | "onChange" | "min" | "max" | "step"
10
+ > & {
11
+ value?: number | string | null
12
+ onChange?: React.ChangeEventHandler<HTMLInputElement>
13
+ onValueChange?: (value: string) => void
14
+ onNumberChange?: (value: number | null) => void
15
+ min?: number
16
+ max?: number
17
+ step?: number
18
+ allowEmpty?: boolean
19
+ }
20
+
21
+ function parseNumberInput(value: string) {
22
+ return parseDecimalInput(value)
23
+ }
24
+
25
+ function clampNumber(value: number, min?: number, max?: number) {
26
+ return clampNumericValue(value, min, max)
27
+ }
28
+
29
+ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
30
+ (
31
+ {
32
+ value,
33
+ onChange,
34
+ onValueChange,
35
+ onNumberChange,
36
+ min,
37
+ max,
38
+ step,
39
+ allowEmpty = true,
40
+ inputMode = "decimal",
41
+ onBlur,
42
+ ...props
43
+ },
44
+ ref
45
+ ) => {
46
+ const stringValue = getInputValue(value)
47
+
48
+ const emitValue = (rawValue: string) => {
49
+ const parsed = parseNumberInput(rawValue)
50
+ const clamped = parsed == null ? null : clampNumber(parsed, min, max)
51
+
52
+ onValueChange?.(rawValue)
53
+ onNumberChange?.(clamped)
54
+ }
55
+
56
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
57
+ onChange?.(event)
58
+ emitValue(event.target.value)
59
+ }
60
+
61
+ const handleBlur: React.FocusEventHandler<HTMLInputElement> = (event) => {
62
+ if (!allowEmpty && !event.target.value.trim()) {
63
+ emitValue(String(min ?? 0))
64
+ }
65
+
66
+ onBlur?.(event)
67
+ }
68
+
69
+ return (
70
+ <Input
71
+ ref={ref}
72
+ type="text"
73
+ value={stringValue}
74
+ inputMode={inputMode}
75
+ min={min}
76
+ max={max}
77
+ step={step}
78
+ onChange={handleChange}
79
+ onBlur={handleBlur}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+ )
85
+ NumberInput.displayName = "NumberInput"
86
+
87
+ export { NumberInput, parseNumberInput, clampNumber }