Skip to content

Commit 44f823e

Browse files
author
Cameron Joyner
committed
larger scaffolding
1 parent ff84dc1 commit 44f823e

1 file changed

Lines changed: 151 additions & 7 deletions

File tree

packages/core/src/components/breadcrumbs-next/breadcrumbsNext.tsx

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,164 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { DISPLAYNAME_PREFIX } from "../../common";
18-
import { Breadcrumbs, type BreadcrumbsProps } from "../breadcrumbs/breadcrumbs";
17+
import classNames from "classnames";
18+
import { memo, useCallback } from "react";
1919

20-
export type BreadcrumbsNextProps = Omit<BreadcrumbsProps, "popoverProps">;
20+
import { Boundary, Classes, DISPLAYNAME_PREFIX, type Props, removeNonHTMLProps } from "../../common";
21+
import { Menu } from "../menu/menu";
22+
import { MenuItem } from "../menu/menuItem";
23+
import { OverflowList, type OverflowListProps } from "../overflow-list/overflowList";
24+
import { PopoverNext } from "../popover-next/popoverNext";
25+
import { Breadcrumb, type BreadcrumbProps } from "../breadcrumbs/breadcrumb";
26+
27+
const EMPTY_ITEMS: readonly BreadcrumbProps[] = [];
28+
29+
export interface BreadcrumbsNextProps extends Props {
30+
/**
31+
* Callback invoked to render visible breadcrumbs. Best practice is to
32+
* render a `<Breadcrumb>` element. If `currentBreadcrumbRenderer` is also
33+
* supplied, that callback will be used for the current breadcrumb instead.
34+
*
35+
* @default Breadcrumb
36+
*/
37+
breadcrumbRenderer?: (props: BreadcrumbProps) => React.JSX.Element;
38+
39+
/**
40+
* Which direction the breadcrumbs should collapse from: start or end.
41+
*
42+
* @default Boundary.START
43+
*/
44+
collapseFrom?: Boundary;
45+
46+
/**
47+
* Callback invoked to render the current breadcrumb, which is the last
48+
* element in the `items` array.
49+
*
50+
* If this prop is omitted, `breadcrumbRenderer` will be invoked for the
51+
* current breadcrumb instead.
52+
*/
53+
currentBreadcrumbRenderer?: (props: BreadcrumbProps) => React.JSX.Element;
54+
55+
/**
56+
* All breadcrumbs to display. Breadcrumbs that do not fit in the container
57+
* will be rendered in an overflow menu instead.
58+
*
59+
* @default []
60+
*/
61+
items?: readonly BreadcrumbProps[];
62+
63+
/**
64+
* The minimum number of visible breadcrumbs that should never collapse into
65+
* the overflow menu, regardless of DOM dimensions.
66+
*
67+
* @default 0
68+
*/
69+
minVisibleItems?: number;
70+
71+
/**
72+
* Props to spread to the `OverflowList` popover target.
73+
*/
74+
overflowButtonProps?: React.HTMLProps<HTMLSpanElement>;
75+
76+
/**
77+
* Props to spread to `OverflowList`. Note that `items`,
78+
* `overflowRenderer`, and `visibleItemRenderer` cannot be changed.
79+
*/
80+
overflowListProps?: Partial<
81+
Omit<OverflowListProps<BreadcrumbProps>, "items" | "overflowRenderer" | "visibleItemRenderer">
82+
>;
83+
}
2184

2285
/**
2386
* BreadcrumbsNext component.
2487
*
25-
* Thin wrapper around `Breadcrumbs` that omits the `popoverProps` prop.
88+
* Uses PopoverNext (Floating UI) for the overflow menu instead of Popover (Popper.js).
89+
* Does not expose popover configuration props, making it forward-compatible with
90+
* future popover implementation changes.
2691
*
2792
* @see https://blueprintjs.com/docs/#core/components/breadcrumbs
2893
*/
29-
export const BreadcrumbsNext: React.FC<BreadcrumbsNextProps> = props => {
30-
return <Breadcrumbs {...props} />;
31-
};
94+
export const BreadcrumbsNext: React.FC<BreadcrumbsNextProps> = memo(props => {
95+
const {
96+
breadcrumbRenderer,
97+
className,
98+
collapseFrom = Boundary.START,
99+
currentBreadcrumbRenderer,
100+
items = EMPTY_ITEMS,
101+
minVisibleItems = 0,
102+
overflowButtonProps,
103+
overflowListProps = {},
104+
} = props;
105+
106+
const renderBreadcrumb = useCallback(
107+
(breadcrumbProps: BreadcrumbProps, isCurrent: boolean) => {
108+
if (isCurrent && currentBreadcrumbRenderer != null) {
109+
return currentBreadcrumbRenderer(breadcrumbProps);
110+
} else if (breadcrumbRenderer != null) {
111+
return breadcrumbRenderer(breadcrumbProps);
112+
} else {
113+
return <Breadcrumb current={isCurrent} {...breadcrumbProps} />;
114+
}
115+
},
116+
[breadcrumbRenderer, currentBreadcrumbRenderer],
117+
);
118+
119+
const renderBreadcrumbWrapper = useCallback(
120+
(breadcrumbProps: BreadcrumbProps, index: number) => {
121+
const isCurrent = items[items.length - 1] === breadcrumbProps;
122+
return <li key={index}>{renderBreadcrumb(breadcrumbProps, isCurrent)}</li>;
123+
},
124+
[items, renderBreadcrumb],
125+
);
126+
127+
const renderOverflowBreadcrumb = useCallback((breadcrumbProps: BreadcrumbProps, index: number) => {
128+
const isClickable = breadcrumbProps.href != null || breadcrumbProps.onClick != null;
129+
const htmlProps = removeNonHTMLProps(breadcrumbProps);
130+
return <MenuItem disabled={!isClickable} {...htmlProps} text={breadcrumbProps.text} key={index} />;
131+
}, []);
132+
133+
const renderOverflow = useCallback(
134+
(overflowItems: readonly BreadcrumbProps[]) => {
135+
let orderedItems = overflowItems;
136+
if (collapseFrom === Boundary.START) {
137+
orderedItems = overflowItems.slice().reverse();
138+
}
139+
140+
return (
141+
<li>
142+
<PopoverNext
143+
content={<Menu>{orderedItems.map(renderOverflowBreadcrumb)}</Menu>}
144+
disabled={orderedItems.length === 0}
145+
placement={collapseFrom === Boundary.END ? "bottom-end" : "bottom-start"}
146+
>
147+
<span
148+
aria-label="collapsed breadcrumbs"
149+
role="button"
150+
tabIndex={0}
151+
{...overflowButtonProps}
152+
className={classNames(Classes.BREADCRUMBS_COLLAPSED, overflowButtonProps?.className)}
153+
/>
154+
</PopoverNext>
155+
</li>
156+
);
157+
},
158+
[collapseFrom, overflowButtonProps, renderOverflowBreadcrumb],
159+
);
160+
161+
return (
162+
<OverflowList
163+
collapseFrom={collapseFrom}
164+
minVisibleItems={minVisibleItems}
165+
tagName="ol"
166+
navigable={true}
167+
navigationAriaLabel="Breadcrumb"
168+
{...overflowListProps}
169+
className={classNames(Classes.BREADCRUMBS, overflowListProps.className, className)}
170+
items={items}
171+
overflowRenderer={renderOverflow}
172+
visibleItemRenderer={renderBreadcrumbWrapper}
173+
/>
174+
);
175+
});
32176

33177
BreadcrumbsNext.displayName = `${DISPLAYNAME_PREFIX}.BreadcrumbsNext`;

0 commit comments

Comments
 (0)