@dust-tt/sparkle 0.2.239 → 0.2.241

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.
@@ -1,6 +1,7 @@
1
1
  import { Menu, Transition } from "@headlessui/react";
2
2
  import React, {
3
3
  ComponentType,
4
+ forwardRef,
4
5
  Fragment,
5
6
  JSXElementConstructor,
6
7
  MouseEvent,
@@ -102,135 +103,164 @@ export interface DropdownButtonProps {
102
103
  className?: string;
103
104
  disabled?: boolean;
104
105
  children?: React.ReactNode;
106
+ ref?: React.Ref<HTMLButtonElement>;
107
+ onClick?: () => void;
105
108
  }
106
109
 
107
- DropdownMenu.Button = function ({
108
- label,
109
- type = "menu",
110
- size = "sm",
111
- tooltip,
112
- icon,
113
- children,
114
- tooltipPosition = "above",
115
- className = "",
116
- disabled = false,
117
- }: DropdownButtonProps) {
118
- const finalLabelClasses = classNames(
119
- labelClasses.base,
120
- labelSizeClasses[size],
121
- labelClasses.dark.base,
122
- !disabled ? labelClasses.active : "",
123
- !disabled ? labelClasses.dark.active : "",
124
- !disabled ? labelClasses.hover : "",
125
- !disabled ? labelClasses.dark.hover : "",
126
- disabled ? labelClasses.disabled : ""
127
- );
110
+ DropdownMenu.Button = forwardRef<HTMLButtonElement, DropdownButtonProps>(
111
+ function DropdownMenuButton(
112
+ {
113
+ label,
114
+ type = "menu",
115
+ size = "sm",
116
+ tooltip,
117
+ icon,
118
+ children,
119
+ tooltipPosition = "above",
120
+ className = "",
121
+ disabled = false,
122
+ onClick,
123
+ },
124
+ forwardedRef
125
+ ) {
126
+ const contextRef = useContext(ButtonRefContext);
127
+
128
+ const finalLabelClasses = classNames(
129
+ labelClasses.base,
130
+ labelSizeClasses[size],
131
+ labelClasses.dark.base,
132
+ !disabled ? labelClasses.active : "",
133
+ !disabled ? labelClasses.dark.active : "",
134
+ !disabled ? labelClasses.hover : "",
135
+ !disabled ? labelClasses.dark.hover : "",
136
+ disabled ? labelClasses.disabled : ""
137
+ );
128
138
 
129
- const finalIconClasses = classNames(
130
- iconClasses.base,
131
- iconClasses.dark.base,
132
- !disabled ? iconClasses.hover : "",
133
- !disabled ? iconClasses.dark.hover : "",
134
- disabled ? iconClasses.disabled : ""
135
- );
139
+ const finalIconClasses = classNames(
140
+ iconClasses.base,
141
+ iconClasses.dark.base,
142
+ !disabled ? iconClasses.hover : "",
143
+ !disabled ? iconClasses.dark.hover : "",
144
+ disabled ? iconClasses.disabled : ""
145
+ );
136
146
 
137
- const finalChevronClasses = classNames(
138
- chevronClasses.base,
139
- !disabled ? chevronClasses.hover : "",
140
- chevronClasses.dark.base,
141
- !disabled ? chevronClasses.dark.hover : "",
142
- disabled ? chevronClasses.disabled : ""
143
- );
147
+ const finalChevronClasses = classNames(
148
+ chevronClasses.base,
149
+ !disabled ? chevronClasses.hover : "",
150
+ chevronClasses.dark.base,
151
+ !disabled ? chevronClasses.dark.hover : "",
152
+ disabled ? chevronClasses.disabled : ""
153
+ );
144
154
 
145
- const buttonRef = useContext(ButtonRefContext);
155
+ const aggregatedRef = (value: HTMLButtonElement) => {
156
+ if (contextRef) {
157
+ contextRef.current = value;
158
+ }
159
+ if (typeof forwardedRef === "function") {
160
+ forwardedRef(value);
161
+ } else if (forwardedRef) {
162
+ forwardedRef.current = value;
163
+ }
164
+ };
165
+
166
+ if (children) {
167
+ return (
168
+ <Menu.Button
169
+ as="div"
170
+ disabled={disabled}
171
+ ref={aggregatedRef}
172
+ className={classNames(
173
+ disabled ? "s-cursor-default" : "s-cursor-pointer",
174
+ className,
175
+ "s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
176
+ label ? "s-gap-1.5" : "s-gap-0"
177
+ )}
178
+ onClick={(e) => e.stopPropagation()}
179
+ >
180
+ {tooltip ? (
181
+ <Tooltip position={tooltipPosition} label={tooltip}>
182
+ {children}
183
+ </Tooltip>
184
+ ) : (
185
+ children
186
+ )}
187
+ </Menu.Button>
188
+ );
189
+ }
190
+
191
+ const chevronIcon =
192
+ type === "select"
193
+ ? ChevronUpDownIcon
194
+ : type === "submenu"
195
+ ? ChevronRightIcon
196
+ : ChevronDownIcon;
146
197
 
147
- if (children) {
148
198
  return (
149
- <Menu.Button
150
- as="div"
151
- disabled={disabled}
152
- ref={buttonRef}
153
- className={classNames(
154
- disabled ? "s-cursor-default" : "s-cursor-pointer",
155
- className,
156
- "s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
157
- label ? "s-gap-1.5" : "s-gap-0"
158
- )}
159
- onClick={(e) => e.stopPropagation()}
160
- >
199
+ <>
161
200
  {tooltip ? (
162
- <Tooltip position={tooltipPosition} label={tooltip}>
163
- {children}
201
+ <Tooltip label={tooltip} position={tooltipPosition}>
202
+ <Menu.Button
203
+ disabled={disabled}
204
+ ref={aggregatedRef}
205
+ className={classNames(
206
+ disabled ? "s-cursor-default" : "s-cursor-pointer",
207
+ className,
208
+ "s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
209
+ label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5"
210
+ )}
211
+ onClick={(e) => {
212
+ e.stopPropagation();
213
+ if (onClick) {
214
+ onClick();
215
+ }
216
+ }}
217
+ >
218
+ <Icon visual={icon} size={size} className={finalIconClasses} />
219
+ <Icon
220
+ visual={chevronIcon}
221
+ size={size === "sm" ? "xs" : "sm"}
222
+ className={finalChevronClasses}
223
+ />
224
+ </Menu.Button>
164
225
  </Tooltip>
165
226
  ) : (
166
- children
167
- )}
168
- </Menu.Button>
169
- );
170
- }
171
-
172
- const chevronIcon =
173
- type === "select"
174
- ? ChevronUpDownIcon
175
- : type === "submenu"
176
- ? ChevronRightIcon
177
- : ChevronDownIcon;
178
-
179
- return (
180
- <>
181
- {tooltip ? (
182
- <Tooltip label={tooltip} position={tooltipPosition}>
183
227
  <Menu.Button
184
228
  disabled={disabled}
185
- ref={buttonRef}
229
+ ref={aggregatedRef}
186
230
  className={classNames(
187
231
  disabled ? "s-cursor-default" : "s-cursor-pointer",
188
232
  className,
189
233
  "s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
190
- label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5"
234
+ label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5",
235
+ type === "submenu" ? "s-opacity-50" : ""
191
236
  )}
192
- onClick={(e) => e.stopPropagation()}
237
+ onClick={(e) => {
238
+ e.stopPropagation();
239
+ if (onClick) {
240
+ onClick();
241
+ }
242
+ }}
193
243
  >
194
244
  <Icon visual={icon} size={size} className={finalIconClasses} />
245
+ <span
246
+ className={classNames(
247
+ finalLabelClasses,
248
+ type === "submenu" ? "s-w-full" : ""
249
+ )}
250
+ >
251
+ {label}
252
+ </span>
195
253
  <Icon
196
254
  visual={chevronIcon}
197
255
  size={size === "sm" ? "xs" : "sm"}
198
256
  className={finalChevronClasses}
199
257
  />
200
258
  </Menu.Button>
201
- </Tooltip>
202
- ) : (
203
- <Menu.Button
204
- disabled={disabled}
205
- ref={buttonRef}
206
- className={classNames(
207
- disabled ? "s-cursor-default" : "s-cursor-pointer",
208
- className,
209
- "s-group/dm s-flex s-justify-items-center s-text-sm s-font-medium focus:s-outline-none focus:s-ring-0",
210
- label ? (size === "md" ? "s-gap-2" : "s-gap-1.5") : "s-gap-0.5",
211
- type === "submenu" ? "s-opacity-50" : ""
212
- )}
213
- onClick={(e) => e.stopPropagation()}
214
- >
215
- <Icon visual={icon} size={size} className={finalIconClasses} />
216
- <span
217
- className={classNames(
218
- finalLabelClasses,
219
- type === "submenu" ? "s-w-full" : ""
220
- )}
221
- >
222
- {label}
223
- </span>
224
- <Icon
225
- visual={chevronIcon}
226
- size={size === "sm" ? "xs" : "sm"}
227
- className={finalChevronClasses}
228
- />
229
- </Menu.Button>
230
- )}
231
- </>
232
- );
233
- };
259
+ )}
260
+ </>
261
+ );
262
+ }
263
+ );
234
264
 
235
265
  export interface DropdownItemProps {
236
266
  children?: React.ReactNode;
@@ -1,5 +1,5 @@
1
1
  import type { Meta } from "@storybook/react";
2
- import { ColumnDef, PaginationState } from "@tanstack/react-table";
2
+ import { ColumnDef, PaginationState, SortingState } from "@tanstack/react-table";
3
3
  import React, { useMemo } from "react";
4
4
 
5
5
  import { DropdownItemProps } from "@sparkle/components/DropdownMenu";
@@ -206,6 +206,32 @@ export const DataTableExample = () => {
206
206
  );
207
207
  };
208
208
 
209
+ export const DataTableClientSideSortingExample = () => {
210
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: "name", desc: true }])
211
+ const [filter, setFilter] = React.useState<string>("");
212
+
213
+ return (
214
+ <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
215
+ <Input
216
+ name="filter"
217
+ placeholder="Filter"
218
+ value={filter}
219
+ onChange={(v) => setFilter(v)}
220
+ />
221
+ <DataTable
222
+ className="s-w-full s-max-w-4xl s-overflow-x-auto"
223
+ data={data}
224
+ filter={filter}
225
+ filterColumn="name"
226
+ columns={columns}
227
+ columnsBreakpoints={{ lastUpdated: "sm" }}
228
+ sorting={sorting}
229
+ setSorting={setSorting}
230
+ />
231
+ </div>
232
+ );
233
+ };
234
+
209
235
  export const DataTablePaginatedExample = () => {
210
236
  const [pagination, setPagination] = React.useState<PaginationState>({
211
237
  pageIndex: 0,
@@ -229,7 +255,6 @@ export const DataTablePaginatedExample = () => {
229
255
  pagination={pagination}
230
256
  setPagination={setPagination}
231
257
  columns={columns}
232
- initialColumnOrder={[{ id: "name", desc: false }]}
233
258
  columnsBreakpoints={{ lastUpdated: "sm" }}
234
259
  />
235
260
  </div>
@@ -241,14 +266,25 @@ export const DataTablePaginatedServerSideExample = () => {
241
266
  pageIndex: 0,
242
267
  pageSize: 2,
243
268
  });
269
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: "name", desc: true }])
244
270
  const [filter, setFilter] = React.useState<string>("");
245
271
  const rows = useMemo(() => {
272
+ if (sorting.length > 0) {
273
+ const order = sorting[0].desc ? -1: 1;
274
+ return data.sort(
275
+ (a: Data, b: Data) => {
276
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase()) * order
277
+ }
278
+ ).slice(
279
+ pagination.pageIndex * pagination.pageSize,
280
+ (pagination.pageIndex + 1) * pagination.pageSize
281
+ );
282
+ }
246
283
  return data.slice(
247
284
  pagination.pageIndex * pagination.pageSize,
248
285
  (pagination.pageIndex + 1) * pagination.pageSize
249
286
  );
250
- }, [data, pagination]);
251
-
287
+ }, [data, pagination, sorting]);
252
288
  return (
253
289
  <div className="s-w-full s-max-w-4xl s-overflow-x-auto">
254
290
  <Input
@@ -266,8 +302,10 @@ export const DataTablePaginatedServerSideExample = () => {
266
302
  pagination={pagination}
267
303
  setPagination={setPagination}
268
304
  columns={columns}
269
- initialColumnOrder={[{ id: "name", desc: false }]}
305
+ sorting={sorting}
306
+ setSorting={setSorting}
270
307
  columnsBreakpoints={{ lastUpdated: "sm" }}
308
+ isServerSideSorting={true}
271
309
  />
272
310
  </div>
273
311
  );
@@ -48,7 +48,7 @@ export const DropdownExample = () => {
48
48
  <div className="s-flex s-gap-6">
49
49
  <div className="s-text-sm">Action</div>
50
50
  <DropdownMenu>
51
- <DropdownMenu.Button label="Action" />
51
+ <DropdownMenu.Button label="Action" onClick={() => console.log("CLICK")}/>
52
52
  <DropdownMenu.Items width={220}>
53
53
  <DropdownMenu.SectionHeader label="Edition" />
54
54
  <DropdownMenu.Item