@dust-tt/sparkle 0.2.225 → 0.2.227-rc

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.
Files changed (52) hide show
  1. package/dist/cjs/index.js +283 -113
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/esm/components/DataTable.d.ts +5 -2
  4. package/dist/esm/components/DataTable.d.ts.map +1 -1
  5. package/dist/esm/components/DataTable.js +48 -29
  6. package/dist/esm/components/DataTable.js.map +1 -1
  7. package/dist/esm/components/Item.d.ts +6 -52
  8. package/dist/esm/components/Item.d.ts.map +1 -1
  9. package/dist/esm/components/Item.js.map +1 -1
  10. package/dist/esm/components/PaginatedCitationsGrid.d.ts.map +1 -1
  11. package/dist/esm/components/PaginatedCitationsGrid.js +8 -4
  12. package/dist/esm/components/PaginatedCitationsGrid.js.map +1 -1
  13. package/dist/esm/components/Pagination.d.ts +5 -4
  14. package/dist/esm/components/Pagination.d.ts.map +1 -1
  15. package/dist/esm/components/Pagination.js +35 -35
  16. package/dist/esm/components/Pagination.js.map +1 -1
  17. package/dist/esm/hooks/index.d.ts +3 -0
  18. package/dist/esm/hooks/index.d.ts.map +1 -0
  19. package/dist/esm/hooks/index.js +3 -0
  20. package/dist/esm/hooks/index.js.map +1 -0
  21. package/dist/esm/hooks/useHashParams.d.ts +18 -0
  22. package/dist/esm/hooks/useHashParams.d.ts.map +1 -0
  23. package/dist/esm/hooks/useHashParams.js +67 -0
  24. package/dist/esm/hooks/useHashParams.js.map +1 -0
  25. package/dist/esm/hooks/usePaginationFromUrl.d.ts +6 -0
  26. package/dist/esm/hooks/usePaginationFromUrl.d.ts.map +1 -0
  27. package/dist/esm/hooks/usePaginationFromUrl.js +23 -0
  28. package/dist/esm/hooks/usePaginationFromUrl.js.map +1 -0
  29. package/dist/esm/index.d.ts +1 -0
  30. package/dist/esm/index.d.ts.map +1 -1
  31. package/dist/esm/index.js +1 -0
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/stories/DataTable.stories.d.ts +2 -0
  34. package/dist/esm/stories/DataTable.stories.d.ts.map +1 -1
  35. package/dist/esm/stories/DataTable.stories.js +133 -106
  36. package/dist/esm/stories/DataTable.stories.js.map +1 -1
  37. package/dist/esm/stories/Pagination.stories.d.ts +1 -0
  38. package/dist/esm/stories/Pagination.stories.d.ts.map +1 -1
  39. package/dist/esm/stories/Pagination.stories.js +25 -3
  40. package/dist/esm/stories/Pagination.stories.js.map +1 -1
  41. package/dist/sparkle.css +4 -0
  42. package/package.json +1 -1
  43. package/src/components/DataTable.tsx +114 -75
  44. package/src/components/Item.tsx +47 -52
  45. package/src/components/PaginatedCitationsGrid.tsx +11 -6
  46. package/src/components/Pagination.tsx +48 -45
  47. package/src/hooks/index.ts +2 -0
  48. package/src/hooks/useHashParams.ts +95 -0
  49. package/src/hooks/usePaginationFromUrl.ts +36 -0
  50. package/src/index.ts +1 -0
  51. package/src/stories/DataTable.stories.tsx +200 -130
  52. package/src/stories/Pagination.stories.tsx +38 -10
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Original code from : https://www.npmjs.com/package/use-hash-param
3
+ * ISC License
4
+ */
5
+
6
+ import { useCallback, useEffect, useState } from "react";
7
+
8
+ const getHashSearchParams = (location: Location): [string, URLSearchParams] => {
9
+ const hash = location.hash.slice(1);
10
+ const [prefix, query] = hash.split("?");
11
+
12
+ return [prefix, new URLSearchParams(query)];
13
+ };
14
+
15
+ const getHashParam = (key: string, defaultValue?: string) => {
16
+ if (typeof window === "undefined") {
17
+ return defaultValue;
18
+ }
19
+
20
+ const [, searchParams] = getHashSearchParams(window.location);
21
+
22
+ return searchParams.get(key) ?? defaultValue;
23
+ };
24
+
25
+ const setHashParam = (
26
+ key: string,
27
+ value: string | undefined,
28
+ shouldReplaceState: boolean
29
+ ) => {
30
+ if (typeof window !== "undefined") {
31
+ const [prefix, searchParams] = getHashSearchParams(window.location);
32
+
33
+ if (typeof value === "undefined" || value === "") {
34
+ searchParams.delete(key);
35
+ } else {
36
+ searchParams.set(key, value);
37
+ }
38
+
39
+ const search = searchParams.toString();
40
+ const hash = search ? `${prefix}?${search}` : prefix;
41
+ if (shouldReplaceState && "replaceState" in history) {
42
+ history.replaceState(null, "", `#${hash}`);
43
+ } else {
44
+ window.location.hash = hash;
45
+ }
46
+ }
47
+ };
48
+
49
+ type Updater = (prevValue?: string) => string;
50
+ type SetterOptions = {
51
+ history: "replace" | "push";
52
+ };
53
+ type Setter = (value?: string | Updater, options?: SetterOptions) => void;
54
+
55
+ /**
56
+ * @param key The parameter-name to use from the hash-string query string.
57
+ * @param defaultValue A default value to use if the parameter is not specified and on the server.
58
+ * @returns A two-tuple, the first element is the selected param value (either extracted from the hash param or the default value).
59
+ * The second element is a setter function to change the param value.
60
+ */
61
+
62
+ export const useHashParam = (
63
+ key: string,
64
+ defaultValue?: string
65
+ ): [string | undefined, Setter] => {
66
+ const [innerValue, setInnerValue] = useState<string | undefined>(() =>
67
+ getHashParam(key, defaultValue)
68
+ );
69
+
70
+ useEffect(() => {
71
+ const handleHashChange = () =>
72
+ setInnerValue(getHashParam(key, defaultValue));
73
+ handleHashChange();
74
+ window.addEventListener("hashchange", handleHashChange);
75
+ return () => window.removeEventListener("hashchange", handleHashChange);
76
+ }, [key]);
77
+
78
+ const setValue = useCallback<Setter>(
79
+ (
80
+ newValue?: string | Updater,
81
+ options: SetterOptions = { history: "replace" }
82
+ ) => {
83
+ const newInnerValue =
84
+ typeof newValue === "function"
85
+ ? newValue(getHashParam(key, defaultValue))
86
+ : newValue;
87
+
88
+ setInnerValue(newInnerValue);
89
+ setHashParam(key, newInnerValue, options.history === "replace");
90
+ },
91
+ [key]
92
+ );
93
+
94
+ return [innerValue || defaultValue, setValue];
95
+ };
@@ -0,0 +1,36 @@
1
+ import { PaginationState } from "@tanstack/react-table";
2
+ import { useMemo } from "react";
3
+
4
+ import { useHashParam } from "@sparkle/hooks/useHashParams";
5
+
6
+ const defaultPageSize = 25;
7
+
8
+ export const usePaginationFromUrl = (
9
+ urlPrefix?: string,
10
+ initialPageSize = defaultPageSize
11
+ ) => {
12
+ const [pageIndexParam, setPageIndexParam] = useHashParam(
13
+ urlPrefix ? `${urlPrefix}PageIndex` : "pageIndex"
14
+ );
15
+ const [pageSizeParam, setPageSizeParam] = useHashParam(
16
+ urlPrefix ? `${urlPrefix}PageSize` : "pageSize"
17
+ );
18
+
19
+ const pageIndex = pageIndexParam ? parseInt(pageIndexParam) : 0;
20
+ const pageSize = pageSizeParam ? parseInt(pageSizeParam) : initialPageSize;
21
+
22
+ const res = useMemo(() => {
23
+ const pagination: PaginationState = { pageIndex, pageSize };
24
+
25
+ const setPagination = (newValue: PaginationState) => {
26
+ if (newValue.pageIndex !== pageIndex || newValue.pageSize !== pageSize) {
27
+ setPageIndexParam(newValue.pageIndex.toString());
28
+ setPageSizeParam(newValue.pageSize.toString());
29
+ }
30
+ };
31
+
32
+ return { pagination, setPagination };
33
+ }, [pageIndex, pageSize, urlPrefix]);
34
+
35
+ return res;
36
+ };
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components";
2
2
  export { SparkleContext } from "./context";
3
+ export * from "./hooks";
3
4
  export * from "./icons";
4
5
  export * from "./lib";
5
6
  export * from "./logo";
@@ -1,8 +1,9 @@
1
1
  import type { Meta } from "@storybook/react";
2
- import { ColumnDef } from "@tanstack/react-table";
3
- import React from "react";
2
+ import { ColumnDef, PaginationState } from "@tanstack/react-table";
3
+ import React, { useMemo } from "react";
4
4
 
5
5
  import { DropdownItemProps } from "@sparkle/components/DropdownMenu";
6
+ import { Input } from "@sparkle/components/Input";
6
7
 
7
8
  import { DataTable, FolderIcon } from "../index_with_tw_base";
8
9
 
@@ -28,141 +29,210 @@ type Data = {
28
29
  roundedAvatar?: boolean;
29
30
  };
30
31
 
32
+ const data: Data[] = [
33
+ {
34
+ name: "Soupinou with tooltip on avatar",
35
+ usedBy: 100,
36
+ addedBy: "User1",
37
+ lastUpdated: "July 8, 2023",
38
+ size: "32kb",
39
+ avatarUrl: "https://avatars.githubusercontent.com/u/138893015?s=200&v=4",
40
+ avatarTooltipLabel: "Meow",
41
+ roundedAvatar: true,
42
+ onClick: () => console.log("hehe"),
43
+ },
44
+ {
45
+ name: "Marketing",
46
+ description: "(23 items)",
47
+ usedBy: 8,
48
+ addedBy: "User1",
49
+ lastUpdated: "July 8, 2023",
50
+ size: "32kb",
51
+ avatarUrl: "https://dust.tt/static/droidavatar/Droid_Lime_3.jpg",
52
+ roundedAvatar: true,
53
+ onClick: () => alert("Marketing clicked"),
54
+ },
55
+ {
56
+ name: "Design",
57
+ usedBy: 2,
58
+ addedBy: "User2",
59
+ lastUpdated: "2023-07-09",
60
+ size: "64kb",
61
+ icon: FolderIcon,
62
+ moreMenuItems: [
63
+ {
64
+ label: "Edit (disabled)",
65
+ onClick: () => alert("Design menu clicked"),
66
+ disabled: true,
67
+ },
68
+ ],
69
+ onClick: () => alert("Design clicked"),
70
+ },
71
+ {
72
+ name: "design",
73
+ usedBy: 3,
74
+ addedBy: "User21",
75
+ lastUpdated: "2023-07-09",
76
+ size: "64kb",
77
+ icon: FolderIcon,
78
+ moreMenuItems: [
79
+ {
80
+ label: "Edit",
81
+ onClick: () => alert("Design menu clicked"),
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ name: "Development",
87
+ usedBy: 5,
88
+ addedBy: "User3",
89
+ lastUpdated: "2023-07-07",
90
+ size: "128kb",
91
+ },
92
+ {
93
+ name: "Sales",
94
+ usedBy: 10,
95
+ addedBy: "User4",
96
+ lastUpdated: "2023-07-10",
97
+ size: "16kb",
98
+ },
99
+ {
100
+ name: "HR",
101
+ usedBy: 3,
102
+ addedBy: "User5",
103
+ lastUpdated: "2023-07-06",
104
+ size: "48kb",
105
+ },
106
+ ];
107
+
108
+ const columns: ColumnDef<Data>[] = [
109
+ {
110
+ accessorKey: "name",
111
+ header: "Name",
112
+ sortingFn: "text",
113
+ cell: (info) => (
114
+ <DataTable.CellContent
115
+ avatarUrl={info.row.original.avatarUrl}
116
+ avatarTooltipLabel={info.row.original.avatarTooltipLabel}
117
+ icon={info.row.original.icon}
118
+ description={info.row.original.description}
119
+ roundedAvatar={info.row.original.roundedAvatar}
120
+ >
121
+ {info.row.original.name}
122
+ </DataTable.CellContent>
123
+ ),
124
+ },
125
+ {
126
+ accessorKey: "usedBy",
127
+ header: "Used by",
128
+ cell: (info) => (
129
+ <DataTable.CellContent>{info.row.original.usedBy}</DataTable.CellContent>
130
+ ),
131
+ },
132
+ {
133
+ accessorKey: "addedBy",
134
+ header: "Added by",
135
+ cell: (info) => (
136
+ <DataTable.CellContent>{info.row.original.addedBy}</DataTable.CellContent>
137
+ ),
138
+ },
139
+ {
140
+ accessorKey: "lastUpdated",
141
+ header: "Last updated",
142
+ cell: (info) => (
143
+ <DataTable.CellContent>
144
+ {info.row.original.lastUpdated}
145
+ </DataTable.CellContent>
146
+ ),
147
+ enableSorting: false,
148
+ },
149
+ {
150
+ accessorKey: "size",
151
+ header: "Size",
152
+ cell: (info) => (
153
+ <DataTable.CellContent>{info.row.original.size}</DataTable.CellContent>
154
+ ),
155
+ },
156
+ ];
157
+
31
158
  export const DataTableExample = () => {
32
- const data: Data[] = [
33
- {
34
- name: "Soupinou with tooltip on avatar",
35
- usedBy: 100,
36
- addedBy: "User1",
37
- lastUpdated: "July 8, 2023",
38
- size: "32kb",
39
- avatarUrl: "https://avatars.githubusercontent.com/u/138893015?s=200&v=4",
40
- avatarTooltipLabel: "Meow",
41
- roundedAvatar: true,
42
- onClick: () => console.log("hehe"),
43
- },
44
- {
45
- name: "Marketing",
46
- description: "(23 items)",
47
- usedBy: 8,
48
- addedBy: "User1",
49
- lastUpdated: "July 8, 2023",
50
- size: "32kb",
51
- avatarUrl: "https://dust.tt/static/droidavatar/Droid_Lime_3.jpg",
52
- roundedAvatar: true,
53
- onClick: () => alert("Marketing clicked"),
54
- },
55
- {
56
- name: "Design",
57
- usedBy: 2,
58
- addedBy: "User2",
59
- lastUpdated: "2023-07-09",
60
- size: "64kb",
61
- icon: FolderIcon,
62
- moreMenuItems: [
63
- {
64
- label: "Edit (disabled)",
65
- onClick: () => alert("Design menu clicked"),
66
- disabled: true,
67
- },
68
- ],
69
- onClick: () => alert("Design clicked"),
70
- },
71
- {
72
- name: "design",
73
- usedBy: 3,
74
- addedBy: "User21",
75
- lastUpdated: "2023-07-09",
76
- size: "64kb",
77
- icon: FolderIcon,
78
- moreMenuItems: [
79
- {
80
- label: "Edit",
81
- onClick: () => alert("Design menu clicked"),
82
- },
83
- ],
84
- },
85
- {
86
- name: "Development",
87
- usedBy: 5,
88
- addedBy: "User3",
89
- lastUpdated: "2023-07-07",
90
- size: "128kb",
91
- },
92
- {
93
- name: "Sales",
94
- usedBy: 10,
95
- addedBy: "User4",
96
- lastUpdated: "2023-07-10",
97
- size: "16kb",
98
- },
99
- {
100
- name: "HR",
101
- usedBy: 3,
102
- addedBy: "User5",
103
- lastUpdated: "2023-07-06",
104
- size: "48kb",
105
- },
106
- ];
159
+ const [filter, setFilter] = React.useState<string>("");
160
+
161
+ return (
162
+ <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
163
+ <Input
164
+ name="filter"
165
+ placeholder="Filter"
166
+ value={filter}
167
+ onChange={(v) => setFilter(v)}
168
+ />
169
+ <DataTable
170
+ data={data}
171
+ filter={filter}
172
+ filterColumn="name"
173
+ columns={columns}
174
+ />
175
+ </div>
176
+ );
177
+ };
107
178
 
108
- const columns: ColumnDef<Data>[] = [
109
- {
110
- accessorKey: "name",
111
- header: "Name",
112
- sortingFn: "text",
113
- cell: (info) => (
114
- <DataTable.CellContent
115
- avatarUrl={info.row.original.avatarUrl}
116
- avatarTooltipLabel={info.row.original.avatarTooltipLabel}
117
- icon={info.row.original.icon}
118
- description={info.row.original.description}
119
- roundedAvatar={info.row.original.roundedAvatar}
120
- >
121
- {info.row.original.name}
122
- </DataTable.CellContent>
123
- ),
124
- },
125
- {
126
- accessorKey: "usedBy",
127
- header: "Used by",
128
- cell: (info) => (
129
- <DataTable.CellContent>
130
- {info.row.original.usedBy}
131
- </DataTable.CellContent>
132
- ),
133
- },
134
- {
135
- accessorKey: "addedBy",
136
- header: "Added by",
137
- cell: (info) => (
138
- <DataTable.CellContent>
139
- {info.row.original.addedBy}
140
- </DataTable.CellContent>
141
- ),
142
- },
143
- {
144
- accessorKey: "lastUpdated",
145
- header: "Last updated",
146
- cell: (info) => (
147
- <DataTable.CellContent>
148
- {info.row.original.lastUpdated}
149
- </DataTable.CellContent>
150
- ),
151
- enableSorting: false,
152
- },
153
- {
154
- accessorKey: "size",
155
- header: "Size",
156
- cell: (info) => (
157
- <DataTable.CellContent>{info.row.original.size}</DataTable.CellContent>
158
- ),
159
- },
160
- ];
179
+ export const DataTablePaginatedExample = () => {
180
+ const [pagination, setPagination] = React.useState<PaginationState>({
181
+ pageIndex: 0,
182
+ pageSize: 2,
183
+ });
184
+ const [filter, setFilter] = React.useState<string>("");
161
185
 
162
186
  return (
163
187
  <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
188
+ <Input
189
+ name="filter"
190
+ placeholder="Filter"
191
+ value={filter}
192
+ onChange={(v) => setFilter(v)}
193
+ />
164
194
  <DataTable
165
195
  data={data}
196
+ filter={filter}
197
+ filterColumn="name"
198
+ pagination={pagination}
199
+ setPagination={setPagination}
200
+ columns={columns}
201
+ initialColumnOrder={[{ id: "name", desc: false }]}
202
+ columnsBreakpoints={{ lastUpdated: "sm" }}
203
+ />
204
+ </div>
205
+ );
206
+ };
207
+
208
+ export const DataTablePaginatedServerSideExample = () => {
209
+ const [pagination, setPagination] = React.useState<PaginationState>({
210
+ pageIndex: 0,
211
+ pageSize: 2,
212
+ });
213
+ const [filter, setFilter] = React.useState<string>("");
214
+ const rows = useMemo(() => {
215
+ return data.slice(
216
+ pagination.pageIndex * pagination.pageSize,
217
+ (pagination.pageIndex + 1) * pagination.pageSize
218
+ );
219
+ }, [data, pagination]);
220
+
221
+ return (
222
+ <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
223
+ <Input
224
+ name="filter"
225
+ placeholder="Filter"
226
+ value={filter}
227
+ onChange={(v) => setFilter(v)}
228
+ />
229
+ <DataTable
230
+ data={rows}
231
+ totalRowCount={data.length}
232
+ filter={filter}
233
+ filterColumn="name"
234
+ pagination={pagination}
235
+ setPagination={setPagination}
166
236
  columns={columns}
167
237
  initialColumnOrder={[{ id: "name", desc: false }]}
168
238
  columnsBreakpoints={{ lastUpdated: "sm" }}
@@ -1,6 +1,8 @@
1
1
  import type { Meta } from "@storybook/react";
2
2
  import React from "react";
3
3
 
4
+ import { usePaginationFromUrl } from "@sparkle/hooks/usePaginationFromUrl";
5
+
4
6
  import { Pagination } from "../index_with_tw_base";
5
7
 
6
8
  const meta = {
@@ -9,38 +11,64 @@ const meta = {
9
11
  } satisfies Meta<typeof Pagination>;
10
12
 
11
13
  export default meta;
12
-
13
14
  export const PaginationSM = () => {
15
+ const [pagination, setPagination] = React.useState({
16
+ pageIndex: 0,
17
+ pageSize: 50,
18
+ });
14
19
  return (
15
20
  <Pagination
16
- itemsCount={960}
17
- maxItemsPerPage={50}
18
- onButtonClick={() => {}}
21
+ rowCount={960}
22
+ pagination={pagination}
23
+ setPagination={setPagination}
19
24
  />
20
25
  );
21
26
  };
22
27
 
23
28
  export const PaginationXS = () => {
29
+ const [pagination, setPagination] = React.useState({
30
+ pageIndex: 0,
31
+ pageSize: 50,
32
+ });
24
33
  return (
25
34
  <Pagination
26
- itemsCount={960}
27
- maxItemsPerPage={50}
35
+ rowCount={960}
28
36
  size="xs"
29
37
  showDetails={false}
30
- onButtonClick={() => {}}
38
+ pagination={pagination}
39
+ setPagination={setPagination}
31
40
  />
32
41
  );
33
42
  };
34
43
 
35
44
  export const PaginationNoPageButtons = () => {
45
+ const [pagination, setPagination] = React.useState({
46
+ pageIndex: 0,
47
+ pageSize: 50,
48
+ });
36
49
  return (
37
50
  <Pagination
38
- itemsCount={960}
39
- maxItemsPerPage={50}
51
+ rowCount={960}
40
52
  size="xs"
41
53
  showDetails={false}
42
54
  showPageButtons={false}
43
- onButtonClick={() => {}}
55
+ pagination={pagination}
56
+ setPagination={setPagination}
44
57
  />
45
58
  );
46
59
  };
60
+
61
+ export const PaginationWithUrl = () => {
62
+ const { pagination, setPagination } = usePaginationFromUrl("example", 50);
63
+
64
+ return (
65
+ <>
66
+ <div className="s-p-2">Current hash: {location.hash}</div>
67
+ <Pagination
68
+ rowCount={960}
69
+ pagination={pagination}
70
+ setPagination={setPagination}
71
+ />
72
+ </>
73
+ );
74
+ };