diff --git a/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.component.tsx b/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.component.tsx new file mode 100644 index 0000000000..9b66fb0bd2 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.component.tsx @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ReactNode } from "react" +import "./description-definition.css" + +export interface DescriptionDefinitionProps { + /** + * Content to be displayed as the description, accommodating text or more complex nodes to explain or define the associated term. + */ + children: ReactNode + /** + * Additional class names for applying custom styles or overriding default styles on the
element. + */ + className?: string +} + +/** + * Represents the definition or description in a description list, rendering as an HTML
element. + * Pairs with DescriptionTerm to complete the term-description association, offering flexible content styling. + */ +export const DescriptionDefinition: React.FC = ({ children, className = "" }) => ( +
{children}
+) + +export const DD = DescriptionDefinition diff --git a/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.test.tsx b/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.test.tsx new file mode 100644 index 0000000000..b6e305f76e --- /dev/null +++ b/packages/ui-components/src/components/DescriptionDefinition/DescriptionDefinition.test.tsx @@ -0,0 +1,39 @@ +/* + * 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 } from "@testing-library/react" +import { describe, it, expect } from "vitest" +import { DescriptionDefinition } from "../DescriptionDefinition" + +describe("DescriptionDefinition", () => { + it("renders the children correctly", () => { + render(Test Description) + expect(screen.getByText("Test Description")).toBeInTheDocument() + }) + + it("applies custom className", () => { + const customClass = "custom-class" + render(Test Description) + const ddElement = screen.getByText("Test Description") + expect(ddElement).toHaveClass(customClass) + }) + + it("renders within a
element", () => { + render(Test Description) + const ddElement = screen.getByText("Test Description") + expect(ddElement.tagName).toBe("DD") + }) + + it("can render complex children", () => { + render( + + Complex Content + + ) + expect(screen.getByText("Complex")).toBeInTheDocument() + expect(screen.getByText("Content")).toBeInTheDocument() + }) +}) diff --git a/packages/ui-components/src/components/DescriptionDefinition/description-definition.css b/packages/ui-components/src/components/DescriptionDefinition/description-definition.css new file mode 100644 index 0000000000..2743c2666d --- /dev/null +++ b/packages/ui-components/src/components/DescriptionDefinition/description-definition.css @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.dd { + background-color: var(--color-dd-background); + border-bottom: 1px solid var(--color-dd-dt-border); + color: var(--color-dd-text); + display: flex; + align-items: center; + height: 2rem; + padding: 0.5rem; + gap: 0.25rem; +} diff --git a/packages/ui-components/src/components/DescriptionDefinition/index.ts b/packages/ui-components/src/components/DescriptionDefinition/index.ts new file mode 100644 index 0000000000..b419dbeb63 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionDefinition/index.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { DescriptionDefinition, type DescriptionDefinitionProps } from "./DescriptionDefinition.component" diff --git a/packages/ui-components/src/components/DescriptionList/DescriptionList.component.tsx b/packages/ui-components/src/components/DescriptionList/DescriptionList.component.tsx new file mode 100644 index 0000000000..539e65b7e4 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionList/DescriptionList.component.tsx @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ReactElement } from "react" +import { DescriptionTermProps } from "../DescriptionTerm" +import { DescriptionDefinitionProps } from "../DescriptionDefinition" +import "./description-list.css" + +export interface DescriptionListProps { + /** + * Child components must be either DescriptionTerm or DescriptionDefinition to maintain semantic structure. + * Supports multiple instances to create a detailed list of terms and definitions. + */ + children: + | ReactElement + | Array> + /** + * Determines the alignment of terms within the list. Align terms to the left or right based on preference for display style. + */ + alignTerms?: "left" | "right" + /** + * Additional custom class names to apply styles to the
element or to extend styling from the design system. + */ + className?: string +} + +/** + * A wrapper component that semantically represents a list of terms and their corresponding descriptions using HTML
elements. + * This component enforces structure by expecting child elements of DescriptionTerm or DescriptionDefinition, + * aligning them according to the specified terms alignment. + */ +export const DescriptionList: React.FC = ({ children, alignTerms = "right", className = "" }) => ( +
+ {children} +
+) + +export const DL = DescriptionList diff --git a/packages/ui-components/src/components/DescriptionList/DescriptionList.stories.tsx b/packages/ui-components/src/components/DescriptionList/DescriptionList.stories.tsx new file mode 100644 index 0000000000..abcf929f0f --- /dev/null +++ b/packages/ui-components/src/components/DescriptionList/DescriptionList.stories.tsx @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from "react" +import type { Meta, StoryObj } from "@storybook/react-vite" +import { DL } from "./DescriptionList.component" +import { DT } from "../DescriptionTerm/DescriptionTerm.component" +import { DD } from "../DescriptionDefinition/DescriptionDefinition.component" + +const meta: Meta = { + title: "Components/DescriptionList", + component: DL, + argTypes: { + children: { + control: false, + }, + }, +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: (args) => ( +
+
Warranty
+
2 years limited warranty with options for extension.
+
+ ), +} + +/** + * You can use many definitions per term. + */ +export const MultipleDefinitions: Story = { + render: (args) => ( +
+
Shipping
+
Standard shipping: 5-7 business days.
+
Express shipping: 2-3 business days.
+
Payment Options
+
Credit/Debit cards, PayPal, and bank transfer.
+
+ ), +} + +export const GroupedDefinitions: Story = { + render: (args) => ( +
+
+
Privacy Policy
+
We value your privacy and ensure data protection.
+
Your information will not be shared without consent.
+
Regular audits are conducted for security assurance.
+
+
+
Terms of Service
+
By using our services, you agree to our terms.
+
+
+ ), +} + +/** + * You can wrap a term with multiple definitions in a
for grouping. + */ +export const UserProfileDetails: Story = { + render: (args) => ( +
+
+
Full Name
+
John Doe
+
+
+
Email
+
johndoe@example.com
+
+
+
Membership
+
Platinum Member
+
Joined: January 2020
+
+
+
Preferences
+
Language: English
+
Theme: Dark
+
+
+ ), +} diff --git a/packages/ui-components/src/components/DescriptionList/DescriptionList.test.tsx b/packages/ui-components/src/components/DescriptionList/DescriptionList.test.tsx new file mode 100644 index 0000000000..c4226e85d3 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionList/DescriptionList.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 } from "@testing-library/react" +import { describe, it, expect } from "vitest" +import { DescriptionList } from "./DescriptionList.component" +import { DescriptionTerm } from "../DescriptionTerm" +import { DescriptionDefinition } from "../DescriptionDefinition" + +describe("DescriptionList", () => { + it("renders child DescriptionTerm and DescriptionDefinition components correctly", () => { + render( + + Term 1 + Definition 1 + + ) + expect(screen.getByText("Term 1")).toBeInTheDocument() + expect(screen.getByText("Definition 1")).toBeInTheDocument() + }) + + it("applies custom className to the
element", () => { + const customClass = "custom-class" + render( + + Term 2 + Definition 2 + + ) + + const dlElement = screen.getByTestId("description-list") + expect(dlElement).toHaveClass(customClass) + }) + + it("aligns terms to the right by default", () => { + render( + + Term 3 + Definition 3 + + ) + }) + + it("aligns terms to the left when specified", () => { + render( + + Left Term + Definition for Left Term + + ) + }) + + it("renders multiple terms and definitions in a single list", () => { + render( + + Term 4 + Definition 4 + Term 5 + Definition 5 + + ) + expect(screen.getByText("Term 4")).toBeInTheDocument() + expect(screen.getByText("Definition 4")).toBeInTheDocument() + expect(screen.getByText("Term 5")).toBeInTheDocument() + expect(screen.getByText("Definition 5")).toBeInTheDocument() + }) +}) diff --git a/packages/ui-components/src/components/DescriptionList/description-list.css b/packages/ui-components/src/components/DescriptionList/description-list.css new file mode 100644 index 0000000000..75ed0a8728 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionList/description-list.css @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.dl { + display: flex; + flex-direction: row; + gap: 0.25rem; +} diff --git a/packages/ui-components/src/components/DescriptionList/index.ts b/packages/ui-components/src/components/DescriptionList/index.ts new file mode 100644 index 0000000000..d8d66b9ab9 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionList/index.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { DescriptionList, type DescriptionListProps } from "./DescriptionList.component" diff --git a/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.component.tsx b/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.component.tsx new file mode 100644 index 0000000000..8c267b001d --- /dev/null +++ b/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.component.tsx @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ReactNode } from "react" +import "./description-term.css" + +export interface DescriptionTermProps { + /** + * Content to be displayed as the term, which could be simple text or any ReactNode, providing semantic meaning to the associated description. + */ + children: ReactNode + /** + * Custom class names to apply additional styling to the
element, useful for overrides or custom styles. + */ + className?: string +} + +/** + * Represents a term in a description list, rendering an HTML
element. + * Used to denote terms, headers, or keys in a semantic way, allowing for flexible styling. + */ +export const DescriptionTerm: React.FC = ({ children, className = "" }) => ( +
+
{children}
+
+) + +export const DT = DescriptionTerm diff --git a/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.test.tsx b/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.test.tsx new file mode 100644 index 0000000000..a20e775f8f --- /dev/null +++ b/packages/ui-components/src/components/DescriptionTerm/DescriptionTerm.test.tsx @@ -0,0 +1,39 @@ +/* + * 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 } from "@testing-library/react" +import { describe, it, expect } from "vitest" +import { DescriptionTerm } from "./DescriptionTerm.component" + +describe("DescriptionTerm", () => { + it("renders the children properly", () => { + render(Test Term) + expect(screen.getByText("Test Term")).toBeInTheDocument() + }) + + it("applies custom className to the
element", () => { + const customClass = "custom-class" + render(Styled Term) + const dtElement = screen.getByText("Styled Term") + expect(dtElement).toHaveClass(customClass) + }) + + it("renders within a
element", () => { + render(Term Element) + const dtElement = screen.getByText("Term Element") + expect(dtElement.tagName.toLowerCase()).toBe("dt") + }) + + it("can render complex children", () => { + render( + + Complex Term Content + + ) + expect(screen.getByText("Complex Term")).toBeInTheDocument() + expect(screen.getByText("Content")).toBeInTheDocument() + }) +}) diff --git a/packages/ui-components/src/components/DescriptionTerm/description-term.css b/packages/ui-components/src/components/DescriptionTerm/description-term.css new file mode 100644 index 0000000000..0cbd322783 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionTerm/description-term.css @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.dt { + background-color: var(--color-dt-background); + border-bottom: 1px solid var(--color-dd-dt-border); + font-weight: bold; + color: var(--color-dt-text); + display: flex; + align-items: center; + height: 2rem; + padding: 0.5rem; + white-space: nowrap; + gap: 0.25rem; +} diff --git a/packages/ui-components/src/components/DescriptionTerm/index.ts b/packages/ui-components/src/components/DescriptionTerm/index.ts new file mode 100644 index 0000000000..99c3cb2656 --- /dev/null +++ b/packages/ui-components/src/components/DescriptionTerm/index.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { DescriptionTerm, type DescriptionTermProps } from "./DescriptionTerm.component" diff --git a/packages/ui-components/src/components/Modal/Modal.stories.tsx b/packages/ui-components/src/components/Modal/Modal.stories.tsx index 5e78781455..a3d97f1fb5 100644 --- a/packages/ui-components/src/components/Modal/Modal.stories.tsx +++ b/packages/ui-components/src/components/Modal/Modal.stories.tsx @@ -115,6 +115,35 @@ export const SimpleConfirmDialogWithDisabledButtons: Story = { }, } +export const DisabledCloseButton: Story = { + render: Template, + args: { + title: "Disabled Close Button Modal", + children:

This Modal has a disabled top-right close button.

, + disableCloseButton: true, + cancelButtonLabel: "Cancel", + }, +} + +export const NonCloseable: Story = { + render: Template, + args: { + title: "Non-Closeable Modal", + children: + "Use only if all else fails. If you need to inform users about something, in 99.9% of cases is the better choice.", + closeable: false, + }, +} + +export const CloseOnBackdropClick: Story = { + render: Template, + args: { + title: "Close on Backdrop Click", + children:

This Modal closes when clicking the backdrop.

, + closeOnBackdropClick: true, + }, +} + export const AutoFocusDialog: Story = { render: Template, args: { @@ -209,35 +238,6 @@ export const XXLWithForm: Story = { }, } -export const NonCloseable: Story = { - render: Template, - args: { - title: "Non-Closeable Modal", - children: - "Use only if all else fails. If you need to inform users about something, in 99.9% of cases is the better choice.", - closeable: false, - }, -} - -export const CloseOnBackdropClick: Story = { - render: Template, - args: { - title: "Close on Backdrop Click", - children:

This Modal closes when clicking the backdrop.

, - closeOnBackdropClick: true, - }, -} - -export const DisabledCloseButton: Story = { - render: Template, - args: { - title: "Disabled Close Button Modal", - children:

This Modal has a disabled top-right close button.

, - disableCloseButton: true, - cancelButtonLabel: "Cancel", - }, -} - export const Login: Story = { render: Template, args: { diff --git a/packages/ui-components/src/global.css b/packages/ui-components/src/global.css index a23bdd9675..ba80e6ef58 100644 --- a/packages/ui-components/src/global.css +++ b/packages/ui-components/src/global.css @@ -14,6 +14,9 @@ @import "./components/ThemeToggle/themeToggle.css" layer(utilities); @import "./components/DataGridRow/data-grid-row.css" layer(utilities); @import "./components/SideNavigationItem/sidenavigationitem.css" layer(utilities); +@import "./components/DescriptionList/description-list.css" layer(utilities); +@import "./components/DescriptionTerm/description-term.css" layer(utilities); +@import "./components/DescriptionDefinition/description-definition.css" layer(utilities); :root, :host { @@ -406,7 +409,7 @@ --text-color-theme-sidenavigation-item-active: var(--color-sidenavigation-item-active); /* Component Border Colors: */ - --border-color-theme-default: var(--color-default-border); + --border-color-theme-default: var(--color-dd-dt-border); --border-color-theme-box-default: var(--color-box-border); @@ -722,6 +725,13 @@ /* LT Box Shadow */ --box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3); --box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.3)), 0 2px 8px 0 rgba(0, 0, 0, 0.3); + /* LT Description - DescriptionList, DescriptionTerm, DescriptionDefinition */ + --color-dt-background: var(--color-background-lvl-1); + --color-dt-text: var(--color-text-high); + --color-dd-background: var(--color-transparent); + --color-dd-text: var(--color-text-high); + --color-dd-dt-border: var(--color-border-default); + --color-border-default: var(--color-juno-grey-blue-3); } /* JUNO THEME: DARK MODE */ @@ -960,6 +970,13 @@ /* DT Box Shadow */ --box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3); --box-shadow-hover: 0 1px 2px 0 rgba(34, 54, 73, 0.3); + /* DT Description - DescriptionList, DescriptionTerm, DescriptionDefinition */ + --color-dt-background: var(--color-background-lvl-0); + --color-dt-text: var(--color-text-high); + --color-dd-background: var(--color-transparent); + --color-dd-text: var(--color-text-high); + --color-dd-dt-border: var(--color-border-default); + --color-border-default: var(--color-juno-grey-light-7); } /* diff --git a/packages/ui-components/src/theme.css b/packages/ui-components/src/theme.css index d9049faf95..98e1a9c0ce 100644 --- a/packages/ui-components/src/theme.css +++ b/packages/ui-components/src/theme.css @@ -398,7 +398,7 @@ --text-color-theme-sidenavigation-item-active: var(--color-sidenavigation-item-active); /* Component Border Colors: */ - --border-color-theme-default: var(--color-default-border); + --border-color-theme-default: var(--color-dd-dt-border); --border-color-theme-box-default: var(--color-box-border); @@ -715,6 +715,13 @@ /* LT Box Shadow */ --box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3); --box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.3)), 0 2px 8px 0 rgba(0, 0, 0, 0.3); + /* LT Description - DescriptionList, DescriptionTerm, DescriptionDefinition */ + --color-dt-background: var(--color-background-lvl-1); + --color-dt-text: var(--color-text-high); + --color-dd-background: var(--color-transparent); + --color-dd-text: var(--color-text-high); + --color-dd-dt-border: var(--color-border-default); + --color-border-default: var(--color-juno-grey-blue-3); } /* JUNO THEME: DARK MODE */ @@ -953,6 +960,13 @@ /* DT Box Shadow */ --box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3); --box-shadow-hover: 0 1px 2px 0 rgba(34, 54, 73, 0.3); + /* DT Description - DescriptionList, DescriptionTerm, DescriptionDefinition */ + --color-dt-background: var(--color-background-lvl-1); + --color-dt-text: var(--color-text-high); + --color-dd-background: var(--color-transparent); + --color-dd-text: var(--color-text-high); + --color-dd-dt-border: var(--color-border-default); + --color-border-default: var(--color-juno-grey-light-7); } /*