diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.scss b/src/elements/content-sidebar/MetadataSidebarRedesign.scss index 4f300c8cd9..786463fcf5 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.scss +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.scss @@ -4,6 +4,11 @@ border-left: 1px solid $gray-10; .bcs-MetadataSidebarRedesign-content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; padding: $space-2; background-color: $gray-02; } diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index c977dd7d67..db1502552d 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import flow from 'lodash/flow'; import { FormattedMessage, useIntl } from 'react-intl'; import { InlineError, LoadingIndicator } from '@box/blueprint-web'; -import { AddMetadataTemplateDropdown } from '@box/metadata-editor'; +import { AddMetadataTemplateDropdown , MetadataEmptyState } from '@box/metadata-editor'; import API from '../../api'; import SidebarContent from './SidebarContent'; @@ -31,6 +31,7 @@ const MARK_NAME_JS_READY = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}_${EVENT_JS_READY mark(MARK_NAME_JS_READY); export interface ExternalProps { + isBoxAiSuggestionsEnabled: boolean; isFeatureEnabled: boolean; } @@ -53,12 +54,24 @@ export interface MetadataSidebarRedesignProps extends PropsWithoutContext, Error api: API; } -function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEnabled }: MetadataSidebarRedesignProps) { +function MetadataSidebarRedesign({ + api, + elementId, + fileId, + isBoxAiSuggestionsEnabled, + onError, + isFeatureEnabled, +}: MetadataSidebarRedesignProps) { const { formatMessage } = useIntl(); const [selectedTemplates, setSelectedTemplates] = React.useState>([]); - const { templates, errorMessage, status } = useSidebarMetadataFetcher(api, fileId, onError, isFeatureEnabled); + const { editors, file, templates, errorMessage, status } = useSidebarMetadataFetcher( + api, + fileId, + onError, + isFeatureEnabled, + ); const metadataDropdown = status === STATUS.SUCCESS && templates && ( ); + const showEditor = file && templates && editors; + const showEmptyState = showEditor && editors.length === 0; + return ( )} + {showEmptyState && ( + + )} ); diff --git a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx index 50ab231f55..79d26e214e 100644 --- a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx @@ -33,6 +33,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { api: {}, fileId: 'test-file-id-1', elementId: 'element-1', + isBoxAiSuggestionsEnabled: true, isFeatureEnabled: true, onError: jest.fn(), } satisfies MetadataSidebarRedesignProps; @@ -43,6 +44,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { beforeEach(() => { mockUseSidebarMetadataFetcher.mockReturnValue({ templates: mockTemplates, + editors: [], errorMessage: null, status: STATUS.SUCCESS, file: mockFile, @@ -83,6 +85,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { test('should render metadata sidebar with error', async () => { mockUseSidebarMetadataFetcher.mockReturnValue({ + editors: [], templates: [], errorMessage: { id: 'error', @@ -101,6 +104,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { test('should render metadata sidebar with loading indicator', async () => { mockUseSidebarMetadataFetcher.mockReturnValue({ + editors: [], templates: [], errorMessage: null, status: STATUS.LOADING, @@ -113,4 +117,22 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { expect(screen.getByTestId('loading')).toBeInTheDocument(); expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); }); + + test('should correctly render empty state when AI feature is enabled', () => { + renderComponent(); + expect(screen.getByRole('heading', { level: 2, name: 'Autofill Metadata with Box AI' })).toBeInTheDocument(); + expect( + screen.getByText( + 'Use the power of Box AI to quickly capture document metadata, with ever-increasing accuracy.', + ), + ).toBeInTheDocument(); + }); + + test('should correctly render empty state when AI feature is disabled', () => { + renderComponent({ isBoxAiSuggestionsEnabled: false }); + expect(screen.getByRole('heading', { level: 2, name: 'Add Metadata Templates' })).toBeInTheDocument(); + expect( + screen.getByText('Add Metadata to your file to support business operations, workflows, and more!'), + ).toBeInTheDocument(); + }); }); diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts index 0fe877a566..dc46ba8601 100644 --- a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -4,7 +4,7 @@ import { type MessageDescriptor } from 'react-intl'; import API from '../../../api'; import { type ElementsXhrError } from '../../../common/types/api'; import { isUserCorrectableError } from '../../../utils/error'; -import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; +import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD, IS_ERROR_DISPLAYED} from '../../../constants'; import messages from '../../common/messages'; @@ -19,9 +19,10 @@ export enum STATUS { SUCCESS = 'success', } interface DataFetcher { - status: STATUS; + editors: Array; file: BoxItem | null; errorMessage: MessageDescriptor | null; + status: STATUS; templates: Array; } @@ -35,6 +36,7 @@ function useSidebarMetadataFetcher( const [file, setFile] = React.useState(null); const [templates, setTemplates] = React.useState(null); const [errorMessage, setErrorMessage] = React.useState(null); + const [editors, setEditors] = React.useState>([]); const onApiError = React.useCallback( (error: ElementsXhrError, code: string, message: MessageDescriptor) => { @@ -51,7 +53,8 @@ function useSidebarMetadataFetcher( ); const fetchMetadataSuccessCallback = React.useCallback( - ({ templates: fetchedTemplates }: { editors: Array; templates: Array }) => { + ({ editors: fetchedEditors, templates: fetchedTemplates }: { editors: Array; templates: Array }) => { + setEditors(fetchedEditors); setErrorMessage(null); setStatus(STATUS.SUCCESS); setTemplates(fetchedTemplates); @@ -61,6 +64,7 @@ function useSidebarMetadataFetcher( const fetchMetadataErrorCallback = React.useCallback( (e: ElementsXhrError, code: string) => { + setEditors(null); setTemplates(null); onApiError(e, code, messages.sidebarMetadataFetchingErrorContent); }, @@ -115,9 +119,10 @@ function useSidebarMetadataFetcher( }, [api, fetchFileErrorCallback, fetchFileSuccessCallback, fileId, status]); return { - status, file, + editors, errorMessage, + status, templates, }; } diff --git a/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx index d6bb46a277..fa9c7e38d0 100644 --- a/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx +++ b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx @@ -1,10 +1,11 @@ import { type StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; -import { type ComponentProps } from 'react'; +import React, { type ComponentProps } from 'react'; import MetadataSidebarRedesign from '../MetadataSidebarRedesign'; import ContentSidebar from '../ContentSidebar'; const fileIdWithMetadata = global.FILE_ID; +const fileIdWithNoMetadata = '416047501580'; const mockFeatures = { 'metadata.redesign.enabled': true, }; @@ -15,6 +16,7 @@ const mockLogger = { }; const defaultMetadataSidebarProps: ComponentProps = { + isBoxAiSuggestionsEnabled: true, isFeatureEnabled: true, onError: fn, }; @@ -30,6 +32,27 @@ export default { token: global.TOKEN, metadataSidebarProps: defaultMetadataSidebarProps, }, + render: args => { + return ; + }, }; export const AddTemplateDropdownMenu: StoryObj = {}; + +export const EmptyStateWithBoxAiEnabled: StoryObj = { + args: { + fileId: fileIdWithNoMetadata, + metadataSidebarProps: { + ...defaultMetadataSidebarProps, + }, + },}; + +export const EmptyStateWithBoxAiDisabled: StoryObj = { + args: { + fileId: fileIdWithNoMetadata, + metadataSidebarProps: { + ...defaultMetadataSidebarProps, + isBoxAiSuggestionsEnabled: false, + }, + }, +};