Skip to content

Commit 7a1372d

Browse files
committed
feat(metadata-view): Implement metadata sidepanel
1 parent b68cd70 commit 7a1372d

File tree

11 files changed

+607
-110
lines changed

11 files changed

+607
-110
lines changed

src/elements/common/sub-header/SubHeader.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface SubHeaderProps {
2828
onGridViewSliderChange?: (newSliderValue: number) => void;
2929
onItemClick: (id: string | null, triggerNavigationEvent: boolean | null) => void;
3030
onSortChange: (sortBy: string, sortDirection: string) => void;
31+
onToggleMetadataSidePanel?: () => void;
3132
onUpload: () => void;
3233
onViewModeChange?: (viewMode: ViewMode) => void;
3334
portalElement?: HTMLElement;
@@ -53,6 +54,7 @@ const SubHeader = ({
5354
onCreate,
5455
onItemClick,
5556
onSortChange,
57+
onToggleMetadataSidePanel,
5658
onUpload,
5759
onViewModeChange,
5860
portalElement,
@@ -109,9 +111,11 @@ const SubHeader = ({
109111
onCreate={onCreate}
110112
onGridViewSliderChange={onGridViewSliderChange}
111113
onSortChange={onSortChange}
114+
onToggleMetadataSidePanel={onToggleMetadataSidePanel}
112115
onUpload={onUpload}
113116
onViewModeChange={onViewModeChange}
114117
portalElement={portalElement}
118+
selectedItemIds={selectedItemIds}
115119
view={view}
116120
viewMode={viewMode}
117121
/>

src/elements/common/sub-header/SubHeaderLeftV2.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { useMemo } from 'react';
1+
import * as React from 'react';
22
import { useIntl } from 'react-intl';
33
import { XMark } from '@box/blueprint-web-assets/icons/Fill/index';
44
import { IconButton, PageHeader, Text } from '@box/blueprint-web';
55
import type { Selection } from 'react-aria-components';
6+
import { useSelectedItemText } from '../../content-explorer/utils';
67
import type { Collection } from '../../../common/types/core';
78
import messages from '../messages';
89

@@ -20,27 +21,7 @@ const SubHeaderLeftV2 = (props: SubHeaderLeftV2Props) => {
2021
const { currentCollection, onClearSelectedItemIds, rootName, selectedItemIds, title } = props;
2122
const { formatMessage } = useIntl();
2223

23-
// Generate selected item text based on selected keys
24-
const selectedItemText: string = useMemo(() => {
25-
const selectedCount = selectedItemIds === 'all' ? currentCollection.items.length : selectedItemIds.size;
26-
27-
if (selectedCount === 0) {
28-
return '';
29-
}
30-
31-
// Case 1: Single selected item - show item name
32-
if (selectedCount === 1) {
33-
const selectedKey =
34-
selectedItemIds === 'all' ? currentCollection.items[0].id : selectedItemIds.values().next().value;
35-
const selectedItem = currentCollection.items.find(item => item.id === selectedKey);
36-
return selectedItem?.name ?? '';
37-
}
38-
// Case 2: Multiple selected items - show count
39-
if (selectedCount > 1) {
40-
return formatMessage(messages.numFilesSelected, { numSelected: selectedCount });
41-
}
42-
return '';
43-
}, [currentCollection.items, formatMessage, selectedItemIds]);
24+
const selectedItemText = useSelectedItemText(currentCollection, selectedItemIds);
4425

4526
// Case 1 and 2: selected item text with X button
4627
if (selectedItemText) {

src/elements/common/sub-header/SubHeaderRight.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22
import { Button } from '@box/blueprint-web';
33
import { Pencil } from '@box/blueprint-web-assets/icons/Fill';
44
import { useIntl } from 'react-intl';
5+
import type { Selection } from 'react-aria-components';
56
import Sort from './Sort';
67
import Add from './Add';
78
import GridViewSlider from '../../../components/grid-view/GridViewSlider';
@@ -27,9 +28,11 @@ export interface SubHeaderRightProps {
2728
onCreate: () => void;
2829
onGridViewSliderChange: (newSliderValue: number) => void;
2930
onSortChange: (sortBy: SortBy, sortDirection: SortDirection) => void;
31+
onToggleMetadataSidePanel?: () => void;
3032
onUpload: () => void;
3133
onViewModeChange?: (viewMode: ViewMode) => void;
3234
portalElement?: HTMLElement;
35+
selectedItemIds?: Selection;
3336
view: View;
3437
viewMode: ViewMode;
3538
}
@@ -45,9 +48,11 @@ const SubHeaderRight = ({
4548
onCreate,
4649
onGridViewSliderChange,
4750
onSortChange,
51+
onToggleMetadataSidePanel,
4852
onUpload,
4953
onViewModeChange,
5054
portalElement,
55+
selectedItemIds,
5156
view,
5257
viewMode,
5358
}: SubHeaderRightProps) => {
@@ -60,6 +65,8 @@ const SubHeaderRight = ({
6065
const showSort: boolean = isFolder && hasItems;
6166
const showAdd: boolean = (!!canUpload || !!canCreateNewFolder) && isFolder;
6267
const isMetadataView: boolean = view === VIEW_METADATA;
68+
const isMetadataViewV2ItemSelected: boolean =
69+
selectedItemIds && (selectedItemIds === 'all' || selectedItemIds.size > 0);
6370
return (
6471
<div className="be-sub-header-right">
6572
{!isMetadataView && (
@@ -90,8 +97,8 @@ const SubHeaderRight = ({
9097
</>
9198
)}
9299

93-
{isMetadataView && isMetadataViewV2Feature && (
94-
<Button icon={Pencil} size="large" variant="primary">
100+
{isMetadataView && isMetadataViewV2Feature && isMetadataViewV2ItemSelected && (
101+
<Button icon={Pencil} size="large" variant="primary" onClick={onToggleMetadataSidePanel}>
95102
{formatMessage(messages.metadata)}
96103
</Button>
97104
)}

src/elements/content-explorer/ContentExplorer.scss

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,22 @@
77
.bcpr {
88
z-index: 1; // Prevents overlay issues with list-item when a file is previewed
99
}
10+
11+
.bce-container {
12+
display: flex;
13+
height: 100%;
14+
min-height: 0;
15+
}
16+
17+
.bce-main {
18+
display: flex;
19+
flex: 1;
20+
flex-direction: column;
21+
min-width: 0;
22+
}
23+
24+
.bce-sidepanel {
25+
margin-left: var(--space-4);
26+
}
1027
}
1128
}

src/elements/content-explorer/ContentExplorer.tsx

Lines changed: 122 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import ThemingStyles from '../common/theming';
2424
import API from '../../api';
2525
import MetadataQueryAPIHelperV2 from './MetadataQueryAPIHelper';
2626
import MetadataQueryAPIHelper from '../../features/metadata-based-view/MetadataQueryAPIHelper';
27+
import MetadataSidePanel from './MetadataSidePanel';
2728
import Footer from './Footer';
2829
import PreviewDialog from '../common/preview-dialog/PreviewDialog';
2930
import ShareDialog from './ShareDialog';
@@ -169,6 +170,7 @@ type State = {
169170
isCreateFolderModalOpen: boolean;
170171
isDeleteModalOpen: boolean;
171172
isLoading: boolean;
173+
isMetadataSidePanelOpen: boolean;
172174
isPreviewModalOpen: boolean;
173175
isRenameModalOpen: boolean;
174176
isShareModalOpen: boolean;
@@ -294,6 +296,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
294296
isCreateFolderModalOpen: false,
295297
isDeleteModalOpen: false,
296298
isLoading: false,
299+
isMetadataSidePanelOpen: false,
297300
isPreviewModalOpen: false,
298301
isRenameModalOpen: false,
299302
isShareModalOpen: false,
@@ -1543,7 +1546,11 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
15431546
selectedKeys: selectedItemIds,
15441547
onSelectionChange: (ids: Selection) => {
15451548
onSelectionChange?.(ids);
1546-
this.setState({ selectedItemIds: ids });
1549+
const isSelectionEmpty = ids !== 'all' && ids.size === 0;
1550+
this.setState({
1551+
selectedItemIds: ids,
1552+
...(isSelectionEmpty && { isMetadataSidePanelOpen: false }),
1553+
});
15471554
},
15481555
},
15491556
};
@@ -1625,7 +1632,32 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
16251632
};
16261633

16271634
clearSelectedItemIds = () => {
1628-
this.setState({ selectedItemIds: new Set() });
1635+
this.setState({
1636+
selectedItemIds: new Set(),
1637+
isMetadataSidePanelOpen: false,
1638+
});
1639+
};
1640+
1641+
/**
1642+
* Toggle metadata side panel visibility
1643+
*
1644+
* @private
1645+
* @return {void}
1646+
*/
1647+
toggleMetadataSidePanel = () => {
1648+
this.setState(prevState => ({
1649+
isMetadataSidePanelOpen: !prevState.isMetadataSidePanelOpen,
1650+
}));
1651+
};
1652+
1653+
/**
1654+
* Close metadata side panel
1655+
*
1656+
* @private
1657+
* @return {void}
1658+
*/
1659+
closeMetadataSidePanel = () => {
1660+
this.setState({ isMetadataSidePanelOpen: false });
16291661
};
16301662

16311663
/**
@@ -1725,6 +1757,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17251757
const allowUpload: boolean = canUpload && !!can_upload;
17261758
const allowCreate: boolean = canCreateNewFolder && !!can_upload;
17271759
const isDefaultViewMetadata: boolean = defaultView === DEFAULT_VIEW_METADATA;
1760+
const isMetadataViewV2Feature = isFeatureEnabled(features, 'contentExplorer.metadataViewV2');
17281761
const isErrorView: boolean = view === VIEW_ERROR;
17291762

17301763
const viewMode = this.getViewMode();
@@ -1743,76 +1776,96 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
17431776
<div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-explorer">
17441777
<ThemingStyles selector={`#${this.id}`} theme={theme} />
17451778
<div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
1746-
{!isDefaultViewMetadata && <Header view={view} logoUrl={logoUrl} onSearch={this.search} />}
1747-
1748-
<SubHeader
1749-
view={view}
1750-
viewMode={viewMode}
1751-
rootId={rootFolderId}
1752-
isSmall={isSmall}
1753-
rootName={rootName}
1754-
currentCollection={currentCollection}
1755-
canUpload={allowUpload}
1756-
canCreateNewFolder={allowCreate}
1757-
gridColumnCount={gridColumnCount}
1758-
gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
1759-
gridMinColumns={GRID_VIEW_MIN_COLUMNS}
1760-
maxGridColumnCountForWidth={maxGridColumnCount}
1761-
onUpload={this.upload}
1762-
onClearSelectedItemIds={this.clearSelectedItemIds}
1763-
onCreate={this.createFolder}
1764-
onGridViewSliderChange={this.onGridViewSliderChange}
1765-
onItemClick={this.fetchFolder}
1766-
onSortChange={this.sort}
1767-
onViewModeChange={this.changeViewMode}
1768-
portalElement={this.rootElement}
1769-
selectedItemIds={this.state.selectedItemIds}
1770-
title={title}
1771-
/>
1779+
<div className="bce-container">
1780+
<div className="bce-main">
1781+
{!isDefaultViewMetadata && (
1782+
<Header view={view} logoUrl={logoUrl} onSearch={this.search} />
1783+
)}
1784+
1785+
<SubHeader
1786+
view={view}
1787+
viewMode={viewMode}
1788+
rootId={rootFolderId}
1789+
isSmall={isSmall}
1790+
rootName={rootName}
1791+
currentCollection={currentCollection}
1792+
canUpload={allowUpload}
1793+
canCreateNewFolder={allowCreate}
1794+
gridColumnCount={gridColumnCount}
1795+
gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
1796+
gridMinColumns={GRID_VIEW_MIN_COLUMNS}
1797+
maxGridColumnCountForWidth={maxGridColumnCount}
1798+
onUpload={this.upload}
1799+
onClearSelectedItemIds={this.clearSelectedItemIds}
1800+
onCreate={this.createFolder}
1801+
onGridViewSliderChange={this.onGridViewSliderChange}
1802+
onItemClick={this.fetchFolder}
1803+
onSortChange={this.sort}
1804+
onToggleMetadataSidePanel={this.toggleMetadataSidePanel}
1805+
onViewModeChange={this.changeViewMode}
1806+
portalElement={this.rootElement}
1807+
selectedItemIds={this.state.selectedItemIds}
1808+
title={title}
1809+
/>
17721810

1773-
<Content
1774-
canDelete={canDelete}
1775-
canDownload={canDownload}
1776-
canPreview={canPreview}
1777-
canRename={canRename}
1778-
canShare={canShare}
1779-
currentCollection={currentCollection}
1780-
features={features}
1781-
gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
1782-
isMedium={isMedium}
1783-
isSmall={isSmall}
1784-
isTouch={isTouch}
1785-
itemActions={itemActions}
1786-
fieldsToShow={fieldsToShow}
1787-
metadataTemplate={metadataTemplate}
1788-
metadataViewProps={metadataViewProps}
1789-
onItemClick={this.onItemClick}
1790-
onItemDelete={this.delete}
1791-
onItemDownload={this.download}
1792-
onItemPreview={this.preview}
1793-
onItemRename={this.rename}
1794-
onItemSelect={this.select}
1795-
onItemShare={this.share}
1796-
onMetadataUpdate={this.updateMetadata}
1797-
onSortChange={this.sort}
1798-
portalElement={this.rootElement}
1799-
view={view}
1800-
viewMode={viewMode}
1801-
/>
1802-
{!isErrorView && (
1803-
<Footer>
1804-
<Pagination
1805-
hasNextMarker={hasNextMarker}
1806-
hasPrevMarker={hasPreviousMarker}
1811+
<Content
1812+
canDelete={canDelete}
1813+
canDownload={canDownload}
1814+
canPreview={canPreview}
1815+
canRename={canRename}
1816+
canShare={canShare}
1817+
currentCollection={currentCollection}
1818+
features={features}
1819+
gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
1820+
isMedium={isMedium}
18071821
isSmall={isSmall}
1808-
offset={offset}
1809-
onOffsetChange={this.paginate}
1810-
pageSize={currentPageSize}
1811-
totalCount={totalCount}
1812-
onMarkerBasedPageChange={this.markerBasedPaginate}
1822+
isTouch={isTouch}
1823+
itemActions={itemActions}
1824+
fieldsToShow={fieldsToShow}
1825+
metadataTemplate={metadataTemplate}
1826+
metadataViewProps={metadataViewProps}
1827+
onItemClick={this.onItemClick}
1828+
onItemDelete={this.delete}
1829+
onItemDownload={this.download}
1830+
onItemPreview={this.preview}
1831+
onItemRename={this.rename}
1832+
onItemSelect={this.select}
1833+
onItemShare={this.share}
1834+
onMetadataUpdate={this.updateMetadata}
1835+
onSortChange={this.sort}
1836+
portalElement={this.rootElement}
1837+
view={view}
1838+
viewMode={viewMode}
18131839
/>
1814-
</Footer>
1815-
)}
1840+
1841+
{!isErrorView && (
1842+
<Footer>
1843+
<Pagination
1844+
hasNextMarker={hasNextMarker}
1845+
hasPrevMarker={hasPreviousMarker}
1846+
isSmall={isSmall}
1847+
offset={offset}
1848+
onOffsetChange={this.paginate}
1849+
pageSize={currentPageSize}
1850+
totalCount={totalCount}
1851+
onMarkerBasedPageChange={this.markerBasedPaginate}
1852+
/>
1853+
</Footer>
1854+
)}
1855+
</div>
1856+
{isDefaultViewMetadata &&
1857+
isMetadataViewV2Feature &&
1858+
this.state.isMetadataSidePanelOpen && (
1859+
<div className="bce-sidepanel">
1860+
<MetadataSidePanel
1861+
currentCollection={currentCollection}
1862+
closeMetadataSidePanel={this.closeMetadataSidePanel}
1863+
metadataTemplate={metadataTemplate}
1864+
selectedItemIds={this.state.selectedItemIds}
1865+
/>
1866+
</div>
1867+
)}
1868+
</div>
18161869
</div>
18171870
{allowUpload && !!this.appElement ? (
18181871
<UploadDialog

0 commit comments

Comments
 (0)