@spear-ai/spectral 1.20.0 → 1.20.2
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/dist/Accordion.js +0 -4
- package/dist/Accordion.js.map +1 -1
- package/dist/Alert/AlertBase.d.ts +0 -1
- package/dist/Alert/AlertBase.d.ts.map +1 -1
- package/dist/Alert/AlertBase.js +14 -17
- package/dist/Alert/AlertBase.js.map +1 -1
- package/dist/Alert.js +16 -4
- package/dist/Alert.js.map +1 -1
- package/dist/AlertDialog.js +3 -3
- package/dist/AlertDialog.js.map +1 -1
- package/dist/Avatar.js +1 -9
- package/dist/Avatar.js.map +1 -1
- package/dist/Badge.js +0 -1
- package/dist/Badge.js.map +1 -1
- package/dist/Button.js +0 -3
- package/dist/Button.js.map +1 -1
- package/dist/ButtonGroup.js +0 -4
- package/dist/ButtonGroup.js.map +1 -1
- package/dist/ButtonIcon.js +0 -1
- package/dist/ButtonIcon.js.map +1 -1
- package/dist/ButtonIconSlideout.js +0 -3
- package/dist/ButtonIconSlideout.js.map +1 -1
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox.js +5 -8
- package/dist/Checkbox.js.map +1 -1
- package/dist/Combobox.d.ts +1 -0
- package/dist/Combobox.d.ts.map +1 -1
- package/dist/Combobox.js +0 -4
- package/dist/Combobox.js.map +1 -1
- package/dist/ControlGroup/ControlGroupSelect.js +0 -5
- package/dist/ControlGroup/ControlGroupSelect.js.map +1 -1
- package/dist/DataCard/Card.js +0 -6
- package/dist/DataCard/Card.js.map +1 -1
- package/dist/DataCard.js +1 -7
- package/dist/DataCard.js.map +1 -1
- package/dist/DateTimePicker/Calendar.js +0 -1
- package/dist/DateTimePicker/Calendar.js.map +1 -1
- package/dist/DateTimePicker/DateTimeInput.js +0 -1
- package/dist/DateTimePicker/DateTimeInput.js.map +1 -1
- package/dist/DateTimePicker/TimePeriodSelect.js +0 -4
- package/dist/DateTimePicker/TimePeriodSelect.js.map +1 -1
- package/dist/DateTimePicker/TimePicker.js +0 -3
- package/dist/DateTimePicker/TimePicker.js.map +1 -1
- package/dist/DateTimePicker.js +0 -4
- package/dist/DateTimePicker.js.map +1 -1
- package/dist/Dialog.js +0 -16
- package/dist/Dialog.js.map +1 -1
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js +0 -2
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js.map +1 -1
- package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js +0 -1
- package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js.map +1 -1
- package/dist/DirectionalColorWheel.js +9 -21
- package/dist/DirectionalColorWheel.js.map +1 -1
- package/dist/Drawer.js +6 -29
- package/dist/Drawer.js.map +1 -1
- package/dist/DropdownMenu.js +1 -9
- package/dist/DropdownMenu.js.map +1 -1
- package/dist/FormFieldMessage.js +0 -1
- package/dist/FormFieldMessage.js.map +1 -1
- package/dist/HoverCard.js +0 -3
- package/dist/HoverCard.js.map +1 -1
- package/dist/Input.js +0 -10
- package/dist/Input.js.map +1 -1
- package/dist/InputOTP.js +0 -4
- package/dist/InputOTP.js.map +1 -1
- package/dist/InputSearch.js +3 -15
- package/dist/InputSearch.js.map +1 -1
- package/dist/Kbd.js +0 -2
- package/dist/Kbd.js.map +1 -1
- package/dist/Meter.d.ts +23 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +45 -0
- package/dist/Meter.js.map +1 -0
- package/dist/MultiSelect/MultiSelectBase.js +1 -16
- package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
- package/dist/Popover.js +0 -3
- package/dist/Popover.js.map +1 -1
- package/dist/RadialMenu.js +1 -7
- package/dist/RadialMenu.js.map +1 -1
- package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
- package/dist/RadioButtonGroup/RadioButtonGroupBase.js +1 -4
- package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
- package/dist/RadioButtonGroup.js +1 -1
- package/dist/RadioButtonGroup.js.map +1 -1
- package/dist/RadioGroup.js +0 -6
- package/dist/RadioGroup.js.map +1 -1
- package/dist/Select.js +11 -40
- package/dist/Select.js.map +1 -1
- package/dist/Slider.js +2 -11
- package/dist/Slider.js.map +1 -1
- package/dist/Switch.js +0 -6
- package/dist/Switch.js.map +1 -1
- package/dist/Tabs/TabsBase.js +0 -5
- package/dist/Tabs/TabsBase.js.map +1 -1
- package/dist/Textarea.js +0 -4
- package/dist/Textarea.js.map +1 -1
- package/dist/Toast.js +1 -3
- package/dist/Toast.js.map +1 -1
- package/dist/Toggle.js +0 -1
- package/dist/Toggle.js.map +1 -1
- package/dist/ToggleGroup/ToggleGroupItem.js +0 -1
- package/dist/ToggleGroup/ToggleGroupItem.js.map +1 -1
- package/dist/ToggleGroup.js +0 -1
- package/dist/ToggleGroup.js.map +1 -1
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip.js +4 -5
- package/dist/Tooltip.js.map +1 -1
- package/dist/Tray.js +1 -9
- package/dist/Tray.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/styles/horizon/base.css +31 -16
- package/dist/styles/horizon/colors.css +37 -21
- package/dist/styles/horizon/theme.css +15 -7
- package/dist/styles/horizon/utilities.css +19 -45
- package/dist/styles/spectral.css +1 -1
- package/package.json +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimeInput.js","names":[],"sources":["../../src/components/DateTimePicker/DateTimeInput.tsx"],"sourcesContent":["import { Input } from '@primitives/input'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useMemo, useRef, type ComponentProps, type KeyboardEvent, type Ref } from 'react'\nimport { DEFAULT_TRANSLATIONS, formatTimeNumber, getAriaLabel, getAriaValueMinMax, getAriaValueNow, getArrowByType, getDateByType, getResolvedLocale, setDateByType, type Period, type TimePickerTranslations, type TimePickerType } from './DateTimeUtils'\n\nexport interface DateTimeInputProps extends Omit<ComponentProps<'input'>, 'onChange' | 'type'> {\n date: Date | undefined\n /** Locale for number formatting */\n locale?: string\n onLeftFocus?: () => void\n onRightFocus?: () => void\n period?: Period\n picker: TimePickerType\n setDate: (date: Date | undefined) => void\n /** Override translation strings for ARIA labels */\n translations?: TimePickerTranslations\n}\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nexport const DateTimeInput = ({ className, date, disabled, id, locale, name, onKeyDown, onLeftFocus, onRightFocus, period, picker, ref, setDate, translations = DEFAULT_TRANSLATIONS, ...props }: DateTimeInputProps & { ref?: Ref<HTMLInputElement> }) => {\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(internalRef, ref)\n const editBufferRef = useRef('')\n const isEditingRef = useRef(false)\n\n const resolvedLocale = useMemo(() => getResolvedLocale(locale), [locale])\n\n const effectiveDate = useMemo(() => {\n if (date) return date\n const d = new Date()\n d.setHours(0, 0, 0, 0)\n return d\n }, [date])\n\n const { min, max } = useMemo(() => getAriaValueMinMax(picker), [picker])\n\n const internalValue = useMemo(() => getDateByType(effectiveDate, picker), [effectiveDate, picker])\n\n const displayValue = useMemo(() => formatTimeNumber(parseInt(internalValue, 10), resolvedLocale), [internalValue, resolvedLocale])\n\n const ariaProps = useMemo(\n () => ({\n 'aria-label': getAriaLabel(picker, translations),\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuenow': getAriaValueNow(effectiveDate, picker),\n 'aria-valuetext': displayValue,\n }),\n [effectiveDate, picker, displayValue, translations, min, max],\n )\n\n useEffect(() => {\n if (internalRef.current && !isEditingRef.current) {\n internalRef.current.value = displayValue\n }\n }, [displayValue])\n\n const commitValue = useCallback(\n (value: string) => {\n editBufferRef.current = ''\n isEditingRef.current = false\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, value, period))\n },\n [effectiveDate, picker, period, setDate],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Tab') {\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n return\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n onRightFocus?.()\n return\n }\n\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n onLeftFocus?.()\n return\n }\n\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault()\n editBufferRef.current = ''\n isEditingRef.current = false\n const step = e.key === 'ArrowUp' ? 1 : -1\n const newValue = getArrowByType(step, picker, internalValue)\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, newValue, period))\n return\n }\n\n if (e.key === 'Home' || e.key === 'End') {\n e.preventDefault()\n editBufferRef.current = ''\n isEditingRef.current = false\n const newValue = (e.key === 'Home' ? min : max).toString().padStart(2, '0')\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, newValue, period))\n return\n }\n\n if (e.key >= '0' && e.key <= '9') {\n e.preventDefault()\n const newBuffer = editBufferRef.current + e.key\n const numValue = parseInt(newBuffer, 10)\n\n if (newBuffer.length >= 2) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n onRightFocus?.()\n } else if (numValue * 10 > max) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n onRightFocus?.()\n } else {\n editBufferRef.current = newBuffer\n isEditingRef.current = true\n if (internalRef.current) {\n internalRef.current.value = formatTimeNumber(numValue, resolvedLocale)\n }\n }\n return\n }\n\n if (e.key === 'Backspace') {\n e.preventDefault()\n if (editBufferRef.current) {\n const newBuffer = editBufferRef.current.slice(0, -1)\n editBufferRef.current = newBuffer\n isEditingRef.current = newBuffer.length > 0\n if (internalRef.current) {\n internalRef.current.value = newBuffer ? formatTimeNumber(parseInt(newBuffer, 10), resolvedLocale) : displayValue\n }\n } else {\n editBufferRef.current = ''\n commitValue(min.toString().padStart(2, '0'))\n if (internalRef.current) {\n internalRef.current.value = formatTimeNumber(min, resolvedLocale)\n }\n }\n }\n },\n [internalValue, effectiveDate, min, max, onLeftFocus, onRightFocus, period, picker, setDate, commitValue, displayValue, resolvedLocale],\n )\n\n const handleFocus = useCallback(() => {\n if (!isEditingRef.current) {\n editBufferRef.current = ''\n }\n }, [])\n\n const handleBlur = useCallback(() => {\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n } else {\n editBufferRef.current = ''\n isEditingRef.current = false\n }\n }\n }, [min, max, commitValue])\n\n return (\n <Input\n className={cn('text-center tabular-nums', className)}\n data-slot='datetime-input'\n data-testid={`spectral-datetime-input-${picker}`}\n defaultValue={displayValue}\n disabled={disabled}\n id={id ?? picker}\n inputMode='numeric'\n name={name ?? picker}\n onBlur={handleBlur}\n onFocus={handleFocus}\n onKeyDown={(e) => {\n onKeyDown?.(e)\n handleKeyDown(e)\n }}\n readOnly\n ref={inputRef}\n role='spinbutton'\n {...ariaProps}\n {...props}\n />\n )\n}\n\nDateTimeInput.displayName = 'DateTimeInput'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateTimeInput.js","names":[],"sources":["../../src/components/DateTimePicker/DateTimeInput.tsx"],"sourcesContent":["import { Input } from '@primitives/input'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useMemo, useRef, type ComponentProps, type KeyboardEvent, type Ref } from 'react'\nimport { DEFAULT_TRANSLATIONS, formatTimeNumber, getAriaLabel, getAriaValueMinMax, getAriaValueNow, getArrowByType, getDateByType, getResolvedLocale, setDateByType, type Period, type TimePickerTranslations, type TimePickerType } from './DateTimeUtils'\n\nexport interface DateTimeInputProps extends Omit<ComponentProps<'input'>, 'onChange' | 'type'> {\n date: Date | undefined\n /** Locale for number formatting */\n locale?: string\n onLeftFocus?: () => void\n onRightFocus?: () => void\n period?: Period\n picker: TimePickerType\n setDate: (date: Date | undefined) => void\n /** Override translation strings for ARIA labels */\n translations?: TimePickerTranslations\n}\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nexport const DateTimeInput = ({ className, date, disabled, id, locale, name, onKeyDown, onLeftFocus, onRightFocus, period, picker, ref, setDate, translations = DEFAULT_TRANSLATIONS, ...props }: DateTimeInputProps & { ref?: Ref<HTMLInputElement> }) => {\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(internalRef, ref)\n const editBufferRef = useRef('')\n const isEditingRef = useRef(false)\n\n const resolvedLocale = useMemo(() => getResolvedLocale(locale), [locale])\n\n const effectiveDate = useMemo(() => {\n if (date) return date\n const d = new Date()\n d.setHours(0, 0, 0, 0)\n return d\n }, [date])\n\n const { min, max } = useMemo(() => getAriaValueMinMax(picker), [picker])\n const internalValue = useMemo(() => getDateByType(effectiveDate, picker), [effectiveDate, picker])\n const displayValue = useMemo(() => formatTimeNumber(parseInt(internalValue, 10), resolvedLocale), [internalValue, resolvedLocale])\n\n const ariaProps = useMemo(\n () => ({\n 'aria-label': getAriaLabel(picker, translations),\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuenow': getAriaValueNow(effectiveDate, picker),\n 'aria-valuetext': displayValue,\n }),\n [effectiveDate, picker, displayValue, translations, min, max],\n )\n\n useEffect(() => {\n if (internalRef.current && !isEditingRef.current) {\n internalRef.current.value = displayValue\n }\n }, [displayValue])\n\n const commitValue = useCallback(\n (value: string) => {\n editBufferRef.current = ''\n isEditingRef.current = false\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, value, period))\n },\n [effectiveDate, picker, period, setDate],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Tab') {\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n return\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n onRightFocus?.()\n return\n }\n\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n }\n }\n onLeftFocus?.()\n return\n }\n\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault()\n editBufferRef.current = ''\n isEditingRef.current = false\n const step = e.key === 'ArrowUp' ? 1 : -1\n const newValue = getArrowByType(step, picker, internalValue)\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, newValue, period))\n return\n }\n\n if (e.key === 'Home' || e.key === 'End') {\n e.preventDefault()\n editBufferRef.current = ''\n isEditingRef.current = false\n const newValue = (e.key === 'Home' ? min : max).toString().padStart(2, '0')\n const newDate = new Date(effectiveDate)\n setDate(setDateByType(newDate, picker, newValue, period))\n return\n }\n\n if (e.key >= '0' && e.key <= '9') {\n e.preventDefault()\n const newBuffer = editBufferRef.current + e.key\n const numValue = parseInt(newBuffer, 10)\n\n if (newBuffer.length >= 2) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n onRightFocus?.()\n } else if (numValue * 10 > max) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n onRightFocus?.()\n } else {\n editBufferRef.current = newBuffer\n isEditingRef.current = true\n if (internalRef.current) {\n internalRef.current.value = formatTimeNumber(numValue, resolvedLocale)\n }\n }\n return\n }\n\n if (e.key === 'Backspace') {\n e.preventDefault()\n if (editBufferRef.current) {\n const newBuffer = editBufferRef.current.slice(0, -1)\n editBufferRef.current = newBuffer\n isEditingRef.current = newBuffer.length > 0\n if (internalRef.current) {\n internalRef.current.value = newBuffer ? formatTimeNumber(parseInt(newBuffer, 10), resolvedLocale) : displayValue\n }\n } else {\n editBufferRef.current = ''\n commitValue(min.toString().padStart(2, '0'))\n if (internalRef.current) {\n internalRef.current.value = formatTimeNumber(min, resolvedLocale)\n }\n }\n }\n },\n [internalValue, effectiveDate, min, max, onLeftFocus, onRightFocus, period, picker, setDate, commitValue, displayValue, resolvedLocale],\n )\n\n const handleFocus = useCallback(() => {\n if (!isEditingRef.current) {\n editBufferRef.current = ''\n }\n }, [])\n\n const handleBlur = useCallback(() => {\n if (editBufferRef.current) {\n const numValue = parseInt(editBufferRef.current, 10)\n if (!isNaN(numValue)) {\n const clamped = Math.max(min, Math.min(max, numValue))\n commitValue(clamped.toString().padStart(2, '0'))\n } else {\n editBufferRef.current = ''\n isEditingRef.current = false\n }\n }\n }, [min, max, commitValue])\n\n return (\n <Input\n className={cn('text-center tabular-nums', className)}\n data-slot='datetime-input'\n data-testid={`spectral-datetime-input-${picker}`}\n defaultValue={displayValue}\n disabled={disabled}\n id={id ?? picker}\n inputMode='numeric'\n name={name ?? picker}\n onBlur={handleBlur}\n onFocus={handleFocus}\n onKeyDown={(e) => {\n onKeyDown?.(e)\n handleKeyDown(e)\n }}\n readOnly\n ref={inputRef}\n role='spinbutton'\n {...ariaProps}\n {...props}\n />\n )\n}\n\nDateTimeInput.displayName = 'DateTimeInput'\n"],"mappings":";;;;;;;;AAkBA,MAAM,aAAiB,GAAG,SAAyC;AACjE,SAAQ,UAAoB;AAC1B,OAAK,SAAS,QAAQ;AACpB,OAAI,CAAC,IAAK;AACV,OAAI,OAAO,QAAQ,WACjB,KAAI,MAAK;OAER,CAAC,IAA8B,UAAU;IAE7C;;;AAIL,MAAa,iBAAiB,EAAE,WAAW,MAAM,UAAU,IAAI,QAAQ,MAAM,WAAW,aAAa,cAAc,QAAQ,QAAQ,KAAK,SAAS,eAAe,sBAAsB,GAAG,YAAkE;CACzP,MAAM,cAAc,OAAyB,KAAI;CACjD,MAAM,WAAW,UAAU,aAAa,IAAG;CAC3C,MAAM,gBAAgB,OAAO,GAAE;CAC/B,MAAM,eAAe,OAAO,MAAK;CAEjC,MAAM,iBAAiB,cAAc,kBAAkB,OAAO,EAAE,CAAC,OAAO,CAAA;CAExE,MAAM,gBAAgB,cAAc;AAClC,MAAI,KAAM,QAAO;EACjB,MAAM,oBAAI,IAAI,MAAK;AACnB,IAAE,SAAS,GAAG,GAAG,GAAG,EAAC;AACrB,SAAO;IACN,CAAC,KAAK,CAAA;CAET,MAAM,EAAE,KAAK,QAAQ,cAAc,mBAAmB,OAAO,EAAE,CAAC,OAAO,CAAA;CACvE,MAAM,gBAAgB,cAAc,cAAc,eAAe,OAAO,EAAE,CAAC,eAAe,OAAO,CAAA;CACjG,MAAM,eAAe,cAAc,iBAAiB,SAAS,eAAe,GAAG,EAAE,eAAe,EAAE,CAAC,eAAe,eAAe,CAAA;CAEjI,MAAM,YAAY,eACT;EACL,cAAc,aAAa,QAAQ,aAAa;EAChD,iBAAiB;EACjB,iBAAiB;EACjB,iBAAiB,gBAAgB,eAAe,OAAO;EACvD,kBAAkB;EACnB,GACD;EAAC;EAAe;EAAQ;EAAc;EAAc;EAAK;EAAI,CAC/D;AAEA,iBAAgB;AACd,MAAI,YAAY,WAAW,CAAC,aAAa,QACvC,aAAY,QAAQ,QAAQ;IAE7B,CAAC,aAAa,CAAA;CAEjB,MAAM,cAAc,aACjB,UAAkB;AACjB,gBAAc,UAAU;AACxB,eAAa,UAAU;AAEvB,UAAQ,cAAc,IADF,KAAK,cACI,EAAE,QAAQ,OAAO,OAAO,CAAA;IAEvD;EAAC;EAAe;EAAQ;EAAQ;EAAQ,CAC1C;CAEA,MAAM,gBAAgB,aACnB,MAAuC;AACtC,MAAI,EAAE,QAAQ,OAAO;AACnB,OAAI,cAAc,SAAS;IACzB,MAAM,WAAW,SAAS,cAAc,SAAS,GAAE;AACnD,QAAI,CAAC,MAAM,SAAS,CAElB,aADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;;AAGnD;;AAGF,MAAI,EAAE,QAAQ,cAAc;AAC1B,KAAE,gBAAe;AACjB,OAAI,cAAc,SAAS;IACzB,MAAM,WAAW,SAAS,cAAc,SAAS,GAAE;AACnD,QAAI,CAAC,MAAM,SAAS,CAElB,aADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;;AAGnD,mBAAe;AACf;;AAGF,MAAI,EAAE,QAAQ,aAAa;AACzB,KAAE,gBAAe;AACjB,OAAI,cAAc,SAAS;IACzB,MAAM,WAAW,SAAS,cAAc,SAAS,GAAE;AACnD,QAAI,CAAC,MAAM,SAAS,CAElB,aADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;;AAGnD,kBAAc;AACd;;AAGF,MAAI,EAAE,QAAQ,aAAa,EAAE,QAAQ,aAAa;AAChD,KAAE,gBAAe;AACjB,iBAAc,UAAU;AACxB,gBAAa,UAAU;GAEvB,MAAM,WAAW,eADJ,EAAE,QAAQ,YAAY,IAAI,IACD,QAAQ,cAAa;AAE3D,WAAQ,cAAc,IADF,KAAK,cACI,EAAE,QAAQ,UAAU,OAAO,CAAA;AACxD;;AAGF,MAAI,EAAE,QAAQ,UAAU,EAAE,QAAQ,OAAO;AACvC,KAAE,gBAAe;AACjB,iBAAc,UAAU;AACxB,gBAAa,UAAU;GACvB,MAAM,YAAY,EAAE,QAAQ,SAAS,MAAM,KAAK,UAAU,CAAC,SAAS,GAAG,IAAG;AAE1E,WAAQ,cAAc,IADF,KAAK,cACI,EAAE,QAAQ,UAAU,OAAO,CAAA;AACxD;;AAGF,MAAI,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAChC,KAAE,gBAAe;GACjB,MAAM,YAAY,cAAc,UAAU,EAAE;GAC5C,MAAM,WAAW,SAAS,WAAW,GAAE;AAEvC,OAAI,UAAU,UAAU,GAAG;AAEzB,gBADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;AAC/C,oBAAe;cACN,WAAW,KAAK,KAAK;AAE9B,gBADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;AAC/C,oBAAe;UACV;AACL,kBAAc,UAAU;AACxB,iBAAa,UAAU;AACvB,QAAI,YAAY,QACd,aAAY,QAAQ,QAAQ,iBAAiB,UAAU,eAAc;;AAGzE;;AAGF,MAAI,EAAE,QAAQ,aAAa;AACzB,KAAE,gBAAe;AACjB,OAAI,cAAc,SAAS;IACzB,MAAM,YAAY,cAAc,QAAQ,MAAM,GAAG,GAAE;AACnD,kBAAc,UAAU;AACxB,iBAAa,UAAU,UAAU,SAAS;AAC1C,QAAI,YAAY,QACd,aAAY,QAAQ,QAAQ,YAAY,iBAAiB,SAAS,WAAW,GAAG,EAAE,eAAe,GAAG;UAEjG;AACL,kBAAc,UAAU;AACxB,gBAAY,IAAI,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;AAC3C,QAAI,YAAY,QACd,aAAY,QAAQ,QAAQ,iBAAiB,KAAK,eAAc;;;IAKxE;EAAC;EAAe;EAAe;EAAK;EAAK;EAAa;EAAc;EAAQ;EAAQ;EAAS;EAAa;EAAc;EAAe,CACzI;CAEA,MAAM,cAAc,kBAAkB;AACpC,MAAI,CAAC,aAAa,QAChB,eAAc,UAAU;IAEzB,EAAE,CAAA;CAEL,MAAM,aAAa,kBAAkB;AACnC,MAAI,cAAc,SAAS;GACzB,MAAM,WAAW,SAAS,cAAc,SAAS,GAAE;AACnD,OAAI,CAAC,MAAM,SAAS,CAElB,aADgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,SAAS,CAClC,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAA;QAC1C;AACL,kBAAc,UAAU;AACxB,iBAAa,UAAU;;;IAG1B;EAAC;EAAK;EAAK;EAAY,CAAA;AAE1B,QACE,oBAAC,OAAD;EACE,WAAW,GAAG,4BAA4B,UAAU;EACpD,aAAU;EAEV,cAAc;EACJ;EACV,IAAI,MAAM;EACV,WAAU;EACV,MAAM,QAAQ;EACd,QAAQ;EACR,SAAS;EACT,YAAY,MAAM;AAChB,eAAY,EAAC;AACb,iBAAc,EAAC;;EAEjB;EACA,KAAK;EACL,MAAK;EACL,GAAI;EACJ,GAAI;EACL;;AAIL,cAAc,cAAc"}
|
|
@@ -50,23 +50,19 @@ const TimePeriodSelect = ({ ariaLabel, className, disabled, labels = DEFAULT_LAB
|
|
|
50
50
|
"aria-label": effectiveAriaLabel,
|
|
51
51
|
className: cn("h-9 w-fit min-w-20 grid-cols-[max-content_auto]", className),
|
|
52
52
|
"data-slot": "time-period-select",
|
|
53
|
-
"data-testid": "spectral-time-period-select",
|
|
54
53
|
onKeyDown: handleKeyDown,
|
|
55
54
|
ref,
|
|
56
55
|
size: "sm",
|
|
57
56
|
...props,
|
|
58
57
|
children: /* @__PURE__ */ jsx(SelectValue, { children: period === "am" ? labels.am : labels.pm })
|
|
59
58
|
}), /* @__PURE__ */ jsxs(SelectContent, {
|
|
60
|
-
"data-testid": "spectral-time-period-select-content",
|
|
61
59
|
onKeyDown: handleContentKeyDown,
|
|
62
60
|
position: "popper",
|
|
63
61
|
sideOffset: 4,
|
|
64
62
|
children: [/* @__PURE__ */ jsx(SelectItem, {
|
|
65
|
-
"data-testid": "spectral-time-period-select-am",
|
|
66
63
|
value: "am",
|
|
67
64
|
children: labels.am
|
|
68
65
|
}), /* @__PURE__ */ jsx(SelectItem, {
|
|
69
|
-
"data-testid": "spectral-time-period-select-pm",
|
|
70
66
|
value: "pm",
|
|
71
67
|
children: labels.pm
|
|
72
68
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePeriodSelect.js","names":[],"sources":["../../src/components/DateTimePicker/TimePeriodSelect.tsx"],"sourcesContent":["import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@primitives/select'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentProps, type KeyboardEvent, type Ref } from 'react'\nimport { type Period, type PeriodLabels } from './DateTimeUtils'\n\nexport interface TimePeriodSelectProps extends Omit<ComponentProps<'button'>, 'onChange' | 'ref'> {\n ariaLabel?: string\n labels?: PeriodLabels\n onLeftFocus?: () => void\n onPeriodChange: (period: Period) => void\n onRightFocus?: () => void\n period: Period\n}\n\nconst DEFAULT_LABELS: PeriodLabels = { am: 'am', pm: 'pm' }\n\nexport const TimePeriodSelect = ({ ariaLabel, className, disabled, labels = DEFAULT_LABELS, onLeftFocus, onPeriodChange, onRightFocus, period, ref, ...props }: TimePeriodSelectProps & { ref?: Ref<HTMLButtonElement> }) => {\n // Generate default ARIA label from the provided labels if not explicitly provided\n const effectiveAriaLabel = ariaLabel ?? `Select ${labels.am} or ${labels.pm}`\n const isSpaceKey = (key: string) => key === ' ' || key === 'Space' || key === 'Spacebar'\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const isOpen = e.currentTarget.getAttribute('data-state') === 'open'\n const isSelectionCommitKey = e.key === 'Enter' || isSpaceKey(e.key)\n\n if (isOpen && isSelectionCommitKey) {\n const contentId = e.currentTarget.getAttribute('aria-controls')\n const highlightedItem = contentId\n ? e.currentTarget.ownerDocument.getElementById(contentId)?.querySelector<HTMLElement>(\"[data-slot='select-item'][data-highlighted]\")\n : null\n if (highlightedItem) {\n e.preventDefault()\n highlightedItem.click()\n return\n }\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n onRightFocus?.()\n }\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n onLeftFocus?.()\n }\n }\n\n const handleContentKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (!isSpaceKey(e.key)) return\n\n const highlightedItem = e.currentTarget.querySelector<HTMLElement>(\"[data-slot='select-item'][data-highlighted]\")\n if (highlightedItem) {\n e.preventDefault()\n highlightedItem.click()\n }\n }\n\n return (\n <Select defaultValue={period} disabled={disabled} onValueChange={(value) => onPeriodChange(value as Period)} value={period}>\n <SelectTrigger\n aria-label={effectiveAriaLabel}\n className={cn('h-9 w-fit min-w-20 grid-cols-[max-content_auto]', className)}\n data-slot='time-period-select'\n data-testid='spectral-time-period-select'\n onKeyDown={handleKeyDown}\n ref={ref}\n size='sm'\n {...props}\n >\n <SelectValue>{period === 'am' ? labels.am : labels.pm}</SelectValue>\n </SelectTrigger>\n <SelectContent data-testid='spectral-time-period-select-content' onKeyDown={handleContentKeyDown} position='popper' sideOffset={4}>\n <SelectItem data-testid='spectral-time-period-select-am' value='am'>\n {labels.am}\n </SelectItem>\n <SelectItem data-testid='spectral-time-period-select-pm' value='pm'>\n {labels.pm}\n </SelectItem>\n </SelectContent>\n </Select>\n )\n}\n\nTimePeriodSelect.displayName = 'TimePeriodSelect'\n"],"mappings":";;;;;;;AAcA,MAAM,iBAA+B;CAAE,IAAI;CAAM,IAAI;
|
|
1
|
+
{"version":3,"file":"TimePeriodSelect.js","names":[],"sources":["../../src/components/DateTimePicker/TimePeriodSelect.tsx"],"sourcesContent":["import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@primitives/select'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentProps, type KeyboardEvent, type Ref } from 'react'\nimport { type Period, type PeriodLabels } from './DateTimeUtils'\n\nexport interface TimePeriodSelectProps extends Omit<ComponentProps<'button'>, 'onChange' | 'ref'> {\n ariaLabel?: string\n labels?: PeriodLabels\n onLeftFocus?: () => void\n onPeriodChange: (period: Period) => void\n onRightFocus?: () => void\n period: Period\n}\n\nconst DEFAULT_LABELS: PeriodLabels = { am: 'am', pm: 'pm' }\n\nexport const TimePeriodSelect = ({ ariaLabel, className, disabled, labels = DEFAULT_LABELS, onLeftFocus, onPeriodChange, onRightFocus, period, ref, ...props }: TimePeriodSelectProps & { ref?: Ref<HTMLButtonElement> }) => {\n // Generate default ARIA label from the provided labels if not explicitly provided\n const effectiveAriaLabel = ariaLabel ?? `Select ${labels.am} or ${labels.pm}`\n const isSpaceKey = (key: string) => key === ' ' || key === 'Space' || key === 'Spacebar'\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n const isOpen = e.currentTarget.getAttribute('data-state') === 'open'\n const isSelectionCommitKey = e.key === 'Enter' || isSpaceKey(e.key)\n\n if (isOpen && isSelectionCommitKey) {\n const contentId = e.currentTarget.getAttribute('aria-controls')\n const highlightedItem = contentId\n ? e.currentTarget.ownerDocument.getElementById(contentId)?.querySelector<HTMLElement>(\"[data-slot='select-item'][data-highlighted]\")\n : null\n if (highlightedItem) {\n e.preventDefault()\n highlightedItem.click()\n return\n }\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n onRightFocus?.()\n }\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n onLeftFocus?.()\n }\n }\n\n const handleContentKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (!isSpaceKey(e.key)) return\n\n const highlightedItem = e.currentTarget.querySelector<HTMLElement>(\"[data-slot='select-item'][data-highlighted]\")\n if (highlightedItem) {\n e.preventDefault()\n highlightedItem.click()\n }\n }\n\n return (\n <Select defaultValue={period} disabled={disabled} onValueChange={(value) => onPeriodChange(value as Period)} value={period}>\n <SelectTrigger\n aria-label={effectiveAriaLabel}\n className={cn('h-9 w-fit min-w-20 grid-cols-[max-content_auto]', className)}\n data-slot='time-period-select'\n data-testid='spectral-time-period-select'\n onKeyDown={handleKeyDown}\n ref={ref}\n size='sm'\n {...props}\n >\n <SelectValue>{period === 'am' ? labels.am : labels.pm}</SelectValue>\n </SelectTrigger>\n <SelectContent data-testid='spectral-time-period-select-content' onKeyDown={handleContentKeyDown} position='popper' sideOffset={4}>\n <SelectItem data-testid='spectral-time-period-select-am' value='am'>\n {labels.am}\n </SelectItem>\n <SelectItem data-testid='spectral-time-period-select-pm' value='pm'>\n {labels.pm}\n </SelectItem>\n </SelectContent>\n </Select>\n )\n}\n\nTimePeriodSelect.displayName = 'TimePeriodSelect'\n"],"mappings":";;;;;;;AAcA,MAAM,iBAA+B;CAAE,IAAI;CAAM,IAAI;CAAK;AAE1D,MAAa,oBAAoB,EAAE,WAAW,WAAW,UAAU,SAAS,gBAAgB,aAAa,gBAAgB,cAAc,QAAQ,KAAK,GAAG,YAAsE;CAE3N,MAAM,qBAAqB,aAAa,UAAU,OAAO,GAAG,MAAM,OAAO;CACzE,MAAM,cAAc,QAAgB,QAAQ,OAAO,QAAQ,WAAW,QAAQ;CAE9E,MAAM,iBAAiB,MAAwC;EAC7D,MAAM,SAAS,EAAE,cAAc,aAAa,aAAa,KAAK;EAC9D,MAAM,uBAAuB,EAAE,QAAQ,WAAW,WAAW,EAAE,IAAG;AAElE,MAAI,UAAU,sBAAsB;GAClC,MAAM,YAAY,EAAE,cAAc,aAAa,gBAAe;GAC9D,MAAM,kBAAkB,YACpB,EAAE,cAAc,cAAc,eAAe,UAAU,EAAE,cAA2B,8CAA6C,GACjI;AACJ,OAAI,iBAAiB;AACnB,MAAE,gBAAe;AACjB,oBAAgB,OAAM;AACtB;;;AAIJ,MAAI,EAAE,QAAQ,cAAc;AAC1B,KAAE,gBAAe;AACjB,mBAAe;;AAEjB,MAAI,EAAE,QAAQ,aAAa;AACzB,KAAE,gBAAe;AACjB,kBAAc;;;CAIlB,MAAM,wBAAwB,MAAqC;AACjE,MAAI,CAAC,WAAW,EAAE,IAAI,CAAE;EAExB,MAAM,kBAAkB,EAAE,cAAc,cAA2B,8CAA6C;AAChH,MAAI,iBAAiB;AACnB,KAAE,gBAAe;AACjB,mBAAgB,OAAM;;;AAI1B,QACE,qBAAC,QAAD;EAAQ,cAAc;EAAkB;EAAU,gBAAgB,UAAU,eAAe,MAAgB;EAAE,OAAO;YAApH,CACE,oBAAC,eAAD;GACE,cAAY;GACZ,WAAW,GAAG,mDAAmD,UAAU;GAC3E,aAAU;GAEV,WAAW;GACN;GACL,MAAK;GACL,GAAI;aAEJ,oBAAC,aAAD,YAAc,WAAW,OAAO,OAAO,KAAK,OAAO,IAAgB;GACtD,GACf,qBAAC,eAAD;GAAiE,WAAW;GAAsB,UAAS;GAAS,YAAY;aAAhI,CACE,oBAAC,YAAD;IAAyD,OAAM;cAC5D,OAAO;IACE,GACZ,oBAAC,YAAD;IAAyD,OAAM;cAC5D,OAAO;IACE,EACC;KACT;;;AAIZ,iBAAiB,cAAc"}
|
|
@@ -42,14 +42,12 @@ const TimePicker = ({ className, date, hourFormat, locale, onChange, onPeriodCha
|
|
|
42
42
|
"aria-label": is24Hour ? mergedTranslations.timePicker24Hour : mergedTranslations.timePicker12Hour,
|
|
43
43
|
className: cn("gap-1 inline-flex items-center", className),
|
|
44
44
|
"data-slot": "time-picker",
|
|
45
|
-
"data-testid": "spectral-time-picker",
|
|
46
45
|
role: "group",
|
|
47
46
|
...props,
|
|
48
47
|
children: [
|
|
49
48
|
/* @__PURE__ */ jsx(DateTimeInput, {
|
|
50
49
|
"aria-label": is24Hour ? mergedTranslations.hours24 : mergedTranslations.hours12,
|
|
51
50
|
className: "w-12",
|
|
52
|
-
"data-testid": "spectral-time-picker-hours",
|
|
53
51
|
date,
|
|
54
52
|
locale: resolvedLocale,
|
|
55
53
|
onRightFocus: () => minuteRef.current?.focus(),
|
|
@@ -67,7 +65,6 @@ const TimePicker = ({ className, date, hourFormat, locale, onChange, onPeriodCha
|
|
|
67
65
|
/* @__PURE__ */ jsx(DateTimeInput, {
|
|
68
66
|
"aria-label": mergedTranslations.minutes,
|
|
69
67
|
className: "w-12",
|
|
70
|
-
"data-testid": "spectral-time-picker-minutes",
|
|
71
68
|
date,
|
|
72
69
|
locale: resolvedLocale,
|
|
73
70
|
onLeftFocus: () => hourRef.current?.focus(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePicker.js","names":[],"sources":["../../src/components/DateTimePicker/TimePicker.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { useMemo, useRef, type ComponentProps } from 'react'\nimport { DateTimeInput } from './DateTimeInput'\nimport { DEFAULT_TRANSLATIONS, detectHourFormat, formatSelectPeriodLabel, getLocalizedPeriodLabels, getPeriodFromHours, getResolvedLocale, type HourFormat, type Period, type TimePickerTranslations } from './DateTimeUtils'\nimport { TimePeriodSelect } from './TimePeriodSelect'\n\nexport interface TimePickerProps extends Omit<ComponentProps<'div'>, 'onChange'> {\n date: Date | undefined\n hourFormat?: HourFormat\n locale?: string\n onChange: (date: Date | undefined) => void\n onPeriodChange?: (newPeriod: Period) => void\n /** Override translation strings for ARIA labels */\n translations?: Partial<TimePickerTranslations>\n}\n\nexport const TimePicker = ({ className, date, hourFormat, locale, onChange, onPeriodChange, translations, ...props }: TimePickerProps) => {\n const minuteRef = useRef<HTMLInputElement>(null)\n const hourRef = useRef<HTMLInputElement>(null)\n const periodRef = useRef<HTMLButtonElement>(null)\n\n // Resolve locale with fallback chain\n const resolvedLocale = useMemo(() => getResolvedLocale(locale), [locale])\n\n // Merge translations with defaults\n const mergedTranslations = useMemo(() => ({ ...DEFAULT_TRANSLATIONS, ...translations }), [translations])\n\n // Auto-detect hour format from locale if not explicitly provided\n const effectiveHourFormat = useMemo(() => hourFormat ?? detectHourFormat(resolvedLocale), [hourFormat, resolvedLocale])\n\n // Get localized am/pm labels\n const periodLabels = useMemo(() => getLocalizedPeriodLabels(resolvedLocale), [resolvedLocale])\n\n // Generate localized ARIA label for period select\n const selectPeriodAriaLabel = useMemo(() => formatSelectPeriodLabel(periodLabels, mergedTranslations.selectPeriodTemplate), [mergedTranslations.selectPeriodTemplate, periodLabels])\n\n const is24Hour = effectiveHourFormat === '24'\n const period = date ? getPeriodFromHours(date.getHours()) : 'am'\n\n const handlePeriodChange = (newPeriod: Period) => {\n if (!date) {\n // Match DateTimeInput behavior: initialize from today's date at midnight.\n const initialDate = new Date()\n initialDate.setHours(newPeriod === 'pm' ? 12 : 0, 0, 0, 0)\n onChange(initialDate)\n onPeriodChange?.(newPeriod)\n return\n }\n\n // Update the date to reflect the new period\n const newDate = new Date(date)\n const currentHours = newDate.getHours()\n const currentPeriod = getPeriodFromHours(currentHours)\n\n if (currentPeriod !== newPeriod) {\n // Toggle between am and pm (add or subtract 12 hours)\n const adjustment = newPeriod === 'pm' ? 12 : -12\n newDate.setHours(currentHours + adjustment)\n onChange(newDate)\n }\n\n onPeriodChange?.(newPeriod)\n }\n\n return (\n <div aria-label={is24Hour ? mergedTranslations.timePicker24Hour : mergedTranslations.timePicker12Hour} className={cn('gap-1 inline-flex items-center', className)} data-slot='time-picker' data-testid='spectral-time-picker' role='group' {...props}>\n <DateTimeInput\n aria-label={is24Hour ? mergedTranslations.hours24 : mergedTranslations.hours12}\n className='w-12'\n data-testid='spectral-time-picker-hours'\n date={date}\n locale={resolvedLocale}\n onRightFocus={() => minuteRef.current?.focus()}\n period={is24Hour ? undefined : period}\n picker={is24Hour ? 'hours' : '12hours'}\n ref={hourRef}\n setDate={onChange}\n translations={mergedTranslations}\n />\n\n <span aria-hidden='true' className='select-none'>\n :\n </span>\n\n <DateTimeInput\n aria-label={mergedTranslations.minutes}\n className='w-12'\n data-testid='spectral-time-picker-minutes'\n date={date}\n locale={resolvedLocale}\n onLeftFocus={() => hourRef.current?.focus()}\n onRightFocus={!is24Hour ? () => periodRef.current?.focus() : undefined}\n period={is24Hour ? undefined : period}\n picker='minutes'\n ref={minuteRef}\n setDate={onChange}\n translations={mergedTranslations}\n />\n\n {!is24Hour && <TimePeriodSelect ariaLabel={selectPeriodAriaLabel} labels={periodLabels} onLeftFocus={() => minuteRef.current?.focus()} onPeriodChange={handlePeriodChange} period={period} ref={periodRef} />}\n </div>\n )\n}\nTimePicker.displayName = 'TimePicker'\n"],"mappings":";;;;;;;;;AAgBA,MAAa,cAAc,EAAE,WAAW,MAAM,YAAY,QAAQ,UAAU,gBAAgB,cAAc,GAAG,YAA6B;CACxI,MAAM,YAAY,OAAyB,
|
|
1
|
+
{"version":3,"file":"TimePicker.js","names":[],"sources":["../../src/components/DateTimePicker/TimePicker.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { useMemo, useRef, type ComponentProps } from 'react'\nimport { DateTimeInput } from './DateTimeInput'\nimport { DEFAULT_TRANSLATIONS, detectHourFormat, formatSelectPeriodLabel, getLocalizedPeriodLabels, getPeriodFromHours, getResolvedLocale, type HourFormat, type Period, type TimePickerTranslations } from './DateTimeUtils'\nimport { TimePeriodSelect } from './TimePeriodSelect'\n\nexport interface TimePickerProps extends Omit<ComponentProps<'div'>, 'onChange'> {\n date: Date | undefined\n hourFormat?: HourFormat\n locale?: string\n onChange: (date: Date | undefined) => void\n onPeriodChange?: (newPeriod: Period) => void\n /** Override translation strings for ARIA labels */\n translations?: Partial<TimePickerTranslations>\n}\n\nexport const TimePicker = ({ className, date, hourFormat, locale, onChange, onPeriodChange, translations, ...props }: TimePickerProps) => {\n const minuteRef = useRef<HTMLInputElement>(null)\n const hourRef = useRef<HTMLInputElement>(null)\n const periodRef = useRef<HTMLButtonElement>(null)\n\n // Resolve locale with fallback chain\n const resolvedLocale = useMemo(() => getResolvedLocale(locale), [locale])\n\n // Merge translations with defaults\n const mergedTranslations = useMemo(() => ({ ...DEFAULT_TRANSLATIONS, ...translations }), [translations])\n\n // Auto-detect hour format from locale if not explicitly provided\n const effectiveHourFormat = useMemo(() => hourFormat ?? detectHourFormat(resolvedLocale), [hourFormat, resolvedLocale])\n\n // Get localized am/pm labels\n const periodLabels = useMemo(() => getLocalizedPeriodLabels(resolvedLocale), [resolvedLocale])\n\n // Generate localized ARIA label for period select\n const selectPeriodAriaLabel = useMemo(() => formatSelectPeriodLabel(periodLabels, mergedTranslations.selectPeriodTemplate), [mergedTranslations.selectPeriodTemplate, periodLabels])\n\n const is24Hour = effectiveHourFormat === '24'\n const period = date ? getPeriodFromHours(date.getHours()) : 'am'\n\n const handlePeriodChange = (newPeriod: Period) => {\n if (!date) {\n // Match DateTimeInput behavior: initialize from today's date at midnight.\n const initialDate = new Date()\n initialDate.setHours(newPeriod === 'pm' ? 12 : 0, 0, 0, 0)\n onChange(initialDate)\n onPeriodChange?.(newPeriod)\n return\n }\n\n // Update the date to reflect the new period\n const newDate = new Date(date)\n const currentHours = newDate.getHours()\n const currentPeriod = getPeriodFromHours(currentHours)\n\n if (currentPeriod !== newPeriod) {\n // Toggle between am and pm (add or subtract 12 hours)\n const adjustment = newPeriod === 'pm' ? 12 : -12\n newDate.setHours(currentHours + adjustment)\n onChange(newDate)\n }\n\n onPeriodChange?.(newPeriod)\n }\n\n return (\n <div aria-label={is24Hour ? mergedTranslations.timePicker24Hour : mergedTranslations.timePicker12Hour} className={cn('gap-1 inline-flex items-center', className)} data-slot='time-picker' data-testid='spectral-time-picker' role='group' {...props}>\n <DateTimeInput\n aria-label={is24Hour ? mergedTranslations.hours24 : mergedTranslations.hours12}\n className='w-12'\n data-testid='spectral-time-picker-hours'\n date={date}\n locale={resolvedLocale}\n onRightFocus={() => minuteRef.current?.focus()}\n period={is24Hour ? undefined : period}\n picker={is24Hour ? 'hours' : '12hours'}\n ref={hourRef}\n setDate={onChange}\n translations={mergedTranslations}\n />\n\n <span aria-hidden='true' className='select-none'>\n :\n </span>\n\n <DateTimeInput\n aria-label={mergedTranslations.minutes}\n className='w-12'\n data-testid='spectral-time-picker-minutes'\n date={date}\n locale={resolvedLocale}\n onLeftFocus={() => hourRef.current?.focus()}\n onRightFocus={!is24Hour ? () => periodRef.current?.focus() : undefined}\n period={is24Hour ? undefined : period}\n picker='minutes'\n ref={minuteRef}\n setDate={onChange}\n translations={mergedTranslations}\n />\n\n {!is24Hour && <TimePeriodSelect ariaLabel={selectPeriodAriaLabel} labels={periodLabels} onLeftFocus={() => minuteRef.current?.focus()} onPeriodChange={handlePeriodChange} period={period} ref={periodRef} />}\n </div>\n )\n}\nTimePicker.displayName = 'TimePicker'\n"],"mappings":";;;;;;;;;AAgBA,MAAa,cAAc,EAAE,WAAW,MAAM,YAAY,QAAQ,UAAU,gBAAgB,cAAc,GAAG,YAA6B;CACxI,MAAM,YAAY,OAAyB,KAAI;CAC/C,MAAM,UAAU,OAAyB,KAAI;CAC7C,MAAM,YAAY,OAA0B,KAAI;CAGhD,MAAM,iBAAiB,cAAc,kBAAkB,OAAO,EAAE,CAAC,OAAO,CAAA;CAGxE,MAAM,qBAAqB,eAAe;EAAE,GAAG;EAAsB,GAAG;EAAc,GAAG,CAAC,aAAa,CAAA;CAGvG,MAAM,sBAAsB,cAAc,cAAc,iBAAiB,eAAe,EAAE,CAAC,YAAY,eAAe,CAAA;CAGtH,MAAM,eAAe,cAAc,yBAAyB,eAAe,EAAE,CAAC,eAAe,CAAA;CAG7F,MAAM,wBAAwB,cAAc,wBAAwB,cAAc,mBAAmB,qBAAqB,EAAE,CAAC,mBAAmB,sBAAsB,aAAa,CAAA;CAEnL,MAAM,WAAW,wBAAwB;CACzC,MAAM,SAAS,OAAO,mBAAmB,KAAK,UAAU,CAAC,GAAG;CAE5D,MAAM,sBAAsB,cAAsB;AAChD,MAAI,CAAC,MAAM;GAET,MAAM,8BAAc,IAAI,MAAK;AAC7B,eAAY,SAAS,cAAc,OAAO,KAAK,GAAG,GAAG,GAAG,EAAC;AACzD,YAAS,YAAW;AACpB,oBAAiB,UAAS;AAC1B;;EAIF,MAAM,UAAU,IAAI,KAAK,KAAI;EAC7B,MAAM,eAAe,QAAQ,UAAS;AAGtC,MAFsB,mBAAmB,aAExB,KAAK,WAAW;GAE/B,MAAM,aAAa,cAAc,OAAO,KAAK;AAC7C,WAAQ,SAAS,eAAe,WAAU;AAC1C,YAAS,QAAO;;AAGlB,mBAAiB,UAAS;;AAG5B,QACE,qBAAC,OAAD;EAAK,cAAY,WAAW,mBAAmB,mBAAmB,mBAAmB;EAAkB,WAAW,GAAG,kCAAkC,UAAU;EAAE,aAAU;EAAiD,MAAK;EAAQ,GAAI;YAA/O;GACE,oBAAC,eAAD;IACE,cAAY,WAAW,mBAAmB,UAAU,mBAAmB;IACvE,WAAU;IAEJ;IACN,QAAQ;IACR,oBAAoB,UAAU,SAAS,OAAO;IAC9C,QAAQ,WAAW,SAAY;IAC/B,QAAQ,WAAW,UAAU;IAC7B,KAAK;IACL,SAAS;IACT,cAAc;IACf;GAED,oBAAC,QAAD;IAAM,eAAY;IAAO,WAAU;cAAa;IAE1C;GAEN,oBAAC,eAAD;IACE,cAAY,mBAAmB;IAC/B,WAAU;IAEJ;IACN,QAAQ;IACR,mBAAmB,QAAQ,SAAS,OAAO;IAC3C,cAAc,CAAC,iBAAiB,UAAU,SAAS,OAAO,GAAG;IAC7D,QAAQ,WAAW,SAAY;IAC/B,QAAO;IACP,KAAK;IACL,SAAS;IACT,cAAc;IACf;GAEA,CAAC,YAAY,oBAAC,kBAAD;IAAkB,WAAW;IAAuB,QAAQ;IAAc,mBAAmB,UAAU,SAAS,OAAO;IAAE,gBAAgB;IAA4B;IAAQ,KAAK;IAAa;GAC1M;;;AAGT,WAAW,cAAc"}
|
package/dist/DateTimePicker.js
CHANGED
|
@@ -45,13 +45,11 @@ const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = fal
|
|
|
45
45
|
return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsxs("div", {
|
|
46
46
|
className: cn("w-full", className),
|
|
47
47
|
"data-slot": "datetime-picker",
|
|
48
|
-
"data-testid": "spectral-datetime-picker",
|
|
49
48
|
...props,
|
|
50
49
|
children: [/* @__PURE__ */ jsx(DateTimeDisplayInput, {
|
|
51
50
|
"aria-describedby": describedBy,
|
|
52
51
|
"aria-invalid": state === "error",
|
|
53
52
|
className: cn("gap-4 pr-12 flex w-full justify-start", !date && "text-text-secondary", inputClassName),
|
|
54
|
-
"data-testid": "spectral-datetime-picker-input",
|
|
55
53
|
disabled,
|
|
56
54
|
endIcon: /* @__PURE__ */ jsx(PopoverTrigger, {
|
|
57
55
|
asChild: true,
|
|
@@ -59,7 +57,6 @@ const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = fal
|
|
|
59
57
|
children: /* @__PURE__ */ jsx(CalendarIcon, {
|
|
60
58
|
"aria-label": "Open date picker",
|
|
61
59
|
className: cn("right-4 text-input-icon hover:text-input-icon--hover absolute top-1/2 -translate-y-1/2 cursor-pointer focus:outline-none", disabled ? "pointer-events-none cursor-not-allowed opacity-50" : "hover:text-input-icon--hover cursor-pointer"),
|
|
62
|
-
"data-testid": "spectral-datetime-picker-trigger",
|
|
63
60
|
disabled
|
|
64
61
|
})
|
|
65
62
|
}),
|
|
@@ -80,7 +77,6 @@ const DateTimePicker = ({ calendarProps, className, defaultValue, disabled = fal
|
|
|
80
77
|
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
81
78
|
align: "start",
|
|
82
79
|
className: cn("rounded-lg py-4 px-6 flex", !showTimePicker && "w-[330px]", showTimePicker && effectiveHourFormat === "24" && "w-[486px]", showTimePicker && effectiveHourFormat === "12" && "w-[560px]"),
|
|
83
|
-
"data-testid": "spectral-datetime-picker-popover",
|
|
84
80
|
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
85
81
|
children: [/* @__PURE__ */ jsx(Calendar, {
|
|
86
82
|
...calendarProps,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimePicker.js","names":[],"sources":["../src/components/DateTimePicker/DateTimePicker.tsx"],"sourcesContent":["import { CalendarIcon } from '@components/Icons'\nimport { Popover, PopoverContent, PopoverTrigger } from '@components/Popover/Popover'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useMemo, type ComponentProps } from 'react'\nimport { type Locale } from 'react-day-picker'\nimport { Calendar, type CalendarProps } from './Calendar'\nimport { DateTimeDisplayInput } from './DateTimeDisplayInput'\nimport { detectHourFormat, getResolvedLocale, type HourFormat, type Period, type TimePickerTranslations } from './DateTimeUtils'\nimport { TimePicker } from './TimePicker'\n\nexport interface DateTimePickerProps extends Omit<ComponentProps<'div'>, 'onChange' | 'defaultValue'> {\n calendarProps?: Omit<CalendarProps, 'mode' | 'selected' | 'onSelect' | 'disablePastDates' | 'locale'>\n defaultValue?: Date\n disabled?: boolean\n disablePastDates?: boolean\n errorMessage?: string | string[] | Record<string, unknown> | null\n hourFormat?: HourFormat\n inputClassName?: string\n label?: string\n locale?: Partial<Locale>\n messageReserveLines?: number\n messageReserveSpace?: boolean\n onChange?: (date: Date | undefined) => void\n showTimePicker?: boolean\n state?: 'default' | 'disabled' | 'error'\n timeLocale?: string\n /** Override translation strings for time picker ARIA labels */\n timeTranslations?: Partial<TimePickerTranslations>\n value?: Date\n}\n\nexport const DateTimePicker = ({\n calendarProps,\n className,\n defaultValue,\n disabled = false,\n disablePastDates = true,\n errorMessage,\n hourFormat,\n inputClassName,\n label,\n locale,\n messageReserveLines = 1,\n messageReserveSpace = false,\n onChange,\n showTimePicker = true,\n state = 'default',\n timeLocale,\n timeTranslations,\n value,\n id,\n ...props\n}: DateTimePickerProps) => {\n const fieldId = useFormFieldId(id)\n const errorMessageId = getErrorMessageId(fieldId)\n const describedBy = state === 'error' && errorMessage ? errorMessageId : undefined\n const [date, setDate] = useUncontrolledState<Date | undefined>({\n defaultValue,\n onChange,\n value,\n })\n\n // Resolve time locale with fallback chain\n const resolvedTimeLocale = useMemo(() => getResolvedLocale(timeLocale), [timeLocale])\n\n // Auto-detect hour format from locale if not explicitly provided\n const effectiveHourFormat = useMemo(() => hourFormat ?? detectHourFormat(resolvedTimeLocale), [hourFormat, resolvedTimeLocale])\n\n const handleDateSelect = (selectedDate: Date | undefined) => {\n if (!selectedDate) {\n setDate(undefined)\n return\n }\n\n // Preserve the time from the existing date\n if (date) {\n selectedDate.setHours(date.getHours(), date.getMinutes(), 0, 0)\n }\n\n setDate(selectedDate)\n }\n\n const handleTimeChange = useCallback(\n (newDate: Date | undefined) => {\n setDate(newDate)\n },\n [setDate],\n )\n\n const handlePeriodChange = (period: Period) => {\n // Period change is already handled in TimePicker\n // This callback is for external consumers who want to react to period changes\n void period\n }\n\n const handleInputDateChange = (newDate: Date | undefined) => {\n setDate(newDate)\n }\n\n return (\n <Popover>\n <div className={cn('w-full', className)} data-slot='datetime-picker' data-testid='spectral-datetime-picker' {...props}>\n <DateTimeDisplayInput\n aria-describedby={describedBy}\n aria-invalid={state === 'error'}\n className={cn('gap-4 pr-12 flex w-full justify-start', !date && 'text-text-secondary', inputClassName)}\n data-testid='spectral-datetime-picker-input'\n disabled={disabled}\n endIcon={\n <PopoverTrigger asChild disabled={disabled}>\n <CalendarIcon\n aria-label='Open date picker'\n className={cn('right-4 text-input-icon hover:text-input-icon--hover absolute top-1/2 -translate-y-1/2 cursor-pointer focus:outline-none', disabled ? 'pointer-events-none cursor-not-allowed opacity-50' : 'hover:text-input-icon--hover cursor-pointer')}\n data-testid='spectral-datetime-picker-trigger'\n disabled={disabled}\n />\n </PopoverTrigger>\n }\n hourFormat={effectiveHourFormat}\n id={fieldId}\n label={label}\n onChange={handleInputDateChange}\n showTime={showTimePicker}\n state={state}\n value={date}\n />\n <ErrorMessage\n dataTestId='spectral-datetime-picker-error-message'\n id={errorMessageId}\n message={state === 'error' ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n\n <PopoverContent\n align='start'\n className={cn('rounded-lg py-4 px-6 flex', !showTimePicker && 'w-[330px]', showTimePicker && effectiveHourFormat === '24' && 'w-[486px]', showTimePicker && effectiveHourFormat === '12' && 'w-[560px]')}\n data-testid='spectral-datetime-picker-popover'\n onOpenAutoFocus={(e) => e.preventDefault()}\n >\n <Calendar {...calendarProps} disablePastDates={disablePastDates} locale={locale} mode='single' onSelect={handleDateSelect} selected={date} />\n {showTimePicker && (\n <div className='pl-6 border-l border-border-secondary'>\n <TimePicker date={date} hourFormat={effectiveHourFormat} locale={resolvedTimeLocale} onChange={handleTimeChange} onPeriodChange={handlePeriodChange} translations={timeTranslations} />\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n\nDateTimePicker.displayName = 'DateTimePicker'\n\n// Re-export sub-components for granular usage\nexport { Calendar } from './Calendar'\nexport { DateTimeDisplayInput } from './DateTimeDisplayInput'\nexport { DateTimeInput } from './DateTimeInput'\nexport { TimePeriodSelect } from './TimePeriodSelect'\nexport { TimePicker } from './TimePicker'\nexport * from './DateTimeUtils'\nexport { type Locale } from 'react-day-picker'\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiCA,MAAa,kBAAkB,EAC7B,eACA,WACA,cACA,WAAW,OACX,mBAAmB,MACnB,cACA,YACA,gBACA,OACA,QACA,sBAAsB,GACtB,sBAAsB,OACtB,UACA,iBAAiB,MACjB,QAAQ,WACR,YACA,kBACA,OACA,IACA,GAAG,YACsB;CACzB,MAAM,UAAU,eAAe,
|
|
1
|
+
{"version":3,"file":"DateTimePicker.js","names":[],"sources":["../src/components/DateTimePicker/DateTimePicker.tsx"],"sourcesContent":["import { CalendarIcon } from '@components/Icons'\nimport { Popover, PopoverContent, PopoverTrigger } from '@components/Popover/Popover'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useMemo, type ComponentProps } from 'react'\nimport { type Locale } from 'react-day-picker'\nimport { Calendar, type CalendarProps } from './Calendar'\nimport { DateTimeDisplayInput } from './DateTimeDisplayInput'\nimport { detectHourFormat, getResolvedLocale, type HourFormat, type Period, type TimePickerTranslations } from './DateTimeUtils'\nimport { TimePicker } from './TimePicker'\n\nexport interface DateTimePickerProps extends Omit<ComponentProps<'div'>, 'onChange' | 'defaultValue'> {\n calendarProps?: Omit<CalendarProps, 'mode' | 'selected' | 'onSelect' | 'disablePastDates' | 'locale'>\n defaultValue?: Date\n disabled?: boolean\n disablePastDates?: boolean\n errorMessage?: string | string[] | Record<string, unknown> | null\n hourFormat?: HourFormat\n inputClassName?: string\n label?: string\n locale?: Partial<Locale>\n messageReserveLines?: number\n messageReserveSpace?: boolean\n onChange?: (date: Date | undefined) => void\n showTimePicker?: boolean\n state?: 'default' | 'disabled' | 'error'\n timeLocale?: string\n /** Override translation strings for time picker ARIA labels */\n timeTranslations?: Partial<TimePickerTranslations>\n value?: Date\n}\n\nexport const DateTimePicker = ({\n calendarProps,\n className,\n defaultValue,\n disabled = false,\n disablePastDates = true,\n errorMessage,\n hourFormat,\n inputClassName,\n label,\n locale,\n messageReserveLines = 1,\n messageReserveSpace = false,\n onChange,\n showTimePicker = true,\n state = 'default',\n timeLocale,\n timeTranslations,\n value,\n id,\n ...props\n}: DateTimePickerProps) => {\n const fieldId = useFormFieldId(id)\n const errorMessageId = getErrorMessageId(fieldId)\n const describedBy = state === 'error' && errorMessage ? errorMessageId : undefined\n const [date, setDate] = useUncontrolledState<Date | undefined>({\n defaultValue,\n onChange,\n value,\n })\n\n // Resolve time locale with fallback chain\n const resolvedTimeLocale = useMemo(() => getResolvedLocale(timeLocale), [timeLocale])\n\n // Auto-detect hour format from locale if not explicitly provided\n const effectiveHourFormat = useMemo(() => hourFormat ?? detectHourFormat(resolvedTimeLocale), [hourFormat, resolvedTimeLocale])\n\n const handleDateSelect = (selectedDate: Date | undefined) => {\n if (!selectedDate) {\n setDate(undefined)\n return\n }\n\n // Preserve the time from the existing date\n if (date) {\n selectedDate.setHours(date.getHours(), date.getMinutes(), 0, 0)\n }\n\n setDate(selectedDate)\n }\n\n const handleTimeChange = useCallback(\n (newDate: Date | undefined) => {\n setDate(newDate)\n },\n [setDate],\n )\n\n const handlePeriodChange = (period: Period) => {\n // Period change is already handled in TimePicker\n // This callback is for external consumers who want to react to period changes\n void period\n }\n\n const handleInputDateChange = (newDate: Date | undefined) => {\n setDate(newDate)\n }\n\n return (\n <Popover>\n <div className={cn('w-full', className)} data-slot='datetime-picker' data-testid='spectral-datetime-picker' {...props}>\n <DateTimeDisplayInput\n aria-describedby={describedBy}\n aria-invalid={state === 'error'}\n className={cn('gap-4 pr-12 flex w-full justify-start', !date && 'text-text-secondary', inputClassName)}\n data-testid='spectral-datetime-picker-input'\n disabled={disabled}\n endIcon={\n <PopoverTrigger asChild disabled={disabled}>\n <CalendarIcon\n aria-label='Open date picker'\n className={cn('right-4 text-input-icon hover:text-input-icon--hover absolute top-1/2 -translate-y-1/2 cursor-pointer focus:outline-none', disabled ? 'pointer-events-none cursor-not-allowed opacity-50' : 'hover:text-input-icon--hover cursor-pointer')}\n data-testid='spectral-datetime-picker-trigger'\n disabled={disabled}\n />\n </PopoverTrigger>\n }\n hourFormat={effectiveHourFormat}\n id={fieldId}\n label={label}\n onChange={handleInputDateChange}\n showTime={showTimePicker}\n state={state}\n value={date}\n />\n <ErrorMessage\n dataTestId='spectral-datetime-picker-error-message'\n id={errorMessageId}\n message={state === 'error' ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n\n <PopoverContent\n align='start'\n className={cn('rounded-lg py-4 px-6 flex', !showTimePicker && 'w-[330px]', showTimePicker && effectiveHourFormat === '24' && 'w-[486px]', showTimePicker && effectiveHourFormat === '12' && 'w-[560px]')}\n data-testid='spectral-datetime-picker-popover'\n onOpenAutoFocus={(e) => e.preventDefault()}\n >\n <Calendar {...calendarProps} disablePastDates={disablePastDates} locale={locale} mode='single' onSelect={handleDateSelect} selected={date} />\n {showTimePicker && (\n <div className='pl-6 border-l border-border-secondary'>\n <TimePicker date={date} hourFormat={effectiveHourFormat} locale={resolvedTimeLocale} onChange={handleTimeChange} onPeriodChange={handlePeriodChange} translations={timeTranslations} />\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n\nDateTimePicker.displayName = 'DateTimePicker'\n\n// Re-export sub-components for granular usage\nexport { Calendar } from './Calendar'\nexport { DateTimeDisplayInput } from './DateTimeDisplayInput'\nexport { DateTimeInput } from './DateTimeInput'\nexport { TimePeriodSelect } from './TimePeriodSelect'\nexport { TimePicker } from './TimePicker'\nexport * from './DateTimeUtils'\nexport { type Locale } from 'react-day-picker'\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiCA,MAAa,kBAAkB,EAC7B,eACA,WACA,cACA,WAAW,OACX,mBAAmB,MACnB,cACA,YACA,gBACA,OACA,QACA,sBAAsB,GACtB,sBAAsB,OACtB,UACA,iBAAiB,MACjB,QAAQ,WACR,YACA,kBACA,OACA,IACA,GAAG,YACsB;CACzB,MAAM,UAAU,eAAe,GAAE;CACjC,MAAM,iBAAiB,kBAAkB,QAAO;CAChD,MAAM,cAAc,UAAU,WAAW,eAAe,iBAAiB;CACzE,MAAM,CAAC,MAAM,WAAW,qBAAuC;EAC7D;EACA;EACA;EACD,CAAA;CAGD,MAAM,qBAAqB,cAAc,kBAAkB,WAAW,EAAE,CAAC,WAAW,CAAA;CAGpF,MAAM,sBAAsB,cAAc,cAAc,iBAAiB,mBAAmB,EAAE,CAAC,YAAY,mBAAmB,CAAA;CAE9H,MAAM,oBAAoB,iBAAmC;AAC3D,MAAI,CAAC,cAAc;AACjB,WAAQ,OAAS;AACjB;;AAIF,MAAI,KACF,cAAa,SAAS,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,GAAG,EAAC;AAGhE,UAAQ,aAAY;;CAGtB,MAAM,mBAAmB,aACtB,YAA8B;AAC7B,UAAQ,QAAO;IAEjB,CAAC,QAAQ,CACX;CAEA,MAAM,sBAAsB,WAAmB;CAM/C,MAAM,yBAAyB,YAA8B;AAC3D,UAAQ,QAAO;;AAGjB,QACE,qBAAC,SAAD,aACE,qBAAC,OAAD;EAAK,WAAW,GAAG,UAAU,UAAU;EAAE,aAAU;EAAyD,GAAI;YAAhH,CACE,oBAAC,sBAAD;GACE,oBAAkB;GAClB,gBAAc,UAAU;GACxB,WAAW,GAAG,yCAAyC,CAAC,QAAQ,uBAAuB,eAAe;GAE5F;GACV,SACE,oBAAC,gBAAD;IAAgB;IAAkB;cAChC,oBAAC,cAAD;KACE,cAAW;KACX,WAAW,GAAG,4HAA4H,WAAW,sDAAsD,8CAA8C;KAE/O;KACX;IACa;GAElB,YAAY;GACZ,IAAI;GACG;GACP,UAAU;GACV,UAAU;GACH;GACP,OAAO;GACR,GACD,oBAAC,cAAD;GACE,YAAW;GACX,IAAI;GACJ,SAAS,UAAU,UAAU,eAAe;GACvB;GACA;GACtB,EACE;KAEL,qBAAC,gBAAD;EACE,OAAM;EACN,WAAW,GAAG,6BAA6B,CAAC,kBAAkB,aAAa,kBAAkB,wBAAwB,QAAQ,aAAa,kBAAkB,wBAAwB,QAAQ,YAAY;EAExM,kBAAkB,MAAM,EAAE,gBAAgB;YAJ5C,CAME,oBAAC,UAAD;GAAU,GAAI;GAAiC;GAA0B;GAAQ,MAAK;GAAS,UAAU;GAAkB,UAAU;GAAO,GAC3I,kBACC,oBAAC,OAAD;GAAK,WAAU;aACb,oBAAC,YAAD;IAAkB;IAAM,YAAY;IAAqB,QAAQ;IAAoB,UAAU;IAAkB,gBAAgB;IAAoB,cAAc;IAAmB;GACnL,EAEO;IACT;;AAIb,eAAe,cAAc"}
|
package/dist/Dialog.js
CHANGED
|
@@ -12,21 +12,18 @@ const Dialog = ({ isOpen, modal = false, ...props }) => {
|
|
|
12
12
|
open: isOpen,
|
|
13
13
|
modal,
|
|
14
14
|
"data-slot": "dialog",
|
|
15
|
-
"data-testid": "spectral-dialog",
|
|
16
15
|
...props
|
|
17
16
|
});
|
|
18
17
|
};
|
|
19
18
|
const DialogTrigger = ({ ...props }) => {
|
|
20
19
|
return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, {
|
|
21
20
|
asChild: true,
|
|
22
|
-
"data-testid": "spectral-dialog-trigger",
|
|
23
21
|
...props
|
|
24
22
|
});
|
|
25
23
|
};
|
|
26
24
|
const DialogPortal = ({ ...props }) => {
|
|
27
25
|
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, {
|
|
28
26
|
"data-slot": "dialog-portal",
|
|
29
|
-
"data-testid": "dialog-portal",
|
|
30
27
|
...props
|
|
31
28
|
});
|
|
32
29
|
};
|
|
@@ -34,7 +31,6 @@ const DialogClose = ({ className, ...props }) => {
|
|
|
34
31
|
return /* @__PURE__ */ jsx(DialogPrimitive.Close, {
|
|
35
32
|
asChild: true,
|
|
36
33
|
"data-slot": "dialog-close",
|
|
37
|
-
"data-testid": "spectral-dialog-close",
|
|
38
34
|
...props,
|
|
39
35
|
className: cn("hover:cursor-pointer", className)
|
|
40
36
|
});
|
|
@@ -43,18 +39,15 @@ const DialogOverlay = ({ className, ...props }) => {
|
|
|
43
39
|
return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
|
|
44
40
|
className: cn("inset-0 bg-black/50 backdrop-blur-sm fixed z-50 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:animate-out", "motion-safe:data-[state=closed]:fade-out-0", className),
|
|
45
41
|
"data-slot": "dialog-overlay",
|
|
46
|
-
"data-testid": "spectral-dialog-overlay",
|
|
47
42
|
...props
|
|
48
43
|
});
|
|
49
44
|
};
|
|
50
45
|
const DialogContent = ({ children, className, dialogOverlay = true, onEscapeKeyDown, onInteractOutside, onPointerDownOutside, showCloseButton = true, ...props }) => {
|
|
51
46
|
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
52
47
|
"data-slot": "dialog-portal",
|
|
53
|
-
"data-testid": "spectral-dialog-portal",
|
|
54
48
|
children: [dialogOverlay && /* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
55
49
|
className: cn("max-w-xl gap-4 rounded-lg p-6 shadow-elevation-3 has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg", "motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!", "motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95", RemoveScroll.classNames.fullWidth, className),
|
|
56
50
|
"data-slot": "dialog-content",
|
|
57
|
-
"data-testid": "spectral-dialog-content",
|
|
58
51
|
onEscapeKeyDown,
|
|
59
52
|
onInteractOutside,
|
|
60
53
|
onPointerDownOutside,
|
|
@@ -62,7 +55,6 @@ const DialogContent = ({ children, className, dialogOverlay = true, onEscapeKeyD
|
|
|
62
55
|
children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
|
|
63
56
|
className: `focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-accent data-[state=open]:text-text-primary top-4 right-4 rounded-xs absolute opacity-70 transition-opacity hover:opacity-100 hover:cursor-pointer disabled:pointer-events-none data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0`,
|
|
64
57
|
"data-slot": "dialog-close",
|
|
65
|
-
"data-testid": "spectral-dialog-close",
|
|
66
58
|
children: [/* @__PURE__ */ jsx(CloseIcon, { size: 18 }), /* @__PURE__ */ jsx("span", {
|
|
67
59
|
className: "sr-only",
|
|
68
60
|
children: "Close dialog"
|
|
@@ -75,7 +67,6 @@ const DialogHeader = ({ className, ...props }) => {
|
|
|
75
67
|
return /* @__PURE__ */ jsx("div", {
|
|
76
68
|
className: cn("gap-2 sm:text-left flex flex-col text-center", className),
|
|
77
69
|
"data-slot": "dialog-header",
|
|
78
|
-
"data-testid": "spectral-dialog-header",
|
|
79
70
|
...props
|
|
80
71
|
});
|
|
81
72
|
};
|
|
@@ -83,7 +74,6 @@ const DialogFooter = ({ className, ...props }) => {
|
|
|
83
74
|
return /* @__PURE__ */ jsx("div", {
|
|
84
75
|
className: cn("gap-2 sm:flex-row sm:justify-end bottom-0 py-4 px-6 -mx-6 sticky z-10 flex flex-col-reverse bg-dialog-bg/85", className),
|
|
85
76
|
"data-slot": "dialog-footer",
|
|
86
|
-
"data-testid": "spectral-dialog-footer",
|
|
87
77
|
...props
|
|
88
78
|
});
|
|
89
79
|
};
|
|
@@ -92,21 +82,18 @@ const DialogTitle = ({ children, className, ...props }) => {
|
|
|
92
82
|
if (hasTextContent(children)) return /* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
93
83
|
className: cn("text-2xl font-semibold leading-none", className),
|
|
94
84
|
"data-slot": "dialog-title",
|
|
95
|
-
"data-testid": "spectral-dialog-title",
|
|
96
85
|
...props,
|
|
97
86
|
children
|
|
98
87
|
});
|
|
99
88
|
if (isValidElement(children)) return /* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
100
89
|
asChild: true,
|
|
101
90
|
"data-slot": "dialog-title",
|
|
102
|
-
"data-testid": "spectral-dialog-title",
|
|
103
91
|
...props,
|
|
104
92
|
children
|
|
105
93
|
});
|
|
106
94
|
return /* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
107
95
|
className,
|
|
108
96
|
"data-slot": "dialog-title",
|
|
109
|
-
"data-testid": "spectral-dialog-title",
|
|
110
97
|
...props,
|
|
111
98
|
children
|
|
112
99
|
});
|
|
@@ -115,21 +102,18 @@ const DialogDescription = ({ children, className, ...props }) => {
|
|
|
115
102
|
if (hasTextContent(children)) return /* @__PURE__ */ jsx(DialogPrimitive.Description, {
|
|
116
103
|
className: cn("text-muted-foreground text-sm", className),
|
|
117
104
|
"data-slot": "dialog-description",
|
|
118
|
-
"data-testid": "spectral-dialog-description",
|
|
119
105
|
...props,
|
|
120
106
|
children
|
|
121
107
|
});
|
|
122
108
|
if (isValidElement(children)) return /* @__PURE__ */ jsx(DialogPrimitive.Description, {
|
|
123
109
|
asChild: true,
|
|
124
110
|
"data-slot": "dialog-description",
|
|
125
|
-
"data-testid": "spectral-dialog-description",
|
|
126
111
|
...props,
|
|
127
112
|
children
|
|
128
113
|
});
|
|
129
114
|
return /* @__PURE__ */ jsx(DialogPrimitive.Description, {
|
|
130
115
|
className,
|
|
131
116
|
"data-slot": "dialog-description",
|
|
132
|
-
"data-testid": "spectral-dialog-description",
|
|
133
117
|
...props,
|
|
134
118
|
children
|
|
135
119
|
});
|
package/dist/Dialog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","names":[],"sources":["../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import { CloseIcon } from '@components/Icons/CloseIcon'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { cn } from '@utils/twUtils'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { RemoveScroll } from 'react-remove-scroll'\n\nconst Dialog = ({\n isOpen,\n modal = false,\n ...props\n}: Omit<ComponentProps<typeof DialogPrimitive.Root>, 'open'> & {\n isOpen?: boolean\n dialogOverlay?: boolean\n}) => {\n return <DialogPrimitive.Root open={isOpen} modal={modal} data-slot='dialog' data-testid='spectral-dialog' {...props} />\n}\n\nconst DialogTrigger = ({ ...props }: ComponentProps<typeof DialogPrimitive.Trigger>) => {\n return <DialogPrimitive.Trigger asChild data-testid='spectral-dialog-trigger' {...props} />\n}\n\nconst DialogPortal = ({ ...props }: ComponentProps<typeof DialogPrimitive.Portal>) => {\n return <DialogPrimitive.Portal data-slot='dialog-portal' data-testid='dialog-portal' {...props} />\n}\n\nconst DialogClose = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Close>) => {\n return <DialogPrimitive.Close asChild data-slot='dialog-close' data-testid='spectral-dialog-close' {...props} className={cn('hover:cursor-pointer', className)} />\n}\n\nconst DialogOverlay = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) => {\n return (\n <DialogPrimitive.Overlay\n className={cn('inset-0 bg-black/50 backdrop-blur-sm fixed z-50 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:animate-out',\n 'motion-safe:data-[state=closed]:fade-out-0', className)}\n data-slot='dialog-overlay'\n data-testid='spectral-dialog-overlay'\n {...props}\n />\n )\n}\n\nconst DialogContent = ({\n children,\n className,\n dialogOverlay = true,\n onEscapeKeyDown,\n onInteractOutside,\n onPointerDownOutside,\n showCloseButton = true,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Content> & {\n dialogOverlay?: boolean\n onEscapeKeyDown?: (event: KeyboardEvent) => void\n onInteractOutside?: (event: PointerEvent | FocusEvent) => void\n onPointerDownOutside?: (event: PointerEvent) => void\n showCloseButton?: boolean\n}) => {\n return (\n <DialogPortal data-slot='dialog-portal' data-testid='spectral-dialog-portal'>\n {dialogOverlay && <DialogOverlay />}\n <DialogPrimitive.Content\n className={cn(\n 'max-w-xl gap-4 rounded-lg p-6 shadow-elevation-3 has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg',\n 'motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!',\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n RemoveScroll.classNames.fullWidth,\n className,\n )}\n data-slot='dialog-content'\n data-testid='spectral-dialog-content'\n onEscapeKeyDown={onEscapeKeyDown}\n onInteractOutside={onInteractOutside}\n onPointerDownOutside={onPointerDownOutside}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className={`focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-accent data-[state=open]:text-text-primary top-4 right-4 rounded-xs absolute opacity-70 transition-opacity hover:opacity-100 hover:cursor-pointer disabled:pointer-events-none data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0`}\n data-slot='dialog-close'\n data-testid='spectral-dialog-close'\n >\n <CloseIcon size={18} />\n <span className='sr-only'>Close dialog</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nconst DialogHeader = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:text-left flex flex-col text-center', className)} data-slot='dialog-header' data-testid='spectral-dialog-header' {...props} />\n}\n\nconst DialogFooter = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:flex-row sm:justify-end bottom-0 py-4 px-6 -mx-6 sticky z-10 flex flex-col-reverse bg-dialog-bg/85', className)} data-slot='dialog-footer' data-testid='spectral-dialog-footer' {...props} />\n}\n\nconst hasTextContent = (value: ReactNode) => (typeof value === 'string' ? value.trim().length > 0 : typeof value === 'number')\n\nconst DialogTitle = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Title className={cn('text-2xl font-semibold leading-none', className)} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Title asChild data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n return <DialogPrimitive.Title className={className} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n}\n\nconst DialogDescription = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Description>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Description className={cn('text-muted-foreground text-sm', className)} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Description asChild data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n return <DialogPrimitive.Description className={className} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n}\n\nexport { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger }\n"],"mappings":";;;;;;;;;AAMA,MAAM,UAAU,EACd,QACA,QAAQ,OACR,GAAG,YAIC;AACJ,QAAO,oBAAC,gBAAgB,MAAjB;EAAsB,MAAM;EAAe;EAAO,aAAU;
|
|
1
|
+
{"version":3,"file":"Dialog.js","names":[],"sources":["../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import { CloseIcon } from '@components/Icons/CloseIcon'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { cn } from '@utils/twUtils'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { RemoveScroll } from 'react-remove-scroll'\n\nconst Dialog = ({\n isOpen,\n modal = false,\n ...props\n}: Omit<ComponentProps<typeof DialogPrimitive.Root>, 'open'> & {\n isOpen?: boolean\n dialogOverlay?: boolean\n}) => {\n return <DialogPrimitive.Root open={isOpen} modal={modal} data-slot='dialog' data-testid='spectral-dialog' {...props} />\n}\n\nconst DialogTrigger = ({ ...props }: ComponentProps<typeof DialogPrimitive.Trigger>) => {\n return <DialogPrimitive.Trigger asChild data-testid='spectral-dialog-trigger' {...props} />\n}\n\nconst DialogPortal = ({ ...props }: ComponentProps<typeof DialogPrimitive.Portal>) => {\n return <DialogPrimitive.Portal data-slot='dialog-portal' data-testid='dialog-portal' {...props} />\n}\n\nconst DialogClose = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Close>) => {\n return <DialogPrimitive.Close asChild data-slot='dialog-close' data-testid='spectral-dialog-close' {...props} className={cn('hover:cursor-pointer', className)} />\n}\n\nconst DialogOverlay = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) => {\n return (\n <DialogPrimitive.Overlay\n className={cn('inset-0 bg-black/50 backdrop-blur-sm fixed z-50 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:animate-out',\n 'motion-safe:data-[state=closed]:fade-out-0', className)}\n data-slot='dialog-overlay'\n data-testid='spectral-dialog-overlay'\n {...props}\n />\n )\n}\n\nconst DialogContent = ({\n children,\n className,\n dialogOverlay = true,\n onEscapeKeyDown,\n onInteractOutside,\n onPointerDownOutside,\n showCloseButton = true,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Content> & {\n dialogOverlay?: boolean\n onEscapeKeyDown?: (event: KeyboardEvent) => void\n onInteractOutside?: (event: PointerEvent | FocusEvent) => void\n onPointerDownOutside?: (event: PointerEvent) => void\n showCloseButton?: boolean\n}) => {\n return (\n <DialogPortal data-slot='dialog-portal' data-testid='spectral-dialog-portal'>\n {dialogOverlay && <DialogOverlay />}\n <DialogPrimitive.Content\n className={cn(\n 'max-w-xl gap-4 rounded-lg p-6 shadow-elevation-3 has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg',\n 'motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!',\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n RemoveScroll.classNames.fullWidth,\n className,\n )}\n data-slot='dialog-content'\n data-testid='spectral-dialog-content'\n onEscapeKeyDown={onEscapeKeyDown}\n onInteractOutside={onInteractOutside}\n onPointerDownOutside={onPointerDownOutside}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className={`focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-accent data-[state=open]:text-text-primary top-4 right-4 rounded-xs absolute opacity-70 transition-opacity hover:opacity-100 hover:cursor-pointer disabled:pointer-events-none data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0`}\n data-slot='dialog-close'\n data-testid='spectral-dialog-close'\n >\n <CloseIcon size={18} />\n <span className='sr-only'>Close dialog</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nconst DialogHeader = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:text-left flex flex-col text-center', className)} data-slot='dialog-header' data-testid='spectral-dialog-header' {...props} />\n}\n\nconst DialogFooter = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:flex-row sm:justify-end bottom-0 py-4 px-6 -mx-6 sticky z-10 flex flex-col-reverse bg-dialog-bg/85', className)} data-slot='dialog-footer' data-testid='spectral-dialog-footer' {...props} />\n}\n\nconst hasTextContent = (value: ReactNode) => (typeof value === 'string' ? value.trim().length > 0 : typeof value === 'number')\n\nconst DialogTitle = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Title className={cn('text-2xl font-semibold leading-none', className)} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Title asChild data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n return <DialogPrimitive.Title className={className} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n}\n\nconst DialogDescription = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Description>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Description className={cn('text-muted-foreground text-sm', className)} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Description asChild data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n return <DialogPrimitive.Description className={className} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n}\n\nexport { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger }\n"],"mappings":";;;;;;;;;AAMA,MAAM,UAAU,EACd,QACA,QAAQ,OACR,GAAG,YAIC;AACJ,QAAO,oBAAC,gBAAgB,MAAjB;EAAsB,MAAM;EAAe;EAAO,aAAU;EAAuC,GAAI;EAAQ;;AAGxH,MAAM,iBAAiB,EAAE,GAAG,YAA4D;AACtF,QAAO,oBAAC,gBAAgB,SAAjB;EAAyB;EAA8C,GAAI;EAAQ;;AAG5F,MAAM,gBAAgB,EAAE,GAAG,YAA2D;AACpF,QAAO,oBAAC,gBAAgB,QAAjB;EAAwB,aAAU;EAA4C,GAAI;EAAQ;;AAGnG,MAAM,eAAe,EAAE,WAAW,GAAG,YAA0D;AAC7F,QAAO,oBAAC,gBAAgB,OAAjB;EAAuB;EAAQ,aAAU;EAAmD,GAAI;EAAO,WAAW,GAAG,wBAAwB,UAAU;EAAG;;AAGnK,MAAM,iBAAiB,EAAE,WAAW,GAAG,YAA4D;AACjG,QACE,oBAAC,gBAAgB,SAAjB;EACE,WAAW,GAAG,gLACd,8CAA8C,UAAU;EACxD,aAAU;EAEV,GAAI;EACL;;AAIL,MAAM,iBAAiB,EACrB,UACA,WACA,gBAAgB,MAChB,iBACA,mBACA,sBACA,kBAAkB,MAClB,GAAG,YAOC;AACJ,QACE,qBAAC,cAAD;EAAc,aAAU;YAAxB,CACG,iBAAiB,oBAAC,eAAD,EAAiB,GACnC,qBAAC,gBAAgB,SAAjB;GACE,WAAW,GACT,6OACA,wLACA,sIACA,aAAa,WAAW,WACxB,UACD;GACD,aAAU;GAEO;GACE;GACG;GACtB,GAAI;aAbN,CAeG,UACA,mBACC,qBAAC,gBAAgB,OAAjB;IACE,WAAW;IACX,aAAU;cAFZ,CAKE,oBAAC,WAAD,EAAW,MAAM,IAAK,GACtB,oBAAC,QAAD;KAAM,WAAU;eAAU;KAAkB,EACvB;MAEF;KACb;;;AAIlB,MAAM,gBAAgB,EAAE,WAAW,GAAG,YAAmC;AACvE,QAAO,oBAAC,OAAD;EAAK,WAAW,GAAG,gDAAgD,UAAU;EAAE,aAAU;EAAqD,GAAI;EAAQ;;AAGnK,MAAM,gBAAgB,EAAE,WAAW,GAAG,YAAmC;AACvE,QAAO,oBAAC,OAAD;EAAK,WAAW,GAAG,+GAA+G,UAAU;EAAE,aAAU;EAAqD,GAAI;EAAQ;;AAGlO,MAAM,kBAAkB,UAAsB,OAAO,UAAU,WAAW,MAAM,MAAM,CAAC,SAAS,IAAI,OAAO,UAAU;AAErH,MAAM,eAAe,EAAE,UAAU,WAAW,GAAG,YAA0D;AACvG,KAAI,eAAe,SAAS,CAC1B,QAAO,oBAAC,gBAAgB,OAAjB;EAAuB,WAAW,GAAG,uCAAuC,UAAU;EAAE,aAAU;EAAmD,GAAI;EAAQ;EAAgC;AAG1M,KAAI,eAAe,SAAS,CAC1B,QAAO,oBAAC,gBAAgB,OAAjB;EAAuB;EAAQ,aAAU;EAAmD,GAAI;EAAQ;EAAgC;AAGjJ,QAAO,oBAAC,gBAAgB,OAAjB;EAAkC;EAAW,aAAU;EAAmD,GAAI;EAAQ;EAAgC;;AAG/J,MAAM,qBAAqB,EAAE,UAAU,WAAW,GAAG,YAAgE;AACnH,KAAI,eAAe,SAAS,CAC1B,QAAO,oBAAC,gBAAgB,aAAjB;EAA6B,WAAW,GAAG,iCAAiC,UAAU;EAAE,aAAU;EAA+D,GAAI;EAAQ;EAAsC;AAG5N,KAAI,eAAe,SAAS,CAC1B,QAAO,oBAAC,gBAAgB,aAAjB;EAA6B;EAAQ,aAAU;EAA+D,GAAI;EAAQ;EAAsC;AAGzK,QAAO,oBAAC,gBAAgB,aAAjB;EAAwC;EAAW,aAAU;EAA+D,GAAI;EAAQ;EAAsC"}
|
|
@@ -109,7 +109,6 @@ const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color l
|
|
|
109
109
|
"aria-label": accessibleName,
|
|
110
110
|
className: cn("inline-flex items-center justify-center rounded-full text-text-primary outline-none hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-50 motion-safe:transition-opacity motion-safe:duration-150", draggable && "touch-none", draggable && (dragging ? "cursor-grabbing" : "cursor-grab"), triggerClassName),
|
|
111
111
|
"data-slot": "directional-color-wheel-disclosure-trigger",
|
|
112
|
-
"data-testid": `${dataTestId}-trigger`,
|
|
113
112
|
disabled,
|
|
114
113
|
onClick: handleTriggerClick,
|
|
115
114
|
onLostPointerCapture: endDrag,
|
|
@@ -129,7 +128,6 @@ const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color l
|
|
|
129
128
|
className: "p-0 flex items-center justify-center overflow-visible border-none bg-transparent shadow-none",
|
|
130
129
|
collisionPadding,
|
|
131
130
|
"data-slot": "directional-color-wheel-disclosure-content",
|
|
132
|
-
"data-testid": `${dataTestId}-content`,
|
|
133
131
|
onEscapeKeyDown: dismissOnInteractOutside ? void 0 : (event) => event.preventDefault(),
|
|
134
132
|
onInteractOutside: dismissOnInteractOutside ? void 0 : (event) => event.preventDefault(),
|
|
135
133
|
onOpenAutoFocus: (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DirectionalColorWheelDisclosure.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"sourcesContent":["import { Card } from '@components/DataCard/Card'\nimport { Popover, PopoverContent, PopoverTrigger } from '@components/Popover/Popover'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type Ref, type RefObject } from 'react'\nimport { DirectionalColorWheelGlyph } from './DirectionalColorWheelGlyph'\n\ntype DisclosureAlign = 'start' | 'center' | 'end'\ntype DisclosureSide = 'top' | 'right' | 'bottom' | 'left'\n\n/** Pixel offset of the draggable disclosure from where it is mounted (default `{ x: 0, y: 0 }`). */\nexport interface DirectionalColorWheelDisclosurePosition {\n /** Horizontal offset in px (positive moves right). */\n x: number\n /** Vertical offset in px (positive moves down). */\n y: number\n}\n\n// Drag distance (px) below which a press on the glyph is a tap (toggle), not a reposition drag.\nconst DRAG_SLOP_PX = 4\nconst ORIGIN: DirectionalColorWheelDisclosurePosition = { x: 0, y: 0 }\n\nexport interface DirectionalColorWheelDisclosureProps {\n /** Accessible name for the collapsed trigger button (`aria-expanded` conveys the open state). */\n accessibleName?: string\n /** How the panel aligns to the trigger. Defaults to `start` for a corner-anchored feed widget. */\n align?: DisclosureAlign\n /** The expandable content: the bare wheel, or the wheel framed by the GRAMS controls. */\n children: ReactNode\n /** Layout-only class extension for the expanded panel. */\n className?: string\n /**\n * Override the collapsed affordance. Defaults to a {@link DirectionalColorWheelGlyph} mini\n * compass; pass a palette-matched glyph (or any node) to mirror the expanded wheel's colours.\n */\n collapsedIcon?: ReactNode\n /** Min distance (px) from the viewport edge before the panel flips/shifts. */\n collisionPadding?: number\n dataTestId?: string\n /**\n * Element the drag is clamped to so the glyph can't be parked off the feed. Pass a ref to the\n * container the disclosure is mounted over (e.g. the plot surface). `undefined`/`null` = no clamp\n * (free drag). Only used when `draggable`.\n */\n dragBoundsRef?: RefObject<HTMLElement | null> | null\n /** Uncontrolled initial expanded state. */\n defaultExpanded?: boolean\n /** Uncontrolled initial position offset (px) from the mount point. Defaults to the corner it is mounted at. */\n defaultPosition?: DirectionalColorWheelDisclosurePosition\n disabled?: boolean\n /**\n * When `true`, the operator can drag the collapsed glyph to reposition the whole widget (the\n * expanded panel reflows with it in real time). A tap still toggles; only a drag past a small\n * slop repositions. Defaults to `false` (fixed at its mount point).\n */\n draggable?: boolean\n /**\n * When `false`, the panel stays open during outside interaction and Esc — it toggles only via the\n * trigger (or a controlled `expanded`). Use this for a feed-mounted wheel so clicking/scrubbing the\n * feed underneath doesn't dismiss the legend. Defaults to `true` (standard popover dismissal).\n */\n dismissOnInteractOutside?: boolean\n /**\n * Controlled expanded state. `undefined`/`null` leaves the disclosure uncontrolled, so Horizon\n * can gate the feature with a single nullable value (PAT-028).\n */\n expanded?: boolean | null\n /** Called whenever the disclosure expands or collapses (click, Esc, or outside dismiss). */\n onExpandedChange?: (expanded: boolean) => void\n /** Called as the glyph is dragged with the next position offset (px). Pair with `position` to persist it. */\n onPositionChange?: (position: DirectionalColorWheelDisclosurePosition) => void\n /**\n * Controlled position offset (px) from the mount point. `undefined`/`null` leaves it uncontrolled,\n * so Horizon can gate the feature with a single nullable value (PAT-028). Requires `draggable` to move.\n */\n position?: DirectionalColorWheelDisclosurePosition | null\n ref?: Ref<HTMLButtonElement>\n /** Which side of the trigger the panel grows toward. Defaults to `top` (icon at a bottom corner). */\n side?: DisclosureSide\n /** Gap (px) between the trigger and the panel. */\n sideOffset?: number\n /**\n * Inline styles merged onto the expanded panel (the {@link Card}). Use this for visual overrides\n * — background, border, shadow — that the `card-effects` base class doesn't expose to `className`\n * utilities. The default panel width is applied first, so a `width` set here wins over the `width` prop.\n */\n style?: CSSProperties\n /** Layout-only class extension for the collapsed trigger button. */\n triggerClassName?: string\n /** Panel width: a pixel number or a CSS width string. Defaults to `'fit-content'` (hugs content). */\n width?: number | string\n}\n\n/**\n * Collapse/expand affordance for the {@link DirectionalColorWheel}, built on the Spectral\n * {@link Popover} primitive so it inherits corner anchoring, click-outside/Esc dismissal, focus\n * management, and `aria-expanded`/`aria-controls` for free. Collapsed it is a small compass glyph\n * (a real `<button>`); expanded it reveals whatever `children` it wraps — the bare wheel, or the\n * wheel plus the GRAMS Color Angle / Threshold / Sectors controls — inside a Spectral {@link Card}\n * panel, growing from the icon with a `transform-origin`-anchored zoom+fade that is automatically\n * skipped under `prefers-reduced-motion`. `className` extends the Card panel's classes and `style`\n * applies inline overrides to it (background, border, shadow, …).\n *\n * Pass `draggable` to let the operator drag the glyph to reposition the whole widget on a feed; the\n * expanded panel reflows with it in real time, and `dragBoundsRef` clamps it inside its container so\n * it can't be parked off the feed. Position is controlled+uncontrolled via\n * `position` / `defaultPosition` / `onPositionChange` (a nullable px offset from the mount point,\n * PAT-028) so a consumer can persist where the operator parked it.\n */\nexport const DirectionalColorWheelDisclosure = ({\n accessibleName = 'Directional color legend',\n align = 'start',\n children,\n className,\n collapsedIcon,\n collisionPadding = 8,\n dataTestId = 'spectral-directional-color-wheel-disclosure',\n defaultExpanded,\n defaultPosition,\n disabled = false,\n dismissOnInteractOutside = true,\n draggable = false,\n dragBoundsRef,\n expanded,\n onExpandedChange,\n onPositionChange,\n position,\n ref,\n side = 'top',\n sideOffset = 8,\n style,\n triggerClassName,\n width = 'fit-content',\n}: DirectionalColorWheelDisclosureProps) => {\n const panelRef = useRef<HTMLDivElement>(null)\n // `width` sizes the Card panel; the popover hugs it (fit-content) so there is no empty gap.\n const panelWidth = typeof width === 'number' ? `${width}px` : width\n\n const [positionValue, setPositionValue] = useUncontrolledState<DirectionalColorWheelDisclosurePosition>({ defaultValue: defaultPosition ?? ORIGIN, onChange: onPositionChange, value: position ?? undefined })\n const [dragging, setDragging] = useState(false)\n // Live reposition drag (a ref so rapid moves don't hinge on re-render timing). `bounds` is the\n // clamp rect (the dragBoundsRef element); `homeLeft`/`homeTop` are the trigger's viewport position\n // at translate 0, so the allowed offset range keeps the glyph fully inside `bounds`.\n const dragRef = useRef<{ baseX: number; baseY: number; bounds: DOMRect | null; height: number; homeLeft: number; homeTop: number; moved: boolean; pointerId: number; startX: number; startY: number; width: number } | null>(null)\n // Set when a drag passes the slop so the trailing click is swallowed (preventDefault → Radix's\n // composed trigger toggle bails) instead of also expanding/collapsing the panel.\n const suppressClickRef = useRef(false)\n\n const handleTriggerPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n suppressClickRef.current = false\n if (!draggable || disabled) return\n try {\n event.currentTarget.setPointerCapture(event.pointerId)\n } catch {\n // setPointerCapture can throw without an active pointer (e.g. synthetic test events).\n }\n const triggerRect = event.currentTarget.getBoundingClientRect()\n dragRef.current = {\n baseX: positionValue.x,\n baseY: positionValue.y,\n bounds: dragBoundsRef?.current?.getBoundingClientRect() ?? null,\n height: triggerRect.height,\n // Subtract the active translate to recover the trigger's home (translate-0) position.\n homeLeft: triggerRect.left - positionValue.x,\n homeTop: triggerRect.top - positionValue.y,\n moved: false,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n width: triggerRect.width,\n }\n },\n [disabled, draggable, dragBoundsRef, positionValue.x, positionValue.y],\n )\n\n const handleTriggerPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n const drag = dragRef.current\n if (drag === null || drag.pointerId !== event.pointerId) return\n const deltaX = event.clientX - drag.startX\n const deltaY = event.clientY - drag.startY\n if (!drag.moved) {\n if (Math.hypot(deltaX, deltaY) <= DRAG_SLOP_PX) return\n drag.moved = true\n suppressClickRef.current = true\n // Promote the trigger to its own compositor layer and poll the popover position every frame\n // (`updatePositionStrategy='always'`) so the panel reflows with the glyph rather than snapping.\n setDragging(true)\n }\n let nextX = drag.baseX + deltaX\n let nextY = drag.baseY + deltaY\n // Clamp the offset so the glyph stays fully inside the bounds element (can't be lost off the feed).\n if (drag.bounds !== null) {\n nextX = Math.min(Math.max(nextX, drag.bounds.left - drag.homeLeft), drag.bounds.right - drag.homeLeft - drag.width)\n nextY = Math.min(Math.max(nextY, drag.bounds.top - drag.homeTop), drag.bounds.bottom - drag.homeTop - drag.height)\n }\n setPositionValue({ x: nextX, y: nextY })\n },\n [setPositionValue],\n )\n\n const endDrag = useCallback(() => {\n dragRef.current = null\n setDragging(false)\n }, [])\n\n const handleTriggerClick = useCallback((event: ReactMouseEvent<HTMLButtonElement>) => {\n if (suppressClickRef.current) {\n suppressClickRef.current = false\n event.preventDefault()\n }\n }, [])\n\n return (\n <Popover\n defaultOpen={defaultExpanded}\n onOpenChange={onExpandedChange}\n open={expanded ?? undefined}\n >\n <PopoverTrigger asChild>\n <button\n aria-label={accessibleName}\n className={cn(\n 'inline-flex items-center justify-center rounded-full text-text-primary outline-none hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-50 motion-safe:transition-opacity motion-safe:duration-150',\n draggable && 'touch-none',\n draggable && (dragging ? 'cursor-grabbing' : 'cursor-grab'),\n triggerClassName,\n )}\n data-slot='directional-color-wheel-disclosure-trigger'\n data-testid={`${dataTestId}-trigger`}\n disabled={disabled}\n onClick={handleTriggerClick}\n onLostPointerCapture={endDrag}\n onPointerDown={handleTriggerPointerDown}\n onPointerMove={handleTriggerPointerMove}\n onPointerUp={endDrag}\n ref={ref}\n // A pure translate (no left/top) keeps the reposition on the compositor; `will-change`\n // promotes it for the duration of the drag.\n style={draggable ? { transform: `translate(${positionValue.x}px, ${positionValue.y}px)`, willChange: dragging ? 'transform' : undefined } : undefined}\n type='button'\n >\n {collapsedIcon ?? <DirectionalColorWheelGlyph />}\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n // Chrome-less positioning/animation layer; the visible panel is the Spectral Card below.\n className='p-0 flex items-center justify-center overflow-visible border-none bg-transparent shadow-none'\n collisionPadding={collisionPadding}\n data-slot='directional-color-wheel-disclosure-content'\n data-testid={`${dataTestId}-content`}\n // When dismissal is off, swallow Radix's outside-pointer/Esc dismiss so the panel stays open\n // while the user interacts with whatever sits under it (e.g. the feed); the trigger still toggles.\n onEscapeKeyDown={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onInteractOutside={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onOpenAutoFocus={(event) => {\n // Focus the panel, not the first sector wedge (which would paint an accent ring on a sector).\n event.preventDefault()\n panelRef.current?.focus()\n }}\n side={side}\n sideOffset={sideOffset}\n // While dragging, poll the anchor position every animation frame so the panel tracks the\n // glyph in real time; back to the cheaper 'optimized' strategy at rest.\n updatePositionStrategy={dragging ? 'always' : 'optimized'}\n width='fit-content'\n >\n <Card\n className={cn('outline-none', className)}\n data-slot='directional-color-wheel-disclosure-panel'\n ref={panelRef}\n // The default width is applied first so a caller-supplied `style.width` (or any other\n // visual override the `card-effects` base class locks down) takes precedence.\n style={{ width: panelWidth, ...style }}\n tabIndex={-1}\n >\n {children}\n </Card>\n </PopoverContent>\n </Popover>\n )\n}\n\nDirectionalColorWheelDisclosure.displayName = 'DirectionalColorWheelDisclosure'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DirectionalColorWheelDisclosure.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"sourcesContent":["import { Card } from '@components/DataCard/Card'\nimport { Popover, PopoverContent, PopoverTrigger } from '@components/Popover/Popover'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type Ref, type RefObject } from 'react'\nimport { DirectionalColorWheelGlyph } from './DirectionalColorWheelGlyph'\n\ntype DisclosureAlign = 'start' | 'center' | 'end'\ntype DisclosureSide = 'top' | 'right' | 'bottom' | 'left'\n\n/** Pixel offset of the draggable disclosure from where it is mounted (default `{ x: 0, y: 0 }`). */\nexport interface DirectionalColorWheelDisclosurePosition {\n /** Horizontal offset in px (positive moves right). */\n x: number\n /** Vertical offset in px (positive moves down). */\n y: number\n}\n\n// Drag distance (px) below which a press on the glyph is a tap (toggle), not a reposition drag.\nconst DRAG_SLOP_PX = 4\nconst ORIGIN: DirectionalColorWheelDisclosurePosition = { x: 0, y: 0 }\n\nexport interface DirectionalColorWheelDisclosureProps {\n /** Accessible name for the collapsed trigger button (`aria-expanded` conveys the open state). */\n accessibleName?: string\n /** How the panel aligns to the trigger. Defaults to `start` for a corner-anchored feed widget. */\n align?: DisclosureAlign\n /** The expandable content: the bare wheel, or the wheel framed by the GRAMS controls. */\n children: ReactNode\n /** Layout-only class extension for the expanded panel. */\n className?: string\n /**\n * Override the collapsed affordance. Defaults to a {@link DirectionalColorWheelGlyph} mini\n * compass; pass a palette-matched glyph (or any node) to mirror the expanded wheel's colours.\n */\n collapsedIcon?: ReactNode\n /** Min distance (px) from the viewport edge before the panel flips/shifts. */\n collisionPadding?: number\n dataTestId?: string\n /**\n * Element the drag is clamped to so the glyph can't be parked off the feed. Pass a ref to the\n * container the disclosure is mounted over (e.g. the plot surface). `undefined`/`null` = no clamp\n * (free drag). Only used when `draggable`.\n */\n dragBoundsRef?: RefObject<HTMLElement | null> | null\n /** Uncontrolled initial expanded state. */\n defaultExpanded?: boolean\n /** Uncontrolled initial position offset (px) from the mount point. Defaults to the corner it is mounted at. */\n defaultPosition?: DirectionalColorWheelDisclosurePosition\n disabled?: boolean\n /**\n * When `true`, the operator can drag the collapsed glyph to reposition the whole widget (the\n * expanded panel reflows with it in real time). A tap still toggles; only a drag past a small\n * slop repositions. Defaults to `false` (fixed at its mount point).\n */\n draggable?: boolean\n /**\n * When `false`, the panel stays open during outside interaction and Esc — it toggles only via the\n * trigger (or a controlled `expanded`). Use this for a feed-mounted wheel so clicking/scrubbing the\n * feed underneath doesn't dismiss the legend. Defaults to `true` (standard popover dismissal).\n */\n dismissOnInteractOutside?: boolean\n /**\n * Controlled expanded state. `undefined`/`null` leaves the disclosure uncontrolled, so Horizon\n * can gate the feature with a single nullable value (PAT-028).\n */\n expanded?: boolean | null\n /** Called whenever the disclosure expands or collapses (click, Esc, or outside dismiss). */\n onExpandedChange?: (expanded: boolean) => void\n /** Called as the glyph is dragged with the next position offset (px). Pair with `position` to persist it. */\n onPositionChange?: (position: DirectionalColorWheelDisclosurePosition) => void\n /**\n * Controlled position offset (px) from the mount point. `undefined`/`null` leaves it uncontrolled,\n * so Horizon can gate the feature with a single nullable value (PAT-028). Requires `draggable` to move.\n */\n position?: DirectionalColorWheelDisclosurePosition | null\n ref?: Ref<HTMLButtonElement>\n /** Which side of the trigger the panel grows toward. Defaults to `top` (icon at a bottom corner). */\n side?: DisclosureSide\n /** Gap (px) between the trigger and the panel. */\n sideOffset?: number\n /**\n * Inline styles merged onto the expanded panel (the {@link Card}). Use this for visual overrides\n * — background, border, shadow — that the `card-effects` base class doesn't expose to `className`\n * utilities. The default panel width is applied first, so a `width` set here wins over the `width` prop.\n */\n style?: CSSProperties\n /** Layout-only class extension for the collapsed trigger button. */\n triggerClassName?: string\n /** Panel width: a pixel number or a CSS width string. Defaults to `'fit-content'` (hugs content). */\n width?: number | string\n}\n\n/**\n * Collapse/expand affordance for the {@link DirectionalColorWheel}, built on the Spectral\n * {@link Popover} primitive so it inherits corner anchoring, click-outside/Esc dismissal, focus\n * management, and `aria-expanded`/`aria-controls` for free. Collapsed it is a small compass glyph\n * (a real `<button>`); expanded it reveals whatever `children` it wraps — the bare wheel, or the\n * wheel plus the GRAMS Color Angle / Threshold / Sectors controls — inside a Spectral {@link Card}\n * panel, growing from the icon with a `transform-origin`-anchored zoom+fade that is automatically\n * skipped under `prefers-reduced-motion`. `className` extends the Card panel's classes and `style`\n * applies inline overrides to it (background, border, shadow, …).\n *\n * Pass `draggable` to let the operator drag the glyph to reposition the whole widget on a feed; the\n * expanded panel reflows with it in real time, and `dragBoundsRef` clamps it inside its container so\n * it can't be parked off the feed. Position is controlled+uncontrolled via\n * `position` / `defaultPosition` / `onPositionChange` (a nullable px offset from the mount point,\n * PAT-028) so a consumer can persist where the operator parked it.\n */\nexport const DirectionalColorWheelDisclosure = ({\n accessibleName = 'Directional color legend',\n align = 'start',\n children,\n className,\n collapsedIcon,\n collisionPadding = 8,\n dataTestId = 'spectral-directional-color-wheel-disclosure',\n defaultExpanded,\n defaultPosition,\n disabled = false,\n dismissOnInteractOutside = true,\n draggable = false,\n dragBoundsRef,\n expanded,\n onExpandedChange,\n onPositionChange,\n position,\n ref,\n side = 'top',\n sideOffset = 8,\n style,\n triggerClassName,\n width = 'fit-content',\n}: DirectionalColorWheelDisclosureProps) => {\n const panelRef = useRef<HTMLDivElement>(null)\n // `width` sizes the Card panel; the popover hugs it (fit-content) so there is no empty gap.\n const panelWidth = typeof width === 'number' ? `${width}px` : width\n\n const [positionValue, setPositionValue] = useUncontrolledState<DirectionalColorWheelDisclosurePosition>({ defaultValue: defaultPosition ?? ORIGIN, onChange: onPositionChange, value: position ?? undefined })\n const [dragging, setDragging] = useState(false)\n // Live reposition drag (a ref so rapid moves don't hinge on re-render timing). `bounds` is the\n // clamp rect (the dragBoundsRef element); `homeLeft`/`homeTop` are the trigger's viewport position\n // at translate 0, so the allowed offset range keeps the glyph fully inside `bounds`.\n const dragRef = useRef<{ baseX: number; baseY: number; bounds: DOMRect | null; height: number; homeLeft: number; homeTop: number; moved: boolean; pointerId: number; startX: number; startY: number; width: number } | null>(null)\n // Set when a drag passes the slop so the trailing click is swallowed (preventDefault → Radix's\n // composed trigger toggle bails) instead of also expanding/collapsing the panel.\n const suppressClickRef = useRef(false)\n\n const handleTriggerPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n suppressClickRef.current = false\n if (!draggable || disabled) return\n try {\n event.currentTarget.setPointerCapture(event.pointerId)\n } catch {\n // setPointerCapture can throw without an active pointer (e.g. synthetic test events).\n }\n const triggerRect = event.currentTarget.getBoundingClientRect()\n dragRef.current = {\n baseX: positionValue.x,\n baseY: positionValue.y,\n bounds: dragBoundsRef?.current?.getBoundingClientRect() ?? null,\n height: triggerRect.height,\n // Subtract the active translate to recover the trigger's home (translate-0) position.\n homeLeft: triggerRect.left - positionValue.x,\n homeTop: triggerRect.top - positionValue.y,\n moved: false,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n width: triggerRect.width,\n }\n },\n [disabled, draggable, dragBoundsRef, positionValue.x, positionValue.y],\n )\n\n const handleTriggerPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n const drag = dragRef.current\n if (drag === null || drag.pointerId !== event.pointerId) return\n const deltaX = event.clientX - drag.startX\n const deltaY = event.clientY - drag.startY\n if (!drag.moved) {\n if (Math.hypot(deltaX, deltaY) <= DRAG_SLOP_PX) return\n drag.moved = true\n suppressClickRef.current = true\n // Promote the trigger to its own compositor layer and poll the popover position every frame\n // (`updatePositionStrategy='always'`) so the panel reflows with the glyph rather than snapping.\n setDragging(true)\n }\n let nextX = drag.baseX + deltaX\n let nextY = drag.baseY + deltaY\n // Clamp the offset so the glyph stays fully inside the bounds element (can't be lost off the feed).\n if (drag.bounds !== null) {\n nextX = Math.min(Math.max(nextX, drag.bounds.left - drag.homeLeft), drag.bounds.right - drag.homeLeft - drag.width)\n nextY = Math.min(Math.max(nextY, drag.bounds.top - drag.homeTop), drag.bounds.bottom - drag.homeTop - drag.height)\n }\n setPositionValue({ x: nextX, y: nextY })\n },\n [setPositionValue],\n )\n\n const endDrag = useCallback(() => {\n dragRef.current = null\n setDragging(false)\n }, [])\n\n const handleTriggerClick = useCallback((event: ReactMouseEvent<HTMLButtonElement>) => {\n if (suppressClickRef.current) {\n suppressClickRef.current = false\n event.preventDefault()\n }\n }, [])\n\n return (\n <Popover\n defaultOpen={defaultExpanded}\n onOpenChange={onExpandedChange}\n open={expanded ?? undefined}\n >\n <PopoverTrigger asChild>\n <button\n aria-label={accessibleName}\n className={cn(\n 'inline-flex items-center justify-center rounded-full text-text-primary outline-none hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-50 motion-safe:transition-opacity motion-safe:duration-150',\n draggable && 'touch-none',\n draggable && (dragging ? 'cursor-grabbing' : 'cursor-grab'),\n triggerClassName,\n )}\n data-slot='directional-color-wheel-disclosure-trigger'\n data-testid={`${dataTestId}-trigger`}\n disabled={disabled}\n onClick={handleTriggerClick}\n onLostPointerCapture={endDrag}\n onPointerDown={handleTriggerPointerDown}\n onPointerMove={handleTriggerPointerMove}\n onPointerUp={endDrag}\n ref={ref}\n // A pure translate (no left/top) keeps the reposition on the compositor; `will-change`\n // promotes it for the duration of the drag.\n style={draggable ? { transform: `translate(${positionValue.x}px, ${positionValue.y}px)`, willChange: dragging ? 'transform' : undefined } : undefined}\n type='button'\n >\n {collapsedIcon ?? <DirectionalColorWheelGlyph />}\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n // Chrome-less positioning/animation layer; the visible panel is the Spectral Card below.\n className='p-0 flex items-center justify-center overflow-visible border-none bg-transparent shadow-none'\n collisionPadding={collisionPadding}\n data-slot='directional-color-wheel-disclosure-content'\n data-testid={`${dataTestId}-content`}\n // When dismissal is off, swallow Radix's outside-pointer/Esc dismiss so the panel stays open\n // while the user interacts with whatever sits under it (e.g. the feed); the trigger still toggles.\n onEscapeKeyDown={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onInteractOutside={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onOpenAutoFocus={(event) => {\n // Focus the panel, not the first sector wedge (which would paint an accent ring on a sector).\n event.preventDefault()\n panelRef.current?.focus()\n }}\n side={side}\n sideOffset={sideOffset}\n // While dragging, poll the anchor position every animation frame so the panel tracks the\n // glyph in real time; back to the cheaper 'optimized' strategy at rest.\n updatePositionStrategy={dragging ? 'always' : 'optimized'}\n width='fit-content'\n >\n <Card\n className={cn('outline-none', className)}\n data-slot='directional-color-wheel-disclosure-panel'\n ref={panelRef}\n // The default width is applied first so a caller-supplied `style.width` (or any other\n // visual override the `card-effects` base class locks down) takes precedence.\n style={{ width: panelWidth, ...style }}\n tabIndex={-1}\n >\n {children}\n </Card>\n </PopoverContent>\n </Popover>\n )\n}\n\nDirectionalColorWheelDisclosure.displayName = 'DirectionalColorWheelDisclosure'\n"],"mappings":"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"}
|
|
@@ -57,7 +57,6 @@ const DirectionalColorWheelGlyph = ({ className, colorAngle = 0, colorForBearing
|
|
|
57
57
|
"aria-hidden": true,
|
|
58
58
|
className: cn("shrink-0", className),
|
|
59
59
|
"data-slot": "directional-color-wheel-glyph",
|
|
60
|
-
"data-testid": dataTestId,
|
|
61
60
|
height: size,
|
|
62
61
|
ref,
|
|
63
62
|
viewBox: "0 0 100 100",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DirectionalColorWheelGlyph.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { useId, useMemo, type Ref } from 'react'\nimport { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, type DirectionalColorStop, type DirectionalSectorCount } from './directionalColorWheelMath'\n\nexport interface DirectionalColorWheelGlyphProps {\n /** Layout-only class extension (margins/padding). */\n className?: string\n /** \"Color Angle\": degrees the colours are rotated clockwise, mirroring the full wheel. */\n colorAngle?: number\n /** Same advanced parity resolver as the wheel; when set it wins over `colorStops`. */\n colorForBearing?: ((bearingDegrees: number) => string) | null\n /** Palette stops; defaults to the directional DIFAR palette (matches the full wheel). */\n colorStops?: DirectionalColorStop[] | null\n dataTestId?: string\n /**\n * Compass bearing (degrees) the miniature needle points at. Defaults to `0` (North) so the\n * glyph always reads as a compass; `null` still renders a North-pointing needle.\n */\n needleBearing?: number | null\n ref?: Ref<SVGSVGElement>\n /** Number of coloured wedges drawn in the ring. */\n sectorCount?: DirectionalSectorCount\n /** Rendered diameter in pixels. */\n size?: number\n}\n\n// 0..100 viewBox mirroring the wheel: coloured ring, cardinal ticks, gradient hub, two-facet needle.\nconst CENTER = 50\nconst OUTER_RADIUS = 46\nconst INNER_RADIUS = 27\nconst TICK_INNER_RADIUS = 47.5\nconst TICK_OUTER_RADIUS = 49.5\nconst NEEDLE_TIP_RADIUS = 43\nconst NEEDLE_BASE_RADIUS = INNER_RADIUS\nconst NEEDLE_HALF_WIDTH_DEGREES = 8\nconst PIVOT_RADIUS = 5\n// Short rim ticks at N/E/S/W orient the glyph as a compass without crowding it with labels.\nconst CARDINAL_TICKS = [0, 90, 180, 270].map((bearing) => ({\n bearing,\n inner: polarToCartesian(CENTER, CENTER, TICK_INNER_RADIUS, bearing),\n outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing),\n}))\n\n/**\n * Miniature, decorative render of the {@link DirectionalColorWheel} colour ring with a compass\n * needle — the collapsed affordance for {@link DirectionalColorWheelDisclosure}. It reuses the\n * wheel's geometry/colour helpers (and palette props) so it stays a true legend in miniature. It\n * is `aria-hidden`; the surrounding trigger button carries the accessible name.\n */\nexport const DirectionalColorWheelGlyph = ({ className, colorAngle = 0, colorForBearing, colorStops, dataTestId = 'spectral-directional-color-wheel-glyph', needleBearing, ref, sectorCount = 12, size = 28 }: DirectionalColorWheelGlyphProps) => {\n const hubGradientId = `directional-glyph-hub-${useId().replaceAll(':', '')}`\n\n const sectors = useMemo(\n () =>\n Array.from({ length: sectorCount }, (_, index) => {\n const range = sectorBearingRange(index, sectorCount)\n return {\n color: resolveSectorColor({ colorForBearing, colorStops }, (range.startBearing + range.endBearing) / 2, colorAngle),\n key: `${range.startBearing}-${range.endBearing}`,\n path: annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing),\n }\n }),\n [colorAngle, colorForBearing, colorStops, sectorCount],\n )\n\n const bearing = normalizeBearing(typeof needleBearing === 'number' && Number.isFinite(needleBearing) ? needleBearing : 0)\n const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, bearing)\n const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing)\n const needleLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing - NEEDLE_HALF_WIDTH_DEGREES)\n const needleRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing + NEEDLE_HALF_WIDTH_DEGREES)\n\n return (\n <svg\n aria-hidden\n className={cn('shrink-0', className)}\n data-slot='directional-color-wheel-glyph'\n data-testid={dataTestId}\n height={size}\n ref={ref}\n viewBox='0 0 100 100'\n width={size}\n xmlns='http://www.w3.org/2000/svg'\n >\n <defs>\n <radialGradient\n cx='38%'\n cy='30%'\n id={hubGradientId}\n r='75%'\n >\n <stop\n offset='0%'\n stopColor='var(--color-level-four)'\n />\n <stop\n offset='82%'\n stopColor='var(--color-level-one)'\n />\n </radialGradient>\n </defs>\n <g data-slot='directional-color-wheel-glyph-ring'>\n {sectors.map((sector) => (\n <path\n d={sector.path}\n fill={sector.color}\n key={sector.key}\n />\n ))}\n </g>\n <circle\n className='fill-none stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={OUTER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-ticks'>\n {CARDINAL_TICKS.map((tick) => (\n <line\n className='stroke-text-secondary'\n key={tick.bearing}\n strokeLinecap='round'\n strokeWidth={1.5}\n x1={tick.inner.x}\n x2={tick.outer.x}\n y1={tick.inner.y}\n y2={tick.outer.y}\n />\n ))}\n </g>\n <circle\n className='stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n fill={`url(#${hubGradientId})`}\n r={INNER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-needle'>\n <polygon\n className='fill-danger-500'\n points={`${needleTip.x},${needleTip.y} ${needleLeft.x},${needleLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`}\n />\n <polygon\n className='fill-danger-400'\n points={`${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleRight.x},${needleRight.y}`}\n />\n </g>\n <circle\n className='fill-level-four stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={PIVOT_RADIUS}\n strokeWidth={1.5}\n />\n </svg>\n )\n}\n\nDirectionalColorWheelGlyph.displayName = 'DirectionalColorWheelGlyph'\n"],"mappings":";;;;;;;AA2BA,MAAM,SAAS;AACf,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,4BAA4B;AAClC,MAAM,eAAe;AAErB,MAAM,iBAAiB;CAAC;CAAG;CAAI;CAAK;CAAI,CAAC,KAAK,aAAa;CACzD;CACA,OAAO,iBAAiB,QAAQ,QAAQ,mBAAmB,QAAQ;CACnE,OAAO,iBAAiB,QAAQ,QAAQ,mBAAmB,QAAQ;CACpE,
|
|
1
|
+
{"version":3,"file":"DirectionalColorWheelGlyph.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { useId, useMemo, type Ref } from 'react'\nimport { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, type DirectionalColorStop, type DirectionalSectorCount } from './directionalColorWheelMath'\n\nexport interface DirectionalColorWheelGlyphProps {\n /** Layout-only class extension (margins/padding). */\n className?: string\n /** \"Color Angle\": degrees the colours are rotated clockwise, mirroring the full wheel. */\n colorAngle?: number\n /** Same advanced parity resolver as the wheel; when set it wins over `colorStops`. */\n colorForBearing?: ((bearingDegrees: number) => string) | null\n /** Palette stops; defaults to the directional DIFAR palette (matches the full wheel). */\n colorStops?: DirectionalColorStop[] | null\n dataTestId?: string\n /**\n * Compass bearing (degrees) the miniature needle points at. Defaults to `0` (North) so the\n * glyph always reads as a compass; `null` still renders a North-pointing needle.\n */\n needleBearing?: number | null\n ref?: Ref<SVGSVGElement>\n /** Number of coloured wedges drawn in the ring. */\n sectorCount?: DirectionalSectorCount\n /** Rendered diameter in pixels. */\n size?: number\n}\n\n// 0..100 viewBox mirroring the wheel: coloured ring, cardinal ticks, gradient hub, two-facet needle.\nconst CENTER = 50\nconst OUTER_RADIUS = 46\nconst INNER_RADIUS = 27\nconst TICK_INNER_RADIUS = 47.5\nconst TICK_OUTER_RADIUS = 49.5\nconst NEEDLE_TIP_RADIUS = 43\nconst NEEDLE_BASE_RADIUS = INNER_RADIUS\nconst NEEDLE_HALF_WIDTH_DEGREES = 8\nconst PIVOT_RADIUS = 5\n// Short rim ticks at N/E/S/W orient the glyph as a compass without crowding it with labels.\nconst CARDINAL_TICKS = [0, 90, 180, 270].map((bearing) => ({\n bearing,\n inner: polarToCartesian(CENTER, CENTER, TICK_INNER_RADIUS, bearing),\n outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing),\n}))\n\n/**\n * Miniature, decorative render of the {@link DirectionalColorWheel} colour ring with a compass\n * needle — the collapsed affordance for {@link DirectionalColorWheelDisclosure}. It reuses the\n * wheel's geometry/colour helpers (and palette props) so it stays a true legend in miniature. It\n * is `aria-hidden`; the surrounding trigger button carries the accessible name.\n */\nexport const DirectionalColorWheelGlyph = ({ className, colorAngle = 0, colorForBearing, colorStops, dataTestId = 'spectral-directional-color-wheel-glyph', needleBearing, ref, sectorCount = 12, size = 28 }: DirectionalColorWheelGlyphProps) => {\n const hubGradientId = `directional-glyph-hub-${useId().replaceAll(':', '')}`\n\n const sectors = useMemo(\n () =>\n Array.from({ length: sectorCount }, (_, index) => {\n const range = sectorBearingRange(index, sectorCount)\n return {\n color: resolveSectorColor({ colorForBearing, colorStops }, (range.startBearing + range.endBearing) / 2, colorAngle),\n key: `${range.startBearing}-${range.endBearing}`,\n path: annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing),\n }\n }),\n [colorAngle, colorForBearing, colorStops, sectorCount],\n )\n\n const bearing = normalizeBearing(typeof needleBearing === 'number' && Number.isFinite(needleBearing) ? needleBearing : 0)\n const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, bearing)\n const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing)\n const needleLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing - NEEDLE_HALF_WIDTH_DEGREES)\n const needleRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing + NEEDLE_HALF_WIDTH_DEGREES)\n\n return (\n <svg\n aria-hidden\n className={cn('shrink-0', className)}\n data-slot='directional-color-wheel-glyph'\n data-testid={dataTestId}\n height={size}\n ref={ref}\n viewBox='0 0 100 100'\n width={size}\n xmlns='http://www.w3.org/2000/svg'\n >\n <defs>\n <radialGradient\n cx='38%'\n cy='30%'\n id={hubGradientId}\n r='75%'\n >\n <stop\n offset='0%'\n stopColor='var(--color-level-four)'\n />\n <stop\n offset='82%'\n stopColor='var(--color-level-one)'\n />\n </radialGradient>\n </defs>\n <g data-slot='directional-color-wheel-glyph-ring'>\n {sectors.map((sector) => (\n <path\n d={sector.path}\n fill={sector.color}\n key={sector.key}\n />\n ))}\n </g>\n <circle\n className='fill-none stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={OUTER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-ticks'>\n {CARDINAL_TICKS.map((tick) => (\n <line\n className='stroke-text-secondary'\n key={tick.bearing}\n strokeLinecap='round'\n strokeWidth={1.5}\n x1={tick.inner.x}\n x2={tick.outer.x}\n y1={tick.inner.y}\n y2={tick.outer.y}\n />\n ))}\n </g>\n <circle\n className='stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n fill={`url(#${hubGradientId})`}\n r={INNER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-needle'>\n <polygon\n className='fill-danger-500'\n points={`${needleTip.x},${needleTip.y} ${needleLeft.x},${needleLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`}\n />\n <polygon\n className='fill-danger-400'\n points={`${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleRight.x},${needleRight.y}`}\n />\n </g>\n <circle\n className='fill-level-four stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={PIVOT_RADIUS}\n strokeWidth={1.5}\n />\n </svg>\n )\n}\n\nDirectionalColorWheelGlyph.displayName = 'DirectionalColorWheelGlyph'\n"],"mappings":"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"}
|