Skip to content
Draft
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 {
Copy link
Contributor

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 existintheme- variable.

display: grid;
align-items: center;
border-bottom: 1px solid var(--color-dd-dt-border);
background-color: var(--color-dd-background);
color: var(--color-dd-text);
gap: 0.25rem;
grid-column: 2;
height: 2rem;
}
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,45 @@
/*
* 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>>
| ReactElement<"div">
/**
* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have a story with more DT and DD pairs with some more real-world-like metadata content to better illustrating their intended use case. Maybe we can supply some content, @edda ?

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { DescriptionList } from "./DescriptionList.component"
import { DT } from "../DescriptionTerm/DescriptionTerm.component"
import { DD } from "../DescriptionDefinition/DescriptionDefinition.component"

const meta: Meta<typeof DescriptionList> = {
title: "Components/DescriptionList",
component: DescriptionList,
argTypes: {
children: {
control: false,
},
},
}

export default meta
type Story = StoryObj<typeof DescriptionList>

export const Default: Story = {
render: (args) => (
<DescriptionList {...args}>
<DT>Warranty</DT>
<DD>2 years limited warranty with options for extension.</DD>
</DescriptionList>
),
}

/**
* You can use many definitions per term.
*/
export const MultipleDefinitions: Story = {
render: (args) => (
<DescriptionList {...args}>
<DT>Shipping</DT>
<DD>Standard shipping: 5-7 business days.</DD>
<DT>Payment Options</DT>
<DD>Credit/Debit cards, PayPal, and bank transfer.</DD>
<div className="">
<DT>Delivery Time</DT>
<DD>1 day, 2 days, 3 days.</DD>
</div>
</DescriptionList>
),
}
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,22 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

.dl {
display: grid;
grid-template-columns: 1fr 2fr;
padding: 0.5rem;
}

.dl > div {
display: contents;
}

.dl > div > .dt {
grid-column: 1;
}

.dl > div > .dd {
grid-column: 2;
}
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,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-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 = "" }) => (
<dt className={`dt ${className}`}>{children}</dt>
)

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()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

.dt {
Copy link
Contributor

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 existintheme- variable.

display: grid;
align-items: center;
border-bottom: 1px solid var(--color-dd-dt-border);
background-color: var(--color-dt-background);
color: var(--color-dt-text);
font-weight: bold;
gap: 0.25rem;
grid-column: 1;
height: 2rem;
white-space: nowrap;
}
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 { DescriptionTerm, type DescriptionTermProps } from "./DescriptionTerm.component"
Loading
Loading