Skip to content

Commit 043e35e

Browse files
committed
Add lists, tables, and highlighter to circuit-breaker-retry-budget-cascade-failure-prevention articles
1 parent b1781da commit 043e35e

16 files changed

Lines changed: 471 additions & 200 deletions

File tree

_TODO.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ src/content/articles/internal-developer-portal-platform-self-service-actions/pdf
283283
├──
284284
└──
285285

286+
<span class="font-mono">
287+
</span>
288+
286289
<Highlighter>
287290
</Highlighter>
288291

src/components/Content/Layout/index.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ const isDeepDive = section === 'Deep Dive Articles'
6868
sizes="100vw"
6969
formats={['avif', 'webp', 'jpeg']}
7070
layout="constrained"
71+
loading="eager"
72+
fetchpriority="high"
7173
fit="cover"
7274
position="center"
7375
class="absolute inset-0 h-full w-full"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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('Inset (Astro)', () => {
6+
let container: AstroContainer
7+
8+
beforeEach(async () => {
9+
container = await AstroContainer.create()
10+
})
11+
12+
test('renders a figure caption below the inset body when provided', async () => {
13+
const Inset = (await import('@components/Inset/index.astro')).default
14+
15+
const renderedHtml = await container.renderToString(Inset, {
16+
props: {
17+
color: 'warning-inverse',
18+
figure: 'Retry budget pseudocode',
19+
variant: 'default',
20+
},
21+
slots: {
22+
default: 'if (!budget.canRetry()) return false;',
23+
},
24+
})
25+
26+
await withJsdomEnvironment(async ({ window }) => {
27+
window.document.body.innerHTML = renderedHtml
28+
29+
const figure = window.document.querySelector('figure')
30+
expect(figure).toBeTruthy()
31+
expect(figure?.className).toContain('mb-8')
32+
33+
const figcaption = figure?.querySelector('figcaption')
34+
expect(figcaption).toBeTruthy()
35+
expect(figcaption?.textContent).toContain('Retry budget pseudocode')
36+
expect(figcaption?.className).toContain('italic')
37+
expect(figcaption?.className).toContain('text-center')
38+
39+
const insetBody = figure?.querySelector('div')
40+
expect(insetBody).toBeTruthy()
41+
expect(insetBody?.className).toContain('w-full')
42+
expect(insetBody?.textContent).toContain('if (!budget.canRetry()) return false;')
43+
})
44+
})
45+
46+
test('renders a fit-width inset when fullWidth is false', async () => {
47+
const Inset = (await import('@components/Inset/index.astro')).default
48+
49+
const renderedHtml = await container.renderToString(Inset, {
50+
props: {
51+
fullWidth: false,
52+
variant: 'default',
53+
},
54+
slots: {
55+
default: 'const fallback = queueForRetry(request)',
56+
},
57+
})
58+
59+
await withJsdomEnvironment(async ({ window }) => {
60+
window.document.body.innerHTML = renderedHtml
61+
62+
const figure = window.document.querySelector('figure')
63+
expect(figure).toBeTruthy()
64+
const insetBody = figure?.querySelector('div')
65+
expect(insetBody).toBeTruthy()
66+
expect(insetBody?.className).toContain('w-fit')
67+
expect(insetBody?.className).toContain('max-w-full')
68+
expect(insetBody?.className).toContain('mx-auto')
69+
})
70+
})
71+
72+
test('omits the figcaption when no figure text is provided', async () => {
73+
const Inset = (await import('@components/Inset/index.astro')).default
74+
75+
const renderedHtml = await container.renderToString(Inset, {
76+
props: {
77+
variant: 'default',
78+
},
79+
slots: {
80+
default: 'const circuit = new CircuitBreaker(service, config)',
81+
},
82+
})
83+
84+
await withJsdomEnvironment(async ({ window }) => {
85+
window.document.body.innerHTML = renderedHtml
86+
87+
expect(window.document.querySelector('figcaption')).toBeNull()
88+
expect(window.document.querySelector('figure div')?.textContent).toContain(
89+
'const circuit = new CircuitBreaker(service, config)'
90+
)
91+
})
92+
})
93+
})

src/components/Inset/index.astro

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,35 @@ import DefaultInset from './layouts/DefaultInset.astro'
33
44
export type Props = {
55
classes?: {
6+
figure?: string
67
layout?: string
78
wrapper?: string
89
}
910
color?: string | undefined
11+
figure?: string
12+
fullWidth?: boolean
1013
size?: number
1114
variant: string
1215
}
1316
14-
const { variant = 'default', classes, color } = Astro.props
17+
const { variant = 'default', classes, color, figure, fullWidth = true } = Astro.props
18+
const layoutClasses: NonNullable<Pick<NonNullable<Props['classes']>, 'layout'>> = {
19+
...(classes?.layout !== undefined ? { layout: classes.layout } : {}),
20+
}
21+
const defaultInsetProps = {
22+
...(Object.keys(layoutClasses).length > 0 ? { classes: layoutClasses } : {}),
23+
...(color !== undefined ? { color } : {}),
24+
fullWidth,
25+
}
1526
---
1627

17-
<div class:list={[classes?.wrapper ? classes.wrapper : 'mb-8']}>
18-
{variant === 'default' &&
19-
(classes?.layout !== undefined ? (
20-
<DefaultInset classes={{ layout: classes.layout }} color={color}>
21-
<slot />
22-
</DefaultInset>
23-
) : (
24-
<DefaultInset color={color}>
25-
<slot />
26-
</DefaultInset>
27-
))}
28-
</div>
28+
<figure class:list={[classes?.wrapper ? classes.wrapper : 'mb-8']}>
29+
{variant === 'default' && (
30+
<DefaultInset {...defaultInsetProps}>
31+
<slot />
32+
</DefaultInset>
33+
)}
34+
{figure && (
35+
<figcaption class="italic text-base text-center mt-3" set:html={figure}></figcaption>
36+
)}
37+
</figure>
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
---
22
export type Props = {
33
classes?: {
4-
layout?: string
4+
layout?: string | undefined
55
}
66
color?: string | undefined
7+
fullWidth?: boolean
78
}
89
9-
const { classes, color }: Props = Astro.props
10+
const { classes, color, fullWidth = true }: Props = Astro.props
1011
1112
const backgroundClass = color ? `bg-${color}` : 'bg-content-inverse-active'
1213
const layoutClass = `font-mono whitespace-pre-wrap ${backgroundClass} border-2 border-trim rounded-md px-6 pt-6`
14+
const wrapperClass = fullWidth ? 'w-full' : 'mx-auto w-fit max-w-full'
1315
---
1416

15-
<div class:list={[layoutClass, classes?.layout]}>
17+
<div class:list={[wrapperClass, layoutClass, classes?.layout]}>
1618
<slot />
1719
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
/**
3+
* HighlighterIconBank
4+
*
5+
* Renders the hidden SVG icon bank used by all HighlighterElement instances on the page.
6+
* Must be rendered exactly once per page, outside any paragraph or phrasing-content context.
7+
* MarkdownLayout.astro owns this responsibility.
8+
*/
9+
import Icon from '@components/Icon/index.astro'
10+
---
11+
12+
<span id="highlighter-icon-bank" hidden aria-hidden="true">
13+
<span data-highlighter-icon="x"><Icon icon="x" size={5} color="inherit" classes="share-icon" /></span>
14+
<span data-highlighter-icon="linkedin"><Icon icon="linkedin" size={5} color="inherit" classes="share-icon" /></span>
15+
<span data-highlighter-icon="bluesky"><Icon icon="bluesky" size={5} color="inherit" classes="share-icon" /></span>
16+
<span data-highlighter-icon="reddit"><Icon icon="reddit" size={5} color="inherit" classes="share-icon" /></span>
17+
<span data-highlighter-icon="mastodon"><Icon icon="mastodon" size={5} color="inherit" classes="share-icon" /></span>
18+
</span>

src/components/Social/Highlighter/client/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ export class HighlighterElement extends LitElement {
213213
const trigger = queryHighlighterTrigger(this)
214214
if (!trigger || trigger === this.triggerButton) return
215215
this.triggerButton = trigger
216-
this.triggerButton.style.cursor = 'pointer'
217216

218217
addButtonEventListeners(
219218
trigger,

src/components/Social/Highlighter/index.astro

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
* <Highlighter ariaLabel="Share this insight">important insight</Highlighter>
1818
* ```
1919
*/
20-
import Icon from '@components/Icon/index.astro'
2120
2221
export interface Props {
2322
/**
@@ -35,16 +34,4 @@ export interface Props {
3534
const { ariaLabel, class: className } = Astro.props
3635
---
3736

38-
<span id="highlighter-icon-bank" hidden aria-hidden="true">
39-
<span data-highlighter-icon="x"><Icon icon="x" size={5} color="inherit" classes="share-icon" /></span>
40-
<span data-highlighter-icon="linkedin"><Icon icon="linkedin" size={5} color="inherit" classes="share-icon" /></span>
41-
<span data-highlighter-icon="bluesky"><Icon icon="bluesky" size={5} color="inherit" classes="share-icon" /></span>
42-
<span data-highlighter-icon="reddit"><Icon icon="reddit" size={5} color="inherit" classes="share-icon" /></span>
43-
<span data-highlighter-icon="mastodon"><Icon icon="mastodon" size={5} color="inherit" classes="share-icon" /></span>
44-
</span>
45-
46-
<highlighter-element aria-label={ariaLabel}>
47-
<span class:list={['text-content', 'bg-warning-inverse', 'rounded', 'px-2', 'py-0.5', className]}>
48-
<slot />
49-
</span>
50-
</highlighter-element>
37+
<highlighter-element aria-label={ariaLabel}><span class:list={['text-content', 'bg-warning-inverse', 'rounded', 'px-2', 'py-0.5', className]}><slot /></span></highlighter-element>

src/components/Social/Highlighter/index.css

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
1-
:where(highlighter-element) {
1+
highlighter-element {
22
display: inline;
33
position: relative;
44
}
55

6-
:where(highlighter-element) .highlighter__wrapper {
6+
highlighter-element .highlighter__wrapper {
77
display: inline;
88
position: relative;
99
}
1010

11-
:where(highlighter-element) .highlighter__trigger {
11+
highlighter-element .highlighter__trigger {
12+
appearance: none;
1213
background: transparent;
1314
border: none;
1415
color: inherit;
15-
cursor: inherit;
16-
display: inline;
16+
cursor: pointer;
17+
display: contents;
1718
font: inherit;
1819
padding: 0;
1920
text-align: left;
2021
vertical-align: baseline;
2122
white-space: normal;
2223
}
2324

24-
:where(highlighter-element) .highlighter__trigger:focus-visible {
25-
outline: 2px solid var(--color-spotlight);
26-
outline-offset: 2px;
27-
}
28-
29-
:where(highlighter-element) .highlighter__content {
25+
highlighter-element .highlighter__content {
3026
background-color: transparent;
3127
border-radius: 0;
3228
color: inherit;
3329
padding: 0;
3430
}
3531

36-
:where(highlighter-element) .share-dialog {
32+
highlighter-element .highlighter__trigger:focus-visible .highlighter__content {
33+
outline: 2px solid var(--color-spotlight);
34+
outline-offset: 2px;
35+
}
36+
37+
highlighter-element .share-dialog {
3738
background-color: var(--color-page-offset);
3839
border-radius: 0.5rem;
3940
bottom: calc(100% + 0.75rem);
@@ -46,8 +47,8 @@
4647
z-index: var(--z-content-floating);
4748
}
4849

49-
:where(highlighter-element) .share-dialog::before,
50-
:where(highlighter-element) .share-dialog::after {
50+
highlighter-element .share-dialog::before,
51+
highlighter-element .share-dialog::after {
5152
background: transparent;
5253
content: '';
5354
height: 0.75rem;
@@ -57,26 +58,27 @@
5758
right: 0;
5859
}
5960

60-
:where(highlighter-element) .share-dialog::before {
61+
highlighter-element .share-dialog::before {
6162
top: -0.75rem;
6263
}
6364

64-
:where(highlighter-element) .share-dialog::after {
65+
highlighter-element .share-dialog::after {
6566
bottom: -0.75rem;
6667
}
6768

68-
:where(highlighter-element) .share-dialog[aria-hidden="false"] {
69+
highlighter-element .share-dialog[aria-hidden="false"] {
6970
display: block;
7071
}
7172

72-
:where(highlighter-element) .share-dialog__buttons {
73+
highlighter-element .share-dialog__buttons {
7374
align-items: center;
7475
display: flex;
7576
gap: 0.25rem;
7677
}
7778

78-
:where(highlighter-element) .share-button {
79+
highlighter-element .share-button {
7980
align-items: center;
81+
appearance: none;
8082
background-color: transparent;
8183
border: none;
8284
border-radius: 9999px;
@@ -89,27 +91,27 @@
8991
width: 2.5rem;
9092
}
9193

92-
:where(highlighter-element) .share-button:hover {
94+
highlighter-element .share-button:focus-visible {
95+
outline: 2px solid var(--color-spotlight);
96+
outline-offset: 2px;
97+
}
98+
99+
highlighter-element .share-button:hover {
93100
background-color: var(--color-page-base);
94101
transform: translateY(-1px);
95102
}
96103

97-
:where(highlighter-element) .share-button:active {
104+
highlighter-element .share-button:active {
98105
transform: translateY(0);
99106
}
100107

101-
:where(highlighter-element) .share-button:focus-visible {
102-
outline: 2px solid var(--color-spotlight);
103-
outline-offset: 2px;
104-
}
105-
106-
:where(highlighter-element) .share-icon {
108+
highlighter-element .share-icon {
107109
fill: currentcolor;
108110
height: 1.25rem;
109111
width: 1.25rem;
110112
}
111113

112-
:where(highlighter-element) .share-dialog__arrow {
114+
highlighter-element .share-dialog__arrow {
113115
border-left: 0.5rem solid transparent;
114116
border-right: 0.5rem solid transparent;
115117
border-top: 0.5rem solid var(--color-page-offset);

0 commit comments

Comments
 (0)