UNPKG

@conduction/components

Version:

React (Gatsby) components used within the Conduction Skeleton Application (and its implementations)

150 lines (149 loc) 8.64 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import * as React from "react"; import * as styles from "./select.module.css"; import clsx from "clsx"; import CreatableSelect from "react-select/creatable"; import ReactSelect, { components } from "react-select"; import { Controller } from "react-hook-form"; import { ErrorMessage } from "../errorMessage/ErrorMessage"; const selectStyles = { menuPortal: (base) => ({ ...base, zIndex: 100 }), option: (base, state) => ({ ...base, fontFamily: `var(--conduction-input-select-list-option-font-family, ${base.fontFamily})`, backgroundColor: [ state.isFocused ? `var(--conduction-input-select-list-option-focus-background-color, ${base.backgroundColor})` : state.isSelected ? `var(--conduction-input-select-list-option-selected-background-color, ${base.backgroundColor})` : `var(--conduction-input-select-list-option-background-color, ${base.backgroundColor})`, ], color: [ state.isFocused ? `var(--conduction-input-select-list-option-focus-color, ${base.color})` : state.isSelected ? `var(--conduction-input-select-list-option-selected-color, ${base.color})` : `var(--conduction-input-select-list-option-color, ${base.color})`, ], "&:hover": { backgroundColor: `var(--conduction-input-select-list-option-hover-background-color, ${base.backgroundColor})`, color: `var(--conduction-input-select-list-option-hover-color, ${base.color})`, fontFamily: `var(--conduction-input-select-list-option-hover-font-family, var(--conduction-input-select-list-option-font-family, ${base.fontFamily}))`, }, }), placeholder: (base) => ({ ...base, fontFamily: `var(--conduction-input-select-placeholder-font-family, var(--utrecht-form-input-placeholder-font-family, ${base.fontFamily}))`, color: `var(--conduction-input-select-placeholder-color, var(--utrecht-form-input-placeholder-color, ${base.color}) )`, }), dropdownIndicator: (base) => ({ ...base, color: "#949494", "&:hover": { color: "#949494", }, }), }; const setAttributes = () => { const setRoleToPresentation = (selector, role) => { document.querySelectorAll(selector).forEach((element) => { if (element.getAttribute("role") !== "presentation") element.setAttribute("role", role); element.removeAttribute("aria-relevant"); element.removeAttribute("aria-atomic"); element.removeAttribute("aria-live"); }); }; const updateIndicatorAttributes = (indicator, isInteractive) => { if (isInteractive) { indicator.setAttribute("role", "button"); indicator.setAttribute("tabindex", "0"); indicator.setAttribute("aria-label", "Clear selection"); } else { indicator.setAttribute("role", "presentation"); indicator.removeAttribute("tabindex"); indicator.removeAttribute("aria-label"); } }; const setAriaLabelsForIndicators = () => { document.querySelectorAll('[class*="control"]').forEach((control) => { const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement; if (!indicatorsParent) return; const indicators = indicatorsParent.querySelectorAll('[class*="indicatorContainer"]'); const hasSelection = indicators.length === 2; indicators.forEach((indicator, index) => { const isClearButton = hasSelection && index === 0; updateIndicatorAttributes(indicator, isClearButton); }); }); }; // Initial static setup setRoleToPresentation('[id*="live-region"]', "presentation"); setRoleToPresentation('[class*="indicatorSeparator"]', "separator"); setRoleToPresentation('[class*="a11yText"]', "presentation"); // Dynamic setup after render setTimeout(() => { setAriaLabelsForIndicators(); const observer = new MutationObserver(setAriaLabelsForIndicators); document.querySelectorAll('[class*="control"]').forEach((control) => { const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement; if (indicatorsParent) { observer.observe(indicatorsParent, { childList: true, subtree: false }); } }); }, 100); }; // Custom ClearIndicator component that handles keyboard events for accessibility const ClearIndicator = (props) => { const { clearValue, innerProps, children } = props; const handleKeyDown = (event) => { if (event.key === " " || event.key === "Enter") { event.preventDefault(); event.stopPropagation(); if (clearValue) { clearValue(); } if (innerProps?.onClick) { innerProps.onClick(event); } } else if (innerProps?.onKeyDown) { innerProps.onKeyDown(event); } }; return (_jsx(components.ClearIndicator, { ...props, innerProps: { ...innerProps, onKeyDown: handleKeyDown, }, children: children })); }; export const SelectMultiple = ({ id, name, options, errors, control, validation, defaultValue, disabled, hideErrorMessage, menuPlacement, placeholder, ariaLabel, }) => { React.useEffect(() => { setAttributes(); }, []); return (_jsx(Controller, { control, name, defaultValue, rules: validation, render: ({ field: { onChange, value } }) => { return (_jsxs(_Fragment, { children: [_jsx(ReactSelect, { "aria-label": ariaLabel, inputId: id, value: value ?? "", className: clsx(styles.select, errors[name] && styles.error), isMulti: true, isDisabled: disabled, options, onChange, errors, menuPortalTarget: document.body, menuPlacement: menuPlacement, styles: selectStyles, placeholder: disabled ? "Disabled..." : placeholder ?? "Select one or more options...", formatGroupLabel: (group) => _jsx(GroupLabel, { group }) }), errors[name] && !hideErrorMessage && _jsx(ErrorMessage, { message: errors[name]?.message })] })); } })); }; export const SelectCreate = ({ id, name, options, errors, control, validation, defaultValue, disabled, hideErrorMessage, menuPlacement, placeholder, ariaLabel, }) => { React.useEffect(() => { setAttributes(); }, []); return (_jsx(Controller, { control, name, defaultValue, rules: validation, render: ({ field: { onChange, value } }) => { return (_jsxs(_Fragment, { children: [_jsx(CreatableSelect, { "aria-label": ariaLabel, inputId: id, value: value ?? "", placeholder: disabled ? "Disabled..." : placeholder ?? "Select one or more options...", className: clsx(styles.select, errors[name] && styles.error), isMulti: true, isDisabled: disabled, options, onChange, errors, menuPortalTarget: document.body, menuPlacement: menuPlacement, styles: selectStyles, formatGroupLabel: (group) => _jsx(GroupLabel, { group }) }), errors[name] && !hideErrorMessage && _jsx(ErrorMessage, { message: errors[name]?.message })] })); } })); }; export const SelectSingle = ({ id, name, options, errors, control, validation, isClearable, defaultValue, disabled, hideErrorMessage, menuPlacement, placeholder, ariaLabel, }) => { React.useEffect(() => { setAttributes(); }, []); return (_jsx(Controller, { control, name, defaultValue, rules: validation, render: ({ field: { onChange, value } }) => { return (_jsxs(_Fragment, { children: [_jsx(ReactSelect, { "aria-label": ariaLabel, inputId: id, value: value ?? "", className: clsx(styles.select, errors[name] && styles.error), isDisabled: disabled, options, onChange, errors, isClearable, menuPortalTarget: document.body, menuPlacement: menuPlacement, styles: selectStyles, placeholder: disabled ? "Disabled..." : placeholder ?? "Select one or more options...", formatGroupLabel: (group) => _jsx(GroupLabel, { group }), components: isClearable ? { ClearIndicator } : undefined }), errors[name] && !hideErrorMessage && _jsx(ErrorMessage, { message: errors[name]?.message })] })); } })); }; const GroupLabel = ({ group }) => { if (!group.label) return _jsx(_Fragment, {}); return _jsx("span", { className: styles.groupLabel, children: group.label }); };