Skip to content

Commit 7a36b9d

Browse files
committed
Add lists, tables, and highlighter to blameless-postmortem-incident-analysis-systemic-causes articles
1 parent 30e299e commit 7a36b9d

26 files changed

Lines changed: 532 additions & 278 deletions

File tree

‎_TODO.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,6 @@ https://mermaid.js.org/config/directives.html
280280

281281
src/content/articles/internal-developer-portal-platform-self-service-actions/pdf.mdx
282282

283-
## Prompt for Mermaid Images
284-
285283
├──
286284
└──
287285

@@ -293,14 +291,17 @@ src/content/articles/internal-developer-portal-platform-self-service-actions/pdf
293291
size="2xl"
294292
/>
295293

294+
[&>span]:
295+
[&>*:first-child]:
296+
296297
<List
297298
classes={{
298-
wrapper: "sm:ml-6 sm:mr-12 sm:mb-6",
299+
wrapper: "sm:ml-6 sm:mr-12 mb-6",
299300
}}
300301
/>
301302

302303
<Table
303304
classes={{
304-
figure: "sm:ml-8 sm:mr-12 sm:mb-6",
305+
figure: "sm:ml-8 sm:mr-12 mb-6",
305306
}}
306307
/>

‎src/components/Diagram/index.astro‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
---
2+
import type { ImageMetadata } from 'astro'
23
import Image from '@components/Image/index.astro'
34
4-
type ImageSource = {
5-
src: string
6-
width: number
7-
height: number
8-
}
5+
type ImageSource = ImageMetadata
96
107
export interface Props {
118
/** The image source for the diagram */
12-
src?: ImageSource
9+
src?: ImageSource | undefined
1310
/** The desktop preview max width token for the diagram image */
14-
size?: '3xl' | '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'
11+
size?: '3xl' | '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | undefined
1512
/** The magnify button corner for the preview image */
16-
magnifyButtonPosition?: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'
13+
magnifyButtonPosition?: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left' | undefined
1714
/** The title of the diagram */
1815
title: string
1916
/** A detailed description of the diagram for vision-impaired users */
2017
description: string
2118
}
2219
2320
const { src, title, description, magnifyButtonPosition = 'top-right', size } = Astro.props as Props
21+
const imageProps = {
22+
...(typeof size !== 'undefined' ? { size } : {}),
23+
...(typeof magnifyButtonPosition !== 'undefined' ? { magnifyButtonPosition } : {}),
24+
}
2425
const hasDefaultSlot = Astro.slots.has('default')
2526
---
2627

@@ -30,8 +31,7 @@ const hasDefaultSlot = Astro.slots.has('default')
3031
src={src}
3132
alt={title}
3233
variant="center"
33-
size={size}
34-
magnifyButtonPosition={magnifyButtonPosition}
34+
{...imageProps}
3535
/>
3636
)}
3737

‎src/components/Image/client/__fixtures__/index.fixture.astro‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ const {
1515
size,
1616
magnifyButtonPosition = 'top-right',
1717
}: Props = Astro.props
18+
19+
const imageProps = {
20+
...(typeof size !== 'undefined' ? { size } : {}),
21+
...(typeof magnifyButtonPosition !== 'undefined' ? { magnifyButtonPosition } : {}),
22+
}
1823
---
1924

2025
<Image
2126
src={backstageBackground}
2227
alt={alt}
2328
variant={variant}
24-
size={size}
25-
magnifyButtonPosition={magnifyButtonPosition}
29+
{...imageProps}
2630
/>

‎src/components/Image/index.astro‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
---
2+
import type { ImageMetadata } from 'astro'
23
import { Image as AstroImage } from 'astro:assets'
34
import Icon from '@components/Icon/index.astro'
45
5-
type ImageSource = {
6-
src: string
7-
width: number
8-
height: number
9-
}
6+
type ImageSource = ImageMetadata
107
118
type MagnifyButtonPosition = 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'
129
type ImageSize = '3xl' | '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'
1310
1411
export interface Props {
15-
src?: ImageSource
16-
alt?: string
17-
variant?: 'left' | 'right' | 'center' | 'full'
18-
size?: ImageSize
19-
magnifyButtonPosition?: MagnifyButtonPosition
20-
loading?: 'lazy' | 'eager'
12+
src?: ImageSource | undefined
13+
alt?: string | undefined
14+
variant?: 'left' | 'right' | 'center' | 'full' | undefined
15+
size?: ImageSize | undefined
16+
magnifyButtonPosition?: MagnifyButtonPosition | undefined
17+
loading?: 'lazy' | 'eager' | undefined
2118
}
2219
2320
const {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { beforeEach, describe, expect, test } from 'vitest'
2+
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
3+
import { withJsdomEnvironment } from '@test/unit/helpers/litRuntime'
4+
5+
describe('List (Astro)', () => {
6+
let container: AstroContainer
7+
8+
beforeEach(async () => {
9+
container = await AstroContainer.create()
10+
})
11+
12+
test('renders the chat-bubbles variant as question and answer definition pairs', async () => {
13+
const List = (await import('@components/List/index.astro')).default
14+
15+
const renderedHtml = await container.renderToString(List, {
16+
props: {
17+
variant: 'chat-bubbles',
18+
size: '2xl',
19+
items: [
20+
{
21+
lead: 'Why did the system go down?',
22+
text: 'Because a config change broke it.',
23+
},
24+
{
25+
lead: 'Why did the config change break it?',
26+
text: 'Because the engineer made a mistake.',
27+
},
28+
],
29+
},
30+
})
31+
32+
await withJsdomEnvironment(async ({ window }) => {
33+
window.document.body.innerHTML = renderedHtml
34+
35+
const definitionList = window.document.querySelector('dl')
36+
expect(definitionList).toBeTruthy()
37+
expect(definitionList?.className).toContain('max-w-2xl')
38+
expect(definitionList?.className).toContain('mx-auto')
39+
40+
const questions = window.document.querySelectorAll('dt')
41+
const answers = window.document.querySelectorAll('dd')
42+
43+
expect(questions).toHaveLength(2)
44+
expect(answers).toHaveLength(2)
45+
expect(questions[0]?.textContent).toContain('Q:')
46+
expect(questions[0]?.textContent).toContain('Why did the system go down?')
47+
expect(answers[0]?.textContent).toContain('A:')
48+
expect(answers[0]?.textContent).toContain('Because a config change broke it.')
49+
expect(answers[0]?.className).toContain('bg-secondary')
50+
})
51+
})
52+
53+
test('throws for an invalid chat-bubbles size', async () => {
54+
const List = (await import('@components/List/index.astro')).default
55+
56+
await expect(
57+
container.renderToString(List, {
58+
props: {
59+
variant: 'chat-bubbles',
60+
size: '3xl',
61+
items: [
62+
{
63+
lead: 'Why did the system go down?',
64+
text: 'Because a config change broke it.',
65+
},
66+
],
67+
},
68+
})
69+
).rejects.toThrow('ChatBubbles: invalid size "3xl". Expected one of: lg, xl, 2xl.')
70+
})
71+
})

‎src/components/List/index.astro‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import AccentBorderLeftList from '@components/List/layouts/AccentBorderLeftList.astro'
33
import BadgeList from '@components/List/layouts/BadgeList.astro'
44
import CardGridList from '@components/List/layouts/CardGridList.astro'
5+
import ChatBubbles from '@components/List/layouts/ChatBubbles.astro'
56
import CheckIconsList from '@components/List/layouts/CheckIconsList.astro'
67
import ChevronList from '@components/List/layouts/ChevronList.astro'
78
import ColoredMarkerList from '@components/List/layouts/ColoredMarkerList.astro'
@@ -37,14 +38,15 @@ export type Props = {
3738
text?: string
3839
wrapper?: string
3940
}
40-
size?: number
41+
size?: number | 'lg' | 'xl' | '2xl'
4142
variant: string
4243
style?: Record<string, unknown>
4344
}
4445
4546
const { items, size, variant = 'default', classes, style } = Astro.props
4647
const classesProps = classes ? { classes } : {}
47-
const sizeProps = size !== undefined ? { size } : {}
48+
const numericSizeProps = typeof size === 'number' ? { size } : {}
49+
const chatBubbleSizeProps = typeof size === 'string' ? { size } : {}
4850
const styleProps = style ? { style } : {}
4951
const itemsWithColor = items as Array<{
5052
title?: string
@@ -71,15 +73,16 @@ const plainIconItems = items.filter((item): item is Props['items'][number] & { i
7173
{variant === 'accent-border-left-list' && <AccentBorderLeftList items={items} {...classesProps} {...styleProps} />}
7274
{variant === 'badge-list' && <BadgeList items={items} {...classesProps} {...styleProps} />}
7375
{variant === 'card-grid-list' && <CardGridList items={items} {...classesProps} {...styleProps} />}
76+
{variant === 'chat-bubbles' && <ChatBubbles items={items} {...classesProps} {...chatBubbleSizeProps} />}
7477
{variant === 'check-icons-list' && <CheckIconsList items={items} {...classesProps} {...styleProps} />}
7578
{variant === 'chevron-list' && <ChevronList items={items} {...classesProps} {...styleProps} />}
76-
{variant === 'colored-marker-list' && <ColoredMarkerList items={itemsWithColor} {...classesProps} {...sizeProps} {...styleProps} />}
79+
{variant === 'colored-marker-list' && <ColoredMarkerList items={itemsWithColor} {...classesProps} {...numericSizeProps} {...styleProps} />}
7780
{variant === 'numbered-with-background-list' && <NumberedWithBackgroundList items={items} {...classesProps} {...styleProps} />}
78-
{variant === 'plain-icon-list' && <PlainIconList items={plainIconItems} {...classesProps} {...sizeProps} {...styleProps} />}
81+
{variant === 'plain-icon-list' && <PlainIconList items={plainIconItems} {...classesProps} {...numericSizeProps} {...styleProps} />}
7982
{variant === 'side-by-side-list' && <SideBySideList items={items} {...classesProps} {...styleProps} />}
8083
{variant === 'timeline-list' && <TimelineList items={items} {...classesProps} {...styleProps} />}
81-
{variant === 'two-column-check-icons-list' && <TwoColumnCheckIconsList items={items} {...classesProps} {...sizeProps} {...styleProps} />}
82-
{variant === 'two-column-icon-list' && <TwoColumnIconList items={itemsWithIconAndColor} {...classesProps} {...sizeProps} {...styleProps} />}
83-
{variant === 'three-column-icon-list' && <ThreeColumnIconList items={itemsWithIconAndColor} {...classesProps} {...sizeProps} {...styleProps} />}
84+
{variant === 'two-column-check-icons-list' && <TwoColumnCheckIconsList items={items} {...classesProps} {...numericSizeProps} {...styleProps} />}
85+
{variant === 'two-column-icon-list' && <TwoColumnIconList items={itemsWithIconAndColor} {...classesProps} {...numericSizeProps} {...styleProps} />}
86+
{variant === 'three-column-icon-list' && <ThreeColumnIconList items={itemsWithIconAndColor} {...classesProps} {...numericSizeProps} {...styleProps} />}
8487
{variant === 'zebra-list' && <ZebraList items={items} {...classesProps} {...styleProps} />}
8588
</div>

‎src/components/List/layouts/BadgeList.astro‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ const { items, classes }: Props = Astro.props
1919
2020
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]
2121
const liClass = ["flex flex-col gap-2 sm:table-row", classes?.li]
22-
const titleCellClass = "sm:table-cell sm:align-baseline"
22+
const titleCellClass = "sm:table-cell sm:align-top"
2323
const titleClass = [
24-
"inline-block px-2 py-1 rounded bg-content text-page-base font-mono text-xs font-bold",
24+
"inline-block px-2 py-1 rounded bg-content text-page-base font-mono text-xs font-bold sm:mt-1",
2525
classes?.titleClass,
2626
]
27-
const bodyClass = ["sm:table-cell sm:align-baseline", classes?.content]
27+
const bodyClass = ["sm:table-cell sm:align-top", classes?.content]
2828
const emClass = ["text-content font-bold not-italic mr-2", classes?.em]
2929
---
3030

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
export type Props = {
3+
items: {
4+
lead?: string
5+
text: string
6+
}[]
7+
size?: 'lg' | 'xl' | '2xl'
8+
classes?: {
9+
ul?: string
10+
li?: string
11+
em?: string
12+
}
13+
}
14+
15+
const { items, classes, size = 'lg' }: Props = Astro.props
16+
17+
const maxWidthClasses = {
18+
lg: 'max-w-lg',
19+
xl: 'max-w-xl',
20+
'2xl': 'max-w-2xl',
21+
} as const
22+
23+
if (!(size in maxWidthClasses)) {
24+
throw new Error(`ChatBubbles: invalid size "${String(size)}". Expected one of: lg, xl, 2xl.`)
25+
}
26+
27+
const dlClass = ['mx-auto flex w-full flex-col space-y-8', maxWidthClasses[size], classes?.ul]
28+
const itemClass = ['flex flex-col space-y-2', classes?.li]
29+
const dtClass = [
30+
'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',
31+
classes?.em,
32+
]
33+
const ddClass = [
34+
'max-w-[85%] self-end rounded-2xl rounded-tr-sm bg-secondary px-6 py-4 text-secondary-inverse shadow-sm',
35+
]
36+
---
37+
38+
<dl class:list={dlClass}>
39+
{
40+
items.map((item) => (
41+
<div class:list={itemClass}>
42+
{item.lead && (
43+
<dt class:list={dtClass}>
44+
<span class="mr-2 font-bold text-secondary" aria-hidden="true">Q:</span>
45+
<span set:html={item.lead}></span>
46+
</dt>
47+
)}
48+
<dd class:list={ddClass}>
49+
<span class="mr-2 font-bold text-secondary-inverse" aria-hidden="true">A:</span>
50+
<span set:html={item.text}></span>
51+
</dd>
52+
</div>
53+
))
54+
}
55+
</dl>

‎src/components/Social/Highlighter/client/__tests__/index.spec.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,10 @@ describe('HighlighterElement', () => {
159159
expect(element.querySelectorAll('.share-button')).toHaveLength(mockPlatforms.length)
160160
expect(element.getAttribute('aria-label')).toBe(defaultProps.ariaLabel)
161161

162-
const trigger = element.querySelector('.highlighter__trigger') as HTMLSpanElement | null
162+
const trigger = element.querySelector('.highlighter__trigger') as HTMLButtonElement | null
163163
expect(trigger).toBeTruthy()
164164
expect(trigger?.getAttribute('aria-label')).toBeNull()
165-
expect(trigger?.getAttribute('role')).toBe('button')
166-
expect(trigger?.getAttribute('tabindex')).toBe('0')
165+
expect(trigger?.getAttribute('type')).toBe('button')
167166
expect(trigger?.textContent).toContain(defaultProps.content)
168167

169168
const dialog = element.querySelector('.share-dialog') as HTMLElement | null
@@ -186,7 +185,7 @@ describe('HighlighterElement', () => {
186185
const dialog = element.querySelector('.share-dialog') as HTMLElement | null
187186
expect(dialog?.getAttribute('aria-hidden')).toBe('true')
188187

189-
const trigger = element.querySelector('.highlighter__trigger') as HTMLSpanElement | null
188+
const trigger = element.querySelector('.highlighter__trigger') as HTMLButtonElement | null
190189
expect(trigger?.getAttribute('aria-expanded')).toBe('false')
191190

192191
element.dispatchEvent(new window.MouseEvent('mouseenter', { bubbles: true }))
@@ -204,7 +203,7 @@ describe('HighlighterElement', () => {
204203
const dialog = element.querySelector('.share-dialog') as HTMLElement | null
205204
expect(dialog?.getAttribute('aria-hidden')).toBe('true')
206205

207-
const trigger = element.querySelector('.highlighter__trigger') as HTMLSpanElement | null
206+
const trigger = element.querySelector('.highlighter__trigger') as HTMLButtonElement | null
208207
expect(trigger).toBeTruthy()
209208

210209
if (!trigger) {

‎src/components/Social/Highlighter/client/__tests__/selectors.spec.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('HighlighterElement selectors', () => {
4545
expect(
4646
trigger,
4747
'HighlighterElement should render a trigger with .highlighter__trigger'
48-
).toBeInstanceOf(HTMLSpanElement)
48+
).toBeInstanceOf(HTMLButtonElement)
4949
expect(
5050
dialog,
5151
'HighlighterElement should render a share dialog with .share-dialog'
@@ -66,8 +66,7 @@ describe('HighlighterElement selectors', () => {
6666

6767
const describedBy = trigger.getAttribute('aria-describedby')
6868
expect(describedBy, 'HighlighterElement trigger should set aria-describedby').toBeTruthy()
69-
expect(trigger.getAttribute('role')).toBe('button')
70-
expect(trigger.getAttribute('tabindex')).toBe('0')
69+
expect(trigger.getAttribute('type')).toBe('button')
7170

7271
const hint = describedBy ? queryElementById(element, describedBy) : null
7372
expect(

0 commit comments

Comments
 (0)