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,149 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type SliderProps = Omit<React.ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange"> & {
6
+ value?: number
7
+ defaultValue?: number
8
+ onValueChange?: (value: number) => void
9
+ min?: number
10
+ max?: number
11
+ step?: number
12
+ label?: React.ReactNode
13
+ description?: React.ReactNode
14
+ showValue?: boolean
15
+ formatValue?: (value: number) => React.ReactNode
16
+ }
17
+
18
+ function Slider({
19
+ value,
20
+ defaultValue = 0,
21
+ onValueChange,
22
+ min = 0,
23
+ max = 100,
24
+ step = 1,
25
+ label,
26
+ description,
27
+ showValue = false,
28
+ formatValue,
29
+ className,
30
+ disabled,
31
+ ...props
32
+ }: SliderProps) {
33
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
34
+ const currentValue = value ?? internalValue
35
+
36
+ const updateValue = (nextValue: number) => {
37
+ if (value === undefined) setInternalValue(nextValue)
38
+ onValueChange?.(nextValue)
39
+ }
40
+
41
+ return (
42
+ <div data-slot="slider" className={cn("grid gap-2", className)}>
43
+ {(label || description || showValue) && (
44
+ <div className="flex items-start justify-between gap-3">
45
+ <div className="grid gap-0.5">
46
+ {label && <label className="text-sm font-medium text-foreground">{label}</label>}
47
+ {description && <div className="text-xs text-muted-foreground">{description}</div>}
48
+ </div>
49
+ {showValue && (
50
+ <div className="text-sm font-medium text-muted-foreground">
51
+ {formatValue?.(currentValue) ?? currentValue}
52
+ </div>
53
+ )}
54
+ </div>
55
+ )}
56
+ <input
57
+ data-slot="slider-input"
58
+ type="range"
59
+ value={currentValue}
60
+ min={min}
61
+ max={max}
62
+ step={step}
63
+ disabled={disabled}
64
+ className="h-2 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary disabled:cursor-not-allowed disabled:opacity-50"
65
+ onChange={(event) => updateValue(event.currentTarget.valueAsNumber)}
66
+ {...props}
67
+ />
68
+ </div>
69
+ )
70
+ }
71
+
72
+ export type RangeSliderValue = [number, number]
73
+
74
+ export type RangeSliderProps = Omit<SliderProps, "value" | "defaultValue" | "onValueChange" | "showValue" | "formatValue"> & {
75
+ value?: RangeSliderValue
76
+ defaultValue?: RangeSliderValue
77
+ onValueChange?: (value: RangeSliderValue) => void
78
+ showValue?: boolean
79
+ formatValue?: (value: RangeSliderValue) => React.ReactNode
80
+ }
81
+
82
+ function RangeSlider({
83
+ value,
84
+ defaultValue = [0, 100],
85
+ onValueChange,
86
+ min = 0,
87
+ max = 100,
88
+ step = 1,
89
+ label,
90
+ description,
91
+ showValue = false,
92
+ formatValue,
93
+ className,
94
+ disabled,
95
+ }: RangeSliderProps) {
96
+ const [internalValue, setInternalValue] = React.useState<RangeSliderValue>(defaultValue)
97
+ const currentValue = value ?? internalValue
98
+
99
+ const updateValue = (index: 0 | 1, nextPart: number) => {
100
+ const nextValue: RangeSliderValue = index === 0
101
+ ? [Math.min(nextPart, currentValue[1]), currentValue[1]]
102
+ : [currentValue[0], Math.max(nextPart, currentValue[0])]
103
+
104
+ if (value === undefined) setInternalValue(nextValue)
105
+ onValueChange?.(nextValue)
106
+ }
107
+
108
+ return (
109
+ <div data-slot="range-slider" className={cn("grid gap-2", className)}>
110
+ {(label || description || showValue) && (
111
+ <div className="flex items-start justify-between gap-3">
112
+ <div className="grid gap-0.5">
113
+ {label && <label className="text-sm font-medium text-foreground">{label}</label>}
114
+ {description && <div className="text-xs text-muted-foreground">{description}</div>}
115
+ </div>
116
+ {showValue && (
117
+ <div className="text-sm font-medium text-muted-foreground">
118
+ {formatValue?.(currentValue) ?? `${currentValue[0]} - ${currentValue[1]}`}
119
+ </div>
120
+ )}
121
+ </div>
122
+ )}
123
+ <div className="grid gap-2">
124
+ <input
125
+ type="range"
126
+ value={currentValue[0]}
127
+ min={min}
128
+ max={max}
129
+ step={step}
130
+ disabled={disabled}
131
+ className="h-2 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary disabled:cursor-not-allowed disabled:opacity-50"
132
+ onChange={(event) => updateValue(0, event.currentTarget.valueAsNumber)}
133
+ />
134
+ <input
135
+ type="range"
136
+ value={currentValue[1]}
137
+ min={min}
138
+ max={max}
139
+ step={step}
140
+ disabled={disabled}
141
+ className="h-2 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary disabled:cursor-not-allowed disabled:opacity-50"
142
+ onChange={(event) => updateValue(1, event.currentTarget.valueAsNumber)}
143
+ />
144
+ </div>
145
+ </div>
146
+ )
147
+ }
148
+
149
+ export { RangeSlider, Slider }
@@ -0,0 +1,104 @@
1
+ import * as React from "react"
2
+ import { XIcon } from "lucide-react"
3
+
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { Input } from "@/components/ui/input"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ export type TagInputProps = Omit<React.ComponentProps<"div">, "onChange"> & {
9
+ value?: string[]
10
+ defaultValue?: string[]
11
+ onValueChange?: (value: string[]) => void
12
+ placeholder?: string
13
+ disabled?: boolean
14
+ readOnly?: boolean
15
+ maxTags?: number
16
+ allowDuplicates?: boolean
17
+ separators?: string[]
18
+ normalizeTag?: (tag: string) => string
19
+ }
20
+
21
+ function TagInput({
22
+ value,
23
+ defaultValue = [],
24
+ onValueChange,
25
+ placeholder = "Add tag...",
26
+ disabled = false,
27
+ readOnly = false,
28
+ maxTags,
29
+ allowDuplicates = false,
30
+ separators = [",", "Enter"],
31
+ normalizeTag = (tag) => tag.trim(),
32
+ className,
33
+ ...props
34
+ }: TagInputProps) {
35
+ const controlled = value !== undefined
36
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
37
+ const [inputValue, setInputValue] = React.useState("")
38
+ const tags = controlled ? value : internalValue
39
+
40
+ const setTags = React.useCallback(
41
+ (nextTags: string[]) => {
42
+ if (!controlled) setInternalValue(nextTags)
43
+ onValueChange?.(nextTags)
44
+ },
45
+ [controlled, onValueChange]
46
+ )
47
+
48
+ const addTag = React.useCallback(
49
+ (rawTag: string) => {
50
+ const tag = normalizeTag(rawTag)
51
+ if (!tag) return
52
+ if (maxTags !== undefined && tags.length >= maxTags) return
53
+ if (!allowDuplicates && tags.includes(tag)) return
54
+ setTags([...tags, tag])
55
+ setInputValue("")
56
+ },
57
+ [allowDuplicates, maxTags, normalizeTag, setTags, tags]
58
+ )
59
+
60
+ const removeTag = React.useCallback(
61
+ (tagToRemove: string) => {
62
+ setTags(tags.filter((tag) => tag !== tagToRemove))
63
+ },
64
+ [setTags, tags]
65
+ )
66
+
67
+ return (
68
+ <div data-slot="tag-input" className={cn("grid gap-2", className)} {...props}>
69
+ <div className={cn("flex min-h-10 flex-wrap items-center gap-2 rounded-md border bg-background px-2 py-2", disabled && "opacity-60", readOnly && "bg-muted/30")}>
70
+ {tags.map((tag) => (
71
+ <Badge key={tag} variant="secondary" className="gap-1 pr-1">
72
+ {tag}
73
+ {!readOnly && !disabled && (
74
+ <button type="button" className="rounded-full p-0.5 hover:bg-muted" onClick={() => removeTag(tag)}>
75
+ <XIcon className="size-3" />
76
+ </button>
77
+ )}
78
+ </Badge>
79
+ ))}
80
+ {!readOnly && (
81
+ <Input
82
+ value={inputValue}
83
+ disabled={disabled || (maxTags !== undefined && tags.length >= maxTags)}
84
+ placeholder={placeholder}
85
+ className="h-7 min-w-28 flex-1 border-0 bg-transparent px-1 shadow-none focus-visible:ring-0"
86
+ onChange={(event) => setInputValue(event.target.value)}
87
+ onBlur={() => addTag(inputValue)}
88
+ onKeyDown={(event) => {
89
+ if (separators.includes(event.key)) {
90
+ event.preventDefault()
91
+ addTag(inputValue)
92
+ }
93
+ if (event.key === "Backspace" && !inputValue && tags.length) {
94
+ removeTag(tags[tags.length - 1])
95
+ }
96
+ }}
97
+ />
98
+ )}
99
+ </div>
100
+ </div>
101
+ )
102
+ }
103
+
104
+ export { TagInput }
@@ -0,0 +1,46 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type AppHeaderProps = React.ComponentProps<"header"> & {
6
+ left?: React.ReactNode
7
+ center?: React.ReactNode
8
+ right?: React.ReactNode
9
+ sticky?: boolean
10
+ heightClassName?: string
11
+ }
12
+
13
+ function AppHeader({
14
+ className,
15
+ left,
16
+ center,
17
+ right,
18
+ sticky = true,
19
+ heightClassName = "h-14",
20
+ children,
21
+ ...props
22
+ }: AppHeaderProps) {
23
+ return (
24
+ <header
25
+ data-slot="app-header"
26
+ data-sticky={sticky || undefined}
27
+ className={cn(
28
+ "z-30 flex shrink-0 items-center gap-3 border-b bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/80",
29
+ sticky && "sticky top-0",
30
+ heightClassName,
31
+ className
32
+ )}
33
+ {...props}
34
+ >
35
+ {children ?? (
36
+ <>
37
+ {left && <div className="flex min-w-0 flex-1 items-center gap-2">{left}</div>}
38
+ {center && <div className="hidden min-w-0 flex-1 items-center justify-center md:flex">{center}</div>}
39
+ {right && <div className="ml-auto flex shrink-0 items-center gap-2">{right}</div>}
40
+ </>
41
+ )}
42
+ </header>
43
+ )
44
+ }
45
+
46
+ export { AppHeader }
@@ -0,0 +1,243 @@
1
+ import * as React from "react"
2
+ import { MenuIcon, XIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type AppShellSidebarWidth = "sm" | "default" | "lg" | "xl"
8
+ export type AppShellAsideWidth = "sm" | "default" | "lg"
9
+
10
+ export type AppShellProps = React.ComponentProps<"div"> & {
11
+ header?: React.ReactNode
12
+ sidebar?: React.ReactNode
13
+ aside?: React.ReactNode
14
+ footer?: React.ReactNode
15
+ sidebarCollapsed?: boolean
16
+ defaultSidebarCollapsed?: boolean
17
+ onSidebarCollapsedChange?: (collapsed: boolean) => void
18
+ mobileSidebarOpen?: boolean
19
+ defaultMobileSidebarOpen?: boolean
20
+ onMobileSidebarOpenChange?: (open: boolean) => void
21
+ sidebarWidth?: AppShellSidebarWidth
22
+ asideWidth?: AppShellAsideWidth
23
+ sidebarMode?: "fixed" | "static"
24
+ mobileSidebar?: React.ReactNode
25
+ mobileOverlay?: boolean
26
+ collapseBreakpoint?: "sm" | "md" | "lg" | "xl"
27
+ showMobileMenuButton?: boolean
28
+ mobileMenuLabel?: string
29
+ mobileCloseLabel?: string
30
+ contentClassName?: string
31
+ mainClassName?: string
32
+ mainWrapperClassName?: string
33
+ sidebarClassName?: string
34
+ mobileSidebarClassName?: string
35
+ asideClassName?: string
36
+ }
37
+
38
+ const sidebarWidthClassName: Record<AppShellSidebarWidth, string> = {
39
+ sm: "w-56",
40
+ default: "w-64",
41
+ lg: "w-72",
42
+ xl: "w-80",
43
+ }
44
+
45
+ const sidebarPaddingClassName: Record<AppShellSidebarWidth, string> = {
46
+ sm: "md:pl-56",
47
+ default: "md:pl-64",
48
+ lg: "md:pl-72",
49
+ xl: "md:pl-80",
50
+ }
51
+
52
+ const asideWidthClassName: Record<AppShellAsideWidth, string> = {
53
+ sm: "w-72",
54
+ default: "w-80",
55
+ lg: "w-96",
56
+ }
57
+
58
+ function useControllableState({
59
+ value,
60
+ defaultValue,
61
+ onChange,
62
+ }: {
63
+ value?: boolean
64
+ defaultValue: boolean
65
+ onChange?: (value: boolean) => void
66
+ }) {
67
+ const controlled = value !== undefined
68
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
69
+ const currentValue = controlled ? value : internalValue
70
+
71
+ const setValue = React.useCallback(
72
+ (nextValue: boolean) => {
73
+ if (!controlled) {
74
+ setInternalValue(nextValue)
75
+ }
76
+
77
+ onChange?.(nextValue)
78
+ },
79
+ [controlled, onChange]
80
+ )
81
+
82
+ return [currentValue, setValue] as const
83
+ }
84
+
85
+ function AppShell({
86
+ className,
87
+ header,
88
+ sidebar,
89
+ aside,
90
+ footer,
91
+ sidebarCollapsed,
92
+ defaultSidebarCollapsed = false,
93
+ onSidebarCollapsedChange,
94
+ mobileSidebarOpen,
95
+ defaultMobileSidebarOpen = false,
96
+ onMobileSidebarOpenChange,
97
+ sidebarWidth = "default",
98
+ asideWidth = "default",
99
+ sidebarMode = "fixed",
100
+ mobileSidebar,
101
+ mobileOverlay = true,
102
+ showMobileMenuButton = true,
103
+ mobileMenuLabel = "Open navigation",
104
+ mobileCloseLabel = "Close navigation",
105
+ contentClassName,
106
+ mainClassName,
107
+ mainWrapperClassName,
108
+ sidebarClassName,
109
+ mobileSidebarClassName,
110
+ asideClassName,
111
+ children,
112
+ ...props
113
+ }: AppShellProps) {
114
+ const [isSidebarCollapsed] = useControllableState({
115
+ value: sidebarCollapsed,
116
+ defaultValue: defaultSidebarCollapsed,
117
+ onChange: onSidebarCollapsedChange,
118
+ })
119
+ const [isMobileSidebarOpen, setMobileSidebarOpen] = useControllableState({
120
+ value: mobileSidebarOpen,
121
+ defaultValue: defaultMobileSidebarOpen,
122
+ onChange: onMobileSidebarOpenChange,
123
+ })
124
+ const renderedMobileSidebar = mobileSidebar ?? sidebar
125
+ const hasSidebar = Boolean(sidebar)
126
+ const fixedSidebar = sidebarMode === "fixed"
127
+
128
+ return (
129
+ <div
130
+ data-slot="app-shell"
131
+ data-sidebar-collapsed={isSidebarCollapsed || undefined}
132
+ data-mobile-sidebar-open={isMobileSidebarOpen || undefined}
133
+ className={cn("min-h-screen bg-[radial-gradient(circle_at_top,color-mix(in_oklch,var(--primary),transparent_92%)_0%,transparent_38%),var(--background)] text-foreground", className)}
134
+ {...props}
135
+ >
136
+ {hasSidebar && fixedSidebar && (
137
+ <div
138
+ data-slot="app-shell-sidebar"
139
+ className={cn(
140
+ "fixed inset-y-0 left-0 z-40 hidden border-r border-sidebar-border/70 bg-sidebar/96 backdrop-blur transition-[width] duration-200 md:block",
141
+ isSidebarCollapsed ? "w-16" : sidebarWidthClassName[sidebarWidth],
142
+ sidebarClassName
143
+ )}
144
+ >
145
+ {sidebar}
146
+ </div>
147
+ )}
148
+
149
+ {renderedMobileSidebar && (
150
+ <div
151
+ data-slot="app-shell-mobile-sidebar-root"
152
+ data-open={isMobileSidebarOpen || undefined}
153
+ className="md:hidden"
154
+ >
155
+ {mobileOverlay && isMobileSidebarOpen && (
156
+ <button
157
+ type="button"
158
+ aria-label={mobileCloseLabel}
159
+ className="fixed inset-0 z-40 bg-background/78 backdrop-blur-md"
160
+ onClick={() => setMobileSidebarOpen(false)}
161
+ />
162
+ )}
163
+
164
+ <div
165
+ data-slot="app-shell-mobile-sidebar"
166
+ className={cn(
167
+ "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[85vw] flex-col border-r border-sidebar-border/70 bg-sidebar/98 text-sidebar-foreground shadow-[0_20px_60px_color-mix(in_oklch,var(--foreground),transparent_86%)] backdrop-blur transition-transform duration-200",
168
+ isMobileSidebarOpen ? "translate-x-0" : "-translate-x-full",
169
+ mobileSidebarClassName
170
+ )}
171
+ >
172
+ <div className="flex h-14 items-center justify-end border-b border-sidebar-border/70 px-3">
173
+ <Button type="button" variant="ghost" size="icon-sm" aria-label={mobileCloseLabel} onClick={() => setMobileSidebarOpen(false)}>
174
+ <XIcon />
175
+ </Button>
176
+ </div>
177
+ <div className="min-h-0 flex-1 overflow-y-auto">{renderedMobileSidebar}</div>
178
+ </div>
179
+ </div>
180
+ )}
181
+
182
+ <div
183
+ data-slot="app-shell-content"
184
+ className={cn(
185
+ "flex min-h-screen min-w-0 flex-col",
186
+ hasSidebar && fixedSidebar && (isSidebarCollapsed ? "md:pl-16" : sidebarPaddingClassName[sidebarWidth]),
187
+ contentClassName
188
+ )}
189
+ >
190
+ {header && (
191
+ <div data-slot="app-shell-header" className="contents">
192
+ {showMobileMenuButton && renderedMobileSidebar ? (
193
+ <div className="md:hidden">
194
+ <div className="flex h-14 items-center border-b border-border/70 bg-background/88 px-4 backdrop-blur">
195
+ <Button type="button" variant="ghost" size="icon-sm" aria-label={mobileMenuLabel} onClick={() => setMobileSidebarOpen(true)}>
196
+ <MenuIcon />
197
+ </Button>
198
+ </div>
199
+ </div>
200
+ ) : null}
201
+ {header}
202
+ </div>
203
+ )}
204
+
205
+ <div data-slot="app-shell-main-wrapper" className={cn("flex min-w-0 flex-1", mainWrapperClassName)}>
206
+ {hasSidebar && !fixedSidebar && (
207
+ <div
208
+ data-slot="app-shell-sidebar-static"
209
+ className={cn(
210
+ "hidden shrink-0 border-r border-sidebar-border/70 bg-sidebar/96 md:block",
211
+ isSidebarCollapsed ? "w-16" : sidebarWidthClassName[sidebarWidth],
212
+ sidebarClassName
213
+ )}
214
+ >
215
+ {sidebar}
216
+ </div>
217
+ )}
218
+
219
+ <main data-slot="app-shell-main" className={cn("min-w-0 flex-1", mainClassName)}>
220
+ {children}
221
+ </main>
222
+
223
+ {aside && (
224
+ <aside
225
+ data-slot="app-shell-aside"
226
+ className={cn(
227
+ "hidden shrink-0 border-l border-border/70 bg-card/45 xl:block",
228
+ asideWidthClassName[asideWidth],
229
+ asideClassName
230
+ )}
231
+ >
232
+ {aside}
233
+ </aside>
234
+ )}
235
+ </div>
236
+
237
+ {footer}
238
+ </div>
239
+ </div>
240
+ )
241
+ }
242
+
243
+ export { AppShell }