From df9a1231d62321618dad5b5bf39c7d3db753c16a Mon Sep 17 00:00:00 2001 From: Noah Pan Date: Mon, 6 Oct 2025 19:11:04 -0400 Subject: [PATCH] feat(content-sidebar): implement for custom sidebar panels (BREAKING CHANGE) --- .../content-sidebar/ContentSidebar.js | 5 +- src/elements/content-sidebar/Sidebar.js | 14 +- src/elements/content-sidebar/SidebarNav.js | 224 +++++++++------ src/elements/content-sidebar/SidebarPanels.js | 162 +++++++---- .../__tests__/SidebarNav.test.js | 245 ++++++++++++---- .../__tests__/SidebarPanels.test.js | 263 +++++++++++++++--- src/elements/content-sidebar/flowTypes.js | 14 + .../content-sidebar/stories/BoxAISideBar.mdx | 5 - .../stories/BoxAISidebar.stories.tsx | 52 ---- .../tests/BoxAISidebar-visual.stories.tsx | 94 ------- .../tests/ContentSidebar-visual.stories.tsx | 16 +- 11 files changed, 700 insertions(+), 394 deletions(-) delete mode 100644 src/elements/content-sidebar/stories/BoxAISideBar.mdx delete mode 100644 src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx delete mode 100644 src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx diff --git a/src/elements/content-sidebar/ContentSidebar.js b/src/elements/content-sidebar/ContentSidebar.js index 6c218e689c..9eb094d926 100644 --- a/src/elements/content-sidebar/ContentSidebar.js +++ b/src/elements/content-sidebar/ContentSidebar.js @@ -46,7 +46,7 @@ import type { WithLoggerProps } from '../../common/types/logging'; import type { ElementsXhrError, RequestOptions, ErrorContextProps } from '../../common/types/api'; import type { MetadataEditor } from '../../common/types/metadata'; import type { StringMap, Token, User, BoxItem } from '../../common/types/core'; -import type { AdditionalSidebarTab } from './flowTypes'; +import type { AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes'; import type { FeatureConfig } from '../common/feature-checking'; // $FlowFixMe TypeScript file import type { Theme } from '../common/theming'; @@ -66,6 +66,7 @@ type Props = { className: string, clientName: string, currentUser?: User, + customSidebarPanels?: Array, defaultView: string, detailsSidebarProps: DetailsSidebarProps, docGenSidebarProps?: DocGenSidebarProps, @@ -354,6 +355,7 @@ class ContentSidebar extends React.Component { boxAISidebarProps, className, currentUser, + customSidebarPanels, defaultView, detailsSidebarProps, docGenSidebarProps, @@ -399,6 +401,7 @@ class ContentSidebar extends React.Component { boxAISidebarProps={boxAISidebarProps} className={className} currentUser={currentUser} + customSidebarPanels={customSidebarPanels} detailsSidebarProps={detailsSidebarProps} docGenSidebarProps={docGenSidebarProps} file={file} diff --git a/src/elements/content-sidebar/Sidebar.js b/src/elements/content-sidebar/Sidebar.js index 5a5babdf5c..1f84322e86 100644 --- a/src/elements/content-sidebar/Sidebar.js +++ b/src/elements/content-sidebar/Sidebar.js @@ -28,14 +28,14 @@ import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar'; import type { MetadataSidebarProps } from './MetadataSidebar'; import type { BoxAISidebarProps } from './BoxAISidebar'; import type { VersionsSidebarProps } from './versions'; -import type { AdditionalSidebarTab } from './flowTypes'; +import type { AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes'; import type { MetadataEditor } from '../../common/types/metadata'; import type { BoxItem, User } from '../../common/types/core'; import type { SignSidebarProps } from './SidebarNavSign'; import type { Errors } from '../common/flowTypes'; // $FlowFixMe TypeScript file import type { Theme } from '../common/theming'; -import { SIDEBAR_VIEW_DOCGEN } from '../../constants'; +import { SIDEBAR_VIEW_DOCGEN, SIDEBAR_VIEW_BOXAI } from '../../constants'; import API from '../../api'; type Props = { @@ -46,6 +46,7 @@ type Props = { className: string, currentUser?: User, currentUserError?: Errors, + customSidebarPanels?: Array, detailsSidebarProps: DetailsSidebarProps, docGenSidebarProps: DocGenSidebarProps, features: FeatureConfig, @@ -297,6 +298,7 @@ class Sidebar extends React.Component { className, currentUser, currentUserError, + customSidebarPanels, detailsSidebarProps, docGenSidebarProps, file, @@ -316,12 +318,14 @@ class Sidebar extends React.Component { versionsSidebarProps, }: Props = this.props; const isOpen = this.isOpen(); - const hasBoxAI = SidebarUtils.canHaveBoxAISidebar(this.props); const hasActivity = SidebarUtils.canHaveActivitySidebar(this.props); const hasDetails = SidebarUtils.canHaveDetailsSidebar(this.props); const hasMetadata = SidebarUtils.shouldRenderMetadataSidebar(this.props, metadataEditors); const hasSkills = SidebarUtils.shouldRenderSkillsSidebar(this.props, file); const onVersionHistoryClick = hasVersions ? this.handleVersionHistoryClick : this.props.onVersionHistoryClick; + const hasBoxAI = customSidebarPanels + ? !!customSidebarPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI) + : false; const styleClassName = classNames('be bcs', className, { 'bcs-is-open': isOpen, 'bcs-is-wider': hasBoxAI, @@ -340,11 +344,11 @@ class Sidebar extends React.Component { {hasNav && ( { boxAISidebarProps={boxAISidebarProps} currentUser={currentUser} currentUserError={currentUserError} + customPanels={customSidebarPanels} elementId={this.id} defaultPanel={defaultPanel} detailsSidebarProps={detailsSidebarProps} @@ -368,7 +373,6 @@ class Sidebar extends React.Component { getPreview={getPreview} getViewer={getViewer} hasActivity={hasActivity} - hasBoxAI={hasBoxAI} hasDetails={hasDetails} hasDocGen={docGenSidebarProps.isDocGenTemplate} hasMetadata={hasMetadata} diff --git a/src/elements/content-sidebar/SidebarNav.js b/src/elements/content-sidebar/SidebarNav.js index f687162326..b8b2eba19b 100644 --- a/src/elements/content-sidebar/SidebarNav.js +++ b/src/elements/content-sidebar/SidebarNav.js @@ -8,10 +8,6 @@ import * as React from 'react'; import { injectIntl } from 'react-intl'; import type { IntlShape } from 'react-intl'; import noop from 'lodash/noop'; -// $FlowFixMe -import { BoxAiLogo } from '@box/blueprint-web-assets/icons/Logo'; -// $FlowFixMe -import { Size6 } from '@box/blueprint-web-assets/tokens/tokens'; import { usePromptFocus } from '@box/box-ai-content-answers'; import AdditionalTabs from './additional-tabs'; import DocGenIcon from '../../icon/fill/DocGenIcon'; @@ -33,19 +29,18 @@ import { SIDEBAR_VIEW_METADATA, SIDEBAR_VIEW_SKILLS, } from '../../constants'; -import { useFeatureConfig } from '../common/feature-checking'; -import type { NavigateOptions, AdditionalSidebarTab } from './flowTypes'; +import type { NavigateOptions, AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes'; import type { InternalSidebarNavigation, InternalSidebarNavigationHandler } from '../common/types/SidebarNavigation'; import './SidebarNav.scss'; import type { SignSidebarProps } from './SidebarNavSign'; type Props = { additionalTabs?: Array, + customTabs?: Array, elementId: string, fileId: string, hasActivity: boolean, hasAdditionalTabs: boolean, - hasBoxAI: boolean, hasDetails: boolean, hasDocGen?: boolean, hasMetadata: boolean, @@ -62,11 +57,11 @@ type Props = { const SidebarNav = ({ additionalTabs, + customTabs, elementId, fileId, hasActivity, hasAdditionalTabs, - hasBoxAI, hasDetails, hasMetadata, hasSkills, @@ -81,8 +76,6 @@ const SidebarNav = ({ signSidebarProps, }: Props) => { const { enabled: hasBoxSign } = signSidebarProps || {}; - const { disabledTooltip: boxAIDisabledTooltip, showOnlyNavButton: showOnlyBoxAINavButton } = - useFeatureConfig('boxai.sidebar'); const { focusPrompt } = usePromptFocus('.be.bcs'); @@ -94,6 +87,139 @@ const SidebarNav = ({ focusPrompt(); } }; + const boxAiTab = customTabs ? customTabs.find(tab => tab.id === SIDEBAR_VIEW_BOXAI) : undefined; + const otherCustomTabs = customTabs ? customTabs.filter(tab => tab.id !== SIDEBAR_VIEW_BOXAI) : []; + const hasOtherCustomTabs = otherCustomTabs.length > 0; + + const sidebarTabs = [ + boxAiTab && ( + + {boxAiTab.icon && + (React.isValidElement(boxAiTab.icon) ? ( + boxAiTab.icon + ) : ( + // $FlowFixMe: Flow doesn't understand dynamic component creation + + ))} + + ), + hasActivity && ( + + + + ), + hasDetails && ( + + + + ), + hasSkills && ( + + + + ), + hasMetadata && ( + + + + ), + hasDocGen && ( + + + + ), + ]; + + // Filter out falsy values first + const visibleTabs = sidebarTabs.filter(Boolean); + + // Insert custom tabs - box-ai goes at the top, others at the end + if (hasOtherCustomTabs) { + // Add other custom tabs at the end + otherCustomTabs.forEach(customTab => { + const { + id: customTabId, + path: customTabPath, + icon: CustomTabIcon, + title: customTabTitle, + navButtonProps, + } = customTab; + + const customTabButton = ( + + {CustomTabIcon && + (React.isValidElement(CustomTabIcon) ? ( + CustomTabIcon + ) : ( + // $FlowFixMe: Flow doesn't understand dynamic component creation + + ))} + + ); + + visibleTabs.push(customTabButton); // Add at the end + }); + } return (
@@ -106,83 +232,7 @@ const SidebarNav = ({ onNavigate={onNavigate} routerDisabled={routerDisabled} > - {hasBoxAI && ( - - - - )} - {hasActivity && ( - - - - )} - {hasDetails && ( - - - - )} - {hasSkills && ( - - - - )} - {hasMetadata && ( - - - - )} - {hasDocGen && ( - - - - )} + {visibleTabs} {hasBoxSign && ( diff --git a/src/elements/content-sidebar/SidebarPanels.js b/src/elements/content-sidebar/SidebarPanels.js index e860dc8f98..f0c484cf08 100644 --- a/src/elements/content-sidebar/SidebarPanels.js +++ b/src/elements/content-sidebar/SidebarPanels.js @@ -30,7 +30,6 @@ import { SIDEBAR_VIEW_DOCGEN, SIDEBAR_VIEW_METADATA_REDESIGN, SIDEBAR_VIEW_BOXAI, - ORIGIN_BOXAI_SIDEBAR, } from '../../constants'; import type { DetailsSidebarProps } from './DetailsSidebar'; import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar'; @@ -42,10 +41,12 @@ import type { User, BoxItem } from '../../common/types/core'; import type { Errors } from '../common/flowTypes'; import type { FeatureConfig } from '../common/feature-checking'; import type { BoxAISidebarCache } from './types/BoxAISidebarTypes'; +import type { CustomSidebarPanel } from './flowTypes'; type Props = { activitySidebarProps: ActivitySidebarProps, boxAISidebarProps: BoxAISidebarProps, + customPanels?: Array, currentUser?: User, currentUserError?: Errors, defaultPanel?: string, @@ -86,7 +87,6 @@ type ElementRefType = { const BASE_EVENT_NAME = '_JS_LOADING'; const MARK_NAME_JS_LOADING_DETAILS = `${ORIGIN_DETAILS_SIDEBAR}${BASE_EVENT_NAME}`; const MARK_NAME_JS_LOADING_ACTIVITY = `${ORIGIN_ACTIVITY_SIDEBAR}${BASE_EVENT_NAME}`; -const MARK_NAME_JS_LOADING_BOXAI = `${ORIGIN_BOXAI_SIDEBAR}${BASE_EVENT_NAME}`; const MARK_NAME_JS_LOADING_SKILLS = `${ORIGIN_SKILLS_SIDEBAR}${BASE_EVENT_NAME}`; const MARK_NAME_JS_LOADING_METADATA = `${ORIGIN_METADATA_SIDEBAR}${BASE_EVENT_NAME}`; const MARK_NAME_JS_LOADING_METADATA_REDESIGNED = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}${BASE_EVENT_NAME}`; @@ -95,12 +95,20 @@ const MARK_NAME_JS_LOADING_VERSIONS = `${ORIGIN_VERSIONS_SIDEBAR}${BASE_EVENT_NA const URL_TO_FEED_ITEM_TYPE = { annotations: 'annotation', comments: 'comment', tasks: 'task' }; +// Default sidebar views in order (excluding BoxAI which is handled dynamically) +const DEFAULT_SIDEBAR_VIEWS = [ + SIDEBAR_VIEW_DOCGEN, + SIDEBAR_VIEW_SKILLS, + SIDEBAR_VIEW_ACTIVITY, + SIDEBAR_VIEW_DETAILS, + SIDEBAR_VIEW_METADATA, +]; + const LoadableDetailsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DETAILS, MARK_NAME_JS_LOADING_DETAILS); const LoadableActivitySidebar = SidebarUtils.getAsyncSidebarContent( SIDEBAR_VIEW_ACTIVITY, MARK_NAME_JS_LOADING_ACTIVITY, ); -const LoadableBoxAISidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_BOXAI, MARK_NAME_JS_LOADING_BOXAI); const LoadableSkillsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_SKILLS, MARK_NAME_JS_LOADING_SKILLS); const LoadableMetadataSidebar = SidebarUtils.getAsyncSidebarContent( SIDEBAR_VIEW_METADATA, @@ -125,6 +133,8 @@ class SidebarPanels extends React.Component { detailsSidebar: ElementRefType = React.createRef(); + customSidebars: Map = new Map(); + initialPanel: { current: null | string } = React.createRef(); metadataSidebar: ElementRefType = React.createRef(); @@ -173,8 +183,16 @@ class SidebarPanels extends React.Component { } }; - setBoxAiSidebarCacheValue = (key: 'agents' | 'encodedSession' | 'questions' | 'shouldShowLandingPage' | 'suggestedQuestions', value: any) => { - this.boxAiSidebarCache[key] = value; + getCustomSidebarRef = (panelId: string): ElementRefType => { + if (!this.customSidebars.has(panelId)) { + this.customSidebars.set(panelId, React.createRef()); + } + // Flow doesn't understand that we just set the value above, so we need to assert it exists + const ref = this.customSidebars.get(panelId); + if (!ref) { + throw new Error(`Failed to get or create ref for panel ${panelId}`); + } + return ref; }; /** @@ -200,6 +218,13 @@ class SidebarPanels extends React.Component { detailsSidebar.refresh(); } + // Refresh all custom sidebars + this.customSidebars.forEach(ref => { + if (ref.current) { + ref.current.refresh(); + } + }); + if (metadataSidebar) { metadataSidebar.refresh(); } @@ -209,10 +234,28 @@ class SidebarPanels extends React.Component { } } + getPanelOrder = (customPanels?: Array, shouldBoxAIBeDefaultPanel: boolean): string[] => { + // No custom panels - return default panels + if (!customPanels || customPanels.length === 0) { + return DEFAULT_SIDEBAR_VIEWS; + } + + // Separate box-ai custom panel from other custom panels + const boxAiCustomPanel = customPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI); + const otherCustomPanels = customPanels.filter(panel => panel.id !== SIDEBAR_VIEW_BOXAI); + const otherCustomPanelPaths = otherCustomPanels.map(panel => panel.path); + + if (boxAiCustomPanel && shouldBoxAIBeDefaultPanel) { + return [boxAiCustomPanel.path, ...DEFAULT_SIDEBAR_VIEWS, ...otherCustomPanelPaths]; + } + + return [...DEFAULT_SIDEBAR_VIEWS, ...customPanels.map(panel => panel.path)]; + }; + render() { const { activitySidebarProps, - boxAISidebarProps, + customPanels, currentUser, currentUserError, defaultPanel = '', @@ -225,7 +268,6 @@ class SidebarPanels extends React.Component { getPreview, getViewer, hasActivity, - hasBoxAI, hasDetails, hasDocGen, hasMetadata, @@ -243,51 +285,79 @@ class SidebarPanels extends React.Component { const isMetadataSidebarRedesignEnabled = isFeatureEnabled(features, 'metadata.redesign.enabled'); const isMetadataAiSuggestionsEnabled = isFeatureEnabled(features, 'metadata.aiSuggestions.enabled'); - const { shouldBeDefaultPanel: shouldBoxAIBeDefaultPanel, showOnlyNavButton: showOnlyBoxAINavButton } = - getFeatureConfig(features, 'boxai.sidebar'); + const { shouldBeDefaultPanel: shouldBoxAIBeDefaultPanel } = getFeatureConfig(features, 'boxai.sidebar'); - const canShowBoxAISidebarPanel = hasBoxAI && !showOnlyBoxAINavButton; + const hasCustomPanels = customPanels && customPanels.length > 0; + + // Build eligibility for custom panels + const customPanelEligibility = {}; + if (hasCustomPanels) { + // $FlowFixMe: customPanels is checked for existence in hasCustomPanels + customPanels.forEach(({ path, isDisabled }) => { + customPanelEligibility[path] = !isDisabled; + }); + } const panelsEligibility = { - [SIDEBAR_VIEW_BOXAI]: canShowBoxAISidebarPanel, [SIDEBAR_VIEW_DOCGEN]: hasDocGen, [SIDEBAR_VIEW_SKILLS]: hasSkills, [SIDEBAR_VIEW_ACTIVITY]: hasActivity, [SIDEBAR_VIEW_DETAILS]: hasDetails, [SIDEBAR_VIEW_METADATA]: hasMetadata, + ...customPanelEligibility, }; const showDefaultPanel: boolean = !!(defaultPanel && panelsEligibility[defaultPanel]); - if (!isOpen || (!hasBoxAI && !hasActivity && !hasDetails && !hasMetadata && !hasSkills && !hasVersions)) { + if ( + !isOpen || + (!hasActivity && + !hasDetails && + !hasMetadata && + !hasSkills && + !hasDocGen && + !hasVersions && + !hasCustomPanels) + ) { return null; } return ( - {canShowBoxAISidebarPanel && ( - { - this.handlePanelRender(SIDEBAR_VIEW_BOXAI); - return ( - - ); - }} - /> - )} + {hasCustomPanels && + // $FlowFixMe: customPanels is checked for existence in hasCustomPanels + customPanels.map(customPanel => { + const { + id: customPanelId, + path: customPanelPath, + component: CustomPanelComponent, + isDisabled, + } = customPanel; + + if (isDisabled || !CustomPanelComponent) { + return null; + } + + return ( + { + this.handlePanelRender(customPanelPath); + return CustomPanelComponent ? ( + + ) : null; + }} + /> + ); + })} {hasSkills && ( { if (showDefaultPanel) { redirect = defaultPanel; - } else if (canShowBoxAISidebarPanel && shouldBoxAIBeDefaultPanel) { - redirect = SIDEBAR_VIEW_BOXAI; - } else if (hasDocGen) { - redirect = SIDEBAR_VIEW_DOCGEN; - } else if (hasSkills) { - redirect = SIDEBAR_VIEW_SKILLS; - } else if (hasActivity) { - redirect = SIDEBAR_VIEW_ACTIVITY; - } else if (hasDetails) { - redirect = SIDEBAR_VIEW_DETAILS; - } else if (hasMetadata) { - redirect = SIDEBAR_VIEW_METADATA; - } else if (canShowBoxAISidebarPanel && !shouldBoxAIBeDefaultPanel) { - redirect = SIDEBAR_VIEW_BOXAI; + } else { + // Use panel order to determine redirect + const panelOrder = this.getPanelOrder(customPanels, shouldBoxAIBeDefaultPanel); + const firstEligiblePanel = panelOrder.find(panel => panelsEligibility[panel]); + if (firstEligiblePanel) { + redirect = firstEligiblePanel; + } } - return ; }} /> diff --git a/src/elements/content-sidebar/__tests__/SidebarNav.test.js b/src/elements/content-sidebar/__tests__/SidebarNav.test.js index 8c112a9c1f..ec4a2a3419 100644 --- a/src/elements/content-sidebar/__tests__/SidebarNav.test.js +++ b/src/elements/content-sidebar/__tests__/SidebarNav.test.js @@ -29,13 +29,23 @@ describe('elements/content-sidebar/SidebarNav', () => { ); }; + // Helper function to create Box AI custom tab + const createBoxAITab = (overrides = {}) => ({ + id: 'boxai', + path: 'boxai', + title: 'Box AI', + icon: null, + isDisabled: false, + navButtonProps: {}, + ...overrides, + }); + describe('individual tab rendering', () => { const TABS_CONFIG = { skills: { testId: 'sidebarskills', propName: 'hasSkills' }, details: { testId: 'sidebardetails', propName: 'hasDetails' }, activity: { testId: 'sidebaractivity', propName: 'hasActivity' }, metadata: { testId: 'sidebarmetadata', propName: 'hasMetadata' }, - boxai: { testId: 'sidebarboxai', propName: 'hasBoxAI' }, docgen: { testId: 'sidebardocgen', propName: 'hasDocGen' }, }; @@ -61,59 +71,13 @@ describe('elements/content-sidebar/SidebarNav', () => { }); }); - describe('should render box ai tab with correct disabled state and tooltip', () => { - test.each` - disabledTooltip | expectedTooltip - ${'tooltip msg'} | ${'tooltip msg'} - ${'another tooltip msg'} | ${'another tooltip msg'} - `( - 'given feature boxai.sidebar.showOnlyNavButton = true and boxai.sidebar.disabledTooltip = $disabledTooltip, should render box ai tab with disabled state and tooltip = $expectedTooltip', - async ({ disabledTooltip, expectedTooltip }) => { - const user = userEvent(); - - renderSidebarNav({ - features: { boxai: { sidebar: { disabledTooltip, showOnlyNavButton: true } } }, - props: { hasBoxAI: true }, - }); - - const button = screen.getByTestId('sidebarboxai'); - - await user.hover(button); - - expect(button).toHaveAttribute('aria-disabled', 'true'); - expect(screen.getByText(expectedTooltip)).toBeInTheDocument(); - }, - ); - - test('given feature boxai.sidebar.showOnlyNavButton = false, should render box ai tab with default tooltip', async () => { - const user = userEvent(); - - renderSidebarNav({ - features: { boxai: { sidebar: { showOnlyNavButton: false } } }, - props: { hasBoxAI: true }, - }); - - const button = screen.getByTestId('sidebarboxai'); - - await user.hover(button); - - expect(button).not.toHaveAttribute('aria-disabled'); - expect(screen.getByText('Box AI')).toBeInTheDocument(); - }); - }); - test('should call focusBoxAISidebarPrompt when clicked on Box AI Tab', async () => { const user = userEvent(); renderSidebarNav({ - features: { - boxai: { - sidebar: { - showOnlyNavButton: false, - }, - }, + props: { + customTabs: [createBoxAITab()], }, - props: { hasBoxAI: true }, }); const button = screen.getByTestId('sidebarboxai'); @@ -132,9 +96,9 @@ describe('elements/content-sidebar/SidebarNav', () => { path: '/activity', props: { hasActivity: true, - hasBoxAI: true, hasMetadata: true, hasSkills: true, + customTabs: [createBoxAITab()], }, }); @@ -176,4 +140,185 @@ describe('elements/content-sidebar/SidebarNav', () => { const boxSignSection = screen.getByRole('button', { name: /sign/i }); expect(boxSignSection).toBeInTheDocument(); }); + + describe('multiple customTabs rendering', () => { + // Helper function to create a generic custom tab + const createCustomTab = (id, overrides = {}) => ({ + id, + path: id, + title: `${id.charAt(0).toUpperCase()}${id.slice(1)} Tab`, + icon: null, + isDisabled: false, + navButtonProps: {}, + ...overrides, + }); + + test('should render multiple custom tabs including Box AI', () => { + const customTab1 = createCustomTab('customtab1'); + const customTab2 = createCustomTab('customtab2'); + const boxAiTab = createBoxAITab(); + + renderSidebarNav({ + props: { + customTabs: [boxAiTab, customTab1, customTab2], + }, + }); + + expect(screen.getByTestId('sidebarboxai')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarcustomtab1')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarcustomtab2')).toBeInTheDocument(); + + const navButtons = screen.getAllByRole('tab'); + expect(navButtons).toHaveLength(3); + + // Verify Box AI is rendered first in the DOM order + expect(navButtons[0]).toHaveAttribute('data-testid', 'sidebarboxai'); + expect(navButtons[1]).toHaveAttribute('data-testid', 'sidebarcustomtab1'); + expect(navButtons[2]).toHaveAttribute('data-testid', 'sidebarcustomtab2'); + }); + + test('should render Box AI first even when passed in different order', () => { + const customTab1 = createCustomTab('customtab1'); + const customTab2 = createCustomTab('customtab2'); + const boxAiTab = createBoxAITab(); + + renderSidebarNav({ + props: { + customTabs: [customTab1, boxAiTab, customTab2], + }, + }); + + const navButtons = screen.getAllByRole('tab'); + expect(navButtons).toHaveLength(3); + + expect(navButtons[0]).toHaveAttribute('data-testid', 'sidebarboxai'); + expect(navButtons[1]).toHaveAttribute('data-testid', 'sidebarcustomtab1'); + expect(navButtons[2]).toHaveAttribute('data-testid', 'sidebarcustomtab2'); + }); + + test('should render custom tabs with regular tabs', () => { + const customTab1 = createCustomTab('analytics'); + const boxAiTab = createBoxAITab(); + + renderSidebarNav({ + props: { + hasActivity: true, + hasMetadata: true, + customTabs: [boxAiTab, customTab1], + }, + }); + + expect(screen.getByTestId('sidebarboxai')).toBeInTheDocument(); + expect(screen.getByTestId('sidebaractivity')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarmetadata')).toBeInTheDocument(); + expect(screen.getByTestId('sidebaranalytics')).toBeInTheDocument(); + + const navButtons = screen.getAllByRole('tab'); + expect(navButtons).toHaveLength(4); + + // Verify order: Box AI first, regular tabs, then custom tabs at the end + expect(navButtons[0]).toHaveAttribute('data-testid', 'sidebarboxai'); + expect(navButtons[3]).toHaveAttribute('data-testid', 'sidebaranalytics'); + }); + + test('should handle custom tabs with different properties', () => { + const disabledTab = createCustomTab('disabled', { + isDisabled: true, + title: 'Disabled Tab', + }); + const customTitleTab = createCustomTab('customtitle', { + title: 'Custom Title Tab', + }); + + renderSidebarNav({ + props: { + customTabs: [disabledTab, customTitleTab], + }, + }); + + const disabledButton = screen.getByTestId('sidebardisabled'); + const customTitleButton = screen.getByTestId('sidebarcustomtitle'); + + expect(disabledButton).toBeInTheDocument(); + expect(disabledButton).toHaveAttribute('aria-disabled', 'true'); + expect(disabledButton).toHaveAttribute('aria-label', 'Disabled Tab'); + + expect(customTitleButton).toBeInTheDocument(); + expect(customTitleButton).toHaveAttribute('aria-label', 'Custom Title Tab'); + }); + + test('should handle custom tabs without Box AI', () => { + const customTab1 = createCustomTab('reports'); + const customTab2 = createCustomTab('settings'); + + renderSidebarNav({ + props: { + hasActivity: true, + customTabs: [customTab1, customTab2], + }, + }); + + expect(screen.getByTestId('sidebaractivity')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarreports')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarsettings')).toBeInTheDocument(); + + expect(screen.queryByTestId('sidebarboxai')).not.toBeInTheDocument(); + + const navButtons = screen.getAllByRole('tab'); + expect(navButtons).toHaveLength(3); + }); + + test('should handle empty customTabs array', () => { + renderSidebarNav({ + props: { + hasActivity: true, + hasMetadata: true, + customTabs: [], + }, + }); + + expect(screen.getByTestId('sidebaractivity')).toBeInTheDocument(); + expect(screen.getByTestId('sidebarmetadata')).toBeInTheDocument(); + + expect(screen.queryByTestId('sidebarboxai')).not.toBeInTheDocument(); + + const navButtons = screen.getAllByRole('tab'); + expect(navButtons).toHaveLength(2); + }); + + test('should handle customTabs with icons', () => { + const MockIcon = () =>
Icon
; + const tabWithIcon = createCustomTab('icontest', { + icon: MockIcon, + title: 'Tab with Icon', + }); + + renderSidebarNav({ + props: { + customTabs: [tabWithIcon], + }, + }); + + expect(screen.getByTestId('sidebaricontest')).toBeInTheDocument(); + expect(screen.getByTestId('mock-icon')).toBeInTheDocument(); + }); + + test('should call onPanelChange when custom tab is clicked', async () => { + const user = userEvent(); + const onPanelChangeMock = jest.fn(); + const customTab = createCustomTab('testclick'); + + renderSidebarNav({ + props: { + customTabs: [customTab], + onPanelChange: onPanelChangeMock, + }, + }); + + const button = screen.getByTestId('sidebartestclick'); + await user.click(button); + + expect(onPanelChangeMock).toHaveBeenCalledWith('testclick', false); + }); + }); }); diff --git a/src/elements/content-sidebar/__tests__/SidebarPanels.test.js b/src/elements/content-sidebar/__tests__/SidebarPanels.test.js index 06d31a82f3..25124e9b98 100644 --- a/src/elements/content-sidebar/__tests__/SidebarPanels.test.js +++ b/src/elements/content-sidebar/__tests__/SidebarPanels.test.js @@ -10,11 +10,26 @@ import { SidebarPanelsComponent as SidebarPanels } from '../SidebarPanels'; jest.mock('../SidebarUtils'); describe('elements/content-sidebar/SidebarPanels', () => { - const getWrapper = ({ path = '/', ...rest } = {}) => - mount( + // Helper function to create Box AI custom panel + const createBoxAIPanel = (overrides = {}) => { + // Create a mock component that matches what the test expects + const BoxAISidebar = () =>
; + BoxAISidebar.displayName = 'BoxAISidebar'; + + return { + id: 'boxai', + path: 'boxai', + component: BoxAISidebar, + isDisabled: false, + ...overrides, + }; + }; + + const getWrapper = ({ path = '/', customPanels, ...rest } = {}) => { + return mount( { }, }, ); - - const getSidebarPanels = ({ path = '/', ...props }) => ( - - - , - - ); + }; + + const getSidebarPanels = ({ path = '/', customPanels, ...props }) => { + return ( + + + + ); + }; describe('render', () => { test.each` @@ -74,7 +91,8 @@ describe('elements/content-sidebar/SidebarPanels', () => { ${'/nonsense'} | ${'DocGenSidebar'} ${'/'} | ${'DocGenSidebar'} `('should render $sidebar given the path $path', ({ path, sidebar }) => { - const wrapper = getWrapper({ path }); + const customPanels = sidebar === 'BoxAISidebar' ? [createBoxAIPanel()] : undefined; + const wrapper = getWrapper({ path, customPanels }); expect(wrapper.exists(sidebar)).toBe(true); }); @@ -87,6 +105,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { ({ path, sidebar }) => { const wrapper = getWrapper({ features: { boxai: { sidebar: { shouldBeDefaultPanel: true } } }, + customPanels: [createBoxAIPanel()], path, }); expect(wrapper.exists(sidebar)).toBe(true); @@ -107,10 +126,12 @@ describe('elements/content-sidebar/SidebarPanels', () => { 'should render $sidebar and call onPanelChange with $expectedPanelName given the path = "/" and defaultPanel = $defaultPanel', ({ defaultPanel, sidebar, expectedPanelName }) => { const onPanelChange = jest.fn(); + const customPanels = expectedPanelName === 'boxai' ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ defaultPanel, onPanelChange, + customPanels, }), ); expect(screen.getByTestId(sidebar)).toBeInTheDocument(); @@ -131,6 +152,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { defaultPanel, features: { boxai: { sidebar: { shouldBeDefaultPanel: true } } }, onPanelChange, + customPanels: [createBoxAIPanel()], }), ); expect(screen.getByTestId(sidebar)).toBeInTheDocument(); @@ -162,6 +184,8 @@ describe('elements/content-sidebar/SidebarPanels', () => { expectedPanelName, }) => { const onPanelChange = jest.fn(); + // Only provide BoxAI custom panel when hasBoxAI is true and showOnlyNavButton is false + const customPanels = hasBoxAI && !showOnlyBoxAINavButton ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ features: { boxai: { sidebar: { showOnlyNavButton: showOnlyBoxAINavButton } } }, @@ -171,7 +195,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { hasMetadata, hasSkills, hasDocGen, - hasBoxAI, + customPanels, onPanelChange, }), ); @@ -204,6 +228,8 @@ describe('elements/content-sidebar/SidebarPanels', () => { expectedPanelName, }) => { const onPanelChange = jest.fn(); + // Only provide BoxAI custom panel when hasBoxAI is true and showOnlyNavButton is false + const customPanels = hasBoxAI && !showOnlyBoxAINavButton ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ features: { @@ -220,7 +246,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { hasMetadata, hasSkills, hasDocGen, - hasBoxAI, + customPanels, onPanelChange, }), ); @@ -253,11 +279,13 @@ describe('elements/content-sidebar/SidebarPanels', () => { 'should render $sidebar and call onPanelChange with $expectedPanelName given the path = $path and defaultPanel = $defaultPanel', ({ path, sidebar, defaultPanel, expectedPanelName }) => { const onPanelChange = jest.fn(); + const customPanels = expectedPanelName === 'boxai' ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ defaultPanel, onPanelChange, path, + customPanels, }), ); expect(screen.getByTestId(sidebar)).toBeInTheDocument(); @@ -289,10 +317,12 @@ describe('elements/content-sidebar/SidebarPanels', () => { ${'/'} | ${'docgen'} `('should call onPanelChange with $expectedPanelName given the path = $path', ({ path, expectedPanelName }) => { const onPanelChange = jest.fn(); + const customPanels = expectedPanelName === 'boxai' ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ path, onPanelChange, + customPanels, }), ); expect(onPanelChange).toHaveBeenCalledWith(expectedPanelName, true); @@ -311,6 +341,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { features: { boxai: { sidebar: { shouldBeDefaultPanel: true } } }, path, onPanelChange, + customPanels: [createBoxAIPanel()], }), ); expect(onPanelChange).toHaveBeenCalledWith(expectedPanelName, true); @@ -352,16 +383,17 @@ describe('elements/content-sidebar/SidebarPanels', () => { expectedPanelName, }) => { const onPanelChange = jest.fn(); + const customPanels = hasBoxAI && !showOnlyBoxAINavButton ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ features: { boxai: { sidebar: { showOnlyNavButton: showOnlyBoxAINavButton } } }, hasActivity, - hasBoxAI, hasDetails, hasDocGen, hasMetadata, hasSkills, hasVersions, + customPanels, onPanelChange, path, }), @@ -405,6 +437,7 @@ describe('elements/content-sidebar/SidebarPanels', () => { expectedPanelName, }) => { const onPanelChange = jest.fn(); + const customPanels = hasBoxAI && !showOnlyBoxAINavButton ? [createBoxAIPanel()] : undefined; render( getSidebarPanels({ features: { @@ -416,12 +449,12 @@ describe('elements/content-sidebar/SidebarPanels', () => { }, }, hasActivity, - hasBoxAI, hasDetails, hasDocGen, hasMetadata, hasSkills, hasVersions, + customPanels, onPanelChange, path, }), @@ -457,12 +490,12 @@ describe('elements/content-sidebar/SidebarPanels', () => { test('should render nothing if all sidebars are disabled', () => { const wrapper = getWrapper({ - hasBoxAI: false, hasActivity: false, hasDetails: false, hasMetadata: false, hasSkills: false, hasVersions: false, + hasDocGen: false, }); expect(wrapper.isEmptyRender()).toBe(true); }); @@ -535,28 +568,28 @@ describe('elements/content-sidebar/SidebarPanels', () => { }); describe('boxai sidebar', () => { - test('should render, given feature boxai.sidebar.shouldBeDefaultPanel = true and hasBoxAI = true and feature boxai.sidebar.showOnlyNavButton = false', () => { + test('should render, given feature boxai.sidebar.shouldBeDefaultPanel = true and customPanels includes Box AI and feature boxai.sidebar.showOnlyNavButton = false', () => { render( getSidebarPanels({ features: { boxai: { sidebar: { shouldBeDefaultPanel: true, showOnlyNavButton: false } } }, - hasBoxAI: true, + customPanels: [createBoxAIPanel()], }), ); expect(screen.getByTestId('boxai-sidebar')).toBeInTheDocument(); }); test.each` - hasBoxAI | showOnlyNavButton - ${true} | ${true} - ${false} | ${true} - ${false} | ${false} + customPanels | showOnlyNavButton + ${[createBoxAIPanel()]} | ${true} + ${undefined} | ${true} + ${undefined} | ${false} `( - 'should not render, given hasBoxAI = $hasBoxAI and feature boxai.sidebar.showOnlyNavButton = $showOnlyNavButton', - ({ hasBoxAI, showOnlyNavButton }) => { + 'should not render, given customPanels = $customPanels and feature boxai.sidebar.showOnlyNavButton = $showOnlyNavButton', + ({ customPanels, showOnlyNavButton }) => { render( getSidebarPanels({ features: { boxai: { sidebar: { showOnlyNavButton } } }, - hasBoxAI, + customPanels, }), ); expect(screen.queryByTestId('boxai-sidebar')).not.toBeInTheDocument(); @@ -626,4 +659,162 @@ describe('elements/content-sidebar/SidebarPanels', () => { expect(onVersionChange).toBeCalledWith(null); }); }); + + describe('multiple customPanels rendering', () => { + // Helper function to create custom panels for testing + const createCustomPanel = (id, overrides = {}) => ({ + id, + path: id, + component: () =>
, + isDisabled: false, + ...overrides, + }); + + test('should render multiple custom panels including Box AI', () => { + const customPanels = [ + createBoxAIPanel(), + createCustomPanel('custom1', { title: 'Custom Panel 1' }), + createCustomPanel('custom2', { title: 'Custom Panel 2' }), + ]; + + const wrapper = getWrapper({ + path: '/boxai', + customPanels, + }); + + // Box AI should be rendered (since path is /boxai) + expect(wrapper.exists('BoxAISidebar')).toBe(true); + + // Other custom panels should not be rendered + expect(wrapper.exists('CustomPanel[id="custom1"]')).toBe(false); + expect(wrapper.exists('CustomPanel[id="custom2"]')).toBe(false); + }); + + test('should render custom panels with regular panels', () => { + const customPanels = [ + createCustomPanel('analytics', { title: 'Analytics Panel' }), + createCustomPanel('reports', { title: 'Reports Panel' }), + ]; + + const wrapper = getWrapper({ + path: '/analytics', + customPanels, + hasActivity: true, + hasMetadata: true, + }); + + // Custom panel should be rendered + expect(wrapper.find('div[data-testid="analytics-sidebar"]')).toHaveLength(1); + }); + + test('should handle custom panels with different properties', () => { + const disabledPanel = createCustomPanel('disabled', { + isDisabled: true, + title: 'Disabled Panel', + }); + const enabledPanel = createCustomPanel('enabled', { + title: 'Enabled Panel', + }); + + const wrapper = getWrapper({ + path: '/enabled', + customPanels: [disabledPanel, enabledPanel], + }); + + // Enabled panel should be rendered + expect(wrapper.find('div[data-testid="enabled-sidebar"]')).toHaveLength(1); + + // Try to navigate to disabled panel - should fall back to default + const disabledWrapper = getWrapper({ + path: '/disabled', + customPanels: [disabledPanel, enabledPanel], + }); + + // Should not render the disabled panel, should fall back to default + expect(disabledWrapper.find('div[data-testid="disabled-sidebar"]')).toHaveLength(0); + }); + + test('should render custom panels without Box AI', () => { + const customPanels = [ + createCustomPanel('workflow', { title: 'Workflow Panel' }), + createCustomPanel('integration', { title: 'Integration Panel' }), + ]; + + const wrapper = getWrapper({ + path: '/workflow', + customPanels, + }); + + // Custom panel should be rendered + expect(wrapper.find('div[data-testid="workflow-sidebar"]')).toHaveLength(1); + }); + + test('should handle empty custom panels array', () => { + const wrapper = getWrapper({ + path: '/activity', + customPanels: [], + hasActivity: true, + }); + + // Should render regular activity panel + expect(wrapper.exists('ActivitySidebar')).toBe(true); + }); + + test('should handle custom panels with Box AI in different positions', () => { + const customPanels = [ + createCustomPanel('before', { title: 'Before Box AI' }), + createBoxAIPanel(), + createCustomPanel('after', { title: 'After Box AI' }), + ]; + + // Test Box AI panel + const boxAIWrapper = getWrapper({ + path: '/boxai', + customPanels, + }); + expect(boxAIWrapper.exists('BoxAISidebar')).toBe(true); + + // Test custom panel before Box AI + const beforeWrapper = getWrapper({ + path: '/before', + customPanels, + }); + expect(beforeWrapper.find('div[data-testid="before-sidebar"]')).toHaveLength(1); + + // Test custom panel after Box AI + const afterWrapper = getWrapper({ + path: '/after', + customPanels, + }); + expect(afterWrapper.find('div[data-testid="after-sidebar"]')).toHaveLength(1); + }); + + test('should call onPanelChange with correct panel names for custom panels', () => { + const onPanelChange = jest.fn(); + const customPanels = [createCustomPanel('dashboard', { title: 'Dashboard' }), createBoxAIPanel()]; + + // Test custom panel + render( + getSidebarPanels({ + path: '/dashboard', + customPanels, + onPanelChange, + }), + ); + expect(onPanelChange).toHaveBeenCalledWith('dashboard', true); + + // Reset mock + onPanelChange.mockClear(); + + // Test Box AI panel + render( + getSidebarPanels({ + path: '/boxai', + customPanels, + onPanelChange, + }), + ); + expect(onPanelChange).toHaveBeenCalledWith('boxai', true); + }); + }); }); diff --git a/src/elements/content-sidebar/flowTypes.js b/src/elements/content-sidebar/flowTypes.js index d4692e6ca3..3178abb6ea 100644 --- a/src/elements/content-sidebar/flowTypes.js +++ b/src/elements/content-sidebar/flowTypes.js @@ -33,6 +33,18 @@ type AdditionalSidebarTab = { icon?: React.Node, }; +type CustomSidebarPanel = { + id: string, + path: string, + component: React.ComponentType, + title: string, + isDisabled?: boolean, + icon?: React.ComponentType | React.Element, + navButtonProps?: Object, +}; + +type CustomSidebarPanels = Array; + type Translations = { onTranslate?: Function, translationEnabled?: boolean, @@ -49,6 +61,8 @@ type FileAccessStats = { export type { ClassificationInfo, ContentInsights, + CustomSidebarPanel, + CustomSidebarPanels, NavigateOptions, AdditionalSidebarTab, AdditionalSidebarTabFtuxData, diff --git a/src/elements/content-sidebar/stories/BoxAISideBar.mdx b/src/elements/content-sidebar/stories/BoxAISideBar.mdx deleted file mode 100644 index 75e027e24a..0000000000 --- a/src/elements/content-sidebar/stories/BoxAISideBar.mdx +++ /dev/null @@ -1,5 +0,0 @@ -import { Meta } from '@storybook/addon-docs/blocks'; - -import BoxAISidebarStories from './BoxAISidebar.stories'; - - diff --git a/src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx b/src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx deleted file mode 100644 index 4bdfe190f7..0000000000 --- a/src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import ContentSidebar from '../ContentSidebar'; - -const mockFeatures = { - 'boxai.sidebar.enabled': true, -}; - -export const basic = {}; - -export const withFileTypeNotSupported = { - args: { - features: { - boxai: { - sidebar: { - disabledTooltip: 'Box AI is not currently supported for this file type', - enabled: true, - showOnlyNavButton: true, - }, - }, - }, - }, -}; - -export default { - title: 'Elements/ContentSidebar/BoxAISidebar', - component: ContentSidebar, - args: { - features: mockFeatures, - fileId: global.FILE_ID, - token: global.TOKEN, - boxAISidebarProps: { - createSessionRequest: () => ({ encodedSession: '1234' }), - fetchTimeout: { initial: 20000 }, - getAgentConfig: () => ({}), - getAIStudioAgents: () => ({}), - getAnswer: () => ({}), - getAnswerStreaming: () => ({}), - getSuggestedQuestions: null, - hostAppName: 'storybook-test', - isAgentSelectorEnabled: false, - isAIStudioAgentSelectorEnabled: true, - isCitationsEnabled: true, - isDebugModeEnabled: true, - isFeedbackEnabled: true, - isIntelligentQueryMode: false, - isMarkdownEnabled: true, - isStopResponseEnabled: true, - isStreamingEnabled: false, - items: [{ id: '123', name: 'Document (PDF).pdf', type: 'file', fileType: 'pdf', status: 'supported' }], - recordAction: () => ({}), - }, - }, -}; diff --git a/src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx deleted file mode 100644 index 17b627dec6..0000000000 --- a/src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { type StoryObj, type Meta } from '@storybook/react'; -import { expect, within } from 'storybook/test'; -import { http, HttpResponse } from 'msw'; -import type { HttpHandler } from 'msw'; -import ContentSidebar from '../../ContentSidebar'; -import BoxAISidebar from '../../BoxAISidebar'; -import { mockFileRequest, mockUserRequest } from '../../../common/__mocks__/mockRequests'; - -const mockFeatures = { - 'boxai.sidebar.enabled': true, -}; - -export const basic: StoryObj = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const clearButton = await canvas.findByRole('button', { name: 'Clear conversation' }); - expect(clearButton).toBeInTheDocument(); - - expect(await canvas.findByText(/Welcome to Box AI/i)).toBeInTheDocument(); - expect(await canvas.findByText('Chat cleared when you close content')).toBeInTheDocument(); - expect(await canvas.findByPlaceholderText('Ask Box AI')).toBeInTheDocument(); - expect(await canvas.findByText('Summarize this document')).toBeInTheDocument(); - expect(await canvas.findByText('What are the key takeaways?')).toBeInTheDocument(); - expect(await canvas.findByText('How can this document be improved?')).toBeInTheDocument(); - expect(await canvas.findByText('Are there any next steps defined?')).toBeInTheDocument(); - }, -}; - -const meta: Meta & { parameters: { msw: { handlers: HttpHandler[] } } } = { - title: 'Elements/ContentSidebar/BoxAISidebar/tests/visual-regression-tests', - component: ContentSidebar, - args: { - features: mockFeatures, - fileId: global.FILE_ID, - token: global.TOKEN, - boxAISidebarProps: { - createSessionRequest: () => ({ encodedSession: '1234' }), - fetchTimeout: { initial: 20000 }, - getAgentConfig: () => ({}), - getAIStudioAgents: () => ({}), - getAnswer: () => ({}), - getAnswerStreaming: () => ({}), - getSuggestedQuestions: null, - hostAppName: 'storybook-test', - isAgentSelectorEnabled: false, - isAIStudioAgentSelectorEnabled: true, - isCitationsEnabled: true, - isFeedbackEnabled: true, - isDebugModeEnabled: true, - isIntelligentQueryMode: false, - isMarkdownEnabled: true, - isStopResponseEnabled: true, - isStreamingEnabled: false, - items: [{ id: '123', name: 'Document (PDF).pdf', type: 'file', fileType: 'pdf', status: 'supported' }], - localizedQuestions: [ - { - id: 'suggested-question-1', - label: 'Summarize this document', - prompt: 'Summarize this document', - }, - { - id: 'suggested-question-2', - label: 'What are the key takeaways?', - prompt: 'What are the key takeaways?', - }, - { - id: 'suggested-question-3', - label: 'How can this document be improved?', - prompt: 'How can this document be improved?', - }, - { - id: 'suggested-question-4', - label: 'Are there any next steps defined?', - prompt: 'Are there any next steps defined?', - }, - ], - recordAction: () => ({}), - }, - }, - parameters: { - msw: { - handlers: [ - http.get(mockUserRequest.url, () => { - return HttpResponse.json(mockUserRequest.response); - }), - http.get(mockFileRequest.url, () => { - return HttpResponse.json(mockFileRequest.response); - }), - ], - }, - }, -}; - -export default meta; diff --git a/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx index c572e45ea6..64222fe01e 100644 --- a/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx @@ -1,6 +1,5 @@ -import { type StoryObj } from '@storybook/react'; +import type { StoryObj } from '@storybook/react'; import ContentSidebarComponent from '../../ContentSidebar'; -import BoxAISidebar from '../../BoxAISidebarContent'; export default { title: 'Elements/ContentSidebar/tests/visual-regression-tests', @@ -29,21 +28,10 @@ export const Modernization = { }, }; -export const ContentSidebarWithBoxAIDisabled: StoryObj = { +export const ContentSidebar: StoryObj = { args: { features: { ...global.FEATURE_FLAGS, - 'boxai.sidebar.enabled': false, - 'metadata.redesign.enabled': true, - }, - }, -}; - -export const ContentSidebarWithBoxAIEnabled: StoryObj = { - args: { - features: { - ...global.FEATURE_FLAGS, - 'boxai.sidebar.enabled': true, 'metadata.redesign.enabled': true, }, },