From 19bc10be6f600f612637fdccd9a16c63487eaeb9 Mon Sep 17 00:00:00 2001 From: Jordan Koschei <91091570+jordankoschei-okta@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:10:18 -0400 Subject: [PATCH] Data view, stack, and table (#2267) OKTA-726074 feat: add DataComponent work feat: add DataComponent stories feat: add enableWrapping for backwards compat with hasTextWrapping refactor: alphabetize all the props refactor: reorganize the imports refactor: adding translations refactor: remove negative margin from card accessory refactor: convert sx to styled wherever needed refactor: fix some missing callback deps feat: adding row expansion to stack cards feat: add proper open mechanism to stack cards feat: updating table translations refactor: replace Box with MuiBox for native flex support refactor: implement all code review feedback feat: add docs for Data View --- packages/odyssey-react-mui/src/Card.tsx | 57 +- .../src/DataTable/DataTable.tsx | 4 +- .../src/DataTable/DataTableRowActions.tsx | 2 +- .../src/OdysseyCacheProvider.tsx | 14 +- .../odyssey-react-mui/src/OdysseyProvider.tsx | 2 +- .../src/Pagination/usePagination.ts | 4 +- .../labs/DataComponents/BulkActionsMenu.tsx | 97 + .../src/labs/DataComponents/DataStack.tsx | 99 + .../src/labs/DataComponents/DataTable.tsx | 111 + .../src/labs/DataComponents/DataView.tsx | 394 ++++ .../src/labs/DataComponents/DetailPanel.tsx | 31 + .../labs/DataComponents/LayoutSwitcher.tsx | 73 + .../src/labs/DataComponents/RowActions.tsx | 122 ++ .../src/labs/DataComponents/StackCard.tsx | 256 +++ .../src/labs/DataComponents/StackContent.tsx | 254 +++ .../src/labs/DataComponents/TableContent.tsx | 390 ++++ .../src/labs/DataComponents/TableSettings.tsx | 138 ++ .../src/labs/DataComponents/componentTypes.ts | 111 + .../src/labs/DataComponents/constants.tsx | 20 + .../src/labs/DataComponents/dataTypes.ts | 77 + .../src/labs/DataComponents/fetchData.ts | 47 + .../src/labs/DataComponents/index.tsx | 19 + .../labs/DataComponents/tableConstants.tsx | 162 ++ .../DataComponents/useFilterConversion.ts | 92 + .../src/labs/DataFilters.tsx | 12 +- packages/odyssey-react-mui/src/labs/index.ts | 2 + .../properties/odyssey-react-mui.properties | 11 +- .../src/theme/components.tsx | 9 +- .../DataComponents/DataComponents.mdx | 476 +++++ .../DataComponents/DataComponents.stories.tsx | 910 ++++++++ .../DataComponents/dataFunctions.ts | 123 ++ .../DataComponents/personData.tsx | 1885 +++++++++++++++++ .../odyssey-mui/Card/Card.stories.tsx | 27 +- .../DataTable/DataTable.stories.tsx | 14 +- 34 files changed, 5991 insertions(+), 54 deletions(-) create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/BulkActionsMenu.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/DataStack.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/DataTable.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/DataView.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/DetailPanel.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/LayoutSwitcher.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/RowActions.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/StackCard.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/StackContent.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/TableContent.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/TableSettings.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/componentTypes.ts create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/constants.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/dataTypes.ts create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/fetchData.ts create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/index.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/tableConstants.tsx create mode 100644 packages/odyssey-react-mui/src/labs/DataComponents/useFilterConversion.ts create mode 100644 packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.mdx create mode 100644 packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/DataComponents.stories.tsx create mode 100644 packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/dataFunctions.ts create mode 100644 packages/odyssey-storybook/src/components/odyssey-labs/DataComponents/personData.tsx diff --git a/packages/odyssey-react-mui/src/Card.tsx b/packages/odyssey-react-mui/src/Card.tsx index 41c540ec05..3097fd42ba 100644 --- a/packages/odyssey-react-mui/src/Card.tsx +++ b/packages/odyssey-react-mui/src/Card.tsx @@ -33,6 +33,7 @@ import { useOdysseyDesignTokens, } from "./OdysseyDesignTokensContext"; import { Heading5, Paragraph, Support } from "./Typography"; +import { Box } from "./Box"; export const CARD_IMAGE_HEIGHT = "64px"; @@ -63,7 +64,7 @@ const ImageContainer = styled("div", { }>(({ odysseyDesignTokens, hasMenuButtonChildren }) => ({ display: "flex", alignItems: "flex-start", - maxHeight: `${CARD_IMAGE_HEIGHT}`, + maxHeight: CARD_IMAGE_HEIGHT, marginBlockEnd: odysseyDesignTokens.Spacing5, paddingRight: hasMenuButtonChildren ? odysseyDesignTokens.Spacing5 : 0, })); @@ -76,6 +77,10 @@ const MenuButtonContainer = styled("div", { top: odysseyDesignTokens.Spacing3, })); +const CardContentContainer = styled("div")(() => ({ + display: "flex", +})); + const buttonProviderValue = { isFullWidth: true }; const Card = ({ @@ -91,30 +96,32 @@ const Card = ({ const cardContent = useMemo( () => ( - <> - {image && ( - - {image} - - )} - - {overline && {overline}} - {title && {title}} - {description && ( - {description} - )} - - {button && ( - - - {button} - - - )} - + + + {image && ( + + {image} + + )} + + {overline && {overline}} + {title && {title}} + {description && ( + {description} + )} + + {button && ( + + + {button} + + + )} + + ), [ button, diff --git a/packages/odyssey-react-mui/src/DataTable/DataTable.tsx b/packages/odyssey-react-mui/src/DataTable/DataTable.tsx index ae6d5b0547..e727eabdd8 100644 --- a/packages/odyssey-react-mui/src/DataTable/DataTable.tsx +++ b/packages/odyssey-react-mui/src/DataTable/DataTable.tsx @@ -34,6 +34,7 @@ import { MRT_ColumnDef, MRT_TableInstance, } from "material-react-table"; +import { useTranslation } from "react-i18next"; import { ArrowDownIcon, ArrowUnsortedIcon, @@ -61,7 +62,6 @@ import { useScrollIndication } from "./useScrollIndication"; import styled from "@emotion/styled"; import { EmptyState } from "../EmptyState"; import { Callout } from "../Callout"; -import { t } from "i18next"; export type DataTableColumn = MRT_ColumnDef & { /** @@ -387,6 +387,8 @@ const DataTable = ({ searchDelayTime, totalRows, }: DataTableProps) => { + const { t } = useTranslation(); + const [data, setData] = useState([]); const [pagination, setPagination] = useState({ pageIndex: currentPage, diff --git a/packages/odyssey-react-mui/src/DataTable/DataTableRowActions.tsx b/packages/odyssey-react-mui/src/DataTable/DataTableRowActions.tsx index 773aed4882..2e80e6407f 100644 --- a/packages/odyssey-react-mui/src/DataTable/DataTableRowActions.tsx +++ b/packages/odyssey-react-mui/src/DataTable/DataTableRowActions.tsx @@ -27,7 +27,7 @@ import { DataTableProps } from "./DataTable"; import { Trans, useTranslation } from "react-i18next"; export type DataTableRowActionsProps = { - row: MRT_Row; + row: MRT_Row | MRT_RowData; rowIndex: number; rowActionButtons?: ( row: MRT_RowData, diff --git a/packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx b/packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx index 43742f4491..3973c7e726 100644 --- a/packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx +++ b/packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx @@ -23,16 +23,20 @@ import { CacheProvider } from "@emotion/react"; export type OdysseyCacheProviderProps = { children: ReactNode; - nonce?: string; /** * Emotion caches styles into the style element. * When enabling this prop, Emotion caches the styles at this element, rather than in . */ emotionRoot?: HTMLStyleElement; + hasShadowDom?: boolean; + nonce?: string; /** * Emotion renders into this HTML element. * When enabling this prop, Emotion renders at the top of this component rather than the bottom like it does in the HTML ``. */ + /** + * @deprecated Will be removed in a future Odyssey version. Use `hasShadowDomElement` instead. + */ shadowDomElement?: HTMLDivElement | HTMLElement; stylisPlugins?: StylisPlugin[]; }; @@ -40,21 +44,25 @@ export type OdysseyCacheProviderProps = { const OdysseyCacheProvider = ({ children, emotionRoot, + hasShadowDom: hasShadowDomProp, nonce, + shadowDomElement, stylisPlugins, }: OdysseyCacheProviderProps) => { const uniqueAlphabeticalId = useUniqueAlphabeticalId(); + const hasShadowDom = hasShadowDomProp || shadowDomElement; + const emotionCache = useMemo(() => { return createCache({ ...(emotionRoot && { container: emotionRoot }), key: uniqueAlphabeticalId, nonce: nonce ?? window.cspNonce, prepend: true, - speedy: false, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122 + speedy: hasShadowDom ? false : true, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122 ...(stylisPlugins && { stylisPlugins }), }); - }, [emotionRoot, nonce, stylisPlugins, uniqueAlphabeticalId]); + }, [emotionRoot, hasShadowDom, nonce, stylisPlugins, uniqueAlphabeticalId]); return {children}; }; diff --git a/packages/odyssey-react-mui/src/OdysseyProvider.tsx b/packages/odyssey-react-mui/src/OdysseyProvider.tsx index 69e8f89368..228ee84535 100644 --- a/packages/odyssey-react-mui/src/OdysseyProvider.tsx +++ b/packages/odyssey-react-mui/src/OdysseyProvider.tsx @@ -49,7 +49,7 @@ const OdysseyProvider = ({ { + const { t } = useTranslation(); + const firstRow = pageSize * (pageIndex - 1) + 1; const lastRow = firstRow + (pageSize - 1); if (totalRows && lastRow > totalRows) { diff --git a/packages/odyssey-react-mui/src/labs/DataComponents/BulkActionsMenu.tsx b/packages/odyssey-react-mui/src/labs/DataComponents/BulkActionsMenu.tsx new file mode 100644 index 0000000000..bc39f705fc --- /dev/null +++ b/packages/odyssey-react-mui/src/labs/DataComponents/BulkActionsMenu.tsx @@ -0,0 +1,97 @@ +/*! + * Copyright (c) 2024-present, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") + * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and limitations under the License. + */ + +import { memo, useCallback, Dispatch, SetStateAction } from "react"; +import { MRT_RowData, MRT_RowSelectionState } from "material-react-table"; +import styled from "@emotion/styled"; +import { useTranslation } from "react-i18next"; + +import { Box } from "../../Box"; +import { Button } from "../../Button"; +import { ChevronDownIcon } from "../../icons.generated"; +import { MenuButton } from "../../MenuButton"; +import { UniversalProps } from "./componentTypes"; +import { + DesignTokens, + useOdysseyDesignTokens, +} from "../../OdysseyDesignTokensContext"; + +export type BulkActionsMenuProps = { + data: MRT_RowData[]; + menuItems: UniversalProps["bulkActionMenuItems"]; + rowSelection: MRT_RowSelectionState; + setRowSelection: Dispatch>; +}; + +const BulkActionsContainer = styled("div", { + shouldForwardProp: (prop) => prop !== "odysseyDesignTokens", +})<{ + odysseyDesignTokens: DesignTokens; +}>(({ odysseyDesignTokens }) => ({ + display: "flex", + gap: odysseyDesignTokens.Spacing2, +})); + +const BulkActionsMenu = ({ + data, + menuItems, + rowSelection, + setRowSelection, +}: BulkActionsMenuProps) => { + const odysseyDesignTokens = useOdysseyDesignTokens(); + const { t } = useTranslation(); + + const selectedRowCount = Object.values(rowSelection).filter(Boolean).length; + + const handleSelectAll = useCallback(() => { + const rows = Object.fromEntries(data.map((row) => [row.id, true])); + setRowSelection(rows); + }, [data, setRowSelection]); + + const handleSelectNone = useCallback(() => { + setRowSelection({}); + }, [setRowSelection]); + + return ( + + {selectedRowCount > 0 && ( + } + > + {menuItems?.(rowSelection)} + + )} + +