Skip to content

Commit 249c12b

Browse files
committed
Improve actions and feedback guides.
1 parent 106344c commit 249c12b

12 files changed

Lines changed: 716 additions & 70 deletions

File tree

‎frontend/docs/componentGuide.ts‎

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import {
2+
BREAKPOINTS,
3+
mediaBelow,
4+
styleBuilder,
5+
themes,
6+
} from '@purestack/ts-style'
7+
import { defineComponent, html, type RefOrValue } from 'regor'
8+
9+
export interface ComponentHeader {
10+
name: RefOrValue<string>
11+
summary: RefOrValue<string>
12+
source: RefOrValue<string>
13+
kind: RefOrValue<string>
14+
section?: RefOrValue<string>
15+
sectionHref?: RefOrValue<string>
16+
}
17+
18+
export interface ComponentExample {
19+
label: RefOrValue<string>
20+
caption: RefOrValue<string>
21+
}
22+
23+
export function defineComponentGuideComponents() {
24+
return {
25+
ComponentHeader: defineComponent<ComponentHeader>(
26+
html`<header class="component-header">
27+
<div class="component-header__top">
28+
<div class="component-header__identity"><code>{{ name }}</code><span>{{ kind }}</span></div>
29+
<nav class="component-header__links" aria-label="Component resources">
30+
<a href="#api-reference">API <span aria-hidden="true">↗</span></a>
31+
<a :href="source" target="_blank" rel="noopener noreferrer">Source <span aria-hidden="true">↗</span></a>
32+
</nav>
33+
</div>
34+
<p class="component-header__summary">{{ summary }}</p>
35+
<a class="component-header__back" :href="sectionHref">{{ section }} <span aria-hidden="true">↗</span></a>
36+
</header>`,
37+
{
38+
props: ['name', 'summary', 'source', 'kind', 'section', 'sectionHref'],
39+
context: (head) => ({
40+
...head.props,
41+
section: head.props.section ?? 'Actions & feedback',
42+
sectionHref: head.props.sectionHref ?? '/components/actions/',
43+
}),
44+
},
45+
),
46+
ComponentExample: defineComponent<ComponentExample>(
47+
html`<section class="component-example" :aria-label="label">
48+
<header class="component-example__heading"><span class="component-example__dot" aria-hidden="true"></span><strong>{{ label }}</strong><span class="component-example__tag">COMPOSITION</span></header>
49+
<div class="component-example__stage"><slot></slot></div>
50+
<p class="component-example__caption">{{ caption }}</p>
51+
</section>`,
52+
{ props: ['label', 'caption'] },
53+
),
54+
}
55+
}
56+
57+
export function registerComponentGuideStyles() {
58+
themes.forEach((theme, palette) => {
59+
const root = styleBuilder.get(theme)
60+
const neutral = palette.semanticTone.neutral
61+
const accent = palette.semanticTone.accent
62+
root.select('.component-guide-card > .badge').css({ alignSelf: 'start' })
63+
root.select('.component-header').css({
64+
position: 'relative',
65+
overflow: 'hidden',
66+
padding: '1rem 1.25rem',
67+
margin: '0.75rem 0 1.5rem',
68+
border: `1px solid ${neutral.border.subtle}`,
69+
borderLeft: `3px solid ${accent.border.default}`,
70+
borderRadius: '0.75rem',
71+
background: `radial-gradient(ellipse at 100% 0%, ${accent.surface.rest.background}, transparent 70%), ${neutral.surface.rest.background}`,
72+
})
73+
root.select('.component-header__top').css({
74+
display: 'flex',
75+
flexWrap: 'wrap',
76+
alignItems: 'baseline',
77+
justifyContent: 'space-between',
78+
gap: '0.6rem 1.25rem',
79+
})
80+
root.select('.component-header__back').css({
81+
fontSize: '0.75rem',
82+
color: neutral.text.subtle,
83+
textDecoration: 'none',
84+
})
85+
root.select('.component-header__identity').css({
86+
display: 'flex',
87+
flexWrap: 'wrap',
88+
alignItems: 'baseline',
89+
gap: '0.4rem 0.75rem',
90+
minWidth: '0',
91+
})
92+
root.select('.component-header__identity code').css({
93+
color: accent.text.default,
94+
fontSize: '1rem',
95+
background: 'none',
96+
border: 'none',
97+
padding: '0',
98+
margin: '0',
99+
lineHeight: '1.4',
100+
overflowWrap: 'anywhere',
101+
})
102+
root
103+
.select('.component-header__identity span, .component-example__tag')
104+
.css({
105+
fontSize: '0.65rem',
106+
letterSpacing: '0.1em',
107+
textTransform: 'uppercase',
108+
color: neutral.text.subtle,
109+
})
110+
root.select('.component-header__summary').css({
111+
fontSize: '0.95rem',
112+
lineHeight: '1.6',
113+
margin: '0.6rem 0 0.4rem',
114+
})
115+
root.select('.component-header__links').css({
116+
display: 'flex',
117+
flexWrap: 'wrap',
118+
alignItems: 'baseline',
119+
gap: '0.75rem',
120+
flexShrink: '0',
121+
})
122+
root.select('.component-header__links a').css({
123+
fontSize: '0.75rem',
124+
fontWeight: '600',
125+
textDecoration: 'none',
126+
color: accent.text.default,
127+
})
128+
root
129+
.select('.component-header a:hover')
130+
.css({ textDecoration: 'underline' })
131+
root.select('.component-example').css({
132+
margin: '1.5rem 0',
133+
border: `1px solid ${neutral.border.subtle}`,
134+
borderRadius: '1rem',
135+
background: neutral.surface.rest.background,
136+
})
137+
root.select('.component-example__heading').css({
138+
display: 'flex',
139+
flexWrap: 'wrap',
140+
alignItems: 'center',
141+
gap: '0.6rem',
142+
padding: '0.9rem 1.25rem',
143+
borderBottom: `1px solid ${neutral.border.subtle}`,
144+
fontSize: '0.8rem',
145+
})
146+
root.select('.component-example__dot').css({
147+
width: '0.5rem',
148+
height: '0.5rem',
149+
borderRadius: '50%',
150+
background: accent.text.default,
151+
})
152+
root.select('.component-example__tag').css({ marginLeft: 'auto' })
153+
root.select('.component-example__stage').css({
154+
padding: 'clamp(1rem, 3vw, 2rem)',
155+
background: `radial-gradient(${neutral.border.subtle} 1px, transparent 1px)`,
156+
backgroundSize: '16px 16px',
157+
minWidth: '0',
158+
})
159+
root.select('.component-example__caption').css({
160+
margin: '0',
161+
padding: '0.9rem 1.25rem',
162+
borderTop: `1px solid ${neutral.border.subtle}`,
163+
fontSize: '0.8rem',
164+
color: neutral.text.subtle,
165+
lineHeight: '1.6',
166+
})
167+
root
168+
.select('.component-preview-stage')
169+
.css({ padding: 'clamp(1rem, 3vw, 2rem)', minWidth: '0' })
170+
root.select('.component-appearance-grid').css({
171+
display: 'grid',
172+
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 14rem), 1fr))',
173+
gap: '1rem',
174+
alignItems: 'start',
175+
})
176+
root.select('.component-appearance-cell').css({
177+
display: 'flex',
178+
flexDirection: 'column',
179+
alignItems: 'stretch',
180+
gap: '1rem',
181+
padding: '1rem',
182+
minWidth: '0',
183+
border: `1px solid ${neutral.border.subtle}`,
184+
borderRadius: '0.75rem',
185+
background: neutral.surface.rest.background,
186+
})
187+
root.select('.component-appearance-cell > code').css({
188+
fontSize: '0.75rem',
189+
color: neutral.text.subtle,
190+
background: 'none',
191+
border: 'none',
192+
padding: '0',
193+
})
194+
root
195+
.select('.component-appearance-cell > .btn-group__dropdown')
196+
.css({ alignSelf: 'start' })
197+
root.select('.component-directory-lead').css({
198+
fontSize: 'clamp(1.4rem, 3vw, 2rem)',
199+
lineHeight: '1.35',
200+
fontWeight: '600',
201+
maxWidth: '34rem',
202+
color: accent.text.default,
203+
})
204+
root.select('.component-recipe').css({
205+
padding: '1.5rem',
206+
border: `1px solid ${neutral.border.subtle}`,
207+
borderRadius: '0.85rem',
208+
background: neutral.surface.rest.background,
209+
boxShadow: '0 8px 24px rgb(0 0 0 / 0.06)',
210+
})
211+
root
212+
.select('.component-recipe h3')
213+
.css({ margin: '0.4rem 0 0.75rem', fontSize: '1.25rem' })
214+
root
215+
.select('.component-recipe > p')
216+
.css({ margin: '0.5rem 0 1.25rem', color: neutral.text.subtle })
217+
root.select('.component-guide-grid').css({
218+
display: 'grid',
219+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
220+
gap: '1rem',
221+
})
222+
root.select('.component-guide-card').css({
223+
display: 'flex',
224+
flexDirection: 'column',
225+
padding: '1.5rem',
226+
border: `1px solid ${neutral.border.subtle}`,
227+
borderRadius: '1rem',
228+
background: neutral.surface.rest.background,
229+
minWidth: '0',
230+
})
231+
root
232+
.select('.component-guide-card h3')
233+
.css({ margin: '1.25rem 0 0.5rem', overflowWrap: 'anywhere' })
234+
root
235+
.select('.component-guide-card > p')
236+
.css({ color: neutral.text.subtle, fontSize: '0.9rem', flexGrow: '1' })
237+
root.select('.component-guide-card__preview').css({
238+
display: 'flex',
239+
alignItems: 'center',
240+
justifyContent: 'center',
241+
flexWrap: 'wrap',
242+
gap: '0.5rem',
243+
minHeight: '6rem',
244+
padding: '1rem',
245+
borderRadius: '0.75rem',
246+
background: accent.surface.rest.background,
247+
})
248+
root.select('.component-guide-card > a').css({
249+
fontWeight: '600',
250+
fontSize: '0.85rem',
251+
color: accent.text.default,
252+
})
253+
root
254+
.media(mediaBelow(BREAKPOINTS.md))
255+
.select('.component-guide-grid')
256+
.css({ gridTemplateColumns: 'minmax(0, 1fr)' })
257+
})
258+
}

‎frontend/docs/docsComponents.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { definePageTocPreviewComponent } from '../purestack.studio/components/si
55
import { defineSignInPreviewComponent } from '../purestack.studio/components/site/sign-in/preview'
66
import { defineTopBarPreviewComponent } from '../purestack.studio/components/site/top-bar/preview'
77
import { defineApiPropertyComponent } from './apiProperty'
8+
import { defineComponentGuideComponents } from './componentGuide'
89
import { defineModalExampleComponents } from './modal'
910

1011
export function defineDocumentationComponents(site: SiteConfig) {
1112
return {
13+
...defineComponentGuideComponents(),
1214
ApiProperty: defineApiPropertyComponent(),
1315
ConsentPreview: defineConsentPreviewComponent(site),
1416
NavMenuPreview: defineNavMenuPreviewComponent(site),

‎frontend/purestack.studio/components/actions/alert-box/alert-box.mdx‎

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,30 @@ nav:
1313

1414
# Alert<wbr/>Box
1515

16-
Give a message the right emphasis with a title, semantic tone, optional icon and contextual actions.
16+
<ComponentHeader name="AlertBox" kind="Contextual feedback" summary="Turn an important message into a useful next step. Combine context, semantic color, and actions in one notice." source="https://github.com/PureStackStudio/PureStack/blob/main/packages/ts-components/src/standard/alertBox/alertBox.ts"/>
1717

18-
<Badge tone="accent" variant="surface">AlertBox</Badge>
19-
20-
[View source](https://github.com/PureStackStudio/PureStack/blob/main/packages/ts-components/src/standard/alertBox/alertBox.ts) · [API reference](#api-reference)
18+
## In context
2119

20+
<ComponentExample label="A complete message" caption="Every content feature is visible: eyebrow, title, badge, icon, body, metadata, and the actions slot.">
21+
<div class="component-recipe"><AlertBox tone="info" variant="spotlight" eyebrow="DESIGN SYSTEM" title="Make feedback useful" badge="Tip" icon="lucide:check" meta="Semantic tones adapt to light and dark themes."><p>Explain what happened, why it matters, and what the reader can do next.</p><template #actions><BtnLink href="/guides/semantic-tones/" tone="info" variant="outline">Explore semantic tones</BtnLink></template></AlertBox></div>
22+
</ComponentExample>
2223

24+
```html
25+
<AlertBox tone="info" variant="spotlight" eyebrow="DESIGN SYSTEM" title="Make feedback useful" badge="Tip" icon="lucide:check" meta="Semantic tones adapt to light and dark themes.">
26+
<p>Explain what happened, why it matters, and what the reader can do next.</p>
27+
<template #actions>
28+
<BtnLink href="/guides/semantic-tones/" tone="info" variant="outline">Explore semantic tones</BtnLink>
29+
</template>
30+
</AlertBox>
31+
```
2332

2433
## An actionable notice
2534

2635
Make the next step part of the message. The actions slot keeps it alongside the context.
2736

2837
<Tabs group="alert-box-sample-1" selectedTab="alert-box-sample-1-preview" ariaLabel="An actionable notice example" tone="neutral" variant="surface" tabVariant="underline">
2938
<TabPane id="alert-box-sample-1-preview" label="Preview">
39+
<div class="component-preview-stage">
3040
<AlertBox
3141
tone="info"
3242
title="A shared design language"
@@ -42,7 +52,8 @@ Make the next step part of the message. The actions slot keeps it alongside the
4252
</BtnLink>
4353
</template>
4454
</AlertBox>
45-
</TabPane>
55+
</div>
56+
</TabPane>
4657
<TabPane id="alert-box-sample-1-source-0" label="actionable-notice.mdx">
4758
```html
4859
<AlertBox
@@ -70,6 +81,7 @@ Keep the message explicit so color is never the only signal.
7081

7182
<Tabs group="alert-box-sample-2" selectedTab="alert-box-sample-2-preview" ariaLabel="Choose the right urgency example" tone="neutral" variant="surface" tabVariant="underline">
7283
<TabPane id="alert-box-sample-2-preview" label="Preview">
84+
<div class="component-preview-stage">
7385
<Flex direction="column">
7486
<AlertBox tone="success" title="All checks passed">
7587
The preview is ready for review.
@@ -81,7 +93,8 @@ Keep the message explicit so color is never the only signal.
8193
Correct the source error and build again.
8294
</AlertBox>
8395
</Flex>
84-
</TabPane>
96+
</div>
97+
</TabPane>
8598
<TabPane id="alert-box-sample-2-source-0" label="message-tones.mdx">
8699
```html
87100
<Flex direction="column">
@@ -99,6 +112,63 @@ Keep the message explicit so color is never the only signal.
99112
</TabPane>
100113
</Tabs>
101114

115+
## All semantic tones
116+
117+
All ten tones, with visible labels. Explain intent through the message as well as color.
118+
119+
<ComponentExample label="All semantic tones" caption="All values are shown together. Switch the site theme to compare light and dark rendering.">
120+
<div class="component-appearance-grid">
121+
<div class="component-appearance-cell"><code>neutral</code><AlertBox tone="neutral" title="neutral">A clear message with supporting context.</AlertBox></div>
122+
<div class="component-appearance-cell"><code>accent</code><AlertBox tone="accent" title="accent">A clear message with supporting context.</AlertBox></div>
123+
<div class="component-appearance-cell"><code>feature</code><AlertBox tone="feature" title="feature">A clear message with supporting context.</AlertBox></div>
124+
<div class="component-appearance-cell"><code>secondary</code><AlertBox tone="secondary" title="secondary">A clear message with supporting context.</AlertBox></div>
125+
<div class="component-appearance-cell"><code>custom</code><AlertBox tone="custom" title="custom">A clear message with supporting context.</AlertBox></div>
126+
<div class="component-appearance-cell"><code>ghost</code><AlertBox tone="ghost" title="ghost">A clear message with supporting context.</AlertBox></div>
127+
<div class="component-appearance-cell"><code>info</code><AlertBox tone="info" title="info">A clear message with supporting context.</AlertBox></div>
128+
<div class="component-appearance-cell"><code>success</code><AlertBox tone="success" title="success">A clear message with supporting context.</AlertBox></div>
129+
<div class="component-appearance-cell"><code>warning</code><AlertBox tone="warning" title="warning">A clear message with supporting context.</AlertBox></div>
130+
<div class="component-appearance-cell"><code>danger</code><AlertBox tone="danger" title="danger">A clear message with supporting context.</AlertBox></div>
131+
</div>
132+
</ComponentExample>
133+
134+
## All visual variants
135+
136+
All eighteen treatments in the info tone. Content, icons, and actions share the same API across treatments.
137+
138+
<ComponentExample label="All visual variants" caption="All values are shown together. Switch the site theme to compare light and dark rendering.">
139+
<div class="component-appearance-grid">
140+
<div class="component-appearance-cell"><code>solid</code><AlertBox tone="info" variant="solid" title="solid">Supporting message.</AlertBox></div>
141+
<div class="component-appearance-cell"><code>surface</code><AlertBox tone="info" variant="surface" title="surface">Supporting message.</AlertBox></div>
142+
<div class="component-appearance-cell"><code>surfaceAlt</code><AlertBox tone="info" variant="surfaceAlt" title="surfaceAlt">Supporting message.</AlertBox></div>
143+
<div class="component-appearance-cell"><code>spotlight</code><AlertBox tone="info" variant="spotlight" title="spotlight">Supporting message.</AlertBox></div>
144+
<div class="component-appearance-cell"><code>glass</code><AlertBox tone="info" variant="glass" title="glass">Supporting message.</AlertBox></div>
145+
<div class="component-appearance-cell"><code>flat</code><AlertBox tone="info" variant="flat" title="flat">Supporting message.</AlertBox></div>
146+
<div class="component-appearance-cell"><code>flatAlt</code><AlertBox tone="info" variant="flatAlt" title="flatAlt">Supporting message.</AlertBox></div>
147+
<div class="component-appearance-cell"><code>flatSolid</code><AlertBox tone="info" variant="flatSolid" title="flatSolid">Supporting message.</AlertBox></div>
148+
<div class="component-appearance-cell"><code>outlineFill</code><AlertBox tone="info" variant="outlineFill" title="outlineFill">Supporting message.</AlertBox></div>
149+
<div class="component-appearance-cell"><code>outline</code><AlertBox tone="info" variant="outline" title="outline">Supporting message.</AlertBox></div>
150+
<div class="component-appearance-cell"><code>subtle</code><AlertBox tone="info" variant="subtle" title="subtle">Supporting message.</AlertBox></div>
151+
<div class="component-appearance-cell"><code>subtleBtn</code><AlertBox tone="info" variant="subtleBtn" title="subtleBtn">Supporting message.</AlertBox></div>
152+
<div class="component-appearance-cell"><code>link</code><AlertBox tone="info" variant="link" title="link">Supporting message.</AlertBox></div>
153+
<div class="component-appearance-cell"><code>sheen</code><AlertBox tone="info" variant="sheen" title="sheen">Supporting message.</AlertBox></div>
154+
<div class="component-appearance-cell"><code>underline</code><AlertBox tone="info" variant="underline" title="underline">Supporting message.</AlertBox></div>
155+
<div class="component-appearance-cell"><code>rail</code><AlertBox tone="info" variant="rail" title="rail">Supporting message.</AlertBox></div>
156+
<div class="component-appearance-cell"><code>bracket</code><AlertBox tone="info" variant="bracket" title="bracket">Supporting message.</AlertBox></div>
157+
<div class="component-appearance-cell"><code>none</code><AlertBox tone="info" variant="none" title="none">Supporting message.</AlertBox></div>
158+
</div>
159+
</ComponentExample>
160+
161+
## Variant modes
162+
163+
Stateless is the default. Stateful enables hover, focus, and active styling; it does not add action handlers. Hover or focus these notices to compare.
164+
165+
<ComponentExample label="Variant modes" caption="All values are shown together. Switch the site theme to compare light and dark rendering.">
166+
<div class="component-appearance-grid">
167+
<div class="component-appearance-cell"><code>stateless</code><AlertBox tone="info" variant="outlineFill" variantMode="stateless" title="stateless" tabindex="0">Compare the treatment.</AlertBox></div>
168+
<div class="component-appearance-cell"><code>stateful</code><AlertBox tone="info" variant="outlineFill" variantMode="stateful" title="stateful" tabindex="0">Compare the treatment.</AlertBox></div>
169+
</div>
170+
</ComponentExample>
171+
102172
## Behavior and accessibility
103173

104174
AlertBox is a visual content component and does not automatically announce messages. For newly inserted urgent messages use an appropriate live region; for routine form feedback use FormStatus. Static notices should not interrupt screen-reader users.

0 commit comments

Comments
 (0)