From 81c84e8de5a74b0c60b78d8f7a95b258dda417dd Mon Sep 17 00:00:00 2001 From: brooke Date: Tue, 10 Dec 2024 21:26:23 -0500 Subject: [PATCH] adds orderedlist and list item to typography package --- .changeset/lovely-planets-sort.md | 5 ++ packages/typography/README.md | 15 ++++ packages/typography/package.json | 1 + .../OrderedList/ListItem/ListItem.styles.ts | 69 +++++++++++++++++++ .../src/OrderedList/ListItem/ListItem.tsx | 51 ++++++++++++++ .../OrderedList/ListItem/ListItem.types.ts | 9 +++ .../src/OrderedList/ListItem/index.ts | 2 + .../OrderedList/OrderedList.spec.tsx | 66 ++++++++++++++++++ .../OrderedList/OrderedList.stories.tsx | 50 ++++++++++++++ .../OrderedList/OrderedList.styles.ts | 7 ++ .../OrderedList/OrderedList/OrderedList.tsx | 45 ++++++++++++ .../OrderedList/OrderedList.types.tsx | 3 + .../src/OrderedList/OrderedList/index.ts | 2 + .../OrderedListContext/OrderedListContext.ts | 11 +++ .../OrderedList/OrderedListContext/index.ts | 4 ++ packages/typography/src/OrderedList/index.ts | 6 ++ .../typography/src/Typography.stories.tsx | 17 +++++ packages/typography/src/index.ts | 1 + packages/typography/tsconfig.json | 3 + 19 files changed, 367 insertions(+) create mode 100644 .changeset/lovely-planets-sort.md create mode 100644 packages/typography/src/OrderedList/ListItem/ListItem.styles.ts create mode 100644 packages/typography/src/OrderedList/ListItem/ListItem.tsx create mode 100644 packages/typography/src/OrderedList/ListItem/ListItem.types.ts create mode 100644 packages/typography/src/OrderedList/ListItem/index.ts create mode 100644 packages/typography/src/OrderedList/OrderedList/OrderedList.spec.tsx create mode 100644 packages/typography/src/OrderedList/OrderedList/OrderedList.stories.tsx create mode 100644 packages/typography/src/OrderedList/OrderedList/OrderedList.styles.ts create mode 100644 packages/typography/src/OrderedList/OrderedList/OrderedList.tsx create mode 100644 packages/typography/src/OrderedList/OrderedList/OrderedList.types.tsx create mode 100644 packages/typography/src/OrderedList/OrderedList/index.ts create mode 100644 packages/typography/src/OrderedList/OrderedListContext/OrderedListContext.ts create mode 100644 packages/typography/src/OrderedList/OrderedListContext/index.ts create mode 100644 packages/typography/src/OrderedList/index.ts diff --git a/.changeset/lovely-planets-sort.md b/.changeset/lovely-planets-sort.md new file mode 100644 index 0000000000..ea799dba47 --- /dev/null +++ b/.changeset/lovely-planets-sort.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/typography': minor +--- + +Adds OrderedList and ListItem components diff --git a/packages/typography/README.md b/packages/typography/README.md index 22a6af51a2..113c148207 100644 --- a/packages/typography/README.md +++ b/packages/typography/README.md @@ -98,6 +98,8 @@ All props extend the HTMLElementProps of their root tag, however the below compo | `Label` | `label` | | `Description` | `p` | | `Label` | `p` | +| `OrderedList` | `ol` | +| `ListItem` | `li` | # H1 @@ -205,3 +207,16 @@ _Note_: `BackLink` is intended for internal linking only | -------------- | ------------ | ------------------------------------------------ | --------------------------------------------- | | `darkMode` | `boolean` | Determines if the component renders in dark mode | `false` | | `baseFontSize` | `13` \| `16` | font-size applied to typography element | Defaults to value set by LeafyGreen Provider. | + +# OrderedList + +| Prop | Type | Description | Default | +| ---------- | --------- | ------------------------------------------------ | ------- | +| `darkMode` | `boolean` | Determines if the component renders in dark mode | `false` | + +# ListItem + +| Prop | Type | Description | Default | +| ----------- | ----------------- | -------------------------------------------------------------- | ------- | +| title | `string` | The title of the step. | | +| description | `React.ReactNode` | The description of the step. This will render below the title. | | diff --git a/packages/typography/package.json b/packages/typography/package.json index b191f619db..a3d38c5770 100644 --- a/packages/typography/package.json +++ b/packages/typography/package.json @@ -22,6 +22,7 @@ "access": "public" }, "dependencies": { + "@leafygreen-ui/descendants": "^1.0.1", "@leafygreen-ui/emotion": "^4.0.8", "@leafygreen-ui/icon": "^12.6.0", "@leafygreen-ui/lib": "^13.6.1", diff --git a/packages/typography/src/OrderedList/ListItem/ListItem.styles.ts b/packages/typography/src/OrderedList/ListItem/ListItem.styles.ts new file mode 100644 index 0000000000..43cbaade6b --- /dev/null +++ b/packages/typography/src/OrderedList/ListItem/ListItem.styles.ts @@ -0,0 +1,69 @@ +import { css } from '@leafygreen-ui/emotion'; +import { createUniqueClassName, Theme } from '@leafygreen-ui/lib'; +import { palette } from '@leafygreen-ui/palette'; +import { spacing, transitionDuration } from '@leafygreen-ui/tokens'; + +export const contentClassName = createUniqueClassName('content'); +export const stepIconClassName = createUniqueClassName('step'); + +const STEP_SIZE = 20; + +export const baseStyles = css` + display: flex; + gap: ${spacing[200]}px; + margin-bottom: ${spacing[100]}px; + + &:last-of-type { + .${contentClassName} { + margin: 0; + } + } +`; + +export const stepWrapperStyles = css` + position: relative; + + &::after { + background: ${palette.gray.base}; + position: absolute; + width: 1px; + height: calc(100% - ${STEP_SIZE}px); + left: 50%; + transition: background ${transitionDuration.default}ms ease; + } +`; + +export const contentStyles = css` + margin-block-end: ${spacing[400]}px; + transition: margin-block-end ${transitionDuration.slowest}ms ease-in-out; + width: 100%; +`; + +export const stepStyles = css` + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid; + transition: ${transitionDuration.default}ms ease; + width: ${STEP_SIZE}px; + height: ${STEP_SIZE}px; + position: relative; + font-size: 12px; + font-weight: 500; +`; + +export const titleStyles = css` + font-weight: bold; + line-height: unset; +`; + +export const getThemedStateStyles = (theme: Theme) => { + const isLight = theme === Theme.Light; + + return css` + color: ${isLight ? palette.gray.dark1 : palette.gray.light1}; + background-color: rgba(255, 255, 255, 0); + border-color: ${isLight ? palette.gray.base : palette.gray.light1}; + `; +}; diff --git a/packages/typography/src/OrderedList/ListItem/ListItem.tsx b/packages/typography/src/OrderedList/ListItem/ListItem.tsx new file mode 100644 index 0000000000..67ef4190cf --- /dev/null +++ b/packages/typography/src/OrderedList/ListItem/ListItem.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import { useDescendant } from '@leafygreen-ui/descendants'; +import { cx } from '@leafygreen-ui/emotion'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; + +import { Body, Description } from '../..'; +import { OrderedListContext } from '../OrderedListContext/'; + +import { + baseStyles, + contentClassName, + contentStyles, + getThemedStateStyles, + stepIconClassName, + stepStyles, + stepWrapperStyles, + titleStyles, +} from './ListItem.styles'; +import { ListItemProps } from './ListItem.types'; + +const ListItem = React.forwardRef( + ( + { children, className, title, description, ...rest }: ListItemProps, + forwardRef: React.ForwardedRef, + ) => { + const { index, ref } = useDescendant(OrderedListContext, forwardRef); + const { theme } = useDarkMode(); + + return ( +
  • +
    +
    + {index + 1} +
    +
    + +
    + + {title} + + {description} +
    +
  • + ); + }, +); + +ListItem.displayName = 'ListItem'; + +export { ListItem }; diff --git a/packages/typography/src/OrderedList/ListItem/ListItem.types.ts b/packages/typography/src/OrderedList/ListItem/ListItem.types.ts new file mode 100644 index 0000000000..e189d49ef9 --- /dev/null +++ b/packages/typography/src/OrderedList/ListItem/ListItem.types.ts @@ -0,0 +1,9 @@ +import { HTMLElementProps } from '@leafygreen-ui/lib'; + +import { CommonTypographyProps } from '../../types'; + +export type ListItemProps = HTMLElementProps<'li'> & + CommonTypographyProps & { + title?: React.ReactNode; + description?: React.ReactNode; + }; diff --git a/packages/typography/src/OrderedList/ListItem/index.ts b/packages/typography/src/OrderedList/ListItem/index.ts new file mode 100644 index 0000000000..b792cdd87a --- /dev/null +++ b/packages/typography/src/OrderedList/ListItem/index.ts @@ -0,0 +1,2 @@ +export { ListItem } from './ListItem'; +export { ListItemProps } from './ListItem.types'; diff --git a/packages/typography/src/OrderedList/OrderedList/OrderedList.spec.tsx b/packages/typography/src/OrderedList/OrderedList/OrderedList.spec.tsx new file mode 100644 index 0000000000..db70883589 --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/OrderedList.spec.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { ListItem } from '../ListItem'; + +import { OrderedList } from './OrderedList'; + +const renderOL = () => { + return render( + + + + + , + ); +}; + +describe('packages/typography/ordered-list', () => { + describe('OrderedList', () => { + test('renders an ordered list', () => { + renderOL(); + + const ol = screen.getByRole('list', { name: 'OrderedList' }); + expect(ol).toBeInTheDocument(); + }); + + test('renders list items', () => { + renderOL(); + + const items = screen.getAllByRole('listitem'); + expect(items).toHaveLength(3); + }); + + test('renders list items with titles', () => { + renderOL(); + + const titles = screen.getAllByText('Title'); + expect(titles).toHaveLength(3); + }); + + test('renders list items with descriptions', () => { + renderOL(); + + const descriptions = screen.getAllByText('Description'); + expect(descriptions).toHaveLength(3); + }); + + test('renders list items with step numbers', () => { + renderOL(); + + const steps = screen.getAllByRole('listitem'); + steps.forEach((step, index) => { + expect(step).toHaveTextContent(`${index + 1}`); + }); + }); + + test('renders list items with step numbers in order', () => { + renderOL(); + + const steps = screen.getAllByRole('listitem'); + steps.forEach((step, index) => { + expect(step).toHaveTextContent(`${index + 1}`); + }); + }); + }); +}); diff --git a/packages/typography/src/OrderedList/OrderedList/OrderedList.stories.tsx b/packages/typography/src/OrderedList/OrderedList/OrderedList.stories.tsx new file mode 100644 index 0000000000..27e518b7a1 --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/OrderedList.stories.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { type StoryMetaType } from '@lg-tools/storybook-utils'; + +import { Link, ListItem, OrderedList } from '../../index'; + +import { OrderedListProps } from './OrderedList.types'; + +export const Basic = (args: OrderedListProps) => { + return ( + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna.{' '} + Learn more. + + } + /> + + + ); +}; + +const meta: StoryMetaType = { + title: 'Components/Typography/OrderedList', + component: OrderedList, + parameters: { + default: 'Basic', + generate: { + combineArgs: { + darkMode: [false, true], + }, + }, + }, + args: { + darkMode: false, + }, +}; +export default meta; + +export const Generated = () => <>; diff --git a/packages/typography/src/OrderedList/OrderedList/OrderedList.styles.ts b/packages/typography/src/OrderedList/OrderedList/OrderedList.styles.ts new file mode 100644 index 0000000000..c89b8fdbdb --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/OrderedList.styles.ts @@ -0,0 +1,7 @@ +import { css } from '@leafygreen-ui/emotion'; + +export const baseStyles = css` + list-style-type: none; + padding: 0; + margin: 0; +`; diff --git a/packages/typography/src/OrderedList/OrderedList/OrderedList.tsx b/packages/typography/src/OrderedList/OrderedList/OrderedList.tsx new file mode 100644 index 0000000000..f7211aaf6f --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/OrderedList.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { + DescendantsProvider, + useInitDescendants, +} from '@leafygreen-ui/descendants'; +import { cx } from '@leafygreen-ui/emotion'; +import LeafyGreenProvider, { + useDarkMode, +} from '@leafygreen-ui/leafygreen-provider'; + +import { OrderedListContext } from '../OrderedListContext/OrderedListContext'; + +import { baseStyles } from './OrderedList.styles'; +import { OrderedListProps } from './OrderedList.types'; + +const OrderedList = React.forwardRef( + ( + { children, className, darkMode: darkModeProp, ...rest }: OrderedListProps, + ref: React.ForwardedRef, + ) => { + const { descendants, dispatch } = + useInitDescendants(OrderedListContext); + + const { darkMode } = useDarkMode(darkModeProp); + + return ( + + +
      + {children} +
    +
    +
    + ); + }, +); + +OrderedList.displayName = 'OrderedList'; + +export { OrderedList }; diff --git a/packages/typography/src/OrderedList/OrderedList/OrderedList.types.tsx b/packages/typography/src/OrderedList/OrderedList/OrderedList.types.tsx new file mode 100644 index 0000000000..8e54cef6ad --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/OrderedList.types.tsx @@ -0,0 +1,3 @@ +import { HTMLElementProps } from '@leafygreen-ui/lib'; + +export type OrderedListProps = HTMLElementProps<'ol'> & { darkMode?: boolean }; diff --git a/packages/typography/src/OrderedList/OrderedList/index.ts b/packages/typography/src/OrderedList/OrderedList/index.ts new file mode 100644 index 0000000000..1d65558f4e --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedList/index.ts @@ -0,0 +1,2 @@ +export { OrderedList } from './OrderedList'; +export { OrderedListProps } from './OrderedList.types'; diff --git a/packages/typography/src/OrderedList/OrderedListContext/OrderedListContext.ts b/packages/typography/src/OrderedList/OrderedListContext/OrderedListContext.ts new file mode 100644 index 0000000000..4946de566f --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedListContext/OrderedListContext.ts @@ -0,0 +1,11 @@ +import { + createDescendantsContext, + useDescendantsContext, +} from '@leafygreen-ui/descendants'; + +export const OrderedListContext = + createDescendantsContext('OrderedListContext'); + +export function useOrderedListContext() { + return useDescendantsContext(OrderedListContext); +} diff --git a/packages/typography/src/OrderedList/OrderedListContext/index.ts b/packages/typography/src/OrderedList/OrderedListContext/index.ts new file mode 100644 index 0000000000..4e154c363d --- /dev/null +++ b/packages/typography/src/OrderedList/OrderedListContext/index.ts @@ -0,0 +1,4 @@ +export { + OrderedListContext, + useOrderedListContext, +} from './OrderedListContext'; diff --git a/packages/typography/src/OrderedList/index.ts b/packages/typography/src/OrderedList/index.ts new file mode 100644 index 0000000000..0b3d2c5578 --- /dev/null +++ b/packages/typography/src/OrderedList/index.ts @@ -0,0 +1,6 @@ +export { ListItem } from './ListItem'; +export { OrderedList, OrderedListProps } from './OrderedList'; +export { + OrderedListContext, + useOrderedListContext, +} from './OrderedListContext'; diff --git a/packages/typography/src/Typography.stories.tsx b/packages/typography/src/Typography.stories.tsx index e7ae633452..d34eb71121 100644 --- a/packages/typography/src/Typography.stories.tsx +++ b/packages/typography/src/Typography.stories.tsx @@ -23,6 +23,8 @@ import { InlineKeyCode, Label, Link, + ListItem, + OrderedList, Overline, StaticWidthText, Subtitle, @@ -121,6 +123,21 @@ const TypographyDemoComponent = ({ Hello I am an Error! + + + + + +
    diff --git a/packages/typography/src/index.ts b/packages/typography/src/index.ts index f095f0139b..8ed7cff9a1 100644 --- a/packages/typography/src/index.ts +++ b/packages/typography/src/index.ts @@ -29,6 +29,7 @@ export { type LinkProps, linkStyles, } from './Link'; +export { ListItem, OrderedList } from './OrderedList'; export { default as Overline } from './Overline/Overline'; export type { OverlineProps } from './Overline/Overline.types'; export { bodyTypeScaleStyles } from './styles'; diff --git a/packages/typography/tsconfig.json b/packages/typography/tsconfig.json index 1bd4ee6ae4..c0027b0444 100644 --- a/packages/typography/tsconfig.json +++ b/packages/typography/tsconfig.json @@ -15,6 +15,9 @@ ], "exclude": ["**/*.spec.*", "**/*.stories.*"], "references": [ + { + "path": "../descendants" + }, { "path": "../emotion" },