Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

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
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,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">
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't wrap the <dt> in a container div, see https://github.com/cloudoperators/juno/pull/1329/files#r2538718640, the exact same applies here.

<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()
})
})
Loading
Loading