diff --git a/packages/odyssey-react-mui/src/Card.tsx b/packages/odyssey-react-mui/src/Card.tsx index e4dacf33e7..d39c697ce2 100644 --- a/packages/odyssey-react-mui/src/Card.tsx +++ b/packages/odyssey-react-mui/src/Card.tsx @@ -11,171 +11,460 @@ */ import { + memo, MouseEventHandler, ReactElement, - memo, + ReactNode, useMemo, - useEffect, } from "react"; import { + CardActionArea as MuiCardActionArea, Card as MuiCard, CardActions as MuiCardActions, - CardActionArea as MuiCardActionArea, + Skeleton as MuiSkeleton, } from "@mui/material"; -import styled from "@emotion/styled"; - -import { Button, ButtonContext, MenuButton, MenuButtonProps } from "./Buttons"; -import { MoreIcon } from "./icons.generated"; import { + Button, + ButtonContext, DesignTokens, + MenuButton, + MenuButtonProps, + Paragraph, + Support, + Typography, useOdysseyDesignTokens, -} from "./OdysseyDesignTokensContext"; -import { Heading5, Paragraph, Support } from "./Typography"; -import { Box } from "./Box"; +} from "."; +import styled from "@emotion/styled"; +import { MoreIcon } from "./icons.generated"; +import { useTranslation } from "react-i18next"; -export const CARD_IMAGE_HEIGHT = "64px"; +export const cardVariantValues = ["tile", "stack", "compact"] as const; export type CardProps = { + children?: ReactNode; description?: string; + detailPanel?: ReactNode; image?: ReactElement; + isLoading?: boolean; overline?: string; title?: string; + variant?: (typeof cardVariantValues)[number]; } & ( | { - onClick: MouseEventHandler; + accessory?: never; button?: never; menuButtonChildren?: never; + onClick: MouseEventHandler; } | { - onClick?: never; + accessory?: ReactNode; button?: ReactElement; menuButtonChildren?: MenuButtonProps["children"]; + onClick?: never; } ); +const StyledCard = styled(MuiCard, { + shouldForwardProp: (prop) => + prop !== "odysseyDesignTokens" && prop !== "isClickable", +})<{ + odysseyDesignTokens: DesignTokens; + isClickable: boolean; +}>(({ odysseyDesignTokens, isClickable }) => ({ + border: `${odysseyDesignTokens.BorderWidthMain} ${odysseyDesignTokens.BorderColorDisplay} ${odysseyDesignTokens.BorderStyleMain}`, + boxShadow: "none", + padding: 0, + display: "flex", + + ["&:hover"]: isClickable + ? { + borderColor: odysseyDesignTokens.HueNeutralWhite, + boxShadow: odysseyDesignTokens.DepthMedium, + } + : {}, + + "& .MuiSkeleton-root": { + transform: "none", + }, + + "& .MuiCardActions-root .MuiSkeleton-root": { + height: odysseyDesignTokens.Spacing7, + }, +})); + +const StyledCardActionArea = styled(MuiCardActionArea)(() => ({ + margin: 0, + padding: 0, +})); + +const AccessoriesContainer = styled("div", { + shouldForwardProp: (prop) => + prop !== "odysseyDesignTokens" && prop !== "variant", +})<{ + odysseyDesignTokens: DesignTokens; + variant: CardProps["variant"]; +}>(({ odysseyDesignTokens, variant }) => ({ + display: "flex", + flexDirection: variant === "compact" ? "row" : "column", + alignItems: "center", + gap: odysseyDesignTokens.Spacing1, + marginBlockStart: + variant === "compact" + ? odysseyDesignTokens.Spacing4 + : odysseyDesignTokens.Spacing5, + marginInlineStart: + variant === "compact" + ? odysseyDesignTokens.Spacing4 + : odysseyDesignTokens.Spacing4, + marginInlineEnd: `-${odysseyDesignTokens.Spacing2}`, + height: variant === "compact" ? odysseyDesignTokens.Spacing8 : "auto", + + "& .MuiSkeleton-root": { + width: odysseyDesignTokens.Spacing5, + height: odysseyDesignTokens.Spacing5, + }, +})); + +const InnerContainer = styled("div", { + shouldForwardProp: (prop) => + prop !== "odysseyDesignTokens" && prop !== "variant", +})<{ + odysseyDesignTokens: DesignTokens; + variant: CardProps["variant"]; +}>(({ odysseyDesignTokens, variant }) => ({ + display: "flex", + backgroundColor: odysseyDesignTokens.HueNeutralWhite, + padding: + variant === "compact" + ? odysseyDesignTokens.Spacing4 + : odysseyDesignTokens.Spacing5, + flexDirection: variant === "tile" ? "column" : "row", + gap: odysseyDesignTokens.Spacing4, + width: "100%", +})); + const ImageContainer = styled("div", { shouldForwardProp: (prop) => - prop !== "odysseyDesignTokens" && prop !== "hasMenuButtonChildren", + prop !== "odysseyDesignTokens" && + prop !== "variant" && + prop !== "hasMenuButton", +})<{ + odysseyDesignTokens: DesignTokens; + variant: CardProps["variant"]; + hasMenuButton: boolean; +}>(({ odysseyDesignTokens, variant, hasMenuButton }) => ({ + height: + variant === "compact" ? odysseyDesignTokens.Spacing8 : "4.5714285714rem", + paddingInlineEnd: + variant === "tile" && hasMenuButton ? odysseyDesignTokens.Spacing4 : 0, + + "& > .MuiSkeleton-root": { + height: "100%", + aspectRatio: 1, + }, +})); + +const ContentContainer = styled("div", { + shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", +})<{ + odysseyDesignTokens: DesignTokens; +}>(({ odysseyDesignTokens }) => ({ + display: "flex", + flexDirection: "column", + gap: odysseyDesignTokens.Spacing4, + width: "100%", + + "& > *": { + marginBlock: 0, + }, + + "& .MuiSkeleton-root + .MuiSkeleton-root": { + marginBlockStart: odysseyDesignTokens.Spacing1, + }, +})); + +const UpperContentContainer = styled("div", { + shouldForwardProp: (prop) => + prop !== "odysseyDesignTokens" && + prop !== "variant" && + prop !== "hasMenuButton", })<{ odysseyDesignTokens: DesignTokens; - hasMenuButtonChildren: boolean; -}>(({ odysseyDesignTokens, hasMenuButtonChildren }) => ({ + variant: CardProps["variant"]; + hasMenuButton: boolean; +}>(({ odysseyDesignTokens, variant, hasMenuButton }) => ({ display: "flex", - alignItems: "flex-start", - maxHeight: CARD_IMAGE_HEIGHT, - height: CARD_IMAGE_HEIGHT, - marginBlockEnd: odysseyDesignTokens.Spacing5, - paddingRight: hasMenuButtonChildren ? odysseyDesignTokens.Spacing5 : 0, + flexDirection: "column", + gap: + variant === "compact" + ? odysseyDesignTokens.Spacing1 + : odysseyDesignTokens.Spacing4, + paddingInlineEnd: + variant !== "tile" && hasMenuButton ? odysseyDesignTokens.Spacing4 : 0, + + "& > .MuiTypography-root": { + marginBlockEnd: 0, + }, })); -const MenuButtonContainer = styled("div", { +const OverlineTitleContainer = styled("div", { shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", -})<{ odysseyDesignTokens: DesignTokens }>(({ odysseyDesignTokens }) => ({ - position: "absolute", - right: odysseyDesignTokens.Spacing3, - top: odysseyDesignTokens.Spacing3, +})<{ + odysseyDesignTokens: DesignTokens; +}>(({ odysseyDesignTokens }) => ({ + display: "flex", + flexDirection: "column", + gap: odysseyDesignTokens.Spacing1, + + "& > .MuiTypography-root": { + marginBlockEnd: 0, + }, })); -const CardContentContainer = styled("div")(() => ({ +const Content = styled("div", { + shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", +})<{ + odysseyDesignTokens: DesignTokens; +}>(({ odysseyDesignTokens }) => ({ display: "flex", + flexDirection: "column", + alignItems: "flex-start", + backgroundColor: odysseyDesignTokens.HueNeutralWhite, })); -const buttonProviderValue = { isFullWidth: true }; +const MenuButtonContainer = styled("div", { + shouldForwardProp: (prop) => + prop !== "odysseyDesignTokens" && prop !== "variant", +})<{ + odysseyDesignTokens: DesignTokens; + variant: CardProps["variant"]; +}>(({ odysseyDesignTokens, variant }) => ({ + position: "absolute", + right: odysseyDesignTokens.Spacing2, + top: + variant === "compact" + ? odysseyDesignTokens.Spacing4 + : odysseyDesignTokens.Spacing2, + height: variant === "compact" ? odysseyDesignTokens.Spacing8 : "auto", + display: "flex", + alignItems: "center", + + "& .MuiSkeleton-root": { + width: odysseyDesignTokens.Spacing5, + height: odysseyDesignTokens.Spacing5, + margin: odysseyDesignTokens.Spacing1, + }, +})); const Card = ({ - button, + children, description, + detailPanel, image, - menuButtonChildren, - onClick, + isLoading, overline, title, + variant, + accessory, + button, + menuButtonChildren, + onClick, }: CardProps) => { const odysseyDesignTokens = useOdysseyDesignTokens(); + const { t } = useTranslation(); - const cardContent = useMemo( + const loadingContent = useMemo( () => ( - - - {image && ( - - {image} - - )} + + {image && ( + + + + )} + + + + {overline && ( + + + + )} + {title && ( + + + + )} + + {description && ( + + + + + )} + - {overline && {overline}} - {title && {title}} - {description && ( - {description} + {children && ( + + + )} {button && ( - - {button} - + )} - - + + ), [ button, + children, description, image, menuButtonChildren, + odysseyDesignTokens, overline, title, - odysseyDesignTokens, + variant, ], ); + const cardContent = useMemo(() => { + const buttonProviderValue = { isFullWidth: true }; + return ( + + {image && ( + + {image} + + )} + + + + {overline && {overline}} + {title && ( + + {title} + + )} + + {description && ( + {description} + )} + + + {children && ( + + {children} + + )} + + {button && ( + + + {button} + + + )} + + {detailPanel && ( + + {detailPanel} + + )} + + + ); + }, [ + button, + children, + description, + detailPanel, + image, + menuButtonChildren, + odysseyDesignTokens, + overline, + title, + variant, + ]); + return ( - - {onClick ? ( - {cardContent} + + {Boolean(accessory) && ( + + {isLoading ? : accessory} + + )} + {isLoading ? ( + loadingContent + ) : onClick ? ( + + {cardContent} + ) : ( cardContent )} - {menuButtonChildren && ( - - } - ariaLabel="Card menu" - buttonVariant="floating" - menuAlignment="right" - size="small" - tooltipText="Actions" - > - {menuButtonChildren} - + + {isLoading ? ( + + ) : ( + } + ariaLabel={t("table.moreactions.arialabel")} + buttonVariant="floating" + menuAlignment="right" + size="small" + tooltipText={t("table.actions")} + > + {menuButtonChildren} + + )} )} - + ); }; const MemoizedCard = memo(Card); MemoizedCard.displayName = "Card"; -/** - * @deprecated The 'Tile' component is now called 'Card'. Please update your references as 'Tile' will be deprecated soon. - */ -const Tile = (props: CardProps) => { - useEffect(() => { - console.warn( - "Warning: The 'Tile' component is now called 'Card'. Please update your references as 'Tile' will be deprecated soon.", - ); - }, []); - - return ; -}; - -const MemoizedTile = memo(Tile); -MemoizedTile.displayName = "Tile"; - -export { MemoizedCard as Card, MemoizedTile as Tile }; +export { MemoizedCard as Card }; diff --git a/packages/odyssey-react-mui/src/labs/DataView/CardLayoutContent.tsx b/packages/odyssey-react-mui/src/labs/DataView/CardLayoutContent.tsx index 6f3f589493..389f4257c6 100644 --- a/packages/odyssey-react-mui/src/labs/DataView/CardLayoutContent.tsx +++ b/packages/odyssey-react-mui/src/labs/DataView/CardLayoutContent.tsx @@ -20,15 +20,14 @@ import { } from "material-react-table"; import { Box } from "../../Box"; -import { Checkbox as MuiCheckbox } from "@mui/material"; import { CircularProgress } from "../../CircularProgress"; import { DesignTokens, useOdysseyDesignTokens, } from "../../OdysseyDesignTokensContext"; import { RowActions } from "./RowActions"; -import { DataCard } from "./DataCard"; import { CardLayout, CardLayoutProps, UniversalProps } from "./componentTypes"; +import { DataCard } from "./DataCard"; export type CardLayoutContentProps = { currentLayout: CardLayout; @@ -129,14 +128,6 @@ const LoadingContainer = styled("div", { paddingBlock: odysseyDesignTokens.Spacing5, })); -const CheckboxContainer = styled("div", { - shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", -})<{ - odysseyDesignTokens: DesignTokens; -}>(({ odysseyDesignTokens }) => ({ - marginBlockStart: `-${odysseyDesignTokens.Spacing1}`, -})); - const CardLayoutContent = ({ currentLayout, data, @@ -203,24 +194,13 @@ const CardLayoutContent = ({ return ( - handleRowSelectionChange(row)} - /> - - ) - } children={children} description={description} - renderDetailPanel={cardLayoutOptions.renderDetailPanel} - row={row} + detailPanel={cardLayoutOptions.renderDetailPanel?.({ row })} + hasSelection={hasRowSelection} image={image} + isSelected={rowSelection[row.id] ?? false} + onSelectionChange={() => handleRowSelectionChange(row)} key={row.id} menuButtonChildren={ (cardLayoutOptions.rowActionMenuItems || diff --git a/packages/odyssey-react-mui/src/labs/DataView/DataCard.tsx b/packages/odyssey-react-mui/src/labs/DataView/DataCard.tsx index 39c5d801a7..e5d770c66c 100644 --- a/packages/odyssey-react-mui/src/labs/DataView/DataCard.tsx +++ b/packages/odyssey-react-mui/src/labs/DataView/DataCard.tsx @@ -10,205 +10,88 @@ * See the License for the specific language governing permissions and limitations under the License. */ +import { memo, useMemo, useState } from "react"; import { - MouseEventHandler, - ReactElement, - memo, - useMemo, - ReactNode, - useState, -} from "react"; -import { - IconButton as MuiIconButton, - Card as MuiCard, - CardActions as MuiCardActions, - CardActionArea as MuiCardActionArea, Tooltip as MuiTooltip, + IconButton as MuiIconButton, + Checkbox as MuiCheckbox, } from "@mui/material"; -import styled from "@emotion/styled"; import { useTranslation } from "react-i18next"; - -import { Box } from "../../Box"; -import { - Button, - ButtonContext, - MenuButton, - MenuButtonProps, -} from "../../Buttons"; +import { ChevronDownIcon, ChevronUpIcon } from "../../icons.generated"; +import { Card, CardProps } from "../../Card"; +import styled from "@emotion/styled"; import { DesignTokens, useOdysseyDesignTokens, } from "../../OdysseyDesignTokensContext"; -import { Heading5, Paragraph, Support } from "../../Typography"; -import { - ChevronDownIcon, - ChevronUpIcon, - MoreIcon, -} from "../../icons.generated"; -import { CardLayoutProps } from "./componentTypes"; -import { MRT_RowData } from "material-react-table"; - -export const CARD_IMAGE_SIZE = "64px"; -export const CARD_IMAGE_SIZE_COMPACT = "48px"; -export const cardVariantValues = ["tile", "stack", "compact"] as const; - -export type DataCardProps = { - children?: ReactNode; - description?: string; - image?: ReactElement; - overline?: string; - renderDetailPanel?: CardLayoutProps["renderDetailPanel"]; - row: TData; - title?: string; - variant?: (typeof cardVariantValues)[number]; -} & ( - | { - Accessory?: never; - button?: never; - menuButtonChildren?: never; - onClick: MouseEventHandler; - } - | { - Accessory?: ReactNode; - button?: ReactElement; - menuButtonChildren?: MenuButtonProps["children"]; - onClick?: never; - } -); - -type DataCardComponent = (( - props: DataCardProps, -) => JSX.Element) & { - displayName?: string; +export type DataCardProps = { + children?: CardProps["children"]; + description?: CardProps["description"]; + detailPanel?: CardProps["detailPanel"]; + image?: CardProps["image"]; + overline?: CardProps["overline"]; + title?: CardProps["title"]; + variant?: CardProps["variant"]; + menuButtonChildren?: CardProps["menuButtonChildren"]; + hasSelection?: boolean; + isSelected?: boolean; + onSelectionChange?: () => void; }; -const AccessoryContainer = styled("div", { - shouldForwardProp: (prop) => - prop !== "odysseyDesignTokens" && prop !== "variant", -})<{ - odysseyDesignTokens: DesignTokens; - variant: (typeof cardVariantValues)[number]; -}>(({ odysseyDesignTokens, variant }) => ({ - display: "flex", - flexDirection: variant === "compact" ? "row" : "column", - alignItems: "center", - gap: odysseyDesignTokens.Spacing2, - height: variant === "compact" ? CARD_IMAGE_SIZE_COMPACT : "auto", -})); - -const ImageContainer = styled("div", { - shouldForwardProp: (prop) => - prop !== "odysseyDesignTokens" && - prop !== "hasMenuButtonChildren" && - prop !== "variant", -})<{ - odysseyDesignTokens: DesignTokens; - hasMenuButtonChildren: boolean; - variant: (typeof cardVariantValues)[number]; -}>(({ odysseyDesignTokens, hasMenuButtonChildren, variant }) => ({ - display: "flex", - alignItems: "flex-start", - height: variant === "compact" ? CARD_IMAGE_SIZE_COMPACT : CARD_IMAGE_SIZE, - maxHeight: variant === "compact" ? CARD_IMAGE_SIZE_COMPACT : CARD_IMAGE_SIZE, - marginBlockEnd: variant === "tile" ? odysseyDesignTokens.Spacing5 : 0, - paddingRight: hasMenuButtonChildren ? odysseyDesignTokens.Spacing5 : 0, -})); - -const MenuButtonContainer = styled("div", { - shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", -})<{ - odysseyDesignTokens: DesignTokens; - variant: (typeof cardVariantValues)[number]; -}>(({ odysseyDesignTokens, variant }) => ({ - position: "absolute", - right: odysseyDesignTokens.Spacing3, - top: - variant === "compact" - ? odysseyDesignTokens.Spacing4 - : odysseyDesignTokens.Spacing3, - height: variant === "compact" ? CARD_IMAGE_SIZE_COMPACT : "auto", - display: "flex", - alignItems: "center", -})); - -const CardInnerContainer = styled("div", { - shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", -})<{ odysseyDesignTokens: DesignTokens }>(({ odysseyDesignTokens }) => ({ - display: "flex", - gap: odysseyDesignTokens.Spacing3, -})); - -const CardImageAndContentContainer = styled("div", { - shouldForwardProp: (prop) => prop !== "variant" && prop !== "centerContent", -})<{ variant: (typeof cardVariantValues)[number]; centerContent: boolean }>( - ({ variant, centerContent }) => ({ - display: "flex", - flexDirection: variant === "tile" ? "column" : "row", - alignItems: centerContent ? "center" : "flex-start", - }), -); - -const CardContent = styled("div", { +const CheckboxContainer = styled("div", { shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", })<{ odysseyDesignTokens: DesignTokens; - variant: (typeof cardVariantValues)[number]; -}>(({ odysseyDesignTokens, variant }) => ({ - "& > .MuiTypography-h5:not(:last-child)": { - marginBlockEnd: `${variant === "compact" ? odysseyDesignTokens.Spacing1 : odysseyDesignTokens.Spacing3} !important`, - }, - "& > *:last-child": { - marginBlockEnd: 0, - }, -})); - -const CardChildrenContainer = styled("div", { - shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", -})<{ odysseyDesignTokens: DesignTokens }>(({ odysseyDesignTokens }) => ({ - ["&:not(:first-child)"]: { - marginBlockStart: odysseyDesignTokens.Spacing3, - }, +}>(({ odysseyDesignTokens }) => ({ + marginBlockStart: `-${odysseyDesignTokens.Spacing1}`, })); const AccessoryPlaceholder = styled(MuiIconButton)(() => ({ visibility: "hidden", })); -const buttonProviderValue = { isFullWidth: true }; -const DataCard: DataCardComponent = ({ - Accessory: AccessoryProp, - button, +const DataCard = ({ children, description, + detailPanel, + hasSelection, image, - menuButtonChildren, - onClick, + isSelected, + onSelectionChange, overline, - renderDetailPanel, - row, title, - variant = "tile", -}: DataCardProps) => { + variant, + menuButtonChildren, +}: DataCardProps) => { const odysseyDesignTokens = useOdysseyDesignTokens(); const { t } = useTranslation(); - const [isDetailPanelOpen, setIsDetailPanelOpen] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + + const SelectionCheckbox = useMemo( + () => ( + + + + ), + [isSelected, odysseyDesignTokens, onSelectionChange], + ); const ExpansionToggle = useMemo(() => { - return renderDetailPanel?.({ row }) ? ( + return detailPanel ? ( : } - onClick={() => setIsDetailPanelOpen(!isDetailPanelOpen)} + children={isExpanded ? : } + onClick={() => setIsExpanded(!isExpanded)} aria-label={ - isDetailPanelOpen + isExpanded ? t("table.rowexpansion.collapse") : t("table.rowexpansion.expand") } @@ -219,138 +102,33 @@ const DataCard: DataCardComponent = ({ ); - }, [isDetailPanelOpen, renderDetailPanel, row, t]); + }, [isExpanded, detailPanel, t]); const Accessory = useMemo(() => { return ( - - {AccessoryProp} - {renderDetailPanel && ExpansionToggle} - + <> + {hasSelection && SelectionCheckbox} + {detailPanel && ExpansionToggle} + ); - }, [ - AccessoryProp, - ExpansionToggle, - odysseyDesignTokens, - renderDetailPanel, - variant, - ]); - - const cardContent = useMemo(() => { - const countDefinedProps = ( - props: Array, - ) => { - return props.filter((prop) => prop !== undefined).length; - }; - - const shouldCenterContent = - variant === "compact" && - (!renderDetailPanel || !isDetailPanelOpen) && - countDefinedProps([title, description, overline, button, children]) <= 2; - - return ( - - {(AccessoryProp || renderDetailPanel) && {Accessory}} - - {image && ( - - {image} - - )} - - - {overline && {overline}} - {title && {title}} - {description && ( - {description} - )} - - {button && ( - - - {button} - - - )} - - {children && ( - - {children} - - )} - - {renderDetailPanel && isDetailPanelOpen && ( - - {renderDetailPanel({ row })} - - )} - - - - ); - }, [ - odysseyDesignTokens, - AccessoryProp, - renderDetailPanel, - Accessory, - variant, - image, - menuButtonChildren, - overline, - title, - description, - button, - children, - isDetailPanelOpen, - row, - ]); + }, [ExpansionToggle, SelectionCheckbox, detailPanel, hasSelection]); return ( - - {onClick ? ( - {cardContent} - ) : ( - cardContent - )} - - {menuButtonChildren && ( - - } - ariaLabel={t("table.moreactions.arialabel")} - buttonVariant="floating" - menuAlignment="right" - size="small" - tooltipText={t("table.actions")} - > - {menuButtonChildren} - - - )} - + ); }; -const MemoizedDataCard = memo(DataCard) as DataCardComponent; +const MemoizedDataCard = memo(DataCard); MemoizedDataCard.displayName = "DataCard"; export { MemoizedDataCard as DataCard }; diff --git a/packages/odyssey-react-mui/src/labs/DataView/componentTypes.ts b/packages/odyssey-react-mui/src/labs/DataView/componentTypes.ts index 664339b4b4..7e8e7a2ec3 100644 --- a/packages/odyssey-react-mui/src/labs/DataView/componentTypes.ts +++ b/packages/odyssey-react-mui/src/labs/DataView/componentTypes.ts @@ -30,9 +30,9 @@ import { } from "./dataTypes"; import { MenuButtonProps } from "../.."; import { paginationTypeValues } from "../DataTablePagination"; -import { DataCardProps } from "./DataCard"; import { type PaginationProps } from "../../Pagination"; import { RowActionsProps } from "./RowActions"; +import { DataCardProps } from "./DataCard"; export type DataLayout = (typeof availableLayouts)[number]; export type CardLayout = (typeof availableCardLayouts)[number]; @@ -109,7 +109,7 @@ export type TableLayoutProps = { }; export type CardLayoutProps = { - itemProps: (row: TData) => Omit, "row">; + itemProps: (row: TData) => DataCardProps; maxGridColumns?: number; renderDetailPanel?: (props: { row: TData }) => ReactNode; rowActionMenuItems?: RowActionsProps["rowActionMenuItems"]; diff --git a/packages/odyssey-storybook/src/components/odyssey-labs/DataView/DataView.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-labs/DataView/DataView.stories.tsx index 836d44d52f..85a958b3b9 100644 --- a/packages/odyssey-storybook/src/components/odyssey-labs/DataView/DataView.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-labs/DataView/DataView.stories.tsx @@ -365,7 +365,7 @@ const customNoResultsPlaceholder = ( /> ); -const itemProps = (row: DataRow) => ({ +const itemProps = (row: DataRow): DataCardProps => ({ overline: `${row.city}, ${row.state}`, title: row.name, description: `${row.name} is ${row.age} years old.`, @@ -1020,7 +1020,7 @@ const stackItemProps = (row: Person) => ({ overline: "Overline", title: row.name, description: `${row.name} is ${row.age} years old.`, - variant: "stack" as DataCardProps["variant"], + variant: "stack" as DataCardProps["variant"], image: Logo, }); @@ -1045,7 +1045,7 @@ export const StackCards: StoryObj = { const compactItemProps = (row: Person) => ({ title: row.name, - variant: "compact" as DataCardProps["variant"], + variant: "compact" as DataCardProps["variant"], image: Logo, }); diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Card/Card.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Card/Card.stories.tsx index b09761c5eb..abc0536289 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Card/Card.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Card/Card.stories.tsx @@ -13,97 +13,92 @@ import { Meta, StoryObj } from "@storybook/react"; import { MuiThemeDecorator } from "../../../../.storybook/components"; import { - Box, Button, Card, MenuItem, CardProps, + cardVariantValues, + Checkbox, } from "@okta/odyssey-react-mui"; +import { IconButton as MuiIconButton } from "@mui/material"; +import { CheckboxProps as MuiCheckboxProps } from "@okta/odyssey-react-mui"; +import { ChevronDownIcon, ChevronUpIcon } from "@okta/odyssey-react-mui/icons"; +import { jest } from "@storybook/jest"; +import { MouseEventHandler, useCallback, useState } from "react"; +import styled from "@emotion/styled"; +type CardMetaProps = Omit< + CardProps, + | "children" + | "detailPanel" + | "image" + | "accessory" + | "button" + | "menuButtonChildren" + | "onClick" +> & { + children?: boolean; + detailPanel?: boolean; + image?: boolean; + accessory?: boolean; + button?: boolean; + menuButtonChildren?: boolean; + onClick?: boolean; +}; -const storybookMeta: Meta = { +const storybookMeta: Meta = { title: "MUI Components/Card", - component: Card, argTypes: { - title: { - control: "text", - description: "The heading of the card.", - table: { - type: { - summary: "string", - }, - defaultValue: "", - }, + children: { + control: "boolean", }, description: { control: "text", - description: - "The body text of the card. The consumer is responsible for truncating this string.", - table: { - type: { - summary: "string", - }, - defaultValue: "", - }, + }, + detailPanel: { + control: "boolean", + }, + image: { + control: "boolean", + }, + isLoading: { + control: "boolean", }, overline: { control: "text", - description: 'The "eyebrow" text above the card title.', - table: { - type: { - summary: "string", - }, - defaultValue: "", - }, }, - image: { - control: null, - description: - "An optional image or icon at the top of the card, preferably as an img or svg element.", - table: { - type: { - summary: "ReactElement", - }, - defaultValue: "", - }, + title: { + control: "text", }, - onClick: { - control: null, - description: "The event handler for when the user clicks the card.", - table: { - type: { - summary: "MouseEventHandler", - }, - defaultValue: "", - }, + variant: { + control: "select", + options: cardVariantValues, + }, + accessory: { + control: "boolean", }, button: { - control: null, - description: - "The main action button for the card. Not valid if the card itself is clickable.", - table: { - type: { - summary: "ReactElement", - }, - defaultValue: "", - }, + control: "boolean", }, menuButtonChildren: { - control: null, - description: - "Menu items to be rendered in the card's optional menu button. If this prop is undefined, the menu button will not be shown. Not valid if the card itself is clickable.", - table: { - type: { - summary: "[MenuItem | Divider | ListSubheader]", - }, - defaultValue: "", - }, + control: "boolean", + }, + onClick: { + control: "boolean", }, }, args: { - title: "Title", + children: false, description: - "Identity can create great user experiences, increase customer sign-ups, and...", - overline: "Overline", + "This is a description of the app. Descriptions should be concise, and truncation is handled by the consumer rather than the component, so if you write a novel, it will display a novel.", + detailPanel: false, + image: undefined, + isLoading: false, + overline: "Category", + title: "App title", + variant: "tile", + accessory: false, + button: undefined, + menuButtonChildren: false, onClick: undefined, }, decorators: [MuiThemeDecorator], @@ -111,82 +106,205 @@ const storybookMeta: Meta = { export default storybookMeta; -export const Default: StoryObj = { - render: ({ ...props }) => ( - +const image = Example logo; + +const children = <>Children.; +const detailPanel = <>Detail panel.; +const button =