altitude-design 0.3.2 → 0.4.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/README.md +1 -0
- package/dist/cli/altitude.mjs +120 -20
- package/dist/guidance/README.md +4 -0
- package/dist/guidance/agent-setup.md +3 -2
- package/dist/guidance/components/AILabel/README.md +25 -0
- package/dist/guidance/components/Accordion/README.md +7 -0
- package/dist/guidance/components/AgentFleet/README.md +34 -0
- package/dist/guidance/components/AgentLanes/README.md +21 -0
- package/dist/guidance/components/AgentRequests/README.md +29 -0
- package/dist/guidance/components/Alert/README.md +22 -0
- package/dist/guidance/components/AltitudeProvider/README.md +48 -0
- package/dist/guidance/components/AppShell/README.md +51 -0
- package/dist/guidance/components/AreaChart/README.md +43 -0
- package/dist/guidance/components/Artifact/README.md +30 -0
- package/dist/guidance/components/Avatar/README.md +34 -0
- package/dist/guidance/components/Await/README.md +80 -0
- package/dist/guidance/components/Badge/README.md +22 -0
- package/dist/guidance/components/BarChart/README.md +47 -0
- package/dist/guidance/components/BarList/README.md +29 -0
- package/dist/guidance/components/Board/README.md +31 -0
- package/dist/guidance/components/Breadcrumb/README.md +35 -0
- package/dist/guidance/components/Button/README.md +17 -0
- package/dist/guidance/components/Card/README.md +14 -0
- package/dist/guidance/components/ChartBrush/README.md +41 -0
- package/dist/guidance/components/ChartContainer/README.md +152 -0
- package/dist/guidance/components/ChartLegend/README.md +35 -0
- package/dist/guidance/components/ChartReference/README.md +41 -0
- package/dist/guidance/components/ChartTooltip/README.md +38 -0
- package/dist/guidance/components/Checkbox/README.md +11 -0
- package/dist/guidance/components/Checklist/README.md +71 -0
- package/dist/guidance/components/CodeBlock/README.md +28 -0
- package/dist/guidance/components/ColorPicker/README.md +30 -0
- package/dist/guidance/components/Combobox/README.md +16 -0
- package/dist/guidance/components/CommandPalette/README.md +27 -0
- package/dist/guidance/components/CommentThread/README.md +30 -0
- package/dist/guidance/components/Confirmation/README.md +34 -0
- package/dist/guidance/components/ContextMenu/README.md +30 -0
- package/dist/guidance/components/Conversation/README.md +45 -0
- package/dist/guidance/components/CopyButton/README.md +23 -0
- package/dist/guidance/components/DataGrid/README.md +82 -0
- package/dist/guidance/components/DatePicker/README.md +67 -0
- package/dist/guidance/components/Dialog/README.md +29 -0
- package/dist/guidance/components/DiffView/README.md +30 -0
- package/dist/guidance/components/DotPlot/README.md +38 -0
- package/dist/guidance/components/Drilldown/README.md +15 -0
- package/dist/guidance/components/DropZone/README.md +21 -0
- package/dist/guidance/components/DropdownMenu/README.md +19 -0
- package/dist/guidance/components/EditRegion/README.md +104 -0
- package/dist/guidance/components/EmptyState/README.md +17 -0
- package/dist/guidance/components/FeatureGate/README.md +28 -0
- package/dist/guidance/components/Field/README.md +10 -0
- package/dist/guidance/components/FilterBar/README.md +69 -0
- package/dist/guidance/components/FormattedNumber/README.md +37 -0
- package/dist/guidance/components/Heatmap/README.md +45 -0
- package/dist/guidance/components/HoldButton/README.md +16 -0
- package/dist/guidance/components/HoverCard/README.md +18 -0
- package/dist/guidance/components/InlineCitation/README.md +24 -0
- package/dist/guidance/components/InlineEdit/README.md +133 -0
- package/dist/guidance/components/Input/README.md +27 -0
- package/dist/guidance/components/InputOTP/README.md +30 -0
- package/dist/guidance/components/ItemGroup/README.md +93 -0
- package/dist/guidance/components/Kbd/README.md +9 -0
- package/dist/guidance/components/LineChart/README.md +47 -0
- package/dist/guidance/components/Link/README.md +28 -0
- package/dist/guidance/components/Markdown/README.md +32 -0
- package/dist/guidance/components/Meter/README.md +29 -0
- package/dist/guidance/components/NotificationCenter/README.md +28 -0
- package/dist/guidance/components/NumberInput/README.md +29 -0
- package/dist/guidance/components/PageHeader/README.md +29 -0
- package/dist/guidance/components/Pagination/README.md +25 -0
- package/dist/guidance/components/PasswordInput/README.md +34 -0
- package/dist/guidance/components/PermissionMatrix/README.md +28 -0
- package/dist/guidance/components/Pile/README.md +12 -0
- package/dist/guidance/components/PlanPicker/README.md +25 -0
- package/dist/guidance/components/Popover/README.md +12 -0
- package/dist/guidance/components/Progress/README.md +10 -0
- package/dist/guidance/components/PromptInput/README.md +40 -0
- package/dist/guidance/components/PropertyList/README.md +57 -0
- package/dist/guidance/components/Question/README.md +27 -0
- package/dist/guidance/components/RadioGroup/README.md +10 -0
- package/dist/guidance/components/RelativeTime/README.md +26 -0
- package/dist/guidance/components/RemovalReceipt/README.md +34 -0
- package/dist/guidance/components/Resizable/README.md +36 -0
- package/dist/guidance/components/SaveBar/README.md +27 -0
- package/dist/guidance/components/SavedViews/README.md +24 -0
- package/dist/guidance/components/ScatterChart/README.md +41 -0
- package/dist/guidance/components/Schedule/README.md +76 -0
- package/dist/guidance/components/ScrollArea/README.md +7 -0
- package/dist/guidance/components/SecretField/README.md +28 -0
- package/dist/guidance/components/Select/README.md +27 -0
- package/dist/guidance/components/Separator/README.md +26 -0
- package/dist/guidance/components/Sheet/README.md +32 -0
- package/dist/guidance/components/Skeleton/README.md +14 -0
- package/dist/guidance/components/Slider/README.md +25 -0
- package/dist/guidance/components/SortableList/README.md +11 -0
- package/dist/guidance/components/Sparkline/README.md +29 -0
- package/dist/guidance/components/SplitButton/README.md +26 -0
- package/dist/guidance/components/Stat/README.md +31 -0
- package/dist/guidance/components/Stepper/README.md +29 -0
- package/dist/guidance/components/Suggestions/README.md +29 -0
- package/dist/guidance/components/Surface/README.md +20 -0
- package/dist/guidance/components/Switch/README.md +9 -0
- package/dist/guidance/components/SyncIndicator/README.md +24 -0
- package/dist/guidance/components/Table/README.md +76 -0
- package/dist/guidance/components/Tabs/README.md +18 -0
- package/dist/guidance/components/TagInput/README.md +66 -0
- package/dist/guidance/components/Terminal/README.md +27 -0
- package/dist/guidance/components/ThreadList/README.md +37 -0
- package/dist/guidance/components/TimeInput/README.md +26 -0
- package/dist/guidance/components/Timeline/README.md +32 -0
- package/dist/guidance/components/Toaster/README.md +16 -0
- package/dist/guidance/components/Toggle/README.md +33 -0
- package/dist/guidance/components/Toolbar/README.md +37 -0
- package/dist/guidance/components/Tooltip/README.md +12 -0
- package/dist/guidance/components/Tour/README.md +29 -0
- package/dist/guidance/components/Trace/README.md +37 -0
- package/dist/guidance/components/Tree/README.md +86 -0
- package/dist/guidance/evidence.json +18 -0
- package/dist/guidance/index.json +811 -7
- package/dist/guidance/llms.txt +153 -2
- package/dist/guidance/manifest.json +133 -12
- package/dist/guidance/patterns/screen-layout.md +1 -1
- package/dist/guidance/references/build/checks/agent-setup.mjs +7 -0
- package/dist/guidance/references/build/checks/guidance.mjs +8 -2
- package/dist/guidance/references/build/checks/source-check.mjs +101 -0
- package/dist/guidance/rules.json +155 -0
- package/dist/guidance/visual/ActionsInPlace.md +463 -0
- package/dist/guidance/visual/Charts.md +119 -0
- package/dist/guidance/visual/Distinctiveness.md +106 -0
- package/dist/guidance/visual/Elevation.md +1 -1
- package/dist/guidance/visual/Mobile.md +64 -0
- package/dist/guidance/visual/Motion.md +225 -0
- package/dist/guidance/visual/README.md +98 -0
- package/dist/guidance/visual/Research.md +1 -1
- package/dist/guidance/visual/Theming.md +349 -0
- package/package.json +1 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Altitude is a design and HCI framework with React components where **height is the interface**. Its one idea: **the ground is the record.** Settled content sits on the ground. Anything live rises above it (something to press, something in your hand, something waiting to be confirmed, something that just changed), and the higher it goes, the more temporary it is and the more it asks of you. Anything that's waiting to receive sinks below it (a field, a slot, a track, a problem to fix). Motion only ever travels along that axis. Color schemes are swappable and the accent can be any color; the elevation language is the same in all of them.
|
|
2
|
+
|
|
3
|
+
Read **Elevation** for the model and every signal height carries, **Motion** before animating anything, **Charts** before drawing data, **Mobile** before laying anything out for touch or a small screen, **Research** for the evidence and measurements behind each rule, **Theming** for schemes, custom accents and shadcn/ui, **Distinctiveness** before composing a screen, so it doesn't read as generated, and **Fundamentals** for the established design laws generated interfaces break, and where Altitude answers each.
|
|
4
|
+
|
|
5
|
+
## The laws
|
|
6
|
+
|
|
7
|
+
1. **Altitude is importance.** The more something matters right now, the higher it sits. The primary action rests at level 2; one thing at level 5, ever. Importance is the person's: what their task needs now, never what the business would rather they chose.
|
|
8
|
+
2. **Push up, fill down.** Things you press are raised slabs whose height you can see (a wall under the face, a convex sheen); things you fill are openings cut into the page, their back wall showing until they're filled. Every choice control is built like the Switch: a trench, a raised thumb, and the accent pouring in behind it. Never style an input as raised or a button as sunk.
|
|
9
|
+
3. **Tiers carry meaning, levels carry motion.** People read four tiers (sunk, ground, raised, floating) at a glance; the eight levels exist so hover and press can be *felt*. Never make two things mean different things by putting them one level apart.
|
|
10
|
+
4. **Only what moves, moves.** A surface lifts on hover only if clicking it does something, and nothing raised moves until the pointer is on it: no climbing toward an approaching pointer, no leaning after it. Only openings anticipate (an empty field deepens as a hand comes near). Pressing returns it to ground; disabled things stay flush.
|
|
11
|
+
5. **Live things are above, settled things are on the ground.** New content arrives lifted and settles; pending writes hover until they land; whatever is in a hand floats.
|
|
12
|
+
6. **The light never moves.** One light, straight above. Shadows only fall downward and are always darker than the ground.
|
|
13
|
+
7. **Height never travels alone.** Every elevation signal also has a word, an icon, an ARIA state or a border, so it survives screen readers, forced colors and low vision.
|
|
14
|
+
|
|
15
|
+
## Content
|
|
16
|
+
|
|
17
|
+
- Sentence case everywhere: buttons, titles, menu items, tabs ("Publish changes", not "Publish Changes").
|
|
18
|
+
- Buttons start with a verb and name the outcome: "Delete project", "Send invite". Never "OK" or "Submit".
|
|
19
|
+
- Speak to the person as "you"; the product speaks as "we" only in errors it caused ("We couldn't reach the server").
|
|
20
|
+
- Errors say what happened and what to do, in one or two short sentences: "Use at least 5 characters." No "Oops", no exclamation marks, no emoji.
|
|
21
|
+
- Status always carries a word and an icon, never color alone: "Operational", "Degraded", "Outage".
|
|
22
|
+
- Links name where they go: "View March invoice", not "Learn more", "Read more" or "Click here". A link has to make sense read on its own, because screen readers list them that way.
|
|
23
|
+
- An icon-only button's accessible name says the action and its object ("Delete invoice"), the words a visible label would use.
|
|
24
|
+
- Every field has a visible label (`Field label`). A placeholder is an example of the answer, not a label: it disappears when typing starts. A search field with only an icon still takes an `aria-label`.
|
|
25
|
+
- Alt text says what an image shows, in its context. A decorative image takes `alt=""`. Never "image", "photo" or a file name.
|
|
26
|
+
- Headings describe the section under them. One `h1` per view (`PageHeader` draws it), levels never skip, and the page has a `main` landmark, with `header` and `nav` where it has them.
|
|
27
|
+
- Show real data or say there's none. Never fill a screen with invented figures, ratings, names or testimonials presented as real.
|
|
28
|
+
|
|
29
|
+
## Honesty
|
|
30
|
+
|
|
31
|
+
Generated interfaces add deceptive patterns nobody asked for (see [Fundamentals](Fundamentals.md)). Emphasis serves the person's task, so:
|
|
32
|
+
|
|
33
|
+
- Urgency only when a real deadline exists, and say the deadline ("Your trial ends on 12 March"). No countdowns, "only 3 left" or "limited time" without a real limit behind them.
|
|
34
|
+
- Declining is as easy to find as accepting. In an offer or request the product makes (subscribe, upgrade, consent, share), the choice the business prefers isn't the primary by default; when one choice is primary, the decline is still a secondary button, never a ghost, a faint link or a small grey word. Name it plainly ("Don't subscribe"), never with shame ("No thanks, I like paying full price").
|
|
35
|
+
- Nothing that costs money, shares data or subscribes someone is pre-selected.
|
|
36
|
+
- Every overlay can be dismissed: Escape always, and anything that blocks the page or asks for something (a dialog, a sheet, a prompt) has a visible way out, a close button or a Cancel. A decision the person must make has both choices as buttons.
|
|
37
|
+
- Prices, terms and conditions sit beside the action they apply to, at body size. A total includes every charge the service has quoted.
|
|
38
|
+
- Leaving takes no more steps than joining did. A cancellation can say what will be lost (as a downgrade's review does), never plead or offer a discount the person didn't get when they joined.
|
|
39
|
+
- Reviews, ratings and counts shown as real are real, and say how they were chosen ("Most recent", "Highest rated").
|
|
40
|
+
|
|
41
|
+
## Color
|
|
42
|
+
|
|
43
|
+
- Build every screen from the `surface-*` ladder: `surface-ground` for the page, `surface-raised` for cards and buttons, `surface-well` for inputs, and so on. Never invent an in-between gray; pick the level instead.
|
|
44
|
+
- `ink` for primary text and `ink-muted` for secondary text, on any `surface-*`. `ink-faint` is for disabled labels only.
|
|
45
|
+
- `accent` is the scheme's one hue: primary buttons, checked controls, fills. Put `on-accent` text on it. When the accent is text or a link, use `accent-ink`. Behind accent text, use `accent-soft`.
|
|
46
|
+
- `positive`, `caution`, `critical` are for status and each has a `-soft` ground. `critical` also fills the danger button, with `on-critical` text.
|
|
47
|
+
- `data-*` is for charts only, as `presence-*` is for people: `data-1`–`data-6` tell series apart, `data-seq-*` carries magnitude and `data-div-*` polarity. Never use them for text, surfaces or status. [Charts](Charts.md) says which job a chart's colour does.
|
|
48
|
+
- `line` is for decorative dividers only. Anything that marks the edge of a control (inputs, checkboxes, radios, the switch track) uses `line-control`, which holds 3:1 against every ground.
|
|
49
|
+
- Every text token clears 4.5:1 on all eight surfaces in both schemes. Keep it that way when adding colors: test against `surface-trench` and `surface-modal`, the extremes.
|
|
50
|
+
|
|
51
|
+
## Type
|
|
52
|
+
|
|
53
|
+
- One family, Instrument Sans (Google Fonts, weights 400–700), with JetBrains Mono for code, keys and values.
|
|
54
|
+
- `text-title` for page titles, `text-heading` for section, card and dialog titles, `text-body` for paragraphs, `text-label` for every control, `text-caption` for hints and metadata. `text-display` once per view, at most.
|
|
55
|
+
- Weight carries hierarchy before size does: labels are 500, titles 600. Never use 700 in the interface.
|
|
56
|
+
- Tracking tightens as size grows (−0.025em at 44px, −0.01em at 19px, 0 at 15px and below).
|
|
57
|
+
- Prose runs 45 to 75 characters a line: cap paragraphs at `max-width: 70ch`, as `PageHeader`'s description does. Tables, code and data are exempt.
|
|
58
|
+
|
|
59
|
+
## Spacing and radius
|
|
60
|
+
|
|
61
|
+
- 4px base: `space-1` to `space-12`. Inside a surface, padding grows with altitude: menus `space-1`, cards `space-5`, dialogs `space-6`.
|
|
62
|
+
- Radii grow with surface size and height: `radius-xs` checkboxes and keys, `radius-md` buttons and inputs, `radius-lg` cards, menus and toasts, `radius-xl` dialogs. `radius-full` for switches, thumbs, radios, avatars and counts; `radius-sm` for badge plates.
|
|
63
|
+
- Nest concentrically: an inner surface's radius is the outer radius minus the gap between them. The tab indicator is `radius-md` − `space-1`.
|
|
64
|
+
|
|
65
|
+
## Focus and states
|
|
66
|
+
|
|
67
|
+
- Keyboard focus is a 2px solid `accent-ink` ring, 2px offset, on every control you press or switch; on walled controls (buttons, breadcrumb steps) it wraps the face and the wall together. Fields are the exception: a focused field, or an open select, is held, not targeted, so its floor lights; when you reached it by keyboard its own rim also darkens to a 2px neutral (`--alt-rim-held`), with no accent and no halo. A field you clicked into needs no rim, since you know where you clicked and the caret is there. Keyboard focus is never removed or replaced by a shadow alone. Focus also triggers the same one-level lift as hover, so keyboard and pointer users get the same feedback.
|
|
68
|
+
- Hover: +1 level. Press: down to 0, sheen flattened. Selected: raised out of its well. Disabled: flush, `ink-faint`, no shadow. Invalid: sunk to trench depth on `critical-soft` with a `critical` rim. Loading: held pressed. Pending: hovering. Failed: dropped into a trench.
|
|
69
|
+
- Everything that looks pressable does something. A wall, an underline or a hover lift on something with no action is a false signifier; make it flat (a raised card that only holds content is fine, since it carries no wall and doesn't lift).
|
|
70
|
+
- Don't disable a submit button to say a form isn't valid yet: a disabled button can't explain itself and keyboard users may never reach it. Let it be pressed and list what's missing (`ErrorSummary`). Disable it only while the submission is in flight, so it can't be sent twice.
|
|
71
|
+
- Use `presence-1`–`presence-3` only for other people (their holds, cursors, selections), never for status or decoration.
|
|
72
|
+
|
|
73
|
+
## Iconography
|
|
74
|
+
|
|
75
|
+
- Line icons on a 24px grid at 1.75 stroke, round caps and joins, in `currentColor`. The components ship the handful they need (check, chevron, close, info, status). For everything else use Lucide, which shares the grid and stroke, at 16px in controls and 18px in alerts and toasts.
|
|
76
|
+
- Icons sit flush. They never get their own shadow or elevation.
|
|
77
|
+
- No emoji in the interface.
|
|
78
|
+
|
|
79
|
+
## Using the components
|
|
80
|
+
|
|
81
|
+
- Pass your data as it is while it loads (`user?.name`, `rows={deploys}`): components whose content is still undefined imprint themselves at their own size and rise out when it arrives, with no skeleton code (see `Await`).
|
|
82
|
+
- Wrap the app once in `AltitudeProvider`. It sets the scheme, derives a custom accent, honors reduced motion and high contrast, and hosts dialogs and toasts so the page can recede behind them. Render `Toaster` once inside it.
|
|
83
|
+
- Put depth signals on any `Surface` or `Card` with props: `settle` (afterglow when a value changes), `status` (pending, committed, failed), `heldBy` (a collaborator's hold) and `relevance` (match or miss). In your own components, spread `useDepth(...)` onto the element.
|
|
84
|
+
- The bundle exposes everything on `window.Altitude` and expects React 18. It is a dependency-free reference implementation; the props mirror shadcn/ui, so the same styles can wrap Radix primitives in a production package.
|
|
85
|
+
|
|
86
|
+
## Charts
|
|
87
|
+
|
|
88
|
+
In a chart, the data is the record: marks are flat ink standing on a ground line, never raised, shadowed or lifted on hover, because a bar's length is already its height. Only the chart's chrome (the readout, a toggleable legend, a brush, a drill-down) uses height, and each is built like a component you already have. Colour does one job per chart, and every chart has a summary, a keyboard path and a table view. [Charts](Charts.md) has the rules, the form chooser and the build of every part.
|
|
89
|
+
|
|
90
|
+
## Actions in place
|
|
91
|
+
|
|
92
|
+
Finish a small edit where its value lives with `InlineEdit`, and several fields of one record saved together with `EditRegion`. Perform a reversible action directly when the application supplies real recovery and a durable destination. Use a focused `Dialog` when the consequence requires an explicit decision. [Actions in place](ActionsInPlace.md) contains executable recipes and the integrated Files example.
|
|
93
|
+
|
|
94
|
+
Editing is temporary local input; it is not a pending write. Pending begins only when persistence is dispatched. Keep committed content on the ground, and carry every live state with text, semantics or borders as well as height. Persistence, authorization, concurrency and reversibility remain application responsibilities.
|
|
95
|
+
|
|
96
|
+
## Task patterns
|
|
97
|
+
|
|
98
|
+
For complete product tasks, start with the [framework entry](../README.md). Its edit-record, async-collection, consequential-action and review-and-submit patterns combine design decisions, small workflow hooks and verification. Use only applicable states and actual application capabilities. The elevation model remains the same; a pending or successful signal must correspond to the operation's real state.
|
|
@@ -622,7 +622,7 @@ A measurement, then a design review ([#97](https://github.com/richlaconte/altitu
|
|
|
622
622
|
- **A pointer over the list holds the close.** A delayed close could move the next row under a pointer reaching for it. The close waits while a mouse is over the list, capped at `dur-settle`; touch has no hover, so it doesn't wait.
|
|
623
623
|
- **`dur-linger` is 2000ms by judgement.** About four words read in roughly a second (Nielsen's 1s flow limit) plus a second to act; the top of the "second or two" asked for.
|
|
624
624
|
|
|
625
|
-
Not yet tested with people: whether the close is noticeable enough at `dur-travel-3`, and whether waiting for the pointer to leave the list reads as lag. Both are open in [the validation record](ActionsInPlaceValidation.md).
|
|
625
|
+
Not yet tested with people: whether the close is noticeable enough at `dur-travel-3`, and whether waiting for the pointer to leave the list reads as lag. Both are open in [the validation record](https://github.com/richlaconte/altitude/blob/v0.4.0/project/ActionsInPlaceValidation.md).
|
|
626
626
|
|
|
627
627
|
### E47 · Can a chart Altitude doesn't draw keep its rules?
|
|
628
628
|
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
## Schemes
|
|
4
|
+
|
|
5
|
+
Altitude ships two schemes, one light and one dark. Each is a complete set of the same tokens, so every component works in all of them unchanged.
|
|
6
|
+
|
|
7
|
+
| Scheme | Ground | Accent | Character |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `daylight` | cool off-white | cobalt | The default. Neutral, crisp, shadow-led. |
|
|
10
|
+
| `nightfall` | blue-black | periwinkle | Dark, tonal elevation: higher is lighter. |
|
|
11
|
+
|
|
12
|
+
Set a scheme with `<AltitudeProvider scheme="nightfall">`, or with `data-theme="nightfall"` on any element. Schemes nest: a `nightfall` panel inside a `daylight` page is just a provider (or a `data-theme`) around that panel.
|
|
13
|
+
|
|
14
|
+
`mesa` and `alpine` were retired because they matched the palettes generated interfaces now default to: terracotta on cream, and one mint accent on near-black ([Distinctiveness](Distinctiveness.md), #324). Their names still work, as `daylight` and `nightfall`, and the provider warns once in development. Give your product its own colour with `accent`, or its own grounds with a custom scheme.
|
|
15
|
+
|
|
16
|
+
## Custom accent
|
|
17
|
+
|
|
18
|
+
`<AltitudeProvider accent="#0f8a6b">` takes any hex color and derives the four accent tokens against the active scheme:
|
|
19
|
+
|
|
20
|
+
- `accent` is your color as given, for fills.
|
|
21
|
+
- `on-accent` is white if white reads at 5:1 on it (leaving room for the sheen's lighter top), otherwise a near-black of the same hue.
|
|
22
|
+
- `accent-ink` is your color nudged toward ink until it reads at 4.5:1 on every surface from trench to modal, for text, links and the focus ring.
|
|
23
|
+
- `accent-soft` is a 13% (light) or 20% (dark) tint of the ground.
|
|
24
|
+
|
|
25
|
+
The derivation reruns when the scheme changes, so one brand color works in light and dark.
|
|
26
|
+
|
|
27
|
+
The accent leads the chart palette too (see **The data palette** below). The scheme's slot nearest your colour makes way for one in your colour's hue, at the lightness nearest yours that clears every gate (3:1 marks, the lightness band, and the distance from status and presence hues). If no colour within 30° of your hue clears them, that nearest slot leads unchanged: a green brand in daylight leads with daylight's own green, because `positive` blocks the rest. The other five slots re-order behind `data-1`. Every order is tried, and the one that keeps neighbours furthest apart for colour-blind readers wins. The magnitude ramp `data-seq-*` follows `data-1`'s hue. Nothing is generated beyond the scheme's own slots, the result is the same every time for the same accent and scheme, and a slot is dropped (the palette then ends in `data-other`) only if no order keeps every neighbour 8 apart, which the provider says in a console warning. Another slot can still sit close to your accent; `checkScheme(el, { include: ['data'] })` reports it. Very pale accents (yellows, pastels) still need checking: a checked checkbox's fill should hold 3:1 against the ground.
|
|
28
|
+
|
|
29
|
+
## Your own scheme
|
|
30
|
+
|
|
31
|
+
When a brand needs more than an accent (its own grounds, ink and status colours), pass a custom scheme to the provider:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<AltitudeProvider scheme={{
|
|
35
|
+
name: 'paper',
|
|
36
|
+
base: 'daylight', // light or dark handling, and every token you leave out
|
|
37
|
+
tokens: {
|
|
38
|
+
'surface-trench': '#e5e1d2', 'surface-well': '#edeadf', 'surface-ground': '#f5f3ea', 'surface-raised': '#fffdf7',
|
|
39
|
+
'surface-lifted': '#fffdf8', 'surface-floating': '#fffefa', 'surface-overlay': '#fffefb', 'surface-modal': '#fffefc',
|
|
40
|
+
ink: '#1f3329', 'ink-muted': '#4b5d53', line: '#e2ded0', 'line-control': '#76806f',
|
|
41
|
+
accent: '#284d39', 'on-accent': '#ffffff', 'accent-ink': '#284d39', 'accent-soft': '#e1e9dc', positive: '#2c6e3f',
|
|
42
|
+
},
|
|
43
|
+
}}>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Token names are the ones in `tokens.json`, without `--`; any colour or shadow token can be set. `base` must be `nightfall` for a dark scheme, since casts, rims and the inside walls of openings are drawn differently on dark grounds. The provider scopes the tokens to its own root, so a custom scheme nests like a built-in one; `accent` still works on top of it (derived against your surfaces); and shadows and `line` give way to high contrast as the built-in ones do. Forced colours replaces every colour with the system's, as it does for any scheme.
|
|
47
|
+
|
|
48
|
+
A custom scheme has to keep the invariants below, or the elevation language and its contrast quietly break. The provider checks them when the scheme is applied and warns in the console with one line per problem. Check them in your own tests too: `checkScheme` takes the colour tokens by name (merge your tokens over the base's from `altitude-design/tokens.json`) or an element to read them from, and returns the problems, `[]` if there are none.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { checkScheme } from 'altitude-design';
|
|
52
|
+
import tokens from 'altitude-design/tokens.json';
|
|
53
|
+
|
|
54
|
+
const base = Object.fromEntries(tokens.color.tokens.map((t) => [t.name, typeof t.value === 'string' ? t.value : t.value.daylight]));
|
|
55
|
+
expect(checkScheme({ ...base, ...paper.tokens })).toEqual([]);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Only hex colours can be measured; `checkScheme` reports any other value it was asked to check. Its data-palette gates are opt-in, so existing tests keep passing: `checkScheme(tokens, { include: ['core', 'data'] })` runs both, and `include: ['data']` only the palette's. The provider runs both on a custom scheme, so a scheme that changes its grounds or status hues and leaves the `data-*` tokens to its base says which slots it broke. To ship a scheme as CSS instead, write a `[data-theme="paper"]` block that sets the tokens and pass `scheme="paper"`. Anything the block leaves out falls back to daylight, and nothing checks it for you, so run `checkScheme(element)` on it in a test.
|
|
59
|
+
|
|
60
|
+
## Making a new scheme
|
|
61
|
+
|
|
62
|
+
To add a scheme to Altitude itself, add a theme to `tokens.json` and give it a value for every color and shadow token. Keep these invariants:
|
|
63
|
+
|
|
64
|
+
- The surfaces form a ladder: trench < well < ground < raised ≤ lifted ≤ floating ≤ overlay ≤ modal in lightness (dark schemes step up about 3% per level; light schemes go to white at raised).
|
|
65
|
+
- `ink`, `ink-muted`, `accent-ink` and the three status colors each clear 4.5:1 on all eight surfaces. `line-control` and `accent` clear 3:1 on ground, well, trench and raised.
|
|
66
|
+
- `presence-1`–`presence-3` clear 3:1 on every surface, and `on-presence` clears 4.5:1 on each.
|
|
67
|
+
- Button and key walls are derived (the face colour mixed 34% toward black; `surface-trench` under neutral faces), so a new scheme or a custom accent gets correct walls without new tokens.
|
|
68
|
+
- Every `data-*` token keeps the gates in **The data palette** below; `node build/palette.mjs` chooses them and checks them.
|
|
69
|
+
- Shadows keep the four-layer structure (rim, ring, key, ambient) at every level, including `elev-0`, `well` and `trench`, so transitions interpolate.
|
|
70
|
+
|
|
71
|
+
## The data palette
|
|
72
|
+
|
|
73
|
+
Charts get their own colours, so a series never borrows a meaning (see [Charts](Charts.md), rule 5). Each scheme has:
|
|
74
|
+
|
|
75
|
+
| Tokens | Job | How it's built |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `data-1` … `data-6` | identity: which series | six hues in a fixed order. `data-1` is in the accent's hue where a slot can be, at a different lightness from the accent; where the accent's hue sits too near `critical` or `caution`, it's the nearest hue that clears them |
|
|
78
|
+
| `data-other` | the seventh and later series, folded | a neutral in the ground's hue |
|
|
79
|
+
| `data-context` | what a chart isn't about: the series or records outside its `focus` | a neutral in the ground's hue, as receding as 3:1 allows |
|
|
80
|
+
| `data-seq-1` … `data-seq-7` | magnitude: how much | `data-1`'s hue, lightness in even steps; light schemes darken from near-white, dark schemes climb from near their ground |
|
|
81
|
+
| `data-div-neg-3` … `data-div-mid` … `data-div-pos-3` | polarity: which side of a midpoint | orange below, blue above, around a neutral; the blue–yellow axis is the one colour-blind readers keep |
|
|
82
|
+
|
|
83
|
+
Six is a measured limit, not a choice: past it, series fold into `data-other` or become small multiples, and no colour is ever generated. The values are chosen by `build/palette.mjs`, which enumerates OKLCH candidates and orders and keeps the order with the best colour-blind separation that passes every gate, and CI fails if `tokens.json` drifts from it. The gates, measured in OKLab ΔE×100 with colour-blind vision simulated after Machado, Oliveira & Fernandes (2009):
|
|
84
|
+
|
|
85
|
+
- Each scheme chooses its own values. Dark schemes are stepped for their own ground, not inverted.
|
|
86
|
+
- Categorical slots sit at least 12 from `accent`, `positive`, `caution`, `critical` and `presence-*`, so a series never impersonates a control, a status or a person (Research, E23, says why 12 and not 15).
|
|
87
|
+
- Any two slots: normal-vision ΔE ≥ 15, hues at least 25° apart, colour-blind ΔE ≥ 6. Adjacent slots: colour-blind ΔE ≥ 8 under both protanopia and deuteranopia.
|
|
88
|
+
- OKLCH lightness within 0.43–0.77 in light schemes and 0.48–0.67 in dark ones; chroma ≥ 0.10.
|
|
89
|
+
- Marks hold 3:1 against both `surface-ground` and `surface-raised`, because charts sit on the page and in `Card`s. `data-other` too.
|
|
90
|
+
- `data-context` is neutral (chroma ≤ 0.02), holds 3:1 on `surface-ground` and `surface-raised`, and sits at least ΔE 15 from `line` (so context isn't read as gridlines) and 10 from `data-other`. It's exempt from the slots' distance gate, because a context series is never told apart by its colour: it's unlabelled, or labelled in `ink-muted` (Research, E105).
|
|
91
|
+
- The ramps are monotone in lightness, with steps of at least 0.06.
|
|
92
|
+
- The diverging poles are never `positive` or `critical` (ΔE ≥ 15 from both): red–green is the worst pair for colour-blind readers, and both hues mean status.
|
|
93
|
+
- When a series *means* good or bad (error rate, uptime), it wears the status tokens with an icon and a word, not a `data-*` slot, and never both in one chart.
|
|
94
|
+
|
|
95
|
+
A custom scheme that sets its own surfaces or status colours should set the `data-*` tokens too, and check them with `checkScheme(tokens, { include: ['data'] })`. A custom accent re-leads the palette (see **Custom accent**).
|
|
96
|
+
|
|
97
|
+
## Density and target size
|
|
98
|
+
|
|
99
|
+
Controls take their heights from the `size-*` tokens, so one setting can make them fit a finger without making a desktop sparse:
|
|
100
|
+
|
|
101
|
+
| Token | With a mouse | Under a coarse pointer | Used by |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| `size-target-min` | 24px | 44px | the hit area every control reaches (WCAG 2.5.8, and 2.5.5 on touch) |
|
|
104
|
+
| `size-control-sm` | 30px | 40px | small buttons, tab triggers, breadcrumb steps |
|
|
105
|
+
| `size-control-md` | 36px | 44px | buttons, menu items (−2px), sidebar items (−2px), folder tabs (+2px) |
|
|
106
|
+
| `size-field-sm` | 34px | 44px | small inputs |
|
|
107
|
+
| `size-field-md` | 40px | 48px | inputs, select triggers, command palette rows |
|
|
108
|
+
| `size-row-md` | 32px | 44px | tree, list and filter rows |
|
|
109
|
+
|
|
110
|
+
Under `(pointer: coarse)` the faces step up on their own. `<AltitudeProvider density>` overrides that: `'comfortable'` uses the larger sizes everywhere (a kiosk, an accessibility setting), `'compact'` keeps the mouse sizes even on touch. The hit-area minimum follows the pointer alone, so a compact face on a phone is still 44px to hit. Walls and sheens scale with the face, as they do between `sm` and `lg`; no tier or level changes.
|
|
111
|
+
|
|
112
|
+
Two breakpoints are the only widths a layout switches at: `breakpoint-narrow` (480px), for a component's own narrow layout, and `breakpoint-compact` (640px), for page chrome (the sidebar becomes a drawer, a panel becomes a sheet). CSS can't read a token in a media query, so they're written as numbers, and `checks/density.mjs` fails on any other.
|
|
113
|
+
|
|
114
|
+
**Safe areas.** In an edge-to-edge page (`viewport-fit=cover`), layers fixed to an edge (Toaster, SaveBar, Sheet, Dialog) clear the notch and the home indicator through `env(safe-area-inset-*)`. Pages that don't opt in are unaffected.
|
|
115
|
+
|
|
116
|
+
## Your own fonts
|
|
117
|
+
|
|
118
|
+
`altitude.css` loads Instrument Sans and JetBrains Mono from Google Fonts. To make no third-party request, import `altitude-design/altitude.no-fonts.css` instead (the same tokens and styles without the `@import`) and either self-host those two families or point the type at your own:
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
:root {
|
|
122
|
+
--font-sans: "Avenir Next", ui-sans-serif, system-ui, sans-serif;
|
|
123
|
+
--font-mono: ui-monospace, "SF Mono", Menlo, monospace;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Both tokens already fall back to system fonts, so without either the components still render, in the system face. `altitude-design/fonts.css` is the font import on its own, if you pair `tokens.css` with your own styles but want the fonts. Typography carries no elevation signal, so nothing about height changes with the face.
|
|
128
|
+
|
|
129
|
+
## With shadcn/ui
|
|
130
|
+
|
|
131
|
+
The props mirror shadcn/ui, and the tokens can drive shadcn's own components. Both systems define a variable called `--accent` (in shadcn it means a hover tint), so don't redefine shadcn's `:root` variables on top of Altitude's. Point Tailwind's theme straight at Altitude's tokens instead (Tailwind v4, shadcn's current setup), and delete shadcn's generated color variables:
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
@theme inline {
|
|
135
|
+
--color-background: var(--surface-ground);
|
|
136
|
+
--color-foreground: var(--ink);
|
|
137
|
+
--color-card: var(--surface-raised);
|
|
138
|
+
--color-card-foreground: var(--ink);
|
|
139
|
+
--color-popover: var(--surface-floating);
|
|
140
|
+
--color-popover-foreground: var(--ink);
|
|
141
|
+
--color-primary: var(--accent);
|
|
142
|
+
--color-primary-foreground: var(--on-accent);
|
|
143
|
+
--color-secondary: var(--surface-raised);
|
|
144
|
+
--color-secondary-foreground: var(--ink);
|
|
145
|
+
--color-muted: var(--surface-well);
|
|
146
|
+
--color-muted-foreground: var(--ink-muted);
|
|
147
|
+
--color-accent: var(--accent-soft);
|
|
148
|
+
--color-accent-foreground: var(--ink);
|
|
149
|
+
--color-destructive: var(--critical);
|
|
150
|
+
--color-border: var(--line);
|
|
151
|
+
--color-input: var(--line-control);
|
|
152
|
+
--color-ring: var(--accent-ink);
|
|
153
|
+
|
|
154
|
+
/* shadcn's shadow utilities, re-pointed at the ladder */
|
|
155
|
+
--shadow-xs: var(--elev-1);
|
|
156
|
+
--shadow-sm: var(--elev-1);
|
|
157
|
+
--shadow-md: var(--elev-2);
|
|
158
|
+
--shadow-lg: var(--elev-3);
|
|
159
|
+
--shadow-xl: var(--elev-4);
|
|
160
|
+
--shadow-2xl: var(--elev-5);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
That gets shadcn's components onto Altitude's colors and shadows. The elevation behavior (hover lift, press to ground, wells for inputs, the receding page) lives in Altitude's component styles, so shadcn components adopted this way still need those states added to their class lists.
|
|
165
|
+
|
|
166
|
+
### Charts
|
|
167
|
+
|
|
168
|
+
shadcn/ui's `Chart` is Recharts underneath (Tremor wraps Recharts too), so charts already built with it can take Altitude's colours and most of its chart rules without being rewritten. What they can't take is listed at the end. [`examples/shadcn-chart`](https://github.com/richlaconte/altitude/blob/v0.4.0/examples/shadcn-chart) is this setup, checked in both schemes by `build/checks/shadcn-chart.mjs`.
|
|
169
|
+
|
|
170
|
+
**Map the palette.** Add the chart lines to the `@theme inline` block above, so Tailwind's `fill-chart-1` and the like are Altitude's slots:
|
|
171
|
+
|
|
172
|
+
```css
|
|
173
|
+
@theme inline {
|
|
174
|
+
--color-chart-1: var(--data-1);
|
|
175
|
+
--color-chart-2: var(--data-2);
|
|
176
|
+
--color-chart-3: var(--data-3);
|
|
177
|
+
--color-chart-4: var(--data-4);
|
|
178
|
+
--color-chart-5: var(--data-5);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
shadcn's chart configs name the variables directly (`color: "var(--chart-1)"`), so point those at the palette too, on every element a scheme can be set on:
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
:root, [data-theme] {
|
|
186
|
+
--chart-1: var(--data-1);
|
|
187
|
+
--chart-2: var(--data-2);
|
|
188
|
+
--chart-3: var(--data-3);
|
|
189
|
+
--chart-4: var(--data-4);
|
|
190
|
+
--chart-5: var(--data-5);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
On `:root` alone, a `nightfall` panel inside a `daylight` page would draw daylight's colours: a variable that refers to another is resolved where it's declared, and children inherit the result. `AltitudeProvider` always sets `data-theme` on its root, custom accent and custom scheme included, so `[data-theme]` covers them.
|
|
195
|
+
|
|
196
|
+
Delete shadcn's own `--chart-*` values, in `:root` and `.dark` alike. They aren't checked against Altitude's grounds, its status hues or its reserved hues (`accent`, `presence-*`), and the five Altitude slots are (see **The data palette**). A sixth series is `data-6`; past that, fold into `data-other`.
|
|
197
|
+
|
|
198
|
+
**Recharts props that keep Altitude's rules.** The rules are in [Charts](Charts.md) ("The rules"). These are documentation, not code; set them on your own charts:
|
|
199
|
+
|
|
200
|
+
| Rule | Recharts |
|
|
201
|
+
|---|---|
|
|
202
|
+
| Recessive grid | `<CartesianGrid vertical={false} stroke="var(--line)" />` |
|
|
203
|
+
| Ground line | `<ReferenceLine y={0} stroke="var(--ink-muted)" />` |
|
|
204
|
+
| Flat marks, rounded only at the data end | `<Bar radius={[4, 4, 0, 0]} />`; no `<defs>` gradients on `<Area>`, a flat `fill` instead |
|
|
205
|
+
| Readout at level 3 | `<ChartTooltipContent indicator="line" className="bg-popover shadow-lg" />`: `surface-floating` and `elev-3` through the mapping above |
|
|
206
|
+
| Hover is a promise | `cursor={false}` on a bar chart's `<ChartTooltip>` (no band behind the category); on a line or area chart `cursor={{ style: { stroke: 'var(--line-control)' } }}`, the crosshair, and `activeDot={false}` on each `<Line>` and `<Area>` |
|
|
207
|
+
| Keyboard | keep `accessibilityLayer` (on by default in Recharts 3): the chart is one tab stop and the arrow keys move the readout |
|
|
208
|
+
| Reduced motion | `isAnimationActive={!prefersReducedMotion}` on every mark, from a `matchMedia('(prefers-reduced-motion: reduce)')` hook of your own |
|
|
209
|
+
| Text in ink | `tick={{ fill: 'var(--ink-muted)' }}` on each axis, never the series colour |
|
|
210
|
+
|
|
211
|
+
Three things the example found:
|
|
212
|
+
|
|
213
|
+
- **Pass `tick` yourself.** `ChartContainer` colours ticks with a selector for `.recharts-cartesian-axis-tick text`. Recharts 3.10 draws tick text in a separate group, so that selector no longer matches and ticks fall back to Recharts' grey.
|
|
214
|
+
- **Colour the crosshair with `style`.** `ChartContainer`'s stylesheet colours it `border`, and a stylesheet beats Recharts' `stroke` attribute; an inline style beats both.
|
|
215
|
+
- **Give an `<Area>` its series colour as `stroke`.** The legend's swatch and the readout's indicator are taken from the stroke, so a stroke in the plot's colour (to part stacked areas, as Altitude's `AreaChart` does) leaves them blank.
|
|
216
|
+
|
|
217
|
+
**What doesn't carry over.** These need Altitude's own chart components (`ChartContainer` and the charts inside it) or code of your own:
|
|
218
|
+
|
|
219
|
+
- **Marks that act.** A clickable mark's 2px `ink` outline and pointer cursor, its action in the readout, and selection that recedes the rest are your own code.
|
|
220
|
+
- **Legend keys as switches.** shadcn's legend only labels; toggling a series is your own state.
|
|
221
|
+
- **Waiting and absence.** No Suspense imprint at the chart's size and no `EmptyState` opening; Recharts draws empty axes.
|
|
222
|
+
- **A table view and a written summary** for every chart.
|
|
223
|
+
- **Afterglows and staged rescales.** Recharts animates every change the same way, and on first render grows marks from the baseline unless animation is off.
|
|
224
|
+
- **Patterns under forced colours, and direct labels** so no series is told apart by colour alone.
|
|
225
|
+
|
|
226
|
+
## Charts Altitude doesn't draw
|
|
227
|
+
|
|
228
|
+
Altitude's charts are SVG, and they stop where SVG does: tens of thousands of points, maps, sankeys, treemaps, candlesticks. For those, use a canvas library. `altitude-design/chart-themes` gives ECharts and Chart.js Altitude's palette, type, surfaces and motion, read from the tokens of the active scheme, so nobody copies hex values. It returns plain objects and imports neither library.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import * as echarts from 'echarts';
|
|
232
|
+
import { echartsTheme } from 'altitude-design/chart-themes';
|
|
233
|
+
|
|
234
|
+
echarts.registerTheme('altitude', echartsTheme(document.documentElement)); // reads the computed tokens of the active scheme
|
|
235
|
+
const chart = echarts.init(el, 'altitude');
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Pass the chart's own element (or anything inside an `AltitudeProvider`) to read that scheme and accent, or pass the tokens by name (`{ 'data-1': '#…', ink: '#…', … }`, as `checkScheme` takes them) where there's no DOM.
|
|
239
|
+
|
|
240
|
+
| | `echartsTheme(source, options?)` | `chartjsDefaults(source, options?)` |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| Use | `echarts.init(el, theme)` or `registerTheme` | `Chart.defaults.set(defaults)` |
|
|
243
|
+
| Series | `color`: `data-1` … `data-6`, then `data-other` | none: Chart.js has no palette default, so give each dataset a colour from `dataColors(source)` |
|
|
244
|
+
| Magnitude | `visualMap.inRange.color`: `data-seq-1` … `data-seq-7` (swap in `data-div-*` for polarity) | |
|
|
245
|
+
| Text | `--font-sans`; `ink`, axis labels `ink-muted` | `--font-sans`; `ink-muted`, legend `ink` |
|
|
246
|
+
| Axes and grid | axis lines `line-control`, split lines `line` | `scale.border` `line-control`, `scale.grid` `line` |
|
|
247
|
+
| Readout | `surface-floating` with `extraCssText: box-shadow: <elev-3>`; a `line-control` crosshair | `surface-floating`, in `ink` (a canvas readout can't cast a shadow) |
|
|
248
|
+
| Accessibility | `aria.enabled`; `aria.decal.show` under forced colours or with `{ patterns: true }` | |
|
|
249
|
+
| Motion | `dur-travel-3` on ease-glide's curve; 0 under reduced motion | the same; `animation: false` under reduced motion |
|
|
250
|
+
|
|
251
|
+
Reduced motion is the provider's `motion` if it sets one, else the system's; forced colours is the system's. Both can be passed in `options` instead.
|
|
252
|
+
|
|
253
|
+
**Re-theme on scheme change.** The objects are read once. `useChartTheme` re-reads them when an `AltitudeProvider`'s `scheme`, `accent` or `motion` changes, a `data-theme` changes on the page, or the reduced-motion or forced-colours setting does. It's `null` until mounted:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { echartsTheme, useChartTheme } from 'altitude-design/chart-themes';
|
|
257
|
+
|
|
258
|
+
function Flows({ option }: { option: echarts.EChartsOption }) {
|
|
259
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
260
|
+
const theme = useChartTheme(echartsTheme, ref);
|
|
261
|
+
React.useEffect(() => {
|
|
262
|
+
if (!theme) return;
|
|
263
|
+
const chart = echarts.init(ref.current!, theme);
|
|
264
|
+
chart.setOption(option);
|
|
265
|
+
return () => chart.dispose();
|
|
266
|
+
}, [theme, option]);
|
|
267
|
+
return <div ref={ref} style={{ height: 360 }} />;
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
What Altitude can't vouch for:
|
|
272
|
+
|
|
273
|
+
- **Keyboard and screen readers.** ECharts' `aria` writes a description of the chart for screen readers, and only if its `AriaComponent` is imported (`echarts.use([AriaComponent])` with the modular build). Neither library gives a keyboard path through the data the way Altitude's charts do. Whatever keyboard support the library has is what you get, so add a table view of the data yourself.
|
|
274
|
+
- **The rest of [Charts](Charts.md).** A theme can't keep you from a dual axis, a pie, a gradient or a 3D bar. The rules still apply, and choosing the form is still yours.
|
|
275
|
+
|
|
276
|
+
**Observable Plot and Vega-Lite** need no helper. Pass the palette and ink:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { dataColors, readChartTokens } from 'altitude-design/chart-themes';
|
|
280
|
+
const t = readChartTokens(el);
|
|
281
|
+
|
|
282
|
+
// Observable Plot
|
|
283
|
+
Plot.plot({ color: { range: dataColors(el) }, style: { color: t['ink-muted'], fontFamily: t['font-sans'], background: 'transparent' }, marks });
|
|
284
|
+
|
|
285
|
+
// Vega-Lite: the config block
|
|
286
|
+
const config = {
|
|
287
|
+
background: 'transparent', font: t['font-sans'],
|
|
288
|
+
range: { category: dataColors(el), ramp: [1, 2, 3, 4, 5, 6, 7].map((n) => t[`data-seq-${n}`]) },
|
|
289
|
+
axis: { domainColor: t['line-control'], tickColor: t['line-control'], gridColor: t.line, labelColor: t['ink-muted'], titleColor: t.ink },
|
|
290
|
+
legend: { labelColor: t.ink, titleColor: t.ink }, title: { color: t.ink },
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Rich text, code and canvases
|
|
295
|
+
|
|
296
|
+
Altitude doesn't own a rich text editor, a code editor or a node canvas: each is a large engine of its own. It ships **adapters** instead, so the one part of the screen drawn by another library still follows the elevation model. Each adapter reads the active scheme's custom properties, so both schemes, custom accents and `contrast="more"` work. The library is an optional peer dependency your app installs; the adapters import nothing else, and they leave the library's own keyboard support and ARIA alone. Import a CSS adapter after `altitude.css`.
|
|
297
|
+
|
|
298
|
+
### Tiptap
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import 'altitude-design/altitude.css';
|
|
302
|
+
import 'altitude-design/adapters/tiptap.css';
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Styles Tiptap's `.tiptap` editor (ProseMirror, Tiptap 2 or later):
|
|
306
|
+
|
|
307
|
+
- **The editor is an opening**, built like `Textarea`: empty, it's a well showing its inside wall; once it holds content the floor rises flush; focus lights the floor and keyboard focus adds the 2px neutral rim. `aria-invalid="true"` on the editor drops it to a trench.
|
|
308
|
+
- **Content** follows Altitude's type: links in `accent-ink`, inline code on a mono plate, code blocks on `CodeBlock`'s ground, mentions (`@tiptap/extension-mention`) as flush Tag plates, Tiptap's Placeholder in `ink-muted`.
|
|
309
|
+
- **The formatting bar is yours**, built from `Toolbar` and `ToolbarToggle`, so each mark is a latched toggle: pass `pressed={editor.isActive('bold')}`.
|
|
310
|
+
- **The mention list is yours too.** Render it with class `alt-rte-mentions`, `role="listbox"` and options with `aria-selected`, and it floats at level 3 like `Combobox`'s list.
|
|
311
|
+
|
|
312
|
+
What it can't cover: Tiptap's menus beyond mentions (bubble and floating menus are your markup: build them from `Popover` and `Toolbar`), and anything a custom extension renders itself.
|
|
313
|
+
|
|
314
|
+
### CodeMirror
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { EditorView } from '@codemirror/view';
|
|
318
|
+
import { altitudeTheme } from 'altitude-design/adapters/codemirror';
|
|
319
|
+
|
|
320
|
+
new EditorView({ parent, extensions: [altitudeTheme(), /* your language, lint, keymaps */] });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`altitudeTheme()` is a CodeMirror 6 extension: a theme plus a syntax highlight style. Every value is one of the scheme's custom properties, so the editor follows the active scheme, a custom accent and `contrast="more"` without being re-created. `altitudeEditorTheme` and `altitudeHighlightStyle` are exported separately if you compose your own.
|
|
324
|
+
|
|
325
|
+
- **The editor is an opening**: a well with its inside wall; focus lights the floor and keyboard focus adds the 2px neutral rim.
|
|
326
|
+
- **Gutter and lines are ground.** The active line is a tint, never a height; the selection is accent.
|
|
327
|
+
- **Syntax uses `ink`, `ink-muted` and `accent-ink` only**: keywords and literals in accent-ink, comments and punctuation in ink-muted, the rest in ink. Code is text to read, not data to compare, so there are no `data-*` hues.
|
|
328
|
+
- **Lint errors** (`@codemirror/lint`) sink their gutter marker into a trench on `critical-soft`, underline the range in critical, and keep the message in words in the tooltip.
|
|
329
|
+
- **Tooltips and completion lists** float at level 3.
|
|
330
|
+
|
|
331
|
+
What it can't cover: the editor's state changes without a transition, because a CodeMirror theme can't scope a reduced-motion query to the editor itself. If you add `drawSelection()`, its blinking cursor is CodeMirror's own. Search and other panels take ground and line but keep CodeMirror's layout.
|
|
332
|
+
|
|
333
|
+
### React Flow
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import '@xyflow/react/dist/base.css';
|
|
337
|
+
import 'altitude-design/altitude.css';
|
|
338
|
+
import 'altitude-design/adapters/react-flow.css';
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Styles a React Flow 12 (`@xyflow/react`) canvas by setting its `--xy-*` properties from the active scheme and styling its elements:
|
|
342
|
+
|
|
343
|
+
- **The canvas is ground**, with a dot grid in `line-control` (`<Background variant="dots" />`).
|
|
344
|
+
- **Nodes are Cards at level 1.** A node you can select lifts one level when pointed at; a dragged node is in your hand at level 3; selection is a 2px accent rim, never extra height.
|
|
345
|
+
- **Status uses the commitment signal**: give a running node `className: 'alt-node-pending'` (it hovers, after the grace period) and a failed one `'alt-node-failed'` (it drops into a trench on `critical-soft`). Put the state in the node's words too ("Failed: token expired"); a failed edge can take `'alt-edge-failed'`.
|
|
346
|
+
- **Ports are openings**: small wells that deepen under an edge being drawn, and sink to a trench with the accent rim where it can land. The edge being drawn is dashed; edges are `line-control`, a selected edge `accent-ink`.
|
|
347
|
+
- **The minimap is a well** with the visible area outlined as its lens; the controls are a raised toolbar.
|
|
348
|
+
|
|
349
|
+
What it can't cover: custom node components draw themselves (build them from `Card` or `Surface`); a dragged node can't tilt, because React Flow owns its transform; and keyboard access is React Flow's own (Tab to a node, Enter to select, arrows to move), with no way to follow edges by keyboard, so offer a list or table of the same steps.
|