@dust-tt/sparkle 0.2.196 → 0.2.197

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dust-tt/sparkle",
3
- "version": "0.2.196",
3
+ "version": "0.2.197",
4
4
  "scripts": {
5
5
  "build": "rm -rf dist && rollup -c",
6
6
  "build:with-tw-base": "rollup -c --environment INCLUDE_TW_BASE:true",
@@ -81,6 +81,7 @@
81
81
  "@emoji-mart/data": "^1.1.2",
82
82
  "@emoji-mart/react": "^1.1.1",
83
83
  "@headlessui/react": "^1.7.19",
84
+ "@tanstack/react-table": "^8.13.0",
84
85
  "emoji-mart": "^5.5.2",
85
86
  "esbuild": "^0.20.0",
86
87
  "eslint-plugin-import": "^2.29.1",
@@ -1,108 +1,270 @@
1
- import React, { ReactNode } from "react";
1
+ import {
2
+ type ColumnDef,
3
+ ColumnFiltersState,
4
+ flexRender,
5
+ getCoreRowModel,
6
+ getFilteredRowModel,
7
+ getSortedRowModel,
8
+ type SortingState,
9
+ useReactTable,
10
+ } from "@tanstack/react-table";
11
+ import React, { ReactNode, useEffect, useState } from "react";
2
12
 
3
- import { Page } from "@sparkle/index";
4
- import { ClipboardCheckIcon, ClipboardIcon, IconButton } from "@sparkle/index";
5
- import { useCopyToClipboard } from "@sparkle/lib/utils";
13
+ import { Avatar, MoreIcon } from "@sparkle/index";
14
+ import { ArrowDownIcon, ArrowUpIcon } from "@sparkle/index";
15
+ import { classNames } from "@sparkle/lib/utils";
6
16
 
7
- type SupportedRowType = string | number;
17
+ import { Icon } from "./Icon";
8
18
 
9
- interface DataTableProps {
10
- name?: string;
11
- columns: string[];
12
- rows: SupportedRowType[][];
13
- enableCopy?: boolean;
14
- showLimit?: number;
15
- downloadButton?: ReactNode;
19
+ interface DataTableProps<TData, TValue> {
20
+ data: TData[];
21
+ columns: ColumnDef<TData, TValue>[];
22
+ className?: string;
23
+ filter?: string;
24
+ filterColumn?: string;
16
25
  }
17
26
 
18
- export const DataTable = React.forwardRef<HTMLTableElement, DataTableProps>(
19
- (
20
- {
21
- name,
22
- columns,
23
- rows,
24
- enableCopy,
25
- showLimit,
26
- downloadButton,
27
- }: DataTableProps,
28
- ref
29
- ) => {
30
- const [isCopied, copyToClipboard] = useCopyToClipboard();
31
-
32
- const handleCopyTable = async () => {
33
- const columnHeaders = columns.join(",");
34
- const data = rows.map((row) => row.join(",")).join("\n");
35
- const clipboardText = `${columnHeaders}\n${data}`;
36
-
37
- if (copyToClipboard) {
38
- await copyToClipboard(
39
- new ClipboardItem({
40
- "text/plain": new Blob([clipboardText], {
41
- type: "text/plain",
42
- }),
43
- })
44
- );
45
- }
46
- };
47
-
48
- const maxRows = showLimit ?? -1;
49
-
50
- return (
51
- <div className="s-mt-2 s-flex s-flex-col s-gap-2">
52
- <div className="s-flex s-w-full s-items-center s-justify-between s-gap-1">
53
- {name && <Page.H variant="h6">{name}</Page.H>}
54
- {downloadButton}
55
- </div>
56
- <div className="s-relative">
57
- <div className="relative overflow-x-auto s-dark:border-structure-200-dark s-w-auto s-rounded-lg s-border s-border-structure-200">
58
- <table
59
- ref={ref}
60
- className="s-w-full s-table-auto s-divide-y s-divide-structure-200"
61
- >
62
- <thead className="s-dark:bg-structure-50-dark s-bg-structure-50 s-px-2 s-py-2">
63
- <tr>
64
- <th className="s-px-4"></th>
65
- {columns.map((column, index) => (
66
- <th
67
- className="s-dark:text-element-700-dark s-whitespace-nowrap s-px-6 s-py-2 s-text-left s-text-xs s-font-semibold s-uppercase s-tracking-wider s-text-element-700"
68
- key={index}
69
- >
70
- {column}
71
- </th>
72
- ))}
73
- </tr>
74
- </thead>
75
- <tbody className="s-dark:divide-structure-600 s-divide-y s-divide-structure-100 s-bg-white">
76
- {rows.slice(0, maxRows).map((row, index) => (
77
- <tr key={index}>
78
- <td className="s-dark:text-element-800-dark s-px-4 s-text-sm s-text-element-600">
79
- {index + 1}
80
- </td>
81
- {row.map((cell, cellIndex) => (
82
- <td
83
- className="s-dark:text-element-800-dark s-px-6 s-py-3 s-text-sm s-text-element-800"
84
- key={cellIndex}
85
- >
86
- {cell}
87
- </td>
88
- ))}
89
- </tr>
90
- ))}
91
- </tbody>
92
- </table>
93
- {enableCopy && (
94
- <div className="s-absolute s-right-2 s-top-2 s-mx-2 s-rounded-xl s-bg-structure-50">
95
- <IconButton
96
- variant="tertiary"
97
- size="xs"
98
- icon={isCopied ? ClipboardCheckIcon : ClipboardIcon}
99
- onClick={handleCopyTable}
100
- />
101
- </div>
102
- )}
103
- </div>
27
+ export function DataTable<TData, TValue>({
28
+ data,
29
+ columns,
30
+ className,
31
+ filter,
32
+ filterColumn,
33
+ }: DataTableProps<TData, TValue>) {
34
+ const [sorting, setSorting] = useState<SortingState>([]);
35
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
36
+
37
+ const table = useReactTable({
38
+ data,
39
+ columns,
40
+ onSortingChange: setSorting,
41
+ getCoreRowModel: getCoreRowModel(),
42
+ getSortedRowModel: getSortedRowModel(),
43
+ getFilteredRowModel: getFilteredRowModel(),
44
+ onColumnFiltersChange: setColumnFilters,
45
+ state: {
46
+ columnFilters,
47
+ sorting,
48
+ },
49
+ });
50
+
51
+ useEffect(() => {
52
+ if (filterColumn) {
53
+ table.getColumn(filterColumn)?.setFilterValue(filter);
54
+ }
55
+ }, [filter, filterColumn]);
56
+
57
+ return (
58
+ <DataTable.Root className={className}>
59
+ <DataTable.Header>
60
+ {table.getHeaderGroups().map((headerGroup) => (
61
+ <DataTable.Row key={headerGroup.id}>
62
+ {headerGroup.headers.map((header) => (
63
+ <DataTable.Head
64
+ key={header.id}
65
+ onClick={header.column.getToggleSortingHandler()}
66
+ className={header.column.getCanSort() ? "s-cursor-pointer" : ""}
67
+ >
68
+ <div className="s-flex s-items-center s-space-x-1 s-whitespace-nowrap">
69
+ {flexRender(
70
+ header.column.columnDef.header,
71
+ header.getContext()
72
+ )}
73
+ {header.column.getCanSort() && (
74
+ <Icon
75
+ visual={
76
+ header.column.getIsSorted() === "asc"
77
+ ? ArrowUpIcon
78
+ : header.column.getIsSorted() === "desc"
79
+ ? ArrowDownIcon
80
+ : ArrowDownIcon
81
+ }
82
+ className={classNames(
83
+ "s-h-4 s-w-3 s-font-extralight",
84
+ header.column.getIsSorted()
85
+ ? "s-opacity-100"
86
+ : "s-opacity-0"
87
+ )}
88
+ />
89
+ )}
90
+ </div>
91
+ </DataTable.Head>
92
+ ))}
93
+ </DataTable.Row>
94
+ ))}
95
+ </DataTable.Header>
96
+ <DataTable.Body>
97
+ {table.getRowModel().rows.map((row) => (
98
+ <DataTable.Row
99
+ key={row.id}
100
+ clickable={row.original.clickable}
101
+ onClick={row.original.onClick}
102
+ >
103
+ {row.getVisibleCells().map((cell) => (
104
+ <DataTable.Cell key={cell.id}>
105
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
106
+ </DataTable.Cell>
107
+ ))}
108
+ </DataTable.Row>
109
+ ))}
110
+ </DataTable.Body>
111
+ </DataTable.Root>
112
+ );
113
+ }
114
+
115
+ interface DataTableRootProps extends React.HTMLAttributes<HTMLTableElement> {
116
+ children: ReactNode;
117
+ }
118
+
119
+ DataTable.Root = function DataTableRoot({
120
+ children,
121
+ className,
122
+ ...props
123
+ }: DataTableRootProps) {
124
+ return (
125
+ <table
126
+ className={classNames("s-w-full s-border-collapse", className || "")}
127
+ {...props}
128
+ >
129
+ {children}
130
+ </table>
131
+ );
132
+ };
133
+
134
+ interface HeaderProps extends React.HTMLAttributes<HTMLTableSectionElement> {
135
+ children: ReactNode;
136
+ }
137
+
138
+ DataTable.Header = function Header({
139
+ children,
140
+ className,
141
+ ...props
142
+ }: HeaderProps) {
143
+ return (
144
+ <thead
145
+ className={classNames("s-text-xs s-capitalize", className || "")}
146
+ {...props}
147
+ >
148
+ {children}
149
+ </thead>
150
+ );
151
+ };
152
+
153
+ interface HeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
154
+ children?: ReactNode;
155
+ }
156
+
157
+ DataTable.Head = function Head({ children, className, ...props }: HeadProps) {
158
+ return (
159
+ <th
160
+ className={classNames(
161
+ "s-w-full s-py-1 s-pr-3 s-text-left s-font-medium s-text-element-800",
162
+ className || ""
163
+ )}
164
+ {...props}
165
+ >
166
+ {children}
167
+ </th>
168
+ );
169
+ };
170
+
171
+ DataTable.Body = function Body({
172
+ children,
173
+ className,
174
+ ...props
175
+ }: React.HTMLAttributes<HTMLTableSectionElement>) {
176
+ return (
177
+ <tbody className={className} {...props}>
178
+ {children}
179
+ </tbody>
180
+ );
181
+ };
182
+
183
+ interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
184
+ children: ReactNode;
185
+ clickable?: boolean;
186
+ onClick?: () => void;
187
+ }
188
+
189
+ DataTable.Row = function Row({
190
+ children,
191
+ className,
192
+ clickable = false,
193
+ onClick,
194
+ ...props
195
+ }: RowProps) {
196
+ return (
197
+ <tr
198
+ className={classNames(
199
+ "s-border-b s-border-structure-200 s-text-sm",
200
+ className || ""
201
+ )}
202
+ {...props}
203
+ >
204
+ {children}
205
+ {clickable && (
206
+ <td
207
+ className="s-w-1 s-cursor-pointer s-pl-1 s-text-element-600"
208
+ onClick={clickable ? onClick : undefined}
209
+ >
210
+ <Icon visual={MoreIcon} size="sm" />
211
+ </td>
212
+ )}
213
+ </tr>
214
+ );
215
+ };
216
+
217
+ interface CellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
218
+ avatarUrl?: string;
219
+ icon?: React.ComponentType<{ className?: string }>;
220
+ children?: ReactNode;
221
+ description?: string;
222
+ }
223
+
224
+ DataTable.Cell = function Cell({
225
+ children,
226
+ className,
227
+ avatarUrl,
228
+ icon,
229
+ description,
230
+ ...props
231
+ }: CellProps) {
232
+ return (
233
+ <td
234
+ className={classNames(
235
+ "s-whitespace-nowrap s-py-2 s-pl-0.5 s-text-element-800",
236
+ className || ""
237
+ )}
238
+ {...props}
239
+ >
240
+ <div className="s-flex">
241
+ {avatarUrl && (
242
+ <Avatar visual={avatarUrl} size="xs" className="s-mr-2" />
243
+ )}
244
+ {icon && (
245
+ <Icon visual={icon} size="sm" className="s-mr-2 s-text-element-600" />
246
+ )}
247
+ <div className="s-flex">
248
+ <span className="s-text-sm s-text-element-800">{children}</span>
249
+ {description && (
250
+ <span className="s-pl-2 s-text-sm s-text-element-600">
251
+ {description}
252
+ </span>
253
+ )}
104
254
  </div>
105
255
  </div>
106
- );
107
- }
108
- );
256
+ </td>
257
+ );
258
+ };
259
+
260
+ DataTable.Caption = function Caption({
261
+ children,
262
+ className,
263
+ ...props
264
+ }: React.HTMLAttributes<HTMLTableCaptionElement>) {
265
+ return (
266
+ <caption className={className} {...props}>
267
+ {children}
268
+ </caption>
269
+ );
270
+ };
@@ -1,7 +1,8 @@
1
- import type { Meta } from "@storybook/react";
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { ColumnDef } from "@tanstack/react-table";
2
3
  import React from "react";
3
4
 
4
- import { Button, DataTable } from "../_index";
5
+ import { DataTable, FolderIcon } from "../index_with_tw_base";
5
6
 
6
7
  const meta = {
7
8
  title: "Components/DataTable",
@@ -9,28 +10,116 @@ const meta = {
9
10
  } satisfies Meta<typeof DataTable>;
10
11
 
11
12
  export default meta;
13
+ type Story = StoryObj<typeof meta>;
12
14
 
13
- export const DataTableExample = () => {
14
- const columns = ["Name", "Age", "Location", "Email"];
15
- const data = [
16
- ["Alice", 30, "New York", "test@test.tt"],
17
- ["Bob", 25, "Los Angeles", "test@test.tt"],
18
- ["Charlie", 35, "Chicago", "test@test.tt"],
19
- ["Paul", 35, "London", "test@test.tt"],
20
- ["Charles", 5, "Paris", "test@test.tt"],
15
+ type Data = {
16
+ name: string;
17
+ description?: string;
18
+ usedBy: number;
19
+ addedBy: string;
20
+ lastUpdated: string;
21
+ size: string;
22
+ avatarUrl?: string;
23
+ icon?: React.ComponentType<{ className?: string }>;
24
+ clickable?: boolean;
25
+ onClick?: () => void;
26
+ };
27
+
28
+ const DataTableExample = () => {
29
+ const data: Data[] = [
30
+ {
31
+ name: "Marketing",
32
+ description: "(23 items)",
33
+ usedBy: 8,
34
+ addedBy: "User1",
35
+ lastUpdated: "July 8, 2023",
36
+ size: "32kb",
37
+ avatarUrl: "https://dust.tt/static/droidavatar/Droid_Lime_3.jpg",
38
+ clickable: true,
39
+ onClick: () => console.log("hehe"),
40
+ },
41
+ {
42
+ name: "Design",
43
+ usedBy: 2,
44
+ addedBy: "User2",
45
+ lastUpdated: "2023-07-09",
46
+ size: "64kb",
47
+ icon: FolderIcon,
48
+ clickable: false,
49
+ },
50
+ {
51
+ name: "Development",
52
+ usedBy: 5,
53
+ addedBy: "User3",
54
+ lastUpdated: "2023-07-07",
55
+ size: "128kb",
56
+ },
57
+ {
58
+ name: "Sales",
59
+ usedBy: 10,
60
+ addedBy: "User4",
61
+ lastUpdated: "2023-07-10",
62
+ size: "16kb",
63
+ },
64
+ {
65
+ name: "HR",
66
+ usedBy: 3,
67
+ addedBy: "User5",
68
+ lastUpdated: "2023-07-06",
69
+ size: "48kb",
70
+ },
71
+ ];
72
+
73
+ const columns: ColumnDef<Data>[] = [
74
+ {
75
+ accessorKey: "name",
76
+ header: "Name",
77
+ cell: (info) => (
78
+ <DataTable.Cell
79
+ avatarUrl={info.row.original.avatarUrl}
80
+ icon={info.row.original.icon}
81
+ description={info.row.original.description}
82
+ >
83
+ {info.row.original.name}
84
+ </DataTable.Cell>
85
+ ),
86
+ },
87
+ {
88
+ accessorKey: "usedBy",
89
+ header: "Used by",
90
+ cell: (info) => (
91
+ <DataTable.Cell>{info.row.original.usedBy}</DataTable.Cell>
92
+ ),
93
+ },
94
+ {
95
+ accessorKey: "addedBy",
96
+ header: "Added by",
97
+ cell: (info) => (
98
+ <DataTable.Cell>{info.row.original.addedBy}</DataTable.Cell>
99
+ ),
100
+ },
101
+ {
102
+ accessorKey: "lastUpdated",
103
+ header: "Last updated",
104
+ cell: (info) => (
105
+ <DataTable.Cell>{info.row.original.lastUpdated}</DataTable.Cell>
106
+ ),
107
+ enableSorting: false,
108
+ },
109
+ {
110
+ accessorKey: "size",
111
+ header: "Size",
112
+ cell: (info) => <DataTable.Cell>{info.row.original.size}</DataTable.Cell>,
113
+ },
21
114
  ];
22
- const downloadButton = (
23
- <Button className="mt-4" size="sm" variant="tertiary" label="Download" />
24
- );
25
115
 
26
116
  return (
27
- <DataTable
28
- columns={columns}
29
- rows={data}
30
- name="Test table"
31
- enableCopy={true}
32
- showLimit={3}
33
- downloadButton={downloadButton}
34
- />
117
+ <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
118
+ <DataTable data={data} columns={columns} />
119
+ </div>
35
120
  );
36
121
  };
122
+
123
+ export const Default: Story = {
124
+ render: () => <DataTableExample />,
125
+ };