@vector-im/compound-web 8.1.2 → 8.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ActivityMarker/Pill.cjs.map +1 -1
- package/dist/components/ActivityMarker/Pill.d.ts.map +1 -1
- package/dist/components/ActivityMarker/Pill.js.map +1 -1
- package/dist/components/ActivityMarker/Unread.cjs.map +1 -1
- package/dist/components/ActivityMarker/Unread.d.ts.map +1 -1
- package/dist/components/ActivityMarker/Unread.js.map +1 -1
- package/dist/components/ActivityMarker/UnreadCounter.cjs.map +1 -1
- package/dist/components/ActivityMarker/UnreadCounter.d.ts.map +1 -1
- package/dist/components/ActivityMarker/UnreadCounter.js.map +1 -1
- package/dist/components/Alert/Alert.cjs.map +1 -1
- package/dist/components/Alert/Alert.d.ts.map +1 -1
- package/dist/components/Alert/Alert.js.map +1 -1
- package/dist/components/Avatar/Avatar.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Avatar/AvatarStack.cjs.map +1 -1
- package/dist/components/Avatar/AvatarStack.js.map +1 -1
- package/dist/components/Avatar/avatar-clip.mask.svg.cjs.map +1 -1
- package/dist/components/Avatar/avatar-clip.mask.svg.js.map +1 -1
- package/dist/components/Avatar/useIdColorHash.cjs.map +1 -1
- package/dist/components/Avatar/useIdColorHash.js.map +1 -1
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.cjs.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Button/IconButton/IconButton.cjs.map +1 -1
- package/dist/components/Button/IconButton/IconButton.d.ts.map +1 -1
- package/dist/components/Button/IconButton/IconButton.js.map +1 -1
- package/dist/components/Button/UnstyledButton.cjs.map +1 -1
- package/dist/components/Button/UnstyledButton.d.ts.map +1 -1
- package/dist/components/Button/UnstyledButton.js.map +1 -1
- package/dist/components/ChatFilter/ChatFilter.cjs.map +1 -1
- package/dist/components/ChatFilter/ChatFilter.d.ts.map +1 -1
- package/dist/components/ChatFilter/ChatFilter.js.map +1 -1
- package/dist/components/Dropdown/Dropdown.cjs.map +1 -1
- package/dist/components/Dropdown/Dropdown.js.map +1 -1
- package/dist/components/Form/Controls/Action/Action.cjs.map +1 -1
- package/dist/components/Form/Controls/Action/Action.d.ts.map +1 -1
- package/dist/components/Form/Controls/Action/Action.js.map +1 -1
- package/dist/components/Form/Controls/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/Form/Controls/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/Form/Controls/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Form/Controls/EditInPlace/EditInPlace.cjs.map +1 -1
- package/dist/components/Form/Controls/EditInPlace/EditInPlace.js.map +1 -1
- package/dist/components/Form/Controls/MFA/MFA.cjs.map +1 -1
- package/dist/components/Form/Controls/MFA/MFA.js.map +1 -1
- package/dist/components/Form/Controls/Password/Password.cjs.map +1 -1
- package/dist/components/Form/Controls/Password/Password.d.ts.map +1 -1
- package/dist/components/Form/Controls/Password/Password.js.map +1 -1
- package/dist/components/Form/Controls/Radio/Radio.cjs.map +1 -1
- package/dist/components/Form/Controls/Radio/Radio.js.map +1 -1
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.cjs +4 -2
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.cjs.map +1 -1
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.d.ts.map +1 -1
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.js +5 -3
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.js.map +1 -1
- package/dist/components/Form/Controls/Text/Text.cjs.map +1 -1
- package/dist/components/Form/Controls/Text/Text.d.ts.map +1 -1
- package/dist/components/Form/Controls/Text/Text.js.map +1 -1
- package/dist/components/Form/Controls/Toggle/Toggle.cjs +10 -1
- package/dist/components/Form/Controls/Toggle/Toggle.cjs.map +1 -1
- package/dist/components/Form/Controls/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/Form/Controls/Toggle/Toggle.js +10 -1
- package/dist/components/Form/Controls/Toggle/Toggle.js.map +1 -1
- package/dist/components/Form/Field.cjs.map +1 -1
- package/dist/components/Form/Field.d.ts.map +1 -1
- package/dist/components/Form/Field.js.map +1 -1
- package/dist/components/Form/InlineField.cjs.map +1 -1
- package/dist/components/Form/InlineField.d.ts.map +1 -1
- package/dist/components/Form/InlineField.js.map +1 -1
- package/dist/components/Form/Label.cjs.map +1 -1
- package/dist/components/Form/Label.d.ts.map +1 -1
- package/dist/components/Form/Label.js.map +1 -1
- package/dist/components/Form/Message.cjs.map +1 -1
- package/dist/components/Form/Message.d.ts.map +1 -1
- package/dist/components/Form/Message.js.map +1 -1
- package/dist/components/Form/Root.cjs.map +1 -1
- package/dist/components/Form/Root.d.ts.map +1 -1
- package/dist/components/Form/Root.js.map +1 -1
- package/dist/components/Form/Submit.cjs.map +1 -1
- package/dist/components/Form/Submit.d.ts.map +1 -1
- package/dist/components/Form/Submit.js.map +1 -1
- package/dist/components/Glass/Glass.cjs.map +1 -1
- package/dist/components/Glass/Glass.d.ts.map +1 -1
- package/dist/components/Glass/Glass.js.map +1 -1
- package/dist/components/Icon/BigIcon/BigIcon.cjs.map +1 -1
- package/dist/components/Icon/BigIcon/BigIcon.d.ts.map +1 -1
- package/dist/components/Icon/BigIcon/BigIcon.js.map +1 -1
- package/dist/components/Icon/IndicatorIcon/IndicatorIcon.cjs.map +1 -1
- package/dist/components/Icon/IndicatorIcon/IndicatorIcon.d.ts.map +1 -1
- package/dist/components/Icon/IndicatorIcon/IndicatorIcon.js.map +1 -1
- package/dist/components/InlineSpinner/InlineSpinner.cjs.map +1 -1
- package/dist/components/InlineSpinner/InlineSpinner.d.ts.map +1 -1
- package/dist/components/InlineSpinner/InlineSpinner.js.map +1 -1
- package/dist/components/Link/Link.cjs.map +1 -1
- package/dist/components/Link/Link.d.ts.map +1 -1
- package/dist/components/Link/Link.js.map +1 -1
- package/dist/components/Menu/CheckboxMenuItem.cjs.map +1 -1
- package/dist/components/Menu/CheckboxMenuItem.d.ts.map +1 -1
- package/dist/components/Menu/CheckboxMenuItem.js.map +1 -1
- package/dist/components/Menu/ContextMenu.cjs.map +1 -1
- package/dist/components/Menu/ContextMenu.d.ts.map +1 -1
- package/dist/components/Menu/ContextMenu.js.map +1 -1
- package/dist/components/Menu/DrawerMenu.cjs.map +1 -1
- package/dist/components/Menu/DrawerMenu.d.ts.map +1 -1
- package/dist/components/Menu/DrawerMenu.js.map +1 -1
- package/dist/components/Menu/DrawerMenu.module.css.cjs +3 -3
- package/dist/components/Menu/DrawerMenu.module.css.js +3 -3
- package/dist/components/Menu/FloatingMenu.cjs.map +1 -1
- package/dist/components/Menu/FloatingMenu.d.ts.map +1 -1
- package/dist/components/Menu/FloatingMenu.js.map +1 -1
- package/dist/components/Menu/FloatingMenu.module.css.cjs +2 -2
- package/dist/components/Menu/FloatingMenu.module.css.js +2 -2
- package/dist/components/Menu/Menu.cjs.map +1 -1
- package/dist/components/Menu/Menu.d.ts.map +1 -1
- package/dist/components/Menu/Menu.js.map +1 -1
- package/dist/components/Menu/MenuContext.cjs.map +1 -1
- package/dist/components/Menu/MenuContext.d.ts.map +1 -1
- package/dist/components/Menu/MenuContext.js.map +1 -1
- package/dist/components/Menu/MenuItem.cjs.map +1 -1
- package/dist/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/components/Menu/MenuItem.js.map +1 -1
- package/dist/components/Menu/MenuTitle.cjs.map +1 -1
- package/dist/components/Menu/MenuTitle.js.map +1 -1
- package/dist/components/Menu/RadioMenuItem.cjs.map +1 -1
- package/dist/components/Menu/RadioMenuItem.d.ts.map +1 -1
- package/dist/components/Menu/RadioMenuItem.js.map +1 -1
- package/dist/components/Menu/ToggleMenuItem.cjs.map +1 -1
- package/dist/components/Menu/ToggleMenuItem.d.ts.map +1 -1
- package/dist/components/Menu/ToggleMenuItem.js.map +1 -1
- package/dist/components/Nav/NavBar.cjs.map +1 -1
- package/dist/components/Nav/NavBar.d.ts.map +1 -1
- package/dist/components/Nav/NavBar.js.map +1 -1
- package/dist/components/Nav/NavItem.cjs.map +1 -1
- package/dist/components/Nav/NavItem.d.ts.map +1 -1
- package/dist/components/Nav/NavItem.js.map +1 -1
- package/dist/components/Progress/Progress.cjs.map +1 -1
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncement.cjs.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncement.d.ts.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncement.js.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncementContext.cjs.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncementContext.d.ts.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncementContext.js.map +1 -1
- package/dist/components/ReleaseAnnouncement/useReleaseAnnouncement.cjs.map +1 -1
- package/dist/components/ReleaseAnnouncement/useReleaseAnnouncement.d.ts.map +1 -1
- package/dist/components/ReleaseAnnouncement/useReleaseAnnouncement.js.map +1 -1
- package/dist/components/Search/Search.cjs.map +1 -1
- package/dist/components/Search/Search.d.ts.map +1 -1
- package/dist/components/Search/Search.js.map +1 -1
- package/dist/components/Separator/Separator.cjs.map +1 -1
- package/dist/components/Separator/Separator.d.ts.map +1 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Toast/Toast.cjs.map +1 -1
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/TooltipContext.cjs.map +1 -1
- package/dist/components/Tooltip/TooltipContext.d.ts.map +1 -1
- package/dist/components/Tooltip/TooltipContext.js.map +1 -1
- package/dist/components/Tooltip/TooltipProvider.cjs.map +1 -1
- package/dist/components/Tooltip/TooltipProvider.d.ts.map +1 -1
- package/dist/components/Tooltip/TooltipProvider.js.map +1 -1
- package/dist/components/Tooltip/useTooltip.cjs.map +1 -1
- package/dist/components/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/useTooltip.js.map +1 -1
- package/dist/components/Typography/Body.cjs.map +1 -1
- package/dist/components/Typography/Body.js.map +1 -1
- package/dist/components/Typography/Heading.cjs.map +1 -1
- package/dist/components/Typography/Heading.js.map +1 -1
- package/dist/components/Typography/Text.cjs.map +1 -1
- package/dist/components/Typography/Text.js.map +1 -1
- package/dist/components/Typography/Typography.cjs.map +1 -1
- package/dist/components/Typography/Typography.d.ts.map +1 -1
- package/dist/components/Typography/Typography.js.map +1 -1
- package/dist/components/VisualList/VisualList.cjs.map +1 -1
- package/dist/components/VisualList/VisualList.d.ts.map +1 -1
- package/dist/components/VisualList/VisualList.js.map +1 -1
- package/dist/components/VisualList/VisualListItem.cjs.map +1 -1
- package/dist/components/VisualList/VisualListItem.d.ts.map +1 -1
- package/dist/components/VisualList/VisualListItem.js.map +1 -1
- package/dist/{style.css.css → style.css} +21 -21
- package/dist/utils/__ComponentTemplate__/__ComponentTemplate__.d.ts.map +1 -1
- package/dist/utils/platform.cjs.map +1 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/string.cjs.map +1 -1
- package/dist/utils/string.js.map +1 -1
- package/package.json +30 -32
- package/src/components/ActivityMarker/Pill.tsx +1 -1
- package/src/components/ActivityMarker/Unread.tsx +1 -1
- package/src/components/ActivityMarker/UnreadCounter.tsx +1 -1
- package/src/components/Alert/Alert.tsx +1 -1
- package/src/components/Avatar/Avatar.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +6 -1
- package/src/components/Button/Button.tsx +5 -5
- package/src/components/Button/IconButton/IconButton.tsx +2 -2
- package/src/components/Button/UnstyledButton.tsx +6 -1
- package/src/components/ChatFilter/ChatFilter.tsx +2 -2
- package/src/components/Dropdown/Dropdown.tsx +5 -5
- package/src/components/Form/Controls/Action/Action.tsx +6 -1
- package/src/components/Form/Controls/Checkbox/Checkbox.tsx +5 -1
- package/src/components/Form/Controls/MFA/MFA.tsx +3 -3
- package/src/components/Form/Controls/Password/Password.tsx +2 -2
- package/src/components/Form/Controls/Radio/Radio.tsx +3 -3
- package/src/components/Form/Controls/SettingsToggle/SettingsToggle.tsx +8 -5
- package/src/components/Form/Controls/Text/Text.tsx +5 -1
- package/src/components/Form/Controls/Toggle/Toggle.tsx +10 -4
- package/src/components/Form/Field.tsx +1 -1
- package/src/components/Form/InlineField.tsx +1 -1
- package/src/components/Form/Label.tsx +1 -1
- package/src/components/Form/Message.tsx +1 -1
- package/src/components/Form/Root.tsx +1 -1
- package/src/components/Form/Submit.tsx +1 -1
- package/src/components/Glass/Glass.tsx +2 -2
- package/src/components/Icon/BigIcon/BigIcon.tsx +1 -1
- package/src/components/Icon/IndicatorIcon/IndicatorIcon.tsx +1 -1
- package/src/components/InlineSpinner/InlineSpinner.tsx +1 -1
- package/src/components/Link/Link.tsx +1 -1
- package/src/components/Menu/CheckboxMenuItem.tsx +6 -1
- package/src/components/Menu/ContextMenu.tsx +12 -2
- package/src/components/Menu/DrawerMenu.module.css +1 -1
- package/src/components/Menu/DrawerMenu.tsx +5 -1
- package/src/components/Menu/FloatingMenu.module.css +1 -1
- package/src/components/Menu/FloatingMenu.tsx +2 -2
- package/src/components/Menu/Menu.tsx +6 -2
- package/src/components/Menu/MenuContext.tsx +1 -1
- package/src/components/Menu/MenuItem.tsx +6 -6
- package/src/components/Menu/RadioMenuItem.tsx +6 -1
- package/src/components/Menu/ToggleMenuItem.tsx +6 -1
- package/src/components/Nav/NavItem.tsx +4 -4
- package/src/components/ReleaseAnnouncement/ReleaseAnnouncement.tsx +4 -4
- package/src/components/ReleaseAnnouncement/useReleaseAnnouncement.tsx +2 -2
- package/src/components/Search/Search.tsx +1 -1
- package/src/components/Separator/Separator.tsx +3 -3
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +9 -9
- package/src/components/Tooltip/TooltipProvider.tsx +1 -1
- package/src/components/Tooltip/useTooltip.ts +4 -4
- package/src/components/Typography/Typography.tsx +1 -1
- package/src/components/VisualList/VisualList.tsx +1 -1
- package/src/components/VisualList/VisualListItem.tsx +5 -1
- package/src/utils/__ComponentTemplate__/__ComponentTemplate__.tsx +1 -1
- package/tsconfig.json +3 -1
|
@@ -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 type Dispatch,\n forwardRef,\n type HTMLProps,\n memo,\n type RefObject,\n type SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n type 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 +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 type Dispatch,\n forwardRef,\n type HTMLProps,\n memo,\n type RefObject,\n type SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n type 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Action.cjs","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"sourcesContent":["/*\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {
|
|
1
|
+
{"version":3,"file":"Action.cjs","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"sourcesContent":["/*\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n forwardRef,\n type ComponentRef,\n type ComponentProps,\n useId,\n} from \"react\";\nimport styles from \"./Action.module.css\";\nimport { TextInput } from \"../Text\";\n\nimport { Control } from \"@radix-ui/react-form\";\nimport { Tooltip } from \"../../../Tooltip/Tooltip\";\n\ntype Props = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The React component to display on the right hand side of the icon.\n */\n Icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;\n /**\n * On click callback for the icon on the right hand side\n */\n onActionClick: (e: React.MouseEvent) => void;\n /**\n * The name of the action to perform (e.g. \"Copy to clipboard\")\n */\n actionLabel: string;\n} & React.ComponentProps<typeof TextInput>;\n\n/**\n * A generic input with an action icon on the right hand side.\n * You should rarely use it directly,\n * but it is powering the likes of \"copy to clipboard input\", \"password reveal\", ...\n */\nexport const ActionInput = forwardRef<HTMLInputElement, Props>(\n function ActionControl(\n { Icon, className, actionLabel, onActionClick, ...props },\n ref,\n ) {\n const id = useId();\n const classes = classnames(styles.container, className);\n return (\n <div className={classes} id={id}>\n <TextInput ref={ref} {...props} className={styles.control} />\n\n <Tooltip label={actionLabel}>\n <button\n type=\"button\"\n className={styles.action}\n onClick={onActionClick}\n aria-controls={id}\n >\n <Icon aria-hidden />\n </button>\n </Tooltip>\n </div>\n );\n },\n);\n\n/**\n * A styled text input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const ActionControl = forwardRef<\n ComponentRef<typeof ActionInput>,\n ComponentProps<typeof ActionInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <ActionInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","ActionControl","useId","classnames","styles","jsxs","jsx","TextInput","Tooltip","Control"],"mappings":";;;;;;;;;AA4CO,MAAM,cAAcA,MAAAA;AAAAA,EACzB,SAASC,eACP,EAAE,MAAM,WAAW,aAAa,eAAe,GAAG,MAAA,GAClD,KACA;AACA,UAAM,KAAKC,MAAAA,MAAA;AACX,UAAM,UAAUC,WAAWC,sBAAO,WAAW,SAAS;AACtD,WACEC,2BAAAA,KAAC,OAAA,EAAI,WAAW,SAAS,IACvB,UAAA;AAAA,MAAAC,+BAACC,KAAAA,aAAU,KAAW,GAAG,OAAO,WAAWH,cAAAA,QAAO,SAAS;AAAA,MAE3DE,2BAAAA,IAACE,QAAAA,SAAA,EAAQ,OAAO,aACd,UAAAF,2BAAAA;AAAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAWF,cAAAA,QAAO;AAAA,UAClB,SAAS;AAAA,UACT,iBAAe;AAAA,UAEf,UAAAE,2BAAAA,IAAC,MAAA,EAAK,eAAW,KAAA,CAAC;AAAA,QAAA;AAAA,MAAA,EACpB,CACF;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AAKO,MAAM,gBAAgBN,MAAAA,WAG3B,SAASC,eAAc,OAAO,KAAK;AACnC,SACEK,2BAAAA,IAACG,UAAAA,WAAQ,SAAO,MACd,yCAAC,aAAA,EAAY,KAAW,GAAG,MAAA,CAAO,EAAA,CACpC;AAEJ,CAAC;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Action.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"names":[],"mappings":"AAQA,OAAO,
|
|
1
|
+
{"version":3,"file":"Action.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"names":[],"mappings":"AAQA,OAAO,KAKN,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAKpC,KAAK,KAAK,GAAG;IACX;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,IAAI,EAAE,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;IAC/D;;OAEG;IACH,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC7C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,WAAW,6FAwBvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,kJASxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Action.js","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"sourcesContent":["/*\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {
|
|
1
|
+
{"version":3,"file":"Action.js","sources":["../../../../../src/components/Form/Controls/Action/Action.tsx"],"sourcesContent":["/*\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n forwardRef,\n type ComponentRef,\n type ComponentProps,\n useId,\n} from \"react\";\nimport styles from \"./Action.module.css\";\nimport { TextInput } from \"../Text\";\n\nimport { Control } from \"@radix-ui/react-form\";\nimport { Tooltip } from \"../../../Tooltip/Tooltip\";\n\ntype Props = {\n /**\n * The CSS class name.\n */\n className?: string;\n /**\n * The React component to display on the right hand side of the icon.\n */\n Icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;\n /**\n * On click callback for the icon on the right hand side\n */\n onActionClick: (e: React.MouseEvent) => void;\n /**\n * The name of the action to perform (e.g. \"Copy to clipboard\")\n */\n actionLabel: string;\n} & React.ComponentProps<typeof TextInput>;\n\n/**\n * A generic input with an action icon on the right hand side.\n * You should rarely use it directly,\n * but it is powering the likes of \"copy to clipboard input\", \"password reveal\", ...\n */\nexport const ActionInput = forwardRef<HTMLInputElement, Props>(\n function ActionControl(\n { Icon, className, actionLabel, onActionClick, ...props },\n ref,\n ) {\n const id = useId();\n const classes = classnames(styles.container, className);\n return (\n <div className={classes} id={id}>\n <TextInput ref={ref} {...props} className={styles.control} />\n\n <Tooltip label={actionLabel}>\n <button\n type=\"button\"\n className={styles.action}\n onClick={onActionClick}\n aria-controls={id}\n >\n <Icon aria-hidden />\n </button>\n </Tooltip>\n </div>\n );\n },\n);\n\n/**\n * A styled text input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const ActionControl = forwardRef<\n ComponentRef<typeof ActionInput>,\n ComponentProps<typeof ActionInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <ActionInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["ActionControl","classnames"],"mappings":";;;;;;;AA4CO,MAAM,cAAc;AAAA,EACzB,SAASA,eACP,EAAE,MAAM,WAAW,aAAa,eAAe,GAAG,MAAA,GAClD,KACA;AACA,UAAM,KAAK,MAAA;AACX,UAAM,UAAUC,WAAW,OAAO,WAAW,SAAS;AACtD,WACE,qBAAC,OAAA,EAAI,WAAW,SAAS,IACvB,UAAA;AAAA,MAAA,oBAAC,aAAU,KAAW,GAAG,OAAO,WAAW,OAAO,SAAS;AAAA,MAE3D,oBAAC,SAAA,EAAQ,OAAO,aACd,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,OAAO;AAAA,UAClB,SAAS;AAAA,UACT,iBAAe;AAAA,UAEf,UAAA,oBAAC,MAAA,EAAK,eAAW,KAAA,CAAC;AAAA,QAAA;AAAA,MAAA,EACpB,CACF;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AAKO,MAAM,gBAAgB,WAG3B,SAASD,eAAc,OAAO,KAAK;AACnC,SACE,oBAAC,WAAQ,SAAO,MACd,8BAAC,aAAA,EAAY,KAAW,GAAG,MAAA,CAAO,EAAA,CACpC;AAEJ,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.cjs","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, { ComponentProps
|
|
1
|
+
{"version":3,"file":"Checkbox.cjs","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Checkbox.module.css\";\n\ntype Props = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<ComponentProps<\"input\">, \"type\">;\n\n/**\n * A styled checkbox input, for standalone use.\n */\nexport const CheckboxInput = forwardRef<HTMLInputElement, Props>(\n function Checkbox({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} className={styles.input} {...props} type=\"checkbox\" />\n <div className={styles.ui}>\n <CheckIcon aria-hidden={true} />\n </div>\n </div>\n );\n },\n);\n\n/**\n * A styled checkbox input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const CheckboxControl = forwardRef<\n ComponentRef<typeof CheckboxInput>,\n ComponentProps<typeof CheckboxInput>\n>(function CheckboxControl(props, ref) {\n return (\n <Control asChild>\n <CheckboxInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","classnames","styles","jsxs","jsx","CheckboxControl","Control"],"mappings":";;;;;;;;AA8BO,MAAM,gBAAgBA,MAAAA;AAAAA,EAC3B,SAAS,SAAS,EAAE,WAAW,GAAG,MAAA,GAAS,KAAK;AAC9C,UAAM,UAAUC,WAAWC,wBAAO,WAAW,SAAS;AACtD,WACEC,2BAAAA,KAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,MAAAC,2BAAAA,IAAC,SAAA,EAAM,KAAU,WAAWF,gBAAAA,QAAO,OAAQ,GAAG,OAAO,MAAK,YAAW;AAAA,MACrEE,2BAAAA,IAAC,SAAI,WAAWF,gBAAAA,QAAO,IACrB,UAAAE,2BAAAA,IAAC,WAAA,EAAU,eAAa,KAAA,CAAM,EAAA,CAChC;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AAKO,MAAM,kBAAkBJ,MAAAA,WAG7B,SAASK,iBAAgB,OAAO,KAAK;AACrC,SACED,2BAAAA,IAACE,UAAAA,WAAQ,SAAO,MACd,yCAAC,eAAA,EAAc,KAAW,GAAG,MAAA,CAAO,EAAA,CACtC;AAEJ,CAAC;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,EACZ,KAAK,cAAc,EAGpB,MAAM,OAAO,CAAC;AAMf,KAAK,KAAK,GAAG;IACX;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAE1C;;GAEG;AACH,eAAO,MAAM,aAAa,6FAYzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,kJAS1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, { ComponentProps
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sources":["../../../../../src/components/Form/Controls/Checkbox/Checkbox.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n} from \"react\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Checkbox.module.css\";\n\ntype Props = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<ComponentProps<\"input\">, \"type\">;\n\n/**\n * A styled checkbox input, for standalone use.\n */\nexport const CheckboxInput = forwardRef<HTMLInputElement, Props>(\n function Checkbox({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} className={styles.input} {...props} type=\"checkbox\" />\n <div className={styles.ui}>\n <CheckIcon aria-hidden={true} />\n </div>\n </div>\n );\n },\n);\n\n/**\n * A styled checkbox input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const CheckboxControl = forwardRef<\n ComponentRef<typeof CheckboxInput>,\n ComponentProps<typeof CheckboxInput>\n>(function CheckboxControl(props, ref) {\n return (\n <Control asChild>\n <CheckboxInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["classnames","CheckboxControl"],"mappings":";;;;;;AA8BO,MAAM,gBAAgB;AAAA,EAC3B,SAAS,SAAS,EAAE,WAAW,GAAG,MAAA,GAAS,KAAK;AAC9C,UAAM,UAAUA,WAAW,OAAO,WAAW,SAAS;AACtD,WACE,qBAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAM,KAAU,WAAW,OAAO,OAAQ,GAAG,OAAO,MAAK,YAAW;AAAA,MACrE,oBAAC,SAAI,WAAW,OAAO,IACrB,UAAA,oBAAC,WAAA,EAAU,eAAa,KAAA,CAAM,EAAA,CAChC;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AAKO,MAAM,kBAAkB,WAG7B,SAASC,iBAAgB,OAAO,KAAK;AACrC,SACE,oBAAC,WAAQ,SAAO,MACd,8BAAC,eAAA,EAAc,KAAW,GAAG,MAAA,CAAO,EAAA,CACtC;AAEJ,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditInPlace.cjs","sources":["../../../../../src/components/Form/Controls/EditInPlace/EditInPlace.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 React, {\n forwardRef,\n useCallback,\n useRef,\n useState,\n useEffect,\n useReducer,\n} from \"react\";\nimport { Submit, ValidityState } from \"@radix-ui/react-form\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport CancelIcon from \"@vector-im/compound-design-tokens/assets/web/icons/close\";\n\nimport styles from \"./EditInPlace.module.css\";\n\nimport {\n Field,\n HelpMessage,\n Label,\n LoadingMessage,\n Root,\n SuccessMessage,\n TextControl,\n} from \"../..\";\nimport { Button, Tooltip } from \"../../../..\";\n\ntype Props = {\n /**\n * The label for the control\n */\n label: string;\n\n /**\n * The CSS class name.\n */\n className?: string;\n\n /**\n * Callback for when the user confirms the change\n */\n onSave?: (e: React.FormEvent<HTMLFormElement>) => Promise<void> | void;\n\n /**\n * Callback for when the user wishes to cancel the change\n */\n onCancel?: (e: React.FormEvent<HTMLFormElement>) => void;\n\n /**\n * onInput event handler on the text control\n */\n onInput?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n\n /**\n * Callback for when the server validation errors should be cleared.\n */\n onClearServerErrors?: () => void;\n\n /**\n * Whether the field is in an error state according to the server validation.\n *\n * For validation messages, use native validations properties directly, or add custom error messages as children.\n */\n serverInvalid?: boolean;\n\n /**\n * Label to be displayed by the green check at the bottom. Will only be displayed\n * for 2 seconds after the onSave callback promise resolves successfully.\n */\n savedLabel?: string;\n\n /**\n * The label for the save button\n */\n saveButtonLabel: string;\n\n /**\n * The label for the 'in progress' saving caption\n */\n savingLabel: string;\n\n /**\n * The label for the cancel button\n */\n cancelButtonLabel: string;\n\n /**\n * Label to be displayed under the input as a help text\n */\n helpLabel?: string;\n\n /**\n * If true, disabled the entire component to disallow editing.\n */\n disabled?: boolean;\n} & React.ComponentProps<typeof TextControl>;\n\nenum State {\n /** No changes on the input has been made */\n Initial,\n\n /** The input has been changed */\n Dirty,\n\n /** The input is being saved */\n Saving,\n\n /** The input has been saved */\n Saved,\n}\n\nenum Event {\n Touch, // The user 'touched' the control\n Save, // The user has clicked the save button\n Saved, // The onSave callback finished successfully\n SaveError, // The onSave callback finished with an error\n Cancel, // The user has clicked the cancel button\n SavedTimeout, // The user has clicked the save button and the saved label has been shown for 2 seconds\n}\n\nfunction reducer(state: State, action: Event): State {\n switch (action) {\n case Event.Touch:\n if (state === State.Initial || state === State.Saved) return State.Dirty;\n else return state;\n\n case Event.Save:\n return State.Saving;\n\n case Event.Cancel:\n return State.Initial;\n\n case Event.Saved:\n if (state === State.Saving) return State.Saved;\n else return state;\n\n case Event.SaveError:\n if (state === State.Saving) return State.Initial;\n else return state;\n\n case Event.SavedTimeout:\n if (state === State.Saved) return State.Initial;\n else return state;\n }\n\n assertNever(action);\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unreachable value: ${value}`);\n}\n\n/**\n * A text box with save/cancel buttons that appear when the field is active.\n * Since thios control has its own 'save' button, it should *not* appear as part\n * of a larger form: it exists as its own form that submits separately.\n */\nexport const EditInPlace = forwardRef<HTMLInputElement, Props>(\n function EditInPlace(\n {\n className,\n label,\n onSave,\n onCancel,\n onInput,\n onClearServerErrors,\n serverInvalid,\n saveButtonLabel,\n cancelButtonLabel,\n savedLabel,\n savingLabel,\n helpLabel,\n disabled,\n children,\n ...props\n },\n ref,\n ) {\n const [state, dispatch] = useReducer(reducer, State.Initial);\n\n // Tracks the focus state of the form\n // This uses a `ref` to make sure the onFocus/onBlur callback don't trigger unnecessary re-renders\n // and a state to track the focus state and hide the buttons when the form is not focused\n const isFocusWithinRef = useRef(false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n\n const shouldShowSaveButton =\n state === State.Dirty || state === State.Saving || isFocusWithin;\n\n const hideTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n\n useEffect(() => {\n // Start a timer when we switch to the saved state\n if (state === State.Saved) {\n hideTimer.current = setTimeout(() => {\n dispatch(Event.SavedTimeout);\n hideTimer.current = undefined;\n }, 2000);\n }\n\n return () => {\n // Clear any timers that may have been set\n if (hideTimer.current) clearTimeout(hideTimer.current);\n hideTimer.current = undefined;\n };\n }, [state]);\n\n const formRef = useRef<HTMLFormElement>(null);\n const saveButtonRef = useRef<HTMLButtonElement>(null);\n const cancelButtonRef = useRef<HTMLButtonElement>(null);\n\n const onFocus = useCallback(() => {\n if (isFocusWithinRef.current) return;\n isFocusWithinRef.current = true;\n setFocusWithin(true);\n }, [isFocusWithin, setFocusWithin]);\n\n const onBlur = useCallback(\n (e: React.FocusEvent) => {\n if (!isFocusWithinRef.current) return;\n // If the user switched to another element within the form\n // consider that we're still focused within the form\n if (e.currentTarget.contains(e.relatedTarget)) return;\n\n isFocusWithinRef.current = false;\n setFocusWithin(false);\n },\n [isFocusWithin, setFocusWithin],\n );\n\n const onInputHandler = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n dispatch(Event.Touch);\n onInput?.(e);\n },\n [dispatch, onInput],\n );\n\n const onFormSubmit = useCallback(\n async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n // Prevent submitting the form if the user has not yet entered any text\n if (state === State.Initial) {\n return;\n }\n\n try {\n dispatch(Event.Save);\n saveButtonRef.current?.blur();\n await onSave?.(e);\n dispatch(Event.Saved);\n } catch {\n // We don't really need to do anything here, we just don't want to display the\n // 'saved' label, obviously. The user of the component can update the error to\n // show what failed.\n dispatch(Event.SaveError);\n }\n },\n [onSave, state, hideTimer],\n );\n\n const onFormReset = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n cancelButtonRef.current?.blur();\n onCancel?.(e);\n dispatch(Event.Cancel);\n },\n [cancelButtonRef, onCancel],\n );\n\n return (\n <Root\n className={className}\n onSubmit={onFormSubmit}\n onReset={onFormReset}\n onFocus={onFocus}\n onBlur={onBlur}\n onClearServerErrors={onClearServerErrors}\n ref={formRef}\n >\n <Field name=\"input\" serverInvalid={serverInvalid}>\n <Label>{label}</Label>\n <div className={styles.controls}>\n <TextControl\n ref={ref}\n {...props}\n onInput={onInputHandler}\n disabled={disabled || state === State.Saving}\n />\n\n {shouldShowSaveButton && (\n <div className={styles[\"button-group\"]}>\n <Tooltip label={saveButtonLabel}>\n <Submit asChild>\n <Button\n type=\"submit\"\n kind=\"primary\"\n size=\"sm\"\n ref={saveButtonRef}\n disabled={state !== State.Dirty}\n iconOnly\n Icon={CheckIcon}\n />\n </Submit>\n </Tooltip>\n\n <Tooltip label={cancelButtonLabel}>\n <Button\n type=\"reset\"\n kind=\"secondary\"\n size=\"sm\"\n ref={cancelButtonRef}\n className={styles.button}\n disabled={state === State.Saving}\n iconOnly\n Icon={CancelIcon}\n />\n </Tooltip>\n </div>\n )}\n </div>\n\n {/*\n During the loading saving state, we only show the saving message.\n Else, we show whatever children were passed on, as they will have other validation messages\n */}\n {state === State.Saving ? (\n <LoadingMessage>{savingLabel}</LoadingMessage>\n ) : (\n children\n )}\n\n {savedLabel && state === State.Saved && (\n <SuccessMessage>{savedLabel}</SuccessMessage>\n )}\n\n {/*\n We show the help message only if:\n - the helpLabel is set\n - the form hasn't been validated yet\n - the 'serverInvalid' prop is not set\n - we're in the initial or dirty state\n */}\n {helpLabel && (state === State.Initial || state === State.Dirty) && (\n <ValidityState>\n {(validity) =>\n (validity === undefined || validity.valid) &&\n !serverInvalid && <HelpMessage>{helpLabel}</HelpMessage>\n }\n </ValidityState>\n )}\n </Field>\n </Root>\n );\n },\n);\n"],"names":["forwardRef","EditInPlace","useReducer","useRef","useState","useEffect","useCallback","jsx","Root","jsxs","Field","Label","styles","TextControl","Tooltip","Submit","Button","CancelIcon","LoadingMessage","SuccessMessage","ValidityState","HelpMessage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditInPlace.cjs","sources":["../../../../../src/components/Form/Controls/EditInPlace/EditInPlace.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 React, {\n forwardRef,\n useCallback,\n useRef,\n useState,\n useEffect,\n useReducer,\n} from \"react\";\nimport { Submit, ValidityState } from \"@radix-ui/react-form\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport CancelIcon from \"@vector-im/compound-design-tokens/assets/web/icons/close\";\n\nimport styles from \"./EditInPlace.module.css\";\n\nimport {\n Field,\n HelpMessage,\n Label,\n LoadingMessage,\n Root,\n SuccessMessage,\n TextControl,\n} from \"../..\";\nimport { Button, Tooltip } from \"../../../..\";\n\ntype Props = {\n /**\n * The label for the control\n */\n label: string;\n\n /**\n * The CSS class name.\n */\n className?: string;\n\n /**\n * Callback for when the user confirms the change\n */\n onSave?: (e: React.FormEvent<HTMLFormElement>) => Promise<void> | void;\n\n /**\n * Callback for when the user wishes to cancel the change\n */\n onCancel?: (e: React.FormEvent<HTMLFormElement>) => void;\n\n /**\n * onInput event handler on the text control\n */\n onInput?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n\n /**\n * Callback for when the server validation errors should be cleared.\n */\n onClearServerErrors?: () => void;\n\n /**\n * Whether the field is in an error state according to the server validation.\n *\n * For validation messages, use native validations properties directly, or add custom error messages as children.\n */\n serverInvalid?: boolean;\n\n /**\n * Label to be displayed by the green check at the bottom. Will only be displayed\n * for 2 seconds after the onSave callback promise resolves successfully.\n */\n savedLabel?: string;\n\n /**\n * The label for the save button\n */\n saveButtonLabel: string;\n\n /**\n * The label for the 'in progress' saving caption\n */\n savingLabel: string;\n\n /**\n * The label for the cancel button\n */\n cancelButtonLabel: string;\n\n /**\n * Label to be displayed under the input as a help text\n */\n helpLabel?: string;\n\n /**\n * If true, disabled the entire component to disallow editing.\n */\n disabled?: boolean;\n} & React.ComponentProps<typeof TextControl>;\n\nenum State {\n /** No changes on the input has been made */\n Initial,\n\n /** The input has been changed */\n Dirty,\n\n /** The input is being saved */\n Saving,\n\n /** The input has been saved */\n Saved,\n}\n\nenum Event {\n Touch, // The user 'touched' the control\n Save, // The user has clicked the save button\n Saved, // The onSave callback finished successfully\n SaveError, // The onSave callback finished with an error\n Cancel, // The user has clicked the cancel button\n SavedTimeout, // The user has clicked the save button and the saved label has been shown for 2 seconds\n}\n\nfunction reducer(state: State, action: Event): State {\n switch (action) {\n case Event.Touch:\n if (state === State.Initial || state === State.Saved) return State.Dirty;\n else return state;\n\n case Event.Save:\n return State.Saving;\n\n case Event.Cancel:\n return State.Initial;\n\n case Event.Saved:\n if (state === State.Saving) return State.Saved;\n else return state;\n\n case Event.SaveError:\n if (state === State.Saving) return State.Initial;\n else return state;\n\n case Event.SavedTimeout:\n if (state === State.Saved) return State.Initial;\n else return state;\n }\n\n assertNever(action);\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unreachable value: ${value}`);\n}\n\n/**\n * A text box with save/cancel buttons that appear when the field is active.\n * Since thios control has its own 'save' button, it should *not* appear as part\n * of a larger form: it exists as its own form that submits separately.\n */\nexport const EditInPlace = forwardRef<HTMLInputElement, Props>(\n function EditInPlace(\n {\n className,\n label,\n onSave,\n onCancel,\n onInput,\n onClearServerErrors,\n serverInvalid,\n saveButtonLabel,\n cancelButtonLabel,\n savedLabel,\n savingLabel,\n helpLabel,\n disabled,\n children,\n ...props\n },\n ref,\n ) {\n const [state, dispatch] = useReducer(reducer, State.Initial);\n\n // Tracks the focus state of the form\n // This uses a `ref` to make sure the onFocus/onBlur callback don't trigger unnecessary re-renders\n // and a state to track the focus state and hide the buttons when the form is not focused\n const isFocusWithinRef = useRef(false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n\n const shouldShowSaveButton =\n state === State.Dirty || state === State.Saving || isFocusWithin;\n\n const hideTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n\n useEffect(() => {\n // Start a timer when we switch to the saved state\n if (state === State.Saved) {\n hideTimer.current = setTimeout(() => {\n dispatch(Event.SavedTimeout);\n hideTimer.current = undefined;\n }, 2000);\n }\n\n return () => {\n // Clear any timers that may have been set\n if (hideTimer.current) clearTimeout(hideTimer.current);\n hideTimer.current = undefined;\n };\n }, [state]);\n\n const formRef = useRef<HTMLFormElement>(null);\n const saveButtonRef = useRef<HTMLButtonElement>(null);\n const cancelButtonRef = useRef<HTMLButtonElement>(null);\n\n const onFocus = useCallback(() => {\n if (isFocusWithinRef.current) return;\n isFocusWithinRef.current = true;\n setFocusWithin(true);\n }, [isFocusWithin, setFocusWithin]);\n\n const onBlur = useCallback(\n (e: React.FocusEvent) => {\n if (!isFocusWithinRef.current) return;\n // If the user switched to another element within the form\n // consider that we're still focused within the form\n if (e.currentTarget.contains(e.relatedTarget)) return;\n\n isFocusWithinRef.current = false;\n setFocusWithin(false);\n },\n [isFocusWithin, setFocusWithin],\n );\n\n const onInputHandler = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n dispatch(Event.Touch);\n onInput?.(e);\n },\n [dispatch, onInput],\n );\n\n const onFormSubmit = useCallback(\n async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n // Prevent submitting the form if the user has not yet entered any text\n if (state === State.Initial) {\n return;\n }\n\n try {\n dispatch(Event.Save);\n saveButtonRef.current?.blur();\n await onSave?.(e);\n dispatch(Event.Saved);\n } catch {\n // We don't really need to do anything here, we just don't want to display the\n // 'saved' label, obviously. The user of the component can update the error to\n // show what failed.\n dispatch(Event.SaveError);\n }\n },\n [onSave, state, hideTimer],\n );\n\n const onFormReset = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n cancelButtonRef.current?.blur();\n onCancel?.(e);\n dispatch(Event.Cancel);\n },\n [cancelButtonRef, onCancel],\n );\n\n return (\n <Root\n className={className}\n onSubmit={onFormSubmit}\n onReset={onFormReset}\n onFocus={onFocus}\n onBlur={onBlur}\n onClearServerErrors={onClearServerErrors}\n ref={formRef}\n >\n <Field name=\"input\" serverInvalid={serverInvalid}>\n <Label>{label}</Label>\n <div className={styles.controls}>\n <TextControl\n ref={ref}\n {...props}\n onInput={onInputHandler}\n disabled={disabled || state === State.Saving}\n />\n\n {shouldShowSaveButton && (\n <div className={styles[\"button-group\"]}>\n <Tooltip label={saveButtonLabel}>\n <Submit asChild>\n <Button\n type=\"submit\"\n kind=\"primary\"\n size=\"sm\"\n ref={saveButtonRef}\n disabled={state !== State.Dirty}\n iconOnly\n Icon={CheckIcon}\n />\n </Submit>\n </Tooltip>\n\n <Tooltip label={cancelButtonLabel}>\n <Button\n type=\"reset\"\n kind=\"secondary\"\n size=\"sm\"\n ref={cancelButtonRef}\n className={styles.button}\n disabled={state === State.Saving}\n iconOnly\n Icon={CancelIcon}\n />\n </Tooltip>\n </div>\n )}\n </div>\n\n {/*\n During the loading saving state, we only show the saving message.\n Else, we show whatever children were passed on, as they will have other validation messages\n */}\n {state === State.Saving ? (\n <LoadingMessage>{savingLabel}</LoadingMessage>\n ) : (\n children\n )}\n\n {savedLabel && state === State.Saved && (\n <SuccessMessage>{savedLabel}</SuccessMessage>\n )}\n\n {/*\n We show the help message only if:\n - the helpLabel is set\n - the form hasn't been validated yet\n - the 'serverInvalid' prop is not set\n - we're in the initial or dirty state\n */}\n {helpLabel && (state === State.Initial || state === State.Dirty) && (\n <ValidityState>\n {(validity) =>\n (validity === undefined || validity.valid) &&\n !serverInvalid && <HelpMessage>{helpLabel}</HelpMessage>\n }\n </ValidityState>\n )}\n </Field>\n </Root>\n );\n },\n);\n"],"names":["forwardRef","EditInPlace","useReducer","useRef","useState","useEffect","useCallback","jsx","Root","jsxs","Field","Label","styles","TextControl","Tooltip","Submit","Button","CancelIcon","LoadingMessage","SuccessMessage","ValidityState","HelpMessage"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditInPlace.js","sources":["../../../../../src/components/Form/Controls/EditInPlace/EditInPlace.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 React, {\n forwardRef,\n useCallback,\n useRef,\n useState,\n useEffect,\n useReducer,\n} from \"react\";\nimport { Submit, ValidityState } from \"@radix-ui/react-form\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport CancelIcon from \"@vector-im/compound-design-tokens/assets/web/icons/close\";\n\nimport styles from \"./EditInPlace.module.css\";\n\nimport {\n Field,\n HelpMessage,\n Label,\n LoadingMessage,\n Root,\n SuccessMessage,\n TextControl,\n} from \"../..\";\nimport { Button, Tooltip } from \"../../../..\";\n\ntype Props = {\n /**\n * The label for the control\n */\n label: string;\n\n /**\n * The CSS class name.\n */\n className?: string;\n\n /**\n * Callback for when the user confirms the change\n */\n onSave?: (e: React.FormEvent<HTMLFormElement>) => Promise<void> | void;\n\n /**\n * Callback for when the user wishes to cancel the change\n */\n onCancel?: (e: React.FormEvent<HTMLFormElement>) => void;\n\n /**\n * onInput event handler on the text control\n */\n onInput?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n\n /**\n * Callback for when the server validation errors should be cleared.\n */\n onClearServerErrors?: () => void;\n\n /**\n * Whether the field is in an error state according to the server validation.\n *\n * For validation messages, use native validations properties directly, or add custom error messages as children.\n */\n serverInvalid?: boolean;\n\n /**\n * Label to be displayed by the green check at the bottom. Will only be displayed\n * for 2 seconds after the onSave callback promise resolves successfully.\n */\n savedLabel?: string;\n\n /**\n * The label for the save button\n */\n saveButtonLabel: string;\n\n /**\n * The label for the 'in progress' saving caption\n */\n savingLabel: string;\n\n /**\n * The label for the cancel button\n */\n cancelButtonLabel: string;\n\n /**\n * Label to be displayed under the input as a help text\n */\n helpLabel?: string;\n\n /**\n * If true, disabled the entire component to disallow editing.\n */\n disabled?: boolean;\n} & React.ComponentProps<typeof TextControl>;\n\nenum State {\n /** No changes on the input has been made */\n Initial,\n\n /** The input has been changed */\n Dirty,\n\n /** The input is being saved */\n Saving,\n\n /** The input has been saved */\n Saved,\n}\n\nenum Event {\n Touch, // The user 'touched' the control\n Save, // The user has clicked the save button\n Saved, // The onSave callback finished successfully\n SaveError, // The onSave callback finished with an error\n Cancel, // The user has clicked the cancel button\n SavedTimeout, // The user has clicked the save button and the saved label has been shown for 2 seconds\n}\n\nfunction reducer(state: State, action: Event): State {\n switch (action) {\n case Event.Touch:\n if (state === State.Initial || state === State.Saved) return State.Dirty;\n else return state;\n\n case Event.Save:\n return State.Saving;\n\n case Event.Cancel:\n return State.Initial;\n\n case Event.Saved:\n if (state === State.Saving) return State.Saved;\n else return state;\n\n case Event.SaveError:\n if (state === State.Saving) return State.Initial;\n else return state;\n\n case Event.SavedTimeout:\n if (state === State.Saved) return State.Initial;\n else return state;\n }\n\n assertNever(action);\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unreachable value: ${value}`);\n}\n\n/**\n * A text box with save/cancel buttons that appear when the field is active.\n * Since thios control has its own 'save' button, it should *not* appear as part\n * of a larger form: it exists as its own form that submits separately.\n */\nexport const EditInPlace = forwardRef<HTMLInputElement, Props>(\n function EditInPlace(\n {\n className,\n label,\n onSave,\n onCancel,\n onInput,\n onClearServerErrors,\n serverInvalid,\n saveButtonLabel,\n cancelButtonLabel,\n savedLabel,\n savingLabel,\n helpLabel,\n disabled,\n children,\n ...props\n },\n ref,\n ) {\n const [state, dispatch] = useReducer(reducer, State.Initial);\n\n // Tracks the focus state of the form\n // This uses a `ref` to make sure the onFocus/onBlur callback don't trigger unnecessary re-renders\n // and a state to track the focus state and hide the buttons when the form is not focused\n const isFocusWithinRef = useRef(false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n\n const shouldShowSaveButton =\n state === State.Dirty || state === State.Saving || isFocusWithin;\n\n const hideTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n\n useEffect(() => {\n // Start a timer when we switch to the saved state\n if (state === State.Saved) {\n hideTimer.current = setTimeout(() => {\n dispatch(Event.SavedTimeout);\n hideTimer.current = undefined;\n }, 2000);\n }\n\n return () => {\n // Clear any timers that may have been set\n if (hideTimer.current) clearTimeout(hideTimer.current);\n hideTimer.current = undefined;\n };\n }, [state]);\n\n const formRef = useRef<HTMLFormElement>(null);\n const saveButtonRef = useRef<HTMLButtonElement>(null);\n const cancelButtonRef = useRef<HTMLButtonElement>(null);\n\n const onFocus = useCallback(() => {\n if (isFocusWithinRef.current) return;\n isFocusWithinRef.current = true;\n setFocusWithin(true);\n }, [isFocusWithin, setFocusWithin]);\n\n const onBlur = useCallback(\n (e: React.FocusEvent) => {\n if (!isFocusWithinRef.current) return;\n // If the user switched to another element within the form\n // consider that we're still focused within the form\n if (e.currentTarget.contains(e.relatedTarget)) return;\n\n isFocusWithinRef.current = false;\n setFocusWithin(false);\n },\n [isFocusWithin, setFocusWithin],\n );\n\n const onInputHandler = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n dispatch(Event.Touch);\n onInput?.(e);\n },\n [dispatch, onInput],\n );\n\n const onFormSubmit = useCallback(\n async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n // Prevent submitting the form if the user has not yet entered any text\n if (state === State.Initial) {\n return;\n }\n\n try {\n dispatch(Event.Save);\n saveButtonRef.current?.blur();\n await onSave?.(e);\n dispatch(Event.Saved);\n } catch {\n // We don't really need to do anything here, we just don't want to display the\n // 'saved' label, obviously. The user of the component can update the error to\n // show what failed.\n dispatch(Event.SaveError);\n }\n },\n [onSave, state, hideTimer],\n );\n\n const onFormReset = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n cancelButtonRef.current?.blur();\n onCancel?.(e);\n dispatch(Event.Cancel);\n },\n [cancelButtonRef, onCancel],\n );\n\n return (\n <Root\n className={className}\n onSubmit={onFormSubmit}\n onReset={onFormReset}\n onFocus={onFocus}\n onBlur={onBlur}\n onClearServerErrors={onClearServerErrors}\n ref={formRef}\n >\n <Field name=\"input\" serverInvalid={serverInvalid}>\n <Label>{label}</Label>\n <div className={styles.controls}>\n <TextControl\n ref={ref}\n {...props}\n onInput={onInputHandler}\n disabled={disabled || state === State.Saving}\n />\n\n {shouldShowSaveButton && (\n <div className={styles[\"button-group\"]}>\n <Tooltip label={saveButtonLabel}>\n <Submit asChild>\n <Button\n type=\"submit\"\n kind=\"primary\"\n size=\"sm\"\n ref={saveButtonRef}\n disabled={state !== State.Dirty}\n iconOnly\n Icon={CheckIcon}\n />\n </Submit>\n </Tooltip>\n\n <Tooltip label={cancelButtonLabel}>\n <Button\n type=\"reset\"\n kind=\"secondary\"\n size=\"sm\"\n ref={cancelButtonRef}\n className={styles.button}\n disabled={state === State.Saving}\n iconOnly\n Icon={CancelIcon}\n />\n </Tooltip>\n </div>\n )}\n </div>\n\n {/*\n During the loading saving state, we only show the saving message.\n Else, we show whatever children were passed on, as they will have other validation messages\n */}\n {state === State.Saving ? (\n <LoadingMessage>{savingLabel}</LoadingMessage>\n ) : (\n children\n )}\n\n {savedLabel && state === State.Saved && (\n <SuccessMessage>{savedLabel}</SuccessMessage>\n )}\n\n {/*\n We show the help message only if:\n - the helpLabel is set\n - the form hasn't been validated yet\n - the 'serverInvalid' prop is not set\n - we're in the initial or dirty state\n */}\n {helpLabel && (state === State.Initial || state === State.Dirty) && (\n <ValidityState>\n {(validity) =>\n (validity === undefined || validity.valid) &&\n !serverInvalid && <HelpMessage>{helpLabel}</HelpMessage>\n }\n </ValidityState>\n )}\n </Field>\n </Root>\n );\n },\n);\n"],"names":["EditInPlace","CancelIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditInPlace.js","sources":["../../../../../src/components/Form/Controls/EditInPlace/EditInPlace.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 React, {\n forwardRef,\n useCallback,\n useRef,\n useState,\n useEffect,\n useReducer,\n} from \"react\";\nimport { Submit, ValidityState } from \"@radix-ui/react-form\";\nimport CheckIcon from \"@vector-im/compound-design-tokens/assets/web/icons/check\";\nimport CancelIcon from \"@vector-im/compound-design-tokens/assets/web/icons/close\";\n\nimport styles from \"./EditInPlace.module.css\";\n\nimport {\n Field,\n HelpMessage,\n Label,\n LoadingMessage,\n Root,\n SuccessMessage,\n TextControl,\n} from \"../..\";\nimport { Button, Tooltip } from \"../../../..\";\n\ntype Props = {\n /**\n * The label for the control\n */\n label: string;\n\n /**\n * The CSS class name.\n */\n className?: string;\n\n /**\n * Callback for when the user confirms the change\n */\n onSave?: (e: React.FormEvent<HTMLFormElement>) => Promise<void> | void;\n\n /**\n * Callback for when the user wishes to cancel the change\n */\n onCancel?: (e: React.FormEvent<HTMLFormElement>) => void;\n\n /**\n * onInput event handler on the text control\n */\n onInput?: (e: React.ChangeEvent<HTMLInputElement>) => void;\n\n /**\n * Callback for when the server validation errors should be cleared.\n */\n onClearServerErrors?: () => void;\n\n /**\n * Whether the field is in an error state according to the server validation.\n *\n * For validation messages, use native validations properties directly, or add custom error messages as children.\n */\n serverInvalid?: boolean;\n\n /**\n * Label to be displayed by the green check at the bottom. Will only be displayed\n * for 2 seconds after the onSave callback promise resolves successfully.\n */\n savedLabel?: string;\n\n /**\n * The label for the save button\n */\n saveButtonLabel: string;\n\n /**\n * The label for the 'in progress' saving caption\n */\n savingLabel: string;\n\n /**\n * The label for the cancel button\n */\n cancelButtonLabel: string;\n\n /**\n * Label to be displayed under the input as a help text\n */\n helpLabel?: string;\n\n /**\n * If true, disabled the entire component to disallow editing.\n */\n disabled?: boolean;\n} & React.ComponentProps<typeof TextControl>;\n\nenum State {\n /** No changes on the input has been made */\n Initial,\n\n /** The input has been changed */\n Dirty,\n\n /** The input is being saved */\n Saving,\n\n /** The input has been saved */\n Saved,\n}\n\nenum Event {\n Touch, // The user 'touched' the control\n Save, // The user has clicked the save button\n Saved, // The onSave callback finished successfully\n SaveError, // The onSave callback finished with an error\n Cancel, // The user has clicked the cancel button\n SavedTimeout, // The user has clicked the save button and the saved label has been shown for 2 seconds\n}\n\nfunction reducer(state: State, action: Event): State {\n switch (action) {\n case Event.Touch:\n if (state === State.Initial || state === State.Saved) return State.Dirty;\n else return state;\n\n case Event.Save:\n return State.Saving;\n\n case Event.Cancel:\n return State.Initial;\n\n case Event.Saved:\n if (state === State.Saving) return State.Saved;\n else return state;\n\n case Event.SaveError:\n if (state === State.Saving) return State.Initial;\n else return state;\n\n case Event.SavedTimeout:\n if (state === State.Saved) return State.Initial;\n else return state;\n }\n\n assertNever(action);\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unreachable value: ${value}`);\n}\n\n/**\n * A text box with save/cancel buttons that appear when the field is active.\n * Since thios control has its own 'save' button, it should *not* appear as part\n * of a larger form: it exists as its own form that submits separately.\n */\nexport const EditInPlace = forwardRef<HTMLInputElement, Props>(\n function EditInPlace(\n {\n className,\n label,\n onSave,\n onCancel,\n onInput,\n onClearServerErrors,\n serverInvalid,\n saveButtonLabel,\n cancelButtonLabel,\n savedLabel,\n savingLabel,\n helpLabel,\n disabled,\n children,\n ...props\n },\n ref,\n ) {\n const [state, dispatch] = useReducer(reducer, State.Initial);\n\n // Tracks the focus state of the form\n // This uses a `ref` to make sure the onFocus/onBlur callback don't trigger unnecessary re-renders\n // and a state to track the focus state and hide the buttons when the form is not focused\n const isFocusWithinRef = useRef(false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n\n const shouldShowSaveButton =\n state === State.Dirty || state === State.Saving || isFocusWithin;\n\n const hideTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n\n useEffect(() => {\n // Start a timer when we switch to the saved state\n if (state === State.Saved) {\n hideTimer.current = setTimeout(() => {\n dispatch(Event.SavedTimeout);\n hideTimer.current = undefined;\n }, 2000);\n }\n\n return () => {\n // Clear any timers that may have been set\n if (hideTimer.current) clearTimeout(hideTimer.current);\n hideTimer.current = undefined;\n };\n }, [state]);\n\n const formRef = useRef<HTMLFormElement>(null);\n const saveButtonRef = useRef<HTMLButtonElement>(null);\n const cancelButtonRef = useRef<HTMLButtonElement>(null);\n\n const onFocus = useCallback(() => {\n if (isFocusWithinRef.current) return;\n isFocusWithinRef.current = true;\n setFocusWithin(true);\n }, [isFocusWithin, setFocusWithin]);\n\n const onBlur = useCallback(\n (e: React.FocusEvent) => {\n if (!isFocusWithinRef.current) return;\n // If the user switched to another element within the form\n // consider that we're still focused within the form\n if (e.currentTarget.contains(e.relatedTarget)) return;\n\n isFocusWithinRef.current = false;\n setFocusWithin(false);\n },\n [isFocusWithin, setFocusWithin],\n );\n\n const onInputHandler = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n dispatch(Event.Touch);\n onInput?.(e);\n },\n [dispatch, onInput],\n );\n\n const onFormSubmit = useCallback(\n async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n // Prevent submitting the form if the user has not yet entered any text\n if (state === State.Initial) {\n return;\n }\n\n try {\n dispatch(Event.Save);\n saveButtonRef.current?.blur();\n await onSave?.(e);\n dispatch(Event.Saved);\n } catch {\n // We don't really need to do anything here, we just don't want to display the\n // 'saved' label, obviously. The user of the component can update the error to\n // show what failed.\n dispatch(Event.SaveError);\n }\n },\n [onSave, state, hideTimer],\n );\n\n const onFormReset = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n cancelButtonRef.current?.blur();\n onCancel?.(e);\n dispatch(Event.Cancel);\n },\n [cancelButtonRef, onCancel],\n );\n\n return (\n <Root\n className={className}\n onSubmit={onFormSubmit}\n onReset={onFormReset}\n onFocus={onFocus}\n onBlur={onBlur}\n onClearServerErrors={onClearServerErrors}\n ref={formRef}\n >\n <Field name=\"input\" serverInvalid={serverInvalid}>\n <Label>{label}</Label>\n <div className={styles.controls}>\n <TextControl\n ref={ref}\n {...props}\n onInput={onInputHandler}\n disabled={disabled || state === State.Saving}\n />\n\n {shouldShowSaveButton && (\n <div className={styles[\"button-group\"]}>\n <Tooltip label={saveButtonLabel}>\n <Submit asChild>\n <Button\n type=\"submit\"\n kind=\"primary\"\n size=\"sm\"\n ref={saveButtonRef}\n disabled={state !== State.Dirty}\n iconOnly\n Icon={CheckIcon}\n />\n </Submit>\n </Tooltip>\n\n <Tooltip label={cancelButtonLabel}>\n <Button\n type=\"reset\"\n kind=\"secondary\"\n size=\"sm\"\n ref={cancelButtonRef}\n className={styles.button}\n disabled={state === State.Saving}\n iconOnly\n Icon={CancelIcon}\n />\n </Tooltip>\n </div>\n )}\n </div>\n\n {/*\n During the loading saving state, we only show the saving message.\n Else, we show whatever children were passed on, as they will have other validation messages\n */}\n {state === State.Saving ? (\n <LoadingMessage>{savingLabel}</LoadingMessage>\n ) : (\n children\n )}\n\n {savedLabel && state === State.Saved && (\n <SuccessMessage>{savedLabel}</SuccessMessage>\n )}\n\n {/*\n We show the help message only if:\n - the helpLabel is set\n - the form hasn't been validated yet\n - the 'serverInvalid' prop is not set\n - we're in the initial or dirty state\n */}\n {helpLabel && (state === State.Initial || state === State.Dirty) && (\n <ValidityState>\n {(validity) =>\n (validity === undefined || validity.valid) &&\n !serverInvalid && <HelpMessage>{helpLabel}</HelpMessage>\n }\n </ValidityState>\n )}\n </Field>\n </Root>\n );\n },\n);\n"],"names":["EditInPlace","CancelIcon"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MFA.cjs","sources":["../../../../../src/components/Form/Controls/MFA/MFA.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 React, {\n ComponentProps,\n ComponentRef,\n forwardRef,\n PropsWithoutRef,\n} from \"react\";\n\nimport styles from \"./MFA.module.css\";\nimport classNames from \"classnames\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype DigitProps = {\n filled: boolean;\n selected: boolean;\n};\n\nconst Digit: React.FC<DigitProps> = ({ filled, selected }) => (\n <div\n className={styles.digit}\n aria-hidden=\"true\"\n data-filled={filled ? \"\" : undefined}\n data-selected={selected ? \"\" : undefined}\n />\n);\n\ntype MFAProps = {\n className?: string;\n length?: number;\n disabled?: boolean;\n} & Omit<\n React.ComponentProps<\"input\">,\n \"type\" | \"inputMode\" | \"pattern\" | \"autoComplete\"\n>;\n\nexport const MFAInput = forwardRef(function MFAInput(\n { className, length = 6, ...props }: PropsWithoutRef<MFAProps>,\n ref: React.ForwardedRef<HTMLInputElement>,\n) {\n const classes = classNames(styles.container, className);\n const [currentLength, setCurrentLength] = React.useState(0);\n const [selection, setSelection] = React.useState<null | [number, number]>(\n null,\n );\n\n const update = (event: React.SyntheticEvent<HTMLInputElement>) => {\n const input = event.currentTarget;\n setCurrentLength(input.value?.length);\n\n if (\n document.activeElement !== input ||\n input.selectionStart === null ||\n input.selectionEnd === null\n ) {\n setSelection(null);\n } else {\n setSelection([input.selectionStart, input.selectionEnd]);\n }\n };\n\n return (\n <div className={classes}>\n <input\n {...props}\n inputMode=\"numeric\"\n // Showing digits on mobile browsers. Using numbers is not really suited\n // as it often adds a way to increment or decrement the current value\n // which is not interesting for this use case\n type=\"text\"\n minLength={0}\n maxLength={length}\n className={styles.control}\n pattern={`\\\\d{${length}}`}\n autoComplete=\"one-time-code\"\n onSelect={update}\n onFocus={update}\n onBlur={update}\n onMouseDown={update}\n onMouseMove={update}\n onMouseUp={update}\n onChange={update}\n ref={ref}\n />\n {Array.from(Array(length).keys()).map((index) => (\n <Digit\n key={index}\n filled={index < currentLength}\n selected={\n !!selection && index >= selection[0] && index < selection[1]\n }\n />\n ))}\n </div>\n );\n});\n\nexport const MFAControl = forwardRef<\n ComponentRef<typeof MFAInput>,\n ComponentProps<typeof MFAInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <MFAInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["jsx","styles","forwardRef","MFAInput","jsxs","Control"],"mappings":";;;;;;;AAyBA,MAAM,QAA8B,CAAC,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"MFA.cjs","sources":["../../../../../src/components/Form/Controls/MFA/MFA.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n type PropsWithoutRef,\n} from \"react\";\n\nimport styles from \"./MFA.module.css\";\nimport classNames from \"classnames\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype DigitProps = {\n filled: boolean;\n selected: boolean;\n};\n\nconst Digit: React.FC<DigitProps> = ({ filled, selected }) => (\n <div\n className={styles.digit}\n aria-hidden=\"true\"\n data-filled={filled ? \"\" : undefined}\n data-selected={selected ? \"\" : undefined}\n />\n);\n\ntype MFAProps = {\n className?: string;\n length?: number;\n disabled?: boolean;\n} & Omit<\n React.ComponentProps<\"input\">,\n \"type\" | \"inputMode\" | \"pattern\" | \"autoComplete\"\n>;\n\nexport const MFAInput = forwardRef(function MFAInput(\n { className, length = 6, ...props }: PropsWithoutRef<MFAProps>,\n ref: React.ForwardedRef<HTMLInputElement>,\n) {\n const classes = classNames(styles.container, className);\n const [currentLength, setCurrentLength] = React.useState(0);\n const [selection, setSelection] = React.useState<null | [number, number]>(\n null,\n );\n\n const update = (event: React.SyntheticEvent<HTMLInputElement>) => {\n const input = event.currentTarget;\n setCurrentLength(input.value?.length);\n\n if (\n document.activeElement !== input ||\n input.selectionStart === null ||\n input.selectionEnd === null\n ) {\n setSelection(null);\n } else {\n setSelection([input.selectionStart, input.selectionEnd]);\n }\n };\n\n return (\n <div className={classes}>\n <input\n {...props}\n inputMode=\"numeric\"\n // Showing digits on mobile browsers. Using numbers is not really suited\n // as it often adds a way to increment or decrement the current value\n // which is not interesting for this use case\n type=\"text\"\n minLength={0}\n maxLength={length}\n className={styles.control}\n pattern={`\\\\d{${length}}`}\n autoComplete=\"one-time-code\"\n onSelect={update}\n onFocus={update}\n onBlur={update}\n onMouseDown={update}\n onMouseMove={update}\n onMouseUp={update}\n onChange={update}\n ref={ref}\n />\n {Array.from(Array(length).keys()).map((index) => (\n <Digit\n key={index}\n filled={index < currentLength}\n selected={\n !!selection && index >= selection[0] && index < selection[1]\n }\n />\n ))}\n </div>\n );\n});\n\nexport const MFAControl = forwardRef<\n ComponentRef<typeof MFAInput>,\n ComponentProps<typeof MFAInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <MFAInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["jsx","styles","forwardRef","MFAInput","jsxs","Control"],"mappings":";;;;;;;AAyBA,MAAM,QAA8B,CAAC,EAAE,QAAQ,eAC7CA,2BAAAA;AAAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAWC,WAAAA,QAAO;AAAA,IAClB,eAAY;AAAA,IACZ,eAAa,SAAS,KAAK;AAAA,IAC3B,iBAAe,WAAW,KAAK;AAAA,EAAA;AACjC;AAYK,MAAM,WAAWC,MAAAA,WAAW,SAASC,UAC1C,EAAE,WAAW,SAAS,GAAG,GAAG,MAAA,GAC5B,KACA;AACA,QAAM,UAAU,WAAWF,mBAAO,WAAW,SAAS;AACtD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,CAAC;AAC1D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM;AAAA,IACtC;AAAA,EAAA;AAGF,QAAM,SAAS,CAAC,UAAkD;AAChE,UAAM,QAAQ,MAAM;AACpB,qBAAiB,MAAM,OAAO,MAAM;AAEpC,QACE,SAAS,kBAAkB,SAC3B,MAAM,mBAAmB,QACzB,MAAM,iBAAiB,MACvB;AACA,mBAAa,IAAI;AAAA,IACnB,OAAO;AACL,mBAAa,CAAC,MAAM,gBAAgB,MAAM,YAAY,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,SACEG,2BAAAA,KAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,IAAAJ,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAU;AAAA,QAIV,MAAK;AAAA,QACL,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAWC,WAAAA,QAAO;AAAA,QAClB,SAAS,OAAO,MAAM;AAAA,QACtB,cAAa;AAAA,QACb,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,aAAa;AAAA,QACb,WAAW;AAAA,QACX,UAAU;AAAA,QACV;AAAA,MAAA;AAAA,IAAA;AAAA,IAED,MAAM,KAAK,MAAM,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,UACrCD,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAEC,QAAQ,QAAQ;AAAA,QAChB,UACE,CAAC,CAAC,aAAa,SAAS,UAAU,CAAC,KAAK,QAAQ,UAAU,CAAC;AAAA,MAAA;AAAA,MAHxD;AAAA,IAAA,CAMR;AAAA,EAAA,GACH;AAEJ,CAAC;AAEM,MAAM,aAAaE,MAAAA,WAGxB,SAAS,cAAc,OAAO,KAAK;AACnC,SACEF,2BAAAA,IAACK,UAAAA,WAAQ,SAAO,MACd,yCAAC,UAAA,EAAS,KAAW,GAAG,MAAA,CAAO,EAAA,CACjC;AAEJ,CAAC;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MFA.js","sources":["../../../../../src/components/Form/Controls/MFA/MFA.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 React, {\n ComponentProps,\n ComponentRef,\n forwardRef,\n PropsWithoutRef,\n} from \"react\";\n\nimport styles from \"./MFA.module.css\";\nimport classNames from \"classnames\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype DigitProps = {\n filled: boolean;\n selected: boolean;\n};\n\nconst Digit: React.FC<DigitProps> = ({ filled, selected }) => (\n <div\n className={styles.digit}\n aria-hidden=\"true\"\n data-filled={filled ? \"\" : undefined}\n data-selected={selected ? \"\" : undefined}\n />\n);\n\ntype MFAProps = {\n className?: string;\n length?: number;\n disabled?: boolean;\n} & Omit<\n React.ComponentProps<\"input\">,\n \"type\" | \"inputMode\" | \"pattern\" | \"autoComplete\"\n>;\n\nexport const MFAInput = forwardRef(function MFAInput(\n { className, length = 6, ...props }: PropsWithoutRef<MFAProps>,\n ref: React.ForwardedRef<HTMLInputElement>,\n) {\n const classes = classNames(styles.container, className);\n const [currentLength, setCurrentLength] = React.useState(0);\n const [selection, setSelection] = React.useState<null | [number, number]>(\n null,\n );\n\n const update = (event: React.SyntheticEvent<HTMLInputElement>) => {\n const input = event.currentTarget;\n setCurrentLength(input.value?.length);\n\n if (\n document.activeElement !== input ||\n input.selectionStart === null ||\n input.selectionEnd === null\n ) {\n setSelection(null);\n } else {\n setSelection([input.selectionStart, input.selectionEnd]);\n }\n };\n\n return (\n <div className={classes}>\n <input\n {...props}\n inputMode=\"numeric\"\n // Showing digits on mobile browsers. Using numbers is not really suited\n // as it often adds a way to increment or decrement the current value\n // which is not interesting for this use case\n type=\"text\"\n minLength={0}\n maxLength={length}\n className={styles.control}\n pattern={`\\\\d{${length}}`}\n autoComplete=\"one-time-code\"\n onSelect={update}\n onFocus={update}\n onBlur={update}\n onMouseDown={update}\n onMouseMove={update}\n onMouseUp={update}\n onChange={update}\n ref={ref}\n />\n {Array.from(Array(length).keys()).map((index) => (\n <Digit\n key={index}\n filled={index < currentLength}\n selected={\n !!selection && index >= selection[0] && index < selection[1]\n }\n />\n ))}\n </div>\n );\n});\n\nexport const MFAControl = forwardRef<\n ComponentRef<typeof MFAInput>,\n ComponentProps<typeof MFAInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <MFAInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["MFAInput"],"mappings":";;;;;AAyBA,MAAM,QAA8B,CAAC,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"MFA.js","sources":["../../../../../src/components/Form/Controls/MFA/MFA.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n type PropsWithoutRef,\n} from \"react\";\n\nimport styles from \"./MFA.module.css\";\nimport classNames from \"classnames\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype DigitProps = {\n filled: boolean;\n selected: boolean;\n};\n\nconst Digit: React.FC<DigitProps> = ({ filled, selected }) => (\n <div\n className={styles.digit}\n aria-hidden=\"true\"\n data-filled={filled ? \"\" : undefined}\n data-selected={selected ? \"\" : undefined}\n />\n);\n\ntype MFAProps = {\n className?: string;\n length?: number;\n disabled?: boolean;\n} & Omit<\n React.ComponentProps<\"input\">,\n \"type\" | \"inputMode\" | \"pattern\" | \"autoComplete\"\n>;\n\nexport const MFAInput = forwardRef(function MFAInput(\n { className, length = 6, ...props }: PropsWithoutRef<MFAProps>,\n ref: React.ForwardedRef<HTMLInputElement>,\n) {\n const classes = classNames(styles.container, className);\n const [currentLength, setCurrentLength] = React.useState(0);\n const [selection, setSelection] = React.useState<null | [number, number]>(\n null,\n );\n\n const update = (event: React.SyntheticEvent<HTMLInputElement>) => {\n const input = event.currentTarget;\n setCurrentLength(input.value?.length);\n\n if (\n document.activeElement !== input ||\n input.selectionStart === null ||\n input.selectionEnd === null\n ) {\n setSelection(null);\n } else {\n setSelection([input.selectionStart, input.selectionEnd]);\n }\n };\n\n return (\n <div className={classes}>\n <input\n {...props}\n inputMode=\"numeric\"\n // Showing digits on mobile browsers. Using numbers is not really suited\n // as it often adds a way to increment or decrement the current value\n // which is not interesting for this use case\n type=\"text\"\n minLength={0}\n maxLength={length}\n className={styles.control}\n pattern={`\\\\d{${length}}`}\n autoComplete=\"one-time-code\"\n onSelect={update}\n onFocus={update}\n onBlur={update}\n onMouseDown={update}\n onMouseMove={update}\n onMouseUp={update}\n onChange={update}\n ref={ref}\n />\n {Array.from(Array(length).keys()).map((index) => (\n <Digit\n key={index}\n filled={index < currentLength}\n selected={\n !!selection && index >= selection[0] && index < selection[1]\n }\n />\n ))}\n </div>\n );\n});\n\nexport const MFAControl = forwardRef<\n ComponentRef<typeof MFAInput>,\n ComponentProps<typeof MFAInput>\n>(function ActionControl(props, ref) {\n return (\n <Control asChild>\n <MFAInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["MFAInput"],"mappings":";;;;;AAyBA,MAAM,QAA8B,CAAC,EAAE,QAAQ,eAC7C;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAW,OAAO;AAAA,IAClB,eAAY;AAAA,IACZ,eAAa,SAAS,KAAK;AAAA,IAC3B,iBAAe,WAAW,KAAK;AAAA,EAAA;AACjC;AAYK,MAAM,WAAW,WAAW,SAASA,UAC1C,EAAE,WAAW,SAAS,GAAG,GAAG,MAAA,GAC5B,KACA;AACA,QAAM,UAAU,WAAW,OAAO,WAAW,SAAS;AACtD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,CAAC;AAC1D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM;AAAA,IACtC;AAAA,EAAA;AAGF,QAAM,SAAS,CAAC,UAAkD;AAChE,UAAM,QAAQ,MAAM;AACpB,qBAAiB,MAAM,OAAO,MAAM;AAEpC,QACE,SAAS,kBAAkB,SAC3B,MAAM,mBAAmB,QACzB,MAAM,iBAAiB,MACvB;AACA,mBAAa,IAAI;AAAA,IACnB,OAAO;AACL,mBAAa,CAAC,MAAM,gBAAgB,MAAM,YAAY,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,SACE,qBAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAU;AAAA,QAIV,MAAK;AAAA,QACL,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO,MAAM;AAAA,QACtB,cAAa;AAAA,QACb,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,aAAa;AAAA,QACb,WAAW;AAAA,QACX,UAAU;AAAA,QACV;AAAA,MAAA;AAAA,IAAA;AAAA,IAED,MAAM,KAAK,MAAM,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,UACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,QAAQ,QAAQ;AAAA,QAChB,UACE,CAAC,CAAC,aAAa,SAAS,UAAU,CAAC,KAAK,QAAQ,UAAU,CAAC;AAAA,MAAA;AAAA,MAHxD;AAAA,IAAA,CAMR;AAAA,EAAA,GACH;AAEJ,CAAC;AAEM,MAAM,aAAa,WAGxB,SAAS,cAAc,OAAO,KAAK;AACnC,SACE,oBAAC,WAAQ,SAAO,MACd,8BAAC,UAAA,EAAS,KAAW,GAAG,MAAA,CAAO,EAAA,CACjC;AAEJ,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Password.cjs","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"sourcesContent":["/*\nCopyright 2023 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*/\nimport React, {\n ComponentRef,\n forwardRef,\n ComponentProps,\n useReducer,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport { ActionInput } from \"../Action\";\n\nimport VisibilityOn from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-on\";\nimport VisibilityOff from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-off\";\n\nconst showState = {\n isHidden: true,\n icon: VisibilityOff,\n label: \"Show\",\n type: \"password\",\n};\n\nconst hideState = {\n isHidden: false,\n icon: VisibilityOn,\n label: \"Hide\",\n type: \"text\",\n};\n\ntype Props = Omit<\n ComponentProps<typeof ActionInput>,\n \"type\" | \"actionLabel\" | \"onActionClick\" | \"Icon\"\n>;\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Standalone input to be used outside of Radix forms.\n */\nexport const PasswordInput = forwardRef<\n ComponentRef<typeof ActionInput>,\n Props\n>(function PasswordControl(props, ref) {\n const [{ icon, label, type }, togglePasswordVisibility] = useReducer(\n (state) => (!state.isHidden ? showState : hideState),\n showState,\n );\n\n return (\n <ActionInput\n ref={ref}\n {...props}\n Icon={icon}\n // TODO: Replace with a function that deal with i18n of those values\n actionLabel={label}\n onActionClick={() => togglePasswordVisibility()}\n type={type}\n />\n );\n});\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Control to be used in a Radix form.\n */\nexport const PasswordControl = forwardRef<\n ComponentRef<typeof PasswordInput>,\n ComponentProps<typeof PasswordInput>\n>(function PasswordControl(props, ref) {\n return (\n <Control asChild>\n <PasswordInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","PasswordControl","useReducer","jsx","ActionInput","Control"],"mappings":";;;;;;;;AAmBA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAYO,MAAM,gBAAgBA,MAAAA,WAG3B,SAASC,iBAAgB,OAAO,KAAK;AACrC,QAAM,CAAC,EAAE,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"Password.cjs","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"sourcesContent":["/*\nCopyright 2023 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*/\nimport React, {\n type ComponentRef,\n forwardRef,\n type ComponentProps,\n useReducer,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport { ActionInput } from \"../Action\";\n\nimport VisibilityOn from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-on\";\nimport VisibilityOff from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-off\";\n\nconst showState = {\n isHidden: true,\n icon: VisibilityOff,\n label: \"Show\",\n type: \"password\",\n};\n\nconst hideState = {\n isHidden: false,\n icon: VisibilityOn,\n label: \"Hide\",\n type: \"text\",\n};\n\ntype Props = Omit<\n ComponentProps<typeof ActionInput>,\n \"type\" | \"actionLabel\" | \"onActionClick\" | \"Icon\"\n>;\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Standalone input to be used outside of Radix forms.\n */\nexport const PasswordInput = forwardRef<\n ComponentRef<typeof ActionInput>,\n Props\n>(function PasswordControl(props, ref) {\n const [{ icon, label, type }, togglePasswordVisibility] = useReducer(\n (state) => (!state.isHidden ? showState : hideState),\n showState,\n );\n\n return (\n <ActionInput\n ref={ref}\n {...props}\n Icon={icon}\n // TODO: Replace with a function that deal with i18n of those values\n actionLabel={label}\n onActionClick={() => togglePasswordVisibility()}\n type={type}\n />\n );\n});\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Control to be used in a Radix form.\n */\nexport const PasswordControl = forwardRef<\n ComponentRef<typeof PasswordInput>,\n ComponentProps<typeof PasswordInput>\n>(function PasswordControl(props, ref) {\n return (\n <Control asChild>\n <PasswordInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","PasswordControl","useReducer","jsx","ActionInput","Control"],"mappings":";;;;;;;;AAmBA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAYO,MAAM,gBAAgBA,MAAAA,WAG3B,SAASC,iBAAgB,OAAO,KAAK;AACrC,QAAM,CAAC,EAAE,MAAM,OAAO,KAAA,GAAQ,wBAAwB,IAAIC,MAAAA;AAAAA,IACxD,CAAC,UAAW,CAAC,MAAM,WAAW,YAAY;AAAA,IAC1C;AAAA,EAAA;AAGF,SACEC,2BAAAA;AAAAA,IAACC,OAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACC,GAAG;AAAA,MACJ,MAAM;AAAA,MAEN,aAAa;AAAA,MACb,eAAe,MAAM,yBAAA;AAAA,MACrB;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;AAOM,MAAM,kBAAkBJ,MAAAA,WAG7B,SAASC,iBAAgB,OAAO,KAAK;AACrC,SACEE,2BAAAA,IAACE,UAAAA,WAAQ,SAAO,MACd,yCAAC,eAAA,EAAc,KAAW,GAAG,MAAA,CAAO,EAAA,CACtC;AAEJ,CAAC;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Password.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,EAGZ,cAAc,
|
|
1
|
+
{"version":3,"file":"Password.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,EAGZ,KAAK,cAAc,EAEpB,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAmBxC,KAAK,KAAK,GAAG,IAAI,CACf,cAAc,CAAC,OAAO,WAAW,CAAC,EAClC,MAAM,GAAG,aAAa,GAAG,eAAe,GAAG,MAAM,CAClD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,6FAoBxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,eAAe,kJAS1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Password.js","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"sourcesContent":["/*\nCopyright 2023 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*/\nimport React, {\n ComponentRef,\n forwardRef,\n ComponentProps,\n useReducer,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport { ActionInput } from \"../Action\";\n\nimport VisibilityOn from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-on\";\nimport VisibilityOff from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-off\";\n\nconst showState = {\n isHidden: true,\n icon: VisibilityOff,\n label: \"Show\",\n type: \"password\",\n};\n\nconst hideState = {\n isHidden: false,\n icon: VisibilityOn,\n label: \"Hide\",\n type: \"text\",\n};\n\ntype Props = Omit<\n ComponentProps<typeof ActionInput>,\n \"type\" | \"actionLabel\" | \"onActionClick\" | \"Icon\"\n>;\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Standalone input to be used outside of Radix forms.\n */\nexport const PasswordInput = forwardRef<\n ComponentRef<typeof ActionInput>,\n Props\n>(function PasswordControl(props, ref) {\n const [{ icon, label, type }, togglePasswordVisibility] = useReducer(\n (state) => (!state.isHidden ? showState : hideState),\n showState,\n );\n\n return (\n <ActionInput\n ref={ref}\n {...props}\n Icon={icon}\n // TODO: Replace with a function that deal with i18n of those values\n actionLabel={label}\n onActionClick={() => togglePasswordVisibility()}\n type={type}\n />\n );\n});\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Control to be used in a Radix form.\n */\nexport const PasswordControl = forwardRef<\n ComponentRef<typeof PasswordInput>,\n ComponentProps<typeof PasswordInput>\n>(function PasswordControl(props, ref) {\n return (\n <Control asChild>\n <PasswordInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["PasswordControl"],"mappings":";;;;;;AAmBA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAYO,MAAM,gBAAgB,WAG3B,SAASA,iBAAgB,OAAO,KAAK;AACrC,QAAM,CAAC,EAAE,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"Password.js","sources":["../../../../../src/components/Form/Controls/Password/Password.tsx"],"sourcesContent":["/*\nCopyright 2023 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*/\nimport React, {\n type ComponentRef,\n forwardRef,\n type ComponentProps,\n useReducer,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport { ActionInput } from \"../Action\";\n\nimport VisibilityOn from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-on\";\nimport VisibilityOff from \"@vector-im/compound-design-tokens/assets/web/icons/visibility-off\";\n\nconst showState = {\n isHidden: true,\n icon: VisibilityOff,\n label: \"Show\",\n type: \"password\",\n};\n\nconst hideState = {\n isHidden: false,\n icon: VisibilityOn,\n label: \"Hide\",\n type: \"text\",\n};\n\ntype Props = Omit<\n ComponentProps<typeof ActionInput>,\n \"type\" | \"actionLabel\" | \"onActionClick\" | \"Icon\"\n>;\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Standalone input to be used outside of Radix forms.\n */\nexport const PasswordInput = forwardRef<\n ComponentRef<typeof ActionInput>,\n Props\n>(function PasswordControl(props, ref) {\n const [{ icon, label, type }, togglePasswordVisibility] = useReducer(\n (state) => (!state.isHidden ? showState : hideState),\n showState,\n );\n\n return (\n <ActionInput\n ref={ref}\n {...props}\n Icon={icon}\n // TODO: Replace with a function that deal with i18n of those values\n actionLabel={label}\n onActionClick={() => togglePasswordVisibility()}\n type={type}\n />\n );\n});\n\n/**\n * A password input with a toggle to show/hide the password.\n *\n * Control to be used in a Radix form.\n */\nexport const PasswordControl = forwardRef<\n ComponentRef<typeof PasswordInput>,\n ComponentProps<typeof PasswordInput>\n>(function PasswordControl(props, ref) {\n return (\n <Control asChild>\n <PasswordInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["PasswordControl"],"mappings":";;;;;;AAmBA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,YAAY;AAAA,EAChB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAYO,MAAM,gBAAgB,WAG3B,SAASA,iBAAgB,OAAO,KAAK;AACrC,QAAM,CAAC,EAAE,MAAM,OAAO,KAAA,GAAQ,wBAAwB,IAAI;AAAA,IACxD,CAAC,UAAW,CAAC,MAAM,WAAW,YAAY;AAAA,IAC1C;AAAA,EAAA;AAGF,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACC,GAAG;AAAA,MACJ,MAAM;AAAA,MAEN,aAAa;AAAA,MACb,eAAe,MAAM,yBAAA;AAAA,MACrB;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;AAOM,MAAM,kBAAkB,WAG7B,SAASA,iBAAgB,OAAO,KAAK;AACrC,SACE,oBAAC,WAAQ,SAAO,MACd,8BAAC,eAAA,EAAc,KAAW,GAAG,MAAA,CAAO,EAAA,CACtC;AAEJ,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.cjs","sources":["../../../../../src/components/Form/Controls/Radio/Radio.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n ComponentProps,\n ComponentRef,\n forwardRef,\n PropsWithChildren,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Radio.module.css\";\n\ntype RadioProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<React.ComponentPropsWithoutRef<\"input\">, \"type\">;\n\n/**\n * A radio component.\n */\nexport const RadioInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<RadioProps>\n>(function Radio({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} {...props} className={styles.input} type=\"radio\" />\n <div className={styles.ui} />\n </div>\n );\n});\n\nexport const RadioControl = forwardRef<\n ComponentRef<typeof RadioInput>,\n ComponentProps<typeof RadioInput>\n>(function RadioControl(props, ref) {\n return (\n <Control asChild>\n <RadioInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","classnames","styles","jsxs","jsx","RadioControl","Control"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"Radio.cjs","sources":["../../../../../src/components/Form/Controls/Radio/Radio.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n type PropsWithChildren,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Radio.module.css\";\n\ntype RadioProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<React.ComponentPropsWithoutRef<\"input\">, \"type\">;\n\n/**\n * A radio component.\n */\nexport const RadioInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<RadioProps>\n>(function Radio({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} {...props} className={styles.input} type=\"radio\" />\n <div className={styles.ui} />\n </div>\n );\n});\n\nexport const RadioControl = forwardRef<\n ComponentRef<typeof RadioInput>,\n ComponentProps<typeof RadioInput>\n>(function RadioControl(props, ref) {\n return (\n <Control asChild>\n <RadioInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","classnames","styles","jsxs","jsx","RadioControl","Control"],"mappings":";;;;;;;AA8BO,MAAM,aAAaA,MAAAA,WAGxB,SAAS,MAAM,EAAE,WAAW,GAAG,MAAA,GAAS,KAAK;AAC7C,QAAM,UAAUC,WAAWC,qBAAO,WAAW,SAAS;AACtD,SACEC,2BAAAA,KAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,IAAAC,2BAAAA,IAAC,SAAA,EAAM,KAAW,GAAG,OAAO,WAAWF,aAAAA,QAAO,OAAO,MAAK,SAAQ;AAAA,IAClEE,2BAAAA,IAAC,OAAA,EAAI,WAAWF,aAAAA,QAAO,GAAA,CAAI;AAAA,EAAA,GAC7B;AAEJ,CAAC;AAEM,MAAM,eAAeF,MAAAA,WAG1B,SAASK,cAAa,OAAO,KAAK;AAClC,SACED,2BAAAA,IAACE,UAAAA,WAAQ,SAAO,MACd,yCAAC,YAAA,EAAW,KAAW,GAAG,MAAA,CAAO,EAAA,CACnC;AAEJ,CAAC;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.js","sources":["../../../../../src/components/Form/Controls/Radio/Radio.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n ComponentProps,\n ComponentRef,\n forwardRef,\n PropsWithChildren,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Radio.module.css\";\n\ntype RadioProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<React.ComponentPropsWithoutRef<\"input\">, \"type\">;\n\n/**\n * A radio component.\n */\nexport const RadioInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<RadioProps>\n>(function Radio({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} {...props} className={styles.input} type=\"radio\" />\n <div className={styles.ui} />\n </div>\n );\n});\n\nexport const RadioControl = forwardRef<\n ComponentRef<typeof RadioInput>,\n ComponentProps<typeof RadioInput>\n>(function RadioControl(props, ref) {\n return (\n <Control asChild>\n <RadioInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["classnames","RadioControl"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"Radio.js","sources":["../../../../../src/components/Form/Controls/Radio/Radio.tsx"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd.\nCopyright 2023 The Matrix.org Foundation C.I.C.\nCopyright 2023 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 classnames from \"classnames\";\nimport React, {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n type PropsWithChildren,\n} from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\nimport styles from \"./Radio.module.css\";\n\ntype RadioProps = {\n /**\n * The CSS class name.\n */\n className?: string;\n} & Omit<React.ComponentPropsWithoutRef<\"input\">, \"type\">;\n\n/**\n * A radio component.\n */\nexport const RadioInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<RadioProps>\n>(function Radio({ className, ...props }, ref) {\n const classes = classnames(styles.container, className);\n return (\n <div className={classes}>\n <input ref={ref} {...props} className={styles.input} type=\"radio\" />\n <div className={styles.ui} />\n </div>\n );\n});\n\nexport const RadioControl = forwardRef<\n ComponentRef<typeof RadioInput>,\n ComponentProps<typeof RadioInput>\n>(function RadioControl(props, ref) {\n return (\n <Control asChild>\n <RadioInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["classnames","RadioControl"],"mappings":";;;;;AA8BO,MAAM,aAAa,WAGxB,SAAS,MAAM,EAAE,WAAW,GAAG,MAAA,GAAS,KAAK;AAC7C,QAAM,UAAUA,WAAW,OAAO,WAAW,SAAS;AACtD,SACE,qBAAC,OAAA,EAAI,WAAW,SACd,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAM,KAAW,GAAG,OAAO,WAAW,OAAO,OAAO,MAAK,SAAQ;AAAA,IAClE,oBAAC,OAAA,EAAI,WAAW,OAAO,GAAA,CAAI;AAAA,EAAA,GAC7B;AAEJ,CAAC;AAEM,MAAM,eAAe,WAG1B,SAASC,cAAa,OAAO,KAAK;AAClC,SACE,oBAAC,WAAQ,SAAO,MACd,8BAAC,YAAA,EAAW,KAAW,GAAG,MAAA,CAAO,EAAA,CACnC;AAEJ,CAAC;"}
|
|
@@ -8,14 +8,16 @@ const InlineField = require("../../InlineField.cjs");
|
|
|
8
8
|
const reactForm = require("@radix-ui/react-form");
|
|
9
9
|
const Toggle = require("../Toggle/Toggle.cjs");
|
|
10
10
|
const SettingsToggleInput = React.forwardRef(function Toggle$1({ className, label, helpMessage, disabledMessage, name, ...props }, ref) {
|
|
11
|
+
const generatedId = React.useId();
|
|
12
|
+
const id = props.id ?? generatedId;
|
|
11
13
|
const content = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
12
14
|
InlineField.InlineField,
|
|
13
15
|
{
|
|
14
16
|
className,
|
|
15
17
|
name,
|
|
16
|
-
control: /* @__PURE__ */ jsxRuntime.jsx(Toggle.ToggleInput, { ref, ...props }),
|
|
18
|
+
control: /* @__PURE__ */ jsxRuntime.jsx(Toggle.ToggleInput, { id, ref, ...props }),
|
|
17
19
|
children: [
|
|
18
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label.Label, { children: label }),
|
|
20
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label.Label, { htmlFor: id, children: label }),
|
|
19
21
|
helpMessage && /* @__PURE__ */ jsxRuntime.jsx(Message.HelpMessage, { children: helpMessage }),
|
|
20
22
|
disabledMessage && props.disabled && /* @__PURE__ */ jsxRuntime.jsx(Message.HelpMessage, { children: disabledMessage })
|
|
21
23
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsToggle.cjs","sources":["../../../../../src/components/Form/Controls/SettingsToggle/SettingsToggle.tsx"],"sourcesContent":["/*\nCopyright 2025 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 {\n ComponentProps,\n ComponentRef,\n forwardRef,\n PropsWithChildren,\n} from \"react\";\nimport { ToggleInput } from \"../Toggle\";\nimport { Label } from \"../../Label\";\nimport { HelpMessage } from \"../../Message\";\nimport { InlineField } from \"../../InlineField\";\nimport React from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype SettingsToggleProps = {\n /**\n * The CSS class name for the containing field.\n */\n className?: string;\n /**\n * The field name.\n */\n name: string;\n label: string;\n /**\n * Optional help text to display below the setting.\n */\n helpMessage?: string;\n /**\n * Optional help text to display below the setting.\n */\n disabledMessage?: string;\n} & Omit<ComponentProps<typeof ToggleInput>, \"type\">;\n\n/**\n * A toggle component.\n */\nexport const SettingsToggleInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<SettingsToggleProps>\n>(function Toggle(\n { className, label, helpMessage, disabledMessage, name, ...props },\n ref,\n) {\n const content = (\n <InlineField\n className={className}\n name={name}\n control={<ToggleInput ref={ref} {...props} />}\n >\n <Label>{label}</Label>\n {helpMessage && <HelpMessage>{helpMessage}</HelpMessage>}\n {disabledMessage && props.disabled && (\n <HelpMessage>{disabledMessage}</HelpMessage>\n )}\n </InlineField>\n );\n return content;\n});\n\n/**\n * A styled checkbox input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const SettingsToggleControl = forwardRef<\n ComponentRef<typeof SettingsToggleInput>,\n ComponentProps<typeof SettingsToggleInput>\n>(function ToggleControl(props, ref) {\n return (\n <Control asChild>\n <SettingsToggleInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","Toggle","jsxs","InlineField","jsx","ToggleInput","Label","HelpMessage","Control"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"SettingsToggle.cjs","sources":["../../../../../src/components/Form/Controls/SettingsToggle/SettingsToggle.tsx"],"sourcesContent":["/*\nCopyright 2025 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 {\n type ComponentProps,\n type ComponentRef,\n forwardRef,\n type PropsWithChildren,\n useId,\n} from \"react\";\nimport { ToggleInput } from \"../Toggle\";\nimport { Label } from \"../../Label\";\nimport { HelpMessage } from \"../../Message\";\nimport { InlineField } from \"../../InlineField\";\nimport React from \"react\";\nimport { Control } from \"@radix-ui/react-form\";\n\ntype SettingsToggleProps = {\n /**\n * The CSS class name for the containing field.\n */\n className?: string;\n /**\n * The field name.\n */\n name: string;\n label: string;\n /**\n * Optional help text to display below the setting.\n */\n helpMessage?: string;\n /**\n * Optional help text to display below the setting.\n */\n disabledMessage?: string;\n} & Omit<ComponentProps<typeof ToggleInput>, \"type\">;\n\n/**\n * A toggle component.\n */\nexport const SettingsToggleInput = forwardRef<\n HTMLInputElement,\n PropsWithChildren<SettingsToggleProps>\n>(function Toggle(\n { className, label, helpMessage, disabledMessage, name, ...props },\n ref,\n) {\n const generatedId = useId();\n const id = props.id ?? generatedId;\n const content = (\n <InlineField\n className={className}\n name={name}\n control={<ToggleInput id={id} ref={ref} {...props} />}\n >\n <Label htmlFor={id}>{label}</Label>\n {helpMessage && <HelpMessage>{helpMessage}</HelpMessage>}\n {disabledMessage && props.disabled && (\n <HelpMessage>{disabledMessage}</HelpMessage>\n )}\n </InlineField>\n );\n return content;\n});\n\n/**\n * A styled checkbox input wrapped in a `Control` component, for use in Radix forms.\n */\nexport const SettingsToggleControl = forwardRef<\n ComponentRef<typeof SettingsToggleInput>,\n ComponentProps<typeof SettingsToggleInput>\n>(function ToggleControl(props, ref) {\n return (\n <Control asChild>\n <SettingsToggleInput ref={ref} {...props} />\n </Control>\n );\n});\n"],"names":["forwardRef","Toggle","useId","jsxs","InlineField","jsx","ToggleInput","Label","HelpMessage","Control"],"mappings":";;;;;;;;;AA4CO,MAAM,sBAAsBA,MAAAA,WAGjC,SAASC,SACT,EAAE,WAAW,OAAO,aAAa,iBAAiB,MAAM,GAAG,MAAA,GAC3D,KACA;AACA,QAAM,cAAcC,MAAAA,MAAA;AACpB,QAAM,KAAK,MAAM,MAAM;AACvB,QAAM,UACJC,2BAAAA;AAAAA,IAACC,YAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,SAASC,2BAAAA,IAACC,oBAAA,EAAY,IAAQ,KAAW,GAAG,OAAO;AAAA,MAEnD,UAAA;AAAA,QAAAD,2BAAAA,IAACE,MAAAA,OAAA,EAAM,SAAS,IAAK,UAAA,OAAM;AAAA,QAC1B,eAAeF,2BAAAA,IAACG,QAAAA,aAAA,EAAa,UAAA,YAAA,CAAY;AAAA,QACzC,mBAAmB,MAAM,YACxBH,2BAAAA,IAACG,QAAAA,eAAa,UAAA,gBAAA,CAAgB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIpC,SAAO;AACT,CAAC;AAKM,MAAM,wBAAwBR,MAAAA,WAGnC,SAAS,cAAc,OAAO,KAAK;AACnC,SACEK,2BAAAA,IAACI,UAAAA,WAAQ,SAAO,MACd,yCAAC,qBAAA,EAAoB,KAAW,GAAG,MAAA,CAAO,EAAA,CAC5C;AAEJ,CAAC;;;"}
|