From 695cd6d6a65dc73a7ee6dd9943437df0a5d9e1b6 Mon Sep 17 00:00:00 2001 From: Jordan Koschei Date: Mon, 24 Jun 2024 15:37:46 -0400 Subject: [PATCH] feat: adding row expansion to stack cards --- .../src/labs/DataComponents/StackCard.tsx | 70 +++++++++++++++++-- .../src/labs/DataComponents/StackContent.tsx | 10 +++ .../src/labs/DataComponents/TableContent.tsx | 18 ++++- .../src/labs/DataComponents/componentTypes.ts | 1 + .../labs/DataComponents/tableConstants.tsx | 5 ++ .../properties/odyssey-react-mui.properties | 4 +- .../DataComponents/DataComponents.stories.tsx | 39 +++++++++++ 7 files changed, 139 insertions(+), 8 deletions(-) diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/StackCard.tsx b/packages/odyssey-react-mui/src/labs/DataComponents/StackCard.tsx index aad67afce5..53a0e07ff7 100644 --- a/packages/odyssey-react-mui/src/labs/DataComponents/StackCard.tsx +++ b/packages/odyssey-react-mui/src/labs/DataComponents/StackCard.tsx @@ -16,11 +16,14 @@ import { memo, useMemo, ReactNode, + useState, } from "react"; import { + IconButton as MuiIconButton, Card as MuiCard, CardActions as MuiCardActions, CardActionArea as MuiCardActionArea, + Tooltip as MuiTooltip, } from "@mui/material"; import styled from "@emotion/styled"; import { useTranslation } from "react-i18next"; @@ -34,13 +37,18 @@ import { } from "../../OdysseyDesignTokensContext"; import { Heading5, Paragraph, Support } from "../../Typography"; import { MenuButton, MenuButtonProps } from "../../MenuButton"; -import { MoreIcon } from "../../icons.generated"; +import { + ChevronDownIcon, + ChevronUpIcon, + MoreIcon, +} from "../../icons.generated"; export const CARD_IMAGE_HEIGHT = "64px"; export type StackCardProps = { children?: ReactNode; description?: string; + detailPanel?: ReactNode; image?: ReactElement; overline?: string; title?: string; @@ -88,19 +96,22 @@ const CardContentContainer = styled("div", { gap: odysseyDesignTokens.Spacing3, })); -const CardChildrenContainer = styled("div")(() => ({ +const CardChildrenContainer = styled("div", { + shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", +})<{ odysseyDesignTokens: DesignTokens }>(({ odysseyDesignTokens }) => ({ ["* + &"]: { - marginBlockStart: 3, + marginBlockStart: odysseyDesignTokens.Spacing3, }, })); const buttonProviderValue = { isFullWidth: true }; const StackCard = ({ - Accessory, + Accessory: AccessoryProp, button, children, description, + detailPanel, image, menuButtonChildren, onClick, @@ -110,6 +121,43 @@ const StackCard = ({ const odysseyDesignTokens = useOdysseyDesignTokens(); const { t } = useTranslation(); + const [isDetailPanelOpen, setIsDetailPanelOpen] = useState(false); + + const Accessory = useMemo( + () => ( + + {AccessoryProp} + + : + } + onClick={() => setIsDetailPanelOpen(!isDetailPanelOpen)} + aria-label={ + isDetailPanelOpen + ? t("table.rowexpansion.close") + : t("table.rowexpansion.open") + } + /> + + + ), + [AccessoryProp, isDetailPanelOpen, t], + ); + const cardContent = useMemo( () => ( @@ -139,21 +187,31 @@ const StackCard = ({ )} {children && ( - {children} + + {children} + + )} + + {detailPanel && isDetailPanelOpen && ( + + {detailPanel} + )} ), [ + odysseyDesignTokens, Accessory, + detailPanel, image, - odysseyDesignTokens, menuButtonChildren, overline, title, description, button, children, + isDetailPanelOpen, ], ); diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/StackContent.tsx b/packages/odyssey-react-mui/src/labs/DataComponents/StackContent.tsx index ef17310a68..858badf3f4 100644 --- a/packages/odyssey-react-mui/src/labs/DataComponents/StackContent.tsx +++ b/packages/odyssey-react-mui/src/labs/DataComponents/StackContent.tsx @@ -120,6 +120,15 @@ const StackContent = ({ [setRowSelection], ); + const renderDetailPanelProp = stackOptions.renderDetailPanel; + + const renderDetailPanel = useCallback( + (row: MRT_RowData) => { + return renderDetailPanelProp?.({ row }); + }, + [renderDetailPanelProp], + ); + const { updateRowOrder } = rowReorderingUtilities; const odysseyDesignTokens = useOdysseyDesignTokens(); @@ -200,6 +209,7 @@ const StackContent = ({ } children={children} description={description} + detailPanel={renderDetailPanel(row)} image={image} key={row.id} menuButtonChildren={ diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/TableContent.tsx b/packages/odyssey-react-mui/src/labs/DataComponents/TableContent.tsx index a06bd0694c..646a5aa20a 100644 --- a/packages/odyssey-react-mui/src/labs/DataComponents/TableContent.tsx +++ b/packages/odyssey-react-mui/src/labs/DataComponents/TableContent.tsx @@ -303,12 +303,28 @@ const TableContent = ({ renderRowActions: ({ row }) => renderRowActions({ row }), enableRowOrdering: hasRowReordering && Boolean(onReorderRows), enableRowDragging: hasRowReordering && Boolean(onReorderRows), - muiTableBodyRowProps: ({ table, row }) => ({ + muiDetailPanelProps: ({ row }) => ({ + sx: { + paddingBlock: row.getIsExpanded() + ? `${odysseyDesignTokens.Spacing3} !important` + : undefined, + }, + }), + muiTableBodyRowProps: ({ table, row, isDetailPanel }) => ({ className: draggableTableBodyRowClassName({ currentRowId: row.id, draggingRowId: draggingRow?.id, hoveredRowId: table.getState().hoveredRow?.id, }), + sx: isDetailPanel + ? { + paddingBlock: "0 !important", + border: 0, + ["&:hover"]: { + backgroundColor: `${odysseyDesignTokens.HueNeutralWhite} !important`, + }, + } + : {}, }), muiRowDragHandleProps: ({ table, row }) => ({ onKeyDown: (event) => handleDragHandleKeyDown({ table, row, event }), diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/componentTypes.ts b/packages/odyssey-react-mui/src/labs/DataComponents/componentTypes.ts index b8d12bf5d1..13ffde63bd 100644 --- a/packages/odyssey-react-mui/src/labs/DataComponents/componentTypes.ts +++ b/packages/odyssey-react-mui/src/labs/DataComponents/componentTypes.ts @@ -93,6 +93,7 @@ export type TableProps = { export type StackProps = { cardProps: (row: MRT_RowData) => StackCardProps; maxGridColumns?: number; + renderDetailPanel?: (props: { row: MRT_RowData }) => ReactNode; rowActionMenuItems?: DataTableRowActionsProps["rowActionMenuItems"]; }; diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/tableConstants.tsx b/packages/odyssey-react-mui/src/labs/DataComponents/tableConstants.tsx index adbc9b1f5f..8e8dc06ec6 100644 --- a/packages/odyssey-react-mui/src/labs/DataComponents/tableConstants.tsx +++ b/packages/odyssey-react-mui/src/labs/DataComponents/tableConstants.tsx @@ -96,6 +96,11 @@ export const displayColumnDefOptions = { }, "mrt-row-expand": { header: "", + muiTableBodyCellProps: { + sx: { + overflow: "visible", + }, + }, }, }; diff --git a/packages/odyssey-react-mui/src/properties/odyssey-react-mui.properties b/packages/odyssey-react-mui/src/properties/odyssey-react-mui.properties index 3deedc76ba..d049210e78 100644 --- a/packages/odyssey-react-mui/src/properties/odyssey-react-mui.properties +++ b/packages/odyssey-react-mui/src/properties/odyssey-react-mui.properties @@ -109,4 +109,6 @@ pagination.rowswithtotal = {{firstRow}}-{{lastRow}} of {{totalRows}} rows pagination.rowswithouttotal = {{firstRow}}-{{lastRow}} rows table.actions.selectall = Select all table.actions.selectnone = Select none -table.actions.selectsome = {{selectedRowCount}} selected \ No newline at end of file +table.actions.selectsome = {{selectedRowCount}} selected +table.rowexpansion.open = Open +table.rowexpansion.close = Close \ No newline at end of file diff --git a/packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.stories.tsx index f01c3b99d3..fc8f0f993f 100644 --- a/packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.stories.tsx @@ -41,6 +41,7 @@ import { Button, EmptyState, paginationTypeValues, + DataTableRenderDetailPanelType, } from "@okta/odyssey-react-mui"; type DataViewMetaProps = DataViewProps & @@ -562,3 +563,41 @@ export const DataStackComponent: StoryObj = { ); }, }; + +export const ExpandableRowsAndCards: StoryObj = { + render: function Base(args) { + console.log(args); + + const [data, setData] = useState(personData); + const { getData, onReorderRows, onChangeRowSelection } = useDataCallbacks( + data, + setData, + ); + + const renderAdditionalContent = useCallback( + ({ row }: DataTableRenderDetailPanelType) => { + return This is additional content for row {row.id}; + }, + [], + ); + + return ( + + ); + }, +};