From 2a465e4b208d973d0b343121c9d475e827f29e9e Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 16:15:14 +0100 Subject: [PATCH 1/6] :sparkles: #28 - feat: add severial aliases to paginator props in datagrid for easier use --- src/components/data/datagrid/datagrid.tsx | 68 ++++++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/components/data/datagrid/datagrid.tsx b/src/components/data/datagrid/datagrid.tsx index 6a367fef..161050c1 100644 --- a/src/components/data/datagrid/datagrid.tsx +++ b/src/components/data/datagrid/datagrid.tsx @@ -57,9 +57,15 @@ export type DataGridProps = { /** Props for Bool. */ boolProps?: Omit; - /** If set, the paginator is enabled. */ + /** + * If set, the paginator is enabled. + * @see {PaginatorPropsAliases} + */ paginatorProps?: PaginatorProps; + /** Defaults to whether paginatorProps is set. */ + showPaginator?: boolean; + /** Props for P. */ pProps?: PProps; @@ -67,6 +73,20 @@ export type DataGridProps = { title?: string; onSort?: (sort: string) => Promise | void; +} & PaginatorPropsAliases; + +/** + * A subset of `PaginatorProps` that act as aliases. + * @see {PaginatorProps} + */ +type PaginatorPropsAliases = { + count?: PaginatorProps["count"]; + loading?: PaginatorProps["loading"]; + page?: PaginatorProps["page"]; + pageSize?: PaginatorProps["pageSize"]; + pageSizeOptions?: PaginatorProps["pageSizeOptions"]; + onPageChange?: PaginatorProps["onPageChange"]; + onPageSizeChange?: PaginatorProps["onPageSizeChange"]; }; /** @@ -78,10 +98,18 @@ export type DataGridProps = { * @param paginatorProps * @param results * @param fields + * @param showPaginator * @param pProps * @param sort * @param title * @param urlFields + * @param count + * @param loading + * @param page + * @param pageSize + * @param pageSizeOptions + * @param onPageChange + * @param onPageSizeChange * @param props * @constructor */ @@ -92,11 +120,20 @@ export const DataGrid: React.FC = ({ results, fields = results?.length ? Object.keys(results[0]) : [], paginatorProps, + showPaginator = Boolean(paginatorProps), pProps, sort, title = "", urlFields = DEFAULT_URL_FIELDS, onSort, + // Aliases + count, + loading, + page, + pageSize, + pageSizeOptions, + onPageChange, + onPageSizeChange, ...props }) => { const id = useId(); @@ -141,6 +178,22 @@ export const DataGrid: React.FC = ({ const page = paginatorProps?.page; const key = `sort-${sortField}${sortDirection}-page-${page}-row-$${rowIndex}-column-${fieldIndex}`; + // Run assertions for aliased fields. + if (showPaginator) { + console.assert( + count || paginatorProps?.count, + "Either `count` or `paginatorProps.count` should be set when `showPaginator` is `true`.", + ); + console.assert( + page || paginatorProps?.page, + "Either `page` or `paginatorProps.page` should be set when `showPaginator` is `true`.", + ); + console.assert( + pageSize || paginatorProps?.pageSize, + "Either `pageSize` or `paginatorProps.pageSize` should be set when `showPaginator` is `true`.", + ); + } + return ( = ({ {/* Paginator */} - {paginatorProps && ( + {showPaginator && ( = ({ colSpan={renderableFields.length} > - + From d855085545f788be333c7dc480c7184d410c9ff8 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 16:16:35 +0100 Subject: [PATCH 2/6] :white_check_mark: #28 - test: update datagrid jsonplaceholder test to use online paginated and sorted data --- .../data/datagrid/datagrid.stories.tsx | 73 ++++++++----------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/src/components/data/datagrid/datagrid.stories.tsx b/src/components/data/datagrid/datagrid.stories.tsx index 5bcf1f4e..a8161938 100644 --- a/src/components/data/datagrid/datagrid.stories.tsx +++ b/src/components/data/datagrid/datagrid.stories.tsx @@ -1,9 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react"; import React, { useEffect, useState } from "react"; -import { sortAttributeDataArray } from "../../../lib/data/attributedata"; +import { AttributeData } from "../../../lib/data/attributedata"; import { Page } from "../../page"; -import { PaginatorProps } from "../paginator"; import { DataGrid } from "./datagrid"; const meta = { @@ -143,19 +142,8 @@ export const SortedDataGrid: Story = { export const JSONPlaceholderExample: Story = { args: { - paginatorProps: { - count: 100, - page: 1, - pageSize: 10, - pageSizeOptions: [ - { label: 10 }, - { label: 20 }, - { label: 30 }, - { label: 40 }, - { label: 50 }, - ], - }, results: [], + showPaginator: true, sort: true, title: "Posts", }, @@ -165,40 +153,28 @@ export const JSONPlaceholderExample: Story = { const [pageSize, setPageSize] = useState( args.paginatorProps?.pageSize || 10, ); - const [results, setResults] = useState(args.results); + const [results, setResults] = useState([]); const [sort, setSort] = useState(""); - const paginatorProps = args.paginatorProps as PaginatorProps; - - paginatorProps.pageSize = pageSize; + /** + * Fetches data from jsonplaceholder.typicode.com. + */ useEffect(() => { setLoading(true); - const index = page - 1; const abortController = new AbortController(); + const sortKey = sort.replace(/^-/, ""); + const sortDirection = sort.startsWith("-") ? "desc" : "asc"; // Process sorting and pagination locally in place for demonstration purposes. - fetch("https://jsonplaceholder.typicode.com/posts", { - signal: abortController.signal, - }) + fetch( + `https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${pageSize}&_sort=${sortKey}&_order=${sortDirection}`, + { + signal: abortController.signal, + }, + ) .then((response) => response.json()) - .then((data) => { - // Sort. - const direction = String(sort).startsWith("-") ? "DESC" : "ASC"; - const sorted = sort - ? sortAttributeDataArray( - data, - String(sort).replace(/^-/, ""), - direction, - ) - : data; - - // Paginate. - const posts = sorted.slice( - index * pageSize, - index * pageSize + pageSize, - ); - - setResults(posts); + .then((data: AttributeData[]) => { + setResults(data); setLoading(false); }); @@ -208,15 +184,24 @@ export const JSONPlaceholderExample: Story = { }; }, [page, pageSize, sort]); - paginatorProps.loading = loading; - paginatorProps.onPageChange = (page) => setPage(page); - paginatorProps.onPageSizeChange = async (pageSize) => setPageSize(pageSize); - return ( setSort(field)} + loading={loading} + page={page} + pageSize={pageSize} + pageSizeOptions={[ + { label: 10 }, + { label: 20 }, + { label: 30 }, + { label: 40 }, + { label: 50 }, + ]} + onPageChange={setPage} + onPageSizeChange={setPageSize} /> ); }, From 6fb2f40b605f8685a9938e936e4ae9b7cf385263 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 16:28:08 +0100 Subject: [PATCH 3/6] :lipstick: #28 - style: alter the dark mode style for tranparent buttons --- src/style/tokens/dark.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/style/tokens/dark.scss b/src/style/tokens/dark.scss index 679111b4..e24eba68 100644 --- a/src/style/tokens/dark.scss +++ b/src/style/tokens/dark.scss @@ -15,8 +15,10 @@ --button-color-text-outline: #f3f3f3; --button-color-background-outline-hover: #f3f3f3; --button-color-background-outline-active: #f3f3f3; + --button-color-background-transparent-hover: tranparent; + --button-color-border-transparent-hover: #f3f3f3; + --button-color-border-transparent-active: #f3f3f3; --button-color-text-primary: #f3f3f3; - --button-color-text-transparent-active: #313741; - --button-color-background-transparent-hover: #f3f3f3; - --button-color-background-transparent-active: #f3f3f3; + --button-color-text-outline-active: #313741; + --button-color-text-transparent-hover: #f3f3f3; } From 22d36a78b090d9f699797599188f1eff74e64a66 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 16:37:04 +0100 Subject: [PATCH 4/6] :bug: #28 - style: fix incorrect color for spinner in paginator in darkm mode --- src/components/data/paginator/paginator.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/data/paginator/paginator.scss b/src/components/data/paginator/paginator.scss index 225a66df..c3ecda2d 100644 --- a/src/components/data/paginator/paginator.scss +++ b/src/components/data/paginator/paginator.scss @@ -15,6 +15,10 @@ margin-block-end: 0; } + &__section--form .mykn-icon:first-child { + color: var(--typography-color-body); + } + @media screen and (max-width: constants.$breakpoint-desktop - 1px) { &__section--form { display: none; From 7d2c66b457fb865919b0e05afc04e095257940cf Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 16:44:10 +0100 Subject: [PATCH 5/6] :construction_worker: #28 - ci: attempt to make mobile snapshots working on Chromatic --- .storybook/modes.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.storybook/modes.ts b/.storybook/modes.ts index da3cc7fd..ea9c41a7 100644 --- a/.storybook/modes.ts +++ b/.storybook/modes.ts @@ -7,7 +7,10 @@ export const allModes = { "light mobile": { backgrounds: "light", theme: "light", - viewport: "small", + viewport: { + width: 320, + height: 568, + }, }, "dark desktop": { backgrounds: "dark", @@ -17,6 +20,9 @@ export const allModes = { "dark mobile": { backgrounds: "dark", theme: "dark", - viewport: "small", + viewport: { + width: 320, + height: 568, + }, }, }; From 2d14aaae3053896ddeaf97232b5618640d8fb69c Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 13 Feb 2024 17:14:10 +0100 Subject: [PATCH 6/6] :white_check_mark: #28 - test: update Chromatic stories --- .../attributelist/attributelist.stories.tsx | 7 --- .../data/paginator/paginator.stories.tsx | 21 +++++--- src/components/dropdown/dropdown.stories.tsx | 7 --- src/components/navbar/navbar.stories.tsx | 48 +++++++++++++++++++ src/components/toolbar/toolbar.stories.tsx | 7 --- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/components/data/attributelist/attributelist.stories.tsx b/src/components/data/attributelist/attributelist.stories.tsx index 37bf9c76..bb39043e 100644 --- a/src/components/data/attributelist/attributelist.stories.tsx +++ b/src/components/data/attributelist/attributelist.stories.tsx @@ -30,13 +30,6 @@ export const AttributeListComponent: Story = { }, }; -export const AttributeListOnMobile: Story = { - ...AttributeListComponent, - parameters: { - viewport: { defaultViewport: "mobile1" }, - }, -}; - export const SelectedFieldOnly: Story = { ...AttributeListComponent, args: { diff --git a/src/components/data/paginator/paginator.stories.tsx b/src/components/data/paginator/paginator.stories.tsx index 10bda318..e0dc1202 100644 --- a/src/components/data/paginator/paginator.stories.tsx +++ b/src/components/data/paginator/paginator.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { expect, userEvent, waitFor, within } from "@storybook/test"; import React from "react"; +import { allModes } from "../../../../.storybook/modes"; import { Page } from "../../page"; import { Paginator } from "./paginator"; @@ -39,20 +40,26 @@ export const PaginatorComponent: Story = { }, }; -export const PaginatorOnMobile = { - ...PaginatorComponent, - parameters: { - viewport: { defaultViewport: "mobile1" }, - }, -}; - export const PaginatorComponentWithSpinner: Story = { ...PaginatorComponent, args: { ...PaginatorComponent.args, onPageChange: () => new Promise((resolve) => setTimeout(resolve, 1000)), }, + parameters: { + chromatic: { + modes: { + "light desktop": allModes["light desktop"], + "dark desktop": allModes["dark desktop"], + }, + }, + }, play: async ({ canvasElement }) => { + // Spinner not supported on mobile. + if (window?.matchMedia("(max-width: 767px)").matches) { + return; + } + const canvas = within(canvasElement); const pageInput = canvas.getByRole("spinbutton"); const previousButton = canvas.getByLabelText("Previous"); diff --git a/src/components/dropdown/dropdown.stories.tsx b/src/components/dropdown/dropdown.stories.tsx index 31cd45a0..fbc57e3b 100644 --- a/src/components/dropdown/dropdown.stories.tsx +++ b/src/components/dropdown/dropdown.stories.tsx @@ -98,13 +98,6 @@ export const DropdownComponent: Story = { }, }; -export const DropdownOnMobile: Story = { - ...DropdownComponent, - parameters: { - viewport: { defaultViewport: "mobile1" }, - }, -}; - export const ActivateOnHover: Story = { args: { activateOnHover: true, diff --git a/src/components/navbar/navbar.stories.tsx b/src/components/navbar/navbar.stories.tsx index ddd4a746..eadf0b84 100644 --- a/src/components/navbar/navbar.stories.tsx +++ b/src/components/navbar/navbar.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { expect, userEvent, waitFor, within } from "@storybook/test"; import React from "react"; +import { allModes } from "../../../.storybook/modes"; import { Button, ButtonLink } from "../button"; import { Outline } from "../icon"; import { Page } from "../page"; @@ -51,4 +53,50 @@ export const NavbarComponent: Story = { ), }, + parameters: { + chromatic: { + modes: { + "light desktop": allModes["light desktop"], + "dark desktop": allModes["dark desktop"], + }, + }, + }, +}; + +export const NavbarOnMobile: Story = { + ...NavbarComponent, + parameters: { + viewport: { defaultViewport: "mobile1" }, + chromatic: { + modes: { + "light mobile": allModes["light mobile"], + "dark mobile": allModes["dark mobile"], + }, + }, + }, + play: async ({ canvasElement }) => { + // Hamburger menu not supported on desktop. + if (window?.matchMedia("(min-width: 768px)").matches) { + return; + } + const canvas = within(canvasElement); + const button = canvas.getByRole("button"); + + // Click opens, escape closes. + await userEvent.click(button, { delay: 10 }); + await expect(canvas.getByRole("dialog")).toBeVisible(); + await userEvent.keyboard("{Escape}", { delay: 10 }); + await waitFor(() => expect(canvas.queryByRole("dialog")).toBeNull()); + await userEvent.click(button, { delay: 10 }); + await expect(canvas.getByRole("dialog")).toBeVisible(); + + // Tab focuses items. + await userEvent.tab({ delay: 10 }); + await userEvent.tab({ delay: 10 }); + await userEvent.tab({ delay: 10 }); + + expect(canvas.getByRole("dialog")).toContainElement( + document.activeElement as HTMLElement, + ); + }, }; diff --git a/src/components/toolbar/toolbar.stories.tsx b/src/components/toolbar/toolbar.stories.tsx index 27e31449..724e33a5 100644 --- a/src/components/toolbar/toolbar.stories.tsx +++ b/src/components/toolbar/toolbar.stories.tsx @@ -55,13 +55,6 @@ export const ToolbarComponent: Story = { }, }; -export const ToolbarOnMobile: Story = { - ...ToolbarComponent, - parameters: { - viewport: { defaultViewport: "mobile1" }, - }, -}; - export const TransparentToolbar: Story = { ...ToolbarComponent, args: {