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
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "azamat-ui-kit-cli",
3
+ "version": "0.2.2",
4
+ "description": "CLI for Azamat UI Kit source-copy, presets, and theme setup.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "azamat-ui-kit": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "vendor",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "build": "node ../../scripts/sync-cli-assets.mjs && tsup",
20
+ "clean": "node -e \"import('node:fs').then(fs => { fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('vendor', { recursive: true, force: true }); })\"",
21
+ "prepack": "npm run build"
22
+ },
23
+ "dependencies": {
24
+ "commander": "^15.0.0",
25
+ "execa": "^9.6.1",
26
+ "fs-extra": "^11.3.5",
27
+ "kolorist": "^1.8.0",
28
+ "prompts": "^2.4.2"
29
+ },
30
+ "devDependencies": {
31
+ "tsup": "^8.5.1",
32
+ "typescript": "~6.0.2"
33
+ }
34
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "azamat-ui-kit",
3
+ "private": true
4
+ }
@@ -0,0 +1,35 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type ActionBarProps = React.ComponentProps<"div"> & {
6
+ title?: React.ReactNode
7
+ description?: React.ReactNode
8
+ start?: React.ReactNode
9
+ end?: React.ReactNode
10
+ sticky?: boolean
11
+ }
12
+
13
+ function ActionBar({ title, description, start, end, sticky = false, className, children, ...props }: ActionBarProps) {
14
+ return (
15
+ <div
16
+ data-slot="action-bar"
17
+ className={cn("flex flex-wrap items-center justify-between gap-3 rounded-lg border bg-card p-3", sticky && "sticky bottom-4 z-20 shadow-lg", className)}
18
+ {...props}
19
+ >
20
+ <div className="flex min-w-0 flex-1 items-center gap-3">
21
+ {start}
22
+ {(title || description) && (
23
+ <div className="grid min-w-0 gap-0.5">
24
+ {title && <div className="truncate text-sm font-medium text-foreground">{title}</div>}
25
+ {description && <div className="truncate text-xs text-muted-foreground">{description}</div>}
26
+ </div>
27
+ )}
28
+ {children}
29
+ </div>
30
+ {end && <div className="flex shrink-0 items-center gap-2">{end}</div>}
31
+ </div>
32
+ )
33
+ }
34
+
35
+ export { ActionBar }
@@ -0,0 +1,120 @@
1
+ import * as React from "react"
2
+ import { Loader2Icon, MoreHorizontalIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from "@/components/ui/dropdown-menu"
13
+ import { cn } from "@/lib/utils"
14
+
15
+ export type ActionMenuItem = {
16
+ key: string
17
+ label: React.ReactNode
18
+ icon?: React.ReactNode
19
+ shortcut?: React.ReactNode
20
+ disabled?: boolean
21
+ loading?: boolean
22
+ destructive?: boolean
23
+ hidden?: boolean
24
+ onSelect?: () => void | Promise<void>
25
+ }
26
+
27
+ export type ActionMenuProps = {
28
+ actions: ActionMenuItem[]
29
+ label?: React.ReactNode
30
+ trigger?: React.ReactElement
31
+ align?: "start" | "center" | "end"
32
+ side?: "top" | "right" | "bottom" | "left"
33
+ sideOffset?: number
34
+ disabled?: boolean
35
+ contentClassName?: string
36
+ triggerClassName?: string
37
+ emptyLabel?: React.ReactNode
38
+ }
39
+
40
+ function ActionMenu({
41
+ actions,
42
+ label,
43
+ trigger,
44
+ align = "end",
45
+ side = "bottom",
46
+ sideOffset = 4,
47
+ disabled = false,
48
+ contentClassName,
49
+ triggerClassName,
50
+ emptyLabel = "No actions",
51
+ }: ActionMenuProps) {
52
+ const visibleActions = actions.filter((action) => !action.hidden)
53
+ const [loadingKey, setLoadingKey] = React.useState<string | null>(null)
54
+
55
+ const handleSelect = async (action: ActionMenuItem) => {
56
+ if (action.disabled || action.loading || loadingKey) return
57
+
58
+ try {
59
+ setLoadingKey(action.key)
60
+ await action.onSelect?.()
61
+ } finally {
62
+ setLoadingKey(null)
63
+ }
64
+ }
65
+
66
+ return (
67
+ <DropdownMenu>
68
+ <DropdownMenuTrigger
69
+ render={
70
+ trigger ?? (
71
+ <Button
72
+ type="button"
73
+ variant="ghost"
74
+ size="icon-sm"
75
+ disabled={disabled}
76
+ className={triggerClassName}
77
+ />
78
+ )
79
+ }
80
+ >
81
+ {!trigger && <MoreHorizontalIcon />}
82
+ <span className="sr-only">Open actions</span>
83
+ </DropdownMenuTrigger>
84
+ <DropdownMenuContent
85
+ align={align}
86
+ side={side}
87
+ sideOffset={sideOffset}
88
+ className={cn("min-w-40", contentClassName)}
89
+ >
90
+ {label && <DropdownMenuLabel>{label}</DropdownMenuLabel>}
91
+ {label && visibleActions.length > 0 && <DropdownMenuSeparator />}
92
+
93
+ {visibleActions.length === 0 && (
94
+ <DropdownMenuItem disabled>{emptyLabel}</DropdownMenuItem>
95
+ )}
96
+
97
+ {visibleActions.map((action) => {
98
+ const isLoading = action.loading || loadingKey === action.key
99
+
100
+ return (
101
+ <DropdownMenuItem
102
+ key={action.key}
103
+ disabled={action.disabled || isLoading}
104
+ variant={action.destructive ? "destructive" : "default"}
105
+ onClick={() => void handleSelect(action)}
106
+ >
107
+ {isLoading ? <Loader2Icon className="animate-spin" /> : action.icon}
108
+ <span className="min-w-0 flex-1 truncate">{action.label}</span>
109
+ {action.shortcut && (
110
+ <span className="ml-auto text-xs text-muted-foreground">{action.shortcut}</span>
111
+ )}
112
+ </DropdownMenuItem>
113
+ )
114
+ })}
115
+ </DropdownMenuContent>
116
+ </DropdownMenu>
117
+ )
118
+ }
119
+
120
+ export { ActionMenu }
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ type ButtonProps = React.ComponentProps<typeof Button>
7
+
8
+ export type ButtonGroupItem = Omit<ButtonProps, "children"> & {
9
+ key: string
10
+ label: React.ReactNode
11
+ }
12
+
13
+ export type ButtonGroupProps = React.ComponentProps<"div"> & {
14
+ items?: ButtonGroupItem[]
15
+ attached?: boolean
16
+ size?: ButtonProps["size"]
17
+ variant?: ButtonProps["variant"]
18
+ }
19
+
20
+ function ButtonGroup({ items, attached = true, size = "sm", variant = "outline", className, children, ...props }: ButtonGroupProps) {
21
+ return (
22
+ <div
23
+ data-slot="button-group"
24
+ role="group"
25
+ className={cn("inline-flex items-center", !attached && "gap-2", className)}
26
+ {...props}
27
+ >
28
+ {items?.map(({ key, label, className: itemClassName, size: itemSize, variant: itemVariant, ...item }) => (
29
+ <Button
30
+ key={key}
31
+ size={itemSize ?? size}
32
+ variant={itemVariant ?? variant}
33
+ className={cn(
34
+ attached && "rounded-none first:rounded-l-md last:rounded-r-md -ml-px first:ml-0",
35
+ itemClassName
36
+ )}
37
+ {...item}
38
+ >
39
+ {label}
40
+ </Button>
41
+ ))}
42
+ {children}
43
+ </div>
44
+ )
45
+ }
46
+
47
+ export { ButtonGroup }
@@ -0,0 +1,91 @@
1
+ import * as React from "react"
2
+ import { CheckIcon, CopyIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+
6
+ export type CopyButtonProps = Omit<React.ComponentProps<typeof Button>, "onClick"> & {
7
+ value: string
8
+ copiedLabel?: React.ReactNode
9
+ copyLabel?: React.ReactNode
10
+ copiedTimeout?: number
11
+ onCopied?: (value: string) => void
12
+ onCopyError?: (error: unknown) => void
13
+ showIcon?: boolean
14
+ }
15
+
16
+ async function copyToClipboard(value: string) {
17
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
18
+ await navigator.clipboard.writeText(value)
19
+ return
20
+ }
21
+
22
+ if (typeof document === "undefined") {
23
+ throw new Error("Clipboard is not available")
24
+ }
25
+
26
+ const textarea = document.createElement("textarea")
27
+ textarea.value = value
28
+ textarea.setAttribute("readonly", "")
29
+ textarea.style.position = "fixed"
30
+ textarea.style.left = "-9999px"
31
+ document.body.appendChild(textarea)
32
+ textarea.select()
33
+
34
+ try {
35
+ document.execCommand("copy")
36
+ } finally {
37
+ document.body.removeChild(textarea)
38
+ }
39
+ }
40
+
41
+ function CopyButton({
42
+ value,
43
+ copiedLabel = "Copied",
44
+ copyLabel = "Copy",
45
+ copiedTimeout = 1600,
46
+ onCopied,
47
+ onCopyError,
48
+ showIcon = true,
49
+ disabled,
50
+ children,
51
+ type = "button",
52
+ ...props
53
+ }: CopyButtonProps) {
54
+ const [copied, setCopied] = React.useState(false)
55
+ const timeoutRef = React.useRef<number | undefined>(undefined)
56
+
57
+ React.useEffect(() => {
58
+ return () => {
59
+ if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
60
+ }
61
+ }, [])
62
+
63
+ const handleCopy = React.useCallback(async () => {
64
+ try {
65
+ await copyToClipboard(value)
66
+ setCopied(true)
67
+ onCopied?.(value)
68
+
69
+ if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
70
+ timeoutRef.current = window.setTimeout(() => setCopied(false), copiedTimeout)
71
+ } catch (error) {
72
+ onCopyError?.(error)
73
+ }
74
+ }, [copiedTimeout, onCopied, onCopyError, value])
75
+
76
+ return (
77
+ <Button
78
+ data-slot="copy-button"
79
+ data-copied={copied || undefined}
80
+ type={type}
81
+ disabled={disabled || !value}
82
+ onClick={handleCopy}
83
+ {...props}
84
+ >
85
+ {showIcon && (copied ? <CheckIcon data-icon="inline-start" /> : <CopyIcon data-icon="inline-start" />)}
86
+ {children ?? (copied ? copiedLabel : copyLabel)}
87
+ </Button>
88
+ )
89
+ }
90
+
91
+ export { CopyButton }
@@ -0,0 +1,31 @@
1
+ import * as React from "react"
2
+
3
+ import { CopyButton, type CopyButtonProps } from "@/components/actions/copy-button"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type CopyFieldProps = React.ComponentProps<"div"> & {
7
+ value: string
8
+ label?: React.ReactNode
9
+ description?: React.ReactNode
10
+ copyButtonProps?: Omit<CopyButtonProps, "value">
11
+ monospace?: boolean
12
+ }
13
+
14
+ function CopyField({ value, label, description, copyButtonProps, monospace = true, className, ...props }: CopyFieldProps) {
15
+ return (
16
+ <div data-slot="copy-field" className={cn("grid gap-2", className)} {...props}>
17
+ {(label || description) && (
18
+ <div className="grid gap-0.5">
19
+ {label && <div className="text-sm font-medium text-foreground">{label}</div>}
20
+ {description && <div className="text-xs text-muted-foreground">{description}</div>}
21
+ </div>
22
+ )}
23
+ <div className="flex items-center gap-2 rounded-lg border bg-card p-2">
24
+ <div className={cn("min-w-0 flex-1 truncate text-sm text-muted-foreground", monospace && "font-mono")}>{value}</div>
25
+ <CopyButton value={value} size="sm" variant="outline" {...copyButtonProps} />
26
+ </div>
27
+ </div>
28
+ )
29
+ }
30
+
31
+ export { CopyField }
@@ -0,0 +1,33 @@
1
+ import * as React from "react"
2
+ import { PlusIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type FloatingActionButtonProps = React.ComponentProps<typeof Button> & {
8
+ position?: "bottom-right" | "bottom-left" | "top-right" | "top-left"
9
+ icon?: React.ReactNode
10
+ }
11
+
12
+ const positionClassName = {
13
+ "bottom-right": "bottom-6 right-6",
14
+ "bottom-left": "bottom-6 left-6",
15
+ "top-right": "right-6 top-6",
16
+ "top-left": "left-6 top-6",
17
+ }
18
+
19
+ function FloatingActionButton({ position = "bottom-right", icon, className, children, ...props }: FloatingActionButtonProps) {
20
+ return (
21
+ <Button
22
+ data-slot="floating-action-button"
23
+ size={children ? "lg" : "icon-lg"}
24
+ className={cn("fixed z-40 rounded-full shadow-lg", positionClassName[position], className)}
25
+ {...props}
26
+ >
27
+ {icon ?? <PlusIcon />}
28
+ {children}
29
+ </Button>
30
+ )
31
+ }
32
+
33
+ export { FloatingActionButton }
@@ -0,0 +1,7 @@
1
+ export * from './action-menu'
2
+ export * from './copy-button'
3
+ export * from './copy-field'
4
+ export * from './button-group'
5
+ export * from './floating-action-button'
6
+ export * from './action-bar'
7
+ export * from './quick-action-grid'
@@ -0,0 +1,5 @@
1
+ export * from "./action-menu"
2
+ export * from "./copy-button"
3
+ export * from "./copy-field"
4
+ export * from "./button-group"
5
+ export * from "./quick-action-grid"
@@ -0,0 +1,162 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge } from "@/components/ui/badge"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type QuickActionGridColumn = 1 | 2 | 3 | 4
7
+
8
+ export type QuickActionGridItem = {
9
+ key: string
10
+ label: React.ReactNode
11
+ description?: React.ReactNode
12
+ icon?: React.ReactNode
13
+ badge?: React.ReactNode
14
+ disabled?: boolean
15
+ hidden?: boolean
16
+ href?: string
17
+ external?: boolean
18
+ onSelect?: () => void
19
+ className?: string
20
+ }
21
+
22
+ export type QuickActionGridProps = React.ComponentProps<"div"> & {
23
+ items: QuickActionGridItem[]
24
+ columns?: QuickActionGridColumn
25
+ compact?: boolean
26
+ itemClassName?: string
27
+ renderLink?: (props: React.ComponentProps<"a"> & { item: QuickActionGridItem; [key: `data-${string}`]: string | boolean | undefined }) => React.ReactNode
28
+ }
29
+
30
+ const columnsClassName: Record<QuickActionGridColumn, string> = {
31
+ 1: "grid-cols-1",
32
+ 2: "grid-cols-1 sm:grid-cols-2",
33
+ 3: "grid-cols-1 sm:grid-cols-2 xl:grid-cols-3",
34
+ 4: "grid-cols-1 sm:grid-cols-2 xl:grid-cols-4",
35
+ }
36
+
37
+ function QuickActionGrid({
38
+ items,
39
+ columns = 3,
40
+ compact = false,
41
+ itemClassName,
42
+ renderLink,
43
+ className,
44
+ ...props
45
+ }: QuickActionGridProps) {
46
+ const visibleItems = items.filter((item) => !item.hidden)
47
+
48
+ return (
49
+ <div data-slot="quick-action-grid" className={cn("grid gap-3", columnsClassName[columns], className)} {...props}>
50
+ {visibleItems.map((item) => {
51
+ const content = (
52
+ <>
53
+ <div className="flex min-w-0 items-start justify-between gap-3">
54
+ <div className="flex min-w-0 items-start gap-3">
55
+ {item.icon && <span className="mt-0.5 shrink-0 rounded-lg bg-muted p-2 text-muted-foreground [&_svg]:size-4">{item.icon}</span>}
56
+ <div className="min-w-0 space-y-1">
57
+ <div className="truncate text-sm font-medium text-foreground">{item.label}</div>
58
+ {item.description && <div className="line-clamp-2 text-xs leading-5 text-muted-foreground">{item.description}</div>}
59
+ </div>
60
+ </div>
61
+ {item.badge && <Badge variant="outline" className="shrink-0 text-[10px]">{item.badge}</Badge>}
62
+ </div>
63
+ </>
64
+ )
65
+
66
+ const commonClassName = cn(
67
+ "min-w-0 rounded-xl border bg-card text-left text-card-foreground shadow-sm transition-all hover:border-primary/40 hover:bg-muted/20",
68
+ compact ? "p-3" : "p-4",
69
+ item.disabled && "pointer-events-none opacity-55",
70
+ itemClassName,
71
+ item.className
72
+ )
73
+
74
+ const isInternalLink = item.href?.startsWith("/")
75
+
76
+ if (item.href && isInternalLink) {
77
+ return (
78
+ renderLink ? (
79
+ <React.Fragment key={item.key}>
80
+ {renderLink({
81
+ item,
82
+ href: item.href,
83
+ "data-slot": "quick-action-grid-item",
84
+ "aria-disabled": item.disabled || undefined,
85
+ className: commonClassName,
86
+ onClick: (event) => {
87
+ if (item.disabled) {
88
+ event.preventDefault()
89
+ return
90
+ }
91
+ item.onSelect?.()
92
+ },
93
+ children: content,
94
+ })}
95
+ </React.Fragment>
96
+ ) : (
97
+ <a
98
+ key={item.key}
99
+ data-slot="quick-action-grid-item"
100
+ href={item.href}
101
+ aria-disabled={item.disabled || undefined}
102
+ className={commonClassName}
103
+ onClick={(event) => {
104
+ if (item.disabled) {
105
+ event.preventDefault()
106
+ return
107
+ }
108
+ item.onSelect?.()
109
+ }}
110
+ >
111
+ {content}
112
+ </a>
113
+ )
114
+ )
115
+ }
116
+
117
+ if (item.href) {
118
+ return (
119
+ <button
120
+ key={item.key}
121
+ data-slot="quick-action-grid-item"
122
+ type="button"
123
+ aria-disabled={item.disabled || undefined}
124
+ className={commonClassName}
125
+ onClick={() => {
126
+ if (item.disabled) return
127
+ item.onSelect?.()
128
+
129
+ const href = item.href
130
+ if (!href) return
131
+
132
+ if (item.external) {
133
+ window.open(href, "_blank", "noopener,noreferrer")
134
+ return
135
+ }
136
+
137
+ window.location.assign(href)
138
+ }}
139
+ >
140
+ {content}
141
+ </button>
142
+ )
143
+ }
144
+
145
+ return (
146
+ <button
147
+ key={item.key}
148
+ data-slot="quick-action-grid-item"
149
+ type="button"
150
+ disabled={item.disabled}
151
+ className={commonClassName}
152
+ onClick={item.onSelect}
153
+ >
154
+ {content}
155
+ </button>
156
+ )
157
+ })}
158
+ </div>
159
+ )
160
+ }
161
+
162
+ export { QuickActionGrid }