box-ui-elements 23.4.0 → 23.5.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/explorer.css +1 -1
- package/dist/explorer.js +1 -1
- package/dist/preview.js +1 -1
- package/dist/sidebar.js +1 -1
- package/es/elements/common/routing/index.js +1 -1
- package/es/elements/common/routing/index.js.flow +2 -1
- package/es/elements/common/routing/index.js.map +1 -1
- package/es/elements/common/routing/withRouterIfEnabled.js +15 -0
- package/es/elements/common/routing/withRouterIfEnabled.js.flow +24 -0
- package/es/elements/common/routing/withRouterIfEnabled.js.map +1 -0
- package/es/elements/content-explorer/ContentExplorer.js +23 -25
- package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
- package/es/elements/content-explorer/MetadataViewContainer.js +53 -3
- package/es/elements/content-explorer/MetadataViewContainer.js.map +1 -1
- package/es/elements/content-explorer/stories/MetadataView.stories.js +3 -0
- package/es/elements/content-explorer/stories/MetadataView.stories.js.map +1 -1
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +44 -1
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
- package/es/elements/content-sidebar/AddTaskButton.js +3 -3
- package/es/elements/content-sidebar/AddTaskButton.js.flow +5 -4
- package/es/elements/content-sidebar/AddTaskButton.js.map +1 -1
- package/es/elements/content-sidebar/SidebarToggle.js +2 -2
- package/es/elements/content-sidebar/SidebarToggle.js.flow +3 -2
- package/es/elements/content-sidebar/SidebarToggle.js.map +1 -1
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js +4 -3
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js.flow +5 -4
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js.map +1 -1
- package/es/src/elements/content-explorer/ContentExplorer.d.ts +0 -7
- package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +13 -2
- package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +1 -0
- package/package.json +9 -9
- package/src/elements/common/routing/__tests__/withRouterIfEnabled.test.js +55 -0
- package/src/elements/common/routing/index.js +2 -1
- package/src/elements/common/routing/withRouterIfEnabled.js +24 -0
- package/src/elements/content-explorer/ContentExplorer.tsx +23 -25
- package/src/elements/content-explorer/MetadataViewContainer.tsx +77 -12
- package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +38 -3
- package/src/elements/content-explorer/stories/MetadataView.stories.tsx +4 -0
- package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +37 -3
- package/src/elements/content-sidebar/AddTaskButton.js +5 -4
- package/src/elements/content-sidebar/SidebarToggle.js +3 -2
- package/src/elements/content-sidebar/__tests__/__snapshots__/ActivitySidebar.test.js.snap +1 -1
- package/src/elements/content-sidebar/versions/VersionsSidebarContainer.js +5 -4
|
@@ -4,5 +4,6 @@ type Story = StoryObj<typeof ContentExplorer>;
|
|
|
4
4
|
export declare const metadataView: Story;
|
|
5
5
|
export declare const metadataViewV2: Story;
|
|
6
6
|
export declare const metadataViewV2WithCustomActions: Story;
|
|
7
|
+
export declare const metadataViewV2WithInitialFilterValues: Story;
|
|
7
8
|
declare const meta: Meta<typeof ContentExplorer>;
|
|
8
9
|
export default meta;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "box-ui-elements",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.5.0-beta.2",
|
|
4
4
|
"description": "Box UI Elements",
|
|
5
5
|
"author": "Box (https://www.box.com/)",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -132,15 +132,15 @@
|
|
|
132
132
|
"@box/blueprint-web-assets": "4.61.5",
|
|
133
133
|
"@box/box-ai-agent-selector": "^0.52.0",
|
|
134
134
|
"@box/box-ai-content-answers": "^0.124.1",
|
|
135
|
-
"@box/box-item-type-selector": "^0.
|
|
135
|
+
"@box/box-item-type-selector": "^0.63.12",
|
|
136
136
|
"@box/cldr-data": "^34.2.0",
|
|
137
137
|
"@box/combobox-with-api": "^0.34.9",
|
|
138
138
|
"@box/frontend": "^11.0.1",
|
|
139
|
-
"@box/item-icon": "^0.17.
|
|
139
|
+
"@box/item-icon": "^0.17.15",
|
|
140
140
|
"@box/languages": "^1.0.0",
|
|
141
141
|
"@box/metadata-editor": "^0.122.12",
|
|
142
|
-
"@box/metadata-filter": "^1.
|
|
143
|
-
"@box/metadata-view": "^0.
|
|
142
|
+
"@box/metadata-filter": "^1.19.2",
|
|
143
|
+
"@box/metadata-view": "^0.41.2",
|
|
144
144
|
"@box/react-virtualized": "^9.22.3-rc-box.10",
|
|
145
145
|
"@box/types": "^0.2.1",
|
|
146
146
|
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
|
|
@@ -301,13 +301,13 @@
|
|
|
301
301
|
"@box/blueprint-web-assets": "4.61.5",
|
|
302
302
|
"@box/box-ai-agent-selector": "^0.52.0",
|
|
303
303
|
"@box/box-ai-content-answers": "^0.124.1",
|
|
304
|
-
"@box/box-item-type-selector": "^0.
|
|
304
|
+
"@box/box-item-type-selector": "^0.63.12",
|
|
305
305
|
"@box/cldr-data": ">=34.2.0",
|
|
306
306
|
"@box/combobox-with-api": "^0.34.9",
|
|
307
|
-
"@box/item-icon": "^0.17.
|
|
307
|
+
"@box/item-icon": "^0.17.15",
|
|
308
308
|
"@box/metadata-editor": "^0.122.12",
|
|
309
|
-
"@box/metadata-filter": "^1.
|
|
310
|
-
"@box/metadata-view": "^0.
|
|
309
|
+
"@box/metadata-filter": "^1.19.2",
|
|
310
|
+
"@box/metadata-view": "^0.41.2",
|
|
311
311
|
"@box/react-virtualized": "^9.22.3-rc-box.10",
|
|
312
312
|
"@box/types": "^0.2.1",
|
|
313
313
|
"@hapi/address": "^2.1.4",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import { render } from '../../../../test-utils/testing-library';
|
|
5
|
+
import withRouterIfEnabled from '../withRouterIfEnabled';
|
|
6
|
+
|
|
7
|
+
const TestComponent = (props: any) => {
|
|
8
|
+
const { history, location, match, routerDisabled } = props;
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
data-testid="test-component"
|
|
12
|
+
data-router-disabled={routerDisabled ? 'true' : undefined}
|
|
13
|
+
data-has-history={history ? 'true' : undefined}
|
|
14
|
+
data-has-location={location ? 'true' : undefined}
|
|
15
|
+
data-has-match={match ? 'true' : undefined}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
TestComponent.displayName = 'TestComponent';
|
|
20
|
+
|
|
21
|
+
const WithRouterIfEnabled = withRouterIfEnabled(TestComponent);
|
|
22
|
+
|
|
23
|
+
test('injects router props when wrapped in a Router', () => {
|
|
24
|
+
const { getByTestId } = render(
|
|
25
|
+
<MemoryRouter initialEntries={[{ pathname: '/foo' }]}>
|
|
26
|
+
<WithRouterIfEnabled />
|
|
27
|
+
</MemoryRouter>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const component = getByTestId('test-component');
|
|
31
|
+
expect(component).toHaveAttribute('data-has-history', 'true');
|
|
32
|
+
expect(component).toHaveAttribute('data-has-location', 'true');
|
|
33
|
+
expect(component).toHaveAttribute('data-has-match', 'true');
|
|
34
|
+
expect(component).not.toHaveAttribute('data-router-disabled');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('renders without Router and without router props (routerDisabled prop)', () => {
|
|
38
|
+
const { getByTestId } = render(<WithRouterIfEnabled routerDisabled />);
|
|
39
|
+
const component = getByTestId('test-component');
|
|
40
|
+
expect(component).not.toHaveAttribute('data-has-history');
|
|
41
|
+
expect(component).not.toHaveAttribute('data-has-location');
|
|
42
|
+
expect(component).not.toHaveAttribute('data-has-match');
|
|
43
|
+
expect(component).toHaveAttribute('data-router-disabled', 'true');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('renders without Router and without router props (feature flag)', () => {
|
|
47
|
+
const features = { routerDisabled: { value: true } };
|
|
48
|
+
const { getByTestId } = render(<WithRouterIfEnabled features={features} />);
|
|
49
|
+
|
|
50
|
+
const component = getByTestId('test-component');
|
|
51
|
+
expect(component).not.toHaveAttribute('data-has-history');
|
|
52
|
+
expect(component).not.toHaveAttribute('data-has-location');
|
|
53
|
+
expect(component).not.toHaveAttribute('data-has-match');
|
|
54
|
+
});
|
|
55
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { withRouter } from 'react-router-dom';
|
|
4
|
+
import { isFeatureEnabled } from '../feature-checking';
|
|
5
|
+
|
|
6
|
+
export default function withRouterIfEnabled(Wrapped: React.ComponentType<any>) {
|
|
7
|
+
|
|
8
|
+
const WrappedWithRouter = withRouter(Wrapped);
|
|
9
|
+
|
|
10
|
+
const WithRouterIfEnabled = (props: any) => {
|
|
11
|
+
const routerDisabled =
|
|
12
|
+
props?.routerDisabled === true || isFeatureEnabled(props?.features, 'routerDisabled.value');
|
|
13
|
+
|
|
14
|
+
const Component = routerDisabled ? Wrapped : WrappedWithRouter;
|
|
15
|
+
|
|
16
|
+
return <Component {...props} />;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const name = Wrapped.displayName || Wrapped.name || 'Component';
|
|
20
|
+
WithRouterIfEnabled.displayName = `withRouterIfEnabled(${name})`;
|
|
21
|
+
|
|
22
|
+
return WithRouterIfEnabled;
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -338,7 +338,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
338
338
|
* @return {void}
|
|
339
339
|
*/
|
|
340
340
|
componentDidMount() {
|
|
341
|
-
const { currentFolderId, defaultView
|
|
341
|
+
const { currentFolderId, defaultView }: ContentExplorerProps = this.props;
|
|
342
342
|
this.rootElement = document.getElementById(this.id) as HTMLElement;
|
|
343
343
|
this.appElement = this.rootElement.firstElementChild as HTMLElement;
|
|
344
344
|
|
|
@@ -348,7 +348,6 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
348
348
|
break;
|
|
349
349
|
case DEFAULT_VIEW_METADATA:
|
|
350
350
|
this.showMetadataQueryResults();
|
|
351
|
-
this.fetchFolderName(metadataQuery?.ancestor_folder_id);
|
|
352
351
|
break;
|
|
353
352
|
default:
|
|
354
353
|
this.fetchFolder(currentFolderId);
|
|
@@ -390,12 +389,14 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
390
389
|
metadataTemplate: MetadataTemplate,
|
|
391
390
|
): void => {
|
|
392
391
|
const { nextMarker } = metadataQueryCollection;
|
|
392
|
+
const { metadataQuery, features } = this.props;
|
|
393
393
|
const { currentCollection, currentPageNumber, markers }: State = this.state;
|
|
394
394
|
const cloneMarkers = [...markers];
|
|
395
395
|
if (nextMarker) {
|
|
396
396
|
cloneMarkers[currentPageNumber + 1] = nextMarker;
|
|
397
397
|
}
|
|
398
|
-
|
|
398
|
+
|
|
399
|
+
const nextState = {
|
|
399
400
|
currentCollection: {
|
|
400
401
|
...currentCollection,
|
|
401
402
|
...metadataQueryCollection,
|
|
@@ -403,7 +404,25 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
403
404
|
},
|
|
404
405
|
markers: cloneMarkers,
|
|
405
406
|
metadataTemplate,
|
|
406
|
-
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// if v2, fetch folder name and add to state
|
|
410
|
+
if (metadataQuery?.ancestor_folder_id && isFeatureEnabled(features, 'contentExplorer.metadataViewV2')) {
|
|
411
|
+
this.api.getFolderAPI().getFolderFields(
|
|
412
|
+
metadataQuery.ancestor_folder_id,
|
|
413
|
+
({ name }) => {
|
|
414
|
+
this.setState({
|
|
415
|
+
...nextState,
|
|
416
|
+
rootName: name || '',
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
this.errorCallback,
|
|
420
|
+
{ fields: [FIELD_NAME] },
|
|
421
|
+
);
|
|
422
|
+
} else {
|
|
423
|
+
// No folder name to fetch, update state immediately with just metadata
|
|
424
|
+
this.setState(nextState);
|
|
425
|
+
}
|
|
407
426
|
};
|
|
408
427
|
|
|
409
428
|
/**
|
|
@@ -1628,27 +1647,6 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1628
1647
|
this.setState({ selectedItemIds: new Set() });
|
|
1629
1648
|
};
|
|
1630
1649
|
|
|
1631
|
-
/**
|
|
1632
|
-
* Fetches the folder name and stores it in state rootName if successful
|
|
1633
|
-
*
|
|
1634
|
-
* @private
|
|
1635
|
-
* @return {void}
|
|
1636
|
-
*/
|
|
1637
|
-
fetchFolderName = (folderId?: string) => {
|
|
1638
|
-
if (!folderId) {
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
this.api.getFolderAPI(false).getFolderFields(
|
|
1643
|
-
folderId,
|
|
1644
|
-
({ name }) => {
|
|
1645
|
-
this.setState({ rootName: name });
|
|
1646
|
-
},
|
|
1647
|
-
this.errorCallback,
|
|
1648
|
-
{ fields: [FIELD_NAME] },
|
|
1649
|
-
);
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
1650
|
/**
|
|
1653
1651
|
* Renders the file picker
|
|
1654
1652
|
*
|
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import type { EnumType, FloatType, MetadataFormFieldValue, RangeType } from '@box/metadata-filter';
|
|
2
3
|
import { MetadataView, type MetadataViewProps } from '@box/metadata-view';
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
import type { Collection } from '../../common/types/core';
|
|
6
|
+
import type { MetadataTemplate } from '../../common/types/metadata';
|
|
7
|
+
|
|
8
|
+
// Public-friendly version of MetadataFormFieldValue from @box/metadata-filter
|
|
9
|
+
// (string[] for enum type, range/float objects stay the same)
|
|
10
|
+
type EnumToStringArray<T> = T extends EnumType ? string[] : T;
|
|
11
|
+
type ExternalMetadataFormFieldValue = EnumToStringArray<MetadataFormFieldValue>;
|
|
12
|
+
|
|
13
|
+
type ExternalFilterValues = Record<
|
|
14
|
+
string,
|
|
15
|
+
{
|
|
16
|
+
value: ExternalMetadataFormFieldValue;
|
|
17
|
+
}
|
|
18
|
+
>;
|
|
5
19
|
|
|
6
|
-
|
|
20
|
+
type ActionBarProps = Omit<
|
|
21
|
+
MetadataViewProps['actionBarProps'],
|
|
22
|
+
'initialFilterValues' | 'onFilterSubmit' | 'filterGroups'
|
|
23
|
+
> & {
|
|
24
|
+
initialFilterValues?: ExternalFilterValues;
|
|
25
|
+
onFilterSubmit?: (filterValues: ExternalFilterValues) => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function transformInitialFilterValuesToInternal(
|
|
29
|
+
publicValues?: ExternalFilterValues,
|
|
30
|
+
): Record<string, { value: MetadataFormFieldValue }> | undefined {
|
|
31
|
+
if (!publicValues) return undefined;
|
|
32
|
+
|
|
33
|
+
return Object.entries(publicValues).reduce<Record<string, { value: MetadataFormFieldValue }>>(
|
|
34
|
+
(acc, [key, { value }]) => {
|
|
35
|
+
acc[key] = Array.isArray(value) ? { value: { enum: value } } : { value };
|
|
36
|
+
return acc;
|
|
37
|
+
},
|
|
38
|
+
{},
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function transformInternalFieldsToPublic(
|
|
43
|
+
fields: Record<string, { value: MetadataFormFieldValue }>,
|
|
44
|
+
): ExternalFilterValues {
|
|
45
|
+
return Object.entries(fields).reduce<ExternalFilterValues>((acc, [key, { value }]) => {
|
|
46
|
+
acc[key] =
|
|
47
|
+
'enum' in value && Array.isArray(value.enum)
|
|
48
|
+
? { value: value.enum }
|
|
49
|
+
: { value: value as RangeType | FloatType };
|
|
50
|
+
return acc;
|
|
51
|
+
}, {});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MetadataViewContainerProps extends Omit<MetadataViewProps, 'items' | 'actionBarProps'> {
|
|
55
|
+
actionBarProps?: ActionBarProps;
|
|
7
56
|
currentCollection: Collection;
|
|
8
57
|
metadataTemplate: MetadataTemplate;
|
|
9
58
|
}
|
|
@@ -16,6 +65,7 @@ const MetadataViewContainer = ({
|
|
|
16
65
|
...rest
|
|
17
66
|
}: MetadataViewContainerProps) => {
|
|
18
67
|
const { items = [] } = currentCollection;
|
|
68
|
+
const { initialFilterValues: initialFilterValuesProp, onFilterSubmit: onFilterSubmitProp } = actionBarProps ?? {};
|
|
19
69
|
|
|
20
70
|
const filterGroups = React.useMemo(
|
|
21
71
|
() => [
|
|
@@ -36,17 +86,32 @@ const MetadataViewContainer = ({
|
|
|
36
86
|
[metadataTemplate],
|
|
37
87
|
);
|
|
38
88
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
filterGroups,
|
|
44
|
-
}}
|
|
45
|
-
columns={columns}
|
|
46
|
-
items={items}
|
|
47
|
-
{...rest}
|
|
48
|
-
/>
|
|
89
|
+
// Transform initial filter values to internal field format
|
|
90
|
+
const initialFilterValues = React.useMemo(
|
|
91
|
+
() => transformInitialFilterValuesToInternal(initialFilterValuesProp),
|
|
92
|
+
[initialFilterValuesProp],
|
|
49
93
|
);
|
|
94
|
+
|
|
95
|
+
// Transform field values to public-friendly format
|
|
96
|
+
const onFilterSubmit = React.useCallback(
|
|
97
|
+
(fields: Record<string, { value: MetadataFormFieldValue }>) => {
|
|
98
|
+
if (!onFilterSubmitProp) return;
|
|
99
|
+
const transformed = transformInternalFieldsToPublic(fields);
|
|
100
|
+
onFilterSubmitProp(transformed);
|
|
101
|
+
},
|
|
102
|
+
[onFilterSubmitProp],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const transformedActionBarProps = React.useMemo(() => {
|
|
106
|
+
return {
|
|
107
|
+
...actionBarProps,
|
|
108
|
+
initialFilterValues,
|
|
109
|
+
onFilterSubmit,
|
|
110
|
+
filterGroups,
|
|
111
|
+
};
|
|
112
|
+
}, [actionBarProps, initialFilterValues, onFilterSubmit, filterGroups]);
|
|
113
|
+
|
|
114
|
+
return <MetadataView actionBarProps={transformedActionBarProps} columns={columns} items={items} {...rest} />;
|
|
50
115
|
};
|
|
51
116
|
|
|
52
117
|
export default MetadataViewContainer;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
import MetadataViewContainer, { MetadataViewContainerProps } from '../MetadataViewContainer';
|
|
2
|
+
|
|
4
3
|
import type { Collection } from '../../../common/types/core';
|
|
5
4
|
import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata';
|
|
5
|
+
import { render, screen, userEvent, waitFor, within } from '../../../test-utils/testing-library';
|
|
6
|
+
import MetadataViewContainer, { type MetadataViewContainerProps } from '../MetadataViewContainer';
|
|
6
7
|
|
|
7
8
|
describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
8
9
|
const mockItems = [
|
|
@@ -18,7 +19,7 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
18
19
|
type: 'string',
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
|
-
id: '
|
|
22
|
+
id: 'field2',
|
|
22
23
|
key: 'industry',
|
|
23
24
|
displayName: 'Industry',
|
|
24
25
|
type: 'enum',
|
|
@@ -80,4 +81,38 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
80
81
|
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
81
82
|
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
82
83
|
});
|
|
84
|
+
|
|
85
|
+
test('should pass values as string[] on submit', async () => {
|
|
86
|
+
const onFilterSubmit = jest.fn();
|
|
87
|
+
const template: MetadataTemplate = {
|
|
88
|
+
...mockMetadataTemplate,
|
|
89
|
+
fields: [
|
|
90
|
+
{
|
|
91
|
+
id: 'ms1',
|
|
92
|
+
key: 'role',
|
|
93
|
+
displayName: 'Contact Role',
|
|
94
|
+
type: 'multiSelect',
|
|
95
|
+
options: [
|
|
96
|
+
{ id: 'r1', key: 'Developer' },
|
|
97
|
+
{ id: 'r2', key: 'Marketing' },
|
|
98
|
+
{ id: 'r3', key: 'Sales' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
renderComponent({ metadataTemplate: template, actionBarProps: { onFilterSubmit } });
|
|
105
|
+
|
|
106
|
+
await userEvent().click(screen.getByRole('button', { name: /Contact Role/ }));
|
|
107
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Developer' }));
|
|
108
|
+
// Re-open the chip to select a second value (menu closes after submit)
|
|
109
|
+
await userEvent().click(screen.getByRole('button', { name: /Contact Role/ }));
|
|
110
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Marketing' }));
|
|
111
|
+
|
|
112
|
+
await waitFor(() => expect(onFilterSubmit).toHaveBeenCalledTimes(2));
|
|
113
|
+
const firstCall = onFilterSubmit.mock.calls[0][0];
|
|
114
|
+
const secondCall = onFilterSubmit.mock.calls[1][0];
|
|
115
|
+
expect(firstCall['role-filter'].value).toEqual(['Developer']);
|
|
116
|
+
expect(secondCall['role-filter'].value).toEqual(['Developer', 'Marketing']);
|
|
117
|
+
});
|
|
83
118
|
});
|
|
@@ -5,6 +5,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
5
5
|
import ContentExplorer from '../ContentExplorer';
|
|
6
6
|
import { DEFAULT_HOSTNAME_API } from '../../../constants';
|
|
7
7
|
import { mockMetadata, mockSchema } from '../../common/__mocks__/mockMetadata';
|
|
8
|
+
import { mockRootFolder } from '../../common/__mocks__/mockRootFolder';
|
|
8
9
|
|
|
9
10
|
const EID = '0';
|
|
10
11
|
const templateName = 'templateName';
|
|
@@ -120,6 +121,9 @@ const meta: Meta<typeof ContentExplorer> = {
|
|
|
120
121
|
http.get(`${DEFAULT_HOSTNAME_API}/2.0/metadata_templates/enterprise/templateName/schema`, () => {
|
|
121
122
|
return HttpResponse.json(mockSchema);
|
|
122
123
|
}),
|
|
124
|
+
http.get(`${DEFAULT_HOSTNAME_API}/2.0/folders/:id`, () => {
|
|
125
|
+
return HttpResponse.json(mockRootFolder);
|
|
126
|
+
}),
|
|
123
127
|
],
|
|
124
128
|
},
|
|
125
129
|
},
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { http, HttpResponse } from 'msw';
|
|
2
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { http, HttpResponse } from 'msw';
|
|
3
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
3
4
|
import { Download, SignMeOthers } from '@box/blueprint-web-assets/icons/Fill/index';
|
|
4
5
|
import { Sign } from '@box/blueprint-web-assets/icons/Line';
|
|
5
|
-
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
6
6
|
import noop from 'lodash/noop';
|
|
7
|
+
|
|
7
8
|
import ContentExplorer from '../../ContentExplorer';
|
|
8
9
|
import { DEFAULT_HOSTNAME_API } from '../../../../constants';
|
|
9
10
|
import { mockMetadata, mockSchema } from '../../../common/__mocks__/mockMetadata';
|
|
11
|
+
import { mockRootFolder } from '../../../common/__mocks__/mockRootFolder';
|
|
10
12
|
|
|
11
13
|
// The intent behind relying on mockMetadata is to allow a developer to paste in their own metadata template schema for use with live API calls.
|
|
12
14
|
const { scope: templateScope, templateKey } = mockSchema;
|
|
@@ -126,13 +128,42 @@ export const metadataViewV2WithCustomActions: Story = {
|
|
|
126
128
|
await waitFor(() => {
|
|
127
129
|
expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
|
|
128
130
|
});
|
|
129
|
-
|
|
130
131
|
const firstRow = canvas.getByRole('row', { name: /Child 2/i });
|
|
131
132
|
const ellipsesButton = within(firstRow).getByRole('button', { name: 'Action menu' });
|
|
132
133
|
userEvent.click(ellipsesButton);
|
|
133
134
|
},
|
|
134
135
|
};
|
|
135
136
|
|
|
137
|
+
const initialFilterActionBarProps = {
|
|
138
|
+
initialFilterValues: {
|
|
139
|
+
'industry-filter': { value: ['Legal'] },
|
|
140
|
+
'mimetype-filter': { value: ['boxnoteType', 'documentType', 'threedType'] },
|
|
141
|
+
'role-filter': { value: ['Developer', 'Business Owner', 'Marketing'] },
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const metadataViewV2WithInitialFilterValues: Story = {
|
|
146
|
+
args: {
|
|
147
|
+
...metadataViewV2ElementProps,
|
|
148
|
+
metadataViewProps: {
|
|
149
|
+
columns,
|
|
150
|
+
actionBarProps: initialFilterActionBarProps,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
play: async ({ canvas }) => {
|
|
154
|
+
// Wait for chips to update with initial values
|
|
155
|
+
await waitFor(() => {
|
|
156
|
+
expect(canvas.getByRole('button', { name: /Industry/i })).toHaveTextContent(/\(1\)/);
|
|
157
|
+
});
|
|
158
|
+
// Other chips should reflect initialized values
|
|
159
|
+
const contactRoleChip = canvas.getByRole('button', { name: /Contact Role/i });
|
|
160
|
+
expect(contactRoleChip).toHaveTextContent(/\(3\)/);
|
|
161
|
+
|
|
162
|
+
const fileTypeChip = canvas.getByRole('button', { name: /Box Note/i });
|
|
163
|
+
expect(fileTypeChip).toHaveTextContent(/\+2/);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
136
167
|
const meta: Meta<typeof ContentExplorer> = {
|
|
137
168
|
title: 'Elements/ContentExplorer/tests/MetadataView/visual',
|
|
138
169
|
component: ContentExplorer,
|
|
@@ -150,6 +181,9 @@ const meta: Meta<typeof ContentExplorer> = {
|
|
|
150
181
|
http.get(`${DEFAULT_HOSTNAME_API}/2.0/metadata_templates/enterprise/templateName/schema`, () => {
|
|
151
182
|
return HttpResponse.json(mockSchema);
|
|
152
183
|
}),
|
|
184
|
+
http.get(`${DEFAULT_HOSTNAME_API}/2.0/folders/:id`, () => {
|
|
185
|
+
return HttpResponse.json(mockRootFolder);
|
|
186
|
+
}),
|
|
153
187
|
],
|
|
154
188
|
},
|
|
155
189
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { type RouterHistory } from 'react-router-dom';
|
|
4
|
+
import { withRouterIfEnabled } from '../common/routing';
|
|
4
5
|
|
|
5
6
|
import AddTaskMenu from './AddTaskMenu';
|
|
6
7
|
import TaskModal from './TaskModal';
|
|
@@ -11,7 +12,7 @@ import type { ElementsXhrError } from '../../common/types/api';
|
|
|
11
12
|
import type { InternalSidebarNavigation, InternalSidebarNavigationHandler } from '../common/types/SidebarNavigation';
|
|
12
13
|
|
|
13
14
|
type Props = {|
|
|
14
|
-
history
|
|
15
|
+
history?: RouterHistory,
|
|
15
16
|
internalSidebarNavigation?: InternalSidebarNavigation,
|
|
16
17
|
internalSidebarNavigationHandler?: InternalSidebarNavigationHandler,
|
|
17
18
|
isDisabled: boolean,
|
|
@@ -54,7 +55,7 @@ class AddTaskButton extends React.Component<Props, State> {
|
|
|
54
55
|
},
|
|
55
56
|
true,
|
|
56
57
|
);
|
|
57
|
-
} else {
|
|
58
|
+
} else if (history) {
|
|
58
59
|
history.replace({ state: { open: true } });
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -103,4 +104,4 @@ class AddTaskButton extends React.Component<Props, State> {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
export { AddTaskButton as AddTaskButtonComponent };
|
|
106
|
-
export default
|
|
107
|
+
export default withRouterIfEnabled(AddTaskButton);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as React from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { type RouterHistory } from 'react-router-dom';
|
|
9
|
+
import { withRouterIfEnabled } from '../common/routing';
|
|
9
10
|
import SidebarToggleButton from '../../components/sidebar-toggle-button/SidebarToggleButton';
|
|
10
11
|
import { SIDEBAR_NAV_TARGETS } from '../common/interactionTargets';
|
|
11
12
|
import type { InternalSidebarNavigation, InternalSidebarNavigationHandler } from '../common/types/SidebarNavigation';
|
|
@@ -53,4 +54,4 @@ const SidebarToggle = ({
|
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
export { SidebarToggle as SidebarToggleComponent };
|
|
56
|
-
export default
|
|
57
|
+
export default withRouterIfEnabled(SidebarToggle);
|
|
@@ -4,7 +4,7 @@ exports[`elements/content-sidebar/ActivitySidebar render() should render the act
|
|
|
4
4
|
<SidebarContent
|
|
5
5
|
actions={
|
|
6
6
|
<React.Fragment>
|
|
7
|
-
<
|
|
7
|
+
<withRouterIfEnabled(AddTaskButton)
|
|
8
8
|
isDisabled={false}
|
|
9
9
|
onTaskModalClose={[Function]}
|
|
10
10
|
taskFormProps={
|
|
@@ -9,7 +9,7 @@ import flow from 'lodash/flow';
|
|
|
9
9
|
import getProp from 'lodash/get';
|
|
10
10
|
import merge from 'lodash/merge';
|
|
11
11
|
import noop from 'lodash/noop';
|
|
12
|
-
import { generatePath
|
|
12
|
+
import { generatePath } from 'react-router-dom';
|
|
13
13
|
import type { Match, RouterHistory } from 'react-router-dom';
|
|
14
14
|
import type { MessageDescriptor } from 'react-intl';
|
|
15
15
|
import { withFeatureConsumer, isFeatureEnabled } from '../../common/feature-checking';
|
|
@@ -21,6 +21,7 @@ import StaticVersionsSidebar from './StaticVersionSidebar';
|
|
|
21
21
|
import VersionsSidebar from './VersionsSidebar';
|
|
22
22
|
import VersionsSidebarAPI from './VersionsSidebarAPI';
|
|
23
23
|
import { withAPIContext } from '../../common/api-context';
|
|
24
|
+
import { withRouterIfEnabled } from '../../common/routing';
|
|
24
25
|
import type { FeatureConfig } from '../../common/feature-checking';
|
|
25
26
|
import type { VersionActionCallback, VersionChangeCallback, SidebarLoadCallback } from './flowTypes';
|
|
26
27
|
import type { BoxItemVersion, BoxItem, FileVersions } from '../../../common/types/core';
|
|
@@ -36,7 +37,7 @@ type Props = {
|
|
|
36
37
|
features: FeatureConfig,
|
|
37
38
|
fileId: string,
|
|
38
39
|
hasSidebarInitialized?: boolean,
|
|
39
|
-
history
|
|
40
|
+
history?: RouterHistory,
|
|
40
41
|
internalSidebarNavigation?: InternalSidebarNavigation,
|
|
41
42
|
internalSidebarNavigationHandler?: InternalSidebarNavigationHandler,
|
|
42
43
|
match: Match,
|
|
@@ -283,7 +284,7 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
|
|
|
283
284
|
delete navigationUpdate.versionId;
|
|
284
285
|
}
|
|
285
286
|
internalSidebarNavigationHandler(navigationUpdate);
|
|
286
|
-
} else {
|
|
287
|
+
} else if (history) {
|
|
287
288
|
history.push(generatePath(match.path, { ...match.params, versionId }));
|
|
288
289
|
}
|
|
289
290
|
};
|
|
@@ -349,4 +350,4 @@ class VersionsSidebarContainer extends React.Component<Props, State> {
|
|
|
349
350
|
|
|
350
351
|
export type VersionsSidebarProps = Props;
|
|
351
352
|
export { VersionsSidebarContainer as VersionsSidebarContainerComponent };
|
|
352
|
-
export default flow([
|
|
353
|
+
export default flow([withRouterIfEnabled, withAPIContext, withFeatureConsumer])(VersionsSidebarContainer);
|