From 9c7aab012966bf1dc1475d0c5e6819d552fc0aa5 Mon Sep 17 00:00:00 2001 From: kkuliczkowski-box <105496865+kkuliczkowski-box@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:47:28 +0100 Subject: [PATCH] feat(content-sidebar): Expand Box AI Sidebar to Modal (#3878) * feat(content-sidebar): Expand Box AI Sidebar to Modal * feat(content-sidebar): Expand Box AI Sidebar to Modal --- i18n/en-US.properties | 2 + package.json | 8 +- src/elements/common/messages.js | 5 + .../content-sidebar/BoxAISidebar.scss | 27 +++- src/elements/content-sidebar/BoxAISidebar.tsx | 21 ++- .../content-sidebar/BoxAISidebarContent.tsx | 129 +++++++++++++----- .../__tests__/BoxAISidebar.test.tsx | 40 ++++++ yarn.lock | 47 ++----- 8 files changed, 196 insertions(+), 83 deletions(-) diff --git a/i18n/en-US.properties b/i18n/en-US.properties index 850b882868..f8449d69af 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -644,6 +644,8 @@ be.sidebarAccessStats = Access Stats be.sidebarActivityTitle = Activity # Generic Box AI content type opened used in welcome message and placeholder be.sidebarBoxAIContent = content +# Label for button that triggers switch to Box AI Modal +be.sidebarBoxAISwitchToModalView = Switch to modal view # Title for the preview Box AI feed. be.sidebarBoxAITitle = Box AI # Title for the sidebar content insights. diff --git a/package.json b/package.json index 94d129eafe..558dabb937 100644 --- a/package.json +++ b/package.json @@ -124,8 +124,8 @@ "@babel/types": "^7.24.7", "@box/blueprint-web": "^7.36.3", "@box/blueprint-web-assets": "^4.28.0", - "@box/box-ai-agent-selector": "^0.15.8", - "@box/box-ai-content-answers": "^0.79.3", + "@box/box-ai-agent-selector": "^0.22.0", + "@box/box-ai-content-answers": "^0.85.2", "@box/cldr-data": "^34.2.0", "@box/frontend": "^10.0.0", "@box/item-icon": "^0.9.58", @@ -306,8 +306,8 @@ "peerDependencies": { "@box/blueprint-web": "^7.36.3", "@box/blueprint-web-assets": "^4.28.0", - "@box/box-ai-agent-selector": "^0.15.8", - "@box/box-ai-content-answers": "^0.79.3", + "@box/box-ai-agent-selector": "^0.22.0", + "@box/box-ai-content-answers": "^0.85.2", "@box/cldr-data": ">=34.2.0", "@box/item-icon": "^0.9.58", "@box/metadata-editor": "^0.79.1", diff --git a/src/elements/common/messages.js b/src/elements/common/messages.js index c443242cbc..8d26237e9c 100644 --- a/src/elements/common/messages.js +++ b/src/elements/common/messages.js @@ -447,6 +447,11 @@ const messages = defineMessages({ description: 'Generic Box AI content type opened used in welcome message and placeholder', defaultMessage: 'content', }, + sidebarBoxAISwitchToModalView: { + id: 'be.sidebarBoxAISwitchToModalView', + description: 'Label for button that triggers switch to Box AI Modal', + defaultMessage: 'Switch to modal view', + }, sidebarActivityTitle: { id: 'be.sidebarActivityTitle', description: 'Title for the preview activity feed.', diff --git a/src/elements/content-sidebar/BoxAISidebar.scss b/src/elements/content-sidebar/BoxAISidebar.scss index e0cca3580a..5bd4d4cbdc 100644 --- a/src/elements/content-sidebar/BoxAISidebar.scss +++ b/src/elements/content-sidebar/BoxAISidebar.scss @@ -9,6 +9,14 @@ $agentSelectorSidebarWidth: 211px; height: 100%; max-height: 100%; + &.with-modal-open { + display: none; + } + + .bcs-content-header { + margin: 0 tokens.$space-4; + } + .bcs-scroll-content { width: auto; height: 100%; @@ -34,13 +42,20 @@ $agentSelectorSidebarWidth: 211px; } } + // Limit the width of Agent Selector to accomodate action buttons + .bcs-BoxAISidebar-agent-selector-container { + max-width: 217px; + } + + @include breakpoint($medium-screen) { + .bcs-BoxAISidebar-agent-selector-container { + max-width: none; + } + } + .bcs-BoxAISidebar-chat-actions { display: flex; justify-content: right; - - .bcs-BoxAISidebar-expand { - margin-left: tokens.$space-2; - } } .sidebar-chip { @@ -51,5 +66,9 @@ $agentSelectorSidebarWidth: 211px; width: 100%; } } + + .bcs-BoxAISidebar-expand { + margin-left: tokens.$space-2; + } } } diff --git a/src/elements/content-sidebar/BoxAISidebar.tsx b/src/elements/content-sidebar/BoxAISidebar.tsx index a383b84d93..dfd888043f 100644 --- a/src/elements/content-sidebar/BoxAISidebar.tsx +++ b/src/elements/content-sidebar/BoxAISidebar.tsx @@ -16,16 +16,19 @@ export interface BoxAISidebarContextValues { cache: { encodedSession?: string | null; questions?: QuestionType[] }; contentName: string; elementId: string; + fileExtension: string; isStopResponseEnabled: boolean; + itemSize?: string; recordAction: (params: RecordActionType) => void; setCacheValue: (key: 'encodedSession' | 'questions', value: string | null | QuestionType[]) => void; - userInfo: { name: string, avatarURL: string }; + userInfo: { name: string; avatarURL: string }; } export const BoxAISidebarContext = React.createContext({ cache: null, contentName: '', elementId: '', + fileExtension: '', isStopResponseEnabled: false, recordAction: noop, setCacheValue: noop, @@ -66,7 +69,8 @@ export interface BoxAISidebarProps { isResetChatEnabled: boolean; isStopResponseEnabled?: boolean; isStreamingEnabled: boolean; - userInfo: { name: string, avatarURL: string }; + itemSize?: string; + userInfo: { name: string; avatarURL: string }; recordAction: (params: RecordActionType) => void; setCacheValue: (key: 'encodedSession' | 'questions', value: string | null | QuestionType[]) => void; } @@ -81,6 +85,7 @@ const BoxAISidebar = (props: BoxAISidebarProps) => { getSuggestedQuestions, isIntelligentQueryMode, isStopResponseEnabled, + itemSize, recordAction, setCacheValue, userInfo, @@ -113,7 +118,17 @@ const BoxAISidebar = (props: BoxAISidebarProps) => { // BoxAISidebarContent is using withApiWrapper that is not passing all provided props, // that's why we need to use provider to pass other props { + setIsModalOpen(false); + }; + + const onSwitchToModalClick = () => { + setIsModalOpen(true); + }; + React.useEffect(() => { if (!encodedSession && createSession) { createSession(); @@ -83,15 +104,16 @@ function BoxAISidebarContent(props: ApiWrapperProps) { {formatMessage(messages.sidebarBoxAITitle)} {isAIStudioAgentSelectorEnabled && ( - +
+ +
)} ); @@ -101,35 +123,70 @@ function BoxAISidebarContent(props: ApiWrapperProps) { <> {renderBoxAISidebarTitle()} {isResetChatEnabled && } + + + ); return ( - -
- -
-
+ <> + +
+ +
+
+ +
); } diff --git a/src/elements/content-sidebar/__tests__/BoxAISidebar.test.tsx b/src/elements/content-sidebar/__tests__/BoxAISidebar.test.tsx index 8943bc76da..82dcf106e5 100644 --- a/src/elements/content-sidebar/__tests__/BoxAISidebar.test.tsx +++ b/src/elements/content-sidebar/__tests__/BoxAISidebar.test.tsx @@ -73,6 +73,7 @@ describe('elements/content-sidebar/BoxAISidebar', () => { getAnswerStreaming: jest.fn(), getSuggestedQuestions: jest.fn(), hostAppName: 'appName', + itemSize: '1234', isAgentSelectorEnabled: false, isAIStudioAgentSelectorEnabled: true, isCitationsEnabled: true, @@ -93,6 +94,15 @@ describe('elements/content-sidebar/BoxAISidebar', () => { }); }; + beforeAll(() => { + // Required to pass Blueprint Interactivity test for buttons with tooltip + Object.defineProperty(HTMLElement.prototype, 'offsetParent', { + get() { + return this.parentNode; + }, + }); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -180,4 +190,34 @@ describe('elements/content-sidebar/BoxAISidebar', () => { expect(tooltip).toBeInTheDocument(); }); + + test('should have accessible "Switch to modal view" button', async () => { + await renderComponent(); + + expect(screen.getByRole('button', { name: 'Switch to modal view' })).toBeInTheDocument(); + }); + + test('should display "Switch to modal view" tooltip', async () => { + await renderComponent(); + + const button = screen.getByRole('button', { name: 'Switch to modal view' }); + await userEvent.hover(button); + const tooltip = await screen.findByRole('tooltip', { name: 'Switch to modal view' }); + + expect(tooltip).toBeInTheDocument(); + }); + + test('should open Intelligence Modal when clicking on "Switch to modal view" button and close when clicking "Switch to sidebar view"', async () => { + await renderComponent(); + + const switchToModalButton = screen.getByRole('button', { name: 'Switch to modal view' }); + await userEvent.click(switchToModalButton); + + expect(await screen.findByTestId('content-answers-modal')).toBeInTheDocument(); + + const switchToSidebarButton = screen.getByRole('button', { name: 'Switch to sidebar view' }); + await userEvent.click(switchToSidebarButton); + + expect(screen.queryByTestId('content-answers-modal')).not.toBeInTheDocument(); + }); }); diff --git a/yarn.lock b/yarn.lock index 3f79b6e874..f036fa5456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1489,15 +1489,15 @@ tabbable "^4.0.0" type-fest "^3.2.0" -"@box/box-ai-agent-selector@^0.15.8": - version "0.15.8" - resolved "https://registry.yarnpkg.com/@box/box-ai-agent-selector/-/box-ai-agent-selector-0.15.8.tgz#30484b819f2c57da7a5e461f7d66a8c9fdbcfd18" - integrity sha512-bFwrE82P7ePxKQ/x5H4Q77gdGYkFhNn7ejOi7IfEKU8ekbJd8cVI5d/T/luxsrG74bEG0sLDswFzMGYbI8H5lg== +"@box/box-ai-agent-selector@^0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@box/box-ai-agent-selector/-/box-ai-agent-selector-0.22.0.tgz#d91e4270766a9f7e95166808c4ed735247d196c2" + integrity sha512-eDj088pwuG9OIj+Ut4g7tz1jgdOcZjUDH+vt+hAazCIZJRBnzft6thDVUlrh4XeQ00cdoopIxfNkP1Un937SxA== -"@box/box-ai-content-answers@^0.79.3": - version "0.79.4" - resolved "https://registry.yarnpkg.com/@box/box-ai-content-answers/-/box-ai-content-answers-0.79.4.tgz#53f6d6af89eb5167a87101440cf053510593287f" - integrity sha512-4hAvEOisVJsvO380C9WahoCmqe3UVpsMzPxJmpdgtXgm/2k9fsCg2yQLPFDNeIJSP2pSEjLpPfQbKl4jVzb7Tw== +"@box/box-ai-content-answers@^0.85.2": + version "0.85.2" + resolved "https://registry.yarnpkg.com/@box/box-ai-content-answers/-/box-ai-content-answers-0.85.2.tgz#f8cdc874aa4086870a940189f8fb8844d8d95b95" + integrity sha512-CLSZ+24cRKkUTJn1vQiy414DwP2/QbQWXnBsHsU1szOTJ1QNIRIsKY01lTtEmxurk9ax66xvbMGn5ShcfMZt4A== "@box/cldr-data@^34.2.0": version "34.8.0" @@ -22369,7 +22369,7 @@ string-replace-loader@^3.1.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -22387,15 +22387,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -22534,7 +22525,7 @@ stringify-package@^1.0.0, stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -22562,13 +22553,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -24768,7 +24752,7 @@ worker-farm@^1.6.0, worker-farm@^1.7.0: dependencies: errno "~0.1.7" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -24811,15 +24795,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"