diff --git a/.changeset/clean-hoops-study.md b/.changeset/clean-hoops-study.md new file mode 100644 index 0000000000..7e0636ae77 --- /dev/null +++ b/.changeset/clean-hoops-study.md @@ -0,0 +1,9 @@ +--- +"@cloudoperators/juno-ui-components": minor +--- + +feat(ui): Streamline `DataGridCheckboxCell` and add `verticalAlignment` prop to `DataGridCell`. + +`DataGridCheckboxCell` is now removed completely. As it has been WIP for all the time and there isn't one documented use, we will release this as a minor instead of major, even though it is technically breaking. + +`DataGridCell` gets a `verticalAlignment` prop (`"center" | "top"`) that overrides the parent `DataGrid`'s `cellVerticalAlignment` for individual cells. diff --git a/packages/ui-components/src/components/DataGrid/DataGridHeader.stories.tsx b/packages/ui-components/src/components/DataGrid/DataGridHeader.stories.tsx index 18e3d92839..1de83ef402 100644 --- a/packages/ui-components/src/components/DataGrid/DataGridHeader.stories.tsx +++ b/packages/ui-components/src/components/DataGrid/DataGridHeader.stories.tsx @@ -9,7 +9,6 @@ import { DataGrid } from "./DataGrid.component" import { DataGridRow } from "../DataGridRow" import { DataGridCell } from "../DataGridCell" import { DataGridHeadCell } from "../DataGridHeadCell" -import { DataGridCheckboxCell } from "../DataGridCheckboxCell" import { DataGridToolbar } from "../DataGridToolbar" import { Stack } from "../Stack" import { Button } from "../Button" @@ -157,6 +156,7 @@ export const WithSearchOnly: Story = { export const FullyFeatured: Story = { render: () => { const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc") + const [selected, setSelected] = useState>({}) return ( <> @@ -250,7 +250,13 @@ export const FullyFeatured: Story = { {servers.map((s) => ( - + + setSelected((prev) => ({ ...prev, [s.id]: e.target.checked }))} + /> + {s.name} {s.region} {s.status} @@ -367,7 +373,13 @@ export const FullyFeatured: Story = { {servers.map((s) => ( - + + setSelected((prev) => ({ ...prev, [s.id]: e.target.checked }))} + /> + {s.name} {s.region} {s.status} diff --git a/packages/ui-components/src/components/DataGridCell/DataGridCell.component.tsx b/packages/ui-components/src/components/DataGridCell/DataGridCell.component.tsx index 4d4c579ae0..1ab02735e0 100644 --- a/packages/ui-components/src/components/DataGridCell/DataGridCell.component.tsx +++ b/packages/ui-components/src/components/DataGridCell/DataGridCell.component.tsx @@ -15,7 +15,7 @@ const cellBaseStyles = (nowrap: boolean, cellVerticalAlignment: CellVerticalAlig ? ` jn:justify-center jn:flex - jn:flex-col + jn:flex-col ` : "" } @@ -42,13 +42,13 @@ const cellCustomStyles = (colSpan: number | undefined) => { * @see {@link DataGridCellProps} */ export const DataGridCell = forwardRef( - ({ colSpan, nowrap = false, className = "", children, ...props }, ref) => { + ({ colSpan, nowrap = false, verticalAlignment, className = "", children, ...props }, ref) => { const dataGridContext = useDataGridContext() || {} - const cellVerticalAlignment = dataGridContext.cellVerticalAlignment + const effectiveVerticalAlignment = verticalAlignment ?? dataGridContext.cellVerticalAlignment return (
{ */ nowrap?: boolean + /** + * Overrides the parent `DataGrid`'s `cellVerticalAlignment` for this cell. + * When not set, the cell inherits the grid-level setting. + */ + verticalAlignment?: CellVerticalAlignmentType + /** Components or elements to render within the DataGridCell. */ children?: ReactNode diff --git a/packages/ui-components/src/components/DataGridCell/DataGridCell.stories.tsx b/packages/ui-components/src/components/DataGridCell/DataGridCell.stories.tsx index 540fd98022..67fcb36b9b 100644 --- a/packages/ui-components/src/components/DataGridCell/DataGridCell.stories.tsx +++ b/packages/ui-components/src/components/DataGridCell/DataGridCell.stories.tsx @@ -7,6 +7,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite" import React from "react" import { DataGrid } from "../DataGrid/index" import { DataGridRow } from "../DataGridRow/index" +import { DataGridHeadCell } from "../DataGridHeadCell/index" import { DataGridCell } from "./index" const meta: Meta = { @@ -19,21 +20,9 @@ const meta: Meta = { type: { summary: "ReactNode" }, }, }, - }, - decorators: [ - (Story) => ( - - - - - - ), - ], - parameters: { - docs: { - source: { - excludeDecorators: false, - }, + verticalAlignment: { + control: { type: "radio" }, + options: ["center", "top"], }, }, } @@ -41,43 +30,135 @@ const meta: Meta = { export default meta type Story = StoryObj +const items = [ + { id: "1", name: "node-prod-01", status: "Running" }, + { id: "2", name: "node-prod-02", status: "Stopped" }, + { id: "3", name: "node-staging-01", status: "Error" }, +] + +const longItems = [ + { + id: "1", + name: "node-prod-01", + status: "Running — all systems operational, no issues detected, last checked 2 minutes ago", + }, + { + id: "2", + name: "node-prod-02", + status: "Stopped — scheduled maintenance window currently active, expected to resume at 06:00 UTC", + }, + { + id: "3", + name: "node-staging-01", + status: + "Error — health check failed on port 8080, automatic restart attempted 3 times, manual intervention required", + }, +] + export const Default: Story = { parameters: { docs: { description: { - story: "Juno DataGridCell for use in DataGrid", + story: "A standard `DataGridCell` inside a `DataGrid`.", }, }, }, - args: { - children: ["DataGridCell"], - }, + render: () => ( + + + Name + Status + ID + + {items.map((item) => ( + + {item.name} + {item.status} + {item.id} + + ))} + + ), } export const NoWrap: Story = { parameters: { docs: { description: { - story: "Juno DataGridCell with nowrap option (content has white-space: nowrap;)", + story: + "With `nowrap`, cell content will not wrap onto multiple lines. Non-wrapping cells push their column to the available maximum width, and overflowing content is visible by default — consumers are responsible for handling overflow. The last row demonstrates truncation with an ellipsis: wrap the cell content in a `` with `block` and `truncate` — the span must be a block element because `text-overflow: ellipsis` does not apply directly to flex containers.", }, }, }, - args: { - nowrap: true, - children: ["DataGridCell does not wrap"], - }, + render: () => ( + + + Name + Status + ID + + {longItems.map((item, index) => ( + + {item.name} + + {index === longItems.length - 1 ? {item.status} : item.status} + + {item.id} + + ))} + + ), } export const ColSpan: Story = { parameters: { docs: { description: { - story: "Juno DataGridCell with colspan", + story: "A `DataGridCell` with `colSpan` spanning multiple columns.", }, }, }, - args: { - colSpan: 3, - children: ["DataGridCell with colspan"], + render: () => ( + + + Name + Status + ID + + {items.map((item) => ( + + {item.name} + {item.id} + + ))} + + ), +} + +export const VerticalAlignmentOverride: Story = { + parameters: { + docs: { + description: { + story: + 'Use `verticalAlignment` to override the parent `DataGrid`\'s `cellVerticalAlignment` for individual cells. Here the grid is set to `cellVerticalAlignment="center"` (the default), but the description cell uses `verticalAlignment="top"` to align longer content to the top while the name cell remains centered.', + }, + }, }, + render: () => ( + + + Name + Description + + {items.map((item) => ( + + {item.name} + + This is a longer description for {item.name} that spans multiple lines to demonstrate that top alignment + works independently of the grid-level setting. + + + ))} + + ), } diff --git a/packages/ui-components/src/components/DataGridCell/DataGridCell.test.tsx b/packages/ui-components/src/components/DataGridCell/DataGridCell.test.tsx index 669b7aa6a0..c3a04e58d9 100644 --- a/packages/ui-components/src/components/DataGridCell/DataGridCell.test.tsx +++ b/packages/ui-components/src/components/DataGridCell/DataGridCell.test.tsx @@ -6,16 +6,72 @@ import * as React from "react" import { render, screen } from "@testing-library/react" import { DataGridCell } from "./index" +import { DataGrid } from "../DataGrid/index" describe("DataGridCell", () => { test("renders a DataGridCell", () => { render() expect(screen.getByRole("gridcell")).toBeInTheDocument() + expect(screen.getByRole("gridcell")).toHaveClass("juno-datagrid-cell") }) test("renders a custom className", () => { render() - expect(screen.getByRole("gridcell")).toBeInTheDocument() expect(screen.getByRole("gridcell")).toHaveClass("my-custom-class") }) + + test("renders arbitrary props", () => { + render() + expect(screen.getByTestId("my-cell")).toHaveAttribute("data-foo", "bar") + }) + + test("inherits cellVerticalAlignment from parent DataGrid context", () => { + render( + + + + ) + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex") + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex-col") + expect(screen.getByRole("gridcell")).toHaveClass("jn:justify-center") + }) + + test("verticalAlignment prop overrides parent DataGrid context", () => { + render( + + + + ) + expect(screen.getByRole("gridcell")).not.toHaveClass("jn:flex") + expect(screen.getByRole("gridcell")).not.toHaveClass("jn:flex-col") + expect(screen.getByRole("gridcell")).not.toHaveClass("jn:justify-center") + }) + + test("verticalAlignment='center' overrides parent DataGrid cellVerticalAlignment='top'", () => { + render( + + + + ) + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex") + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex-col") + expect(screen.getByRole("gridcell")).toHaveClass("jn:justify-center") + }) + + test("renders nowrap class when nowrap is set", () => { + render() + expect(screen.getByRole("gridcell")).toHaveClass("jn:whitespace-nowrap") + }) + + test("verticalAlignment prop works without a parent DataGrid context", () => { + render() + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex") + expect(screen.getByRole("gridcell")).toHaveClass("jn:flex-col") + expect(screen.getByRole("gridcell")).toHaveClass("jn:justify-center") + }) + + test("renders colSpan via inline style", () => { + render() + expect(screen.getByRole("gridcell")).toHaveStyle({ gridColumn: "span 3 / span 3" }) + }) }) diff --git a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.component.tsx b/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.component.tsx deleted file mode 100644 index 3c8f858eb9..0000000000 --- a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.component.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { ChangeEventHandler, HTMLAttributes, ReactNode } from "react" -import { Checkbox } from "../Checkbox/Checkbox.component" -import { DataGridCell } from "../DataGridCell/DataGridCell.component" - -/** - * `DataGridCheckboxCell` integrates a checkbox within a `DataGrid` cell, ideal for selection-based interactions. - * It manages the selected state and supports custom behaviors upon state changes. - * @see https://cloudoperators.github.io/juno/?path=/docs/wip-datagrid-datagridcheckboxcell--docs - * @see {@link DataGridCheckboxCellProps} - */ -export const DataGridCheckboxCell = ({ - selected = false, - disabled = false, - className = "", - onChange, - ...props -}: DataGridCheckboxCellProps): ReactNode => { - return ( - - - - ) -} - -export interface DataGridCheckboxCellProps extends HTMLAttributes { - /** - * Indicates selected state of the associated row. - * @default false - */ - selected?: boolean - - /** - * Determines if the checkbox is disabled. - * @default false - */ - disabled?: boolean - - /** - * Additional class names for styling. - * @default "" - */ - className?: string - - /** Event handler for changes in the checkbox's state. */ - onChange?: ChangeEventHandler -} diff --git a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.stories.tsx b/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.stories.tsx deleted file mode 100644 index 14531955bb..0000000000 --- a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.stories.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import type { Meta, StoryObj } from "@storybook/react-vite" -import React from "react" -import { DataGrid } from "../DataGrid/index" -import { DataGridRow } from "../DataGridRow/index" -import { DataGridCheckboxCell } from "./index" - -const meta: Meta = { - title: "WiP/DataGrid/DataGridCheckboxCell", - component: DataGridCheckboxCell, - decorators: [ - (Story) => ( - - - - - - ), - ], - parameters: { - docs: { - source: { - excludeDecorators: false, - }, - }, - }, -} - -export default meta -type Story = StoryObj - -export const Default: Story = { - parameters: { - docs: { - description: { - story: "Juno DataGridCheckboxCell for use in DataGrid", - }, - }, - }, - args: {}, -} - -export const Disabled: Story = { - parameters: { - docs: { - description: { - story: "Disabled Juno DataGridCheckboxCell for use in DataGrid", - }, - }, - }, - args: { - disabled: true, - }, -} diff --git a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.test.tsx b/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.test.tsx deleted file mode 100644 index acc612c666..0000000000 --- a/packages/ui-components/src/components/DataGridCheckboxCell/DataGridCheckboxCell.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as React from "react" -import { render, screen } from "@testing-library/react" -import { DataGridCheckboxCell } from "./index" - -describe("DataGridCheckboxCell", () => { - test("renders a DataGridCheckboxCell", () => { - render() - expect(screen.getByRole("gridcell")).toBeInTheDocument() - }) - - test("renders a custom className", () => { - render() - expect(screen.getByRole("gridcell")).toBeInTheDocument() - expect(screen.getByRole("gridcell")).toHaveClass("my-custom-class") - }) -}) diff --git a/packages/ui-components/src/components/DataGridCheckboxCell/index.ts b/packages/ui-components/src/components/DataGridCheckboxCell/index.ts deleted file mode 100644 index 1f372e622f..0000000000 --- a/packages/ui-components/src/components/DataGridCheckboxCell/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { DataGridCheckboxCell, type DataGridCheckboxCellProps } from "./DataGridCheckboxCell.component"