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,61 @@
1
+ import * as React from "react"
2
+
3
+ import { DataView, type DataViewProps } from "@/components/patterns/data-view"
4
+ import { EntityDetails, type EntityDetailsProps } from "@/components/patterns/entity-details"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type ResourceSystemMode = "list" | "detail" | "split"
8
+
9
+ export type ResourceSystemProps<TItem = unknown> = React.ComponentProps<"div"> & {
10
+ mode?: ResourceSystemMode
11
+ title?: React.ReactNode
12
+ description?: React.ReactNode
13
+ actions?: React.ReactNode
14
+ list: DataViewProps<TItem>
15
+ detail?: React.ReactNode | EntityDetailsProps
16
+ aside?: React.ReactNode
17
+ splitSize?: "sm" | "md" | "lg"
18
+ renderHeader?: () => React.ReactNode
19
+ listClassName?: string
20
+ detailClassName?: string
21
+ }
22
+
23
+ const splitClassName = {
24
+ sm: "lg:grid-cols-[minmax(0,1fr)_320px]",
25
+ md: "lg:grid-cols-[minmax(0,1fr)_420px]",
26
+ lg: "lg:grid-cols-[minmax(0,1fr)_520px]",
27
+ }
28
+
29
+ function ResourceSystem<TItem = unknown>({ mode = "list", title, description, actions, list, detail, aside, splitSize = "md", renderHeader, listClassName, detailClassName, className, ...props }: ResourceSystemProps<TItem>) {
30
+ const detailNode = isEntityDetailsProps(detail) ? <EntityDetails {...detail} /> : detail
31
+
32
+ return (
33
+ <div data-slot="resource-system" data-mode={mode} className={cn("grid gap-4", className)} {...props}>
34
+ {renderHeader?.() ?? ((title || description || actions) && (
35
+ <div className="flex flex-wrap items-start justify-between gap-3">
36
+ <div className="grid gap-1">
37
+ {title && <h1 className="text-2xl font-semibold tracking-tight text-foreground">{title}</h1>}
38
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
39
+ </div>
40
+ {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
41
+ </div>
42
+ ))}
43
+ {mode === "split" ? (
44
+ <div className={cn("grid gap-4", splitClassName[splitSize])}>
45
+ <DataView {...list} className={cn(list.className, listClassName)} />
46
+ <div className={cn("min-w-0", detailClassName)}>{detailNode ?? aside}</div>
47
+ </div>
48
+ ) : mode === "detail" ? (
49
+ <div className={detailClassName}>{detailNode ?? aside}</div>
50
+ ) : (
51
+ <DataView {...list} className={cn(list.className, listClassName)} />
52
+ )}
53
+ </div>
54
+ )
55
+ }
56
+
57
+ function isEntityDetailsProps(value: unknown): value is EntityDetailsProps {
58
+ return Boolean(value && typeof value === "object" && "title" in value)
59
+ }
60
+
61
+ export { ResourceSystem }
@@ -0,0 +1,46 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type SettingsSectionProps = React.ComponentProps<"section"> & {
6
+ title?: React.ReactNode
7
+ description?: React.ReactNode
8
+ actions?: React.ReactNode
9
+ }
10
+
11
+ function SettingsSection({ title, description, actions, className, children, ...props }: SettingsSectionProps) {
12
+ return (
13
+ <section data-slot="settings-section" className={cn("grid gap-4 rounded-lg border bg-card p-4", className)} {...props}>
14
+ {(title || description || actions) && (
15
+ <div className="flex items-start justify-between gap-3">
16
+ <div className="grid gap-1">
17
+ {title && <h3 className="text-base font-semibold text-foreground">{title}</h3>}
18
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
19
+ </div>
20
+ {actions}
21
+ </div>
22
+ )}
23
+ <div className="grid divide-y">{children}</div>
24
+ </section>
25
+ )
26
+ }
27
+
28
+ export type SettingsRowProps = React.ComponentProps<"div"> & {
29
+ label: React.ReactNode
30
+ description?: React.ReactNode
31
+ control?: React.ReactNode
32
+ }
33
+
34
+ function SettingsRow({ label, description, control, className, children, ...props }: SettingsRowProps) {
35
+ return (
36
+ <div data-slot="settings-row" className={cn("flex flex-wrap items-center justify-between gap-3 py-3 first:pt-0 last:pb-0", className)} {...props}>
37
+ <div className="grid min-w-0 gap-1">
38
+ <div className="text-sm font-medium text-foreground">{label}</div>
39
+ {description && <div className="text-sm text-muted-foreground">{description}</div>}
40
+ </div>
41
+ <div className="shrink-0">{control ?? children}</div>
42
+ </div>
43
+ )
44
+ }
45
+
46
+ export { SettingsRow, SettingsSection }
@@ -0,0 +1,89 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge } from "@/components/ui/badge"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type StatusTone = "neutral" | "success" | "warning" | "danger" | "info"
7
+
8
+ export type StatusState<TValue extends string = string> = {
9
+ value: TValue
10
+ label: React.ReactNode
11
+ icon?: React.ReactNode
12
+ tone?: StatusTone
13
+ next?: TValue[]
14
+ disabled?: boolean
15
+ }
16
+
17
+ export type StatusSystemProps<TValue extends string = string> = React.ComponentProps<"div"> & {
18
+ value: TValue
19
+ states: StatusState<TValue>[]
20
+ mode?: "badge" | "select" | "steps"
21
+ allowAllTransitions?: boolean
22
+ onValueChange?: (value: TValue, state: StatusState<TValue>) => void
23
+ renderState?: (state: StatusState<TValue>, meta: { active: boolean; passed: boolean; disabled: boolean }) => React.ReactNode
24
+ }
25
+
26
+ const toneClassName: Record<StatusTone, string> = {
27
+ neutral: "border-border bg-muted text-muted-foreground",
28
+ success: "border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400",
29
+ warning: "border-amber-500/20 bg-amber-500/10 text-amber-700 dark:text-amber-400",
30
+ danger: "border-destructive/20 bg-destructive/10 text-destructive",
31
+ info: "border-primary/20 bg-primary/10 text-primary",
32
+ }
33
+
34
+ function StatusSystem<TValue extends string = string>({ value, states, mode = "badge", allowAllTransitions = false, onValueChange, renderState, className, ...props }: StatusSystemProps<TValue>) {
35
+ const activeIndex = states.findIndex((state) => state.value === value)
36
+ const activeState = states[activeIndex]
37
+ const nextValues = new Set(allowAllTransitions ? states.map((state) => state.value) : activeState?.next ?? [])
38
+
39
+ if (!activeState) return null
40
+
41
+ if (mode === "select") {
42
+ return (
43
+ <div data-slot="status-system" className={cn("inline-flex", className)} {...props}>
44
+ <select
45
+ value={value}
46
+ className="h-8 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
47
+ onChange={(event) => {
48
+ const state = states.find((item) => item.value === event.currentTarget.value)
49
+ if (state) onValueChange?.(state.value, state)
50
+ }}
51
+ >
52
+ {states.map((state) => {
53
+ const active = state.value === value
54
+ const disabled = state.disabled || (!active && !nextValues.has(state.value))
55
+ return <option key={state.value} value={state.value} disabled={disabled}>{typeof state.label === "string" ? state.label : state.value}</option>
56
+ })}
57
+ </select>
58
+ </div>
59
+ )
60
+ }
61
+
62
+ if (mode === "steps") {
63
+ return (
64
+ <div data-slot="status-system" className={cn("flex flex-wrap items-center gap-3", className)} {...props}>
65
+ {states.map((state, index) => {
66
+ const active = state.value === value
67
+ const passed = activeIndex >= index
68
+ const disabled = state.disabled || (!active && !nextValues.has(state.value))
69
+ return renderState?.(state, { active, passed, disabled }) ?? (
70
+ <button key={state.value} type="button" disabled={disabled} className="flex items-center gap-2 disabled:opacity-50" onClick={() => onValueChange?.(state.value, state)}>
71
+ <span className={cn("flex size-6 items-center justify-center rounded-full border text-xs", passed ? "border-primary bg-primary text-primary-foreground" : "bg-muted text-muted-foreground")}>{index + 1}</span>
72
+ <span className={cn("text-sm", active ? "font-medium text-foreground" : "text-muted-foreground")}>{state.label}</span>
73
+ </button>
74
+ )
75
+ })}
76
+ </div>
77
+ )
78
+ }
79
+
80
+ return (
81
+ <div data-slot="status-system" className={className} {...props}>
82
+ {renderState?.(activeState, { active: true, passed: true, disabled: Boolean(activeState.disabled) }) ?? (
83
+ <Badge variant="outline" className={cn("gap-1", toneClassName[activeState.tone ?? "neutral"])}>{activeState.icon}{activeState.label}</Badge>
84
+ )}
85
+ </div>
86
+ )
87
+ }
88
+
89
+ export { StatusSystem }
@@ -0,0 +1,51 @@
1
+ import * as React from "react"
2
+
3
+ type ThemeMode = "light" | "dark"
4
+
5
+ type ThemeContextValue = {
6
+ theme: ThemeMode
7
+ setTheme: (theme: ThemeMode) => void
8
+ toggleTheme: () => void
9
+ }
10
+
11
+ const THEME_STORAGE_KEY = "azamat-ui-theme"
12
+
13
+ const ThemeContext = React.createContext<ThemeContextValue | null>(null)
14
+
15
+ function resolveInitialTheme(): ThemeMode {
16
+ if (typeof window === "undefined") return "light"
17
+
18
+ const stored = window.localStorage.getItem(THEME_STORAGE_KEY)
19
+ if (stored === "light" || stored === "dark") return stored
20
+
21
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
22
+ }
23
+
24
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
25
+ const [theme, setThemeState] = React.useState<ThemeMode>(resolveInitialTheme)
26
+
27
+ React.useEffect(() => {
28
+ const root = window.document.documentElement
29
+ root.classList.toggle("dark", theme === "dark")
30
+ root.style.colorScheme = theme
31
+ window.localStorage.setItem(THEME_STORAGE_KEY, theme)
32
+ }, [theme])
33
+
34
+ const setTheme = React.useCallback((nextTheme: ThemeMode) => {
35
+ setThemeState(nextTheme)
36
+ }, [])
37
+
38
+ const toggleTheme = React.useCallback(() => {
39
+ setThemeState((current) => (current === "dark" ? "light" : "dark"))
40
+ }, [])
41
+
42
+ const value = React.useMemo(() => ({ theme, setTheme, toggleTheme }), [theme, setTheme, toggleTheme])
43
+
44
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
45
+ }
46
+
47
+ export function useTheme() {
48
+ const context = React.useContext(ThemeContext)
49
+ if (!context) throw new Error("useTheme must be used within ThemeProvider")
50
+ return context
51
+ }
@@ -0,0 +1,52 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props"
2
+ import { useRender } from "@base-ui/react/use-render"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
15
+ destructive:
16
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
17
+ outline:
18
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
19
+ ghost:
20
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ }
28
+ )
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ render,
34
+ ...props
35
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
36
+ return useRender({
37
+ defaultTagName: "span",
38
+ props: mergeProps<"span">(
39
+ {
40
+ className: cn(badgeVariants({ variant }), className),
41
+ },
42
+ props
43
+ ),
44
+ render,
45
+ state: {
46
+ slot: "badge",
47
+ variant,
48
+ },
49
+ })
50
+ }
51
+
52
+ export { Badge, badgeVariants }
@@ -0,0 +1,61 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const buttonVariants = cva(
7
+ "group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-[transform,background-color,border-color,color,box-shadow] outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-primary/90 bg-primary text-primary-foreground shadow-[0_10px_24px_color-mix(in_oklch,var(--primary),transparent_78%)] hover:bg-[color-mix(in_oklch,var(--primary),white_8%)] hover:shadow-[0_14px_30px_color-mix(in_oklch,var(--primary),transparent_72%)]",
13
+ outline:
14
+ "border-border/80 bg-background/92 text-foreground shadow-sm hover:border-border hover:bg-accent/70 hover:text-foreground aria-expanded:bg-accent/70 aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/55",
15
+ secondary:
16
+ "border-transparent bg-secondary text-secondary-foreground shadow-sm hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_4%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
17
+ ghost:
18
+ "border-transparent bg-transparent hover:bg-accent/70 hover:text-foreground aria-expanded:bg-accent/70 aria-expanded:text-foreground dark:hover:bg-muted/50",
19
+ destructive:
20
+ "border-destructive/15 bg-destructive text-white shadow-[0_10px_24px_color-mix(in_oklch,var(--destructive),transparent_78%)] hover:bg-[color-mix(in_oklch,var(--destructive),black_6%)] focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default:
25
+ "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
26
+ xs: "h-7 gap-1 rounded-[min(var(--radius-lg),12px)] px-2.5 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-8 gap-1 rounded-[min(var(--radius-lg),14px)] px-3 text-[0.82rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
29
+ icon: "size-9",
30
+ "icon-xs":
31
+ "size-7 rounded-[min(var(--radius-lg),12px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
32
+ "icon-sm":
33
+ "size-8 rounded-[min(var(--radius-lg),14px)] in-data-[slot=button-group]:rounded-lg",
34
+ "icon-lg": "size-10",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ }
42
+ )
43
+
44
+ export type ButtonProps = ButtonPrimitive.Props & VariantProps<typeof buttonVariants>
45
+
46
+ function Button({
47
+ className,
48
+ variant = "default",
49
+ size = "default",
50
+ ...props
51
+ }: ButtonProps) {
52
+ return (
53
+ <ButtonPrimitive
54
+ data-slot="button"
55
+ className={cn(buttonVariants({ variant, size, className }))}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ export { Button, buttonVariants }
@@ -0,0 +1,103 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn(
15
+ "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-[var(--radius-2xl)] border border-border/75 bg-card/98 py-(--card-spacing) text-sm text-card-foreground shadow-sm [--card-spacing:--spacing(5)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-[var(--radius-2xl)] *:[img:last-child]:rounded-b-[var(--radius-2xl)]",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1.5 rounded-t-[var(--radius-2xl)] px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="card-title"
40
+ className={cn(
41
+ "font-heading text-[1.05rem] leading-snug font-semibold tracking-tight group-data-[size=sm]/card:text-sm",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
50
+ return (
51
+ <div
52
+ data-slot="card-description"
53
+ className={cn("text-sm leading-6 text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="card-action"
63
+ className={cn(
64
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="card-content"
76
+ className={cn("px-(--card-spacing)", className)}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="card-footer"
86
+ className={cn(
87
+ "flex items-center rounded-b-[var(--radius-2xl)] border-t border-border/70 bg-muted/45 p-(--card-spacing)",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ export {
96
+ Card,
97
+ CardHeader,
98
+ CardFooter,
99
+ CardTitle,
100
+ CardAction,
101
+ CardDescription,
102
+ CardContent,
103
+ }
@@ -0,0 +1,82 @@
1
+ import * as React from "react"
2
+ import { CheckIcon, MinusIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type CheckboxCheckedState = boolean | "indeterminate"
7
+
8
+ export type CheckboxProps = Omit<
9
+ React.ComponentPropsWithoutRef<"button">,
10
+ "checked" | "defaultChecked" | "onChange" | "value"
11
+ > & {
12
+ checked?: CheckboxCheckedState
13
+ defaultChecked?: CheckboxCheckedState
14
+ onCheckedChange?: (checked: boolean) => void
15
+ }
16
+
17
+ function getNextCheckedState(checked: CheckboxCheckedState) {
18
+ return checked === true ? false : true
19
+ }
20
+
21
+ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
22
+ (
23
+ {
24
+ className,
25
+ checked,
26
+ defaultChecked = false,
27
+ onCheckedChange,
28
+ disabled,
29
+ onClick,
30
+ children,
31
+ ...props
32
+ },
33
+ ref
34
+ ) => {
35
+ const isControlled = checked !== undefined
36
+ const [internalChecked, setInternalChecked] = React.useState<CheckboxCheckedState>(defaultChecked)
37
+ const currentChecked = isControlled ? checked : internalChecked
38
+ const dataState = currentChecked === "indeterminate" ? "indeterminate" : currentChecked ? "checked" : "unchecked"
39
+
40
+ const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
41
+ const nextChecked = getNextCheckedState(currentChecked)
42
+
43
+ if (!isControlled) {
44
+ setInternalChecked(nextChecked)
45
+ }
46
+
47
+ onCheckedChange?.(nextChecked)
48
+ onClick?.(event)
49
+ }
50
+
51
+ return (
52
+ <button
53
+ ref={ref}
54
+ type="button"
55
+ role="checkbox"
56
+ aria-checked={currentChecked === "indeterminate" ? "mixed" : currentChecked}
57
+ data-state={dataState}
58
+ data-slot="checkbox"
59
+ disabled={disabled}
60
+ className={cn(
61
+ "peer flex size-5 shrink-0 items-center justify-center rounded-[min(var(--radius-lg),8px)] border border-input/90 bg-background/92 text-primary-foreground shadow-sm outline-none transition-[background-color,border-color,box-shadow,color] hover:border-border focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/45 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:shadow-[0_10px_24px_color-mix(in_oklch,var(--primary),transparent_80%)] data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:shadow-[0_10px_24px_color-mix(in_oklch,var(--primary),transparent_80%)]",
62
+ className
63
+ )}
64
+ onClick={handleClick}
65
+ {...props}
66
+ >
67
+ {children ?? (
68
+ <span className="flex items-center justify-center">
69
+ {currentChecked === "indeterminate" ? (
70
+ <MinusIcon className="size-3.5" />
71
+ ) : currentChecked ? (
72
+ <CheckIcon className="size-3.5" />
73
+ ) : null}
74
+ </span>
75
+ )}
76
+ </button>
77
+ )
78
+ }
79
+ )
80
+ Checkbox.displayName = "Checkbox"
81
+
82
+ export { Checkbox }
@@ -0,0 +1,126 @@
1
+ import * as React from "react"
2
+ import { ChevronDownIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type CollapseProps = React.ComponentProps<"details"> & {
7
+ defaultOpen?: boolean
8
+ open?: boolean
9
+ onOpenChange?: (open: boolean) => void
10
+ }
11
+
12
+ function Collapse({ open, defaultOpen, onOpenChange, onToggle, className, children, ...props }: CollapseProps) {
13
+ const controlledProps = open === undefined ? { defaultOpen } : { open }
14
+
15
+ return (
16
+ <details
17
+ data-slot="collapse"
18
+ className={cn("group rounded-lg border bg-card text-card-foreground", className)}
19
+ onToggle={(event) => {
20
+ onToggle?.(event)
21
+ onOpenChange?.(event.currentTarget.open)
22
+ }}
23
+ {...controlledProps}
24
+ {...props}
25
+ >
26
+ {children}
27
+ </details>
28
+ )
29
+ }
30
+
31
+ export type CollapseTriggerProps = React.ComponentProps<"summary"> & {
32
+ icon?: React.ReactNode
33
+ hideIcon?: boolean
34
+ }
35
+
36
+ function CollapseTrigger({ icon, hideIcon = false, className, children, ...props }: CollapseTriggerProps) {
37
+ return (
38
+ <summary
39
+ data-slot="collapse-trigger"
40
+ className={cn(
41
+ "flex cursor-pointer list-none items-center justify-between gap-3 rounded-lg px-4 py-3 text-sm font-medium outline-none transition-colors hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden",
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ <span className="min-w-0 flex-1">{children}</span>
47
+ {!hideIcon && (
48
+ <span className="shrink-0 text-muted-foreground transition-transform group-open:rotate-180">
49
+ {icon ?? <ChevronDownIcon className="size-4" />}
50
+ </span>
51
+ )}
52
+ </summary>
53
+ )
54
+ }
55
+
56
+ function CollapseContent({ className, ...props }: React.ComponentProps<"div">) {
57
+ return (
58
+ <div
59
+ data-slot="collapse-content"
60
+ className={cn("border-t px-4 py-3 text-sm text-muted-foreground", className)}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export type CollapseItem = {
67
+ key: string
68
+ title: React.ReactNode
69
+ content: React.ReactNode
70
+ description?: React.ReactNode
71
+ disabled?: boolean
72
+ }
73
+
74
+ export type CollapseGroupProps = React.ComponentProps<"div"> & {
75
+ items: CollapseItem[]
76
+ type?: "single" | "multiple"
77
+ value?: string | string[]
78
+ defaultValue?: string | string[]
79
+ onValueChange?: (value: string | string[]) => void
80
+ }
81
+
82
+ function CollapseGroup({ items, type = "multiple", value, defaultValue, onValueChange, className, ...props }: CollapseGroupProps) {
83
+ const initialValue = React.useMemo(() => {
84
+ if (value !== undefined) return value
85
+ if (defaultValue !== undefined) return defaultValue
86
+ return type === "single" ? "" : []
87
+ }, [defaultValue, type, value])
88
+ const [internalValue, setInternalValue] = React.useState<string | string[]>(initialValue)
89
+ const currentValue = value ?? internalValue
90
+
91
+ const isOpen = (key: string) => Array.isArray(currentValue) ? currentValue.includes(key) : currentValue === key
92
+
93
+ const updateValue = (key: string, nextOpen: boolean) => {
94
+ const nextValue = type === "single"
95
+ ? nextOpen ? key : ""
96
+ : nextOpen
97
+ ? Array.from(new Set([...(Array.isArray(currentValue) ? currentValue : []), key]))
98
+ : (Array.isArray(currentValue) ? currentValue.filter((item) => item !== key) : [])
99
+
100
+ if (value === undefined) setInternalValue(nextValue)
101
+ onValueChange?.(nextValue)
102
+ }
103
+
104
+ return (
105
+ <div data-slot="collapse-group" className={cn("grid gap-2", className)} {...props}>
106
+ {items.map((item) => (
107
+ <Collapse
108
+ key={item.key}
109
+ open={isOpen(item.key)}
110
+ onOpenChange={(open) => updateValue(item.key, open)}
111
+ className={cn(item.disabled && "pointer-events-none opacity-60")}
112
+ >
113
+ <CollapseTrigger>
114
+ <span className="grid gap-0.5">
115
+ <span>{item.title}</span>
116
+ {item.description && <span className="text-xs font-normal text-muted-foreground">{item.description}</span>}
117
+ </span>
118
+ </CollapseTrigger>
119
+ <CollapseContent>{item.content}</CollapseContent>
120
+ </Collapse>
121
+ ))}
122
+ </div>
123
+ )
124
+ }
125
+
126
+ export { Collapse, CollapseContent, CollapseGroup, CollapseTrigger }