@comitor/ui 2.0.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 (131) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +14 -6
  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-wrapper.js +1 -1
  88. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  89. package/dist/data-grid/data-grid-context-menu.js +1 -1
  90. package/dist/data-grid/data-grid-context-menu.js.map +1 -1
  91. package/dist/data-grid/data-grid-paste-dialog.js +1 -1
  92. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
  93. package/dist/data-grid/data-grid-row.js +1 -1
  94. package/dist/data-grid/data-grid-row.js.map +1 -1
  95. package/dist/data-grid/data-grid-search.js +1 -1
  96. package/dist/data-grid/data-grid-search.js.map +1 -1
  97. package/dist/data-grid/data-grid.js +1 -1
  98. package/dist/data-grid/data-grid.js.map +1 -1
  99. package/dist/data-grid/sortable.js +1 -1
  100. package/dist/data-grid/sortable.js.map +1 -1
  101. package/dist/data-grid/use-data-grid-undo-redo.js +2 -2
  102. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
  103. package/dist/data-grid/use-data-grid.js +3 -3
  104. package/dist/data-grid/use-data-grid.js.map +1 -1
  105. package/dist/index.d.ts +31 -0
  106. package/dist/index.js +42 -0
  107. package/dist/index.js.map +1 -1
  108. package/dist/lib/compose-refs.js.map +1 -0
  109. package/dist/lib/use-as-ref.js.map +1 -0
  110. package/dist/lib/use-is-mobile.d.ts +10 -0
  111. package/dist/lib/use-is-mobile.js +21 -0
  112. package/dist/lib/use-is-mobile.js.map +1 -0
  113. package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
  114. package/dist/lib/use-lazy-ref.js.map +1 -0
  115. package/dist/lib/visually-hidden-input.d.ts +12 -0
  116. package/dist/lib/visually-hidden-input.js +105 -0
  117. package/dist/lib/visually-hidden-input.js.map +1 -0
  118. package/package.json +5 -1
  119. package/styles.css +19 -0
  120. package/dist/data-grid/compose-refs.js.map +0 -1
  121. package/dist/data-grid/use-as-ref.js.map +0 -1
  122. package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
  123. package/dist/data-grid/use-lazy-ref.js.map +0 -1
  124. /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
  125. /package/dist/{data-grid → lib}/compose-refs.js +0 -0
  126. /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
  127. /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
  128. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
  129. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
  130. /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
  131. /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
@@ -0,0 +1,179 @@
1
+ "use client";
2
+ import { Fragment, 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
+ import { useComposedRefs } from "../../lib/compose-refs.js";
7
+ import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
8
+ import { Badge } from "./badge.js";
9
+ const defaultOverflowLabel = (count) => `v\xE0 ${count} m\u1EE5c kh\xE1c`;
10
+ function BadgeOverflow(props) {
11
+ const {
12
+ items,
13
+ getBadgeLabel: getBadgeLabelProp,
14
+ lineCount = 1,
15
+ renderBadge,
16
+ renderOverflow,
17
+ overflowLabel = defaultOverflowLabel,
18
+ asChild,
19
+ className,
20
+ style,
21
+ ref,
22
+ ...rootProps
23
+ } = props;
24
+ const getBadgeLabel = React.useCallback(
25
+ (item) => {
26
+ if (typeof item === "object" && !getBadgeLabelProp) {
27
+ throw new Error("`getBadgeLabel` l\xE0 b\u1EAFt bu\u1ED9c khi ph\u1EA7n t\u1EED l\xE0 object");
28
+ }
29
+ return getBadgeLabelProp ? getBadgeLabelProp(item) : item;
30
+ },
31
+ [getBadgeLabelProp]
32
+ );
33
+ const rootRef = React.useRef(null);
34
+ const composedRef = useComposedRefs(ref, rootRef);
35
+ const measureRef = React.useRef(null);
36
+ const [containerWidth, setContainerWidth] = React.useState(0);
37
+ const [badgeGap, setBadgeGap] = React.useState(4);
38
+ const [badgeHeight, setBadgeHeight] = React.useState(20);
39
+ const [overflowBadgeWidth, setOverflowBadgeWidth] = React.useState(40);
40
+ const [isMeasured, setIsMeasured] = React.useState(false);
41
+ const [badgeWidths, setBadgeWidths] = React.useState(/* @__PURE__ */ new Map());
42
+ useIsomorphicLayoutEffect(() => {
43
+ if (!rootRef.current || !measureRef.current) return;
44
+ function measureContainer() {
45
+ if (!rootRef.current || !measureRef.current) return;
46
+ const computedStyle = getComputedStyle(rootRef.current);
47
+ const gapValue = computedStyle.gap;
48
+ const gap = gapValue ? parseFloat(gapValue) : 4;
49
+ setBadgeGap(gap);
50
+ const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;
51
+ const paddingRight = parseFloat(computedStyle.paddingRight) || 0;
52
+ const totalPadding = paddingLeft + paddingRight;
53
+ const widthMap = /* @__PURE__ */ new Map();
54
+ const measureChildren = measureRef.current.children;
55
+ for (let i = 0; i < items.length; i++) {
56
+ const child = measureChildren[i];
57
+ if (child) {
58
+ const label = getBadgeLabel(items[i]);
59
+ widthMap.set(label, child.offsetWidth);
60
+ }
61
+ }
62
+ setBadgeWidths(widthMap);
63
+ const firstBadge = measureChildren[0];
64
+ if (firstBadge) {
65
+ setBadgeHeight(firstBadge.offsetHeight || 20);
66
+ }
67
+ const overflowChild = measureChildren[items.length];
68
+ if (overflowChild) {
69
+ setOverflowBadgeWidth(overflowChild.offsetWidth || 40);
70
+ }
71
+ const width = rootRef.current.clientWidth - totalPadding;
72
+ setContainerWidth(width);
73
+ setIsMeasured(true);
74
+ }
75
+ measureContainer();
76
+ const resizeObserver = new ResizeObserver(measureContainer);
77
+ resizeObserver.observe(rootRef.current);
78
+ return () => {
79
+ resizeObserver.disconnect();
80
+ };
81
+ }, [items, getBadgeLabel]);
82
+ const placeholderHeight = React.useMemo(
83
+ () => badgeHeight * lineCount + badgeGap * (lineCount - 1),
84
+ [badgeHeight, badgeGap, lineCount]
85
+ );
86
+ const { visibleItems, hiddenCount } = React.useMemo(() => {
87
+ if (!containerWidth || items.length === 0 || badgeWidths.size === 0) {
88
+ return { visibleItems: items, hiddenCount: 0 };
89
+ }
90
+ let currentLineWidth = 0;
91
+ let currentLine = 1;
92
+ const visible = [];
93
+ for (let i = 0; i < items.length; i++) {
94
+ const item = items[i];
95
+ if (!item) continue;
96
+ const label = getBadgeLabel(item);
97
+ const badgeWidth = badgeWidths.get(label);
98
+ if (!badgeWidth) {
99
+ continue;
100
+ }
101
+ const widthWithGap = badgeWidth + badgeGap;
102
+ const isLastLine = currentLine === lineCount;
103
+ const hasMoreItems = i < items.length - 1;
104
+ const availableWidth = isLastLine && hasMoreItems ? containerWidth - overflowBadgeWidth - badgeGap : containerWidth;
105
+ if (currentLineWidth + widthWithGap <= availableWidth) {
106
+ currentLineWidth += widthWithGap;
107
+ visible.push(item);
108
+ } else if (currentLine < lineCount) {
109
+ currentLine++;
110
+ currentLineWidth = widthWithGap;
111
+ visible.push(item);
112
+ } else {
113
+ break;
114
+ }
115
+ }
116
+ return {
117
+ visibleItems: visible,
118
+ hiddenCount: Math.max(0, items.length - visible.length)
119
+ };
120
+ }, [items, getBadgeLabel, containerWidth, lineCount, badgeGap, overflowBadgeWidth, badgeWidths]);
121
+ const renderDefaultOverflow = (count) => /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
122
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
123
+ "+",
124
+ count
125
+ ] }),
126
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: overflowLabel(count) })
127
+ ] });
128
+ const Comp = asChild ? Slot : "div";
129
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
130
+ /* @__PURE__ */ jsxs("div", { ref: measureRef, className: "pointer-events-none invisible absolute flex flex-wrap", style: { gap: badgeGap }, children: [
131
+ items.map((item, index) => (
132
+ // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau
133
+ /* @__PURE__ */ jsx(React.Fragment, { children: renderBadge(item, getBadgeLabel(item)) }, index)
134
+ )),
135
+ renderOverflow ? renderOverflow(99) : renderDefaultOverflow(99)
136
+ ] }),
137
+ isMeasured ? /* @__PURE__ */ jsxs(
138
+ Comp,
139
+ {
140
+ "data-slot": "badge-overflow",
141
+ ...rootProps,
142
+ ref: composedRef,
143
+ className: cn("flex flex-wrap", className),
144
+ style: {
145
+ gap: badgeGap,
146
+ ...style
147
+ },
148
+ children: [
149
+ visibleItems.map((item, index) => (
150
+ // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau
151
+ /* @__PURE__ */ jsx(React.Fragment, { children: renderBadge(item, getBadgeLabel(item)) }, index)
152
+ )),
153
+ hiddenCount > 0 && (renderOverflow ? renderOverflow(hiddenCount) : renderDefaultOverflow(hiddenCount))
154
+ ]
155
+ }
156
+ ) : /* @__PURE__ */ jsx(
157
+ Comp,
158
+ {
159
+ "data-slot": "badge-overflow",
160
+ ...rootProps,
161
+ ref: composedRef,
162
+ className: cn("flex flex-wrap", className),
163
+ style: {
164
+ gap: badgeGap,
165
+ minHeight: placeholderHeight,
166
+ ...style
167
+ },
168
+ children: items.slice(0, Math.min(items.length, lineCount * 3 - (lineCount > 1 ? 1 : 0))).map((item, index) => (
169
+ // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau
170
+ /* @__PURE__ */ jsx(React.Fragment, { children: renderBadge(item, getBadgeLabel(item)) }, index)
171
+ ))
172
+ }
173
+ )
174
+ ] });
175
+ }
176
+ export {
177
+ BadgeOverflow
178
+ };
179
+ //# sourceMappingURL=badge-overflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/badge-overflow.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/badge-overflow) — 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 * ⚠ Cố ý KHÔNG dùng `src/data-grid/use-badge-overflow.ts` (biến thể của tablecn): tầng\n * `data-grid` là entry riêng kéo theo peer `@tanstack/react-table`, entry chính không được chạm.\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { Badge } from \"./badge.js\";\n\ninterface GetBadgeLabel<T> {\n /**\n * Trả về chuỗi nhãn của mỗi badge.\n * Không bắt buộc với mảng giá trị nguyên thuỷ (chuỗi, số); BẮT BUỘC với mảng object.\n * @example getBadgeLabel={(item) => item.name}\n */\n getBadgeLabel: (item: T) => string;\n}\n\ntype BadgeOverflowElement = React.ComponentRef<typeof BadgeOverflow>;\n\ntype BadgeOverflowProps<T = string> = React.ComponentProps<\"div\"> &\n (T extends object ? GetBadgeLabel<T> : Partial<GetBadgeLabel<T>>) & {\n items: T[];\n lineCount?: number;\n renderBadge: (item: T, label: string) => React.ReactNode;\n renderOverflow?: (count: number) => React.ReactNode;\n /**\n * Câu trình đọc màn hình đọc thay cho chữ \"+N\" của ô tràn MẶC ĐỊNH — mặc định\n * `(count) => \"và \" + count + \" mục khác\"`.\n *\n * ⚠ Thêm so với thượng nguồn: \"+3\" đọc lên thành \"cộng ba\" — không nói ba CÁI GÌ. Chữ \"+N\"\n * vẫn hiện nhưng ẩn khỏi trình đọc màn hình; câu này nằm trong `sr-only` ngay cạnh. Là HÀM\n * vì có số chèn vào giữa câu. Không áp dụng khi truyền `renderOverflow` — khi đó nội dung ô\n * tràn (kể cả phần cho trình đọc màn hình) do app tự quyết.\n */\n overflowLabel?: (count: number) => string;\n asChild?: boolean;\n };\n\nconst defaultOverflowLabel = (count: number) => `và ${count} mục khác`;\n\nfunction BadgeOverflow<T = string>(props: BadgeOverflowProps<T>) {\n const {\n items,\n getBadgeLabel: getBadgeLabelProp,\n lineCount = 1,\n renderBadge,\n renderOverflow,\n overflowLabel = defaultOverflowLabel,\n asChild,\n className,\n style,\n ref,\n ...rootProps\n } = props;\n\n const getBadgeLabel = React.useCallback(\n (item: T): string => {\n if (typeof item === \"object\" && !getBadgeLabelProp) {\n throw new Error(\"`getBadgeLabel` là bắt buộc khi phần tử là object\");\n }\n return getBadgeLabelProp ? getBadgeLabelProp(item) : (item as string);\n },\n [getBadgeLabelProp]\n );\n\n const rootRef = React.useRef<BadgeOverflowElement | null>(null);\n const composedRef = useComposedRefs(ref, rootRef);\n const measureRef = React.useRef<HTMLDivElement | null>(null);\n const [containerWidth, setContainerWidth] = React.useState(0);\n const [badgeGap, setBadgeGap] = React.useState(4);\n const [badgeHeight, setBadgeHeight] = React.useState(20);\n const [overflowBadgeWidth, setOverflowBadgeWidth] = React.useState(40);\n const [isMeasured, setIsMeasured] = React.useState(false);\n const [badgeWidths, setBadgeWidths] = React.useState<Map<string, number>>(new Map());\n\n // ⚠ Khác thượng nguồn: `useIsomorphicLayoutEffect` thay `React.useLayoutEffect` — an toàn khi SSR.\n useIsomorphicLayoutEffect(() => {\n if (!rootRef.current || !measureRef.current) return;\n\n function measureContainer() {\n if (!rootRef.current || !measureRef.current) return;\n\n const computedStyle = getComputedStyle(rootRef.current);\n\n const gapValue = computedStyle.gap;\n const gap = gapValue ? parseFloat(gapValue) : 4;\n setBadgeGap(gap);\n\n const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;\n const paddingRight = parseFloat(computedStyle.paddingRight) || 0;\n const totalPadding = paddingLeft + paddingRight;\n\n const widthMap = new Map<string, number>();\n const measureChildren = measureRef.current.children;\n\n for (let i = 0; i < items.length; i++) {\n const child = measureChildren[i] as HTMLElement | undefined;\n if (child) {\n const label = getBadgeLabel(items[i] as T);\n widthMap.set(label, child.offsetWidth);\n }\n }\n setBadgeWidths(widthMap);\n\n const firstBadge = measureChildren[0] as HTMLElement | undefined;\n if (firstBadge) {\n setBadgeHeight(firstBadge.offsetHeight || 20);\n }\n\n const overflowChild = measureChildren[items.length] as HTMLElement | undefined;\n\n if (overflowChild) {\n setOverflowBadgeWidth(overflowChild.offsetWidth || 40);\n }\n\n const width = rootRef.current.clientWidth - totalPadding;\n setContainerWidth(width);\n setIsMeasured(true);\n }\n\n measureContainer();\n\n const resizeObserver = new ResizeObserver(measureContainer);\n resizeObserver.observe(rootRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [items, getBadgeLabel]);\n\n const placeholderHeight = React.useMemo(\n () => badgeHeight * lineCount + badgeGap * (lineCount - 1),\n [badgeHeight, badgeGap, lineCount]\n );\n\n const { visibleItems, hiddenCount } = React.useMemo(() => {\n if (!containerWidth || items.length === 0 || badgeWidths.size === 0) {\n return { visibleItems: items, hiddenCount: 0 };\n }\n\n let currentLineWidth = 0;\n let currentLine = 1;\n const visible: T[] = [];\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n if (!item) continue;\n\n const label = getBadgeLabel(item);\n const badgeWidth = badgeWidths.get(label);\n\n if (!badgeWidth) {\n // Bỏ qua mục chưa đo được.\n continue;\n }\n\n const widthWithGap = badgeWidth + badgeGap;\n const isLastLine = currentLine === lineCount;\n const hasMoreItems = i < items.length - 1;\n\n const availableWidth =\n isLastLine && hasMoreItems ? containerWidth - overflowBadgeWidth - badgeGap : containerWidth;\n\n if (currentLineWidth + widthWithGap <= availableWidth) {\n currentLineWidth += widthWithGap;\n visible.push(item);\n } else if (currentLine < lineCount) {\n currentLine++;\n currentLineWidth = widthWithGap;\n visible.push(item);\n } else {\n // Đang ở dòng cuối và badge này không vừa.\n break;\n }\n }\n\n return {\n visibleItems: visible,\n hiddenCount: Math.max(0, items.length - visible.length)\n };\n }, [items, getBadgeLabel, containerWidth, lineCount, badgeGap, overflowBadgeWidth, badgeWidths]);\n\n /*\n * ⚠ Khác thượng nguồn: ô tràn mặc định là `Badge variant=\"outline\"` của gói (cùng khuôn \"+N\"\n * với `Combobox`) thay cho một `div` tự vẽ viền. Bản gốc chỉ viết `border` trần, mà ở gói này\n * `border` trần nhận viền TRANG TRÍ 1,16:1 — ô \"+N\" nền trong suốt sẽ gần như tàng hình. Biến\n * thể `outline` dùng `border-control-edge` (3,83:1 light · 4,94:1 dark) và cao bằng `Badge`\n * mà app gần như chắc chắn dùng trong `renderBadge`.\n *\n * Chữ \"+N\" ẩn khỏi trình đọc màn hình, câu `overflowLabel` đọc thay — xem prop đó.\n */\n const renderDefaultOverflow = (count: number) => (\n <Badge variant=\"outline\" className=\"shrink-0\">\n <span aria-hidden=\"true\">+{count}</span>\n <span className=\"sr-only\">{overflowLabel(count)}</span>\n </Badge>\n );\n\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <>\n <div ref={measureRef} className=\"pointer-events-none invisible absolute flex flex-wrap\" style={{ gap: badgeGap }}>\n {items.map((item, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau\n <React.Fragment key={index}>{renderBadge(item, getBadgeLabel(item))}</React.Fragment>\n ))}\n {renderOverflow ? renderOverflow(99) : renderDefaultOverflow(99)}\n </div>\n {isMeasured ? (\n <Comp\n data-slot=\"badge-overflow\"\n {...rootProps}\n ref={composedRef}\n className={cn(\"flex flex-wrap\", className)}\n style={{\n gap: badgeGap,\n ...style\n }}\n >\n {visibleItems.map((item, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau\n <React.Fragment key={index}>{renderBadge(item, getBadgeLabel(item))}</React.Fragment>\n ))}\n {hiddenCount > 0 && (renderOverflow ? renderOverflow(hiddenCount) : renderDefaultOverflow(hiddenCount))}\n </Comp>\n ) : (\n <Comp\n data-slot=\"badge-overflow\"\n {...rootProps}\n ref={composedRef}\n className={cn(\"flex flex-wrap\", className)}\n style={{\n gap: badgeGap,\n minHeight: placeholderHeight,\n ...style\n }}\n >\n {items.slice(0, Math.min(items.length, lineCount * 3 - (lineCount > 1 ? 1 : 0))).map((item, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: thượng nguồn khoá theo chỉ số — nhãn có thể trùng nhau\n <React.Fragment key={index}>{renderBadge(item, getBadgeLabel(item))}</React.Fragment>\n ))}\n </Comp>\n )}\n </>\n );\n}\n\nexport { BadgeOverflow, type BadgeOverflowProps };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,79 @@
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 { Button } from './button.js';
5
+
6
+ type BannerVariant = "default" | "info" | "success" | "warning" | "destructive";
7
+ type BannerSide = "top" | "bottom";
8
+ type BannerStrategy = "fixed" | "static" | "sticky" | "absolute";
9
+ interface DivProps extends React.ComponentProps<"div"> {
10
+ asChild?: boolean;
11
+ }
12
+ interface BannerRenderProps {
13
+ id: string;
14
+ variant?: BannerVariant;
15
+ dismissible: boolean;
16
+ onClose: () => void;
17
+ onRemove: () => void;
18
+ }
19
+ type BannerContentValue = React.ReactNode | ((props: BannerRenderProps) => React.ReactNode);
20
+ interface BannerData {
21
+ id: string;
22
+ content: BannerContentValue;
23
+ variant?: BannerVariant;
24
+ priority?: number;
25
+ duration?: number;
26
+ dismissible?: boolean;
27
+ onDismiss?: () => void;
28
+ }
29
+ declare function useBanner(): {
30
+ id: string | undefined;
31
+ variant: BannerVariant | null | undefined;
32
+ dismissible: boolean | undefined;
33
+ onClose: (() => void) | undefined;
34
+ onRemove: (() => void) | undefined;
35
+ };
36
+ interface BannersProps {
37
+ children?: React.ReactNode;
38
+ maxVisible?: number;
39
+ side?: BannerSide;
40
+ strategy?: BannerStrategy;
41
+ container?: Element | DocumentFragment | null;
42
+ }
43
+ declare function Banners(props: BannersProps): React.JSX.Element;
44
+ declare function useBanners(): {
45
+ onBannerAdd: (banner: Omit<BannerData, "id">) => string;
46
+ onBannerRemove: (id: string) => void;
47
+ onBannersClear: () => void;
48
+ banners: BannerData[];
49
+ };
50
+ declare const bannerVariants: (props?: ({
51
+ variant?: "default" | "success" | "warning" | "destructive" | "info" | null | undefined;
52
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
53
+ interface BannerProps extends DivProps, VariantProps<typeof bannerVariants> {
54
+ open?: boolean;
55
+ defaultOpen?: boolean;
56
+ onOpenChange?: (open: boolean) => void;
57
+ onDismiss?: () => void;
58
+ priority?: number;
59
+ duration?: number;
60
+ dismissible?: boolean;
61
+ }
62
+ declare function Banner(props: BannerProps): React.JSX.Element | null;
63
+ declare function BannerIcon(props: DivProps): React.JSX.Element;
64
+ declare function BannerContent(props: DivProps): React.JSX.Element;
65
+ declare function BannerTitle(props: React.ComponentProps<"div">): React.JSX.Element;
66
+ declare function BannerDescription(props: React.ComponentProps<"div">): React.JSX.Element;
67
+ declare function BannerActions(props: DivProps): React.JSX.Element;
68
+ type BannerCloseProps = React.ComponentProps<typeof Button>;
69
+ /**
70
+ * Nút đóng banner. Không truyền `children` thì vẽ icon X.
71
+ *
72
+ * ⚠ Khác thượng nguồn: có tên cho trình đọc màn hình — `aria-label` mặc định "Đóng thông báo",
73
+ * ghi đè bằng chính prop `aria-label`. Bản gốc là `Button` chỉ chứa icon X, tức một nút KHÔNG
74
+ * TÊN. Nếu `children` là CHỮ nhìn thấy được thì truyền `aria-label` trùng với chữ đó (WCAG
75
+ * 2.5.3), hoặc `aria-label={undefined}`.
76
+ */
77
+ declare function BannerClose(props: BannerCloseProps): React.JSX.Element;
78
+
79
+ export { Banner, BannerActions, BannerClose, type BannerCloseProps, BannerContent, type BannerData, BannerDescription, BannerIcon, type BannerProps, type BannerRenderProps, BannerTitle, type BannerVariant, Banners, type BannersProps, useBanner, useBanners };