@fanvue/ui 2.18.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Autocomplete/Autocomplete.cjs +6 -2
- package/dist/cjs/components/Autocomplete/Autocomplete.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs +46 -3
- package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/constants.cjs +32 -0
- package/dist/cjs/components/Autocomplete/constants.cjs.map +1 -1
- package/dist/cjs/components/Autocomplete/useAutocomplete.cjs +15 -9
- package/dist/cjs/components/Autocomplete/useAutocomplete.cjs.map +1 -1
- package/dist/cjs/components/Checkbox/Checkbox.cjs +19 -6
- package/dist/cjs/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +189 -11
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/cjs/components/Table/Table.cjs +66 -46
- package/dist/cjs/components/Table/Table.cjs.map +1 -1
- package/dist/cjs/components/Table/TablePagination.cjs +7 -7
- package/dist/cjs/components/Table/TablePagination.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/components/Autocomplete/Autocomplete.mjs +6 -2
- package/dist/components/Autocomplete/Autocomplete.mjs.map +1 -1
- package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs +46 -3
- package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs.map +1 -1
- package/dist/components/Autocomplete/constants.mjs +33 -1
- package/dist/components/Autocomplete/constants.mjs.map +1 -1
- package/dist/components/Autocomplete/useAutocomplete.mjs +16 -10
- package/dist/components/Autocomplete/useAutocomplete.mjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.mjs +19 -6
- package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.mjs +189 -11
- package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
- package/dist/components/Table/Table.mjs +66 -46
- package/dist/components/Table/Table.mjs.map +1 -1
- package/dist/components/Table/TablePagination.mjs +7 -7
- package/dist/components/Table/TablePagination.mjs.map +1 -1
- package/dist/index.d.ts +299 -31
- package/dist/index.mjs +6 -2
- package/dist/styles/base.css +2 -0
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAutocomplete.mjs","sources":["../../../src/components/Autocomplete/useAutocomplete.ts"],"sourcesContent":["import * as React from \"react\";\nimport type { AutocompleteOption, AutocompleteProps } from \"./Autocomplete\";\nimport { CREATE_PREFIX, defaultFilter, getLabel } from \"./constants\";\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Combobox hook managing interconnected controlled/uncontrolled state\nexport function useAutocomplete(props: AutocompleteProps) {\n const {\n id,\n options,\n disabled = false,\n filterFn,\n creatable = false,\n creatableLabel,\n onCreate,\n inputValue: inputValueProp,\n onInputChange,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n } = props;\n\n const generatedId = React.useId();\n const inputId = id ?? generatedId;\n const helperTextId = `${inputId}-helper`;\n const listboxId = `${inputId}-listbox`;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n\n const isMulti = props.multiple === true;\n\n // --- single-select state ---\n const [internalValue, setInternalValue] = React.useState<string | null>(\n (!isMulti && props.defaultValue) || null,\n );\n const selectedValue: string | null = !isMulti\n ? props.value !== undefined\n ? props.value\n : internalValue\n : null;\n\n // --- multi-select state ---\n const [internalMultiValue, setInternalMultiValue] = React.useState<string[]>(\n isMulti && props.defaultValue ? props.defaultValue : [],\n );\n const selectedMultiValues: string[] = isMulti\n ? props.value !== undefined\n ? props.value\n : internalMultiValue\n : [];\n\n // --- input text ---\n const [internalInputValue, setInternalInputValue] = React.useState(\"\");\n const searchText = inputValueProp !== undefined ? inputValueProp : internalInputValue;\n\n // --- open state ---\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const isOpen = openProp !== undefined ? openProp : internalOpen;\n\n const setOpen = React.useCallback(\n (next: boolean) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [openProp, onOpenChange],\n );\n\n // --- active index ---\n const [activeIndex, setActiveIndex] = React.useState(-1);\n\n // --- filtering ---\n const filter = filterFn ?? defaultFilter;\n\n const filteredOptions = React.useMemo(() => {\n if (!searchText) return options;\n return options.filter((o) => filter(o, searchText));\n }, [options, searchText, filter]);\n\n const showCreate =\n creatable &&\n searchText.length > 0 &&\n !options.some((o) => (o.label ?? o.value).toLowerCase() === searchText.toLowerCase());\n\n const visibleOptions = React.useMemo(() => {\n if (!showCreate) return filteredOptions;\n const createOption: AutocompleteOption = {\n value: `${CREATE_PREFIX}${searchText}`,\n label: creatableLabel ? creatableLabel(searchText) : searchText,\n };\n return [...filteredOptions, createOption];\n }, [filteredOptions, showCreate, searchText, creatableLabel]);\n\n const prevOptionsLengthRef = React.useRef(visibleOptions.length);\n const prevSearchTextRef = React.useRef(searchText);\n\n React.useEffect(() => {\n if (\n searchText !== prevSearchTextRef.current ||\n visibleOptions.length !== prevOptionsLengthRef.current\n ) {\n setActiveIndex(-1);\n prevSearchTextRef.current = searchText;\n prevOptionsLengthRef.current = visibleOptions.length;\n }\n }, [searchText, visibleOptions.length]);\n\n // --- scroll active option into view ---\n React.useEffect(() => {\n if (activeIndex < 0 || !listRef.current) return;\n const el = listRef.current.querySelector(`[data-option-index=\"${activeIndex}\"]`);\n if (typeof el?.scrollIntoView === \"function\") {\n el.scrollIntoView({ block: \"nearest\" });\n }\n }, [activeIndex]);\n\n // --- helpers ---\n function clearInputText() {\n if (inputValueProp === undefined) setInternalInputValue(\"\");\n onInputChange?.(\"\");\n }\n\n function selectSingle(val: string | null) {\n if (!isMulti && props.value === undefined) setInternalValue(val);\n if (!isMulti) (props as { onChange?: (v: string | null) => void }).onChange?.(val);\n }\n\n function toggleMulti(val: string) {\n const next = selectedMultiValues.includes(val)\n ? selectedMultiValues.filter((v) => v !== val)\n : [...selectedMultiValues, val];\n if (isMulti && props.value === undefined) setInternalMultiValue(next);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.(next);\n }\n\n function handleSelect(option: AutocompleteOption) {\n if (option.value.startsWith(CREATE_PREFIX)) {\n const raw = option.value.slice(CREATE_PREFIX.length);\n onCreate?.(raw);\n if (isMulti) {\n toggleMulti(raw);\n } else {\n selectSingle(raw);\n setOpen(false);\n }\n clearInputText();\n return;\n }\n\n if (isMulti) {\n toggleMulti(option.value);\n clearInputText();\n } else {\n selectSingle(option.value);\n clearInputText();\n setOpen(false);\n }\n }\n\n function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {\n const val = e.target.value;\n if (inputValueProp === undefined) setInternalInputValue(val);\n onInputChange?.(val);\n if (!isOpen) setOpen(true);\n }\n\n function findNextEnabled(start: number, direction: 1 | -1): number | null {\n let idx = start;\n while (idx >= 0 && idx < visibleOptions.length && visibleOptions[idx]?.disabled) {\n idx += direction;\n }\n return idx >= 0 && idx < visibleOptions.length ? idx : null;\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Flat switch is clearer than splitting into separate handler functions\n function handleKeyDown(e: React.KeyboardEvent) {\n if (disabled) return;\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev + 1, 1) ?? prev);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev - 1, -1) ?? prev);\n break;\n }\n case \"Enter\": {\n if (isOpen && activeIndex >= 0 && activeIndex < visibleOptions.length) {\n e.preventDefault();\n const opt = visibleOptions[activeIndex];\n if (opt && !opt.disabled) handleSelect(opt);\n }\n break;\n }\n case \"Escape\": {\n e.preventDefault();\n if (isOpen) setOpen(false);\n break;\n }\n case \"Backspace\": {\n if (isMulti && searchText === \"\" && selectedMultiValues.length > 0) {\n const lastVal = selectedMultiValues[selectedMultiValues.length - 1];\n if (lastVal !== undefined) toggleMulti(lastVal);\n }\n break;\n }\n case \"Home\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(0, 1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n case \"End\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(visibleOptions.length - 1, -1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n }\n }\n\n function handleClear(e: React.MouseEvent) {\n e.stopPropagation();\n if (isMulti) {\n if (props.value === undefined) setInternalMultiValue([]);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.([]);\n } else {\n selectSingle(null);\n }\n clearInputText();\n inputRef.current?.focus();\n }\n\n function handleBlur(e: React.FocusEvent<HTMLInputElement>) {\n const container = e.currentTarget.closest(\"[data-autocomplete-root]\");\n if (container && e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {\n return;\n }\n if (\n e.relatedTarget instanceof Node &&\n (e.relatedTarget as Element).closest?.(\"[data-radix-popper-content-wrapper]\")\n ) {\n return;\n }\n if (isOpen) setOpen(false);\n }\n\n function handleFocus() {\n if (!disabled && !isOpen) setOpen(true);\n }\n\n function handleContainerClick() {\n if (!disabled) {\n inputRef.current?.focus();\n if (!isOpen) setOpen(true);\n }\n }\n\n function handleOpenChange(next: boolean) {\n setOpen(next);\n if (!next) {\n clearInputText();\n setActiveIndex(-1);\n }\n }\n\n const selectedOption = React.useMemo(\n () => options.find((o) => o.value === selectedValue),\n [options, selectedValue],\n );\n\n const selectedMultiOptions = React.useMemo(\n () =>\n selectedMultiValues\n .map((v) => options.find((o) => o.value === v))\n .filter(Boolean) as AutocompleteOption[],\n [options, selectedMultiValues],\n );\n\n const hasClearableValue = isMulti ? selectedMultiValues.length > 0 : selectedValue != null;\n\n const displayInputValue = React.useMemo(() => {\n if (searchText) return searchText;\n if (isMulti || isOpen) return \"\";\n return selectedOption ? getLabel(selectedOption) : \"\";\n }, [searchText, isMulti, isOpen, selectedOption]);\n\n const activeDescendantId = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined;\n\n return {\n inputId,\n helperTextId,\n listboxId,\n inputRef,\n listRef,\n isMulti,\n isOpen,\n selectedValue,\n selectedMultiValues,\n selectedMultiOptions,\n searchText,\n visibleOptions,\n activeIndex,\n activeDescendantId,\n hasClearableValue,\n displayInputValue,\n setActiveIndex,\n handleSelect,\n handleInputChange,\n handleKeyDown,\n handleClear,\n handleBlur,\n handleFocus,\n handleContainerClick,\n handleOpenChange,\n toggleMulti,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAutocomplete.mjs","sources":["../../../src/components/Autocomplete/useAutocomplete.ts"],"sourcesContent":["import * as React from \"react\";\nimport type { AutocompleteOption, AutocompleteProps } from \"./Autocomplete\";\nimport {\n type AutocompleteVisibleSections,\n CREATE_PREFIX,\n defaultFilter,\n flattenVisibleSections,\n getLabel,\n getVisibleSections,\n} from \"./constants\";\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Combobox hook managing interconnected controlled/uncontrolled state\nexport function useAutocomplete(props: AutocompleteProps) {\n const {\n id,\n options,\n groups,\n disabled = false,\n filterFn,\n creatable = false,\n creatableLabel,\n onCreate,\n inputValue: inputValueProp,\n onInputChange,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n } = props;\n\n const generatedId = React.useId();\n const inputId = id ?? generatedId;\n const helperTextId = `${inputId}-helper`;\n const listboxId = `${inputId}-listbox`;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n\n const isMulti = props.multiple === true;\n\n // --- single-select state ---\n const [internalValue, setInternalValue] = React.useState<string | null>(\n (!isMulti && props.defaultValue) || null,\n );\n const selectedValue: string | null = !isMulti\n ? props.value !== undefined\n ? props.value\n : internalValue\n : null;\n\n // --- multi-select state ---\n const [internalMultiValue, setInternalMultiValue] = React.useState<string[]>(\n isMulti && props.defaultValue ? props.defaultValue : [],\n );\n const selectedMultiValues: string[] = isMulti\n ? props.value !== undefined\n ? props.value\n : internalMultiValue\n : [];\n\n // --- input text ---\n const [internalInputValue, setInternalInputValue] = React.useState(\"\");\n const searchText = inputValueProp !== undefined ? inputValueProp : internalInputValue;\n\n // --- open state ---\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const isOpen = openProp !== undefined ? openProp : internalOpen;\n\n const setOpen = React.useCallback(\n (next: boolean) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [openProp, onOpenChange],\n );\n\n // --- active index ---\n const [activeIndex, setActiveIndex] = React.useState(-1);\n\n // --- filtering ---\n const filter = filterFn ?? defaultFilter;\n\n const showCreate =\n creatable &&\n searchText.length > 0 &&\n !options.some((o) => (o.label ?? o.value).toLowerCase() === searchText.toLowerCase());\n\n const createOption: AutocompleteOption | null = React.useMemo(() => {\n if (!showCreate) return null;\n return {\n value: `${CREATE_PREFIX}${searchText}`,\n label: creatableLabel ? creatableLabel(searchText) : searchText,\n };\n }, [showCreate, searchText, creatableLabel]);\n\n const visibleSections: AutocompleteVisibleSections = React.useMemo(\n () => getVisibleSections(options, groups, filter, searchText),\n [options, groups, filter, searchText],\n );\n\n const visibleOptions = React.useMemo(() => {\n const flat = flattenVisibleSections(visibleSections);\n return createOption ? [...flat, createOption] : flat;\n }, [visibleSections, createOption]);\n\n const prevOptionsLengthRef = React.useRef(visibleOptions.length);\n const prevSearchTextRef = React.useRef(searchText);\n\n React.useEffect(() => {\n if (\n searchText !== prevSearchTextRef.current ||\n visibleOptions.length !== prevOptionsLengthRef.current\n ) {\n setActiveIndex(-1);\n prevSearchTextRef.current = searchText;\n prevOptionsLengthRef.current = visibleOptions.length;\n }\n }, [searchText, visibleOptions.length]);\n\n // --- scroll active option into view ---\n React.useEffect(() => {\n if (activeIndex < 0 || !listRef.current) return;\n const el = listRef.current.querySelector(`[data-option-index=\"${activeIndex}\"]`);\n if (typeof el?.scrollIntoView === \"function\") {\n el.scrollIntoView({ block: \"nearest\" });\n }\n }, [activeIndex]);\n\n // --- helpers ---\n function clearInputText() {\n if (inputValueProp === undefined) setInternalInputValue(\"\");\n onInputChange?.(\"\");\n }\n\n function selectSingle(val: string | null) {\n if (!isMulti && props.value === undefined) setInternalValue(val);\n if (!isMulti) (props as { onChange?: (v: string | null) => void }).onChange?.(val);\n }\n\n function toggleMulti(val: string) {\n const next = selectedMultiValues.includes(val)\n ? selectedMultiValues.filter((v) => v !== val)\n : [...selectedMultiValues, val];\n if (isMulti && props.value === undefined) setInternalMultiValue(next);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.(next);\n }\n\n function handleSelect(option: AutocompleteOption) {\n if (option.value.startsWith(CREATE_PREFIX)) {\n const raw = option.value.slice(CREATE_PREFIX.length);\n onCreate?.(raw);\n if (isMulti) {\n toggleMulti(raw);\n } else {\n selectSingle(raw);\n setOpen(false);\n }\n clearInputText();\n return;\n }\n\n if (isMulti) {\n toggleMulti(option.value);\n clearInputText();\n } else {\n selectSingle(option.value);\n clearInputText();\n setOpen(false);\n }\n }\n\n function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {\n const val = e.target.value;\n if (inputValueProp === undefined) setInternalInputValue(val);\n onInputChange?.(val);\n if (!isOpen) setOpen(true);\n }\n\n function findNextEnabled(start: number, direction: 1 | -1): number | null {\n let idx = start;\n while (idx >= 0 && idx < visibleOptions.length && visibleOptions[idx]?.disabled) {\n idx += direction;\n }\n return idx >= 0 && idx < visibleOptions.length ? idx : null;\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Flat switch is clearer than splitting into separate handler functions\n function handleKeyDown(e: React.KeyboardEvent) {\n if (disabled) return;\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev + 1, 1) ?? prev);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev - 1, -1) ?? prev);\n break;\n }\n case \"Enter\": {\n if (isOpen && activeIndex >= 0 && activeIndex < visibleOptions.length) {\n e.preventDefault();\n const opt = visibleOptions[activeIndex];\n if (opt && !opt.disabled) handleSelect(opt);\n }\n break;\n }\n case \"Escape\": {\n e.preventDefault();\n if (isOpen) setOpen(false);\n break;\n }\n case \"Backspace\": {\n if (isMulti && searchText === \"\" && selectedMultiValues.length > 0) {\n const lastVal = selectedMultiValues[selectedMultiValues.length - 1];\n if (lastVal !== undefined) toggleMulti(lastVal);\n }\n break;\n }\n case \"Home\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(0, 1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n case \"End\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(visibleOptions.length - 1, -1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n }\n }\n\n function handleClear(e: React.MouseEvent) {\n e.stopPropagation();\n if (isMulti) {\n if (props.value === undefined) setInternalMultiValue([]);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.([]);\n } else {\n selectSingle(null);\n }\n clearInputText();\n inputRef.current?.focus();\n }\n\n function handleBlur(e: React.FocusEvent<HTMLInputElement>) {\n const container = e.currentTarget.closest(\"[data-autocomplete-root]\");\n if (container && e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {\n return;\n }\n if (\n e.relatedTarget instanceof Node &&\n (e.relatedTarget as Element).closest?.(\"[data-radix-popper-content-wrapper]\")\n ) {\n return;\n }\n if (isOpen) setOpen(false);\n }\n\n function handleFocus() {\n if (!disabled && !isOpen) setOpen(true);\n }\n\n function handleContainerClick() {\n if (!disabled) {\n inputRef.current?.focus();\n if (!isOpen) setOpen(true);\n }\n }\n\n function handleOpenChange(next: boolean) {\n setOpen(next);\n if (!next) {\n clearInputText();\n setActiveIndex(-1);\n }\n }\n\n const selectedOption = React.useMemo(\n () => options.find((o) => o.value === selectedValue),\n [options, selectedValue],\n );\n\n const selectedMultiOptions = React.useMemo(\n () =>\n selectedMultiValues\n .map((v) => options.find((o) => o.value === v))\n .filter(Boolean) as AutocompleteOption[],\n [options, selectedMultiValues],\n );\n\n const hasClearableValue = isMulti ? selectedMultiValues.length > 0 : selectedValue != null;\n\n const displayInputValue = React.useMemo(() => {\n if (searchText) return searchText;\n if (isMulti || isOpen) return \"\";\n return selectedOption ? getLabel(selectedOption) : \"\";\n }, [searchText, isMulti, isOpen, selectedOption]);\n\n const activeDescendantId = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined;\n\n return {\n inputId,\n helperTextId,\n listboxId,\n inputRef,\n listRef,\n isMulti,\n isOpen,\n selectedValue,\n selectedMultiValues,\n selectedMultiOptions,\n searchText,\n visibleOptions,\n visibleSections,\n createOption,\n activeIndex,\n activeDescendantId,\n hasClearableValue,\n displayInputValue,\n setActiveIndex,\n handleSelect,\n handleInputChange,\n handleKeyDown,\n handleClear,\n handleBlur,\n handleFocus,\n handleContainerClick,\n handleOpenChange,\n toggleMulti,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -5,11 +5,21 @@ import * as React from "react";
|
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
6
|
import { CheckIcon } from "../Icons/CheckIcon.mjs";
|
|
7
7
|
import { MinusIcon } from "../Icons/MinusIcon.mjs";
|
|
8
|
+
const BOX_SIZE_CLASS = {
|
|
9
|
+
"20": "size-5",
|
|
10
|
+
"16": "size-4"
|
|
11
|
+
};
|
|
12
|
+
const INDICATOR_SIZE_CLASS = {
|
|
13
|
+
"20": "size-3",
|
|
14
|
+
"16": "size-2.5"
|
|
15
|
+
};
|
|
8
16
|
const Checkbox = React.forwardRef(
|
|
9
|
-
({ className, size = "
|
|
17
|
+
({ className, size = "20", label, helperText, disabled, name, ...props }, ref) => {
|
|
10
18
|
const id = React.useId();
|
|
11
19
|
const helperTextId = helperText ? `${id}-helper` : void 0;
|
|
12
20
|
const hasLabel = Boolean(label || helperText);
|
|
21
|
+
const boxSize = size === "16" ? "16" : "20";
|
|
22
|
+
const useSmallLabelTypography = size === "small";
|
|
13
23
|
if (process.env.NODE_ENV !== "production" && !label && !props["aria-label"] && !props["aria-labelledby"]) {
|
|
14
24
|
console.warn(
|
|
15
25
|
"Checkbox: No accessible name provided. Add a `label`, `aria-label`, or `aria-labelledby` prop so screen readers can announce this checkbox."
|
|
@@ -29,7 +39,8 @@ const Checkbox = React.forwardRef(
|
|
|
29
39
|
"span",
|
|
30
40
|
{
|
|
31
41
|
className: cn(
|
|
32
|
-
"relative inline-flex
|
|
42
|
+
"relative inline-flex shrink-0",
|
|
43
|
+
BOX_SIZE_CLASS[boxSize],
|
|
33
44
|
// Alignment when used with label
|
|
34
45
|
label && (helperText ? "mt-1" : "mt-0.5")
|
|
35
46
|
),
|
|
@@ -60,7 +71,8 @@ const Checkbox = React.forwardRef(
|
|
|
60
71
|
onCheckedChange: handleCheckedChange,
|
|
61
72
|
className: cn(
|
|
62
73
|
// Base styles
|
|
63
|
-
"flex
|
|
74
|
+
"flex items-center justify-center rounded border-2",
|
|
75
|
+
BOX_SIZE_CLASS[boxSize],
|
|
64
76
|
"transition-[border-color,background-color,color,box-shadow] duration-150",
|
|
65
77
|
// Default state
|
|
66
78
|
"border-content-primary bg-transparent text-transparent",
|
|
@@ -83,7 +95,8 @@ const Checkbox = React.forwardRef(
|
|
|
83
95
|
{
|
|
84
96
|
forceMount: true,
|
|
85
97
|
className: cn(
|
|
86
|
-
"flex
|
|
98
|
+
"flex items-center justify-center text-content-primary-inverted",
|
|
99
|
+
INDICATOR_SIZE_CLASS[boxSize],
|
|
87
100
|
"data-[state=unchecked]:invisible"
|
|
88
101
|
),
|
|
89
102
|
children: props.checked === "indeterminate" ? /* @__PURE__ */ jsx(MinusIcon, {}) : /* @__PURE__ */ jsx(CheckIcon, {})
|
|
@@ -115,7 +128,7 @@ const Checkbox = React.forwardRef(
|
|
|
115
128
|
className: cn(
|
|
116
129
|
"cursor-pointer select-none text-content-primary",
|
|
117
130
|
"group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary",
|
|
118
|
-
|
|
131
|
+
useSmallLabelTypography ? "typography-semibold-body-md" : "typography-semibold-body-lg"
|
|
119
132
|
),
|
|
120
133
|
children: label
|
|
121
134
|
}
|
|
@@ -128,7 +141,7 @@ const Checkbox = React.forwardRef(
|
|
|
128
141
|
className: cn(
|
|
129
142
|
"ml-7 text-content-secondary",
|
|
130
143
|
"in-[.is-disabled]:cursor-not-allowed in-[.is-disabled]:text-content-tertiary",
|
|
131
|
-
|
|
144
|
+
useSmallLabelTypography ? "typography-regular-body-sm" : "typography-regular-body-md"
|
|
132
145
|
),
|
|
133
146
|
children: helperText
|
|
134
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.mjs","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport { MinusIcon } from \"../Icons/MinusIcon\";\n\n/** Size variant controlling label and helper text typography. */\nexport type CheckboxSize = \"default\" | \"small\";\n\nexport interface CheckboxProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, \"asChild\"> {\n /** Size variant that controls label and helper text typography. @default \"default\" */\n size?: CheckboxSize;\n /** Label text displayed next to the checkbox. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n}\n\n/**\n * A checkbox input with optional label and helper text. Supports checked,\n * unchecked, and indeterminate states.\n *\n * The ref type is intentionally `HTMLInputElement` (not `HTMLButtonElement`) for\n * form-library compatibility — libraries like react-hook-form call `register()`\n * which expects an `HTMLInputElement` ref. A hidden `<input>` is synced to the\n * Radix checkbox state via `useImperativeHandle`.\n *\n * @example\n * ```tsx\n * <Checkbox label=\"Accept terms\" helperText=\"Required to continue\" />\n * ```\n */\nexport const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(\n ({ className, size = \"default\", label, helperText, disabled, name, ...props }, ref) => {\n const id = React.useId();\n const helperTextId = helperText ? `${id}-helper` : undefined;\n const hasLabel = Boolean(label || helperText);\n\n if (\n process.env.NODE_ENV !== \"production\" &&\n !label &&\n !props[\"aria-label\"] &&\n !props[\"aria-labelledby\"]\n ) {\n console.warn(\n \"Checkbox: No accessible name provided. Add a `label`, `aria-label`, or `aria-labelledby` prop so screen readers can announce this checkbox.\",\n );\n }\n\n // Hidden input for form library compatibility (e.g. react-hook-form register)\n const inputRef = React.useRef<HTMLInputElement>(null);\n React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement);\n\n const handleCheckedChange = (value: boolean | \"indeterminate\") => {\n const checked = value === true;\n if (inputRef.current) {\n inputRef.current.checked = checked;\n inputRef.current.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n props.onCheckedChange?.(value);\n };\n\n const checkboxElement = (\n <span\n className={cn(\n \"relative inline-flex size-5 shrink-0\",\n // Alignment when used with label\n label && (helperText ? \"mt-1\" : \"mt-0.5\"),\n )}\n >\n <input\n ref={inputRef}\n type=\"checkbox\"\n name={name}\n disabled={disabled}\n aria-hidden\n tabIndex={-1}\n onChange={() => {}}\n className=\"pointer-events-none absolute size-px overflow-hidden opacity-0\"\n style={{ clip: \"rect(0,0,0,0)\" }}\n />\n <CheckboxPrimitive.Root\n id={id}\n disabled={disabled}\n aria-describedby={helperTextId}\n data-testid=\"checkbox\"\n {...props}\n onCheckedChange={handleCheckedChange}\n className={cn(\n // Base styles\n \"flex size-5 items-center justify-center rounded border-2\",\n \"transition-[border-color,background-color,color,box-shadow] duration-150\",\n // Default state\n \"border-content-primary bg-transparent text-transparent\",\n // Checked state\n \"data-[state=checked]:border-content-primary data-[state=checked]:bg-content-primary data-[state=checked]:text-content-primary-inverted\",\n // Indeterminate state\n \"data-[state=indeterminate]:border-content-primary data-[state=indeterminate]:bg-content-primary data-[state=indeterminate]:text-content-primary-inverted\",\n // Hover & active state\n \"hover:ring-2 hover:ring-brand-primary-default group-hover:ring-2 group-hover:ring-brand-primary-default\",\n \"not-disabled:active:ring-2 not-disabled:active:ring-brand-primary-default\",\n // Focus state\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interaction-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary\",\n // Disabled state\n \"disabled:cursor-not-allowed disabled:border-neutral-alphas-600 disabled:ring-0 disabled:group-hover:ring-0\",\n \"disabled:data-[state=checked]:border-neutral-alphas-600 disabled:data-[state=checked]:bg-neutral-alphas-600 disabled:data-[state=checked]:text-content-tertiary\",\n !hasLabel && className,\n )}\n >\n <CheckboxPrimitive.Indicator\n forceMount\n className={cn(\n \"flex size-3 items-center justify-center text-content-primary-inverted\",\n \"data-[state=unchecked]:invisible\",\n )}\n >\n {props.checked === \"indeterminate\" ? <MinusIcon /> : <CheckIcon />}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n </span>\n );\n\n if (!hasLabel) {\n return checkboxElement;\n }\n\n return (\n <div\n className={cn(\n \"inline-flex flex-col gap-0.5\",\n disabled && \"is-disabled cursor-not-allowed\",\n className,\n )}\n >\n <div className=\"group inline-flex items-start gap-2\">\n {checkboxElement}\n {label && (\n <label\n htmlFor={id}\n className={cn(\n \"cursor-pointer select-none text-content-primary\",\n \"group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary\",\n size === \"small\" ? \"typography-semibold-body-md\" : \"typography-semibold-body-lg\",\n )}\n >\n {label}\n </label>\n )}\n </div>\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"ml-7 text-content-secondary\",\n \"in-[.is-disabled]:cursor-not-allowed in-[.is-disabled]:text-content-tertiary\",\n size === \"small\" ? \"typography-regular-body-sm\" : \"typography-regular-body-md\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n );\n },\n);\n\nCheckbox.displayName = \"Checkbox\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Checkbox.mjs","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport { MinusIcon } from \"../Icons/MinusIcon\";\n\n/**\n * Size variant for the checkbox.\n *\n * - `\"20\"` (default) — 20px box, body-lg label.\n * - `\"16\"` — 16px box, body-md label, used in compact contexts like data tables.\n * - `\"default\"` and `\"small\"` are legacy aliases retained for back-compat\n * (`\"default\"` → `\"20\"`, `\"small\"` → `\"20\"` with smaller label typography).\n */\nexport type CheckboxSize =\n | \"20\"\n | \"16\"\n /** @deprecated Use `\"20\"` instead. */\n | \"default\"\n /** @deprecated Use `\"20\"` (the smaller-typography variant remains via `\"small\"`). */\n | \"small\";\n\nconst BOX_SIZE_CLASS: Record<\"20\" | \"16\", string> = {\n \"20\": \"size-5\",\n \"16\": \"size-4\",\n};\n\nconst INDICATOR_SIZE_CLASS: Record<\"20\" | \"16\", string> = {\n \"20\": \"size-3\",\n \"16\": \"size-2.5\",\n};\n\nexport interface CheckboxProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, \"asChild\"> {\n /** Size variant. @default \"20\" */\n size?: CheckboxSize;\n /** Label text displayed next to the checkbox. */\n label?: string;\n /** Descriptive text displayed below the label. */\n helperText?: string;\n}\n\n/**\n * A checkbox input with optional label and helper text. Supports checked,\n * unchecked, and indeterminate states.\n *\n * The ref type is intentionally `HTMLInputElement` (not `HTMLButtonElement`) for\n * form-library compatibility — libraries like react-hook-form call `register()`\n * which expects an `HTMLInputElement` ref. A hidden `<input>` is synced to the\n * Radix checkbox state via `useImperativeHandle`.\n *\n * @example\n * ```tsx\n * <Checkbox label=\"Accept terms\" helperText=\"Required to continue\" />\n * ```\n */\nexport const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(\n ({ className, size = \"20\", label, helperText, disabled, name, ...props }, ref) => {\n const id = React.useId();\n const helperTextId = helperText ? `${id}-helper` : undefined;\n const hasLabel = Boolean(label || helperText);\n const boxSize: \"20\" | \"16\" = size === \"16\" ? \"16\" : \"20\";\n const useSmallLabelTypography = size === \"small\";\n\n if (\n process.env.NODE_ENV !== \"production\" &&\n !label &&\n !props[\"aria-label\"] &&\n !props[\"aria-labelledby\"]\n ) {\n console.warn(\n \"Checkbox: No accessible name provided. Add a `label`, `aria-label`, or `aria-labelledby` prop so screen readers can announce this checkbox.\",\n );\n }\n\n // Hidden input for form library compatibility (e.g. react-hook-form register)\n const inputRef = React.useRef<HTMLInputElement>(null);\n React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement);\n\n const handleCheckedChange = (value: boolean | \"indeterminate\") => {\n const checked = value === true;\n if (inputRef.current) {\n inputRef.current.checked = checked;\n inputRef.current.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n props.onCheckedChange?.(value);\n };\n\n const checkboxElement = (\n <span\n className={cn(\n \"relative inline-flex shrink-0\",\n BOX_SIZE_CLASS[boxSize],\n // Alignment when used with label\n label && (helperText ? \"mt-1\" : \"mt-0.5\"),\n )}\n >\n <input\n ref={inputRef}\n type=\"checkbox\"\n name={name}\n disabled={disabled}\n aria-hidden\n tabIndex={-1}\n onChange={() => {}}\n className=\"pointer-events-none absolute size-px overflow-hidden opacity-0\"\n style={{ clip: \"rect(0,0,0,0)\" }}\n />\n <CheckboxPrimitive.Root\n id={id}\n disabled={disabled}\n aria-describedby={helperTextId}\n data-testid=\"checkbox\"\n {...props}\n onCheckedChange={handleCheckedChange}\n className={cn(\n // Base styles\n \"flex items-center justify-center rounded border-2\",\n BOX_SIZE_CLASS[boxSize],\n \"transition-[border-color,background-color,color,box-shadow] duration-150\",\n // Default state\n \"border-content-primary bg-transparent text-transparent\",\n // Checked state\n \"data-[state=checked]:border-content-primary data-[state=checked]:bg-content-primary data-[state=checked]:text-content-primary-inverted\",\n // Indeterminate state\n \"data-[state=indeterminate]:border-content-primary data-[state=indeterminate]:bg-content-primary data-[state=indeterminate]:text-content-primary-inverted\",\n // Hover & active state\n \"hover:ring-2 hover:ring-brand-primary-default group-hover:ring-2 group-hover:ring-brand-primary-default\",\n \"not-disabled:active:ring-2 not-disabled:active:ring-brand-primary-default\",\n // Focus state\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interaction-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary\",\n // Disabled state\n \"disabled:cursor-not-allowed disabled:border-neutral-alphas-600 disabled:ring-0 disabled:group-hover:ring-0\",\n \"disabled:data-[state=checked]:border-neutral-alphas-600 disabled:data-[state=checked]:bg-neutral-alphas-600 disabled:data-[state=checked]:text-content-tertiary\",\n !hasLabel && className,\n )}\n >\n <CheckboxPrimitive.Indicator\n forceMount\n className={cn(\n \"flex items-center justify-center text-content-primary-inverted\",\n INDICATOR_SIZE_CLASS[boxSize],\n \"data-[state=unchecked]:invisible\",\n )}\n >\n {props.checked === \"indeterminate\" ? <MinusIcon /> : <CheckIcon />}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n </span>\n );\n\n if (!hasLabel) {\n return checkboxElement;\n }\n\n return (\n <div\n className={cn(\n \"inline-flex flex-col gap-0.5\",\n disabled && \"is-disabled cursor-not-allowed\",\n className,\n )}\n >\n <div className=\"group inline-flex items-start gap-2\">\n {checkboxElement}\n {label && (\n <label\n htmlFor={id}\n className={cn(\n \"cursor-pointer select-none text-content-primary\",\n \"group-has-disabled:cursor-not-allowed group-has-disabled:text-content-tertiary\",\n useSmallLabelTypography\n ? \"typography-semibold-body-md\"\n : \"typography-semibold-body-lg\",\n )}\n >\n {label}\n </label>\n )}\n </div>\n {helperText && (\n <span\n id={helperTextId}\n className={cn(\n \"ml-7 text-content-secondary\",\n \"in-[.is-disabled]:cursor-not-allowed in-[.is-disabled]:text-content-tertiary\",\n useSmallLabelTypography ? \"typography-regular-body-sm\" : \"typography-regular-body-md\",\n )}\n >\n {helperText}\n </span>\n )}\n </div>\n );\n },\n);\n\nCheckbox.displayName = \"Checkbox\";\n"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,19 @@ import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { cn } from "../../utils/cn.mjs";
|
|
7
7
|
import { FLOATING_CONTENT_COLLISION_PADDING } from "../../utils/floatingContentCollisionPadding.mjs";
|
|
8
|
+
import { IconButton } from "../IconButton/IconButton.mjs";
|
|
9
|
+
import { CloseIcon } from "../Icons/CloseIcon.mjs";
|
|
10
|
+
import { SearchIcon } from "../Icons/SearchIcon.mjs";
|
|
8
11
|
const TAP_MOVEMENT_THRESHOLD_PX = 10;
|
|
12
|
+
const NAVIGATION_KEYS = /* @__PURE__ */ new Set([
|
|
13
|
+
"ArrowDown",
|
|
14
|
+
"ArrowUp",
|
|
15
|
+
"ArrowLeft",
|
|
16
|
+
"ArrowRight",
|
|
17
|
+
"Escape",
|
|
18
|
+
"Tab",
|
|
19
|
+
"Enter"
|
|
20
|
+
]);
|
|
9
21
|
const ToggleOpenContext = React.createContext(null);
|
|
10
22
|
function DropdownMenu({
|
|
11
23
|
open: openProp,
|
|
@@ -91,7 +103,7 @@ const DropdownMenuContent = React.forwardRef(
|
|
|
91
103
|
sideOffset,
|
|
92
104
|
collisionPadding,
|
|
93
105
|
className: cn(
|
|
94
|
-
"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-
|
|
106
|
+
"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-sm border border-neutral-alphas-200 bg-surface-primary p-1 text-content-primary shadow-lg",
|
|
95
107
|
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
96
108
|
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
97
109
|
"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2",
|
|
@@ -110,18 +122,36 @@ const DropdownMenuContent = React.forwardRef(
|
|
|
110
122
|
DropdownMenuContent.displayName = "DropdownMenuContent";
|
|
111
123
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
112
124
|
DropdownMenuGroup.displayName = "DropdownMenuGroup";
|
|
113
|
-
const DropdownMenuLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
125
|
+
const DropdownMenuLabel = React.forwardRef(({ className, position = "default", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
114
126
|
DropdownMenuPrimitive.Label,
|
|
115
127
|
{
|
|
116
128
|
ref,
|
|
117
|
-
className: cn(
|
|
129
|
+
className: cn(
|
|
130
|
+
"typography-regular-body-sm flex items-center px-3 text-content-secondary",
|
|
131
|
+
position === "top" ? "py-2" : "pb-2 pt-4",
|
|
132
|
+
className
|
|
133
|
+
),
|
|
118
134
|
...props
|
|
119
135
|
}
|
|
120
136
|
));
|
|
121
137
|
DropdownMenuLabel.displayName = "DropdownMenuLabel";
|
|
138
|
+
const SIZE_NORMALIZED = {
|
|
139
|
+
"40": "40",
|
|
140
|
+
md: "40",
|
|
141
|
+
"32": "32",
|
|
142
|
+
sm: "32"
|
|
143
|
+
};
|
|
144
|
+
const ITEM_SIZE_CLASSES = {
|
|
145
|
+
"40": "min-h-10 py-2 typography-regular-body-lg",
|
|
146
|
+
"32": "min-h-8 py-[7px] typography-regular-body-md"
|
|
147
|
+
};
|
|
148
|
+
const ITEM_SELECTED_TYPOGRAPHY = {
|
|
149
|
+
"40": "typography-semibold-body-lg",
|
|
150
|
+
"32": "typography-semibold-body-md"
|
|
151
|
+
};
|
|
122
152
|
const DropdownMenuItem = React.forwardRef(
|
|
123
153
|
({
|
|
124
|
-
size = "
|
|
154
|
+
size = "40",
|
|
125
155
|
destructive,
|
|
126
156
|
leadingIcon,
|
|
127
157
|
trailingIcon,
|
|
@@ -131,14 +161,18 @@ const DropdownMenuItem = React.forwardRef(
|
|
|
131
161
|
asChild,
|
|
132
162
|
...props
|
|
133
163
|
}, ref) => {
|
|
164
|
+
const normalizedSize = SIZE_NORMALIZED[size];
|
|
134
165
|
const itemClassName = cn(
|
|
135
|
-
"flex w-full cursor-pointer items-center gap-
|
|
136
|
-
|
|
137
|
-
"data-[highlighted]:bg-neutral-alphas-
|
|
138
|
-
|
|
139
|
-
size === "sm" ? "typography-medium-body-sm" : "typography-medium-body-md",
|
|
166
|
+
"flex w-full cursor-pointer items-center gap-2 rounded-xs px-3 outline-none",
|
|
167
|
+
ITEM_SIZE_CLASSES[normalizedSize],
|
|
168
|
+
"data-[highlighted]:bg-neutral-alphas-50",
|
|
169
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled",
|
|
140
170
|
destructive && "text-error-content",
|
|
141
|
-
selected &&
|
|
171
|
+
selected && [
|
|
172
|
+
"bg-buttons-primary text-content-primary-inverted",
|
|
173
|
+
"data-[highlighted]:bg-buttons-primary",
|
|
174
|
+
ITEM_SELECTED_TYPOGRAPHY[normalizedSize]
|
|
175
|
+
],
|
|
142
176
|
className
|
|
143
177
|
);
|
|
144
178
|
if (asChild) {
|
|
@@ -146,7 +180,7 @@ const DropdownMenuItem = React.forwardRef(
|
|
|
146
180
|
}
|
|
147
181
|
return /* @__PURE__ */ jsxs(DropdownMenuPrimitive.Item, { ref, className: itemClassName, ...props, children: [
|
|
148
182
|
leadingIcon,
|
|
149
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1", children }),
|
|
183
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children }),
|
|
150
184
|
trailingIcon
|
|
151
185
|
] });
|
|
152
186
|
}
|
|
@@ -161,12 +195,156 @@ const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) =>
|
|
|
161
195
|
}
|
|
162
196
|
));
|
|
163
197
|
DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
|
|
198
|
+
const DropdownMenuHeader = React.forwardRef(
|
|
199
|
+
({
|
|
200
|
+
type = "default",
|
|
201
|
+
size = "40",
|
|
202
|
+
title,
|
|
203
|
+
searchProps,
|
|
204
|
+
showClose = true,
|
|
205
|
+
onClose,
|
|
206
|
+
closeLabel = "Close menu",
|
|
207
|
+
className,
|
|
208
|
+
children,
|
|
209
|
+
...props
|
|
210
|
+
}, ref) => {
|
|
211
|
+
const titleTypography = size === "32" ? "typography-semibold-body-md" : "typography-semibold-body-lg";
|
|
212
|
+
const toggleOpen = React.useContext(ToggleOpenContext);
|
|
213
|
+
const handleClose = () => {
|
|
214
|
+
onClose?.();
|
|
215
|
+
toggleOpen?.(() => false);
|
|
216
|
+
};
|
|
217
|
+
return /* @__PURE__ */ jsxs(
|
|
218
|
+
"div",
|
|
219
|
+
{
|
|
220
|
+
ref,
|
|
221
|
+
className: cn(
|
|
222
|
+
"flex flex-col px-1 pt-1 mb-1",
|
|
223
|
+
// Search needs an 8px gap between the input and the divider; the
|
|
224
|
+
// default (title) variant uses 4px because the title baseline sits
|
|
225
|
+
// closer to the divider naturally.
|
|
226
|
+
type === "search" ? "gap-2" : "gap-1",
|
|
227
|
+
className
|
|
228
|
+
),
|
|
229
|
+
...props,
|
|
230
|
+
children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 pl-2", children: [
|
|
232
|
+
type === "default" ? /* @__PURE__ */ jsx("div", { className: cn("min-w-0 flex-1 truncate text-content-primary", titleTypography), children: children ?? title }) : /* @__PURE__ */ jsx(SearchInput, { ...searchProps }),
|
|
233
|
+
showClose && /* @__PURE__ */ jsx(
|
|
234
|
+
IconButton,
|
|
235
|
+
{
|
|
236
|
+
variant: "tertiary",
|
|
237
|
+
size: "32",
|
|
238
|
+
icon: /* @__PURE__ */ jsx(CloseIcon, {}),
|
|
239
|
+
onClick: handleClose,
|
|
240
|
+
"aria-label": closeLabel
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
] }),
|
|
244
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, { className: "my-0" })
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
DropdownMenuHeader.displayName = "DropdownMenuHeader";
|
|
251
|
+
function SearchInput({
|
|
252
|
+
value,
|
|
253
|
+
defaultValue,
|
|
254
|
+
onChange,
|
|
255
|
+
placeholder = "Search…",
|
|
256
|
+
"aria-label": ariaLabel = "Search"
|
|
257
|
+
} = {}) {
|
|
258
|
+
return /* @__PURE__ */ jsxs(
|
|
259
|
+
"label",
|
|
260
|
+
{
|
|
261
|
+
className: cn(
|
|
262
|
+
"flex min-w-0 flex-1 items-center gap-2 rounded-xs border border-border-primary",
|
|
263
|
+
"bg-neutral-alphas-50 px-3 py-1 text-content-primary",
|
|
264
|
+
"focus-within:shadow-focus-ring focus-within:outline-none"
|
|
265
|
+
),
|
|
266
|
+
children: [
|
|
267
|
+
/* @__PURE__ */ jsx(SearchIcon, { className: "size-4 shrink-0 text-content-tertiary", "aria-hidden": "true" }),
|
|
268
|
+
/* @__PURE__ */ jsx(
|
|
269
|
+
"input",
|
|
270
|
+
{
|
|
271
|
+
type: "search",
|
|
272
|
+
className: cn(
|
|
273
|
+
"typography-regular-body-lg min-w-0 flex-1 bg-transparent outline-none",
|
|
274
|
+
"placeholder:text-content-tertiary"
|
|
275
|
+
),
|
|
276
|
+
value,
|
|
277
|
+
defaultValue,
|
|
278
|
+
placeholder,
|
|
279
|
+
"aria-label": ariaLabel,
|
|
280
|
+
onChange: (event) => onChange?.(event.target.value),
|
|
281
|
+
onKeyDown: (event) => {
|
|
282
|
+
if (!NAVIGATION_KEYS.has(event.key)) event.stopPropagation();
|
|
283
|
+
},
|
|
284
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
285
|
+
onMouseDown: (event) => event.stopPropagation()
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
293
|
+
const DropdownMenuRadioItem = React.forwardRef(({ className, children, helper, size: _size = "40", ...props }, ref) => {
|
|
294
|
+
return /* @__PURE__ */ jsxs(
|
|
295
|
+
DropdownMenuPrimitive.RadioItem,
|
|
296
|
+
{
|
|
297
|
+
ref,
|
|
298
|
+
className: cn(
|
|
299
|
+
"group flex w-full cursor-pointer items-start gap-3 rounded-xs px-4 py-2 outline-none",
|
|
300
|
+
"data-[highlighted]:bg-neutral-alphas-50",
|
|
301
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled",
|
|
302
|
+
"data-[state=checked]:bg-buttons-primary data-[state=checked]:text-content-primary-inverted",
|
|
303
|
+
"data-[state=checked]:data-[highlighted]:bg-buttons-primary",
|
|
304
|
+
className
|
|
305
|
+
),
|
|
306
|
+
...props,
|
|
307
|
+
children: [
|
|
308
|
+
/* @__PURE__ */ jsx(
|
|
309
|
+
"span",
|
|
310
|
+
{
|
|
311
|
+
className: cn(
|
|
312
|
+
"mt-1 flex size-4 shrink-0 items-center justify-center rounded-full border border-icons-primary",
|
|
313
|
+
"group-data-[disabled]:border-content-disabled",
|
|
314
|
+
"group-data-[state=checked]:border-icons-primary-inverted"
|
|
315
|
+
),
|
|
316
|
+
"aria-hidden": "true",
|
|
317
|
+
children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-content-primary-inverted" }) })
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
321
|
+
/* @__PURE__ */ jsx("span", { className: "typography-semibold-body-lg truncate", children }),
|
|
322
|
+
helper && /* @__PURE__ */ jsx(
|
|
323
|
+
"span",
|
|
324
|
+
{
|
|
325
|
+
className: cn(
|
|
326
|
+
"typography-regular-body-sm text-content-secondary",
|
|
327
|
+
"group-data-[state=checked]:text-content-primary-inverted",
|
|
328
|
+
"group-data-[disabled]:text-content-disabled"
|
|
329
|
+
),
|
|
330
|
+
children: helper
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
] })
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
DropdownMenuRadioItem.displayName = "DropdownMenuRadioItem";
|
|
164
339
|
export {
|
|
165
340
|
DropdownMenu,
|
|
166
341
|
DropdownMenuContent,
|
|
167
342
|
DropdownMenuGroup,
|
|
343
|
+
DropdownMenuHeader,
|
|
168
344
|
DropdownMenuItem,
|
|
169
345
|
DropdownMenuLabel,
|
|
346
|
+
DropdownMenuRadioGroup,
|
|
347
|
+
DropdownMenuRadioItem,
|
|
170
348
|
DropdownMenuSeparator,
|
|
171
349
|
DropdownMenuTrigger
|
|
172
350
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.mjs","sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\n\n// Movement, in CSS px, above which a touch press-and-release counts as a drag.\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\n// Lets DropdownMenuTrigger toggle the menu directly so it can gate on touch\n// movement — see radix-ui/primitives#1912.\nconst ToggleOpenContext = React.createContext<\n ((updater: (prev: boolean) => boolean) => void) | null\n>(null);\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport function DropdownMenu({\n open: openProp,\n defaultOpen,\n onOpenChange,\n children,\n ...props\n}: DropdownMenuProps) {\n const [open = false, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen ?? false,\n onChange: onOpenChange,\n });\n\n return (\n <ToggleOpenContext.Provider value={setOpen}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props}>\n {children}\n </DropdownMenuPrimitive.Root>\n </ToggleOpenContext.Provider>\n );\n}\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/**\n * The element that toggles the dropdown menu when clicked.\n *\n * On touch devices, the menu only opens if the press-and-release stays within\n * a small movement threshold. A drag that incidentally ends over the trigger\n * (common when scrolling a feed on Android Chrome) is ignored. Mouse and\n * keyboard interactions are unchanged.\n */\nexport const DropdownMenuTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Trigger>,\n DropdownMenuTriggerProps\n>((props, ref) => {\n const toggleOpen = React.useContext(ToggleOpenContext);\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n // Used outside our DropdownMenu wrapper — fall through to Radix defaults.\n if (toggleOpen === null) {\n return <DropdownMenuPrimitive.Trigger {...props} ref={ref} />;\n }\n\n return (\n <DropdownMenuPrimitive.Trigger\n {...props}\n ref={ref}\n onPointerDown={(event) => {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\" || props.disabled) return;\n // Keep pointerup / pointercancel on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n // preventDefault stops Radix's pointerdown open path via composeEventHandlers.\n event.preventDefault();\n }}\n onPointerMove={(event) => {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {\n return;\n }\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n }}\n onPointerUp={(event) => {\n props.onPointerUp?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId) return;\n const wasDrag = tap.movedPastThreshold;\n tapRef.current = null;\n if (!wasDrag && !props.disabled) {\n toggleOpen((prev) => !prev);\n }\n }}\n onPointerCancel={(event) => {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n }}\n />\n );\n});\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-xs border border-neutral-alphas-200 bg-bg-primary p-1 shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/** A label for a group of items. Not focusable or selectable. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"typography-medium-body-xs px-2 py-1.5 text-content-secondary\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/** Available sizes for a dropdown menu item. */\nexport type DropdownMenuItemSize = \"sm\" | \"md\";\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"sm\" */\n size?: DropdownMenuItemSize;\n /** Whether the item uses destructive (error) styling. */\n destructive?: boolean;\n /** Icon rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Whether the item is in a selected state. */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"sm\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-1 rounded px-2 outline-none\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"data-[highlighted]:bg-neutral-alphas-100\",\n size === \"sm\" ? \"min-h-[34px] py-1\" : \"min-h-[40px] py-1.5\",\n size === \"sm\" ? \"typography-medium-body-sm\" : \"typography-medium-body-md\",\n destructive && \"text-error-content\",\n selected && \"bg-success-surface\",\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"flex-1\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DropdownMenu.mjs","sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { SearchIcon } from \"../Icons/SearchIcon\";\n\n// Movement, in CSS px, above which a touch press-and-release counts as a drag.\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\n// Keys that the menu must keep handling even when focus is inside a child\n// input — arrows move the highlight into the list, Tab leaves the menu, and\n// Enter / Escape close it.\nconst NAVIGATION_KEYS = new Set([\n \"ArrowDown\",\n \"ArrowUp\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Escape\",\n \"Tab\",\n \"Enter\",\n]);\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\n// Lets DropdownMenuTrigger toggle the menu directly so it can gate on touch\n// movement — see radix-ui/primitives#1912.\nconst ToggleOpenContext = React.createContext<\n ((updater: (prev: boolean) => boolean) => void) | null\n>(null);\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport function DropdownMenu({\n open: openProp,\n defaultOpen,\n onOpenChange,\n children,\n ...props\n}: DropdownMenuProps) {\n const [open = false, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen ?? false,\n onChange: onOpenChange,\n });\n\n return (\n <ToggleOpenContext.Provider value={setOpen}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props}>\n {children}\n </DropdownMenuPrimitive.Root>\n </ToggleOpenContext.Provider>\n );\n}\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/**\n * The element that toggles the dropdown menu when clicked.\n *\n * On touch devices, the menu only opens if the press-and-release stays within\n * a small movement threshold. A drag that incidentally ends over the trigger\n * (common when scrolling a feed on Android Chrome) is ignored. Mouse and\n * keyboard interactions are unchanged.\n */\nexport const DropdownMenuTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Trigger>,\n DropdownMenuTriggerProps\n>((props, ref) => {\n const toggleOpen = React.useContext(ToggleOpenContext);\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n // Used outside our DropdownMenu wrapper — fall through to Radix defaults.\n if (toggleOpen === null) {\n return <DropdownMenuPrimitive.Trigger {...props} ref={ref} />;\n }\n\n return (\n <DropdownMenuPrimitive.Trigger\n {...props}\n ref={ref}\n onPointerDown={(event) => {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\" || props.disabled) return;\n // Keep pointerup / pointercancel on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n // preventDefault stops Radix's pointerdown open path via composeEventHandlers.\n event.preventDefault();\n }}\n onPointerMove={(event) => {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {\n return;\n }\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n }}\n onPointerUp={(event) => {\n props.onPointerUp?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId) return;\n const wasDrag = tap.movedPastThreshold;\n tapRef.current = null;\n if (!wasDrag && !props.disabled) {\n toggleOpen((prev) => !prev);\n }\n }}\n onPointerCancel={(event) => {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n }}\n />\n );\n});\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-sm border border-neutral-alphas-200 bg-surface-primary p-1 text-content-primary shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Vertical placement of a {@link DropdownMenuLabel} within its group. */\nexport type DropdownMenuLabelPosition = \"default\" | \"top\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {\n /**\n * Vertical placement within the surrounding group. `\"top\"` is used for the\n * first label directly under a header; `\"default\"` adds extra top padding to\n * separate it from preceding items. @default \"default\"\n */\n position?: DropdownMenuLabelPosition;\n}\n\n/** A non-interactive label that groups related items within a menu. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, position = \"default\", ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n \"typography-regular-body-sm flex items-center px-3 text-content-secondary\",\n position === \"top\" ? \"py-2\" : \"pb-2 pt-4\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/**\n * Height preset for a dropdown menu item.\n *\n * `\"40\"` (default) and `\"32\"` are the v2 numeric tokens that mirror the Figma\n * design system. `\"sm\"` and `\"md\"` are deprecated aliases retained for\n * backwards compatibility — `\"sm\"` maps to `\"32\"`, `\"md\"` maps to `\"40\"`.\n */\nexport type DropdownMenuItemSize =\n | \"40\"\n | \"32\"\n /** @deprecated Use `\"32\"` instead. */\n | \"sm\"\n /** @deprecated Use `\"40\"` instead. */\n | \"md\";\n\nconst SIZE_NORMALIZED: Record<DropdownMenuItemSize, \"40\" | \"32\"> = {\n \"40\": \"40\",\n md: \"40\",\n \"32\": \"32\",\n sm: \"32\",\n};\n\nconst ITEM_SIZE_CLASSES: Record<\"40\" | \"32\", string> = {\n \"40\": \"min-h-10 py-2 typography-regular-body-lg\",\n \"32\": \"min-h-8 py-[7px] typography-regular-body-md\",\n};\n\nconst ITEM_SELECTED_TYPOGRAPHY: Record<\"40\" | \"32\", string> = {\n \"40\": \"typography-semibold-body-lg\",\n \"32\": \"typography-semibold-body-md\",\n};\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"40\" */\n size?: DropdownMenuItemSize;\n /** Applies the destructive (error) treatment. Use for irreversible actions. @default false */\n destructive?: boolean;\n /** Icon (or other node) rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon (or other node) rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Marks the item as the current selection in a single-select menu. @default false */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"40\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const normalizedSize = SIZE_NORMALIZED[size];\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-2 rounded-xs px-3 outline-none\",\n ITEM_SIZE_CLASSES[normalizedSize],\n \"data-[highlighted]:bg-neutral-alphas-50\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled\",\n destructive && \"text-error-content\",\n selected && [\n \"bg-buttons-primary text-content-primary-inverted\",\n \"data-[highlighted]:bg-buttons-primary\",\n ITEM_SELECTED_TYPOGRAPHY[normalizedSize],\n ],\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n\n/** Header type. `\"default\"` shows a title; `\"search\"` shows a search input. */\nexport type DropdownMenuHeaderType = \"default\" | \"search\";\n\n/** Header height preset. Matches the menu item sizing scale. */\nexport type DropdownMenuHeaderSize = \"40\" | \"32\";\n\n/** Search-input configuration for {@link DropdownMenuHeader} when `type=\"search\"`. */\nexport interface DropdownMenuHeaderSearchProps {\n /** Controlled value of the search input. */\n value?: string;\n /** Uncontrolled default value. */\n defaultValue?: string;\n /** Fires when the input value changes. */\n onChange?: (value: string) => void;\n /** Placeholder text shown when the input is empty. @default \"Search…\" */\n placeholder?: string;\n /** Accessible label for the search input. @default \"Search\" */\n \"aria-label\"?: string;\n}\n\nexport interface DropdownMenuHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Visual type. `\"default\"` shows a title; `\"search\"` shows a search input. @default \"default\" */\n type?: DropdownMenuHeaderType;\n /** Height preset for the header row. @default \"40\" */\n size?: DropdownMenuHeaderSize;\n /** Title text shown when `type=\"default\"`. Ignored if `children` is provided. */\n title?: string;\n /** Configuration for the embedded search input when `type=\"search\"`. */\n searchProps?: DropdownMenuHeaderSearchProps;\n /** Whether to render the close icon button on the right. @default true */\n showClose?: boolean;\n /** Fires when the close icon button is activated. */\n onClose?: () => void;\n /** Accessible label for the close button. @default \"Close menu\" */\n closeLabel?: string;\n}\n\n/**\n * Optional header rendered at the top of a {@link DropdownMenuContent}. Use\n * `type=\"default\"` to title the menu, or `type=\"search\"` to embed a search\n * input for filtering long lists.\n *\n * Renders an inset separator beneath the row so it slots cleanly above the\n * first group of items.\n *\n * @example\n * ```tsx\n * <DropdownMenuContent>\n * <DropdownMenuHeader title=\"Sort by\" onClose={() => setOpen(false)} />\n * <DropdownMenuItem>Newest</DropdownMenuItem>\n * <DropdownMenuItem>Oldest</DropdownMenuItem>\n * </DropdownMenuContent>\n * ```\n */\nexport const DropdownMenuHeader = React.forwardRef<HTMLDivElement, DropdownMenuHeaderProps>(\n (\n {\n type = \"default\",\n size = \"40\",\n title,\n searchProps,\n showClose = true,\n onClose,\n closeLabel = \"Close menu\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const titleTypography =\n size === \"32\" ? \"typography-semibold-body-md\" : \"typography-semibold-body-lg\";\n const toggleOpen = React.useContext(ToggleOpenContext);\n\n const handleClose = () => {\n onClose?.();\n // Also dismiss the Radix menu when used in uncontrolled mode — otherwise\n // the close button would look broken to consumers that don't wire up\n // `open` / `onOpenChange` themselves.\n toggleOpen?.(() => false);\n };\n\n return (\n <div\n ref={ref}\n className={cn(\n \"flex flex-col px-1 pt-1 mb-1\",\n // Search needs an 8px gap between the input and the divider; the\n // default (title) variant uses 4px because the title baseline sits\n // closer to the divider naturally.\n type === \"search\" ? \"gap-2\" : \"gap-1\",\n className,\n )}\n {...props}\n >\n <div className=\"flex items-center gap-4 pl-2\">\n {type === \"default\" ? (\n <div className={cn(\"min-w-0 flex-1 truncate text-content-primary\", titleTypography)}>\n {children ?? title}\n </div>\n ) : (\n <SearchInput {...searchProps} />\n )}\n {showClose && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<CloseIcon />}\n onClick={handleClose}\n aria-label={closeLabel}\n />\n )}\n </div>\n <DropdownMenuSeparator className=\"my-0\" />\n </div>\n );\n },\n);\nDropdownMenuHeader.displayName = \"DropdownMenuHeader\";\n\nfunction SearchInput({\n value,\n defaultValue,\n onChange,\n placeholder = \"Search\\u2026\",\n \"aria-label\": ariaLabel = \"Search\",\n}: DropdownMenuHeaderSearchProps = {}) {\n return (\n <label\n className={cn(\n \"flex min-w-0 flex-1 items-center gap-2 rounded-xs border border-border-primary\",\n \"bg-neutral-alphas-50 px-3 py-1 text-content-primary\",\n \"focus-within:shadow-focus-ring focus-within:outline-none\",\n )}\n >\n <SearchIcon className=\"size-4 shrink-0 text-content-tertiary\" aria-hidden=\"true\" />\n <input\n type=\"search\"\n className={cn(\n \"typography-regular-body-lg min-w-0 flex-1 bg-transparent outline-none\",\n \"placeholder:text-content-tertiary\",\n )}\n value={value}\n defaultValue={defaultValue}\n placeholder={placeholder}\n aria-label={ariaLabel}\n onChange={(event) => onChange?.(event.target.value)}\n // Radix DropdownMenu listens for keystrokes on Content for its\n // typeahead (typing letters jumps focus to a matching item). That\n // listener steals focus from the input after the first letter, so we\n // stop character keys from bubbling. Navigation keys (arrows / Tab /\n // Escape / Enter) are still allowed through so the user can leave the\n // input for the list or close the menu. Pointer events are stopped so\n // clicking back into the input doesn't fight the menu's focus\n // management either.\n onKeyDown={(event) => {\n if (!NAVIGATION_KEYS.has(event.key)) event.stopPropagation();\n }}\n onPointerDown={(event) => event.stopPropagation()}\n onMouseDown={(event) => event.stopPropagation()}\n />\n </label>\n );\n}\n\n/** Props for the {@link DropdownMenuRadioGroup} component. */\nexport interface DropdownMenuRadioGroupProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioGroup> {}\n\n/**\n * Groups {@link DropdownMenuRadioItem} children so they behave as a\n * single-select set. Controlled via `value`/`onValueChange`.\n *\n * @example\n * ```tsx\n * <DropdownMenuRadioGroup value={sort} onValueChange={setSort}>\n * <DropdownMenuRadioItem value=\"newest\">Newest first</DropdownMenuRadioItem>\n * <DropdownMenuRadioItem value=\"oldest\">Oldest first</DropdownMenuRadioItem>\n * </DropdownMenuRadioGroup>\n * ```\n */\nexport const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\n/** Height preset for a {@link DropdownMenuRadioItem}. */\nexport type DropdownMenuRadioItemSize = \"40\";\n\nexport interface DropdownMenuRadioItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> {\n /** Optional secondary text shown below the title. */\n helper?: string;\n /** Height of the item row. @default \"40\" */\n size?: DropdownMenuRadioItemSize;\n}\n\n/**\n * A single radio-style choice within a {@link DropdownMenuRadioGroup}. Shows\n * a circular indicator that fills when selected, plus an optional helper line\n * underneath the title.\n */\nexport const DropdownMenuRadioItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,\n DropdownMenuRadioItemProps\n>(({ className, children, helper, size: _size = \"40\", ...props }, ref) => {\n return (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"group flex w-full cursor-pointer items-start gap-3 rounded-xs px-4 py-2 outline-none\",\n \"data-[highlighted]:bg-neutral-alphas-50\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled\",\n \"data-[state=checked]:bg-buttons-primary data-[state=checked]:text-content-primary-inverted\",\n \"data-[state=checked]:data-[highlighted]:bg-buttons-primary\",\n className,\n )}\n {...props}\n >\n <span\n className={cn(\n \"mt-1 flex size-4 shrink-0 items-center justify-center rounded-full border border-icons-primary\",\n \"group-data-[disabled]:border-content-disabled\",\n \"group-data-[state=checked]:border-icons-primary-inverted\",\n )}\n aria-hidden=\"true\"\n >\n <DropdownMenuPrimitive.ItemIndicator asChild>\n <span className=\"size-2 rounded-full bg-content-primary-inverted\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <span className=\"typography-semibold-body-lg truncate\">{children}</span>\n {helper && (\n <span\n className={cn(\n \"typography-regular-body-sm text-content-secondary\",\n \"group-data-[state=checked]:text-content-primary-inverted\",\n \"group-data-[disabled]:text-content-disabled\",\n )}\n >\n {helper}\n </span>\n )}\n </span>\n </DropdownMenuPrimitive.RadioItem>\n );\n});\nDropdownMenuRadioItem.displayName = \"DropdownMenuRadioItem\";\n"],"names":[],"mappings":"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"}
|