@box/blueprint-web 12.106.2 → 12.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib-esm/breadcrumb/breadcrumb.js +7 -7
- package/dist/lib-esm/index.js +1 -0
- package/dist/lib-esm/modal/alert-modal.d.ts +20 -0
- package/dist/lib-esm/modal/alert-modal.js +89 -0
- package/dist/lib-esm/modal/alert-modal.types.d.ts +15 -0
- package/dist/lib-esm/modal/index.d.ts +2 -0
- package/package.json +3 -3
|
@@ -33,11 +33,11 @@ const Breadcrumb = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
33
33
|
const breakpoint = useBreakpoint();
|
|
34
34
|
const isMobile = isResponsiveEnabled || breakpoint <= Breakpoint.Medium;
|
|
35
35
|
// If there are more than 7 crumbs, break up crumbs into first link, ellipsis icon button, and current page ancestor
|
|
36
|
-
const shouldTruncateCrumbs = crumbs
|
|
37
|
-
const shouldUseEllipsisTruncation = !isMobile && truncationMethod === 'ellipsis' && shouldTruncateCrumbs
|
|
36
|
+
const shouldTruncateCrumbs = crumbs.length > 7;
|
|
37
|
+
const shouldUseEllipsisTruncation = !isMobile && truncationMethod === 'ellipsis' && shouldTruncateCrumbs;
|
|
38
38
|
// Get the current page (last crumb) and all ancestors (all crumbs except last)
|
|
39
|
-
const currentPage = crumbs
|
|
40
|
-
const ancestorCrumbs = crumbs
|
|
39
|
+
const currentPage = crumbs[crumbs.length - 1];
|
|
40
|
+
const ancestorCrumbs = crumbs.slice(0, -1);
|
|
41
41
|
return jsx("nav", {
|
|
42
42
|
ref: forwardedRef,
|
|
43
43
|
"aria-label": breadcrumbAriaLabel,
|
|
@@ -57,7 +57,7 @@ const Breadcrumb = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
57
57
|
width: getSeparatorSize(size)
|
|
58
58
|
})]
|
|
59
59
|
}), isMobile && crumbs && currentPage && jsxs(Fragment, {
|
|
60
|
-
children: [jsxs("li", {
|
|
60
|
+
children: [ancestorCrumbs.length > 0 && jsxs("li", {
|
|
61
61
|
className: styles.pageLink,
|
|
62
62
|
children: [jsxs(DropdownMenu.Root, {
|
|
63
63
|
children: [jsx(DropdownMenu.Trigger, {
|
|
@@ -68,7 +68,7 @@ const Breadcrumb = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
68
68
|
})
|
|
69
69
|
}), jsx(DropdownMenu.Content, {
|
|
70
70
|
align: "start",
|
|
71
|
-
children: ancestorCrumbs
|
|
71
|
+
children: ancestorCrumbs.map(crumb => jsx(DropdownMenu.Item, {
|
|
72
72
|
onSelect: handlePageLinkClick(crumb.id),
|
|
73
73
|
children: jsx(Text, {
|
|
74
74
|
as: "span",
|
|
@@ -134,7 +134,7 @@ const Breadcrumb = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
134
134
|
onPageLinkClick: onPageLinkClick,
|
|
135
135
|
size: size
|
|
136
136
|
})]
|
|
137
|
-
}), !isMobile && !shouldUseEllipsisTruncation && crumbs
|
|
137
|
+
}), !isMobile && !shouldUseEllipsisTruncation && crumbs.map((crumb, index) => {
|
|
138
138
|
return jsx(PageLink, {
|
|
139
139
|
crumb: crumb,
|
|
140
140
|
isInteractive: isInteractive,
|
package/dist/lib-esm/index.js
CHANGED
|
@@ -40,6 +40,7 @@ export { InputChip } from './input-chip/input-chip.js';
|
|
|
40
40
|
export { LargeList } from './large-list-item/index.js';
|
|
41
41
|
export { ActionCell, Cell, Column, DropIndicator, Row, Table, TableBody, TableHeader } from './list-item/list-item.js';
|
|
42
42
|
export { LoadingIndicator } from './loading-indicator/loading-indicator.js';
|
|
43
|
+
export { AlertModal } from './modal/alert-modal.js';
|
|
43
44
|
export { Modal } from './modal/modal.js';
|
|
44
45
|
export { NavigationMenu } from './navigation-menu/index.js';
|
|
45
46
|
export { Page } from './page/index.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AlertModalProps } from './alert-modal.types';
|
|
2
|
+
export declare const AlertModal: {
|
|
3
|
+
({ children, closeButtonAriaLabel, container, heading, onOpenChange, open, size, textContent, ...props }: AlertModalProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
displayName: string;
|
|
5
|
+
} & {
|
|
6
|
+
PrimaryButton: import("react").ForwardRefExoticComponent<(Omit<import("../primitives/base-button").BaseButtonInterface & Required<Pick<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading>> & Omit<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading> & {
|
|
7
|
+
children?: string | string[];
|
|
8
|
+
icon?: import("react").FunctionComponent<import("react").PropsWithChildren<import("react").SVGProps<SVGSVGElement>>>;
|
|
9
|
+
}, "ref"> | Omit<import("../primitives/base-button").BaseButtonInterface & Partial<Record<keyof import("../primitives/base-button").Loading, never>> & Omit<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading> & {
|
|
10
|
+
children?: string | string[];
|
|
11
|
+
icon?: import("react").FunctionComponent<import("react").PropsWithChildren<import("react").SVGProps<SVGSVGElement>>>;
|
|
12
|
+
}, "ref">) & import("react").RefAttributes<HTMLButtonElement>>;
|
|
13
|
+
SecondaryButton: import("react").ForwardRefExoticComponent<(Omit<import("../primitives/base-button").BaseButtonInterface & Required<Pick<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading>> & Omit<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading> & {
|
|
14
|
+
children?: string | string[];
|
|
15
|
+
icon?: import("react").FunctionComponent<import("react").PropsWithChildren<import("react").SVGProps<SVGSVGElement>>>;
|
|
16
|
+
}, "ref"> | Omit<import("../primitives/base-button").BaseButtonInterface & Partial<Record<keyof import("../primitives/base-button").Loading, never>> & Omit<import("../primitives/base-button").Loading, keyof import("../primitives/base-button").Loading> & {
|
|
17
|
+
children?: string | string[];
|
|
18
|
+
icon?: import("react").FunctionComponent<import("react").PropsWithChildren<import("react").SVGProps<SVGSVGElement>>>;
|
|
19
|
+
}, "ref">) & import("react").RefAttributes<HTMLButtonElement>>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useId, Children, isValidElement } from 'react';
|
|
3
|
+
import { Modal } from './modal.js';
|
|
4
|
+
import { ModalFooter } from './modal-footer.js';
|
|
5
|
+
|
|
6
|
+
// Validate that the children are valid AlertModal.SecondaryButton and AlertModal.PrimaryButton
|
|
7
|
+
// If not, throw an error
|
|
8
|
+
const validateChildren = children => {
|
|
9
|
+
const childrenArray = Children.toArray(children);
|
|
10
|
+
if (childrenArray.length !== 2) {
|
|
11
|
+
throw new Error('AlertModal requires exactly two children: AlertModal.SecondaryButton followed by AlertModal.PrimaryButton');
|
|
12
|
+
}
|
|
13
|
+
const [firstChild, secondChild] = childrenArray;
|
|
14
|
+
if (! /*#__PURE__*/isValidElement(firstChild) || firstChild.type !== ModalFooter.SecondaryButton) {
|
|
15
|
+
throw new Error('AlertModal requires the first child to be AlertModal.SecondaryButton');
|
|
16
|
+
}
|
|
17
|
+
if (! /*#__PURE__*/isValidElement(secondChild) || secondChild.type !== ModalFooter.PrimaryButton) {
|
|
18
|
+
throw new Error('AlertModal requires the second child to be AlertModal.PrimaryButton');
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* AlertModal is a specialized modal component that enforces specific requirements:
|
|
23
|
+
* - Always has a heading/title (required)
|
|
24
|
+
* - Always has a primary and secondary button in the footer
|
|
25
|
+
* - Only accepts text as content
|
|
26
|
+
* - Has role="alertdialog" for accessibility
|
|
27
|
+
* - Announces the content of the modal to the user when it is opened
|
|
28
|
+
* - Always includes a close button with i18n support
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <AlertModal
|
|
32
|
+
* open={isOpen}
|
|
33
|
+
* onOpenChange={setIsOpen}
|
|
34
|
+
* heading="Delete Item"
|
|
35
|
+
* textContent="Are you sure you want to delete this item?"
|
|
36
|
+
* closeButtonAriaLabel={formatMessage(commonMessages.closeButtonText)}
|
|
37
|
+
* >
|
|
38
|
+
* <AlertModal.SecondaryButton onClick={() => setIsOpen(false)}>
|
|
39
|
+
* Cancel
|
|
40
|
+
* </AlertModal.SecondaryButton>
|
|
41
|
+
* <AlertModal.PrimaryButton onClick={handleDelete}>
|
|
42
|
+
* Delete
|
|
43
|
+
* </AlertModal.PrimaryButton>
|
|
44
|
+
* </AlertModal>
|
|
45
|
+
*/
|
|
46
|
+
const AlertModalComponent = ({
|
|
47
|
+
children,
|
|
48
|
+
closeButtonAriaLabel,
|
|
49
|
+
container,
|
|
50
|
+
heading,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
open,
|
|
53
|
+
size = 'small',
|
|
54
|
+
textContent,
|
|
55
|
+
...props
|
|
56
|
+
}) => {
|
|
57
|
+
const textContentId = useId();
|
|
58
|
+
validateChildren(children);
|
|
59
|
+
return jsx(Modal, {
|
|
60
|
+
onOpenChange: onOpenChange,
|
|
61
|
+
open: open,
|
|
62
|
+
...props,
|
|
63
|
+
children: jsxs(Modal.Content, {
|
|
64
|
+
"aria-describedby": textContentId,
|
|
65
|
+
container: container,
|
|
66
|
+
role: "alertdialog",
|
|
67
|
+
size: size,
|
|
68
|
+
children: [jsx(Modal.Header, {
|
|
69
|
+
children: heading
|
|
70
|
+
}), jsx(Modal.ScrollableContainer, {
|
|
71
|
+
children: jsx(Modal.Body, {
|
|
72
|
+
id: textContentId,
|
|
73
|
+
children: textContent
|
|
74
|
+
})
|
|
75
|
+
}), jsx(Modal.Footer, {
|
|
76
|
+
children: children
|
|
77
|
+
}), jsx(Modal.Close, {
|
|
78
|
+
"aria-label": closeButtonAriaLabel
|
|
79
|
+
})]
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
AlertModalComponent.displayName = 'AlertModal';
|
|
84
|
+
const AlertModal = Object.assign(AlertModalComponent, {
|
|
85
|
+
PrimaryButton: ModalFooter.PrimaryButton,
|
|
86
|
+
SecondaryButton: ModalFooter.SecondaryButton
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export { AlertModal };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type DialogPortalProps, type DialogProps } from '@radix-ui/react-dialog';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import { type ModalContentSize } from './types';
|
|
4
|
+
export interface AlertModalProps extends Omit<DialogProps, 'modal'>, Pick<DialogPortalProps, 'container'> {
|
|
5
|
+
/** The text content to display in the modal body. Should be an i18n string (e.g., from formatMessage). */
|
|
6
|
+
textContent: string;
|
|
7
|
+
/** Heading/title for the modal (required) */
|
|
8
|
+
heading: string;
|
|
9
|
+
/** Children must include AlertModal.PrimaryButton and AlertModal.SecondaryButton */
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/** Size of the modal content */
|
|
12
|
+
size?: ModalContentSize;
|
|
13
|
+
/** Aria label for the close button. Should be an i18n string. */
|
|
14
|
+
closeButtonAriaLabel: string;
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.107.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@ariakit/react": "0.4.15",
|
|
49
49
|
"@ariakit/react-core": "0.4.15",
|
|
50
|
-
"@box/blueprint-web-assets": "^4.88.
|
|
50
|
+
"@box/blueprint-web-assets": "^4.88.4",
|
|
51
51
|
"@internationalized/date": "^3.7.0",
|
|
52
52
|
"@radix-ui/react-accordion": "1.1.2",
|
|
53
53
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"type-fest": "^3.2.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@box/storybook-utils": "^0.14.
|
|
80
|
+
"@box/storybook-utils": "^0.14.32",
|
|
81
81
|
"@types/react": "^18.0.0",
|
|
82
82
|
"@types/react-dom": "^18.0.0",
|
|
83
83
|
"react": "^18.3.0",
|