@misoto22/design 0.3.1 → 0.5.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/article.css +393 -0
- package/dist/components/AppShell/AppShell.d.ts +15 -1
- package/dist/components/AppShell/AppShell.js +8 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +51 -0
- package/dist/components/Article/Article.js +21 -0
- package/dist/components/Article/Article.js.map +1 -0
- package/dist/components/Button/Button.js +2 -2
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +1 -1
- package/dist/components/Calendar/Calendar.js +259 -24
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.js +1 -1
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.d.ts +15 -1
- package/dist/components/Collapsible/Collapsible.js +18 -0
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.js +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.js +7 -7
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +2 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/Diagram/Diagram.d.ts +81 -0
- package/dist/components/Diagram/Diagram.js +139 -0
- package/dist/components/Diagram/Diagram.js.map +1 -0
- package/dist/components/Dialog/Dialog.js +1 -1
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/FigureBand/FigureBand.js +41 -30
- package/dist/components/FigureBand/FigureBand.js.map +1 -1
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Select/Select.js +2 -2
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Sheet/Sheet.js +1 -1
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Slider/Slider.js +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Steps/Steps.d.ts +65 -0
- package/dist/components/Steps/Steps.js +55 -0
- package/dist/components/Steps/Steps.js.map +1 -0
- package/dist/components/Switch/Switch.js +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/useSelectionIndicator.js +14 -10
- package/dist/lib/useSelectionIndicator.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/themes.css +53 -7
- package/dist/tokens.css +75 -10
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.js +53 -8
- package/dist/tokens.json +53 -8
- package/package.json +22 -19
package/dist/article.css
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
ARTICLE — the long-form reading surface
|
|
3
|
+
--------------------------------------------------------------------------
|
|
4
|
+
The one place in this package that styles TAGS rather than components, and
|
|
5
|
+
the reason is that its input is not JSX: it is whatever a Markdown or MDX
|
|
6
|
+
pipeline produced. There is no component to hang a class on, so the contract
|
|
7
|
+
has to be the element names themselves.
|
|
8
|
+
|
|
9
|
+
Scoped to `[data-m22-article]`, which the `Article` component writes. Nothing
|
|
10
|
+
here leaks: a page that does not opt in is untouched, and a component
|
|
11
|
+
rendered INSIDE an article keeps its own classes, because every rule below
|
|
12
|
+
is a bare element selector at specificity 0,1,1 — a utility on a component
|
|
13
|
+
wins.
|
|
14
|
+
|
|
15
|
+
It reads only tokens. Re-point the palette or move the radius factor and the
|
|
16
|
+
article moves with everything else, which is the whole reason this is here
|
|
17
|
+
rather than in each consuming site: three sites hand-rolling a prose
|
|
18
|
+
stylesheet is three prose stylesheets that drift.
|
|
19
|
+
========================================================================== */
|
|
20
|
+
|
|
21
|
+
[data-m22-article] {
|
|
22
|
+
/* A measure, not a width. A narrower column still wins, and a figure or a
|
|
23
|
+
table opts out of it explicitly below. */
|
|
24
|
+
max-width: var(--w-reading);
|
|
25
|
+
color: var(--ink-2);
|
|
26
|
+
font-family: var(--sans);
|
|
27
|
+
font-size: 1rem;
|
|
28
|
+
font-weight: 300;
|
|
29
|
+
line-height: 1.75;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ─── Headings ─────────────────────────────────────────────────────────────
|
|
33
|
+
The type ladder, unchanged. An article does not get its own scale — it is
|
|
34
|
+
the same five steps every other surface climbs, which is what makes a post
|
|
35
|
+
and a component page read as one publication. */
|
|
36
|
+
|
|
37
|
+
[data-m22-article] :is(h1, h2, h3, h4, h5, h6) {
|
|
38
|
+
margin: 0;
|
|
39
|
+
color: var(--ink);
|
|
40
|
+
font-family: var(--font-heading, var(--serif));
|
|
41
|
+
font-weight: 400;
|
|
42
|
+
line-height: 1.25;
|
|
43
|
+
/* An anchored heading has to clear the masthead it scrolls under. */
|
|
44
|
+
scroll-margin-top: var(--scroll-offset);
|
|
45
|
+
}
|
|
46
|
+
[data-m22-article] h1 {
|
|
47
|
+
font-size: var(--fs-title);
|
|
48
|
+
letter-spacing: -0.02em;
|
|
49
|
+
}
|
|
50
|
+
[data-m22-article] h2 {
|
|
51
|
+
font-size: var(--fs-heading);
|
|
52
|
+
}
|
|
53
|
+
[data-m22-article] h3 {
|
|
54
|
+
font-size: var(--fs-sub);
|
|
55
|
+
}
|
|
56
|
+
[data-m22-article] h4 {
|
|
57
|
+
font-size: var(--fs-item);
|
|
58
|
+
}
|
|
59
|
+
[data-m22-article] :is(h5, h6) {
|
|
60
|
+
font-family: var(--mono);
|
|
61
|
+
font-size: 11px;
|
|
62
|
+
font-weight: 400;
|
|
63
|
+
letter-spacing: 0.2em;
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
color: var(--ink-3-aa);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ─── Rhythm ───────────────────────────────────────────────────────────────
|
|
69
|
+
Every block gets air ABOVE it and none below, the first one gets none at
|
|
70
|
+
all, and a heading gets more air above than below so it belongs to what
|
|
71
|
+
follows rather than to what it interrupts.
|
|
72
|
+
|
|
73
|
+
The reset uses `:where()`, and that is the load-bearing part. A plain
|
|
74
|
+
`[data-m22-article] p { margin: 0 }` is specificity 0,1,1 and outranks
|
|
75
|
+
`[data-m22-article] > * + *` at 0,1,0 — so the reset silently won and every
|
|
76
|
+
paragraph in the article lost its spacing, which is exactly the defect this
|
|
77
|
+
replaces. `:where()` contributes nothing, so the reset and the rhythm sit at
|
|
78
|
+
the same weight and source order decides, which is what a reset is for. */
|
|
79
|
+
|
|
80
|
+
[data-m22-article] :where(*) {
|
|
81
|
+
margin: 0;
|
|
82
|
+
}
|
|
83
|
+
[data-m22-article] > * + * {
|
|
84
|
+
margin-block-start: 1.25em;
|
|
85
|
+
}
|
|
86
|
+
/* `:is()` takes the specificity of its heaviest argument, so these two land at
|
|
87
|
+
0,1,1 and win over the general rule above whatever their order. */
|
|
88
|
+
[data-m22-article] > :is(h1, h2, h3, h4, h5, h6) {
|
|
89
|
+
margin-block-start: 2.25em;
|
|
90
|
+
}
|
|
91
|
+
[data-m22-article] > :is(h1, h2, h3, h4, h5, h6) + * {
|
|
92
|
+
margin-block-start: 0.75em;
|
|
93
|
+
}
|
|
94
|
+
[data-m22-article] > :first-child {
|
|
95
|
+
margin-block-start: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* The opening paragraph carries the piece. Marked, not guessed: an author
|
|
99
|
+
writes `<p class="lead">` (or a pipeline emits it), rather than the
|
|
100
|
+
stylesheet deciding that whatever came first is a standfirst. */
|
|
101
|
+
[data-m22-article] p.lead {
|
|
102
|
+
font-size: var(--fs-item);
|
|
103
|
+
line-height: 1.6;
|
|
104
|
+
color: var(--ink);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ─── Inline ─────────────────────────────────────────────────────────────── */
|
|
108
|
+
|
|
109
|
+
[data-m22-article] a {
|
|
110
|
+
color: var(--ink);
|
|
111
|
+
text-decoration: underline;
|
|
112
|
+
text-decoration-color: var(--rule-2);
|
|
113
|
+
text-underline-offset: 4px;
|
|
114
|
+
transition: text-decoration-color var(--fast) var(--ease);
|
|
115
|
+
}
|
|
116
|
+
[data-m22-article] a:hover {
|
|
117
|
+
text-decoration-color: var(--ink);
|
|
118
|
+
}
|
|
119
|
+
[data-m22-article] strong {
|
|
120
|
+
color: var(--ink);
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
}
|
|
123
|
+
[data-m22-article] em {
|
|
124
|
+
font-style: italic;
|
|
125
|
+
}
|
|
126
|
+
[data-m22-article] mark {
|
|
127
|
+
background: var(--accent-muted);
|
|
128
|
+
color: var(--ink);
|
|
129
|
+
padding-inline: 0.2em;
|
|
130
|
+
}
|
|
131
|
+
[data-m22-article] abbr[title] {
|
|
132
|
+
text-decoration: underline dotted var(--rule-2);
|
|
133
|
+
text-underline-offset: 3px;
|
|
134
|
+
}
|
|
135
|
+
[data-m22-article] :is(sub, sup) {
|
|
136
|
+
font-size: 0.75em;
|
|
137
|
+
line-height: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ─── Lists ────────────────────────────────────────────────────────────────
|
|
141
|
+
The marker is a token, not a glyph the browser picks: a default disc is a
|
|
142
|
+
filled circle at whatever size the font decides, and this system's only
|
|
143
|
+
filled circle is a status dot. */
|
|
144
|
+
|
|
145
|
+
[data-m22-article] :is(ul, ol) {
|
|
146
|
+
padding-inline-start: 1.4em;
|
|
147
|
+
}
|
|
148
|
+
[data-m22-article] :is(ul, ol) :is(ul, ol) {
|
|
149
|
+
margin-block-start: 0.4em;
|
|
150
|
+
}
|
|
151
|
+
[data-m22-article] li + li {
|
|
152
|
+
margin-block-start: 0.4em;
|
|
153
|
+
}
|
|
154
|
+
/* A paragraph or a fence nested inside a list item is still a block. */
|
|
155
|
+
[data-m22-article] li > * + * {
|
|
156
|
+
margin-block-start: 0.6em;
|
|
157
|
+
}
|
|
158
|
+
[data-m22-article] li::marker {
|
|
159
|
+
color: var(--ink-3-aa);
|
|
160
|
+
font-size: 0.9em;
|
|
161
|
+
}
|
|
162
|
+
[data-m22-article] ul {
|
|
163
|
+
list-style: none;
|
|
164
|
+
padding-inline-start: 1.15em;
|
|
165
|
+
}
|
|
166
|
+
[data-m22-article] ul > li {
|
|
167
|
+
position: relative;
|
|
168
|
+
}
|
|
169
|
+
/* A hairline dash, which is the marker the rest of the system uses. */
|
|
170
|
+
[data-m22-article] ul > li::before {
|
|
171
|
+
content: '';
|
|
172
|
+
position: absolute;
|
|
173
|
+
inset-inline-start: -1.15em;
|
|
174
|
+
inset-block-start: 0.85em;
|
|
175
|
+
width: 0.55em;
|
|
176
|
+
height: 1px;
|
|
177
|
+
background: var(--rule-2);
|
|
178
|
+
}
|
|
179
|
+
/* A task list already has its own marker — the checkbox. Both spellings,
|
|
180
|
+
because a pipeline may wrap the input in a label and `:has()` is what makes
|
|
181
|
+
the rule work either way. */
|
|
182
|
+
[data-m22-article] ul > li:has(input[type='checkbox'])::before {
|
|
183
|
+
content: none;
|
|
184
|
+
}
|
|
185
|
+
[data-m22-article] li:has(input[type='checkbox']) {
|
|
186
|
+
padding-inline-start: 0;
|
|
187
|
+
}
|
|
188
|
+
[data-m22-article] input[type='checkbox'] {
|
|
189
|
+
accent-color: var(--accent);
|
|
190
|
+
margin-inline-end: 0.5em;
|
|
191
|
+
/* A published checklist REPORTS state; it does not collect it. Disabled at
|
|
192
|
+
full opacity, because the dimming a browser applies to a disabled control
|
|
193
|
+
is a hint that it is unavailable, not that it is unticked. */
|
|
194
|
+
opacity: 1;
|
|
195
|
+
}
|
|
196
|
+
[data-m22-article] li:has(input[type='checkbox']) label {
|
|
197
|
+
display: inline;
|
|
198
|
+
cursor: default;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[data-m22-article] dt {
|
|
202
|
+
color: var(--ink);
|
|
203
|
+
font-weight: 500;
|
|
204
|
+
}
|
|
205
|
+
[data-m22-article] dd {
|
|
206
|
+
margin-block-end: 0.75em;
|
|
207
|
+
padding-inline-start: 1em;
|
|
208
|
+
border-inline-start: 1px solid var(--rule);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* ─── Quotation ────────────────────────────────────────────────────────────
|
|
212
|
+
A rule and a change of face, not italics and not a giant quote mark. */
|
|
213
|
+
|
|
214
|
+
[data-m22-article] blockquote {
|
|
215
|
+
padding-inline-start: 1.25em;
|
|
216
|
+
border-inline-start: 2px solid var(--rule-hard);
|
|
217
|
+
color: var(--ink);
|
|
218
|
+
font-family: var(--serif);
|
|
219
|
+
font-size: var(--fs-item);
|
|
220
|
+
line-height: 1.55;
|
|
221
|
+
}
|
|
222
|
+
[data-m22-article] blockquote p + p {
|
|
223
|
+
margin-block-start: 0.6em;
|
|
224
|
+
}
|
|
225
|
+
[data-m22-article] blockquote cite {
|
|
226
|
+
display: block;
|
|
227
|
+
margin-block-start: 0.75em;
|
|
228
|
+
color: var(--ink-3-aa);
|
|
229
|
+
font-family: var(--mono);
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
font-style: normal;
|
|
232
|
+
letter-spacing: 0.08em;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ─── Code ─────────────────────────────────────────────────────────────────
|
|
236
|
+
An inline span is a chip; a block is a plate. Neither is a second surface
|
|
237
|
+
colour — both sit on the one elevated step the system has. */
|
|
238
|
+
|
|
239
|
+
[data-m22-article] code {
|
|
240
|
+
background: var(--stone);
|
|
241
|
+
border-radius: var(--radius-sm);
|
|
242
|
+
padding: 0.15em 0.4em;
|
|
243
|
+
color: var(--ink);
|
|
244
|
+
font-family: var(--mono);
|
|
245
|
+
font-size: 0.85em;
|
|
246
|
+
}
|
|
247
|
+
[data-m22-article] pre {
|
|
248
|
+
padding: 1rem 1.125rem;
|
|
249
|
+
overflow-x: auto;
|
|
250
|
+
background: var(--paper-2);
|
|
251
|
+
border: 1px solid var(--rule);
|
|
252
|
+
border-radius: var(--radius-lg);
|
|
253
|
+
font-size: 13px;
|
|
254
|
+
line-height: 1.7;
|
|
255
|
+
scrollbar-width: thin;
|
|
256
|
+
scrollbar-color: var(--rule-2) transparent;
|
|
257
|
+
}
|
|
258
|
+
[data-m22-article] pre code {
|
|
259
|
+
background: none;
|
|
260
|
+
border-radius: 0;
|
|
261
|
+
padding: 0;
|
|
262
|
+
font-size: inherit;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* ─── Figures, media and tables ────────────────────────────────────────────
|
|
266
|
+
These are the three things allowed out of the measure: a table with six
|
|
267
|
+
columns and an image with a subject in it are both unreadable at 46rem. */
|
|
268
|
+
|
|
269
|
+
[data-m22-article] :is(figure, table, .m22-wide) {
|
|
270
|
+
width: 100%;
|
|
271
|
+
max-width: none;
|
|
272
|
+
}
|
|
273
|
+
[data-m22-article] > figure {
|
|
274
|
+
margin-block-start: 2em;
|
|
275
|
+
}
|
|
276
|
+
[data-m22-article] :is(img, video) {
|
|
277
|
+
display: block;
|
|
278
|
+
max-width: 100%;
|
|
279
|
+
height: auto;
|
|
280
|
+
border-radius: var(--radius);
|
|
281
|
+
}
|
|
282
|
+
[data-m22-article] figcaption {
|
|
283
|
+
margin-block-start: 0.75em;
|
|
284
|
+
color: var(--ink-3-aa);
|
|
285
|
+
font-family: var(--mono);
|
|
286
|
+
font-size: 11px;
|
|
287
|
+
letter-spacing: 0.08em;
|
|
288
|
+
line-height: 1.6;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
[data-m22-article] .m22-table-scroll {
|
|
292
|
+
overflow-x: auto;
|
|
293
|
+
scrollbar-width: thin;
|
|
294
|
+
scrollbar-color: var(--rule-2) transparent;
|
|
295
|
+
}
|
|
296
|
+
[data-m22-article] table {
|
|
297
|
+
border-collapse: collapse;
|
|
298
|
+
font-size: 0.9375rem;
|
|
299
|
+
text-align: start;
|
|
300
|
+
}
|
|
301
|
+
[data-m22-article] th {
|
|
302
|
+
padding: 0.65em 1.25em 0.65em 0;
|
|
303
|
+
border-block-end: 1px solid var(--rule-hard);
|
|
304
|
+
color: var(--ink-3-aa);
|
|
305
|
+
font-family: var(--mono);
|
|
306
|
+
font-size: 11px;
|
|
307
|
+
font-weight: 400;
|
|
308
|
+
letter-spacing: 0.2em;
|
|
309
|
+
text-transform: uppercase;
|
|
310
|
+
text-align: start;
|
|
311
|
+
white-space: nowrap;
|
|
312
|
+
}
|
|
313
|
+
[data-m22-article] td {
|
|
314
|
+
padding: 0.65em 1.25em 0.65em 0;
|
|
315
|
+
border-block-end: 1px solid var(--rule);
|
|
316
|
+
color: var(--ink-2);
|
|
317
|
+
vertical-align: baseline;
|
|
318
|
+
}
|
|
319
|
+
[data-m22-article] :is(th, td):last-child {
|
|
320
|
+
padding-inline-end: 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
[data-m22-article] > hr {
|
|
324
|
+
margin-block-start: 2.5em;
|
|
325
|
+
}
|
|
326
|
+
[data-m22-article] hr {
|
|
327
|
+
border: 0;
|
|
328
|
+
border-block-start: 1px solid var(--rule);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
[data-m22-article] kbd {
|
|
332
|
+
border: 1px solid var(--rule-2);
|
|
333
|
+
border-radius: var(--radius-sm);
|
|
334
|
+
padding: 0.1em 0.4em;
|
|
335
|
+
color: var(--ink-2);
|
|
336
|
+
font-family: var(--mono);
|
|
337
|
+
font-size: 0.8em;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* ─── Mathematics ──────────────────────────────────────────────────────────
|
|
341
|
+
MathML, which is why there is no font to load and no second stylesheet: the
|
|
342
|
+
browser sets it in the surrounding face. A display formula is centred and
|
|
343
|
+
allowed to scroll, because a long equation is the one block that genuinely
|
|
344
|
+
cannot wrap. */
|
|
345
|
+
|
|
346
|
+
[data-m22-article] math {
|
|
347
|
+
color: var(--ink);
|
|
348
|
+
font-size: 1.05em;
|
|
349
|
+
}
|
|
350
|
+
[data-m22-article] .m22-math {
|
|
351
|
+
/* `overflow-x: auto` alone turns the OTHER axis into `auto` too, which drew
|
|
352
|
+
a vertical scrollbar down the side of every formula. */
|
|
353
|
+
overflow-x: auto;
|
|
354
|
+
overflow-y: hidden;
|
|
355
|
+
padding-block: 0.35em;
|
|
356
|
+
scrollbar-width: thin;
|
|
357
|
+
scrollbar-color: var(--rule-2) transparent;
|
|
358
|
+
}
|
|
359
|
+
/* A block-level `math` is not affected by the container's `text-align`, so it
|
|
360
|
+
is centred the way any block is: by its own margins. */
|
|
361
|
+
[data-m22-article] math[display='block'] {
|
|
362
|
+
display: block;
|
|
363
|
+
width: fit-content;
|
|
364
|
+
margin-inline: auto;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* ─── Footnotes ────────────────────────────────────────────────────────────
|
|
368
|
+
Ruled off and set small: they are apparatus, not the piece. */
|
|
369
|
+
|
|
370
|
+
[data-m22-article] > .footnotes {
|
|
371
|
+
margin-block-start: 3em;
|
|
372
|
+
}
|
|
373
|
+
[data-m22-article] .footnotes {
|
|
374
|
+
padding-block-start: 1.5em;
|
|
375
|
+
border-block-start: 1px solid var(--rule);
|
|
376
|
+
font-size: 0.875rem;
|
|
377
|
+
}
|
|
378
|
+
[data-m22-article] .footnotes h2 {
|
|
379
|
+
font-family: var(--mono);
|
|
380
|
+
font-size: 11px;
|
|
381
|
+
letter-spacing: 0.2em;
|
|
382
|
+
text-transform: uppercase;
|
|
383
|
+
color: var(--ink-3-aa);
|
|
384
|
+
}
|
|
385
|
+
/* The reference and its back-arrow keep the underline every other link in the
|
|
386
|
+
article has. Stripping it was a WCAG 1.4.1 failure and axe says so: a link
|
|
387
|
+
inside a block of text has to be distinguishable from the text without
|
|
388
|
+
relying on colour, and ink on --ink-2 is 2.14:1 — well under the 3:1 that
|
|
389
|
+
would let colour carry it alone. */
|
|
390
|
+
[data-m22-article] .footnote-backref {
|
|
391
|
+
/* The glyph is the affordance; a name is not. */
|
|
392
|
+
font-size: 0.9em;
|
|
393
|
+
}
|
|
@@ -18,6 +18,20 @@ interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
18
18
|
* longer answer "where is the content".
|
|
19
19
|
*/
|
|
20
20
|
contentAs?: 'main' | 'div';
|
|
21
|
+
/**
|
|
22
|
+
* Names the sidebar landmark.
|
|
23
|
+
*
|
|
24
|
+
* Two `complementary` landmarks with the same name cannot be told apart, and
|
|
25
|
+
* a shell rendered inside another page — a preview, a screenshot harness —
|
|
26
|
+
* makes exactly that pair. It is also the only way a non-English app gets a
|
|
27
|
+
* landmark name its readers can read.
|
|
28
|
+
*/
|
|
29
|
+
sidebarLabel?: string;
|
|
30
|
+
/** Names the navigation landmark inside the sidebar. */
|
|
31
|
+
navLabel?: string;
|
|
32
|
+
/** The drawer toggle, closed and open. */
|
|
33
|
+
openLabel?: string;
|
|
34
|
+
closeLabel?: string;
|
|
21
35
|
children: ReactNode;
|
|
22
36
|
}
|
|
23
37
|
/**
|
|
@@ -33,6 +47,6 @@ interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
33
47
|
* <Card>…</Card>
|
|
34
48
|
* </AppShell>
|
|
35
49
|
*/
|
|
36
|
-
declare function AppShell({ sidebar, topbar, brand, contentAs: Content, children, className, ...rest }: AppShellProps): react.JSX.Element;
|
|
50
|
+
declare function AppShell({ sidebar, topbar, brand, contentAs: Content, sidebarLabel, navLabel, openLabel, closeLabel, children, className, ...rest }: AppShellProps): react.JSX.Element;
|
|
37
51
|
|
|
38
52
|
export { AppShell, type AppShellProps, AppShell as default };
|
|
@@ -8,6 +8,10 @@ function AppShell({
|
|
|
8
8
|
topbar,
|
|
9
9
|
brand,
|
|
10
10
|
contentAs: Content = "main",
|
|
11
|
+
sidebarLabel = "Sidebar",
|
|
12
|
+
navLabel = "Primary",
|
|
13
|
+
openLabel = "Open navigation",
|
|
14
|
+
closeLabel = "Close navigation",
|
|
11
15
|
children,
|
|
12
16
|
className,
|
|
13
17
|
...rest
|
|
@@ -32,7 +36,7 @@ function AppShell({
|
|
|
32
36
|
"button",
|
|
33
37
|
{
|
|
34
38
|
type: "button",
|
|
35
|
-
"aria-label":
|
|
39
|
+
"aria-label": closeLabel,
|
|
36
40
|
onClick: () => setOpen(false),
|
|
37
41
|
className: "fixed inset-0 z-(--z-scrim) bg-(--scrim) md:hidden"
|
|
38
42
|
}
|
|
@@ -41,7 +45,7 @@ function AppShell({
|
|
|
41
45
|
"aside",
|
|
42
46
|
{
|
|
43
47
|
id: sidebarId,
|
|
44
|
-
"aria-label":
|
|
48
|
+
"aria-label": sidebarLabel,
|
|
45
49
|
className: cn(
|
|
46
50
|
"fixed inset-y-0 start-0 z-(--z-modal) flex w-60 flex-col border-e border-(--rule) bg-(--paper) transition-transform duration-(--duration-slow) ease-(--ease-out-expo)",
|
|
47
51
|
// The drawer slides in from the edge reading STARTS at, so in an
|
|
@@ -54,7 +58,7 @@ function AppShell({
|
|
|
54
58
|
/* @__PURE__ */ jsx(
|
|
55
59
|
"nav",
|
|
56
60
|
{
|
|
57
|
-
"aria-label":
|
|
61
|
+
"aria-label": navLabel,
|
|
58
62
|
className: "flex flex-1 flex-col gap-1 overflow-y-auto p-3 scroll-slim",
|
|
59
63
|
children: sidebar
|
|
60
64
|
}
|
|
@@ -68,7 +72,7 @@ function AppShell({
|
|
|
68
72
|
"button",
|
|
69
73
|
{
|
|
70
74
|
type: "button",
|
|
71
|
-
"aria-label": open ?
|
|
75
|
+
"aria-label": open ? closeLabel : openLabel,
|
|
72
76
|
"aria-expanded": open,
|
|
73
77
|
"aria-controls": sidebarId,
|
|
74
78
|
onClick: () => setOpen((previous) => !previous),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["'use client'\n\nimport { Menu, X } from 'lucide-react'\nimport { useEffect, useId, useState } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Navigation content for the sidebar (e.g. a stack of `NavItem`s). */\n sidebar: ReactNode\n /** Optional content for the sticky topbar, laid out after the mobile toggle. */\n topbar?: ReactNode\n /** Optional brand lockup pinned to the top of the sidebar. */\n brand?: ReactNode\n /**\n * Which element the content well renders as.\n *\n * `main` is right for the shell of an application, and is the default. A\n * document may contain exactly one `main` landmark, so an AppShell rendered\n * INSIDE another page — a preview on a documentation site, a screenshot\n * harness — must pass `div`, or the page has two and assistive tech can no\n * longer answer \"where is the content\".\n */\n contentAs?: 'main' | 'div'\n children: ReactNode\n}\n\n/**\n * Two columns on a desktop, one column and a drawer on a phone.\n *\n * The drawer closes on Escape as well as on the scrim, because a drawer that\n * only closes by tapping outside it strands a keyboard user inside a menu they\n * cannot leave. The scrim is a `<button>` for the same reason — a `<div>` with\n * an `onClick` is not reachable by keyboard and not announced as anything.\n *\n * @example\n * <AppShell brand={<Wordmark />} sidebar={<NavItem href=\"/\">Home</NavItem>}>\n * <Card>…</Card>\n * </AppShell>\n */\nexport function AppShell({\n sidebar,\n topbar,\n brand,\n contentAs: Content = 'main',\n children,\n className,\n ...rest\n}: AppShellProps) {\n const [open, setOpen] = useState(false)\n const sidebarId = useId()\n\n useEffect(() => {\n if (!open) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [open])\n\n return (\n <div\n className={cn('min-h-svh bg-(--paper) md:grid md:grid-cols-[15rem_1fr]', className)}\n {...rest}\n >\n {open && (\n <button\n type=\"button\"\n aria-label
|
|
1
|
+
{"version":3,"sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["'use client'\n\nimport { Menu, X } from 'lucide-react'\nimport { useEffect, useId, useState } from 'react'\nimport type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Navigation content for the sidebar (e.g. a stack of `NavItem`s). */\n sidebar: ReactNode\n /** Optional content for the sticky topbar, laid out after the mobile toggle. */\n topbar?: ReactNode\n /** Optional brand lockup pinned to the top of the sidebar. */\n brand?: ReactNode\n /**\n * Which element the content well renders as.\n *\n * `main` is right for the shell of an application, and is the default. A\n * document may contain exactly one `main` landmark, so an AppShell rendered\n * INSIDE another page — a preview on a documentation site, a screenshot\n * harness — must pass `div`, or the page has two and assistive tech can no\n * longer answer \"where is the content\".\n */\n contentAs?: 'main' | 'div'\n /**\n * Names the sidebar landmark.\n *\n * Two `complementary` landmarks with the same name cannot be told apart, and\n * a shell rendered inside another page — a preview, a screenshot harness —\n * makes exactly that pair. It is also the only way a non-English app gets a\n * landmark name its readers can read.\n */\n sidebarLabel?: string\n /** Names the navigation landmark inside the sidebar. */\n navLabel?: string\n /** The drawer toggle, closed and open. */\n openLabel?: string\n closeLabel?: string\n children: ReactNode\n}\n\n/**\n * Two columns on a desktop, one column and a drawer on a phone.\n *\n * The drawer closes on Escape as well as on the scrim, because a drawer that\n * only closes by tapping outside it strands a keyboard user inside a menu they\n * cannot leave. The scrim is a `<button>` for the same reason — a `<div>` with\n * an `onClick` is not reachable by keyboard and not announced as anything.\n *\n * @example\n * <AppShell brand={<Wordmark />} sidebar={<NavItem href=\"/\">Home</NavItem>}>\n * <Card>…</Card>\n * </AppShell>\n */\nexport function AppShell({\n sidebar,\n topbar,\n brand,\n contentAs: Content = 'main',\n sidebarLabel = 'Sidebar',\n navLabel = 'Primary',\n openLabel = 'Open navigation',\n closeLabel = 'Close navigation',\n children,\n className,\n ...rest\n}: AppShellProps) {\n const [open, setOpen] = useState(false)\n const sidebarId = useId()\n\n useEffect(() => {\n if (!open) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [open])\n\n return (\n <div\n className={cn('min-h-svh bg-(--paper) md:grid md:grid-cols-[15rem_1fr]', className)}\n {...rest}\n >\n {open && (\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={() => setOpen(false)}\n className=\"fixed inset-0 z-(--z-scrim) bg-(--scrim) md:hidden\"\n />\n )}\n\n <aside\n id={sidebarId}\n aria-label={sidebarLabel}\n className={cn(\n 'fixed inset-y-0 start-0 z-(--z-modal) flex w-60 flex-col border-e border-(--rule) bg-(--paper) transition-transform duration-(--duration-slow) ease-(--ease-out-expo)',\n // The drawer slides in from the edge reading STARTS at, so in an\n // right-to-left document it comes from the right.\n open ? 'translate-x-0' : '-translate-x-full rtl:translate-x-full',\n 'md:static md:z-auto md:translate-x-0',\n )}\n >\n {brand && (\n <div className=\"flex h-14 items-center border-b border-(--rule) px-5\">{brand}</div>\n )}\n <nav\n aria-label={navLabel}\n className=\"flex flex-1 flex-col gap-1 overflow-y-auto p-3 scroll-slim\"\n >\n {sidebar}\n </nav>\n </aside>\n\n <div className=\"flex min-w-0 flex-col\">\n <header className=\"sticky top-0 z-(--z-sticky) flex h-14 items-center gap-3 border-b border-(--rule) bg-(--paper)/85 px-(--page-pad) backdrop-blur\">\n <button\n type=\"button\"\n aria-label={open ? closeLabel : openLabel}\n aria-expanded={open}\n aria-controls={sidebarId}\n onClick={() => setOpen((previous) => !previous)}\n className=\"-ms-2.5 grid size-11 place-items-center rounded-(--radius) text-(--ink-2) transition-colors duration-(--duration-fast) hover:text-(--ink) md:hidden\"\n >\n {open ? <X size={20} strokeWidth={1.5} aria-hidden /> : <Menu size={20} strokeWidth={1.5} aria-hidden />}\n </button>\n {topbar}\n </header>\n\n <Content className=\"mx-auto w-full max-w-(--w-page) px-(--page-pad) py-8\">{children}</Content>\n </div>\n </div>\n )\n}\n\nexport default AppShell\n"],"mappings":";AAqFQ,cAQF,YARE;AAnFR,SAAS,MAAM,SAAS;AACxB,SAAS,WAAW,OAAO,gBAAgB;AAE3C,SAAS,UAAU;AAiDZ,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,UAAU;AAAA,EACrB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,YAAY,MAAM;AAExB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,UAAyB;AACtC,UAAI,MAAM,QAAQ,SAAU,SAAQ,KAAK;AAAA,IAC3C;AACA,aAAS,iBAAiB,WAAW,KAAK;AAC1C,WAAO,MAAM,SAAS,oBAAoB,WAAW,KAAK;AAAA,EAC5D,GAAG,CAAC,IAAI,CAAC;AAET,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,2DAA2D,SAAS;AAAA,MACjF,GAAG;AAAA,MAEH;AAAA,gBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAY;AAAA,YACZ,SAAS,MAAM,QAAQ,KAAK;AAAA,YAC5B,WAAU;AAAA;AAAA,QACZ;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,cAAY;AAAA,YACZ,WAAW;AAAA,cACT;AAAA;AAAA;AAAA,cAGA,OAAO,kBAAkB;AAAA,cACzB;AAAA,YACF;AAAA,YAEC;AAAA,uBACC,oBAAC,SAAI,WAAU,wDAAwD,iBAAM;AAAA,cAE/E;AAAA,gBAAC;AAAA;AAAA,kBACC,cAAY;AAAA,kBACZ,WAAU;AAAA,kBAET;AAAA;AAAA,cACH;AAAA;AAAA;AAAA,QACF;AAAA,QAEA,qBAAC,SAAI,WAAU,yBACb;AAAA,+BAAC,YAAO,WAAU,mIAChB;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAY,OAAO,aAAa;AAAA,gBAChC,iBAAe;AAAA,gBACf,iBAAe;AAAA,gBACf,SAAS,MAAM,QAAQ,CAAC,aAAa,CAAC,QAAQ;AAAA,gBAC9C,WAAU;AAAA,gBAET,iBAAO,oBAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,IAAK,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,YACxG;AAAA,YACC;AAAA,aACH;AAAA,UAEA,oBAAC,WAAQ,WAAU,wDAAwD,UAAS;AAAA,WACtF;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ArticleProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Rendered HTML from a Markdown or MDX pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Trusted markup only. This sets `dangerouslySetInnerHTML`, so it must come
|
|
9
|
+
* from content you control — a repository's own posts, a CMS you author —
|
|
10
|
+
* and never from a reader. Untrusted Markdown has to be sanitised before it
|
|
11
|
+
* reaches here; that is a pipeline decision, not a component one, and a
|
|
12
|
+
* sanitiser bolted on inside a styling primitive would be the wrong place to
|
|
13
|
+
* make it and the easiest place to get it wrong.
|
|
14
|
+
*/
|
|
15
|
+
html?: string;
|
|
16
|
+
/** The element to render. `article` by default; pass `div` for a fragment. */
|
|
17
|
+
as?: 'article' | 'div' | 'section';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The long-form reading surface.
|
|
21
|
+
*
|
|
22
|
+
* Everything a Markdown pipeline can emit — headings, prose, lists, tables,
|
|
23
|
+
* quotations, code, figures, footnotes, MathML — set in this system's type,
|
|
24
|
+
* colour and rules. The styles live in `article.css` rather than in a class
|
|
25
|
+
* string, because the input is not JSX: there is no component to hang a class
|
|
26
|
+
* on when the markup arrived as a string, so the contract has to be the element
|
|
27
|
+
* names themselves.
|
|
28
|
+
*
|
|
29
|
+
* That file is published on its own as `@misoto22/design/article.css`, so a
|
|
30
|
+
* site with its own Markdown pipeline can take the reading surface without
|
|
31
|
+
* taking the components — which is the case this exists for. Three sites
|
|
32
|
+
* hand-rolling a prose stylesheet is three prose stylesheets that drift.
|
|
33
|
+
*
|
|
34
|
+
* Composable either way: pass `html` for a rendered string, or children for
|
|
35
|
+
* real elements. A post that mixes the two — prose with a `Diagram` dropped
|
|
36
|
+
* into the middle — renders its blocks in order and gives each one an
|
|
37
|
+
* `Article`; a component inside keeps its own classes, because every rule in
|
|
38
|
+
* the layer is a bare element selector that a utility outranks.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* <Article html={renderedMarkdown} />
|
|
42
|
+
* @example
|
|
43
|
+
* <Article>
|
|
44
|
+
* <h1>The White Reset</h1>
|
|
45
|
+
* <p className="lead">A monochrome system for software and writing.</p>
|
|
46
|
+
* <Diagram spec={spec} />
|
|
47
|
+
* </Article>
|
|
48
|
+
*/
|
|
49
|
+
declare function Article({ html, as: Comp, className, children, ...rest }: ArticleProps): react.JSX.Element;
|
|
50
|
+
|
|
51
|
+
export { Article, type ArticleProps, Article as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
function Article({ html, as: Comp = "article", className, children, ...rest }) {
|
|
3
|
+
if (html !== void 0) {
|
|
4
|
+
return /* @__PURE__ */ jsx(
|
|
5
|
+
Comp,
|
|
6
|
+
{
|
|
7
|
+
"data-m22-article": true,
|
|
8
|
+
className,
|
|
9
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
10
|
+
...rest
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return /* @__PURE__ */ jsx(Comp, { "data-m22-article": true, className, ...rest, children });
|
|
15
|
+
}
|
|
16
|
+
var Article_default = Article;
|
|
17
|
+
export {
|
|
18
|
+
Article,
|
|
19
|
+
Article_default as default
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=Article.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Article/Article.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react'\n\nexport interface ArticleProps extends HTMLAttributes<HTMLElement> {\n /**\n * Rendered HTML from a Markdown or MDX pipeline.\n *\n * Trusted markup only. This sets `dangerouslySetInnerHTML`, so it must come\n * from content you control — a repository's own posts, a CMS you author —\n * and never from a reader. Untrusted Markdown has to be sanitised before it\n * reaches here; that is a pipeline decision, not a component one, and a\n * sanitiser bolted on inside a styling primitive would be the wrong place to\n * make it and the easiest place to get it wrong.\n */\n html?: string\n /** The element to render. `article` by default; pass `div` for a fragment. */\n as?: 'article' | 'div' | 'section'\n}\n\n/**\n * The long-form reading surface.\n *\n * Everything a Markdown pipeline can emit — headings, prose, lists, tables,\n * quotations, code, figures, footnotes, MathML — set in this system's type,\n * colour and rules. The styles live in `article.css` rather than in a class\n * string, because the input is not JSX: there is no component to hang a class\n * on when the markup arrived as a string, so the contract has to be the element\n * names themselves.\n *\n * That file is published on its own as `@misoto22/design/article.css`, so a\n * site with its own Markdown pipeline can take the reading surface without\n * taking the components — which is the case this exists for. Three sites\n * hand-rolling a prose stylesheet is three prose stylesheets that drift.\n *\n * Composable either way: pass `html` for a rendered string, or children for\n * real elements. A post that mixes the two — prose with a `Diagram` dropped\n * into the middle — renders its blocks in order and gives each one an\n * `Article`; a component inside keeps its own classes, because every rule in\n * the layer is a bare element selector that a utility outranks.\n *\n * @example\n * <Article html={renderedMarkdown} />\n * @example\n * <Article>\n * <h1>The White Reset</h1>\n * <p className=\"lead\">A monochrome system for software and writing.</p>\n * <Diagram spec={spec} />\n * </Article>\n */\nexport function Article({ html, as: Comp = 'article', className, children, ...rest }: ArticleProps) {\n // One or the other, never both. The rhythm rules are child combinators, so a\n // wrapper around the rendered string — even a `display: contents` one, which\n // removes the box but not the DOM node — would take every heading and\n // paragraph out of the article's direct children and cost them their\n // spacing. A page with both is a page with two Articles, and it should say\n // so.\n if (html !== undefined) {\n return (\n <Comp\n data-m22-article\n className={className}\n dangerouslySetInnerHTML={{ __html: html }}\n {...rest}\n />\n )\n }\n\n return (\n <Comp data-m22-article className={className} {...rest}>\n {children}\n </Comp>\n )\n}\n\nexport default Article\n"],"mappings":"AAyDM;AATC,SAAS,QAAQ,EAAE,MAAM,IAAI,OAAO,WAAW,WAAW,UAAU,GAAG,KAAK,GAAiB;AAOlG,MAAI,SAAS,QAAW;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,oBAAgB;AAAA,QAChB;AAAA,QACA,yBAAyB,EAAE,QAAQ,KAAK;AAAA,QACvC,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AAEA,SACE,oBAAC,QAAK,oBAAgB,MAAC,WAAuB,GAAG,MAC9C,UACH;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
4
|
import { Spinner } from "../Spinner/Spinner.js";
|
|
5
|
-
const BASE = "inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius
|
|
5
|
+
const BASE = "inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none";
|
|
6
6
|
const VARIANT = {
|
|
7
7
|
primary: "bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85",
|
|
8
8
|
secondary: "border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)",
|
|
@@ -23,7 +23,7 @@ const ICON_SIZE = {
|
|
|
23
23
|
};
|
|
24
24
|
const SPINNER_SIZE = { sm: "sm", md: "sm", lg: "md" };
|
|
25
25
|
function Keycap({ glyph }) {
|
|
26
|
-
return /* @__PURE__ */ jsx("span", { className: "rounded-(--radius-
|
|
26
|
+
return /* @__PURE__ */ jsx("span", { className: "rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55", children: glyph });
|
|
27
27
|
}
|
|
28
28
|
function Button(props) {
|
|
29
29
|
const {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Spinner } from '../Spinner/Spinner'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * The base every variant shares.\n *\n * It reads `--radius`, the CONTROL step, which is the same corner an `Input`,\n * a `Select` trigger and a `Textarea` draw. It used to be a pill, and a pill\n * beside an 8px field is two different ideas of what a control is — the one\n * inconsistency in the system that every reader noticed and none of the\n * component pages could explain. Everything on the control line now agrees,\n * and the capsule is kept for the things that genuinely are one: a badge, a\n * status pill, a segmented strip, a progress track.\n *\n * It does not move on hover. An earlier base lifted a pixel and asked for a\n * shadow the White Reset does not have — `--shadow` resolves to `none`, so that\n * hover state was invisible on every button in the system while still costing a\n * transition. Opacity carries the filled hover; the border carries the outlined\n * one.\n *\n * The transition names its properties rather than using `all`, so a layout\n * change (a width that grows when the label becomes \"Saving…\") is instant\n * instead of sliding.\n */\nconst BASE =\n 'inline-flex items-center justify-center gap-(--control-gap) rounded-(--radius) font-sans transition-[opacity,color,border-color,background-color] duration-(--duration-fast) ease-(--ease-out-expo) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none aria-disabled:opacity-(--disabled-opacity) aria-disabled:pointer-events-none'\n\nconst VARIANT: Record<ButtonVariant, string> = {\n primary: 'bg-(--accent) text-(--accent-foreground) border border-(--accent) hover:opacity-85',\n secondary: 'border border-(--rule-2) text-(--ink) bg-transparent hover:border-(--ink)',\n ghost: 'border border-transparent text-(--ink-2) hover:text-(--ink) hover:bg-(--stone)',\n /* The one place chroma is allowed on a control, and only because the action\n is destructive — that is a state, not a brand colour. */\n danger: 'bg-(--danger) text-(--paper) border border-(--danger) hover:opacity-85',\n}\n\n/**\n * `min-h` rather than `h`, so a button that wraps to two lines grows instead of\n * clipping.\n *\n * The heights come from the density tokens rather than from a literal, so a\n * subtree marked `data-density=\"compact\"` shrinks its controls without a single\n * call site being told. At the default density `md` is 44px — the pointer\n * target WCAG 2.5.5 asks for.\n */\nconst SIZE: Record<ButtonSize, string> = {\n sm: 'min-h-(--control-h-sm) px-(--control-px-sm) py-(--control-py-sm) text-[13px]',\n md: 'min-h-(--control-h-md) px-(--control-px-md) py-(--control-py-md) text-sm',\n lg: 'min-h-(--control-h-lg) px-(--control-px-lg) py-(--control-py-lg) text-[15px]',\n}\n\n/** Square, because an icon has no label to give the box its width. */\nconst ICON_SIZE: Record<ButtonSize, string> = {\n sm: 'size-(--control-h-sm) p-0',\n md: 'size-(--control-h-md) p-0',\n lg: 'size-(--control-h-lg) p-0',\n}\n\nconst SPINNER_SIZE: Record<ButtonSize, 'sm' | 'md'> = { sm: 'sm', md: 'sm', lg: 'md' }\n\ninterface CommonProps {\n children?: ReactNode\n /**\n * Which action this is. `primary` is the one thing the screen wants you to\n * do, so there is at most one per view; `danger` is reserved for destructive\n * actions and is the only place chroma is allowed on a control.\n */\n variant?: ButtonVariant\n /** 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. */\n size?: ButtonSize\n /** Optional mono keycap glyph rendered after the label (e.g. \"P\"). */\n keycap?: string\n /**\n * Swaps the leading content for a spinner and blocks interaction. The label\n * stays put — a button that empties out while it works loses its width, and\n * the page reflows under the pointer that just clicked it.\n */\n loading?: boolean\n /**\n * Square control with no label. REQUIRES `aria-label`: an icon-only button\n * with no accessible name is invisible to a screen reader, and this is the\n * single most common way a design system ships an unusable control.\n */\n iconOnly?: boolean\n /**\n * Render the child element instead of a `<button>`, keeping these styles.\n * Use it to hand the styling to a router's `Link` — `asChild` is what keeps\n * this package free of any one router.\n *\n * The decoration slots (`keycap`, `loading`) are not injected into a slotted\n * child; compose them inside it yourself.\n *\n * @example\n * <Button asChild><Link href=\"/work\">Work</Link></Button>\n */\n asChild?: boolean\n className?: string\n}\n\ntype ButtonAsLink = CommonProps &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> & { href: string }\n\ntype ButtonAsButton = CommonProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> & { href?: undefined }\n\nexport type ButtonProps = ButtonAsLink | ButtonAsButton\n\nfunction Keycap({ glyph }: { glyph: string }) {\n return (\n <span className=\"rounded-(--radius-sm) border border-current px-2 py-0.5 font-mono text-[11px] opacity-55\">\n {glyph}\n </span>\n )\n}\n\n/**\n * The system's action.\n *\n * Renders a `<button>`, or an `<a>` when given `href`, or whatever you hand it\n * via `asChild`. No router is imported, so the package stays framework-agnostic\n * and a Next or React Router app wires its own `Link` at the call site.\n *\n * Server-component friendly: there is no client boundary here, so it renders in\n * a static page as well as an interactive one.\n *\n * @example\n * <Button variant=\"secondary\" keycap=\"P\">View projects</Button>\n * @example\n * <Button loading disabled>Saving…</Button>\n * @example\n * <Button iconOnly aria-label=\"Close\"><X size={16} /></Button>\n */\nexport function Button(props: ButtonProps) {\n const {\n children,\n variant = 'primary',\n size = 'md',\n keycap,\n loading = false,\n iconOnly = false,\n asChild = false,\n className,\n } = props\n\n const cls = cn(BASE, VARIANT[variant], iconOnly ? ICON_SIZE[size] : SIZE[size], className)\n\n // A filled ground needs a spinner in the inherited colour; an outlined one\n // reads better against the page's own rule scale.\n const spinnerTone = variant === 'primary' || variant === 'danger' ? 'current' : 'default'\n const decoration = (\n <>\n {loading && <Spinner size={SPINNER_SIZE[size]} tone={spinnerTone} label={null} />}\n {children}\n {keycap && <Keycap glyph={keycap} />}\n </>\n )\n\n if (asChild) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <Slot className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n if (props.href !== undefined) {\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, href, ...rest } = props\n return (\n <a\n href={href}\n className={cls}\n // A link cannot be `disabled`; `aria-disabled` plus the pointer-events\n // rule in BASE is the accessible equivalent.\n aria-disabled={loading || undefined}\n {...rest}\n >\n {decoration}\n </a>\n )\n }\n\n const { children: _c, variant: _v, size: _s, keycap: _k, loading: _l, iconOnly: _i, asChild: _a, className: _cn, ...rest } = props\n return (\n <button\n type={rest.type ?? 'button'}\n aria-busy={loading || undefined}\n disabled={rest.disabled || loading}\n className={cls}\n {...rest}\n >\n {decoration}\n </button>\n )\n}\n\nexport default Button\n"],"mappings":"AAkHI,SAyCA,UAzCA,KAyCA,YAzCA;AAlHJ,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,eAAe;AA0BxB,MAAM,OACJ;AAEF,MAAM,UAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,QAAQ;AACV;AAWA,MAAM,OAAmC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,MAAM,YAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,eAAgD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AAiDrF,SAAS,OAAO,EAAE,MAAM,GAAsB;AAC5C,SACE,oBAAC,UAAK,WAAU,4FACb,iBACH;AAEJ;AAmBO,SAAS,OAAO,OAAoB;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,QAAM,MAAM,GAAG,MAAM,QAAQ,OAAO,GAAG,WAAW,UAAU,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AAIzF,QAAM,cAAc,YAAY,aAAa,YAAY,WAAW,YAAY;AAChF,QAAM,aACJ,iCACG;AAAA,eAAW,oBAAC,WAAQ,MAAM,aAAa,IAAI,GAAG,MAAM,aAAa,OAAO,MAAM;AAAA,IAC9E;AAAA,IACA,UAAU,oBAAC,UAAO,OAAO,QAAQ;AAAA,KACpC;AAGF,MAAI,SAAS;AACX,UAAM,EAAE,UAAUA,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,GAAGC,MAAK,IAAI;AAC7H,WACE,oBAAC,QAAK,WAAW,KAAM,GAAGA,OACvB,UACH;AAAA,EAEJ;AAEA,MAAI,MAAM,SAAS,QAAW;AAC5B,UAAM,EAAE,UAAUR,KAAI,SAASC,KAAI,MAAMC,KAAI,QAAQC,KAAI,SAASC,KAAI,UAAUC,KAAI,SAASC,KAAI,WAAWC,MAAK,MAAM,GAAGC,MAAK,IAAI;AACnI,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,QAGX,iBAAe,WAAW;AAAA,QACzB,GAAGA;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,EAAE,UAAU,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,QAAQ;AAAA,MACnB,aAAW,WAAW;AAAA,MACtB,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW;AAAA,MACV,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,iBAAQ;","names":["_c","_v","_s","_k","_l","_i","_a","_cn","rest"]}
|
|
@@ -23,6 +23,6 @@ type CalendarProps = DayPickerProps;
|
|
|
23
23
|
* // A birth date: widen the years, since the default span is deliberately short.
|
|
24
24
|
* <Calendar mode="single" startMonth={new Date(1920, 0)} endMonth={new Date()} />
|
|
25
25
|
*/
|
|
26
|
-
declare function Calendar({ className, classNames,
|
|
26
|
+
declare function Calendar({ className, classNames, components, ...props }: CalendarProps): react.JSX.Element;
|
|
27
27
|
|
|
28
28
|
export { Calendar, type CalendarProps, Calendar as default };
|