@comitor/ui 1.11.0 → 2.1.0

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 (176) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +23 -7
  3. package/dist/components/ui/action-bar-constants.d.ts +31 -0
  4. package/dist/components/ui/action-bar-constants.js +9 -0
  5. package/dist/components/ui/action-bar-constants.js.map +1 -0
  6. package/dist/components/ui/action-bar.d.ts +67 -0
  7. package/dist/components/ui/action-bar.js +522 -0
  8. package/dist/components/ui/action-bar.js.map +1 -0
  9. package/dist/components/ui/badge-overflow.d.ts +30 -0
  10. package/dist/components/ui/badge-overflow.js +179 -0
  11. package/dist/components/ui/badge-overflow.js.map +1 -0
  12. package/dist/components/ui/banner.d.ts +79 -0
  13. package/dist/components/ui/banner.js +518 -0
  14. package/dist/components/ui/banner.js.map +1 -0
  15. package/dist/components/ui/circular-progress.d.ts +50 -0
  16. package/dist/components/ui/circular-progress.js +266 -0
  17. package/dist/components/ui/circular-progress.js.map +1 -0
  18. package/dist/components/ui/cropper-constants.d.ts +34 -0
  19. package/dist/components/ui/cropper-constants.js +10 -0
  20. package/dist/components/ui/cropper-constants.js.map +1 -0
  21. package/dist/components/ui/cropper.d.ts +106 -0
  22. package/dist/components/ui/cropper.js +1269 -0
  23. package/dist/components/ui/cropper.js.map +1 -0
  24. package/dist/components/ui/drawer.d.ts +1 -1
  25. package/dist/components/ui/drawer.js +2 -2
  26. package/dist/components/ui/drawer.js.map +1 -1
  27. package/dist/components/ui/kanban-constants.d.ts +61 -0
  28. package/dist/components/ui/kanban-constants.js +21 -0
  29. package/dist/components/ui/kanban-constants.js.map +1 -0
  30. package/dist/components/ui/kanban.d.ts +72 -0
  31. package/dist/components/ui/kanban.js +787 -0
  32. package/dist/components/ui/kanban.js.map +1 -0
  33. package/dist/components/ui/key-value-constants.d.ts +33 -0
  34. package/dist/components/ui/key-value-constants.js +11 -0
  35. package/dist/components/ui/key-value-constants.js.map +1 -0
  36. package/dist/components/ui/key-value.d.ts +96 -0
  37. package/dist/components/ui/key-value.js +641 -0
  38. package/dist/components/ui/key-value.js.map +1 -0
  39. package/dist/components/ui/mask-input-constants.d.ts +99 -0
  40. package/dist/components/ui/mask-input-constants.js +601 -0
  41. package/dist/components/ui/mask-input-constants.js.map +1 -0
  42. package/dist/components/ui/mask-input.d.ts +36 -0
  43. package/dist/components/ui/mask-input.js +558 -0
  44. package/dist/components/ui/mask-input.js.map +1 -0
  45. package/dist/components/ui/mention.d.ts +31 -0
  46. package/dist/components/ui/mention.js +128 -0
  47. package/dist/components/ui/mention.js.map +1 -0
  48. package/dist/components/ui/phone-input-constants.d.ts +54 -0
  49. package/dist/components/ui/phone-input-constants.js +9 -0
  50. package/dist/components/ui/phone-input-constants.js.map +1 -0
  51. package/dist/components/ui/phone-input.d.ts +85 -0
  52. package/dist/components/ui/phone-input.js +743 -0
  53. package/dist/components/ui/phone-input.js.map +1 -0
  54. package/dist/components/ui/rating.d.ts +56 -0
  55. package/dist/components/ui/rating.js +646 -0
  56. package/dist/components/ui/rating.js.map +1 -0
  57. package/dist/components/ui/responsive-dialog.d.ts +35 -0
  58. package/dist/components/ui/responsive-dialog.js +193 -0
  59. package/dist/components/ui/responsive-dialog.js.map +1 -0
  60. package/dist/components/ui/scroll-spy.d.ts +42 -0
  61. package/dist/components/ui/scroll-spy.js +334 -0
  62. package/dist/components/ui/scroll-spy.js.map +1 -0
  63. package/dist/components/ui/stepper-constants.d.ts +29 -0
  64. package/dist/components/ui/stepper-constants.js +9 -0
  65. package/dist/components/ui/stepper-constants.js.map +1 -0
  66. package/dist/components/ui/stepper.d.ts +80 -0
  67. package/dist/components/ui/stepper.js +902 -0
  68. package/dist/components/ui/stepper.js.map +1 -0
  69. package/dist/components/ui/tags-input.d.ts +41 -0
  70. package/dist/components/ui/tags-input.js +127 -0
  71. package/dist/components/ui/tags-input.js.map +1 -0
  72. package/dist/components/ui/time-picker-constants.d.ts +48 -0
  73. package/dist/components/ui/time-picker-constants.js +14 -0
  74. package/dist/components/ui/time-picker-constants.js.map +1 -0
  75. package/dist/components/ui/time-picker.d.ts +115 -0
  76. package/dist/components/ui/time-picker.js +1620 -0
  77. package/dist/components/ui/time-picker.js.map +1 -0
  78. package/dist/components/ui/timeline.d.ts +32 -0
  79. package/dist/components/ui/timeline.js +559 -0
  80. package/dist/components/ui/timeline.js.map +1 -0
  81. package/dist/components/ui/tour-constants.d.ts +39 -0
  82. package/dist/components/ui/tour-constants.js +12 -0
  83. package/dist/components/ui/tour-constants.js.map +1 -0
  84. package/dist/components/ui/tour.d.ts +124 -0
  85. package/dist/components/ui/tour.js +1207 -0
  86. package/dist/components/ui/tour.js.map +1 -0
  87. package/dist/data-grid/data-grid-cell-variants.js +10 -6
  88. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  89. package/dist/data-grid/data-grid-cell-wrapper.js +1 -1
  90. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  91. package/dist/data-grid/data-grid-column-header.js +4 -2
  92. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  93. package/dist/data-grid/data-grid-context-menu.js +5 -4
  94. package/dist/data-grid/data-grid-context-menu.js.map +1 -1
  95. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -1
  96. package/dist/data-grid/data-grid-filter-menu.js +28 -17
  97. package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
  98. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +12 -1
  99. package/dist/data-grid/data-grid-keyboard-shortcuts.js +7 -5
  100. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
  101. package/dist/data-grid/data-grid-locale.d.ts +24 -0
  102. package/dist/data-grid/data-grid-locale.js +25 -0
  103. package/dist/data-grid/data-grid-locale.js.map +1 -0
  104. package/dist/data-grid/data-grid-paste-dialog.js +3 -2
  105. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
  106. package/dist/data-grid/data-grid-row-height-menu.d.ts +11 -1
  107. package/dist/data-grid/data-grid-row-height-menu.js +25 -28
  108. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
  109. package/dist/data-grid/data-grid-row.js +1 -1
  110. package/dist/data-grid/data-grid-row.js.map +1 -1
  111. package/dist/data-grid/data-grid-search.js +6 -5
  112. package/dist/data-grid/data-grid-search.js.map +1 -1
  113. package/dist/data-grid/data-grid-select-column.js +3 -1
  114. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  115. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -1
  116. package/dist/data-grid/data-grid-sort-menu.js +25 -13
  117. package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
  118. package/dist/data-grid/data-grid-view-menu.d.ts +12 -1
  119. package/dist/data-grid/data-grid-view-menu.js +12 -4
  120. package/dist/data-grid/data-grid-view-menu.js.map +1 -1
  121. package/dist/data-grid/data-grid.d.ts +14 -2
  122. package/dist/data-grid/data-grid.js +7 -4
  123. package/dist/data-grid/data-grid.js.map +1 -1
  124. package/dist/data-grid/filters.d.ts +17 -21
  125. package/dist/data-grid/filters.js +73 -52
  126. package/dist/data-grid/filters.js.map +1 -1
  127. package/dist/data-grid/index.d.ts +4 -1
  128. package/dist/data-grid/index.js +20 -2
  129. package/dist/data-grid/index.js.map +1 -1
  130. package/dist/data-grid/messages-en.d.ts +294 -0
  131. package/dist/data-grid/messages-en.js +279 -0
  132. package/dist/data-grid/messages-en.js.map +1 -0
  133. package/dist/data-grid/messages-vi.d.ts +12 -0
  134. package/dist/data-grid/messages-vi.js +279 -0
  135. package/dist/data-grid/messages-vi.js.map +1 -0
  136. package/dist/data-grid/messages.d.ts +549 -262
  137. package/dist/data-grid/messages.js +16 -275
  138. package/dist/data-grid/messages.js.map +1 -1
  139. package/dist/data-grid/sortable.js +1 -1
  140. package/dist/data-grid/sortable.js.map +1 -1
  141. package/dist/data-grid/use-data-grid-undo-redo.d.ts +13 -1
  142. package/dist/data-grid/use-data-grid-undo-redo.js +13 -10
  143. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
  144. package/dist/data-grid/use-data-grid.d.ts +281 -1
  145. package/dist/data-grid/use-data-grid.js +24 -19
  146. package/dist/data-grid/use-data-grid.js.map +1 -1
  147. package/dist/data-grid/utils.d.ts +9 -1
  148. package/dist/data-grid/utils.js +3 -2
  149. package/dist/data-grid/utils.js.map +1 -1
  150. package/dist/index.d.ts +31 -0
  151. package/dist/index.js +42 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/lib/compose-refs.js.map +1 -0
  154. package/dist/lib/use-as-ref.js.map +1 -0
  155. package/dist/lib/use-is-mobile.d.ts +10 -0
  156. package/dist/lib/use-is-mobile.js +21 -0
  157. package/dist/lib/use-is-mobile.js.map +1 -0
  158. package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
  159. package/dist/lib/use-lazy-ref.js.map +1 -0
  160. package/dist/lib/visually-hidden-input.d.ts +12 -0
  161. package/dist/lib/visually-hidden-input.js +105 -0
  162. package/dist/lib/visually-hidden-input.js.map +1 -0
  163. package/package.json +5 -1
  164. package/styles.css +19 -0
  165. package/dist/data-grid/compose-refs.js.map +0 -1
  166. package/dist/data-grid/use-as-ref.js.map +0 -1
  167. package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
  168. package/dist/data-grid/use-lazy-ref.js.map +0 -1
  169. /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
  170. /package/dist/{data-grid → lib}/compose-refs.js +0 -0
  171. /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
  172. /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
  173. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
  174. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
  175. /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
  176. /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
@@ -0,0 +1,266 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import * as React from "react";
5
+ import { cn } from "../../lib/cn.js";
6
+ const CIRCULAR_PROGRESS_NAME = "CircularProgress";
7
+ const INDICATOR_NAME = "CircularProgressIndicator";
8
+ const TRACK_NAME = "CircularProgressTrack";
9
+ const RANGE_NAME = "CircularProgressRange";
10
+ const VALUE_TEXT_NAME = "CircularProgressValueText";
11
+ const DEFAULT_MAX = 100;
12
+ const IS_DEV = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
13
+ function getProgressState(value, maxValue) {
14
+ return value == null ? "indeterminate" : value === maxValue ? "complete" : "loading";
15
+ }
16
+ function getIsValidNumber(value) {
17
+ return typeof value === "number" && Number.isFinite(value);
18
+ }
19
+ function getIsValidMaxNumber(max) {
20
+ return getIsValidNumber(max) && max > 0;
21
+ }
22
+ function getIsValidValueNumber(value, min, max) {
23
+ return getIsValidNumber(value) && value <= max && value >= min;
24
+ }
25
+ function getDefaultValueText(value, min, max) {
26
+ const percentage = max === min ? 100 : (value - min) / (max - min) * 100;
27
+ return `${Math.round(percentage)}%`;
28
+ }
29
+ function getInvalidValueError(propValue, componentName) {
30
+ return `Prop \`value\` = \`${propValue}\` truy\u1EC1n cho \`${componentName}\` kh\xF4ng h\u1EE3p l\u1EC7. \`value\` ph\u1EA3i l\xE0 s\u1ED1 trong kho\u1EA3ng \`min\`\u2026\`max\` (t\xEDnh c\u1EA3 hai \u0111\u1EA7u), ho\u1EB7c \`null\`/\`undefined\` cho ti\u1EBFn tr\xECnh kh\xF4ng x\xE1c \u0111\u1ECBnh. Gi\xE1 tr\u1ECB s\u1EBD b\u1ECB k\u1EB9p v\u1EC1 kho\u1EA3ng h\u1EE3p l\u1EC7.`;
31
+ }
32
+ function getInvalidMaxError(propValue, componentName) {
33
+ return `Prop \`max\` = \`${propValue}\` truy\u1EC1n cho \`${componentName}\` kh\xF4ng h\u1EE3p l\u1EC7. Ch\u1EC9 nh\u1EADn s\u1ED1 l\u1EDBn h\u01A1n 0 \u2014 d\xF9ng m\u1EB7c \u0111\u1ECBnh ${DEFAULT_MAX}.`;
34
+ }
35
+ const CircularProgressContext = React.createContext(null);
36
+ function useCircularProgressContext(consumerName) {
37
+ const context = React.useContext(CircularProgressContext);
38
+ if (!context) {
39
+ throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${CIRCULAR_PROGRESS_NAME}\``);
40
+ }
41
+ return context;
42
+ }
43
+ function CircularProgress(props) {
44
+ const {
45
+ value: valueProp = null,
46
+ getValueText = getDefaultValueText,
47
+ min: minProp = 0,
48
+ max: maxProp,
49
+ size = 48,
50
+ thickness = 4,
51
+ label,
52
+ asChild,
53
+ className,
54
+ children,
55
+ ...rootProps
56
+ } = props;
57
+ if ((maxProp || maxProp === 0) && !getIsValidMaxNumber(maxProp)) {
58
+ if (IS_DEV) {
59
+ console.error(getInvalidMaxError(`${maxProp}`, CIRCULAR_PROGRESS_NAME));
60
+ }
61
+ }
62
+ const rawMax = getIsValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX;
63
+ const min = getIsValidNumber(minProp) ? minProp : 0;
64
+ const max = rawMax <= min ? min + 1 : rawMax;
65
+ if (IS_DEV && thickness >= size) {
66
+ console.warn(
67
+ `CircularProgress: \`thickness\` (${thickness}) ph\u1EA3i nh\u1ECF h\u01A1n \`size\` (${size}) th\xEC v\xF2ng m\u1EDBi v\u1EBD \u0111\xFAng.`
68
+ );
69
+ }
70
+ if (valueProp !== null && !getIsValidValueNumber(valueProp, min, max)) {
71
+ if (IS_DEV) {
72
+ console.error(getInvalidValueError(`${valueProp}`, CIRCULAR_PROGRESS_NAME));
73
+ }
74
+ }
75
+ const value = getIsValidValueNumber(valueProp, min, max) ? valueProp : getIsValidNumber(valueProp) && valueProp > max ? max : getIsValidNumber(valueProp) && valueProp < min ? min : null;
76
+ const valueText = getIsValidNumber(value) ? getValueText(value, min, max) : void 0;
77
+ const state = getProgressState(value, max);
78
+ const radius = Math.max(0, (size - thickness) / 2);
79
+ const center = size / 2;
80
+ const circumference = 2 * Math.PI * radius;
81
+ const percentage = getIsValidNumber(value) ? max === min ? 1 : (value - min) / (max - min) : null;
82
+ const labelId = React.useId();
83
+ const valueTextId = React.useId();
84
+ const contextValue = React.useMemo(
85
+ () => ({
86
+ value,
87
+ valueText,
88
+ max,
89
+ min,
90
+ state,
91
+ radius,
92
+ thickness,
93
+ size,
94
+ center,
95
+ circumference,
96
+ percentage,
97
+ valueTextId
98
+ }),
99
+ [value, valueText, max, min, state, radius, thickness, size, center, circumference, percentage, valueTextId]
100
+ );
101
+ const RootPrimitive = asChild ? Slot : "div";
102
+ return /* @__PURE__ */ jsx(CircularProgressContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
103
+ RootPrimitive,
104
+ {
105
+ role: "progressbar",
106
+ "aria-describedby": valueText ? valueTextId : void 0,
107
+ "aria-labelledby": label ? labelId : void 0,
108
+ "aria-valuemax": max,
109
+ "aria-valuemin": min,
110
+ "aria-valuenow": getIsValidNumber(value) ? value : void 0,
111
+ "aria-valuetext": valueText,
112
+ "data-state": state,
113
+ "data-value": value ?? void 0,
114
+ "data-max": max,
115
+ "data-min": min,
116
+ "data-percentage": percentage,
117
+ "data-slot": "circular-progress",
118
+ ...rootProps,
119
+ className: cn("relative inline-flex w-fit items-center justify-center", label && "flex-col gap-1.5", className),
120
+ children: [
121
+ label ? /* @__PURE__ */ jsx("div", { "data-slot": "circular-progress-ring", className: "relative inline-flex items-center justify-center", children }) : children,
122
+ label && /* @__PURE__ */ jsx("div", { id: labelId, "data-slot": "circular-progress-label", className: "text-sm", children: label })
123
+ ]
124
+ }
125
+ ) });
126
+ }
127
+ function CircularProgressIndicator(props) {
128
+ const { className, ...indicatorProps } = props;
129
+ const context = useCircularProgressContext(INDICATOR_NAME);
130
+ return /* @__PURE__ */ jsx(
131
+ "svg",
132
+ {
133
+ "aria-hidden": "true",
134
+ focusable: "false",
135
+ viewBox: `0 0 ${context.size} ${context.size}`,
136
+ "data-state": context.state,
137
+ "data-value": context.value ?? void 0,
138
+ "data-max": context.max,
139
+ "data-min": context.min,
140
+ "data-percentage": context.percentage,
141
+ "data-slot": "circular-progress-indicator",
142
+ width: context.size,
143
+ height: context.size,
144
+ ...indicatorProps,
145
+ className: cn("-rotate-90 transform", className)
146
+ }
147
+ );
148
+ }
149
+ CircularProgressIndicator.displayName = INDICATOR_NAME;
150
+ function CircularProgressTrack(props) {
151
+ const { className, ...trackProps } = props;
152
+ const context = useCircularProgressContext(TRACK_NAME);
153
+ return /* @__PURE__ */ jsx(
154
+ "circle",
155
+ {
156
+ "data-state": context.state,
157
+ "data-slot": "circular-progress-track",
158
+ cx: context.center,
159
+ cy: context.center,
160
+ r: context.radius,
161
+ fill: "none",
162
+ stroke: "currentColor",
163
+ strokeWidth: context.thickness,
164
+ strokeLinecap: "round",
165
+ vectorEffect: "non-scaling-stroke",
166
+ ...trackProps,
167
+ className: cn(
168
+ /*
169
+ * GIỮ NGUYÊN thượng nguồn — rãnh xám trung tính `text-muted-foreground/20`, KHÔNG phải tint
170
+ * `/20` của bậc ink như rãnh `Progress`. Đã đo cả hai; thứ mang THÔNG TIN ở đây là ranh
171
+ * giới cung-đã-chạy ⇄ rãnh (chỗ cung dừng lại chính là giá trị), và rãnh trung tính giữ
172
+ * ranh giới đó TỐT HƠN (bảng tương phản cao, cung `text-primary-ink`):
173
+ *
174
+ * rãnh trung tính tint `primary-ink/20`
175
+ * cung ⇄ rãnh trên --background : 5,03 (L) · 9,65 (D) 4,85 (L) · 7,97 (D)
176
+ * tệ nhất, rãnh trên --muted : 4,71 (L) · 8,00 (D) 4,54 (L) · 6,52 (D)
177
+ * (viền trang trí --border : 2,62 (L) ✗ — loại)
178
+ *
179
+ * Và nó không phụ thuộc tông: app đổi cung sang `text-success-ink`/`text-destructive-ink`
180
+ * thì rãnh vẫn đúng (5,03 · 5,51:1 trên nền trắng) mà không phải đổi theo.
181
+ *
182
+ * Rãnh ⇄ nền trang chỉ ~1,3:1 — CỐ Ý, như rãnh `Progress`: không có cặp màu nào vừa ≥3:1
183
+ * với nhau vừa ≥3:1 với nền trắng (chứng minh ở chú thích `Slider`). Khác `Progress` ở chỗ
184
+ * vòng KHÔNG cần viền khoanh mốc 100%: hình tròn tự khép và cung luôn bắt đầu ở 12 giờ, nên
185
+ * TỈ LỆ đọc được từ góc của cung mà không cần thấy rãnh. Một nét tròn liền, không nét nào
186
+ * cắt nhau, nên màu có alpha không để lộ vệt nối (xem "Cạm bẫy" về icon trong CLAUDE.md).
187
+ */
188
+ "text-muted-foreground/20",
189
+ className
190
+ )
191
+ }
192
+ );
193
+ }
194
+ function CircularProgressRange(props) {
195
+ const { className, ...rangeProps } = props;
196
+ const context = useCircularProgressContext(RANGE_NAME);
197
+ const strokeDasharray = context.circumference;
198
+ const strokeDashoffset = context.state === "indeterminate" ? context.circumference * 0.75 : context.percentage !== null ? context.circumference - context.percentage * context.circumference : context.circumference;
199
+ return /* @__PURE__ */ jsx(
200
+ "circle",
201
+ {
202
+ "data-state": context.state,
203
+ "data-value": context.value ?? void 0,
204
+ "data-max": context.max,
205
+ "data-min": context.min,
206
+ "data-slot": "circular-progress-range",
207
+ cx: context.center,
208
+ cy: context.center,
209
+ r: context.radius,
210
+ fill: "none",
211
+ stroke: "currentColor",
212
+ strokeWidth: context.thickness,
213
+ strokeLinecap: "round",
214
+ strokeDasharray,
215
+ strokeDashoffset,
216
+ vectorEffect: "non-scaling-stroke",
217
+ ...rangeProps,
218
+ className: cn(
219
+ /*
220
+ * ⚠ Khác thượng nguồn: `text-primary` (bậc TÔ NỀN, gold #E8B824 chỉ 1,86:1 trên nền trắng)
221
+ * → `text-primary-ink`, đúng bậc của thanh `Progress`: 6,51:1 (light) · 12,67:1 (dark)
222
+ * trên --background, tệ nhất 6,09:1 · 10,80:1 trên --muted (bảng tương phản cao). Cung là
223
+ * thông tin thị giác (WCAG 1.4.11, 3:1).
224
+ * Hiệu ứng quay đọc `--animate-spin-around` của styles.css; `motion-reduce` thì đứng yên.
225
+ */
226
+ "origin-center text-primary-ink transition-all duration-300 ease-in-out",
227
+ context.state === "indeterminate" && "motion-safe:[animation:var(--animate-spin-around)] motion-reduce:animate-none",
228
+ className
229
+ )
230
+ }
231
+ );
232
+ }
233
+ function CircularProgressValueText(props) {
234
+ const { asChild, className, children, ...valueTextProps } = props;
235
+ const context = useCircularProgressContext(VALUE_TEXT_NAME);
236
+ const ValueTextPrimitive = asChild ? Slot : "span";
237
+ return /* @__PURE__ */ jsx(
238
+ ValueTextPrimitive,
239
+ {
240
+ id: context.valueTextId,
241
+ "data-state": context.state,
242
+ "data-slot": "circular-progress-value-text",
243
+ ...valueTextProps,
244
+ className: cn("absolute inset-0 flex items-center justify-center text-sm font-medium", className),
245
+ children: children ?? context.valueText
246
+ }
247
+ );
248
+ }
249
+ function CircularProgressCombined(props) {
250
+ return /* @__PURE__ */ jsxs(CircularProgress, { ...props, children: [
251
+ /* @__PURE__ */ jsxs(CircularProgressIndicator, { children: [
252
+ /* @__PURE__ */ jsx(CircularProgressTrack, {}),
253
+ /* @__PURE__ */ jsx(CircularProgressRange, {})
254
+ ] }),
255
+ /* @__PURE__ */ jsx(CircularProgressValueText, {})
256
+ ] });
257
+ }
258
+ export {
259
+ CircularProgress,
260
+ CircularProgressCombined,
261
+ CircularProgressIndicator,
262
+ CircularProgressRange,
263
+ CircularProgressTrack,
264
+ CircularProgressValueText
265
+ };
266
+ //# sourceMappingURL=circular-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/circular-progress.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/circular-progress) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\nconst CIRCULAR_PROGRESS_NAME = \"CircularProgress\";\nconst INDICATOR_NAME = \"CircularProgressIndicator\";\nconst TRACK_NAME = \"CircularProgressTrack\";\nconst RANGE_NAME = \"CircularProgressRange\";\nconst VALUE_TEXT_NAME = \"CircularProgressValueText\";\n\nconst DEFAULT_MAX = 100;\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc đọc thẳng `process.env.NODE_ENV` ngay trong lúc render. Gói ra ESM\n * từng file, không bundle; ở môi trường không có bundler thay chuỗi đó (ESM thuần trên trình\n * duyệt), `process` không tồn tại → ReferenceError lúc render. Bọc `typeof` để chỗ đó chỉ mất cảnh\n * báo dev chứ không gãy; bundler vẫn thay `process.env.NODE_ENV` và loại nhánh dev như cũ.\n */\nconst IS_DEV = typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\";\n\ntype ProgressState = \"indeterminate\" | \"complete\" | \"loading\";\n\nfunction getProgressState(value: number | undefined | null, maxValue: number): ProgressState {\n return value == null ? \"indeterminate\" : value === maxValue ? \"complete\" : \"loading\";\n}\n\nfunction getIsValidNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction getIsValidMaxNumber(max: unknown): max is number {\n return getIsValidNumber(max) && max > 0;\n}\n\nfunction getIsValidValueNumber(value: unknown, min: number, max: number): value is number {\n return getIsValidNumber(value) && value <= max && value >= min;\n}\n\nfunction getDefaultValueText(value: number, min: number, max: number): string {\n const percentage = max === min ? 100 : ((value - min) / (max - min)) * 100;\n return `${Math.round(percentage)}%`;\n}\n\nfunction getInvalidValueError(propValue: string, componentName: string): string {\n return `Prop \\`value\\` = \\`${propValue}\\` truyền cho \\`${componentName}\\` không hợp lệ. \\`value\\` phải là số trong khoảng \\`min\\`…\\`max\\` (tính cả hai đầu), hoặc \\`null\\`/\\`undefined\\` cho tiến trình không xác định. Giá trị sẽ bị kẹp về khoảng hợp lệ.`;\n}\n\nfunction getInvalidMaxError(propValue: string, componentName: string): string {\n return `Prop \\`max\\` = \\`${propValue}\\` truyền cho \\`${componentName}\\` không hợp lệ. Chỉ nhận số lớn hơn 0 — dùng mặc định ${DEFAULT_MAX}.`;\n}\n\ninterface CircularProgressContextValue {\n value: number | null;\n valueText: string | undefined;\n max: number;\n min: number;\n state: ProgressState;\n radius: number;\n thickness: number;\n size: number;\n center: number;\n circumference: number;\n percentage: number | null;\n valueTextId?: string;\n}\n\nconst CircularProgressContext = React.createContext<CircularProgressContextValue | null>(null);\n\nfunction useCircularProgressContext(consumerName: string) {\n const context = React.useContext(CircularProgressContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${CIRCULAR_PROGRESS_NAME}\\``);\n }\n return context;\n}\n\ninterface CircularProgressProps extends React.ComponentProps<\"div\"> {\n /** Giá trị hiện tại; `null`/`undefined` = không xác định (vòng quay). */\n value?: number | null | undefined;\n /**\n * Chuỗi trình đọc màn hình đọc thay cho con số (`aria-valuetext`) và chữ giữa vòng — mặc định\n * phần trăm làm tròn, vd `\"45%\"`.\n */\n getValueText?(value: number, min: number, max: number): string;\n min?: number;\n max?: number;\n /** Đường kính, px. */\n size?: number;\n /** Độ dày nét, px. */\n thickness?: number;\n /**\n * Nhãn HIỆN của vòng, đồng thời là tên của `role=\"progressbar\"`. Không đặt nhãn hiện thì PHẢI\n * truyền `aria-label` — progressbar không tên là lỗi a11y (ARIA 1.2), gói không tự đặt tên\n * chung chung thay app (giống `Progress`).\n */\n label?: string;\n asChild?: boolean;\n}\n\n/**\n * CircularProgress — tiến trình dạng VÒNG, cặp với `Progress` (dạng thanh) của gói.\n * Ráp từ các phần (`CircularProgressIndicator` › `Track` + `Range`, `CircularProgressValueText`),\n * hoặc dùng sẵn `CircularProgressCombined`.\n *\n * Phần đã chạy là `text-primary-ink`, cùng bậc với thanh `Progress`; rãnh xám trung tính. Đổi tông\n * thì truyền bậc INK cho `CircularProgressRange` (vd `className=\"text-success-ink\"`), không phải\n * token tô nền `text-success` — rãnh không cần đổi theo (số đo ở `CircularProgressTrack`).\n *\n * ⚠ Trạng thái không xác định cần `--animate-spin-around` + `@keyframes spin-around` trong\n * styles.css; thiếu thì vòng đứng yên ở một cung 25% (không lỗi, không cảnh báo).\n */\nfunction CircularProgress(props: CircularProgressProps) {\n const {\n value: valueProp = null,\n getValueText = getDefaultValueText,\n min: minProp = 0,\n max: maxProp,\n size = 48,\n thickness = 4,\n label,\n asChild,\n className,\n children,\n ...rootProps\n } = props;\n\n if ((maxProp || maxProp === 0) && !getIsValidMaxNumber(maxProp)) {\n if (IS_DEV) {\n console.error(getInvalidMaxError(`${maxProp}`, CIRCULAR_PROGRESS_NAME));\n }\n }\n\n const rawMax = getIsValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX;\n const min = getIsValidNumber(minProp) ? minProp : 0;\n const max = rawMax <= min ? min + 1 : rawMax;\n\n if (IS_DEV && thickness >= size) {\n console.warn(\n `CircularProgress: \\`thickness\\` (${thickness}) phải nhỏ hơn \\`size\\` (${size}) thì vòng mới vẽ đúng.`\n );\n }\n\n if (valueProp !== null && !getIsValidValueNumber(valueProp, min, max)) {\n if (IS_DEV) {\n console.error(getInvalidValueError(`${valueProp}`, CIRCULAR_PROGRESS_NAME));\n }\n }\n\n const value = getIsValidValueNumber(valueProp, min, max)\n ? valueProp\n : getIsValidNumber(valueProp) && valueProp > max\n ? max\n : getIsValidNumber(valueProp) && valueProp < min\n ? min\n : null;\n\n const valueText = getIsValidNumber(value) ? getValueText(value, min, max) : undefined;\n const state = getProgressState(value, max);\n const radius = Math.max(0, (size - thickness) / 2);\n const center = size / 2;\n const circumference = 2 * Math.PI * radius;\n\n const percentage = getIsValidNumber(value) ? (max === min ? 1 : (value - min) / (max - min)) : null;\n\n const labelId = React.useId();\n const valueTextId = React.useId();\n\n const contextValue = React.useMemo<CircularProgressContextValue>(\n () => ({\n value,\n valueText,\n max,\n min,\n state,\n radius,\n thickness,\n size,\n center,\n circumference,\n percentage,\n valueTextId\n }),\n [value, valueText, max, min, state, radius, thickness, size, center, circumference, percentage, valueTextId]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <CircularProgressContext.Provider value={contextValue}>\n <RootPrimitive\n role=\"progressbar\"\n aria-describedby={valueText ? valueTextId : undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-valuemax={max}\n aria-valuemin={min}\n aria-valuenow={getIsValidNumber(value) ? value : undefined}\n aria-valuetext={valueText}\n data-state={state}\n data-value={value ?? undefined}\n data-max={max}\n data-min={min}\n data-percentage={percentage}\n data-slot=\"circular-progress\"\n {...rootProps}\n className={cn(\"relative inline-flex w-fit items-center justify-center\", label && \"flex-col gap-1.5\", className)}\n >\n {/*\n * ⚠ Khác thượng nguồn: bản gốc đặt nhãn làm anh em của vòng NGAY TRONG gốc, trong khi\n * `CircularProgressValueText` là `absolute inset-0` phủ CẢ gốc — có nhãn là chữ \"65%\" đè\n * lên chữ nhãn. Khi có `label`: bọc phần con trong một khung đúng cỡ vòng (`relative`, nên\n * chữ giá trị căn giữa VÒNG), nhãn xuống dưới vòng. Không có `label` thì DOM y như bản gốc.\n */}\n {label ? (\n <div data-slot=\"circular-progress-ring\" className=\"relative inline-flex items-center justify-center\">\n {children}\n </div>\n ) : (\n children\n )}\n {label && (\n <div id={labelId} data-slot=\"circular-progress-label\" className=\"text-sm\">\n {label}\n </div>\n )}\n </RootPrimitive>\n </CircularProgressContext.Provider>\n );\n}\n\nfunction CircularProgressIndicator(props: React.ComponentProps<\"svg\">) {\n const { className, ...indicatorProps } = props;\n\n const context = useCircularProgressContext(INDICATOR_NAME);\n\n return (\n <svg\n aria-hidden=\"true\"\n focusable=\"false\"\n viewBox={`0 0 ${context.size} ${context.size}`}\n data-state={context.state}\n data-value={context.value ?? undefined}\n data-max={context.max}\n data-min={context.min}\n data-percentage={context.percentage}\n data-slot=\"circular-progress-indicator\"\n width={context.size}\n height={context.size}\n {...indicatorProps}\n className={cn(\"-rotate-90 transform\", className)}\n />\n );\n}\n\nCircularProgressIndicator.displayName = INDICATOR_NAME;\n\nfunction CircularProgressTrack(props: React.ComponentProps<\"circle\">) {\n const { className, ...trackProps } = props;\n\n const context = useCircularProgressContext(TRACK_NAME);\n\n return (\n <circle\n data-state={context.state}\n data-slot=\"circular-progress-track\"\n cx={context.center}\n cy={context.center}\n r={context.radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={context.thickness}\n strokeLinecap=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n {...trackProps}\n className={cn(\n /*\n * GIỮ NGUYÊN thượng nguồn — rãnh xám trung tính `text-muted-foreground/20`, KHÔNG phải tint\n * `/20` của bậc ink như rãnh `Progress`. Đã đo cả hai; thứ mang THÔNG TIN ở đây là ranh\n * giới cung-đã-chạy ⇄ rãnh (chỗ cung dừng lại chính là giá trị), và rãnh trung tính giữ\n * ranh giới đó TỐT HƠN (bảng tương phản cao, cung `text-primary-ink`):\n *\n * rãnh trung tính tint `primary-ink/20`\n * cung ⇄ rãnh trên --background : 5,03 (L) · 9,65 (D) 4,85 (L) · 7,97 (D)\n * tệ nhất, rãnh trên --muted : 4,71 (L) · 8,00 (D) 4,54 (L) · 6,52 (D)\n * (viền trang trí --border : 2,62 (L) ✗ — loại)\n *\n * Và nó không phụ thuộc tông: app đổi cung sang `text-success-ink`/`text-destructive-ink`\n * thì rãnh vẫn đúng (5,03 · 5,51:1 trên nền trắng) mà không phải đổi theo.\n *\n * Rãnh ⇄ nền trang chỉ ~1,3:1 — CỐ Ý, như rãnh `Progress`: không có cặp màu nào vừa ≥3:1\n * với nhau vừa ≥3:1 với nền trắng (chứng minh ở chú thích `Slider`). Khác `Progress` ở chỗ\n * vòng KHÔNG cần viền khoanh mốc 100%: hình tròn tự khép và cung luôn bắt đầu ở 12 giờ, nên\n * TỈ LỆ đọc được từ góc của cung mà không cần thấy rãnh. Một nét tròn liền, không nét nào\n * cắt nhau, nên màu có alpha không để lộ vệt nối (xem \"Cạm bẫy\" về icon trong CLAUDE.md).\n */\n \"text-muted-foreground/20\",\n className\n )}\n />\n );\n}\n\nfunction CircularProgressRange(props: React.ComponentProps<\"circle\">) {\n const { className, ...rangeProps } = props;\n\n const context = useCircularProgressContext(RANGE_NAME);\n\n const strokeDasharray = context.circumference;\n const strokeDashoffset =\n context.state === \"indeterminate\"\n ? context.circumference * 0.75\n : context.percentage !== null\n ? context.circumference - context.percentage * context.circumference\n : context.circumference;\n\n return (\n <circle\n data-state={context.state}\n data-value={context.value ?? undefined}\n data-max={context.max}\n data-min={context.min}\n data-slot=\"circular-progress-range\"\n cx={context.center}\n cy={context.center}\n r={context.radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={context.thickness}\n strokeLinecap=\"round\"\n strokeDasharray={strokeDasharray}\n strokeDashoffset={strokeDashoffset}\n vectorEffect=\"non-scaling-stroke\"\n {...rangeProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: `text-primary` (bậc TÔ NỀN, gold #E8B824 chỉ 1,86:1 trên nền trắng)\n * → `text-primary-ink`, đúng bậc của thanh `Progress`: 6,51:1 (light) · 12,67:1 (dark)\n * trên --background, tệ nhất 6,09:1 · 10,80:1 trên --muted (bảng tương phản cao). Cung là\n * thông tin thị giác (WCAG 1.4.11, 3:1).\n * Hiệu ứng quay đọc `--animate-spin-around` của styles.css; `motion-reduce` thì đứng yên.\n */\n \"origin-center text-primary-ink transition-all duration-300 ease-in-out\",\n context.state === \"indeterminate\" &&\n \"motion-safe:[animation:var(--animate-spin-around)] motion-reduce:animate-none\",\n className\n )}\n />\n );\n}\n\ninterface CircularProgressValueTextProps extends React.ComponentProps<\"span\"> {\n asChild?: boolean;\n}\n\nfunction CircularProgressValueText(props: CircularProgressValueTextProps) {\n const { asChild, className, children, ...valueTextProps } = props;\n\n const context = useCircularProgressContext(VALUE_TEXT_NAME);\n\n const ValueTextPrimitive = asChild ? Slot : \"span\";\n\n return (\n <ValueTextPrimitive\n id={context.valueTextId}\n data-state={context.state}\n data-slot=\"circular-progress-value-text\"\n {...valueTextProps}\n className={cn(\"absolute inset-0 flex items-center justify-center text-sm font-medium\", className)}\n >\n {children ?? context.valueText}\n </ValueTextPrimitive>\n );\n}\n\nfunction CircularProgressCombined(props: CircularProgressProps) {\n return (\n <CircularProgress {...props}>\n <CircularProgressIndicator>\n <CircularProgressTrack />\n <CircularProgressRange />\n </CircularProgressIndicator>\n <CircularProgressValueText />\n </CircularProgress>\n );\n}\n\nexport {\n CircularProgress,\n CircularProgressCombined,\n CircularProgressIndicator,\n type CircularProgressProps,\n CircularProgressRange,\n CircularProgressTrack,\n CircularProgressValueText,\n type CircularProgressValueTextProps\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Nhãn mặc định của `Cropper`.
3
+ *
4
+ * Module **CỐ Ý không có `"use client"`**: mọi export của một file `"use client"` tới Server
5
+ * Component là client-reference proxy, đọc ra `undefined` mà KHÔNG throw — xem
6
+ * `src/chart/chart-colors.ts`. Barrel xuất module này TRỰC TIẾP; `cropper.tsx` chỉ `import`.
7
+ */
8
+ /**
9
+ * Chuỗi a11y của `Cropper` — truyền `labels` cho `Cropper` để ghi đè từng khoá.
10
+ *
11
+ * ⚠ Thượng nguồn (Dice UI) KHÔNG có chuỗi nào: vùng cắt nhận focus bằng Tab (`tabIndex=0`) nhưng
12
+ * không role, không tên, không mô tả — trình đọc màn hình gặp một phần tử focus được mà không
13
+ * biết nó là gì, và người dùng không có cách nào biết phím nào điều khiển được nó.
14
+ */
15
+ interface CropperLabels {
16
+ /**
17
+ * Tên của vùng cắt (`aria-label`). Truyền thẳng `aria-label` cho `Cropper` cũng được — prop
18
+ * đó thắng, vì nó được trải SAU giá trị mặc định.
19
+ */
20
+ label: string;
21
+ /** `aria-roledescription` — tên loại control mà trình đọc màn hình đọc thay cho "ứng dụng". */
22
+ roleDescription: string;
23
+ /** Hướng dẫn phím, gắn vào vùng cắt qua `aria-describedby`. */
24
+ instructions: string;
25
+ /**
26
+ * Câu đọc qua vùng `aria-live` sau mỗi lần phóng/thu bằng phím. `zoom` là hệ số thô (1 = vừa
27
+ * khung). Là HÀM vì có số chèn vào giữa câu.
28
+ */
29
+ zoomStatus: (zoom: number) => string;
30
+ }
31
+ /** Nhãn tiếng Việt mặc định — mọi khoá đều ghi đè được qua prop `labels` của `Cropper`. */
32
+ declare const DEFAULT_CROPPER_LABELS: CropperLabels;
33
+
34
+ export { type CropperLabels, DEFAULT_CROPPER_LABELS };
@@ -0,0 +1,10 @@
1
+ const DEFAULT_CROPPER_LABELS = {
2
+ label: "V\xF9ng c\u1EAFt \u1EA3nh",
3
+ roleDescription: "tr\xECnh c\u1EAFt \u1EA3nh",
4
+ instructions: "D\xF9ng c\xE1c ph\xEDm m\u0169i t\xEAn \u0111\u1EC3 di chuy\u1EC3n \u1EA3nh, gi\u1EEF Shift \u0111\u1EC3 di chuy\u1EC3n ch\u1EADm h\u01A1n. Ph\xEDm + v\xE0 - \u0111\u1EC3 ph\xF3ng to, thu nh\u1ECF.",
5
+ zoomStatus: (zoom) => `Thu ph\xF3ng ${Math.round(zoom * 100)}%`
6
+ };
7
+ export {
8
+ DEFAULT_CROPPER_LABELS
9
+ };
10
+ //# sourceMappingURL=cropper-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/cropper-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `Cropper`.\n *\n * Module **CỐ Ý không có `\"use client\"`**: mọi export của một file `\"use client\"` tới Server\n * Component là client-reference proxy, đọc ra `undefined` mà KHÔNG throw — xem\n * `src/chart/chart-colors.ts`. Barrel xuất module này TRỰC TIẾP; `cropper.tsx` chỉ `import`.\n */\n\n/**\n * Chuỗi a11y của `Cropper` — truyền `labels` cho `Cropper` để ghi đè từng khoá.\n *\n * ⚠ Thượng nguồn (Dice UI) KHÔNG có chuỗi nào: vùng cắt nhận focus bằng Tab (`tabIndex=0`) nhưng\n * không role, không tên, không mô tả — trình đọc màn hình gặp một phần tử focus được mà không\n * biết nó là gì, và người dùng không có cách nào biết phím nào điều khiển được nó.\n */\nexport interface CropperLabels {\n /**\n * Tên của vùng cắt (`aria-label`). Truyền thẳng `aria-label` cho `Cropper` cũng được — prop\n * đó thắng, vì nó được trải SAU giá trị mặc định.\n */\n label: string;\n /** `aria-roledescription` — tên loại control mà trình đọc màn hình đọc thay cho \"ứng dụng\". */\n roleDescription: string;\n /** Hướng dẫn phím, gắn vào vùng cắt qua `aria-describedby`. */\n instructions: string;\n /**\n * Câu đọc qua vùng `aria-live` sau mỗi lần phóng/thu bằng phím. `zoom` là hệ số thô (1 = vừa\n * khung). Là HÀM vì có số chèn vào giữa câu.\n */\n zoomStatus: (zoom: number) => string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều ghi đè được qua prop `labels` của `Cropper`. */\nexport const DEFAULT_CROPPER_LABELS: CropperLabels = {\n label: \"Vùng cắt ảnh\",\n roleDescription: \"trình cắt ảnh\",\n instructions:\n \"Dùng các phím mũi tên để di chuyển ảnh, giữ Shift để di chuyển chậm hơn. Phím + và - để phóng to, thu nhỏ.\",\n zoomStatus: (zoom) => `Thu phóng ${Math.round(zoom * 100)}%`\n};\n"],"mappings":"AAiCO,MAAM,yBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,cACE;AAAA,EACF,YAAY,CAAC,SAAS,gBAAa,KAAK,MAAM,OAAO,GAAG,CAAC;AAC3D;","names":[]}
@@ -0,0 +1,106 @@
1
+ import * as class_variance_authority_types from 'class-variance-authority/types';
2
+ import { VariantProps } from 'class-variance-authority';
3
+ import * as React from 'react';
4
+ import { CropperLabels } from './cropper-constants.js';
5
+
6
+ interface Point {
7
+ x: number;
8
+ y: number;
9
+ }
10
+ interface Size {
11
+ width: number;
12
+ height: number;
13
+ }
14
+ interface Area {
15
+ width: number;
16
+ height: number;
17
+ x: number;
18
+ y: number;
19
+ }
20
+ interface MediaSize {
21
+ width: number;
22
+ height: number;
23
+ naturalWidth: number;
24
+ naturalHeight: number;
25
+ }
26
+ type Shape = "rectangle" | "circle";
27
+ type ObjectFit = "contain" | "cover" | "horizontal-cover" | "vertical-cover";
28
+ interface DivProps extends React.ComponentProps<"div"> {
29
+ asChild?: boolean;
30
+ }
31
+ interface StoreState {
32
+ crop: Point;
33
+ zoom: number;
34
+ rotation: number;
35
+ mediaSize: MediaSize | null;
36
+ cropSize: Size | null;
37
+ isDragging: boolean;
38
+ isWheelZooming: boolean;
39
+ }
40
+ declare function useStore<T>(selector: (state: StoreState) => T): T;
41
+ interface CropperProps extends DivProps {
42
+ crop?: Point;
43
+ zoom?: number;
44
+ minZoom?: number;
45
+ maxZoom?: number;
46
+ zoomSpeed?: number;
47
+ rotation?: number;
48
+ keyboardStep?: number;
49
+ aspectRatio?: number;
50
+ shape?: Shape;
51
+ objectFit?: ObjectFit;
52
+ allowOverflow?: boolean;
53
+ preventScrollZoom?: boolean;
54
+ withGrid?: boolean;
55
+ /**
56
+ * Chuỗi a11y của vùng cắt — ghi đè từng khoá, mặc định `DEFAULT_CROPPER_LABELS` (tiếng Việt).
57
+ *
58
+ * ⚠ Thêm so với thượng nguồn — xem `CropperLabels`.
59
+ */
60
+ labels?: Partial<CropperLabels>;
61
+ onCropChange?: (crop: Point) => void;
62
+ onCropSizeChange?: (cropSize: Size) => void;
63
+ onCropAreaChange?: (croppedArea: Area, croppedAreaPixels: Area) => void;
64
+ onCropComplete?: (croppedArea: Area, croppedAreaPixels: Area) => void;
65
+ onZoomChange?: (zoom: number) => void;
66
+ onRotationChange?: (rotation: number) => void;
67
+ onMediaLoaded?: (mediaSize: MediaSize) => void;
68
+ onInteractionStart?: () => void;
69
+ onInteractionEnd?: () => void;
70
+ onWheelZoom?: (event: WheelEvent) => void;
71
+ }
72
+ /**
73
+ * Cropper — cắt ảnh/video: kéo để dời, lăn chuột/chụm hai ngón để phóng, phím mũi tên và + / -.
74
+ *
75
+ * Ở Comitor dùng để cắt ảnh đại diện hoặc logo công ty TRƯỚC khi tải lên. Component chỉ BÁO vùng
76
+ * cắt (`onCropComplete(croppedArea, croppedAreaPixels)`) — việc vẽ ra canvas và tải lên là của
77
+ * app. Cố ý không phụ thuộc gì ngoài peer bắt buộc để sống được ở entry chính; `ImageUploadField`
78
+ * (entry `/uploader`, kéo theo `@uppy/*`) ghép với nó ở tầng app, không ở đây.
79
+ *
80
+ * `crop` / `zoom` / `rotation` là controlled KHI được truyền, uncontrolled khi bỏ trống.
81
+ */
82
+ declare function Cropper(props: CropperProps): React.JSX.Element;
83
+ declare const cropperMediaVariants: (props?: ({
84
+ objectFit?: "contain" | "cover" | "horizontal-cover" | "vertical-cover" | null | undefined;
85
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
86
+ interface CropperImageProps extends React.ComponentProps<"img">, VariantProps<typeof cropperMediaVariants> {
87
+ asChild?: boolean;
88
+ snapPixels?: boolean;
89
+ }
90
+ /** Ảnh cần cắt. Nhớ truyền `alt` — ảnh ở đây mang nội dung, không phải trang trí. */
91
+ declare function CropperImage(props: CropperImageProps): React.JSX.Element;
92
+ interface CropperVideoProps extends React.ComponentProps<"video">, VariantProps<typeof cropperMediaVariants> {
93
+ asChild?: boolean;
94
+ snapPixels?: boolean;
95
+ }
96
+ declare function CropperVideo(props: CropperVideoProps): React.JSX.Element;
97
+ declare const cropperAreaVariants: (props?: ({
98
+ shape?: "circle" | "rectangle" | null | undefined;
99
+ withGrid?: boolean | null | undefined;
100
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
101
+ interface CropperAreaProps extends DivProps, VariantProps<typeof cropperAreaVariants> {
102
+ snapPixels?: boolean;
103
+ }
104
+ declare function CropperArea(props: CropperAreaProps): React.JSX.Element | null;
105
+
106
+ export { Cropper, CropperArea, type Area as CropperAreaData, type CropperAreaProps, CropperImage, type CropperImageProps, type MediaSize as CropperMediaSize, type ObjectFit as CropperObjectFit, type Point as CropperPoint, type CropperProps, type Shape as CropperShape, type Size as CropperSize, CropperVideo, type CropperVideoProps, useStore as useCropper };