@allxsmith/bestax-bulma 5.26.6 → 5.27.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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/columns/Column.js +48 -0
- package/dist/esm/columns/Column.js.map +1 -0
- package/dist/esm/columns/Columns.js +53 -0
- package/dist/esm/columns/Columns.js.map +1 -0
- package/dist/esm/components/Avatar.js +299 -0
- package/dist/esm/components/Avatar.js.map +1 -0
- package/dist/esm/components/Avatars.js +81 -0
- package/dist/esm/components/Avatars.js.map +1 -0
- package/dist/esm/components/Badge.js +105 -0
- package/dist/esm/components/Badge.js.map +1 -0
- package/dist/esm/components/Breadcrumb.js +36 -0
- package/dist/esm/components/Breadcrumb.js.map +1 -0
- package/dist/esm/components/Card.js +252 -0
- package/dist/esm/components/Card.js.map +1 -0
- package/dist/esm/components/Carousel.js +351 -0
- package/dist/esm/components/Carousel.js.map +1 -0
- package/dist/esm/components/Collapse.js +193 -0
- package/dist/esm/components/Collapse.js.map +1 -0
- package/dist/esm/components/Collapses.js +99 -0
- package/dist/esm/components/Collapses.js.map +1 -0
- package/dist/esm/components/Dialog.js +230 -0
- package/dist/esm/components/Dialog.js.map +1 -0
- package/dist/esm/components/Dropdown.js +362 -0
- package/dist/esm/components/Dropdown.js.map +1 -0
- package/dist/esm/components/Loading.js +87 -0
- package/dist/esm/components/Loading.js.map +1 -0
- package/dist/esm/components/Menu.js +117 -0
- package/dist/esm/components/Menu.js.map +1 -0
- package/dist/esm/components/Message.js +56 -0
- package/dist/esm/components/Message.js.map +1 -0
- package/dist/esm/components/Modal.js +354 -0
- package/dist/esm/components/Modal.js.map +1 -0
- package/dist/esm/components/Navbar.js +287 -0
- package/dist/esm/components/Navbar.js.map +1 -0
- package/dist/esm/components/Pagination.js +191 -0
- package/dist/esm/components/Pagination.js.map +1 -0
- package/dist/esm/components/Panel.js +100 -0
- package/dist/esm/components/Panel.js.map +1 -0
- package/dist/esm/components/Popover.js +298 -0
- package/dist/esm/components/Popover.js.map +1 -0
- package/dist/esm/components/Reveal.js +157 -0
- package/dist/esm/components/Reveal.js.map +1 -0
- package/dist/esm/components/Sidebar.js +209 -0
- package/dist/esm/components/Sidebar.js.map +1 -0
- package/dist/esm/components/Steps.js +128 -0
- package/dist/esm/components/Steps.js.map +1 -0
- package/dist/esm/components/Tabs.js +335 -0
- package/dist/esm/components/Tabs.js.map +1 -0
- package/dist/esm/components/Toast.js +322 -0
- package/dist/esm/components/Toast.js.map +1 -0
- package/dist/esm/components/Tooltip.js +202 -0
- package/dist/esm/components/Tooltip.js.map +1 -0
- package/dist/esm/components/collapsesContext.js +12 -0
- package/dist/esm/components/collapsesContext.js.map +1 -0
- package/dist/esm/elements/Block.js +29 -0
- package/dist/esm/elements/Block.js.map +1 -0
- package/dist/esm/elements/Box.js +32 -0
- package/dist/esm/elements/Box.js.map +1 -0
- package/dist/esm/elements/Button.js +139 -0
- package/dist/esm/elements/Button.js.map +1 -0
- package/dist/esm/elements/Buttons.js +42 -0
- package/dist/esm/elements/Buttons.js.map +1 -0
- package/dist/esm/elements/Code.js +25 -0
- package/dist/esm/elements/Code.js.map +1 -0
- package/dist/esm/elements/Content.js +33 -0
- package/dist/esm/elements/Content.js.map +1 -0
- package/dist/esm/elements/Delete.js +35 -0
- package/dist/esm/elements/Delete.js.map +1 -0
- package/dist/esm/elements/Divider.js +27 -0
- package/dist/esm/elements/Divider.js.map +1 -0
- package/dist/esm/elements/Emphasis.js +25 -0
- package/dist/esm/elements/Emphasis.js.map +1 -0
- package/dist/esm/elements/Figure.js +58 -0
- package/dist/esm/elements/Figure.js.map +1 -0
- package/dist/esm/elements/Icon.js +256 -0
- package/dist/esm/elements/Icon.js.map +1 -0
- package/dist/esm/elements/IconText.js +36 -0
- package/dist/esm/elements/IconText.js.map +1 -0
- package/dist/esm/elements/Image.js +46 -0
- package/dist/esm/elements/Image.js.map +1 -0
- package/dist/esm/elements/Link.js +36 -0
- package/dist/esm/elements/Link.js.map +1 -0
- package/dist/esm/elements/LinkButton.js +37 -0
- package/dist/esm/elements/LinkButton.js.map +1 -0
- package/dist/esm/elements/ListItem.js +28 -0
- package/dist/esm/elements/ListItem.js.map +1 -0
- package/dist/esm/elements/Loader.js +37 -0
- package/dist/esm/elements/Loader.js.map +1 -0
- package/dist/esm/elements/Notification.js +316 -0
- package/dist/esm/elements/Notification.js.map +1 -0
- package/dist/esm/elements/OrderedList.js +31 -0
- package/dist/esm/elements/OrderedList.js.map +1 -0
- package/dist/esm/elements/Paragraph.js +28 -0
- package/dist/esm/elements/Paragraph.js.map +1 -0
- package/dist/esm/elements/Pre.js +25 -0
- package/dist/esm/elements/Pre.js.map +1 -0
- package/dist/esm/elements/Progress.js +33 -0
- package/dist/esm/elements/Progress.js.map +1 -0
- package/dist/esm/elements/Skeleton.js +30 -0
- package/dist/esm/elements/Skeleton.js.map +1 -0
- package/dist/esm/elements/Span.js +28 -0
- package/dist/esm/elements/Span.js.map +1 -0
- package/dist/esm/elements/Strong.js +25 -0
- package/dist/esm/elements/Strong.js.map +1 -0
- package/dist/esm/elements/SubTitle.js +51 -0
- package/dist/esm/elements/SubTitle.js.map +1 -0
- package/dist/esm/elements/Table.js +51 -0
- package/dist/esm/elements/Table.js.map +1 -0
- package/dist/esm/elements/Tag.js +49 -0
- package/dist/esm/elements/Tag.js.map +1 -0
- package/dist/esm/elements/Tags.js +34 -0
- package/dist/esm/elements/Tags.js.map +1 -0
- package/dist/esm/elements/Tbody.js +26 -0
- package/dist/esm/elements/Tbody.js.map +1 -0
- package/dist/esm/elements/Td.js +61 -0
- package/dist/esm/elements/Td.js.map +1 -0
- package/dist/esm/elements/Tfoot.js +26 -0
- package/dist/esm/elements/Tfoot.js.map +1 -0
- package/dist/esm/elements/Th.js +34 -0
- package/dist/esm/elements/Th.js.map +1 -0
- package/dist/esm/elements/Thead.js +26 -0
- package/dist/esm/elements/Thead.js.map +1 -0
- package/dist/esm/elements/Title.js +44 -0
- package/dist/esm/elements/Title.js.map +1 -0
- package/dist/esm/elements/Tr.js +31 -0
- package/dist/esm/elements/Tr.js.map +1 -0
- package/dist/esm/elements/UnorderedList.js +31 -0
- package/dist/esm/elements/UnorderedList.js.map +1 -0
- package/dist/esm/elements/iconProps.js +49 -0
- package/dist/esm/elements/iconProps.js.map +1 -0
- package/dist/esm/form/Autocomplete.js +279 -0
- package/dist/esm/form/Autocomplete.js.map +1 -0
- package/dist/esm/form/Checkbox.js +111 -0
- package/dist/esm/form/Checkbox.js.map +1 -0
- package/dist/esm/form/Checkboxes.js +89 -0
- package/dist/esm/form/Checkboxes.js.map +1 -0
- package/dist/esm/form/Control.js +79 -0
- package/dist/esm/form/Control.js.map +1 -0
- package/dist/esm/form/DateInput.js +87 -0
- package/dist/esm/form/DateInput.js.map +1 -0
- package/dist/esm/form/DateInputBase.js +263 -0
- package/dist/esm/form/DateInputBase.js.map +1 -0
- package/dist/esm/form/DateRangeInput.js +76 -0
- package/dist/esm/form/DateRangeInput.js.map +1 -0
- package/dist/esm/form/DateRangeInputBase.js +301 -0
- package/dist/esm/form/DateRangeInputBase.js.map +1 -0
- package/dist/esm/form/DateTimeInput.js +87 -0
- package/dist/esm/form/DateTimeInput.js.map +1 -0
- package/dist/esm/form/DateTimeInputBase.js +333 -0
- package/dist/esm/form/DateTimeInputBase.js.map +1 -0
- package/dist/esm/form/Field.js +153 -0
- package/dist/esm/form/Field.js.map +1 -0
- package/dist/esm/form/File.js +109 -0
- package/dist/esm/form/File.js.map +1 -0
- package/dist/esm/form/FormContext.js +170 -0
- package/dist/esm/form/FormContext.js.map +1 -0
- package/dist/esm/form/Input.js +88 -0
- package/dist/esm/form/Input.js.map +1 -0
- package/dist/esm/form/InputBase.js +40 -0
- package/dist/esm/form/InputBase.js.map +1 -0
- package/dist/esm/form/Numberinput.js +199 -0
- package/dist/esm/form/Numberinput.js.map +1 -0
- package/dist/esm/form/Radio.js +108 -0
- package/dist/esm/form/Radio.js.map +1 -0
- package/dist/esm/form/Radios.js +91 -0
- package/dist/esm/form/Radios.js.map +1 -0
- package/dist/esm/form/Rate.js +279 -0
- package/dist/esm/form/Rate.js.map +1 -0
- package/dist/esm/form/Select.js +81 -0
- package/dist/esm/form/Select.js.map +1 -0
- package/dist/esm/form/SelectBase.js +54 -0
- package/dist/esm/form/SelectBase.js.map +1 -0
- package/dist/esm/form/Slider.js +459 -0
- package/dist/esm/form/Slider.js.map +1 -0
- package/dist/esm/form/Switch.js +96 -0
- package/dist/esm/form/Switch.js.map +1 -0
- package/dist/esm/form/Taginput.js +352 -0
- package/dist/esm/form/Taginput.js.map +1 -0
- package/dist/esm/form/TextArea.js +77 -0
- package/dist/esm/form/TextArea.js.map +1 -0
- package/dist/esm/form/TextAreaBase.js +45 -0
- package/dist/esm/form/TextAreaBase.js.map +1 -0
- package/dist/esm/form/TimeInput.js +83 -0
- package/dist/esm/form/TimeInput.js.map +1 -0
- package/dist/esm/form/TimeInputBase.js +246 -0
- package/dist/esm/form/TimeInputBase.js.map +1 -0
- package/dist/esm/form/_pickerInternals/Calendar.js +760 -0
- package/dist/esm/form/_pickerInternals/Calendar.js.map +1 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js +95 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -0
- package/dist/esm/form/_pickerInternals/TimeWheels.js +721 -0
- package/dist/esm/form/_pickerInternals/TimeWheels.js.map +1 -0
- package/dist/esm/form/_pickerInternals/audioTick.js +109 -0
- package/dist/esm/form/_pickerInternals/audioTick.js.map +1 -0
- package/dist/esm/form/_pickerInternals/dateUtils.js +267 -0
- package/dist/esm/form/_pickerInternals/dateUtils.js.map +1 -0
- package/dist/esm/form/_pickerInternals/formatters.js +322 -0
- package/dist/esm/form/_pickerInternals/formatters.js.map +1 -0
- package/dist/esm/form/_pickerInternals/haptics.js +38 -0
- package/dist/esm/form/_pickerInternals/haptics.js.map +1 -0
- package/dist/esm/form/_pickerInternals/nativeInputSupport.js +25 -0
- package/dist/esm/form/_pickerInternals/nativeInputSupport.js.map +1 -0
- package/dist/esm/form/_pickerInternals/pickerTypes.js +50 -0
- package/dist/esm/form/_pickerInternals/pickerTypes.js.map +1 -0
- package/dist/esm/form/_pickerInternals/segmentMap.js +315 -0
- package/dist/esm/form/_pickerInternals/segmentMap.js.map +1 -0
- package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js +49 -0
- package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js.map +1 -0
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js +427 -0
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js.map +1 -0
- package/dist/esm/form/controlLoading.js +18 -0
- package/dist/esm/form/controlLoading.js.map +1 -0
- package/dist/esm/form/useAutoLabelId.js +54 -0
- package/dist/esm/form/useAutoLabelId.js.map +1 -0
- package/dist/esm/grid/Cell.js +35 -0
- package/dist/esm/grid/Cell.js.map +1 -0
- package/dist/esm/grid/Grid.js +51 -0
- package/dist/esm/grid/Grid.js.map +1 -0
- package/dist/esm/helpers/ClientOnly.js +33 -0
- package/dist/esm/helpers/ClientOnly.js.map +1 -0
- package/dist/esm/helpers/Config.js +60 -0
- package/dist/esm/helpers/Config.js.map +1 -0
- package/dist/esm/helpers/Theme.js +947 -0
- package/dist/esm/helpers/Theme.js.map +1 -0
- package/dist/esm/helpers/anchorAttrs.js +72 -0
- package/dist/esm/helpers/anchorAttrs.js.map +1 -0
- package/dist/esm/helpers/bulmaClassHelpers.js +415 -0
- package/dist/esm/helpers/bulmaClassHelpers.js.map +1 -0
- package/dist/esm/helpers/buttonType.js +24 -0
- package/dist/esm/helpers/buttonType.js.map +1 -0
- package/dist/esm/helpers/classNames.js +137 -0
- package/dist/esm/helpers/classNames.js.map +1 -0
- package/dist/esm/helpers/colorDeprecations.js +50 -0
- package/dist/esm/helpers/colorDeprecations.js.map +1 -0
- package/dist/esm/helpers/devWarnings.js +27 -0
- package/dist/esm/helpers/devWarnings.js.map +1 -0
- package/dist/esm/helpers/inertProps.js +27 -0
- package/dist/esm/helpers/inertProps.js.map +1 -0
- package/dist/esm/helpers/mergeBulmaStyles.js +20 -0
- package/dist/esm/helpers/mergeBulmaStyles.js.map +1 -0
- package/dist/esm/helpers/polymorphic.js +18 -0
- package/dist/esm/helpers/polymorphic.js.map +1 -0
- package/dist/esm/helpers/portal.js +76 -0
- package/dist/esm/helpers/portal.js.map +1 -0
- package/dist/esm/helpers/positionStacks.js +45 -0
- package/dist/esm/helpers/positionStacks.js.map +1 -0
- package/dist/esm/helpers/scrollLock.js +38 -0
- package/dist/esm/helpers/scrollLock.js.map +1 -0
- package/dist/esm/helpers/shadowDom.js +71 -0
- package/dist/esm/helpers/shadowDom.js.map +1 -0
- package/dist/esm/helpers/statusRegion.js +282 -0
- package/dist/esm/helpers/statusRegion.js.map +1 -0
- package/dist/esm/helpers/useAnchoredPosition.js +86 -0
- package/dist/esm/helpers/useAnchoredPosition.js.map +1 -0
- package/dist/esm/helpers/useBulmaClasses.js +158 -0
- package/dist/esm/helpers/useBulmaClasses.js.map +1 -0
- package/dist/esm/helpers/useClientLayoutEffect.js +13 -0
- package/dist/esm/helpers/useClientLayoutEffect.js.map +1 -0
- package/dist/esm/helpers/useColorClasses.js +84 -0
- package/dist/esm/helpers/useColorClasses.js.map +1 -0
- package/dist/esm/helpers/useFlexboxClasses.js +92 -0
- package/dist/esm/helpers/useFlexboxClasses.js.map +1 -0
- package/dist/esm/helpers/useFocusTrap.js +470 -0
- package/dist/esm/helpers/useFocusTrap.js.map +1 -0
- package/dist/esm/helpers/useIsHydrated.js +31 -0
- package/dist/esm/helpers/useIsHydrated.js.map +1 -0
- package/dist/esm/helpers/useOtherClasses.js +115 -0
- package/dist/esm/helpers/useOtherClasses.js.map +1 -0
- package/dist/esm/helpers/useSpacingClasses.js +82 -0
- package/dist/esm/helpers/useSpacingClasses.js.map +1 -0
- package/dist/esm/helpers/useTypographyClasses.js +93 -0
- package/dist/esm/helpers/useTypographyClasses.js.map +1 -0
- package/dist/esm/helpers/useVisibilityClasses.js +134 -0
- package/dist/esm/helpers/useVisibilityClasses.js.map +1 -0
- package/dist/esm/helpers/withSubComponents.js +24 -0
- package/dist/esm/helpers/withSubComponents.js.map +1 -0
- package/dist/esm/layout/Container.js +48 -0
- package/dist/esm/layout/Container.js.map +1 -0
- package/dist/esm/layout/Footer.js +33 -0
- package/dist/esm/layout/Footer.js.map +1 -0
- package/dist/esm/layout/Hero.js +90 -0
- package/dist/esm/layout/Hero.js.map +1 -0
- package/dist/esm/layout/Level.js +110 -0
- package/dist/esm/layout/Level.js.map +1 -0
- package/dist/esm/layout/Media.js +86 -0
- package/dist/esm/layout/Media.js.map +1 -0
- package/dist/esm/layout/Section.js +30 -0
- package/dist/esm/layout/Section.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +821 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +115 -16845
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Tabs.d.ts +25 -3
- package/dist/types/elements/Icon.d.ts +10 -7
- package/dist/types/form/DateInputBase.d.ts +8 -6
- package/dist/types/form/DateRangeInput.d.ts +91 -0
- package/dist/types/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types/form/DateTimeInputBase.d.ts +10 -6
- package/dist/types/form/File.d.ts +18 -5
- package/dist/types/form/FormContext.d.ts +11 -0
- package/dist/types/form/TimeInputBase.d.ts +1 -1
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types/form/_pickerInternals/dateUtils.d.ts +7 -0
- package/dist/types/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types/form/_pickerInternals/pickerTypes.d.ts +13 -0
- package/dist/types/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types/index.d.ts +4 -0
- package/dist/types-cjs/components/Tabs.d.ts +25 -3
- package/dist/types-cjs/elements/Icon.d.ts +10 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +8 -6
- package/dist/types-cjs/form/DateRangeInput.d.ts +91 -0
- package/dist/types-cjs/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +10 -6
- package/dist/types-cjs/form/File.d.ts +18 -5
- package/dist/types-cjs/form/FormContext.d.ts +11 -0
- package/dist/types-cjs/form/TimeInputBase.d.ts +1 -1
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +7 -0
- package/dist/types-cjs/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types-cjs/form/_pickerInternals/pickerTypes.d.ts +13 -0
- package/dist/types-cjs/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types-cjs/index.d.ts +4 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_tabs.scss +44 -0
- package/src/scss/form/_dateinput.scss +87 -8
- package/src/scss/form/_daterangeinput.scss +63 -0
- package/src/scss/form/_datetimeinput.scss +7 -2
- package/src/scss/form/_file.scss +37 -0
- package/src/scss/form/_index.scss +2 -0
- package/src/scss/form/_picker-popover.scss +1 -4
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useRef, useState, Children, isValidElement, useEffect, useCallback, cloneElement } from 'react';
|
|
4
|
+
import { classNames, usePrefixedClassNames, prefixedClassNames } from '../helpers/classNames.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { useClassPrefix } from '../helpers/Config.js';
|
|
7
|
+
import { getActiveElementInTree } from '../helpers/shadowDom.js';
|
|
8
|
+
import { Icon } from '../elements/Icon.js';
|
|
9
|
+
import { Button } from '../elements/Button.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Individual carousel item/slide.
|
|
13
|
+
*
|
|
14
|
+
* @function
|
|
15
|
+
* @param {CarouselItemProps} props - Props for the CarouselItem component.
|
|
16
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the carousel item element.
|
|
17
|
+
* @returns {JSX.Element} The rendered carousel item.
|
|
18
|
+
*/
|
|
19
|
+
const CarouselItem = forwardRef(({ active, className, children, ...props }, ref) => {
|
|
20
|
+
const itemClasses = classNames(usePrefixedClassNames('carousel-item', { 'is-active': active }), className);
|
|
21
|
+
return (jsx("div", { ref: ref, className: itemClasses, ...props, children: children }));
|
|
22
|
+
});
|
|
23
|
+
CarouselItem.displayName = 'CarouselItem';
|
|
24
|
+
/**
|
|
25
|
+
* Default SVG icon for the previous arrow button.
|
|
26
|
+
*
|
|
27
|
+
* @function
|
|
28
|
+
* @returns {JSX.Element} The rendered SVG chevron-left icon.
|
|
29
|
+
*/
|
|
30
|
+
const DefaultPrevIcon = () => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("polyline", { points: "15 18 9 12 15 6" }) }));
|
|
31
|
+
/**
|
|
32
|
+
* Default SVG icon for the next arrow button.
|
|
33
|
+
*
|
|
34
|
+
* @function
|
|
35
|
+
* @returns {JSX.Element} The rendered SVG chevron-right icon.
|
|
36
|
+
*/
|
|
37
|
+
const DefaultNextIcon = () => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("polyline", { points: "9 18 15 12 9 6" }) }));
|
|
38
|
+
/**
|
|
39
|
+
* The `Carousel` component provides an image/content slider with navigation arrows and indicators.
|
|
40
|
+
*
|
|
41
|
+
* @function
|
|
42
|
+
* @param {CarouselProps} props - Props for the Carousel component.
|
|
43
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the carousel element.
|
|
44
|
+
* @returns {JSX.Element} The rendered carousel component.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Basic carousel
|
|
48
|
+
* <Carousel>
|
|
49
|
+
* <CarouselItem>Slide 1</CarouselItem>
|
|
50
|
+
* <CarouselItem>Slide 2</CarouselItem>
|
|
51
|
+
* <CarouselItem>Slide 3</CarouselItem>
|
|
52
|
+
* </Carousel>
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Auto-playing carousel
|
|
56
|
+
* <Carousel autoplay interval={3000}>
|
|
57
|
+
* <CarouselItem>
|
|
58
|
+
* <img src="image1.jpg" alt="Slide 1" />
|
|
59
|
+
* </CarouselItem>
|
|
60
|
+
* <CarouselItem>
|
|
61
|
+
* <img src="image2.jpg" alt="Slide 2" />
|
|
62
|
+
* </CarouselItem>
|
|
63
|
+
* </Carousel>
|
|
64
|
+
*/
|
|
65
|
+
const Carousel = forwardRef(({ value: controlledValue, autoplay = false, interval = 5000, pauseOnHover = true, repeat = true, hasDrag = true, arrow = true, arrowHover = false, indicator = true, indicatorInside = false, indicatorPosition = 'bottom', indicatorStyle = 'dots', iconPrev, iconNext, iconLibrary, iconVariant, iconSize, iconFeatures, arrowBackground = true, arrowColor, ariaLabel = 'Image carousel', onChange, className, children, ...props }, ref) => {
|
|
66
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
67
|
+
const carouselRef = useRef(null);
|
|
68
|
+
// Cleanup returned by a consumer's callback ref on attach, held until detach.
|
|
69
|
+
const consumerCleanupRef = useRef(null);
|
|
70
|
+
const containerRef = useRef(null);
|
|
71
|
+
const [internalValue, setInternalValue] = useState(0);
|
|
72
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
73
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
74
|
+
const [dragStart, setDragStart] = useState(0);
|
|
75
|
+
const [dragOffset, setDragOffset] = useState(0);
|
|
76
|
+
const [isWrapping, setIsWrapping] = useState(false);
|
|
77
|
+
const [displayIndex, setDisplayIndex] = useState(0);
|
|
78
|
+
const [skipTransition, setSkipTransition] = useState(false);
|
|
79
|
+
// Get valid children
|
|
80
|
+
const items = Children.toArray(children).filter(isValidElement);
|
|
81
|
+
const itemCount = items.length;
|
|
82
|
+
// Use controlled or internal value
|
|
83
|
+
const activeIndex = controlledValue !== undefined ? controlledValue : internalValue;
|
|
84
|
+
// Sync displayIndex when controlled value changes externally. Intentional
|
|
85
|
+
// external-prop sync that must not run mid-wrap animation; covered by tests
|
|
86
|
+
// and certified in-browser.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (controlledValue !== undefined && !isWrapping) {
|
|
89
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop
|
|
90
|
+
setDisplayIndex(controlledValue);
|
|
91
|
+
}
|
|
92
|
+
}, [controlledValue, isWrapping]);
|
|
93
|
+
// Update active index
|
|
94
|
+
const setActiveIndex = useCallback((index, syncDisplay = true) => {
|
|
95
|
+
let newIndex = index;
|
|
96
|
+
if (repeat) {
|
|
97
|
+
if (index < 0) {
|
|
98
|
+
newIndex = itemCount - 1;
|
|
99
|
+
}
|
|
100
|
+
else if (index >= itemCount) {
|
|
101
|
+
newIndex = 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
newIndex = Math.max(0, Math.min(index, itemCount - 1));
|
|
106
|
+
}
|
|
107
|
+
if (controlledValue === undefined) {
|
|
108
|
+
setInternalValue(newIndex);
|
|
109
|
+
}
|
|
110
|
+
if (syncDisplay) {
|
|
111
|
+
setDisplayIndex(newIndex);
|
|
112
|
+
}
|
|
113
|
+
onChange?.(newIndex);
|
|
114
|
+
}, [controlledValue, itemCount, repeat, onChange]);
|
|
115
|
+
// Navigation handlers
|
|
116
|
+
const goToPrev = useCallback(() => {
|
|
117
|
+
if (activeIndex === 0 && repeat && itemCount > 1) {
|
|
118
|
+
// Animate to clone of last slide (position -1)
|
|
119
|
+
setDisplayIndex(-1);
|
|
120
|
+
setIsWrapping(true);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
setActiveIndex(activeIndex - 1);
|
|
124
|
+
}
|
|
125
|
+
}, [activeIndex, repeat, itemCount, setActiveIndex]);
|
|
126
|
+
const goToNext = useCallback(() => {
|
|
127
|
+
if (activeIndex === itemCount - 1 && repeat && itemCount > 1) {
|
|
128
|
+
// Animate to clone of first slide (position itemCount)
|
|
129
|
+
setDisplayIndex(itemCount);
|
|
130
|
+
setIsWrapping(true);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
setActiveIndex(activeIndex + 1);
|
|
134
|
+
}
|
|
135
|
+
}, [activeIndex, itemCount, repeat, setActiveIndex]);
|
|
136
|
+
const goToSlide = useCallback((index) => {
|
|
137
|
+
setIsWrapping(false);
|
|
138
|
+
setActiveIndex(index);
|
|
139
|
+
}, [setActiveIndex]);
|
|
140
|
+
// Auto-play
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!autoplay || isPaused || itemCount <= 1)
|
|
143
|
+
return undefined;
|
|
144
|
+
const timer = setInterval(() => {
|
|
145
|
+
goToNext();
|
|
146
|
+
}, interval);
|
|
147
|
+
return () => clearInterval(timer);
|
|
148
|
+
}, [autoplay, isPaused, interval, goToNext, itemCount]);
|
|
149
|
+
// Pause on hover
|
|
150
|
+
const handleMouseEnter = useCallback(() => {
|
|
151
|
+
if (pauseOnHover) {
|
|
152
|
+
setIsPaused(true);
|
|
153
|
+
}
|
|
154
|
+
}, [pauseOnHover]);
|
|
155
|
+
const handleMouseLeave = useCallback(() => {
|
|
156
|
+
if (pauseOnHover) {
|
|
157
|
+
setIsPaused(false);
|
|
158
|
+
}
|
|
159
|
+
}, [pauseOnHover]);
|
|
160
|
+
// Drag handlers
|
|
161
|
+
const handleDragStart = useCallback((clientX) => {
|
|
162
|
+
if (!hasDrag)
|
|
163
|
+
return;
|
|
164
|
+
setIsDragging(true);
|
|
165
|
+
setDragStart(clientX);
|
|
166
|
+
setDragOffset(0);
|
|
167
|
+
}, [hasDrag]);
|
|
168
|
+
const handleDragMove = useCallback((clientX) => {
|
|
169
|
+
if (!isDragging)
|
|
170
|
+
return;
|
|
171
|
+
setDragOffset(clientX - dragStart);
|
|
172
|
+
}, [isDragging, dragStart]);
|
|
173
|
+
const handleDragEnd = useCallback(() => {
|
|
174
|
+
if (!isDragging)
|
|
175
|
+
return;
|
|
176
|
+
setIsDragging(false);
|
|
177
|
+
const threshold = 50;
|
|
178
|
+
if (dragOffset < -threshold) {
|
|
179
|
+
goToNext();
|
|
180
|
+
}
|
|
181
|
+
else if (dragOffset > threshold) {
|
|
182
|
+
goToPrev();
|
|
183
|
+
}
|
|
184
|
+
setDragOffset(0);
|
|
185
|
+
}, [isDragging, dragOffset, goToNext, goToPrev]);
|
|
186
|
+
// Mouse events
|
|
187
|
+
const handleMouseDown = useCallback((e) => {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
handleDragStart(e.clientX);
|
|
190
|
+
}, [handleDragStart]);
|
|
191
|
+
const handleMouseMove = useCallback((e) => {
|
|
192
|
+
handleDragMove(e.clientX);
|
|
193
|
+
}, [handleDragMove]);
|
|
194
|
+
const handleMouseUp = useCallback(() => {
|
|
195
|
+
handleDragEnd();
|
|
196
|
+
}, [handleDragEnd]);
|
|
197
|
+
// Touch events
|
|
198
|
+
const handleTouchStart = useCallback((e) => {
|
|
199
|
+
handleDragStart(e.touches[0].clientX);
|
|
200
|
+
}, [handleDragStart]);
|
|
201
|
+
const handleTouchMove = useCallback((e) => {
|
|
202
|
+
handleDragMove(e.touches[0].clientX);
|
|
203
|
+
}, [handleDragMove]);
|
|
204
|
+
const handleTouchEnd = useCallback(() => {
|
|
205
|
+
handleDragEnd();
|
|
206
|
+
}, [handleDragEnd]);
|
|
207
|
+
// Keyboard navigation
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
const handleKeyDown = (e) => {
|
|
210
|
+
const node = carouselRef.current;
|
|
211
|
+
if (!node?.contains(getActiveElementInTree(node)))
|
|
212
|
+
return;
|
|
213
|
+
switch (e.key) {
|
|
214
|
+
case 'ArrowLeft':
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
goToPrev();
|
|
217
|
+
break;
|
|
218
|
+
case 'ArrowRight':
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
goToNext();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
225
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
226
|
+
}, [goToPrev, goToNext]);
|
|
227
|
+
// Handle wrap-around transition end
|
|
228
|
+
const handleTransitionEnd = useCallback(() => {
|
|
229
|
+
if (isWrapping) {
|
|
230
|
+
// Skip transition for the instant snap
|
|
231
|
+
setSkipTransition(true);
|
|
232
|
+
setIsWrapping(false);
|
|
233
|
+
if (displayIndex === itemCount) {
|
|
234
|
+
// Was at clone of first slide, snap to real first slide
|
|
235
|
+
setActiveIndex(0, false);
|
|
236
|
+
setDisplayIndex(0);
|
|
237
|
+
}
|
|
238
|
+
else if (displayIndex === -1) {
|
|
239
|
+
// Was at clone of last slide, snap to real last slide
|
|
240
|
+
setActiveIndex(itemCount - 1, false);
|
|
241
|
+
setDisplayIndex(itemCount - 1);
|
|
242
|
+
}
|
|
243
|
+
// Re-enable transition after the snap
|
|
244
|
+
requestAnimationFrame(() => {
|
|
245
|
+
requestAnimationFrame(() => {
|
|
246
|
+
setSkipTransition(false);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}, [isWrapping, displayIndex, itemCount, setActiveIndex]);
|
|
251
|
+
// Combined ref
|
|
252
|
+
const combinedRef = useCallback((node) => {
|
|
253
|
+
carouselRef.current =
|
|
254
|
+
node;
|
|
255
|
+
if (typeof ref === 'function') {
|
|
256
|
+
// React 19 lets a callback ref return a cleanup function and detaches by
|
|
257
|
+
// running it instead of calling the ref with `null`; React 18 discards the
|
|
258
|
+
// return value entirely. Returning it from here would be a React-19-only
|
|
259
|
+
// contract, so we hold the cleanup and run it ourselves on detach — the
|
|
260
|
+
// same shape as `Dropdown`, `Modal`, `Dialog`, `Sidebar` and `Toast`.
|
|
261
|
+
// The form controls that merge a forwarded ref still discard the cleanup
|
|
262
|
+
// and detach with `ref(null)`, so this is not yet library-wide — grep
|
|
263
|
+
// `typeof ref === 'function'` for the current split.
|
|
264
|
+
if (node === null) {
|
|
265
|
+
const consumerCleanup = consumerCleanupRef.current;
|
|
266
|
+
consumerCleanupRef.current = null;
|
|
267
|
+
if (consumerCleanup) {
|
|
268
|
+
consumerCleanup();
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
ref(null);
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const cleanup = ref(node);
|
|
276
|
+
consumerCleanupRef.current =
|
|
277
|
+
typeof cleanup === 'function' ? cleanup : null;
|
|
278
|
+
}
|
|
279
|
+
else if (ref) {
|
|
280
|
+
ref.current = node;
|
|
281
|
+
}
|
|
282
|
+
}, [ref]);
|
|
283
|
+
// Can navigate
|
|
284
|
+
const canGoPrev = repeat || activeIndex > 0;
|
|
285
|
+
const canGoNext = repeat || activeIndex < itemCount - 1;
|
|
286
|
+
// Generate classes
|
|
287
|
+
const carouselClasses = usePrefixedClassNames('carousel', {
|
|
288
|
+
'is-overlay': indicatorInside,
|
|
289
|
+
'has-arrow-hover': arrowHover,
|
|
290
|
+
[`is-indicator-${indicatorStyle}`]: indicatorStyle !== 'dots',
|
|
291
|
+
[`is-arrow-${arrowColor}`]: !!arrowColor,
|
|
292
|
+
});
|
|
293
|
+
const classPrefix = useClassPrefix();
|
|
294
|
+
const carouselContainerClass = usePrefixedClassNames('carousel-container');
|
|
295
|
+
const carouselSlidesClass = usePrefixedClassNames('carousel-slides');
|
|
296
|
+
const carouselArrowPrevClass = usePrefixedClassNames('carousel-arrow', 'is-prev', { 'is-transparent': !arrowBackground });
|
|
297
|
+
const carouselArrowNextClass = usePrefixedClassNames('carousel-arrow', 'is-next', { 'is-transparent': !arrowBackground });
|
|
298
|
+
const carouselIndicatorClass = usePrefixedClassNames('carousel-indicator', {
|
|
299
|
+
'is-inside': indicatorInside,
|
|
300
|
+
'is-top': indicatorPosition === 'top',
|
|
301
|
+
});
|
|
302
|
+
const combinedClasses = classNames(carouselClasses, bulmaHelperClasses, className);
|
|
303
|
+
// Render slides with active state (includes clones for infinite loop)
|
|
304
|
+
const renderSlides = () => {
|
|
305
|
+
const slides = items.map((item, index) => {
|
|
306
|
+
if (isValidElement(item)) {
|
|
307
|
+
return cloneElement(item, {
|
|
308
|
+
key: index,
|
|
309
|
+
active: index === activeIndex,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return item;
|
|
313
|
+
});
|
|
314
|
+
// Add clones for seamless infinite loop when repeat is enabled
|
|
315
|
+
if (repeat && itemCount > 1) {
|
|
316
|
+
const lastItem = items[itemCount - 1];
|
|
317
|
+
const firstItem = items[0];
|
|
318
|
+
const lastClone = isValidElement(lastItem)
|
|
319
|
+
? cloneElement(lastItem, {
|
|
320
|
+
key: 'clone-last',
|
|
321
|
+
active: false,
|
|
322
|
+
})
|
|
323
|
+
: null;
|
|
324
|
+
const firstClone = isValidElement(firstItem)
|
|
325
|
+
? cloneElement(firstItem, {
|
|
326
|
+
key: 'clone-first',
|
|
327
|
+
active: false,
|
|
328
|
+
})
|
|
329
|
+
: null;
|
|
330
|
+
return [lastClone, ...slides, firstClone];
|
|
331
|
+
}
|
|
332
|
+
return slides;
|
|
333
|
+
};
|
|
334
|
+
return (jsxs("div", { ref: combinedRef, className: combinedClasses, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "region", "aria-roledescription": "carousel", "aria-label": ariaLabel, ...rest, children: [jsxs("div", { ref: containerRef, className: carouselContainerClass, onMouseDown: hasDrag ? handleMouseDown : undefined, onMouseMove: hasDrag && isDragging ? handleMouseMove : undefined, onMouseUp: hasDrag ? handleMouseUp : undefined, onMouseLeave: hasDrag ? handleMouseUp : undefined, onTouchStart: hasDrag ? handleTouchStart : undefined, onTouchMove: hasDrag ? handleTouchMove : undefined, onTouchEnd: hasDrag ? handleTouchEnd : undefined, style: {
|
|
335
|
+
transform: isDragging ? `translateX(${dragOffset}px)` : undefined,
|
|
336
|
+
cursor: hasDrag ? (isDragging ? 'grabbing' : 'grab') : undefined,
|
|
337
|
+
}, children: [jsx("div", { className: carouselSlidesClass, style: {
|
|
338
|
+
// When repeat is enabled, account for the prepended clone (+1 offset)
|
|
339
|
+
transform: `translateX(-${(repeat && itemCount > 1 ? displayIndex + 1 : displayIndex) * 100}%)`,
|
|
340
|
+
// Disable transition during drag or instant snap after wrap-around
|
|
341
|
+
transition: isDragging || skipTransition
|
|
342
|
+
? 'none'
|
|
343
|
+
: 'transform 0.3s ease-in-out',
|
|
344
|
+
}, onTransitionEnd: handleTransitionEnd, children: renderSlides() }), arrow && itemCount > 1 && (jsxs(Fragment, { children: [jsx(Button, { type: "button", className: carouselArrowPrevClass, onClick: goToPrev, isDisabled: !canGoPrev, "aria-label": "Previous slide", children: iconPrev ? (jsx(Icon, { name: iconPrev, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsx(DefaultPrevIcon, {})) }), jsx(Button, { type: "button", className: carouselArrowNextClass, onClick: goToNext, isDisabled: !canGoNext, "aria-label": "Next slide", children: iconNext ? (jsx(Icon, { name: iconNext, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsx(DefaultNextIcon, {})) })] }))] }), indicator && itemCount > 1 && (jsx("div", { className: carouselIndicatorClass, role: "tablist", children: items.map((_, index) => (jsx(Button, { type: "button", className: prefixedClassNames(classPrefix, 'indicator-item', {
|
|
345
|
+
'is-active': index === activeIndex,
|
|
346
|
+
}), onClick: () => goToSlide(index), role: "tab", "aria-selected": index === activeIndex, "aria-label": `Go to slide ${index + 1}` }, index))) }))] }));
|
|
347
|
+
});
|
|
348
|
+
Carousel.displayName = 'Carousel';
|
|
349
|
+
|
|
350
|
+
export { Carousel, CarouselItem };
|
|
351
|
+
//# sourceMappingURL=Carousel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Carousel.js","sources":["../../../src/components/Carousel.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useState,\n useRef,\n useEffect,\n useCallback,\n Children,\n isValidElement,\n cloneElement,\n} from 'react';\nimport {\n classNames,\n prefixedClassNames,\n usePrefixedClassNames,\n} from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { useClassPrefix } from '../helpers/Config';\nimport { getActiveElementInTree } from '../helpers/shadowDom';\nimport { Icon } from '../elements/Icon';\nimport { Button } from '../elements/Button';\n\n/**\n * Props for the CarouselItem component.\n */\nexport interface CarouselItemProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether this item is active (set automatically). */\n active?: boolean;\n}\n\n/**\n * Individual carousel item/slide.\n *\n * @function\n * @param {CarouselItemProps} props - Props for the CarouselItem component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the carousel item element.\n * @returns {JSX.Element} The rendered carousel item.\n */\nexport const CarouselItem = forwardRef<HTMLDivElement, CarouselItemProps>(\n ({ active, className, children, ...props }, ref) => {\n const itemClasses = classNames(\n usePrefixedClassNames('carousel-item', { 'is-active': active }),\n className\n );\n\n return (\n <div ref={ref} className={itemClasses} {...props}>\n {children}\n </div>\n );\n }\n);\n\nCarouselItem.displayName = 'CarouselItem';\n\ntype IconLibrary = 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n\n/**\n * Default SVG icon for the previous arrow button.\n *\n * @function\n * @returns {JSX.Element} The rendered SVG chevron-left icon.\n */\nconst DefaultPrevIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n);\n\n/**\n * Default SVG icon for the next arrow button.\n *\n * @function\n * @returns {JSX.Element} The rendered SVG chevron-right icon.\n */\nconst DefaultNextIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n);\n\n/**\n * Props for the Carousel component.\n * @extraProp {string} [className] - Additional CSS classes.\n * @extraProp {React.Ref<HTMLElement>} [ref] - Ref forwarded to the carousel element.\n */\nexport interface CarouselProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'color'>,\n BulmaClassesProps {\n /**\n * Current active slide index (controlled).\n * @defaultValue 0\n */\n value?: number;\n /** Enable auto-play. */\n autoplay?: boolean;\n /** Auto-play interval in milliseconds. Default: 5000. */\n interval?: number;\n /** Pause auto-play on hover. Default: true. */\n pauseOnHover?: boolean;\n /** Loop back to first slide after last. Default: true. */\n repeat?: boolean;\n /** Enable drag/swipe navigation. Default: true. */\n hasDrag?: boolean;\n /**\n * Show navigation arrows. Default: true. They render `type=\"button\"`, so a\n * carousel inside a form is not submitted by moving between slides.\n */\n arrow?: boolean;\n /** Only show arrows on hover. */\n arrowHover?: boolean;\n /**\n * Show slide indicators. Default: true. They render `type=\"button\"`, so a\n * carousel inside a form is not submitted by picking a slide.\n */\n indicator?: boolean;\n /** Position indicators inside carousel. */\n indicatorInside?: boolean;\n /** Indicator position. Default: 'bottom'. */\n indicatorPosition?: 'bottom' | 'top';\n /** Indicator style. Default: 'dots'. */\n indicatorStyle?: 'circles' | 'dots' | 'lines' | 'bars';\n /** Icon name for the previous arrow button. */\n iconPrev?: string;\n /** Icon name for the next arrow button. */\n iconNext?: string;\n /** Icon library to use. */\n iconLibrary?: IconLibrary;\n /** Icon style variant (e.g., 'solid', 'outlined'). */\n iconVariant?: string;\n /** Icon size modifier. */\n iconSize?: 'small' | 'medium' | 'large';\n /** Additional icon modifiers. */\n iconFeatures?: string | string[];\n /** Show semi-transparent background on arrow buttons. Set to `false` for icon-only arrows. */\n arrowBackground?: boolean;\n /** Color theme for navigation arrows. */\n arrowColor?: 'light' | 'dark';\n /** Accessible label for the carousel region. Default: 'Image carousel'. */\n ariaLabel?: string;\n /** Callback when slide changes. */\n onChange?: (value: number) => void;\n /** CarouselItem children. */\n children?: React.ReactNode;\n}\n\n/**\n * The `Carousel` component provides an image/content slider with navigation arrows and indicators.\n *\n * @function\n * @param {CarouselProps} props - Props for the Carousel component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the carousel element.\n * @returns {JSX.Element} The rendered carousel component.\n *\n * @example\n * // Basic carousel\n * <Carousel>\n * <CarouselItem>Slide 1</CarouselItem>\n * <CarouselItem>Slide 2</CarouselItem>\n * <CarouselItem>Slide 3</CarouselItem>\n * </Carousel>\n *\n * @example\n * // Auto-playing carousel\n * <Carousel autoplay interval={3000}>\n * <CarouselItem>\n * <img src=\"image1.jpg\" alt=\"Slide 1\" />\n * </CarouselItem>\n * <CarouselItem>\n * <img src=\"image2.jpg\" alt=\"Slide 2\" />\n * </CarouselItem>\n * </Carousel>\n */\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(\n (\n {\n value: controlledValue,\n autoplay = false,\n interval = 5000,\n pauseOnHover = true,\n repeat = true,\n hasDrag = true,\n arrow = true,\n arrowHover = false,\n indicator = true,\n indicatorInside = false,\n indicatorPosition = 'bottom',\n indicatorStyle = 'dots',\n iconPrev,\n iconNext,\n iconLibrary,\n iconVariant,\n iconSize,\n iconFeatures,\n arrowBackground = true,\n arrowColor,\n ariaLabel = 'Image carousel',\n onChange,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const carouselRef = useRef<HTMLDivElement>(null);\n // Cleanup returned by a consumer's callback ref on attach, held until detach.\n const consumerCleanupRef = useRef<(() => void) | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const [internalValue, setInternalValue] = useState(0);\n const [isPaused, setIsPaused] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n const [dragStart, setDragStart] = useState(0);\n const [dragOffset, setDragOffset] = useState(0);\n const [isWrapping, setIsWrapping] = useState(false);\n const [displayIndex, setDisplayIndex] = useState(0);\n const [skipTransition, setSkipTransition] = useState(false);\n\n // Get valid children\n const items = Children.toArray(children).filter(isValidElement);\n const itemCount = items.length;\n\n // Use controlled or internal value\n const activeIndex =\n controlledValue !== undefined ? controlledValue : internalValue;\n\n // Sync displayIndex when controlled value changes externally. Intentional\n // external-prop sync that must not run mid-wrap animation; covered by tests\n // and certified in-browser.\n useEffect(() => {\n if (controlledValue !== undefined && !isWrapping) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop\n setDisplayIndex(controlledValue);\n }\n }, [controlledValue, isWrapping]);\n\n // Update active index\n const setActiveIndex = useCallback(\n (index: number, syncDisplay = true) => {\n let newIndex = index;\n\n if (repeat) {\n if (index < 0) {\n newIndex = itemCount - 1;\n } else if (index >= itemCount) {\n newIndex = 0;\n }\n } else {\n newIndex = Math.max(0, Math.min(index, itemCount - 1));\n }\n\n if (controlledValue === undefined) {\n setInternalValue(newIndex);\n }\n if (syncDisplay) {\n setDisplayIndex(newIndex);\n }\n onChange?.(newIndex);\n },\n [controlledValue, itemCount, repeat, onChange]\n );\n\n // Navigation handlers\n const goToPrev = useCallback(() => {\n if (activeIndex === 0 && repeat && itemCount > 1) {\n // Animate to clone of last slide (position -1)\n setDisplayIndex(-1);\n setIsWrapping(true);\n } else {\n setActiveIndex(activeIndex - 1);\n }\n }, [activeIndex, repeat, itemCount, setActiveIndex]);\n\n const goToNext = useCallback(() => {\n if (activeIndex === itemCount - 1 && repeat && itemCount > 1) {\n // Animate to clone of first slide (position itemCount)\n setDisplayIndex(itemCount);\n setIsWrapping(true);\n } else {\n setActiveIndex(activeIndex + 1);\n }\n }, [activeIndex, itemCount, repeat, setActiveIndex]);\n\n const goToSlide = useCallback(\n (index: number) => {\n setIsWrapping(false);\n setActiveIndex(index);\n },\n [setActiveIndex]\n );\n\n // Auto-play\n useEffect(() => {\n if (!autoplay || isPaused || itemCount <= 1) return undefined;\n\n const timer = setInterval(() => {\n goToNext();\n }, interval);\n\n return () => clearInterval(timer);\n }, [autoplay, isPaused, interval, goToNext, itemCount]);\n\n // Pause on hover\n const handleMouseEnter = useCallback(() => {\n if (pauseOnHover) {\n setIsPaused(true);\n }\n }, [pauseOnHover]);\n\n const handleMouseLeave = useCallback(() => {\n if (pauseOnHover) {\n setIsPaused(false);\n }\n }, [pauseOnHover]);\n\n // Drag handlers\n const handleDragStart = useCallback(\n (clientX: number) => {\n if (!hasDrag) return;\n setIsDragging(true);\n setDragStart(clientX);\n setDragOffset(0);\n },\n [hasDrag]\n );\n\n const handleDragMove = useCallback(\n (clientX: number) => {\n if (!isDragging) return;\n setDragOffset(clientX - dragStart);\n },\n [isDragging, dragStart]\n );\n\n const handleDragEnd = useCallback(() => {\n if (!isDragging) return;\n setIsDragging(false);\n\n const threshold = 50;\n if (dragOffset < -threshold) {\n goToNext();\n } else if (dragOffset > threshold) {\n goToPrev();\n }\n\n setDragOffset(0);\n }, [isDragging, dragOffset, goToNext, goToPrev]);\n\n // Mouse events\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n handleDragStart(e.clientX);\n },\n [handleDragStart]\n );\n\n const handleMouseMove = useCallback(\n (e: React.MouseEvent) => {\n handleDragMove(e.clientX);\n },\n [handleDragMove]\n );\n\n const handleMouseUp = useCallback(() => {\n handleDragEnd();\n }, [handleDragEnd]);\n\n // Touch events\n const handleTouchStart = useCallback(\n (e: React.TouchEvent) => {\n handleDragStart(e.touches[0].clientX);\n },\n [handleDragStart]\n );\n\n const handleTouchMove = useCallback(\n (e: React.TouchEvent) => {\n handleDragMove(e.touches[0].clientX);\n },\n [handleDragMove]\n );\n\n const handleTouchEnd = useCallback(() => {\n handleDragEnd();\n }, [handleDragEnd]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const node = carouselRef.current;\n if (!node?.contains(getActiveElementInTree(node))) return;\n\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault();\n goToPrev();\n break;\n case 'ArrowRight':\n e.preventDefault();\n goToNext();\n break;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [goToPrev, goToNext]);\n\n // Handle wrap-around transition end\n const handleTransitionEnd = useCallback(() => {\n if (isWrapping) {\n // Skip transition for the instant snap\n setSkipTransition(true);\n setIsWrapping(false);\n\n if (displayIndex === itemCount) {\n // Was at clone of first slide, snap to real first slide\n setActiveIndex(0, false);\n setDisplayIndex(0);\n } else if (displayIndex === -1) {\n // Was at clone of last slide, snap to real last slide\n setActiveIndex(itemCount - 1, false);\n setDisplayIndex(itemCount - 1);\n }\n\n // Re-enable transition after the snap\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setSkipTransition(false);\n });\n });\n }\n }, [isWrapping, displayIndex, itemCount, setActiveIndex]);\n\n // Combined ref\n const combinedRef = useCallback(\n (node: HTMLDivElement | null) => {\n (carouselRef as React.MutableRefObject<HTMLDivElement | null>).current =\n node;\n if (typeof ref === 'function') {\n // React 19 lets a callback ref return a cleanup function and detaches by\n // running it instead of calling the ref with `null`; React 18 discards the\n // return value entirely. Returning it from here would be a React-19-only\n // contract, so we hold the cleanup and run it ourselves on detach — the\n // same shape as `Dropdown`, `Modal`, `Dialog`, `Sidebar` and `Toast`.\n // The form controls that merge a forwarded ref still discard the cleanup\n // and detach with `ref(null)`, so this is not yet library-wide — grep\n // `typeof ref === 'function'` for the current split.\n if (node === null) {\n const consumerCleanup = consumerCleanupRef.current;\n consumerCleanupRef.current = null;\n if (consumerCleanup) {\n consumerCleanup();\n } else {\n ref(null);\n }\n return;\n }\n const cleanup: unknown = ref(node);\n consumerCleanupRef.current =\n typeof cleanup === 'function' ? (cleanup as () => void) : null;\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [ref]\n );\n\n // Can navigate\n const canGoPrev = repeat || activeIndex > 0;\n const canGoNext = repeat || activeIndex < itemCount - 1;\n\n // Generate classes\n const carouselClasses = usePrefixedClassNames('carousel', {\n 'is-overlay': indicatorInside,\n 'has-arrow-hover': arrowHover,\n [`is-indicator-${indicatorStyle}`]: indicatorStyle !== 'dots',\n [`is-arrow-${arrowColor}`]: !!arrowColor,\n });\n const classPrefix = useClassPrefix();\n const carouselContainerClass = usePrefixedClassNames('carousel-container');\n const carouselSlidesClass = usePrefixedClassNames('carousel-slides');\n const carouselArrowPrevClass = usePrefixedClassNames(\n 'carousel-arrow',\n 'is-prev',\n { 'is-transparent': !arrowBackground }\n );\n const carouselArrowNextClass = usePrefixedClassNames(\n 'carousel-arrow',\n 'is-next',\n { 'is-transparent': !arrowBackground }\n );\n const carouselIndicatorClass = usePrefixedClassNames('carousel-indicator', {\n 'is-inside': indicatorInside,\n 'is-top': indicatorPosition === 'top',\n });\n\n const combinedClasses = classNames(\n carouselClasses,\n bulmaHelperClasses,\n className\n );\n\n // Render slides with active state (includes clones for infinite loop)\n const renderSlides = () => {\n const slides = items.map((item, index) => {\n if (isValidElement<CarouselItemProps>(item)) {\n return cloneElement(item, {\n key: index,\n active: index === activeIndex,\n });\n }\n return item;\n });\n\n // Add clones for seamless infinite loop when repeat is enabled\n if (repeat && itemCount > 1) {\n const lastItem = items[itemCount - 1];\n const firstItem = items[0];\n\n const lastClone = isValidElement<CarouselItemProps>(lastItem)\n ? cloneElement(lastItem as React.ReactElement<CarouselItemProps>, {\n key: 'clone-last',\n active: false,\n })\n : null;\n\n const firstClone = isValidElement<CarouselItemProps>(firstItem)\n ? cloneElement(firstItem as React.ReactElement<CarouselItemProps>, {\n key: 'clone-first',\n active: false,\n })\n : null;\n\n return [lastClone, ...slides, firstClone];\n }\n\n return slides;\n };\n\n return (\n <div\n ref={combinedRef}\n className={combinedClasses}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label={ariaLabel}\n {...rest}\n >\n <div\n ref={containerRef}\n className={carouselContainerClass}\n onMouseDown={hasDrag ? handleMouseDown : undefined}\n onMouseMove={hasDrag && isDragging ? handleMouseMove : undefined}\n onMouseUp={hasDrag ? handleMouseUp : undefined}\n onMouseLeave={hasDrag ? handleMouseUp : undefined}\n onTouchStart={hasDrag ? handleTouchStart : undefined}\n onTouchMove={hasDrag ? handleTouchMove : undefined}\n onTouchEnd={hasDrag ? handleTouchEnd : undefined}\n style={{\n transform: isDragging ? `translateX(${dragOffset}px)` : undefined,\n cursor: hasDrag ? (isDragging ? 'grabbing' : 'grab') : undefined,\n }}\n >\n <div\n className={carouselSlidesClass}\n style={{\n // When repeat is enabled, account for the prepended clone (+1 offset)\n transform: `translateX(-${(repeat && itemCount > 1 ? displayIndex + 1 : displayIndex) * 100}%)`,\n // Disable transition during drag or instant snap after wrap-around\n transition:\n isDragging || skipTransition\n ? 'none'\n : 'transform 0.3s ease-in-out',\n }}\n onTransitionEnd={handleTransitionEnd}\n >\n {renderSlides()}\n </div>\n {arrow && itemCount > 1 && (\n <>\n <Button\n type=\"button\"\n className={carouselArrowPrevClass}\n onClick={goToPrev}\n isDisabled={!canGoPrev}\n aria-label=\"Previous slide\"\n >\n {iconPrev ? (\n <Icon\n name={iconPrev}\n library={iconLibrary}\n variant={iconVariant}\n size={iconSize}\n features={iconFeatures}\n />\n ) : (\n <DefaultPrevIcon />\n )}\n </Button>\n <Button\n type=\"button\"\n className={carouselArrowNextClass}\n onClick={goToNext}\n isDisabled={!canGoNext}\n aria-label=\"Next slide\"\n >\n {iconNext ? (\n <Icon\n name={iconNext}\n library={iconLibrary}\n variant={iconVariant}\n size={iconSize}\n features={iconFeatures}\n />\n ) : (\n <DefaultNextIcon />\n )}\n </Button>\n </>\n )}\n </div>\n\n {indicator && itemCount > 1 && (\n <div className={carouselIndicatorClass} role=\"tablist\">\n {items.map((_, index) => (\n <Button\n key={index}\n type=\"button\"\n className={prefixedClassNames(classPrefix, 'indicator-item', {\n 'is-active': index === activeIndex,\n })}\n onClick={() => goToSlide(index)}\n role=\"tab\"\n aria-selected={index === activeIndex}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n </div>\n );\n }\n);\n\nCarousel.displayName = 'Carousel';\n\nexport default Carousel;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { useRef, useContext, useState, useId, useEffect, useCallback } from 'react';
|
|
4
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { warnOnce } from '../helpers/devWarnings.js';
|
|
7
|
+
import { inertProps } from '../helpers/inertProps.js';
|
|
8
|
+
import { CollapsesItemContext } from './collapsesContext.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `Collapse` component provides an expandable/collapsible content panel.
|
|
12
|
+
*
|
|
13
|
+
* @function
|
|
14
|
+
* @param {CollapseProps} props - Props for the Collapse component.
|
|
15
|
+
* @returns {JSX.Element} The rendered collapse component.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Basic uncontrolled collapse
|
|
19
|
+
* <Collapse trigger={<button>Click to toggle</button>}>
|
|
20
|
+
* <p>Collapsible content here</p>
|
|
21
|
+
* </Collapse>
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Controlled collapse, driven by its own trigger
|
|
25
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
26
|
+
* <Collapse open={isOpen} onOpenChange={setIsOpen} trigger={<strong>Toggle</strong>}>
|
|
27
|
+
* <p>Content</p>
|
|
28
|
+
* </Collapse>
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // With callbacks
|
|
32
|
+
* <Collapse
|
|
33
|
+
* trigger={<button>Toggle</button>}
|
|
34
|
+
* onOpen={() => console.log('Opened!')}
|
|
35
|
+
* onClose={() => console.log('Closed!')}
|
|
36
|
+
* >
|
|
37
|
+
* <p>Content</p>
|
|
38
|
+
* </Collapse>
|
|
39
|
+
*/
|
|
40
|
+
const Collapse = ({ open: controlledOpen, defaultOpen = false, onOpen, onClose, onOpenChange, trigger, animation = 'fade', position = 'top', ariaId, bordered, children, className, triggerClassName, contentClassName, ...props }) => {
|
|
41
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
42
|
+
const contentRef = useRef(null);
|
|
43
|
+
// Three possible owners of the open state, in order: the caller when `open`
|
|
44
|
+
// is set, an enclosing Collapses group, and this Collapse itself. A caller's
|
|
45
|
+
// `open` wins over the group, which is how the group leaves such an item
|
|
46
|
+
// alone.
|
|
47
|
+
const isControlled = controlledOpen !== undefined;
|
|
48
|
+
const group = useContext(CollapsesItemContext);
|
|
49
|
+
const groupItem = isControlled ? null : group;
|
|
50
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
51
|
+
const isOpen = controlledOpen ?? groupItem?.open ?? internalOpen;
|
|
52
|
+
// A group owns the state of the items it manages, so these props do
|
|
53
|
+
// nothing there. That is easy to miss, so development says which ones and
|
|
54
|
+
// what to use instead, once per combination (the way Avatar keys its
|
|
55
|
+
// warning), and `warnOnce` keeps production silent.
|
|
56
|
+
if (groupItem) {
|
|
57
|
+
const ignored = [
|
|
58
|
+
defaultOpen && 'defaultOpen',
|
|
59
|
+
onOpen && 'onOpen',
|
|
60
|
+
onClose && 'onClose',
|
|
61
|
+
].filter((name) => Boolean(name));
|
|
62
|
+
if (ignored.length > 0) {
|
|
63
|
+
const callbacks = ignored.filter(name => name !== 'defaultOpen');
|
|
64
|
+
warnOnce(`Collapse:ignored-in-Collapses:${ignored.join('+')}`, `[bestax-bulma] <Collapse ${ignored.join(' ')}> inside <Collapses>: ` +
|
|
65
|
+
[
|
|
66
|
+
ignored.includes('defaultOpen') &&
|
|
67
|
+
`defaultOpen has no effect, because the group decides which ` +
|
|
68
|
+
`items start open. Pass the item's index in the group's ` +
|
|
69
|
+
`defaultValue instead.`,
|
|
70
|
+
callbacks.length > 0 &&
|
|
71
|
+
`${callbacks.join(' and ')} never fire${callbacks.length > 1 ? '' : 's'}, ` +
|
|
72
|
+
`because the group holds the item's open state. Use the ` +
|
|
73
|
+
`item's onOpenChange, or the group's onChange, instead.`,
|
|
74
|
+
]
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.join(' '));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Start from the resolved state rather than `defaultOpen`, so an item that
|
|
80
|
+
// mounts open through `open` or a group is not first painted (or rendered
|
|
81
|
+
// on the server) at height 0 under the slide animation.
|
|
82
|
+
const [height, setHeight] = useState(isOpen ? 'auto' : 0);
|
|
83
|
+
// Generate unique ID for accessibility (useId is SSR-safe and stable across
|
|
84
|
+
// renders; works in React 18 and 19).
|
|
85
|
+
const generatedId = useId();
|
|
86
|
+
const uniqueId = ariaId || generatedId;
|
|
87
|
+
// Update height when open state changes (for 'slide' animation)
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!contentRef.current)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (animation !== 'slide')
|
|
92
|
+
return undefined;
|
|
93
|
+
if (isOpen) {
|
|
94
|
+
// Opening: set height to content's scrollHeight
|
|
95
|
+
const contentHeight = contentRef.current.scrollHeight;
|
|
96
|
+
setHeight(contentHeight);
|
|
97
|
+
// After animation completes, set to auto for dynamic content
|
|
98
|
+
const timer = setTimeout(() => {
|
|
99
|
+
setHeight('auto');
|
|
100
|
+
}, 300); // Match CSS transition duration
|
|
101
|
+
return () => clearTimeout(timer);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Closing: first set to current height, then to 0
|
|
105
|
+
if (height === 'auto') {
|
|
106
|
+
// Read scrollHeight and offsetHeight to capture the current size and
|
|
107
|
+
// force a layout flush, then schedule the height transition to 0.
|
|
108
|
+
// (Reading offsetHeight after assignment is the standard reflow trick;
|
|
109
|
+
// assigning into a state setter avoids a bare expression statement.)
|
|
110
|
+
const currentHeight = contentRef.current.scrollHeight;
|
|
111
|
+
const reflowHeight = contentRef.current.offsetHeight;
|
|
112
|
+
setHeight(Math.max(currentHeight, reflowHeight));
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
setHeight(0);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
setHeight(0);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
// `height` is read here but is an output of this effect; re-running when it
|
|
123
|
+
// changes would re-trigger the open/close animation, so it's intentionally
|
|
124
|
+
// excluded from the deps.
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
|
+
}, [isOpen, animation]);
|
|
127
|
+
// The trigger reports in every mode, then changes only the state this
|
|
128
|
+
// Collapse owns. `onOpen`/`onClose` belong to that last case alone.
|
|
129
|
+
const handleToggle = useCallback(() => {
|
|
130
|
+
const next = !isOpen;
|
|
131
|
+
onOpenChange?.(next);
|
|
132
|
+
if (isControlled)
|
|
133
|
+
return;
|
|
134
|
+
if (groupItem) {
|
|
135
|
+
groupItem.setOpen(next);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
setInternalOpen(next);
|
|
139
|
+
if (next) {
|
|
140
|
+
onOpen?.();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
onClose?.();
|
|
144
|
+
}
|
|
145
|
+
}, [isOpen, isControlled, groupItem, onOpenChange, onOpen, onClose]);
|
|
146
|
+
// Handle keyboard interaction
|
|
147
|
+
const handleKeyDown = useCallback((e) => {
|
|
148
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
handleToggle();
|
|
151
|
+
}
|
|
152
|
+
}, [handleToggle]);
|
|
153
|
+
// Generate Bulma classes
|
|
154
|
+
const collapseClasses = usePrefixedClassNames('collapse', {
|
|
155
|
+
'is-active': isOpen,
|
|
156
|
+
'is-bordered': bordered,
|
|
157
|
+
});
|
|
158
|
+
const combinedClasses = classNames(collapseClasses, bulmaHelperClasses, className);
|
|
159
|
+
const combinedTriggerClasses = classNames(usePrefixedClassNames('collapse-trigger'), triggerClassName);
|
|
160
|
+
const combinedContentClasses = classNames(usePrefixedClassNames('collapse-content', {
|
|
161
|
+
'is-active': isOpen,
|
|
162
|
+
}), contentClassName);
|
|
163
|
+
// Content wrapper styles based on animation type
|
|
164
|
+
let contentWrapperStyle;
|
|
165
|
+
if (animation === 'slide') {
|
|
166
|
+
contentWrapperStyle = {
|
|
167
|
+
height: height === 'auto' ? 'auto' : `${height}px`,
|
|
168
|
+
overflow: 'hidden',
|
|
169
|
+
transition: 'height 0.3s ease-in-out',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
else if (animation === 'fade') {
|
|
173
|
+
contentWrapperStyle = {
|
|
174
|
+
opacity: isOpen ? 1 : 0,
|
|
175
|
+
overflow: 'hidden',
|
|
176
|
+
height: isOpen ? 'auto' : 0,
|
|
177
|
+
transition: 'opacity 0.3s ease-in-out',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
contentWrapperStyle = {
|
|
182
|
+
display: isOpen ? 'block' : 'none',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const triggerElement = (jsx("div", { className: combinedTriggerClasses, onClick: handleToggle, onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-expanded": isOpen, "aria-controls": uniqueId, children: trigger }));
|
|
186
|
+
const contentElement = (jsx("div", { style: contentWrapperStyle, children: jsx("div", { ref: contentRef, id: uniqueId, className: combinedContentClasses, "aria-hidden": !isOpen, ...inertProps(!isOpen), children: children }) }));
|
|
187
|
+
// The group's item context stops here: a Collapse nested in this one's
|
|
188
|
+
// content is not an item of this one's group.
|
|
189
|
+
return (jsx(CollapsesItemContext.Provider, { value: null, children: jsx("div", { className: combinedClasses, ...rest, children: position === 'bottom' ? (jsxs(Fragment, { children: [contentElement, triggerElement] })) : (jsxs(Fragment, { children: [triggerElement, contentElement] })) }) }));
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export { Collapse };
|
|
193
|
+
//# sourceMappingURL=Collapse.js.map
|