Skip to content

Commit

Permalink
feat(metadata-sidebar): Metadata sidebar redesign (#3654)
Browse files Browse the repository at this point in the history
* feat(metadata-sidebar): Add metadata add template dropdown menu (#3606)

* feat(metadata-sidebar): Add AddMetadataTemplateDropdown

and improve basic styling

* feat(metadata-sidebar): Fix failing tests

Extend Jest configuration to not transforming metadata-editor code

* feat(metadata-sidebar): add AddMetadataTemplateDropdown

To MetadataSidebarRedesign

* feat(content-sidebar): Bring back changes to mockServiceWorker.js

No idea why they got there in the first place

* feat(metadata-sidebar): update storybook

* feat(metadata-sidebar): PR comments

* feat(metadata-sidebar): simplify storybook

* feat(metadata-sidebar): enum status

* feat(metadata-sidebar): global variables and enum upper case change

* feat(metadata-sidebar): useSidebarMetadataFetcher tests

* feat(metadata-sidebar): useSidebarMetadataFetcher tests

* feat(metadata-sidebar): PR comments

* feat(metadata-sidebar): PR comments

* feat(metadata-sidebar): loading status test

* feat(metadata-sidebar): use SidebarContent + tests

* feat(metadata-sidebar): template dropdown menu nit fixes

---------

Co-authored-by: Karolina Rusek-Bieniek <[email protected]>
Co-authored-by: Wiola <[email protected]>

* feat(metadata-sidebar): add metadata empty state to metadata sidebar redesign (#3605)

* chore(content-sidebar): Temporarily remove files

while we be working with not yet publish internal library.
Will remove this commit after the library will become publicly
available on NPM and added to BUIE.

feat(metadata-sidebar): MetadataEmptyState

feat(metadata-sidebar): comment api to pass test

feat(metadata-sidebar): uncomment comments

feat(metadata-sidebar): add states

feat(metadata-sidebar): tests update

feat(metadata-sidebar): PR comments

feat(metadata-sidebar): delete git add . in showEditor

feat(metadata-sidebar): PR comments

feat(metadata-sidebar): storybook tests

feat(metadata-sidebar): update unit tests

feat(metadata-sidebar): pr comments

feat(metadata-sidebar): variables name changes

feat(metadata-sidebar): PR comments

feat(metadata-sidebar): PR comments

feat(metadata-sidebar): global token change  and status enum

feat(metadata-sidebar): global token update

feat(metadata-sidebar): global variables and enum upper case change

feat(metadata-sidebar): PR comments

feat(metadata-sidebar): styles import and title deletion

feat(metadata-sidebar): convert type to interface

* feat(metadata-sidebar): missing tests after rebase

---------

Co-authored-by: Dawid Jankowiak <[email protected]>

* feat(metadata-sidebar): Metadata Instance Editor (#3632)

* feat(metadata-sidebar): metadata instance editor

* feat(metadata-sidebar): modal and tests

* feat(metadata-sidebar): isLoading change

* feat(metadata-sidebar): change imports

* feat(metadata-sidebar): pr comments

* feat(metadata-sidebar): Metadata.test.js test fix

* feat(metadata-sidebar): modal story additional check

* feat(metadata-sidebar): modal open prop

* feat(metadata-sidebar): stories

* feat(metadata-sidebar): lint comments

* feat(metadata-sidebar): stories update

* feat(metadata-sidebar): Disable lint for 2 console.logs

---------

Co-authored-by: Karolina Rusek-Bieniek <[email protected]>
Co-authored-by: Wiola <[email protected]>
Co-authored-by: karolinaru <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored Sep 17, 2024
1 parent 2b564de commit fe4fede
Show file tree
Hide file tree
Showing 14 changed files with 838 additions and 37 deletions.
4 changes: 3 additions & 1 deletion scripts/jest/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ module.exports = {
testEnvironment: 'jsdom',
testMatch: ['**/__tests__/**/*.test.+(js|jsx|ts|tsx)'],
testPathIgnorePatterns: ['stories.test.js$', 'stories.test.tsx$', 'stories.test.d.ts'],
transformIgnorePatterns: ['node_modules/(?!(@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets)/)'],
transformIgnorePatterns: [
'node_modules/(?!(@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets|@box/metadata-editor)/)',
],
};
1 change: 1 addition & 0 deletions src/api/Metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class Metadata extends File {
scope: METADATA_SCOPE_GLOBAL,
templateKey: METADATA_TEMPLATE_PROPERTIES,
hidden: false,
fields: [],
};
}

Expand Down
1 change: 1 addition & 0 deletions src/api/__tests__/Metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('api/Metadata', () => {
scope: METADATA_SCOPE_GLOBAL,
templateKey: METADATA_TEMPLATE_PROPERTIES,
hidden: false,
fields: [],
});
});
});
Expand Down
49 changes: 49 additions & 0 deletions src/elements/content-sidebar/MetadataInstanceEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
MetadataInstanceForm,
type MetadataTemplateInstance,
UnsavedChangesModal,
type MetadataTemplate,
} from '@box/metadata-editor';
import React from 'react';

export interface MetadataInstanceEditorProps {
isBoxAiSuggestionsEnabled: boolean;
isUnsavedChangesModalOpen: boolean;
template: MetadataTemplateInstance | MetadataTemplate;
}

const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
isBoxAiSuggestionsEnabled,
isUnsavedChangesModalOpen,
template,
}) => {
const handleSubmit = () => {
// TODO in a future PR
};
const handleCancel = () => {
// TODO in a future PR
};
const handleDelete = () => {
// TODO in a future PR
};

return (
<>
<MetadataInstanceForm
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
isLoading={false}
selectedTemplateInstance={template}
onCancel={handleCancel}
onDelete={handleDelete}
onSubmit={handleSubmit}
/>
<UnsavedChangesModal
onDismiss={handleCancel}
onSaveAndContinue={handleSubmit}
open={isUnsavedChangesModalOpen}
/>
</>
);
};

export default MetadataInstanceEditor;
14 changes: 12 additions & 2 deletions src/elements/content-sidebar/MetadataSidebarRedesign.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
@import '../common/variables';
@import '~@box/blueprint-web-assets/tokens/tokens.scss';

.bcs-MetadataSidebarRedesign {
padding-inline: 10px;
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;
}
}
130 changes: 119 additions & 11 deletions src/elements/content-sidebar/MetadataSidebarRedesign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,138 @@
*/
import * as React from 'react';
import flow from 'lodash/flow';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import { InlineError, LoadingIndicator } from '@box/blueprint-web';
import {
AddMetadataTemplateDropdown,
MetadataEmptyState,
type MetadataTemplateInstance,
type MetadataTemplate,
} from '@box/metadata-editor';

import API from '../../api';
import SidebarContent from './SidebarContent';
import { withAPIContext } from '../common/api-context';
import { withErrorBoundary } from '../common/error-boundary';
import { withLogger } from '../common/logger';
import { ORIGIN_METADATA_SIDEBAR_REDESIGN } from '../../constants';
import { ORIGIN_METADATA_SIDEBAR_REDESIGN, SIDEBAR_VIEW_METADATA } from '../../constants';
import { EVENT_JS_READY } from '../common/logger/constants';
import { mark } from '../../utils/performance';
import messages from '../common/messages';
import useSidebarMetadataFetcher, { STATUS } from './hooks/useSidebarMetadataFetcher';

import { type ElementsXhrError } from '../../common/types/api';
import { type ElementOrigin } from '../common/flowTypes';
import { type WithLoggerProps } from '../../common/types/logging';

import messages from '../common/messages';
import './MetadataSidebarRedesign.scss';
import MetadataInstanceEditor from './MetadataInstanceEditor';

const MARK_NAME_JS_READY = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}_${EVENT_JS_READY}`;

mark(MARK_NAME_JS_READY);

function MetadataSidebarRedesign() {
export interface ExternalProps {
isBoxAiSuggestionsEnabled: boolean;
isFeatureEnabled: boolean;
}

interface PropsWithoutContext extends ExternalProps {
elementId: string;
fileId: string;
hasSidebarInitialized?: boolean;
}

interface ContextInfo {
isErrorDisplayed: boolean;
error: ElementsXhrError | Error;
}

export interface ErrorContextProps {
onError: (error: ElementsXhrError | Error, code: string, contextInfo?: ContextInfo, origin?: ElementOrigin) => void;
}

export interface MetadataSidebarRedesignProps extends PropsWithoutContext, ErrorContextProps, WithLoggerProps {
api: API;
}

function MetadataSidebarRedesign({
api,
elementId,
fileId,
isBoxAiSuggestionsEnabled,
onError,
isFeatureEnabled,
}: MetadataSidebarRedesignProps) {
const { formatMessage } = useIntl();

const [selectedTemplates, setSelectedTemplates] = React.useState<Array<MetadataTemplate>>([]);
const [editingTemplate, setEditingTemplate] = React.useState<MetadataTemplateInstance | MetadataTemplate | null>(
null,
);
const [isUnsavedChangesModalOpen, setIsUnsavedChangesModalOpen] = React.useState<boolean>(false);

const { file, templates, errorMessage, status, templateInstances } = useSidebarMetadataFetcher(
api,
fileId,
onError,
isFeatureEnabled,
);

const handleUnsavedChanges = () => {
setIsUnsavedChangesModalOpen(true);
};

const handleTemplateSelect = (selectedTemplate: MetadataTemplate) => {
setSelectedTemplates([...selectedTemplates, selectedTemplate]);
setEditingTemplate(selectedTemplate);
};

const metadataDropdown = status === STATUS.SUCCESS && templates && (
<AddMetadataTemplateDropdown
availableTemplates={templates}
selectedTemplates={selectedTemplates}
onSelect={(selectedTemplate): void => {
editingTemplate ? handleUnsavedChanges() : handleTemplateSelect(selectedTemplate);
}}
/>
);

const errorMessageDisplay = status === STATUS.ERROR && errorMessage && (
<InlineError>
<FormattedMessage {...errorMessage} />
</InlineError>
);

const showTemplateInstances = file && templates && templateInstances;
const showEmptyState = showTemplateInstances && templateInstances.length === 0 && !editingTemplate;

return (
<div className="bcs-MetadataSidebarRedesign">
<h3>
<FormattedMessage {...messages.sidebarMetadataTitle} />
</h3>
<hr />
<p>Hello from Metadata Sidebar redesign</p>
</div>
<SidebarContent
actions={metadataDropdown}
className={'bcs-MetadataSidebarRedesign'}
elementId={elementId}
sidebarView={SIDEBAR_VIEW_METADATA}
title={formatMessage(messages.sidebarMetadataTitle)}
>
<div className="bcs-MetadataSidebarRedesign-content">
{errorMessageDisplay}
{status === STATUS.LOADING && (
<LoadingIndicator aria-label={formatMessage(messages.loading)} data-testid="loading" />
)}
{showEmptyState ? (
<MetadataEmptyState level={'file'} isBoxAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled} />
) : (
editingTemplate && (
<MetadataInstanceEditor
isBoxAiSuggestionsEnabled={isBoxAiSuggestionsEnabled}
isUnsavedChangesModalOpen={isUnsavedChangesModalOpen}
template={editingTemplate}
/>
)
)}
</div>
</SidebarContent>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { type MetadataTemplate } from '@box/metadata-editor';
import { screen, render } from '../../../test-utils/testing-library';
import MetadataInstanceEditor, { MetadataInstanceEditorProps } from '../MetadataInstanceEditor';

describe('MetadataInstanceEditor', () => {
const mockCustomMetadataTemplate: MetadataTemplate = {
id: 'template-id',
fields: [],
scope: 'global',
templateKey: 'properties',
type: 'template-id',
hidden: false,
};

const mockMetadataTemplate: MetadataTemplate = { ...mockCustomMetadataTemplate, displayName: 'Template Name' };

const defaultProps: MetadataInstanceEditorProps = {
isBoxAiSuggestionsEnabled: true,
isUnsavedChangesModalOpen: false,
template: mockMetadataTemplate,
};

test('should render MetadataInstanceForm with correct props', () => {
render(<MetadataInstanceEditor {...defaultProps} />);

const templateHeader = screen.getByText(mockMetadataTemplate.displayName);
expect(templateHeader).toBeInTheDocument();
});

test('should render MetadataInstanceForm with Custom Template', () => {
const props = { ...defaultProps, template: mockCustomMetadataTemplate };
render(<MetadataInstanceEditor {...props} />);

const templateHeader = screen.getByText('Custom Metadata');
expect(templateHeader).toBeInTheDocument();
});

test('should render UnsavedChangesModal if isUnsavedChangesModalOpen is true', () => {
// Mock window.matchMedia to simulate media query behavior for this test,
// as the UnsavedChangesModal component relies on it.
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

const props = { ...defaultProps, isUnsavedChangesModalOpen: true };
render(<MetadataInstanceEditor {...props} />);

const unsavedChangesModal = screen.getByText('Unsaved Changes');
expect(unsavedChangesModal).toBeInTheDocument();
});
});
Loading

0 comments on commit fe4fede

Please sign in to comment.