-
Notifications
You must be signed in to change notification settings - Fork 1
feat(ui): create DescriptionList with DescriptionTerm and DescriptionDefinition #1329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ef78491
0efe2f8
abba480
f8b967d
92b2d7f
312c43a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <dd> element. | ||
| */ | ||
| className?: string | ||
| } | ||
|
|
||
| /** | ||
| * Represents the definition or description in a description list, rendering as an HTML <dd> element. | ||
| * Pairs with DescriptionTerm to complete the term-description association, offering flexible content styling. | ||
| */ | ||
| export const DescriptionDefinition: React.FC<DescriptionDefinitionProps> = ({ children, className = "" }) => ( | ||
| <dd className={`dd ${className}`}>{children}</dd> | ||
| ) | ||
|
|
||
| export const DD = DescriptionDefinition |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(<DescriptionDefinition>Test Description</DescriptionDefinition>) | ||
| expect(screen.getByText("Test Description")).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it("applies custom className", () => { | ||
| const customClass = "custom-class" | ||
| render(<DescriptionDefinition className={customClass}>Test Description</DescriptionDefinition>) | ||
| const ddElement = screen.getByText("Test Description") | ||
| expect(ddElement).toHaveClass(customClass) | ||
| }) | ||
|
|
||
| it("renders within a <dd> element", () => { | ||
| render(<DescriptionDefinition>Test Description</DescriptionDefinition>) | ||
| const ddElement = screen.getByText("Test Description") | ||
| expect(ddElement.tagName).toBe("DD") | ||
| }) | ||
|
|
||
| it("can render complex children", () => { | ||
| render( | ||
| <DescriptionDefinition> | ||
| <span>Complex</span> <strong>Content</strong> | ||
| </DescriptionDefinition> | ||
| ) | ||
| expect(screen.getByText("Complex")).toBeInTheDocument() | ||
| expect(screen.getByText("Content")).toBeInTheDocument() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<DescriptionTermProps | DescriptionDefinitionProps> | ||
| | Array<ReactElement<DescriptionTermProps | DescriptionDefinitionProps>> | ||
| /** | ||
| * 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 <dl> 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 <dl> 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<DescriptionListProps> = ({ children, alignTerms = "right", className = "" }) => ( | ||
| <dl | ||
| className={`dl ${className}`} | ||
| data-testid="description-list" | ||
| style={{ justifyContent: alignTerms === "right" ? "flex-end" : "flex-start" }} | ||
| > | ||
| {children} | ||
| </dl> | ||
| ) | ||
|
|
||
| export const DL = DescriptionList |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be good to have a story with more |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<typeof DL> = { | ||
| title: "Components/DescriptionList", | ||
| component: DL, | ||
| argTypes: { | ||
| children: { | ||
| control: false, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| export default meta | ||
| type Story = StoryObj<typeof DL> | ||
|
|
||
| export const Default: Story = { | ||
| render: (args) => ( | ||
| <DL {...args}> | ||
| <DT>Warranty</DT> | ||
| <DD>2 years limited warranty with options for extension.</DD> | ||
| </DL> | ||
| ), | ||
| } | ||
|
|
||
| /** | ||
| * You can use many definitions per term. | ||
| */ | ||
| export const MultipleDefinitions: Story = { | ||
| render: (args) => ( | ||
| <DL {...args}> | ||
| <DT>Shipping</DT> | ||
| <DD>Standard shipping: 5-7 business days.</DD> | ||
| <DD>Express shipping: 2-3 business days.</DD> | ||
| <DT>Payment Options</DT> | ||
| <DD>Credit/Debit cards, PayPal, and bank transfer.</DD> | ||
| </DL> | ||
| ), | ||
| } | ||
|
|
||
| export const GroupedDefinitions: Story = { | ||
| render: (args) => ( | ||
| <DL {...args}> | ||
| <div> | ||
| <DT>Privacy Policy</DT> | ||
| <DD>We value your privacy and ensure data protection.</DD> | ||
| <DD>Your information will not be shared without consent.</DD> | ||
| <DD>Regular audits are conducted for security assurance.</DD> | ||
| </div> | ||
| <div> | ||
| <DT>Terms of Service</DT> | ||
| <DD>By using our services, you agree to our terms.</DD> | ||
| </div> | ||
| </DL> | ||
| ), | ||
| } | ||
|
|
||
| /** | ||
| * You can wrap a term with multiple definitions in a <div> for grouping. | ||
| */ | ||
| export const UserProfileDetails: Story = { | ||
| render: (args) => ( | ||
| <DL {...args}> | ||
| <div> | ||
| <DT>Full Name</DT> | ||
| <DD>John Doe</DD> | ||
| </div> | ||
| <div> | ||
| <DT>Email</DT> | ||
| <DD>johndoe@example.com</DD> | ||
| </div> | ||
| <div> | ||
| <DT>Membership</DT> | ||
| <DD>Platinum Member</DD> | ||
| <DD>Joined: January 2020</DD> | ||
| </div> | ||
| <div> | ||
| <DT>Preferences</DT> | ||
| <DD>Language: English</DD> | ||
| <DD>Theme: Dark</DD> | ||
| </div> | ||
| </DL> | ||
| ), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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( | ||
| <DescriptionList> | ||
| <DescriptionTerm>Term 1</DescriptionTerm> | ||
| <DescriptionDefinition>Definition 1</DescriptionDefinition> | ||
| </DescriptionList> | ||
| ) | ||
| expect(screen.getByText("Term 1")).toBeInTheDocument() | ||
| expect(screen.getByText("Definition 1")).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it("applies custom className to the <dl> element", () => { | ||
| const customClass = "custom-class" | ||
| render( | ||
| <DescriptionList className={customClass}> | ||
| <DescriptionTerm>Term 2</DescriptionTerm> | ||
| <DescriptionDefinition>Definition 2</DescriptionDefinition> | ||
| </DescriptionList> | ||
| ) | ||
|
|
||
| const dlElement = screen.getByTestId("description-list") | ||
| expect(dlElement).toHaveClass(customClass) | ||
| }) | ||
|
|
||
| it("aligns terms to the right by default", () => { | ||
| render( | ||
| <DescriptionList> | ||
| <DescriptionTerm>Term 3</DescriptionTerm> | ||
| <DescriptionDefinition>Definition 3</DescriptionDefinition> | ||
| </DescriptionList> | ||
| ) | ||
| }) | ||
|
|
||
| it("aligns terms to the left when specified", () => { | ||
| render( | ||
| <DescriptionList alignTerms="left"> | ||
| <DescriptionTerm>Left Term</DescriptionTerm> | ||
| <DescriptionDefinition>Definition for Left Term</DescriptionDefinition> | ||
| </DescriptionList> | ||
| ) | ||
| }) | ||
|
|
||
| it("renders multiple terms and definitions in a single list", () => { | ||
| render( | ||
| <DescriptionList> | ||
| <DescriptionTerm>Term 4</DescriptionTerm> | ||
| <DescriptionDefinition>Definition 4</DescriptionDefinition> | ||
| <DescriptionTerm>Term 5</DescriptionTerm> | ||
| <DescriptionDefinition>Definition 5</DescriptionDefinition> | ||
| </DescriptionList> | ||
| ) | ||
| expect(screen.getByText("Term 4")).toBeInTheDocument() | ||
| expect(screen.getByText("Definition 4")).toBeInTheDocument() | ||
| expect(screen.getByText("Term 5")).toBeInTheDocument() | ||
| expect(screen.getByText("Definition 5")).toBeInTheDocument() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <dt> element, useful for overrides or custom styles. | ||
| */ | ||
| className?: string | ||
| } | ||
|
|
||
| /** | ||
| * Represents a term in a description list, rendering an HTML <dt> element. | ||
| * Used to denote terms, headers, or keys in a semantic way, allowing for flexible styling. | ||
| */ | ||
| export const DescriptionTerm: React.FC<DescriptionTermProps> = ({ children, className = "" }) => ( | ||
| <div className="dt-container"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't wrap the |
||
| <dt className={`dt ${className}`}>{children}</dt> | ||
| </div> | ||
| ) | ||
|
|
||
| export const DT = DescriptionTerm | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(<DescriptionTerm>Test Term</DescriptionTerm>) | ||
| expect(screen.getByText("Test Term")).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it("applies custom className to the <dt> element", () => { | ||
| const customClass = "custom-class" | ||
| render(<DescriptionTerm className={customClass}>Styled Term</DescriptionTerm>) | ||
| const dtElement = screen.getByText("Styled Term") | ||
| expect(dtElement).toHaveClass(customClass) | ||
| }) | ||
|
|
||
| it("renders within a <dt> element", () => { | ||
| render(<DescriptionTerm>Term Element</DescriptionTerm>) | ||
| const dtElement = screen.getByText("Term Element") | ||
| expect(dtElement.tagName.toLowerCase()).toBe("dt") | ||
| }) | ||
|
|
||
| it("can render complex children", () => { | ||
| render( | ||
| <DescriptionTerm> | ||
| <span>Complex Term</span> <strong>Content</strong> | ||
| </DescriptionTerm> | ||
| ) | ||
| expect(screen.getByText("Complex Term")).toBeInTheDocument() | ||
| expect(screen.getByText("Content")).toBeInTheDocument() | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both
<dt>and<dd>should have a bottom border as specified in the ticket.Updated the variable table in the ticket to map a new variable to the existin
theme- variable.