From 6baf889bf992419bdcbd2bdd3f8292584c5744a6 Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Thu, 18 Jan 2024 15:49:30 +0200 Subject: [PATCH 01/24] fix: move useLocation within useNotify hook to cater for latest react-router-dom limitations Signed-off-by: Mason Hu --- .../NotificationProvider.tsx | 26 +++++++++++-------- src/components/NotificationProvider/types.ts | 1 + 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/NotificationProvider/NotificationProvider.tsx b/src/components/NotificationProvider/NotificationProvider.tsx index fcf73fb13..97cb6664d 100644 --- a/src/components/NotificationProvider/NotificationProvider.tsx +++ b/src/components/NotificationProvider/NotificationProvider.tsx @@ -24,13 +24,13 @@ const NotifyContext = createContext({ success: () => undefined, info: () => undefined, queue: () => undefined, + setDeduplicated: () => undefined, }); export const NotificationProvider: FC = ({ children }) => { const [notification, setNotification] = useState( null ); - const { state, pathname } = useLocation() as QueuedNotification; const clear = () => notification !== null && setNotification(null); @@ -41,15 +41,6 @@ export const NotificationProvider: FC = ({ children }) => { return value; }; - useEffect(() => { - if (state?.queuedNotification) { - setDeduplicated(state.queuedNotification); - window.history.replaceState({}, ""); - } else { - clear(); - } - }, [state, pathname]); - const helper: NotificationHelper = { notification, clear, @@ -58,6 +49,7 @@ export const NotificationProvider: FC = ({ children }) => { setDeduplicated(failure(title, error, message, actions)), info: (message, title) => setDeduplicated(info(message, title)), success: (message) => setDeduplicated(success(message)), + setDeduplicated, }; return ( @@ -66,7 +58,19 @@ export const NotificationProvider: FC = ({ children }) => { }; export function useNotify() { - return useContext(NotifyContext); + const ctx = useContext(NotifyContext); + const { state, pathname } = useLocation() as QueuedNotification; + + useEffect(() => { + if (state?.queuedNotification) { + ctx.setDeduplicated(state.queuedNotification); + window.history.replaceState({}, ""); + } else { + ctx.clear(); + } + }, [state, pathname]); + + return ctx; } export const NotificationConsumer: FC = () => { diff --git a/src/components/NotificationProvider/types.ts b/src/components/NotificationProvider/types.ts index b0f8d5e47..f45bfc500 100644 --- a/src/components/NotificationProvider/types.ts +++ b/src/components/NotificationProvider/types.ts @@ -37,4 +37,5 @@ export interface NotificationHelper { info: (message: ReactNode, title?: string) => NotificationType; success: (message: ReactNode) => NotificationType; queue: (notification: NotificationType) => QueuedNotification; + setDeduplicated: (value: NotificationType) => NotificationType; } From 9e9ffc5e3487ce14962b0ea261bce73296a10060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 04:45:29 +0000 Subject: [PATCH 02/24] build(deps): bump follow-redirects from 1.15.3 to 1.15.4 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3764a459f..f34e2ed9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7061,9 +7061,9 @@ flow-parser@0.*: integrity sha512-RoM3ARqVYvxnwtkM36RjQFzo5Z9p22jUqtuMrN8gzA/8fU6iMLFE3cXkdSFPyfHRXLU8ILH8TCtSFADk1ACPCg== follow-redirects@^1.14.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== for-each@^0.3.3: version "0.3.3" From b27d6e9d294d2f5e0843cb2860d080afbc4969ed Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Tue, 12 Dec 2023 12:42:20 +0200 Subject: [PATCH 03/24] feat: improve auto grow functionality for TextArea Signed-off-by: Mason Hu --- cypress.config.ts | 1 + cypress/e2e/textArea.cy.js | 24 ++++++++++++++++++++ src/components/Textarea/Textarea.stories.mdx | 15 ++++++++++++ src/components/Textarea/Textarea.tsx | 22 ++++++++++++++---- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 cypress/e2e/textArea.cy.js diff --git a/cypress.config.ts b/cypress.config.ts index c5eb52825..76b9e10fa 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -8,4 +8,5 @@ export default defineConfig({ return require("./cypress/plugins/index.js")(on, config); }, }, + experimentalStudio: true, }); diff --git a/cypress/e2e/textArea.cy.js b/cypress/e2e/textArea.cy.js new file mode 100644 index 000000000..c42881c0f --- /dev/null +++ b/cypress/e2e/textArea.cy.js @@ -0,0 +1,24 @@ +context("TextArea", () => { + beforeEach(() => { + cy.visitPage("TextArea", "Dynamic-Height"); + }); + + it("should adjust height automatically with text content change", () => { + let initialHeight = 0; + cy.findByRole("textbox").then(($element) => { + initialHeight = $element.height(); + }); + cy.findByRole("textbox").focus(); + cy.findByRole("textbox").type("{Enter}{Enter}{Enter}"); + cy.findByRole("textbox").then(($element) => { + const finalHeight = $element.height(); + expect(finalHeight).to.be.greaterThan(initialHeight); + initialHeight = finalHeight; + }); + cy.findByRole("textbox").clear(); + cy.findByRole("textbox").then(($element) => { + const finalHeight = $element.height(); + expect(finalHeight).to.be.lessThan(initialHeight); + }); + }); +}); diff --git a/src/components/Textarea/Textarea.stories.mdx b/src/components/Textarea/Textarea.stories.mdx index 55eb5b5bb..04d790464 100644 --- a/src/components/Textarea/Textarea.stories.mdx +++ b/src/components/Textarea/Textarea.stories.mdx @@ -66,3 +66,18 @@ The Textarea component defines a multi-line text input control. {Template.bind({})} + +### Dynamic Height + + + + {Template.bind({})} + + diff --git a/src/components/Textarea/Textarea.tsx b/src/components/Textarea/Textarea.tsx index a114c7ca7..39ec6fd8e 100644 --- a/src/components/Textarea/Textarea.tsx +++ b/src/components/Textarea/Textarea.tsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; import type { TextareaHTMLAttributes, ReactNode } from "react"; import Field from "../Field"; @@ -90,6 +90,7 @@ const Textarea = ({ const validationId = useId(); const helpId = useId(); const hasError = !!error; + const [innerValue, setInnervalue] = useState(props.defaultValue); useEffect(() => { if (takeFocus) { @@ -97,6 +98,17 @@ const Textarea = ({ } }, [takeFocus]); + useLayoutEffect(() => { + if (grow) { + const textArea = textareaRef.current; + if (textArea) { + textArea.style.height = "0px"; + const scrollHeight = textArea.scrollHeight; + textArea.style.height = `${scrollHeight}px`; + } + } + }, [textareaRef, grow, innerValue, props.value]); + return ( { onKeyUp && onKeyUp(evt); - if (grow) { - evt.currentTarget.style.height = - evt.currentTarget.scrollHeight + "px"; + }} + onChange={(evt) => { + if (!props.value) { + setInnervalue(evt.target.value); } }} ref={textareaRef} @@ -139,6 +152,7 @@ const Textarea = ({ style } {...props} + value={props.value || innerValue} /> ); From 0e463ace64d6f1814d6b4a29fce1cb88198d9e52 Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Wed, 10 Jan 2024 15:14:46 +0200 Subject: [PATCH 04/24] feat: upstream generic pagination component for tables Signed-off-by: Mason Hu --- .../TablePagination/TablePagination.scss | 40 ++++ .../TablePagination.stories.mdx | 214 ++++++++++++++++++ .../TablePagination/TablePagination.test.tsx | 90 ++++++++ .../TablePagination/TablePagination.tsx | 180 +++++++++++++++ .../TablePaginationControls.test.tsx | 21 ++ .../TablePaginationControls.tsx | 79 +++++++ .../TablePaginationControls.test.tsx.snap | 35 +++ .../TablePaginationControls/index.ts | 2 + .../TablePagination.test.tsx.snap | 98 ++++++++ src/components/TablePagination/index.ts | 2 + src/index.ts | 2 + 11 files changed, 763 insertions(+) create mode 100644 src/components/TablePagination/TablePagination.scss create mode 100644 src/components/TablePagination/TablePagination.stories.mdx create mode 100644 src/components/TablePagination/TablePagination.test.tsx create mode 100644 src/components/TablePagination/TablePagination.tsx create mode 100644 src/components/TablePagination/TablePaginationControls/TablePaginationControls.test.tsx create mode 100644 src/components/TablePagination/TablePaginationControls/TablePaginationControls.tsx create mode 100644 src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap create mode 100644 src/components/TablePagination/TablePaginationControls/index.ts create mode 100644 src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap create mode 100644 src/components/TablePagination/index.ts diff --git a/src/components/TablePagination/TablePagination.scss b/src/components/TablePagination/TablePagination.scss new file mode 100644 index 000000000..a7756b72c --- /dev/null +++ b/src/components/TablePagination/TablePagination.scss @@ -0,0 +1,40 @@ +@import "~vanilla-framework/scss/settings"; + +.pagination { + align-items: baseline; + display: flex; + margin-top: 1.2rem; + + .description { + flex-grow: 1; + } + + .back { + margin: 0 $spv--large; + + .p-icon--chevron-down { + rotate: 90deg; + } + } + + .next { + margin: 0 $spv--large; + + .p-icon--chevron-down { + rotate: 270deg; + } + } + + .pagination-input { + margin-right: $spv--small; + min-width: 0; + width: 3rem; + } + + .pagination-select { + margin-bottom: 0; + margin-left: $spv--x-large; + min-width: 0; + width: 7rem; + } +} diff --git a/src/components/TablePagination/TablePagination.stories.mdx b/src/components/TablePagination/TablePagination.stories.mdx new file mode 100644 index 000000000..6679f2546 --- /dev/null +++ b/src/components/TablePagination/TablePagination.stories.mdx @@ -0,0 +1,214 @@ +import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs"; + +import TablePagination from "./TablePagination"; +import MainTable from "../MainTable"; + + + +export const Template = (args) => ; + +### TablePagination + +This is an HOC [React](https://reactjs.org/) component for applying pagination to input data for direct child components. +This component is un-opinionated about the structure of the input data and can be used with any child component that displays +a list of data. However, the styling and behaviour of this component were designed to work nicely with the ```MainTable``` component. + +To use this component, simply wrap a child component with it and provide the data that you want to paginate to the ```data``` prop. +This component will then pass the paged data to all direct child components via a child prop specified by ```dataForwardProp```. + +### Props + + + +### Default + + + + {Template.bind({})} + + + +### Custom page limit + + + + {Template.bind({})} + + + +### Custom display title + + + Hello there + }} + > + {Template.bind({})} + + + +### Render above + + + + {() => { + const data = [ + { + columns: [ + { content: "Ready", role: "rowheader" }, + { content: 1, className: "u-align--right" }, + { content: "1 GiB", className: "u-align--right" }, + { content: 2, className: "u-align--right" }, + { content: 42, className: "u-align--right" }, + ], + sortData: { + status: "ready", + cores: 2, + ram: 1, + disks: 2, + }, + }, + { + columns: [ + { content: "Idle", role: "rowheader" }, + { content: 1, className: "u-align--right" }, + { content: "1 GiB", className: "u-align--right" }, + { content: 2, className: "u-align--right" }, + { content: 23, className: "u-align--right" }, + ], + sortData: { + status: "idle", + cores: 1, + ram: 1, + disks: 2, + }, + }, + { + columns: [ + { content: "Waiting", role: "rowheader" }, + { content: 8, className: "u-align--right" }, + { content: "3.9 GiB", className: "u-align--right" }, + { content: 3, className: "u-align--right" }, + { content: 0, className: "u-align--right" }, + ], + sortData: { + status: "waiting", + cores: 8, + ram: 3.9, + disks: 3, + }, + }, + ]; + + const headers = [ + { content: "Status", sortKey: "status" }, + { content: "Cores", sortKey: "cores", className: "u-align--right" }, + { content: "RAM", sortKey: "ram", className: "u-align--right" }, + { content: "Disks", sortKey: "disks", className: "u-align--right" }, + { content: "Networks", className: "u-align--right" }, + ]; + + return ( + + + + ); + }} + + + +### Render below + + + + {() => { + const data = [ + { + columns: [ + { content: "Ready", role: "rowheader" }, + { content: 1, className: "u-align--right" }, + { content: "1 GiB", className: "u-align--right" }, + { content: 2, className: "u-align--right" }, + { content: 42, className: "u-align--right" }, + ], + sortData: { + status: "ready", + cores: 2, + ram: 1, + disks: 2, + }, + }, + { + columns: [ + { content: "Idle", role: "rowheader" }, + { content: 1, className: "u-align--right" }, + { content: "1 GiB", className: "u-align--right" }, + { content: 2, className: "u-align--right" }, + { content: 23, className: "u-align--right" }, + ], + sortData: { + status: "idle", + cores: 1, + ram: 1, + disks: 2, + }, + }, + { + columns: [ + { content: "Waiting", role: "rowheader" }, + { content: 8, className: "u-align--right" }, + { content: "3.9 GiB", className: "u-align--right" }, + { content: 3, className: "u-align--right" }, + { content: 0, className: "u-align--right" }, + ], + sortData: { + status: "waiting", + cores: 8, + ram: 3.9, + disks: 3, + }, + }, + ]; + + const headers = [ + { content: "Status", sortKey: "status" }, + { content: "Cores", sortKey: "cores", className: "u-align--right" }, + { content: "RAM", sortKey: "ram", className: "u-align--right" }, + { content: "Disks", sortKey: "disks", className: "u-align--right" }, + { content: "Networks", className: "u-align--right" }, + ]; + + return ( + + + + ); + }} + + diff --git a/src/components/TablePagination/TablePagination.test.tsx b/src/components/TablePagination/TablePagination.test.tsx new file mode 100644 index 000000000..6093dbee1 --- /dev/null +++ b/src/components/TablePagination/TablePagination.test.tsx @@ -0,0 +1,90 @@ +/* eslint-disable testing-library/no-node-access */ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import TablePagination from "./TablePagination"; +import userEvent from "@testing-library/user-event"; + +const dummyData = [ + { id: "row-1" }, + { id: "row-2" }, + { id: "row-3" }, + { id: "row-4" }, + { id: "row-5" }, +]; + +describe("", () => { + // snapshot tests + it("renders table pagination and matches the snapshot", () => { + render(); + + expect(screen.getByRole("navigation")).toMatchSnapshot(); + }); + + // unit tests + it("renders default display title correctly when no pagination takes place", () => { + render(); + + expect(screen.getByRole("navigation")).toHaveTextContent( + "Showing all 5 items" + ); + }); + + it("renders default display title correctly when pagination takes place", () => { + render(); + + expect(screen.getByRole("navigation")).toHaveTextContent( + "Showing 1 out of 5 items" + ); + }); + + it("has correct per page setting when changed", () => { + render(); + + expect(screen.getByRole("navigation")).toHaveTextContent("2/page"); + userEvent.selectOptions( + screen.getByRole("combobox", { name: "Items per page" }), + "5" + ); + expect(screen.getByRole("navigation")).toHaveTextContent("5/page"); + }); + + it("resets to first page when page size is changed", () => { + render(); + + expect(screen.getByRole("navigation")).toHaveTextContent("2/page"); + userEvent.selectOptions( + screen.getByRole("combobox", { name: "Items per page" }), + "5" + ); + const currentPageInput = screen.getByRole("spinbutton", { + name: "Page number", + }); + expect(currentPageInput).toHaveValue(1); + }); + + it("should paginate correctly in incrementing or decrementing directions", async () => { + render(); + const incButton = screen.getByRole("button", { name: "Next page" }); + const decButton = screen.getByRole("button", { name: "Previous page" }); + const currentPageInput = screen.getByRole("spinbutton", { + name: "Page number", + }); + const pageSizeSelector = screen.getByRole("combobox", { + name: "Items per page", + }); + + expect(currentPageInput).toHaveValue(1); + await userEvent.click(decButton); + expect(currentPageInput).toHaveValue(1); + await userEvent.click(incButton); + expect(currentPageInput).toHaveValue(2); + await userEvent.selectOptions(pageSizeSelector, "2"); + expect(currentPageInput).toHaveValue(1); + await fireEvent.change(currentPageInput, { target: { value: 3 } }); + expect(currentPageInput).toHaveValue(3); + await userEvent.click(incButton); + expect(currentPageInput).toHaveValue(3); + await userEvent.click(decButton); + expect(currentPageInput).toHaveValue(2); + }); +}); diff --git a/src/components/TablePagination/TablePagination.tsx b/src/components/TablePagination/TablePagination.tsx new file mode 100644 index 000000000..c6ec1f35d --- /dev/null +++ b/src/components/TablePagination/TablePagination.tsx @@ -0,0 +1,180 @@ +import React, { + ChangeEvent, + Children, + HTMLAttributes, + PropsWithChildren, + ReactElement, + ReactNode, + RefObject, + cloneElement, + useEffect, + useRef, + useState, +} from "react"; +import classnames from "classnames"; +import { usePagination } from "hooks"; +import Select from "components/Select"; +import TablePaginationControls from "./TablePaginationControls"; +import "./TablePagination.scss"; + +/** + * Determine if we are working with a small screen. + * 'small screen' in this case is relative to the width of the description div + */ +const figureSmallScreen = (descriptionRef: RefObject) => { + const descriptionElement = descriptionRef.current; + if (!descriptionElement) { + return true; + } + return descriptionElement.getBoundingClientRect().width < 230; +}; + +/** + * Iterate direct react child components and override the value of the prop specified by @param dataForwardProp + * for those child components. + * @param children - react node children to iterate + * @param dataForwardProp - the name of the prop from the children components to override + * @param data - actual data to be passed to the prop specified by @param dataForwardProp + */ +const renderChildren = ( + children: ReactNode, + dataForwardProp: string, + data: unknown[] +) => { + return Children.map(children, (child) => { + return cloneElement(child as ReactElement, { + [dataForwardProp]: data, + }); + }); +}; + +const DEFAULT_PAGE_LIMITS = [50, 100, 200]; +const generatePagingOptions = (pageLimits: number[]) => { + return pageLimits.map((limit) => ({ value: limit, label: `${limit}/page` })); +}; + +export type Props = PropsWithChildren<{ + /** + * list of data elements to be paginated. This component is un-opinionated about + * the structure of the data but it should be identical to the data structure + * reuiqred by the child table component + */ + data: unknown[]; + /** + * prop name of the child table component that receives paginated data. + * default value is set to @constant rows, which is the data prop for the @func MainTable component + */ + dataForwardProp?: string; + /** + * the name of the item associated to each row within the table. + */ + itemName?: string; + /** + * custom styling for the pagination container + */ + className?: string; + /** + * custom description to be displayed by the pagination + */ + description?: ReactNode; + /** + * custom per page limits express as an array of numbers. + */ + pageLimits?: number[]; + /** + * place the pagination component above or below the table? + */ + position?: "above" | "below"; +}> & + HTMLAttributes; + +const TablePagination = ({ + data, + className, + itemName = "item", + description, + position = "above", + dataForwardProp = "rows", + pageLimits = DEFAULT_PAGE_LIMITS, + children, + ...divProps +}: Props) => { + const descriptionRef = useRef(null); + const [isSmallScreen, setSmallScreen] = useState(false); + const [pageSize, setPageSize] = useState(() => { + return generatePagingOptions(pageLimits)[0].value; + }); + const { paginate, currentPage, pageData, totalItems } = usePagination(data, { + itemsPerPage: pageSize, + autoResetPage: true, + }); + + useEffect(() => { + const handleResize = () => { + setSmallScreen(figureSmallScreen(descriptionRef)); + }; + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [isSmallScreen]); + + const handlePageSizeChange = (e: ChangeEvent) => { + paginate(1); + setPageSize(parseInt(e.target.value)); + }; + + const getDescription = () => { + if (description) { + return description; + } + + const visibleCount = pageData.length; + + if (isSmallScreen) { + return `${visibleCount} out of ${totalItems}`; + } + + if (visibleCount === totalItems && visibleCount > 1) { + return `Showing all ${totalItems} ${itemName}s`; + } + + return `Showing ${visibleCount} out of ${totalItems} ${itemName}${ + totalItems !== 1 ? "s" : "" + }`; + }; + + const totalPages = Math.ceil(data.length / pageSize); + const clonedChildren = renderChildren(children, dataForwardProp, pageData); + return ( + <> + {position === "below" && clonedChildren} +
+
+ {getDescription()} +
+ + {" "} + of {totalPages} + + + ); +}; + +export default TablePaginationControls; diff --git a/src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap b/src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap new file mode 100644 index 000000000..3fc41511a --- /dev/null +++ b/src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders table pagination controls and matches the snapshot 1`] = ` +Array [ + , + , +] +`; + +exports[` renders table pagination controls and matches the snapshot 2`] = ` + +`; diff --git a/src/components/TablePagination/TablePaginationControls/index.ts b/src/components/TablePagination/TablePaginationControls/index.ts new file mode 100644 index 000000000..48aa68113 --- /dev/null +++ b/src/components/TablePagination/TablePaginationControls/index.ts @@ -0,0 +1,2 @@ +export { default } from "./TablePaginationControls"; +export type { Props as TablePaginationControlsProps } from "./TablePaginationControls"; diff --git a/src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap b/src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap new file mode 100644 index 000000000..ee4d0a51a --- /dev/null +++ b/src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap @@ -0,0 +1,98 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders table pagination and matches the snapshot 1`] = ` + +`; diff --git a/src/components/TablePagination/index.ts b/src/components/TablePagination/index.ts new file mode 100644 index 000000000..aac664179 --- /dev/null +++ b/src/components/TablePagination/index.ts @@ -0,0 +1,2 @@ +export { default } from "./TablePagination"; +export type { Props as TablePaginationProps } from "./TablePagination"; diff --git a/src/index.ts b/src/index.ts index 44ce5f406..ad41e8f08 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,6 +64,7 @@ export { default as TableRow } from "./components/TableRow"; export { default as Tabs } from "./components/Tabs"; export { default as Textarea } from "./components/Textarea"; export { default as Tooltip } from "./components/Tooltip"; +export { default as TablePagination } from "./components/TablePagination"; export type { AccordionProps } from "./components/Accordion"; export type { ActionButtonProps } from "./components/ActionButton"; @@ -134,6 +135,7 @@ export type { TableRowProps } from "./components/TableRow"; export type { TabsProps } from "./components/Tabs"; export type { TextareaProps } from "./components/Textarea"; export type { TooltipProps } from "./components/Tooltip"; +export type { TablePaginationProps } from "./components/TablePagination"; export { useOnClickOutside, From a67dfde968111a64e745f60b75c24bb40eb080ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:02:45 +0000 Subject: [PATCH 05/24] build(deps): bump @adobe/css-tools from 4.3.1 to 4.3.2 Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/adobe/css-tools/blob/main/History.md) - [Commits](https://github.com/adobe/css-tools/commits) --- updated-dependencies: - dependency-name: "@adobe/css-tools" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f34e2ed9c..6ce2dac98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,9 +8,9 @@ integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@adobe/css-tools@^4.0.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" - integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== + version "4.3.2" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" + integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw== "@ampproject/remapping@^2.2.0": version "2.2.1" From d483b87b84d4c782c353eace3cc6446ee194d0e5 Mon Sep 17 00:00:00 2001 From: andesol Date: Mon, 20 Nov 2023 15:42:53 +0100 Subject: [PATCH 06/24] fix(modal): scroll to top when opened --- src/components/Modal/Modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 78d68bad7..7464fb48f 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -99,7 +99,7 @@ export const Modal = ({ if (close && focusableModalElements.current.length > 1) { focusIndex = 1; } - focusableModalElements.current[focusIndex]?.focus(); + focusableModalElements.current[focusIndex]?.focus({ preventScroll: true }); }, [close]); useEffect(() => { From a3e1608cbb825c31a59cc6166500f0d7bfa4d6ff Mon Sep 17 00:00:00 2001 From: Peter Makowski Date: Fri, 1 Dec 2023 16:53:18 +0100 Subject: [PATCH 07/24] docs: fix contextual menu story --- src/components/ContextualMenu/ContextualMenu.stories.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ContextualMenu/ContextualMenu.stories.mdx b/src/components/ContextualMenu/ContextualMenu.stories.mdx index 690f580d0..a8914a119 100644 --- a/src/components/ContextualMenu/ContextualMenu.stories.mdx +++ b/src/components/ContextualMenu/ContextualMenu.stories.mdx @@ -1,5 +1,6 @@ import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs"; +import { Button } from "../Button"; import ContextualMenu from "./ContextualMenu"; Date: Fri, 19 Jan 2024 10:47:27 +0000 Subject: [PATCH 08/24] chore(deps): update dependency vanilla-framework to v4.6.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8756184e6..db7eff6d0 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "ts-jest": "27.1.5", "tsc-alias": "1.8.8", "typescript": "4.9.5", - "vanilla-framework": "4.5.1", + "vanilla-framework": "4.6.0", "wait-on": "5.3.0", "webpack": "5.89.0" }, diff --git a/yarn.lock b/yarn.lock index 6ce2dac98..b4a039028 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12530,10 +12530,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -vanilla-framework@4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/vanilla-framework/-/vanilla-framework-4.5.1.tgz#19b55dd4771c304b2bae8af646312916f5f45917" - integrity sha512-vF7GGZsXcQZnM2522Q2am08to/iM2K+EhsfYPuYsiHvp2kRGxY/rJ8tcjbKftNY859qOqT8yJg0PE+sVmKI5zg== +vanilla-framework@4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/vanilla-framework/-/vanilla-framework-4.6.0.tgz#61b93a2197eed0d869f85e1014dd9e67bacc355e" + integrity sha512-pRjJknqsL4CLA+ovLMlg7MhVh9nIcu7Ev81Z62gh3hAzUDIiZOUI2W9+B16XWv4QFmFxEOgQcfVi2cezLun9SQ== vary@~1.1.2: version "1.1.2" From 209177179d40f6a53c70f647f811523baafad5e7 Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Fri, 19 Jan 2024 13:23:47 +0200 Subject: [PATCH 09/24] chore: release 0.47.2 Signed-off-by: Mason Hu --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db7eff6d0..3be03df3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@canonical/react-components", - "version": "0.47.1", + "version": "0.47.2", "main": "dist/index.js", "module": "dist/index.js", "author": "Huw Wilkins ", From ffd122e2d2c56264b50695bd4514499d7eefc2d1 Mon Sep 17 00:00:00 2001 From: Mason Hu Date: Fri, 19 Jan 2024 16:46:44 +0200 Subject: [PATCH 10/24] fix: remove invalid syntax for style imports Signed-off-by: Mason Hu --- src/components/TablePagination/TablePagination.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TablePagination/TablePagination.scss b/src/components/TablePagination/TablePagination.scss index a7756b72c..6138a1366 100644 --- a/src/components/TablePagination/TablePagination.scss +++ b/src/components/TablePagination/TablePagination.scss @@ -1,4 +1,4 @@ -@import "~vanilla-framework/scss/settings"; +@import "vanilla-framework/scss/settings"; .pagination { align-items: baseline; From 0d44c0fcdb2f8cd3cdced239154990cf3037a1d7 Mon Sep 17 00:00:00 2001 From: Aaryan Porwal Date: Fri, 19 Jan 2024 17:13:08 +0530 Subject: [PATCH 11/24] fix(select,slider,textarea,passwordtoggle): auto generate id --- src/components/PasswordToggle/PasswordToggle.stories.mdx | 8 ++++---- src/components/PasswordToggle/PasswordToggle.test.tsx | 2 +- src/components/PasswordToggle/PasswordToggle.tsx | 6 ++++-- src/components/Select/Select.test.tsx | 4 +++- src/components/Select/Select.tsx | 6 ++++-- src/components/Slider/Slider.test.tsx | 4 +++- src/components/Slider/Slider.tsx | 5 ++++- src/components/Textarea/Textarea.test.tsx | 4 +++- src/components/Textarea/Textarea.tsx | 6 ++++-- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/components/PasswordToggle/PasswordToggle.stories.mdx b/src/components/PasswordToggle/PasswordToggle.stories.mdx index cbcb5361a..8cb309e6e 100644 --- a/src/components/PasswordToggle/PasswordToggle.stories.mdx +++ b/src/components/PasswordToggle/PasswordToggle.stories.mdx @@ -54,7 +54,7 @@ It can be used when an input needs to obscure its value, whilst giving the user @@ -67,7 +67,7 @@ It can be used when an input needs to obscure its value, whilst giving the user @@ -80,7 +80,7 @@ It can be used when an input needs to obscure its value, whilst giving the user @@ -92,7 +92,7 @@ It can be used when an input needs to obscure its value, whilst giving the user diff --git a/src/components/PasswordToggle/PasswordToggle.test.tsx b/src/components/PasswordToggle/PasswordToggle.test.tsx index 6ebebd600..88fda3560 100644 --- a/src/components/PasswordToggle/PasswordToggle.test.tsx +++ b/src/components/PasswordToggle/PasswordToggle.test.tsx @@ -49,7 +49,7 @@ describe("PasswordToggle", () => { it("can display an error", async () => { render(); - expect(screen.getByLabelText("password")).toHaveErrorMessage( + expect(screen.getByLabelText("password")).toHaveAccessibleErrorMessage( "Error: Uh oh!" ); }); diff --git a/src/components/PasswordToggle/PasswordToggle.tsx b/src/components/PasswordToggle/PasswordToggle.tsx index 0de58497b..dc1350f8c 100644 --- a/src/components/PasswordToggle/PasswordToggle.tsx +++ b/src/components/PasswordToggle/PasswordToggle.tsx @@ -89,6 +89,8 @@ const PasswordToggle = React.forwardRef( const validationId = useId(); const helpId = useId(); const hasError = !!error; + const defaultPasswordToggleId = useId(); + const passwordToggleId = id || defaultPasswordToggleId; const togglePassword = () => { if (isPassword) { @@ -110,7 +112,7 @@ const PasswordToggle = React.forwardRef( validationId={validationId} >
- + {label}