@bspk/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Accordion.d.ts +20 -0
- package/Accordion.js +18 -0
- package/Accordion.js.map +1 -0
- package/Badge.d.ts +33 -0
- package/Badge.js +43 -0
- package/Badge.js.map +1 -0
- package/BannerAlert.d.ts +53 -0
- package/BannerAlert.js +133 -0
- package/BannerAlert.js.map +1 -0
- package/BottomNavigation.d.ts +20 -0
- package/BottomNavigation.js +18 -0
- package/BottomNavigation.js.map +1 -0
- package/BottomSheet.d.ts +20 -0
- package/BottomSheet.js +18 -0
- package/BottomSheet.js.map +1 -0
- package/Breadcrumb.d.ts +20 -0
- package/Breadcrumb.js +18 -0
- package/Breadcrumb.js.map +1 -0
- package/Button.d.ts +66 -0
- package/Button.js +254 -0
- package/Button.js.map +1 -0
- package/ButtonDock.d.ts +20 -0
- package/ButtonDock.js +18 -0
- package/ButtonDock.js.map +1 -0
- package/CODE_OF_CONDUCT.md +137 -0
- package/CONTRIBUTING.md +42 -0
- package/Card.d.ts +37 -0
- package/Card.js +38 -0
- package/Card.js.map +1 -0
- package/Chart.d.ts +20 -0
- package/Chart.js +18 -0
- package/Chart.js.map +1 -0
- package/Checkbox.d.ts +26 -0
- package/Checkbox.js +123 -0
- package/Checkbox.js.map +1 -0
- package/CheckboxGroup.d.ts +49 -0
- package/CheckboxGroup.js +18 -0
- package/CheckboxGroup.js.map +1 -0
- package/CheckboxOption.d.ts +14 -0
- package/CheckboxOption.js +16 -0
- package/CheckboxOption.js.map +1 -0
- package/Chip.d.ts +50 -0
- package/Chip.js +87 -0
- package/Chip.js.map +1 -0
- package/DatePicker.d.ts +20 -0
- package/DatePicker.js +18 -0
- package/DatePicker.js.map +1 -0
- package/Dialog.d.ts +46 -0
- package/Dialog.js +221 -0
- package/Dialog.js.map +1 -0
- package/Divider.d.ts +44 -0
- package/Divider.js +76 -0
- package/Divider.js.map +1 -0
- package/Drawer.d.ts +20 -0
- package/Drawer.js +18 -0
- package/Drawer.js.map +1 -0
- package/Dropdown.d.ts +50 -0
- package/Dropdown.js +153 -0
- package/Dropdown.js.map +1 -0
- package/DropdownField.d.ts +16 -0
- package/DropdownField.js +19 -0
- package/DropdownField.js.map +1 -0
- package/EmptyState.d.ts +35 -0
- package/EmptyState.js +20 -0
- package/EmptyState.js.map +1 -0
- package/Fab.d.ts +43 -0
- package/Fab.js +146 -0
- package/Fab.js.map +1 -0
- package/FileUpload.d.ts +20 -0
- package/FileUpload.js +18 -0
- package/FileUpload.js.map +1 -0
- package/FormField.d.ts +48 -0
- package/FormField.js +39 -0
- package/FormField.js.map +1 -0
- package/GUIDELINES.md +41 -0
- package/Image.d.ts +20 -0
- package/Image.js +18 -0
- package/Image.js.map +1 -0
- package/Img.d.ts +26 -0
- package/Img.js +20 -0
- package/Img.js.map +1 -0
- package/InlineAlert.d.ts +31 -0
- package/InlineAlert.js +73 -0
- package/InlineAlert.js.map +1 -0
- package/LICENSE +395 -0
- package/Layout.d.ts +48 -0
- package/Layout.js +24 -0
- package/Layout.js.map +1 -0
- package/Link.d.ts +38 -0
- package/Link.js +37 -0
- package/Link.js.map +1 -0
- package/ListItem.d.ts +68 -0
- package/ListItem.js +207 -0
- package/ListItem.js.map +1 -0
- package/Menu.d.ts +86 -0
- package/Menu.js +98 -0
- package/Menu.js.map +1 -0
- package/MenuButton.d.ts +16 -0
- package/MenuButton.js +30 -0
- package/MenuButton.js.map +1 -0
- package/Modal.d.ts +31 -0
- package/Modal.js +53 -0
- package/Modal.js.map +1 -0
- package/MultiSelection.d.ts +20 -0
- package/MultiSelection.js +18 -0
- package/MultiSelection.js.map +1 -0
- package/NavigationRail.d.ts +20 -0
- package/NavigationRail.js +18 -0
- package/NavigationRail.js.map +1 -0
- package/NumberField.d.ts +17 -0
- package/NumberField.js +20 -0
- package/NumberField.js.map +1 -0
- package/NumberInput.d.ts +34 -0
- package/NumberInput.js +188 -0
- package/NumberInput.js.map +1 -0
- package/OTPInput.d.ts +20 -0
- package/OTPInput.js +18 -0
- package/OTPInput.js.map +1 -0
- package/PageControl.d.ts +20 -0
- package/PageControl.js +18 -0
- package/PageControl.js.map +1 -0
- package/PasswordInput.d.ts +20 -0
- package/PasswordInput.js +18 -0
- package/PasswordInput.js.map +1 -0
- package/PhoneNumberInput.d.ts +20 -0
- package/PhoneNumberInput.js +18 -0
- package/PhoneNumberInput.js.map +1 -0
- package/Popover.d.ts +44 -0
- package/Popover.js +152 -0
- package/Popover.js.map +1 -0
- package/Portal.d.ts +24 -0
- package/Portal.js +23 -0
- package/Portal.js.map +1 -0
- package/Profile.d.ts +41 -0
- package/Profile.js +83 -0
- package/Profile.js.map +1 -0
- package/ProgressBar.d.ts +38 -0
- package/ProgressBar.js +64 -0
- package/ProgressBar.js.map +1 -0
- package/ProgressCircle.d.ts +31 -0
- package/ProgressCircle.js +82 -0
- package/ProgressCircle.js.map +1 -0
- package/ProgressionStepper.d.ts +66 -0
- package/ProgressionStepper.js +197 -0
- package/ProgressionStepper.js.map +1 -0
- package/ProgressionStepperBar.d.ts +31 -0
- package/ProgressionStepperBar.js +51 -0
- package/ProgressionStepperBar.js.map +1 -0
- package/README.md +13 -0
- package/Radio.d.ts +17 -0
- package/Radio.js +97 -0
- package/Radio.js.map +1 -0
- package/RadioGroup.d.ts +31 -0
- package/RadioGroup.js +18 -0
- package/RadioGroup.js.map +1 -0
- package/RadioOption.d.ts +14 -0
- package/RadioOption.js +16 -0
- package/RadioOption.js.map +1 -0
- package/Rating.d.ts +16 -0
- package/Rating.js +18 -0
- package/Rating.js.map +1 -0
- package/SearchBar.d.ts +26 -0
- package/SearchBar.js +49 -0
- package/SearchBar.js.map +1 -0
- package/SegmentedControl.d.ts +69 -0
- package/SegmentedControl.js +136 -0
- package/SegmentedControl.js.map +1 -0
- package/Skeleton.d.ts +28 -0
- package/Skeleton.js +57 -0
- package/Skeleton.js.map +1 -0
- package/SliderInput.d.ts +16 -0
- package/SliderInput.js +18 -0
- package/SliderInput.js.map +1 -0
- package/Snackbar.d.ts +16 -0
- package/Snackbar.js +18 -0
- package/Snackbar.js.map +1 -0
- package/StylesProviderAnywhere.d.ts +10 -0
- package/StylesProviderAnywhere.js +29 -0
- package/StylesProviderAnywhere.js.map +1 -0
- package/StylesProviderBetterHomesGardens.d.ts +10 -0
- package/StylesProviderBetterHomesGardens.js +29 -0
- package/StylesProviderBetterHomesGardens.js.map +1 -0
- package/StylesProviderCartus.d.ts +10 -0
- package/StylesProviderCartus.js +29 -0
- package/StylesProviderCartus.js.map +1 -0
- package/StylesProviderCentury21.d.ts +10 -0
- package/StylesProviderCentury21.js +29 -0
- package/StylesProviderCentury21.js.map +1 -0
- package/StylesProviderColdwellBanker.d.ts +10 -0
- package/StylesProviderColdwellBanker.js +29 -0
- package/StylesProviderColdwellBanker.js.map +1 -0
- package/StylesProviderCorcoran.d.ts +10 -0
- package/StylesProviderCorcoran.js +29 -0
- package/StylesProviderCorcoran.js.map +1 -0
- package/StylesProviderDenaliBoss.d.ts +10 -0
- package/StylesProviderDenaliBoss.js +29 -0
- package/StylesProviderDenaliBoss.js.map +1 -0
- package/StylesProviderEra.d.ts +10 -0
- package/StylesProviderEra.js +29 -0
- package/StylesProviderEra.js.map +1 -0
- package/StylesProviderSothebys.d.ts +10 -0
- package/StylesProviderSothebys.js +29 -0
- package/StylesProviderSothebys.js.map +1 -0
- package/Switch.d.ts +33 -0
- package/Switch.js +85 -0
- package/Switch.js.map +1 -0
- package/SwitchGroup.d.ts +35 -0
- package/SwitchGroup.js +18 -0
- package/SwitchGroup.js.map +1 -0
- package/SwitchOption.d.ts +14 -0
- package/SwitchOption.js +16 -0
- package/SwitchOption.js.map +1 -0
- package/TabGroup.d.ts +73 -0
- package/TabGroup.js +119 -0
- package/TabGroup.js.map +1 -0
- package/Table.d.ts +45 -0
- package/Table.js +66 -0
- package/Table.js.map +1 -0
- package/Tag.d.ts +56 -0
- package/Tag.js +77 -0
- package/Tag.js.map +1 -0
- package/TextField.d.ts +16 -0
- package/TextField.js +19 -0
- package/TextField.js.map +1 -0
- package/TextInput.d.ts +45 -0
- package/TextInput.js +172 -0
- package/TextInput.js.map +1 -0
- package/Textarea.d.ts +63 -0
- package/Textarea.js +113 -0
- package/Textarea.js.map +1 -0
- package/TextareaField.d.ts +16 -0
- package/TextareaField.js +26 -0
- package/TextareaField.js.map +1 -0
- package/TimePicker.d.ts +16 -0
- package/TimePicker.js +18 -0
- package/TimePicker.js.map +1 -0
- package/ToggleOption.d.ts +27 -0
- package/ToggleOption.js +91 -0
- package/ToggleOption.js.map +1 -0
- package/Tooltip.d.ts +39 -0
- package/Tooltip.js +104 -0
- package/Tooltip.js.map +1 -0
- package/TopNavigation.d.ts +16 -0
- package/TopNavigation.js +18 -0
- package/TopNavigation.js.map +1 -0
- package/Txt.d.ts +38 -0
- package/Txt.js +21 -0
- package/Txt.js.map +1 -0
- package/hooks/useCheckboxGroupState.d.ts +37 -0
- package/hooks/useCheckboxGroupState.js +61 -0
- package/hooks/useCheckboxGroupState.js.map +1 -0
- package/hooks/useCheckboxState.d.ts +23 -0
- package/hooks/useCheckboxState.js +40 -0
- package/hooks/useCheckboxState.js.map +1 -0
- package/hooks/useFloating.d.ts +38 -0
- package/hooks/useFloating.js +156 -0
- package/hooks/useFloating.js.map +1 -0
- package/hooks/useFloatingMenu.d.ts +35 -0
- package/hooks/useFloatingMenu.js +71 -0
- package/hooks/useFloatingMenu.js.map +1 -0
- package/hooks/useId.d.ts +8 -0
- package/hooks/useId.js +16 -0
- package/hooks/useId.js.map +1 -0
- package/hooks/useKeyboardNavigation.d.ts +35 -0
- package/hooks/useKeyboardNavigation.js +79 -0
- package/hooks/useKeyboardNavigation.js.map +1 -0
- package/hooks/useLongPress.d.ts +11 -0
- package/hooks/useLongPress.js +49 -0
- package/hooks/useLongPress.js.map +1 -0
- package/hooks/useModalState.d.ts +22 -0
- package/hooks/useModalState.js +37 -0
- package/hooks/useModalState.js.map +1 -0
- package/hooks/useNavOptions.d.ts +28 -0
- package/hooks/useNavOptions.js +46 -0
- package/hooks/useNavOptions.js.map +1 -0
- package/hooks/useOutsideClick.d.ts +31 -0
- package/hooks/useOutsideClick.js +50 -0
- package/hooks/useOutsideClick.js.map +1 -0
- package/hooks/useRadioState.d.ts +23 -0
- package/hooks/useRadioState.js +38 -0
- package/hooks/useRadioState.js.map +1 -0
- package/hooks/useSwitchGroupState.d.ts +37 -0
- package/hooks/useSwitchGroupState.js +60 -0
- package/hooks/useSwitchGroupState.js.map +1 -0
- package/hooks/useTimeout.d.ts +26 -0
- package/hooks/useTimeout.js +45 -0
- package/hooks/useTimeout.js.map +1 -0
- package/hooks/useValidChildren.d.ts +6 -0
- package/hooks/useValidChildren.js +33 -0
- package/hooks/useValidChildren.js.map +1 -0
- package/index.d.ts +125 -0
- package/index.js +20 -0
- package/index.js.map +1 -0
- package/meta.d.ts +44 -0
- package/meta.js +5789 -0
- package/meta.js.map +1 -0
- package/package.json +73 -0
- package/src/Accordion.tsx +33 -0
- package/src/Badge.tsx +75 -0
- package/src/BannerAlert.tsx +211 -0
- package/src/BottomNavigation.tsx +33 -0
- package/src/BottomSheet.tsx +33 -0
- package/src/Breadcrumb.tsx +33 -0
- package/src/Button.tsx +358 -0
- package/src/ButtonDock.tsx +33 -0
- package/src/Card.tsx +66 -0
- package/src/Chart.tsx +33 -0
- package/src/Checkbox.tsx +174 -0
- package/src/CheckboxGroup.tsx +95 -0
- package/src/CheckboxOption.tsx +27 -0
- package/src/Chip.tsx +155 -0
- package/src/DatePicker.tsx +33 -0
- package/src/Dialog.tsx +304 -0
- package/src/Divider.tsx +129 -0
- package/src/Drawer.tsx +33 -0
- package/src/Dropdown.tsx +251 -0
- package/src/DropdownField.tsx +57 -0
- package/src/EmptyState.tsx +73 -0
- package/src/Fab.tsx +222 -0
- package/src/FileUpload.tsx +33 -0
- package/src/FormField.tsx +107 -0
- package/src/Image.tsx +33 -0
- package/src/Img.tsx +39 -0
- package/src/InlineAlert.tsx +119 -0
- package/src/Layout.tsx +82 -0
- package/src/Link.tsx +82 -0
- package/src/ListItem.tsx +316 -0
- package/src/Menu.tsx +263 -0
- package/src/MenuButton.tsx +45 -0
- package/src/Modal.tsx +104 -0
- package/src/MultiSelection.tsx +33 -0
- package/src/NavigationRail.tsx +33 -0
- package/src/NumberField.tsx +47 -0
- package/src/NumberInput.tsx +282 -0
- package/src/OTPInput.tsx +33 -0
- package/src/PageControl.tsx +33 -0
- package/src/PasswordInput.tsx +33 -0
- package/src/PhoneNumberInput.tsx +33 -0
- package/src/Popover.tsx +237 -0
- package/src/Portal.tsx +36 -0
- package/src/Profile.tsx +140 -0
- package/src/ProgressBar.tsx +110 -0
- package/src/ProgressCircle.tsx +153 -0
- package/src/ProgressionStepper.tsx +294 -0
- package/src/ProgressionStepperBar.tsx +95 -0
- package/src/Radio.tsx +119 -0
- package/src/RadioGroup.tsx +62 -0
- package/src/RadioOption.tsx +24 -0
- package/src/Rating.tsx +29 -0
- package/src/SearchBar.tsx +121 -0
- package/src/SegmentedControl.tsx +233 -0
- package/src/Skeleton.tsx +90 -0
- package/src/SliderInput.tsx +29 -0
- package/src/Snackbar.tsx +29 -0
- package/src/StylesProviderAnywhere.tsx +31 -0
- package/src/StylesProviderBetterHomesGardens.tsx +31 -0
- package/src/StylesProviderCartus.tsx +31 -0
- package/src/StylesProviderCentury21.tsx +31 -0
- package/src/StylesProviderColdwellBanker.tsx +31 -0
- package/src/StylesProviderCorcoran.tsx +31 -0
- package/src/StylesProviderDenaliBoss.tsx +31 -0
- package/src/StylesProviderEra.tsx +31 -0
- package/src/StylesProviderSothebys.tsx +31 -0
- package/src/Switch.tsx +122 -0
- package/src/SwitchGroup.tsx +60 -0
- package/src/SwitchOption.tsx +24 -0
- package/src/TabGroup.tsx +219 -0
- package/src/Table.tsx +126 -0
- package/src/Tag.tsx +149 -0
- package/src/TextField.tsx +61 -0
- package/src/TextInput.tsx +265 -0
- package/src/Textarea.tsx +205 -0
- package/src/TextareaField.tsx +67 -0
- package/src/TimePicker.tsx +29 -0
- package/src/ToggleOption.tsx +117 -0
- package/src/Tooltip.tsx +170 -0
- package/src/TopNavigation.tsx +29 -0
- package/src/Txt.tsx +69 -0
- package/src/hooks/useCheckboxGroupState.ts +79 -0
- package/src/hooks/useCheckboxState.ts +48 -0
- package/src/hooks/useFloating.ts +202 -0
- package/src/hooks/useFloatingMenu.ts +110 -0
- package/src/hooks/useId.ts +14 -0
- package/src/hooks/useKeyboardNavigation.ts +98 -0
- package/src/hooks/useLongPress.ts +53 -0
- package/src/hooks/useModalState.ts +37 -0
- package/src/hooks/useNavOptions.ts +76 -0
- package/src/hooks/useOutsideClick.ts +51 -0
- package/src/hooks/useRadioState.ts +42 -0
- package/src/hooks/useSwitchGroupState.ts +75 -0
- package/src/hooks/useTimeout.ts +45 -0
- package/src/hooks/useValidChildren.ts +54 -0
- package/src/index.ts +160 -0
- package/src/meta.ts +6238 -0
- package/src/package.json +3 -0
- package/src/styles/anywhere.css +1693 -0
- package/src/styles/anywhere.ts +1694 -0
- package/src/styles/base.css +160 -0
- package/src/styles/base.ts +161 -0
- package/src/styles/better-homes-gardens.css +1693 -0
- package/src/styles/better-homes-gardens.ts +1694 -0
- package/src/styles/cartus.css +1691 -0
- package/src/styles/cartus.ts +1692 -0
- package/src/styles/century-21.css +1689 -0
- package/src/styles/century-21.ts +1690 -0
- package/src/styles/coldwell-banker.css +1691 -0
- package/src/styles/coldwell-banker.ts +1692 -0
- package/src/styles/corcoran.css +1685 -0
- package/src/styles/corcoran.ts +1686 -0
- package/src/styles/denali-boss.css +1681 -0
- package/src/styles/denali-boss.ts +1682 -0
- package/src/styles/era.css +1685 -0
- package/src/styles/era.ts +1686 -0
- package/src/styles/sothebys.css +1677 -0
- package/src/styles/sothebys.ts +1678 -0
- package/src/utils/children.ts +80 -0
- package/src/utils/colorVariants.ts +74 -0
- package/src/utils/errors.tsx +104 -0
- package/src/utils/getValidNode.ts +7 -0
- package/src/utils/keyboard.ts +382 -0
- package/src/utils/placeholder.test.ts +7 -0
- package/src/utils/ref.ts +11 -0
- package/src/utils/scrollElementIntoView.ts +29 -0
- package/src/utils/srOnly.ts +14 -0
- package/src/utils/tryIntPsrse.ts +7 -0
- package/src/utils/txtVariants.ts +53 -0
- package/styles/anywhere.css +1693 -0
- package/styles/anywhere.d.ts +2 -0
- package/styles/anywhere.js +1697 -0
- package/styles/anywhere.js.map +1 -0
- package/styles/anywhere.ts +1694 -0
- package/styles/base.css +160 -0
- package/styles/base.d.ts +2 -0
- package/styles/base.js +164 -0
- package/styles/base.js.map +1 -0
- package/styles/base.ts +161 -0
- package/styles/better-homes-gardens.css +1693 -0
- package/styles/better-homes-gardens.d.ts +2 -0
- package/styles/better-homes-gardens.js +1697 -0
- package/styles/better-homes-gardens.js.map +1 -0
- package/styles/better-homes-gardens.ts +1694 -0
- package/styles/cartus.css +1691 -0
- package/styles/cartus.d.ts +2 -0
- package/styles/cartus.js +1695 -0
- package/styles/cartus.js.map +1 -0
- package/styles/cartus.ts +1692 -0
- package/styles/century-21.css +1689 -0
- package/styles/century-21.d.ts +2 -0
- package/styles/century-21.js +1693 -0
- package/styles/century-21.js.map +1 -0
- package/styles/century-21.ts +1690 -0
- package/styles/coldwell-banker.css +1691 -0
- package/styles/coldwell-banker.d.ts +2 -0
- package/styles/coldwell-banker.js +1695 -0
- package/styles/coldwell-banker.js.map +1 -0
- package/styles/coldwell-banker.ts +1692 -0
- package/styles/corcoran.css +1685 -0
- package/styles/corcoran.d.ts +2 -0
- package/styles/corcoran.js +1689 -0
- package/styles/corcoran.js.map +1 -0
- package/styles/corcoran.ts +1686 -0
- package/styles/denali-boss.css +1681 -0
- package/styles/denali-boss.d.ts +2 -0
- package/styles/denali-boss.js +1685 -0
- package/styles/denali-boss.js.map +1 -0
- package/styles/denali-boss.ts +1682 -0
- package/styles/era.css +1685 -0
- package/styles/era.d.ts +2 -0
- package/styles/era.js +1689 -0
- package/styles/era.js.map +1 -0
- package/styles/era.ts +1686 -0
- package/styles/sothebys.css +1677 -0
- package/styles/sothebys.d.ts +2 -0
- package/styles/sothebys.js +1681 -0
- package/styles/sothebys.js.map +1 -0
- package/styles/sothebys.ts +1678 -0
- package/utils/children.d.ts +29 -0
- package/utils/children.js +70 -0
- package/utils/children.js.map +1 -0
- package/utils/colorVariants.d.ts +5 -0
- package/utils/colorVariants.js +61 -0
- package/utils/colorVariants.js.map +1 -0
- package/utils/errors.d.ts +36 -0
- package/utils/errors.js +65 -0
- package/utils/errors.js.map +1 -0
- package/utils/getValidNode.d.ts +2 -0
- package/utils/getValidNode.js +9 -0
- package/utils/getValidNode.js.map +1 -0
- package/utils/keyboard.d.ts +4 -0
- package/utils/keyboard.js +140 -0
- package/utils/keyboard.js.map +1 -0
- package/utils/ref.d.ts +5 -0
- package/utils/ref.js +10 -0
- package/utils/ref.js.map +1 -0
- package/utils/scrollElementIntoView.d.ts +8 -0
- package/utils/scrollElementIntoView.js +30 -0
- package/utils/scrollElementIntoView.js.map +1 -0
- package/utils/srOnly.d.ts +14 -0
- package/utils/srOnly.js +15 -0
- package/utils/srOnly.js.map +1 -0
- package/utils/tryIntPsrse.d.ts +2 -0
- package/utils/tryIntPsrse.js +10 -0
- package/utils/tryIntPsrse.js.map +1 -0
- package/utils/txtVariants.d.ts +2 -0
- package/utils/txtVariants.js +30 -0
- package/utils/txtVariants.js.map +1 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AriaAttributes, useId, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CommonProps } from '..';
|
|
4
|
+
import { EVENT_KEY } from '../utils/keyboard';
|
|
5
|
+
|
|
6
|
+
import { Placement, useFloating } from './useFloating';
|
|
7
|
+
import { useKeyboardNavigation } from './useKeyboardNavigation';
|
|
8
|
+
import { useOutsideClick } from './useOutsideClick';
|
|
9
|
+
|
|
10
|
+
export type UseFloatingMenuProps = {
|
|
11
|
+
placement: Placement;
|
|
12
|
+
triggerProps?: CommonProps<'disabled' | 'invalid' | 'readOnly'>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type UseFloatingMenuReturn = {
|
|
16
|
+
menuProps: {
|
|
17
|
+
activeIndex: number;
|
|
18
|
+
'data-placement': Placement | undefined;
|
|
19
|
+
id: string;
|
|
20
|
+
innerRef: (node: HTMLElement | null) => void;
|
|
21
|
+
role: 'listbox';
|
|
22
|
+
style: React.CSSProperties;
|
|
23
|
+
tabIndex: number;
|
|
24
|
+
};
|
|
25
|
+
triggerProps: {
|
|
26
|
+
'aria-activedescendant': string | undefined;
|
|
27
|
+
'aria-controls': string;
|
|
28
|
+
'aria-expanded': boolean;
|
|
29
|
+
'aria-haspopup': AriaAttributes['aria-haspopup'];
|
|
30
|
+
'aria-invalid': boolean | undefined;
|
|
31
|
+
'aria-owns': string;
|
|
32
|
+
'aria-readonly': boolean | undefined;
|
|
33
|
+
role: 'combobox';
|
|
34
|
+
tabIndex: number;
|
|
35
|
+
ref: (node: HTMLElement | null) => void;
|
|
36
|
+
onClick: (event: React.MouseEvent) => void;
|
|
37
|
+
onKeyDownCapture: (event: React.KeyboardEvent) => boolean;
|
|
38
|
+
};
|
|
39
|
+
closeMenu: () => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function useFloatingMenu({ placement, triggerProps }: UseFloatingMenuProps): UseFloatingMenuReturn {
|
|
43
|
+
const menuId = useId();
|
|
44
|
+
|
|
45
|
+
const [show, setShow] = useState(false);
|
|
46
|
+
const closeMenu = () => setShow(false);
|
|
47
|
+
const openMenu = () => setShow(true);
|
|
48
|
+
|
|
49
|
+
const { floatingStyles, middlewareData, elements } = useFloating({
|
|
50
|
+
placement,
|
|
51
|
+
strategy: 'fixed',
|
|
52
|
+
offsetOptions: 4,
|
|
53
|
+
refWidth: true,
|
|
54
|
+
hide: !show,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const { handleKeyNavigation, selectedIndex: activeIndex, selectedId } = useKeyboardNavigation(elements.floating);
|
|
58
|
+
|
|
59
|
+
useOutsideClick([elements.floating, elements.trigger], (event) => {
|
|
60
|
+
event?.stopPropagation();
|
|
61
|
+
if (!show) return;
|
|
62
|
+
closeMenu();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
menuProps: {
|
|
67
|
+
activeIndex,
|
|
68
|
+
'data-placement': middlewareData?.offset?.placement,
|
|
69
|
+
id: menuId,
|
|
70
|
+
innerRef: (node: HTMLElement | null) => {
|
|
71
|
+
elements.setFloating(node);
|
|
72
|
+
},
|
|
73
|
+
role: 'listbox',
|
|
74
|
+
style: floatingStyles,
|
|
75
|
+
tabIndex: -1,
|
|
76
|
+
},
|
|
77
|
+
triggerProps: {
|
|
78
|
+
'aria-activedescendant': selectedId || undefined,
|
|
79
|
+
'aria-controls': menuId,
|
|
80
|
+
'aria-expanded': show,
|
|
81
|
+
'aria-haspopup': 'listbox' as AriaAttributes['aria-haspopup'],
|
|
82
|
+
'aria-invalid': triggerProps?.invalid || undefined,
|
|
83
|
+
'aria-owns': menuId,
|
|
84
|
+
'aria-readonly': triggerProps?.readOnly || undefined,
|
|
85
|
+
role: 'combobox',
|
|
86
|
+
tabIndex: 0,
|
|
87
|
+
ref: (node: HTMLElement | null) => elements.setTrigger(node),
|
|
88
|
+
onClick: () => {
|
|
89
|
+
setShow((prev) => !prev);
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* @param {React.KeyboardEvent} event
|
|
93
|
+
* @returns {boolean} True if event was handled internally
|
|
94
|
+
*/
|
|
95
|
+
onKeyDownCapture: (event: React.KeyboardEvent): boolean => {
|
|
96
|
+
if (event.key === EVENT_KEY.Tab || event.key === EVENT_KEY.Escape) {
|
|
97
|
+
closeMenu();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
openMenu();
|
|
102
|
+
|
|
103
|
+
return handleKeyNavigation?.(event.nativeEvent);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
closeMenu,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useId as useIdReact } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A utility hook that generates a unique id for an element. If a default id is provided, it will use that instead.
|
|
5
|
+
*
|
|
6
|
+
* @param {string | undefined | null} defaultId
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function useId(defaultId?: string | null): string {
|
|
10
|
+
const generatedId = useIdReact();
|
|
11
|
+
return defaultId || generatedId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { EVENT_KEY } from '../utils/keyboard';
|
|
4
|
+
import { scrollElementIntoView } from '../utils/scrollElementIntoView';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This hook provides keyboard navigation for a list of elements. Used inside the Dropdown component.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { useRef } from 'react';
|
|
11
|
+
* import { useKeyboardNavigation } from '@bspk/ui/hooks/useKeyboardNavigation';
|
|
12
|
+
*
|
|
13
|
+
* export function Example() {
|
|
14
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
* const { selectedIndex, setSelectedIndex, handleKeyNavigation } = useKeyboardNavigation(containerRef.current, () => true);
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <div ref={containerRef} onKeyDown={handleKeyNavigation} tabIndex={0}>
|
|
19
|
+
* {items.map((item, index) => (
|
|
20
|
+
* <div key={index} data-selected={selectedIndex === index}>
|
|
21
|
+
* {item}
|
|
22
|
+
* </div>
|
|
23
|
+
* ))}
|
|
24
|
+
* </div>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @param containerElement - The container element that holds the list of elements.
|
|
29
|
+
* @param onNavigate - Fires when the user navigates through the list with Arrow keys. If the function returns false,
|
|
30
|
+
* the navigation will be prevented. For example, this is useful when you want to prevent navigation when a dropdown
|
|
31
|
+
* is closed.
|
|
32
|
+
* @returns An object containing the selectedIndex, setSelectedIndex, and handleKeyNavigation function.
|
|
33
|
+
*/
|
|
34
|
+
export function useKeyboardNavigation(
|
|
35
|
+
containerElement?: HTMLElement | null,
|
|
36
|
+
// /**
|
|
37
|
+
// * Fires when the user navigates through the list with Arrow keys. If the function returns false, the navigation will
|
|
38
|
+
// * be prevented. This is useful when you want to prevent navigation when the dropdown is closed.
|
|
39
|
+
// *
|
|
40
|
+
// * @returns Boolean
|
|
41
|
+
// */
|
|
42
|
+
// selectedIndex: number,
|
|
43
|
+
// setSelectedIndex: (index: number) => void,
|
|
44
|
+
): {
|
|
45
|
+
handleKeyNavigation: (event: KeyboardEvent) => boolean;
|
|
46
|
+
selectedIndex: number;
|
|
47
|
+
selectedId: string | undefined;
|
|
48
|
+
setSelectedIndex: (index: number) => void;
|
|
49
|
+
} {
|
|
50
|
+
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
51
|
+
|
|
52
|
+
if (!containerElement)
|
|
53
|
+
return {
|
|
54
|
+
handleKeyNavigation: () => false,
|
|
55
|
+
selectedIndex,
|
|
56
|
+
setSelectedIndex: () => {},
|
|
57
|
+
selectedId: undefined,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleArrowKeyNavigation = (event: KeyboardEvent): boolean => {
|
|
61
|
+
if (event.key !== EVENT_KEY.Enter && !event.key.startsWith('Arrow')) return false;
|
|
62
|
+
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
|
|
65
|
+
if (!containerElement || !containerElement.children.length) return false;
|
|
66
|
+
|
|
67
|
+
const itemElements = Array.from(containerElement?.children) as HTMLElement[];
|
|
68
|
+
|
|
69
|
+
if (event.key === EVENT_KEY.Enter && selectedIndex !== -1) {
|
|
70
|
+
itemElements[selectedIndex].click();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let next = 0;
|
|
75
|
+
if (event.key === EVENT_KEY.ArrowUp || event.key === EVENT_KEY.ArrowLeft) next = selectedIndex - 1;
|
|
76
|
+
if (event.key === EVENT_KEY.ArrowDown || event.key === EVENT_KEY.ArrowRight) next = selectedIndex + 1;
|
|
77
|
+
|
|
78
|
+
if (next < 0) next = itemElements.length - 1;
|
|
79
|
+
if (next >= itemElements.length) next = 0;
|
|
80
|
+
|
|
81
|
+
itemElements.forEach((el, index) => {
|
|
82
|
+
el.dataset.selected = index === next ? 'true' : undefined;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
scrollElementIntoView(itemElements[next], containerElement);
|
|
86
|
+
setSelectedIndex(next);
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
handleKeyNavigation: handleArrowKeyNavigation,
|
|
92
|
+
selectedIndex,
|
|
93
|
+
selectedId: selectedIndex === -1 ? undefined : containerElement.children[selectedIndex].id,
|
|
94
|
+
setSelectedIndex: setSelectedIndex,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ComponentProps, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTimeout } from './useTimeout';
|
|
4
|
+
|
|
5
|
+
export function useLongPress(callback: (pressCount: number) => void, ms: number = 2000, isDisabled = false) {
|
|
6
|
+
const longPressTimeout = useTimeout();
|
|
7
|
+
const longPressCount = useRef(1);
|
|
8
|
+
const duration = useRef(ms);
|
|
9
|
+
|
|
10
|
+
const stop = () => {
|
|
11
|
+
longPressCount.current = 1;
|
|
12
|
+
duration.current = ms;
|
|
13
|
+
longPressTimeout.clear();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const start = () => {
|
|
17
|
+
duration.current /= 2;
|
|
18
|
+
longPressTimeout.set(
|
|
19
|
+
() => {
|
|
20
|
+
callback(longPressCount.current);
|
|
21
|
+
longPressCount.current += 1;
|
|
22
|
+
start();
|
|
23
|
+
},
|
|
24
|
+
//
|
|
25
|
+
Math.max(duration.current, 100),
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buttonProps: ComponentProps<'button'> = {
|
|
30
|
+
onMouseDown: (event) => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
if (isDisabled) return;
|
|
33
|
+
|
|
34
|
+
callback(longPressCount.current);
|
|
35
|
+
start();
|
|
36
|
+
},
|
|
37
|
+
onMouseMove: (event) => {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
if (event.movementX > 100 || event.movementY > 100) stop();
|
|
40
|
+
},
|
|
41
|
+
onMouseLeave: () => stop(),
|
|
42
|
+
onMouseUp: () => stop(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
start,
|
|
47
|
+
stop,
|
|
48
|
+
buttonProps,
|
|
49
|
+
timeout: longPressTimeout,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A hook to manage the state of a modal.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { Modal } from '@bspk/ui/Modal';
|
|
8
|
+
* import { useModalState } from '@bspk/ui/hooks/useModalState';
|
|
9
|
+
*
|
|
10
|
+
* export function Example() {
|
|
11
|
+
* const modalProps = useModalState(false);
|
|
12
|
+
* return <Modal {...modalProps}>...</Modal>
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* @param parentValue - The value of the parent state.
|
|
16
|
+
* @param setParentState - A function to set the parent state.
|
|
17
|
+
* @returns An object with the open state and the `onClose` and `onOpen` callbacks.
|
|
18
|
+
*/
|
|
19
|
+
export function useModalState(parentValue: boolean = false, setParentState?: (next: boolean) => void) {
|
|
20
|
+
const [open, setOpen] = useState(parentValue);
|
|
21
|
+
|
|
22
|
+
useEffect(() => setOpen(parentValue), [parentValue]);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
open,
|
|
26
|
+
onClose: () => {
|
|
27
|
+
setOpen(false);
|
|
28
|
+
setParentState?.(false);
|
|
29
|
+
},
|
|
30
|
+
onOpen: () => {
|
|
31
|
+
setOpen(true);
|
|
32
|
+
setParentState?.(true);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { getChildTypeName, isValidIcon } from '../utils/children';
|
|
4
|
+
import { useErrorLogger } from '../utils/errors';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A utility hook used within navigation components. Returns options ready for use in a navigation component. Validates
|
|
8
|
+
* the icons and sets the icon and iconActive properties.
|
|
9
|
+
*
|
|
10
|
+
* @param options [NavOption[]] The options to display. Each option has a label and an optional leading icon.
|
|
11
|
+
* @returns {undefined} NavOption[] The options with the validated icon and iconActive properties set.
|
|
12
|
+
*/
|
|
13
|
+
export function useNavOptions<T extends NavOption>(options: T[] | undefined): T[] {
|
|
14
|
+
const { logError } = useErrorLogger();
|
|
15
|
+
|
|
16
|
+
return useMemo(() => {
|
|
17
|
+
if (!options) return [];
|
|
18
|
+
|
|
19
|
+
const iconsInvalid = options.some((o) => o.icon) ? !options.every((option) => isValidIcon(option.icon)) : null;
|
|
20
|
+
|
|
21
|
+
logError(
|
|
22
|
+
iconsInvalid === true,
|
|
23
|
+
'useNavOptions - Every option either must have a valid icon or none at all. All icons removed.',
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return options
|
|
27
|
+
.map((option) => {
|
|
28
|
+
let { icon, iconActive } = option;
|
|
29
|
+
|
|
30
|
+
if (iconsInvalid) {
|
|
31
|
+
icon = undefined;
|
|
32
|
+
iconActive = undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
iconActive &&
|
|
37
|
+
// If the iconActive is not a valid icon or the iconActive is not a fill version of the icon, remove the iconActive
|
|
38
|
+
(!icon ||
|
|
39
|
+
!isValidIcon(iconActive) ||
|
|
40
|
+
getChildTypeName(iconActive) !== `${getChildTypeName(icon)}Fill`)
|
|
41
|
+
) {
|
|
42
|
+
iconActive = undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...option,
|
|
47
|
+
value: option.value || option.label,
|
|
48
|
+
icon,
|
|
49
|
+
iconActive,
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
.map((tab, _, arr) => ({ ...tab, icon: arr.every((t) => t.icon) ? tab.icon : undefined }));
|
|
53
|
+
}, [logError, options]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type NavOption = {
|
|
57
|
+
/**
|
|
58
|
+
* The label of the option. This is the text that will be displayed on the option.
|
|
59
|
+
*
|
|
60
|
+
* @required
|
|
61
|
+
*/
|
|
62
|
+
label: string;
|
|
63
|
+
/**
|
|
64
|
+
* Determines if the element is [disabled](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled).
|
|
65
|
+
*
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
/** The value of the option. If not provided, the label will be used as the value. */
|
|
70
|
+
value?: string;
|
|
71
|
+
/** The the icon to display before the label. */
|
|
72
|
+
icon?: React.ReactNode;
|
|
73
|
+
iconActive?: React.ReactNode;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A hook which calls a method when a click occurs outside of the provided ref. Used inside the Dropdown and Modal
|
|
4
|
+
* components.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { useOutsideClick } from '@bspk/ui/hooks/useOutsideClick';
|
|
8
|
+
* import React, { useRef, useState } from 'react';
|
|
9
|
+
*
|
|
10
|
+
* export function Dropdown() {
|
|
11
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
12
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
*
|
|
14
|
+
* useOutsideClick([containerRef.current], () => setIsOpen(false));
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <>
|
|
18
|
+
* {isOpen && (
|
|
19
|
+
* <div className="custom-popup" ref={containerRef}>
|
|
20
|
+
* Content
|
|
21
|
+
* </div>
|
|
22
|
+
* )}
|
|
23
|
+
* </>
|
|
24
|
+
* );
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* @param elements - The elements to check if the click occurred outside of.
|
|
28
|
+
* @param callback - The callback to call when a click occurs outside of the ref.
|
|
29
|
+
* @param disabled - Whether the hook should be disabled. Defaults to false.
|
|
30
|
+
*/
|
|
31
|
+
export function useOutsideClick(
|
|
32
|
+
elements: (HTMLElement | null)[],
|
|
33
|
+
callback: (event?: MouseEvent) => void,
|
|
34
|
+
disabled?: boolean,
|
|
35
|
+
) {
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!elements.length || disabled) return;
|
|
38
|
+
|
|
39
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
40
|
+
if (disabled || elements?.some?.((element) => element?.contains?.(event.target as Node))) return;
|
|
41
|
+
callback(event);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
47
|
+
};
|
|
48
|
+
}, [callback, disabled, elements]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { RadioProps } from '../Radio';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A hook to manage the state of a single radio. Used alongside the Radio component.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { Radio } from '@bspk/ui/Radio';
|
|
10
|
+
* import { useRadioState } from '@bspk/ui/hooks/useRadioState';
|
|
11
|
+
*
|
|
12
|
+
* export function Example() {
|
|
13
|
+
* const { radioProps } = useRadioState('fruits');
|
|
14
|
+
* return <Radio aria-label="cherry" {...radioProps('cherry')} />;
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* @param name - The name of the radio.
|
|
18
|
+
* @param externalState - The externally managed state of the radio.
|
|
19
|
+
* @returns Properties to pass to the radio and the state of the radio.
|
|
20
|
+
*/
|
|
21
|
+
export function useRadioState(
|
|
22
|
+
name: string,
|
|
23
|
+
externalState?: [value: string | undefined, onChange: (next: string) => void],
|
|
24
|
+
) {
|
|
25
|
+
const localState = useState<string>();
|
|
26
|
+
const [value, setValue] = externalState || localState;
|
|
27
|
+
|
|
28
|
+
const radioProps = (radioValue: string): Pick<RadioProps, 'checked' | 'name' | 'onChange' | 'value'> => {
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
checked: value === radioValue,
|
|
32
|
+
value: radioValue,
|
|
33
|
+
onChange: (checked) => {
|
|
34
|
+
if (checked) setValue(radioValue);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return { radioProps, value, setValue };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SwitchProps } from '../Switch';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A hook to manage the state of a group of switches. Used alongside the SwitchGroup component.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { Switch } from '@bspk/ui/Switch';
|
|
10
|
+
* import { useSwitchGroupState } from '@bspk/ui/hooks/useSwitchGroupState';
|
|
11
|
+
*
|
|
12
|
+
* export function Example() {
|
|
13
|
+
* const allValues = ['Red', 'Orange', 'Yellow', 'Green'];
|
|
14
|
+
*
|
|
15
|
+
* const { allSwitchProps, switchProps, values } = useSwitchGroupState(allValues, 'fruits');
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <>
|
|
19
|
+
* <pre>Selected Values: {values.join(', ')}</pre>
|
|
20
|
+
* <Switch aria-label="All" {...allSwitchProps} />
|
|
21
|
+
* {allValues.map((value) => (
|
|
22
|
+
* <Switch key={value} aria-label={value} {...switchProps(value)} />
|
|
23
|
+
* ))}
|
|
24
|
+
* </>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* @param allValues - The values of the switches.
|
|
29
|
+
* @param name - The name of the switches.
|
|
30
|
+
* @param externalState - The externally managed state of the switches.
|
|
31
|
+
* @returns Properties to pass to the switch, other helper functions, and the state of the switch.
|
|
32
|
+
*/
|
|
33
|
+
export function useSwitchGroupState(
|
|
34
|
+
allValues: string[],
|
|
35
|
+
name: string,
|
|
36
|
+
externalState?: [values: string[], setValues: (next: string[]) => void],
|
|
37
|
+
) {
|
|
38
|
+
const localState = useState<string[]>([]);
|
|
39
|
+
const [values, setValues] = externalState || localState;
|
|
40
|
+
|
|
41
|
+
const toggleValue = useCallback(
|
|
42
|
+
(itemValue: string, checked: boolean) => {
|
|
43
|
+
setValues(
|
|
44
|
+
allValues.flatMap((value) => {
|
|
45
|
+
if (value === itemValue) return checked ? value : [];
|
|
46
|
+
return values.includes(value) ? value : [];
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
[allValues, setValues, values],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const switchProps = useCallback(
|
|
54
|
+
(value: string): Pick<SwitchProps, 'checked' | 'name' | 'onChange' | 'value'> => ({
|
|
55
|
+
checked: values.includes(value),
|
|
56
|
+
name,
|
|
57
|
+
onChange: (checked) => toggleValue(value, checked),
|
|
58
|
+
value,
|
|
59
|
+
}),
|
|
60
|
+
[values, name, toggleValue],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const allSwitchProps = useMemo((): Pick<SwitchProps, 'checked' | 'name' | 'onChange' | 'value'> => {
|
|
64
|
+
return {
|
|
65
|
+
checked: allValues.length === values.length,
|
|
66
|
+
name,
|
|
67
|
+
onChange: () => setValues(allValues.length === values.length ? [] : allValues),
|
|
68
|
+
value: 'all',
|
|
69
|
+
};
|
|
70
|
+
}, [allValues, values.length, name, setValues]);
|
|
71
|
+
|
|
72
|
+
return { toggleValue, allSwitchProps, switchProps, values, setValues };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A hook that creates a timeout that is automatically cleared when the component is unmounted.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { useTimeout } from '@bspk/ui/hooks/useTimeout';
|
|
8
|
+
* import { useEffect } from 'react';
|
|
9
|
+
*
|
|
10
|
+
* function MyComponent() {
|
|
11
|
+
* const timeoutRef = useTimeout();
|
|
12
|
+
*
|
|
13
|
+
* useEffect(() => {
|
|
14
|
+
* timeoutRef.current = setTimeout(() => {
|
|
15
|
+
* console.log('Timeout triggered');
|
|
16
|
+
* }, 1000);
|
|
17
|
+
* }, []);
|
|
18
|
+
* return <div>Check the console after 1 second</div>;
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @returns A ref object that can be used to store a timeout id.
|
|
22
|
+
*/
|
|
23
|
+
export function useTimeout() {
|
|
24
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return useMemo(
|
|
31
|
+
() => ({
|
|
32
|
+
clear: () => {
|
|
33
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
34
|
+
},
|
|
35
|
+
set: (callback: () => void, durationMs: number) => {
|
|
36
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
37
|
+
timeoutRef.current = setTimeout(callback, durationMs);
|
|
38
|
+
},
|
|
39
|
+
ref: timeoutRef,
|
|
40
|
+
}),
|
|
41
|
+
[],
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ReactNode, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { getChildrenElements } from '../utils/children';
|
|
4
|
+
import { useErrorLogger } from '../utils/errors';
|
|
5
|
+
|
|
6
|
+
const VALID_CHILDREN = ['Icon', 'string'];
|
|
7
|
+
|
|
8
|
+
export function useValidChildren(
|
|
9
|
+
componentNme: string,
|
|
10
|
+
children: ReactNode,
|
|
11
|
+
ariaLabel: string | undefined,
|
|
12
|
+
override?: boolean,
|
|
13
|
+
): {
|
|
14
|
+
icon?: ReactNode | undefined;
|
|
15
|
+
label?: ReactNode | undefined;
|
|
16
|
+
} {
|
|
17
|
+
const { logError } = useErrorLogger();
|
|
18
|
+
|
|
19
|
+
return useMemo(() => {
|
|
20
|
+
if (override) return { icon: undefined, label: undefined };
|
|
21
|
+
|
|
22
|
+
let icon: ReactNode | undefined;
|
|
23
|
+
let label: ReactNode;
|
|
24
|
+
|
|
25
|
+
const childrenArray = getChildrenElements(children);
|
|
26
|
+
|
|
27
|
+
logError(
|
|
28
|
+
childrenArray.length > 2,
|
|
29
|
+
`${componentNme} - component only accepts two children, ${childrenArray.length} were provided`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
childrenArray.forEach(({ child, name }) => {
|
|
33
|
+
const valid = VALID_CHILDREN.includes(name);
|
|
34
|
+
|
|
35
|
+
logError(!valid, `${componentNme} - component only accepts two children, and Icon and string`);
|
|
36
|
+
|
|
37
|
+
if (valid) {
|
|
38
|
+
if (name === 'string') label = child;
|
|
39
|
+
if (name === 'Icon') icon = child;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
logError(!icon && !label, `${componentNme} - component requires at least one child, an icon or a label`);
|
|
44
|
+
|
|
45
|
+
logError(
|
|
46
|
+
!!icon && !label && !ariaLabel,
|
|
47
|
+
`${componentNme} - component requires the aria-label property when only an icon is provided`,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return { icon, label };
|
|
51
|
+
}, [override, children, logError, componentNme, ariaLabel]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|