@designa8/vibedesign-stripe-deepseek-2-176b 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/styles.css +27 -6
- package/package.json +1 -1
- package/src/atoms/DataTable/DataTable.tsx +200 -49
- package/src/atoms/DataTable/index.ts +12 -0
- package/src/atoms/DataTable/preview.tsx +223 -12
- package/src/atoms/DataTableDesktop/DataTableDesktop.tsx +809 -500
- package/src/atoms/DataTableDesktop/index.ts +14 -1
- package/src/atoms/DataTableDesktop/preview.tsx +193 -8
- package/src/atoms/DataTableMobile/DataTableMobile.tsx +470 -204
- package/src/atoms/DataTableMobile/index.ts +13 -2
- package/src/atoms/DataTableMobile/preview.tsx +101 -10
- package/src/atoms/Sidebar/Sidebar.tsx +48 -13
- package/src/atoms/Sidebar/preview.tsx +154 -14
- package/src/atoms/SidebarDesktop/SidebarDesktop.tsx +327 -201
- package/src/atoms/SidebarMobile/SidebarMobile.tsx +281 -145
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @designa8/vibedesign-stripe-deepseek-2-176b
|
|
2
2
|
|
|
3
|
-
Complete design system generated by Atoms Design, version **0.1.
|
|
3
|
+
Complete design system generated by Atoms Design, version **0.1.2**.
|
|
4
4
|
Tokens, atoms, molecules, organisms, and templates — import from the package root;
|
|
5
5
|
never hardcode visual values in consumer code.
|
|
6
6
|
|
package/dist/styles.css
CHANGED
|
@@ -387,6 +387,9 @@
|
|
|
387
387
|
.-top-xs {
|
|
388
388
|
top: calc(var(--spacing-xs) * -1);
|
|
389
389
|
}
|
|
390
|
+
.-top-xxs {
|
|
391
|
+
top: calc(var(--spacing-xxs) * -1);
|
|
392
|
+
}
|
|
390
393
|
.top-0 {
|
|
391
394
|
top: 0;
|
|
392
395
|
}
|
|
@@ -408,6 +411,9 @@
|
|
|
408
411
|
.-right-xs {
|
|
409
412
|
right: calc(var(--spacing-xs) * -1);
|
|
410
413
|
}
|
|
414
|
+
.-right-xxs {
|
|
415
|
+
right: calc(var(--spacing-xxs) * -1);
|
|
416
|
+
}
|
|
411
417
|
.right-0 {
|
|
412
418
|
right: 0;
|
|
413
419
|
}
|
|
@@ -929,9 +935,6 @@
|
|
|
929
935
|
.max-w-dialog {
|
|
930
936
|
max-width: var(--container-dialog);
|
|
931
937
|
}
|
|
932
|
-
.max-w-drawer {
|
|
933
|
-
max-width: var(--container-drawer);
|
|
934
|
-
}
|
|
935
938
|
.max-w-full {
|
|
936
939
|
max-width: 100%;
|
|
937
940
|
}
|
|
@@ -953,6 +956,9 @@
|
|
|
953
956
|
.min-w-0 {
|
|
954
957
|
min-width: 0;
|
|
955
958
|
}
|
|
959
|
+
.min-w-4 {
|
|
960
|
+
min-width: calc(var(--spacing) * 4);
|
|
961
|
+
}
|
|
956
962
|
.min-w-xl {
|
|
957
963
|
min-width: var(--spacing-xl);
|
|
958
964
|
}
|
|
@@ -1764,6 +1770,9 @@
|
|
|
1764
1770
|
.px-xxl {
|
|
1765
1771
|
padding-inline: var(--spacing-xxl);
|
|
1766
1772
|
}
|
|
1773
|
+
.px-xxs {
|
|
1774
|
+
padding-inline: var(--spacing-xxs);
|
|
1775
|
+
}
|
|
1767
1776
|
.py-0 {
|
|
1768
1777
|
padding-block: 0;
|
|
1769
1778
|
}
|
|
@@ -1830,9 +1839,6 @@
|
|
|
1830
1839
|
.pr-md {
|
|
1831
1840
|
padding-right: var(--spacing-md);
|
|
1832
1841
|
}
|
|
1833
|
-
.pr-sm {
|
|
1834
|
-
padding-right: var(--spacing-sm);
|
|
1835
|
-
}
|
|
1836
1842
|
.pr-xl {
|
|
1837
1843
|
padding-right: var(--spacing-xl);
|
|
1838
1844
|
}
|
|
@@ -3337,6 +3343,16 @@
|
|
|
3337
3343
|
padding-left: var(--spacing-xl);
|
|
3338
3344
|
}
|
|
3339
3345
|
}
|
|
3346
|
+
.md\:sticky {
|
|
3347
|
+
@media (width >= 768px) {
|
|
3348
|
+
position: sticky;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
.md\:top-0 {
|
|
3352
|
+
@media (width >= 768px) {
|
|
3353
|
+
top: 0;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3340
3356
|
.md\:col-span-1 {
|
|
3341
3357
|
@media (width >= 768px) {
|
|
3342
3358
|
grid-column: span 1 / span 1;
|
|
@@ -3377,6 +3393,11 @@
|
|
|
3377
3393
|
display: none;
|
|
3378
3394
|
}
|
|
3379
3395
|
}
|
|
3396
|
+
.md\:h-dvh {
|
|
3397
|
+
@media (width >= 768px) {
|
|
3398
|
+
height: 100dvh;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3380
3401
|
.md\:w-40 {
|
|
3381
3402
|
@media (width >= 768px) {
|
|
3382
3403
|
width: calc(var(--spacing) * 40);
|
package/package.json
CHANGED
|
@@ -1,77 +1,228 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { DataTableDesktop } from "../DataTableDesktop";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
DataTableBaseRow,
|
|
5
|
+
DataTableColumn,
|
|
6
|
+
DataTableDesktopProps,
|
|
7
|
+
DataTableFilter,
|
|
8
|
+
DataTableRange,
|
|
9
|
+
} from "../DataTableDesktop";
|
|
4
10
|
import { DataTableMobile } from "../DataTableMobile";
|
|
5
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
DataTableMobileRowSpec,
|
|
13
|
+
DataTableMobileProps,
|
|
14
|
+
} from "../DataTableMobile";
|
|
6
15
|
|
|
7
16
|
const cn = (...values: Array<string | false | null | undefined>) =>
|
|
8
17
|
values.filter(Boolean).join(" ");
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The generic table contract. `columns` drives the wide layout and `mobileRow`
|
|
21
|
+
* drives the stacked one; everything else is shared, so both halves always show
|
|
22
|
+
* the same filtered, searched, sorted, selected and paginated slice.
|
|
23
|
+
*/
|
|
24
|
+
export interface DataTableProps<T extends DataTableBaseRow = DataTableBaseRow>
|
|
11
25
|
extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "title"> {
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
/** Column definitions for the wide layout. */
|
|
27
|
+
columns?: Array<DataTableColumn<T>>;
|
|
28
|
+
/** Rows to render, in both layouts. */
|
|
29
|
+
rows?: T[];
|
|
30
|
+
/** Stable row identity, defaults to `row.id`. */
|
|
31
|
+
getRowId?: (row: T) => string;
|
|
32
|
+
/** How one row collapses into the stacked mobile line. */
|
|
33
|
+
mobileRow?: DataTableMobileRowSpec<T>;
|
|
34
|
+
/** Filter chips shown above the table. */
|
|
35
|
+
filters?: DataTableFilter[];
|
|
36
|
+
filter?: string;
|
|
37
|
+
defaultFilter?: string;
|
|
38
|
+
onFilterChange?: (value: string) => void;
|
|
39
|
+
filterBy?: (row: T, value: string) => boolean;
|
|
40
|
+
searchPlaceholder?: string;
|
|
41
|
+
query?: string;
|
|
42
|
+
onQueryChange?: (value: string) => void;
|
|
43
|
+
searchBy?: (row: T) => string;
|
|
44
|
+
/** Date-range presets. The trigger is hidden when omitted. */
|
|
45
|
+
ranges?: DataTableRange[];
|
|
46
|
+
range?: string;
|
|
47
|
+
onRangeChange?: (value: string) => void;
|
|
48
|
+
/** Replaces the export / contact-sales pair. */
|
|
49
|
+
toolbar?: React.ReactNode;
|
|
50
|
+
onRowClick?: (row: T) => void;
|
|
51
|
+
selectable?: boolean;
|
|
52
|
+
selectedIds?: string[];
|
|
53
|
+
onSelectionChange?: (ids: string[]) => void;
|
|
54
|
+
bulkActions?: (ids: string[]) => React.ReactNode;
|
|
24
55
|
/** Rows per page in the wide layout. */
|
|
25
56
|
pageSize?: number;
|
|
26
57
|
/** Rows per page in the narrow layout. */
|
|
27
58
|
mobilePageSize?: number;
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
totalCount?: number;
|
|
60
|
+
itemLabel?: string;
|
|
61
|
+
emptyTitle?: React.ReactNode;
|
|
62
|
+
emptyDescription?: React.ReactNode;
|
|
63
|
+
emptyAction?: React.ReactNode;
|
|
64
|
+
dense?: boolean;
|
|
65
|
+
title?: React.ReactNode;
|
|
66
|
+
description?: React.ReactNode;
|
|
67
|
+
|
|
68
|
+
/** @deprecated legacy alias for `rows` on the wide layout. */
|
|
69
|
+
desktopRows?: DataTableDesktopProps["rows"];
|
|
70
|
+
/** @deprecated legacy alias for `rows` on the narrow layout. */
|
|
71
|
+
mobileRows?: DataTableMobileProps["rows"];
|
|
30
72
|
}
|
|
31
73
|
|
|
32
74
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* breakpoint (768px) — `hidden md:block` around the desktop layout, `md:hidden`
|
|
38
|
-
* around the mobile one. No viewport measurement, no hydration mismatch, and no
|
|
39
|
-
* layout reimplementation: each half keeps its own toolbar, rows, and pager.
|
|
75
|
+
* One responsive table: both halves stay mounted and only their visibility
|
|
76
|
+
* flips at this system's `md` breakpoint (768px) — `hidden md:block` around the
|
|
77
|
+
* wide layout, `md:hidden` around the stacked one. No viewport measurement and
|
|
78
|
+
* no hydration mismatch.
|
|
40
79
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
80
|
+
* The wrapper owns the shared state — filter, query, range, selection and page
|
|
81
|
+
* — so resizing never loses what the reader was looking at, and a hidden half
|
|
82
|
+
* never paginates or filters differently from the visible one.
|
|
43
83
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
function DataTableInner<T extends DataTableBaseRow>(
|
|
85
|
+
props: DataTableProps<T>,
|
|
86
|
+
ref: React.Ref<HTMLDivElement>,
|
|
87
|
+
) {
|
|
88
|
+
const {
|
|
89
|
+
columns,
|
|
90
|
+
rows,
|
|
91
|
+
getRowId,
|
|
92
|
+
mobileRow,
|
|
93
|
+
filters,
|
|
94
|
+
filter,
|
|
95
|
+
defaultFilter,
|
|
96
|
+
onFilterChange,
|
|
97
|
+
filterBy,
|
|
98
|
+
searchPlaceholder,
|
|
99
|
+
query,
|
|
100
|
+
onQueryChange,
|
|
101
|
+
searchBy,
|
|
102
|
+
ranges,
|
|
103
|
+
range,
|
|
104
|
+
onRangeChange,
|
|
105
|
+
toolbar,
|
|
106
|
+
onRowClick,
|
|
107
|
+
selectable,
|
|
108
|
+
selectedIds,
|
|
109
|
+
onSelectionChange,
|
|
110
|
+
bulkActions,
|
|
111
|
+
pageSize,
|
|
112
|
+
mobilePageSize,
|
|
113
|
+
totalCount,
|
|
114
|
+
itemLabel,
|
|
115
|
+
emptyTitle,
|
|
116
|
+
emptyDescription,
|
|
117
|
+
emptyAction,
|
|
118
|
+
dense,
|
|
119
|
+
title,
|
|
120
|
+
description,
|
|
121
|
+
desktopRows,
|
|
122
|
+
mobileRows,
|
|
123
|
+
className,
|
|
124
|
+
...rest
|
|
125
|
+
} = props;
|
|
126
|
+
|
|
127
|
+
const legacyRows = rows === undefined && desktopRows !== undefined;
|
|
128
|
+
|
|
129
|
+
const [innerQuery, setInnerQuery] = React.useState("");
|
|
130
|
+
const activeQuery = query ?? innerQuery;
|
|
131
|
+
|
|
132
|
+
const [innerFilter, setInnerFilter] = React.useState(
|
|
133
|
+
defaultFilter ?? filters?.[0]?.value ?? "all",
|
|
134
|
+
);
|
|
135
|
+
const activeFilter = filter ?? innerFilter;
|
|
136
|
+
|
|
137
|
+
const [innerRange, setInnerRange] = React.useState(ranges?.[0]?.value ?? "");
|
|
138
|
+
const activeRangeValue = range ?? innerRange;
|
|
139
|
+
|
|
140
|
+
const [innerSelected, setInnerSelected] = React.useState<string[]>([]);
|
|
141
|
+
const activeSelected = selectedIds ?? innerSelected;
|
|
142
|
+
|
|
143
|
+
const handleQuery = (next: string) => {
|
|
144
|
+
if (query === undefined) setInnerQuery(next);
|
|
145
|
+
onQueryChange?.(next);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleFilter = (next: string) => {
|
|
149
|
+
if (filter === undefined) setInnerFilter(next);
|
|
150
|
+
onFilterChange?.(next);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleRange = (next: string) => {
|
|
154
|
+
if (range === undefined) setInnerRange(next);
|
|
155
|
+
onRangeChange?.(next);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const handleSelection = (next: string[]) => {
|
|
159
|
+
if (selectedIds === undefined) setInnerSelected(next);
|
|
160
|
+
onSelectionChange?.(next);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const shared = {
|
|
164
|
+
getRowId,
|
|
165
|
+
filters,
|
|
166
|
+
filter: activeFilter,
|
|
167
|
+
onFilterChange: handleFilter,
|
|
168
|
+
filterBy,
|
|
169
|
+
searchPlaceholder,
|
|
170
|
+
query: activeQuery,
|
|
171
|
+
onQueryChange: handleQuery,
|
|
172
|
+
searchBy,
|
|
173
|
+
ranges,
|
|
174
|
+
range: activeRangeValue,
|
|
175
|
+
onRangeChange: handleRange,
|
|
176
|
+
toolbar,
|
|
177
|
+
onRowClick,
|
|
178
|
+
selectable,
|
|
179
|
+
selectedIds: activeSelected,
|
|
180
|
+
onSelectionChange: handleSelection,
|
|
181
|
+
bulkActions,
|
|
182
|
+
totalCount,
|
|
183
|
+
itemLabel,
|
|
184
|
+
emptyTitle,
|
|
185
|
+
emptyDescription,
|
|
186
|
+
emptyAction,
|
|
187
|
+
dense,
|
|
188
|
+
title,
|
|
189
|
+
description,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const desktopRowData = legacyRows
|
|
193
|
+
? desktopRows
|
|
194
|
+
: (rows ?? (desktopRows as T[] | undefined));
|
|
195
|
+
|
|
196
|
+
const mobileRowData = rows ?? (mobileRows as T[] | undefined);
|
|
197
|
+
|
|
198
|
+
return (
|
|
58
199
|
<div ref={ref} className={cn("w-full", className)} {...rest}>
|
|
59
200
|
<div className="hidden w-full md:block">
|
|
60
|
-
<DataTableDesktop
|
|
61
|
-
|
|
62
|
-
|
|
201
|
+
<DataTableDesktop<T>
|
|
202
|
+
{...shared}
|
|
203
|
+
columns={columns}
|
|
204
|
+
rows={desktopRowData as T[]}
|
|
63
205
|
pageSize={pageSize}
|
|
64
206
|
/>
|
|
65
207
|
</div>
|
|
66
208
|
<div className="w-full md:hidden">
|
|
67
|
-
<DataTableMobile
|
|
68
|
-
|
|
209
|
+
<DataTableMobile<T>
|
|
210
|
+
{...shared}
|
|
211
|
+
mobileRow={mobileRow}
|
|
212
|
+
rows={mobileRowData as T[]}
|
|
69
213
|
pageSize={mobilePageSize}
|
|
70
|
-
searchPlaceholder={searchPlaceholder}
|
|
71
214
|
/>
|
|
72
215
|
</div>
|
|
73
216
|
</div>
|
|
74
|
-
)
|
|
75
|
-
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
type DataTableComponent = <T extends DataTableBaseRow = DataTableBaseRow>(
|
|
221
|
+
props: DataTableProps<T> & React.RefAttributes<HTMLDivElement>,
|
|
222
|
+
) => React.ReactElement | null;
|
|
223
|
+
|
|
224
|
+
export const DataTable = React.forwardRef(
|
|
225
|
+
DataTableInner,
|
|
226
|
+
) as unknown as DataTableComponent;
|
|
76
227
|
|
|
77
|
-
DataTable.displayName = "DataTable";
|
|
228
|
+
(DataTable as { displayName?: string }).displayName = "DataTable";
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
export { DataTable } from "./DataTable";
|
|
2
2
|
export type { DataTableProps } from "./DataTable";
|
|
3
|
+
export type {
|
|
4
|
+
DataTableAlign,
|
|
5
|
+
DataTableBaseRow,
|
|
6
|
+
DataTableColumn,
|
|
7
|
+
DataTableFilter,
|
|
8
|
+
DataTableRange,
|
|
9
|
+
} from "../DataTableDesktop";
|
|
10
|
+
export type {
|
|
11
|
+
DataTableMobileFilter,
|
|
12
|
+
DataTableMobileRange,
|
|
13
|
+
DataTableMobileRowSpec,
|
|
14
|
+
} from "../DataTableMobile";
|
|
@@ -1,15 +1,226 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { Badge } from "../Badge";
|
|
3
|
+
import { Button } from "../Button";
|
|
2
4
|
import { DataTable } from "./DataTable";
|
|
5
|
+
import type { DataTableColumn, DataTableFilter, DataTableRange } from "./DataTable";
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
interface Invoice {
|
|
8
|
+
id: string;
|
|
9
|
+
customer: string;
|
|
10
|
+
status: "paid" | "open" | "failed";
|
|
11
|
+
amount: number;
|
|
12
|
+
issued: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const STATUS_LABEL: Record<Invoice["status"], string> = {
|
|
16
|
+
paid: "Paid",
|
|
17
|
+
open: "Open",
|
|
18
|
+
failed: "Failed",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const CUSTOMERS = [
|
|
22
|
+
"Northwind Co",
|
|
23
|
+
"Acme Shop",
|
|
24
|
+
"Acme Market",
|
|
25
|
+
"Lumen Studio",
|
|
26
|
+
"Harbor Freight",
|
|
27
|
+
"Tidewater Labs",
|
|
28
|
+
"Rookery Press",
|
|
29
|
+
"Fielding Group",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const invoices: Invoice[] = Array.from({ length: 160 }, (_, index) => {
|
|
33
|
+
const status: Invoice["status"] =
|
|
34
|
+
index % 9 === 0 ? "failed" : index % 4 === 0 ? "open" : "paid";
|
|
35
|
+
return {
|
|
36
|
+
id: `inv_${(10480 + index).toString(36).toUpperCase()}`,
|
|
37
|
+
customer: CUSTOMERS[index % CUSTOMERS.length],
|
|
38
|
+
status,
|
|
39
|
+
amount: 120 + ((index * 137) % 8600),
|
|
40
|
+
issued: `Feb ${((index % 28) + 1).toString().padStart(2, "0")}, 2026`,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const filters: DataTableFilter[] = [
|
|
45
|
+
{ value: "all", label: "All invoices", count: 160 },
|
|
46
|
+
{ value: "paid", label: "Paid", count: 118 },
|
|
47
|
+
{ value: "open", label: "Open", count: 32 },
|
|
48
|
+
{ value: "failed", label: "Failed", count: 10 },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const ranges: DataTableRange[] = [
|
|
52
|
+
{ value: "30", label: "Last 30 days", detail: "Jan 26 – Feb 24, 2026" },
|
|
53
|
+
{ value: "90", label: "Last 90 days", detail: "Nov 26 – Feb 24, 2026" },
|
|
54
|
+
{ value: "ytd", label: "Year to date", detail: "Jan 1 – Feb 24, 2026" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const columns: Array<DataTableColumn<Invoice>> = [
|
|
58
|
+
{
|
|
59
|
+
id: "id",
|
|
60
|
+
header: "Invoice",
|
|
61
|
+
width: "min",
|
|
62
|
+
render: (row) => (
|
|
63
|
+
<span className="whitespace-nowrap text-body-sm text-ink">{row.id}</span>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "customer",
|
|
68
|
+
header: "Customer",
|
|
69
|
+
width: "min",
|
|
70
|
+
render: (row) => (
|
|
71
|
+
<span className="whitespace-nowrap text-body-sm text-ink">
|
|
72
|
+
{row.customer}
|
|
73
|
+
</span>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "status",
|
|
78
|
+
header: "Status",
|
|
79
|
+
width: "min",
|
|
80
|
+
render: (row) =>
|
|
81
|
+
row.status === "paid" ? (
|
|
82
|
+
<span className="inline-flex items-center gap-xs whitespace-nowrap text-body-sm text-ink">
|
|
83
|
+
<span
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
className="size-2 shrink-0 rounded-full bg-primary"
|
|
86
|
+
/>
|
|
87
|
+
{STATUS_LABEL[row.status]}
|
|
88
|
+
</span>
|
|
89
|
+
) : (
|
|
90
|
+
<Badge
|
|
91
|
+
variant="soft"
|
|
92
|
+
tone={row.status === "failed" ? "error" : "info"}
|
|
93
|
+
size="sm"
|
|
94
|
+
>
|
|
95
|
+
{STATUS_LABEL[row.status]}
|
|
96
|
+
</Badge>
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "amount",
|
|
101
|
+
header: "Amount",
|
|
102
|
+
align: "right",
|
|
103
|
+
width: "min",
|
|
104
|
+
sortable: true,
|
|
105
|
+
sortValue: (row) => row.amount,
|
|
106
|
+
render: (row) => (
|
|
107
|
+
<span className="whitespace-nowrap text-body-tabular text-ink">
|
|
108
|
+
{`$${row.amount.toLocaleString("en-US", {
|
|
109
|
+
minimumFractionDigits: 2,
|
|
110
|
+
maximumFractionDigits: 2,
|
|
111
|
+
})}`}
|
|
112
|
+
</span>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "issued",
|
|
117
|
+
header: "Issued",
|
|
118
|
+
width: "min",
|
|
119
|
+
render: (row) => (
|
|
120
|
+
<span className="whitespace-nowrap text-body-sm text-ink">
|
|
121
|
+
{row.issued}
|
|
122
|
+
</span>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const mobileRow = {
|
|
128
|
+
title: (row: Invoice) => row.id,
|
|
129
|
+
subtitle: (row: Invoice) => (
|
|
130
|
+
<Badge
|
|
131
|
+
variant="soft"
|
|
132
|
+
tone={row.status === "failed" ? "error" : "info"}
|
|
133
|
+
size="sm"
|
|
134
|
+
showDot
|
|
135
|
+
>
|
|
136
|
+
{STATUS_LABEL[row.status]}
|
|
137
|
+
</Badge>
|
|
138
|
+
),
|
|
139
|
+
trailing: (row: Invoice) =>
|
|
140
|
+
`$${row.amount.toLocaleString("en-US", {
|
|
141
|
+
minimumFractionDigits: 2,
|
|
142
|
+
maximumFractionDigits: 2,
|
|
143
|
+
})}`,
|
|
144
|
+
meta: (row: Invoice) => `${row.customer} · ${row.issued}`,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default function Preview() {
|
|
148
|
+
const [selected, setSelected] = React.useState<string[]>([]);
|
|
149
|
+
const [filter, setFilter] = React.useState("all");
|
|
150
|
+
const [query, setQuery] = React.useState("");
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className="w-full max-w-5xl mx-auto p-lg space-y-lg bg-canvas">
|
|
154
|
+
<DataTable<Invoice>
|
|
155
|
+
columns={columns}
|
|
156
|
+
rows={invoices}
|
|
157
|
+
filters={filters}
|
|
158
|
+
filter={filter}
|
|
159
|
+
onFilterChange={setFilter}
|
|
160
|
+
filterBy={(row, value) => row.status === value}
|
|
161
|
+
searchPlaceholder="Search invoices"
|
|
162
|
+
query={query}
|
|
163
|
+
onQueryChange={setQuery}
|
|
164
|
+
searchBy={(row) => `${row.id} ${row.customer}`}
|
|
165
|
+
ranges={ranges}
|
|
166
|
+
selectedIds={selected}
|
|
167
|
+
onSelectionChange={setSelected}
|
|
168
|
+
pageSize={10}
|
|
169
|
+
mobilePageSize={4}
|
|
170
|
+
itemLabel="invoices"
|
|
171
|
+
dense
|
|
172
|
+
title="Invoices"
|
|
173
|
+
description="Every invoice issued this quarter, newest first."
|
|
174
|
+
toolbar={
|
|
175
|
+
<>
|
|
176
|
+
<Button variant="outline-soft" size="md">
|
|
177
|
+
Export
|
|
178
|
+
</Button>
|
|
179
|
+
<Button variant="primary" size="md">
|
|
180
|
+
New invoice
|
|
181
|
+
</Button>
|
|
182
|
+
</>
|
|
183
|
+
}
|
|
184
|
+
bulkActions={(ids) => (
|
|
185
|
+
<>
|
|
186
|
+
<Button variant="outline-soft" size="md">
|
|
187
|
+
{`Download ${ids.length}`}
|
|
188
|
+
</Button>
|
|
189
|
+
<Button variant="outline-soft" size="md">
|
|
190
|
+
Archive
|
|
191
|
+
</Button>
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
194
|
+
onRowClick={() => undefined}
|
|
195
|
+
mobileRow={mobileRow}
|
|
196
|
+
emptyTitle="No invoices match those filters"
|
|
197
|
+
emptyDescription="Try a different status or clear the search field."
|
|
198
|
+
emptyAction={
|
|
199
|
+
<Button
|
|
200
|
+
variant="outline-soft"
|
|
201
|
+
size="md"
|
|
202
|
+
onClick={() => {
|
|
203
|
+
setFilter("all");
|
|
204
|
+
setQuery("");
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
Clear filters
|
|
208
|
+
</Button>
|
|
209
|
+
}
|
|
210
|
+
/>
|
|
211
|
+
|
|
212
|
+
<DataTable<Invoice>
|
|
213
|
+
columns={columns}
|
|
214
|
+
rows={invoices.slice(0, 4)}
|
|
215
|
+
filters={filters.slice(0, 1)}
|
|
216
|
+
pageSize={5}
|
|
217
|
+
mobilePageSize={4}
|
|
218
|
+
itemLabel="invoices"
|
|
219
|
+
dense
|
|
220
|
+
title="Compact, no selection"
|
|
221
|
+
selectable={false}
|
|
222
|
+
mobileRow={mobileRow}
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|