Skip to content

Commit

Permalink
Dialogs having consistent look and behavior (#5500)
Browse files Browse the repository at this point in the history
* Calendar dialogs properly scrollable

* Send message dialog

* Post dialogs fixed

* Edit Callout

* Innovation Flow Settings Dialog

* Innovation flow settings form takes full width

---------

Co-authored-by: Carlos Cano <[email protected]>
  • Loading branch information
me-andre and ccanos authored Feb 6, 2024
1 parent 8ee7294 commit 3255382
Show file tree
Hide file tree
Showing 19 changed files with 270 additions and 213 deletions.
8 changes: 4 additions & 4 deletions src/core/ui/actions/Actions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { FC } from 'react';
import React, { FC, forwardRef } from 'react';
import { Box, BoxProps } from '@mui/material';

export interface ActionsProps extends BoxProps {}

export const Actions: FC<ActionsProps> = props => {
return <Box display="flex" gap={1} alignItems="center" {...props} />;
};
export const Actions: FC<ActionsProps> = forwardRef((props, ref) => {
return <Box ref={ref} display="flex" gap={1} alignItems="center" {...props} />;
});
21 changes: 19 additions & 2 deletions src/core/ui/dialog/DialogWithGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { MouseEventHandler } from 'react';
import React, { MouseEventHandler, PropsWithChildren } from 'react';
import { Dialog as MuiDialog, DialogProps as MuiDialogProps, Paper, PaperProps } from '@mui/material';
import GridContainer from '../grid/GridContainer';
import { MAX_CONTENT_WIDTH_WITH_GUTTER_PX, useGlobalGridColumns } from '../grid/constants';
import GridProvider from '../grid/GridProvider';
import GridItem, { GridItemProps } from '../grid/GridItem';
import createLayoutHolder from '../layout/layoutHolder/LayoutHolder';

interface DialogContainerProps extends PaperProps {
columns?: GridItemProps['columns'];
Expand All @@ -12,6 +13,20 @@ interface DialogContainerProps extends PaperProps {
fullScreen?: boolean;
}

/**
* When dialog footer rendering is tightly coupled with the dialog content,
* you can wrap the footer content into DialogFooter for it to be rendered
* as a sibling of the dialog content.
*/
const { LayoutHolder: DialogFooterLayoutHolder, createLayout } = createLayoutHolder(({ layout, children }) => (
<>
{children}
{layout}
</>
));

export const DialogFooter = createLayout(({ children }: PropsWithChildren<{}>) => <>{children}</>);

const DialogContainer = ({
columns,
centeredVertically,
Expand Down Expand Up @@ -42,7 +57,9 @@ const DialogContainer = ({
<GridProvider columns={containerColumns} force>
<GridItem columns={columns}>
<Paper {...paperProps}>
<GridProvider columns={columns ?? containerColumns}>{children}</GridProvider>
<GridProvider columns={columns ?? containerColumns}>
<DialogFooterLayoutHolder>{children}</DialogFooterLayoutHolder>
</GridProvider>
</Paper>
</GridItem>
</GridProvider>
Expand Down
21 changes: 17 additions & 4 deletions src/core/ui/layout/layoutHolder/LayoutHolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {
Dispatch,
FC,
PropsWithChildren,
ReactElement,
SetStateAction,
useContext,
useLayoutEffect,
Expand All @@ -15,7 +16,20 @@ interface LayoutState<P> {
props: P;
}

const createLayoutHolder = () => {
type LayoutRenderOrderProps = PropsWithChildren<{
layout: ReactElement | undefined;
}>;

const DefaultLayoutRenderOrder = ({ layout, children }: LayoutRenderOrderProps) => {
return (
<>
{layout}
{children}
</>
);
};

const createLayoutHolder = (RenderOrder: ComponentType<LayoutRenderOrderProps> = DefaultLayoutRenderOrder) => {
const LayoutContext = createContext<Dispatch<SetStateAction<LayoutState<Record<string, unknown>> | undefined>>>(
() => {
throw new Error('Not within the LayoutHolder.');
Expand All @@ -28,10 +42,9 @@ const createLayoutHolder = () => {
const Component = layout?.component!;

return (
<>
{layout && <Component {...layout.props} />}
<RenderOrder layout={layout && <Component {...layout.props} />}>
<LayoutContext.Provider value={setLayout}>{children}</LayoutContext.Provider>
</>
</RenderOrder>
);
};

Expand Down
12 changes: 12 additions & 0 deletions src/core/ui/themes/default/components/MuiDialogActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Components, Theme } from '@mui/material/styles';
import { gutters } from '../../../grid/utils';

const MuiDialogActions: Components<Theme>['MuiDialogActions'] = {
styleOverrides: {
root: ({ theme }) => ({
padding: gutters(0.5)(theme),
}),
},
};

export default MuiDialogActions;
2 changes: 2 additions & 0 deletions src/core/ui/themes/default/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MuiButtonBase from './MuiButtonBase';
import MuiChip from './MuiChip';
import MuiDialog from './MuiDialog';
import MuiDialogContent from './MuiDialogContent';
import MuiDialogActions from './MuiDialogActions';
import MuiIcon from './MuiIcon';
import MuiLink from './MuiLink';
import MuiMenuItem from './MuiMenuItem';
Expand All @@ -23,6 +24,7 @@ const componentsOverride: Components<Theme> = {
MuiChip,
MuiDialog,
MuiDialogContent,
MuiDialogActions,
MuiIcon,
MuiLink,
MuiMenuItem,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AutoGraphOutlined, Close } from '@mui/icons-material';
import EditIcon from '@mui/icons-material/Edit';
import { Box, IconButton, Theme, useMediaQuery } from '@mui/material';
import { FC, useState } from 'react';
import { Box, DialogActions, IconButton, Theme, useMediaQuery } from '@mui/material';
import { cloneElement, FC, ReactElement, useState } from 'react';
import { Reference, TagsetType, UpdateProfileInput, Visual } from '../../../../core/apollo/generated/graphql-schema';
import PageContentBlock from '../../../../core/ui/content/PageContentBlock';
import PageContentBlockGrid from '../../../../core/ui/content/PageContentBlockGrid';
Expand All @@ -13,6 +13,9 @@ import InnovationFlowProfileView from './InnovationFlowProfileView';
import { gutters } from '../../../../core/ui/grid/utils';
import Icon from '../../../../core/ui/icon/Icon';
import { useTranslation } from 'react-i18next';
import { DialogFooter } from '../../../../core/ui/dialog/DialogWithGrid';
import { useInView } from 'react-intersection-observer';
import PageContentBlockSeamless from '../../../../core/ui/content/PageContentBlockSeamless';

export interface InnovationFlowProfile {
id: string;
Expand Down Expand Up @@ -47,6 +50,17 @@ export interface InnovationFlowProfileBlockProps {
loading?: boolean;
}

const FormActionsRenderer = ({ children }: { children: ReactElement }) => {
const { ref, inView } = useInView();

return (
<>
{cloneElement(children, { ref })}
<DialogFooter>{!inView && <DialogActions>{children}</DialogActions>}</DialogFooter>
</>
);
};

const InnovationFlowProfileBlock: FC<InnovationFlowProfileBlockProps> = ({
editable = false,
onUpdate,
Expand Down Expand Up @@ -74,12 +88,15 @@ const InnovationFlowProfileBlock: FC<InnovationFlowProfileBlockProps> = ({
<PageContentBlock disablePadding disableGap>
<PageContentBlockGrid disablePadding>
{editMode && innovationFlow && (
<PageContentColumn columns={isMobile ? 8 : 6}>
<InnovationFlowProfileForm
profile={innovationFlow.profile}
onSubmit={profileData => handleUpdateProfile(innovationFlow.id, profileData)}
onCancel={() => setEditMode(false)}
/>
<PageContentColumn columns={12}>
<PageContentBlockSeamless>
<InnovationFlowProfileForm
profile={innovationFlow.profile}
onSubmit={profileData => handleUpdateProfile(innovationFlow.id, profileData)}
onCancel={() => setEditMode(false)}
actionsRenderer={FormActionsRenderer}
/>
</PageContentBlockSeamless>
</PageContentColumn>
)}
{!editMode && (
Expand Down Expand Up @@ -116,6 +133,7 @@ const InnovationFlowProfileBlock: FC<InnovationFlowProfileBlockProps> = ({

{children}
</PageContentColumn>
<DialogFooter />
</>
)}
</PageContentBlockGrid>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Button } from '@mui/material';
import { Formik } from 'formik';
import { FC } from 'react';
import { ComponentType, FC, Fragment, ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
import { Reference, Tagset } from '../../../../core/apollo/generated/graphql-schema';
import FormikInputField from '../../../../core/ui/forms/FormikInputField/FormikInputField';
import FormikMarkdownField from '../../../../core/ui/forms/MarkdownInput/FormikMarkdownField';
import { MARKDOWN_TEXT_LENGTH, SMALL_TEXT_LENGTH } from '../../../../core/ui/forms/field-length.constants';
import Gutters from '../../../../core/ui/grid/Gutters';
import ContextReferenceSegment from '../../../../domain/platform/admin/components/Common/ContextReferenceSegment';
import { referenceSegmentSchema } from '../../../../domain/platform/admin/components/Common/ReferenceSegment';
import { tagsetSegmentSchema } from '../../../../domain/platform/admin/components/Common/TagsetSegment';
Expand All @@ -29,9 +28,15 @@ interface InnovationFlowProfileFormProps {
profile?: InnovationFlowProfile;
onSubmit: (formData: InnovationFlowProfileFormValues) => Promise<unknown> | void;
onCancel?: () => void;
actionsRenderer?: ComponentType<{ children: ReactElement }>;
}

const InnovationFlowProfileForm: FC<InnovationFlowProfileFormProps> = ({ profile, onSubmit, onCancel }) => {
const InnovationFlowProfileForm: FC<InnovationFlowProfileFormProps> = ({
profile,
onSubmit,
onCancel,
actionsRenderer: ActionsRenderer = Fragment,
}) => {
const { t } = useTranslation();

const initialValues: InnovationFlowProfileFormValues = {
Expand All @@ -56,21 +61,23 @@ const InnovationFlowProfileForm: FC<InnovationFlowProfileFormProps> = ({ profile
<Formik initialValues={initialValues} validationSchema={validationSchema} enableReinitialize onSubmit={handleSave}>
{({ values: { references }, handleSubmit }) => {
return (
<Gutters>
<>
<FormikInputField name="displayName" title={t('common.title')} maxLength={SMALL_TEXT_LENGTH} />
<FormikMarkdownField name="description" title={t('common.description')} maxLength={MARKDOWN_TEXT_LENGTH} />
{/* TODO: Tags pending <TagsetSegment tagsets={profile?.tagsets ?? []} /> */}
<ContextReferenceSegment references={references || []} profileId={profile?.id} />
<VisualUpload visual={profile?.bannerNarrow} />
<Actions justifyContent="end">
<Button variant="text" onClick={onCancel}>
{t('buttons.cancel')}
</Button>
<LoadingButton loading={loading} variant="contained" onClick={() => handleSubmit()}>
{t('buttons.save')}
</LoadingButton>
</Actions>
</Gutters>
<ActionsRenderer>
<Actions justifyContent="end">
<Button variant="text" onClick={onCancel}>
{t('buttons.cancel')}
</Button>
<LoadingButton loading={loading} variant="contained" onClick={() => handleSubmit()}>
{t('buttons.save')}
</LoadingButton>
</Actions>
</ActionsRenderer>
</>
);
}}
</Formik>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { FC } from 'react';
import { useTranslation } from 'react-i18next';
import DialogHeader from '../../../../core/ui/dialog/DialogHeader';
import DialogWithGrid from '../../../../core/ui/dialog/DialogWithGrid';
import { BlockTitle } from '../../../../core/ui/typography';
import { InnovationFlowIcon } from '../../../platform/admin/templates/InnovationTemplates/InnovationFlow/InnovationFlowIcon';
import InnovationFlowProfileBlock from './InnovationFlowProfileBlock';
import useInnovationFlowSettings from './useInnovationFlowSettings';
Expand Down Expand Up @@ -35,11 +34,7 @@ const InnovationFlowSettingsDialog: FC<InnovationFlowSettingsDialogProps> = ({ o

return (
<DialogWithGrid open={open} columns={12} onClose={onClose}>
<DialogHeader onClose={onClose}>
<BlockTitle>
<InnovationFlowIcon /> {t('common.innovation-flow')}
</BlockTitle>
</DialogHeader>
<DialogHeader icon={<InnovationFlowIcon />} title={t('common.innovation-flow')} onClose={onClose} />
<DialogContent sx={{ paddingTop: 0 }}>
<Gutters disablePadding>
<InnovationFlowProfileBlock
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React, { FC, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box } from '@mui/material';
import Dialog from '@mui/material/Dialog';
import { LoadingButton } from '@mui/lab';
import calloutIcons from '../../utils/calloutIcons';
import { DialogActions, DialogContent } from '../../../../../core/ui/dialog/deprecated';
Expand All @@ -18,6 +16,7 @@ import { getCalloutDisplayLocationValue } from '../../utils/getCalloutDisplayLoc
import { JourneyTypeName } from '../../../../journey/JourneyTypeName';
import { StorageConfigContextProvider } from '../../../../storage/StorageBucket/StorageConfigContext';
import { DEFAULT_TAGSET } from '../../../../common/tags/tagset.constants';
import DialogWithGrid from '../../../../../core/ui/dialog/DialogWithGrid';

export interface CalloutEditDialogProps {
open: boolean;
Expand Down Expand Up @@ -133,16 +132,15 @@ const CalloutEditDialog: FC<CalloutEditDialogProps> = ({

return (
<>
<Dialog open={open} maxWidth="md" fullWidth aria-labelledby="callout-visibility-dialog-title" onClose={onClose}>
<DialogHeader onClose={onClose}>
<Box display="flex" alignItems="center" gap={1}>
{CalloutIcon && <CalloutIcon />}
{t('components.calloutEdit.titleWithType', {
type: t(`components.calloutTypeSelect.label.${calloutType}` as const),
})}
</Box>
</DialogHeader>
<DialogContent dividers>
<DialogWithGrid open={open} columns={8} aria-labelledby="callout-visibility-dialog-title" onClose={onClose}>
<DialogHeader
icon={CalloutIcon && <CalloutIcon />}
title={t('components.calloutEdit.titleWithType', {
type: t(`components.calloutTypeSelect.label.${calloutType}` as const),
})}
onClose={onClose}
/>
<DialogContent>
<StorageConfigContextProvider
locationType="callout"
journeyTypeName={journeyTypeName}
Expand Down Expand Up @@ -181,7 +179,7 @@ const CalloutEditDialog: FC<CalloutEditDialogProps> = ({
{t('buttons.save')}
</LoadingButton>
</DialogActions>
</Dialog>
</DialogWithGrid>
<ConfirmationDialog {...confirmationDialogProps} />
</>
);
Expand Down
2 changes: 2 additions & 0 deletions src/domain/collaboration/post/pages/PostDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PostDashboardView from '../views/PostDashboardView';
import PostDashboardContainer from '../containers/PostDashboardContainer/PostDashboardContainer';
import { PostLayout } from '../views/PostLayoutWithOutlet';
import { PostDialogSection } from '../views/PostDialogSection';
import { DialogFooter } from '../../../../core/ui/dialog/DialogWithGrid';

export interface PostDashboardPageProps {
onClose: () => void;
Expand Down Expand Up @@ -36,6 +37,7 @@ const PostDashboardPage: FC<PostDashboardPageProps> = ({ onClose }) => {
/>
)}
</PostDashboardContainer>
<DialogFooter />
</PostLayout>
);
};
Expand Down
Loading

0 comments on commit 3255382

Please sign in to comment.