design26 0.0.0-stage → 0.1.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/AGENTS.md +57 -0
- package/COMPONENTS.md +325 -0
- package/README.md +99 -2
- package/dist/index.d.ts +15 -0
- package/dist/index.js +6 -0
- package/dist/scientific/parameter-control.d.ts +15 -0
- package/dist/scientific/parameter-control.js +10 -0
- package/dist/scientific/patterns.d.ts +37 -0
- package/dist/scientific/patterns.js +10 -0
- package/dist/scientific.d.ts +4 -0
- package/dist/scientific.js +2 -0
- package/dist/styles.css +185 -0
- package/dist/tokens.css +165 -0
- package/dist/ui/controls.d.ts +15 -0
- package/dist/ui/controls.js +16 -0
- package/dist/ui/dialog.d.ts +8 -0
- package/dist/ui/dialog.js +13 -0
- package/dist/ui/primitives.d.ts +59 -0
- package/dist/ui/primitives.js +13 -0
- package/dist/ui/tabs.d.ts +15 -0
- package/dist/ui/tabs.js +32 -0
- package/dist/ui/technical-grid.d.ts +7 -0
- package/dist/ui/technical-grid.js +79 -0
- package/package.json +38 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# design26 — the personal visual language
|
|
2
|
+
|
|
3
|
+
This repository captures the existing UVID demonstration's visual identity for future projects. Read `docs/DESIGN-AUDIT.md` and `docs/ARCHITECTURE.md` before changing the system. The reference `../uvid-demonstration` is read-only: do not edit, build in, install into, reconfigure or run a dev server from that directory. Do not import its models, datasets, geometry, branding or assets.
|
|
4
|
+
|
|
5
|
+
## Build with existing parts first
|
|
6
|
+
|
|
7
|
+
Import public APIs from `design26` and `design26/scientific`, and import `design26/styles.css` once at the host application's root. Reuse components and semantic tokens before introducing styling. Never import package source paths from applications. Wrap the page in `PageFrame`, or use `.d26-theme` / `[data-design26-theme="light"]` for an embedded region. Styles must remain independent of Tailwind and Next-specific components.
|
|
8
|
+
|
|
9
|
+
Generic UI: TechnicalGrid, PageFrame, Navigation, Panel, Eyebrow, SectionHeading, Button, TextLink, IconButton, StepNavigation, Tabs, Slider, Select, Checkbox, Disclosure, Dialog, StatusNote.
|
|
10
|
+
|
|
11
|
+
Scientific presentation: ParameterControl, DataPanel, Metric, FigureContainer, PlotContainer, PlotLegend. These present host-supplied data and artwork; they do not calculate models or fetch scientific datasets. Inspect exported prop types and `app/catalogue.ts` before extending an API.
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
|
|
15
|
+
- Electric blue `#075afa` frames cool paper `#f7f9fc`. Navy `#072754` is the primary ink; muted text is `#61718a`. Amber `#ed9019` is focus, selected data and meaningful emphasis.
|
|
16
|
+
- Use semantic variables: `--color-background`, `--color-surface`, `--color-surface-control`, `--color-text-primary`, `--color-text-secondary`, `--color-accent`, `--color-border`, `--color-highlight`. Do not add arbitrary hex values to component code.
|
|
17
|
+
- Body and headings are Arial, Helvetica, sans-serif. The original application loads Geist Sans but renders its body in Arial. Preserve that fact. Technical labels use Geist Mono via the host's `--font-geist-mono`, with a system monospace fallback. The package does not fetch fonts.
|
|
18
|
+
- Large medium-weight headings use tight tracking (`-.065em`); technical labels are small, uppercase, monospaced and widely tracked. Use SectionHeading size independently of semantic heading level. One main h1 per page. Use an em child for a single blue accent phrase.
|
|
19
|
+
- Use thin 1px rules, flat surfaces and quiet captions. Ordinary panels have no shadows. The dialog is the elevated exception. Avoid making every section a rounded card.
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
The frame max-width is1920px. Gutters are32px normally,44px at ≥1500px,24px at ≤1100px and14px at ≤760px. Native breakpoints are760,1100 and1500px. Do not replace them with Tailwind defaults.
|
|
24
|
+
|
|
25
|
+
Use asymmetric text/figure composition (47/53 hero,1.15/.85 scientific workspace), equal paired plots, generous section spacing and compact control spacing. `--space-panel-inline`, `--space-section`, `--space-grid`, `--space-control` and the existing recipes are the starting point. The source uses optical adjustments, not a uniform eight-pixel grid. Preserve measured recipes rather than rounding every value.
|
|
26
|
+
|
|
27
|
+
Use min-width:0, minmax(0,1fr), responsive SVG viewBox and intrinsic media ratios. Stack scientific figure/inspector and paired plots at mobile. Keep labels, units, controls and readings close together. Caption text explains what to notice. Do not hide essential information on narrow screens.
|
|
28
|
+
|
|
29
|
+
## Interaction and accessibility
|
|
30
|
+
|
|
31
|
+
Use real buttons for actions, anchors for navigation, labelled native inputs and native details/dialog semantics. IconButton requires aria-label. Distinguish disabled from busy; do not use a waiting cursor on all disabled controls. All interactive elements, including summary/select, use visible amber focus. Do not remove outlines without an equivalent indicator.
|
|
32
|
+
|
|
33
|
+
Tabs implement roving focus, arrows/Home/End, disabled-item skipping and associated panels; do not build a second tab implementation. StepNavigation is numbered section navigation, not an ARIA tablist. Do not attach page-wide arrow shortcuts that steal keys from inputs or nested controls.
|
|
34
|
+
|
|
35
|
+
Use the observed200ms interaction timing,420ms/12px entrance and restrained status pulse. Respect prefers-reduced-motion for CSS and any application animation. Pause continuous work offscreen or in background tabs, and clean up listeners/timers. No Anime.js dependency is needed for the generic library. TechnicalGrid is an optional evidenced figure backdrop; pair it with a pause control. Avoid unrelated decorative autoplay, bouncing cards, large hover shifts and unrelated gradients.
|
|
36
|
+
|
|
37
|
+
## Extending the system
|
|
38
|
+
|
|
39
|
+
1. Find the closest existing component, token and reference pattern.
|
|
40
|
+
2. Compose before extending. Extend a typed variant only when a repeated need is real.
|
|
41
|
+
3. Add a new component only for a distinct reusable responsibility, with evidence or a clearly documented gap. Do not add a Tooltip, NumberInput or Toggle simply to fill a catalogue.
|
|
42
|
+
4. Keep generic UI separate from scientific presentation. Keep business logic in the consumer.
|
|
43
|
+
5. Add semantic tokens for genuine new roles. Preserve exact existing values; document canonicalisation in the audit. Theme overrides belong on the existing theme boundary. Do not invent a dark theme implicitly.
|
|
44
|
+
6. Document purpose, props, variants, a real usage example, accessibility and recommended composition in the catalogue/package guide.
|
|
45
|
+
7. Run library build, TypeScript, Next build, packed-consumer validation and relevant interaction/viewport checks. Verify that the reference fingerprints remain unchanged. Report any verification limits honestly.
|
|
46
|
+
|
|
47
|
+
Avoid application-specific names in public props, copied UVID calculations/assets, global CSS resets, a mandatory icon/chart framework, runtime CSS-in-JS, broad client boundaries, deep private imports, unexplained colours, exaggerated shadows, pill-shaped everything and inaccessible tiny essential text. Compact metadata is part of the identity; essential instructions and interactive content still need to be readable and usable.
|
|
48
|
+
|
|
49
|
+
## Commands
|
|
50
|
+
|
|
51
|
+
- `pnpm dev`: build the package, then start the showcase.
|
|
52
|
+
- `pnpm build`: build package and production showcase.
|
|
53
|
+
- `pnpm typecheck`: package and application types.
|
|
54
|
+
- `pnpm verify:package`: pack/install in an isolated consumer, typecheck and server-render it without Tailwind/Next.
|
|
55
|
+
- `pnpm audit:reference`: verify the read-only reference snapshot.
|
|
56
|
+
|
|
57
|
+
When editing package source during an active showcase session, rebuild the package so public imports reflect the change. Do not work around package boundaries with source aliases. Use `docs/VALIDATION.md` for the latest evidence and known limits.
|
package/COMPONENTS.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# design26 component catalogue
|
|
2
|
+
|
|
3
|
+
Import generic components from `design26` and scientific components from `design26/scientific`. Import `design26/styles.css` once and use a theme wrapper. All components accept their documented native HTML props and React 19 refs.
|
|
4
|
+
|
|
5
|
+
## TechnicalGrid
|
|
6
|
+
|
|
7
|
+
A gently drifting 38px technical mesh, refined in the reference during this extraction.
|
|
8
|
+
|
|
9
|
+
- **Entry:** design26
|
|
10
|
+
- **Props:** active?: boolean; interactive?: boolean; native div props
|
|
11
|
+
- **Variants:** Animated / paused; pointer lens optional
|
|
12
|
+
- **Accessibility:** Decorative and hidden from assistive technology. Reduced motion renders a static grid. Animation pauses offscreen and in hidden tabs; pointer response requires a fine hover pointer.
|
|
13
|
+
- **Recommended use:** An optional figure backdrop, with positioned content above it. Keep it away from dense reading and provide a pause control where appropriate.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<div style={{ position: "relative" }}><TechnicalGrid /><Content /></div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## PageFrame
|
|
20
|
+
|
|
21
|
+
The blue outer frame and opt-in theme boundary.
|
|
22
|
+
|
|
23
|
+
- **Entry:** design26
|
|
24
|
+
- **Props:** Native div props, children, className, ref
|
|
25
|
+
- **Variants:** One faithful light theme
|
|
26
|
+
- **Accessibility:** Use a main landmark inside the frame and a skip link before navigation.
|
|
27
|
+
- **Recommended use:** One outer frame per application. Use d26-theme alone for embedded UI.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<PageFrame><Navigation brand="Project" /><Panel>Content</Panel></PageFrame>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Panel
|
|
34
|
+
|
|
35
|
+
Paper sheets, parameter surfaces and control decks.
|
|
36
|
+
|
|
37
|
+
- **Entry:** design26
|
|
38
|
+
- **Props:** variant?: "sheet" | "control" | "toolbar"; padding?: "content" | "none"; native div props
|
|
39
|
+
- **Variants:** Sheet / control / toolbar
|
|
40
|
+
- **Accessibility:** A visual wrapper has no automatic landmark. Label important sections with semantic HTML.
|
|
41
|
+
- **Recommended use:** Use fine rules to separate content; avoid nesting sheet after sheet.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Panel variant="control"><ParameterControl {...parameter} /></Panel>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Eyebrow
|
|
48
|
+
|
|
49
|
+
Technical context above a heading.
|
|
50
|
+
|
|
51
|
+
- **Entry:** design26
|
|
52
|
+
- **Props:** dot?: boolean; compact?: boolean; native div props
|
|
53
|
+
- **Variants:** Default / compact / status dot
|
|
54
|
+
- **Accessibility:** The dot is decorative. Put the actual status or category in text.
|
|
55
|
+
- **Recommended use:** Short uppercase labels. Do not use letter-spaced metadata for paragraphs.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Eyebrow dot>RESEARCH / 01</Eyebrow>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## SectionHeading
|
|
62
|
+
|
|
63
|
+
The large, tightly tracked heading hierarchy.
|
|
64
|
+
|
|
65
|
+
- **Entry:** design26
|
|
66
|
+
- **Props:** as?: "h1" | "h2" | "h3" | "h4"; size?: "display" | "chapter" | "inspector" | "figure"; native heading props
|
|
67
|
+
- **Variants:** Display / chapter / inspector / figure
|
|
68
|
+
- **Accessibility:** Choose heading level by document structure; size is independent of semantics.
|
|
69
|
+
- **Recommended use:** One clear headline, with a single accent phrase when useful.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<SectionHeading as="h1" size="display">A different <em>perspective.</em></SectionHeading>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Button
|
|
76
|
+
|
|
77
|
+
A primary action or a quiet supporting action.
|
|
78
|
+
|
|
79
|
+
- **Entry:** design26
|
|
80
|
+
- **Props:** variant?: "primary" | "text" | "playback"; endIcon?: ReactNode; busy?: boolean; native button props
|
|
81
|
+
- **Variants:** Primary / text / playback; disabled / busy
|
|
82
|
+
- **Accessibility:** Native keyboard behaviour; type defaults to button. Use busy for pending actions and give useful loading text.
|
|
83
|
+
- **Recommended use:** One primary action per local group. Links use TextLink.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Button endIcon={<Arrow />} onClick={start}>Explore the project</Button>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## TextLink
|
|
90
|
+
|
|
91
|
+
A fine underlined link for supporting destinations.
|
|
92
|
+
|
|
93
|
+
- **Entry:** design26
|
|
94
|
+
- **Props:** Native anchor props including href, target, rel, ref
|
|
95
|
+
- **Variants:** One underlined treatment
|
|
96
|
+
- **Accessibility:** Supply meaningful link text. Indicate a new tab when relevant.
|
|
97
|
+
- **Recommended use:** Use for navigation, downloads and secondary reading.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<TextLink href="/methods">Read the methods</TextLink>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## IconButton
|
|
104
|
+
|
|
105
|
+
Compact playback and circular paging actions.
|
|
106
|
+
|
|
107
|
+
- **Entry:** design26
|
|
108
|
+
- **Props:** aria-label: string; variant?: "circle" | "step" | "plain"; native button props
|
|
109
|
+
- **Variants:** Circle 34px / step 44px / plain 25×26px
|
|
110
|
+
- **Accessibility:** An accessible name is required. Keep enough space around compact controls for touch users.
|
|
111
|
+
- **Recommended use:** Use the step variant for main figure navigation and plain for inline tools.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<IconButton aria-label="Next figure" variant="step"><Arrow /></IconButton>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Navigation
|
|
118
|
+
|
|
119
|
+
White-on-blue wordmark, descriptor and links.
|
|
120
|
+
|
|
121
|
+
- **Entry:** design26
|
|
122
|
+
- **Props:** brand: ReactNode; description?: ReactNode; actions?: ReactNode; native header props
|
|
123
|
+
- **Variants:** Descriptor hides at 760px
|
|
124
|
+
- **Accessibility:** Brand links should identify the home destination. Actions are wrapped in a Main navigation landmark.
|
|
125
|
+
- **Recommended use:** Keep labels short. The header is part of the blue frame.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
<Navigation brand={<a href="/">Project</a>} description="RESEARCH NOTES" actions={<a href="#work">Work</a>} />
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## StepNavigation
|
|
132
|
+
|
|
133
|
+
Numbered section navigation with a fine active rule.
|
|
134
|
+
|
|
135
|
+
- **Entry:** design26
|
|
136
|
+
- **Props:** items: {id,label,disabled?}[]; value: string; onValueChange(value); aria-label: string; native nav props
|
|
137
|
+
- **Variants:** Responsive numbered steps
|
|
138
|
+
- **Accessibility:** Uses aria-current=step. Each button is in the normal tab order; content navigation belongs to the host.
|
|
139
|
+
- **Recommended use:** For ordered sections. Use Tabs for switching panels within a section.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<StepNavigation aria-label="Sections" items={sections} value={section} onValueChange={setSection} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Tabs
|
|
146
|
+
|
|
147
|
+
Switch among related views without leaving a section.
|
|
148
|
+
|
|
149
|
+
- **Entry:** design26
|
|
150
|
+
- **Props:** items: {value,label,content,disabled?}[]; value?; defaultValue?; onValueChange?; aria-label: string; native div props
|
|
151
|
+
- **Variants:** Controlled or uncontrolled; disabled items
|
|
152
|
+
- **Accessibility:** Roving focus, automatic activation, arrows/Home/End, disabled skipping and linked panels. Values must be unique.
|
|
153
|
+
- **Recommended use:** Two to four concise labels. Content belongs to the tab item, without global arrow shortcuts.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<Tabs aria-label="View" items={[{ value: "plot", label: "Plot", content: <Plot /> }, { value: "data", label: "Data", content: <Data /> }]} />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Slider
|
|
160
|
+
|
|
161
|
+
A native range input in either reference treatment.
|
|
162
|
+
|
|
163
|
+
- **Entry:** design26
|
|
164
|
+
- **Props:** variant?: "parameter" | "timeline"; native range props; provide a label or aria-label
|
|
165
|
+
- **Variants:** Parameter 4px track / timeline 3px track
|
|
166
|
+
- **Accessibility:** Native arrows/Home/End. Supply aria-valuetext for formatted units. Disabled remains distinct from busy.
|
|
167
|
+
- **Recommended use:** Use ParameterControl when a visible label, readout and endpoints belong together.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Slider variant="timeline" aria-label="Playback position" value={time} onChange={e => setTime(Number(e.target.value))} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Select
|
|
174
|
+
|
|
175
|
+
An understated native picker with an associated label.
|
|
176
|
+
|
|
177
|
+
- **Entry:** design26
|
|
178
|
+
- **Props:** label: ReactNode; description?: string; native select props; option children
|
|
179
|
+
- **Variants:** Native select, disabled
|
|
180
|
+
- **Accessibility:** Generated stable label and description IDs; native keyboard navigation. Do not remove the visible label.
|
|
181
|
+
- **Recommended use:** Use for longer sets of mutually exclusive options.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Select label="View" defaultValue="overview"><option value="overview">Overview</option></Select>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Checkbox
|
|
188
|
+
|
|
189
|
+
A compact native control for figure overlays and options.
|
|
190
|
+
|
|
191
|
+
- **Entry:** design26
|
|
192
|
+
- **Props:** label: ReactNode; native checkbox props
|
|
193
|
+
- **Variants:** Checked / unchecked / disabled
|
|
194
|
+
- **Accessibility:** Clickable label and native Space toggle. Describe actual effects in its label.
|
|
195
|
+
- **Recommended use:** Independent yes/no settings. Keep groups visually close to the affected figure.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<Checkbox label="Show grid" checked={grid} onChange={e => setGrid(e.target.checked)} />
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Disclosure
|
|
202
|
+
|
|
203
|
+
Secondary settings in a pale bordered details panel.
|
|
204
|
+
|
|
205
|
+
- **Entry:** design26
|
|
206
|
+
- **Props:** title: ReactNode; open?: boolean; name?: string; native details props
|
|
207
|
+
- **Variants:** Native open / closed
|
|
208
|
+
- **Accessibility:** Native summary toggles with Enter or Space and has a visible focus ring.
|
|
209
|
+
- **Recommended use:** Hide secondary complexity; never hide essential errors or primary actions.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<Disclosure title="Advanced settings"><p>Additional controls</p></Disclosure>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Dialog
|
|
216
|
+
|
|
217
|
+
A paper modal with navy heading and a blurred backdrop.
|
|
218
|
+
|
|
219
|
+
- **Entry:** design26
|
|
220
|
+
- **Props:** open: boolean; onOpenChange(open); title: ReactNode; closeLabel?: string; native dialog props except open/title/onClose
|
|
221
|
+
- **Variants:** Controlled native modal
|
|
222
|
+
- **Accessibility:** Native modal focus containment, Escape, labelled title and focus return. Keep the trigger mounted.
|
|
223
|
+
- **Recommended use:** For focused supporting information; keep lengthy documentation in the page.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<Dialog open={open} onOpenChange={setOpen} title="About the project"><p>Project details.</p></Dialog>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## StatusNote
|
|
230
|
+
|
|
231
|
+
A restrained contextual or review note.
|
|
232
|
+
|
|
233
|
+
- **Entry:** design26
|
|
234
|
+
- **Props:** tone?: "info" | "positive" | "warning" | "error"; native paragraph props
|
|
235
|
+
- **Variants:** Info / positive / warning / error
|
|
236
|
+
- **Accessibility:** Write the status in text. Add role=status or role=alert only when the update needs announcement.
|
|
237
|
+
- **Recommended use:** Use observed teal and amber semantics. Avoid inventing a new red/green badge system.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<StatusNote tone="warning">Manual boundary · review required</StatusNote>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## ParameterControl
|
|
244
|
+
|
|
245
|
+
Label, unit-aware readout, native range and endpoint values.
|
|
246
|
+
|
|
247
|
+
- **Entry:** design26/scientific
|
|
248
|
+
- **Props:** label: string; value,min,max: number; onValueChange(number); step?,unit?,precision?,disabled?,description?,name?; native div props
|
|
249
|
+
- **Variants:** Numeric range; explicit precision and units
|
|
250
|
+
- **Accessibility:** Label and output reference the range ID; aria-valuetext includes units. Supply description for constraints.
|
|
251
|
+
- **Recommended use:** Keep valid ranges and scientific calculations in the consuming application.
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
<ParameterControl label="Angle" value={angle} min={-10} max={10} step={0.1} unit="°" onValueChange={setAngle} />
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## DataPanel
|
|
258
|
+
|
|
259
|
+
Ruled label/value rows with monospaced readings.
|
|
260
|
+
|
|
261
|
+
- **Entry:** design26/scientific
|
|
262
|
+
- **Props:** items: {label:ReactNode,value:ReactNode}[]; busy?: boolean; native dl props
|
|
263
|
+
- **Variants:** Default / busy
|
|
264
|
+
- **Accessibility:** A semantic description list. Include units and explain pending values in nearby text.
|
|
265
|
+
- **Recommended use:** Metadata and compact readings. Use a real table for multi-column comparisons.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
<DataPanel items={[{ label: "Sample rate", value: "20 Hz" }]} />
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Metric
|
|
272
|
+
|
|
273
|
+
A large blue reading, with quiet label and unit.
|
|
274
|
+
|
|
275
|
+
- **Entry:** design26/scientific
|
|
276
|
+
- **Props:** label,value: ReactNode; unit?: string; variant?: "default" | "result"; description?: ReactNode; native div props
|
|
277
|
+
- **Variants:** Default 34px / result 42px
|
|
278
|
+
- **Accessibility:** Units are text. Pair colour with labels; avoid live-announcing every animation frame.
|
|
279
|
+
- **Recommended use:** One or two key readings. Do not turn every data row into a metric.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<Metric label="Response time" value="24.8" unit="ms" />
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## FigureContainer
|
|
286
|
+
|
|
287
|
+
A numbered figure, title and caption around supplied artwork.
|
|
288
|
+
|
|
289
|
+
- **Entry:** design26/scientific
|
|
290
|
+
- **Props:** title: ReactNode; number?,caption?: ReactNode; captionPosition?: "before" | "after"; native figure props except title
|
|
291
|
+
- **Variants:** Caption before / after
|
|
292
|
+
- **Accessibility:** Uses figure/figcaption; the child graphic still needs its own accessible title or description.
|
|
293
|
+
- **Recommended use:** Explain what to notice. Plotting algorithms remain with the host application.
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<FigureContainer number="01 / RESPONSE" title="Measured response" caption="Illustrative data."><Plot /></FigureContainer>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## PlotContainer
|
|
300
|
+
|
|
301
|
+
A neutral, responsive frame for SVG, canvas or images.
|
|
302
|
+
|
|
303
|
+
- **Entry:** design26/scientific
|
|
304
|
+
- **Props:** aria-label: string; framed?: boolean; native div props
|
|
305
|
+
- **Variants:** Framed / unframed
|
|
306
|
+
- **Accessibility:** Describe what the figure shows; expose data textually when it matters. Interactive points need keyboard support.
|
|
307
|
+
- **Recommended use:** Use plot colour and axis classes. It is a presentation container, not a charting engine.
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
<PlotContainer aria-label="Response over time"><svg viewBox="0 0 620 450" role="img" aria-label="Illustrative response">…</svg></PlotContainer>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## PlotLegend
|
|
314
|
+
|
|
315
|
+
A small inline key for figure series.
|
|
316
|
+
|
|
317
|
+
- **Entry:** design26/scientific
|
|
318
|
+
- **Props:** items: {label:string,tone?:"series"|"accent"|"highlight"|"reference",dashed?:boolean}[]; native ul props
|
|
319
|
+
- **Variants:** Blue / accent / amber / reference; dashed key
|
|
320
|
+
- **Accessibility:** Text accompanies each marker. Use different line patterns or labels when series must be distinguished without colour.
|
|
321
|
+
- **Recommended use:** Keep keys adjacent to their figure and consistent with the actual marks.
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<PlotLegend items={[{ label: "Observation", tone: "accent" }, { label: "Reference", tone: "highlight", dashed: true }]} />
|
|
325
|
+
```
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# design26
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A personal visual language extracted from a scientific demonstration: electric blue framing, cool paper, navy type, precise technical labels and figure-led explanation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Within a pnpm workspace, include `packages/*` in `pnpm-workspace.yaml`, then add `design26: workspace:*` to the consuming application's dependencies. Build the package before starting the app:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm --filter design26 build
|
|
11
|
+
pnpm add design26@workspace:*
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For a separate repository, run `pnpm pack` from this package and install the resulting tarball with `pnpm add /absolute/path/design26-0.1.0.tgz`. Publication is not required. The package has ESM exports and TypeScript declarations and requires React19 and React DOM19 as peers. It includes no Tailwind, Next.js, chart or animation runtime dependencies. The npm name has not been reserved or published. License: UNLICENSED; choose an appropriate distribution license before public publication.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
Import CSS once in the host root layout or entry file. Interactive examples belong in a client component in Next.js.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import 'design26/styles.css';
|
|
22
|
+
import { PageFrame, Navigation, Panel, SectionHeading, TextLink } from 'design26';
|
|
23
|
+
import { DataPanel, FigureContainer } from 'design26/scientific';
|
|
24
|
+
|
|
25
|
+
export default function Page() {
|
|
26
|
+
return (
|
|
27
|
+
<PageFrame>
|
|
28
|
+
<Navigation brand={<a href="/">My research</a>} description="WORKING NOTES" />
|
|
29
|
+
<main>
|
|
30
|
+
<Panel>
|
|
31
|
+
<SectionHeading as="h1" size="display">A new <em>perspective.</em></SectionHeading>
|
|
32
|
+
<FigureContainer number="01 / OVERVIEW" title="What we observed" caption="Explain the important relationship.">
|
|
33
|
+
<DataPanel items={[{ label: 'Sample rate', value: '20 Hz' }]} />
|
|
34
|
+
</FigureContainer>
|
|
35
|
+
<TextLink href="/methods">Read the methods</TextLink>
|
|
36
|
+
</Panel>
|
|
37
|
+
</main>
|
|
38
|
+
</PageFrame>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
'use client';
|
|
45
|
+
import { useState } from 'react';
|
|
46
|
+
import { Panel } from 'design26';
|
|
47
|
+
import { ParameterControl } from 'design26/scientific';
|
|
48
|
+
|
|
49
|
+
export function Controls() {
|
|
50
|
+
const [angle, setAngle] = useState(2);
|
|
51
|
+
return <Panel variant="control"><ParameterControl label="Angle" min={-10} max={10}
|
|
52
|
+
step={0.1} unit="°" value={angle} onValueChange={setAngle} /></Panel>;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Static components do not require a client boundary. Tabs, form wrappers, Dialog and ParameterControl retain their own `use client` modules in the build. Event-handling application compositions need a host client boundary as usual. CSS imports are explicit and marked as side effects. Package imports use public `.js` ESM files with `.d.ts` definitions.
|
|
57
|
+
|
|
58
|
+
## Theme and fonts
|
|
59
|
+
|
|
60
|
+
PageFrame supplies `.d26-theme`. For embedded components use a div with that class or `data-design26-theme="light"`. Nothing styles an unwrapped document. Component classes use the `d26-` prefix; styles do not reset global body/headings. All supplied components need the stylesheet and a theme ancestor.
|
|
61
|
+
|
|
62
|
+
Body/heading typography is Arial/Helvetica. For exact technical typography, load Geist Mono in the host and expose `--font-geist-mono`, e.g. using `next/font/google` in a Next application. Without it, technical labels use system monospace. Font files are not bundled and the library makes no network requests.
|
|
63
|
+
|
|
64
|
+
Override semantic tokens on the same wrapper. CSS properties inherit; defaults live at the theme boundary. Place custom overrides after the imported stylesheet.
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
.my-theme {
|
|
68
|
+
--color-accent: #075afa;
|
|
69
|
+
--color-background: #075afa;
|
|
70
|
+
--space-section: 36px;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<PageFrame className="my-theme">...</PageFrame>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `design26/tokens.css` alone when building a non-React composition. `design26/styles.css` already imports tokens. One faithful light theme is shipped; a dark theme is not invented. Exported `breakpoints` matches native CSS thresholds: mobile760px, tablet1100px, wide1500px. CSS variables cannot be used in native media-query conditions, so those literal thresholds are intentionally mirrored.
|
|
79
|
+
|
|
80
|
+
## Components
|
|
81
|
+
|
|
82
|
+
Generic: TechnicalGrid, PageFrame, Navigation, Panel, Eyebrow, SectionHeading, Button, TextLink, IconButton, StepNavigation, Tabs, Slider, Select, Checkbox, Disclosure, Dialog, StatusNote.
|
|
83
|
+
|
|
84
|
+
Scientific: ParameterControl, DataPanel, Metric, FigureContainer, PlotContainer, PlotLegend. These receive data/children from the host and contain no UVID scientific logic.
|
|
85
|
+
|
|
86
|
+
See [COMPONENTS.md](COMPONENTS.md) for every component contract and [AGENTS.md](AGENTS.md) for future coding agents. These guides are included in the tarball.
|
|
87
|
+
|
|
88
|
+
Every component exports a matching `*Props` type. Native HTML attributes, including React19 refs, are accepted. Tabs also exports TabItem. The repository's live catalogue documents every prop contract, state, usage example and accessibility note. Source declarations are the authoritative complete native-plus-custom API.
|
|
89
|
+
|
|
90
|
+
## Interaction contracts
|
|
91
|
+
|
|
92
|
+
- Button defaults to type=button; use TextLink for navigation. IconButton requires aria-label.
|
|
93
|
+
- Tabs values must be unique. It supports controlled/uncontrolled selection, arrows/Home/End and disabled skipping; current panel is keyboard focusable.
|
|
94
|
+
- Slider needs an associated label or aria-label. Use ParameterControl for labelled unit-aware ranges. Native min/max/step and meaningful limits are the host's responsibility. Avoid min=max.
|
|
95
|
+
- Select/Checkbox associate labels automatically. Supply descriptions for additional constraints.
|
|
96
|
+
- Dialog is controlled by open/onOpenChange. Keep its trigger mounted so native focus return can work. Give it a concise title; Escape closes it.
|
|
97
|
+
- DataPanel is a description list, not a multi-column table. Figure graphics need their own accessible description; PlotContainer does not make arbitrary child graphics accessible automatically.
|
|
98
|
+
- Focus uses an amber outline. Animation helpers d26-reveal and d26-pulse respect reduced motion. Do not infer scientific validity from presentation.
|
|
99
|
+
|
|
100
|
+
See the root audit and agent instructions before extending the system. Retain component recipes and optical spacing rather than imposing a new generic scale.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { PageFrame, Panel, Eyebrow, SectionHeading, Button, TextLink, IconButton, Navigation, StepNavigation, Disclosure, StatusNote } from "./ui/primitives.js";
|
|
2
|
+
export type { PageFrameProps, PanelProps, EyebrowProps, SectionHeadingProps, ButtonProps, TextLinkProps, IconButtonProps, NavigationProps, StepNavigationProps, DisclosureProps, StatusNoteProps } from "./ui/primitives.js";
|
|
3
|
+
export { Slider, Select, Checkbox } from "./ui/controls.js";
|
|
4
|
+
export type { SliderProps, SelectProps, CheckboxProps } from "./ui/controls.js";
|
|
5
|
+
export { Tabs } from "./ui/tabs.js";
|
|
6
|
+
export type { TabsProps, TabItem } from "./ui/tabs.js";
|
|
7
|
+
export { Dialog } from "./ui/dialog.js";
|
|
8
|
+
export type { DialogProps } from "./ui/dialog.js";
|
|
9
|
+
export declare const breakpoints: {
|
|
10
|
+
readonly mobile: 760;
|
|
11
|
+
readonly tablet: 1100;
|
|
12
|
+
readonly wide: 1500;
|
|
13
|
+
};
|
|
14
|
+
export { TechnicalGrid } from "./ui/technical-grid.js";
|
|
15
|
+
export type { TechnicalGridProps } from "./ui/technical-grid.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { PageFrame, Panel, Eyebrow, SectionHeading, Button, TextLink, IconButton, Navigation, StepNavigation, Disclosure, StatusNote } from "./ui/primitives.js";
|
|
2
|
+
export { Slider, Select, Checkbox } from "./ui/controls.js";
|
|
3
|
+
export { Tabs } from "./ui/tabs.js";
|
|
4
|
+
export { Dialog } from "./ui/dialog.js";
|
|
5
|
+
export const breakpoints = { mobile: 760, tablet: 1100, wide: 1500 };
|
|
6
|
+
export { TechnicalGrid } from "./ui/technical-grid.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
export type ParameterControlProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
unit?: string;
|
|
9
|
+
precision?: number;
|
|
10
|
+
onValueChange: (value: number) => void;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
description?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function ParameterControl({ label, value, min, max, step, unit, precision, onValueChange, disabled, description, name, id, className, ...props }: ParameterControlProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { Slider } from "../ui/controls.js";
|
|
5
|
+
import { cx } from "../ui/primitives.js";
|
|
6
|
+
export function ParameterControl({ label, value, min, max, step = 1, unit = "", precision = step < 1 ? 1 : 0, onValueChange, disabled, description, name, id, className, ...props }) {
|
|
7
|
+
const generated = useId(), uid = id ?? generated;
|
|
8
|
+
const formatted = value.toFixed(precision);
|
|
9
|
+
return _jsxs("div", { ...props, className: cx("d26-parameter", className), children: [_jsxs("label", { htmlFor: uid, children: [label, _jsxs("output", { htmlFor: uid, children: [formatted, _jsxs("span", { children: [" ", unit] })] })] }), _jsx(Slider, { id: uid, name: name, min: min, max: max, step: step, value: value, disabled: disabled, onChange: event => onValueChange(Number(event.target.value)), "aria-valuetext": `${formatted} ${unit}`.trim(), "aria-describedby": description ? `${uid}-description` : undefined }), _jsxs("div", { className: "d26-parameter-range", "aria-hidden": "true", children: [_jsxs("span", { children: [min, unit === "°" ? "°" : ""] }), _jsxs("span", { children: [max, unit === "°" ? "°" : ""] })] }), description && _jsx("p", { id: `${uid}-description`, className: "d26-field-description", children: description })] });
|
|
10
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export type DataPanelProps = HTMLAttributes<HTMLDListElement> & {
|
|
3
|
+
items: {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
value: ReactNode;
|
|
6
|
+
}[];
|
|
7
|
+
busy?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function DataPanel({ items, busy, className, ...props }: DataPanelProps): import("react").JSX.Element;
|
|
10
|
+
export type MetricProps = HTMLAttributes<HTMLDivElement> & {
|
|
11
|
+
label: ReactNode;
|
|
12
|
+
value: ReactNode;
|
|
13
|
+
unit?: string;
|
|
14
|
+
variant?: "default" | "result";
|
|
15
|
+
description?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
export declare function Metric({ label, value, unit, description, variant, className, ...props }: MetricProps): import("react").JSX.Element;
|
|
18
|
+
export type FigureContainerProps = Omit<HTMLAttributes<HTMLElement>, "title"> & {
|
|
19
|
+
number?: string;
|
|
20
|
+
title: ReactNode;
|
|
21
|
+
caption?: ReactNode;
|
|
22
|
+
captionPosition?: "before" | "after";
|
|
23
|
+
};
|
|
24
|
+
export declare function FigureContainer({ number, title, caption, captionPosition, className, children, ...props }: FigureContainerProps): import("react").JSX.Element;
|
|
25
|
+
export type PlotContainerProps = HTMLAttributes<HTMLDivElement> & {
|
|
26
|
+
"aria-label": string;
|
|
27
|
+
framed?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function PlotContainer({ framed, className, ...props }: PlotContainerProps): import("react").JSX.Element;
|
|
30
|
+
export type PlotLegendProps = HTMLAttributes<HTMLUListElement> & {
|
|
31
|
+
items: {
|
|
32
|
+
label: string;
|
|
33
|
+
tone?: "series" | "accent" | "highlight" | "reference";
|
|
34
|
+
dashed?: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
};
|
|
37
|
+
export declare function PlotLegend({ items, className, ...props }: PlotLegendProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "../ui/primitives.js";
|
|
3
|
+
export function DataPanel({ items, busy, className, ...props }) { return _jsx("dl", { ...props, "aria-busy": busy || undefined, className: cx("d26-data", className), children: items.map((item, index) => _jsxs("div", { children: [_jsx("dt", { children: item.label }), _jsx("dd", { children: item.value })] }, index)) }); }
|
|
4
|
+
export function Metric({ label, value, unit, description, variant = "default", className, ...props }) { return _jsxs("div", { ...props, className: cx("d26-metric", `d26-metric--${variant}`, className), children: [_jsx("span", { children: label }), _jsxs("strong", { children: [value, unit && _jsxs("small", { children: [" ", unit] })] }), description && _jsx("p", { children: description })] }); }
|
|
5
|
+
export function FigureContainer({ number, title, caption, captionPosition = "before", className, children, ...props }) {
|
|
6
|
+
const text = _jsxs("figcaption", { children: [number && _jsx("span", { className: "d26-figure-number", children: number }), _jsx("h3", { children: title }), caption && _jsx("p", { children: caption })] });
|
|
7
|
+
return _jsxs("figure", { ...props, className: cx("d26-figure", captionPosition === "after" && "d26-figure--caption-after", className), children: [captionPosition === "before" && text, children, captionPosition === "after" && text] });
|
|
8
|
+
}
|
|
9
|
+
export function PlotContainer({ framed = true, className, ...props }) { return _jsx("div", { ...props, role: props.role ?? "group", className: cx("d26-plot", framed && "d26-plot--framed", className) }); }
|
|
10
|
+
export function PlotLegend({ items, className, ...props }) { return _jsx("ul", { ...props, className: cx("d26-legend", className), children: items.map(item => _jsxs("li", { children: [_jsx("i", { "aria-hidden": "true", className: cx(`d26-legend--${item.tone ?? "series"}`, item.dashed && "d26-legend--dashed") }), item.label] }, item.label)) }); }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DataPanel, Metric, FigureContainer, PlotContainer, PlotLegend } from "./scientific/patterns.js";
|
|
2
|
+
export type { DataPanelProps, MetricProps, FigureContainerProps, PlotContainerProps, PlotLegendProps } from "./scientific/patterns.js";
|
|
3
|
+
export { ParameterControl } from "./scientific/parameter-control.js";
|
|
4
|
+
export type { ParameterControlProps } from "./scientific/parameter-control.js";
|