altitude-design 0.2.0 → 0.2.1
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 +6 -3
- package/dist/altitude.css +57 -15
- package/dist/altitude.iife.js +4 -4
- package/dist/altitude.no-fonts.css +57 -15
- package/dist/index.cjs +714 -426
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +714 -426
- package/dist/index.js.map +4 -4
- package/dist/types/collections.d.ts +6 -2
- package/dist/types/controls.d.ts +37 -8
- package/dist/types/index.d.ts +3 -1
- package/dist/types/overlays.d.ts +9 -2
- package/dist/types/suspense.d.ts +74 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A React component library where **height is the interface**. Its one idea: the ground is the record. Settled content sits on the ground; anything live rises above it; anything waiting to receive sinks below it.
|
|
4
4
|
|
|
5
|
-
- **
|
|
5
|
+
- **Site:** https://richlaconte.github.io/altitude/
|
|
6
|
+
- **Storybook:** every component, with its guidelines and a live preview in all four schemes: https://richlaconte.github.io/altitude/storybook/
|
|
6
7
|
- **The design guide:** [`project/README.md`](https://github.com/richlaconte/altitude/blob/main/project/README.md), then [Elevation](https://github.com/richlaconte/altitude/blob/main/project/Elevation.md), [Motion](https://github.com/richlaconte/altitude/blob/main/project/Motion.md), [Theming](https://github.com/richlaconte/altitude/blob/main/project/Theming.md) and [Research](https://github.com/richlaconte/altitude/blob/main/project/Research.md)
|
|
7
8
|
|
|
8
9
|
## Install
|
|
@@ -44,7 +45,8 @@ Render `AltitudeProvider` once around your app. Schemes are `daylight`, `nightfa
|
|
|
44
45
|
project/ the design system itself: guides, tokens.json, components/src/*.tsx, bundle.css,
|
|
45
46
|
and the built bundle.js and previews (the same files as the design system on claude.ai)
|
|
46
47
|
build/ the kit that builds and checks project/ (see build/SETUP.md)
|
|
47
|
-
scripts/ the npm package build
|
|
48
|
+
scripts/ the npm package build, the Storybook story generator and the landing page build
|
|
49
|
+
site/ the landing page (React, built from the components' own sources into site-dist/)
|
|
48
50
|
.storybook/
|
|
49
51
|
```
|
|
50
52
|
|
|
@@ -55,6 +57,7 @@ There is one source for each thing. Components live in `project/components/src`,
|
|
|
55
57
|
```sh
|
|
56
58
|
npm install && npm install --prefix build
|
|
57
59
|
npm run storybook # http://localhost:6006
|
|
60
|
+
npm run build-site # the landing page, into site-dist/index.html
|
|
58
61
|
```
|
|
59
62
|
|
|
60
63
|
After changing a component, rebuild the design system's bundle and previews, and commit what they produce:
|
|
@@ -71,7 +74,7 @@ Every push and pull request runs the checks: the bundle and previews are rebuilt
|
|
|
71
74
|
|
|
72
75
|
A push to `main` then also:
|
|
73
76
|
|
|
74
|
-
- deploys
|
|
77
|
+
- deploys the landing page to GitHub Pages, with Storybook under `/storybook/`;
|
|
75
78
|
- publishes to npm **if the version in `package.json` isn't published yet**, and tags the commit `v<version>`. To release, bump the version (`npm version patch`, `minor` or `major`) and push.
|
|
76
79
|
|
|
77
80
|
## Found a bug, or need something it doesn't do?
|
package/dist/altitude.css
CHANGED
|
@@ -300,16 +300,19 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
300
300
|
transition: box-shadow var(--dur-travel-1) var(--ease-rise), background-color var(--dur-travel-1) var(--ease-rise),
|
|
301
301
|
color var(--dur-travel-1) var(--ease-rise), transform var(--dur-travel-2) var(--ease-settle);
|
|
302
302
|
}
|
|
303
|
-
/* Focus ring: wraps the whole slab, face and wall, so it follows the button's real shape at every height.
|
|
303
|
+
/* Focus ring: wraps the whole slab, face and wall, so it follows the button's real shape at every height. It's an
|
|
304
|
+
outline 2px off a box that covers the face and the wall, because an outline never adds to the page's scrollable
|
|
305
|
+
area: a button at the edge of a narrow screen must not give the page a sideways scroll. At rest the box is the face
|
|
306
|
+
alone, so only a keyboard-focused button's wall (the box's bottom edge, at most 5px) reaches past its own box. */
|
|
304
307
|
.alt-btn:focus-visible, .alt-crumbs button:focus-visible { outline: none; }
|
|
305
308
|
.alt-btn::after, .alt-crumbs button::after {
|
|
306
309
|
content: ""; position: absolute; pointer-events: none; opacity: 0;
|
|
307
|
-
inset:
|
|
308
|
-
transition: opacity var(--dur-travel-1) var(--ease-rise),
|
|
310
|
+
inset: 0; outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-md);
|
|
311
|
+
transition: opacity var(--dur-travel-1) var(--ease-rise), bottom var(--dur-press) ease-out;
|
|
309
312
|
}
|
|
310
|
-
.alt-btn[data-size="sm"]::after, .alt-crumbs button::after { border-radius:
|
|
311
|
-
.alt-btn:focus-visible::after, .alt-crumbs button:focus-visible::after { opacity: 1; }
|
|
312
|
-
.alt-btn:active::after, .alt-btn[data-loading]::after {
|
|
313
|
+
.alt-btn[data-size="sm"]::after, .alt-crumbs button::after { border-radius: var(--radius-sm); }
|
|
314
|
+
.alt-btn:focus-visible::after, .alt-crumbs button:focus-visible::after { opacity: 1; bottom: calc(-1 * var(--wall, 0px) - 1px); }
|
|
315
|
+
.alt-btn:active::after, .alt-btn[data-loading]::after { bottom: 0; }
|
|
313
316
|
.alt-btn-label { display: inline-flex; align-items: center; gap: var(--space-2); transition: opacity var(--dur-travel-1) var(--ease-rise); }
|
|
314
317
|
.alt-btn-label > svg { flex: none; margin-inline: -2px; }
|
|
315
318
|
.alt-btn[data-variant="primary"] {
|
|
@@ -705,7 +708,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
705
708
|
.alt-avatar[data-presence] .alt-avatar-face { outline: 2px solid var(--alt-presence); outline-offset: 1px; }
|
|
706
709
|
.alt-avatar-lamp {
|
|
707
710
|
--b-lamp: var(--positive);
|
|
708
|
-
position: absolute; z-index: 2; right:
|
|
711
|
+
position: absolute; z-index: 2; right: 0; bottom: 0; width: max(8px, calc(var(--size) * 0.24)); height: max(8px, calc(var(--size) * 0.24)); border-radius: var(--radius-full);
|
|
709
712
|
background: var(--b-lamp); box-shadow: 0 0 0 2px var(--surface-ground); transform: translateY(calc(-1 * var(--lift)));
|
|
710
713
|
}
|
|
711
714
|
.alt-avatar[data-status="idle"] .alt-avatar-lamp { --b-lamp: var(--caution); }
|
|
@@ -713,15 +716,18 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
713
716
|
.alt-avatar[data-interactive] { cursor: pointer; outline: none; }
|
|
714
717
|
.alt-avatar[data-interactive]:focus-visible .alt-avatar-face { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
|
|
715
718
|
.alt-avatar-group { isolation: isolate; display: inline-flex; align-items: flex-end; padding: calc(var(--size, 36px) * 0.32) 0 calc(var(--size, 36px) * 0.1); }
|
|
716
|
-
.alt-avatar-group
|
|
717
|
-
.
|
|
719
|
+
.alt-avatar-group .alt-avatar { isolation: auto; }
|
|
720
|
+
/* Faces overlap by a fifth. An avatar that waited for its data sits inside its loading wrapper (display: contents at
|
|
721
|
+
rest), so the overlap is counted from the group's own children either way, imprints included. */
|
|
722
|
+
.alt-avatar-group > .alt-avatar:not(:first-child),
|
|
723
|
+
.alt-avatar-group > :is(.alt-await, .alt-await-wait):not(:first-child) :is(.alt-avatar, .alt-avatar-imprint) { margin-left: calc(var(--size, 36px) * -0.22); }
|
|
718
724
|
/* Overlapping faces are cut out of each other with a ring of ground; higher faces sit in front. */
|
|
719
|
-
.alt-avatar-group
|
|
720
|
-
.alt-avatar-group
|
|
721
|
-
.alt-avatar-group
|
|
722
|
-
.alt-avatar-group
|
|
723
|
-
.alt-avatar-group
|
|
724
|
-
.alt-avatar-group
|
|
725
|
+
.alt-avatar-group .alt-avatar .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), inset 0 0 0 1px var(--alt-ring); }
|
|
726
|
+
.alt-avatar-group .alt-avatar[data-status="offline"] .alt-avatar-face { box-shadow: 0 0 0 2px var(--surface-ground), inset 0 0 0 1px var(--alt-ring), inset 0 1px 2px var(--alt-cast); }
|
|
727
|
+
.alt-avatar-group .alt-avatar[data-status="active"] .alt-avatar-face { z-index: 3; }
|
|
728
|
+
.alt-avatar-group .alt-avatar[data-status="idle"] .alt-avatar-face { z-index: 2; }
|
|
729
|
+
.alt-avatar-group .alt-avatar:not([data-tint="more"]):hover { --hr: 0.32; }
|
|
730
|
+
.alt-avatar-group .alt-avatar:not([data-tint="more"]):hover .alt-avatar-face { z-index: 5; }
|
|
725
731
|
.alt-avatar-group .alt-avatar-lamp { display: none; }
|
|
726
732
|
|
|
727
733
|
/* ── Skeleton: an imprint of the content to come, pressed into the page. Light passes over it while it waits; when the
|
|
@@ -745,6 +751,38 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
745
751
|
}
|
|
746
752
|
@keyframes alt-rise-out { from { transform: translateY(calc(100% * var(--alt-travel))); opacity: 0.25; } }
|
|
747
753
|
|
|
754
|
+
/* ── Suspense: imprints that come with the component. A component given a promise (Avatar's name or src, a Badge's or
|
|
755
|
+
a card title's text, a DataTable's rows) holds its own Suspense boundary, and its fallback is its own imprint, drawn
|
|
756
|
+
from the same props and tokens, so it's the size and shape of what's coming. Card and CardContent hold one for
|
|
757
|
+
anything of yours that suspends inside them. An imprint waits dur-grace before it shows, so quick loads show nothing;
|
|
758
|
+
when content lands it rises 8px out of its imprint as the imprint fills in behind it, within the attention budget.
|
|
759
|
+
Containers just let their content rise. In a RevealGroup, content that lands early stays an imprint until the rest
|
|
760
|
+
arrive. At rest the wrapper is display: contents: it adds nothing to layout or to your selectors' parent chain. ── */
|
|
761
|
+
.alt-await-wait { display: block; animation: alt-onset var(--dur-travel-1) ease-out var(--dur-grace) backwards; }
|
|
762
|
+
.alt-await-wait[data-inline] { display: inline-block; vertical-align: middle; line-height: 0; }
|
|
763
|
+
.alt-await, .alt-await > .alt-await-face { display: contents; }
|
|
764
|
+
.alt-await[data-phase] { display: block; position: relative; isolation: isolate; }
|
|
765
|
+
.alt-await[data-phase] > .alt-await-face { display: block; }
|
|
766
|
+
.alt-await[data-phase][data-inline], .alt-await[data-phase][data-inline] > .alt-await-face { display: inline-block; vertical-align: middle; line-height: 0; }
|
|
767
|
+
.alt-await-imprint { position: absolute; inset: 0; z-index: -1; }
|
|
768
|
+
.alt-await[data-phase="held"] > .alt-await-face { position: absolute; inset: 0 0 auto; visibility: hidden; }
|
|
769
|
+
.alt-await-held { display: block; }
|
|
770
|
+
.alt-await-held[data-early] { animation: alt-onset var(--dur-travel-1) ease-out var(--alt-grace-left, 0ms) backwards; }
|
|
771
|
+
.alt-await[data-inline] > .alt-await-held { display: inline-block; vertical-align: middle; line-height: 0; }
|
|
772
|
+
.alt-await[data-phase="reveal"] > .alt-await-face { --alt-from-y: 8px; animation: alt-emerge var(--dur-travel-3) var(--ease-rise) var(--alt-rise-delay, 0ms) backwards; }
|
|
773
|
+
.alt-await[data-phase="reveal"] > .alt-await-imprint { animation: alt-await-fill var(--dur-travel-3) linear var(--alt-rise-delay, 0ms) forwards; }
|
|
774
|
+
@keyframes alt-await-fill { to { opacity: 0; } }
|
|
775
|
+
/* Text imprints are as tall as the text they stand for: one line-height of the surrounding type per line. */
|
|
776
|
+
.alt-await-lines { display: flex; flex-direction: column; justify-content: space-around; height: calc(var(--lines, 1) * 1lh); max-width: 100%; }
|
|
777
|
+
.alt-await-lines > .alt-skeleton { flex: none; height: 0.6em; }
|
|
778
|
+
.alt-await-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-5); }
|
|
779
|
+
.alt-await-card-body { font: 400 15px/23px var(--font-sans); }
|
|
780
|
+
.alt-avatar-imprint { display: inline-block; vertical-align: middle; }
|
|
781
|
+
.alt-badge-imprint { display: inline-block; vertical-align: middle; width: 64px; height: 22px; }
|
|
782
|
+
.alt-badge-imprint[data-variant="count"] { width: 22px; border-radius: var(--radius-full); }
|
|
783
|
+
.alt-check-imprint { display: block; width: 22px; height: 22px; border-radius: var(--radius-xs); }
|
|
784
|
+
.alt-cell-imprint { display: block; height: 0.6em; margin: calc((1lh - 0.6em) / 2) 0; }
|
|
785
|
+
|
|
748
786
|
/* ── Alert: a sheet laid on the page. Status that informs is a thin tinted sheet resting on the ground — you can see
|
|
749
787
|
its paper edge and the contact shadow under it — and it never floats. Status that asks you to act is a raised
|
|
750
788
|
card. Dismissing lifts the sheet off the page and the gap closes behind it. Several alerts make an AlertStack:
|
|
@@ -942,6 +980,9 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
942
980
|
}
|
|
943
981
|
.alt-dialog-head { display: flex; flex-direction: column; gap: 6px; padding-right: var(--space-8); }
|
|
944
982
|
.alt-dialog-title { margin: 0; font: 600 19px/26px var(--font-sans); letter-spacing: -0.01em; }
|
|
983
|
+
/* A title that takes focus (initialFocus="title") isn't a control: no ring after a click, the usual ring from the keyboard. */
|
|
984
|
+
.alt-dialog-title:focus { outline: none; }
|
|
985
|
+
.alt-dialog-title:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: var(--radius-xs); }
|
|
945
986
|
.alt-dialog-desc { margin: 0; font: 400 14px/21px var(--font-sans); color: var(--ink-muted); }
|
|
946
987
|
.alt-dialog-body { margin-top: var(--space-5); font: 400 15px/23px var(--font-sans); }
|
|
947
988
|
.alt-dialog-footer { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-6); }
|
|
@@ -1631,6 +1672,7 @@ body { margin: 0; background: var(--surface-ground); color: var(--ink); font-fam
|
|
|
1631
1672
|
/* Forced colours (Windows High Contrast) drop every box-shadow, which erases elevation entirely
|
|
1632
1673
|
(measured: the ladder renders as blank). Height falls back to border weight and style. */
|
|
1633
1674
|
@media (forced-colors: active) {
|
|
1675
|
+
.alt-skeleton { outline: 1px dashed CanvasText; outline-offset: -1px; }
|
|
1634
1676
|
.alt-surface[data-level="1"], .alt-surface[data-level="2"], .alt-card, .alt-btn:not([data-variant="ghost"]), .alt-tabs-indicator,
|
|
1635
1677
|
.alt-switch-thumb, .alt-slider-thumb, .alt-kbd, .alt-avatar-face, .alt-sort-item, .alt-pile-top, .alt-pile-sheet, .alt-scroll { border: 1px solid CanvasText; }
|
|
1636
1678
|
.alt-surface[data-level="3"], .alt-surface[data-level="4"], .alt-pop, .alt-tip, .alt-toast, .alt-sort-item[data-held], [data-held] { border: 2px solid CanvasText; }
|