box-ui-elements 23.5.0-beta.3 → 24.0.0-beta.1

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.
Files changed (66) hide show
  1. package/dist/explorer.css +1 -1
  2. package/dist/explorer.js +1 -1
  3. package/dist/picker.js +1 -1
  4. package/dist/preview.css +1 -1
  5. package/dist/preview.js +1 -1
  6. package/dist/sidebar.css +1 -1
  7. package/dist/sidebar.js +1 -1
  8. package/es/constants.js +13 -0
  9. package/es/constants.js.flow +13 -0
  10. package/es/constants.js.map +1 -1
  11. package/es/elements/common/content-answers/ContentAnswersModal.js +1 -3
  12. package/es/elements/common/content-answers/ContentAnswersModal.js.map +1 -1
  13. package/es/elements/common/sub-header/SubHeader.js +3 -0
  14. package/es/elements/common/sub-header/SubHeader.js.map +1 -1
  15. package/es/elements/common/sub-header/SubHeaderLeftV2.js +3 -23
  16. package/es/elements/common/sub-header/SubHeaderLeftV2.js.map +1 -1
  17. package/es/elements/common/sub-header/SubHeaderRight.js +6 -2
  18. package/es/elements/common/sub-header/SubHeaderRight.js.map +1 -1
  19. package/es/elements/content-explorer/ContentExplorer.js +55 -10
  20. package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
  21. package/es/elements/content-explorer/ContentExplorer.scss +12 -0
  22. package/es/elements/content-explorer/MetadataSidePanel.js +92 -0
  23. package/es/elements/content-explorer/MetadataSidePanel.js.map +1 -0
  24. package/es/elements/content-explorer/MetadataSidePanel.scss +12 -0
  25. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +54 -3
  26. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
  27. package/es/elements/content-explorer/utils.js +67 -0
  28. package/es/elements/content-explorer/utils.js.map +1 -0
  29. package/es/elements/content-sidebar/BoxAISidebar.js.map +1 -1
  30. package/es/elements/content-sidebar/BoxAISidebarContent.js +2 -4
  31. package/es/elements/content-sidebar/BoxAISidebarContent.js.map +1 -1
  32. package/es/elements/content-sidebar/stories/BoxAISidebar.stories.js +0 -1
  33. package/es/elements/content-sidebar/stories/BoxAISidebar.stories.js.map +1 -1
  34. package/es/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.js +0 -1
  35. package/es/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.js.map +1 -1
  36. package/es/src/elements/common/content-answers/ContentAnswersModal.d.ts +0 -1
  37. package/es/src/elements/common/sub-header/SubHeader.d.ts +2 -1
  38. package/es/src/elements/common/sub-header/SubHeaderLeftV2.d.ts +1 -1
  39. package/es/src/elements/common/sub-header/SubHeaderRight.d.ts +4 -1
  40. package/es/src/elements/content-explorer/ContentExplorer.d.ts +15 -0
  41. package/es/src/elements/content-explorer/MetadataSidePanel.d.ts +13 -0
  42. package/es/src/elements/content-explorer/__tests__/MetadataSidePanel.test.d.ts +1 -0
  43. package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +2 -0
  44. package/es/src/elements/content-explorer/utils.d.ts +22 -0
  45. package/es/src/elements/content-sidebar/BoxAISidebar.d.ts +0 -1
  46. package/es/src/elements/content-sidebar/stories/BoxAISidebar.stories.d.ts +0 -1
  47. package/package.json +2 -2
  48. package/src/constants.js +13 -0
  49. package/src/elements/common/content-answers/ContentAnswersModal.tsx +0 -3
  50. package/src/elements/common/content-answers/__tests__/ContentAnswersModal.test.tsx +7 -2
  51. package/src/elements/common/sub-header/SubHeader.tsx +4 -0
  52. package/src/elements/common/sub-header/SubHeaderLeftV2.tsx +3 -22
  53. package/src/elements/common/sub-header/SubHeaderRight.tsx +8 -2
  54. package/src/elements/content-explorer/ContentExplorer.scss +12 -0
  55. package/src/elements/content-explorer/ContentExplorer.tsx +135 -77
  56. package/src/elements/content-explorer/MetadataSidePanel.scss +12 -0
  57. package/src/elements/content-explorer/MetadataSidePanel.tsx +126 -0
  58. package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +80 -16
  59. package/src/elements/content-explorer/__tests__/MetadataSidePanel.test.tsx +127 -0
  60. package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +43 -3
  61. package/src/elements/content-explorer/utils.ts +58 -0
  62. package/src/elements/content-sidebar/BoxAISidebar.tsx +0 -1
  63. package/src/elements/content-sidebar/BoxAISidebarContent.tsx +1 -3
  64. package/src/elements/content-sidebar/__tests__/BoxAISidebar.test.tsx +0 -8
  65. package/src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx +0 -1
  66. package/src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx +0 -1
@@ -0,0 +1,127 @@
1
+ import * as React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '../../../test-utils/testing-library';
4
+ import MetadataSidePanel, { type MetadataSidePanelProps } from '../MetadataSidePanel';
5
+
6
+ // Mock scrollTo method
7
+ Object.defineProperty(Element.prototype, 'scrollTo', {
8
+ value: jest.fn(),
9
+ writable: true,
10
+ });
11
+
12
+ const mockCollection = {
13
+ items: [
14
+ {
15
+ id: '1',
16
+ name: 'Test File 1.pdf',
17
+ type: 'file',
18
+ metadata: {
19
+ enterprise_123: {
20
+ mockTemplate: {
21
+ alias: 'mock-alias-1',
22
+ },
23
+ },
24
+ },
25
+ },
26
+ {
27
+ id: '2',
28
+ name: 'Test File 2.docx',
29
+ type: 'file',
30
+ metadata: {
31
+ enterprise_123: {
32
+ mockTemplate: {
33
+ alias: 'mock-alias-2',
34
+ },
35
+ },
36
+ },
37
+ },
38
+ ],
39
+ nextMarker: null,
40
+ offset: 0,
41
+ totalCount: 2,
42
+ };
43
+
44
+ const mockMetadataTemplate = {
45
+ id: 'template-id',
46
+ displayName: 'Mock Template',
47
+ scope: 'enterprise_123',
48
+ templateKey: 'mockTemplate',
49
+ type: 'metadata_template',
50
+ hidden: false,
51
+ fields: [
52
+ {
53
+ id: '123',
54
+ key: 'alias',
55
+ displayName: 'Alias',
56
+ type: 'string',
57
+ hidden: false,
58
+ options: [],
59
+ },
60
+ ],
61
+ };
62
+
63
+ const mockOnClose = jest.fn();
64
+
65
+ describe('elements/content-explorer/MetadataSidePanel', () => {
66
+ const defaultProps: MetadataSidePanelProps = {
67
+ currentCollection: mockCollection,
68
+ onClose: mockOnClose,
69
+ metadataTemplate: mockMetadataTemplate,
70
+ selectedItemIds: new Set(['1']),
71
+ };
72
+
73
+ const renderComponent = (props: Partial<MetadataSidePanelProps> = {}) =>
74
+ render(<MetadataSidePanel {...defaultProps} {...props} />);
75
+
76
+ test('renders the metadata title', () => {
77
+ renderComponent();
78
+ expect(screen.getByText('Metadata')).toBeInTheDocument();
79
+ });
80
+
81
+ test('renders the close button with proper aria-label', () => {
82
+ renderComponent();
83
+ const closeButton = screen.getByLabelText('Close');
84
+ expect(closeButton).toBeInTheDocument();
85
+ });
86
+
87
+ test('renders the selected item text', () => {
88
+ renderComponent();
89
+ expect(screen.getByText('Test File 1.pdf')).toBeInTheDocument();
90
+ });
91
+
92
+ test('renders metadata instance (view mode) by default', () => {
93
+ renderComponent();
94
+ const editTemplateButton = screen.getByLabelText('Edit Mock Template');
95
+ expect(editTemplateButton).toBeInTheDocument();
96
+ });
97
+
98
+ test('renders field value of selected item', () => {
99
+ renderComponent();
100
+ const fieldValue = screen.getByText('mock-alias-1');
101
+ expect(fieldValue).toBeInTheDocument();
102
+ });
103
+
104
+ test('call onClose when close button is clicked', async () => {
105
+ renderComponent();
106
+ const closeButton = screen.getByLabelText('Close');
107
+ await userEvent.click(closeButton);
108
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
109
+ });
110
+
111
+ test('render correct subtitle when multiple items are selected', () => {
112
+ renderComponent({ selectedItemIds: new Set(['1', '2']) });
113
+ const subtitle = screen.getByText('2 files selected');
114
+ expect(subtitle).toBeInTheDocument();
115
+ });
116
+
117
+ test('render cancel and submit button when in edit mode', async () => {
118
+ renderComponent();
119
+ const editTemplateButton = screen.getByLabelText('Edit Mock Template');
120
+ await userEvent.click(editTemplateButton);
121
+
122
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
123
+ expect(cancelButton).toBeInTheDocument();
124
+ const submitButton = screen.getByRole('button', { name: 'Save' });
125
+ expect(submitButton).toBeInTheDocument();
126
+ });
127
+ });
@@ -21,7 +21,7 @@ const metadataFieldNamePrefix = `metadata.${metadataScopeAndKey}`;
21
21
  const metadataQuery = {
22
22
  from: metadataScopeAndKey,
23
23
  ancestor_folder_id: '0',
24
- sort_by: [
24
+ order_by: [
25
25
  {
26
26
  field_key: `${metadataFieldNamePrefix}.${mockSchema.fields[0].key}`, // Default to sorting by the first field in the schema
27
27
  direction: 'asc',
@@ -49,7 +49,7 @@ const columns = [
49
49
  textValue: 'Name',
50
50
  id: 'name',
51
51
  type: 'string',
52
- allowSorting: true,
52
+ allowsSorting: true,
53
53
  minWidth: 150,
54
54
  maxWidth: 150,
55
55
  },
@@ -57,7 +57,7 @@ const columns = [
57
57
  textValue: field.displayName,
58
58
  id: `${metadataFieldNamePrefix}.${field.key}`,
59
59
  type: field.type,
60
- allowSorting: true,
60
+ allowsSorting: true,
61
61
  minWidth: 150,
62
62
  maxWidth: 150,
63
63
  })),
@@ -94,6 +94,20 @@ export const metadataViewV2: Story = {
94
94
  args: metadataViewV2ElementProps,
95
95
  };
96
96
 
97
+ // @TODO Assert that rows are actually sorted in a different order, once handleSortChange is implemented
98
+ export const metadataViewV2SortsFromHeader: Story = {
99
+ args: metadataViewV2ElementProps,
100
+ play: async ({ canvas }) => {
101
+ await waitFor(() => {
102
+ expect(canvas.getByRole('row', { name: /Industry/i })).toBeInTheDocument();
103
+ });
104
+
105
+ const firstRow = canvas.getByRole('row', { name: /Industry/i });
106
+ const industryHeader = within(firstRow).getByRole('columnheader', { name: 'Industry' });
107
+ userEvent.click(industryHeader);
108
+ },
109
+ };
110
+
97
111
  export const metadataViewV2WithCustomActions: Story = {
98
112
  args: {
99
113
  ...metadataViewV2ElementProps,
@@ -164,6 +178,32 @@ export const metadataViewV2WithInitialFilterValues: Story = {
164
178
  },
165
179
  };
166
180
 
181
+ export const sidePanelOpenWithSingleItemSelected: Story = {
182
+ args: {
183
+ ...metadataViewV2ElementProps,
184
+ metadataViewProps: {
185
+ columns,
186
+ tableProps: {
187
+ isSelectAllEnabled: true,
188
+ },
189
+ },
190
+ },
191
+
192
+ play: async ({ canvas }) => {
193
+ await waitFor(() => {
194
+ expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
195
+ });
196
+
197
+ // Select the first row by clicking its checkbox
198
+ const firstRow = canvas.getByRole('row', { name: /Child 2/i });
199
+ const checkbox = within(firstRow).getByRole('checkbox');
200
+ await userEvent.click(checkbox);
201
+
202
+ const metadataButton = canvas.getByRole('button', { name: 'Metadata' });
203
+ await userEvent.click(metadataButton);
204
+ },
205
+ };
206
+
167
207
  const meta: Meta<typeof ContentExplorer> = {
168
208
  title: 'Elements/ContentExplorer/tests/MetadataView/visual',
169
209
  component: ContentExplorer,
@@ -0,0 +1,58 @@
1
+ import { useMemo } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+
4
+ import type { MetadataTemplate } from '@box/metadata-editor';
5
+ import type { Selection } from 'react-aria-components';
6
+ import type { BoxItem, Collection } from '../../common/types/core';
7
+
8
+ import messages from '../common/messages';
9
+
10
+ // Get selected item text
11
+ export function useSelectedItemText(currentCollection: Collection, selectedItemIds: Selection): string {
12
+ const { formatMessage } = useIntl();
13
+
14
+ return useMemo(() => {
15
+ const selectedCount = selectedItemIds === 'all' ? currentCollection.items.length : selectedItemIds.size;
16
+ if (selectedCount === 0) return '';
17
+
18
+ // Case 1: Single selected item - show item name
19
+ if (selectedCount === 1) {
20
+ const selectedKey =
21
+ selectedItemIds === 'all' ? currentCollection.items[0].id : selectedItemIds.values().next().value;
22
+ const selectedItem = currentCollection.items.find(item => item.id === selectedKey);
23
+ return selectedItem?.name ?? '';
24
+ }
25
+
26
+ // Case 2: Multiple selected items - show count
27
+ return formatMessage(messages.numFilesSelected, { numSelected: selectedCount });
28
+ }, [currentCollection.items, formatMessage, selectedItemIds]);
29
+ }
30
+
31
+ // Get template instance based on metadata template and selected items
32
+ export function getTemplateInstance(metadataTemplate: MetadataTemplate, selectedItems: BoxItem[]) {
33
+ const { displayName, fields, hidden, id, scope, templateKey, type } = metadataTemplate;
34
+
35
+ const selectedItemsFields = fields.map(
36
+ ({ displayName: fieldDisplayName, hidden: fieldHidden, id: fieldId, key, options, type: fieldType }) => ({
37
+ displayName: fieldDisplayName,
38
+ hidden: fieldHidden,
39
+ id: fieldId,
40
+ key,
41
+ options,
42
+ type: fieldType,
43
+ // TODO: Add support for multiple selected items
44
+ value: selectedItems[0].metadata[scope][templateKey][key],
45
+ }),
46
+ );
47
+
48
+ return {
49
+ canEdit: true,
50
+ displayName,
51
+ hidden,
52
+ id,
53
+ fields: selectedItemsFields,
54
+ scope,
55
+ templateKey,
56
+ type,
57
+ };
58
+ }
@@ -45,7 +45,6 @@ export interface BoxAISidebarProps {
45
45
  isFeedbackFormEnabled: boolean;
46
46
  isIntelligentQueryMode: boolean;
47
47
  isMarkdownEnabled: boolean;
48
- isResetChatEnabled: boolean;
49
48
  isStopResponseEnabled?: boolean;
50
49
  isStreamingEnabled: boolean;
51
50
  items: Array<ItemType>;
@@ -50,7 +50,6 @@ function BoxAISidebarContent(
50
50
  hostAppName,
51
51
  isAIStudioAgentSelectorEnabled,
52
52
  isLoading,
53
- isResetChatEnabled,
54
53
  onSelectAgent,
55
54
  questions,
56
55
  shouldShowLandingPage,
@@ -191,7 +190,7 @@ function BoxAISidebarContent(
191
190
  const renderActions = () => (
192
191
  <>
193
192
  {renderBoxAISidebarTitle()}
194
- {isResetChatEnabled && <ClearConversationButton onClick={onClearAction} />}
193
+ <ClearConversationButton onClick={onClearAction} />
195
194
  <Tooltip content={formatMessage(messages.sidebarBoxAISwitchToModalView)} variant="standard">
196
195
  <IconButton
197
196
  aria-label={formatMessage(messages.sidebarBoxAISwitchToModalView)}
@@ -248,7 +247,6 @@ function BoxAISidebarContent(
248
247
  isAIStudioAgentSelectorEnabled={isAIStudioAgentSelectorEnabled}
249
248
  isFeedbackEnabled={isFeedbackEnabled}
250
249
  isFeedbackFormEnabled={isFeedbackFormEnabled}
251
- isResetChatEnabled={isResetChatEnabled}
252
250
  isStopResponseEnabled={isStopResponseEnabled}
253
251
  items={items}
254
252
  itemSize={itemSize}
@@ -36,7 +36,6 @@ jest.mock('@box/box-ai-content-answers', () => ({
36
36
  isMarkdownEnabled={props.isMarkdownEnabled}
37
37
  isLoading={props.isLoading}
38
38
  isOpen
39
- isResetChatEnabled={props.isResetChatEnabled}
40
39
  isStreamingEnabled={props.isStreamingEnabled}
41
40
  itemID={props.itemID}
42
41
  itemIDs={props.itemIDs}
@@ -120,7 +119,6 @@ describe('elements/content-sidebar/BoxAISidebar', () => {
120
119
  isFeedbackFormEnabled: true,
121
120
  isIntelligentQueryMode: true,
122
121
  isMarkdownEnabled: true,
123
- isResetChatEnabled: true,
124
122
  isStopResponseEnabled: true,
125
123
  isStreamingEnabled: true,
126
124
  onFeedbackFormSubmit: jest.fn(),
@@ -186,12 +184,6 @@ describe('elements/content-sidebar/BoxAISidebar', () => {
186
184
  expect(screen.getByRole('button', { name: 'Clear conversation' })).toBeInTheDocument();
187
185
  });
188
186
 
189
- test('should not have accessible "Clear" button if isResetChatEnabled is false', async () => {
190
- await renderComponent({ isResetChatEnabled: false });
191
-
192
- expect(screen.queryByRole('button', { name: 'Clear' })).not.toBeInTheDocument();
193
- });
194
-
195
187
  test('should call recordAction on load if provided', async () => {
196
188
  const mockRecordAction = jest.fn();
197
189
  await renderComponent({ recordAction: mockRecordAction });
@@ -43,7 +43,6 @@ export default {
43
43
  isFeedbackEnabled: true,
44
44
  isIntelligentQueryMode: false,
45
45
  isMarkdownEnabled: true,
46
- isResetChatEnabled: true,
47
46
  isStopResponseEnabled: true,
48
47
  isStreamingEnabled: false,
49
48
  items: [{ id: '123', name: 'Document (PDF).pdf', type: 'file', fileType: 'pdf', status: 'supported' }],
@@ -49,7 +49,6 @@ const meta: Meta<typeof ContentSidebar> & { parameters: { msw: { handlers: HttpH
49
49
  isDebugModeEnabled: true,
50
50
  isIntelligentQueryMode: false,
51
51
  isMarkdownEnabled: true,
52
- isResetChatEnabled: true,
53
52
  isStopResponseEnabled: true,
54
53
  isStreamingEnabled: false,
55
54
  items: [{ id: '123', name: 'Document (PDF).pdf', type: 'file', fileType: 'pdf', status: 'supported' }],