UNPKG

5.99 kBTypeScriptView Raw
1/*
2 * Copyright 2018 Palantir Technologies, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import classNames from "classnames";
18import * as React from "react";
19import { polyfill } from "react-lifecycles-compat";
20
21import { AbstractPureComponent2, Boundary, Classes, Props, Position, removeNonHTMLProps } from "../../common";
22import { Menu } from "../menu/menu";
23import { MenuItem } from "../menu/menuItem";
24import { OverflowListProps, OverflowList } from "../overflow-list/overflowList";
25import { IPopoverProps, Popover } from "../popover/popover";
26import { Breadcrumb, BreadcrumbProps } from "./breadcrumb";
27
28// eslint-disable-next-line deprecation/deprecation
29export type BreadcrumbsProps = IBreadcrumbsProps;
30/** @deprecated use BreadcrumbsProps */
31export interface IBreadcrumbsProps extends Props {
32 /**
33 * Callback invoked to render visible breadcrumbs. Best practice is to
34 * render a `<Breadcrumb>` element. If `currentBreadcrumbRenderer` is also
35 * supplied, that callback will be used for the current breadcrumb instead.
36 *
37 * @default Breadcrumb
38 */
39 breadcrumbRenderer?: (props: BreadcrumbProps) => JSX.Element;
40
41 /**
42 * Which direction the breadcrumbs should collapse from: start or end.
43 *
44 * @default Boundary.START
45 */
46 collapseFrom?: Boundary;
47
48 /**
49 * Callback invoked to render the current breadcrumb, which is the last
50 * element in the `items` array.
51 *
52 * If this prop is omitted, `breadcrumbRenderer` will be invoked for the
53 * current breadcrumb instead.
54 */
55 currentBreadcrumbRenderer?: (props: BreadcrumbProps) => JSX.Element;
56
57 /**
58 * All breadcrumbs to display. Breadcrumbs that do not fit in the container
59 * will be rendered in an overflow menu instead.
60 */
61 items: 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 `OverflowList`. Note that `items`,
73 * `overflowRenderer`, and `visibleItemRenderer` cannot be changed.
74 */
75 overflowListProps?: Partial<OverflowListProps<BreadcrumbProps>>;
76
77 /**
78 * Props to spread to the `Popover` showing the overflow menu.
79 */
80 popoverProps?: IPopoverProps;
81}
82
83@polyfill
84export class Breadcrumbs extends AbstractPureComponent2<BreadcrumbsProps> {
85 public static defaultProps: Partial<BreadcrumbsProps> = {
86 collapseFrom: Boundary.START,
87 };
88
89 public render() {
90 const { className, collapseFrom, items, minVisibleItems, overflowListProps = {} } = this.props;
91 return (
92 <OverflowList
93 collapseFrom={collapseFrom}
94 minVisibleItems={minVisibleItems}
95 tagName="ul"
96 {...overflowListProps}
97 className={classNames(Classes.BREADCRUMBS, overflowListProps.className, className)}
98 items={items}
99 overflowRenderer={this.renderOverflow}
100 visibleItemRenderer={this.renderBreadcrumbWrapper}
101 />
102 );
103 }
104
105 private renderOverflow = (items: BreadcrumbProps[]) => {
106 const { collapseFrom } = this.props;
107 const position = collapseFrom === Boundary.END ? Position.BOTTOM_RIGHT : Position.BOTTOM_LEFT;
108 let orderedItems = items;
109 if (collapseFrom === Boundary.START) {
110 // If we're collapsing from the start, the menu should be read from the bottom to the
111 // top, continuing with the breadcrumbs to the right. Since this means the first
112 // breadcrumb in the props must be the last in the menu, we need to reverse the overlow
113 // order.
114 orderedItems = items.slice().reverse();
115 }
116
117 /* eslint-disable deprecation/deprecation */
118 return (
119 <li>
120 <Popover
121 position={position}
122 disabled={orderedItems.length === 0}
123 content={<Menu>{orderedItems.map(this.renderOverflowBreadcrumb)}</Menu>}
124 {...this.props.popoverProps}
125 >
126 <span className={Classes.BREADCRUMBS_COLLAPSED} />
127 </Popover>
128 </li>
129 );
130 /* eslint-enable deprecation/deprecation */
131 };
132
133 private renderOverflowBreadcrumb = (props: BreadcrumbProps, index: number) => {
134 const isClickable = props.href != null || props.onClick != null;
135 const htmlProps = removeNonHTMLProps(props);
136 return <MenuItem disabled={!isClickable} {...htmlProps} text={props.text} key={index} />;
137 };
138
139 private renderBreadcrumbWrapper = (props: BreadcrumbProps, index: number) => {
140 const isCurrent = this.props.items[this.props.items.length - 1] === props;
141 return <li key={index}>{this.renderBreadcrumb(props, isCurrent)}</li>;
142 };
143
144 private renderBreadcrumb(props: BreadcrumbProps, isCurrent: boolean) {
145 if (isCurrent && this.props.currentBreadcrumbRenderer != null) {
146 return this.props.currentBreadcrumbRenderer(props);
147 } else if (this.props.breadcrumbRenderer != null) {
148 return this.props.breadcrumbRenderer(props);
149 } else {
150 // allow user to override 'current' prop
151 return <Breadcrumb current={isCurrent} {...props} />;
152 }
153 }
154}