Skip to content

Commit 5306413

Browse files
authored
Merge pull request #4262 from yancat160/feat/datagrid-view-toggle
feat: add a manual table / card view toggle to DataGrid
2 parents caa200d + be1161b commit 5306413

1 file changed

Lines changed: 67 additions & 22 deletions

File tree

src/components/common/DataGrid.tsx

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import type { UseQueryResult } from '@tanstack/react-query';
2-
import { Column, Row, SearchField } from '@umami/react-zen';
2+
import {
3+
Button,
4+
Column,
5+
Icon,
6+
Row,
7+
SearchField,
8+
Text,
9+
Tooltip,
10+
TooltipTrigger,
11+
} from '@umami/react-zen';
12+
import { LayoutGrid, Table2 } from 'lucide-react';
313
import {
414
cloneElement,
515
isValidElement,
@@ -12,9 +22,13 @@ import { Empty } from '@/components/common/Empty';
1222
import { LoadingPanel } from '@/components/common/LoadingPanel';
1323
import { Pager } from '@/components/common/Pager';
1424
import { useMessages, useMobile, useNavigation } from '@/components/hooks';
25+
import { getItem, setItem } from '@/lib/storage';
1526
import type { PageResult } from '@/lib/types';
1627

1728
const DEFAULT_SEARCH_DELAY = 600;
29+
const DISPLAY_MODE_STORAGE_KEY = 'umami.datagrid.displayMode';
30+
31+
type DisplayMode = 'table' | 'cards';
1832

1933
export interface DataGridProps {
2034
query: UseQueryResult<PageResult<any>, any>;
@@ -43,7 +57,23 @@ export function DataGrid({
4357
const [search, setSearch] = useState(queryParams?.search || data?.search || '');
4458
const showPager = allowPaging && data && data.count > 0;
4559
const { isMobile } = useMobile();
46-
const displayMode = isMobile ? 'cards' : undefined;
60+
const [userDisplayMode, setUserDisplayMode] = useState<DisplayMode | null>(() => {
61+
// localStorage can hold anything (extensions, manual edits, schema drift),
62+
// so accept only the two values we know how to render and otherwise fall
63+
// back to the useMobile-driven default.
64+
const stored = getItem(DISPLAY_MODE_STORAGE_KEY);
65+
return stored === 'table' || stored === 'cards' ? stored : null;
66+
});
67+
68+
// Effective mode: explicit user choice wins, otherwise fall back to the
69+
// mobile-driven default (cards on small viewports, table elsewhere).
70+
const displayMode: DisplayMode | undefined = userDisplayMode ?? (isMobile ? 'cards' : undefined);
71+
72+
const handleToggleDisplayMode = () => {
73+
const next: DisplayMode = displayMode === 'cards' ? 'table' : 'cards';
74+
setItem(DISPLAY_MODE_STORAGE_KEY, next);
75+
setUserDisplayMode(next);
76+
};
4777

4878
const handleSearch = (value: string) => {
4979
if (value !== search) {
@@ -61,39 +91,54 @@ export function DataGrid({
6191

6292
const child = data ? (typeof children === 'function' ? children(data) : children) : null;
6393

94+
const viewToggleButton = (
95+
<TooltipTrigger delay={0}>
96+
<Button variant="zero" onPress={handleToggleDisplayMode}>
97+
<Icon>{displayMode === 'cards' ? <Table2 /> : <LayoutGrid />}</Icon>
98+
</Button>
99+
<Tooltip>
100+
<Text>{displayMode === 'cards' ? 'Switch to table view' : 'Switch to card view'}</Text>
101+
</Tooltip>
102+
</TooltipTrigger>
103+
);
104+
64105
return (
65-
<Column gap="4" minHeight="300px" justifyContent="space-between">
66-
<Column gap="4">
106+
<Column gap="4" minHeight="300px">
107+
<Row alignItems="center" wrap="wrap" gap>
67108
{allowSearch && (
68-
<Row alignItems="center" justifyContent="space-between" wrap="wrap" gap>
69-
<SearchField
70-
value={search}
71-
onSearch={handleSearch}
72-
delay={searchDelay || DEFAULT_SEARCH_DELAY}
73-
autoFocus={autoFocus}
74-
placeholder={t(labels.search)}
75-
/>
76-
{renderActions?.()}
77-
</Row>
109+
<SearchField
110+
value={search}
111+
onSearch={handleSearch}
112+
delay={searchDelay || DEFAULT_SEARCH_DELAY}
113+
autoFocus={autoFocus}
114+
placeholder={t(labels.search)}
115+
/>
78116
)}
79-
<LoadingPanel
80-
data={data?.data}
81-
isLoading={isLoading}
82-
isFetching={isFetching}
83-
error={error}
84-
renderEmpty={renderEmpty}
85-
>
86-
{data && (
117+
<Row alignItems="center" gap style={{ marginLeft: 'auto' }}>
118+
{renderActions?.()}
119+
{viewToggleButton}
120+
</Row>
121+
</Row>
122+
<LoadingPanel
123+
data={data?.data}
124+
isLoading={isLoading}
125+
isFetching={isFetching}
126+
error={error}
127+
renderEmpty={renderEmpty}
128+
>
129+
{data && (
87130
<div
88131
style={{
89132
display: 'grid',
90133
gridTemplateColumns: 'minmax(0, 1fr)',
91134
overflowX: 'auto',
92135
}}
93136
>
137+
<Column>
94138
{isValidElement(child)
95139
? cloneElement(child as ReactElement<any>, { displayMode })
96140
: child}
141+
</Column>
97142
</div>
98143
)}
99144
</LoadingPanel>

0 commit comments

Comments
 (0)