@comitor/ui 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/components/composite/toast.d.ts +2 -1
- package/dist/components/composite/toast.js +3 -9
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar.js +45 -1
- package/dist/components/ui/action-bar.js.map +1 -1
- package/dist/components/ui/circular-progress.js +10 -2
- package/dist/components/ui/circular-progress.js.map +1 -1
- package/dist/components/ui/kanban.js +9 -3
- package/dist/components/ui/kanban.js.map +1 -1
- package/dist/components/ui/key-value.js +70 -76
- package/dist/components/ui/key-value.js.map +1 -1
- package/dist/components/ui/mask-input-constants.js +31 -71
- package/dist/components/ui/mask-input-constants.js.map +1 -1
- package/dist/components/ui/mask-input.js +18 -20
- package/dist/components/ui/mask-input.js.map +1 -1
- package/dist/components/ui/phone-input.js +5 -2
- package/dist/components/ui/phone-input.js.map +1 -1
- package/dist/components/ui/rating.js +4 -2
- package/dist/components/ui/rating.js.map +1 -1
- package/dist/components/ui/slider.d.ts +1 -1
- package/dist/components/ui/slider.js +15 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/sonner.js +2 -0
- package/dist/components/ui/sonner.js.map +1 -1
- package/dist/components/ui/toggle-group.js +12 -1
- package/dist/components/ui/toggle-group.js.map +1 -1
- package/dist/components/ui/tour.js +3 -2
- package/dist/components/ui/tour.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +2 -2
- package/dist/i18n/locale-provider.d.ts +18 -0
- package/dist/i18n/locale-provider.js.map +1 -1
- package/dist/i18n/namespaces/rating.js +2 -1
- package/dist/i18n/namespaces/rating.js.map +1 -1
- package/dist/i18n/namespaces/shell.d.ts +4 -0
- package/dist/i18n/namespaces/shell.js +1 -1
- package/dist/i18n/namespaces/shell.js.map +1 -1
- package/dist/i18n/namespaces/toast.d.ts +3 -2
- package/dist/i18n/namespaces/toast.js.map +1 -1
- package/dist/lib/toast-language.d.ts +8 -0
- package/dist/lib/toast-language.js +19 -0
- package/dist/lib/toast-language.js.map +1 -0
- package/dist/shell/display-axis-provider.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\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 { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\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\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | 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\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\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 RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định khoá `itemLabel` của namespace\n * `rating` (`ComitorLocaleProvider`).\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel: itemLabelProp,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const t = useComitorMessages(\"rating\");\n const itemLabel = itemLabelProp ?? t.itemLabel;\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\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 const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\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 rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(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 // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, 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 [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\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 RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * 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\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\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 focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\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 = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n aria-label={context.itemLabel(itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"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\",\n \"[&_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\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\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 { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\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\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | 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\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\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 RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định khoá `itemLabel` của namespace\n * `rating` (`ComitorLocaleProvider`).\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel: itemLabelProp,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const t = useComitorMessages(\"rating\");\n const itemLabel = itemLabelProp ?? t.itemLabel;\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\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 const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\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 rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(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 // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, 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 [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\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 RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * 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\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n /*\n * ⚠ Khác thượng nguồn (3.0.1): chỉ CHỌN khi focus tới từ một mục khác của CÙNG nhóm — tức\n * người dùng đang đi bằng phím mũi tên. Bản gốc chọn ở mọi lần focus bằng bàn phím, kể cả lúc\n * Tab ĐI NGANG QUA: nhóm chưa chấm thành 1 sao chỉ vì người dùng Tab qua một biểu mẫu, còn\n * với `clearable` thì Tab vào một nhóm đã chấm lại XOÁ điểm (focus rơi đúng mục đang chọn,\n * và chọn lại mục đang chọn là xoá). Radio group gốc của trình duyệt và mẫu WAI-ARIA đều thế\n * này: Tab vào nhóm chỉ đưa focus tới, mũi tên mới chọn.\n * Tab vào nhóm thì focus đi từ `Rating` gốc (có `tabIndex`) sang một mục, nên `relatedTarget`\n * là chính gốc — không phải một mục khác.\n */\n const previous = event.relatedTarget;\n const cameFromSibling =\n previous instanceof HTMLElement &&\n previous !== event.currentTarget &&\n previous.getAttribute(\"data-slot\") === \"rating-item\" &&\n previous.closest('[data-slot=\"rating\"]') === event.currentTarget.closest('[data-slot=\"rating\"]');\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus && cameFromSibling) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\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 focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\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 = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n /*\n * ⚠ Khác thượng nguồn (3.0.1): mục ĐANG CHỌN của một giá trị lẻ (4,5 → mục 5) mang tên theo giá\n * trị thật. Trước đó trình đọc màn hình nghe \"5 stars, checked\" cho điểm trung bình 4,5 — mục\n * được chọn là mục trần của giá trị, còn tên thì đọc số thứ tự của mục.\n */\n aria-label={context.itemLabel(isChecked && value !== itemValue ? value : itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"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\",\n \"[&_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\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
@@ -26,6 +26,6 @@ import * as React from 'react';
|
|
|
26
26
|
* vừa ≥3:1 với nhau vừa ≥3:1 với nền.
|
|
27
27
|
*/
|
|
28
28
|
type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;
|
|
29
|
-
declare function Slider({ className, defaultValue, value, min, max, ...props }: SliderProps): React.JSX.Element;
|
|
29
|
+
declare function Slider({ className, defaultValue, value, min, max, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: SliderProps): React.JSX.Element;
|
|
30
30
|
|
|
31
31
|
export { Slider, type SliderProps };
|
|
@@ -3,11 +3,22 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
|
-
function Slider({
|
|
6
|
+
function Slider({
|
|
7
|
+
className,
|
|
8
|
+
defaultValue,
|
|
9
|
+
value,
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 100,
|
|
12
|
+
"aria-label": ariaLabel,
|
|
13
|
+
"aria-labelledby": ariaLabelledby,
|
|
14
|
+
...props
|
|
15
|
+
}) {
|
|
7
16
|
const _values = React.useMemo(
|
|
8
17
|
() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
|
|
9
18
|
[value, defaultValue, min, max]
|
|
10
19
|
);
|
|
20
|
+
const singleThumb = _values.length === 1;
|
|
21
|
+
const thumbName = singleThumb ? { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby } : {};
|
|
11
22
|
return /* @__PURE__ */ jsxs(
|
|
12
23
|
SliderPrimitive.Root,
|
|
13
24
|
{
|
|
@@ -21,6 +32,8 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
|
|
|
21
32
|
className
|
|
22
33
|
),
|
|
23
34
|
...props,
|
|
35
|
+
"aria-label": singleThumb ? void 0 : ariaLabel,
|
|
36
|
+
"aria-labelledby": singleThumb ? void 0 : ariaLabelledby,
|
|
24
37
|
children: [
|
|
25
38
|
/* @__PURE__ */ jsx(
|
|
26
39
|
SliderPrimitive.Track,
|
|
@@ -40,6 +53,7 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
|
|
|
40
53
|
SliderPrimitive.Thumb,
|
|
41
54
|
{
|
|
42
55
|
"data-slot": "slider-thumb",
|
|
56
|
+
...thumbName,
|
|
43
57
|
className: "border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 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"
|
|
44
58
|
},
|
|
45
59
|
index
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n *\n * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─\n *\n * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía\n * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã\n * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với\n * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.\n *\n * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):\n *\n * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓\n * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓\n * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n *\n * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT\n * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia\n * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào\n * vừa ≥3:1 với nhau vừa ≥3:1 với nền.\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n *\n * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─\n *\n * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía\n * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã\n * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với\n * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.\n *\n * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):\n *\n * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓\n * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓\n * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n *\n * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT\n * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia\n * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào\n * vừa ≥3:1 với nhau vừa ≥3:1 với nền.\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\n/*\n * ─ TÊN CHO TRÌNH ĐỌC MÀN HÌNH ĐI XUỐNG NÚM (3.0.1) ─\n *\n * Radix đặt `role=\"slider\"` trên từng `Thumb`, còn `Root` chỉ là một `<span>` không vai trò. Trước\n * `3.0.1`, `aria-label` / `aria-labelledby` truyền cho `Slider` rơi xuống `Root` — tức lên một phần\n * tử KHÔNG AI focus, và núm thật sự nhận bàn phím thì không có tên (axe: \"ARIA slider elements\n * must have an accessible name\"). Gặp khi dựng thanh zoom cho `Cropper`.\n *\n * Nay với slider MỘT núm, hai thuộc tính đó đi thẳng xuống núm. Slider NHIỀU núm (range) giữ\n * nguyên như cũ: Radix tự đặt tên từng núm (\"Minimum\" / \"Maximum\"), và chép cùng một tên xuống\n * cả hai núm sẽ xoá mất đúng phân biệt đó — range cần tên riêng thì đặt nhãn cho từng núm ở app.\n */\nfunction Slider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n ...props\n}: SliderProps) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n );\n const singleThumb = _values.length === 1;\n /*\n * Rải CÓ ĐIỀU KIỆN, đừng truyền `aria-label={undefined}` xuống núm: `Thumb` của Radix rải props\n * của app SAU tên nó tự đặt, nên chỉ một khoá mang giá trị `undefined` cũng đủ xoá \"Minimum\" /\n * \"Maximum\" của slider range.\n */\n const thumbName = singleThumb ? { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledby } : {};\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className\n )}\n {...props}\n aria-label={singleThumb ? undefined : ariaLabel}\n aria-labelledby={singleThumb ? undefined : ariaLabelledby}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className=\"bg-slider-track relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"bg-control-on absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n // biome-ignore lint/suspicious/noArrayIndexKey: thumb chỉ phân biệt bằng vị trí, không có id\n key={index}\n {...thumbName}\n className=\"border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 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\"\n />\n ))}\n </SliderPrimitive.Root>\n );\n}\n\nexport { Slider };\n"],"mappings":";AAoEI,SAkBI,KAlBJ;AAlEJ,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAyCnB,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,GAAG;AACL,GAAgB;AACd,QAAM,UAAU,MAAM;AAAA,IACpB,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG;AAAA,IAC5F,CAAC,OAAO,cAAc,KAAK,GAAG;AAAA,EAChC;AACA,QAAM,cAAc,QAAQ,WAAW;AAMvC,QAAM,YAAY,cAAc,EAAE,cAAc,WAAW,mBAAmB,eAAe,IAAI,CAAC;AAElG,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MACJ,cAAY,cAAc,SAAY;AAAA,MACtC,mBAAiB,cAAc,SAAY;AAAA,MAE3C;AAAA;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,cAAC,gBAAgB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAAG,UAC1C;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YAGT,GAAG;AAAA,YACJ,WAAU;AAAA;AAAA,UAFL;AAAA,QAGP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useTheme } from "next-themes";
|
|
4
4
|
import { Toaster as Sonner } from "sonner";
|
|
5
|
+
import { useToastLanguageBridge } from "../../lib/toast-language.js";
|
|
5
6
|
const Toaster = ({ ...props }) => {
|
|
6
7
|
const { theme = "system" } = useTheme();
|
|
8
|
+
useToastLanguageBridge();
|
|
7
9
|
return /* @__PURE__ */ jsx(
|
|
8
10
|
Sonner,
|
|
9
11
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\n/**\n * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.\n *\n * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite\n * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để\n * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và\n * (b) ai thêm `export * from \"./components/ui/sonner.js\"` vào `src/index.ts` sẽ vỡ build ngay,\n * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.\n * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng \"hai toaster,\n * hai tên\" chỉ đúng khi tên trần không còn lối ra.\n */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster as SonnerToaster };\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\nimport { useToastLanguageBridge } from \"../../lib/toast-language.js\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\n/**\n * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.\n *\n * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite\n * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để\n * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và\n * (b) ai thêm `export * from \"./components/ui/sonner.js\"` vào `src/index.ts` sẽ vỡ build ngay,\n * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.\n * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng \"hai toaster,\n * hai tên\" chỉ đúng khi tên trần không còn lối ra.\n */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n /*\n * Ghi ngôn ngữ của `ComitorLocaleProvider` cho `toastPromise` — như `Toaster` ở Tier 2. Thiếu dòng\n * này (tới `3.0.0`) thì toast \"đang chạy\" của app dùng shell luôn nói tiếng Anh, dù đã có provider.\n */\n useToastLanguageBridge();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster as SonnerToaster };\n"],"mappings":";AAuCI;AArCJ,SAAS,gBAAgB;AAEzB,SAAS,WAAW,cAAiC;AACrD,SAAS,8BAA8B;AAyBvC,MAAM,UAAU,CAAC,EAAE,GAAG,MAAM,MAAoB;AAC9C,QAAM,EAAE,QAAQ,SAAS,IAAI,SAAS;AAKtC,yBAAuB;AAEvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OACE;AAAA,QACE,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,MACrB;AAAA,MAED,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -37,7 +37,18 @@ function ToggleGroupItem({ className, children, variant, size, ...props }) {
|
|
|
37
37
|
variant: context.variant || variant,
|
|
38
38
|
size: context.size || size
|
|
39
39
|
}),
|
|
40
|
-
|
|
40
|
+
/*
|
|
41
|
+
* ⚠ Khác shadcn (3.0.1): `min-w-auto` thay cho `min-w-0`. Với `flex-1` (nền 0) mọi mục được
|
|
42
|
+
* chia ĐỀU bề rộng của nhóm, nên mục có nhãn dài hẹp hơn chính nhãn của nó và chữ in đè sang
|
|
43
|
+
* mục bên cạnh — đo ở nhóm `w-fit` mặc định: "vi + locale=en-US" (chữ 120px) trong mục 86px.
|
|
44
|
+
* `min-w-auto` trả lại sàn mặc định của flex item là bề rộng NỘI DUNG (`whitespace-nowrap` nên
|
|
45
|
+
* đúng bằng nhãn): nhóm `w-fit` thì mỗi mục vừa nhãn của nó, nhóm `w-full` vẫn chia đều, chỉ
|
|
46
|
+
* mục nào nhãn dài hơn phần chia mới rộng hơn.
|
|
47
|
+
* ⚠ Chỉ BỎ `min-w-0` là không đủ: tailwind-merge khi đó giữ lại `min-w-9` của `toggleVariants`
|
|
48
|
+
* — một sàn 36px CỐ ĐỊNH, vẫn không theo nhãn. Cả `min-w-0` lẫn `min-w-auto` đều đè nó đi, nên
|
|
49
|
+
* nút chỉ có icon trong nhóm vẫn rộng theo icon như ở `3.0.0`.
|
|
50
|
+
*/
|
|
51
|
+
"min-w-auto flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
|
41
52
|
className
|
|
42
53
|
),
|
|
43
54
|
...props,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/toggle-group.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { toggleVariants } from \"./toggle.js\";\n\n/**\n * ToggleGroup (Radix) — dãy Toggle dính liền, dùng cho bộ lọc dạng segmented.\n * `variant`/`size` đặt ở group sẽ truyền xuống item qua context; item vẫn override được.\n */\n\nexport type ToggleGroupVariantProps = VariantProps<typeof toggleVariants>;\nexport type ToggleGroupProps = React.ComponentProps<typeof ToggleGroupPrimitive.Root> & ToggleGroupVariantProps;\nexport type ToggleGroupItemProps = React.ComponentProps<typeof ToggleGroupPrimitive.Item> & ToggleGroupVariantProps;\n\nconst ToggleGroupContext = React.createContext<ToggleGroupVariantProps>({\n size: \"default\",\n variant: \"default\"\n});\n\nfunction ToggleGroup({ className, variant, size, children, ...props }: ToggleGroupProps) {\n return (\n <ToggleGroupPrimitive.Root\n data-slot=\"toggle-group\"\n data-variant={variant}\n data-size={size}\n className={cn(\n \"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs\",\n className\n )}\n {...props}\n >\n <ToggleGroupContext.Provider value={{ variant, size }}>{children}</ToggleGroupContext.Provider>\n </ToggleGroupPrimitive.Root>\n );\n}\n\nfunction ToggleGroupItem({ className, children, variant, size, ...props }: ToggleGroupItemProps) {\n const context = React.useContext(ToggleGroupContext);\n\n return (\n <ToggleGroupPrimitive.Item\n data-slot=\"toggle-group-item\"\n data-variant={context.variant || variant}\n data-size={context.size || size}\n className={cn(\n toggleVariants({\n variant: context.variant || variant,\n size: context.size || size\n }),\n \"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l\",\n className\n )}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Item>\n );\n}\n\nexport { ToggleGroup, ToggleGroupItem };\n"],"mappings":";AAkCM;AAhCN,YAAY,0BAA0B;AAEtC,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAW/B,MAAM,qBAAqB,MAAM,cAAuC;AAAA,EACtE,MAAM;AAAA,EACN,SAAS;AACX,CAAC;AAED,SAAS,YAAY,EAAE,WAAW,SAAS,MAAM,UAAU,GAAG,MAAM,GAAqB;AACvF,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,aAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,mBAAmB,UAAnB,EAA4B,OAAO,EAAE,SAAS,KAAK,GAAI,UAAS;AAAA;AAAA,EACnE;AAEJ;AAEA,SAAS,gBAAgB,EAAE,WAAW,UAAU,SAAS,MAAM,GAAG,MAAM,GAAyB;AAC/F,QAAM,UAAU,MAAM,WAAW,kBAAkB;AAEnD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,QAAQ,WAAW;AAAA,MACjC,aAAW,QAAQ,QAAQ;AAAA,MAC3B,WAAW;AAAA,QACT,eAAe;AAAA,UACb,SAAS,QAAQ,WAAW;AAAA,UAC5B,MAAM,QAAQ,QAAQ;AAAA,QACxB,CAAC;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/toggle-group.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ToggleGroupPrimitive from \"@radix-ui/react-toggle-group\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { toggleVariants } from \"./toggle.js\";\n\n/**\n * ToggleGroup (Radix) — dãy Toggle dính liền, dùng cho bộ lọc dạng segmented.\n * `variant`/`size` đặt ở group sẽ truyền xuống item qua context; item vẫn override được.\n */\n\nexport type ToggleGroupVariantProps = VariantProps<typeof toggleVariants>;\nexport type ToggleGroupProps = React.ComponentProps<typeof ToggleGroupPrimitive.Root> & ToggleGroupVariantProps;\nexport type ToggleGroupItemProps = React.ComponentProps<typeof ToggleGroupPrimitive.Item> & ToggleGroupVariantProps;\n\nconst ToggleGroupContext = React.createContext<ToggleGroupVariantProps>({\n size: \"default\",\n variant: \"default\"\n});\n\nfunction ToggleGroup({ className, variant, size, children, ...props }: ToggleGroupProps) {\n return (\n <ToggleGroupPrimitive.Root\n data-slot=\"toggle-group\"\n data-variant={variant}\n data-size={size}\n className={cn(\n \"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs\",\n className\n )}\n {...props}\n >\n <ToggleGroupContext.Provider value={{ variant, size }}>{children}</ToggleGroupContext.Provider>\n </ToggleGroupPrimitive.Root>\n );\n}\n\nfunction ToggleGroupItem({ className, children, variant, size, ...props }: ToggleGroupItemProps) {\n const context = React.useContext(ToggleGroupContext);\n\n return (\n <ToggleGroupPrimitive.Item\n data-slot=\"toggle-group-item\"\n data-variant={context.variant || variant}\n data-size={context.size || size}\n className={cn(\n toggleVariants({\n variant: context.variant || variant,\n size: context.size || size\n }),\n /*\n * ⚠ Khác shadcn (3.0.1): `min-w-auto` thay cho `min-w-0`. Với `flex-1` (nền 0) mọi mục được\n * chia ĐỀU bề rộng của nhóm, nên mục có nhãn dài hẹp hơn chính nhãn của nó và chữ in đè sang\n * mục bên cạnh — đo ở nhóm `w-fit` mặc định: \"vi + locale=en-US\" (chữ 120px) trong mục 86px.\n * `min-w-auto` trả lại sàn mặc định của flex item là bề rộng NỘI DUNG (`whitespace-nowrap` nên\n * đúng bằng nhãn): nhóm `w-fit` thì mỗi mục vừa nhãn của nó, nhóm `w-full` vẫn chia đều, chỉ\n * mục nào nhãn dài hơn phần chia mới rộng hơn.\n * ⚠ Chỉ BỎ `min-w-0` là không đủ: tailwind-merge khi đó giữ lại `min-w-9` của `toggleVariants`\n * — một sàn 36px CỐ ĐỊNH, vẫn không theo nhãn. Cả `min-w-0` lẫn `min-w-auto` đều đè nó đi, nên\n * nút chỉ có icon trong nhóm vẫn rộng theo icon như ở `3.0.0`.\n */\n \"min-w-auto flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l\",\n className\n )}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Item>\n );\n}\n\nexport { ToggleGroup, ToggleGroupItem };\n"],"mappings":";AAkCM;AAhCN,YAAY,0BAA0B;AAEtC,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAW/B,MAAM,qBAAqB,MAAM,cAAuC;AAAA,EACtE,MAAM;AAAA,EACN,SAAS;AACX,CAAC;AAED,SAAS,YAAY,EAAE,WAAW,SAAS,MAAM,UAAU,GAAG,MAAM,GAAqB;AACvF,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,aAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,mBAAmB,UAAnB,EAA4B,OAAO,EAAE,SAAS,KAAK,GAAI,UAAS;AAAA;AAAA,EACnE;AAEJ;AAEA,SAAS,gBAAgB,EAAE,WAAW,UAAU,SAAS,MAAM,GAAG,MAAM,GAAyB;AAC/F,QAAM,UAAU,MAAM,WAAW,kBAAkB;AAEnD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,QAAQ,WAAW;AAAA,MACjC,aAAW,QAAQ,QAAQ;AAAA,MAC3B,WAAW;AAAA,QACT,eAAe;AAAA,UACb,SAAS,QAAQ,WAAW;AAAA,UAC5B,MAAM,QAAQ,QAAQ;AAAA,QACxB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYD;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -386,6 +386,7 @@ function Tour(props) {
|
|
|
386
386
|
},
|
|
387
387
|
setState: (key, value) => {
|
|
388
388
|
if (Object.is(stateRef.current[key], value)) return;
|
|
389
|
+
const previousValue = stateRef.current.value;
|
|
389
390
|
stateRef.current[key] = value;
|
|
390
391
|
if (key === "open" && typeof value === "boolean") {
|
|
391
392
|
propsRef.current.onOpenChange?.(value);
|
|
@@ -396,12 +397,12 @@ function Tour(props) {
|
|
|
396
397
|
}
|
|
397
398
|
}
|
|
398
399
|
} else {
|
|
399
|
-
if (stateRef.current.value < (stateRef.current.steps.length || 0)
|
|
400
|
+
if (stateRef.current.value < (stateRef.current.steps.length || 0)) {
|
|
400
401
|
propsRef.current.onSkip?.();
|
|
401
402
|
}
|
|
402
403
|
}
|
|
403
404
|
} else if (key === "value" && typeof value === "number") {
|
|
404
|
-
const prevStep = stateRef.current.steps[
|
|
405
|
+
const prevStep = stateRef.current.steps[previousValue];
|
|
405
406
|
const nextStep = stateRef.current.steps[value];
|
|
406
407
|
prevStep?.onStepLeave?.();
|
|
407
408
|
nextStep?.onStepEnter?.();
|