Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin-ui): DataTable + Skeleton components #4489

Open
wants to merge 27 commits into
base: feat/new-admin-ui
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
11bc8ec
wip: table and datatable
leopuleo Jan 7, 2025
ab19f33
feat: add Skeleton
leopuleo Jan 8, 2025
71f80af
feat: remove label from Checkbox
leopuleo Jan 8, 2025
cc7e71e
wip: data table
leopuleo Jan 8, 2025
dcc80d2
wip: data table
leopuleo Jan 9, 2025
ee8c459
Merge branch 'feat/new-admin-ui' into leo/feat/ui-datatable
leopuleo Jan 13, 2025
a6541cc
wip: export types
leopuleo Jan 13, 2025
4f9d7f1
fix: column direction
leopuleo Jan 13, 2025
8ba00f0
fix: checkbox onclick
leopuleo Jan 13, 2025
c7d2bae
fix: dropdown checkbox
leopuleo Jan 13, 2025
2a62fb7
docs: create Skeleton story
leopuleo Jan 13, 2025
45208dd
chore: remove packages
leopuleo Jan 13, 2025
6e69fa8
docs: add deprecated
leopuleo Jan 13, 2025
efecbbd
fix: datatable stickyRows props
leopuleo Jan 13, 2025
42887da
refactor: add skeleton
leopuleo Jan 13, 2025
22720e7
refactor: add hasLabel to Checkbox vm
leopuleo Jan 13, 2025
f1124d5
refactor: add hasLabel to Checkbox vm
leopuleo Jan 13, 2025
3862c39
chore: fix adio
leopuleo Jan 13, 2025
9411e6c
fix: various DataTable
leopuleo Jan 20, 2025
8869f43
fix: various DataTable
leopuleo Jan 20, 2025
d82814c
fix: various DataTable
leopuleo Jan 20, 2025
1d601d8
fix: revert Skeleton classNames update
leopuleo Jan 21, 2025
0f41f9a
fix: various DataTable
leopuleo Jan 21, 2025
34059bc
fix: various Skeleton
leopuleo Jan 21, 2025
3735745
Merge branch 'feat/new-admin-ui' into leo/feat/ui-datatable
leopuleo Jan 22, 2025
a9687ed
fix: first column render
leopuleo Jan 22, 2025
aaad35e
chore: export type ResizerProps
leopuleo Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-table": "^8.20.6",
"@webiny/react-composition": "0.0.0",
"@webiny/react-router": "0.0.0",
"@webiny/utils": "0.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-ui/src/Checkbox/CheckboxItemDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

export interface CheckboxItemDto {
id?: string;
label: string | React.ReactNode;
label?: string | React.ReactNode;
value?: number | string;
checked?: boolean;
indeterminate?: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/Checkbox/CheckboxItemFormatted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface CheckboxItemFormatted {
checked: boolean;
indeterminate: boolean;
disabled: boolean;
hasLabel: boolean;
}
3 changes: 2 additions & 1 deletion packages/admin-ui/src/Checkbox/CheckboxItemMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export class CheckboxItemMapper {
value: item.value,
checked: item.checked,
indeterminate: item.indeterminate,
disabled: item.disabled
disabled: item.disabled,
hasLabel: !!item.label
};
}
}
14 changes: 5 additions & 9 deletions packages/admin-ui/src/Checkbox/CheckboxPresenter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ describe("CheckboxPresenter", () => {
label: "Label"
});
expect(presenter.vm.item?.label).toEqual("Label");
expect(presenter.vm.item?.hasLabel).toEqual(true);
}

// `id`
{
presenter.init({
onCheckedChange,
label: "Label",
id: "id"
});
expect(presenter.vm.item?.id).toEqual("id");
Expand All @@ -29,7 +29,6 @@ describe("CheckboxPresenter", () => {
{
presenter.init({
onCheckedChange,
label: "Label",
checked: true
});
expect(presenter.vm.item?.checked).toBeTruthy();
Expand All @@ -39,7 +38,6 @@ describe("CheckboxPresenter", () => {
{
presenter.init({
onCheckedChange,
label: "Label",
indeterminate: true
});
expect(presenter.vm.item?.indeterminate).toBeTruthy();
Expand All @@ -49,7 +47,6 @@ describe("CheckboxPresenter", () => {
{
presenter.init({
onCheckedChange,
label: "Label",
disabled: true
});
expect(presenter.vm.item?.disabled).toBeTruthy();
Expand All @@ -58,22 +55,21 @@ describe("CheckboxPresenter", () => {
// default: only mandatory props
{
presenter.init({
onCheckedChange,
label: "Label"
onCheckedChange
});
expect(presenter.vm.item).toEqual({
id: expect.any(String),
label: "Label",
checked: false,
indeterminate: false,
disabled: false
disabled: false,
hasLabel: false
});
}
});

it("should call `onCheckedChange` callback when `changeChecked` is called", () => {
const presenter = new CheckboxPresenter();
presenter.init({ onCheckedChange, label: "Label" });
presenter.init({ onCheckedChange });
presenter.changeChecked(true);
expect(onCheckedChange).toHaveBeenCalledWith(true);
});
Expand Down
24 changes: 15 additions & 9 deletions packages/admin-ui/src/Checkbox/CheckboxPrimitive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const IndeterminateIcon = () => {
*/
const checkboxVariants = cva(
[
"group peer h-md w-md shrink-0 rounded-sm border-sm mt-xxs",
"border-neutral-muted bg-neutral-base fill-neutral-base ring-offset-background",
"group peer h-md w-md shrink-0 rounded-sm border-sm ",
"border-neutral-muted bg-neutral-base [&_svg]:!fill-neutral-base ring-offset-background",
"hover:border-neutral-dark",
"focus:outline-none focus-visible:border-accent-default focus-visible:ring-lg focus-visible:ring-primary-dimmed focus-visible:ring-offset-0",
"disabled:cursor-not-allowed disabled:border-transparent disabled:bg-neutral-disabled",
Expand All @@ -44,6 +44,9 @@ const checkboxVariants = cva(
"data-[state=checked]:focus-visible:border-accent-default",
"data-[state=checked]:disabled:border-transparent"
]
},
hasLabel: {
true: "mt-xxs"
}
}
}
Expand All @@ -70,24 +73,27 @@ type CheckboxPrimitiveRendererProps = Omit<CheckboxPrimitiveProps, "onCheckedCha
const DecoratableCheckboxPrimitiveRenderer = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitives.Root>,
CheckboxPrimitiveRendererProps
>(({ label, id, indeterminate, changeChecked, className, ...props }, ref) => {
>(({ label, id, hasLabel, indeterminate, changeChecked, className, ...props }, ref) => {
return (
<div className="flex items-start space-x-sm-extra">
<CheckboxPrimitives.Root
ref={ref}
{...props}
id={id}
className={cn(checkboxVariants({ indeterminate }), className)}
className={cn(checkboxVariants({ indeterminate, hasLabel }), className)}
onCheckedChange={changeChecked}
>
<span className={cn("flex items-center justify-center")}>
{indeterminate && <IndeterminateIcon />}
<CheckboxPrimitives.Indicator>
<CheckIcon className={"h-sm-extra w-sm-extra"} />
</CheckboxPrimitives.Indicator>
{indeterminate ? (
<IndeterminateIcon />
) : (
<CheckboxPrimitives.Indicator>
<CheckIcon className={"!size-sm-extra"} />
</CheckboxPrimitives.Indicator>
)}
</span>
</CheckboxPrimitives.Root>
<Label id={id} text={label} weight={"light"} className={"text-md"} />
{hasLabel && <Label id={id} text={label} weight={"light"} className={"text-md"} />}
</div>
);
});
Expand Down
226 changes: 226 additions & 0 deletions packages/admin-ui/src/DataTable/DataTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import React, { useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { DataTableColumns, DataTable, DataTableDefaultData, DataTableSorting } from "./DataTable";
import { Avatar } from "~/Avatar";
import { Text } from "~/Text";

const meta: Meta<typeof DataTable> = {
title: "Components/DataTable",
component: DataTable,
tags: ["autodocs"],
parameters: {
layout: "padded"
}
};

export default meta;
type Story<T extends Record<string, any> & DataTableDefaultData> = StoryObj<typeof DataTable<T>>;

// Declare the data structure.
interface Entry {
id: string;
name: string;
createdBy: string;
lastModified: string;
status: string;
$selectable?: boolean;
}

// Define the columns structure for the table.
const columns: DataTableColumns<Entry> = {
name: {
header: "Title"
},
createdBy: {
header: "Author"
},
lastModified: {
header: "Last Modified"
},
status: {
header: "Status"
}
};

// Define the data to display.
function generateEntries(count = 20) {
const statuses = ["Draft", "Published", "Unpublished"];
const randomStatus = () => statuses[Math.floor(Math.random() * statuses.length)];
const randomTime = () => {
const times = ["1 hour ago", "3 hours ago", "1 day ago", "3 days ago", "1 week ago"];
return times[Math.floor(Math.random() * times.length)];
};

const entries = [];
for (let i = 1; i <= count; i++) {
entries.push({
id: `entry-${i}`,
name: `Entry ${i}`,
createdBy: "John Doe",
lastModified: randomTime(),
status: randomStatus()
});
}

return entries;
}

const data = generateEntries();

export const Default: Story<Entry> = {
args: {
data,
columns
}
};

export const Bordered: Story<Entry> = {
args: {
...Default.args,
bordered: true
}
};

export const WithStickyHeader: Story<Entry> = {
args: {
...Default.args,
stickyHeader: true
}
};

export const WithSelectableRows: Story<Entry> = {
args: Default.args,
render: args => {
const [selectedRows, setSelectedRows] = useState<Entry[]>([]);

return (
<DataTable
{...args}
selectedRows={selectedRows}
onSelectRow={rows => setSelectedRows(rows)}
/>
);
}
};

export const WithLoading: Story<Entry> = {
args: {
...Default.args,
loading: true
}
};

export const WithLongColumnContent: Story<Entry> = {
args: {
...Default.args,
columns: {
...columns,
name: {
...columns.name,
header: "Name - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}
},
data: data.map(entry => ({
...entry,
name: `${entry.name} - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla facilisi. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`
}))
}
};

export const WithCustomCellRenderer: Story<Entry> = {
args: {
...Default.args,
columns: {
...columns,
name: {
...columns.name,
cell: (entry: Entry) => {
return (
<div className={"flex items-center gap-sm-extra"}>
<Avatar
image={
<Avatar.Image
src="https://github.com/webiny-bot.png"
alt="@webiny"
/>
}
fallback={<Avatar.Fallback>{entry.name.charAt(0)}</Avatar.Fallback>}
size={"xl"}
/>
<div>
<Text
text={entry.name}
className={"text-neutral-primary font-semibold"}
as={"div"}
/>
<Text
text={`Last updated: ${entry.lastModified}`}
size={"sm"}
className={"text-neutral-strong"}
as={"div"}
/>
</div>
</div>
);
}
}
}
}
};

export const WithCustomColumnSize: Story<Entry> = {
args: {
...Default.args,
columns: {
...columns,
name: {
...columns.name,
size: 200
}
}
}
};

export const WithCustomColumnClassName: Story<Entry> = {
args: {
...Default.args,
columns: {
...columns,
lastModified: {
...columns.lastModified,
className: "bg-primary-subtle"
},
status: {
...columns.status,
className: "text-right"
}
}
}
};

export const WithSorting: Story<Entry> = {
args: {
...Default.args,
columns: {
...columns,
name: {
...columns.name,
enableSorting: true
},
lastModified: {
...columns.lastModified,
enableSorting: true
}
},
sorting: [
{
id: "lastModified",
desc: true
}
]
},
render: ({ sorting: argsSorting = [], ...args }) => {
const [sorting, setSorting] = useState<DataTableSorting>(argsSorting);
return <DataTable {...args} sorting={sorting} onSortingChange={setSorting} />;
}
};
Loading
Loading