@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.
- package/LICENSE +35 -0
- package/README.md +23 -7
- package/dist/components/ui/action-bar-constants.d.ts +31 -0
- package/dist/components/ui/action-bar-constants.js +9 -0
- package/dist/components/ui/action-bar-constants.js.map +1 -0
- package/dist/components/ui/action-bar.d.ts +67 -0
- package/dist/components/ui/action-bar.js +522 -0
- package/dist/components/ui/action-bar.js.map +1 -0
- package/dist/components/ui/badge-overflow.d.ts +30 -0
- package/dist/components/ui/badge-overflow.js +179 -0
- package/dist/components/ui/badge-overflow.js.map +1 -0
- package/dist/components/ui/banner.d.ts +79 -0
- package/dist/components/ui/banner.js +518 -0
- package/dist/components/ui/banner.js.map +1 -0
- package/dist/components/ui/circular-progress.d.ts +50 -0
- package/dist/components/ui/circular-progress.js +266 -0
- package/dist/components/ui/circular-progress.js.map +1 -0
- package/dist/components/ui/cropper-constants.d.ts +34 -0
- package/dist/components/ui/cropper-constants.js +10 -0
- package/dist/components/ui/cropper-constants.js.map +1 -0
- package/dist/components/ui/cropper.d.ts +106 -0
- package/dist/components/ui/cropper.js +1269 -0
- package/dist/components/ui/cropper.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +1 -1
- package/dist/components/ui/drawer.js +2 -2
- package/dist/components/ui/drawer.js.map +1 -1
- package/dist/components/ui/kanban-constants.d.ts +61 -0
- package/dist/components/ui/kanban-constants.js +21 -0
- package/dist/components/ui/kanban-constants.js.map +1 -0
- package/dist/components/ui/kanban.d.ts +72 -0
- package/dist/components/ui/kanban.js +787 -0
- package/dist/components/ui/kanban.js.map +1 -0
- package/dist/components/ui/key-value-constants.d.ts +33 -0
- package/dist/components/ui/key-value-constants.js +11 -0
- package/dist/components/ui/key-value-constants.js.map +1 -0
- package/dist/components/ui/key-value.d.ts +96 -0
- package/dist/components/ui/key-value.js +641 -0
- package/dist/components/ui/key-value.js.map +1 -0
- package/dist/components/ui/mask-input-constants.d.ts +99 -0
- package/dist/components/ui/mask-input-constants.js +601 -0
- package/dist/components/ui/mask-input-constants.js.map +1 -0
- package/dist/components/ui/mask-input.d.ts +36 -0
- package/dist/components/ui/mask-input.js +558 -0
- package/dist/components/ui/mask-input.js.map +1 -0
- package/dist/components/ui/mention.d.ts +31 -0
- package/dist/components/ui/mention.js +128 -0
- package/dist/components/ui/mention.js.map +1 -0
- package/dist/components/ui/phone-input-constants.d.ts +54 -0
- package/dist/components/ui/phone-input-constants.js +9 -0
- package/dist/components/ui/phone-input-constants.js.map +1 -0
- package/dist/components/ui/phone-input.d.ts +85 -0
- package/dist/components/ui/phone-input.js +743 -0
- package/dist/components/ui/phone-input.js.map +1 -0
- package/dist/components/ui/rating.d.ts +56 -0
- package/dist/components/ui/rating.js +646 -0
- package/dist/components/ui/rating.js.map +1 -0
- package/dist/components/ui/responsive-dialog.d.ts +35 -0
- package/dist/components/ui/responsive-dialog.js +193 -0
- package/dist/components/ui/responsive-dialog.js.map +1 -0
- package/dist/components/ui/scroll-spy.d.ts +42 -0
- package/dist/components/ui/scroll-spy.js +334 -0
- package/dist/components/ui/scroll-spy.js.map +1 -0
- package/dist/components/ui/stepper-constants.d.ts +29 -0
- package/dist/components/ui/stepper-constants.js +9 -0
- package/dist/components/ui/stepper-constants.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +80 -0
- package/dist/components/ui/stepper.js +902 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/tags-input.d.ts +41 -0
- package/dist/components/ui/tags-input.js +127 -0
- package/dist/components/ui/tags-input.js.map +1 -0
- package/dist/components/ui/time-picker-constants.d.ts +48 -0
- package/dist/components/ui/time-picker-constants.js +14 -0
- package/dist/components/ui/time-picker-constants.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +115 -0
- package/dist/components/ui/time-picker.js +1620 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/timeline.d.ts +32 -0
- package/dist/components/ui/timeline.js +559 -0
- package/dist/components/ui/timeline.js.map +1 -0
- package/dist/components/ui/tour-constants.d.ts +39 -0
- package/dist/components/ui/tour-constants.js +12 -0
- package/dist/components/ui/tour-constants.js.map +1 -0
- package/dist/components/ui/tour.d.ts +124 -0
- package/dist/components/ui/tour.js +1207 -0
- package/dist/components/ui/tour.js.map +1 -0
- package/dist/data-grid/data-grid-cell-variants.js +10 -6
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
- package/dist/data-grid/data-grid-column-header.js +4 -2
- package/dist/data-grid/data-grid-column-header.js.map +1 -1
- package/dist/data-grid/data-grid-context-menu.js +5 -4
- package/dist/data-grid/data-grid-context-menu.js.map +1 -1
- package/dist/data-grid/data-grid-filter-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-filter-menu.js +28 -17
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +12 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.js +7 -5
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
- package/dist/data-grid/data-grid-locale.d.ts +24 -0
- package/dist/data-grid/data-grid-locale.js +25 -0
- package/dist/data-grid/data-grid-locale.js.map +1 -0
- package/dist/data-grid/data-grid-paste-dialog.js +3 -2
- package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
- package/dist/data-grid/data-grid-row-height-menu.d.ts +11 -1
- package/dist/data-grid/data-grid-row-height-menu.js +25 -28
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
- package/dist/data-grid/data-grid-row.js +1 -1
- package/dist/data-grid/data-grid-row.js.map +1 -1
- package/dist/data-grid/data-grid-search.js +6 -5
- package/dist/data-grid/data-grid-search.js.map +1 -1
- package/dist/data-grid/data-grid-select-column.js +3 -1
- package/dist/data-grid/data-grid-select-column.js.map +1 -1
- package/dist/data-grid/data-grid-sort-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-sort-menu.js +25 -13
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
- package/dist/data-grid/data-grid-view-menu.d.ts +12 -1
- package/dist/data-grid/data-grid-view-menu.js +12 -4
- package/dist/data-grid/data-grid-view-menu.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +14 -2
- package/dist/data-grid/data-grid.js +7 -4
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/filters.d.ts +17 -21
- package/dist/data-grid/filters.js +73 -52
- package/dist/data-grid/filters.js.map +1 -1
- package/dist/data-grid/index.d.ts +4 -1
- package/dist/data-grid/index.js +20 -2
- package/dist/data-grid/index.js.map +1 -1
- package/dist/data-grid/messages-en.d.ts +294 -0
- package/dist/data-grid/messages-en.js +279 -0
- package/dist/data-grid/messages-en.js.map +1 -0
- package/dist/data-grid/messages-vi.d.ts +12 -0
- package/dist/data-grid/messages-vi.js +279 -0
- package/dist/data-grid/messages-vi.js.map +1 -0
- package/dist/data-grid/messages.d.ts +549 -262
- package/dist/data-grid/messages.js +16 -275
- package/dist/data-grid/messages.js.map +1 -1
- package/dist/data-grid/sortable.js +1 -1
- package/dist/data-grid/sortable.js.map +1 -1
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +13 -1
- package/dist/data-grid/use-data-grid-undo-redo.js +13 -10
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +281 -1
- package/dist/data-grid/use-data-grid.js +24 -19
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +9 -1
- package/dist/data-grid/utils.js +3 -2
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/compose-refs.js.map +1 -0
- package/dist/lib/use-as-ref.js.map +1 -0
- package/dist/lib/use-is-mobile.d.ts +10 -0
- package/dist/lib/use-is-mobile.js +21 -0
- package/dist/lib/use-is-mobile.js.map +1 -0
- package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
- package/dist/lib/use-lazy-ref.js.map +1 -0
- package/dist/lib/visually-hidden-input.d.ts +12 -0
- package/dist/lib/visually-hidden-input.js +105 -0
- package/dist/lib/visually-hidden-input.js.map +1 -0
- package/package.json +5 -1
- package/styles.css +19 -0
- package/dist/data-grid/compose-refs.js.map +0 -1
- package/dist/data-grid/use-as-ref.js.map +0 -1
- package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
- package/dist/data-grid/use-lazy-ref.js.map +0 -1
- /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
- /package/dist/{data-grid → lib}/compose-refs.js +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useDirection } from "@radix-ui/react-direction";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import * as ReactDOM from "react-dom";
|
|
8
|
+
import { cn } from "../../lib/cn.js";
|
|
9
|
+
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
10
|
+
import { useAsRef } from "../../lib/use-as-ref.js";
|
|
11
|
+
import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
|
|
12
|
+
import { DEFAULT_ACTION_BAR_LABELS } from "./action-bar-constants.js";
|
|
13
|
+
import { Button } from "./button.js";
|
|
14
|
+
const ROOT_NAME = "ActionBar";
|
|
15
|
+
const GROUP_NAME = "ActionBarGroup";
|
|
16
|
+
const ITEM_NAME = "ActionBarItem";
|
|
17
|
+
const CLOSE_NAME = "ActionBarClose";
|
|
18
|
+
const SELECTION_NAME = "ActionBarSelection";
|
|
19
|
+
const SEPARATOR_NAME = "ActionBarSeparator";
|
|
20
|
+
const ITEM_SELECT = "actionbar.itemSelect";
|
|
21
|
+
const ENTRY_FOCUS = "actionbarFocusGroup.onEntryFocus";
|
|
22
|
+
const EVENT_OPTIONS = { bubbles: false, cancelable: true };
|
|
23
|
+
function focusFirst(candidates, preventScroll = false) {
|
|
24
|
+
const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
|
|
25
|
+
for (const candidateRef of candidates) {
|
|
26
|
+
const candidate = candidateRef.current;
|
|
27
|
+
if (!candidate) continue;
|
|
28
|
+
if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
29
|
+
candidate.focus({ preventScroll });
|
|
30
|
+
if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function wrapArray(array, startIndex) {
|
|
34
|
+
return array.map((_, index) => array[(startIndex + index) % array.length]);
|
|
35
|
+
}
|
|
36
|
+
function getDirectionAwareKey(key, dir) {
|
|
37
|
+
if (dir !== "rtl") return key;
|
|
38
|
+
return key === "ArrowLeft" ? "ArrowRight" : key === "ArrowRight" ? "ArrowLeft" : key;
|
|
39
|
+
}
|
|
40
|
+
const ActionBarContext = React.createContext(null);
|
|
41
|
+
function useActionBarContext(consumerName) {
|
|
42
|
+
const context = React.useContext(ActionBarContext);
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${ROOT_NAME}\``);
|
|
45
|
+
}
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
48
|
+
const FocusContext = React.createContext(null);
|
|
49
|
+
function useFocusContext(consumerName) {
|
|
50
|
+
const context = React.useContext(FocusContext);
|
|
51
|
+
if (!context) {
|
|
52
|
+
throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`FocusProvider\``);
|
|
53
|
+
}
|
|
54
|
+
return context;
|
|
55
|
+
}
|
|
56
|
+
function ActionBar(props) {
|
|
57
|
+
const {
|
|
58
|
+
open = false,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
onEscapeKeyDown,
|
|
61
|
+
side = "bottom",
|
|
62
|
+
alignOffset = 0,
|
|
63
|
+
align = "center",
|
|
64
|
+
sideOffset = 16,
|
|
65
|
+
portalContainer: portalContainerProp,
|
|
66
|
+
dir: dirProp,
|
|
67
|
+
orientation = "horizontal",
|
|
68
|
+
loop = true,
|
|
69
|
+
labels: labelsProp,
|
|
70
|
+
className,
|
|
71
|
+
style,
|
|
72
|
+
ref,
|
|
73
|
+
asChild,
|
|
74
|
+
...rootProps
|
|
75
|
+
} = props;
|
|
76
|
+
const [mounted, setMounted] = React.useState(false);
|
|
77
|
+
const rootRef = React.useRef(null);
|
|
78
|
+
const composedRef = useComposedRefs(ref, rootRef);
|
|
79
|
+
const propsRef = useAsRef({
|
|
80
|
+
onEscapeKeyDown,
|
|
81
|
+
onOpenChange
|
|
82
|
+
});
|
|
83
|
+
const dir = useDirection(dirProp);
|
|
84
|
+
const labels = React.useMemo(() => ({ ...DEFAULT_ACTION_BAR_LABELS, ...labelsProp }), [labelsProp]);
|
|
85
|
+
useIsomorphicLayoutEffect(() => {
|
|
86
|
+
setMounted(true);
|
|
87
|
+
}, []);
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
if (!open) return;
|
|
90
|
+
const ownerDocument = rootRef.current?.ownerDocument ?? document;
|
|
91
|
+
function onKeyDown(event) {
|
|
92
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
93
|
+
propsRef.current.onEscapeKeyDown?.(event);
|
|
94
|
+
if (!event.defaultPrevented) {
|
|
95
|
+
propsRef.current.onOpenChange?.(false);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
ownerDocument.addEventListener("keydown", onKeyDown);
|
|
99
|
+
return () => ownerDocument.removeEventListener("keydown", onKeyDown);
|
|
100
|
+
}, [open, propsRef]);
|
|
101
|
+
const contextValue = React.useMemo(
|
|
102
|
+
() => ({
|
|
103
|
+
onOpenChange,
|
|
104
|
+
dir,
|
|
105
|
+
orientation,
|
|
106
|
+
loop,
|
|
107
|
+
labels
|
|
108
|
+
}),
|
|
109
|
+
[onOpenChange, dir, orientation, loop, labels]
|
|
110
|
+
);
|
|
111
|
+
const portalContainer = portalContainerProp ?? (mounted ? globalThis.document?.body : null);
|
|
112
|
+
if (!portalContainer || !open) return null;
|
|
113
|
+
const RootPrimitive = asChild ? Slot : "div";
|
|
114
|
+
return /* @__PURE__ */ jsx(ActionBarContext.Provider, { value: contextValue, children: ReactDOM.createPortal(
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
RootPrimitive,
|
|
117
|
+
{
|
|
118
|
+
role: "toolbar",
|
|
119
|
+
"aria-label": labels.toolbar,
|
|
120
|
+
"aria-orientation": orientation,
|
|
121
|
+
"data-slot": "action-bar",
|
|
122
|
+
"data-side": side,
|
|
123
|
+
"data-align": align,
|
|
124
|
+
"data-orientation": orientation,
|
|
125
|
+
dir,
|
|
126
|
+
...rootProps,
|
|
127
|
+
ref: composedRef,
|
|
128
|
+
className: cn(
|
|
129
|
+
/*
|
|
130
|
+
* ⚠ Khác thượng nguồn: mặt nổi đi qua bộ token `popover` + `shadow-overlay` như mọi lớp
|
|
131
|
+
* nổi của gói (Popover, DropdownMenu). Bản gốc `bg-card shadow-lg` — `shadow-lg` là
|
|
132
|
+
* bóng mặc định của Tailwind, không có bản dark của gói (xem CLAUDE.md "Bóng").
|
|
133
|
+
*/
|
|
134
|
+
"fixed z-50 rounded-lg border bg-popover text-popover-foreground shadow-overlay outline-none",
|
|
135
|
+
"animate-in duration-250 fade-in-0 zoom-in-95 [animation-timing-function:cubic-bezier(0.16,1,0.3,1)]",
|
|
136
|
+
"data-[side=bottom]:slide-in-from-bottom-4 data-[side=top]:slide-in-from-top-4",
|
|
137
|
+
"motion-reduce:animate-none motion-reduce:transition-none",
|
|
138
|
+
/*
|
|
139
|
+
* ⚠ Khác thượng nguồn: chặn bề rộng theo khung nhìn và cho hàng ngang XUỐNG DÒNG. Bản gốc
|
|
140
|
+
* không có giới hạn nào — đo ở 375px: thanh rộng 413px, bắt đầu ở x = −19px, hai mép bị
|
|
141
|
+
* cắt và "Đã chọn 3" vỡ thành 3 dòng. Xuống dòng chứ không cuộn ngang: vùng cuộn sẽ cắt
|
|
142
|
+
* mất vòng focus của các nút ở mép.
|
|
143
|
+
*/
|
|
144
|
+
"max-w-[calc(100vw-1rem)]",
|
|
145
|
+
orientation === "horizontal" ? "flex flex-row flex-wrap items-center gap-2 px-2 py-1.5" : "flex flex-col items-start gap-2 px-1.5 py-2",
|
|
146
|
+
className
|
|
147
|
+
),
|
|
148
|
+
style: {
|
|
149
|
+
[side]: `${sideOffset}px`,
|
|
150
|
+
...align === "center" && {
|
|
151
|
+
left: "50%",
|
|
152
|
+
translate: "-50% 0"
|
|
153
|
+
},
|
|
154
|
+
...align === "start" && { left: `${alignOffset}px` },
|
|
155
|
+
...align === "end" && { right: `${alignOffset}px` },
|
|
156
|
+
...style
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
portalContainer
|
|
161
|
+
) });
|
|
162
|
+
}
|
|
163
|
+
function ActionBarSelection(props) {
|
|
164
|
+
const { className, asChild, count, children, ...selectionProps } = props;
|
|
165
|
+
const { labels } = useActionBarContext(SELECTION_NAME);
|
|
166
|
+
const SelectionPrimitive = asChild ? Slot : "div";
|
|
167
|
+
return /* @__PURE__ */ jsx(
|
|
168
|
+
SelectionPrimitive,
|
|
169
|
+
{
|
|
170
|
+
"data-slot": "action-bar-selection",
|
|
171
|
+
...selectionProps,
|
|
172
|
+
className: cn(
|
|
173
|
+
"flex shrink-0 items-center gap-1 whitespace-nowrap rounded-sm border px-2 py-1 text-sm font-medium tabular-nums",
|
|
174
|
+
className
|
|
175
|
+
),
|
|
176
|
+
children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
177
|
+
count !== void 0 && labels.selected(count),
|
|
178
|
+
children
|
|
179
|
+
] })
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function ActionBarGroup(props) {
|
|
184
|
+
const {
|
|
185
|
+
onBlur: onBlurProp,
|
|
186
|
+
onFocus: onFocusProp,
|
|
187
|
+
onMouseDown: onMouseDownProp,
|
|
188
|
+
className,
|
|
189
|
+
asChild,
|
|
190
|
+
ref,
|
|
191
|
+
...groupProps
|
|
192
|
+
} = props;
|
|
193
|
+
const [tabStopId, setTabStopId] = React.useState(null);
|
|
194
|
+
const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);
|
|
195
|
+
const [focusableItemCount, setFocusableItemCount] = React.useState(0);
|
|
196
|
+
const groupRef = React.useRef(null);
|
|
197
|
+
const composedRef = useComposedRefs(ref, groupRef);
|
|
198
|
+
const isClickFocusRef = React.useRef(false);
|
|
199
|
+
const itemsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
200
|
+
const { dir, orientation } = useActionBarContext(GROUP_NAME);
|
|
201
|
+
const onItemFocus = React.useCallback((tabStopId2) => {
|
|
202
|
+
setTabStopId(tabStopId2);
|
|
203
|
+
}, []);
|
|
204
|
+
const onItemShiftTab = React.useCallback(() => {
|
|
205
|
+
setIsTabbingBackOut(true);
|
|
206
|
+
}, []);
|
|
207
|
+
const onFocusableItemAdd = React.useCallback(() => {
|
|
208
|
+
setFocusableItemCount((prevCount) => prevCount + 1);
|
|
209
|
+
}, []);
|
|
210
|
+
const onFocusableItemRemove = React.useCallback(() => {
|
|
211
|
+
setFocusableItemCount((prevCount) => prevCount - 1);
|
|
212
|
+
}, []);
|
|
213
|
+
const onItemRegister = React.useCallback((item) => {
|
|
214
|
+
itemsRef.current.set(item.id, item);
|
|
215
|
+
}, []);
|
|
216
|
+
const onItemUnregister = React.useCallback((id) => {
|
|
217
|
+
itemsRef.current.delete(id);
|
|
218
|
+
}, []);
|
|
219
|
+
const getItems = React.useCallback(() => {
|
|
220
|
+
return Array.from(itemsRef.current.values()).filter((item) => item.ref.current).sort((a, b) => {
|
|
221
|
+
const elementA = a.ref.current;
|
|
222
|
+
const elementB = b.ref.current;
|
|
223
|
+
if (!elementA || !elementB) return 0;
|
|
224
|
+
const position = elementA.compareDocumentPosition(elementB);
|
|
225
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
228
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
return 0;
|
|
232
|
+
});
|
|
233
|
+
}, []);
|
|
234
|
+
const onBlur = React.useCallback(
|
|
235
|
+
(event) => {
|
|
236
|
+
onBlurProp?.(event);
|
|
237
|
+
if (event.defaultPrevented) return;
|
|
238
|
+
setIsTabbingBackOut(false);
|
|
239
|
+
},
|
|
240
|
+
[onBlurProp]
|
|
241
|
+
);
|
|
242
|
+
const onFocus = React.useCallback(
|
|
243
|
+
(event) => {
|
|
244
|
+
onFocusProp?.(event);
|
|
245
|
+
if (event.defaultPrevented) return;
|
|
246
|
+
const isKeyboardFocus = !isClickFocusRef.current;
|
|
247
|
+
if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
|
|
248
|
+
const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);
|
|
249
|
+
event.currentTarget.dispatchEvent(entryFocusEvent);
|
|
250
|
+
if (!entryFocusEvent.defaultPrevented) {
|
|
251
|
+
const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);
|
|
252
|
+
const currentItem = items.find((item) => item.id === tabStopId);
|
|
253
|
+
const candidateItems = [currentItem, ...items].filter(Boolean);
|
|
254
|
+
const candidateRefs = candidateItems.map((item) => item.ref);
|
|
255
|
+
focusFirst(candidateRefs, false);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
isClickFocusRef.current = false;
|
|
259
|
+
},
|
|
260
|
+
[onFocusProp, isTabbingBackOut, tabStopId]
|
|
261
|
+
);
|
|
262
|
+
const onMouseDown = React.useCallback(
|
|
263
|
+
(event) => {
|
|
264
|
+
onMouseDownProp?.(event);
|
|
265
|
+
if (event.defaultPrevented) return;
|
|
266
|
+
isClickFocusRef.current = true;
|
|
267
|
+
},
|
|
268
|
+
[onMouseDownProp]
|
|
269
|
+
);
|
|
270
|
+
const focusContextValue = React.useMemo(
|
|
271
|
+
() => ({
|
|
272
|
+
tabStopId,
|
|
273
|
+
onItemFocus,
|
|
274
|
+
onItemShiftTab,
|
|
275
|
+
onFocusableItemAdd,
|
|
276
|
+
onFocusableItemRemove,
|
|
277
|
+
onItemRegister,
|
|
278
|
+
onItemUnregister,
|
|
279
|
+
getItems
|
|
280
|
+
}),
|
|
281
|
+
[
|
|
282
|
+
tabStopId,
|
|
283
|
+
onItemFocus,
|
|
284
|
+
onItemShiftTab,
|
|
285
|
+
onFocusableItemAdd,
|
|
286
|
+
onFocusableItemRemove,
|
|
287
|
+
onItemRegister,
|
|
288
|
+
onItemUnregister,
|
|
289
|
+
getItems
|
|
290
|
+
]
|
|
291
|
+
);
|
|
292
|
+
const GroupPrimitive = asChild ? Slot : "div";
|
|
293
|
+
return /* @__PURE__ */ jsx(FocusContext.Provider, { value: focusContextValue, children: /* @__PURE__ */ jsx(
|
|
294
|
+
GroupPrimitive,
|
|
295
|
+
{
|
|
296
|
+
role: "group",
|
|
297
|
+
"data-slot": "action-bar-group",
|
|
298
|
+
"data-orientation": orientation,
|
|
299
|
+
dir,
|
|
300
|
+
tabIndex: isTabbingBackOut || focusableItemCount === 0 ? -1 : 0,
|
|
301
|
+
...groupProps,
|
|
302
|
+
ref: composedRef,
|
|
303
|
+
className: cn(
|
|
304
|
+
"flex gap-2 outline-none",
|
|
305
|
+
orientation === "horizontal" ? "items-center" : "w-full flex-col items-start",
|
|
306
|
+
className
|
|
307
|
+
),
|
|
308
|
+
onBlur,
|
|
309
|
+
onFocus,
|
|
310
|
+
onMouseDown
|
|
311
|
+
}
|
|
312
|
+
) });
|
|
313
|
+
}
|
|
314
|
+
function ActionBarItem(props) {
|
|
315
|
+
const {
|
|
316
|
+
onSelect,
|
|
317
|
+
onClick: onClickProp,
|
|
318
|
+
onFocus: onFocusProp,
|
|
319
|
+
onKeyDown: onKeyDownProp,
|
|
320
|
+
onMouseDown: onMouseDownProp,
|
|
321
|
+
className,
|
|
322
|
+
disabled,
|
|
323
|
+
ref,
|
|
324
|
+
...itemProps
|
|
325
|
+
} = props;
|
|
326
|
+
const itemRef = React.useRef(null);
|
|
327
|
+
const composedRef = useComposedRefs(ref, itemRef);
|
|
328
|
+
const isMouseClickRef = React.useRef(false);
|
|
329
|
+
const { onOpenChange, dir, orientation, loop } = useActionBarContext(ITEM_NAME);
|
|
330
|
+
const focusContext = useFocusContext(ITEM_NAME);
|
|
331
|
+
const itemId = React.useId();
|
|
332
|
+
const isTabStop = focusContext.tabStopId === itemId;
|
|
333
|
+
useIsomorphicLayoutEffect(() => {
|
|
334
|
+
focusContext.onItemRegister({
|
|
335
|
+
id: itemId,
|
|
336
|
+
ref: itemRef,
|
|
337
|
+
disabled: !!disabled
|
|
338
|
+
});
|
|
339
|
+
if (!disabled) {
|
|
340
|
+
focusContext.onFocusableItemAdd();
|
|
341
|
+
}
|
|
342
|
+
return () => {
|
|
343
|
+
focusContext.onItemUnregister(itemId);
|
|
344
|
+
if (!disabled) {
|
|
345
|
+
focusContext.onFocusableItemRemove();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}, [focusContext, itemId, disabled]);
|
|
349
|
+
const onClick = React.useCallback(
|
|
350
|
+
(event) => {
|
|
351
|
+
onClickProp?.(event);
|
|
352
|
+
if (event.defaultPrevented) return;
|
|
353
|
+
const item = itemRef.current;
|
|
354
|
+
if (!item) return;
|
|
355
|
+
const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
|
|
356
|
+
bubbles: true,
|
|
357
|
+
cancelable: true
|
|
358
|
+
});
|
|
359
|
+
item.addEventListener(ITEM_SELECT, (event2) => onSelect?.(event2), {
|
|
360
|
+
once: true
|
|
361
|
+
});
|
|
362
|
+
item.dispatchEvent(itemSelectEvent);
|
|
363
|
+
if (!itemSelectEvent.defaultPrevented) {
|
|
364
|
+
onOpenChange?.(false);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
[onClickProp, onOpenChange, onSelect]
|
|
368
|
+
);
|
|
369
|
+
const onFocus = React.useCallback(
|
|
370
|
+
(event) => {
|
|
371
|
+
onFocusProp?.(event);
|
|
372
|
+
if (event.defaultPrevented) return;
|
|
373
|
+
focusContext.onItemFocus(itemId);
|
|
374
|
+
isMouseClickRef.current = false;
|
|
375
|
+
},
|
|
376
|
+
[onFocusProp, focusContext, itemId]
|
|
377
|
+
);
|
|
378
|
+
const onKeyDown = React.useCallback(
|
|
379
|
+
(event) => {
|
|
380
|
+
onKeyDownProp?.(event);
|
|
381
|
+
if (event.defaultPrevented) return;
|
|
382
|
+
if (event.key === "Tab" && event.shiftKey) {
|
|
383
|
+
focusContext.onItemShiftTab();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (event.target !== event.currentTarget) return;
|
|
387
|
+
const key = getDirectionAwareKey(event.key, dir);
|
|
388
|
+
let focusIntent;
|
|
389
|
+
if (orientation === "horizontal") {
|
|
390
|
+
if (key === "ArrowLeft") focusIntent = "prev";
|
|
391
|
+
else if (key === "ArrowRight") focusIntent = "next";
|
|
392
|
+
else if (key === "Home") focusIntent = "first";
|
|
393
|
+
else if (key === "End") focusIntent = "last";
|
|
394
|
+
} else {
|
|
395
|
+
if (key === "ArrowUp") focusIntent = "prev";
|
|
396
|
+
else if (key === "ArrowDown") focusIntent = "next";
|
|
397
|
+
else if (key === "Home") focusIntent = "first";
|
|
398
|
+
else if (key === "End") focusIntent = "last";
|
|
399
|
+
}
|
|
400
|
+
if (focusIntent !== void 0) {
|
|
401
|
+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
const items = focusContext.getItems().filter((item) => !item.disabled);
|
|
404
|
+
let candidateRefs = items.map((item) => item.ref);
|
|
405
|
+
if (focusIntent === "last") {
|
|
406
|
+
candidateRefs.reverse();
|
|
407
|
+
} else if (focusIntent === "prev" || focusIntent === "next") {
|
|
408
|
+
if (focusIntent === "prev") candidateRefs.reverse();
|
|
409
|
+
const currentIndex = candidateRefs.findIndex((ref2) => ref2.current === event.currentTarget);
|
|
410
|
+
candidateRefs = loop ? wrapArray(candidateRefs, currentIndex + 1) : candidateRefs.slice(currentIndex + 1);
|
|
411
|
+
}
|
|
412
|
+
queueMicrotask(() => focusFirst(candidateRefs));
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
[onKeyDownProp, focusContext, dir, orientation, loop]
|
|
416
|
+
);
|
|
417
|
+
const onMouseDown = React.useCallback(
|
|
418
|
+
(event) => {
|
|
419
|
+
onMouseDownProp?.(event);
|
|
420
|
+
if (event.defaultPrevented) return;
|
|
421
|
+
isMouseClickRef.current = true;
|
|
422
|
+
if (disabled) {
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
} else {
|
|
425
|
+
focusContext.onItemFocus(itemId);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
[onMouseDownProp, focusContext, itemId, disabled]
|
|
429
|
+
);
|
|
430
|
+
return /* @__PURE__ */ jsx(
|
|
431
|
+
Button,
|
|
432
|
+
{
|
|
433
|
+
type: "button",
|
|
434
|
+
"data-slot": "action-bar-item",
|
|
435
|
+
variant: "secondary",
|
|
436
|
+
size: "sm",
|
|
437
|
+
disabled,
|
|
438
|
+
tabIndex: isTabStop ? 0 : -1,
|
|
439
|
+
...itemProps,
|
|
440
|
+
className: cn(
|
|
441
|
+
/*
|
|
442
|
+
* ⚠ Khác thượng nguồn: `Button` của gói KHÔNG tự đặt cỡ `svg` con (khác shadcn gốc), nên
|
|
443
|
+
* `<Copy />` trần trong mục ra 24px giữa một nút cao 32px. Đặt cỡ ở đây, đúng như
|
|
444
|
+
* `DropdownMenuItem`/`CommandItem` của gói — không thêm luật đó vào `Button` (CHANGELOG 2.0.0).
|
|
445
|
+
*/
|
|
446
|
+
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
447
|
+
orientation === "vertical" && "w-full",
|
|
448
|
+
className
|
|
449
|
+
),
|
|
450
|
+
ref: composedRef,
|
|
451
|
+
onClick,
|
|
452
|
+
onFocus,
|
|
453
|
+
onKeyDown,
|
|
454
|
+
onMouseDown
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
function ActionBarClose(props) {
|
|
459
|
+
const { asChild, className, onClick, children, ...closeProps } = props;
|
|
460
|
+
const { onOpenChange, labels } = useActionBarContext(CLOSE_NAME);
|
|
461
|
+
const onCloseClick = React.useCallback(
|
|
462
|
+
(event) => {
|
|
463
|
+
onClick?.(event);
|
|
464
|
+
if (event.defaultPrevented) return;
|
|
465
|
+
onOpenChange?.(false);
|
|
466
|
+
},
|
|
467
|
+
[onOpenChange, onClick]
|
|
468
|
+
);
|
|
469
|
+
const ClosePrimitive = asChild ? Slot : "button";
|
|
470
|
+
return /* @__PURE__ */ jsx(
|
|
471
|
+
ClosePrimitive,
|
|
472
|
+
{
|
|
473
|
+
type: "button",
|
|
474
|
+
"aria-label": labels.close,
|
|
475
|
+
"data-slot": "action-bar-close",
|
|
476
|
+
...closeProps,
|
|
477
|
+
className: cn(
|
|
478
|
+
/*
|
|
479
|
+
* ⚠ Khác thượng nguồn: cùng khuôn với nút đóng của `Dialog` — `text-muted-foreground` →
|
|
480
|
+
* `hover:text-foreground`, vòng focus `ring-ring/50` có `ring-offset`. Bản gốc
|
|
481
|
+
* `opacity-70 hover:opacity-100` và `focus-visible:border-ring` (nút không có viền nên
|
|
482
|
+
* dòng đó không vẽ gì).
|
|
483
|
+
* ⚠ Và vùng bấm: icon 14px trần là 14×14px, dưới mức 24×24 của WCAG 2.5.8. `p-[5px]` nâng
|
|
484
|
+
* vùng bấm lên 24px, `-m-[5px]` trả lại đúng chừng đó để chip chứa nó không phình ra.
|
|
485
|
+
*/
|
|
486
|
+
"-m-[5px] rounded-xs p-[5px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
487
|
+
className
|
|
488
|
+
),
|
|
489
|
+
onClick: onCloseClick,
|
|
490
|
+
children: children ?? (asChild ? null : /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }))
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
function ActionBarSeparator(props) {
|
|
495
|
+
const { orientation: orientationProp, asChild, className, ...separatorProps } = props;
|
|
496
|
+
const context = useActionBarContext(SEPARATOR_NAME);
|
|
497
|
+
const orientation = orientationProp ?? context.orientation;
|
|
498
|
+
const SeparatorPrimitive = asChild ? Slot : "div";
|
|
499
|
+
return /* @__PURE__ */ jsx(
|
|
500
|
+
SeparatorPrimitive,
|
|
501
|
+
{
|
|
502
|
+
"aria-hidden": "true",
|
|
503
|
+
"data-orientation": orientation,
|
|
504
|
+
"data-slot": "action-bar-separator",
|
|
505
|
+
...separatorProps,
|
|
506
|
+
className: cn(
|
|
507
|
+
"bg-border in-data-[slot=action-bar-selection]:ml-0.5 in-data-[slot=action-bar-selection]:h-4 in-data-[slot=action-bar-selection]:w-px",
|
|
508
|
+
orientation === "horizontal" ? "h-6 w-px" : "h-px w-full",
|
|
509
|
+
className
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
export {
|
|
515
|
+
ActionBar,
|
|
516
|
+
ActionBarClose,
|
|
517
|
+
ActionBarGroup,
|
|
518
|
+
ActionBarItem,
|
|
519
|
+
ActionBarSelection,
|
|
520
|
+
ActionBarSeparator
|
|
521
|
+
};
|
|
522
|
+
//# sourceMappingURL=action-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/action-bar.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/action-bar) — 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 * Dùng ở Comitor: thanh THAO TÁC HÀNG LOẠT nổi ở đáy màn hình khi có dòng được chọn trong\n * `DataTable` / `DataGrid` — `open={selectedIds.length > 0}`, và `onOpenChange(false)` xoá vùng chọn.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { type ActionBarLabels, DEFAULT_ACTION_BAR_LABELS } from \"./action-bar-constants.js\";\nimport { Button } from \"./button.js\";\n\nconst ROOT_NAME = \"ActionBar\";\nconst GROUP_NAME = \"ActionBarGroup\";\nconst ITEM_NAME = \"ActionBarItem\";\nconst CLOSE_NAME = \"ActionBarClose\";\nconst SELECTION_NAME = \"ActionBarSelection\";\nconst SEPARATOR_NAME = \"ActionBarSeparator\";\nconst ITEM_SELECT = \"actionbar.itemSelect\";\nconst ENTRY_FOCUS = \"actionbarFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype RootElement = React.ComponentRef<typeof ActionBar>;\ntype ItemElement = React.ComponentRef<typeof ActionBarItem>;\ntype CloseElement = React.ComponentRef<typeof ActionBarClose>;\n\nfunction focusFirst(candidates: React.RefObject<HTMLElement | null>[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\nfunction wrapArray<T>(array: T[], startIndex: number) {\n return array.map<T>((_, index) => array[(startIndex + index) % array.length] as T);\n}\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== \"rtl\") return key;\n return key === \"ArrowLeft\" ? \"ArrowRight\" : key === \"ArrowRight\" ? \"ArrowLeft\" : key;\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n disabled: boolean;\n}\n\ninterface ActionBarContextValue {\n onOpenChange?: (open: boolean) => void;\n dir: Direction;\n orientation: Orientation;\n loop: boolean;\n labels: ActionBarLabels;\n}\n\nconst ActionBarContext = React.createContext<ActionBarContextValue | null>(null);\n\nfunction useActionBarContext(consumerName: string) {\n const context = React.useContext(ActionBarContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface ActionBarProps extends DivProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n align?: \"start\" | \"center\" | \"end\";\n alignOffset?: number;\n side?: \"top\" | \"bottom\";\n sideOffset?: number;\n portalContainer?: Element | DocumentFragment | null;\n dir?: Direction;\n orientation?: Orientation;\n loop?: boolean;\n /**\n * Chuỗi hiển thị và chuỗi a11y (tên thanh, dòng \"Đã chọn N\", tên nút đóng) — mặc định\n * `DEFAULT_ACTION_BAR_LABELS` (tiếng Việt); truyền một phần là đủ.\n *\n * ⚠ Thêm so với thượng nguồn: bản gốc không có chuỗi nào — thanh `role=\"toolbar\"` không tên và\n * nút đóng chỉ chứa `<X />` là một nút KHÔNG TÊN với trình đọc màn hình.\n */\n labels?: Partial<ActionBarLabels>;\n}\n\nfunction ActionBar(props: ActionBarProps) {\n const {\n open = false,\n onOpenChange,\n onEscapeKeyDown,\n side = \"bottom\",\n alignOffset = 0,\n align = \"center\",\n sideOffset = 16,\n portalContainer: portalContainerProp,\n dir: dirProp,\n orientation = \"horizontal\",\n loop = true,\n labels: labelsProp,\n className,\n style,\n ref,\n asChild,\n ...rootProps\n } = props;\n\n const [mounted, setMounted] = React.useState(false);\n\n const rootRef = React.useRef<RootElement>(null);\n const composedRef = useComposedRefs(ref, rootRef);\n\n const propsRef = useAsRef({\n onEscapeKeyDown,\n onOpenChange\n });\n\n const dir = useDirection(dirProp);\n\n const labels = React.useMemo<ActionBarLabels>(() => ({ ...DEFAULT_ACTION_BAR_LABELS, ...labelsProp }), [labelsProp]);\n\n // Chỉ đọc `document.body` SAU khi mount — lượt render phía server không chạm `document`.\n useIsomorphicLayoutEffect(() => {\n setMounted(true);\n }, []);\n\n React.useEffect(() => {\n if (!open) return;\n\n const ownerDocument = rootRef.current?.ownerDocument ?? document;\n\n function onKeyDown(event: KeyboardEvent) {\n /*\n * ⚠ Khác thượng nguồn: bỏ qua phím Escape ĐÃ được xử lý (`defaultPrevented`) hoặc đang gõ dở\n * bằng bộ gõ (`isComposing` — Telex/VNI qua IME hệ điều hành). Bản gốc nghe Escape ở cấp\n * `document`, nên Escape để đóng một Dialog/Popover/DropdownMenu của Radix (các lớp đó\n * `preventDefault()` khi tự đóng) cũng đóng luôn thanh này — tức XOÁ VÙNG CHỌN của người dùng\n * chỉ vì họ đóng một menu.\n */\n if (event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n\n propsRef.current.onEscapeKeyDown?.(event);\n if (!event.defaultPrevented) {\n propsRef.current.onOpenChange?.(false);\n }\n }\n\n ownerDocument.addEventListener(\"keydown\", onKeyDown);\n return () => ownerDocument.removeEventListener(\"keydown\", onKeyDown);\n }, [open, propsRef]);\n\n const contextValue = React.useMemo<ActionBarContextValue>(\n () => ({\n onOpenChange,\n dir,\n orientation,\n loop,\n labels\n }),\n [onOpenChange, dir, orientation, loop, labels]\n );\n\n const portalContainer = portalContainerProp ?? (mounted ? globalThis.document?.body : null);\n\n if (!portalContainer || !open) return null;\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ActionBarContext.Provider value={contextValue}>\n {ReactDOM.createPortal(\n <RootPrimitive\n role=\"toolbar\"\n aria-label={labels.toolbar}\n aria-orientation={orientation}\n data-slot=\"action-bar\"\n data-side={side}\n data-align={align}\n data-orientation={orientation}\n dir={dir}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: mặt nổi đi qua bộ token `popover` + `shadow-overlay` như mọi lớp\n * nổi của gói (Popover, DropdownMenu). Bản gốc `bg-card shadow-lg` — `shadow-lg` là\n * bóng mặc định của Tailwind, không có bản dark của gói (xem CLAUDE.md \"Bóng\").\n */\n \"fixed z-50 rounded-lg border bg-popover text-popover-foreground shadow-overlay outline-none\",\n \"animate-in duration-250 fade-in-0 zoom-in-95 [animation-timing-function:cubic-bezier(0.16,1,0.3,1)]\",\n \"data-[side=bottom]:slide-in-from-bottom-4 data-[side=top]:slide-in-from-top-4\",\n \"motion-reduce:animate-none motion-reduce:transition-none\",\n /*\n * ⚠ Khác thượng nguồn: chặn bề rộng theo khung nhìn và cho hàng ngang XUỐNG DÒNG. Bản gốc\n * không có giới hạn nào — đo ở 375px: thanh rộng 413px, bắt đầu ở x = −19px, hai mép bị\n * cắt và \"Đã chọn 3\" vỡ thành 3 dòng. Xuống dòng chứ không cuộn ngang: vùng cuộn sẽ cắt\n * mất vòng focus của các nút ở mép.\n */\n \"max-w-[calc(100vw-1rem)]\",\n orientation === \"horizontal\"\n ? \"flex flex-row flex-wrap items-center gap-2 px-2 py-1.5\"\n : \"flex flex-col items-start gap-2 px-1.5 py-2\",\n className\n )}\n style={{\n [side]: `${sideOffset}px`,\n ...(align === \"center\" && {\n left: \"50%\",\n translate: \"-50% 0\"\n }),\n ...(align === \"start\" && { left: `${alignOffset}px` }),\n ...(align === \"end\" && { right: `${alignOffset}px` }),\n ...style\n }}\n />,\n portalContainer\n )}\n </ActionBarContext.Provider>\n );\n}\n\ninterface ActionBarSelectionProps extends DivProps {\n /**\n * Số mục đang chọn. Có giá trị thì phần tử tự in `labels.selected(count)` (\"Đã chọn 3\") TRƯỚC\n * `children` — nên `children` chỉ còn là dấu ngăn + nút đóng.\n *\n * ⚠ Thêm so với thượng nguồn: bản gốc để app tự viết chữ (\"{n} selected\"), tức chuỗi hiển thị\n * không đi qua `labels`. Bỏ qua khi `asChild` (Slot chỉ nhận đúng MỘT phần tử con).\n */\n count?: number;\n}\n\nfunction ActionBarSelection(props: ActionBarSelectionProps) {\n const { className, asChild, count, children, ...selectionProps } = props;\n\n const { labels } = useActionBarContext(SELECTION_NAME);\n\n const SelectionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SelectionPrimitive\n data-slot=\"action-bar-selection\"\n {...selectionProps}\n // ⚠ Khác thượng nguồn: `whitespace-nowrap shrink-0` — con số đếm không được vỡ dòng khi thanh hẹp.\n className={cn(\n \"flex shrink-0 items-center gap-1 whitespace-nowrap rounded-sm border px-2 py-1 text-sm font-medium tabular-nums\",\n className\n )}\n >\n {asChild ? (\n children\n ) : (\n <>\n {count !== undefined && labels.selected(count)}\n {children}\n </>\n )}\n </SelectionPrimitive>\n );\n}\n\nfunction ActionBarGroup(props: DivProps) {\n const {\n onBlur: onBlurProp,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n className,\n asChild,\n ref,\n ...groupProps\n } = props;\n\n const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n\n const groupRef = React.useRef<HTMLDivElement>(null);\n const composedRef = useComposedRefs(ref, groupRef);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const { dir, orientation } = useActionBarContext(GROUP_NAME);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n onBlurProp?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [onBlurProp]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n onFocusProp?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [currentItem, ...items].filter(Boolean) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [onFocusProp, isTabbingBackOut, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n onMouseDownProp?.(event);\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [onMouseDownProp]\n );\n\n const focusContextValue = React.useMemo<FocusContextValue>(\n () => ({\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n ]\n );\n\n const GroupPrimitive = asChild ? Slot : \"div\";\n\n return (\n <FocusContext.Provider value={focusContextValue}>\n <GroupPrimitive\n role=\"group\"\n data-slot=\"action-bar-group\"\n data-orientation={orientation}\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...groupProps}\n ref={composedRef}\n className={cn(\n \"flex gap-2 outline-none\",\n orientation === \"horizontal\" ? \"items-center\" : \"w-full flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n />\n </FocusContext.Provider>\n );\n}\n\ninterface ActionBarItemProps extends Omit<React.ComponentProps<typeof Button>, \"onSelect\"> {\n onSelect?: (event: Event) => void;\n}\n\nfunction ActionBarItem(props: ActionBarItemProps) {\n const {\n onSelect,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n className,\n disabled,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n const isMouseClickRef = React.useRef(false);\n\n const { onOpenChange, dir, orientation, loop } = useActionBarContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n\n const itemId = React.useId();\n const isTabStop = focusContext.tabStopId === itemId;\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n disabled: !!disabled\n });\n\n if (!disabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!disabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, disabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n onClickProp?.(event);\n if (event.defaultPrevented) return;\n\n const item = itemRef.current;\n if (!item) return;\n\n const itemSelectEvent = new CustomEvent(ITEM_SELECT, {\n bubbles: true,\n cancelable: true\n });\n\n item.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {\n once: true\n });\n\n item.dispatchEvent(itemSelectEvent);\n\n if (!itemSelectEvent.defaultPrevented) {\n onOpenChange?.(false);\n }\n },\n [onClickProp, onOpenChange, onSelect]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n onFocusProp?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n isMouseClickRef.current = false;\n },\n [onFocusProp, focusContext, itemId]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n onKeyDownProp?.(event);\n if (event.defaultPrevented) return;\n\n if (event.key === \"Tab\" && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const key = getDirectionAwareKey(event.key, dir);\n let focusIntent: \"first\" | \"last\" | \"prev\" | \"next\" | undefined;\n\n if (orientation === \"horizontal\") {\n if (key === \"ArrowLeft\") focusIntent = \"prev\";\n else if (key === \"ArrowRight\") focusIntent = \"next\";\n else if (key === \"Home\") focusIntent = \"first\";\n else if (key === \"End\") focusIntent = \"last\";\n } else {\n if (key === \"ArrowUp\") focusIntent = \"prev\";\n else if (key === \"ArrowDown\") focusIntent = \"next\";\n else if (key === \"Home\") focusIntent = \"first\";\n else if (key === \"End\") focusIntent = \"last\";\n }\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === \"last\") {\n candidateRefs.reverse();\n } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n if (focusIntent === \"prev\") candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex((ref) => ref.current === event.currentTarget);\n candidateRefs = loop ? wrapArray(candidateRefs, currentIndex + 1) : candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [onKeyDownProp, focusContext, dir, orientation, loop]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n onMouseDownProp?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (disabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [onMouseDownProp, focusContext, itemId, disabled]\n );\n\n return (\n <Button\n type=\"button\"\n data-slot=\"action-bar-item\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: `Button` của gói KHÔNG tự đặt cỡ `svg` con (khác shadcn gốc), nên\n * `<Copy />` trần trong mục ra 24px giữa một nút cao 32px. Đặt cỡ ở đây, đúng như\n * `DropdownMenuItem`/`CommandItem` của gói — không thêm luật đó vào `Button` (CHANGELOG 2.0.0).\n */\n \"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n orientation === \"vertical\" && \"w-full\",\n className\n )}\n ref={composedRef}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n />\n );\n}\n\ninterface ActionBarCloseProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\n/**\n * Nút đóng thanh (thường là \"bỏ chọn\"). Không truyền `children` thì vẽ icon X.\n *\n * ⚠ Khác thượng nguồn: có tên cho trình đọc màn hình — `aria-label` mặc định `labels.close`\n * (\"Bỏ chọn\"). Bản gốc chỉ render `children`, và khuôn dùng của chính thượng nguồn là\n * `<ActionBarClose><X /></ActionBarClose>`: một nút KHÔNG TÊN. Nếu `children` là CHỮ nhìn thấy\n * được thì truyền `aria-label` trùng với chữ đó (WCAG 2.5.3), hoặc `aria-label={undefined}`.\n */\nfunction ActionBarClose(props: ActionBarCloseProps) {\n const { asChild, className, onClick, children, ...closeProps } = props;\n\n const { onOpenChange, labels } = useActionBarContext(CLOSE_NAME);\n\n const onCloseClick = React.useCallback(\n (event: React.MouseEvent<CloseElement>) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n\n onOpenChange?.(false);\n },\n [onOpenChange, onClick]\n );\n\n const ClosePrimitive = asChild ? Slot : \"button\";\n\n return (\n <ClosePrimitive\n type=\"button\"\n aria-label={labels.close}\n data-slot=\"action-bar-close\"\n {...closeProps}\n className={cn(\n /*\n * ⚠ Khác thượng nguồn: cùng khuôn với nút đóng của `Dialog` — `text-muted-foreground` →\n * `hover:text-foreground`, vòng focus `ring-ring/50` có `ring-offset`. Bản gốc\n * `opacity-70 hover:opacity-100` và `focus-visible:border-ring` (nút không có viền nên\n * dòng đó không vẽ gì).\n * ⚠ Và vùng bấm: icon 14px trần là 14×14px, dưới mức 24×24 của WCAG 2.5.8. `p-[5px]` nâng\n * vùng bấm lên 24px, `-m-[5px]` trả lại đúng chừng đó để chip chứa nó không phình ra.\n */\n \"-m-[5px] rounded-xs p-[5px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5\",\n className\n )}\n onClick={onCloseClick}\n >\n {children ?? (asChild ? null : <X aria-hidden=\"true\" />)}\n </ClosePrimitive>\n );\n}\n\ninterface ActionBarSeparatorProps extends DivProps {\n orientation?: Orientation;\n}\n\nfunction ActionBarSeparator(props: ActionBarSeparatorProps) {\n const { orientation: orientationProp, asChild, className, ...separatorProps } = props;\n\n const context = useActionBarContext(SEPARATOR_NAME);\n const orientation = orientationProp ?? context.orientation;\n\n const SeparatorPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SeparatorPrimitive\n /*\n * ⚠ Khác thượng nguồn: bỏ thuộc tính role \"separator\" và `aria-orientation` — bản gốc đặt\n * chúng CÙNG `aria-hidden=\"true\"`, tức phần tử đã ra khỏi cây a11y và hai thuộc tính kia\n * không tới được trình đọc màn hình. Để lại thì cổng tương phản đọc thuộc tính role như dấu\n * hiệu CONTROL (thanh chia `Resizable`) và bắt vạch trang trí 1px này chịu 3:1. Hướng vẫn\n * có ở `data-orientation`.\n */\n aria-hidden=\"true\"\n data-orientation={orientation}\n data-slot=\"action-bar-separator\"\n {...separatorProps}\n className={cn(\n \"bg-border in-data-[slot=action-bar-selection]:ml-0.5 in-data-[slot=action-bar-selection]:h-4 in-data-[slot=action-bar-selection]:w-px\",\n orientation === \"horizontal\" ? \"h-6 w-px\" : \"h-px w-full\",\n className\n )}\n />\n );\n}\n\nexport {\n ActionBar,\n ActionBarClose,\n type ActionBarCloseProps,\n ActionBarGroup,\n ActionBarItem,\n type ActionBarItemProps,\n type ActionBarProps,\n ActionBarSelection,\n type ActionBarSelectionProps,\n ActionBarSeparator,\n type ActionBarSeparatorProps\n};\n"],"mappings":"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","names":["tabStopId","event","ref"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface GetBadgeLabel<T> {
|
|
4
|
+
/**
|
|
5
|
+
* Trả về chuỗi nhãn của mỗi badge.
|
|
6
|
+
* 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.
|
|
7
|
+
* @example getBadgeLabel={(item) => item.name}
|
|
8
|
+
*/
|
|
9
|
+
getBadgeLabel: (item: T) => string;
|
|
10
|
+
}
|
|
11
|
+
type BadgeOverflowProps<T = string> = React.ComponentProps<"div"> & (T extends object ? GetBadgeLabel<T> : Partial<GetBadgeLabel<T>>) & {
|
|
12
|
+
items: T[];
|
|
13
|
+
lineCount?: number;
|
|
14
|
+
renderBadge: (item: T, label: string) => React.ReactNode;
|
|
15
|
+
renderOverflow?: (count: number) => React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Câu trình đọc màn hình đọc thay cho chữ "+N" của ô tràn MẶC ĐỊNH — mặc định
|
|
18
|
+
* `(count) => "và " + count + " mục khác"`.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ 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"
|
|
21
|
+
* 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
|
|
22
|
+
* vì có số chèn vào giữa câu. Không áp dụng khi truyền `renderOverflow` — khi đó nội dung ô
|
|
23
|
+
* tràn (kể cả phần cho trình đọc màn hình) do app tự quyết.
|
|
24
|
+
*/
|
|
25
|
+
overflowLabel?: (count: number) => string;
|
|
26
|
+
asChild?: boolean;
|
|
27
|
+
};
|
|
28
|
+
declare function BadgeOverflow<T = string>(props: BadgeOverflowProps<T>): React.JSX.Element;
|
|
29
|
+
|
|
30
|
+
export { BadgeOverflow, type BadgeOverflowProps };
|