Skip to content

Commit 59c10df

Browse files
author
Noah Pan
committed
feat(content-sidebar): implement for custom sidebar panels (BREAKING CHANGE)
1 parent 6937b60 commit 59c10df

File tree

11 files changed

+700
-394
lines changed

11 files changed

+700
-394
lines changed

src/elements/content-sidebar/ContentSidebar.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import type { WithLoggerProps } from '../../common/types/logging';
4646
import type { ElementsXhrError, RequestOptions, ErrorContextProps } from '../../common/types/api';
4747
import type { MetadataEditor } from '../../common/types/metadata';
4848
import type { StringMap, Token, User, BoxItem } from '../../common/types/core';
49-
import type { AdditionalSidebarTab } from './flowTypes';
49+
import type { AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes';
5050
import type { FeatureConfig } from '../common/feature-checking';
5151
// $FlowFixMe TypeScript file
5252
import type { Theme } from '../common/theming';
@@ -66,6 +66,7 @@ type Props = {
6666
className: string,
6767
clientName: string,
6868
currentUser?: User,
69+
customSidebarPanels?: Array<CustomSidebarPanel>,
6970
defaultView: string,
7071
detailsSidebarProps: DetailsSidebarProps,
7172
docGenSidebarProps?: DocGenSidebarProps,
@@ -354,6 +355,7 @@ class ContentSidebar extends React.Component<Props, State> {
354355
boxAISidebarProps,
355356
className,
356357
currentUser,
358+
customSidebarPanels,
357359
defaultView,
358360
detailsSidebarProps,
359361
docGenSidebarProps,
@@ -399,6 +401,7 @@ class ContentSidebar extends React.Component<Props, State> {
399401
boxAISidebarProps={boxAISidebarProps}
400402
className={className}
401403
currentUser={currentUser}
404+
customSidebarPanels={customSidebarPanels}
402405
detailsSidebarProps={detailsSidebarProps}
403406
docGenSidebarProps={docGenSidebarProps}
404407
file={file}

src/elements/content-sidebar/Sidebar.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar';
2828
import type { MetadataSidebarProps } from './MetadataSidebar';
2929
import type { BoxAISidebarProps } from './BoxAISidebar';
3030
import type { VersionsSidebarProps } from './versions';
31-
import type { AdditionalSidebarTab } from './flowTypes';
31+
import type { AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes';
3232
import type { MetadataEditor } from '../../common/types/metadata';
3333
import type { BoxItem, User } from '../../common/types/core';
3434
import type { SignSidebarProps } from './SidebarNavSign';
3535
import type { Errors } from '../common/flowTypes';
3636
// $FlowFixMe TypeScript file
3737
import type { Theme } from '../common/theming';
38-
import { SIDEBAR_VIEW_DOCGEN } from '../../constants';
38+
import { SIDEBAR_VIEW_DOCGEN, SIDEBAR_VIEW_BOXAI } from '../../constants';
3939
import API from '../../api';
4040

4141
type Props = {
@@ -46,6 +46,7 @@ type Props = {
4646
className: string,
4747
currentUser?: User,
4848
currentUserError?: Errors,
49+
customSidebarPanels?: Array<CustomSidebarPanel>,
4950
detailsSidebarProps: DetailsSidebarProps,
5051
docGenSidebarProps: DocGenSidebarProps,
5152
features: FeatureConfig,
@@ -297,6 +298,7 @@ class Sidebar extends React.Component<Props, State> {
297298
className,
298299
currentUser,
299300
currentUserError,
301+
customSidebarPanels,
300302
detailsSidebarProps,
301303
docGenSidebarProps,
302304
file,
@@ -316,12 +318,14 @@ class Sidebar extends React.Component<Props, State> {
316318
versionsSidebarProps,
317319
}: Props = this.props;
318320
const isOpen = this.isOpen();
319-
const hasBoxAI = SidebarUtils.canHaveBoxAISidebar(this.props);
320321
const hasActivity = SidebarUtils.canHaveActivitySidebar(this.props);
321322
const hasDetails = SidebarUtils.canHaveDetailsSidebar(this.props);
322323
const hasMetadata = SidebarUtils.shouldRenderMetadataSidebar(this.props, metadataEditors);
323324
const hasSkills = SidebarUtils.shouldRenderSkillsSidebar(this.props, file);
324325
const onVersionHistoryClick = hasVersions ? this.handleVersionHistoryClick : this.props.onVersionHistoryClick;
326+
const hasBoxAI = customSidebarPanels
327+
? !!customSidebarPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI)
328+
: false;
325329
const styleClassName = classNames('be bcs', className, {
326330
'bcs-is-open': isOpen,
327331
'bcs-is-wider': hasBoxAI,
@@ -340,11 +344,11 @@ class Sidebar extends React.Component<Props, State> {
340344
{hasNav && (
341345
<SidebarNav
342346
additionalTabs={additionalTabs}
347+
customTabs={customSidebarPanels}
343348
elementId={this.id}
344349
fileId={fileId}
345350
hasActivity={hasActivity}
346351
hasAdditionalTabs={hasAdditionalTabs}
347-
hasBoxAI={hasBoxAI}
348352
hasDetails={hasDetails}
349353
hasMetadata={hasMetadata}
350354
hasSkills={hasSkills}
@@ -359,6 +363,7 @@ class Sidebar extends React.Component<Props, State> {
359363
boxAISidebarProps={boxAISidebarProps}
360364
currentUser={currentUser}
361365
currentUserError={currentUserError}
366+
customPanels={customSidebarPanels}
362367
elementId={this.id}
363368
defaultPanel={defaultPanel}
364369
detailsSidebarProps={detailsSidebarProps}
@@ -368,7 +373,6 @@ class Sidebar extends React.Component<Props, State> {
368373
getPreview={getPreview}
369374
getViewer={getViewer}
370375
hasActivity={hasActivity}
371-
hasBoxAI={hasBoxAI}
372376
hasDetails={hasDetails}
373377
hasDocGen={docGenSidebarProps.isDocGenTemplate}
374378
hasMetadata={hasMetadata}

src/elements/content-sidebar/SidebarNav.js

Lines changed: 137 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import * as React from 'react';
88
import { injectIntl } from 'react-intl';
99
import type { IntlShape } from 'react-intl';
1010
import noop from 'lodash/noop';
11-
// $FlowFixMe
12-
import { BoxAiLogo } from '@box/blueprint-web-assets/icons/Logo';
13-
// $FlowFixMe
14-
import { Size6 } from '@box/blueprint-web-assets/tokens/tokens';
1511
import { usePromptFocus } from '@box/box-ai-content-answers';
1612
import AdditionalTabs from './additional-tabs';
1713
import DocGenIcon from '../../icon/fill/DocGenIcon';
@@ -33,19 +29,18 @@ import {
3329
SIDEBAR_VIEW_METADATA,
3430
SIDEBAR_VIEW_SKILLS,
3531
} from '../../constants';
36-
import { useFeatureConfig } from '../common/feature-checking';
37-
import type { NavigateOptions, AdditionalSidebarTab } from './flowTypes';
32+
import type { NavigateOptions, AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes';
3833
import type { InternalSidebarNavigation, InternalSidebarNavigationHandler } from '../common/types/SidebarNavigation';
3934
import './SidebarNav.scss';
4035
import type { SignSidebarProps } from './SidebarNavSign';
4136

4237
type Props = {
4338
additionalTabs?: Array<AdditionalSidebarTab>,
39+
customTabs?: Array<CustomSidebarPanel>,
4440
elementId: string,
4541
fileId: string,
4642
hasActivity: boolean,
4743
hasAdditionalTabs: boolean,
48-
hasBoxAI: boolean,
4944
hasDetails: boolean,
5045
hasDocGen?: boolean,
5146
hasMetadata: boolean,
@@ -62,11 +57,11 @@ type Props = {
6257

6358
const SidebarNav = ({
6459
additionalTabs,
60+
customTabs,
6561
elementId,
6662
fileId,
6763
hasActivity,
6864
hasAdditionalTabs,
69-
hasBoxAI,
7065
hasDetails,
7166
hasMetadata,
7267
hasSkills,
@@ -81,8 +76,6 @@ const SidebarNav = ({
8176
signSidebarProps,
8277
}: Props) => {
8378
const { enabled: hasBoxSign } = signSidebarProps || {};
84-
const { disabledTooltip: boxAIDisabledTooltip, showOnlyNavButton: showOnlyBoxAINavButton } =
85-
useFeatureConfig('boxai.sidebar');
8679

8780
const { focusPrompt } = usePromptFocus('.be.bcs');
8881

@@ -94,6 +87,139 @@ const SidebarNav = ({
9487
focusPrompt();
9588
}
9689
};
90+
const boxAiTab = customTabs ? customTabs.find(tab => tab.id === SIDEBAR_VIEW_BOXAI) : undefined;
91+
const otherCustomTabs = customTabs ? customTabs.filter(tab => tab.id !== SIDEBAR_VIEW_BOXAI) : [];
92+
const hasOtherCustomTabs = otherCustomTabs.length > 0;
93+
94+
const sidebarTabs = [
95+
boxAiTab && (
96+
<SidebarNavButton
97+
key={boxAiTab.id}
98+
data-target-id={`SidebarNavButton-${boxAiTab.id}`}
99+
data-testid={`sidebar${boxAiTab.id}`}
100+
{...boxAiTab.navButtonProps}
101+
data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
102+
isDisabled={boxAiTab.isDisabled}
103+
onClick={handleSidebarNavButtonClick}
104+
sidebarView={boxAiTab.path}
105+
tooltip={boxAiTab.title ?? boxAiTab.id}
106+
>
107+
{boxAiTab.icon &&
108+
(React.isValidElement(boxAiTab.icon) ? (
109+
boxAiTab.icon
110+
) : (
111+
// $FlowFixMe: Flow doesn't understand dynamic component creation
112+
<boxAiTab.icon className="bcs-SidebarNav-icon" />
113+
))}
114+
</SidebarNavButton>
115+
),
116+
hasActivity && (
117+
<SidebarNavButton
118+
key={SIDEBAR_VIEW_ACTIVITY}
119+
data-resin-target={SIDEBAR_NAV_TARGETS.ACTIVITY}
120+
data-target-id="SidebarNavButton-activity"
121+
data-testid="sidebaractivity"
122+
onClick={handleSidebarNavButtonClick}
123+
sidebarView={SIDEBAR_VIEW_ACTIVITY}
124+
tooltip={intl.formatMessage(messages.sidebarActivityTitle)}
125+
>
126+
<IconChatRound className="bcs-SidebarNav-icon" />
127+
</SidebarNavButton>
128+
),
129+
hasDetails && (
130+
<SidebarNavButton
131+
key={SIDEBAR_VIEW_DETAILS}
132+
data-resin-target={SIDEBAR_NAV_TARGETS.DETAILS}
133+
data-target-id="SidebarNavButton-details"
134+
data-testid="sidebardetails"
135+
onClick={handleSidebarNavButtonClick}
136+
sidebarView={SIDEBAR_VIEW_DETAILS}
137+
tooltip={intl.formatMessage(messages.sidebarDetailsTitle)}
138+
>
139+
<IconDocInfo className="bcs-SidebarNav-icon" />
140+
</SidebarNavButton>
141+
),
142+
hasSkills && (
143+
<SidebarNavButton
144+
key={SIDEBAR_VIEW_SKILLS}
145+
data-resin-target={SIDEBAR_NAV_TARGETS.SKILLS}
146+
data-target-id="SidebarNavButton-skills"
147+
data-testid="sidebarskills"
148+
onClick={handleSidebarNavButtonClick}
149+
sidebarView={SIDEBAR_VIEW_SKILLS}
150+
tooltip={intl.formatMessage(messages.sidebarSkillsTitle)}
151+
>
152+
<IconMagicWand className="bcs-SidebarNav-icon" />
153+
</SidebarNavButton>
154+
),
155+
hasMetadata && (
156+
<SidebarNavButton
157+
key={SIDEBAR_VIEW_METADATA}
158+
data-resin-target={SIDEBAR_NAV_TARGETS.METADATA}
159+
data-target-id="SidebarNavButton-metadata"
160+
data-testid="sidebarmetadata"
161+
onClick={handleSidebarNavButtonClick}
162+
sidebarView={SIDEBAR_VIEW_METADATA}
163+
tooltip={intl.formatMessage(messages.sidebarMetadataTitle)}
164+
>
165+
<IconMetadataThick className="bcs-SidebarNav-icon" />
166+
</SidebarNavButton>
167+
),
168+
hasDocGen && (
169+
<SidebarNavButton
170+
key={SIDEBAR_VIEW_DOCGEN}
171+
data-resin-target={SIDEBAR_NAV_TARGETS.DOCGEN}
172+
data-target-id="SidebarNavButton-docgen"
173+
data-testid="sidebardocgen"
174+
onClick={handleSidebarNavButtonClick}
175+
sidebarView={SIDEBAR_VIEW_DOCGEN}
176+
tooltip={intl.formatMessage(messages.sidebarDocGenTooltip)}
177+
>
178+
<DocGenIcon className="bcs-SidebarNav-icon" />
179+
</SidebarNavButton>
180+
),
181+
];
182+
183+
// Filter out falsy values first
184+
const visibleTabs = sidebarTabs.filter(Boolean);
185+
186+
// Insert custom tabs - box-ai goes at the top, others at the end
187+
if (hasOtherCustomTabs) {
188+
// Add other custom tabs at the end
189+
otherCustomTabs.forEach(customTab => {
190+
const {
191+
id: customTabId,
192+
path: customTabPath,
193+
icon: CustomTabIcon,
194+
title: customTabTitle,
195+
navButtonProps,
196+
} = customTab;
197+
198+
const customTabButton = (
199+
<SidebarNavButton
200+
key={customTabId}
201+
data-resin-target={`sidebar${customTabId}`}
202+
data-target-id={`SidebarNavButton-${customTabId}`}
203+
data-testid={`sidebar${customTabId}`}
204+
{...navButtonProps}
205+
isDisabled={customTab.isDisabled}
206+
onClick={handleSidebarNavButtonClick}
207+
sidebarView={customTabPath}
208+
tooltip={customTabTitle ?? customTabId}
209+
>
210+
{CustomTabIcon &&
211+
(React.isValidElement(CustomTabIcon) ? (
212+
CustomTabIcon
213+
) : (
214+
// $FlowFixMe: Flow doesn't understand dynamic component creation
215+
<CustomTabIcon className="bcs-SidebarNav-icon" />
216+
))}
217+
</SidebarNavButton>
218+
);
219+
220+
visibleTabs.push(customTabButton); // Add at the end
221+
});
222+
}
97223

98224
return (
99225
<div className="bcs-SidebarNav" aria-label={intl.formatMessage(messages.sidebarNavLabel)}>
@@ -106,83 +232,7 @@ const SidebarNav = ({
106232
onNavigate={onNavigate}
107233
routerDisabled={routerDisabled}
108234
>
109-
{hasBoxAI && (
110-
<SidebarNavButton
111-
data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
112-
data-target-id="SidebarNavButton-boxAI"
113-
data-testid="sidebarboxai"
114-
isDisabled={showOnlyBoxAINavButton}
115-
onClick={handleSidebarNavButtonClick}
116-
sidebarView={SIDEBAR_VIEW_BOXAI}
117-
tooltip={
118-
showOnlyBoxAINavButton
119-
? boxAIDisabledTooltip
120-
: intl.formatMessage(messages.sidebarBoxAITitle)
121-
}
122-
>
123-
<BoxAiLogo height={Size6} width={Size6} />
124-
</SidebarNavButton>
125-
)}
126-
{hasActivity && (
127-
<SidebarNavButton
128-
data-resin-target={SIDEBAR_NAV_TARGETS.ACTIVITY}
129-
data-target-id="SidebarNavButton-activity"
130-
data-testid="sidebaractivity"
131-
onClick={handleSidebarNavButtonClick}
132-
sidebarView={SIDEBAR_VIEW_ACTIVITY}
133-
tooltip={intl.formatMessage(messages.sidebarActivityTitle)}
134-
>
135-
<IconChatRound className="bcs-SidebarNav-icon" />
136-
</SidebarNavButton>
137-
)}
138-
{hasDetails && (
139-
<SidebarNavButton
140-
data-resin-target={SIDEBAR_NAV_TARGETS.DETAILS}
141-
data-target-id="SidebarNavButton-details"
142-
data-testid="sidebardetails"
143-
onClick={handleSidebarNavButtonClick}
144-
sidebarView={SIDEBAR_VIEW_DETAILS}
145-
tooltip={intl.formatMessage(messages.sidebarDetailsTitle)}
146-
>
147-
<IconDocInfo className="bcs-SidebarNav-icon" />
148-
</SidebarNavButton>
149-
)}
150-
{hasSkills && (
151-
<SidebarNavButton
152-
data-resin-target={SIDEBAR_NAV_TARGETS.SKILLS}
153-
data-target-id="SidebarNavButton-skills"
154-
data-testid="sidebarskills"
155-
onClick={handleSidebarNavButtonClick}
156-
sidebarView={SIDEBAR_VIEW_SKILLS}
157-
tooltip={intl.formatMessage(messages.sidebarSkillsTitle)}
158-
>
159-
<IconMagicWand className="bcs-SidebarNav-icon" />
160-
</SidebarNavButton>
161-
)}
162-
{hasMetadata && (
163-
<SidebarNavButton
164-
data-resin-target={SIDEBAR_NAV_TARGETS.METADATA}
165-
data-target-id="SidebarNavButton-metadata"
166-
data-testid="sidebarmetadata"
167-
onClick={handleSidebarNavButtonClick}
168-
sidebarView={SIDEBAR_VIEW_METADATA}
169-
tooltip={intl.formatMessage(messages.sidebarMetadataTitle)}
170-
>
171-
<IconMetadataThick className="bcs-SidebarNav-icon" />
172-
</SidebarNavButton>
173-
)}
174-
{hasDocGen && (
175-
<SidebarNavButton
176-
data-resin-target={SIDEBAR_NAV_TARGETS.DOCGEN}
177-
data-target-id="SidebarNavButton-docGen"
178-
data-testid="sidebardocgen"
179-
onClick={handleSidebarNavButtonClick}
180-
sidebarView={SIDEBAR_VIEW_DOCGEN}
181-
tooltip={intl.formatMessage(messages.sidebarDocGenTooltip)}
182-
>
183-
<DocGenIcon className="bcs-SidebarNav-icon" />
184-
</SidebarNavButton>
185-
)}
235+
{visibleTabs}
186236
</SidebarNavTablist>
187237

188238
{hasBoxSign && (

0 commit comments

Comments
 (0)