Skip to content

Commit 89e7af1

Browse files
FBanfiryunsong-contentfulharikakondur
authored
Google Docs: Adding select tabs step [INTEG-3534] (#10704)
* feat: extend timeout to 50 minutes and add in timing markers (#10608) * fix: update AGENT_ANALYZER_ID constant to reflect new workflow agent name (#10613) * fix: update local agents API URL to use constant for base URL * fix(google-docs): add pagination for content type fetching [INTEG-3491] (#10674) * feat: implement pagination for content type fetching in SelectContentTypeModal * fix: add checks to avoid infinite loop * chore: add env example * chore: update readme * fix: cleanup test modal [INTEG-3496] (#10682) * feat(google-docs): make the test modal more dev friendly * fix(google-docs): cleanup content type ids and spacing * cleaning unnecessary steps * adding close button for first step * adding select tabs step * removing unused custom hook and usages * solving conflicts in merge * adding select tabs step * removing unused hook * adding select tabs modal tests * wip changing tabs modal step * refactors and fixing multiselect bug * refactoring selected tabs and content types modals * fixing build errors * adding truncation in content types selection * addressing PR comments * changing onClose from tabs step * addressing pr comments * changing selected tabs setter usage --------- Co-authored-by: ryunsong-contentful <124832189+ryunsong-contentful@users.noreply.github.com> Co-authored-by: Harika Kondur <107296300+harikakondur@users.noreply.github.com> Co-authored-by: harika kondur <harika.kondur@contentful.com>
1 parent afc7810 commit 89e7af1

File tree

12 files changed

+538
-28
lines changed

12 files changed

+538
-28
lines changed

apps/google-docs/src/hooks/useModalManagement.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ export enum ModalType {
55
CONTENT_TYPE_PICKER = 'contentTypePicker',
66
CONFIRM_CANCEL = 'confirmCancel',
77
ERROR_PREVIEW = 'errorPreview',
8+
SELECT_TABS = 'selectTabs',
89
}
910

1011
export const useModalManagement = () => {
1112
const [isUploadModalOpen, setIsUploadModalOpen] = useState<boolean>(false);
1213
const [isContentTypePickerOpen, setIsContentTypePickerOpen] = useState<boolean>(false);
1314
const [isConfirmCancelModalOpen, setIsConfirmCancelModalOpen] = useState<boolean>(false);
1415
const [isErrorPreviewModalOpen, setIsErrorPreviewModalOpen] = useState<boolean>(false);
16+
const [isSelectTabsModalOpen, setIsSelectTabsModalOpen] = useState<boolean>(false);
1517

1618
const openModal = (modalType: ModalType) => {
1719
switch (modalType) {
@@ -27,6 +29,9 @@ export const useModalManagement = () => {
2729
case ModalType.ERROR_PREVIEW:
2830
setIsErrorPreviewModalOpen(true);
2931
break;
32+
case ModalType.SELECT_TABS:
33+
setIsSelectTabsModalOpen(true);
34+
break;
3035
}
3136
};
3237

@@ -44,6 +49,9 @@ export const useModalManagement = () => {
4449
case ModalType.ERROR_PREVIEW:
4550
setIsErrorPreviewModalOpen(false);
4651
break;
52+
case ModalType.SELECT_TABS:
53+
setIsSelectTabsModalOpen(false);
54+
break;
4755
}
4856
};
4957

@@ -53,6 +61,7 @@ export const useModalManagement = () => {
5361
isContentTypePickerOpen,
5462
isConfirmCancelModalOpen,
5563
isErrorPreviewModalOpen,
64+
isSelectTabsModalOpen,
5665
},
5766
openModal,
5867
closeModal,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useEffect, useRef, type RefObject } from 'react';
2+
3+
export const useMultiselectScrollReflow = <T>(selection: T[]): RefObject<HTMLUListElement> => {
4+
const listRef = useRef<HTMLUListElement>(null);
5+
6+
useEffect(() => {
7+
if (listRef.current) {
8+
const element = listRef.current;
9+
const currentScroll = element.scrollTop;
10+
const maxScroll = element.scrollHeight - element.clientHeight;
11+
12+
if (currentScroll >= maxScroll) {
13+
element.scrollTop = currentScroll - 1;
14+
} else {
15+
element.scrollTop = currentScroll + 1;
16+
}
17+
18+
element.scrollTop = currentScroll;
19+
}
20+
}, [selection]);
21+
22+
return listRef;
23+
};

apps/google-docs/src/hooks/useProgressTracking.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import { useState, useCallback } from 'react';
22
import { ContentTypeProps } from 'contentful-management';
3+
import { DocumentTabProps } from '../utils/types';
34

45
export const useProgressTracking = () => {
56
const [documentId, setDocumentId] = useState<string>('');
67
const [selectedContentTypes, setSelectedContentTypes] = useState<ContentTypeProps[]>([]);
78
const [pendingCloseAction, setPendingCloseAction] = useState<(() => void) | null>(null);
9+
const [availableTabs, setAvailableTabs] = useState<DocumentTabProps[]>([]);
10+
const [selectedTabs, setSelectedTabs] = useState<DocumentTabProps[]>([]);
811

912
const hasProgress = documentId.trim().length > 0;
1013

1114
const resetProgress = useCallback(() => {
1215
setDocumentId('');
1316
setSelectedContentTypes([]);
17+
setAvailableTabs([]);
18+
setSelectedTabs([]);
1419
}, []);
1520

1621
return {
1722
documentId,
1823
setDocumentId,
1924
selectedContentTypes,
2025
setSelectedContentTypes,
26+
availableTabs,
27+
setAvailableTabs,
28+
selectedTabs,
29+
setSelectedTabs,
2130
hasProgress,
2231
resetProgress,
2332
pendingCloseAction,

apps/google-docs/src/locations/Page/components/mainpage/ModalOrchestrator.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { useModalManagement, ModalType } from '../../../../hooks/useModalManagem
55
import { useProgressTracking } from '../../../../hooks/useProgressTracking';
66
import { ErrorModal } from '../modals/ErrorModal';
77
import SelectDocumentModal from '../modals/step_1/SelectDocumentModal';
8-
import { ContentTypePickerModal } from '../modals/step_2/SelectContentTypeModal';
98
import { LoadingModal } from '../modals/LoadingModal';
109
import { ERROR_MESSAGES } from '../../../../utils/constants/messages';
10+
import { SelectTabsModal } from '../modals/step_3/SelectTabsModal';
11+
import { DocumentTabProps } from '../../../../utils/types';
12+
import { ContentTypePickerModal } from '../modals/step_2/ContentTypePickerModal';
1113

1214
export interface ModalOrchestratorHandle {
1315
startFlow: () => void;
@@ -26,6 +28,10 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
2628
setDocumentId,
2729
selectedContentTypes,
2830
setSelectedContentTypes,
31+
availableTabs,
32+
setAvailableTabs,
33+
selectedTabs,
34+
setSelectedTabs,
2935
hasProgress,
3036
resetProgress: resetProgressTracking,
3137
pendingCloseAction,
@@ -41,6 +47,7 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
4147
resetProgressTracking();
4248
closeModal(ModalType.UPLOAD);
4349
closeModal(ModalType.CONTENT_TYPE_PICKER);
50+
closeModal(ModalType.SELECT_TABS);
4451
};
4552

4653
const handleUploadModalCloseRequest = (docId?: string) => {
@@ -74,6 +81,18 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
7481
}
7582
};
7683

84+
const handleSelectTabsCloseRequest = () => {
85+
if (hasProgress) {
86+
closeModal(ModalType.SELECT_TABS);
87+
setPendingCloseAction(() => () => {
88+
resetProgress();
89+
});
90+
openModal(ModalType.CONFIRM_CANCEL);
91+
} else {
92+
closeModal(ModalType.SELECT_TABS);
93+
}
94+
};
95+
7796
const handleConfirmCancel = () => {
7897
closeModal(ModalType.CONFIRM_CANCEL);
7998
if (pendingCloseAction) {
@@ -94,7 +113,13 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
94113
// The modal already updates `selectedContentTypes` via `setSelectedContentTypes`, so we don't need to set it here.
95114
void contentTypeIdsCsv;
96115
// setSelectedContentTypes(contentTypes);
97-
// TODO : open content tabs step
116+
openModal(ModalType.SELECT_TABS);
117+
};
118+
119+
const handleSelectTabsContinue = (tabs: DocumentTabProps[]) => {
120+
setSelectedTabs(tabs);
121+
closeModal(ModalType.SELECT_TABS);
122+
// TODO: add preview step and redirect to it, using selectedTabs
98123
};
99124

100125
const handleErrorPreviewModalClose = () => {
@@ -124,6 +149,16 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
124149
setSelectedContentTypes={setSelectedContentTypes}
125150
/>
126151

152+
<SelectTabsModal
153+
isOpen={modalStates.isSelectTabsModalOpen}
154+
onContinue={handleSelectTabsContinue}
155+
onClose={handleSelectTabsCloseRequest}
156+
availableTabs={availableTabs}
157+
setAvailableTabs={setAvailableTabs}
158+
selectedTabs={selectedTabs}
159+
setSelectedTabs={setSelectedTabs}
160+
/>
161+
127162
<ConfirmCancelModal
128163
isOpen={modalStates.isConfirmCancelModalOpen}
129164
onConfirm={handleConfirmCancel}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { css } from '@emotion/css';
2+
import tokens from '@contentful/f36-tokens';
3+
4+
export const multiselect = css({
5+
maxWidth: '74%',
6+
});
7+
8+
export const pillsContainer = css({
9+
marginRight: tokens.spacingXl,
10+
maxHeight: '100px',
11+
overflowY: 'scroll',
12+
'&::-webkit-scrollbar': {
13+
width: tokens.spacingXs,
14+
height: tokens.spacingXs,
15+
},
16+
'&::-webkit-scrollbar-thumb': {
17+
background: tokens.gray400,
18+
borderRadius: tokens.borderRadiusSmall,
19+
},
20+
});

apps/google-docs/src/locations/Page/components/modals/step_2/SelectContentTypeModal.tsx renamed to apps/google-docs/src/locations/Page/components/modals/step_2/ContentTypePickerModal.tsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState, useMemo, useRef } from 'react';
1+
import { useEffect, useState, useMemo } from 'react';
22
import {
33
Button,
44
Flex,
@@ -11,6 +11,9 @@ import {
1111
import { PageAppSDK } from '@contentful/app-sdk';
1212
import { ContentTypeProps } from 'contentful-management';
1313
import { css } from '@emotion/css';
14+
import { useMultiselectScrollReflow } from '../../../../../hooks/useMultiselectReflow';
15+
import { multiselect, pillsContainer } from './ContentTypePickerModal.styles';
16+
import { truncateLabel } from '../../../../../utils/utils';
1417

1518
interface ContentTypePickerModalProps {
1619
sdk: PageAppSDK;
@@ -41,7 +44,7 @@ export const ContentTypePickerModal = ({
4144
const [isLoading, setIsLoading] = useState<boolean>(false);
4245
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState<boolean>(false);
4346
const [hasFetchError, setHasFetchError] = useState<boolean>(false);
44-
const multiselectListRef = useRef<HTMLUListElement>(null);
47+
const multiselectListRef = useMultiselectScrollReflow(selectedContentTypes);
4548

4649
const isInvalidSelection = useMemo(
4750
() => selectedContentTypes.length === 0,
@@ -110,23 +113,6 @@ export const ContentTypePickerModal = ({
110113
}
111114
}, [isOpen, setSelectedContentTypes]);
112115

113-
useEffect(() => {
114-
// Recalculate the Multiselect dropdown position when selection changes
115-
if (multiselectListRef.current) {
116-
const element = multiselectListRef.current;
117-
const currentScroll = element.scrollTop;
118-
const maxScroll = element.scrollHeight - element.clientHeight;
119-
120-
if (currentScroll >= maxScroll) {
121-
element.scrollTop = currentScroll - 1;
122-
} else {
123-
element.scrollTop = currentScroll + 1;
124-
}
125-
126-
element.scrollTop = currentScroll;
127-
}
128-
}, [selectedContentTypes]);
129-
130116
const onSearchValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
131117
const searchTerm = e.target.value.toLowerCase().trim();
132118
setFilteredContentTypes(
@@ -170,24 +156,29 @@ export const ContentTypePickerModal = ({
170156
};
171157

172158
return (
173-
<Modal title="Select content type(s)" isShown={isOpen} onClose={handleClose} size="medium">
159+
<Modal title="Select content type(s)" isShown={isOpen} onClose={handleClose} size="large">
174160
{() => (
175161
<>
176162
<Modal.Header title="Select content type(s)" onClose={handleClose} />
177-
<Modal.Content className={css({ minHeight: '300px' })}>
163+
<Modal.Content>
178164
<Paragraph marginBottom="spacingM" color="gray700">
179165
Select the content type(s) you would like to use with this document.
180166
</Paragraph>
181-
<FormControl isRequired isInvalid={isInvalidSelectionError || showFetchError}>
167+
<FormControl
168+
isRequired
169+
isInvalid={isInvalidSelectionError || showFetchError}
170+
marginBottom="none">
182171
<FormControl.Label>Content type</FormControl.Label>
183172
<Multiselect
173+
className={multiselect}
184174
searchProps={{
185175
searchPlaceholder: 'Search content types',
186176
onSearchValueChange,
187177
}}
188178
currentSelection={selectedContentTypes.map((ct) => ct.name)}
189179
placeholder={getPlaceholderText()}
190180
popoverProps={{
181+
listMaxHeight: 300,
191182
listRef: multiselectListRef,
192183
}}>
193184
{filteredContentTypes.map((ct) => (
@@ -218,11 +209,11 @@ export const ContentTypePickerModal = ({
218209
</FormControl>
219210

220211
{selectedContentTypes.length > 0 && (
221-
<Flex flexWrap="wrap" gap="spacingXs" marginTop="spacingS">
212+
<Flex flexWrap="wrap" gap="spacingXs" marginTop="spacingS" className={pillsContainer}>
222213
{selectedContentTypes.map((ct) => (
223214
<Pill
224215
key={ct.sys.id}
225-
label={ct.name}
216+
label={truncateLabel(ct.name)}
226217
onClose={
227218
isSubmitting
228219
? undefined
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { css } from '@emotion/css';
2+
import tokens from '@contentful/f36-tokens';
3+
4+
export const multiselect = css({
5+
maxWidth: '68%',
6+
});
7+
8+
export const multiselectOption = css({
9+
padding: tokens.spacing2Xs,
10+
});
11+
12+
export const pillsContainer = css({
13+
marginRight: tokens.spacingXl,
14+
maxHeight: '100px',
15+
overflowY: 'scroll',
16+
'&::-webkit-scrollbar': {
17+
width: tokens.spacingXs,
18+
height: tokens.spacingXs,
19+
},
20+
'&::-webkit-scrollbar-thumb': {
21+
background: tokens.gray400,
22+
borderRadius: tokens.borderRadiusSmall,
23+
},
24+
});
25+
26+
export const modalControls = css({
27+
paddingTop: '0',
28+
});

0 commit comments

Comments
 (0)