@dillingerstaffing/strand-ui 0.2.0 → 0.2.2
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/HTML_REFERENCE.md +753 -0
- package/dist/components/DataReadout/DataReadout.d.ts +2 -0
- package/dist/components/DataReadout/DataReadout.d.ts.map +1 -1
- package/dist/css/strand-ui.css +9 -0
- package/dist/index.js +13 -9
- package/package.json +12 -22
- package/src/components/DataReadout/DataReadout.css +9 -0
- package/src/components/DataReadout/DataReadout.test.tsx +36 -0
- package/src/components/DataReadout/DataReadout.tsx +8 -2
- package/LICENSE +0 -21
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
# Strand HTML Reference
|
|
2
|
+
|
|
3
|
+
CSS class API for using Strand UI components as raw HTML. Use this when building without the Preact/React library, for static sites, documentation pages, or server-rendered HTML.
|
|
4
|
+
|
|
5
|
+
## Required CSS
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<link rel="stylesheet" href="path/to/@dillingerstaffing/strand/css/tokens.css">
|
|
9
|
+
<link rel="stylesheet" href="path/to/@dillingerstaffing/strand-ui/dist/css/strand-ui.css">
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Presentation Mode
|
|
13
|
+
|
|
14
|
+
Wrap static previews in `.strand-static` to render at full visual fidelity without interaction:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<div class="strand-static">
|
|
18
|
+
<button class="strand-btn strand-btn--primary strand-btn--md" disabled>
|
|
19
|
+
<span class="strand-btn__content">Submit</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `disabled` attribute prevents interaction. `.strand-static` overrides the disabled visual styling so components appear at full opacity. All transitions and animations are also suppressed inside `.strand-static`.
|
|
25
|
+
|
|
26
|
+
## Recessed Viewport
|
|
27
|
+
|
|
28
|
+
Use `.strand-viewport` for component previews and showcase containers. It creates a recessed instrument panel effect (sits below the card surface):
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<div class="strand-viewport strand-static">
|
|
32
|
+
<!-- component previews here -->
|
|
33
|
+
</div>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Provides: `--strand-surface-recessed` background, inset shadow, `--strand-radius-lg` corners, `--strand-space-6` padding.
|
|
37
|
+
|
|
38
|
+
## Padding Tiers
|
|
39
|
+
|
|
40
|
+
Card padding tiers (used via `strand-card--pad-{sm|md|lg}`):
|
|
41
|
+
- `sm`: 16px (compact widgets, dense UIs)
|
|
42
|
+
- `md`: 24px (standard cards, default for most contexts)
|
|
43
|
+
- `lg`: 40px (showcase, hero, documentation)
|
|
44
|
+
|
|
45
|
+
## Focus States
|
|
46
|
+
|
|
47
|
+
All interactive elements include `:focus-visible` styling per Part XII of the design language:
|
|
48
|
+
|
|
49
|
+
```css
|
|
50
|
+
:focus-visible {
|
|
51
|
+
outline: 2px solid var(--strand-blue-primary);
|
|
52
|
+
outline-offset: 2px;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Appears on keyboard navigation only (not on mouse click)
|
|
57
|
+
- 2px solid blue outline with 2px offset from the element edge
|
|
58
|
+
- Applied to: buttons, links, interactive cards, tab buttons, sort buttons, nav links, hamburger menu
|
|
59
|
+
|
|
60
|
+
No additional classes needed. Focus rings are built into each component's CSS.
|
|
61
|
+
|
|
62
|
+
## Boundary Integrity
|
|
63
|
+
|
|
64
|
+
All container components (Grid, Stack, Card, Container) enforce boundary integrity. Children cannot visually breach the parent's padding zone. This is enforced at the CSS level via `overflow: hidden`, `max-width: 100%`, and `min-width: 0` on children. You do not need to add these yourself.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Input Components
|
|
69
|
+
|
|
70
|
+
### Button
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<button class="strand-btn strand-btn--primary strand-btn--md" type="button">
|
|
74
|
+
<span class="strand-btn__content">Label</span>
|
|
75
|
+
</button>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Variants:** `strand-btn--primary` | `strand-btn--secondary` | `strand-btn--ghost` | `strand-btn--danger`
|
|
79
|
+
**Sizes:** `strand-btn--sm` (32px) | `strand-btn--md` (40px) | `strand-btn--lg` (48px)
|
|
80
|
+
**Modifiers:** `strand-btn--full-width` | `strand-btn--icon-only`
|
|
81
|
+
**Loading:** Add class `strand-btn--loading` + `<span class="strand-btn__spinner" aria-hidden="true"></span>` before content. Set content span `style="visibility:hidden"`.
|
|
82
|
+
**Disabled:** Add `disabled` attribute. Use `.strand-static` parent to show full opacity.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Input
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<div class="strand-input">
|
|
90
|
+
<input type="text" class="strand-input__field" placeholder="Enter text">
|
|
91
|
+
</div>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**States:** `strand-input--error` | `strand-input--disabled`
|
|
95
|
+
**Addons:** Add `strand-input--has-leading` to wrapper + `<span class="strand-input__leading" aria-hidden="true">$</span>` before the input. Add `strand-input--has-trailing` to wrapper + `<span class="strand-input__trailing" aria-hidden="true">kg</span>` after the input.
|
|
96
|
+
**Error:** Add `strand-input--error` to wrapper and `aria-invalid="true"` to the input element.
|
|
97
|
+
**Disabled:** Add `strand-input--disabled` to wrapper and `disabled` to the input element.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### Textarea
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<div class="strand-textarea">
|
|
105
|
+
<textarea class="strand-textarea__field" placeholder="Enter text"></textarea>
|
|
106
|
+
</div>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**States:** `strand-textarea--error` | `strand-textarea--disabled` | `strand-textarea--auto-resize`
|
|
110
|
+
**Character count:** Add `<span class="strand-textarea__count" aria-live="polite">0/500</span>` after the textarea. Set `maxlength` on the textarea.
|
|
111
|
+
**Error:** Add `strand-textarea--error` to wrapper and `aria-invalid="true"` to the textarea.
|
|
112
|
+
**Disabled:** Add `strand-textarea--disabled` to wrapper and `disabled` to the textarea.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### Select
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<div class="strand-select">
|
|
120
|
+
<select class="strand-select__field">
|
|
121
|
+
<option value="" disabled>Choose one</option>
|
|
122
|
+
<option value="a">Option A</option>
|
|
123
|
+
<option value="b">Option B</option>
|
|
124
|
+
</select>
|
|
125
|
+
<span class="strand-select__arrow" aria-hidden="true"></span>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**States:** `strand-select--error` | `strand-select--disabled`
|
|
130
|
+
**Error:** Add `strand-select--error` to wrapper and `aria-invalid="true"` to the select.
|
|
131
|
+
**Disabled:** Add `strand-select--disabled` to wrapper and `disabled` to the select.
|
|
132
|
+
**Note:** The `strand-select__arrow` span renders the dropdown caret via CSS borders. Always include it.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### Checkbox
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<label class="strand-checkbox strand-checkbox--checked">
|
|
140
|
+
<input type="checkbox" class="strand-checkbox__native" checked role="checkbox" aria-checked="true">
|
|
141
|
+
<span class="strand-checkbox__control" aria-hidden="true">
|
|
142
|
+
<svg class="strand-checkbox__icon" viewBox="0 0 16 16" fill="none">
|
|
143
|
+
<path d="M3.5 8L6.5 11L12.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
144
|
+
</svg>
|
|
145
|
+
</span>
|
|
146
|
+
<span class="strand-checkbox__label">Accept terms</span>
|
|
147
|
+
</label>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**States:** `strand-checkbox--checked` | `strand-checkbox--indeterminate` | `strand-checkbox--disabled`
|
|
151
|
+
**Unchecked:** Omit `strand-checkbox--checked` and the SVG inside `strand-checkbox__control`. Set `aria-checked="false"`.
|
|
152
|
+
**Indeterminate:** Use `strand-checkbox--indeterminate` + `aria-checked="mixed"` + indeterminate SVG: `<svg class="strand-checkbox__icon" viewBox="0 0 16 16" fill="none"><line x1="4" y1="8" x2="12" y2="8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>`
|
|
153
|
+
**Note:** The native input is visually hidden via CSS. The `strand-checkbox__control` span provides the custom visual. SVG is required for the check/dash icon.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Radio
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<label class="strand-radio strand-radio--checked">
|
|
161
|
+
<input type="radio" class="strand-radio__native" name="group" value="a" checked>
|
|
162
|
+
<span class="strand-radio__control" aria-hidden="true">
|
|
163
|
+
<span class="strand-radio__dot"></span>
|
|
164
|
+
</span>
|
|
165
|
+
<span class="strand-radio__label">Option A</span>
|
|
166
|
+
</label>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**States:** `strand-radio--checked` | `strand-radio--disabled`
|
|
170
|
+
**Unchecked:** Omit `strand-radio--checked`. The dot is hidden via `transform: scale(0)` in CSS.
|
|
171
|
+
**Note:** The native input is visually hidden. The dot scales up when `strand-radio--checked` is present. Group radios with the same `name` attribute.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### Switch
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<label class="strand-switch strand-switch--checked">
|
|
179
|
+
<button type="button" role="switch" class="strand-switch__track" aria-checked="true">
|
|
180
|
+
<span class="strand-switch__thumb" aria-hidden="true"></span>
|
|
181
|
+
</button>
|
|
182
|
+
<span class="strand-switch__label">Dark mode</span>
|
|
183
|
+
</label>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**States:** `strand-switch--checked` | `strand-switch--disabled`
|
|
187
|
+
**Off:** Omit `strand-switch--checked`. Set `aria-checked="false"`.
|
|
188
|
+
**Disabled:** Add `strand-switch--disabled` to the label and `disabled` to the button.
|
|
189
|
+
**Note:** The track is a `<button>` with `role="switch"`. The thumb translates right when checked via CSS.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### Slider
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<div class="strand-slider">
|
|
197
|
+
<input type="range" class="strand-slider__field" min="0" max="100" step="1" value="50"
|
|
198
|
+
aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
|
|
199
|
+
</div>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**States:** `strand-slider--disabled`
|
|
203
|
+
**Disabled:** Add `strand-slider--disabled` to wrapper and `disabled` to the input.
|
|
204
|
+
**Note:** Thumb styling uses both `-webkit-slider-thumb` and `-moz-range-thumb` pseudo-elements in the CSS.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### FormField
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<div class="strand-form-field">
|
|
212
|
+
<label class="strand-form-field__label" for="email">
|
|
213
|
+
Email
|
|
214
|
+
<span class="strand-form-field__required" aria-hidden="true">*</span>
|
|
215
|
+
</label>
|
|
216
|
+
<div class="strand-form-field__control">
|
|
217
|
+
<!-- Place any input component here -->
|
|
218
|
+
<div class="strand-input">
|
|
219
|
+
<input type="email" id="email" class="strand-input__field" placeholder="you@example.com">
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<p class="strand-form-field__hint" id="email-hint">We will never share your email.</p>
|
|
223
|
+
</div>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**States:** `strand-form-field--error`
|
|
227
|
+
**Error:** Replace `strand-form-field__hint` with `<p class="strand-form-field__error" id="email-error" role="alert">Invalid email address.</p>` and add `strand-form-field--error` to the wrapper.
|
|
228
|
+
**Required indicator:** Include `<span class="strand-form-field__required" aria-hidden="true">*</span>` inside the label.
|
|
229
|
+
**Note:** The `for` attribute on the label must match the `id` on the input control.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Display Components
|
|
234
|
+
|
|
235
|
+
### Card
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<div class="strand-card strand-card--elevated strand-card--pad-md">
|
|
239
|
+
Card content here.
|
|
240
|
+
</div>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Variants:** `strand-card--elevated` | `strand-card--outlined` | `strand-card--interactive`
|
|
244
|
+
**Padding:** `strand-card--pad-none` | `strand-card--pad-sm` | `strand-card--pad-md` | `strand-card--pad-lg`
|
|
245
|
+
**Note:** `strand-card--interactive` adds hover lift and cursor pointer. Use for clickable cards.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### Badge
|
|
250
|
+
|
|
251
|
+
**Inline (standalone):**
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<span class="strand-badge strand-badge--inline">
|
|
255
|
+
<span class="strand-badge__indicator strand-badge--count strand-badge--blue" role="status" aria-label="5 notifications">5</span>
|
|
256
|
+
</span>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Wrapping a child (positioned at top-right):**
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<span class="strand-badge">
|
|
263
|
+
<!-- Wrapped element (e.g., icon, avatar) -->
|
|
264
|
+
<span style="width:24px;height:24px;display:inline-block;background:#ccc;border-radius:4px;"></span>
|
|
265
|
+
<span class="strand-badge__indicator strand-badge--dot strand-badge--red" role="status" aria-label="Status indicator"></span>
|
|
266
|
+
</span>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Display modes:** `strand-badge--dot` (8px circle, no text) | `strand-badge--count` (pill with number)
|
|
270
|
+
**Colors:** `strand-badge--default` | `strand-badge--teal` | `strand-badge--blue` | `strand-badge--amber` | `strand-badge--red`
|
|
271
|
+
**Note:** When wrapping children, omit `strand-badge--inline`. The indicator auto-positions to top-right via `position: absolute`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### Avatar
|
|
276
|
+
|
|
277
|
+
**With image:**
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<div class="strand-avatar strand-avatar--md" role="img" aria-label="Jane Smith">
|
|
281
|
+
<img class="strand-avatar__img" src="photo.jpg" alt="Jane Smith">
|
|
282
|
+
</div>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**With initials (fallback):**
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<div class="strand-avatar strand-avatar--md" role="img" aria-label="JS">
|
|
289
|
+
<span class="strand-avatar__initials" aria-hidden="true">JS</span>
|
|
290
|
+
</div>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Sizes:** `strand-avatar--sm` (32px) | `strand-avatar--md` (40px) | `strand-avatar--lg` (48px) | `strand-avatar--xl` (64px)
|
|
294
|
+
**Note:** Initials are auto-uppercased via CSS. Use 1 or 2 characters.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### Tag
|
|
299
|
+
|
|
300
|
+
```html
|
|
301
|
+
<span class="strand-tag strand-tag--solid strand-tag--blue">
|
|
302
|
+
<span class="strand-tag__text">Design</span>
|
|
303
|
+
</span>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Variants:** `strand-tag--solid` | `strand-tag--outlined`
|
|
307
|
+
**Colors:** `strand-tag--default` | `strand-tag--teal` | `strand-tag--blue` | `strand-tag--amber` | `strand-tag--red`
|
|
308
|
+
**Removable:** Add a remove button after the text span:
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<span class="strand-tag strand-tag--solid strand-tag--blue">
|
|
312
|
+
<span class="strand-tag__text">Design</span>
|
|
313
|
+
<button type="button" class="strand-tag__remove" aria-label="Remove">
|
|
314
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
315
|
+
<path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
316
|
+
</svg>
|
|
317
|
+
</button>
|
|
318
|
+
</span>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Note:** Color classes combine with variant classes (e.g., `strand-tag--solid strand-tag--blue`). The remove icon SVG is required.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### Table
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<div class="strand-table-wrapper">
|
|
329
|
+
<table class="strand-table">
|
|
330
|
+
<thead class="strand-table__head">
|
|
331
|
+
<tr>
|
|
332
|
+
<th class="strand-table__th">Name</th>
|
|
333
|
+
<th class="strand-table__th">Role</th>
|
|
334
|
+
<th class="strand-table__th">Status</th>
|
|
335
|
+
</tr>
|
|
336
|
+
</thead>
|
|
337
|
+
<tbody class="strand-table__body">
|
|
338
|
+
<tr class="strand-table__row">
|
|
339
|
+
<td class="strand-table__td">Alice</td>
|
|
340
|
+
<td class="strand-table__td">Engineer</td>
|
|
341
|
+
<td class="strand-table__td">Active</td>
|
|
342
|
+
</tr>
|
|
343
|
+
<tr class="strand-table__row">
|
|
344
|
+
<td class="strand-table__td">Bob</td>
|
|
345
|
+
<td class="strand-table__td">Designer</td>
|
|
346
|
+
<td class="strand-table__td">Away</td>
|
|
347
|
+
</tr>
|
|
348
|
+
</tbody>
|
|
349
|
+
</table>
|
|
350
|
+
</div>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Sortable column header:**
|
|
354
|
+
|
|
355
|
+
```html
|
|
356
|
+
<th class="strand-table__th">
|
|
357
|
+
<button type="button" class="strand-table__sort-btn" aria-label="Sort by Name">
|
|
358
|
+
Name <span class="strand-table__sort-indicator" aria-hidden="true">↕</span>
|
|
359
|
+
</button>
|
|
360
|
+
</th>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Sort indicators:** `↑` (ascending) | `↓` (descending) | `↕` (unsorted)
|
|
364
|
+
**Note:** `strand-table-wrapper` provides horizontal scroll on overflow. Rows highlight on hover.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
### DataReadout
|
|
369
|
+
|
|
370
|
+
```html
|
|
371
|
+
<!-- Default (md) -->
|
|
372
|
+
<div class="strand-data-readout">
|
|
373
|
+
<span class="strand-data-readout__label">Revenue</span>
|
|
374
|
+
<span class="strand-data-readout__value">$142,800</span>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<!-- Small (compact dashboards, sidebar metrics) -->
|
|
378
|
+
<div class="strand-data-readout strand-data-readout--sm">
|
|
379
|
+
<span class="strand-data-readout__label">Users</span>
|
|
380
|
+
<span class="strand-data-readout__value">12.8K</span>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<!-- Large (hero metrics, feature highlights) -->
|
|
384
|
+
<div class="strand-data-readout strand-data-readout--lg">
|
|
385
|
+
<span class="strand-data-readout__label">Total Revenue</span>
|
|
386
|
+
<span class="strand-data-readout__value">$1.2M</span>
|
|
387
|
+
</div>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Sizes:** `--sm` (text-xl, 25px) | default (text-3xl, 39px) | `--lg` (text-4xl, 49px). Label stays xs across all sizes. Use `--sm` in compact cards and dense data views. Use `--lg` for hero and landing page metrics.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Layout Components
|
|
395
|
+
|
|
396
|
+
### Stack
|
|
397
|
+
|
|
398
|
+
```html
|
|
399
|
+
<div class="strand-stack strand-stack--vertical" style="gap: var(--strand-space-4);">
|
|
400
|
+
<div>Item 1</div>
|
|
401
|
+
<div>Item 2</div>
|
|
402
|
+
<div>Item 3</div>
|
|
403
|
+
</div>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Direction:** `strand-stack--vertical` | `strand-stack--horizontal`
|
|
407
|
+
**Gap (utility classes):** `strand-stack--gap-1` | `strand-stack--gap-2` | `strand-stack--gap-3` | `strand-stack--gap-4` | `strand-stack--gap-5` | `strand-stack--gap-6` | `strand-stack--gap-8`
|
|
408
|
+
**Alignment:** `strand-stack--align-start` | `strand-stack--align-center` | `strand-stack--align-end` (default is stretch)
|
|
409
|
+
**Justification:** `strand-stack--justify-start` | `strand-stack--justify-center` | `strand-stack--justify-end` | `strand-stack--justify-between` | `strand-stack--justify-around`
|
|
410
|
+
**Wrap:** `strand-stack--wrap`
|
|
411
|
+
**Note:** Use either the gap utility class (`strand-stack--gap-4`) or inline `style="gap: var(--strand-space-4)"`. The React component uses inline style; utility classes are available for pure HTML.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### Grid
|
|
416
|
+
|
|
417
|
+
```html
|
|
418
|
+
<div class="strand-grid strand-grid--cols-3 strand-grid--gap-4">
|
|
419
|
+
<div>Cell 1</div>
|
|
420
|
+
<div>Cell 2</div>
|
|
421
|
+
<div>Cell 3</div>
|
|
422
|
+
</div>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Columns (utility classes):** `strand-grid--cols-2` | `strand-grid--cols-3` | `strand-grid--cols-4`
|
|
426
|
+
**Gap (utility classes):** `strand-grid--gap-1` | `strand-grid--gap-2` | `strand-grid--gap-3` | `strand-grid--gap-4` | `strand-grid--gap-5` | `strand-grid--gap-6` | `strand-grid--gap-8`
|
|
427
|
+
**Note:** For column counts beyond 4, use inline style: `style="grid-template-columns: repeat(6, 1fr); gap: var(--strand-space-4);"`. The React component always uses inline style.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### Container
|
|
432
|
+
|
|
433
|
+
```html
|
|
434
|
+
<div class="strand-container strand-container--default">
|
|
435
|
+
Centered content with max-width constraint.
|
|
436
|
+
</div>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Sizes:** `strand-container--narrow` | `strand-container--default` | `strand-container--wide` | `strand-container--full`
|
|
440
|
+
**Note:** Centers content with `margin-inline: auto` and responsive horizontal padding via `clamp(1.5rem, 5vw, 4rem)`.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### Divider
|
|
445
|
+
|
|
446
|
+
**Horizontal (default):**
|
|
447
|
+
|
|
448
|
+
```html
|
|
449
|
+
<hr class="strand-divider strand-divider--horizontal" role="separator" aria-orientation="horizontal">
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Horizontal with label:**
|
|
453
|
+
|
|
454
|
+
```html
|
|
455
|
+
<div class="strand-divider strand-divider--horizontal strand-divider--labeled" role="separator" aria-orientation="horizontal">
|
|
456
|
+
<span class="strand-divider__line"></span>
|
|
457
|
+
<span class="strand-divider__label">Or</span>
|
|
458
|
+
<span class="strand-divider__line"></span>
|
|
459
|
+
</div>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**Vertical:**
|
|
463
|
+
|
|
464
|
+
```html
|
|
465
|
+
<div class="strand-divider strand-divider--vertical" role="separator" aria-orientation="vertical"></div>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Note:** Plain horizontal uses `<hr>`. Labeled horizontal uses `<div>` with two `__line` spans flanking the label. Vertical dividers use `align-self: stretch` to fill the parent height.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### Section
|
|
473
|
+
|
|
474
|
+
```html
|
|
475
|
+
<section class="strand-section strand-section--standard strand-section--bg-primary">
|
|
476
|
+
<div class="strand-container strand-container--default">
|
|
477
|
+
Section content here.
|
|
478
|
+
</div>
|
|
479
|
+
</section>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Variants:** `strand-section--standard` | `strand-section--hero`
|
|
483
|
+
**Backgrounds:** `strand-section--bg-primary` | `strand-section--bg-elevated` | `strand-section--bg-recessed`
|
|
484
|
+
**Note:** `standard` uses responsive vertical padding `clamp(4rem, 8vw, 8rem)`. `hero` uses `clamp(6rem, 12vw, 12rem)`. Pair with `strand-container` inside for horizontal constraints.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Navigation Components
|
|
489
|
+
|
|
490
|
+
### Link
|
|
491
|
+
|
|
492
|
+
```html
|
|
493
|
+
<a href="/about" class="strand-link">About us</a>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**External:** Add `target="_blank"` and `rel="noopener noreferrer"`.
|
|
497
|
+
**Note:** The underline animates on hover from 0% to 100% width via `background-size`. No variant or modifier classes.
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
### Tabs
|
|
502
|
+
|
|
503
|
+
```html
|
|
504
|
+
<div class="strand-tabs">
|
|
505
|
+
<div role="tablist">
|
|
506
|
+
<button id="tab-overview" role="tab" type="button" class="strand-tabs__tab strand-tabs__tab--active"
|
|
507
|
+
aria-selected="true" aria-controls="panel-overview" tabindex="0">Overview</button>
|
|
508
|
+
<button id="tab-specs" role="tab" type="button" class="strand-tabs__tab"
|
|
509
|
+
aria-selected="false" aria-controls="panel-specs" tabindex="-1">Specs</button>
|
|
510
|
+
<button id="tab-reviews" role="tab" type="button" class="strand-tabs__tab"
|
|
511
|
+
aria-selected="false" aria-controls="panel-reviews" tabindex="-1">Reviews</button>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" tabindex="0">
|
|
515
|
+
Overview content here.
|
|
516
|
+
</div>
|
|
517
|
+
<div id="panel-specs" role="tabpanel" aria-labelledby="tab-specs" tabindex="0" hidden>
|
|
518
|
+
Specs content here.
|
|
519
|
+
</div>
|
|
520
|
+
<div id="panel-reviews" role="tabpanel" aria-labelledby="tab-reviews" tabindex="0" hidden>
|
|
521
|
+
Reviews content here.
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Active tab:** Add `strand-tabs__tab--active` + `aria-selected="true"` + `tabindex="0"`.
|
|
527
|
+
**Inactive tabs:** Set `aria-selected="false"` + `tabindex="-1"`. Add `hidden` to their panels.
|
|
528
|
+
**Note:** The tablist has a bottom border. Active tab shows a blue bottom border. Wire `aria-controls` / `aria-labelledby` IDs correctly. Arrow key navigation requires JavaScript.
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
### Breadcrumb
|
|
533
|
+
|
|
534
|
+
```html
|
|
535
|
+
<nav aria-label="Breadcrumb" class="strand-breadcrumb">
|
|
536
|
+
<ol class="strand-breadcrumb__list">
|
|
537
|
+
<li class="strand-breadcrumb__item">
|
|
538
|
+
<a href="/" class="strand-breadcrumb__link">Home</a>
|
|
539
|
+
</li>
|
|
540
|
+
<li class="strand-breadcrumb__item">
|
|
541
|
+
<span class="strand-breadcrumb__separator" aria-hidden="true">/</span>
|
|
542
|
+
<a href="/products" class="strand-breadcrumb__link">Products</a>
|
|
543
|
+
</li>
|
|
544
|
+
<li class="strand-breadcrumb__item">
|
|
545
|
+
<span class="strand-breadcrumb__separator" aria-hidden="true">/</span>
|
|
546
|
+
<span class="strand-breadcrumb__current" aria-current="page">Widget</span>
|
|
547
|
+
</li>
|
|
548
|
+
</ol>
|
|
549
|
+
</nav>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Note:** The last item uses `strand-breadcrumb__current` with `aria-current="page"` instead of a link. Separators use `aria-hidden="true"`. The first item has no separator.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
### Nav
|
|
557
|
+
|
|
558
|
+
```html
|
|
559
|
+
<nav class="strand-nav" aria-label="Main navigation">
|
|
560
|
+
<div class="strand-nav__inner">
|
|
561
|
+
<div class="strand-nav__logo">
|
|
562
|
+
<!-- Logo element (image, SVG, or text) -->
|
|
563
|
+
<strong>Brand</strong>
|
|
564
|
+
</div>
|
|
565
|
+
<div class="strand-nav__items">
|
|
566
|
+
<a href="/" class="strand-nav__link strand-nav__link--active" aria-current="page">Home</a>
|
|
567
|
+
<a href="/about" class="strand-nav__link">About</a>
|
|
568
|
+
<a href="/contact" class="strand-nav__link">Contact</a>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="strand-nav__actions">
|
|
571
|
+
<!-- Action buttons or elements -->
|
|
572
|
+
<button class="strand-btn strand-btn--primary strand-btn--sm" type="button">
|
|
573
|
+
<span class="strand-btn__content">Sign in</span>
|
|
574
|
+
</button>
|
|
575
|
+
</div>
|
|
576
|
+
<button type="button" class="strand-nav__hamburger" aria-expanded="false" aria-label="Menu">
|
|
577
|
+
<span class="strand-nav__hamburger-icon" aria-hidden="true"></span>
|
|
578
|
+
</button>
|
|
579
|
+
</div>
|
|
580
|
+
<!-- Mobile menu (hidden by default, shown via JS toggling display) -->
|
|
581
|
+
<div class="strand-nav__mobile-menu" style="display:none;">
|
|
582
|
+
<a href="/" class="strand-nav__mobile-link strand-nav__mobile-link--active" aria-current="page">Home</a>
|
|
583
|
+
<a href="/about" class="strand-nav__mobile-link">About</a>
|
|
584
|
+
<a href="/contact" class="strand-nav__mobile-link">Contact</a>
|
|
585
|
+
</div>
|
|
586
|
+
</nav>
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Active link:** `strand-nav__link--active` (desktop) | `strand-nav__mobile-link--active` (mobile)
|
|
590
|
+
**Note:** The nav bar is 64px tall. Desktop items and actions hide below 768px; the hamburger and mobile menu show instead. The hamburger icon is a CSS-only three-line icon (middle line + `::before`/`::after` pseudo-elements). Toggle mobile menu visibility with JavaScript.
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Feedback Components
|
|
595
|
+
|
|
596
|
+
### Toast
|
|
597
|
+
|
|
598
|
+
```html
|
|
599
|
+
<div class="strand-toast strand-toast--info" role="status" aria-live="polite">
|
|
600
|
+
<span class="strand-toast__message">Changes saved successfully.</span>
|
|
601
|
+
<button type="button" class="strand-toast__dismiss" aria-label="Dismiss">×</button>
|
|
602
|
+
</div>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Statuses:** `strand-toast--info` | `strand-toast--success` | `strand-toast--warning` | `strand-toast--error`
|
|
606
|
+
**Toast container (for stacking multiple toasts):**
|
|
607
|
+
|
|
608
|
+
```html
|
|
609
|
+
<div class="strand-toast__container">
|
|
610
|
+
<!-- Toasts stack here, newest at bottom -->
|
|
611
|
+
</div>
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Note:** Container is fixed to bottom-right (`position: fixed`). Use `aria-live="assertive"` for `warning` and `error` statuses. Each status sets a colored left border accent. Dismiss button renders `×`.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
### Alert
|
|
619
|
+
|
|
620
|
+
```html
|
|
621
|
+
<div class="strand-alert strand-alert--info" role="status">
|
|
622
|
+
<div class="strand-alert__content">This is an informational message.</div>
|
|
623
|
+
</div>
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Statuses:** `strand-alert--info` | `strand-alert--success` | `strand-alert--warning` | `strand-alert--error`
|
|
627
|
+
**Dismissible:** Add a dismiss button after content:
|
|
628
|
+
|
|
629
|
+
```html
|
|
630
|
+
<div class="strand-alert strand-alert--warning" role="alert">
|
|
631
|
+
<div class="strand-alert__content">Disk space running low.</div>
|
|
632
|
+
<button type="button" class="strand-alert__dismiss" aria-label="Dismiss">×</button>
|
|
633
|
+
</div>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Note:** Use `role="alert"` for `warning` and `error`, `role="status"` for `info` and `success`. Each status has a colored left border and tinted background.
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
### Dialog
|
|
641
|
+
|
|
642
|
+
```html
|
|
643
|
+
<div class="strand-dialog__backdrop">
|
|
644
|
+
<div class="strand-dialog__panel" role="dialog" aria-modal="true" aria-labelledby="dialog-title" tabindex="-1">
|
|
645
|
+
<div class="strand-dialog__header">
|
|
646
|
+
<h2 id="dialog-title" class="strand-dialog__title">Confirm action</h2>
|
|
647
|
+
</div>
|
|
648
|
+
<button type="button" class="strand-dialog__close" aria-label="Close">×</button>
|
|
649
|
+
<div class="strand-dialog__body">
|
|
650
|
+
Are you sure you want to proceed?
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Note:** The backdrop covers the viewport with a semi-transparent overlay (`position: fixed; inset: 0`). The panel is centered, max-width 560px, with elevation shadow. Focus trap and scroll lock require JavaScript. Omit the `strand-dialog__header` div if no title is needed.
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
### Tooltip
|
|
661
|
+
|
|
662
|
+
```html
|
|
663
|
+
<span class="strand-tooltip__wrapper" aria-describedby="tip-1">
|
|
664
|
+
Hover me
|
|
665
|
+
<span id="tip-1" class="strand-tooltip strand-tooltip--top strand-tooltip--visible" role="tooltip">
|
|
666
|
+
Helpful tip
|
|
667
|
+
</span>
|
|
668
|
+
</span>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Positions:** `strand-tooltip--top` | `strand-tooltip--right` | `strand-tooltip--bottom` | `strand-tooltip--left`
|
|
672
|
+
**Visibility:** Add `strand-tooltip--visible` to show. Without it, tooltip has `opacity: 0`.
|
|
673
|
+
**Note:** The wrapper needs `position: relative` (provided by `strand-tooltip__wrapper`). For static display, add `strand-tooltip--visible` directly. For interactive use, toggle that class via JavaScript on hover/focus. Wire `aria-describedby` on the wrapper to the tooltip `id`.
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
### Progress
|
|
678
|
+
|
|
679
|
+
**Determinate bar:**
|
|
680
|
+
|
|
681
|
+
```html
|
|
682
|
+
<div class="strand-progress strand-progress--bar strand-progress--md" role="progressbar"
|
|
683
|
+
aria-valuemin="0" aria-valuemax="100" aria-valuenow="65">
|
|
684
|
+
<div class="strand-progress__fill" style="width: 65%;"></div>
|
|
685
|
+
</div>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Indeterminate bar:**
|
|
689
|
+
|
|
690
|
+
```html
|
|
691
|
+
<div class="strand-progress strand-progress--bar strand-progress--md strand-progress--indeterminate"
|
|
692
|
+
role="progressbar" aria-valuemin="0" aria-valuemax="100">
|
|
693
|
+
<div class="strand-progress__fill"></div>
|
|
694
|
+
</div>
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
**Determinate ring:**
|
|
698
|
+
|
|
699
|
+
```html
|
|
700
|
+
<div class="strand-progress strand-progress--ring strand-progress--md" role="progressbar"
|
|
701
|
+
aria-valuemin="0" aria-valuemax="100" aria-valuenow="65">
|
|
702
|
+
<svg width="40" height="40" viewBox="0 0 40 40" class="strand-progress__ring">
|
|
703
|
+
<circle cx="20" cy="20" r="18.5" fill="none" stroke-width="3" class="strand-progress__track"/>
|
|
704
|
+
<circle cx="20" cy="20" r="18.5" fill="none" stroke-width="3"
|
|
705
|
+
stroke-dasharray="116.24" stroke-dashoffset="40.68"
|
|
706
|
+
stroke-linecap="round" class="strand-progress__fill"
|
|
707
|
+
transform="rotate(-90 20 20)"/>
|
|
708
|
+
</svg>
|
|
709
|
+
</div>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
**Sizes (bar):** `strand-progress--sm` (4px) | `strand-progress--md` (8px) | `strand-progress--lg` (12px)
|
|
713
|
+
**Sizes (ring):** `strand-progress--sm` (24px) | `strand-progress--md` (40px) | `strand-progress--lg` (56px)
|
|
714
|
+
**Note:** For indeterminate bars, omit `aria-valuenow`; the fill animates via CSS. For rings, compute `stroke-dasharray` as `2 * PI * radius` and `stroke-dashoffset` as `dasharray * (1 - value/100)`. Ring dimensions by size: sm=24 (r=10.5), md=40 (r=18.5), lg=56 (r=26.5). Stroke width is always 3.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
### Spinner
|
|
719
|
+
|
|
720
|
+
```html
|
|
721
|
+
<span class="strand-spinner strand-spinner--md" role="status">
|
|
722
|
+
<span class="strand-spinner__ring" aria-hidden="true"></span>
|
|
723
|
+
<span class="strand-spinner__sr-only">Loading</span>
|
|
724
|
+
</span>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Sizes:** `strand-spinner--sm` (16px) | `strand-spinner--md` (20px) | `strand-spinner--lg` (32px)
|
|
728
|
+
**Note:** The ring animates a spinning border. `strand-spinner__sr-only` provides accessible text (visually hidden, read by screen readers). Always include it.
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
### Skeleton
|
|
733
|
+
|
|
734
|
+
```html
|
|
735
|
+
<div class="strand-skeleton strand-skeleton--text strand-skeleton--shimmer" aria-hidden="true"
|
|
736
|
+
style="width: 100%; height: 1em;"></div>
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**Variants:** `strand-skeleton--text` (4px border-radius, 1em default height) | `strand-skeleton--rectangle` (md border-radius) | `strand-skeleton--circle` (full border-radius)
|
|
740
|
+
**Shimmer:** Always add `strand-skeleton--shimmer` for the animated gradient effect.
|
|
741
|
+
**Sizing:** Set `width` and `height` via inline `style`. For circles, set equal width and height.
|
|
742
|
+
|
|
743
|
+
```html
|
|
744
|
+
<!-- Rectangle -->
|
|
745
|
+
<div class="strand-skeleton strand-skeleton--rectangle strand-skeleton--shimmer" aria-hidden="true"
|
|
746
|
+
style="width: 200px; height: 120px;"></div>
|
|
747
|
+
|
|
748
|
+
<!-- Circle -->
|
|
749
|
+
<div class="strand-skeleton strand-skeleton--circle strand-skeleton--shimmer" aria-hidden="true"
|
|
750
|
+
style="width: 48px; height: 48px;"></div>
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
**Note:** Always include `aria-hidden="true"`. Skeletons are placeholder visuals, not interactive.
|
|
@@ -5,6 +5,8 @@ export interface DataReadoutProps extends Omit<JSX.HTMLAttributes<HTMLDivElement
|
|
|
5
5
|
label: string;
|
|
6
6
|
/** The large displayed value */
|
|
7
7
|
value: string | number;
|
|
8
|
+
/** Size variant: sm (compact), md (default), lg (hero) */
|
|
9
|
+
size?: "sm" | "md" | "lg";
|
|
8
10
|
}
|
|
9
11
|
export declare const DataReadout: import("preact").FunctionalComponent<import("preact/compat").PropsWithoutRef<DataReadoutProps> & {
|
|
10
12
|
ref?: import("preact").Ref<HTMLDivElement> | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataReadout.d.ts","sourceRoot":"","sources":["../../../src/components/DataReadout/DataReadout.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGlC,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzD,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"DataReadout.d.ts","sourceRoot":"","sources":["../../../src/components/DataReadout/DataReadout.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGlC,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzD,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,WAAW;;EAiBvB,CAAC"}
|
package/dist/css/strand-ui.css
CHANGED
|
@@ -693,6 +693,15 @@
|
|
|
693
693
|
font-variant-numeric: tabular-nums;
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
+
/* ── Size variants ── */
|
|
697
|
+
.strand-data-readout--sm .strand-data-readout__value {
|
|
698
|
+
font-size: var(--strand-text-xl);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.strand-data-readout--lg .strand-data-readout__value {
|
|
702
|
+
font-size: var(--strand-text-4xl);
|
|
703
|
+
}
|
|
704
|
+
|
|
696
705
|
|
|
697
706
|
/* Dialog */
|
|
698
707
|
/*! Strand UI | MIT License | dillingerstaffing.com */
|
package/dist/index.js
CHANGED
|
@@ -499,7 +499,7 @@ const V = p(
|
|
|
499
499
|
}
|
|
500
500
|
);
|
|
501
501
|
V.displayName = "Avatar";
|
|
502
|
-
const
|
|
502
|
+
const O = p(
|
|
503
503
|
({
|
|
504
504
|
variant: a = "solid",
|
|
505
505
|
status: t = "default",
|
|
@@ -548,8 +548,8 @@ const z = p(
|
|
|
548
548
|
] });
|
|
549
549
|
}
|
|
550
550
|
);
|
|
551
|
-
|
|
552
|
-
const
|
|
551
|
+
O.displayName = "Tag";
|
|
552
|
+
const z = p(
|
|
553
553
|
({ columns: a, data: t, onSort: e, className: n = "", ...i }, o) => {
|
|
554
554
|
const [r, l] = B(null), [c, u] = B("asc"), d = b(
|
|
555
555
|
(m) => {
|
|
@@ -591,11 +591,15 @@ const O = p(
|
|
|
591
591
|
] }) });
|
|
592
592
|
}
|
|
593
593
|
);
|
|
594
|
-
|
|
594
|
+
z.displayName = "Table";
|
|
595
595
|
const W = p(
|
|
596
|
-
({ label: a, value: t,
|
|
597
|
-
const
|
|
598
|
-
|
|
596
|
+
({ label: a, value: t, size: e, className: n = "", ...i }, o) => {
|
|
597
|
+
const r = [
|
|
598
|
+
"strand-data-readout",
|
|
599
|
+
e && e !== "md" ? `strand-data-readout--${e}` : "",
|
|
600
|
+
n
|
|
601
|
+
].filter(Boolean).join(" ");
|
|
602
|
+
return /* @__PURE__ */ _("div", { ref: o, className: r, ...i, children: [
|
|
599
603
|
/* @__PURE__ */ s("span", { className: "strand-data-readout__label", children: a }),
|
|
600
604
|
/* @__PURE__ */ s("span", { className: "strand-data-readout__value", children: t })
|
|
601
605
|
] });
|
|
@@ -1353,9 +1357,9 @@ export {
|
|
|
1353
1357
|
pa as Spinner,
|
|
1354
1358
|
Z as Stack,
|
|
1355
1359
|
F as Switch,
|
|
1356
|
-
|
|
1360
|
+
z as Table,
|
|
1357
1361
|
ea as Tabs,
|
|
1358
|
-
|
|
1362
|
+
O as Tag,
|
|
1359
1363
|
A as Textarea,
|
|
1360
1364
|
ia as Toast,
|
|
1361
1365
|
ra as ToastProvider,
|
package/package.json
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dillingerstaffing/strand-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Strand UI - Preact/React component library built on the Strand Design Language",
|
|
5
5
|
"author": "Dillinger Staffing <engineering@dillingerstaffing.com> (https://dillingerstaffing.com)",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"design-system",
|
|
9
|
-
"ui-components",
|
|
10
|
-
"preact",
|
|
11
|
-
"react",
|
|
12
|
-
"css-custom-properties",
|
|
13
|
-
"design-tokens",
|
|
14
|
-
"accessibility",
|
|
15
|
-
"wcag",
|
|
16
|
-
"aria",
|
|
17
|
-
"component-library"
|
|
18
|
-
],
|
|
7
|
+
"keywords": ["design-system", "ui-components", "preact", "react", "css-custom-properties", "design-tokens", "accessibility", "wcag", "aria", "component-library"],
|
|
19
8
|
"homepage": "https://dillingerstaffing.com/labs/strand",
|
|
20
9
|
"repository": {
|
|
21
10
|
"type": "git",
|
|
@@ -39,11 +28,18 @@
|
|
|
39
28
|
"style": "./dist/css/strand-ui.css",
|
|
40
29
|
"files": [
|
|
41
30
|
"dist/",
|
|
42
|
-
"src/"
|
|
31
|
+
"src/",
|
|
32
|
+
"HTML_REFERENCE.md"
|
|
43
33
|
],
|
|
44
34
|
"sideEffects": [
|
|
45
35
|
"dist/css/*.css"
|
|
46
36
|
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "vite build && tsc --emitDeclarationOnly && cp ../../HTML_REFERENCE.md ./HTML_REFERENCE.md",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"test:coverage": "vitest run --coverage"
|
|
42
|
+
},
|
|
47
43
|
"peerDependencies": {
|
|
48
44
|
"preact": "^10.0.0"
|
|
49
45
|
},
|
|
@@ -53,7 +49,7 @@
|
|
|
53
49
|
}
|
|
54
50
|
},
|
|
55
51
|
"dependencies": {
|
|
56
|
-
"@dillingerstaffing/strand": "0.2.
|
|
52
|
+
"@dillingerstaffing/strand": "^0.2.1"
|
|
57
53
|
},
|
|
58
54
|
"devDependencies": {
|
|
59
55
|
"@testing-library/preact": "^3.2.0",
|
|
@@ -62,11 +58,5 @@
|
|
|
62
58
|
"preact": "^10.25.0",
|
|
63
59
|
"vite": "^6.0.0",
|
|
64
60
|
"vitest": "^3.0.0"
|
|
65
|
-
},
|
|
66
|
-
"scripts": {
|
|
67
|
-
"build": "vite build && tsc --emitDeclarationOnly",
|
|
68
|
-
"test": "vitest run",
|
|
69
|
-
"test:watch": "vitest",
|
|
70
|
-
"test:coverage": "vitest run --coverage"
|
|
71
61
|
}
|
|
72
|
-
}
|
|
62
|
+
}
|
|
@@ -28,3 +28,12 @@
|
|
|
28
28
|
line-height: var(--strand-leading-tight);
|
|
29
29
|
font-variant-numeric: tabular-nums;
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
/* ── Size variants ── */
|
|
33
|
+
.strand-data-readout--sm .strand-data-readout__value {
|
|
34
|
+
font-size: var(--strand-text-xl);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.strand-data-readout--lg .strand-data-readout__value {
|
|
38
|
+
font-size: var(--strand-text-4xl);
|
|
39
|
+
}
|
|
@@ -93,6 +93,42 @@ describe("DataReadout", () => {
|
|
|
93
93
|
expect(readout?.className).toContain("custom");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
// ── Size variants ──
|
|
97
|
+
|
|
98
|
+
it("applies sm size modifier class", () => {
|
|
99
|
+
const { container } = render(
|
|
100
|
+
<DataReadout label="Users" value="12.8K" size="sm" />,
|
|
101
|
+
);
|
|
102
|
+
const readout = container.querySelector(".strand-data-readout");
|
|
103
|
+
expect(readout?.className).toContain("strand-data-readout--sm");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("applies lg size modifier class", () => {
|
|
107
|
+
const { container } = render(
|
|
108
|
+
<DataReadout label="Revenue" value="$1.2M" size="lg" />,
|
|
109
|
+
);
|
|
110
|
+
const readout = container.querySelector(".strand-data-readout");
|
|
111
|
+
expect(readout?.className).toContain("strand-data-readout--lg");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not apply size modifier for md (default)", () => {
|
|
115
|
+
const { container } = render(
|
|
116
|
+
<DataReadout label="Metric" value="100" size="md" />,
|
|
117
|
+
);
|
|
118
|
+
const readout = container.querySelector(".strand-data-readout");
|
|
119
|
+
expect(readout?.className).not.toContain("strand-data-readout--md");
|
|
120
|
+
expect(readout?.className).not.toContain("strand-data-readout--sm");
|
|
121
|
+
expect(readout?.className).not.toContain("strand-data-readout--lg");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not apply size modifier when size is omitted", () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<DataReadout label="Metric" value="100" />,
|
|
127
|
+
);
|
|
128
|
+
const readout = container.querySelector(".strand-data-readout");
|
|
129
|
+
expect(readout?.className).toBe("strand-data-readout");
|
|
130
|
+
});
|
|
131
|
+
|
|
96
132
|
// ── Forwarded props ──
|
|
97
133
|
|
|
98
134
|
it("forwards additional props", () => {
|
|
@@ -9,11 +9,17 @@ export interface DataReadoutProps
|
|
|
9
9
|
label: string;
|
|
10
10
|
/** The large displayed value */
|
|
11
11
|
value: string | number;
|
|
12
|
+
/** Size variant: sm (compact), md (default), lg (hero) */
|
|
13
|
+
size?: "sm" | "md" | "lg";
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const DataReadout = forwardRef<HTMLDivElement, DataReadoutProps>(
|
|
15
|
-
({ label, value, className = "", ...rest }, ref) => {
|
|
16
|
-
const classes = [
|
|
17
|
+
({ label, value, size, className = "", ...rest }, ref) => {
|
|
18
|
+
const classes = [
|
|
19
|
+
"strand-data-readout",
|
|
20
|
+
size && size !== "md" ? `strand-data-readout--${size}` : "",
|
|
21
|
+
className,
|
|
22
|
+
]
|
|
17
23
|
.filter(Boolean)
|
|
18
24
|
.join(" ");
|
|
19
25
|
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Dillinger Staffing (https://dillingerstaffing.com)
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|