Skip to content

Commit

Permalink
Data view, stack, and table (#2267)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jordankoschei-okta authored Jul 2, 2024
1 parent 03349a6 commit 19bc10b
Show file tree
Hide file tree
Showing 34 changed files with 5,991 additions and 54 deletions.
57 changes: 32 additions & 25 deletions packages/odyssey-react-mui/src/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
useOdysseyDesignTokens,
} from "./OdysseyDesignTokensContext";
import { Heading5, Paragraph, Support } from "./Typography";
import { Box } from "./Box";

export const CARD_IMAGE_HEIGHT = "64px";

Expand Down Expand Up @@ -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,
}));
Expand All @@ -76,6 +77,10 @@ const MenuButtonContainer = styled("div", {
top: odysseyDesignTokens.Spacing3,
}));

const CardContentContainer = styled("div")(() => ({
display: "flex",
}));

const buttonProviderValue = { isFullWidth: true };

const Card = ({
Expand All @@ -91,30 +96,32 @@ const Card = ({

const cardContent = useMemo(
() => (
<>
{image && (
<ImageContainer
odysseyDesignTokens={odysseyDesignTokens}
hasMenuButtonChildren={Boolean(menuButtonChildren)}
>
{image}
</ImageContainer>
)}

{overline && <Support component="div">{overline}</Support>}
{title && <Heading5 component="div">{title}</Heading5>}
{description && (
<Paragraph color="textSecondary">{description}</Paragraph>
)}

{button && (
<MuiCardActions>
<ButtonContext.Provider value={buttonProviderValue}>
{button}
</ButtonContext.Provider>
</MuiCardActions>
)}
</>
<CardContentContainer>
<Box>
{image && (
<ImageContainer
odysseyDesignTokens={odysseyDesignTokens}
hasMenuButtonChildren={Boolean(menuButtonChildren)}
>
{image}
</ImageContainer>
)}

{overline && <Support component="div">{overline}</Support>}
{title && <Heading5 component="div">{title}</Heading5>}
{description && (
<Paragraph color="textSecondary">{description}</Paragraph>
)}

{button && (
<MuiCardActions>
<ButtonContext.Provider value={buttonProviderValue}>
{button}
</ButtonContext.Provider>
</MuiCardActions>
)}
</Box>
</CardContentContainer>
),
[
button,
Expand Down
4 changes: 3 additions & 1 deletion packages/odyssey-react-mui/src/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
MRT_ColumnDef,
MRT_TableInstance,
} from "material-react-table";
import { useTranslation } from "react-i18next";
import {
ArrowDownIcon,
ArrowUnsortedIcon,
Expand Down Expand Up @@ -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<T extends DataTableRowData> = MRT_ColumnDef<T> & {
/**
Expand Down Expand Up @@ -387,6 +387,8 @@ const DataTable = ({
searchDelayTime,
totalRows,
}: DataTableProps) => {
const { t } = useTranslation();

const [data, setData] = useState<DataTableRowData[]>([]);
const [pagination, setPagination] = useState({
pageIndex: currentPage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { DataTableProps } from "./DataTable";
import { Trans, useTranslation } from "react-i18next";

export type DataTableRowActionsProps = {
row: MRT_Row<MRT_RowData>;
row: MRT_Row<MRT_RowData> | MRT_RowData;
rowIndex: number;
rowActionButtons?: (
row: MRT_RowData,
Expand Down
14 changes: 11 additions & 3 deletions packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,46 @@ 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 <head>.
*/
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 `<head>`.
*/
/**
* @deprecated Will be removed in a future Odyssey version. Use `hasShadowDomElement` instead.
*/
shadowDomElement?: HTMLDivElement | HTMLElement;
stylisPlugins?: StylisPlugin[];
};

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 <CacheProvider value={emotionCache}>{children}</CacheProvider>;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/odyssey-react-mui/src/OdysseyProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const OdysseyProvider = <SupportedLanguages extends string>({
<OdysseyCacheProvider
nonce={nonce}
emotionRoot={emotionRoot}
shadowDomElement={shadowDomElement}
hasShadowDom={Boolean(shadowDomElement)}
stylisPlugins={stylisPlugins}
>
<OdysseyThemeProvider
Expand Down
4 changes: 3 additions & 1 deletion packages/odyssey-react-mui/src/Pagination/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

import { t } from "i18next";
import { useTranslation } from "react-i18next";

type UsePaginationType = {
pageIndex: number;
Expand All @@ -23,6 +23,8 @@ export const usePagination = ({
pageSize,
totalRows,
}: UsePaginationType) => {
const { t } = useTranslation();

const firstRow = pageSize * (pageIndex - 1) + 1;
const lastRow = firstRow + (pageSize - 1);
if (totalRows && lastRow > totalRows) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SetStateAction<MRT_RowSelectionState>>;
};

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 (
<BulkActionsContainer odysseyDesignTokens={odysseyDesignTokens}>
{selectedRowCount > 0 && (
<MenuButton
ariaLabel="More actions"
buttonLabel={t("table.actions.selectsome", { selectedRowCount })}
buttonVariant="primary"
endIcon={<ChevronDownIcon />}
>
{menuItems?.(rowSelection)}
</MenuButton>
)}
<Box>
<Button
isDisabled={selectedRowCount === data.length} // Disabled if all are selected
label={t("table.actions.selectall")}
onClick={handleSelectAll}
variant="secondary"
/>
<Button
isDisabled={selectedRowCount === 0} // Disabled if none are selected
label={t("table.actions.selectnone")}
onClick={handleSelectNone}
variant="secondary"
/>
</Box>
</BulkActionsContainer>
);
};

const MemoizedBulkActionsMenu = memo(BulkActionsMenu);
MemoizedBulkActionsMenu.displayName = "BulkActionsMenu";

export { MemoizedBulkActionsMenu as BulkActionsMenu };
99 changes: 99 additions & 0 deletions packages/odyssey-react-mui/src/labs/DataComponents/DataStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*!
* 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, useMemo } from "react";

import { availableStackLayouts } from "./constants";
import {
AvailableStackLayouts,
StackProps,
UniversalProps,
} from "./componentTypes";
import { DataView } from "./DataView";

export type DataStackProps = UniversalProps &
StackProps & {
initialLayout?: (typeof availableStackLayouts)[number];
availableLayouts?: AvailableStackLayouts;
};

const DataStack = ({
availableLayouts,
bulkActionMenuItems,
cardProps,
currentPage,
emptyPlaceholder,
errorMessage,
filters,
getData,
hasFilters,
hasPagination,
hasRowReordering,
hasRowSelection,
hasSearch,
hasSearchSubmitButton,
isEmpty,
isLoading,
isNoResults,
isRowReorderingDisabled,
maxGridColumns,
noResultsPlaceholder,
onChangeRowSelection,
paginationType,
resultsPerPage,
rowActionMenuItems,
searchDelayTime,
totalRows,
}: DataStackProps) => {
const stackOptions = useMemo(
() => ({
cardProps,
maxGridColumns,
rowActionMenuItems,
}),
[cardProps, maxGridColumns, rowActionMenuItems],
);

return (
<DataView
availableLayouts={availableLayouts}
bulkActionMenuItems={bulkActionMenuItems}
currentPage={currentPage}
emptyPlaceholder={emptyPlaceholder}
errorMessage={errorMessage}
filters={filters}
getData={getData}
hasFilters={hasFilters}
hasPagination={hasPagination}
hasRowReordering={hasRowReordering}
hasSearch={hasSearch}
hasSearchSubmitButton={hasSearchSubmitButton}
hasRowSelection={hasRowSelection}
isEmpty={isEmpty}
isLoading={isLoading}
isNoResults={isNoResults}
isRowReorderingDisabled={isRowReorderingDisabled}
noResultsPlaceholder={noResultsPlaceholder}
onChangeRowSelection={onChangeRowSelection}
paginationType={paginationType}
resultsPerPage={resultsPerPage}
searchDelayTime={searchDelayTime}
stackOptions={stackOptions}
totalRows={totalRows}
/>
);
};

const MemoizedDataStack = memo(DataStack);
MemoizedDataStack.displayName = "DataStack";

export { MemoizedDataStack as DataStack };
Loading

0 comments on commit 19bc10b

Please sign in to comment.