From 8e6e94d3ca0d09940e733d40faed056120409561 Mon Sep 17 00:00:00 2001 From: Guoda <121792659+guoda-puidokaite@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:21:06 +0100 Subject: [PATCH] chore(ui): migrate Pagination component to Typescript (#616) * chore(ui): initially convert Pagination to Typescript, update index * chore(ui): changeset * chore(ui): fix text input type * chore(ui): fix story, pagination component type errors and extend Select component with instrinsic types * chore(ui): fix tests and component logic * chore(ui): fix casting * chore(ui): tidy change handler * chore(ui): improve comments * chore(ui): extend intrinsic props * chore(ui): change to React.FC --- .changeset/calm-apes-promise.md | 5 + .../Pagination/Pagination.component.tsx | 295 ++++++++++++ ...tion.stories.js => Pagination.stories.tsx} | 32 +- .../components/Pagination/Pagination.test.tsx | 431 ++++++++++++++++++ .../Pagination/{index.js => index.ts} | 0 .../components/Select/Select.component.tsx | 11 +- .../TextInput/TextInput.component.tsx | 2 +- .../Pagination/Pagination.component.js | 0 .../Pagination/Pagination.test.js | 0 .../src/deprecated_js/Pagination/index.js | 6 + packages/ui-components/src/index.js | 2 +- 11 files changed, 751 insertions(+), 33 deletions(-) create mode 100644 .changeset/calm-apes-promise.md create mode 100644 packages/ui-components/src/components/Pagination/Pagination.component.tsx rename packages/ui-components/src/components/Pagination/{Pagination.stories.js => Pagination.stories.tsx} (61%) create mode 100644 packages/ui-components/src/components/Pagination/Pagination.test.tsx rename packages/ui-components/src/components/Pagination/{index.js => index.ts} (100%) rename packages/ui-components/src/{components => deprecated_js}/Pagination/Pagination.component.js (100%) rename packages/ui-components/src/{components => deprecated_js}/Pagination/Pagination.test.js (100%) create mode 100644 packages/ui-components/src/deprecated_js/Pagination/index.js diff --git a/.changeset/calm-apes-promise.md b/.changeset/calm-apes-promise.md new file mode 100644 index 000000000..7e6f80a2a --- /dev/null +++ b/.changeset/calm-apes-promise.md @@ -0,0 +1,5 @@ +--- +"@cloudoperators/juno-ui-components": minor +--- + +Migrate the Pagination component to TypeScript diff --git a/packages/ui-components/src/components/Pagination/Pagination.component.tsx b/packages/ui-components/src/components/Pagination/Pagination.component.tsx new file mode 100644 index 000000000..9124a8d2a --- /dev/null +++ b/packages/ui-components/src/components/Pagination/Pagination.component.tsx @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable no-unused-vars */ + +import React, { useEffect, useState } from "react" + +import { Stack } from "../Stack/Stack.component" +import { Button } from "../Button/Button.component" +import { Spinner } from "../Spinner/Spinner.component" +import { TextInput } from "../TextInput/TextInput.component" +import { Select } from "../Select/Select.component" +import { SelectOption } from "../SelectOption/SelectOption.component" + +const paginationStyles = ` + jn-flex + jn-gap-[0.375rem] + jn-items-center +` +const spinnerStyles = `jn-ml-3` + +const inputStyles = `justify-normal` + +export type PaginationProps = Omit, "onBlur"> & { + /** + * The variant determines the style and user interaction method of the Pagination component: + * - "default": Renders previous and next buttons only. + * - "number": Displays the current page number between next and previous buttons. + * - "select": Provides a dropdown menu between next and previous buttons for specific page selection. + * - "input": Provides an input field for specific page entry. + */ + variant?: "default" | "number" | "select" | "input" + /** + * The current page number. + */ + currentPage?: number + /** + * The total number of pages available. + */ + totalPages?: number + /** + * A fallback property for providing the total number of pages, maintained for backward compatibility. + */ + pages?: number + /** + * When true, disables the entire pagination component, preventing any interaction. + */ + disabled?: boolean + /** + * When true, simulates the component being on the first page, disabling the previous button. + */ + isFirstPage?: boolean + /** + * When true, simulates the component being on the last page, disabling the next button. + */ + isLastPage?: boolean + /** + * Callback function triggered when the previous button is pressed. + */ + onPressPrevious?: (newPage?: number) => void + /** + * Callback function triggered when the next button is pressed. + */ + onPressNext?: (newPage?: number) => void + /** + * Callback function triggered when a new page is selected from the dropdown, applicable when variant is "select". + */ + onSelectChange?: (selected: number) => void + /** + * Callback function triggered when the input field value changes, applicable when variant is "input". + */ + onInputChange?: (inputValue?: number) => void + /** + * Callback function triggered when a key is pressed in the input field, applicable when variant is "input". + */ + onKeyPress?: (controlCurrentPage?: number) => void + /** + * Callback function triggered when the input field loses focus, applicable when variant is "input". + */ + onBlur?: (controlCurrentPage?: number) => void + /** + * Displays a loading spinner and disables interaction when true. + */ + progress?: boolean + /** + * Additional CSS classes for custom styling. + */ + className?: string +} + +/** + * The Pagination component provides navigation controls for paginated content. + */ +export const Pagination: React.FC = ({ + variant = "default", + currentPage, + totalPages, + pages, + disabled = false, + isFirstPage, + isLastPage, + onPressPrevious, + onPressNext, + onSelectChange, + onInputChange, + onKeyPress, + onBlur, + progress = false, + className = "", + ...props +}) => { + const [controlCurrentPage, setControlCurrentPage] = useState(currentPage) + const [controlTotalPage, setControlTotalPage] = useState(pages ? pages : totalPages) + + // Synchronize internal state with props whenever they change + useEffect(() => { + setControlCurrentPage(currentPage) + // Fallback for the “pages” prop supported for backward compatibility + pages ? setControlTotalPage(pages) : setControlTotalPage(totalPages) + // Check that the current page does not exceed total pages + if (controlCurrentPage !== undefined && controlTotalPage !== undefined && controlCurrentPage > controlTotalPage) { + setControlCurrentPage(controlTotalPage) + } + }, [currentPage, totalPages, pages]) + + const handlePrevClick = () => { + let newPage + if (controlCurrentPage !== undefined && controlCurrentPage > 1) { + newPage = controlCurrentPage - 1 + setControlCurrentPage(newPage) + } + + onPressPrevious && onPressPrevious(newPage) + } + + const handleNextClick = () => { + // Increment controlCurrentPage if it exists and is less than controlTotalPage, or if controlTotalPage is undefined + let newPage + if (controlCurrentPage !== undefined) { + if (controlTotalPage === undefined || controlCurrentPage < controlTotalPage) { + newPage = controlCurrentPage + 1 + setControlCurrentPage(newPage) + } + } + + onPressNext && onPressNext(newPage) + } + + /** + * Handler for select dropdown change event + * - Converts the selected value to a number and updates the control page state. + * - Invokes the onSelectChange callback, if defined. + */ + const handleSelectChange = (selectedValue?: string | number | string[]): void => { + if (selectedValue === undefined) return + + let convertedNumber: number | undefined + + // Convert the selected value to a number based on its type + if (typeof selectedValue === "string" || Array.isArray(selectedValue)) { + convertedNumber = parseInt(Array.isArray(selectedValue) ? selectedValue[0] : selectedValue, 10) + } else { + // If selectedValue is a number, assign it directly + convertedNumber = selectedValue + } + + // Ignore invalid conversion results + if (convertedNumber === undefined || isNaN(convertedNumber)) return + + // Update local state and invoke callback + setControlCurrentPage(convertedNumber) + onSelectChange?.(convertedNumber) + } + + const handleInputChange = (event: React.ChangeEvent) => { + // Ensure that the input value is a valid integer + let inputValue = event.target.value ? parseInt(event.target.value, 10) : undefined + + if (inputValue !== undefined) { + // Enforce minimum and maximum limits + if (inputValue < 1) { + inputValue = 1 + } else if (controlTotalPage !== undefined && inputValue > controlTotalPage) { + inputValue = controlTotalPage + } + } + // Update local state and invoke callback + setControlCurrentPage(inputValue) + onInputChange && onInputChange(inputValue) + } + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === "Enter" && controlCurrentPage !== undefined) { + onKeyPress && onKeyPress(controlCurrentPage) + } + } + + // Handler for input field losing focus + const handleBlur = () => { + onBlur && onBlur(controlCurrentPage) + } + + // Calculates the width of input fields dynamically based on the number of characters + const getInputWidthClass = () => { + let logLength = + controlCurrentPage !== undefined && !isNaN(controlCurrentPage) ? controlCurrentPage.toString().length : 1 + logLength = logLength > 5 ? 5 : logLength + const width = `${(logLength * 0.6 + 2.1).toFixed(1)}rem` // 0.6rem per digit + 2.1rem for padding + return { width: width } + } + + return ( +
+
+ ) +} diff --git a/packages/ui-components/src/components/Pagination/Pagination.stories.js b/packages/ui-components/src/components/Pagination/Pagination.stories.tsx similarity index 61% rename from packages/ui-components/src/components/Pagination/Pagination.stories.js rename to packages/ui-components/src/components/Pagination/Pagination.stories.tsx index 36bd0b41a..0b502ec8e 100644 --- a/packages/ui-components/src/components/Pagination/Pagination.stories.js +++ b/packages/ui-components/src/components/Pagination/Pagination.stories.tsx @@ -4,9 +4,9 @@ */ import React from "react" -import PropTypes from "prop-types" -import { Pagination } from "./index.js" -import { PortalProvider } from "../../deprecated_js/PortalProvider/PortalProvider.component" +import { Meta, StoryFn } from "@storybook/react" +import { Pagination, PaginationProps } from "./Pagination.component" +import { PortalProvider } from "../PortalProvider/PortalProvider.component" export default { title: "Components/Pagination", @@ -18,7 +18,7 @@ export default { }, }, decorators: [ - (Story) => ( + (Story: StoryFn) => (
@@ -26,24 +26,9 @@ export default {
), ], -} - -const Template = (args) => { - return -} - -const UncontrolledTemplate = ({ currentPage, pages, ...args }) => { - const [page, setPage] = React.useState(currentPage) - const prev = React.useCallback(() => setPage(page > 1 ? page - 1 : 1), [page]) - const next = React.useCallback(() => setPage(!pages || page < pages ? page + 1 : pages), [page]) - - return -} +} as Meta -UncontrolledTemplate.propTypes = { - currentPage: PropTypes.number, - pages: PropTypes.number, -} +const Template: StoryFn = (args) => export const Default = { render: Template, @@ -56,7 +41,6 @@ export const Default = { export const PaginationWithNumber = { render: Template, - args: { variant: "number", currentPage: 12, @@ -66,7 +50,6 @@ export const PaginationWithNumber = { export const PaginationWithSelect = { render: Template, - args: { variant: "select", currentPage: 2, @@ -76,7 +59,6 @@ export const PaginationWithSelect = { export const PaginationWithInput = { render: Template, - args: { variant: "input", currentPage: 3, @@ -86,7 +68,6 @@ export const PaginationWithInput = { export const DisabledPagination = { render: Template, - args: { disabled: true, }, @@ -94,7 +75,6 @@ export const DisabledPagination = { export const ProgressPagination = { render: Template, - args: { progress: true, }, diff --git a/packages/ui-components/src/components/Pagination/Pagination.test.tsx b/packages/ui-components/src/components/Pagination/Pagination.test.tsx new file mode 100644 index 000000000..d06056376 --- /dev/null +++ b/packages/ui-components/src/components/Pagination/Pagination.test.tsx @@ -0,0 +1,431 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from "react" +import { render, screen, fireEvent, act, waitFor } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import { describe, expect, test, vi } from "vitest" + +import { Pagination } from "./Pagination.component" + +describe("Pagination", () => { + test("renders a Pagination", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("juno-pagination") + }) + + test("renders a default Pagination with only two buttons by default", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("juno-pagination-default") + expect(screen.queryAllByRole("button")).toHaveLength(2) + expect(screen.queryAllByRole("combobox")).toHaveLength(0) + expect(screen.queryAllByRole("textbox")).toHaveLength(0) + }) + + test("renders a number variant Pagination as passed", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("juno-pagination-number") + expect(screen.getByTestId("my-pagination")).toHaveTextContent("12") + expect(screen.queryAllByRole("button")).toHaveLength(2) + expect(screen.queryAllByRole("combobox")).toHaveLength(0) + }) + + test("renders Pagination (number) with currentPage higher than totalPages", () => { + render() + expect(screen.getByTestId("my-pagination")).toHaveTextContent("6") + }) + + test("renders Pagination (number) with currentPage lower than totalPages", () => { + render() + expect(screen.getByTestId("my-pagination")).toHaveTextContent("6") + }) + + test("renders Pagination (number) with currentPage and undefined totalPages", () => { + render() + expect(screen.getByTestId("my-pagination")).toHaveTextContent("6") + }) + + test("renders Pagination (number) with undefined currentPage and defined totalPages", () => { + render() + + const paginationElement = screen.getByTestId("my-pagination") + expect(paginationElement.querySelector(".page-value")).not.toBeInTheDocument() + }) + + test("renders Pagination (number) with undefined currentPage and undefined totalPages", () => { + render() + + const paginationElement = screen.getByTestId("my-pagination") + expect(paginationElement.querySelector(".page-value")).not.toBeInTheDocument() + }) + + test("renders a select variant Pagination as passed", () => { + act(() => { + render() + }) + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("juno-pagination-select") + expect(screen.queryAllByRole("button")).toHaveLength(3) + expect(screen.queryAllByRole("textbox")).toHaveLength(0) + expect(document.querySelector("button.juno-select-toggle")).toBeInTheDocument() + }) + + test("renders Pagination (select) with currentPage higher than totalPages", () => { + act(() => { + render() + }) + expect(screen.getByTestId("my-pagination")).toHaveTextContent("6") + }) + + test("renders an input variant Pagination as passed", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("juno-pagination-input") + expect(screen.queryAllByRole("button")).toHaveLength(2) + expect(screen.queryAllByRole("combobox")).toHaveLength(0) + expect(screen.queryByRole("textbox")).toBeInTheDocument() + }) + + test("renders Pagination (input) with currentPage higher than totalPages", () => { + render() + expect(screen.getByTestId("my-pagination")).toHaveTextContent("6") + }) + + test("fires onPressPrevious handler as passed when Prev button is clicked", () => { + const handlePressPrev = vi.fn() + render() + act(() => { + screen.getByRole("button", { name: "Previous Page" }).click() + }) + expect(handlePressPrev).toHaveBeenCalledTimes(1) + }) + + test("fires onPressNext handler as passed when Next button is clicked", () => { + const handlePressNext = vi.fn() + render() + act(() => { + screen.getByRole("button", { name: "Next Page" }).click() + }) + expect(handlePressNext).toHaveBeenCalledTimes(1) + }) + + test("fires onPressNext handler with undefined currentPage and undefined totalPages when Next button is clicked", async () => { + const handlePressNext = vi.fn() + + render( + + ) + + // Click on the Next button + const nextButton = screen.getByRole("button", { name: "Next Page" }) + await act(async () => { + await userEvent.click(nextButton) + }) + + // Assertions + expect(handlePressNext).toHaveBeenCalledTimes(1) + + // Check that there is no pagination content if currentPage and totalPages are undefined + const paginationElement = screen.getByTestId("my-pagination") + expect(paginationElement.querySelector(".page-value")).not.toBeInTheDocument() + }) + + test("fires onPressNext handler with undefined currentPages", async () => { + const handlePressNext = vi.fn() + + render( + + ) + + // Click on the Next button + const nextButton = screen.getByRole("button", { name: "Next Page" }) + await act(async () => { + await userEvent.click(nextButton) + }) + + // Assertions + expect(handlePressNext).toHaveBeenCalledTimes(1) + + // Check that there is no pagination content if currentPage is undefined + const paginationElement = screen.getByTestId("my-pagination") + expect(paginationElement.querySelector(".page-value")).not.toBeInTheDocument() + }) + + test("fires onPressNext handler with undefinded totalPages", () => { + const handlePressNext = vi.fn() + render( + + ) + act(() => { + screen.getByRole("button", { name: "Next Page" }).click() + }) + expect(handlePressNext).toHaveBeenCalledTimes(1) + expect(screen.getByTestId("my-pagination")).toHaveTextContent("7") + }) + + test("does not fire onPressNext handler with higher currentPage than totalPages", () => { + const handlePressNext = vi.fn() + render( + + ) + act(() => { + screen.getByRole("button", { name: "Next Page" }).click() + }) + expect(handlePressNext).toHaveBeenCalledTimes(0) + expect(screen.getByTestId("my-pagination")).toHaveTextContent("4") + }) + + test("fires onPressNext handler with lower currentPage than totalPages", () => { + const handlePressNext = vi.fn() + render( + + ) + act(() => { + screen.getByRole("button", { name: "Next Page" }).click() + }) + expect(handlePressNext).toHaveBeenCalledTimes(1) + expect(screen.getByTestId("my-pagination")).toHaveTextContent("5") + }) + + test("fires onChange handler as passed when Select changes for select variant", async () => { + const mockHandleChange = vi.fn() + render() + const select = document.querySelector("button.juno-select-toggle") + expect(select).toBeInTheDocument() + expect(select).toHaveTextContent("1") + if (select) { + await waitFor(() => userEvent.click(select)) + } + expect(screen.getByRole("listbox")).toBeInTheDocument() + fireEvent.click(screen.getByRole("option", { name: "4" })) + expect(select).toHaveTextContent("4") + expect(mockHandleChange).toHaveBeenCalledTimes(1) + }) + + test("fires onKeyPress handler on Enter as passed for input variant", async () => { + const handleKeyPress = vi.fn() + render() + + const input = screen.getByRole("textbox") + + // Type Enter key + await act(async () => { + await userEvent.type(input, "{enter}") + }) + + // Check if the onKeyPress handler was called + await waitFor(() => { + expect(handleKeyPress).toHaveBeenCalledTimes(1) + }) + }) + + test("does not fire onKeyPress handler on Enter for input variant with undefined controlPage", async () => { + const handleKeyPress = vi.fn() + render() + + const input = screen.getByRole("textbox") + + // Type Enter key + await act(async () => { + await userEvent.type(input, "{enter}") + }) + + // Check if the onKeyPress handler was not called + await waitFor(() => { + expect(handleKeyPress).not.toHaveBeenCalled() + }) + }) + + test("renders disabled Pagination (default) as passed", () => { + render() + expect(screen.getAllByRole("button")).toHaveLength(2) + expect(screen.getAllByRole("button", { name: /previous page/i })[0]).toBeDisabled() + expect(screen.getAllByRole("button", { name: /next page/i })[0]).toBeDisabled() + }) + + test("renders disabled Pagination (select) as passed", () => { + render() + + // Check that only two buttons (Previous and Next) are rendered + expect(screen.getAllByRole("button")).toHaveLength(3) + + // Check that the Select toggle is disabled + const selectToggle = document.querySelector(".juno-select-toggle") + expect(selectToggle).toBeDisabled() + }) + + test("renders disabled Pagination (input) as passed", () => { + render() + expect(screen.getAllByRole("button")).toHaveLength(2) + expect(screen.getByRole("textbox")).toBeDisabled() + }) + + test("renders Pagination (default) in progress as passed", () => { + render() + expect(screen.getAllByRole("button")).toHaveLength(2) + expect(document.querySelector(".juno-spinner")).toBeInTheDocument() + }) + + test("renders Pagination (select) in progress as passed", () => { + render() + expect(screen.getAllByRole("button")).toHaveLength(2) + expect(document.querySelector(".juno-spinner")).toBeInTheDocument() + }) + + test("renders Pagination (input) in progress as passed", () => { + render() + expect(screen.getAllByRole("button")).toHaveLength(2) + expect(document.querySelector(".juno-spinner")).toBeInTheDocument() + }) + + test("renders Pagination (input) - fires onChange handler as passed", async () => { + const onInputChangeMock = vi.fn() + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "102" } }) + await waitFor(() => { + expect(onInputChangeMock).toHaveBeenCalledTimes(1) + }) + }) + + test("renders Pagination (input) - checks width of textfield based on the entry (2 digits) as passed", async () => { + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "22" } }) + await waitFor(() => { + const computedStyle = window.getComputedStyle(document.querySelector(".juno-pagination-wrapper") as Element) + expect(computedStyle.width).toBe("3.3rem") + }) + }) + + test("renders Pagination (input) - checks width of textfield based on the entry (3 digits) as passed", async () => { + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "333" } }) + await waitFor(() => { + const computedStyle = window.getComputedStyle(document.querySelector(".juno-pagination-wrapper") as Element) + expect(computedStyle.width).toBe("3.9rem") + }) + }) + + test("renders Pagination (input) - checks width of textfield based on the entry (4 digits) as passed", async () => { + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "4444" } }) + await waitFor(() => { + const computedStyle = window.getComputedStyle(document.querySelector(".juno-pagination-wrapper") as Element) + expect(computedStyle.width).toBe("4.5rem") + }) + }) + + test("renders Pagination (input) - checks width of textfield based on the entry (5 digits) as passed", async () => { + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "55555" } }) + await waitFor(() => { + const computedStyle = window.getComputedStyle(document.querySelector(".juno-pagination-wrapper") as Element) + expect(computedStyle.width).toBe("5.1rem") + }) + }) + + test("renders Pagination (input) - checks width of textfield based on the entry (7 digits) as passed", async () => { + render() + const textinput = screen.getByRole("textbox") + fireEvent.change(textinput, { target: { value: "777777" } }) + await waitFor(() => { + const computedStyle = window.getComputedStyle(document.querySelector(".juno-pagination-wrapper") as Element) + expect(computedStyle.width).toBe("5.1rem") + }) + }) + + test("rerenders the active item as passed to the parent if conflicting with new state of active prop passed to child item", () => { + const { rerender } = render() + expect(document.querySelector(".juno-stack")).toHaveTextContent("12") + rerender() + expect(document.querySelector(".juno-stack")).toHaveTextContent("33") + }) + + test("renders Pagination (input) with undefined currentPage prop as passed", () => { + render() + expect(screen.getByRole("textbox")).toHaveValue("") + }) + + test("renders Pagination (select) with undefined currentPage prop as passed", () => { + act(() => { + render() + }) + expect(document.querySelector("button.juno-select-toggle")).toHaveValue("") + }) + + test("renders Pagination (input) with undefined totalPages prop as passed", () => { + render() + expect(screen.getByRole("textbox")).toHaveValue("") + }) + + test("renders Pagination (input) with undefined currentPage but with totalPages as passed", () => { + render() + expect(screen.getByRole("textbox")).toHaveValue("") + }) + + test("renders Pagination (input) with undefined currentPage but with totalPages after clicking previous page button", () => { + render() + act(() => { + screen.getByRole("button", { name: "Previous Page" }).click() + }) + expect(screen.getByRole("textbox")).toHaveValue("") + }) + + test("renders Pagination (select) with undefined totalPages prop as passed", () => { + act(() => { + render() + }) + expect(document.querySelector("button.juno-select-toggle")).toHaveValue("") + }) + + test("renders a custom className as passed", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveClass("my-class") + }) + + test("renders all props as passed", () => { + render() + expect(screen.getByTestId("my-pagination")).toBeInTheDocument() + expect(screen.getByTestId("my-pagination")).toHaveAttribute("data-lolol", "123-456") + }) +}) diff --git a/packages/ui-components/src/components/Pagination/index.js b/packages/ui-components/src/components/Pagination/index.ts similarity index 100% rename from packages/ui-components/src/components/Pagination/index.js rename to packages/ui-components/src/components/Pagination/index.ts diff --git a/packages/ui-components/src/components/Select/Select.component.tsx b/packages/ui-components/src/components/Select/Select.component.tsx index 4308cb62d..402630944 100644 --- a/packages/ui-components/src/components/Select/Select.component.tsx +++ b/packages/ui-components/src/components/Select/Select.component.tsx @@ -76,7 +76,8 @@ export type SelectContextProps = { } export const SelectContext = createContext(undefined) -export interface SelectProps { +export interface SelectProps + extends Omit, "value" | "defaultValue" | "onChange"> { /** Pass an aria-label to the Select toggle button */ ariaLabel?: string /** The children to render as options. Use the SelectOption component, and SelectDivider if needed. */ @@ -107,7 +108,7 @@ export interface SelectProps { name?: string /** Handler to be executed when the selected value changes */ // eslint-disable-next-line no-unused-vars - onChange?: (...args: unknown[]) => unknown + onChange?: (value?: string | number | string[]) => void /** Handle for openning of select */ open?: boolean /** LEGACY: Handler to be executed when the Select value changes. Here for backwards compatibility with apps based on older versions of Select. Use onChange instead. */ @@ -232,9 +233,9 @@ export const Select = ({ setIsLoading(loading) }, [loading]) - const handleChange = (value: unknown) => { - onChange && onChange(value || "") - onValueChange && onValueChange(value || "") + const handleChange = (value: string | number | string[]) => { + onChange && onChange(value) + onValueChange && onValueChange(value) } const portalContainerRef = usePortalRef() diff --git a/packages/ui-components/src/components/TextInput/TextInput.component.tsx b/packages/ui-components/src/components/TextInput/TextInput.component.tsx index 74d7e7a3f..cd40a1e3e 100644 --- a/packages/ui-components/src/components/TextInput/TextInput.component.tsx +++ b/packages/ui-components/src/components/TextInput/TextInput.component.tsx @@ -245,7 +245,7 @@ export const TextInput = ({ type TextInputType = "text" | "email" | "password" | "tel" | "url" | "number" type TextInputWidth = "full" | "auto" -export interface TextInputProps { +export interface TextInputProps extends React.InputHTMLAttributes { /** Pass a name attribute */ name?: string /** Pass a value */ diff --git a/packages/ui-components/src/components/Pagination/Pagination.component.js b/packages/ui-components/src/deprecated_js/Pagination/Pagination.component.js similarity index 100% rename from packages/ui-components/src/components/Pagination/Pagination.component.js rename to packages/ui-components/src/deprecated_js/Pagination/Pagination.component.js diff --git a/packages/ui-components/src/components/Pagination/Pagination.test.js b/packages/ui-components/src/deprecated_js/Pagination/Pagination.test.js similarity index 100% rename from packages/ui-components/src/components/Pagination/Pagination.test.js rename to packages/ui-components/src/deprecated_js/Pagination/Pagination.test.js diff --git a/packages/ui-components/src/deprecated_js/Pagination/index.js b/packages/ui-components/src/deprecated_js/Pagination/index.js new file mode 100644 index 000000000..e5df39820 --- /dev/null +++ b/packages/ui-components/src/deprecated_js/Pagination/index.js @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { Pagination } from "./Pagination.component" diff --git a/packages/ui-components/src/index.js b/packages/ui-components/src/index.js index 7f44bc790..025979d69 100644 --- a/packages/ui-components/src/index.js +++ b/packages/ui-components/src/index.js @@ -72,7 +72,7 @@ export { PanelBody } from "./components/PanelBody/index.js" export { PanelFooter } from "./components/PanelFooter/index.js" export { PageFooter } from "./components/PageFooter/index.js" export { PageHeader } from "./components/PageHeader/index.js" -export { Pagination } from "./components/Pagination/index.js" +export { Pagination } from "./deprecated_js/Pagination/Pagination.component" export { Pill } from "./components/Pill/index.js" export { PortalProvider, usePortalRef } from "./components/PortalProvider/index.js" export { Radio } from "./components/Radio/index.js"