@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.
- package/LICENSE +35 -0
- package/README.md +14 -6
- 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-wrapper.js +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
- package/dist/data-grid/data-grid-context-menu.js +1 -1
- package/dist/data-grid/data-grid-context-menu.js.map +1 -1
- package/dist/data-grid/data-grid-paste-dialog.js +1 -1
- package/dist/data-grid/data-grid-paste-dialog.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 +1 -1
- package/dist/data-grid/data-grid-search.js.map +1 -1
- package/dist/data-grid/data-grid.js +1 -1
- package/dist/data-grid/data-grid.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.js +2 -2
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.js +3 -3
- package/dist/data-grid/use-data-grid.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,646 @@
|
|
|
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 { Star } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { cn } from "../../lib/cn.js";
|
|
8
|
+
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
9
|
+
import { useAsRef } from "../../lib/use-as-ref.js";
|
|
10
|
+
import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
|
|
11
|
+
import { useLazyRef } from "../../lib/use-lazy-ref.js";
|
|
12
|
+
import { VisuallyHiddenInput } from "../../lib/visually-hidden-input.js";
|
|
13
|
+
const ROOT_NAME = "Rating";
|
|
14
|
+
const ITEM_NAME = "RatingItem";
|
|
15
|
+
const ENTRY_FOCUS = "ratingFocusGroup.onEntryFocus";
|
|
16
|
+
const EVENT_OPTIONS = { bubbles: false, cancelable: true };
|
|
17
|
+
function getItemId(id, value) {
|
|
18
|
+
return `${id}-item-${value}`;
|
|
19
|
+
}
|
|
20
|
+
function getPartialFillGradientId(id, step) {
|
|
21
|
+
return `partial-fill-gradient-${id}-${step}`;
|
|
22
|
+
}
|
|
23
|
+
const MAP_KEY_TO_FOCUS_INTENT = {
|
|
24
|
+
ArrowLeft: "prev",
|
|
25
|
+
ArrowUp: "prev",
|
|
26
|
+
ArrowRight: "next",
|
|
27
|
+
ArrowDown: "next",
|
|
28
|
+
Home: "first",
|
|
29
|
+
End: "last"
|
|
30
|
+
};
|
|
31
|
+
function getDirectionAwareKey(key, dir) {
|
|
32
|
+
if (dir !== "rtl") return key;
|
|
33
|
+
return key === "ArrowLeft" ? "ArrowRight" : key === "ArrowRight" ? "ArrowLeft" : key;
|
|
34
|
+
}
|
|
35
|
+
function getFocusIntent(event, dir, orientation) {
|
|
36
|
+
const key = getDirectionAwareKey(event.key, dir);
|
|
37
|
+
if (orientation === "horizontal" && ["ArrowUp", "ArrowDown"].includes(key)) return void 0;
|
|
38
|
+
if (orientation === "vertical" && ["ArrowLeft", "ArrowRight"].includes(key)) return void 0;
|
|
39
|
+
return MAP_KEY_TO_FOCUS_INTENT[key];
|
|
40
|
+
}
|
|
41
|
+
function focusFirst(candidates, preventScroll = false) {
|
|
42
|
+
const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
|
|
43
|
+
for (const candidateRef of candidates) {
|
|
44
|
+
const candidate = candidateRef.current;
|
|
45
|
+
if (!candidate) continue;
|
|
46
|
+
if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
47
|
+
candidate.focus({ preventScroll });
|
|
48
|
+
if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const StoreContext = React.createContext(null);
|
|
52
|
+
function useStoreContext(consumerName) {
|
|
53
|
+
const context = React.useContext(StoreContext);
|
|
54
|
+
if (!context) {
|
|
55
|
+
throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${ROOT_NAME}\``);
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
}
|
|
59
|
+
function useStore(selector, ogStore) {
|
|
60
|
+
const contextStore = React.useContext(StoreContext);
|
|
61
|
+
const store = ogStore ?? contextStore;
|
|
62
|
+
if (!store) {
|
|
63
|
+
throw new Error(`\`useRating\` ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong \`${ROOT_NAME}\``);
|
|
64
|
+
}
|
|
65
|
+
const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);
|
|
66
|
+
return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
67
|
+
}
|
|
68
|
+
const RatingContext = React.createContext(null);
|
|
69
|
+
function useRatingContext(consumerName) {
|
|
70
|
+
const context = React.useContext(RatingContext);
|
|
71
|
+
if (!context) {
|
|
72
|
+
throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`${ROOT_NAME}\``);
|
|
73
|
+
}
|
|
74
|
+
return context;
|
|
75
|
+
}
|
|
76
|
+
const FocusContext = React.createContext(null);
|
|
77
|
+
function useFocusContext(consumerName) {
|
|
78
|
+
const context = React.useContext(FocusContext);
|
|
79
|
+
if (!context) {
|
|
80
|
+
throw new Error(`\`${consumerName}\` ph\u1EA3i n\u1EB1m b\xEAn trong \`FocusProvider\``);
|
|
81
|
+
}
|
|
82
|
+
return context;
|
|
83
|
+
}
|
|
84
|
+
const defaultItemLabel = (value) => `${value} sao`;
|
|
85
|
+
function Rating(props) {
|
|
86
|
+
const {
|
|
87
|
+
value: valueProp,
|
|
88
|
+
defaultValue = 0,
|
|
89
|
+
onValueChange,
|
|
90
|
+
onHover,
|
|
91
|
+
onFocus: onFocusProp,
|
|
92
|
+
onMouseDown: onMouseDownProp,
|
|
93
|
+
dir: dirProp,
|
|
94
|
+
orientation = "horizontal",
|
|
95
|
+
activationMode = "automatic",
|
|
96
|
+
size = "default",
|
|
97
|
+
max = 5,
|
|
98
|
+
step = 1,
|
|
99
|
+
clearable = false,
|
|
100
|
+
asChild,
|
|
101
|
+
disabled = false,
|
|
102
|
+
readOnly = false,
|
|
103
|
+
required = false,
|
|
104
|
+
itemLabel = defaultItemLabel,
|
|
105
|
+
className,
|
|
106
|
+
children,
|
|
107
|
+
id,
|
|
108
|
+
name,
|
|
109
|
+
ref,
|
|
110
|
+
...rootProps
|
|
111
|
+
} = props;
|
|
112
|
+
const dir = useDirection(dirProp);
|
|
113
|
+
const instanceId = React.useId();
|
|
114
|
+
const rootId = id ?? instanceId;
|
|
115
|
+
const listenersRef = useLazyRef(() => /* @__PURE__ */ new Set());
|
|
116
|
+
const stateRef = useLazyRef(() => ({
|
|
117
|
+
value: valueProp ?? defaultValue,
|
|
118
|
+
hoveredValue: null
|
|
119
|
+
}));
|
|
120
|
+
const propsRef = useAsRef({
|
|
121
|
+
onValueChange,
|
|
122
|
+
onHover,
|
|
123
|
+
onFocus: onFocusProp,
|
|
124
|
+
onMouseDown: onMouseDownProp,
|
|
125
|
+
step
|
|
126
|
+
});
|
|
127
|
+
const store = React.useMemo(() => {
|
|
128
|
+
return {
|
|
129
|
+
subscribe: (cb) => {
|
|
130
|
+
listenersRef.current.add(cb);
|
|
131
|
+
return () => listenersRef.current.delete(cb);
|
|
132
|
+
},
|
|
133
|
+
getState: () => stateRef.current,
|
|
134
|
+
setState: (key, value2) => {
|
|
135
|
+
if (Object.is(stateRef.current[key], value2)) return;
|
|
136
|
+
if (key === "value" && typeof value2 === "number") {
|
|
137
|
+
stateRef.current.value = value2;
|
|
138
|
+
propsRef.current.onValueChange?.(value2);
|
|
139
|
+
} else if (key === "hoveredValue") {
|
|
140
|
+
stateRef.current.hoveredValue = value2;
|
|
141
|
+
propsRef.current.onHover?.(value2);
|
|
142
|
+
} else {
|
|
143
|
+
stateRef.current[key] = value2;
|
|
144
|
+
}
|
|
145
|
+
store.notify();
|
|
146
|
+
},
|
|
147
|
+
notify: () => {
|
|
148
|
+
for (const cb of listenersRef.current) {
|
|
149
|
+
cb();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}, [listenersRef, stateRef, propsRef]);
|
|
154
|
+
useIsomorphicLayoutEffect(() => {
|
|
155
|
+
if (valueProp !== void 0) {
|
|
156
|
+
store.setState("value", valueProp);
|
|
157
|
+
}
|
|
158
|
+
}, [valueProp]);
|
|
159
|
+
const value = useStore((state) => state.value, store);
|
|
160
|
+
const [formTrigger, setFormTrigger] = React.useState(null);
|
|
161
|
+
const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));
|
|
162
|
+
const isFormControl = formTrigger ? !!formTrigger.closest("form") : true;
|
|
163
|
+
const [tabStopId, setTabStopId] = React.useState(null);
|
|
164
|
+
const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);
|
|
165
|
+
const [focusableItemCount, setFocusableItemCount] = React.useState(0);
|
|
166
|
+
const isClickFocusRef = React.useRef(false);
|
|
167
|
+
const itemsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
168
|
+
const autoIndexMapRef = React.useRef(/* @__PURE__ */ new Map());
|
|
169
|
+
const nextAutoIndexRef = React.useRef(0);
|
|
170
|
+
const getAutoIndex = React.useCallback((instanceId2) => {
|
|
171
|
+
const existingIndex = autoIndexMapRef.current.get(instanceId2);
|
|
172
|
+
if (existingIndex !== void 0) {
|
|
173
|
+
return existingIndex;
|
|
174
|
+
}
|
|
175
|
+
const newIndex = nextAutoIndexRef.current++;
|
|
176
|
+
autoIndexMapRef.current.set(instanceId2, newIndex);
|
|
177
|
+
return newIndex;
|
|
178
|
+
}, []);
|
|
179
|
+
const onItemFocus = React.useCallback((tabStopId2) => {
|
|
180
|
+
setTabStopId(tabStopId2);
|
|
181
|
+
}, []);
|
|
182
|
+
const onItemShiftTab = React.useCallback(() => {
|
|
183
|
+
setIsTabbingBackOut(true);
|
|
184
|
+
}, []);
|
|
185
|
+
const onFocusableItemAdd = React.useCallback(() => {
|
|
186
|
+
setFocusableItemCount((prevCount) => prevCount + 1);
|
|
187
|
+
}, []);
|
|
188
|
+
const onFocusableItemRemove = React.useCallback(() => {
|
|
189
|
+
setFocusableItemCount((prevCount) => prevCount - 1);
|
|
190
|
+
}, []);
|
|
191
|
+
const onItemRegister = React.useCallback((item) => {
|
|
192
|
+
itemsRef.current.set(item.id, item);
|
|
193
|
+
}, []);
|
|
194
|
+
const onItemUnregister = React.useCallback((id2) => {
|
|
195
|
+
itemsRef.current.delete(id2);
|
|
196
|
+
}, []);
|
|
197
|
+
const getItems = React.useCallback(() => {
|
|
198
|
+
return Array.from(itemsRef.current.values()).filter((item) => item.ref.current).sort((a, b) => {
|
|
199
|
+
const elementA = a.ref.current;
|
|
200
|
+
const elementB = b.ref.current;
|
|
201
|
+
if (!elementA || !elementB) return 0;
|
|
202
|
+
const position = elementA.compareDocumentPosition(elementB);
|
|
203
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
return 0;
|
|
210
|
+
});
|
|
211
|
+
}, []);
|
|
212
|
+
const onBlur = React.useCallback(
|
|
213
|
+
(event) => {
|
|
214
|
+
rootProps.onBlur?.(event);
|
|
215
|
+
if (event.defaultPrevented) return;
|
|
216
|
+
setIsTabbingBackOut(false);
|
|
217
|
+
},
|
|
218
|
+
[rootProps.onBlur]
|
|
219
|
+
);
|
|
220
|
+
const onFocus = React.useCallback(
|
|
221
|
+
(event) => {
|
|
222
|
+
propsRef.current.onFocus?.(event);
|
|
223
|
+
if (event.defaultPrevented) return;
|
|
224
|
+
const isKeyboardFocus = !isClickFocusRef.current;
|
|
225
|
+
if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
|
|
226
|
+
const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);
|
|
227
|
+
event.currentTarget.dispatchEvent(entryFocusEvent);
|
|
228
|
+
if (!entryFocusEvent.defaultPrevented) {
|
|
229
|
+
const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);
|
|
230
|
+
const selectedItem = propsRef.current.step < 1 ? items.find((item) => item.value === Math.ceil(value)) : items.find((item) => item.value === value);
|
|
231
|
+
const currentItem = items.find((item) => item.id === tabStopId);
|
|
232
|
+
const candidateItems = [selectedItem, currentItem, ...items].filter(Boolean);
|
|
233
|
+
const candidateRefs = candidateItems.map((item) => item.ref);
|
|
234
|
+
focusFirst(candidateRefs, false);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
isClickFocusRef.current = false;
|
|
238
|
+
},
|
|
239
|
+
[propsRef, isTabbingBackOut, value, tabStopId]
|
|
240
|
+
);
|
|
241
|
+
const onMouseDown = React.useCallback(
|
|
242
|
+
(event) => {
|
|
243
|
+
propsRef.current.onMouseDown?.(event);
|
|
244
|
+
if (event.defaultPrevented) return;
|
|
245
|
+
isClickFocusRef.current = true;
|
|
246
|
+
},
|
|
247
|
+
[propsRef]
|
|
248
|
+
);
|
|
249
|
+
const contextValue = React.useMemo(
|
|
250
|
+
() => ({
|
|
251
|
+
rootId,
|
|
252
|
+
dir,
|
|
253
|
+
orientation,
|
|
254
|
+
activationMode,
|
|
255
|
+
disabled,
|
|
256
|
+
readOnly,
|
|
257
|
+
size,
|
|
258
|
+
max,
|
|
259
|
+
step,
|
|
260
|
+
clearable,
|
|
261
|
+
itemLabel,
|
|
262
|
+
getAutoIndex
|
|
263
|
+
}),
|
|
264
|
+
[rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]
|
|
265
|
+
);
|
|
266
|
+
const focusContextValue = React.useMemo(
|
|
267
|
+
() => ({
|
|
268
|
+
tabStopId,
|
|
269
|
+
onItemFocus,
|
|
270
|
+
onItemShiftTab,
|
|
271
|
+
onFocusableItemAdd,
|
|
272
|
+
onFocusableItemRemove,
|
|
273
|
+
onItemRegister,
|
|
274
|
+
onItemUnregister,
|
|
275
|
+
getItems
|
|
276
|
+
}),
|
|
277
|
+
[
|
|
278
|
+
tabStopId,
|
|
279
|
+
onItemFocus,
|
|
280
|
+
onItemShiftTab,
|
|
281
|
+
onFocusableItemAdd,
|
|
282
|
+
onFocusableItemRemove,
|
|
283
|
+
onItemRegister,
|
|
284
|
+
onItemUnregister,
|
|
285
|
+
getItems
|
|
286
|
+
]
|
|
287
|
+
);
|
|
288
|
+
const RootPrimitive = asChild ? Slot : "div";
|
|
289
|
+
const partialFillGradient = /* @__PURE__ */ jsx("svg", { width: "0", height: "0", "aria-hidden": "true", focusable: "false", style: { position: "absolute" }, children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("linearGradient", { id: getPartialFillGradientId(rootId, step), children: dir === "rtl" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
290
|
+
/* @__PURE__ */ jsx("stop", { offset: "50%", stopColor: "transparent" }),
|
|
291
|
+
/* @__PURE__ */ jsx("stop", { offset: "50%", stopColor: "currentColor" })
|
|
292
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
293
|
+
/* @__PURE__ */ jsx("stop", { offset: "50%", stopColor: "currentColor" }),
|
|
294
|
+
/* @__PURE__ */ jsx("stop", { offset: "50%", stopColor: "transparent" })
|
|
295
|
+
] }) }) }) });
|
|
296
|
+
return /* @__PURE__ */ jsx(StoreContext.Provider, { value: store, children: /* @__PURE__ */ jsx(RatingContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(FocusContext.Provider, { value: focusContextValue, children: [
|
|
297
|
+
/* @__PURE__ */ jsx(
|
|
298
|
+
RootPrimitive,
|
|
299
|
+
{
|
|
300
|
+
id: rootId,
|
|
301
|
+
role: "radiogroup",
|
|
302
|
+
"aria-orientation": orientation,
|
|
303
|
+
"aria-disabled": disabled || void 0,
|
|
304
|
+
"aria-readonly": readOnly || void 0,
|
|
305
|
+
"aria-required": required || void 0,
|
|
306
|
+
"data-disabled": disabled ? "" : void 0,
|
|
307
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
308
|
+
"data-orientation": orientation,
|
|
309
|
+
"data-slot": "rating",
|
|
310
|
+
dir,
|
|
311
|
+
tabIndex: isTabbingBackOut || focusableItemCount === 0 ? -1 : 0,
|
|
312
|
+
...rootProps,
|
|
313
|
+
ref: composedRef,
|
|
314
|
+
className: cn(
|
|
315
|
+
/*
|
|
316
|
+
* Ngôi sao là thành phần PHI VĂN BẢN mang nghĩa (nó LÀ giá trị) → màu đi qua bậc
|
|
317
|
+
* `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu
|
|
318
|
+
* TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),
|
|
319
|
+
* nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới
|
|
320
|
+
* được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.
|
|
321
|
+
*/
|
|
322
|
+
"flex gap-1 text-primary-ink outline-none",
|
|
323
|
+
orientation === "horizontal" ? "flex-row items-center" : "flex-col items-start",
|
|
324
|
+
className
|
|
325
|
+
),
|
|
326
|
+
onBlur,
|
|
327
|
+
onFocus,
|
|
328
|
+
onMouseDown,
|
|
329
|
+
children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
330
|
+
children,
|
|
331
|
+
partialFillGradient
|
|
332
|
+
] })
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
asChild ? partialFillGradient : null,
|
|
336
|
+
isFormControl && /* @__PURE__ */ jsx(
|
|
337
|
+
VisuallyHiddenInput,
|
|
338
|
+
{
|
|
339
|
+
type: "hidden",
|
|
340
|
+
control: formTrigger,
|
|
341
|
+
name,
|
|
342
|
+
value,
|
|
343
|
+
disabled,
|
|
344
|
+
readOnly,
|
|
345
|
+
required
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
] }) }) });
|
|
349
|
+
}
|
|
350
|
+
function RatingItem(props) {
|
|
351
|
+
const {
|
|
352
|
+
index,
|
|
353
|
+
asChild,
|
|
354
|
+
onClick: onClickProp,
|
|
355
|
+
onFocus: onFocusProp,
|
|
356
|
+
onKeyDown: onKeyDownProp,
|
|
357
|
+
onMouseDown: onMouseDownProp,
|
|
358
|
+
onMouseEnter: onMouseEnterProp,
|
|
359
|
+
onMouseMove: onMouseMoveProp,
|
|
360
|
+
onMouseLeave: onMouseLeaveProp,
|
|
361
|
+
disabled,
|
|
362
|
+
className,
|
|
363
|
+
children,
|
|
364
|
+
ref,
|
|
365
|
+
...itemProps
|
|
366
|
+
} = props;
|
|
367
|
+
const itemRef = React.useRef(null);
|
|
368
|
+
const composedRef = useComposedRefs(ref, itemRef);
|
|
369
|
+
const context = useRatingContext(ITEM_NAME);
|
|
370
|
+
const instanceId = React.useId();
|
|
371
|
+
const actualIndex = React.useMemo(() => {
|
|
372
|
+
if (index !== void 0) {
|
|
373
|
+
return index;
|
|
374
|
+
}
|
|
375
|
+
return context.getAutoIndex(instanceId);
|
|
376
|
+
}, [index, context, instanceId]);
|
|
377
|
+
const itemValue = actualIndex + 1;
|
|
378
|
+
const store = useStoreContext(ITEM_NAME);
|
|
379
|
+
const focusContext = useFocusContext(ITEM_NAME);
|
|
380
|
+
const value = useStore((state) => state.value);
|
|
381
|
+
const hoveredValue = useStore((state) => state.hoveredValue);
|
|
382
|
+
const clearable = context.clearable;
|
|
383
|
+
const step = context.step;
|
|
384
|
+
const activationMode = context.activationMode;
|
|
385
|
+
const itemId = getItemId(context.rootId, itemValue);
|
|
386
|
+
const isDisabled = context.disabled || disabled;
|
|
387
|
+
const isReadOnly = context.readOnly;
|
|
388
|
+
const isTabStop = focusContext.tabStopId === itemId;
|
|
389
|
+
const displayValue = hoveredValue ?? value;
|
|
390
|
+
const isFilled = displayValue >= itemValue;
|
|
391
|
+
const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;
|
|
392
|
+
const isHovered = hoveredValue !== null && hoveredValue < itemValue;
|
|
393
|
+
const isChecked = value > 0 && Math.ceil(value) === itemValue;
|
|
394
|
+
const isMouseClickRef = React.useRef(false);
|
|
395
|
+
const propsRef = useAsRef({
|
|
396
|
+
onClick: onClickProp,
|
|
397
|
+
onFocus: onFocusProp,
|
|
398
|
+
onKeyDown: onKeyDownProp,
|
|
399
|
+
onMouseDown: onMouseDownProp,
|
|
400
|
+
onMouseEnter: onMouseEnterProp,
|
|
401
|
+
onMouseMove: onMouseMoveProp,
|
|
402
|
+
onMouseLeave: onMouseLeaveProp
|
|
403
|
+
});
|
|
404
|
+
useIsomorphicLayoutEffect(() => {
|
|
405
|
+
focusContext.onItemRegister({
|
|
406
|
+
id: itemId,
|
|
407
|
+
ref: itemRef,
|
|
408
|
+
value: itemValue,
|
|
409
|
+
disabled: !!isDisabled
|
|
410
|
+
});
|
|
411
|
+
if (!isDisabled) {
|
|
412
|
+
focusContext.onFocusableItemAdd();
|
|
413
|
+
}
|
|
414
|
+
return () => {
|
|
415
|
+
focusContext.onItemUnregister(itemId);
|
|
416
|
+
if (!isDisabled) {
|
|
417
|
+
focusContext.onFocusableItemRemove();
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}, [focusContext, itemId, itemValue, isDisabled]);
|
|
421
|
+
const onClick = React.useCallback(
|
|
422
|
+
(event) => {
|
|
423
|
+
propsRef.current.onClick?.(event);
|
|
424
|
+
if (event.defaultPrevented) return;
|
|
425
|
+
if (!isDisabled && !isReadOnly) {
|
|
426
|
+
let newValue = itemValue;
|
|
427
|
+
if (step < 1) {
|
|
428
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
429
|
+
const clickX = event.clientX - rect.left;
|
|
430
|
+
const isLeftHalf = clickX < rect.width / 2;
|
|
431
|
+
if (context.dir === "rtl") {
|
|
432
|
+
if (!isLeftHalf) {
|
|
433
|
+
newValue = itemValue - step;
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
if (isLeftHalf) {
|
|
437
|
+
newValue = itemValue - step;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (clearable && value === newValue) {
|
|
442
|
+
newValue = 0;
|
|
443
|
+
}
|
|
444
|
+
store.setState("value", newValue);
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
[isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]
|
|
448
|
+
);
|
|
449
|
+
const onFocus = React.useCallback(
|
|
450
|
+
(event) => {
|
|
451
|
+
propsRef.current.onFocus?.(event);
|
|
452
|
+
if (event.defaultPrevented) return;
|
|
453
|
+
focusContext.onItemFocus(itemId);
|
|
454
|
+
const isKeyboardFocus = !isMouseClickRef.current;
|
|
455
|
+
if (!isDisabled && !isReadOnly && activationMode !== "manual" && isKeyboardFocus) {
|
|
456
|
+
const isHalfStepValue = step < 1 && value === itemValue - step;
|
|
457
|
+
if (!isHalfStepValue) {
|
|
458
|
+
const newValue = clearable && value === itemValue ? 0 : itemValue;
|
|
459
|
+
store.setState("value", newValue);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
isMouseClickRef.current = false;
|
|
463
|
+
},
|
|
464
|
+
[focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]
|
|
465
|
+
);
|
|
466
|
+
const onKeyDown = React.useCallback(
|
|
467
|
+
(event) => {
|
|
468
|
+
propsRef.current.onKeyDown?.(event);
|
|
469
|
+
if (event.defaultPrevented) return;
|
|
470
|
+
if ((event.key === "Enter" || event.key === " ") && activationMode === "manual") {
|
|
471
|
+
event.preventDefault();
|
|
472
|
+
if (!isDisabled && !isReadOnly && itemRef.current) {
|
|
473
|
+
itemRef.current.click();
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (event.key === "Tab" && event.shiftKey) {
|
|
478
|
+
focusContext.onItemShiftTab();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (event.target !== event.currentTarget) return;
|
|
482
|
+
const focusIntent = getFocusIntent(event, context.dir, context.orientation);
|
|
483
|
+
if (focusIntent !== void 0) {
|
|
484
|
+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
if (step < 1 && (focusIntent === "prev" || focusIntent === "next")) {
|
|
487
|
+
if (!isDisabled && !isReadOnly) {
|
|
488
|
+
let newValue = value;
|
|
489
|
+
if (focusIntent === "next") {
|
|
490
|
+
newValue = Math.min(value + step, context.max);
|
|
491
|
+
} else {
|
|
492
|
+
newValue = Math.max(value - step, 0);
|
|
493
|
+
}
|
|
494
|
+
store.setState("value", newValue);
|
|
495
|
+
const items2 = focusContext.getItems().filter((item) => !item.disabled);
|
|
496
|
+
const targetItem = items2.find((item) => item.value === Math.ceil(newValue));
|
|
497
|
+
if (targetItem?.ref.current) {
|
|
498
|
+
queueMicrotask(() => targetItem.ref.current?.focus());
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const items = focusContext.getItems().filter((item) => !item.disabled);
|
|
504
|
+
let candidateRefs = items.map((item) => item.ref);
|
|
505
|
+
if (focusIntent === "last") {
|
|
506
|
+
candidateRefs.reverse();
|
|
507
|
+
} else if (focusIntent === "prev" || focusIntent === "next") {
|
|
508
|
+
if (focusIntent === "prev") candidateRefs.reverse();
|
|
509
|
+
const currentIndex = candidateRefs.findIndex((ref2) => ref2.current === event.currentTarget);
|
|
510
|
+
candidateRefs = candidateRefs.slice(currentIndex + 1);
|
|
511
|
+
}
|
|
512
|
+
queueMicrotask(() => focusFirst(candidateRefs));
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
[
|
|
516
|
+
focusContext,
|
|
517
|
+
context.dir,
|
|
518
|
+
context.orientation,
|
|
519
|
+
activationMode,
|
|
520
|
+
isDisabled,
|
|
521
|
+
isReadOnly,
|
|
522
|
+
step,
|
|
523
|
+
value,
|
|
524
|
+
context.max,
|
|
525
|
+
store,
|
|
526
|
+
propsRef
|
|
527
|
+
]
|
|
528
|
+
);
|
|
529
|
+
const onMouseDown = React.useCallback(
|
|
530
|
+
(event) => {
|
|
531
|
+
propsRef.current.onMouseDown?.(event);
|
|
532
|
+
if (event.defaultPrevented) return;
|
|
533
|
+
isMouseClickRef.current = true;
|
|
534
|
+
if (isDisabled) {
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
} else {
|
|
537
|
+
focusContext.onItemFocus(itemId);
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
[focusContext, itemId, isDisabled, propsRef]
|
|
541
|
+
);
|
|
542
|
+
const onMouseEnter = React.useCallback(
|
|
543
|
+
(event) => {
|
|
544
|
+
propsRef.current.onMouseEnter?.(event);
|
|
545
|
+
if (event.defaultPrevented) return;
|
|
546
|
+
if (!isDisabled && !isReadOnly) {
|
|
547
|
+
let hoverValue = itemValue;
|
|
548
|
+
if (step < 1) {
|
|
549
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
550
|
+
const mouseX = event.clientX - rect.left;
|
|
551
|
+
const isLeftHalf = mouseX < rect.width / 2;
|
|
552
|
+
if (context.dir === "rtl") {
|
|
553
|
+
if (!isLeftHalf) {
|
|
554
|
+
hoverValue = itemValue - step;
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
if (isLeftHalf) {
|
|
558
|
+
hoverValue = itemValue - step;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
store.setState("hoveredValue", hoverValue);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
[isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]
|
|
566
|
+
);
|
|
567
|
+
const onMouseLeave = React.useCallback(
|
|
568
|
+
(event) => {
|
|
569
|
+
propsRef.current.onMouseLeave?.(event);
|
|
570
|
+
if (event.defaultPrevented) return;
|
|
571
|
+
if (!isDisabled && !isReadOnly) {
|
|
572
|
+
store.setState("hoveredValue", null);
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
[isDisabled, isReadOnly, store, propsRef]
|
|
576
|
+
);
|
|
577
|
+
const onMouseMove = React.useCallback(
|
|
578
|
+
(event) => {
|
|
579
|
+
propsRef.current.onMouseMove?.(event);
|
|
580
|
+
if (event.defaultPrevented) return;
|
|
581
|
+
if (!isDisabled && !isReadOnly && step < 1) {
|
|
582
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
583
|
+
const mouseX = event.clientX - rect.left;
|
|
584
|
+
const isLeftHalf = mouseX < rect.width / 2;
|
|
585
|
+
let hoverValue = itemValue;
|
|
586
|
+
if (context.dir === "rtl") {
|
|
587
|
+
hoverValue = !isLeftHalf ? itemValue - step : itemValue;
|
|
588
|
+
} else {
|
|
589
|
+
hoverValue = isLeftHalf ? itemValue - step : itemValue;
|
|
590
|
+
}
|
|
591
|
+
store.setState("hoveredValue", hoverValue);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
[isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]
|
|
595
|
+
);
|
|
596
|
+
const dataState = isFilled ? "full" : isPartiallyFilled ? "partial" : "empty";
|
|
597
|
+
const ItemPrimitive = asChild ? Slot : "button";
|
|
598
|
+
return /* @__PURE__ */ jsx(
|
|
599
|
+
ItemPrimitive,
|
|
600
|
+
{
|
|
601
|
+
role: "radio",
|
|
602
|
+
type: "button",
|
|
603
|
+
id: itemId,
|
|
604
|
+
"aria-label": context.itemLabel(itemValue, context.max),
|
|
605
|
+
"aria-checked": isChecked,
|
|
606
|
+
"aria-posinset": itemValue,
|
|
607
|
+
"aria-setsize": context.max,
|
|
608
|
+
"data-disabled": isDisabled ? "" : void 0,
|
|
609
|
+
"data-readonly": isReadOnly ? "" : void 0,
|
|
610
|
+
"data-state": dataState,
|
|
611
|
+
"data-hovered": isHovered ? "" : void 0,
|
|
612
|
+
"data-slot": "rating-item",
|
|
613
|
+
disabled: isDisabled,
|
|
614
|
+
tabIndex: isTabStop ? 0 : -1,
|
|
615
|
+
...itemProps,
|
|
616
|
+
ref: composedRef,
|
|
617
|
+
style: {
|
|
618
|
+
...itemProps.style,
|
|
619
|
+
...isPartiallyFilled && {
|
|
620
|
+
"--partial-fill": `url(#${getPartialFillGradientId(context.rootId, step)})`
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
className: cn(
|
|
624
|
+
// Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.
|
|
625
|
+
"inline-flex items-center justify-center rounded transition-colors 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 disabled:opacity-50",
|
|
626
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-colors [&_svg]:duration-200 data-[state=empty]:[&_svg]:fill-transparent data-[state=full]:[&_svg]:fill-current data-[state=partial]:[&_svg]:fill-(--partial-fill) [&_svg:not([class*='size-'])]:size-full",
|
|
627
|
+
context.size === "sm" ? "size-4" : context.size === "lg" ? "size-6" : "size-5",
|
|
628
|
+
className
|
|
629
|
+
),
|
|
630
|
+
onClick,
|
|
631
|
+
onFocus,
|
|
632
|
+
onKeyDown,
|
|
633
|
+
onMouseDown,
|
|
634
|
+
onMouseEnter,
|
|
635
|
+
onMouseMove,
|
|
636
|
+
onMouseLeave,
|
|
637
|
+
children: typeof children === "function" ? children(dataState) : children ?? /* @__PURE__ */ jsx(Star, { "aria-hidden": "true" })
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
export {
|
|
642
|
+
Rating,
|
|
643
|
+
RatingItem,
|
|
644
|
+
useStore as useRating
|
|
645
|
+
};
|
|
646
|
+
//# sourceMappingURL=rating.js.map
|