Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Meta } from "@storybook/react";
import { useEffect, useMemo, useState } from "react";
import { ScrollableContent, ScrollableParent } from "src/components";
import { Button } from "src/components/Button";
import { checkboxFilter, multiFilter } from "src/components/Filters";
import { IconButton } from "src/components/IconButton";
import { GridDataRow } from "src/components/Table";
import { collapseColumn, column, numericColumn, selectColumn } from "src/components/Table/utils/columns";
import { simpleHeader } from "src/components/Table/utils/simpleHelpers";
import { Css } from "src/Css";
import { noop } from "src/utils";
import { withBeamDecorator, withRouter, zeroTo } from "src/utils/sb";
import { TestProjectLayout } from "../Layout.stories";
import { OpenRightPaneOpts, useRightPane } from "../RightPaneLayout";
import { GridTableLayout as GridTableLayoutComponent, useGridTableLayoutState } from "./GridTableLayout";

export default {
Expand All @@ -23,8 +27,9 @@ type DataRow = { kind: "data"; id: string; data: Data };
type Row = HeaderRow | ParentRow | DataRow;

export function GridTableLayout() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getFilterDefs(), []);
const columns = useMemo(() => getColumns(false), []);
const columns = useMemo(() => getColumns(false, openRightPane), [openRightPane]);

const layoutState = useGridTableLayoutState({
persistedFilter: {
Expand Down Expand Up @@ -69,8 +74,9 @@ export function GridTableLayout() {
}

export function ManyFilters() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getManyFilterDefs(), []);
const columns = useMemo(() => getColumns(), []);
const columns = useMemo(() => getColumns(false, openRightPane), [openRightPane]);

const layoutState = useGridTableLayoutState({
persistedFilter: {
Expand Down Expand Up @@ -101,8 +107,9 @@ export function ManyFilters() {
}

export function WithCheckboxFilter() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getCheckboxFilterDefs(), []);
const columns = useMemo(() => getColumns(), []);
const columns = useMemo(() => getColumns(false, openRightPane), [openRightPane]);

const layoutState = useGridTableLayoutState({
persistedFilter: {
Expand Down Expand Up @@ -133,8 +140,9 @@ export function WithCheckboxFilter() {
}

export function QueryTableLayout() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getFilterDefs(), []);
const columns = useMemo(() => getColumns(false), []);
const columns = useMemo(() => getColumns(false, openRightPane), [openRightPane]);

const layoutState = useGridTableLayoutState({
persistedFilter: {
Expand Down Expand Up @@ -184,8 +192,9 @@ export function QueryTableLayout() {
}

export function WithPagination() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getFilterDefs(), []);
const columns = useMemo(() => getColumns(), []);
const columns = useMemo(() => getColumns(false, openRightPane), [openRightPane]);

const layoutState = useGridTableLayoutState({
persistedFilter: {
Expand Down Expand Up @@ -221,8 +230,9 @@ export function WithPagination() {
}

export function GridTableLayoutWithColor() {
const { openRightPane } = useRightPane();
const filterDefs = useMemo(() => getFilterDefs(), []);
const columns = useMemo(() => getColumns(true), []);
const columns = useMemo(() => getColumns(true, openRightPane), [openRightPane]);
const storageKey = "with-session-storage-test";

const layoutState = useGridTableLayoutState({
Expand Down Expand Up @@ -463,7 +473,34 @@ function getManyFilterDefs() {
};
}

function getColumns(showColor: boolean = false) {
function DetailPane() {
const { closeRightPane } = useRightPane();

return (
<div css={Css.df.fdc.h100.$}>
<div css={Css.df.jcsb.p2.aic.bb.$}>
<h2 css={Css.py2.$}>Detail Pane</h2>
<div>
<IconButton icon={"x"} onClick={() => closeRightPane()} />
</div>
</div>
<ScrollableParent>
<ScrollableContent virtualized={true}>
<nav>
<ul css={Css.listReset.df.fdc.gap5.mt2.p2.$}>
{zeroTo(20).map((i) => (
<li key={i}>scroll items</li>
))}
<li>Bottom!</li>
</ul>
</nav>
</ScrollableContent>
</ScrollableParent>
</div>
);
}

function getColumns(showColor: boolean, openRightPane: (opts: OpenRightPaneOpts) => void) {
const nameColumn = column<Row>({
id: "name-col",
name: "Name",
Expand Down Expand Up @@ -501,8 +538,15 @@ function getColumns(showColor: boolean = false) {
id: "action-col",
name: "Action",
header: () => ({ content: "Action", css: Css.if(showColor).bgPurple500.$ }),
parent: () => ({ content: <div>Actions</div>, value: "", css: Css.if(showColor).bgPurple500.$ }),
data: () => ({ content: <div>Actions</div>, css: Css.if(showColor).bgPurple500.$ }),
parent: () => ({
content: <Button label={"Action"} onClick={() => openRightPane({ content: <DetailPane /> })} />,
value: "",
css: Css.if(showColor).bgPurple500.$,
}),
data: () => ({
content: <Button label={"Action"} onClick={() => openRightPane({ content: <DetailPane /> })} />,
css: Css.if(showColor).bgPurple500.$,
}),
clientSideSort: false,
w: "100px",
mw: "80px",
Expand Down
79 changes: 46 additions & 33 deletions src/components/Layout/GridTableLayout/GridTableLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { ReactNode, useEffect, useMemo, useState } from "react";
import { Button, ButtonProps } from "src/components/Button";
import { ButtonMenu, ButtonMenuProps } from "src/components/ButtonMenu";
import { FilterDropdownMenu } from "src/components/Filters/FilterDropdownMenu";
Expand All @@ -18,6 +18,7 @@ import { useDebounce } from "use-debounce";
import { StringParam, useQueryParams } from "use-query-params";
import { FullBleed } from "../FullBleed";
import { HeaderBreadcrumb, PageHeaderBreadcrumbs } from "../PageHeaderBreadcrumbs";
import { RightPaneLayout, RightPaneLayoutProps } from "../RightPaneLayout";
import { ScrollableContent } from "../ScrollableContent";
import { QueryResult, QueryTable, QueryTableProps } from "./QueryTable";

Expand Down Expand Up @@ -55,7 +56,7 @@ export type GridTableLayoutProps<
X extends Only<GridTableXss, X>,
QData,
> = {
pageTitle: string;
pageTitle: ReactNode;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw a use case for <AutosaveIndicator /> but didnt want to make a decorator or autosaveIndicator prop

tableProps: GridTablePropsWithRows<R, X> | QueryTablePropsWithQuery<R, X, QData>;
breadcrumb?: HeaderBreadcrumb | HeaderBreadcrumb[];
layoutState?: ReturnType<typeof useGridTableLayoutState<F>>;
Expand All @@ -66,6 +67,7 @@ export type GridTableLayoutProps<
tertiaryAction?: ActionButtonProps;
hideEditColumns?: boolean;
totalCount?: number;
rightPaneProps?: Omit<RightPaneLayoutProps, "children">;
};

/**
Expand Down Expand Up @@ -95,6 +97,8 @@ export type GridTableLayoutProps<
* ```
*
* Pagination is rendered when `totalCount` is provided. Use `layoutState.page` for server query variables.
*
* Right pane is built in
*/
function GridTableLayoutComponent<
F extends Record<string, unknown>,
Expand All @@ -113,6 +117,7 @@ function GridTableLayoutComponent<
actionMenu,
hideEditColumns = false,
totalCount,
rightPaneProps,
} = props;

const tid = useTestIds(props);
Expand All @@ -132,6 +137,7 @@ function GridTableLayoutComponent<
const clientSearch = layoutState?.search === "client" ? layoutState.searchString : undefined;
const showTableActions = layoutState?.filterDefs || layoutState?.search || hasHideableColumns;
const isVirtualized = tableProps.as === "virtual";
const showPagination = layoutState && totalCount !== undefined;

// Sync API changes back to persisted state when persistedColumns is provided
const visibleColumnIds = useComputed(() => api.getVisibleColumnIds(), [api]);
Expand Down Expand Up @@ -178,36 +184,43 @@ function GridTableLayoutComponent<
)}
</TableActions>
)}
<ScrollableContent virtualized={isVirtualized}>
{isGridTableProps(tableProps) ? (
<GridTable
{...tableProps}
api={api}
filter={clientSearch}
style={{ allWhite: true }}
stickyHeader
disableColumnResizing={false}
visibleColumnsStorageKey={visibleColumnsStorageKey}
/>
) : (
<QueryTable
{...(tableProps as QueryTableProps<R, QData, X>)}
api={api}
filter={clientSearch}
style={{ allWhite: true }}
stickyHeader
disableColumnResizing={false}
visibleColumnsStorageKey={visibleColumnsStorageKey}
/>
)}
{layoutState && totalCount !== undefined && (
<Pagination
page={[layoutState.page, layoutState._pagination.setPage]}
totalCount={totalCount}
pageSizes={layoutState._pagination.pageSizes}
{...tid.pagination}
/>
)}
{/* We omit padding here because ScrollableContent uses height to _simulate_ padding, not actually adding the style.
That extra height triggers a scrollbar w/in RightPaneLayout. We recreate the effect by adding padding to Pagination */}
<ScrollableContent omitBottomPadding={!showPagination ? true : undefined} virtualized={isVirtualized}>
<RightPaneLayout {...rightPaneProps}>
<>
{isGridTableProps(tableProps) ? (
<GridTable
{...tableProps}
api={api}
filter={clientSearch}
style={{ allWhite: true }}
stickyHeader
disableColumnResizing={false}
visibleColumnsStorageKey={visibleColumnsStorageKey}
/>
) : (
<QueryTable
{...(tableProps as QueryTableProps<R, QData, X>)}
api={api}
filter={clientSearch}
style={{ allWhite: true }}
stickyHeader
disableColumnResizing={false}
visibleColumnsStorageKey={visibleColumnsStorageKey}
/>
)}
{showPagination && (
<Pagination
paddingXss={Css.pb2.$}
page={[layoutState.page, layoutState._pagination.setPage]}
totalCount={totalCount}
pageSizes={layoutState._pagination.pageSizes}
{...tid.pagination}
/>
)}
</>
</RightPaneLayout>
</ScrollableContent>
</>
);
Expand Down Expand Up @@ -303,7 +316,7 @@ export function useGridTableLayoutState<F extends Record<string, unknown>>({
}

type HeaderProps = {
pageTitle: string;
pageTitle: ReactNode;
breadcrumb?: HeaderBreadcrumb | HeaderBreadcrumb[];
primaryAction?: ActionButtonProps;
secondaryAction?: ActionButtonProps;
Expand Down
8 changes: 5 additions & 3 deletions src/components/Layout/RightPaneLayout/RightPaneLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import { ReactElement, useEffect } from "react";
import { Css, Palette } from "src/Css";
import { useRightPaneContext } from "./RightPaneContext";

export function RightPaneLayout(props: {
export type RightPaneLayoutProps = {
children: ReactElement;
paneBgColor?: Palette;
paneWidth?: number;
defaultPaneContent?: ReactElement;
}) {
};

export function RightPaneLayout(props: RightPaneLayoutProps) {
const { children, paneBgColor = Palette.White, paneWidth = 450, defaultPaneContent } = props;
const { isRightPaneOpen, rightPaneContent, clearPane, closePane } = useRightPaneContext();

// Close pane on page unmount because otherwise the next page that has a right pane will show our stale content
useEffect(() => closePane, [closePane]);

return (
<div css={Css.h100.df.oxh.$}>
<div css={Css.df.h100.oxh.$}>
<>
<div
css={{
Expand Down
7 changes: 4 additions & 3 deletions src/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch } from "react";
import { IconButton } from "src/components";
import { Css, Palette } from "src/Css";
import { Css, Palette, Properties } from "src/Css";
import { SelectField } from "src/inputs";
import { useTestIds } from "src/utils";

Expand All @@ -21,10 +21,11 @@ interface PaginationProps {
page: readonly [PageNumberAndSize, Dispatch<PageNumberAndSize>] | readonly [OffsetAndLimit, Dispatch<OffsetAndLimit>];
totalCount: number;
pageSizes?: number[];
paddingXss?: Pick<Properties, "padding" | "paddingTop" | "paddingBottom" | "paddingLeft" | "paddingRight">;
}

export function Pagination(props: PaginationProps) {
const { totalCount, pageSizes = [100, 500, 1000] } = props;
const { totalCount, pageSizes = [100, 500, 1000], paddingXss } = props;
const [page, setPage] = props.page;
const { pageSize, pageNumber } = toPageNumberSize(page);
const pageOptions = pageSizes.map((size) => ({ id: size, name: String(size) }));
Expand All @@ -48,7 +49,7 @@ export function Pagination(props: PaginationProps) {

const tid = useTestIds(props, "pagination");
return (
<div css={Css.df.bcGray200.bt.xs.gray500.px2.pt2.$} {...tid}>
<div css={{ ...Css.df.bcGray200.bt.xs.gray500.px2.pt2.$, ...paddingXss }} {...tid}>
<div css={Css.df.mya.mr2.$} {...tid.pageSizeLabel}>
Page size:
</div>
Expand Down