From 92a6a514829e5275db1eecf605e50162406127a3 Mon Sep 17 00:00:00 2001 From: Haider Alshamma Date: Thu, 12 Dec 2024 16:11:14 -0500 Subject: [PATCH] feat: Add a DescriptionList component --- src/DescriptionList/DescriptionDetails.tsx | 46 ++++ src/DescriptionList/DescriptionList.story.tsx | 215 ++++++++++++++++++ src/DescriptionList/DescriptionList.tsx | 66 ++++++ .../DescriptionListContext.tsx | 19 ++ src/DescriptionList/DescriptionTerm.tsx | 38 ++++ src/DescriptionList/README.md | 80 +++++++ src/DescriptionList/index.ts | 4 + src/index.ts | 1 - 8 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 src/DescriptionList/DescriptionDetails.tsx create mode 100644 src/DescriptionList/DescriptionList.story.tsx create mode 100644 src/DescriptionList/DescriptionList.tsx create mode 100644 src/DescriptionList/DescriptionListContext.tsx create mode 100644 src/DescriptionList/DescriptionTerm.tsx create mode 100644 src/DescriptionList/README.md create mode 100644 src/DescriptionList/index.ts diff --git a/src/DescriptionList/DescriptionDetails.tsx b/src/DescriptionList/DescriptionDetails.tsx new file mode 100644 index 000000000..6cc6c0afd --- /dev/null +++ b/src/DescriptionList/DescriptionDetails.tsx @@ -0,0 +1,46 @@ +import { styled } from "styled-components"; +import { useDescriptionListContext } from "./DescriptionListContext"; + +export const DescriptionDetails = styled.dd(({ theme }) => { + const { autoLayoutBreakpoint, showDivider, density, layout } = useDescriptionListContext(); + + return { + margin: 0, + paddingLeft: theme.space.none, + paddingRight: theme.space.none, + color: theme.colors.darkGrey, + + ...(density === "compact" && { + paddingTop: theme.space.x0_25, + paddingBottom: theme.space.x0_25, + }), + ...(density === "medium" && { + paddingTop: theme.space.x0_75, + paddingBottom: theme.space.x0_75, + }), + ...(density === "relaxed" && { + paddingTop: theme.space.x1_5, + paddingBottom: theme.space.x1_5, + }), + + ...(showDivider && + layout === "inline" && { + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: theme.colors.lightGrey, + }), + + [`@container (min-width: ${autoLayoutBreakpoint})`]: { + ...(showDivider && + layout !== "stacked" && { + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: theme.colors.lightGrey, + }), + + "&:nth-child(2)": { + border: "none", + }, + }, + }; +}); diff --git a/src/DescriptionList/DescriptionList.story.tsx b/src/DescriptionList/DescriptionList.story.tsx new file mode 100644 index 000000000..983c9c71a --- /dev/null +++ b/src/DescriptionList/DescriptionList.story.tsx @@ -0,0 +1,215 @@ +import React from "react"; +import { Flex } from "../Flex"; +import { Heading4 } from "../Type"; +import { Link } from "../Link"; +import { Icon } from "../Icon"; +import { StatusIndicator } from "../StatusIndicator"; +import { DescriptionDetails, DescriptionList, DescriptionTerm } from "."; + +export default { + title: "Components/DescriptionList", + component: DescriptionList, +}; + +const SampleContent = () => ( + <> + Customer + Nulogy + + + Order number + + + + P12-90381-2039 + + Status + + Paid + + Amount + $202.12 + Amount after exchange + + + US $202.12 CA $287.43 + + + +); + +export function WithDifferentLayouts() { + return ( + + + Auto Layout (Default) + + + + + + + Stacked Layout + + + + + + + Inline Layout + + + + + + + ); +} + +export function WithDifferentDensities() { + return ( + + + Compact Density + + + + + + + + + + + + Medium Density (Default) + + + + + + + + + + + + Relaxed Density + + + + + + + + + + + ); +} + +export function WithDifferentFontSizes() { + return ( + + + Smaller Font Size + + + + + + + Small Font Size + + + + + + + Medium Font Size (Default) + + + + + + ); +} + +export function WithDividers() { + return ( + + + With Dividers (Stacked layout) + + + + + + With Dividers (Inline layout) + + + + + + ); +} + +export function WithCustomBreakpoints() { + return ( + + + Auto layout custom breakpoint (800px) + + + + + + ); +} + +export function WithCustomDescriptionTermWidth() { + return ( + + + Custom description term max-width (33.33%) + + + + + + Custom description term max-width (320px) + + + + + + Custom description term max-width (20em) + + + + + + ); +} + +export function CombinedFeatures() { + return ( + + + + Auto Layout + Dividers + Compact Density + Small Font + 40% Term Width + 720px auto layout breakpoint + + + + + + + ); +} diff --git a/src/DescriptionList/DescriptionList.tsx b/src/DescriptionList/DescriptionList.tsx new file mode 100644 index 000000000..3ce691362 --- /dev/null +++ b/src/DescriptionList/DescriptionList.tsx @@ -0,0 +1,66 @@ +import React, { PropsWithChildren } from "react"; +import styled from "styled-components"; +import { Link } from "../Link"; +import { DescriptionListProvider, useDescriptionListContext, DescriptionListConfig } from "./DescriptionListContext"; + +export type Props = PropsWithChildren; + +export function DescriptionList({ + descriptionTermMaxWidth = "50%", + layout = "auto", + showDivider = false, + density = "medium", + fontSize = "medium", + autoLayoutBreakpoint = "640px", + children, +}: Props) { + return ( + + + {children} + + + ); +} + +export const DescriptionListContainer = styled.div({ + containerType: "inline-size", + width: "100%", +}); + +export const StyledDescriptionList = styled.dl(({ theme }) => { + const { descriptionTermMaxWidth, layout, fontSize, autoLayoutBreakpoint } = useDescriptionListContext(); + + return { + margin: 0, + display: "grid", + fontSize: theme.fontSizes[fontSize] ?? theme.fontSizes.medium, + lineHeight: theme.lineHeights.base, + + ...(layout === "inline" && { + gridTemplateColumns: `minmax(0, ${descriptionTermMaxWidth}) auto`, + }), + + ...((layout === "stacked" || layout === "auto") && { + gridTemplateColumns: "1fr", + }), + + [`${Link}`]: { + fontSize: "inherit", + lineHeight: "inherit", + }, + + [`@container (min-width: ${autoLayoutBreakpoint})`]: { + ...(layout === "auto" && { + gridTemplateColumns: `minmax(0, min(50%, ${descriptionTermMaxWidth})) auto`, + }), + }, + }; +}); diff --git a/src/DescriptionList/DescriptionListContext.tsx b/src/DescriptionList/DescriptionListContext.tsx new file mode 100644 index 000000000..fb9cd3f5c --- /dev/null +++ b/src/DescriptionList/DescriptionListContext.tsx @@ -0,0 +1,19 @@ +import React, { PropsWithChildren } from "react"; +import { DefaultNDSThemeType } from "../theme"; + +export interface DescriptionListConfig { + descriptionTermMaxWidth?: string; + layout?: "stacked" | "inline" | "auto"; + showDivider?: boolean; + density?: "medium" | "compact" | "relaxed"; + fontSize?: keyof DefaultNDSThemeType["fontSizes"]; + autoLayoutBreakpoint?: string; +} + +const DescriptionListContext = React.createContext({}); + +export const useDescriptionListContext = () => React.useContext(DescriptionListContext); + +export function DescriptionListProvider({ children, ...config }: PropsWithChildren) { + return {children}; +} diff --git a/src/DescriptionList/DescriptionTerm.tsx b/src/DescriptionList/DescriptionTerm.tsx new file mode 100644 index 000000000..2a167c506 --- /dev/null +++ b/src/DescriptionList/DescriptionTerm.tsx @@ -0,0 +1,38 @@ +import { styled } from "styled-components"; +import { useDescriptionListContext } from "./DescriptionListContext"; + +export const DescriptionTerm = styled.dt(({ theme }) => { + const { showDivider, density, layout } = useDescriptionListContext(); + + return { + gridColumnStart: 1, + color: theme.colors.midGrey, + paddingLeft: theme.space.none, + paddingRight: theme.space.none, + + ...(density === "compact" && { + paddingTop: theme.space.x0_25, + paddingBottom: theme.space.x0_25, + }), + + ...(density === "medium" && { + paddingTop: theme.space.x0_75, + paddingBottom: theme.space.x0_75, + }), + + ...(density === "relaxed" && { + paddingTop: theme.space.x1_5, + paddingBottom: theme.space.x1_5, + }), + + ...(showDivider && { + borderTopWidth: "1px", + borderTopStyle: "solid", + borderTopColor: theme.colors.lightGrey, + }), + + "&:first-child": { + border: "none", + }, + }; +}); diff --git a/src/DescriptionList/README.md b/src/DescriptionList/README.md new file mode 100644 index 000000000..89bdb74b4 --- /dev/null +++ b/src/DescriptionList/README.md @@ -0,0 +1,80 @@ +# DescriptionList + +The DescriptionList component is a structured list that pairs terms with their descriptions, providing a clear way to display key-value information. +The DescriptionList is ideal for displaying key-value pair information in a structured format organizing and explaining related information. + +## Basic Usage + +```tsx +import { DescriptionList, DescriptionTerm, DescriptionDetails } from '@nulogy/components'; + +function MyComponent() { + return ( + + Customer + Nulogy + Purchase order + PO-2024-00123 + + ); +} +``` + +## Props + +| Prop | Type | Description | Default | +|------|------|-------------|---------| +| `layout` | "stacked" \| "inline" \| "auto" | Controls the arrangement of terms and details | `"auto"` | +| `density` | "compact" \| "medium" \| "relaxed" | Controls the spacing between items | `"medium"` | +| `fontSize` | `keyof FontSizes` | Controls the text size | `"medium"` | +| `showDivider` | boolean | Shows divider lines between items | `false` | +| `descriptionTermMaxWidth` | string | Controls the maximum width of description terms | `"50%"` | +| `autoLayoutBreakpoint` | string | Defines the container width at which the layout changes when layout is set to `auto` | `"640px"` | +| `children` | ReactNode | Content to be displayed inside the list | - | + +## Accessibility + +- Maintains semantic HTML structure using `
`, `
`, and `
` elements +- Preserves logical reading order in different layouts + +## Technical Considerations + +- Uses CSS Container Queries for responsive layouts. When layout is set to "auto", the component will switch between stacked and inline layouts based on it's container width, not the screen size. +- `descriptionTermMaxWidth` accepts any CSS length value, such as `px`, `em`, `ch` or `%` +- `autoLayoutBreakpoint` accepts any valid container query value, such as `px`, `em`, `ch` or `%` + +## Advanced Usage + +```tsx + + Customer + Nulogy + + + Order number + + + + P12-90381-2039 + + Status + + Paid + + Amount + $202.12 + Amount after exchange + + + US $202.12 CA $287.43 + + + +``` diff --git a/src/DescriptionList/index.ts b/src/DescriptionList/index.ts new file mode 100644 index 000000000..b212f05c7 --- /dev/null +++ b/src/DescriptionList/index.ts @@ -0,0 +1,4 @@ +export type { Props as DescriptionListProps } from "./DescriptionList"; +export { DescriptionList } from "./DescriptionList"; +export { DescriptionTerm } from "./DescriptionTerm"; +export { DescriptionDetails } from "./DescriptionDetails"; diff --git a/src/index.ts b/src/index.ts index 2998d25ee..e20e1b3eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,4 +87,3 @@ export { Heading1, Heading2, Heading3, Heading4, Text } from "./Type"; export type { TextProps } from "./Type"; export { useWindowDimensions } from "./utils"; export { InlineValidation } from "./Validation"; -export { TopBar } from "./TopBar";