@duro-app/ui 5.6.0 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Table-B1sWL3GA.js → Table-CAjPFgTX.js} +398 -394
- package/dist/Table-CAjPFgTX.js.map +1 -0
- package/dist/{compiler-runtime-B2IcI8_O.js → compiler-runtime-BuugS4D0.js} +67 -59
- package/dist/{compiler-runtime-B2IcI8_O.js.map → compiler-runtime-BuugS4D0.js.map} +1 -1
- package/dist/components/AppShell/AppShell.d.ts +74 -0
- package/dist/components/AppShell/AppShell.d.ts.map +1 -0
- package/dist/components/AppShell/AppShell.meta.d.ts +3 -0
- package/dist/components/AppShell/AppShell.meta.d.ts.map +1 -0
- package/dist/components/AppShell/AppShell.stories.d.ts +186 -0
- package/dist/components/AppShell/AppShell.stories.d.ts.map +1 -0
- package/dist/components/AppShell/styles.css.d.ts +187 -0
- package/dist/components/AppShell/styles.css.d.ts.map +1 -0
- package/dist/components/Aside/Aside.d.ts +23 -0
- package/dist/components/Aside/Aside.d.ts.map +1 -0
- package/dist/components/Aside/Aside.meta.d.ts +3 -0
- package/dist/components/Aside/Aside.meta.d.ts.map +1 -0
- package/dist/components/Aside/Aside.stories.d.ts +45 -0
- package/dist/components/Aside/Aside.stories.d.ts.map +1 -0
- package/dist/components/Aside/styles.css.d.ts +44 -0
- package/dist/components/Aside/styles.css.d.ts.map +1 -0
- package/dist/components/Button/styles.css.d.ts +2 -0
- package/dist/components/Button/styles.css.d.ts.map +1 -1
- package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts +3 -3
- package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.d.ts +29 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.meta.d.ts +3 -0
- package/dist/components/CodeBlock/CodeBlock.meta.d.ts.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.stories.d.ts +67 -0
- package/dist/components/CodeBlock/CodeBlock.stories.d.ts.map +1 -0
- package/dist/components/CodeBlock/styles.css.d.ts +32 -0
- package/dist/components/CodeBlock/styles.css.d.ts.map +1 -0
- package/dist/components/DragDrop/DragDrop.d.ts +95 -14
- package/dist/components/DragDrop/DragDrop.d.ts.map +1 -1
- package/dist/components/DragDrop/DragDrop.meta.d.ts.map +1 -1
- package/dist/components/DragDrop/DragDropBoard.stories.d.ts +69 -0
- package/dist/components/DragDrop/DragDropBoard.stories.d.ts.map +1 -0
- package/dist/components/DragDrop/styles.css.d.ts +80 -3
- package/dist/components/DragDrop/styles.css.d.ts.map +1 -1
- package/dist/components/Grid/Grid.d.ts +13 -4
- package/dist/components/Grid/Grid.d.ts.map +1 -1
- package/dist/components/Grid/Grid.meta.d.ts.map +1 -1
- package/dist/components/Grid/Grid.stories.d.ts +28 -0
- package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
- package/dist/components/Grid/styles.css.d.ts +3 -0
- package/dist/components/Grid/styles.css.d.ts.map +1 -1
- package/dist/components/Heading/Heading.d.ts +3 -1
- package/dist/components/Heading/Heading.d.ts.map +1 -1
- package/dist/components/Heading/Heading.stories.d.ts +9 -0
- package/dist/components/Heading/Heading.stories.d.ts.map +1 -0
- package/dist/components/LinkButton/LinkButton.stories.d.ts +7 -0
- package/dist/components/LinkButton/LinkButton.stories.d.ts.map +1 -1
- package/dist/components/LinkButton/styles.css.d.ts +2 -0
- package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
- package/dist/components/LiveRegion/LiveRegion.d.ts +28 -0
- package/dist/components/LiveRegion/LiveRegion.d.ts.map +1 -0
- package/dist/components/LiveRegion/LiveRegion.meta.d.ts +3 -0
- package/dist/components/LiveRegion/LiveRegion.meta.d.ts.map +1 -0
- package/dist/components/LiveRegion/LiveRegion.stories.d.ts +66 -0
- package/dist/components/LiveRegion/LiveRegion.stories.d.ts.map +1 -0
- package/dist/components/Menu/Menu.stories.d.ts +7 -0
- package/dist/components/Menu/Menu.stories.d.ts.map +1 -1
- package/dist/components/Menu/styles.css.d.ts +2 -0
- package/dist/components/Menu/styles.css.d.ts.map +1 -1
- package/dist/components/Meter/Meter.d.ts +17 -0
- package/dist/components/Meter/Meter.d.ts.map +1 -0
- package/dist/components/Meter/Meter.meta.d.ts +3 -0
- package/dist/components/Meter/Meter.meta.d.ts.map +1 -0
- package/dist/components/Meter/Meter.stories.d.ts +12 -0
- package/dist/components/Meter/Meter.stories.d.ts.map +1 -0
- package/dist/components/Meter/styles.css.d.ts +34 -0
- package/dist/components/Meter/styles.css.d.ts.map +1 -0
- package/dist/components/PageNav/styles.css.d.ts +1 -1
- package/dist/components/Prose/Prose.d.ts +17 -0
- package/dist/components/Prose/Prose.d.ts.map +1 -0
- package/dist/components/Prose/Prose.meta.d.ts +3 -0
- package/dist/components/Prose/Prose.meta.d.ts.map +1 -0
- package/dist/components/Prose/Prose.stories.d.ts +63 -0
- package/dist/components/Prose/Prose.stories.d.ts.map +1 -0
- package/dist/components/SideNav/SideNav.d.ts +7 -1
- package/dist/components/SideNav/SideNav.d.ts.map +1 -1
- package/dist/components/SideNav/SideNav.stories.d.ts +3 -0
- package/dist/components/SideNav/SideNav.stories.d.ts.map +1 -1
- package/dist/components/Time/Time.d.ts +26 -0
- package/dist/components/Time/Time.d.ts.map +1 -0
- package/dist/components/Time/Time.meta.d.ts +3 -0
- package/dist/components/Time/Time.meta.d.ts.map +1 -0
- package/dist/components/Time/Time.stories.d.ts +77 -0
- package/dist/components/Time/Time.stories.d.ts.map +1 -0
- package/dist/components/Time/relative.d.ts +27 -0
- package/dist/components/Time/relative.d.ts.map +1 -0
- package/dist/components/Timeline/Timeline.d.ts +71 -0
- package/dist/components/Timeline/Timeline.d.ts.map +1 -0
- package/dist/components/Timeline/Timeline.meta.d.ts +3 -0
- package/dist/components/Timeline/Timeline.meta.d.ts.map +1 -0
- package/dist/components/Timeline/Timeline.stories.d.ts +26 -0
- package/dist/components/Timeline/Timeline.stories.d.ts.map +1 -0
- package/dist/components/Timeline/styles.css.d.ts +158 -0
- package/dist/components/Timeline/styles.css.d.ts.map +1 -0
- package/dist/components/Toast/Toast.meta.d.ts.map +1 -1
- package/dist/components/Toast/Toast.stories.d.ts +9 -0
- package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
- package/dist/components/Toast/ToastProvider.d.ts +11 -5
- package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/Toast/index.d.ts +1 -1
- package/dist/components/Toast/index.d.ts.map +1 -1
- package/dist/components/Toast/styles.css.d.ts +6 -0
- package/dist/components/Toast/styles.css.d.ts.map +1 -1
- package/dist/form.js +1 -1
- package/dist/hooks/useContainerBelow.d.ts +18 -0
- package/dist/hooks/useContainerBelow.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7316 -5536
- package/dist/index.js.map +1 -1
- package/dist/table.js +2 -2
- package/package.json +2 -2
- package/src/components/AppShell/AppShell.meta.ts +62 -0
- package/src/components/AppShell/AppShell.stories.tsx +770 -0
- package/src/components/AppShell/AppShell.tsx +346 -0
- package/src/components/AppShell/styles.css.ts +219 -0
- package/src/components/Aside/Aside.meta.ts +33 -0
- package/src/components/Aside/Aside.stories.tsx +106 -0
- package/src/components/Aside/Aside.tsx +42 -0
- package/src/components/Aside/styles.css.ts +60 -0
- package/src/components/Button/styles.css.ts +7 -2
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +4 -5
- package/src/components/CodeBlock/CodeBlock.meta.ts +29 -0
- package/src/components/CodeBlock/CodeBlock.stories.tsx +162 -0
- package/src/components/CodeBlock/CodeBlock.tsx +99 -0
- package/src/components/CodeBlock/styles.css.ts +36 -0
- package/src/components/DragDrop/DragDrop.meta.ts +22 -21
- package/src/components/DragDrop/DragDrop.tsx +1147 -246
- package/src/components/DragDrop/DragDropBoard.stories.tsx +668 -0
- package/src/components/DragDrop/styles.css.ts +74 -4
- package/src/components/Grid/Grid.meta.ts +2 -1
- package/src/components/Grid/Grid.stories.tsx +202 -3
- package/src/components/Grid/Grid.tsx +35 -6
- package/src/components/Grid/styles.css.ts +11 -0
- package/src/components/Heading/Heading.stories.tsx +51 -0
- package/src/components/Heading/Heading.tsx +38 -7
- package/src/components/LinkButton/LinkButton.stories.tsx +41 -1
- package/src/components/LinkButton/styles.css.ts +6 -2
- package/src/components/LiveRegion/LiveRegion.meta.ts +29 -0
- package/src/components/LiveRegion/LiveRegion.stories.tsx +139 -0
- package/src/components/LiveRegion/LiveRegion.tsx +48 -0
- package/src/components/Menu/Menu.stories.tsx +62 -0
- package/src/components/Menu/styles.css.ts +6 -3
- package/src/components/Meter/Meter.meta.ts +24 -0
- package/src/components/Meter/Meter.stories.tsx +64 -0
- package/src/components/Meter/Meter.tsx +55 -0
- package/src/components/Meter/styles.css.ts +30 -0
- package/src/components/Prose/Prose.meta.ts +32 -0
- package/src/components/Prose/Prose.stories.tsx +233 -0
- package/src/components/Prose/Prose.tsx +25 -0
- package/src/components/Prose/prose.css +177 -0
- package/src/components/SideNav/SideNav.stories.tsx +29 -0
- package/src/components/SideNav/SideNav.tsx +14 -2
- package/src/components/Time/Time.meta.ts +17 -0
- package/src/components/Time/Time.stories.tsx +173 -0
- package/src/components/Time/Time.tsx +33 -0
- package/src/components/Time/relative.ts +89 -0
- package/src/components/Timeline/Timeline.meta.ts +46 -0
- package/src/components/Timeline/Timeline.stories.tsx +238 -0
- package/src/components/Timeline/Timeline.tsx +332 -0
- package/src/components/Timeline/styles.css.ts +162 -0
- package/src/components/Toast/Toast.meta.ts +2 -1
- package/src/components/Toast/Toast.stories.tsx +84 -0
- package/src/components/Toast/ToastProvider.tsx +49 -11
- package/src/components/Toast/index.ts +7 -1
- package/src/components/Toast/styles.css.ts +2 -0
- package/src/hooks/useContainerBelow.ts +39 -0
- package/src/index.ts +29 -1
- package/src/strict.css +4 -1
- package/dist/Table-B1sWL3GA.js.map +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
+
import {expect, waitFor} from 'storybook/test'
|
|
3
|
+
import {Prose} from './Prose'
|
|
4
|
+
import {Heading} from '../Heading/Heading'
|
|
5
|
+
import {Text} from '../Text/Text'
|
|
6
|
+
import {CodeBlock} from '../CodeBlock/CodeBlock'
|
|
7
|
+
import {onThemeSurface} from '../../docs/themedSurface'
|
|
8
|
+
|
|
9
|
+
// What a Markdown renderer hands over: HTML that is never authored as JSX.
|
|
10
|
+
const RENDERED_MARKDOWN = `
|
|
11
|
+
<p>Flux reconciles every cluster from <code>kubernetes/</code>. Read the
|
|
12
|
+
<a href="#bootstrap">bootstrap notes</a> first.</p>
|
|
13
|
+
<h2 id="bootstrap" class="docs_title__a1b2">Bootstrap</h2>
|
|
14
|
+
<p>Each cluster starts from the same three steps:</p>
|
|
15
|
+
<ol>
|
|
16
|
+
<li>Install the operator.</li>
|
|
17
|
+
<li>Seed the secrets:
|
|
18
|
+
<ul>
|
|
19
|
+
<li>the Vault token</li>
|
|
20
|
+
<li>the registry pull secret</li>
|
|
21
|
+
</ul>
|
|
22
|
+
</li>
|
|
23
|
+
<li>Point Flux at the repository.</li>
|
|
24
|
+
</ol>
|
|
25
|
+
<blockquote><p>Never commit a decrypted secret.</p></blockquote>
|
|
26
|
+
<h3>Clusters</h3>
|
|
27
|
+
<table>
|
|
28
|
+
<thead><tr><th>Cluster</th><th>Nodes</th><th>Role</th></tr></thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr><td>homelab</td><td>5</td><td>Applications</td></tr>
|
|
31
|
+
<tr><td>nas</td><td>1</td><td>Storage</td></tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
<pre><code>flux bootstrap git --url=ssh://git@forge/homelab</code></pre>
|
|
35
|
+
<hr>
|
|
36
|
+
<p><img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'/%3E"></p>
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
const RELEASE_NOTES = `
|
|
40
|
+
<h2>5.6.0</h2>
|
|
41
|
+
<p>Small controls line up: every <code>size="small"</code> control is at least
|
|
42
|
+
<code>controlSm</code> tall, whatever it holds.</p>
|
|
43
|
+
<h3>Fixed</h3>
|
|
44
|
+
<ul>
|
|
45
|
+
<li>A small <code>Menu.Trigger</code> in a toolbar no longer shrinks.</li>
|
|
46
|
+
<li>Toggle and Button share one small padding.</li>
|
|
47
|
+
</ul>
|
|
48
|
+
<blockquote><p>Upgrade with <code>pnpm add @duro-app/ui@5.6.0</code>.</p></blockquote>
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
const REFERENCE = `
|
|
52
|
+
<h2>relative(date, options)</h2>
|
|
53
|
+
<p>Formats <code>date</code> against <code>options.now</code> with
|
|
54
|
+
<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat">Intl.RelativeTimeFormat</a>.</p>
|
|
55
|
+
<table>
|
|
56
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th></tr></thead>
|
|
57
|
+
<tbody>
|
|
58
|
+
<tr><td><code>now</code></td><td>Date | number</td><td>required</td></tr>
|
|
59
|
+
<tr><td><code>locale</code></td><td>string</td><td>required</td></tr>
|
|
60
|
+
<tr><td><code>numeric</code></td><td>'auto' | 'always'</td><td>'auto'</td></tr>
|
|
61
|
+
</tbody>
|
|
62
|
+
</table>
|
|
63
|
+
<pre><code>relative(commit.date, {now, locale: 'fr'})</code></pre>
|
|
64
|
+
`
|
|
65
|
+
|
|
66
|
+
const DOCUMENTS = {
|
|
67
|
+
runbook: RENDERED_MARKDOWN,
|
|
68
|
+
'release-notes': RELEASE_NOTES,
|
|
69
|
+
reference: REFERENCE,
|
|
70
|
+
} as const
|
|
71
|
+
|
|
72
|
+
type DocumentName = keyof typeof DOCUMENTS
|
|
73
|
+
|
|
74
|
+
interface ProseArgs {
|
|
75
|
+
/** A sample of what a Markdown renderer hands over. */
|
|
76
|
+
document: DocumentName
|
|
77
|
+
/** Your own HTML, in place of the sample; empty to show the sample. */
|
|
78
|
+
html: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Sets the HTML on the element, as a Markdown renderer's output arrives. */
|
|
82
|
+
function Markdown({html = RENDERED_MARKDOWN}: {html?: string}) {
|
|
83
|
+
return (
|
|
84
|
+
<Prose
|
|
85
|
+
ref={(el) => {
|
|
86
|
+
if (el) el.innerHTML = html
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{null}
|
|
90
|
+
</Prose>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const meta = {
|
|
95
|
+
title: 'Components/Prose',
|
|
96
|
+
args: {document: 'runbook', html: ''},
|
|
97
|
+
argTypes: {
|
|
98
|
+
document: {
|
|
99
|
+
control: {
|
|
100
|
+
type: 'select',
|
|
101
|
+
labels: {runbook: 'runbook', 'release-notes': 'release notes', reference: 'API reference'},
|
|
102
|
+
},
|
|
103
|
+
options: Object.keys(DOCUMENTS),
|
|
104
|
+
},
|
|
105
|
+
html: {control: 'text'},
|
|
106
|
+
},
|
|
107
|
+
parameters: {a11y: {test: 'error'}},
|
|
108
|
+
decorators: [onThemeSurface],
|
|
109
|
+
render: (args) => <Markdown html={args.html || DOCUMENTS[args.document]} />,
|
|
110
|
+
} satisfies Meta<ProseArgs>
|
|
111
|
+
|
|
112
|
+
export default meta
|
|
113
|
+
type Story = StoryObj<typeof meta>
|
|
114
|
+
|
|
115
|
+
const TEST_ONLY = ['!dev', '!autodocs']
|
|
116
|
+
|
|
117
|
+
/** The computed value of a token, read the way the page resolves it. */
|
|
118
|
+
function resolved(property: 'color' | 'fontSize', token: string, host: HTMLElement) {
|
|
119
|
+
const probe = document.createElement('span')
|
|
120
|
+
probe.style[property] = `var(${token})`
|
|
121
|
+
host.appendChild(probe)
|
|
122
|
+
const value = getComputedStyle(probe)[property]
|
|
123
|
+
probe.remove()
|
|
124
|
+
return value
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const style = (el: Element) => getComputedStyle(el)
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rendered Markdown styled by Prose. Pick another sample in `document`, or
|
|
131
|
+
* paste your own HTML in `html`.
|
|
132
|
+
*/
|
|
133
|
+
export const Playground: Story = {}
|
|
134
|
+
|
|
135
|
+
/** The runbook sample, measured: flow, headings, lists, tables, code, links. */
|
|
136
|
+
export const RenderedMarkdown: Story = {
|
|
137
|
+
tags: TEST_ONLY,
|
|
138
|
+
play: async ({canvasElement}) => {
|
|
139
|
+
const prose = canvasElement.querySelector('[data-duro-prose]') as HTMLElement
|
|
140
|
+
await waitFor(() => expect(prose.querySelector('h2')).not.toBeNull())
|
|
141
|
+
const q = (selector: string) => prose.querySelector(selector) as HTMLElement
|
|
142
|
+
|
|
143
|
+
// flow: no gap before the first block, the scale between the rest
|
|
144
|
+
await expect(style(q('p')).marginTop).toBe('0px')
|
|
145
|
+
await expect(style(q('ol')).marginTop).toBe('16px')
|
|
146
|
+
// headings as the docs artboard draws them (Reader.dc.html): a section is
|
|
147
|
+
// a step below the page title, with more room above than below
|
|
148
|
+
await expect(style(q('h2')).fontSize).toBe('20px')
|
|
149
|
+
await expect(style(q('h2')).fontWeight).toBe('600')
|
|
150
|
+
await expect(style(q('h2')).marginTop).toBe('24px')
|
|
151
|
+
await expect(style(q('h2')).marginBottom).toBe('8px')
|
|
152
|
+
await expect(style(q('h3')).fontSize).toBe('18px')
|
|
153
|
+
// lists keep their markers and indent
|
|
154
|
+
await expect(style(q('ol')).listStyleType).toBe('decimal')
|
|
155
|
+
await expect(style(q('ol ul')).listStyleType).toBe('disc')
|
|
156
|
+
await expect(style(q('ol')).paddingLeft).toBe('24px')
|
|
157
|
+
await expect(style(q('li + li')).marginTop).toBe('4px')
|
|
158
|
+
// tables: the column's full width, horizontal rules only, a muted header
|
|
159
|
+
const table = q('table')
|
|
160
|
+
await expect(table.getBoundingClientRect().width).toBe(prose.getBoundingClientRect().width)
|
|
161
|
+
await expect(style(q('td')).borderBottomWidth).toBe('1px')
|
|
162
|
+
await expect(style(q('td')).borderLeftWidth).toBe('0px')
|
|
163
|
+
await expect(style(q('th')).borderTopWidth).toBe('0px')
|
|
164
|
+
await expect(style(q('th')).fontWeight).toBe('500')
|
|
165
|
+
await expect(style(q('th')).color).toBe(resolved('color', '--duro-color-text-muted', prose))
|
|
166
|
+
await expect(style(q('td')).paddingLeft).toBe('8px')
|
|
167
|
+
// code: monospace inline and in a block
|
|
168
|
+
await expect(style(q('p code')).fontFamily).toMatch(/mono|Menlo|Consolas|monospace/i)
|
|
169
|
+
await expect(style(q('pre')).overflowX).toBe('auto')
|
|
170
|
+
await expect(style(q('pre code')).borderTopWidth).toBe('0px')
|
|
171
|
+
// links read as links
|
|
172
|
+
await expect(style(q('a')).color).toBe(resolved('color', '--duro-color-accent', prose))
|
|
173
|
+
await expect(style(q('a')).textDecorationLine).toBe('underline')
|
|
174
|
+
await expect(style(q('blockquote')).borderLeftWidth).toBe('3px')
|
|
175
|
+
await expect(style(q('img')).maxWidth).toBe('100%')
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* An app's own CSS wins over every Prose rule: a CSS Module class and even a
|
|
181
|
+
* bare element selector, loaded after Duro's stylesheet. Prose sits in its
|
|
182
|
+
* own cascade layer and weighs nothing (:where()).
|
|
183
|
+
*/
|
|
184
|
+
export const AppCssWins: Story = {
|
|
185
|
+
tags: TEST_ONLY,
|
|
186
|
+
play: async ({canvasElement}) => {
|
|
187
|
+
const prose = canvasElement.querySelector('[data-duro-prose]') as HTMLElement
|
|
188
|
+
await waitFor(() => expect(prose.querySelector('h2')).not.toBeNull())
|
|
189
|
+
const h2 = prose.querySelector('h2') as HTMLElement
|
|
190
|
+
const h3 = prose.querySelector('h3') as HTMLElement
|
|
191
|
+
await expect(style(h2).fontSize).toBe('20px')
|
|
192
|
+
await expect(style(h3).fontSize).toBe('18px')
|
|
193
|
+
|
|
194
|
+
const sheet = document.createElement('style')
|
|
195
|
+
// what kb-vision's docs.module.css compiles to, and the weakest rule an app can write
|
|
196
|
+
sheet.textContent = `.docs_title__a1b2 { font-size: 13px; margin-top: 0px; } h3 { font-size: 15px; }`
|
|
197
|
+
document.head.appendChild(sheet)
|
|
198
|
+
try {
|
|
199
|
+
await expect(style(h2).fontSize).toBe('13px')
|
|
200
|
+
await expect(style(h2).marginTop).toBe('0px')
|
|
201
|
+
await expect(style(h3).fontSize).toBe('15px')
|
|
202
|
+
} finally {
|
|
203
|
+
sheet.remove()
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** A Duro component inside Prose keeps its own styles: Prose is below them. */
|
|
209
|
+
export const DuroComponentsInside: Story = {
|
|
210
|
+
render: () => (
|
|
211
|
+
<Prose>
|
|
212
|
+
<Heading level={2} variant="headingLg">
|
|
213
|
+
Kept at headingLg
|
|
214
|
+
</Heading>
|
|
215
|
+
<Text>A paragraph from Text.</Text>
|
|
216
|
+
<CodeBlock copyLabel="Copy" copiedLabel="Copied">
|
|
217
|
+
pnpm add @duro-app/ui
|
|
218
|
+
</CodeBlock>
|
|
219
|
+
</Prose>
|
|
220
|
+
),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** DuroComponentsInside's checks, kept off the visible story so a Control change cannot fail them. */
|
|
224
|
+
export const DuroComponentsInsideChecks: Story = {
|
|
225
|
+
...DuroComponentsInside,
|
|
226
|
+
tags: TEST_ONLY,
|
|
227
|
+
play: async ({canvas}) => {
|
|
228
|
+
const heading = canvas.getByRole('heading', {level: 2, name: 'Kept at headingLg'})
|
|
229
|
+
// headingLg is 30px; a Prose h2 would be 20px
|
|
230
|
+
await expect(style(heading).fontSize).toBe('30px')
|
|
231
|
+
await expect(canvas.getByRole('button', {name: 'Copy'})).toBeVisible()
|
|
232
|
+
},
|
|
233
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {ReactNode, Ref} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
import './prose.css'
|
|
4
|
+
|
|
5
|
+
interface ProseProps {
|
|
6
|
+
/** HTML Duro does not author: the output of a Markdown renderer. */
|
|
7
|
+
children: ReactNode
|
|
8
|
+
/** The container element. */
|
|
9
|
+
ref?: Ref<HTMLDivElement>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Prose — the container for HTML Duro does not author (rendered Markdown, CMS
|
|
14
|
+
* output): headings, paragraphs, lists, tables, blockquotes, inline code,
|
|
15
|
+
* links and images are styled from tokens by descendant rules scoped to it.
|
|
16
|
+
* Duro components inside it (a TextLink, a CodeBlock) keep their own styles,
|
|
17
|
+
* and an app's own CSS wins over every Prose rule. Web only.
|
|
18
|
+
*/
|
|
19
|
+
export function Prose({children, ref}: ProseProps) {
|
|
20
|
+
return (
|
|
21
|
+
<html.div ref={ref} data-duro-prose="">
|
|
22
|
+
{children}
|
|
23
|
+
</html.div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Prose — styles for HTML that Duro does not author: rendered Markdown, CMS
|
|
3
|
+
* output. StyleX cannot write descendant rules, so these ship as plain CSS in
|
|
4
|
+
* dist/index.css. Only var(--duro-*) tokens: the check in
|
|
5
|
+
* packages/ui/test/prose-css.test.ts fails on any raw length or colour.
|
|
6
|
+
*
|
|
7
|
+
* Two things keep it out of everyone's way:
|
|
8
|
+
*
|
|
9
|
+
* - The `duro-prose` layer sits after `reset` (so these margins beat the
|
|
10
|
+
* reset's `margin: 0`) and before StyleX's own layers (strict.css declares
|
|
11
|
+
* the order). A Duro component rendered inside Prose (a TextLink, a
|
|
12
|
+
* CodeBlock) keeps its own styles, and any unlayered app CSS — a CSS
|
|
13
|
+
* Module rule — wins over every rule here.
|
|
14
|
+
* - Every selector is wrapped in :where(), so the rules weigh nothing even
|
|
15
|
+
* against an app's own layered CSS.
|
|
16
|
+
*
|
|
17
|
+
* Scoped to the Prose element: `data-duro-prose` (what the component renders;
|
|
18
|
+
* react-strict-dom elements take no className) or a `duro-prose` class.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
@layer reset, duro-prose;
|
|
22
|
+
|
|
23
|
+
@layer duro-prose {
|
|
24
|
+
:where(.duro-prose, [data-duro-prose]) {
|
|
25
|
+
color: var(--duro-color-text);
|
|
26
|
+
font-family: var(--duro-typography-font-family);
|
|
27
|
+
font-size: var(--duro-type-scale-font-size4);
|
|
28
|
+
line-height: var(--duro-type-scale-line-height4);
|
|
29
|
+
overflow-wrap: break-word;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Flow: one gap between blocks, none before the first. */
|
|
33
|
+
:where(.duro-prose, [data-duro-prose]) > :where(* + *) {
|
|
34
|
+
margin-block-start: var(--duro-spacing-md);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Headings: the Heading presets, with more room above than below. */
|
|
38
|
+
:where(.duro-prose, [data-duro-prose]) :where(h1, h2, h3, h4, h5, h6) {
|
|
39
|
+
font-family: var(--duro-typography-font-family);
|
|
40
|
+
letter-spacing: var(--duro-type-scale-letter-spacing-tight);
|
|
41
|
+
margin-block-start: var(--duro-spacing-xl);
|
|
42
|
+
margin-block-end: var(--duro-spacing-sm);
|
|
43
|
+
/* A table-of-contents jump lands with room above the heading. */
|
|
44
|
+
scroll-margin-top: var(--duro-spacing-lg);
|
|
45
|
+
}
|
|
46
|
+
:where(.duro-prose, [data-duro-prose]) :where(h1) {
|
|
47
|
+
font-size: var(--duro-type-scale-font-size9);
|
|
48
|
+
line-height: var(--duro-type-scale-line-height9);
|
|
49
|
+
font-weight: var(--duro-typography-font-weight-bold);
|
|
50
|
+
}
|
|
51
|
+
/* h2 down follow the picked docs artboard (Reader.dc.html): a reading
|
|
52
|
+
column's sections are a step below the page title, not a second one. */
|
|
53
|
+
:where(.duro-prose, [data-duro-prose]) :where(h2) {
|
|
54
|
+
font-size: var(--duro-type-scale-font-size6);
|
|
55
|
+
line-height: var(--duro-type-scale-line-height6);
|
|
56
|
+
font-weight: var(--duro-typography-font-weight-semibold);
|
|
57
|
+
margin-block-start: var(--duro-spacing-lg);
|
|
58
|
+
}
|
|
59
|
+
:where(.duro-prose, [data-duro-prose]) :where(h3) {
|
|
60
|
+
font-size: var(--duro-type-scale-font-size5);
|
|
61
|
+
line-height: var(--duro-type-scale-line-height5);
|
|
62
|
+
font-weight: var(--duro-typography-font-weight-semibold);
|
|
63
|
+
margin-block-start: var(--duro-spacing-lg);
|
|
64
|
+
}
|
|
65
|
+
:where(.duro-prose, [data-duro-prose]) :where(h4, h5, h6) {
|
|
66
|
+
font-size: var(--duro-type-scale-font-size4);
|
|
67
|
+
line-height: var(--duro-type-scale-line-height4);
|
|
68
|
+
font-weight: var(--duro-typography-font-weight-semibold);
|
|
69
|
+
margin-block-start: var(--duro-spacing-md);
|
|
70
|
+
}
|
|
71
|
+
:where(.duro-prose, [data-duro-prose]) > :where(:first-child) {
|
|
72
|
+
margin-block-start: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Lists: markers back (the reset removes the indent they hang in). */
|
|
76
|
+
:where(.duro-prose, [data-duro-prose]) :where(ul, ol) {
|
|
77
|
+
padding-inline-start: var(--duro-spacing-lg);
|
|
78
|
+
}
|
|
79
|
+
:where(.duro-prose, [data-duro-prose]) :where(ul) {
|
|
80
|
+
list-style-type: disc;
|
|
81
|
+
}
|
|
82
|
+
:where(.duro-prose, [data-duro-prose]) :where(ol) {
|
|
83
|
+
list-style-type: decimal;
|
|
84
|
+
}
|
|
85
|
+
:where(.duro-prose, [data-duro-prose]) :where(li + li, li > ul, li > ol) {
|
|
86
|
+
margin-block-start: var(--duro-spacing-xs);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Links: the accent, underlined so they read as links in running text. */
|
|
90
|
+
:where(.duro-prose, [data-duro-prose]) :where(a) {
|
|
91
|
+
color: var(--duro-color-accent);
|
|
92
|
+
text-decoration-line: underline;
|
|
93
|
+
text-underline-offset: var(--duro-micro-spacing-px2);
|
|
94
|
+
}
|
|
95
|
+
:where(.duro-prose, [data-duro-prose]) :where(a:hover) {
|
|
96
|
+
color: var(--duro-color-accent-hover);
|
|
97
|
+
}
|
|
98
|
+
:where(.duro-prose, [data-duro-prose]) :where(a:focus-visible) {
|
|
99
|
+
outline: var(--duro-border-focus-ring) solid var(--duro-color-accent);
|
|
100
|
+
outline-offset: var(--duro-border-focus-offset);
|
|
101
|
+
border-radius: var(--duro-radius-xs);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
:where(.duro-prose, [data-duro-prose]) :where(strong, b) {
|
|
105
|
+
font-weight: var(--duro-typography-font-weight-semibold);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:where(.duro-prose, [data-duro-prose]) :where(blockquote) {
|
|
109
|
+
padding-inline-start: var(--duro-spacing-md);
|
|
110
|
+
border-inline-start: var(--duro-border-accent) solid var(--duro-color-border);
|
|
111
|
+
color: var(--duro-color-text-muted);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Inline code; a heading keeps its own size around it. */
|
|
115
|
+
:where(.duro-prose, [data-duro-prose]) :where(code) {
|
|
116
|
+
font-family: var(--duro-typography-font-family-mono);
|
|
117
|
+
font-size: var(--duro-type-scale-font-size3);
|
|
118
|
+
padding-inline: var(--duro-spacing-xs);
|
|
119
|
+
background-color: var(--duro-color-bg-card);
|
|
120
|
+
border: var(--duro-border-hairline) solid var(--duro-color-border);
|
|
121
|
+
border-radius: var(--duro-radius-xs);
|
|
122
|
+
}
|
|
123
|
+
:where(.duro-prose, [data-duro-prose]) :where(h1, h2, h3, h4, h5, h6) :where(code) {
|
|
124
|
+
font-size: inherit;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* A code block outside CodeBlock (CodeBlock styles its own). */
|
|
128
|
+
:where(.duro-prose, [data-duro-prose]) :where(pre) {
|
|
129
|
+
font-family: var(--duro-typography-font-family-mono);
|
|
130
|
+
font-size: var(--duro-type-scale-font-size3);
|
|
131
|
+
line-height: var(--duro-type-scale-line-height3);
|
|
132
|
+
padding: var(--duro-spacing-md);
|
|
133
|
+
overflow-x: auto;
|
|
134
|
+
background-color: var(--duro-color-bg-card);
|
|
135
|
+
border: var(--duro-border-hairline) solid var(--duro-color-border);
|
|
136
|
+
border-radius: var(--duro-radius-md);
|
|
137
|
+
}
|
|
138
|
+
:where(.duro-prose, [data-duro-prose]) :where(pre code) {
|
|
139
|
+
padding: 0;
|
|
140
|
+
font-size: inherit;
|
|
141
|
+
background-color: transparent;
|
|
142
|
+
border: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Tables as the docs artboard draws them: the column's full width,
|
|
146
|
+
horizontal rules only, a muted header. A long cell wraps rather than
|
|
147
|
+
widening the page (a full-width table cannot also scroll on its own). */
|
|
148
|
+
:where(.duro-prose, [data-duro-prose]) :where(table) {
|
|
149
|
+
width: 100%;
|
|
150
|
+
border-collapse: collapse;
|
|
151
|
+
font-size: var(--duro-type-scale-font-size3);
|
|
152
|
+
line-height: var(--duro-type-scale-line-height3);
|
|
153
|
+
}
|
|
154
|
+
:where(.duro-prose, [data-duro-prose]) :where(th, td) {
|
|
155
|
+
padding: var(--duro-spacing-xs) var(--duro-spacing-sm);
|
|
156
|
+
border-block-end: var(--duro-border-hairline) solid var(--duro-color-border);
|
|
157
|
+
text-align: start;
|
|
158
|
+
vertical-align: top;
|
|
159
|
+
overflow-wrap: anywhere;
|
|
160
|
+
}
|
|
161
|
+
:where(.duro-prose, [data-duro-prose]) :where(th) {
|
|
162
|
+
color: var(--duro-color-text-muted);
|
|
163
|
+
font-weight: var(--duro-typography-font-weight-medium);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:where(.duro-prose, [data-duro-prose]) :where(img) {
|
|
167
|
+
max-width: 100%;
|
|
168
|
+
height: auto;
|
|
169
|
+
border-radius: var(--duro-radius-sm);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
:where(.duro-prose, [data-duro-prose]) :where(hr) {
|
|
173
|
+
border: 0;
|
|
174
|
+
border-block-start: var(--duro-border-hairline) solid var(--duro-color-border);
|
|
175
|
+
margin-block: var(--duro-spacing-xl);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
|
|
|
2
2
|
import {expect, fn} from 'storybook/test'
|
|
3
3
|
import {SideNav} from './SideNav'
|
|
4
4
|
import {Icon} from '../Icon/Icon'
|
|
5
|
+
import {onThemeSurface} from '../../docs/themedSurface'
|
|
5
6
|
|
|
6
7
|
const meta: Meta<typeof SideNav.Root> = {
|
|
7
8
|
title: 'Components/SideNav',
|
|
@@ -200,3 +201,31 @@ export const FlatWithIcons: Story = {
|
|
|
200
201
|
await expect(identities).toHaveAttribute('aria-current', 'page')
|
|
201
202
|
},
|
|
202
203
|
}
|
|
204
|
+
|
|
205
|
+
/** `aria-label` names the landmark, so a page with a breadcrumb or a pager
|
|
206
|
+
* beside it still lists the side navigation by name. */
|
|
207
|
+
export const Named: Story = {
|
|
208
|
+
parameters: {
|
|
209
|
+
a11y: {
|
|
210
|
+
test: 'error',
|
|
211
|
+
// holds-until: https://github.com/fredericrous/duro-design-system/issues/79
|
|
212
|
+
// — the selected item's accent on bgCardHover is 6.76:1, under AAA's
|
|
213
|
+
// 7:1 (AA passes). Re-enable this rule when that pair reaches 7:1.
|
|
214
|
+
options: {rules: {'color-contrast-enhanced': {enabled: false}}},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
decorators: [onThemeSurface],
|
|
218
|
+
render: (args) => (
|
|
219
|
+
<SideNav.Root {...args} aria-label="Settings" defaultValue="profile">
|
|
220
|
+
<SideNav.Section label="Account">
|
|
221
|
+
<SideNav.Item value="profile">Profile</SideNav.Item>
|
|
222
|
+
<SideNav.Item value="security">Security</SideNav.Item>
|
|
223
|
+
</SideNav.Section>
|
|
224
|
+
</SideNav.Root>
|
|
225
|
+
),
|
|
226
|
+
play: async ({canvas}) => {
|
|
227
|
+
const nav = canvas.getByRole('navigation', {name: 'Settings'})
|
|
228
|
+
await expect(nav).toHaveAttribute('aria-label', 'Settings')
|
|
229
|
+
await expect(canvas.getByRole('button', {name: 'Profile'})).toBeInTheDocument()
|
|
230
|
+
},
|
|
231
|
+
}
|
|
@@ -34,12 +34,24 @@ import {SideNavContext, useSideNav} from './SideNavContext'
|
|
|
34
34
|
|
|
35
35
|
interface RootProps {
|
|
36
36
|
children: ReactNode
|
|
37
|
+
/**
|
|
38
|
+
* Names the navigation landmark ("Main", "Settings"). Give one whenever
|
|
39
|
+
* the page has another nav (a breadcrumb, a pager): screen readers list
|
|
40
|
+
* landmarks by name.
|
|
41
|
+
*/
|
|
42
|
+
'aria-label'?: string
|
|
37
43
|
value?: string
|
|
38
44
|
defaultValue?: string
|
|
39
45
|
onValueChange?: (value: string) => void
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
function Root({
|
|
48
|
+
function Root({
|
|
49
|
+
children,
|
|
50
|
+
'aria-label': ariaLabel,
|
|
51
|
+
value: controlledValue,
|
|
52
|
+
defaultValue,
|
|
53
|
+
onValueChange,
|
|
54
|
+
}: RootProps) {
|
|
43
55
|
const [activeValue, onSelect] = useControllableValue<string | null>(
|
|
44
56
|
controlledValue,
|
|
45
57
|
defaultValue ?? null,
|
|
@@ -98,7 +110,7 @@ function Root({children, value: controlledValue, defaultValue, onValueChange}: R
|
|
|
98
110
|
orderRef,
|
|
99
111
|
}}
|
|
100
112
|
>
|
|
101
|
-
<html.nav role="navigation" style={styles.root}>
|
|
113
|
+
<html.nav role="navigation" aria-label={ariaLabel} style={styles.root}>
|
|
102
114
|
{children}
|
|
103
115
|
</html.nav>
|
|
104
116
|
</SideNavContext.Provider>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'A date or time in running text as a <time> element: the visible text comes from you, the exact instant goes in dateTime (an ISO string) and title. relative(date, {now, locale}) gives the text on Intl.RelativeTimeFormat ("3 days ago", "il y a 3 jours"); now is required, so the server and the client render the same string. Web only.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'When something happened: "Updated 3 days ago", a commit date, a last-indexed time',
|
|
8
|
+
'Any date shown as relative text whose exact value should stay available',
|
|
9
|
+
],
|
|
10
|
+
whenNotToUse: [
|
|
11
|
+
'A date the person edits — use an Input',
|
|
12
|
+
'A live countdown or a clock — Time renders once from the now you pass',
|
|
13
|
+
],
|
|
14
|
+
example: `<Time dateTime={doc.lastCommitAt} title={formatFull(doc.lastCommitAt, locale)}>
|
|
15
|
+
{relative(doc.lastCommitAt, {now: loaderNow, locale})}
|
|
16
|
+
</Time>`,
|
|
17
|
+
}
|