@dust-tt/sparkle 0.2.591-rc-1 → 0.2.591-rc-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.
Files changed (31) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/components/DataTable.d.ts +1 -1
  3. package/dist/esm/components/DataTable.d.ts.map +1 -1
  4. package/dist/esm/components/DataTable.js +51 -48
  5. package/dist/esm/components/DataTable.js.map +1 -1
  6. package/dist/esm/components/Dropdown.js +3 -3
  7. package/dist/esm/components/Dropdown.js.map +1 -1
  8. package/dist/esm/components/MultiPageSheet.d.ts +5 -0
  9. package/dist/esm/components/MultiPageSheet.d.ts.map +1 -1
  10. package/dist/esm/components/MultiPageSheet.js +1 -1
  11. package/dist/esm/components/MultiPageSheet.js.map +1 -1
  12. package/dist/esm/components/Sheet.d.ts +4 -1
  13. package/dist/esm/components/Sheet.d.ts.map +1 -1
  14. package/dist/esm/components/Sheet.js +5 -3
  15. package/dist/esm/components/Sheet.js.map +1 -1
  16. package/dist/esm/stories/DataTable.stories.d.ts +1 -0
  17. package/dist/esm/stories/DataTable.stories.d.ts.map +1 -1
  18. package/dist/esm/stories/DataTable.stories.js +28 -0
  19. package/dist/esm/stories/DataTable.stories.js.map +1 -1
  20. package/dist/esm/stories/MultiPageSheet.stories.d.ts +1 -0
  21. package/dist/esm/stories/MultiPageSheet.stories.d.ts.map +1 -1
  22. package/dist/esm/stories/MultiPageSheet.stories.js +154 -1
  23. package/dist/esm/stories/MultiPageSheet.stories.js.map +1 -1
  24. package/dist/sparkle.css +5 -4
  25. package/package.json +1 -1
  26. package/src/components/DataTable.tsx +115 -111
  27. package/src/components/Dropdown.tsx +21 -21
  28. package/src/components/MultiPageSheet.tsx +6 -0
  29. package/src/components/Sheet.tsx +17 -10
  30. package/src/stories/DataTable.stories.tsx +61 -0
  31. package/src/stories/MultiPageSheet.stories.tsx +214 -1
@@ -370,25 +370,25 @@ const DropdownMenuItem = React.forwardRef<
370
370
  ref
371
371
  ) => {
372
372
  return (
373
- <LinkWrapper
374
- href={href}
375
- target={target}
376
- rel={rel}
377
- replace={replace}
378
- shallow={shallow}
379
- prefetch={prefetch}
373
+ <DropdownMenuPrimitive.Item
374
+ ref={ref}
375
+ className={cn(
376
+ menuStyleClasses.item({ variant }),
377
+ inset ? menuStyleClasses.inset : "",
378
+ className
379
+ )}
380
+ {...props}
381
+ asChild={asChild}
380
382
  >
381
- <DropdownMenuPrimitive.Item
382
- ref={ref}
383
- className={cn(
384
- menuStyleClasses.item({ variant }),
385
- inset ? menuStyleClasses.inset : "",
386
- className
387
- )}
388
- {...props}
389
- asChild={asChild}
390
- >
391
- <div className="s-h-full s-w-full">
383
+ <div className="s-h-full s-w-full">
384
+ <LinkWrapper
385
+ href={href}
386
+ target={target}
387
+ rel={rel}
388
+ replace={replace}
389
+ shallow={shallow}
390
+ prefetch={prefetch}
391
+ >
392
392
  <ItemWithLabelIconAndDescription
393
393
  label={label}
394
394
  icon={icon}
@@ -398,9 +398,9 @@ const DropdownMenuItem = React.forwardRef<
398
398
  >
399
399
  {children}
400
400
  </ItemWithLabelIconAndDescription>
401
- </div>
402
- </DropdownMenuPrimitive.Item>
403
- </LinkWrapper>
401
+ </LinkWrapper>
402
+ </div>
403
+ </DropdownMenuPrimitive.Item>
404
404
  );
405
405
  }
406
406
  );
@@ -22,6 +22,11 @@ interface MultiPageSheetPage {
22
22
  content: React.ReactNode;
23
23
  fixedContent?: React.ReactNode;
24
24
  footerContent?: React.ReactNode;
25
+ /**
26
+ * Remove the default ScrollArea in the SheetContainer.
27
+ * To be used if you want to manage the scroll yourself
28
+ */
29
+ noScroll?: boolean;
25
30
  }
26
31
 
27
32
  interface MultiPageSheetProps {
@@ -231,6 +236,7 @@ const MultiPageSheetContent = React.forwardRef<
231
236
  )}
232
237
  <SheetContainer
233
238
  className={currentPage.fixedContent ? "s-flex-1" : undefined}
239
+ noScroll={currentPage.noScroll}
234
240
  >
235
241
  {currentPage.content}
236
242
  </SheetContainer>
@@ -180,18 +180,25 @@ const SheetHeader = ({
180
180
  );
181
181
  SheetHeader.displayName = "SheetHeader";
182
182
 
183
- const SheetContainer = ({ children }: React.HTMLAttributes<HTMLDivElement>) => {
183
+ interface SheetContainerProps extends React.HTMLAttributes<HTMLDivElement> {
184
+ noScroll?: boolean;
185
+ }
186
+
187
+ const SheetContainer = ({ children, noScroll }: SheetContainerProps) => {
188
+ const ScrollContainer = noScroll ? React.Fragment : ScrollArea;
184
189
  return (
185
- <ScrollArea
186
- className={cn(
187
- "s-h-full s-w-full s-flex-grow",
188
- "s-border-t s-border-border/60 s-transition-all s-duration-300 dark:s-border-border-night/60"
189
- )}
190
- >
191
- <div className="s-relative s-flex s-h-full s-flex-col s-gap-5 s-p-5 s-text-left s-text-sm s-text-foreground dark:s-text-foreground-night">
192
- {children}
190
+ <ScrollContainer>
191
+ <div
192
+ className={cn(
193
+ "s-h-full s-w-full s-flex-grow",
194
+ "s-border-t s-border-border/60 s-transition-all s-duration-300 dark:s-border-border-night/60"
195
+ )}
196
+ >
197
+ <div className="s-relative s-flex s-h-full s-flex-col s-gap-5 s-p-5 s-text-left s-text-sm s-text-foreground dark:s-text-foreground-night">
198
+ {children}
199
+ </div>
193
200
  </div>
194
- </ScrollArea>
201
+ </ScrollContainer>
195
202
  );
196
203
  };
197
204
  SheetContainer.displayName = "SheetContainer";
@@ -607,6 +607,67 @@ export const ScrollableDataTableExample = () => {
607
607
  );
608
608
  };
609
609
 
610
+ export const ScrollableDataTableFullHeightExample = () => {
611
+ const [filter, setFilter] = useState("");
612
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
613
+ const [data, setData] = useState(() => createData(0, 50));
614
+ const [isLoading, setIsLoading] = useState(false);
615
+
616
+ // Load more data when user scrolls to bottom
617
+ const loadMore = useCallback(() => {
618
+ setIsLoading(true);
619
+
620
+ // Simulate API call delay
621
+ setTimeout(() => {
622
+ setData((prevData) => [...prevData, ...createData(prevData.length, 50)]);
623
+ setIsLoading(false);
624
+ }, 1000);
625
+ }, []);
626
+
627
+ const columnsWithSize = columns.map((column, index) => {
628
+ return { ...column, meta: { sizeRatio: index % 2 === 0 ? 15 : 10 } };
629
+ });
630
+
631
+ const columnsWithSelection: ColumnDef<Data>[] = useMemo(
632
+ () => [createSelectionColumn<Data>(), ...columnsWithSize],
633
+ []
634
+ );
635
+ return (
636
+ <div className="s-flex s-w-full s-max-w-4xl s-flex-col s-gap-6">
637
+ <h3 className="s-text-lg s-font-medium">
638
+ Virtualized ScrollableDataTable with Infinite Scrolling based on parent
639
+ height
640
+ </h3>
641
+
642
+ <div className="s-flex s-h-[400px] s-flex-col s-gap-4">
643
+ <Input
644
+ name="filter"
645
+ placeholder="Filter"
646
+ value={filter}
647
+ onChange={(e) => setFilter(e.target.value)}
648
+ />
649
+
650
+ <ScrollableDataTable
651
+ data={data}
652
+ filter={filter}
653
+ filterColumn="name"
654
+ columns={columnsWithSelection}
655
+ onLoadMore={loadMore}
656
+ isLoading={isLoading}
657
+ maxHeight
658
+ rowSelection={rowSelection}
659
+ setRowSelection={setRowSelection}
660
+ enableRowSelection={true}
661
+ />
662
+
663
+ <div className="s-text-sm s-text-muted-foreground">
664
+ Loaded {data.length} rows. Scroll to the bottom to load more.
665
+ </div>
666
+ </div>
667
+ </div>
668
+ );
669
+ };
670
+
610
671
  export const DataTableWithRowSelectionExample = () => {
611
672
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
612
673
  const [data] = useState<Data[]>(() => createData(0, 10));
@@ -1,7 +1,9 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
- import React, { useState } from "react";
2
+ import type { ColumnDef } from "@tanstack/react-table";
3
+ import React, { useCallback, useState } from "react";
3
4
 
4
5
  import { Button } from "@sparkle/components/Button";
6
+ import { ScrollableDataTable } from "@sparkle/components/DataTable";
5
7
  import {
6
8
  MultiPageSheet,
7
9
  MultiPageSheetContent,
@@ -466,3 +468,214 @@ export const WithConditionalNavigation: Story = {
466
468
  );
467
469
  },
468
470
  };
471
+
472
+ // Sample data types for the ScrollableDataTable
473
+ interface UserData {
474
+ id: string;
475
+ name: string;
476
+ email: string;
477
+ role: string;
478
+ status: string;
479
+ lastActive: string;
480
+ onClick?: () => void;
481
+ }
482
+
483
+ // Generate random user data
484
+ const generateRandomUsers = (
485
+ count: number,
486
+ startId: number = 0
487
+ ): UserData[] => {
488
+ const roles = ["Admin", "User", "Manager", "Developer", "Designer"];
489
+ const statuses = ["Active", "Inactive", "Pending"];
490
+ const firstNames = [
491
+ "John",
492
+ "Jane",
493
+ "Mike",
494
+ "Sarah",
495
+ "David",
496
+ "Lisa",
497
+ "Tom",
498
+ "Anna",
499
+ "Chris",
500
+ "Emma",
501
+ ];
502
+ const lastNames = [
503
+ "Smith",
504
+ "Johnson",
505
+ "Williams",
506
+ "Brown",
507
+ "Jones",
508
+ "Garcia",
509
+ "Miller",
510
+ "Davis",
511
+ "Rodriguez",
512
+ "Martinez",
513
+ ];
514
+
515
+ return Array.from({ length: count }, (_, index) => {
516
+ const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
517
+ const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
518
+ const id = (startId + index + 1).toString();
519
+
520
+ return {
521
+ id,
522
+ name: `${firstName} ${lastName}`,
523
+ email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
524
+ role: roles[Math.floor(Math.random() * roles.length)],
525
+ status: statuses[Math.floor(Math.random() * statuses.length)],
526
+ lastActive: new Date(
527
+ Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000
528
+ ).toLocaleDateString(),
529
+ onClick: () => alert(`Clicked on user: ${firstName} ${lastName}`),
530
+ };
531
+ });
532
+ };
533
+
534
+ export const WithScrollableDataTable: Story = {
535
+ render() {
536
+ const [currentPageId, setCurrentPageId] = useState("users");
537
+ const [users, setUsers] = useState<UserData[]>(() =>
538
+ generateRandomUsers(50)
539
+ );
540
+ const [isLoading, setIsLoading] = useState(false);
541
+ const [hasMore, setHasMore] = useState(true);
542
+
543
+ // Define columns for the data table
544
+ const columns: ColumnDef<UserData>[] = [
545
+ {
546
+ accessorKey: "name",
547
+ header: "Name",
548
+ cell: ({ row }) => (
549
+ <div className="s-font-medium">{row.getValue("name")}</div>
550
+ ),
551
+ meta: { sizeRatio: 25 },
552
+ },
553
+ {
554
+ accessorKey: "email",
555
+ header: "Email",
556
+ cell: ({ row }) => (
557
+ <div className="s-text-muted-foreground">{row.getValue("email")}</div>
558
+ ),
559
+ meta: { sizeRatio: 30 },
560
+ },
561
+ {
562
+ accessorKey: "role",
563
+ header: "Role",
564
+ cell: ({ row }) => (
565
+ <div className="s-inline-flex s-rounded-full s-bg-blue-100 s-px-2 s-py-1 s-text-xs s-font-semibold s-text-blue-800">
566
+ {row.getValue("role")}
567
+ </div>
568
+ ),
569
+ meta: { sizeRatio: 15 },
570
+ },
571
+ {
572
+ accessorKey: "status",
573
+ header: "Status",
574
+ cell: ({ row }) => {
575
+ const status = row.getValue("status") as string;
576
+ const colorClass =
577
+ status === "Active"
578
+ ? "s-bg-green-100 s-text-green-800"
579
+ : status === "Inactive"
580
+ ? "s-bg-red-100 s-text-red-800"
581
+ : "s-bg-yellow-100 s-text-yellow-800";
582
+
583
+ return (
584
+ <div
585
+ className={`s-inline-flex s-rounded-full s-px-2 s-py-1 s-text-xs s-font-semibold ${colorClass}`}
586
+ >
587
+ {status}
588
+ </div>
589
+ );
590
+ },
591
+ meta: { sizeRatio: 15 },
592
+ },
593
+ {
594
+ accessorKey: "lastActive",
595
+ header: "Last Active",
596
+ cell: ({ row }) => (
597
+ <div className="s-text-sm s-text-muted-foreground">
598
+ {row.getValue("lastActive")}
599
+ </div>
600
+ ),
601
+ meta: { sizeRatio: 15 },
602
+ },
603
+ ];
604
+
605
+ // Handle infinite loading
606
+ const handleLoadMore = useCallback(() => {
607
+ if (isLoading || !hasMore) {
608
+ return;
609
+ }
610
+
611
+ setIsLoading(true);
612
+
613
+ // Simulate API call delay
614
+ setTimeout(() => {
615
+ const newUsers = generateRandomUsers(25, users.length);
616
+ setUsers((prev) => [...prev, ...newUsers]);
617
+ setIsLoading(false);
618
+
619
+ // Stop loading more after reaching 200 items for demo purposes
620
+ if (users.length >= 175) {
621
+ setHasMore(false);
622
+ }
623
+ }, 1000);
624
+ }, [isLoading, hasMore, users.length]);
625
+
626
+ const handleSave = () => {
627
+ alert("User data saved!");
628
+ };
629
+
630
+ const scrollableDataTablePages: MultiPageSheetPage[] = [
631
+ {
632
+ id: "users",
633
+ title: "User Management",
634
+ description: "Manage users with infinite scroll",
635
+ icon: UserIcon,
636
+ noScroll: true,
637
+ content: (
638
+ <div className="s-flex s-h-full s-flex-col s-space-y-4">
639
+ <div className="s-flex-shrink-0">
640
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
641
+ Users Database
642
+ </h3>
643
+ <p className="s-text-sm s-text-muted-foreground">
644
+ Browse through all users with infinite scrolling. Click on any
645
+ row to view details.
646
+ </p>
647
+ </div>
648
+ <ScrollableDataTable
649
+ className="s-min-h-0"
650
+ data={users}
651
+ columns={columns}
652
+ maxHeight={true}
653
+ onLoadMore={hasMore ? handleLoadMore : undefined}
654
+ isLoading={isLoading}
655
+ enableRowSelection={false}
656
+ />
657
+ <div className="s-flex-shrink-0 s-text-xs s-text-muted-foreground">
658
+ Showing {users.length} users{" "}
659
+ {hasMore ? "(loading more available)" : "(all users loaded)"}
660
+ </div>
661
+ </div>
662
+ ),
663
+ },
664
+ ];
665
+
666
+ return (
667
+ <MultiPageSheet>
668
+ <MultiPageSheetTrigger asChild>
669
+ <Button label="Open User Management" />
670
+ </MultiPageSheetTrigger>
671
+ <MultiPageSheetContent
672
+ pages={scrollableDataTablePages}
673
+ currentPageId={currentPageId}
674
+ onPageChange={setCurrentPageId}
675
+ size="xl"
676
+ onSave={handleSave}
677
+ />
678
+ </MultiPageSheet>
679
+ );
680
+ },
681
+ };