@allxsmith/bestax-bulma 5.27.0 → 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 +75 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +115 -17516
- package/dist/index.esm.js.map +1 -1
- package/dist/types/elements/Icon.d.ts +10 -7
- package/dist/types/form/DateInputBase.d.ts +3 -4
- package/dist/types/form/DateTimeInputBase.d.ts +3 -4
- package/dist/types/form/File.d.ts +3 -3
- package/dist/types-cjs/elements/Icon.d.ts +10 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +3 -4
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +3 -4
- package/dist/types-cjs/form/File.d.ts +3 -3
- 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/form/_dateinput.scss +37 -6
- package/src/scss/form/_datetimeinput.scss +7 -2
- package/src/scss/form/_picker-popover.scss +1 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import React, { createContext, forwardRef, useContext } from 'react';
|
|
4
|
+
import { classNames, usePrefixedClassNames } from '../helpers/classNames.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
7
|
+
import { omitAttrs } from '../helpers/anchorAttrs.js';
|
|
8
|
+
import { buttonType } from '../helpers/buttonType.js';
|
|
9
|
+
import { isCustomElement } from '../helpers/polymorphic.js';
|
|
10
|
+
|
|
11
|
+
// Context to track MenuList nesting level
|
|
12
|
+
const MenuListLevelContext = createContext(0);
|
|
13
|
+
/**
|
|
14
|
+
* The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
|
|
15
|
+
*
|
|
16
|
+
* @function
|
|
17
|
+
* @param {MenuProps} props - Props for the Menu component.
|
|
18
|
+
* @returns {JSX.Element} The rendered menu.
|
|
19
|
+
* @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}
|
|
20
|
+
*/
|
|
21
|
+
const MenuComponent = ({ className, children, ...props }) => {
|
|
22
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
23
|
+
// Generate Bulma classes with prefix
|
|
24
|
+
const bulmaClasses = usePrefixedClassNames('menu');
|
|
25
|
+
return (jsx("aside", { className: classNames(bulmaClasses, bulmaHelperClasses, className), ...rest, children: children }));
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Bulma Menu label component.
|
|
29
|
+
*
|
|
30
|
+
* @function
|
|
31
|
+
* @param {MenuLabelProps} props - Props for the MenuLabel component.
|
|
32
|
+
* @returns {JSX.Element} The rendered menu label.
|
|
33
|
+
*/
|
|
34
|
+
const MenuLabel = ({ className, children, ...props }) => {
|
|
35
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
36
|
+
return (jsx("p", { className: classNames(usePrefixedClassNames('menu-label'), className, bulmaHelperClasses), ...rest, children: children }));
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* MenuList applies `menu-list` class only at the top level (not for nested lists).
|
|
40
|
+
*
|
|
41
|
+
* @function
|
|
42
|
+
* @param {MenuListProps} props - Props for the MenuList component.
|
|
43
|
+
* @returns {JSX.Element} The rendered menu list.
|
|
44
|
+
*/
|
|
45
|
+
const MenuList = ({ className, children, ...props }) => {
|
|
46
|
+
const level = useContext(MenuListLevelContext);
|
|
47
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
48
|
+
const ulClass = classNames(className, bulmaHelperClasses, {
|
|
49
|
+
[usePrefixedClassNames('menu-list')]: level === 0,
|
|
50
|
+
});
|
|
51
|
+
// Increment level for nested MenuLists
|
|
52
|
+
return (jsx(MenuListLevelContext.Provider, { value: level + 1, children: jsx("ul", { className: ulClass || undefined, ...rest, children: children }) }));
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* `href` alone — annotated so a typo is a compile error rather than a silent
|
|
56
|
+
* no-op, the way both siblings' sets are.
|
|
57
|
+
*/
|
|
58
|
+
const STRIP_FROM_NON_LINK = { href: true };
|
|
59
|
+
/**
|
|
60
|
+
* MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.
|
|
61
|
+
*
|
|
62
|
+
* @function
|
|
63
|
+
* @param {MenuItemProps} props - Props for the MenuItem component.
|
|
64
|
+
* @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.
|
|
65
|
+
* @returns {JSX.Element} The rendered menu item.
|
|
66
|
+
*/
|
|
67
|
+
const MenuItem = forwardRef(function MenuItem(itemProps, ref) {
|
|
68
|
+
const { className, children, active, as: Component = 'a', 'data-testid': testId, style, id, title, role, tabIndex, ...rest } = itemProps;
|
|
69
|
+
const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);
|
|
70
|
+
// `href` reaches an anchor or a custom component (which owns its own prop
|
|
71
|
+
// contract), but not another intrinsic tag — `<span href>` is invalid HTML.
|
|
72
|
+
// The same rule Avatar applies through `isLinkLike` and Button through its
|
|
73
|
+
// intrinsic-only strip. Before #641 `href` was an own prop re-applied only
|
|
74
|
+
// for `as="a"`; deriving it from `as` must not quietly widen that.
|
|
75
|
+
const isLinkLike = Component === 'a' ||
|
|
76
|
+
typeof Component !== 'string' ||
|
|
77
|
+
isCustomElement(Component);
|
|
78
|
+
// `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except
|
|
79
|
+
// `type`; `Level.Item` strips all of them, plus `rel`.
|
|
80
|
+
//
|
|
81
|
+
// Not because nothing else can arrive: `as` here is an open `React.ElementType`,
|
|
82
|
+
// and a non-fresh spread skips excess-property checking, so a typed caller
|
|
83
|
+
// CAN deliver `target` or `download` and they are forwarded. The set stays at
|
|
84
|
+
// `href` because widening it would be wrong for an open `as` — `referrerPolicy`
|
|
85
|
+
// on `as="img"`, `target` on `as="form"` and `media` on `as="source"` are all
|
|
86
|
+
// legal, and a flat strip would delete them. Getting that right needs a
|
|
87
|
+
// per-attribute-per-element rule, which `bestax-migrate` already models in
|
|
88
|
+
// `LINK_ATTR_ELEMENTS`.
|
|
89
|
+
const linkProps = isLinkLike
|
|
90
|
+
? forwarded
|
|
91
|
+
: omitAttrs(forwarded, STRIP_FROM_NON_LINK);
|
|
92
|
+
const itemClass = classNames({ [usePrefixedClassNames('is-active')]: active }, bulmaHelperClasses);
|
|
93
|
+
// Split children into label and nested MenuList(s)
|
|
94
|
+
const labelChildren = [];
|
|
95
|
+
const nestedMenuLists = [];
|
|
96
|
+
React.Children.forEach(children, child => {
|
|
97
|
+
if (React.isValidElement(child) && child.type === MenuList) {
|
|
98
|
+
nestedMenuLists.push(child);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
labelChildren.push(child);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return (jsxs("li", { className: className, "data-testid": testId, style: style, id: id, title: title, role: role, tabIndex: tabIndex, children: [jsx(Component, { ref: ref, className: itemClass || undefined, ...linkProps, ...(Component === 'button'
|
|
105
|
+
? { type: buttonType(linkProps.type) }
|
|
106
|
+
: {}), children: labelChildren }), nestedMenuLists] }));
|
|
107
|
+
});
|
|
108
|
+
MenuItem.displayName = 'MenuItem';
|
|
109
|
+
// Attach static subcomponents
|
|
110
|
+
const Menu = withSubComponents(MenuComponent, {
|
|
111
|
+
Label: MenuLabel,
|
|
112
|
+
List: MenuList,
|
|
113
|
+
Item: MenuItem,
|
|
114
|
+
}, 'Menu');
|
|
115
|
+
|
|
116
|
+
export { Menu, MenuItem, MenuLabel, MenuList };
|
|
117
|
+
//# sourceMappingURL=Menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Menu.js","sources":["../../../src/components/Menu.tsx"],"sourcesContent":["import React, { createContext, forwardRef, useContext } from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport { omitAttrs } from '../helpers/anchorAttrs';\nimport { buttonType } from '../helpers/buttonType';\nimport {\n isCustomElement,\n type PolymorphicComponent,\n} from '../helpers/polymorphic';\n\n// Context to track MenuList nesting level\nconst MenuListLevelContext = createContext(0);\n\n/**\n * Props for the Menu component.\n */\nexport interface MenuProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Menu content (labels, lists, items, etc). */\n children: React.ReactNode;\n}\n\n/**\n * The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.\n *\n * @function\n * @param {MenuProps} props - Props for the Menu component.\n * @returns {JSX.Element} The rendered menu.\n * @see {@link https://bulma.io/documentation/components/menu/ | Bulma Menu documentation}\n */\nconst MenuComponent: React.FC<MenuProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('menu');\n\n return (\n <aside\n className={classNames(bulmaClasses, bulmaHelperClasses, className)}\n {...rest}\n >\n {children}\n </aside>\n );\n};\n\n/**\n * Props for the MenuLabel component.\n */\nexport interface MenuLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLParagraphElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** Label content. */\n children: React.ReactNode;\n}\n\n/**\n * Bulma Menu label component.\n *\n * @function\n * @param {MenuLabelProps} props - Props for the MenuLabel component.\n * @returns {JSX.Element} The rendered menu label.\n */\nexport const MenuLabel: React.FC<MenuLabelProps> = ({\n className,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n return (\n <p\n className={classNames(\n usePrefixedClassNames('menu-label'),\n className,\n bulmaHelperClasses\n )}\n {...rest}\n >\n {children}\n </p>\n );\n};\n\n/**\n * Props for the MenuList component.\n */\nexport interface MenuListProps\n extends\n Omit<React.HTMLAttributes<HTMLUListElement>, keyof BulmaClassesProps>,\n BulmaClassesProps {\n /** Additional CSS classes. */\n className?: string;\n /** List items. */\n children: React.ReactNode;\n}\n\n/**\n * MenuList applies `menu-list` class only at the top level (not for nested lists).\n *\n * @function\n * @param {MenuListProps} props - Props for the MenuList component.\n * @returns {JSX.Element} The rendered menu list.\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n className,\n children,\n ...props\n}) => {\n const level = useContext(MenuListLevelContext);\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n\n const ulClass = classNames(className, bulmaHelperClasses, {\n [usePrefixedClassNames('menu-list')]: level === 0,\n });\n\n // Increment level for nested MenuLists\n return (\n <MenuListLevelContext.Provider value={level + 1}>\n <ul className={ulClass || undefined} {...rest}>\n {children}\n </ul>\n </MenuListLevelContext.Provider>\n );\n};\n\n/**\n * The MenuItem component's own props.\n *\n * A menu item is two elements: a wrapping `<li>` and, inside it, the element\n * `as` names. The props here are the ones the `<li>` consumes; everything else\n * follows `as` onto the inner element.\n */\nexport interface MenuItemOwnProps extends BulmaClassesProps {\n /** Additional CSS classes for the wrapping `<li>`. */\n className?: string;\n /** Item content and optional nested MenuList. */\n children: React.ReactNode;\n /** Highlight item as active. */\n active?: boolean;\n /** Inline styles for the wrapping `<li>`. */\n style?: React.CSSProperties;\n /** `id` for the wrapping `<li>`. */\n id?: string;\n /** `title` for the wrapping `<li>`. */\n title?: string;\n /** ARIA role for the wrapping `<li>`. */\n role?: React.AriaRole;\n /** Tab index for the wrapping `<li>`. */\n tabIndex?: number;\n /** Test id for the wrapping `<li>`. */\n 'data-testid'?: string;\n}\n\n/**\n * Props for the MenuItem component. Everything the `<li>` does not consume\n * follows `as` onto the inner element: with the default `'a'` that means `href`\n * and the other anchor attributes, and with `as={Link}` it means that\n * component's own props.\n *\n * @extraProp {PolymorphicRef<React.ElementType>} [ref] - Ref forwarded to the inner element `as` renders, not the wrapping `<li>`, typed from `as`: the DOM node for an intrinsic tag, or whatever handle a custom component exposes.\n */\nexport type MenuItemProps<T extends React.ElementType = 'a'> =\n MenuItemOwnProps &\n Omit<React.ComponentPropsWithoutRef<T>, keyof MenuItemOwnProps | 'as'> & {\n /**\n * Custom link component (e.g. `Link` from router).\n *\n * `'button'` renders `type=\"button\"` unless you pass `type=\"submit\"` or `type=\"reset\"`, so\n * an item inside a form does not submit it.\n */\n as?: T;\n };\n\n/**\n * `href` alone — annotated so a typo is a compile error rather than a silent\n * no-op, the way both siblings' sets are.\n */\nconst STRIP_FROM_NON_LINK: Readonly<Record<'href', true>> = { href: true };\n\n/**\n * The shape the implementation destructures. The public contract is the generic\n * `MenuItemProps<T>` above — the body cannot see through `T`.\n */\ntype MenuItemImplProps = MenuItemOwnProps & {\n as?: React.ElementType;\n type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];\n};\n\n/**\n * MenuItem supports `as` prop for custom link components, e.g., react-router-dom Link.\n *\n * @function\n * @param {MenuItemProps} props - Props for the MenuItem component.\n * @param {React.Ref} ref - Forwarded ref to the inner element `as` renders.\n * @returns {JSX.Element} The rendered menu item.\n */\nexport const MenuItem = forwardRef(function MenuItem(\n itemProps: MenuItemProps,\n ref: React.Ref<HTMLElement>\n) {\n const {\n className,\n children,\n active,\n as: Component = 'a',\n 'data-testid': testId,\n style,\n id,\n title,\n role,\n tabIndex,\n ...rest\n } = itemProps as MenuItemImplProps;\n const { bulmaHelperClasses, rest: forwarded } = useBulmaClasses(rest);\n // `href` reaches an anchor or a custom component (which owns its own prop\n // contract), but not another intrinsic tag — `<span href>` is invalid HTML.\n // The same rule Avatar applies through `isLinkLike` and Button through its\n // intrinsic-only strip. Before #641 `href` was an own prop re-applied only\n // for `as=\"a\"`; deriving it from `as` must not quietly widen that.\n const isLinkLike =\n Component === 'a' ||\n typeof Component !== 'string' ||\n isCustomElement(Component);\n // `href` ALONE. `Dropdown.Item` strips every anchor-only attribute except\n // `type`; `Level.Item` strips all of them, plus `rel`.\n //\n // Not because nothing else can arrive: `as` here is an open `React.ElementType`,\n // and a non-fresh spread skips excess-property checking, so a typed caller\n // CAN deliver `target` or `download` and they are forwarded. The set stays at\n // `href` because widening it would be wrong for an open `as` — `referrerPolicy`\n // on `as=\"img\"`, `target` on `as=\"form\"` and `media` on `as=\"source\"` are all\n // legal, and a flat strip would delete them. Getting that right needs a\n // per-attribute-per-element rule, which `bestax-migrate` already models in\n // `LINK_ATTR_ELEMENTS`.\n const linkProps = isLinkLike\n ? forwarded\n : omitAttrs(forwarded, STRIP_FROM_NON_LINK);\n const itemClass = classNames(\n { [usePrefixedClassNames('is-active')]: active },\n bulmaHelperClasses\n );\n\n // Split children into label and nested MenuList(s)\n const labelChildren: React.ReactNode[] = [];\n const nestedMenuLists: React.ReactNode[] = [];\n React.Children.forEach(children, child => {\n if (React.isValidElement(child) && child.type === MenuList) {\n nestedMenuLists.push(child);\n } else {\n labelChildren.push(child);\n }\n });\n\n return (\n <li\n className={className}\n data-testid={testId}\n style={style}\n id={id}\n title={title}\n role={role}\n tabIndex={tabIndex}\n >\n <Component\n ref={ref}\n className={itemClass || undefined}\n {...linkProps}\n // A menu item inside a form must not submit it, and `<button>` defaults\n // to type=\"submit\". A sidebar menu sitting in a form is ordinary. A\n // caller's `submit` or `reset` still wins. Anything else becomes\n // `button`, since HTML reads a value it does not define for a button as\n // submit too; `buttonType` says more.\n //\n // After `linkProps`, reading through it rather than before it: React\n // treats `type={undefined}` as \"remove the attribute\", and a spread\n // carrying the key with no value is how that arrives, so a default\n // spread first would be erased by it. `Dropdown.Item` and `Avatar`\n // apply their button type after the spread too.\n {...(Component === 'button'\n ? { type: buttonType(linkProps.type) }\n : {})}\n >\n {labelChildren}\n </Component>\n {nestedMenuLists}\n </li>\n );\n}) as PolymorphicComponent<MenuItemOwnProps, 'a'>;\n\nMenuItem.displayName = 'MenuItem';\n\n// Attach static subcomponents\nexport const Menu = withSubComponents(\n MenuComponent,\n {\n Label: MenuLabel,\n List: MenuList,\n Item: MenuItem,\n },\n 'Menu'\n);\n\nexport default Menu;\n"],"names":["_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { classNames, usePrefixedClassNames, prefixedClassNames } from '../helpers/classNames.js';
|
|
4
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { useConfig } from '../helpers/Config.js';
|
|
7
|
+
|
|
8
|
+
const validMessageSizes = ['small', 'medium', 'large'];
|
|
9
|
+
/**
|
|
10
|
+
* The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
|
|
11
|
+
*
|
|
12
|
+
* @function
|
|
13
|
+
* @param {MessageProps} props - Props for the Message component.
|
|
14
|
+
* @returns {JSX.Element} The rendered message.
|
|
15
|
+
* @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}
|
|
16
|
+
*/
|
|
17
|
+
const MessageComponent = ({ className, title, textColor, color, bgColor, size, onClose, children, ...props }) => {
|
|
18
|
+
const { classPrefix } = useConfig();
|
|
19
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
20
|
+
color: textColor,
|
|
21
|
+
backgroundColor: bgColor,
|
|
22
|
+
...props,
|
|
23
|
+
});
|
|
24
|
+
// Generate Bulma classes with prefix
|
|
25
|
+
const bulmaClasses = usePrefixedClassNames('message', {
|
|
26
|
+
[`is-${color}`]: color,
|
|
27
|
+
[`is-${size}`]: size && validMessageSizes.includes(size),
|
|
28
|
+
});
|
|
29
|
+
const deleteClass = usePrefixedClassNames('delete');
|
|
30
|
+
const messageClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
31
|
+
return (jsxs("article", { className: messageClasses, ...rest, "data-testid": "message", children: [(title || onClose) && (jsxs("div", { className: prefixedClassNames(classPrefix, 'message-header'), children: [title && jsx("span", { children: title }), onClose && (jsx("button", { className: deleteClass, "aria-label": "delete", onClick: onClose, type: "button", "data-testid": "message-close" }))] })), children && (jsx("div", { className: prefixedClassNames(classPrefix, 'message-body'), "data-testid": "message-body", children: children }))] }));
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Message header compound component. Renders a `.message-header` element.
|
|
35
|
+
*
|
|
36
|
+
* @function
|
|
37
|
+
* @param {MessageHeaderProps} props - Props for the MessageHeader component.
|
|
38
|
+
* @returns {JSX.Element} The rendered message header.
|
|
39
|
+
*/
|
|
40
|
+
const MessageHeader = ({ className, children, ...props }) => (jsx("div", { className: classNames(usePrefixedClassNames('message-header'), className), ...props, children: children }));
|
|
41
|
+
/**
|
|
42
|
+
* Message body compound component. Renders a `.message-body` element.
|
|
43
|
+
*
|
|
44
|
+
* @function
|
|
45
|
+
* @param {MessageBodyProps} props - Props for the MessageBody component.
|
|
46
|
+
* @returns {JSX.Element} The rendered message body.
|
|
47
|
+
*/
|
|
48
|
+
const MessageBody = ({ className, children, ...props }) => (jsx("div", { className: classNames(usePrefixedClassNames('message-body'), className), ...props, children: children }));
|
|
49
|
+
// Attach compound components
|
|
50
|
+
const Message = withSubComponents(MessageComponent, {
|
|
51
|
+
Header: MessageHeader,
|
|
52
|
+
Body: MessageBody,
|
|
53
|
+
}, 'Message');
|
|
54
|
+
|
|
55
|
+
export { Message };
|
|
56
|
+
//# sourceMappingURL=Message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Message.js","sources":["../../../src/components/Message.tsx"],"sourcesContent":["import React from 'react';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { withSubComponents } from '../helpers/withSubComponents';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useConfig } from '../helpers/Config';\n\nconst validMessageSizes = ['small', 'medium', 'large'] as const;\n/**\n * Valid size values for the Message component (Bulma message sizes).\n */\nexport type MessageSize = (typeof validMessageSizes)[number];\n\n/**\n * Props for the Message component.\n */\nexport interface MessageProps\n extends\n Omit<React.HTMLAttributes<HTMLElement>, 'title'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor' | 'size'> {\n /** Additional CSS classes. */\n className?: string;\n /** Title string/node (renders header section). */\n title?: React.ReactNode;\n /** Text color for the message (Bulma helper). */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Bulma color modifier for the message. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Background color for the message (Bulma helper). */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Size of the message. */\n size?: MessageSize;\n /** Callback for the close (\"X\") button. */\n onClose?: () => void;\n /** Body content for the message. */\n children?: React.ReactNode;\n}\n\n/**\n * The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.\n *\n * @function\n * @param {MessageProps} props - Props for the Message component.\n * @returns {JSX.Element} The rendered message.\n * @see {@link https://bulma.io/documentation/components/message/ | Bulma Message documentation}\n */\nconst MessageComponent: React.FC<MessageProps> = ({\n className,\n title,\n textColor,\n color,\n bgColor,\n size,\n onClose,\n children,\n ...props\n}) => {\n const { classPrefix } = useConfig();\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n // Generate Bulma classes with prefix\n const bulmaClasses = usePrefixedClassNames('message', {\n [`is-${color}`]: color,\n [`is-${size}`]: size && validMessageSizes.includes(size),\n });\n const deleteClass = usePrefixedClassNames('delete');\n\n const messageClasses = classNames(\n bulmaClasses,\n bulmaHelperClasses,\n className\n );\n\n return (\n <article className={messageClasses} {...rest} data-testid=\"message\">\n {(title || onClose) && (\n <div className={prefixedClassNames(classPrefix, 'message-header')}>\n {title && <span>{title}</span>}\n {onClose && (\n <button\n className={deleteClass}\n aria-label=\"delete\"\n onClick={onClose}\n type=\"button\"\n data-testid=\"message-close\"\n />\n )}\n </div>\n )}\n {children && (\n <div\n className={prefixedClassNames(classPrefix, 'message-body')}\n data-testid=\"message-body\"\n >\n {children}\n </div>\n )}\n </article>\n );\n};\n\n// Compound components for flexible composition\n\n/**\n * Props for the Message.Header compound component.\n */\nexport interface MessageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Header content (title, close button, etc.). */\n children?: React.ReactNode;\n}\n\n/**\n * Props for the Message.Body compound component.\n */\nexport interface MessageBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS classes. */\n className?: string;\n /** Message body content. */\n children?: React.ReactNode;\n}\n\n/**\n * Message header compound component. Renders a `.message-header` element.\n *\n * @function\n * @param {MessageHeaderProps} props - Props for the MessageHeader component.\n * @returns {JSX.Element} The rendered message header.\n */\nconst MessageHeader: React.FC<MessageHeaderProps> = ({\n className,\n children,\n ...props\n}) => (\n <div\n className={classNames(usePrefixedClassNames('message-header'), className)}\n {...props}\n >\n {children}\n </div>\n);\n\n/**\n * Message body compound component. Renders a `.message-body` element.\n *\n * @function\n * @param {MessageBodyProps} props - Props for the MessageBody component.\n * @returns {JSX.Element} The rendered message body.\n */\nconst MessageBody: React.FC<MessageBodyProps> = ({\n className,\n children,\n ...props\n}) => (\n <div\n className={classNames(usePrefixedClassNames('message-body'), className)}\n {...props}\n >\n {children}\n </div>\n);\n\n// Attach compound components\nexport const Message = withSubComponents(\n MessageComponent,\n {\n Header: MessageHeader,\n Body: MessageBody,\n },\n 'Message'\n);\n\nexport default Message;\n"],"names":["_jsxs","_jsx"],"mappings":";;;;;;;AAcA,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU;AA+B/D;;;;;;;AAOG;AACH,MAAM,gBAAgB,GAA2B,CAAC,EAChD,SAAS,EACT,KAAK,EACL,SAAS,EACT,KAAK,EACL,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE;AACnC,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;;AAGF,IAAA,MAAM,YAAY,GAAG,qBAAqB,CAAC,SAAS,EAAE;AACpD,QAAA,CAAC,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,KAAK;AACtB,QAAA,CAAC,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,GAAG,IAAI,IAAI,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;AACzD,KAAA,CAAC;AACF,IAAA,MAAM,WAAW,GAAG,qBAAqB,CAAC,QAAQ,CAAC;IAEnD,MAAM,cAAc,GAAG,UAAU,CAC/B,YAAY,EACZ,kBAAkB,EAClB,SAAS,CACV;AAED,IAAA,QACEA,IAAA,CAAA,SAAA,EAAA,EAAS,SAAS,EAAE,cAAc,KAAM,IAAI,EAAA,aAAA,EAAc,SAAS,EAAA,QAAA,EAAA,CAChE,CAAC,KAAK,IAAI,OAAO,MAChBA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,kBAAkB,CAAC,WAAW,EAAE,gBAAgB,CAAC,EAAA,QAAA,EAAA,CAC9D,KAAK,IAAIC,GAAA,CAAA,MAAA,EAAA,EAAA,QAAA,EAAO,KAAK,EAAA,CAAQ,EAC7B,OAAO,KACNA,GAAA,CAAA,QAAA,EAAA,EACE,SAAS,EAAE,WAAW,gBACX,QAAQ,EACnB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,QAAQ,iBACD,eAAe,EAAA,CAC3B,CACH,CAAA,EAAA,CACG,CACP,EACA,QAAQ,KACPA,aACE,SAAS,EAAE,kBAAkB,CAAC,WAAW,EAAE,cAAc,CAAC,EAAA,aAAA,EAC9C,cAAc,EAAA,QAAA,EAEzB,QAAQ,GACL,CACP,CAAA,EAAA,CACO;AAEd,CAAC;AAwBD;;;;;;AAMG;AACH,MAAM,aAAa,GAAiC,CAAC,EACnD,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,MACCA,GAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,UAAU,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC,KACrE,KAAK,EAAA,QAAA,EAER,QAAQ,EAAA,CACL,CACP;AAED;;;;;;AAMG;AACH,MAAM,WAAW,GAA+B,CAAC,EAC/C,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,MACCA,GAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,UAAU,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,KACnE,KAAK,EAAA,QAAA,EAER,QAAQ,EAAA,CACL,CACP;AAED;AACO,MAAM,OAAO,GAAG,iBAAiB,CACtC,gBAAgB,EAChB;AACE,IAAA,MAAM,EAAE,aAAa;AACrB,IAAA,IAAI,EAAE,WAAW;CAClB,EACD,SAAS;;"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import React, { forwardRef, useRef, useCallback, useId, useEffect } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { usePrefixedClassNames, classNames, prefixedClassNames } from '../helpers/classNames.js';
|
|
6
|
+
import { withSubComponents } from '../helpers/withSubComponents.js';
|
|
7
|
+
import { buttonType } from '../helpers/buttonType.js';
|
|
8
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
9
|
+
import { useConfig } from '../helpers/Config.js';
|
|
10
|
+
import { useScrollLock } from '../helpers/scrollLock.js';
|
|
11
|
+
import { getDeepestActiveElement, getActiveElementInTree } from '../helpers/shadowDom.js';
|
|
12
|
+
import { resolvePortalContainer } from '../helpers/portal.js';
|
|
13
|
+
import { useIsHydrated } from '../helpers/useIsHydrated.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Every currently active modal, in the order they opened. Only the last entry
|
|
17
|
+
* — the topmost modal — reacts to Escape and Tab, so one keypress can't
|
|
18
|
+
* dismiss a whole stack (e.g. a `Dialog` opened on top of a `Modal`).
|
|
19
|
+
*/
|
|
20
|
+
const activeModalStack = [];
|
|
21
|
+
/**
|
|
22
|
+
* Controls the modal can hand focus to. Disabled and hidden controls are
|
|
23
|
+
* excluded because `focus()` on them is a no-op, which would leave focus
|
|
24
|
+
* outside the modal.
|
|
25
|
+
*/
|
|
26
|
+
const FOCUSABLE_SELECTOR = 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex]';
|
|
27
|
+
/**
|
|
28
|
+
* The modal's tab stops, in document order. The selector alone is not the
|
|
29
|
+
* tabbable set — `[href]` and `button` match regardless of `tabindex`, so an
|
|
30
|
+
* `<a href tabIndex={-1}>` would otherwise be treated as a tab stop and let
|
|
31
|
+
* Tab escape the modal when it sorts last. `el.tabIndex` is the browser's own
|
|
32
|
+
* resolved value, so filtering on it drops every negative index (not just
|
|
33
|
+
* `-1`) without a second selector to keep in sync.
|
|
34
|
+
*/
|
|
35
|
+
const getTabbable = (node) => Array.from(node.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => el.tabIndex >= 0);
|
|
36
|
+
/**
|
|
37
|
+
* Modal.Background - Renders the modal background overlay.
|
|
38
|
+
*
|
|
39
|
+
* @function
|
|
40
|
+
* @param {ModalBackgroundProps} props - Component props.
|
|
41
|
+
* @returns {JSX.Element} Modal background element.
|
|
42
|
+
*/
|
|
43
|
+
const ModalBackground = ({ className, ...props }) => {
|
|
44
|
+
const classes = classNames(usePrefixedClassNames('modal-background'), className);
|
|
45
|
+
return jsx("div", { className: classes, ...props });
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Modal.Content - Renders modal content wrapper for custom content.
|
|
49
|
+
*
|
|
50
|
+
* @function
|
|
51
|
+
* @param {ModalContentProps} props - Component props.
|
|
52
|
+
* @returns {JSX.Element} Modal content element.
|
|
53
|
+
*/
|
|
54
|
+
const ModalContent = ({ className, ...props }) => {
|
|
55
|
+
const classes = classNames(usePrefixedClassNames('modal-content'), className);
|
|
56
|
+
return jsx("div", { className: classes, ...props });
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Modal.Card.Head - Renders modal card header section.
|
|
60
|
+
*
|
|
61
|
+
* @function
|
|
62
|
+
* @param {ModalCardHeadProps} props - Component props.
|
|
63
|
+
* @returns {JSX.Element} Modal card header element.
|
|
64
|
+
*/
|
|
65
|
+
const ModalCardHead = ({ className, ...props }) => {
|
|
66
|
+
const classes = classNames(usePrefixedClassNames('modal-card-head'), className);
|
|
67
|
+
return jsx("header", { className: classes, ...props });
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Modal.Card.Title - Renders modal card title.
|
|
71
|
+
*
|
|
72
|
+
* @function
|
|
73
|
+
* @param {ModalCardTitleProps} props - Component props.
|
|
74
|
+
* @returns {JSX.Element} Modal card title element.
|
|
75
|
+
*/
|
|
76
|
+
const ModalCardTitle = ({ className, ...props }) => {
|
|
77
|
+
const classes = classNames(usePrefixedClassNames('modal-card-title'), className);
|
|
78
|
+
return jsx("p", { className: classes, ...props });
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Modal.Card.Body - Renders modal card body section.
|
|
82
|
+
*
|
|
83
|
+
* @function
|
|
84
|
+
* @param {ModalCardBodyProps} props - Component props.
|
|
85
|
+
* @returns {JSX.Element} Modal card body element.
|
|
86
|
+
*/
|
|
87
|
+
const ModalCardBody = ({ className, ...props }) => {
|
|
88
|
+
const classes = classNames(usePrefixedClassNames('modal-card-body'), className);
|
|
89
|
+
return jsx("section", { className: classes, ...props });
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Modal.Card.Foot - Renders modal card footer section.
|
|
93
|
+
*
|
|
94
|
+
* @function
|
|
95
|
+
* @param {ModalCardFootProps} props - Component props.
|
|
96
|
+
* @returns {JSX.Element} Modal card footer element.
|
|
97
|
+
*/
|
|
98
|
+
const ModalCardFoot = ({ className, ...props }) => {
|
|
99
|
+
const classes = classNames(usePrefixedClassNames('modal-card-foot'), className);
|
|
100
|
+
return jsx("footer", { className: classes, ...props });
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Modal.Card - Renders modal card wrapper with compound components.
|
|
104
|
+
* Use with Modal.Card.Head, Modal.Card.Title, Modal.Card.Body, and Modal.Card.Foot.
|
|
105
|
+
*
|
|
106
|
+
* @function
|
|
107
|
+
* @param {ModalCardProps} props - Component props.
|
|
108
|
+
* @returns {JSX.Element} Modal card element.
|
|
109
|
+
*/
|
|
110
|
+
const ModalCardComponent = ({ className, ...props }) => {
|
|
111
|
+
const classes = classNames(usePrefixedClassNames('modal-card'), className);
|
|
112
|
+
return jsx("div", { className: classes, ...props });
|
|
113
|
+
};
|
|
114
|
+
const ModalCard = withSubComponents(ModalCardComponent, {
|
|
115
|
+
Head: ModalCardHead,
|
|
116
|
+
Title: ModalCardTitle,
|
|
117
|
+
Body: ModalCardBody,
|
|
118
|
+
Foot: ModalCardFoot,
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* Modal.Close - Renders modal close button with two variant styles.
|
|
122
|
+
*
|
|
123
|
+
* @function
|
|
124
|
+
* @param {ModalCloseProps} props - Component props.
|
|
125
|
+
* @returns {JSX.Element} Close button element.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* Supports two variants:
|
|
129
|
+
* - 'delete' (default): For use in modal card headers, renders with 'delete' class
|
|
130
|
+
* - 'floating': For floating overlay close button, renders with 'modal-close' class
|
|
131
|
+
*
|
|
132
|
+
* The size prop only applies to the 'floating' variant.
|
|
133
|
+
*/
|
|
134
|
+
const ModalClose = ({ className, size = 'large', variant = 'delete', ...props }) => {
|
|
135
|
+
const classes = classNames(usePrefixedClassNames(variant === 'delete' ? 'delete' : 'modal-close', variant === 'floating' && size && { [`is-${size}`]: true }), className);
|
|
136
|
+
return (jsx("button", { className: classes, ...props, "aria-label": props['aria-label'] || 'close', type: buttonType(props.type) }));
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* The `Modal` component provides an empty, accessible overlay for arbitrary content — for a ready-made confirm or alert, reach for `Dialog` instead.
|
|
140
|
+
*
|
|
141
|
+
* @function
|
|
142
|
+
* @param {ModalProps} props - Props for the Modal component.
|
|
143
|
+
* @returns {JSX.Element} The rendered modal.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Legacy API
|
|
147
|
+
* <Modal active={isOpen} onClose={handleClose} modalCardTitle="Title">
|
|
148
|
+
* Content
|
|
149
|
+
* </Modal>
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // Compound Component API
|
|
153
|
+
* <Modal isActive={isOpen}>
|
|
154
|
+
* <Modal.Background onClick={handleClose} />
|
|
155
|
+
* <Modal.Card>
|
|
156
|
+
* <Modal.Card.Head>
|
|
157
|
+
* <Modal.Card.Title>Title</Modal.Card.Title>
|
|
158
|
+
* <Modal.Close onClick={handleClose} />
|
|
159
|
+
* </Modal.Card.Head>
|
|
160
|
+
* <Modal.Card.Body>Content</Modal.Card.Body>
|
|
161
|
+
* </Modal.Card>
|
|
162
|
+
* </Modal>
|
|
163
|
+
*
|
|
164
|
+
* @see {@link https://bulma.io/documentation/components/modal/ | Bulma Modal documentation}
|
|
165
|
+
*/
|
|
166
|
+
const ModalRoot = forwardRef(function ModalRoot({ active, isActive, onClose, className, textColor, bgColor, modalCardTitle, modalCardFoot, type, children, closeOnEscape = true, lockScroll = true, portal = false, role, 'aria-modal': ariaModalProp, ...props }, ref) {
|
|
167
|
+
const { classPrefix } = useConfig();
|
|
168
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
169
|
+
color: textColor,
|
|
170
|
+
backgroundColor: bgColor,
|
|
171
|
+
...props,
|
|
172
|
+
});
|
|
173
|
+
// Support both active and isActive props
|
|
174
|
+
const isModalActive = active ?? isActive ?? false;
|
|
175
|
+
const modalRootRef = useRef(null);
|
|
176
|
+
// Cleanup returned by a consumer's callback ref on attach, held until detach.
|
|
177
|
+
const consumerCleanupRef = useRef(null);
|
|
178
|
+
// The forwarded ref and the internal one must both see the node: focus
|
|
179
|
+
// management, the scroll lock and the topmost-modal check all read
|
|
180
|
+
// `modalRootRef`, while consumers expect their own ref to resolve.
|
|
181
|
+
const combinedRef = useCallback((node) => {
|
|
182
|
+
modalRootRef.current =
|
|
183
|
+
node;
|
|
184
|
+
if (typeof ref === 'function') {
|
|
185
|
+
// React 19 lets a callback ref return a cleanup function and detaches
|
|
186
|
+
// by running it instead of calling the ref with `null`; React 18
|
|
187
|
+
// discards the return value entirely. Returning it from here would be
|
|
188
|
+
// a React-19-only contract, so instead we hold the cleanup and run it
|
|
189
|
+
// ourselves on detach — a consumer's cleanup ref then behaves the same
|
|
190
|
+
// on both majors of the CI matrix. Same shape as `Dropdown`.
|
|
191
|
+
if (node === null) {
|
|
192
|
+
const consumerCleanup = consumerCleanupRef.current;
|
|
193
|
+
consumerCleanupRef.current = null;
|
|
194
|
+
if (consumerCleanup) {
|
|
195
|
+
consumerCleanup();
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
ref(null);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const cleanup = ref(node);
|
|
203
|
+
consumerCleanupRef.current =
|
|
204
|
+
typeof cleanup === 'function' ? cleanup : null;
|
|
205
|
+
}
|
|
206
|
+
else if (ref) {
|
|
207
|
+
ref.current = node;
|
|
208
|
+
}
|
|
209
|
+
}, [ref]);
|
|
210
|
+
const previouslyFocusedRef = useRef(null);
|
|
211
|
+
const stackTokenRef = useRef({});
|
|
212
|
+
const generatedTitleId = useId();
|
|
213
|
+
// A portal has no server-rendered counterpart, so the server render and the
|
|
214
|
+
// hydrating render have to stay inline and only move into the portal once
|
|
215
|
+
// we're past hydration.
|
|
216
|
+
const onClient = useIsHydrated();
|
|
217
|
+
// Moving into the portal remounts the modal's subtree, so any effect holding
|
|
218
|
+
// a DOM node from before the move is holding a detached one. Effects that
|
|
219
|
+
// touch the modal's nodes key on this so they re-run against the new tree.
|
|
220
|
+
const isPortaled = Boolean(portal) && onClient;
|
|
221
|
+
useScrollLock(isModalActive && lockScroll);
|
|
222
|
+
// Record open order so keyboard handling only applies to the topmost modal.
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (!isModalActive)
|
|
225
|
+
return undefined;
|
|
226
|
+
const token = stackTokenRef.current;
|
|
227
|
+
activeModalStack.push(token);
|
|
228
|
+
return () => {
|
|
229
|
+
const index = activeModalStack.indexOf(token);
|
|
230
|
+
if (index !== -1)
|
|
231
|
+
activeModalStack.splice(index, 1);
|
|
232
|
+
};
|
|
233
|
+
}, [isModalActive]);
|
|
234
|
+
// Escape closes; Tab cycles inside the modal. Both only for the topmost one.
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (!isModalActive)
|
|
237
|
+
return undefined;
|
|
238
|
+
const handleKeyDown = (e) => {
|
|
239
|
+
const node = modalRootRef.current;
|
|
240
|
+
if (activeModalStack[activeModalStack.length - 1] !== stackTokenRef.current) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (e.key === 'Escape') {
|
|
244
|
+
if (closeOnEscape)
|
|
245
|
+
onClose?.();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (e.key !== 'Tab' || !node)
|
|
249
|
+
return;
|
|
250
|
+
// Keep Tab within the modal — `aria-modal` hides the rest of the page
|
|
251
|
+
// from assistive technology, so the keyboard order has to agree.
|
|
252
|
+
const focusable = getTabbable(node);
|
|
253
|
+
const activeElement = getActiveElementInTree(node);
|
|
254
|
+
if (focusable.length === 0) {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
node.focus();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const first = focusable[0];
|
|
260
|
+
const last = focusable[focusable.length - 1];
|
|
261
|
+
const outside = !node.contains(activeElement);
|
|
262
|
+
if (e.shiftKey) {
|
|
263
|
+
if (outside || activeElement === first) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
last.focus();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (outside || activeElement === last) {
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
first.focus();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
274
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
275
|
+
}, [isModalActive, closeOnEscape, onClose]);
|
|
276
|
+
// Move focus into the modal on open, restore it on close
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
if (!isModalActive)
|
|
279
|
+
return undefined;
|
|
280
|
+
// The element that had focus, wherever it sits: a portaled modal renders
|
|
281
|
+
// under `document.body` while its opener can be inside a shadow root.
|
|
282
|
+
previouslyFocusedRef.current =
|
|
283
|
+
getDeepestActiveElement();
|
|
284
|
+
const node = modalRootRef.current;
|
|
285
|
+
const focusable = node ? getTabbable(node)[0] : undefined;
|
|
286
|
+
(focusable ?? node)?.focus();
|
|
287
|
+
return () => {
|
|
288
|
+
// Only hand focus back if this modal still owns it: closing a background
|
|
289
|
+
// modal must not pull focus out of one that is still open on top. A
|
|
290
|
+
// removed subtree leaves focus on <body>, which still counts as ours.
|
|
291
|
+
const activeElement = getActiveElementInTree(node);
|
|
292
|
+
if (activeElement &&
|
|
293
|
+
activeElement !== document.body &&
|
|
294
|
+
!node?.contains(activeElement)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
previouslyFocusedRef.current?.focus?.();
|
|
298
|
+
};
|
|
299
|
+
// `isPortaled` flips once when a hydrated portal modal moves out of the
|
|
300
|
+
// inline tree; re-running rebinds focus onto the remounted nodes. The
|
|
301
|
+
// cleanup above restores focus to the pre-open element first (the detached
|
|
302
|
+
// subtree has left focus on <body>), so the re-run re-records the same
|
|
303
|
+
// element and restore-on-close still lands in the right place.
|
|
304
|
+
}, [isModalActive, isPortaled]);
|
|
305
|
+
// Check if children contain compound components
|
|
306
|
+
const hasCompoundComponents = React.Children.toArray(children).some(child => React.isValidElement(child) &&
|
|
307
|
+
(child.type === ModalBackground ||
|
|
308
|
+
child.type === ModalContent ||
|
|
309
|
+
child.type === ModalCard ||
|
|
310
|
+
child.type === ModalClose));
|
|
311
|
+
// Generate Bulma classes with prefix
|
|
312
|
+
const bulmaClasses = usePrefixedClassNames('modal', {
|
|
313
|
+
'is-active': isModalActive,
|
|
314
|
+
});
|
|
315
|
+
const deleteClass = usePrefixedClassNames('delete');
|
|
316
|
+
const modalClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
317
|
+
const resolvedRole = role ?? (isModalActive ? 'dialog' : undefined);
|
|
318
|
+
const resolvedAriaModal = ariaModalProp ??
|
|
319
|
+
(isModalActive && resolvedRole !== 'presentation' ? 'true' : undefined);
|
|
320
|
+
// A dialog needs an accessible name: the legacy card title is wired up
|
|
321
|
+
// automatically, compound users pass their own aria-label/aria-labelledby
|
|
322
|
+
// (both arrive in `rest`, which is spread last and so wins).
|
|
323
|
+
const titleId = modalCardTitle ? generatedTitleId : undefined;
|
|
324
|
+
let modalElement;
|
|
325
|
+
// If using compound components, render children as-is
|
|
326
|
+
if (hasCompoundComponents) {
|
|
327
|
+
modalElement = (jsx("div", { className: modalClasses, ref: combinedRef, role: resolvedRole, "aria-modal": resolvedAriaModal, tabIndex: -1, ...rest, "data-testid": "modal", children: children }));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// Legacy API: EXPLICIT type wins; fallback to auto detection if not provided
|
|
331
|
+
let isModalCard;
|
|
332
|
+
if (type === 'card')
|
|
333
|
+
isModalCard = true;
|
|
334
|
+
else if (type === 'content')
|
|
335
|
+
isModalCard = false;
|
|
336
|
+
else
|
|
337
|
+
isModalCard = !!modalCardTitle || !!modalCardFoot;
|
|
338
|
+
modalElement = (jsxs("div", { className: modalClasses, ref: combinedRef, role: resolvedRole, "aria-modal": resolvedAriaModal, "aria-labelledby": titleId, tabIndex: -1, ...rest, "data-testid": "modal", children: [jsx("div", { className: prefixedClassNames(classPrefix, 'modal-background'), onClick: onClose, "data-testid": "modal-background" }), isModalCard ? (jsxs("div", { className: prefixedClassNames(classPrefix, 'modal-card'), children: [modalCardTitle && (jsxs("header", { className: prefixedClassNames(classPrefix, 'modal-card-head'), children: [jsx("p", { id: titleId, className: prefixedClassNames(classPrefix, 'modal-card-title'), children: modalCardTitle }), onClose && (jsx("button", { className: deleteClass, "aria-label": "close", onClick: onClose, type: "button", "data-testid": "modal-close" }))] })), jsx("section", { className: prefixedClassNames(classPrefix, 'modal-card-body'), "data-testid": "modal-body", children: children }), modalCardFoot && (jsx("footer", { className: prefixedClassNames(classPrefix, 'modal-card-foot'), children: modalCardFoot }))] })) : (jsx("div", { className: prefixedClassNames(classPrefix, 'modal-content'), "data-testid": "modal-content", children: children })), (!isModalCard || (!modalCardTitle && onClose)) && onClose && (jsx("button", { className: prefixedClassNames(classPrefix, 'modal-close', 'is-large'), "aria-label": "close", onClick: onClose, type: "button", "data-testid": "modal-close-float" }))] }));
|
|
339
|
+
}
|
|
340
|
+
if (isPortaled) {
|
|
341
|
+
const target = resolvePortalContainer(typeof portal === 'boolean' ? undefined : portal);
|
|
342
|
+
return createPortal(modalElement, target);
|
|
343
|
+
}
|
|
344
|
+
return modalElement;
|
|
345
|
+
});
|
|
346
|
+
const Modal = withSubComponents(ModalRoot, {
|
|
347
|
+
Background: ModalBackground,
|
|
348
|
+
Content: ModalContent,
|
|
349
|
+
Card: ModalCard,
|
|
350
|
+
Close: ModalClose,
|
|
351
|
+
}, 'Modal');
|
|
352
|
+
|
|
353
|
+
export { Modal };
|
|
354
|
+
//# sourceMappingURL=Modal.js.map
|