Skip to content

Commit 6772e18

Browse files
committed
New details component, simpler a11y color contrast warning, updated docs bottom nav, updated ref tables
1 parent a019886 commit 6772e18

23 files changed

+315
-44
lines changed

site/src/components/ReferenceTable.astro

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
---
2+
import { readFileSync } from 'node:fs';
3+
import { fileURLToPath } from 'node:url';
4+
import { dirname, join } from 'node:path';
5+
26
interface ReferenceItem {
37
class: string;
4-
styles: string | string[] | Record<string, string>;
8+
styles?: string | string[] | Record<string, string>;
9+
description?: string;
10+
comment?: string; // Optional manual comment to append
511
[key: string]: any; // Allow additional properties
612
}
713
@@ -22,6 +28,68 @@ const {
2228
// Use explicit reference prop or data prop
2329
const referenceData = reference || data || [];
2430
31+
// Parse CSS variables from _root.scss at build time
32+
function parseCSSVariables(): Record<string, string> {
33+
try {
34+
const __filename = fileURLToPath(import.meta.url);
35+
const __dirname = dirname(__filename);
36+
const rootScssPath = join(__dirname, '../../../scss/_root.scss');
37+
const scssContent = readFileSync(rootScssPath, 'utf-8');
38+
39+
const cssVarValues: Record<string, string> = {};
40+
41+
// Match CSS variable declarations: --#{$prefix}variable-name: value;
42+
// This regex captures the variable name and its value
43+
const varRegex = /--#\{\$prefix\}([a-z0-9-]+):\s*([^;]+);/gi;
44+
let match;
45+
46+
while ((match = varRegex.exec(scssContent)) !== null) {
47+
const varName = `--bs-${match[1]}`;
48+
let value = match[2].trim();
49+
50+
// Clean up SCSS interpolation syntax (e.g., #{$variable})
51+
value = value.replace(/#\{[^}]+\}/g, '').trim();
52+
53+
// Remove inline comments
54+
value = value.replace(/\/\/.*$/gm, '').trim();
55+
56+
// Only store if we have a clean value (not empty after removing interpolations)
57+
if (value) {
58+
cssVarValues[varName] = value;
59+
}
60+
}
61+
62+
return cssVarValues;
63+
} catch (error) {
64+
console.warn('Could not parse CSS variables from _root.scss:', error);
65+
return {};
66+
}
67+
}
68+
69+
const cssVarValues = parseCSSVariables();
70+
71+
// Function to add CSS variable value comments
72+
function addVarComments(cssValue: string): string {
73+
const comments: string[] = [];
74+
75+
// Collect resolved values for all CSS variables
76+
cssValue.replace(/var\((--[a-z0-9-]+)\)/gi, (match, varName) => {
77+
const resolvedValue = cssVarValues[varName];
78+
if (resolvedValue) {
79+
comments.push(`<span class="color-3">/* ${resolvedValue} */</span>`);
80+
}
81+
return match;
82+
});
83+
84+
// Append comments after the last semicolon or at the end
85+
if (comments.length > 0) {
86+
const hasSemicolon = cssValue.trimEnd().endsWith(';');
87+
return `${cssValue}${hasSemicolon ? '' : ';'} ${comments.join(' ')}`;
88+
}
89+
90+
return cssValue;
91+
}
92+
2593
// If no explicit columns provided, infer from the first data item
2694
const inferredColumns = columns || (() => {
2795
if (referenceData.length === 0) {
@@ -32,10 +100,12 @@ const inferredColumns = columns || (() => {
32100
}
33101
34102
const firstItem = referenceData[0];
35-
return Object.keys(firstItem).map(key => ({
36-
label: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize first letter
37-
key: key
38-
}));
103+
return Object.keys(firstItem)
104+
.filter(key => key !== 'comment') // Exclude comment field from columns
105+
.map(key => ({
106+
label: key.charAt(0).toUpperCase() + key.slice(1),
107+
key: key
108+
}));
39109
})();
40110
41111
// Transform frontmatter format to table format
@@ -51,26 +121,40 @@ const tableData = referenceData.map((item: ReferenceItem) => {
51121
}
52122
53123
if (key === 'styles') {
124+
let processedStyles = '';
125+
54126
if (typeof value === 'string') {
55-
transformedItem[key] = value;
127+
processedStyles = addVarComments(value);
56128
} else if (typeof value === 'object' && !Array.isArray(value)) {
57129
// Handle object syntax: { prop: value, prop2: value2 }
58-
transformedItem[key] = Object.entries(value)
59-
.map(([prop, val]) => `${prop}: ${val};`)
130+
processedStyles = Object.entries(value)
131+
.map(([prop, val]) => {
132+
const cssLine = `${prop}: ${val};`;
133+
return addVarComments(cssLine);
134+
})
60135
.join('<br/>');
61136
} else if (Array.isArray(value)) {
62-
transformedItem[key] = value.map((style: any) => {
137+
processedStyles = value.map((style: any) => {
63138
if (typeof style === 'string') {
64-
return style.includes(':') ? style + (style.endsWith(';') ? '' : ';') : style;
139+
const formattedStyle = style.includes(':') ? style + (style.endsWith(';') ? '' : ';') : style;
140+
return addVarComments(formattedStyle);
65141
}
66142
if (typeof style === 'object') {
67-
return Object.entries(style).map(([prop, val]) => `${prop}: ${val};`).join(' ');
143+
const cssLine = Object.entries(style).map(([prop, val]) => `${prop}: ${val};`).join(' ');
144+
return addVarComments(cssLine);
68145
}
69146
return style;
70147
}).join('<br/>');
71148
} else {
72-
transformedItem[key] = value || '';
149+
processedStyles = value || '';
73150
}
151+
152+
// Append manual comment if provided in frontmatter
153+
if (item.comment) {
154+
processedStyles += `<br/><span class="color-3">/* ${item.comment} */</span>`;
155+
}
156+
157+
transformedItem[key] = processedStyles;
74158
} else {
75159
transformedItem[key] = value;
76160
}

site/src/components/shortcodes/Callout.astro

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ interface Props {
1313
| 'info-npm-starter'
1414
| 'info-prefersreducedmotion'
1515
| 'info-sanitizer'
16-
| 'warning-color-assistive-technologies'
1716
| 'warning-data-bs-title-vs-title'
1817
| 'warning-input-support'
1918
/**
@@ -28,13 +27,13 @@ const { name, type = 'info' } = Astro.props
2827
let Content: MarkdownInstance<{}>['Content'] | undefined
2928
3029
if (name) {
31-
const callout = await getCalloutByName(name)
30+
const callout = await getCalloutByName(name) as any
3231
3332
if (!callout) {
3433
throw new Error(`Could not find callout with name '${name}'.`)
3534
}
3635
37-
const namedCallout = await callout.render()
36+
const namedCallout = await callout.render() as any
3837
Content = namedCallout.Content
3938
}
4039
---
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
import { getDetailsByName } from '@libs/content'
3+
import type { MarkdownInstance } from 'astro'
4+
5+
interface Props {
6+
/**
7+
* The name of an existing details content to display located in `src/content/details`.
8+
* This will override any content passed in via the default slot.
9+
*/
10+
name?:
11+
| 'danger-example'
12+
| 'info-example'
13+
| 'warning-color-assistive-technologies'
14+
| 'warning-example'
15+
/**
16+
* The summary text displayed before the details are expanded.
17+
* If not provided and `name` is set, will use the `title` from the markdown frontmatter.
18+
*/
19+
summary?: string
20+
}
21+
22+
const { name } = Astro.props
23+
let { summary } = Astro.props
24+
25+
let Content: MarkdownInstance<{}>['Content'] | undefined
26+
27+
if (name) {
28+
const details = await getDetailsByName(name) as any
29+
30+
if (!details) {
31+
throw new Error(`Could not find details with name '${name}'.`)
32+
}
33+
34+
// Use title from frontmatter if summary is not provided
35+
if (!summary && details.data?.title) {
36+
summary = details.data.title
37+
}
38+
39+
const namedDetails = await details.render() as any
40+
Content = namedDetails.Content
41+
}
42+
43+
// Ensure summary is always provided
44+
if (!summary) {
45+
throw new Error('Details component requires either a `summary` prop or a `title` in the markdown frontmatter.')
46+
}
47+
---
48+
49+
<details class="bd-details">
50+
<summary class="bd-details-summary">
51+
<svg class="bd-details-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
52+
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"/>
53+
</svg>
54+
{summary}
55+
</summary>
56+
<div class="bd-details-content">
57+
{Content ? <Content /> : <slot />}
58+
</div>
59+
</details>

site/src/content/callouts/warning-color-assistive-technologies.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

site/src/content/config.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ const docsSchema = z.object({
3535
})
3636
.array()
3737
.optional(),
38+
utility: z.union([z.string(), z.string().array()]).optional(),
39+
classes: z
40+
.object({
41+
class: z.string(),
42+
description: z.string()
43+
})
44+
.array()
45+
.optional(),
3846
sections: z
3947
.object({
4048
description: z.string(),
@@ -57,7 +65,16 @@ const calloutsCollection = defineCollection({
5765
schema: calloutsSchema
5866
})
5967

68+
const detailsSchema = z.object({
69+
title: z.string().optional()
70+
})
71+
72+
const detailsCollection = defineCollection({
73+
schema: detailsSchema
74+
})
75+
6076
export const collections = {
6177
docs: docsCollection,
62-
callouts: calloutsCollection
78+
callouts: calloutsCollection,
79+
details: detailsCollection
6380
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: "Accessibility Tip: Using color to convey meaning"
3+
---
4+
5+
Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies like screen readers. Please ensure the meaning is obvious from the content itself (e.g., the visible text with a [_sufficient_ color contrast](/docs/[[config:docs_version]]/getting-started/accessibility/#color-contrast)) or is included through alternative means, such as additional text hidden with the `.visually-hidden` class.

site/src/content/docs/components/alerts.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Alerts are available for any length of text, as well as an optional close button
1414
A simple ${themeColor.name} alert—check it out!
1515
</div>`)} />
1616

17-
<Callout name="warning-color-assistive-technologies" />
17+
<Details name="warning-color-assistive-technologies" />
1818

1919
### Live example
2020

site/src/content/docs/components/badge.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Set a `background-color` with contrasting foreground `color` with [our `.text-bg
5959
<Example code={getData('theme-colors').map((themeColor) => `<span class="badge theme-${themeColor.name}">${themeColor.title}</span>`)} />
6060
<Example code={getData('theme-colors').map((themeColor) => `<span class="badge badge-subtle theme-${themeColor.name}">${themeColor.title}</span>`)} />
6161

62-
<Callout name="warning-color-assistive-technologies" />
62+
<Details name="warning-color-assistive-technologies" />
6363

6464
## Pill badges
6565

site/src/content/docs/components/buttons.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Bootstrap includes several button variants, each serving its own semantic purpos
2929
`), `
3030
<button type="button" class="btn btn-link">Link</button>`]} />
3131

32-
<Callout name="warning-color-assistive-technologies" />
32+
<Details name="warning-color-assistive-technologies" />
3333

3434
## Disable text wrapping
3535

site/src/content/docs/components/card.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ Set a `background-color` with contrasting foreground `color` with [our `.text-bg
389389
</div>
390390
</div>`)} />
391391

392-
<Callout name="warning-color-assistive-technologies" />
392+
<Details name="warning-color-assistive-technologies" />
393393

394394
### Border
395395

0 commit comments

Comments
 (0)