@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.
@@ -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?.length > 7;
37
- const shouldUseEllipsisTruncation = !isMobile && truncationMethod === 'ellipsis' && shouldTruncateCrumbs && crumbs;
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?.[crumbs.length - 1];
40
- const ancestorCrumbs = crumbs?.slice(0, -1);
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?.map(crumb => jsx(DropdownMenu.Item, {
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?.map((crumb, index) => {
137
+ }), !isMobile && !shouldUseEllipsisTruncation && crumbs.map((crumb, index) => {
138
138
  return jsx(PageLink, {
139
139
  crumb: crumb,
140
140
  isInteractive: isInteractive,
@@ -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
+ }
@@ -1,2 +1,4 @@
1
+ export { AlertModal } from './alert-modal';
2
+ export type { AlertModalProps } from './alert-modal.types';
1
3
  export { Modal } from './modal';
2
4
  export type { ModalCloseProps, ModalContentProps, ModalLoadingProps, ModalProps, ModalTriggerProps } from './types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@box/blueprint-web",
3
- "version": "12.106.2",
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.3",
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.31",
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",