tembro 6.0.3 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/components/actions/action-menu.d.ts +11 -1
  3. package/dist/components/calendar/calendar-range-scheduler.cjs +1 -0
  4. package/dist/components/calendar/calendar-range-scheduler.d.ts +20 -0
  5. package/dist/components/calendar/calendar-range-scheduler.js +1 -0
  6. package/dist/components/calendar/date-time-picker.cjs +1 -0
  7. package/dist/components/calendar/date-time-picker.d.ts +15 -0
  8. package/dist/components/calendar/date-time-picker.js +1 -0
  9. package/dist/components/calendar/index.d.ts +2 -0
  10. package/dist/components/charts/chart-card.cjs +1 -0
  11. package/dist/components/charts/chart-card.d.ts +24 -0
  12. package/dist/components/charts/chart-card.js +1 -0
  13. package/dist/components/charts/index.d.ts +1 -0
  14. package/dist/components/command/advanced-command-menu.cjs +1 -0
  15. package/dist/components/command/advanced-command-menu.d.ts +25 -0
  16. package/dist/components/command/advanced-command-menu.js +1 -0
  17. package/dist/components/command/index.d.ts +1 -0
  18. package/dist/components/data-table/data-grid.cjs +1 -0
  19. package/dist/components/data-table/data-grid.d.ts +15 -0
  20. package/dist/components/data-table/data-grid.js +1 -0
  21. package/dist/components/data-table/index.d.ts +1 -0
  22. package/dist/components/data-table/public.d.ts +1 -0
  23. package/dist/components/display/index.d.ts +0 -2
  24. package/dist/components/feedback/async-boundary.cjs +1 -0
  25. package/dist/components/feedback/async-boundary.d.ts +12 -0
  26. package/dist/components/feedback/async-boundary.js +1 -0
  27. package/dist/components/feedback/index.d.ts +1 -0
  28. package/dist/components/filters/index.d.ts +0 -1
  29. package/dist/components/inputs/index.d.ts +1 -0
  30. package/dist/components/inputs/multi-select.cjs +1 -0
  31. package/dist/components/inputs/multi-select.d.ts +16 -0
  32. package/dist/components/inputs/multi-select.js +1 -0
  33. package/dist/components/inputs/public.d.ts +1 -0
  34. package/dist/components/inputs/time-picker.d.ts +11 -3
  35. package/dist/components/modern/file-manager.cjs +1 -0
  36. package/dist/components/modern/file-manager.d.ts +20 -0
  37. package/dist/components/modern/file-manager.js +1 -0
  38. package/dist/components/modern/index.d.ts +4 -1
  39. package/dist/components/modern/map.cjs +1 -0
  40. package/dist/components/modern/map.d.ts +29 -0
  41. package/dist/components/modern/map.js +1 -0
  42. package/dist/components/modern/media-player.cjs +1 -0
  43. package/dist/components/modern/media-player.d.ts +21 -0
  44. package/dist/components/modern/media-player.js +1 -0
  45. package/dist/components/modern/spreadsheet.cjs +1 -0
  46. package/dist/components/modern/spreadsheet.d.ts +35 -0
  47. package/dist/components/modern/spreadsheet.js +1 -0
  48. package/dist/components/notifications/action-toast.cjs +1 -0
  49. package/dist/components/notifications/action-toast.d.ts +16 -0
  50. package/dist/components/notifications/action-toast.js +1 -0
  51. package/dist/components/notifications/index.d.ts +1 -0
  52. package/dist/components/overlay/drawer.d.ts +3 -1
  53. package/dist/components/patterns/index.d.ts +1 -0
  54. package/dist/components/patterns/permissions-matrix.cjs +1 -0
  55. package/dist/components/patterns/permissions-matrix.d.ts +19 -0
  56. package/dist/components/patterns/permissions-matrix.js +1 -0
  57. package/dist/components/patterns/public.d.ts +1 -0
  58. package/dist/index.d.ts +0 -2
  59. package/dist/src/components/actions/action-menu.cjs +1 -1
  60. package/dist/src/components/actions/action-menu.js +47 -39
  61. package/dist/src/components/actions/copy-button.cjs +1 -1
  62. package/dist/src/components/actions/copy-button.js +4 -1
  63. package/dist/src/components/calendar/calendar-range-scheduler.cjs +1 -0
  64. package/dist/src/components/calendar/calendar-range-scheduler.js +75 -0
  65. package/dist/src/components/calendar/date-picker.cjs +1 -1
  66. package/dist/src/components/calendar/date-picker.js +5 -8
  67. package/dist/src/components/calendar/date-range-picker.cjs +1 -1
  68. package/dist/src/components/calendar/date-range-picker.js +12 -27
  69. package/dist/src/components/calendar/date-time-picker.cjs +1 -0
  70. package/dist/src/components/calendar/date-time-picker.js +36 -0
  71. package/dist/src/components/calendar/index.cjs +1 -1
  72. package/dist/src/components/calendar/index.js +5 -3
  73. package/dist/src/components/charts/chart-card.cjs +1 -0
  74. package/dist/src/components/charts/chart-card.js +55 -0
  75. package/dist/src/components/charts/index.cjs +1 -1
  76. package/dist/src/components/charts/index.js +3 -2
  77. package/dist/src/components/command/advanced-command-menu.cjs +1 -0
  78. package/dist/src/components/command/advanced-command-menu.js +57 -0
  79. package/dist/src/components/command/index.cjs +1 -1
  80. package/dist/src/components/command/index.js +3 -2
  81. package/dist/src/components/data-table/data-grid.cjs +1 -0
  82. package/dist/src/components/data-table/data-grid.js +32 -0
  83. package/dist/src/components/data-table/data-table-saved-filters.cjs +1 -1
  84. package/dist/src/components/data-table/data-table-saved-filters.js +11 -11
  85. package/dist/src/components/data-table/data-table.cjs +1 -1
  86. package/dist/src/components/data-table/data-table.js +175 -174
  87. package/dist/src/components/data-table/index.cjs +1 -1
  88. package/dist/src/components/data-table/index.js +15 -14
  89. package/dist/src/components/data-table/public.cjs +1 -1
  90. package/dist/src/components/data-table/public.js +3 -2
  91. package/dist/src/components/display/index.cjs +1 -1
  92. package/dist/src/components/display/index.js +9 -11
  93. package/dist/src/components/feedback/async-boundary.cjs +1 -0
  94. package/dist/src/components/feedback/async-boundary.js +22 -0
  95. package/dist/src/components/feedback/index.cjs +1 -1
  96. package/dist/src/components/feedback/index.js +2 -1
  97. package/dist/src/components/filters/index.cjs +1 -1
  98. package/dist/src/components/filters/index.js +2 -3
  99. package/dist/src/components/inputs/color-picker.cjs +1 -1
  100. package/dist/src/components/inputs/color-picker.js +63 -46
  101. package/dist/src/components/inputs/index.cjs +1 -1
  102. package/dist/src/components/inputs/index.js +13 -12
  103. package/dist/src/components/inputs/multi-select.cjs +1 -0
  104. package/dist/src/components/inputs/multi-select.js +80 -0
  105. package/dist/src/components/inputs/public.cjs +1 -1
  106. package/dist/src/components/inputs/public.js +13 -12
  107. package/dist/src/components/inputs/slider.cjs +1 -1
  108. package/dist/src/components/inputs/slider.js +3 -3
  109. package/dist/src/components/inputs/tag-input.cjs +1 -1
  110. package/dist/src/components/inputs/tag-input.js +7 -4
  111. package/dist/src/components/inputs/time-picker.cjs +1 -1
  112. package/dist/src/components/inputs/time-picker.js +90 -34
  113. package/dist/src/components/modern/file-manager.cjs +1 -0
  114. package/dist/src/components/modern/file-manager.js +75 -0
  115. package/dist/src/components/modern/index.cjs +1 -1
  116. package/dist/src/components/modern/index.js +7 -4
  117. package/dist/src/components/modern/map.cjs +1 -0
  118. package/dist/src/components/modern/map.js +158 -0
  119. package/dist/src/components/modern/media-player.cjs +1 -0
  120. package/dist/src/components/modern/media-player.js +176 -0
  121. package/dist/src/components/modern/resizable-panel.cjs +1 -1
  122. package/dist/src/components/modern/resizable-panel.js +39 -32
  123. package/dist/src/components/modern/spreadsheet.cjs +1 -0
  124. package/dist/src/components/modern/spreadsheet.js +97 -0
  125. package/dist/src/components/navigation/pagination.cjs +1 -1
  126. package/dist/src/components/navigation/pagination.js +5 -5
  127. package/dist/src/components/notifications/action-toast.cjs +1 -0
  128. package/dist/src/components/notifications/action-toast.js +49 -0
  129. package/dist/src/components/notifications/index.cjs +1 -1
  130. package/dist/src/components/notifications/index.js +3 -2
  131. package/dist/src/components/notifications/toast.cjs +1 -1
  132. package/dist/src/components/notifications/toast.js +68 -63
  133. package/dist/src/components/overlay/alert-dialog.cjs +1 -1
  134. package/dist/src/components/overlay/alert-dialog.js +8 -6
  135. package/dist/src/components/overlay/drawer.cjs +1 -1
  136. package/dist/src/components/overlay/drawer.js +38 -20
  137. package/dist/src/components/patterns/index.cjs +1 -1
  138. package/dist/src/components/patterns/index.js +2 -1
  139. package/dist/src/components/patterns/permissions-matrix.cjs +1 -0
  140. package/dist/src/components/patterns/permissions-matrix.js +43 -0
  141. package/dist/src/components/patterns/public.cjs +1 -1
  142. package/dist/src/components/patterns/public.js +2 -1
  143. package/dist/src/components/ui/checkbox/index.cjs +1 -1
  144. package/dist/src/components/ui/checkbox/index.js +2 -1
  145. package/dist/src/components/ui/dialog/index.cjs +1 -1
  146. package/dist/src/components/ui/dialog/index.js +8 -8
  147. package/dist/src/components/ui/dropdown-menu/index.cjs +1 -1
  148. package/dist/src/components/ui/dropdown-menu/index.js +21 -19
  149. package/dist/src/components/ui/input/group.cjs +1 -1
  150. package/dist/src/components/ui/input/group.js +8 -8
  151. package/dist/src/components/ui/popover/index.cjs +1 -1
  152. package/dist/src/components/ui/popover/index.js +10 -10
  153. package/dist/src/components/ui/tabs/index.cjs +1 -1
  154. package/dist/src/components/ui/tabs/index.js +6 -6
  155. package/dist/src/components/ui/toggle-group/index.cjs +1 -1
  156. package/dist/src/components/ui/toggle-group/index.js +2 -2
  157. package/dist/src/index.cjs +1 -1
  158. package/dist/src/index.js +111 -104
  159. package/dist/src/public-component-surface.cjs +1 -1
  160. package/dist/src/public-component-surface.js +59 -14
  161. package/package.json +1 -2
  162. package/packages/cli/dist/index.cjs +45 -165
  163. package/packages/cli/vendor/src/components/actions/action-menu.tsx +47 -19
  164. package/packages/cli/vendor/src/components/actions/copy-button.tsx +2 -2
  165. package/packages/cli/vendor/src/components/calendar/calendar-range-scheduler.tsx +72 -0
  166. package/packages/cli/vendor/src/components/calendar/date-picker.tsx +2 -5
  167. package/packages/cli/vendor/src/components/calendar/date-range-picker.tsx +5 -12
  168. package/packages/cli/vendor/src/components/calendar/date-time-picker.tsx +53 -0
  169. package/packages/cli/vendor/src/components/calendar/index.ts +5 -3
  170. package/packages/cli/vendor/src/components/charts/chart-card.tsx +81 -0
  171. package/packages/cli/vendor/src/components/charts/index.ts +1 -0
  172. package/packages/cli/vendor/src/components/command/advanced-command-menu.tsx +88 -0
  173. package/packages/cli/vendor/src/components/command/index.ts +2 -1
  174. package/packages/cli/vendor/src/components/data-table/data-grid.tsx +59 -0
  175. package/packages/cli/vendor/src/components/data-table/data-table.tsx +45 -34
  176. package/packages/cli/vendor/src/components/data-table/index.ts +3 -2
  177. package/packages/cli/vendor/src/components/data-table/public.ts +1 -0
  178. package/packages/cli/vendor/src/components/display/index.ts +3 -5
  179. package/packages/cli/vendor/src/components/feedback/async-boundary.tsx +36 -0
  180. package/packages/cli/vendor/src/components/feedback/index.ts +1 -0
  181. package/packages/cli/vendor/src/components/filters/index.ts +0 -1
  182. package/packages/cli/vendor/src/components/inputs/color-picker.tsx +15 -1
  183. package/packages/cli/vendor/src/components/inputs/index.ts +1 -0
  184. package/packages/cli/vendor/src/components/inputs/multi-select.tsx +75 -0
  185. package/packages/cli/vendor/src/components/inputs/public.ts +1 -0
  186. package/packages/cli/vendor/src/components/inputs/slider.tsx +3 -3
  187. package/packages/cli/vendor/src/components/inputs/tag-input.tsx +4 -4
  188. package/packages/cli/vendor/src/components/inputs/time-picker.tsx +91 -38
  189. package/packages/cli/vendor/src/components/modern/file-manager.tsx +79 -0
  190. package/packages/cli/vendor/src/components/modern/index.ts +4 -1
  191. package/packages/cli/vendor/src/components/modern/map.tsx +146 -0
  192. package/packages/cli/vendor/src/components/modern/media-player.tsx +182 -0
  193. package/packages/cli/vendor/src/components/modern/resizable-panel.tsx +15 -5
  194. package/packages/cli/vendor/src/components/modern/spreadsheet.tsx +155 -0
  195. package/packages/cli/vendor/src/components/navigation/pagination.tsx +9 -5
  196. package/packages/cli/vendor/src/components/notifications/action-toast.tsx +78 -0
  197. package/packages/cli/vendor/src/components/notifications/index.ts +2 -1
  198. package/packages/cli/vendor/src/components/notifications/toast.tsx +20 -10
  199. package/packages/cli/vendor/src/components/overlay/alert-dialog.tsx +3 -5
  200. package/packages/cli/vendor/src/components/overlay/drawer.tsx +22 -11
  201. package/packages/cli/vendor/src/components/patterns/index.ts +1 -0
  202. package/packages/cli/vendor/src/components/patterns/permissions-matrix.tsx +71 -0
  203. package/packages/cli/vendor/src/components/patterns/public.ts +1 -0
  204. package/packages/cli/vendor/src/components/ui/checkbox/index.tsx +3 -1
  205. package/packages/cli/vendor/src/components/ui/dialog/index.tsx +4 -4
  206. package/packages/cli/vendor/src/components/ui/dropdown-menu/index.tsx +18 -22
  207. package/packages/cli/vendor/src/components/ui/popover/index.tsx +4 -4
  208. package/packages/cli/vendor/src/components/ui/tabs/index.tsx +8 -3
  209. package/packages/cli/vendor/src/components/ui/toggle-group/index.tsx +6 -6
  210. package/packages/cli/vendor/src/index.ts +0 -2
  211. package/packages/cli/vendor/src/public-component-surface.ts +13 -4
  212. package/packages/cli/vendor/templates/showcase/src/showcase/data/registry.ts +6 -6
  213. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +23 -8
  214. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  215. package/packages/cli/vendor/templates/showcase/src/showcase/sections/PatternsSection.tsx +1 -1
  216. package/packages/cli/vendor/templates/styles/globals.css +0 -120
  217. package/registry.json +3197 -2794
  218. package/dist/components/display/info-card.cjs +0 -1
  219. package/dist/components/display/info-card.d.ts +0 -51
  220. package/dist/components/display/info-card.js +0 -1
  221. package/dist/components/display/statistic.cjs +0 -1
  222. package/dist/components/display/statistic.d.ts +0 -25
  223. package/dist/components/display/statistic.js +0 -1
  224. package/dist/components/filters/filter-bar.cjs +0 -1
  225. package/dist/components/filters/filter-bar.d.ts +0 -28
  226. package/dist/components/filters/filter-bar.js +0 -1
  227. package/dist/components/modern/image-cropper.cjs +0 -1
  228. package/dist/components/modern/image-cropper.d.ts +0 -39
  229. package/dist/components/modern/image-cropper.js +0 -1
  230. package/dist/components/ui/number-field/index.cjs +0 -1
  231. package/dist/components/ui/number-field/index.d.ts +0 -18
  232. package/dist/components/ui/number-field/index.js +0 -1
  233. package/dist/components/ui/toolbar/index.cjs +0 -1
  234. package/dist/components/ui/toolbar/index.d.ts +0 -14
  235. package/dist/components/ui/toolbar/index.js +0 -1
  236. package/dist/src/components/display/info-card.cjs +0 -1
  237. package/dist/src/components/display/info-card.js +0 -124
  238. package/dist/src/components/display/statistic.cjs +0 -1
  239. package/dist/src/components/display/statistic.js +0 -104
  240. package/dist/src/components/filters/filter-bar.cjs +0 -1
  241. package/dist/src/components/filters/filter-bar.js +0 -103
  242. package/dist/src/components/modern/image-cropper.cjs +0 -1
  243. package/dist/src/components/modern/image-cropper.js +0 -143
  244. package/dist/src/components/ui/number-field/index.cjs +0 -1
  245. package/dist/src/components/ui/number-field/index.js +0 -73
  246. package/dist/src/components/ui/toolbar/index.cjs +0 -1
  247. package/dist/src/components/ui/toolbar/index.js +0 -50
  248. package/packages/cli/vendor/src/components/display/info-card.tsx +0 -195
  249. package/packages/cli/vendor/src/components/display/statistic.tsx +0 -114
  250. package/packages/cli/vendor/src/components/filters/filter-bar.tsx +0 -163
  251. package/packages/cli/vendor/src/components/modern/image-cropper.tsx +0 -226
  252. package/packages/cli/vendor/src/components/ui/number-field/index.tsx +0 -89
  253. package/packages/cli/vendor/src/components/ui/toolbar/index.tsx +0 -67
@@ -1,163 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { SlidersHorizontalIcon, XIcon } from "lucide-react"
5
-
6
- import { Badge } from "@/components/ui/badge"
7
- import { Button } from "@/components/ui/button"
8
- import { cn, stopInteractivePropagation } from "@/lib/utils"
9
-
10
- export type FilterBarChip = {
11
- key: string
12
- label: React.ReactNode
13
- value?: React.ReactNode
14
- tone?: "default" | "success" | "warning" | "danger" | "info" | "muted"
15
- disabled?: boolean
16
- hidden?: boolean
17
- }
18
-
19
- export type FilterBarProps = React.ComponentProps<"div"> & {
20
- search?: React.ReactNode
21
- filters?: React.ReactNode
22
- actions?: React.ReactNode
23
- chips?: FilterBarChip[]
24
- activeCount?: number
25
- activeLabel?: (count: number) => React.ReactNode
26
- resetLabel?: React.ReactNode
27
- clearChipLabel?: React.ReactNode
28
- onReset?: () => void
29
- onRemoveChip?: (key: string) => void
30
- collapsible?: boolean
31
- defaultExpanded?: boolean
32
- emptyChips?: React.ReactNode
33
- chipLimit?: number
34
- chipOverflowLabel?: (hiddenCount: number) => React.ReactNode
35
- }
36
-
37
- const chipVariant: Record<NonNullable<FilterBarChip["tone"]>, React.ComponentProps<typeof Badge>["variant"]> = {
38
- default: "secondary",
39
- success: "secondary",
40
- warning: "outline",
41
- danger: "destructive",
42
- info: "outline",
43
- muted: "outline",
44
- }
45
-
46
- function FilterBar({
47
- className,
48
- search,
49
- filters,
50
- actions,
51
- chips,
52
- activeCount,
53
- activeLabel = (count) => `${count} active`,
54
- resetLabel = "Reset",
55
- clearChipLabel,
56
- onReset,
57
- onRemoveChip,
58
- collapsible = false,
59
- defaultExpanded = false,
60
- emptyChips,
61
- chipLimit,
62
- chipOverflowLabel = (hiddenCount) => `+${hiddenCount} more`,
63
- children,
64
- ...props
65
- }: FilterBarProps) {
66
- const [expanded, setExpanded] = React.useState(defaultExpanded)
67
- const visibleChips = chips?.filter((chip) => !chip.hidden) ?? []
68
- const renderedChips = chipLimit && chipLimit > 0 ? visibleChips.slice(0, chipLimit) : visibleChips
69
- const hiddenChipCount = Math.max(visibleChips.length - renderedChips.length, 0)
70
- const resolvedActiveCount = activeCount ?? visibleChips.length
71
- const hasFilters = Boolean(filters || children)
72
- const hasChips = Boolean(chips)
73
- const shouldShowFilters = !collapsible || expanded
74
- const shouldShowChipRow = visibleChips.length > 0 || (hasChips && emptyChips)
75
-
76
- return (
77
- <div
78
- data-slot="filter-bar"
79
- className={cn("flex flex-col gap-3 rounded-lg border bg-card p-3", className)}
80
- {...props}
81
- >
82
- <div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
83
- <div className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
84
- {search && <div className="min-w-0 flex-1">{search}</div>}
85
-
86
- {collapsible && hasFilters && (
87
- <Button
88
- type="button"
89
- variant={expanded ? "secondary" : "outline"}
90
- size="sm"
91
- onClick={() => setExpanded((value) => !value)}
92
- >
93
- <SlidersHorizontalIcon data-icon="inline-start" />
94
- Filters
95
- {resolvedActiveCount > 0 && (
96
- <span className="ml-1 rounded-full bg-background px-1.5 text-xs">
97
- {resolvedActiveCount}
98
- </span>
99
- )}
100
- </Button>
101
- )}
102
- </div>
103
-
104
- <div className="flex shrink-0 flex-wrap items-center gap-2">
105
- {resolvedActiveCount > 0 && (
106
- <span className="text-sm text-muted-foreground">{activeLabel(resolvedActiveCount)}</span>
107
- )}
108
- {resolvedActiveCount > 0 && onReset && (
109
- <Button type="button" variant="ghost" size="sm" onClick={onReset}>
110
- <XIcon data-icon="inline-start" />
111
- {resetLabel}
112
- </Button>
113
- )}
114
- {actions}
115
- </div>
116
- </div>
117
-
118
- {hasFilters && shouldShowFilters && (
119
- <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center">
120
- {filters}
121
- {children}
122
- </div>
123
- )}
124
-
125
- {shouldShowChipRow && (
126
- <div data-slot="filter-bar-chips" className="flex flex-wrap items-center gap-2">
127
- {visibleChips.length > 0 ? (
128
- <>
129
- {renderedChips.map((chip) => (
130
- <Badge key={chip.key} variant={chipVariant[chip.tone ?? "default"]} className={cn("gap-1.5", chip.disabled && "opacity-60")}>
131
- <span>{chip.label}</span>
132
- {chip.value !== undefined && <span className="text-muted-foreground">{chip.value}</span>}
133
- {onRemoveChip && !chip.disabled && (
134
- <button
135
- type="button"
136
- aria-label={typeof clearChipLabel === "string" ? clearChipLabel : "Remove filter"}
137
- className="rounded-full p-0.5 hover:bg-muted"
138
- onClick={(event) => {
139
- stopInteractivePropagation(event)
140
- onRemoveChip(chip.key)
141
- }}
142
- onMouseDown={stopInteractivePropagation}
143
- onDoubleClick={stopInteractivePropagation}
144
- >
145
- <XIcon className="size-3" />
146
- </button>
147
- )}
148
- </Badge>
149
- ))}
150
- {hiddenChipCount > 0 ? (
151
- <Badge variant="outline">{chipOverflowLabel(hiddenChipCount)}</Badge>
152
- ) : null}
153
- </>
154
- ) : (
155
- <span className="text-sm text-muted-foreground">{emptyChips}</span>
156
- )}
157
- </div>
158
- )}
159
- </div>
160
- )
161
- }
162
-
163
- export { FilterBar }
@@ -1,226 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import Cropper, { type Area, type Point } from "react-easy-crop"
5
- import { RotateCwIcon, ZoomInIcon } from "lucide-react"
6
-
7
- import { cn } from "@/lib/utils"
8
-
9
- function degreesToRadians(degrees: number) {
10
- return (degrees * Math.PI) / 180
11
- }
12
-
13
- function getRotatedSize(width: number, height: number, rotation: number) {
14
- const radians = degreesToRadians(rotation)
15
- return {
16
- width: Math.abs(Math.cos(radians) * width) + Math.abs(Math.sin(radians) * height),
17
- height: Math.abs(Math.sin(radians) * width) + Math.abs(Math.cos(radians) * height),
18
- }
19
- }
20
-
21
- function loadImage(src: string, crossOrigin?: string) {
22
- return new Promise<HTMLImageElement>((resolve, reject) => {
23
- const image = new Image()
24
- if (crossOrigin) image.crossOrigin = crossOrigin
25
- image.onload = () => resolve(image)
26
- image.onerror = () => reject(new Error("Crop source image could not be loaded"))
27
- image.src = src
28
- })
29
- }
30
-
31
- export async function createCroppedImageBlob({
32
- src,
33
- crop,
34
- rotation = 0,
35
- type = "image/png",
36
- quality = 0.92,
37
- crossOrigin = "anonymous",
38
- }: {
39
- src: string
40
- crop: Area
41
- rotation?: number
42
- type?: string
43
- quality?: number
44
- crossOrigin?: string
45
- }) {
46
- const image = await loadImage(src, crossOrigin)
47
- const rotatedSize = getRotatedSize(image.naturalWidth, image.naturalHeight, rotation)
48
- const sourceCanvas = document.createElement("canvas")
49
- sourceCanvas.width = Math.ceil(rotatedSize.width)
50
- sourceCanvas.height = Math.ceil(rotatedSize.height)
51
- const sourceContext = sourceCanvas.getContext("2d")
52
- if (!sourceContext) throw new Error("Canvas 2D context is unavailable")
53
-
54
- sourceContext.translate(sourceCanvas.width / 2, sourceCanvas.height / 2)
55
- sourceContext.rotate(degreesToRadians(rotation))
56
- sourceContext.translate(-image.naturalWidth / 2, -image.naturalHeight / 2)
57
- sourceContext.drawImage(image, 0, 0)
58
-
59
- const outputCanvas = document.createElement("canvas")
60
- outputCanvas.width = Math.max(1, Math.round(crop.width))
61
- outputCanvas.height = Math.max(1, Math.round(crop.height))
62
- const outputContext = outputCanvas.getContext("2d")
63
- if (!outputContext) throw new Error("Canvas 2D context is unavailable")
64
- outputContext.drawImage(
65
- sourceCanvas,
66
- Math.round(crop.x),
67
- Math.round(crop.y),
68
- Math.round(crop.width),
69
- Math.round(crop.height),
70
- 0,
71
- 0,
72
- outputCanvas.width,
73
- outputCanvas.height
74
- )
75
-
76
- return new Promise<Blob>((resolve, reject) => {
77
- outputCanvas.toBlob(
78
- (blob) => (blob ? resolve(blob) : reject(new Error("Cropped image could not be encoded"))),
79
- type,
80
- quality
81
- )
82
- })
83
- }
84
-
85
- export type ImageCropperProps = Omit<React.ComponentProps<"div">, "onChange"> & {
86
- src: string
87
- alt?: string
88
- crop?: Point
89
- defaultCrop?: Point
90
- onCropChange?: (crop: Point) => void
91
- zoom?: number
92
- defaultZoom?: number
93
- onZoomChange?: (zoom: number) => void
94
- rotation?: number
95
- defaultRotation?: number
96
- onRotationChange?: (rotation: number) => void
97
- onCropComplete?: (area: Area, areaPixels: Area) => void
98
- aspect?: number
99
- cropShape?: "rect" | "round"
100
- objectFit?: "contain" | "cover" | "horizontal-cover" | "vertical-cover"
101
- minZoom?: number
102
- maxZoom?: number
103
- zoomStep?: number
104
- showGrid?: boolean
105
- disabled?: boolean
106
- labels?: {
107
- cropper?: string
108
- zoom?: string
109
- rotation?: string
110
- }
111
- }
112
-
113
- function ImageCropper({
114
- src,
115
- alt = "Image crop preview",
116
- crop,
117
- defaultCrop = { x: 0, y: 0 },
118
- onCropChange,
119
- zoom,
120
- defaultZoom = 1,
121
- onZoomChange,
122
- rotation,
123
- defaultRotation = 0,
124
- onRotationChange,
125
- onCropComplete,
126
- aspect = 1,
127
- cropShape = "rect",
128
- objectFit = "contain",
129
- minZoom = 1,
130
- maxZoom = 3,
131
- zoomStep = 0.1,
132
- showGrid = true,
133
- disabled = false,
134
- labels,
135
- className,
136
- ...props
137
- }: ImageCropperProps) {
138
- const [internalCrop, setInternalCrop] = React.useState(defaultCrop)
139
- const [internalZoom, setInternalZoom] = React.useState(defaultZoom)
140
- const [internalRotation, setInternalRotation] = React.useState(defaultRotation)
141
- const currentCrop = crop ?? internalCrop
142
- const currentZoom = zoom ?? internalZoom
143
- const currentRotation = rotation ?? internalRotation
144
-
145
- return (
146
- <div data-slot="image-cropper" data-disabled={disabled || undefined} className={cn("grid gap-3 data-[disabled]:opacity-60", className)} {...props}>
147
- <div
148
- role="img"
149
- aria-label={labels?.cropper ?? alt}
150
- className={cn("relative min-h-64 overflow-hidden rounded-md bg-black", disabled && "pointer-events-none")}
151
- style={{ aspectRatio: aspect }}
152
- >
153
- <Cropper
154
- image={src}
155
- crop={currentCrop}
156
- zoom={currentZoom}
157
- rotation={currentRotation}
158
- aspect={aspect}
159
- cropShape={cropShape}
160
- objectFit={objectFit}
161
- minZoom={minZoom}
162
- maxZoom={maxZoom}
163
- zoomSpeed={zoomStep}
164
- showGrid={showGrid}
165
- onCropChange={(nextCrop) => {
166
- if (crop === undefined) setInternalCrop(nextCrop)
167
- onCropChange?.(nextCrop)
168
- }}
169
- onZoomChange={(nextZoom) => {
170
- if (zoom === undefined) setInternalZoom(nextZoom)
171
- onZoomChange?.(nextZoom)
172
- }}
173
- onRotationChange={(nextRotation) => {
174
- if (rotation === undefined) setInternalRotation(nextRotation)
175
- onRotationChange?.(nextRotation)
176
- }}
177
- onCropComplete={onCropComplete}
178
- />
179
- </div>
180
- <div className="grid gap-2 sm:grid-cols-2">
181
- <label className="grid grid-cols-[auto_1fr_auto] items-center gap-2 text-xs text-muted-foreground">
182
- <ZoomInIcon className="size-4" aria-hidden="true" />
183
- <span className="sr-only">{labels?.zoom ?? "Zoom"}</span>
184
- <input
185
- type="range"
186
- min={minZoom}
187
- max={maxZoom}
188
- step={zoomStep}
189
- value={currentZoom}
190
- disabled={disabled}
191
- aria-label={labels?.zoom ?? "Zoom"}
192
- className="w-full accent-foreground"
193
- onChange={(event) => {
194
- const nextZoom = Number(event.target.value)
195
- if (zoom === undefined) setInternalZoom(nextZoom)
196
- onZoomChange?.(nextZoom)
197
- }}
198
- />
199
- <span className="w-9 text-right tabular-nums">{currentZoom.toFixed(1)}x</span>
200
- </label>
201
- <label className="grid grid-cols-[auto_1fr_auto] items-center gap-2 text-xs text-muted-foreground">
202
- <RotateCwIcon className="size-4" aria-hidden="true" />
203
- <span className="sr-only">{labels?.rotation ?? "Rotation"}</span>
204
- <input
205
- type="range"
206
- min="-180"
207
- max="180"
208
- step="1"
209
- value={currentRotation}
210
- disabled={disabled}
211
- aria-label={labels?.rotation ?? "Rotation"}
212
- className="w-full accent-foreground"
213
- onChange={(event) => {
214
- const nextRotation = Number(event.target.value)
215
- if (rotation === undefined) setInternalRotation(nextRotation)
216
- onRotationChange?.(nextRotation)
217
- }}
218
- />
219
- <span className="w-10 text-right tabular-nums">{Math.round(currentRotation)}°</span>
220
- </label>
221
- </div>
222
- </div>
223
- )
224
- }
225
-
226
- export { ImageCropper }
@@ -1,89 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { NumberField as NumberFieldPrimitive } from "@base-ui/react/number-field"
5
- import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
-
7
- import { cn } from "@/lib/utils"
8
-
9
- export type NumberFieldProps = NumberFieldPrimitive.Root.Props
10
- export type NumberFieldGroupProps = NumberFieldPrimitive.Group.Props
11
- export type NumberFieldInputProps = NumberFieldPrimitive.Input.Props
12
- export type NumberFieldIncrementProps = NumberFieldPrimitive.Increment.Props
13
- export type NumberFieldDecrementProps = NumberFieldPrimitive.Decrement.Props
14
- export type NumberFieldScrubAreaProps = NumberFieldPrimitive.ScrubArea.Props
15
- export type NumberFieldScrubAreaCursorProps = NumberFieldPrimitive.ScrubAreaCursor.Props
16
-
17
- function NumberField({ className, ...props }: NumberFieldProps) {
18
- return <NumberFieldPrimitive.Root data-slot="number-field" className={cn("grid gap-2", className)} {...props} />
19
- }
20
-
21
- function NumberFieldGroup({ className, ...props }: NumberFieldGroupProps) {
22
- return (
23
- <NumberFieldPrimitive.Group
24
- data-slot="number-field-group"
25
- className={cn(
26
- "flex min-h-10 w-full overflow-hidden rounded-[var(--radius-md)] border border-input bg-background text-foreground shadow-xs transition-[border-color,box-shadow] focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/35 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50",
27
- className
28
- )}
29
- {...props}
30
- />
31
- )
32
- }
33
-
34
- function NumberFieldInput({ className, ...props }: NumberFieldInputProps) {
35
- return (
36
- <NumberFieldPrimitive.Input
37
- data-slot="number-field-input"
38
- className={cn("min-w-0 flex-1 bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed", className)}
39
- {...props}
40
- />
41
- )
42
- }
43
-
44
- function NumberFieldIncrement({ className, children, ...props }: NumberFieldIncrementProps) {
45
- return (
46
- <NumberFieldPrimitive.Increment
47
- data-slot="number-field-increment"
48
- className={cn("flex h-5 w-8 items-center justify-center border-l border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40", className)}
49
- {...props}
50
- >
51
- {children ?? <ChevronUpIcon aria-hidden="true" className="size-3.5" />}
52
- </NumberFieldPrimitive.Increment>
53
- )
54
- }
55
-
56
- function NumberFieldDecrement({ className, children, ...props }: NumberFieldDecrementProps) {
57
- return (
58
- <NumberFieldPrimitive.Decrement
59
- data-slot="number-field-decrement"
60
- className={cn("flex h-5 w-8 items-center justify-center border-l border-t border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40", className)}
61
- {...props}
62
- >
63
- {children ?? <ChevronDownIcon aria-hidden="true" className="size-3.5" />}
64
- </NumberFieldPrimitive.Decrement>
65
- )
66
- }
67
-
68
- function NumberFieldStepper({ className, ...props }: React.ComponentProps<"div">) {
69
- return <div data-slot="number-field-stepper" className={cn("grid shrink-0 grid-rows-2", className)} {...props} />
70
- }
71
-
72
- function NumberFieldScrubArea({ className, ...props }: NumberFieldScrubAreaProps) {
73
- return <NumberFieldPrimitive.ScrubArea data-slot="number-field-scrub-area" className={cn("cursor-ew-resize select-none", className)} {...props} />
74
- }
75
-
76
- function NumberFieldScrubAreaCursor({ className, ...props }: NumberFieldScrubAreaCursorProps) {
77
- return <NumberFieldPrimitive.ScrubAreaCursor data-slot="number-field-scrub-area-cursor" className={cn("fixed z-50 rounded bg-foreground px-2 py-1 text-xs text-background shadow-lg", className)} {...props} />
78
- }
79
-
80
- export {
81
- NumberField,
82
- NumberFieldDecrement,
83
- NumberFieldGroup,
84
- NumberFieldIncrement,
85
- NumberFieldInput,
86
- NumberFieldScrubArea,
87
- NumberFieldScrubAreaCursor,
88
- NumberFieldStepper,
89
- }
@@ -1,67 +0,0 @@
1
- "use client"
2
-
3
- import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar"
4
-
5
- import { cn } from "@/lib/utils"
6
-
7
- export type ToolbarProps = ToolbarPrimitive.Root.Props
8
- export type ToolbarGroupProps = ToolbarPrimitive.Group.Props
9
- export type ToolbarButtonProps = ToolbarPrimitive.Button.Props
10
- export type ToolbarLinkProps = ToolbarPrimitive.Link.Props
11
- export type ToolbarInputProps = ToolbarPrimitive.Input.Props
12
- export type ToolbarSeparatorProps = ToolbarPrimitive.Separator.Props
13
-
14
- function Toolbar({ className, orientation = "horizontal", ...props }: ToolbarProps) {
15
- return (
16
- <ToolbarPrimitive.Root
17
- data-slot="toolbar"
18
- orientation={orientation}
19
- className={cn(
20
- "flex min-w-0 items-center gap-1 rounded-[var(--radius-lg)] border border-border bg-card p-1 text-card-foreground shadow-xs",
21
- orientation === "vertical" && "w-max flex-col items-stretch",
22
- className
23
- )}
24
- {...props}
25
- />
26
- )
27
- }
28
-
29
- function ToolbarGroup({ className, ...props }: ToolbarGroupProps) {
30
- return <ToolbarPrimitive.Group data-slot="toolbar-group" className={cn("flex min-w-0 items-center gap-1", className)} {...props} />
31
- }
32
-
33
- function ToolbarButton({ className, ...props }: ToolbarButtonProps) {
34
- return (
35
- <ToolbarPrimitive.Button
36
- data-slot="toolbar-button"
37
- className={cn("inline-flex h-8 min-w-8 items-center justify-center gap-1.5 rounded-[var(--radius-md)] px-2.5 text-sm font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[pressed]:bg-muted data-[pressed]:text-foreground [&_svg]:size-4 [&_svg]:shrink-0", className)}
38
- {...props}
39
- />
40
- )
41
- }
42
-
43
- function ToolbarLink({ className, ...props }: ToolbarLinkProps) {
44
- return (
45
- <ToolbarPrimitive.Link
46
- data-slot="toolbar-link"
47
- className={cn("inline-flex h-8 items-center justify-center gap-1.5 rounded-[var(--radius-md)] px-2.5 text-sm font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring", className)}
48
- {...props}
49
- />
50
- )
51
- }
52
-
53
- function ToolbarInput({ className, ...props }: ToolbarInputProps) {
54
- return (
55
- <ToolbarPrimitive.Input
56
- data-slot="toolbar-input"
57
- className={cn("h-8 min-w-32 rounded-[var(--radius-md)] border border-input bg-background px-2.5 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring", className)}
58
- {...props}
59
- />
60
- )
61
- }
62
-
63
- function ToolbarSeparator({ className, ...props }: ToolbarSeparatorProps) {
64
- return <ToolbarPrimitive.Separator data-slot="toolbar-separator" className={cn("mx-1 h-5 w-px bg-border data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full", className)} {...props} />
65
- }
66
-
67
- export { Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput, ToolbarLink, ToolbarSeparator }