@vector-im/compound-web 7.10.2 → 7.11.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.
@@ -171,6 +171,7 @@ function useKeyboardShortcut(open, setOpen, setValue) {
171
171
  const onComboboxKeyDown = React.useCallback(
172
172
  ({ key }) => {
173
173
  switch (key) {
174
+ // Enter and Space already managed because it's a button
174
175
  case "Escape":
175
176
  setOpen(false);
176
177
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.cjs","sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["/*\nCopyright 2024 New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE files in the repository root for full details.\n*/\n\nimport ChevronDown from \"@vector-im/compound-design-tokens/assets/web/icons/chevron-down\";\nimport Check from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport Error from \"@vector-im/compound-design-tokens/assets/web/icons/error-solid\";\n\nimport React, {\n Dispatch,\n forwardRef,\n HTMLProps,\n memo,\n RefObject,\n SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n KeyboardEvent,\n useMemo,\n} from \"react\";\n\nimport classNames from \"classnames\";\n\nimport styles from \"./Dropdown.module.css\";\nimport { useId } from \"@floating-ui/react\";\n\ntype DropdownProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The controlled value of the dropdown.\n */\n value?: string;\n /**\n * The default value of the dropdown, used when uncontrolled.\n */\n defaultValue?: string;\n /**\n * The values of the dropdown.\n * [value, text]\n */\n values: [string, string][];\n /**\n * The placeholder text.\n */\n placeholder: string;\n /**\n * The label to display at the top of the dropdown\n */\n label: string;\n /**\n * The help label to display at the bottom of the dropdown\n */\n helpLabel?: string;\n /**\n * Callback for when the value changes.\n * @param value\n */\n onValueChange?: (value: string) => void;\n /**\n * The error message to display.\n */\n error?: string;\n};\n\n/**\n * The dropdown content.\n */\nexport const Dropdown = forwardRef<HTMLButtonElement, DropdownProps>(\n function Dropdown(\n {\n className,\n label,\n placeholder,\n helpLabel,\n onValueChange,\n error,\n value: controlledValue,\n defaultValue,\n values,\n ...props\n },\n ref,\n ) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const text = useMemo(\n () =>\n value === undefined\n ? placeholder\n : (values.find(([v]) => v === value)?.[1] ?? placeholder),\n [value, values, placeholder],\n );\n\n const setValue = useCallback(\n (value: string) => {\n setUncontrolledValue(value);\n onValueChange?.(value);\n },\n [setUncontrolledValue, onValueChange],\n );\n\n const [open, setOpen, dropdownRef] = useOpen();\n const { listRef, onComboboxKeyDown, onOptionKeyDown } = useKeyboardShortcut(\n open,\n setOpen,\n setValue,\n );\n\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n useEffect(() => {\n // Focus the button when the value is set\n // Test if the value is undefined to avoid focusing on the first render\n if (value !== undefined) buttonRef.current?.focus();\n }, [value]);\n\n const hasPlaceholder = text === placeholder;\n const buttonClasses = classNames({\n [styles.placeholder]: hasPlaceholder,\n });\n const borderClasses = classNames(styles.border, {\n [styles.open]: open,\n });\n const contentClasses = classNames(styles.content, {\n [styles.open]: open,\n });\n\n /**\n * Ids for accessibility.\n */\n const labelId = useId();\n const contentId = useId();\n\n return (\n <div\n ref={dropdownRef}\n className={classNames(className, styles.container)}\n aria-invalid={Boolean(error)}\n >\n <label id={labelId}>{label}</label>\n <button\n className={buttonClasses}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-labelledby={labelId}\n aria-controls={contentId}\n aria-expanded={open}\n ref={(element) => {\n // Private ref to focus the button\n buttonRef.current = element;\n // Handle forwarded ref\n if (typeof ref === \"function\") {\n ref(element);\n } else if (ref) {\n ref.current = element;\n }\n }}\n onClick={() => setOpen((_open) => !_open)}\n onKeyDown={onComboboxKeyDown}\n {...props}\n >\n {text}\n <ChevronDown width=\"24\" height=\"24\" />\n </button>\n <div className={borderClasses} />\n <div className={contentClasses}>\n <ul\n ref={listRef}\n id={contentId}\n role=\"listbox\"\n className={styles.content}\n >\n {values.map(([v, text]) => (\n <DropdownItem\n key={v}\n isDisplayed={open}\n isSelected={value === v}\n onClick={() => {\n setOpen(false);\n setValue(v);\n }}\n onKeyDown={(e) => onOptionKeyDown(e, v)}\n >\n {text}\n </DropdownItem>\n ))}\n </ul>\n </div>\n {!error && helpLabel && (\n <span className={styles.help}>{helpLabel}</span>\n )}\n {error && (\n <span className={styles.error}>\n <Error width=\"20\" height=\"20\" />\n {error}\n </span>\n )}\n </div>\n );\n },\n);\n\ntype DropdownItemProps = HTMLProps<HTMLLIElement> & {\n /**\n * Whether the dropdown item is selected.\n */\n isSelected: boolean;\n /**\n * Whether the dropdown item is displayed.\n */\n isDisplayed: boolean;\n /**\n * The text to display in the dropdown item.\n */\n children: string;\n};\n\n/**\n * A dropdown item component.\n */\nconst DropdownItem = memo(function DropdownItem({\n children,\n isSelected,\n isDisplayed,\n ...props\n}: DropdownItemProps) {\n const ref = useRef<HTMLLIElement>(null);\n\n // Focus the item if the dropdown is open and the item is already selected\n useEffect(() => {\n if (isSelected && isDisplayed) {\n ref.current?.focus();\n }\n }, [isSelected, isDisplayed]);\n\n return (\n <li\n tabIndex={0}\n role=\"option\"\n ref={ref}\n aria-selected={isSelected}\n {...props}\n >\n {children} {isSelected && <Check width=\"20\" height=\"20\" />}\n </li>\n );\n});\n\n/**\n * A hook to manage the open state of the dropdown.\n */\nfunction useOpen(): [\n boolean,\n Dispatch<SetStateAction<boolean>>,\n RefObject<HTMLDivElement | null>,\n] {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement | null>(null);\n\n // If the user clicks outside the dropdown, close it\n useEffect(() => {\n const closeIfOutside = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener(\"click\", closeIfOutside);\n return () => document.removeEventListener(\"click\", closeIfOutside);\n }, [setOpen]);\n\n return [open, setOpen, ref];\n}\n\n/**\n * A hook to manage the keyboard shortcuts of the dropdown.\n * @param open - the dropdown open state.\n * @param setOpen - the dropdown open state setter.\n * @param setValue - set the selected value and text\n */\nfunction useKeyboardShortcut(\n open: boolean,\n setOpen: Dispatch<SetStateAction<boolean>>,\n setValue: (value: string) => void,\n) {\n const listRef = useRef<HTMLUListElement>(null);\n const onComboboxKeyDown = useCallback(\n ({ key }: KeyboardEvent) => {\n switch (key) {\n // Enter and Space already managed because it's a button\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\":\n setOpen(true);\n // If open, focus the first element\n if (open) {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n }\n break;\n case \"ArrowUp\":\n setOpen(true);\n break;\n case \"Home\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n });\n break;\n }\n case \"End\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n });\n break;\n }\n }\n },\n [listRef, open, setOpen],\n );\n\n const onOptionKeyDown = useCallback(\n (evt: KeyboardEvent, value: string) => {\n const { key, altKey } = evt;\n evt.stopPropagation();\n evt.preventDefault();\n\n switch (key) {\n case \"Enter\":\n case \" \": {\n setValue(value);\n setOpen(false);\n break;\n }\n case \"Tab\":\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\": {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.nextElementSibling as HTMLElement)?.focus();\n }\n break;\n }\n case \"ArrowUp\": {\n if (altKey) {\n setValue(value);\n setOpen(false);\n } else {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.previousElementSibling as HTMLElement)?.focus();\n }\n }\n break;\n }\n case \"Home\": {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n break;\n }\n case \"End\": {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n break;\n }\n }\n },\n [listRef, setValue, setOpen],\n );\n\n return { listRef, onComboboxKeyDown, onOptionKeyDown };\n}\n"],"names":["forwardRef","Dropdown","useState","useMemo","useCallback","value","useRef","useEffect","styles","useId","jsxs","jsx","text","Error","memo","DropdownItem","Check"],"mappings":"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"}
1
+ {"version":3,"file":"Dropdown.cjs","sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["/*\nCopyright 2024 New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE files in the repository root for full details.\n*/\n\nimport ChevronDown from \"@vector-im/compound-design-tokens/assets/web/icons/chevron-down\";\nimport Check from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport Error from \"@vector-im/compound-design-tokens/assets/web/icons/error-solid\";\n\nimport React, {\n Dispatch,\n forwardRef,\n HTMLProps,\n memo,\n RefObject,\n SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n KeyboardEvent,\n useMemo,\n} from \"react\";\n\nimport classNames from \"classnames\";\n\nimport styles from \"./Dropdown.module.css\";\nimport { useId } from \"@floating-ui/react\";\n\ntype DropdownProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The controlled value of the dropdown.\n */\n value?: string;\n /**\n * The default value of the dropdown, used when uncontrolled.\n */\n defaultValue?: string;\n /**\n * The values of the dropdown.\n * [value, text]\n */\n values: [string, string][];\n /**\n * The placeholder text.\n */\n placeholder: string;\n /**\n * The label to display at the top of the dropdown\n */\n label: string;\n /**\n * The help label to display at the bottom of the dropdown\n */\n helpLabel?: string;\n /**\n * Callback for when the value changes.\n * @param value\n */\n onValueChange?: (value: string) => void;\n /**\n * The error message to display.\n */\n error?: string;\n};\n\n/**\n * The dropdown content.\n */\nexport const Dropdown = forwardRef<HTMLButtonElement, DropdownProps>(\n function Dropdown(\n {\n className,\n label,\n placeholder,\n helpLabel,\n onValueChange,\n error,\n value: controlledValue,\n defaultValue,\n values,\n ...props\n },\n ref,\n ) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const text = useMemo(\n () =>\n value === undefined\n ? placeholder\n : (values.find(([v]) => v === value)?.[1] ?? placeholder),\n [value, values, placeholder],\n );\n\n const setValue = useCallback(\n (value: string) => {\n setUncontrolledValue(value);\n onValueChange?.(value);\n },\n [setUncontrolledValue, onValueChange],\n );\n\n const [open, setOpen, dropdownRef] = useOpen();\n const { listRef, onComboboxKeyDown, onOptionKeyDown } = useKeyboardShortcut(\n open,\n setOpen,\n setValue,\n );\n\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n useEffect(() => {\n // Focus the button when the value is set\n // Test if the value is undefined to avoid focusing on the first render\n if (value !== undefined) buttonRef.current?.focus();\n }, [value]);\n\n const hasPlaceholder = text === placeholder;\n const buttonClasses = classNames({\n [styles.placeholder]: hasPlaceholder,\n });\n const borderClasses = classNames(styles.border, {\n [styles.open]: open,\n });\n const contentClasses = classNames(styles.content, {\n [styles.open]: open,\n });\n\n /**\n * Ids for accessibility.\n */\n const labelId = useId();\n const contentId = useId();\n\n return (\n <div\n ref={dropdownRef}\n className={classNames(className, styles.container)}\n aria-invalid={Boolean(error)}\n >\n <label id={labelId}>{label}</label>\n <button\n className={buttonClasses}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-labelledby={labelId}\n aria-controls={contentId}\n aria-expanded={open}\n ref={(element) => {\n // Private ref to focus the button\n buttonRef.current = element;\n // Handle forwarded ref\n if (typeof ref === \"function\") {\n ref(element);\n } else if (ref) {\n ref.current = element;\n }\n }}\n onClick={() => setOpen((_open) => !_open)}\n onKeyDown={onComboboxKeyDown}\n {...props}\n >\n {text}\n <ChevronDown width=\"24\" height=\"24\" />\n </button>\n <div className={borderClasses} />\n <div className={contentClasses}>\n <ul\n ref={listRef}\n id={contentId}\n role=\"listbox\"\n className={styles.content}\n >\n {values.map(([v, text]) => (\n <DropdownItem\n key={v}\n isDisplayed={open}\n isSelected={value === v}\n onClick={() => {\n setOpen(false);\n setValue(v);\n }}\n onKeyDown={(e) => onOptionKeyDown(e, v)}\n >\n {text}\n </DropdownItem>\n ))}\n </ul>\n </div>\n {!error && helpLabel && (\n <span className={styles.help}>{helpLabel}</span>\n )}\n {error && (\n <span className={styles.error}>\n <Error width=\"20\" height=\"20\" />\n {error}\n </span>\n )}\n </div>\n );\n },\n);\n\ntype DropdownItemProps = HTMLProps<HTMLLIElement> & {\n /**\n * Whether the dropdown item is selected.\n */\n isSelected: boolean;\n /**\n * Whether the dropdown item is displayed.\n */\n isDisplayed: boolean;\n /**\n * The text to display in the dropdown item.\n */\n children: string;\n};\n\n/**\n * A dropdown item component.\n */\nconst DropdownItem = memo(function DropdownItem({\n children,\n isSelected,\n isDisplayed,\n ...props\n}: DropdownItemProps) {\n const ref = useRef<HTMLLIElement>(null);\n\n // Focus the item if the dropdown is open and the item is already selected\n useEffect(() => {\n if (isSelected && isDisplayed) {\n ref.current?.focus();\n }\n }, [isSelected, isDisplayed]);\n\n return (\n <li\n tabIndex={0}\n role=\"option\"\n ref={ref}\n aria-selected={isSelected}\n {...props}\n >\n {children} {isSelected && <Check width=\"20\" height=\"20\" />}\n </li>\n );\n});\n\n/**\n * A hook to manage the open state of the dropdown.\n */\nfunction useOpen(): [\n boolean,\n Dispatch<SetStateAction<boolean>>,\n RefObject<HTMLDivElement | null>,\n] {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement | null>(null);\n\n // If the user clicks outside the dropdown, close it\n useEffect(() => {\n const closeIfOutside = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener(\"click\", closeIfOutside);\n return () => document.removeEventListener(\"click\", closeIfOutside);\n }, [setOpen]);\n\n return [open, setOpen, ref];\n}\n\n/**\n * A hook to manage the keyboard shortcuts of the dropdown.\n * @param open - the dropdown open state.\n * @param setOpen - the dropdown open state setter.\n * @param setValue - set the selected value and text\n */\nfunction useKeyboardShortcut(\n open: boolean,\n setOpen: Dispatch<SetStateAction<boolean>>,\n setValue: (value: string) => void,\n) {\n const listRef = useRef<HTMLUListElement>(null);\n const onComboboxKeyDown = useCallback(\n ({ key }: KeyboardEvent) => {\n switch (key) {\n // Enter and Space already managed because it's a button\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\":\n setOpen(true);\n // If open, focus the first element\n if (open) {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n }\n break;\n case \"ArrowUp\":\n setOpen(true);\n break;\n case \"Home\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n });\n break;\n }\n case \"End\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n });\n break;\n }\n }\n },\n [listRef, open, setOpen],\n );\n\n const onOptionKeyDown = useCallback(\n (evt: KeyboardEvent, value: string) => {\n const { key, altKey } = evt;\n evt.stopPropagation();\n evt.preventDefault();\n\n switch (key) {\n case \"Enter\":\n case \" \": {\n setValue(value);\n setOpen(false);\n break;\n }\n case \"Tab\":\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\": {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.nextElementSibling as HTMLElement)?.focus();\n }\n break;\n }\n case \"ArrowUp\": {\n if (altKey) {\n setValue(value);\n setOpen(false);\n } else {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.previousElementSibling as HTMLElement)?.focus();\n }\n }\n break;\n }\n case \"Home\": {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n break;\n }\n case \"End\": {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n break;\n }\n }\n },\n [listRef, setValue, setOpen],\n );\n\n return { listRef, onComboboxKeyDown, onOptionKeyDown };\n}\n"],"names":["forwardRef","Dropdown","useState","useMemo","useCallback","value","useRef","useEffect","styles","useId","jsxs","jsx","text","Error","memo","DropdownItem","Check"],"mappings":"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"}
@@ -169,6 +169,7 @@ function useKeyboardShortcut(open, setOpen, setValue) {
169
169
  const onComboboxKeyDown = useCallback(
170
170
  ({ key }) => {
171
171
  switch (key) {
172
+ // Enter and Space already managed because it's a button
172
173
  case "Escape":
173
174
  setOpen(false);
174
175
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.js","sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["/*\nCopyright 2024 New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE files in the repository root for full details.\n*/\n\nimport ChevronDown from \"@vector-im/compound-design-tokens/assets/web/icons/chevron-down\";\nimport Check from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport Error from \"@vector-im/compound-design-tokens/assets/web/icons/error-solid\";\n\nimport React, {\n Dispatch,\n forwardRef,\n HTMLProps,\n memo,\n RefObject,\n SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n KeyboardEvent,\n useMemo,\n} from \"react\";\n\nimport classNames from \"classnames\";\n\nimport styles from \"./Dropdown.module.css\";\nimport { useId } from \"@floating-ui/react\";\n\ntype DropdownProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The controlled value of the dropdown.\n */\n value?: string;\n /**\n * The default value of the dropdown, used when uncontrolled.\n */\n defaultValue?: string;\n /**\n * The values of the dropdown.\n * [value, text]\n */\n values: [string, string][];\n /**\n * The placeholder text.\n */\n placeholder: string;\n /**\n * The label to display at the top of the dropdown\n */\n label: string;\n /**\n * The help label to display at the bottom of the dropdown\n */\n helpLabel?: string;\n /**\n * Callback for when the value changes.\n * @param value\n */\n onValueChange?: (value: string) => void;\n /**\n * The error message to display.\n */\n error?: string;\n};\n\n/**\n * The dropdown content.\n */\nexport const Dropdown = forwardRef<HTMLButtonElement, DropdownProps>(\n function Dropdown(\n {\n className,\n label,\n placeholder,\n helpLabel,\n onValueChange,\n error,\n value: controlledValue,\n defaultValue,\n values,\n ...props\n },\n ref,\n ) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const text = useMemo(\n () =>\n value === undefined\n ? placeholder\n : (values.find(([v]) => v === value)?.[1] ?? placeholder),\n [value, values, placeholder],\n );\n\n const setValue = useCallback(\n (value: string) => {\n setUncontrolledValue(value);\n onValueChange?.(value);\n },\n [setUncontrolledValue, onValueChange],\n );\n\n const [open, setOpen, dropdownRef] = useOpen();\n const { listRef, onComboboxKeyDown, onOptionKeyDown } = useKeyboardShortcut(\n open,\n setOpen,\n setValue,\n );\n\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n useEffect(() => {\n // Focus the button when the value is set\n // Test if the value is undefined to avoid focusing on the first render\n if (value !== undefined) buttonRef.current?.focus();\n }, [value]);\n\n const hasPlaceholder = text === placeholder;\n const buttonClasses = classNames({\n [styles.placeholder]: hasPlaceholder,\n });\n const borderClasses = classNames(styles.border, {\n [styles.open]: open,\n });\n const contentClasses = classNames(styles.content, {\n [styles.open]: open,\n });\n\n /**\n * Ids for accessibility.\n */\n const labelId = useId();\n const contentId = useId();\n\n return (\n <div\n ref={dropdownRef}\n className={classNames(className, styles.container)}\n aria-invalid={Boolean(error)}\n >\n <label id={labelId}>{label}</label>\n <button\n className={buttonClasses}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-labelledby={labelId}\n aria-controls={contentId}\n aria-expanded={open}\n ref={(element) => {\n // Private ref to focus the button\n buttonRef.current = element;\n // Handle forwarded ref\n if (typeof ref === \"function\") {\n ref(element);\n } else if (ref) {\n ref.current = element;\n }\n }}\n onClick={() => setOpen((_open) => !_open)}\n onKeyDown={onComboboxKeyDown}\n {...props}\n >\n {text}\n <ChevronDown width=\"24\" height=\"24\" />\n </button>\n <div className={borderClasses} />\n <div className={contentClasses}>\n <ul\n ref={listRef}\n id={contentId}\n role=\"listbox\"\n className={styles.content}\n >\n {values.map(([v, text]) => (\n <DropdownItem\n key={v}\n isDisplayed={open}\n isSelected={value === v}\n onClick={() => {\n setOpen(false);\n setValue(v);\n }}\n onKeyDown={(e) => onOptionKeyDown(e, v)}\n >\n {text}\n </DropdownItem>\n ))}\n </ul>\n </div>\n {!error && helpLabel && (\n <span className={styles.help}>{helpLabel}</span>\n )}\n {error && (\n <span className={styles.error}>\n <Error width=\"20\" height=\"20\" />\n {error}\n </span>\n )}\n </div>\n );\n },\n);\n\ntype DropdownItemProps = HTMLProps<HTMLLIElement> & {\n /**\n * Whether the dropdown item is selected.\n */\n isSelected: boolean;\n /**\n * Whether the dropdown item is displayed.\n */\n isDisplayed: boolean;\n /**\n * The text to display in the dropdown item.\n */\n children: string;\n};\n\n/**\n * A dropdown item component.\n */\nconst DropdownItem = memo(function DropdownItem({\n children,\n isSelected,\n isDisplayed,\n ...props\n}: DropdownItemProps) {\n const ref = useRef<HTMLLIElement>(null);\n\n // Focus the item if the dropdown is open and the item is already selected\n useEffect(() => {\n if (isSelected && isDisplayed) {\n ref.current?.focus();\n }\n }, [isSelected, isDisplayed]);\n\n return (\n <li\n tabIndex={0}\n role=\"option\"\n ref={ref}\n aria-selected={isSelected}\n {...props}\n >\n {children} {isSelected && <Check width=\"20\" height=\"20\" />}\n </li>\n );\n});\n\n/**\n * A hook to manage the open state of the dropdown.\n */\nfunction useOpen(): [\n boolean,\n Dispatch<SetStateAction<boolean>>,\n RefObject<HTMLDivElement | null>,\n] {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement | null>(null);\n\n // If the user clicks outside the dropdown, close it\n useEffect(() => {\n const closeIfOutside = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener(\"click\", closeIfOutside);\n return () => document.removeEventListener(\"click\", closeIfOutside);\n }, [setOpen]);\n\n return [open, setOpen, ref];\n}\n\n/**\n * A hook to manage the keyboard shortcuts of the dropdown.\n * @param open - the dropdown open state.\n * @param setOpen - the dropdown open state setter.\n * @param setValue - set the selected value and text\n */\nfunction useKeyboardShortcut(\n open: boolean,\n setOpen: Dispatch<SetStateAction<boolean>>,\n setValue: (value: string) => void,\n) {\n const listRef = useRef<HTMLUListElement>(null);\n const onComboboxKeyDown = useCallback(\n ({ key }: KeyboardEvent) => {\n switch (key) {\n // Enter and Space already managed because it's a button\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\":\n setOpen(true);\n // If open, focus the first element\n if (open) {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n }\n break;\n case \"ArrowUp\":\n setOpen(true);\n break;\n case \"Home\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n });\n break;\n }\n case \"End\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n });\n break;\n }\n }\n },\n [listRef, open, setOpen],\n );\n\n const onOptionKeyDown = useCallback(\n (evt: KeyboardEvent, value: string) => {\n const { key, altKey } = evt;\n evt.stopPropagation();\n evt.preventDefault();\n\n switch (key) {\n case \"Enter\":\n case \" \": {\n setValue(value);\n setOpen(false);\n break;\n }\n case \"Tab\":\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\": {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.nextElementSibling as HTMLElement)?.focus();\n }\n break;\n }\n case \"ArrowUp\": {\n if (altKey) {\n setValue(value);\n setOpen(false);\n } else {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.previousElementSibling as HTMLElement)?.focus();\n }\n }\n break;\n }\n case \"Home\": {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n break;\n }\n case \"End\": {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n break;\n }\n }\n },\n [listRef, setValue, setOpen],\n );\n\n return { listRef, onComboboxKeyDown, onOptionKeyDown };\n}\n"],"names":["Dropdown","value","text","Error","DropdownItem","Check"],"mappings":"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"}
1
+ {"version":3,"file":"Dropdown.js","sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["/*\nCopyright 2024 New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE files in the repository root for full details.\n*/\n\nimport ChevronDown from \"@vector-im/compound-design-tokens/assets/web/icons/chevron-down\";\nimport Check from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport Error from \"@vector-im/compound-design-tokens/assets/web/icons/error-solid\";\n\nimport React, {\n Dispatch,\n forwardRef,\n HTMLProps,\n memo,\n RefObject,\n SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n KeyboardEvent,\n useMemo,\n} from \"react\";\n\nimport classNames from \"classnames\";\n\nimport styles from \"./Dropdown.module.css\";\nimport { useId } from \"@floating-ui/react\";\n\ntype DropdownProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The controlled value of the dropdown.\n */\n value?: string;\n /**\n * The default value of the dropdown, used when uncontrolled.\n */\n defaultValue?: string;\n /**\n * The values of the dropdown.\n * [value, text]\n */\n values: [string, string][];\n /**\n * The placeholder text.\n */\n placeholder: string;\n /**\n * The label to display at the top of the dropdown\n */\n label: string;\n /**\n * The help label to display at the bottom of the dropdown\n */\n helpLabel?: string;\n /**\n * Callback for when the value changes.\n * @param value\n */\n onValueChange?: (value: string) => void;\n /**\n * The error message to display.\n */\n error?: string;\n};\n\n/**\n * The dropdown content.\n */\nexport const Dropdown = forwardRef<HTMLButtonElement, DropdownProps>(\n function Dropdown(\n {\n className,\n label,\n placeholder,\n helpLabel,\n onValueChange,\n error,\n value: controlledValue,\n defaultValue,\n values,\n ...props\n },\n ref,\n ) {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const text = useMemo(\n () =>\n value === undefined\n ? placeholder\n : (values.find(([v]) => v === value)?.[1] ?? placeholder),\n [value, values, placeholder],\n );\n\n const setValue = useCallback(\n (value: string) => {\n setUncontrolledValue(value);\n onValueChange?.(value);\n },\n [setUncontrolledValue, onValueChange],\n );\n\n const [open, setOpen, dropdownRef] = useOpen();\n const { listRef, onComboboxKeyDown, onOptionKeyDown } = useKeyboardShortcut(\n open,\n setOpen,\n setValue,\n );\n\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n useEffect(() => {\n // Focus the button when the value is set\n // Test if the value is undefined to avoid focusing on the first render\n if (value !== undefined) buttonRef.current?.focus();\n }, [value]);\n\n const hasPlaceholder = text === placeholder;\n const buttonClasses = classNames({\n [styles.placeholder]: hasPlaceholder,\n });\n const borderClasses = classNames(styles.border, {\n [styles.open]: open,\n });\n const contentClasses = classNames(styles.content, {\n [styles.open]: open,\n });\n\n /**\n * Ids for accessibility.\n */\n const labelId = useId();\n const contentId = useId();\n\n return (\n <div\n ref={dropdownRef}\n className={classNames(className, styles.container)}\n aria-invalid={Boolean(error)}\n >\n <label id={labelId}>{label}</label>\n <button\n className={buttonClasses}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-labelledby={labelId}\n aria-controls={contentId}\n aria-expanded={open}\n ref={(element) => {\n // Private ref to focus the button\n buttonRef.current = element;\n // Handle forwarded ref\n if (typeof ref === \"function\") {\n ref(element);\n } else if (ref) {\n ref.current = element;\n }\n }}\n onClick={() => setOpen((_open) => !_open)}\n onKeyDown={onComboboxKeyDown}\n {...props}\n >\n {text}\n <ChevronDown width=\"24\" height=\"24\" />\n </button>\n <div className={borderClasses} />\n <div className={contentClasses}>\n <ul\n ref={listRef}\n id={contentId}\n role=\"listbox\"\n className={styles.content}\n >\n {values.map(([v, text]) => (\n <DropdownItem\n key={v}\n isDisplayed={open}\n isSelected={value === v}\n onClick={() => {\n setOpen(false);\n setValue(v);\n }}\n onKeyDown={(e) => onOptionKeyDown(e, v)}\n >\n {text}\n </DropdownItem>\n ))}\n </ul>\n </div>\n {!error && helpLabel && (\n <span className={styles.help}>{helpLabel}</span>\n )}\n {error && (\n <span className={styles.error}>\n <Error width=\"20\" height=\"20\" />\n {error}\n </span>\n )}\n </div>\n );\n },\n);\n\ntype DropdownItemProps = HTMLProps<HTMLLIElement> & {\n /**\n * Whether the dropdown item is selected.\n */\n isSelected: boolean;\n /**\n * Whether the dropdown item is displayed.\n */\n isDisplayed: boolean;\n /**\n * The text to display in the dropdown item.\n */\n children: string;\n};\n\n/**\n * A dropdown item component.\n */\nconst DropdownItem = memo(function DropdownItem({\n children,\n isSelected,\n isDisplayed,\n ...props\n}: DropdownItemProps) {\n const ref = useRef<HTMLLIElement>(null);\n\n // Focus the item if the dropdown is open and the item is already selected\n useEffect(() => {\n if (isSelected && isDisplayed) {\n ref.current?.focus();\n }\n }, [isSelected, isDisplayed]);\n\n return (\n <li\n tabIndex={0}\n role=\"option\"\n ref={ref}\n aria-selected={isSelected}\n {...props}\n >\n {children} {isSelected && <Check width=\"20\" height=\"20\" />}\n </li>\n );\n});\n\n/**\n * A hook to manage the open state of the dropdown.\n */\nfunction useOpen(): [\n boolean,\n Dispatch<SetStateAction<boolean>>,\n RefObject<HTMLDivElement | null>,\n] {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement | null>(null);\n\n // If the user clicks outside the dropdown, close it\n useEffect(() => {\n const closeIfOutside = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener(\"click\", closeIfOutside);\n return () => document.removeEventListener(\"click\", closeIfOutside);\n }, [setOpen]);\n\n return [open, setOpen, ref];\n}\n\n/**\n * A hook to manage the keyboard shortcuts of the dropdown.\n * @param open - the dropdown open state.\n * @param setOpen - the dropdown open state setter.\n * @param setValue - set the selected value and text\n */\nfunction useKeyboardShortcut(\n open: boolean,\n setOpen: Dispatch<SetStateAction<boolean>>,\n setValue: (value: string) => void,\n) {\n const listRef = useRef<HTMLUListElement>(null);\n const onComboboxKeyDown = useCallback(\n ({ key }: KeyboardEvent) => {\n switch (key) {\n // Enter and Space already managed because it's a button\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\":\n setOpen(true);\n // If open, focus the first element\n if (open) {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n }\n break;\n case \"ArrowUp\":\n setOpen(true);\n break;\n case \"Home\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n });\n break;\n }\n case \"End\": {\n setOpen(true);\n // Wait for the dropdown to be opened\n Promise.resolve().then(() => {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n });\n break;\n }\n }\n },\n [listRef, open, setOpen],\n );\n\n const onOptionKeyDown = useCallback(\n (evt: KeyboardEvent, value: string) => {\n const { key, altKey } = evt;\n evt.stopPropagation();\n evt.preventDefault();\n\n switch (key) {\n case \"Enter\":\n case \" \": {\n setValue(value);\n setOpen(false);\n break;\n }\n case \"Tab\":\n case \"Escape\":\n setOpen(false);\n break;\n case \"ArrowDown\": {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.nextElementSibling as HTMLElement)?.focus();\n }\n break;\n }\n case \"ArrowUp\": {\n if (altKey) {\n setValue(value);\n setOpen(false);\n } else {\n const currentFocus = document.activeElement;\n if (listRef.current?.contains(currentFocus) && currentFocus) {\n (currentFocus.previousElementSibling as HTMLElement)?.focus();\n }\n }\n break;\n }\n case \"Home\": {\n (listRef.current?.firstElementChild as HTMLElement)?.focus();\n break;\n }\n case \"End\": {\n (listRef.current?.lastElementChild as HTMLElement)?.focus();\n break;\n }\n }\n },\n [listRef, setValue, setOpen],\n );\n\n return { listRef, onComboboxKeyDown, onOptionKeyDown };\n}\n"],"names":["Dropdown","value","text","Error","DropdownItem","Check"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const MenuItem = require("./MenuItem.cjs");
6
+ const Radio = require("../Form/Controls/Radio/Radio.cjs");
7
+ const RadioMenuItem = React.forwardRef(
8
+ function RadioMenuItem2({ className, label, onSelect, checked, disabled }, ref) {
9
+ const toggleId = React.useId();
10
+ const onChange = React.useCallback(() => {
11
+ }, []);
12
+ return /* @__PURE__ */ jsxRuntime.jsx(
13
+ MenuItem.MenuItem,
14
+ {
15
+ as: "div",
16
+ role: "menuitemradio",
17
+ "aria-checked": checked,
18
+ className,
19
+ label,
20
+ onSelect,
21
+ disabled,
22
+ Icon: /* @__PURE__ */ jsxRuntime.jsx(
23
+ Radio.RadioInput,
24
+ {
25
+ id: toggleId,
26
+ ref,
27
+ "aria-hidden": true,
28
+ checked,
29
+ disabled,
30
+ onChange
31
+ }
32
+ )
33
+ }
34
+ );
35
+ }
36
+ );
37
+ exports.RadioMenuItem = RadioMenuItem;
38
+ //# sourceMappingURL=RadioMenuItem.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioMenuItem.cjs","sources":["../../../src/components/Menu/RadioMenuItem.tsx"],"sourcesContent":["/*\n * Copyright 2025 New Vector Ltd\n *\n * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\n * Please see LICENSE files in the repository root for full details.\n */\n\nimport React, { ComponentProps, forwardRef, useCallback, useId } from \"react\";\nimport { MenuItem } from \"./MenuItem\";\nimport { RadioInput } from \"../Form\";\n\ntype Props = Pick<\n ComponentProps<typeof MenuItem>,\n \"className\" | \"label\" | \"onSelect\" | \"disabled\"\n> & {\n /**\n * Whether the radio is checked.\n */\n checked: boolean;\n};\n\n/**\n * A menu item with a radio control.\n * Must be used within a compound Menu or other `menu` or `menubar` aria role subtree.\n */\nexport const RadioMenuItem = forwardRef<HTMLInputElement, Props>(\n function RadioMenuItem(\n { className, label, onSelect, checked, disabled },\n ref,\n ) {\n const toggleId = useId();\n // The radio is controlled and we intend to ignore its events. We do need\n // to at least set onChange though to make React happy.\n const onChange = useCallback(() => {}, []);\n\n // <label> elements are not allowed to have a role like menuitemradio, so\n // we must instead use a plain <div> for the menu item and use aria-checked\n // etc. to communicate its state.\n return (\n <MenuItem\n as=\"div\"\n role=\"menuitemradio\"\n aria-checked={checked}\n className={className}\n label={label}\n onSelect={onSelect}\n disabled={disabled}\n Icon={\n <RadioInput\n id={toggleId}\n ref={ref}\n // This is purely cosmetic; really the whole MenuItem is the toggle.\n aria-hidden\n checked={checked}\n disabled={disabled}\n onChange={onChange}\n />\n }\n ></MenuItem>\n );\n },\n);\n"],"names":["forwardRef","RadioMenuItem","useId","useCallback","jsx","MenuItem","RadioInput"],"mappings":";;;;;;AAyBO,MAAM,gBAAgBA,MAAA;AAAA,EAC3B,SAASC,eACP,EAAE,WAAW,OAAO,UAAU,SAAS,SAAS,GAChD,KACA;AACA,UAAM,WAAWC,MAAAA,MAAM;AAGjB,UAAA,WAAWC,MAAAA,YAAY,MAAM;AAAA,IAAC,GAAG,EAAE;AAMvC,WAAAC,2BAAA;AAAA,MAACC,SAAA;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,MAAK;AAAA,QACL,gBAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MACED,2BAAA;AAAA,UAACE,MAAA;AAAA,UAAA;AAAA,YACC,IAAI;AAAA,YACJ;AAAA,YAEA,eAAW;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAEH;AAAA,EAAA;AAGP;;"}
@@ -0,0 +1,23 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * A menu item with a radio control.
4
+ * Must be used within a compound Menu or other `menu` or `menubar` aria role subtree.
5
+ */
6
+ export declare const RadioMenuItem: React.ForwardRefExoticComponent<Pick<{
7
+ as?: ("a" | "button" | "div") | undefined;
8
+ className?: string;
9
+ Icon?: React.ComponentType<React.SVGAttributes<SVGElement>> | React.ReactElement;
10
+ label: string | null;
11
+ labelProps?: React.ComponentPropsWithoutRef<typeof import('../..').Text>;
12
+ onSelect: ((e: Event) => void) | null;
13
+ onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement> | undefined;
14
+ kind?: "primary" | "critical";
15
+ disabled?: boolean;
16
+ hideChevron?: boolean;
17
+ } & Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> | Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> | Omit<React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref">, "onClick" | "onSelect">, "label" | "className" | "disabled" | "onSelect"> & {
18
+ /**
19
+ * Whether the radio is checked.
20
+ */
21
+ checked: boolean;
22
+ } & React.RefAttributes<HTMLInputElement>>;
23
+ //# sourceMappingURL=RadioMenuItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioMenuItem.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/RadioMenuItem.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAyD,MAAM,OAAO,CAAC;AAc9E;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;IAVxB;;OAEG;aACM,OAAO;0CA2CjB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useId, useCallback } from "react";
3
+ import { MenuItem } from "./MenuItem.js";
4
+ import { RadioInput } from "../Form/Controls/Radio/Radio.js";
5
+ const RadioMenuItem = forwardRef(
6
+ function RadioMenuItem2({ className, label, onSelect, checked, disabled }, ref) {
7
+ const toggleId = useId();
8
+ const onChange = useCallback(() => {
9
+ }, []);
10
+ return /* @__PURE__ */ jsx(
11
+ MenuItem,
12
+ {
13
+ as: "div",
14
+ role: "menuitemradio",
15
+ "aria-checked": checked,
16
+ className,
17
+ label,
18
+ onSelect,
19
+ disabled,
20
+ Icon: /* @__PURE__ */ jsx(
21
+ RadioInput,
22
+ {
23
+ id: toggleId,
24
+ ref,
25
+ "aria-hidden": true,
26
+ checked,
27
+ disabled,
28
+ onChange
29
+ }
30
+ )
31
+ }
32
+ );
33
+ }
34
+ );
35
+ export {
36
+ RadioMenuItem
37
+ };
38
+ //# sourceMappingURL=RadioMenuItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadioMenuItem.js","sources":["../../../src/components/Menu/RadioMenuItem.tsx"],"sourcesContent":["/*\n * Copyright 2025 New Vector Ltd\n *\n * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\n * Please see LICENSE files in the repository root for full details.\n */\n\nimport React, { ComponentProps, forwardRef, useCallback, useId } from \"react\";\nimport { MenuItem } from \"./MenuItem\";\nimport { RadioInput } from \"../Form\";\n\ntype Props = Pick<\n ComponentProps<typeof MenuItem>,\n \"className\" | \"label\" | \"onSelect\" | \"disabled\"\n> & {\n /**\n * Whether the radio is checked.\n */\n checked: boolean;\n};\n\n/**\n * A menu item with a radio control.\n * Must be used within a compound Menu or other `menu` or `menubar` aria role subtree.\n */\nexport const RadioMenuItem = forwardRef<HTMLInputElement, Props>(\n function RadioMenuItem(\n { className, label, onSelect, checked, disabled },\n ref,\n ) {\n const toggleId = useId();\n // The radio is controlled and we intend to ignore its events. We do need\n // to at least set onChange though to make React happy.\n const onChange = useCallback(() => {}, []);\n\n // <label> elements are not allowed to have a role like menuitemradio, so\n // we must instead use a plain <div> for the menu item and use aria-checked\n // etc. to communicate its state.\n return (\n <MenuItem\n as=\"div\"\n role=\"menuitemradio\"\n aria-checked={checked}\n className={className}\n label={label}\n onSelect={onSelect}\n disabled={disabled}\n Icon={\n <RadioInput\n id={toggleId}\n ref={ref}\n // This is purely cosmetic; really the whole MenuItem is the toggle.\n aria-hidden\n checked={checked}\n disabled={disabled}\n onChange={onChange}\n />\n }\n ></MenuItem>\n );\n },\n);\n"],"names":["RadioMenuItem"],"mappings":";;;;AAyBO,MAAM,gBAAgB;AAAA,EAC3B,SAASA,eACP,EAAE,WAAW,OAAO,UAAU,SAAS,SAAS,GAChD,KACA;AACA,UAAM,WAAW,MAAM;AAGjB,UAAA,WAAW,YAAY,MAAM;AAAA,IAAC,GAAG,EAAE;AAMvC,WAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,MAAK;AAAA,QACL,gBAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MACE;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAI;AAAA,YACJ;AAAA,YAEA,eAAW;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAEH;AAAA,EAAA;AAGP;"}
package/dist/index.cjs CHANGED
@@ -14,6 +14,7 @@ const Link = require("./components/Link/Link.cjs");
14
14
  const Menu = require("./components/Menu/Menu.cjs");
15
15
  const MenuItem = require("./components/Menu/MenuItem.cjs");
16
16
  const MenuTitle = require("./components/Menu/MenuTitle.cjs");
17
+ const RadioMenuItem = require("./components/Menu/RadioMenuItem.cjs");
17
18
  const Progress = require("./components/Progress/Progress.cjs");
18
19
  const Search = require("./components/Search/Search.cjs");
19
20
  const Separator = require("./components/Separator/Separator.cjs");
@@ -76,6 +77,7 @@ exports.Link = Link.Link;
76
77
  exports.Menu = Menu.Menu;
77
78
  exports.MenuItem = MenuItem.MenuItem;
78
79
  exports.MenuTitle = MenuTitle.MenuTitle;
80
+ exports.RadioMenuItem = RadioMenuItem.RadioMenuItem;
79
81
  exports.Progress = Progress.Progress;
80
82
  exports.Search = Search.Search;
81
83
  exports.Separator = Separator.Separator;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { NavBar, NavItem } from './components/Nav';
18
18
  export { Menu } from './components/Menu/Menu';
19
19
  export { MenuItem } from './components/Menu/MenuItem';
20
20
  export { MenuTitle } from './components/Menu/MenuTitle';
21
+ export { RadioMenuItem } from './components/Menu/RadioMenuItem';
21
22
  export { Progress } from './components/Progress/Progress';
22
23
  export { Search } from './components/Search/Search';
23
24
  export { Separator } from './components/Separator/Separator';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EACL,OAAO,EACP,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,EACF,EAAE,GACH,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,+CAA+C,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAE1E,OAAO,EACL,WAAW,EAEX,WAAW,IAAI,OAAO,EACtB,SAAS,EACT,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,EACb,UAAU,EACV,QAAQ,EACR,eAAe,EACf,aAAa,EAEb,aAAa,IAAI,QAAQ,EACzB,YAAY,EACZ,UAAU,EAEV,UAAU,IAAI,KAAK,EACnB,aAAa,EACb,WAAW,EAEX,WAAW,IAAI,MAAM,EACrB,IAAI,EACJ,MAAM,EACN,OAAO,EACP,YAAY,EACZ,WAAW,EACX,aAAa,EACb,KAAK,EACL,WAAW,EACX,KAAK,EACL,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAC;AAE1C;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { Link } from "./components/Link/Link.js";
12
12
  import { Menu } from "./components/Menu/Menu.js";
13
13
  import { MenuItem } from "./components/Menu/MenuItem.js";
14
14
  import { MenuTitle } from "./components/Menu/MenuTitle.js";
15
+ import { RadioMenuItem } from "./components/Menu/RadioMenuItem.js";
15
16
  import { Progress } from "./components/Progress/Progress.js";
16
17
  import { Search } from "./components/Search/Search.js";
17
18
  import { Separator } from "./components/Separator/Separator.js";
@@ -107,6 +108,7 @@ export {
107
108
  RadioInput as Radio,
108
109
  RadioControl,
109
110
  RadioInput2 as RadioInput,
111
+ RadioMenuItem,
110
112
  ReleaseAnnouncement,
111
113
  Root,
112
114
  Search,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vector-im/compound-web",
3
- "version": "7.10.2",
3
+ "version": "7.11.0",
4
4
  "description": "Compound components for the Web",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -76,15 +76,15 @@
76
76
  "@typescript-eslint/parser": "^8.0.0",
77
77
  "@vector-im/compound-design-tokens": "^4.0.0",
78
78
  "@vitejs/plugin-react": "^4.0.4",
79
- "@vitest/coverage-v8": "^2.0.0",
79
+ "@vitest/coverage-v8": "^3.0.0",
80
80
  "browserslist-to-esbuild": "^2.0.0",
81
81
  "eslint": "^8.49.0",
82
- "eslint-config-prettier": "^9.0.0",
82
+ "eslint-config-prettier": "^10.0.0",
83
83
  "eslint-plugin-matrix-org": "^1.2.1",
84
84
  "eslint-plugin-prettier": "^5.0.0",
85
85
  "eslint-plugin-react": "^7.33.2",
86
86
  "eslint-plugin-storybook": "^0.11.0",
87
- "jsdom": "^25.0.0",
87
+ "jsdom": "^26.0.0",
88
88
  "prettier": "3.4.2",
89
89
  "react": "^19.0.0",
90
90
  "react-dom": "^19.0.0",
@@ -98,9 +98,9 @@
98
98
  "stylelint-use-logical": "^2.1.0",
99
99
  "stylelint-value-no-unknown-custom-properties": "^6.0.0",
100
100
  "typescript": "^5.2.2",
101
- "vite": "^5.2.11",
101
+ "vite": "^6.0.0",
102
102
  "vite-plugin-dts": "^4.0.0",
103
- "vitest": "^2.0.0"
103
+ "vitest": "^3.0.0"
104
104
  },
105
105
  "dependencies": {
106
106
  "@floating-ui/react": "^0.27.0",
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Copyright 2025 New Vector Ltd
3
+ *
4
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5
+ * Please see LICENSE files in the repository root for full details.
6
+ */
7
+
8
+ import React, { ComponentProps, forwardRef, useCallback, useId } from "react";
9
+ import { MenuItem } from "./MenuItem";
10
+ import { RadioInput } from "../Form";
11
+
12
+ type Props = Pick<
13
+ ComponentProps<typeof MenuItem>,
14
+ "className" | "label" | "onSelect" | "disabled"
15
+ > & {
16
+ /**
17
+ * Whether the radio is checked.
18
+ */
19
+ checked: boolean;
20
+ };
21
+
22
+ /**
23
+ * A menu item with a radio control.
24
+ * Must be used within a compound Menu or other `menu` or `menubar` aria role subtree.
25
+ */
26
+ export const RadioMenuItem = forwardRef<HTMLInputElement, Props>(
27
+ function RadioMenuItem(
28
+ { className, label, onSelect, checked, disabled },
29
+ ref,
30
+ ) {
31
+ const toggleId = useId();
32
+ // The radio is controlled and we intend to ignore its events. We do need
33
+ // to at least set onChange though to make React happy.
34
+ const onChange = useCallback(() => {}, []);
35
+
36
+ // <label> elements are not allowed to have a role like menuitemradio, so
37
+ // we must instead use a plain <div> for the menu item and use aria-checked
38
+ // etc. to communicate its state.
39
+ return (
40
+ <MenuItem
41
+ as="div"
42
+ role="menuitemradio"
43
+ aria-checked={checked}
44
+ className={className}
45
+ label={label}
46
+ onSelect={onSelect}
47
+ disabled={disabled}
48
+ Icon={
49
+ <RadioInput
50
+ id={toggleId}
51
+ ref={ref}
52
+ // This is purely cosmetic; really the whole MenuItem is the toggle.
53
+ aria-hidden
54
+ checked={checked}
55
+ disabled={disabled}
56
+ onChange={onChange}
57
+ />
58
+ }
59
+ ></MenuItem>
60
+ );
61
+ },
62
+ );
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ export { NavBar, NavItem } from "./components/Nav";
35
35
  export { Menu } from "./components/Menu/Menu";
36
36
  export { MenuItem } from "./components/Menu/MenuItem";
37
37
  export { MenuTitle } from "./components/Menu/MenuTitle";
38
+ export { RadioMenuItem } from "./components/Menu/RadioMenuItem";
38
39
  export { Progress } from "./components/Progress/Progress";
39
40
  export { Search } from "./components/Search/Search";
40
41
  export { Separator } from "./components/Separator/Separator";
@@ -956,6 +956,125 @@ button._item_dyt4i_8 {
956
956
  color: var(--cpd-color-text-disabled);
957
957
  }
958
958
  /*
959
+ Copyright 2025 New Vector Ltd.
960
+ Copyright 2023 The Matrix.org Foundation C.I.C.
961
+ Copyright 2023 New Vector Ltd
962
+
963
+ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
964
+ Please see LICENSE files in the repository root for full details.
965
+ */
966
+
967
+ ._container_1e0uz_10 {
968
+ --size: 20px;
969
+
970
+ display: grid;
971
+ inline-size: var(--size);
972
+ block-size: var(--size);
973
+ }
974
+
975
+ ._input_1e0uz_18,
976
+ ._ui_1e0uz_19 {
977
+ box-sizing: border-box;
978
+ grid-area: 1/1;
979
+ inline-size: var(--size);
980
+ block-size: var(--size);
981
+ }
982
+
983
+ ._input_1e0uz_18 {
984
+ opacity: 0;
985
+ margin: 0;
986
+ cursor: pointer;
987
+ }
988
+
989
+ ._ui_1e0uz_19 {
990
+ pointer-events: none;
991
+ border-radius: 50%;
992
+ border: 1px solid;
993
+ border-color: var(--cpd-color-border-interactive-primary);
994
+
995
+ /* To align the ::after pseudo-element to the center of the radio button */
996
+ display: flex;
997
+ align-items: center;
998
+ justify-content: center;
999
+ }
1000
+
1001
+ ._ui_1e0uz_19::after {
1002
+ content: "";
1003
+ inline-size: 6px;
1004
+ block-size: 6px;
1005
+ border-radius: 50%;
1006
+ background: transparent;
1007
+ }
1008
+
1009
+ ._input_1e0uz_18:checked + ._ui_1e0uz_19 {
1010
+ background-color: var(--cpd-color-bg-accent-rest);
1011
+ border-color: var(--cpd-color-bg-accent-rest);
1012
+ }
1013
+
1014
+ ._input_1e0uz_18:checked + ._ui_1e0uz_19::after {
1015
+ background: var(--cpd-color-icon-on-solid-primary);
1016
+ }
1017
+
1018
+ ._input_1e0uz_18:focus-visible + ._ui_1e0uz_19 {
1019
+ outline: 2px solid var(--cpd-color-border-focused);
1020
+ outline-offset: 1px;
1021
+ }
1022
+
1023
+ ._input_1e0uz_18[readonly] {
1024
+ pointer-events: none;
1025
+ }
1026
+
1027
+ ._input_1e0uz_18[readonly] + ._ui_1e0uz_19 {
1028
+ border-color: var(--cpd-color-border-interactive-secondary);
1029
+ background: var(--cpd-color-bg-subtle-secondary);
1030
+ }
1031
+
1032
+ ._input_1e0uz_18[disabled] + ._ui_1e0uz_19 {
1033
+ border-color: var(--cpd-color-border-disabled);
1034
+ background: var(--cpd-color-bg-canvas-disabled);
1035
+ }
1036
+
1037
+ ._input_1e0uz_18[disabled]:checked + ._ui_1e0uz_19 {
1038
+ border-color: var(--cpd-color-bg-action-primary-disabled);
1039
+ background: var(--cpd-color-bg-action-primary-disabled);
1040
+ }
1041
+
1042
+ ._input_1e0uz_18[readonly]:checked + ._ui_1e0uz_19::after {
1043
+ background-color: var(--cpd-color-icon-secondary);
1044
+ }
1045
+
1046
+ @media (hover) {
1047
+ ._input_1e0uz_18:not([disabled], [readonly], :checked):hover + ._ui_1e0uz_19 {
1048
+ border-color: var(--cpd-color-bg-accent-hovered);
1049
+
1050
+ /** TODO: have the shadow in the design tokens */
1051
+ box-shadow: 0 1.2px 2.4px 0 rgb(0 0 0 / 15%);
1052
+ }
1053
+
1054
+ ._input_1e0uz_18:not([disabled], [readonly], :checked):hover + ._ui_1e0uz_19::after {
1055
+ background: var(--cpd-color-icon-quaternary);
1056
+ }
1057
+
1058
+ ._input_1e0uz_18:not([disabled], [readonly]):checked:hover + ._ui_1e0uz_19 {
1059
+ border-color: var(--cpd-color-bg-accent-hovered);
1060
+ background: var(--cpd-color-bg-accent-hovered);
1061
+ }
1062
+
1063
+ ._input_1e0uz_18[data-invalid]:not([disabled], [readonly]):checked:hover + ._ui_1e0uz_19 {
1064
+ border-color: var(--cpd-color-bg-critical-hovered);
1065
+ background: var(--cpd-color-bg-critical-hovered);
1066
+ }
1067
+ }
1068
+
1069
+ ._input_1e0uz_18[data-invalid]:not([disabled], [readonly], :checked) + ._ui_1e0uz_19 {
1070
+ border-color: var(--cpd-color-border-critical-primary);
1071
+ }
1072
+
1073
+ ._input_1e0uz_18[data-invalid]:not([disabled], [readonly]):checked + ._ui_1e0uz_19 {
1074
+ background-color: var(--cpd-color-bg-critical-primary);
1075
+ border-color: var(--cpd-color-bg-critical-primary);
1076
+ }
1077
+ /*
959
1078
  Copyright 2024 New Vector Ltd.
960
1079
 
961
1080
  SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
@@ -1988,125 +2107,6 @@ Please see LICENSE files in the repository root for full details.
1988
2107
  background-color: var(--cpd-color-bg-info-subtle);
1989
2108
  }
1990
2109
  /*
1991
- Copyright 2025 New Vector Ltd.
1992
- Copyright 2023 The Matrix.org Foundation C.I.C.
1993
- Copyright 2023 New Vector Ltd
1994
-
1995
- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
1996
- Please see LICENSE files in the repository root for full details.
1997
- */
1998
-
1999
- ._container_1e0uz_10 {
2000
- --size: 20px;
2001
-
2002
- display: grid;
2003
- inline-size: var(--size);
2004
- block-size: var(--size);
2005
- }
2006
-
2007
- ._input_1e0uz_18,
2008
- ._ui_1e0uz_19 {
2009
- box-sizing: border-box;
2010
- grid-area: 1/1;
2011
- inline-size: var(--size);
2012
- block-size: var(--size);
2013
- }
2014
-
2015
- ._input_1e0uz_18 {
2016
- opacity: 0;
2017
- margin: 0;
2018
- cursor: pointer;
2019
- }
2020
-
2021
- ._ui_1e0uz_19 {
2022
- pointer-events: none;
2023
- border-radius: 50%;
2024
- border: 1px solid;
2025
- border-color: var(--cpd-color-border-interactive-primary);
2026
-
2027
- /* To align the ::after pseudo-element to the center of the radio button */
2028
- display: flex;
2029
- align-items: center;
2030
- justify-content: center;
2031
- }
2032
-
2033
- ._ui_1e0uz_19::after {
2034
- content: "";
2035
- inline-size: 6px;
2036
- block-size: 6px;
2037
- border-radius: 50%;
2038
- background: transparent;
2039
- }
2040
-
2041
- ._input_1e0uz_18:checked + ._ui_1e0uz_19 {
2042
- background-color: var(--cpd-color-bg-accent-rest);
2043
- border-color: var(--cpd-color-bg-accent-rest);
2044
- }
2045
-
2046
- ._input_1e0uz_18:checked + ._ui_1e0uz_19::after {
2047
- background: var(--cpd-color-icon-on-solid-primary);
2048
- }
2049
-
2050
- ._input_1e0uz_18:focus-visible + ._ui_1e0uz_19 {
2051
- outline: 2px solid var(--cpd-color-border-focused);
2052
- outline-offset: 1px;
2053
- }
2054
-
2055
- ._input_1e0uz_18[readonly] {
2056
- pointer-events: none;
2057
- }
2058
-
2059
- ._input_1e0uz_18[readonly] + ._ui_1e0uz_19 {
2060
- border-color: var(--cpd-color-border-interactive-secondary);
2061
- background: var(--cpd-color-bg-subtle-secondary);
2062
- }
2063
-
2064
- ._input_1e0uz_18[disabled] + ._ui_1e0uz_19 {
2065
- border-color: var(--cpd-color-border-disabled);
2066
- background: var(--cpd-color-bg-canvas-disabled);
2067
- }
2068
-
2069
- ._input_1e0uz_18[disabled]:checked + ._ui_1e0uz_19 {
2070
- border-color: var(--cpd-color-bg-action-primary-disabled);
2071
- background: var(--cpd-color-bg-action-primary-disabled);
2072
- }
2073
-
2074
- ._input_1e0uz_18[readonly]:checked + ._ui_1e0uz_19::after {
2075
- background-color: var(--cpd-color-icon-secondary);
2076
- }
2077
-
2078
- @media (hover) {
2079
- ._input_1e0uz_18:not([disabled], [readonly], :checked):hover + ._ui_1e0uz_19 {
2080
- border-color: var(--cpd-color-bg-accent-hovered);
2081
-
2082
- /** TODO: have the shadow in the design tokens */
2083
- box-shadow: 0 1.2px 2.4px 0 rgb(0 0 0 / 15%);
2084
- }
2085
-
2086
- ._input_1e0uz_18:not([disabled], [readonly], :checked):hover + ._ui_1e0uz_19::after {
2087
- background: var(--cpd-color-icon-quaternary);
2088
- }
2089
-
2090
- ._input_1e0uz_18:not([disabled], [readonly]):checked:hover + ._ui_1e0uz_19 {
2091
- border-color: var(--cpd-color-bg-accent-hovered);
2092
- background: var(--cpd-color-bg-accent-hovered);
2093
- }
2094
-
2095
- ._input_1e0uz_18[data-invalid]:not([disabled], [readonly]):checked:hover + ._ui_1e0uz_19 {
2096
- border-color: var(--cpd-color-bg-critical-hovered);
2097
- background: var(--cpd-color-bg-critical-hovered);
2098
- }
2099
- }
2100
-
2101
- ._input_1e0uz_18[data-invalid]:not([disabled], [readonly], :checked) + ._ui_1e0uz_19 {
2102
- border-color: var(--cpd-color-border-critical-primary);
2103
- }
2104
-
2105
- ._input_1e0uz_18[data-invalid]:not([disabled], [readonly]):checked + ._ui_1e0uz_19 {
2106
- background-color: var(--cpd-color-bg-critical-primary);
2107
- border-color: var(--cpd-color-bg-critical-primary);
2108
- }
2109
- /*
2110
2110
  Copyright 2024 New Vector Ltd.
2111
2111
 
2112
2112
  SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial