+
diff --git a/src/components/List/__tests__/index.spec.ts b/src/components/List/__tests__/index.spec.ts
new file mode 100644
index 000000000..dfb8d8794
--- /dev/null
+++ b/src/components/List/__tests__/index.spec.ts
@@ -0,0 +1,135 @@
+import { beforeEach, describe, expect, test } from 'vitest'
+import { experimental_AstroContainer as AstroContainer } from 'astro/container'
+import { withJsdomEnvironment } from '@test/unit/helpers/litRuntime'
+
+describe('List (Astro)', () => {
+ let container: AstroContainer
+
+ beforeEach(async () => {
+ container = await AstroContainer.create()
+ })
+
+ test('renders the chat-bubbles variant as question and answer definition pairs', async () => {
+ const List = (await import('@components/List/index.astro')).default
+
+ const renderedHtml = await container.renderToString(List, {
+ props: {
+ variant: 'chat-bubbles',
+ size: '2xl',
+ items: [
+ {
+ lead: 'Why did the system go down?',
+ text: 'Because a config change broke it.',
+ },
+ {
+ lead: 'Why did the config change break it?',
+ text: 'Because the engineer made a mistake.',
+ },
+ ],
+ },
+ })
+
+ await withJsdomEnvironment(async ({ window }) => {
+ window.document.body.innerHTML = renderedHtml
+
+ const definitionList = window.document.querySelector('dl')
+ expect(definitionList).toBeTruthy()
+ expect(definitionList?.className).toContain('max-w-2xl')
+ expect(definitionList?.className).toContain('mx-auto')
+
+ const questions = window.document.querySelectorAll('dt')
+ const answers = window.document.querySelectorAll('dd')
+
+ expect(questions).toHaveLength(2)
+ expect(answers).toHaveLength(2)
+ expect(questions[0]?.textContent).toContain('Q:')
+ expect(questions[0]?.textContent).toContain('Why did the system go down?')
+ expect(answers[0]?.textContent).toContain('A:')
+ expect(answers[0]?.textContent).toContain('Because a config change broke it.')
+ expect(answers[0]?.className).toContain('bg-secondary')
+ })
+ })
+
+ test('throws for an invalid chat-bubbles size', async () => {
+ const List = (await import('@components/List/index.astro')).default
+
+ await expect(
+ container.renderToString(List, {
+ props: {
+ variant: 'chat-bubbles',
+ size: '3xl',
+ items: [
+ {
+ lead: 'Why did the system go down?',
+ text: 'Because a config change broke it.',
+ },
+ ],
+ },
+ })
+ ).rejects.toThrow('ChatBubbles: invalid size "3xl". Expected one of: lg, xl, 2xl.')
+ })
+
+ test('starts numbering at the provided startNumber and applies the provided color for the numbered-with-background-list variant', async () => {
+ const List = (await import('@components/List/index.astro')).default
+
+ const renderedHtml = await container.renderToString(List, {
+ props: {
+ variant: 'numbered-with-background-list',
+ color: 'info',
+ startNumber: 4,
+ items: [
+ {
+ lead: 'Step one',
+ text: 'First item.',
+ },
+ {
+ lead: 'Step two',
+ text: 'Second item.',
+ },
+ ],
+ },
+ })
+
+ await withJsdomEnvironment(async ({ window }) => {
+ window.document.body.innerHTML = renderedHtml
+
+ const badges = Array.from(window.document.querySelectorAll('li > span:first-child'))
+ const leads = Array.from(window.document.querySelectorAll('li em'))
+
+ expect(badges).toHaveLength(2)
+ expect(leads).toHaveLength(2)
+ expect(badges[0]?.textContent?.trim()).toBe('4')
+ expect(badges[1]?.textContent?.trim()).toBe('5')
+ expect(badges[0]?.getAttribute('style')).toContain('background-color: var(--color-info);')
+ expect(leads[0]?.getAttribute('style')).toContain('color: var(--color-info);')
+ })
+ })
+
+ test('uses the default marker color for numbered-with-background-list when no color is provided', async () => {
+ const List = (await import('@components/List/index.astro')).default
+
+ const renderedHtml = await container.renderToString(List, {
+ props: {
+ variant: 'numbered-with-background-list',
+ items: [
+ {
+ lead: 'Default step',
+ text: 'Uses the default marker color.',
+ },
+ ],
+ },
+ })
+
+ await withJsdomEnvironment(async ({ window }) => {
+ window.document.body.innerHTML = renderedHtml
+
+ const badge = window.document.querySelector('li > span:first-child')
+ const lead = window.document.querySelector('li em')
+
+ expect(badge?.className).toContain('bg-primary-offset')
+ expect(lead?.className).toContain('text-primary-offset')
+ expect(badge?.getAttribute('style')).toBeNull()
+ expect(lead?.getAttribute('style')).toBeNull()
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/components/List/index.astro b/src/components/List/index.astro
index c9c1c5860..4bbdb63c9 100644
--- a/src/components/List/index.astro
+++ b/src/components/List/index.astro
@@ -2,6 +2,7 @@
import AccentBorderLeftList from '@components/List/layouts/AccentBorderLeftList.astro'
import BadgeList from '@components/List/layouts/BadgeList.astro'
import CardGridList from '@components/List/layouts/CardGridList.astro'
+import ChatBubbles from '@components/List/layouts/ChatBubbles.astro'
import CheckIconsList from '@components/List/layouts/CheckIconsList.astro'
import ChevronList from '@components/List/layouts/ChevronList.astro'
import ColoredMarkerList from '@components/List/layouts/ColoredMarkerList.astro'
@@ -37,14 +38,19 @@ export type Props = {
text?: string
wrapper?: string
}
- size?: number
+ size?: number | 'lg' | 'xl' | '2xl'
+ color?: string
+ startNumber?: number
variant: string
style?: Record
}
-const { items, size, variant = 'default', classes, style } = Astro.props
+const { items, size, color, startNumber, variant = 'default', classes, style } = Astro.props
const classesProps = classes ? { classes } : {}
-const sizeProps = size !== undefined ? { size } : {}
+const numericSizeProps = typeof size === 'number' ? { size } : {}
+const chatBubbleSizeProps = typeof size === 'string' ? { size } : {}
+const colorProps = typeof color === 'string' && color.trim().length > 0 ? { color } : {}
+const startNumberProps = typeof startNumber === 'number' ? { startNumber } : {}
const styleProps = style ? { style } : {}
const itemsWithColor = items as Array<{
title?: string
@@ -71,15 +77,16 @@ const plainIconItems = items.filter((item): item is Props['items'][number] & { i
{variant === 'accent-border-left-list' && }
{variant === 'badge-list' && }
{variant === 'card-grid-list' && }
+ {variant === 'chat-bubbles' && }
{variant === 'check-icons-list' && }
{variant === 'chevron-list' && }
- {variant === 'colored-marker-list' && }
- {variant === 'numbered-with-background-list' && }
- {variant === 'plain-icon-list' && }
+ {variant === 'colored-marker-list' && }
+ {variant === 'numbered-with-background-list' && }
+ {variant === 'plain-icon-list' && }
{variant === 'side-by-side-list' && }
{variant === 'timeline-list' && }
- {variant === 'two-column-check-icons-list' && }
- {variant === 'two-column-icon-list' && }
- {variant === 'three-column-icon-list' && }
+ {variant === 'two-column-check-icons-list' && }
+ {variant === 'two-column-icon-list' && }
+ {variant === 'three-column-icon-list' && }
{variant === 'zebra-list' && }
diff --git a/src/components/List/layouts/BadgeList.astro b/src/components/List/layouts/BadgeList.astro
index b3cc2dfe2..141ded47f 100644
--- a/src/components/List/layouts/BadgeList.astro
+++ b/src/components/List/layouts/BadgeList.astro
@@ -19,12 +19,12 @@ const { items, classes }: Props = Astro.props
const ulClass = ["space-y-4 sm:space-y-0 sm:table sm:border-separate sm:border-spacing-x-4 sm:border-spacing-y-4", classes?.ul]
const liClass = ["flex flex-col gap-2 sm:table-row", classes?.li]
-const titleCellClass = "sm:table-cell sm:align-baseline"
+const titleCellClass = "sm:table-cell sm:align-top"
const titleClass = [
- "inline-block px-2 py-1 rounded bg-content text-page-base font-mono text-xs font-bold",
+ "inline-block px-2 py-1 rounded bg-content text-page-base font-mono text-xs font-bold sm:mt-1",
classes?.titleClass,
]
-const bodyClass = ["sm:table-cell sm:align-baseline", classes?.content]
+const bodyClass = ["sm:table-cell sm:align-top", classes?.content]
const emClass = ["text-content font-bold not-italic mr-2", classes?.em]
---
diff --git a/src/components/List/layouts/ChatBubbles.astro b/src/components/List/layouts/ChatBubbles.astro
new file mode 100644
index 000000000..15cbae1a1
--- /dev/null
+++ b/src/components/List/layouts/ChatBubbles.astro
@@ -0,0 +1,55 @@
+---
+export type Props = {
+ items: {
+ lead?: string
+ text: string
+ }[]
+ size?: 'lg' | 'xl' | '2xl'
+ classes?: {
+ ul?: string
+ li?: string
+ em?: string
+ }
+}
+
+const { items, classes, size = 'lg' }: Props = Astro.props
+
+const maxWidthClasses = {
+ lg: 'max-w-lg',
+ xl: 'max-w-xl',
+ '2xl': 'max-w-2xl',
+} as const
+
+if (!(size in maxWidthClasses)) {
+ throw new Error(`ChatBubbles: invalid size "${String(size)}". Expected one of: lg, xl, 2xl.`)
+}
+
+const dlClass = ['mx-auto flex w-full flex-col space-y-8', maxWidthClasses[size], classes?.ul]
+const itemClass = ['flex flex-col space-y-2', classes?.li]
+const dtClass = [
+ 'max-w-[85%] self-start rounded-2xl rounded-tl-sm bg-page-offset px-6 py-3 text-lg font-medium text-content-active shadow-sm',
+ classes?.em,
+]
+const ddClass = [
+ 'max-w-[85%] self-end rounded-2xl rounded-tr-sm bg-secondary px-6 py-4 text-secondary-inverse shadow-sm',
+]
+---
+
+