Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust ProgressReport rich text cell styling #1591

Merged
merged 7 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 34 additions & 11 deletions src/components/RichText/RichTextView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,54 @@ import type { OutputData as RichTextData } from '@editorjs/editorjs';
import { Divider, Typography } from '@mui/material';
import Blocks from 'editorjs-blocks-react-renderer';
import HTMLReactParser from 'html-react-parser';
import { memo } from 'react';
import { ToolKey } from './editorJsTools';
import { ComponentType, memo, useMemo } from 'react';
import { BlockDataMap, ToolKey } from './editorJsTools';

export type Renderers = {
[K in ToolKey]?: ComponentType<BlockProps<K>>;
};

export interface BlockProps<K extends ToolKey> {
data?: K extends keyof BlockDataMap ? BlockDataMap[K] : never;
className?: string;
}

export const RichTextView = memo(function RichTextView({
data,
renderers: renderersInput,
}: {
data?: RichTextData | null;
renderers?: Renderers;
}) {
const renderers = useMemo(
() => ({ ...defaultRenderers, ...renderersInput }),
[renderersInput]
);
if (!data) {
return null;
}
const data1 = { version: '0', time: 0, ...data };
return <Blocks data={data1} renderers={renderers} />;
return (
<Blocks
data={data1}
// @ts-expect-error our types are stricter
renderers={renderers}
/>
);
});

type RenderFn<T = undefined> = (_: { data?: T }) => JSX.Element;
const ParagraphBlock = ({ data }: BlockProps<'paragraph'>) => (
<Typography paragraph>
<Text data={data} />
</Typography>
);

const ParagraphBlock: RenderFn<{ text: string }> = ({ data }) => {
export const Text = ({ data }: BlockProps<'paragraph'>) => {
const { text } = data ?? {};
return <Typography paragraph>{text && HTMLReactParser(text)}</Typography>;
return <>{text && HTMLReactParser(text)}</>;
};

const HeaderBlock: RenderFn<{ text: string; level: 1 | 2 | 3 | 4 | 5 | 6 }> = ({
data,
}) => {
const HeaderBlock = ({ data }: BlockProps<'header'>) => {
const { text, level = 1 } = data ?? {};
return (
<Typography variant={`h${level}`} gutterBottom>
Expand All @@ -35,9 +58,9 @@ const HeaderBlock: RenderFn<{ text: string; level: 1 | 2 | 3 | 4 | 5 | 6 }> = ({
);
};

const DelimiterBlock: RenderFn = () => <Divider sx={{ my: 2 }} />;
const DelimiterBlock = () => <Divider sx={{ my: 2 }} />;

const renderers: { [K in ToolKey]?: RenderFn<any> } = {
const defaultRenderers: Renderers = {
paragraph: ParagraphBlock,
header: HeaderBlock,
delimiter: DelimiterBlock,
Expand Down
6 changes: 6 additions & 0 deletions src/components/RichText/editorJsTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ export type ToolKey = keyof typeof EDITOR_JS_TOOLS;

export const customTools = (toolsNames: ToolKey[]) =>
pick(EDITOR_JS_TOOLS, toolsNames);

export interface BlockDataMap {
paragraph: { text: string };
header: { text: string; level: 1 | 2 | 3 | 4 | 5 | 6 };
list: { style: 'unordered' | 'ordered'; items: string[] };
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Box } from '@mui/material';
import {
DataGridProProps as DataGridProps,
GridRenderCellParams,
GridRowId,
GridToolbarColumnsButton,
GridToolbarFilterButton,
Expand All @@ -24,9 +26,34 @@ import {

const COLLAPSED_ROW_HEIGHT = 54;

// Position cell text consistently regardless of expansion
const NonExpansionCell = ({ formattedValue }: GridRenderCellParams) => (
<Box
sx={{
'--height': `${COLLAPSED_ROW_HEIGHT}px`,
// Causes row to grow by 1px on row height auto
// Shift the 1px to padding to prevent.
height: 'calc(var(--height) - 1px)',
pt: '1px',
// This is exactly what MUI does when not auto height
lineHeight: 'calc(var(--height) - 1px)',

display: 'flex',
alignItems: 'center',
}}
>
{formattedValue}
</Box>
);

const columns = entries(ProgressReportsColumnMap).map(([name, col]) => ({
field: name,
...col,
...(!(
'cellClassName' in col && col.cellClassName.includes(ExpansionMarker)
) && {
renderCell: NonExpansionCell,
}),
}));

const initialState = {
Expand Down Expand Up @@ -93,11 +120,6 @@ export const ProgressReportsExpandedGrid = (props: Partial<DataGridProps>) => {
'.MuiDataGrid-cell': {
minHeight: COLLAPSED_ROW_HEIGHT,
},
[`.MuiDataGrid-row--dynamicHeight .MuiDataGrid-cell:not(.${ExpansionMarker})`]:
{
// Magic number to keep text vertically unchanged when the row is expanded
pt: '17.5px',
},
},
...extendSx(props.sx),
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ import {
textColumn,
useDataGridSource,
} from '~/components/Grid';
import { RichTextView } from '~/components/RichText';
import { Link } from '~/components/Routing';
import {
ProgressReportsDataGridRowFragment as ProgressReport,
ProgressReportsDocument,
} from './progressReportsDataGridRow.graphql';
import { RichTextCell } from './RichTextCell';
import { VariantResponseCell } from './VariantResponseCell';

export type ProgressReportColumnMapShape = Record<
string,
SetOptional<GridColDef<ProgressReport>, 'field'>
>;

export const ExpansionMarker = 'multiline';
export const ExpansionMarker = 'expandable';

export const ProgressReportsColumnMap = {
project: {
Expand Down Expand Up @@ -99,14 +99,14 @@ export const ProgressReportsColumnMap = {
},
'varianceExplanation.comments': {
headerName: 'Variance Explanation',
width: 250,
width: 400,
sortable: false,
filterable: false,
valueGetter: (_, { varianceExplanation }) =>
varianceExplanation.comments.value,
renderCell: ({ value }) => (
<Box m={1} display="flex" alignItems="center" gap={1}>
<RichTextView data={value} />
renderCell: (props) => (
<Box my={1}>
<RichTextCell {...props} />
</Box>
),
cellClassName: ExpansionMarker,
Expand Down
94 changes: 94 additions & 0 deletions src/scenes/Dashboard/ProgressReportsWidget/RichTextCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Box, Typography } from '@mui/material';
import {
GridState,
GridRenderCellParams as RenderCellParams,
useGridSelector,
} from '@mui/x-data-grid';
import { extendSx, RichTextJson, StyleProps } from '~/common';
import {
BlockProps,
Renderers,
RichTextView,
Text,
} from '../../../components/RichText';
import { ProgressReportsDataGridRowFragment as ProgressReport } from './progressReportsDataGridRow.graphql';

type CellParams = RenderCellParams<ProgressReport, RichTextJson>;

export const RichTextCell = ({
id,
value,
api,
sx,
className,
}: Pick<CellParams, 'value' | 'id' | 'api'> & StyleProps) => {
const selectedRows = useGridSelector(
{ current: api },
(state: GridState) => state.rowSelection
);
const isExpanded = selectedRows.includes(id);

if (!value) return null;

return (
<Box
sx={[
{
overflow: 'hidden',
textWrap: 'wrap',
display: isExpanded ? 'contents' : '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',

// No trailing spacing on response
'& > *:last-child': { mb: 0 },
},
...extendSx(sx),
]}
className={className}
>
<RichTextView data={value} renderers={renderers} />
</Box>
);
};

const ParagraphBlock = ({ data }: BlockProps<'paragraph'>) => (
<Typography variant="body2" paragraph>
<Text data={data} />
</Typography>
);

const List = ({ data }: BlockProps<'list'>) => {
// eslint-disable-next-line react/jsx-no-useless-fragment
if (!data) return <></>;
CarsonF marked this conversation as resolved.
Show resolved Hide resolved
const { style, items } = data;
return (
<Box
component={style === 'unordered' ? 'ul' : 'ol'}
sx={{ display: 'contents' }}
>
{items.map((text, index) => (
<Typography
key={index}
variant="body2"
component="li"
gutterBottom
sx={{
display: 'block',
'&::before': {
content: style === 'unordered' ? `"• "` : `"${index + 1}. "`,
},
'&:last-of-type': { mb: 0 },
}}
>
<Text data={{ text }} />
</Typography>
))}
</Box>
);
};

const renderers: Renderers = {
paragraph: ParagraphBlock,
list: List,
};
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
import { Box } from '@mui/material';
import { styled } from '@mui/material/styles';
import {
GridState,
GridRenderCellParams as RenderCellParams,
useGridSelector,
} from '@mui/x-data-grid';
import { GridRenderCellParams as RenderCellParams } from '@mui/x-data-grid';
import { VariantResponseFragment as VariantResponse } from '~/common/fragments';
import { RichTextView } from '../../../components/RichText';
import { RoleIcon as BaseRoleIcon } from '../../../components/RoleIcon';
import { ProgressReportsDataGridRowFragment as ProgressReport } from './progressReportsDataGridRow.graphql';
import { RichTextCell } from './RichTextCell';

type CellParams = RenderCellParams<ProgressReport, VariantResponse>;

export const VariantResponseCell = ({ value, ...props }: CellParams) => {
const selectedRows = useGridSelector(
{ current: props.api },
(state: GridState) => state.rowSelection
);
const isExpanded = selectedRows[0] === props.id;

if (!value) return null;

const { variant } = value;
const response = value.response.value!;
return (
<Box
sx={{
my: 1,

overflow: 'hidden',
textWrap: 'wrap',
display: isExpanded ? undefined : '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',

// No trailing spacing on response
'& > *:last-child': { mb: 0 },
}}
>
<Box my={1}>
<RoleIcon
variantRole={variant.responsibleRole}
sx={{ fontSize: 36, float: 'left', mr: 1 }}
/>
<RichTextView data={response} />
<RichTextCell value={response} {...props} />
</Box>
);
};
Expand Down
Loading