@hanzo/ui 8.0.46 → 8.0.48

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.
@@ -10,22 +10,31 @@ const jsx_runtime_1 = require("react/jsx-runtime");
10
10
  *
11
11
  * It is a surface, not a layout: it composes AROUND content and adds nothing
12
12
  * but the material. The component itself is a solid raised panel; the
13
- * translucency + blur live in theme.css under `@supports (backdrop-filter)`,
14
- * keyed by [data-slot="glass"] — so a platform that cannot blur (older
15
- * engines, native without a blur view) keeps the solid panel and content
16
- * never becomes illegible over a busy backdrop. One material, declared once.
13
+ * translucency + blur live in glass.css under `@supports (backdrop-filter)`,
14
+ * keyed by [data-slot="glass"] — so a platform that cannot blur (older engines,
15
+ * native without a blur view) keeps the solid panel and content never becomes
16
+ * illegible over a busy backdrop. One material, declared once.
17
+ *
18
+ * The look comes from `glass(level)` in `@hanzo/ui/glass`, not from a `styled`
19
+ * of its own. It used to be both, and they disagreed: the frame's stand-in
20
+ * ground was `$color2` while the material is 72% of `--background`, so a
21
+ * browser that could blur and one that could not showed two different colours
22
+ * of menu. A component and a recipe describing the same material in two places
23
+ * is how they end up describing two materials. This is the component FORM of
24
+ * that value — it exists so a surface built from scratch does not have to reach
25
+ * for an `XStack` and remember to spread. Every surface the library already
26
+ * names (dialog, popover, select, dropdown, tooltip) is glass by slot and needs
27
+ * neither.
17
28
  *
18
29
  * Page sections never wear glass; they sit on the surface ladder ($color2/3).
19
30
  */
20
31
  const gui_1 = require("@hanzo/gui");
32
+ const glass_1 = require("../../glass.cjs");
21
33
  const slot_1 = require("./slot.cjs");
22
- const GlassFrame = (0, gui_1.styled)(gui_1.YStack, {
23
- name: 'Glass',
24
- borderWidth: 1,
25
- borderColor: '$borderColor',
26
- // The stand-in ground; theme.css swaps it for the blurred material where
27
- // backdrop-filter is real.
28
- bg: '$color2',
29
- });
30
- const Glass = (props) => (0, jsx_runtime_1.jsx)(GlassFrame, { ...(0, slot_1.slot)('glass'), ...props });
34
+ /**
35
+ * The border WIDTH is geometry and belongs to the component, not to the
36
+ * material — a sticky bar cut from the same glass wants one edge, not four —
37
+ * so the recipe states only the colour and this states the default width.
38
+ */
39
+ const Glass = ({ level = 2, ...props }) => ((0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('glass'), borderWidth: 1, ...(0, glass_1.glass)(level), ...props }));
31
40
  exports.Glass = Glass;
@@ -1,9 +1,40 @@
1
+ /**
2
+ * Glass — the ONE frosted material, for FLOATING chrome only (menus, dialogs,
3
+ * popovers, docked toolbars). macOS/iOS vibrancy: a translucent ground the
4
+ * backdrop blurs through, sealed with a hairline border.
5
+ *
6
+ * It is a surface, not a layout: it composes AROUND content and adds nothing
7
+ * but the material. The component itself is a solid raised panel; the
8
+ * translucency + blur live in glass.css under `@supports (backdrop-filter)`,
9
+ * keyed by [data-slot="glass"] — so a platform that cannot blur (older engines,
10
+ * native without a blur view) keeps the solid panel and content never becomes
11
+ * illegible over a busy backdrop. One material, declared once.
12
+ *
13
+ * The look comes from `glass(level)` in `@hanzo/ui/glass`, not from a `styled`
14
+ * of its own. It used to be both, and they disagreed: the frame's stand-in
15
+ * ground was `$color2` while the material is 72% of `--background`, so a
16
+ * browser that could blur and one that could not showed two different colours
17
+ * of menu. A component and a recipe describing the same material in two places
18
+ * is how they end up describing two materials. This is the component FORM of
19
+ * that value — it exists so a surface built from scratch does not have to reach
20
+ * for an `XStack` and remember to spread. Every surface the library already
21
+ * names (dialog, popover, select, dropdown, tooltip) is glass by slot and needs
22
+ * neither.
23
+ *
24
+ * Page sections never wear glass; they sit on the surface ladder ($color2/3).
25
+ */
26
+ import { YStack } from '@hanzo/gui';
1
27
  import type { ComponentProps } from 'react';
2
- declare const GlassFrame: import("@hanzogui/web").GuiComponent<import("@hanzogui/web").TamaDefer, import("@hanzogui/web").GuiElement, import("@hanzogui/core").RNGuiViewNonStyleProps, import("@hanzogui/web").StackStyleBase, {
3
- elevation?: number | import("@hanzogui/web").SizeTokens | undefined;
4
- fullscreen?: boolean | undefined;
5
- }, import("@hanzogui/web").StaticConfigPublic>;
6
- export type GlassProps = ComponentProps<typeof GlassFrame>;
7
- declare const Glass: (props: GlassProps) => import("react/jsx-runtime").JSX.Element;
28
+ import { type Lift } from '../../glass.js';
29
+ export type GlassProps = ComponentProps<typeof YStack> & {
30
+ /** 2 is anchored — a menu, a toast, a bar. 3 floats free over the page. */
31
+ level?: Lift;
32
+ };
33
+ /**
34
+ * The border WIDTH is geometry and belongs to the component, not to the
35
+ * material — a sticky bar cut from the same glass wants one edge, not four —
36
+ * so the recipe states only the colour and this states the default width.
37
+ */
38
+ declare const Glass: ({ level, ...props }: GlassProps) => import("react/jsx-runtime").JSX.Element;
8
39
  export { Glass };
9
40
  //# sourceMappingURL=glass.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"glass.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/glass.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAG3C,QAAA,MAAM,UAAU;;;8CAOd,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,CAAA;AAE1D,QAAA,MAAM,KAAK,GAAI,OAAO,UAAU,4CAAiD,CAAA;AAEjF,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"glass.d.ts","sourceRoot":"","sources":["../../../src/backends/gui/glass.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,KAAK,IAAI,EAAS,MAAM,aAAa,CAAA;AAG9C,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,GAAG;IACvD,2EAA2E;IAC3E,KAAK,CAAC,EAAE,IAAI,CAAA;CACb,CAAA;AAED;;;;GAIG;AACH,QAAA,MAAM,KAAK,GAAI,qBAAyB,UAAU,4CAEjD,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -7,23 +7,32 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  *
8
8
  * It is a surface, not a layout: it composes AROUND content and adds nothing
9
9
  * but the material. The component itself is a solid raised panel; the
10
- * translucency + blur live in theme.css under `@supports (backdrop-filter)`,
11
- * keyed by [data-slot="glass"] — so a platform that cannot blur (older
12
- * engines, native without a blur view) keeps the solid panel and content
13
- * never becomes illegible over a busy backdrop. One material, declared once.
10
+ * translucency + blur live in glass.css under `@supports (backdrop-filter)`,
11
+ * keyed by [data-slot="glass"] — so a platform that cannot blur (older engines,
12
+ * native without a blur view) keeps the solid panel and content never becomes
13
+ * illegible over a busy backdrop. One material, declared once.
14
+ *
15
+ * The look comes from `glass(level)` in `@hanzo/ui/glass`, not from a `styled`
16
+ * of its own. It used to be both, and they disagreed: the frame's stand-in
17
+ * ground was `$color2` while the material is 72% of `--background`, so a
18
+ * browser that could blur and one that could not showed two different colours
19
+ * of menu. A component and a recipe describing the same material in two places
20
+ * is how they end up describing two materials. This is the component FORM of
21
+ * that value — it exists so a surface built from scratch does not have to reach
22
+ * for an `XStack` and remember to spread. Every surface the library already
23
+ * names (dialog, popover, select, dropdown, tooltip) is glass by slot and needs
24
+ * neither.
14
25
  *
15
26
  * Page sections never wear glass; they sit on the surface ladder ($color2/3).
16
27
  */
17
- import { YStack, styled } from '@hanzo/gui';
28
+ import { YStack } from '@hanzo/gui';
29
+ import { glass } from '../../glass.js';
18
30
  import { slot } from './slot.js';
19
- const GlassFrame = styled(YStack, {
20
- name: 'Glass',
21
- borderWidth: 1,
22
- borderColor: '$borderColor',
23
- // The stand-in ground; theme.css swaps it for the blurred material where
24
- // backdrop-filter is real.
25
- bg: '$color2',
26
- });
27
- const Glass = (props) => _jsx(GlassFrame, { ...slot('glass'), ...props });
31
+ /**
32
+ * The border WIDTH is geometry and belongs to the component, not to the
33
+ * material — a sticky bar cut from the same glass wants one edge, not four —
34
+ * so the recipe states only the colour and this states the default width.
35
+ */
36
+ const Glass = ({ level = 2, ...props }) => (_jsx(YStack, { ...slot('glass'), borderWidth: 1, ...glass(level), ...props }));
28
37
  export { Glass };
29
38
  //# sourceMappingURL=glass.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glass.js","sourceRoot":"","sources":["../../../src/backends/gui/glass.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAE7B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE;IAChC,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,cAAc;IAC3B,yEAAyE;IACzE,2BAA2B;IAC3B,EAAE,EAAE,SAAS;CACd,CAAC,CAAA;AAIF,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAC,UAAU,OAAK,IAAI,CAAC,OAAO,CAAC,KAAM,KAAK,GAAI,CAAA;AAEjF,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"glass.js","sourceRoot":"","sources":["../../../src/backends/gui/glass.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGnC,OAAO,EAAa,KAAK,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAO7B;;;;GAIG;AACH,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,KAAK,EAAc,EAAE,EAAE,CAAC,CACrD,KAAC,MAAM,OAAK,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC,KAAM,KAAK,CAAC,KAAK,CAAC,KAAM,KAAK,GAAI,CAC3E,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
package/dist/glass.cjs ADDED
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.screen = exports.accent = exports.selected = exports.row = exports.rows = exports.panel = exports.scrim = exports.glass = void 0;
4
+ /**
5
+ * The chrome, as values — `@hanzo/ui/glass`.
6
+ *
7
+ * import { glass, panel, scrim } from '@hanzo/ui/glass'
8
+ * import '@hanzo/ui/glass.css' // or '@hanzo/ui/theme.css', which inlines it
9
+ *
10
+ * Eight recipes, because eight things were being spelled out by hand on every
11
+ * page and drifting apart every time: what a floating surface is made of, what
12
+ * a resting one is made of, what dims the page under a modal, how a set of
13
+ * rows is grouped, what one of those rows looks like, which item in a set is
14
+ * the current one, which control is the loud one, and what it means to fill the
15
+ * screen. Each is ONE value here and nowhere else.
16
+ *
17
+ * They are plain objects, spread onto any `@hanzo/gui` element:
18
+ *
19
+ * <YStack {...panel}>…</YStack>
20
+ * <XStack {...glass(2)} borderBottomWidth={1}>…</XStack>
21
+ *
22
+ * A recipe rather than a component or a variant, because a variant only reaches
23
+ * the one component that declares it and half of what wants these is an
24
+ * `XStack`, a `SizableText` or a `Link`. One value that spreads onto any
25
+ * element beats N spellings of it.
26
+ *
27
+ * The material and the ladder live in `glass.css`; these name them. Nothing
28
+ * here restates a colour, a shadow or a blur radius — see that file for where
29
+ * the values come from (`@hanzo/design`).
30
+ */
31
+ const slot_1 = require("./backends/gui/slot.cjs");
32
+ /**
33
+ * Glass — the material floating chrome is made of, and only floating chrome.
34
+ *
35
+ * Menus, dialogs, popovers, toasts, sticky bars: things with page under them.
36
+ * Vibrancy is a lens, so a surface covering nothing but the flat canvas has
37
+ * nothing to soften — an in-flow card wearing this is not glass, it is the page
38
+ * with extra steps, one shade off what it sits on and harder to find than
39
+ * `panel`. Over content -> `glass`, in the flow -> `panel`. No third case.
40
+ *
41
+ * The library's own dialogs, popovers, selects, dropdowns and tooltips are
42
+ * already glass BY SLOT — `glass.css` attaches the material to what a thing IS,
43
+ * so they need nothing from a call site and cannot fall out of step. This is
44
+ * for the surfaces no slot names: a hand-rolled bar, a custom menu, a docked
45
+ * toolbar.
46
+ *
47
+ * MATERIAL ONLY, no geometry. What glass is made of does not tell you what
48
+ * shape it was cut into: a menu is rounded on four sides, a sticky bar is
49
+ * full-bleed with one hairline along its bottom. Baking `borderRadius` and
50
+ * `borderWidth` in here would make every bar undo them, so edges stay at the
51
+ * call site — which only ever needs to say WHICH ones (`borderBottomWidth={1}`),
52
+ * never what colour, because the material already knows.
53
+ *
54
+ * The level is the one thing that genuinely varies, so it is the argument
55
+ * (`selected(on)` is shaped the same way). 2 is anchored — a menu, a toast, a
56
+ * bar, lifted off the page but tied to it. 3 is a modal, floating free over
57
+ * everything with a scrim under it.
58
+ *
59
+ * `backgroundColor` is the opaque fallback, and it is load-bearing rather than
60
+ * belt-and-braces: the material lives inside `@supports (backdrop-filter)`, so
61
+ * a browser that cannot blur gets no rule at all — and a menu with no
62
+ * background is a menu you read the page through.
63
+ */
64
+ const glass = (level = 2) => ({
65
+ className: `glass elevation-${level}`,
66
+ backgroundColor: '$background',
67
+ borderColor: '$borderColor',
68
+ });
69
+ exports.glass = glass;
70
+ /**
71
+ * The scrim under anything modal — the dim that makes a floating panel read as
72
+ * floating rather than as more page.
73
+ *
74
+ * Hand-rolled modals reach for `backgroundColor="black"`, with no alpha. That
75
+ * does not dim the page behind them, it DELETES it — and a glass panel over a
76
+ * solid black wall has nothing left to be translucent about, so the scrim
77
+ * cancels the very material it exists to make read.
78
+ *
79
+ * The value is a design token, not a literal, because both languages need it
80
+ * and only one of them can hold the copy: `[data-slot="dialog-overlay"]` reads
81
+ * `--surface-scrim` in `glass.css` and this reads the same token, so the dim
82
+ * behind a library dialog and the dim behind a hand-rolled one are the same dim
83
+ * by construction rather than by two people remembering a number.
84
+ */
85
+ exports.scrim = { backgroundColor: 'var(--surface-scrim, rgb(0 0 0 / .8))' };
86
+ /**
87
+ * A panel: the quiet surface content in the FLOW sits on.
88
+ *
89
+ * `$color2` is a step off the page, so the fill alone separates the panel from
90
+ * the page and the hairline only finishes the edge. Panels are otherwise
91
+ * variously `$background` (a border with nothing behind it), `$color3` (which
92
+ * is the ACTIVE-item fill — a state, not a surface) and as many radii as there
93
+ * are authors.
94
+ *
95
+ * Rung 1 on the ladder, which is NOT the same claim as "this floats". A cast
96
+ * shadow does say "above the page", and that stays false for a panel in the
97
+ * flow — so rung 1 spends almost nothing on shadow and buys its depth from the
98
+ * lit top edge instead. On a near-black canvas that is the only half that reads
99
+ * at all.
100
+ */
101
+ exports.panel = {
102
+ backgroundColor: '$color2',
103
+ borderWidth: 1,
104
+ borderColor: '$borderColor',
105
+ borderRadius: '$6',
106
+ className: 'elevation-1',
107
+ };
108
+ /**
109
+ * A group of rows — the settings card, and the reason a settings page ends up
110
+ * with four cards where it wanted one.
111
+ *
112
+ * Four toggles as four separate panels reads as four unrelated decisions, each
113
+ * with its own four edges; the same four inside one card, parted by hairlines,
114
+ * reads as one set and drops twelve edges of noise. That is the macOS settings
115
+ * group, and it is the shape every settings surface reaches for by hand.
116
+ *
117
+ * `overflow: hidden` is not decoration — the separators are drawn by the card
118
+ * (`[data-slot="rows"] > * + *`), and without the clip the topmost one runs
119
+ * straight through the rounded corner and out the side.
120
+ *
121
+ * It spreads `panel` WHOLE, so moving the panel up or down the ladder moves its
122
+ * groups with it. The separator hangs off a slot rather than a second class
123
+ * name for the same reason: there is no string to keep in step.
124
+ */
125
+ exports.rows = {
126
+ ...exports.panel,
127
+ ...(0, slot_1.slot)('rows'),
128
+ overflow: 'hidden',
129
+ };
130
+ /**
131
+ * One line inside a `rows` card: name on the left, control hard right.
132
+ *
133
+ * The rhythm, so it stops being re-decided per file. Pair it with a label at
134
+ * `fontSize="$3"` in `$color` and any explanation under it at `$1` in
135
+ * `$color11` — both halves matter, because a row whose label is written at
136
+ * `$color11` (the SECONDARY colour) gives the name of the setting and the
137
+ * footnote about it the same weight of grey, and the row has no first thing to
138
+ * read.
139
+ *
140
+ * No border here. A row that draws its own top hairline is a row that has to
141
+ * know whether it is first, and the card already knows — see `rows`.
142
+ */
143
+ exports.row = {
144
+ flexDirection: 'row',
145
+ alignItems: 'center',
146
+ justifyContent: 'space-between',
147
+ gap: '$4',
148
+ paddingHorizontal: '$4',
149
+ paddingVertical: '$3',
150
+ };
151
+ /**
152
+ * Selected — the ONE "you are here" look, wherever a set offers a choice: nav
153
+ * rows, tabs, a filter chip.
154
+ *
155
+ * `$color3` fill with a full-strength label, one step above the panel it sits
156
+ * on. The alternatives in the wild are a pure-white lozenge, and — far more
157
+ * often — no answer at all: an unstyled `TabsTrigger` paints a selected tab
158
+ * exactly like its neighbours, so the page simply does not say which one you
159
+ * are on. Nothing in the markup looks wrong; the state has no picture.
160
+ *
161
+ * The label is `$color12`, not `$color`, and that is a measured distinction
162
+ * rather than a preference. Themes NEST: at the page scope `--color` is
163
+ * hsl(0 0% 100%), but inside a sidebar's scope it is re-based to hsl(0 0% 80%)
164
+ * — the SAME value as `--color11`. So a selected row asking for `$color` gets
165
+ * exactly its neighbours' colour, and had been doing so all along; the markup
166
+ * said "highlight this" and the token quietly collapsed underneath. `$color12`
167
+ * is the theme's full-strength foreground in every scope, so the state survives
168
+ * nesting.
169
+ */
170
+ const selected = (on) => on
171
+ ? { backgroundColor: '$color3', color: '$color12' }
172
+ : { backgroundColor: 'transparent', color: '$color11' };
173
+ exports.selected = selected;
174
+ /**
175
+ * The primary control — the ONE loud thing a page is allowed.
176
+ *
177
+ * `$color5` on `$color6` is a raised pushbutton: legible, quiet, monochrome.
178
+ *
179
+ * Under-emphasis is the failure mode, and it leaves no mark on a screenshot,
180
+ * which is exactly why it needs a rule rather than an eye. `Button`'s `default`
181
+ * variant is a QUIET control on the surface ladder (`$color2`, the same fill
182
+ * `panel` uses), so a dialog whose only action is an unnamed `<Button>` paints
183
+ * its single ask as another surface. Name a variant on every control, and when
184
+ * one is the primary action, spread this. Never `variant="default"`.
185
+ *
186
+ * Not `variant="primary"` either, even though it paints the same pixels — a
187
+ * variant only reaches a `Button`, and half the loud controls in a real app are
188
+ * an `XStack`, a `SizableText` or a `Link`.
189
+ */
190
+ exports.accent = {
191
+ backgroundColor: '$color5',
192
+ borderWidth: 1,
193
+ borderColor: '$color6',
194
+ /**
195
+ * The foreground is part of the recipe, not an afterthought.
196
+ *
197
+ * Spell the fill by hand and you own the label too, and that half is the one
198
+ * people drop: a hand-spelled `color="$color1"` (4% grey) on a fill that has
199
+ * since moved to `$color2` (8%) paints a label at 1.07:1 — measured in the
200
+ * browser, not guessed. The sign-in modal's only button was invisible.
201
+ *
202
+ * And it only reaches a label the BUTTON paints. This is the half that is
203
+ * easy to miss: `<Button {...accent}><SizableText>…</SizableText></Button>`
204
+ * puts a second component between the recipe and the text, and that component
205
+ * resolves its own colour from the theme scope the Button just mounted —
206
+ * where `--color` is re-based to 80%. Measured: fill rgb(51,51,51) and border
207
+ * rgb(69,69,69), correct, with the label at rgb(204,204,204) — `$color11`,
208
+ * the QUIET foreground, on the loudest control in the dialog.
209
+ *
210
+ * Two guesses about the cure are both WRONG, and each was measured:
211
+ *
212
+ * - "Just don't name a colour." A bare `<SizableText>` inside the Button
213
+ * still measures rgb(204,204,204). Silence is not neutral here; the
214
+ * nested scope answers for you. The rule is to SAY `$color12`, not to say
215
+ * nothing.
216
+ * - "Hoist the size to the Button and drop the wrapper." `fontSize="$1"` on
217
+ * the Button does not reach the label — still the `size` default. So a
218
+ * control that genuinely wants a smaller label keeps its wrapper.
219
+ *
220
+ * Which leaves two honest shapes. No size of your own -> hand the string
221
+ * straight to the Button and let its text host paint it. A deliberate size or
222
+ * `numberOfLines` -> keep the wrapper and give it `color="$color12"`.
223
+ *
224
+ * BUTTON ONLY. A plain `XStack` mounts no theme scope, so a label inside one
225
+ * that asks for `$color` measures rgb(255,255,255) — correct, and not to be
226
+ * "fixed". The ban is exactly as wide as the re-basing.
227
+ */
228
+ color: '$color12',
229
+ hoverStyle: { backgroundColor: '$color6', color: '$color12' },
230
+ };
231
+ /**
232
+ * A state that owns the whole screen: a loading gate, a 404, a crash screen, an
233
+ * OAuth callback, a receipt. It stands alone — no shell above it — so it must
234
+ * MEASURE the screen itself.
235
+ *
236
+ * `minHeight="100%"` does not measure it, and that is the bug this value ends.
237
+ * A percentage resolves against the parent's computed height; every ancestor up
238
+ * to `<body>` is `height: auto`, and `@hanzo/gui`'s provider span is
239
+ * `display: contents`, so there is no box in between either. The percentage
240
+ * resolves to auto, the stack shrink-wraps its content, and `justifyContent:
241
+ * center` then centres inside THAT — perfectly, and invisibly, in a box the
242
+ * height of the content. Measured: a brand mark at y=0 of a 903px viewport with
243
+ * 860px of black under it, across ten states carrying the identical line.
244
+ *
245
+ * `dvh`, not `vh`: on a phone the URL bar collapses and `100vh` overflows by
246
+ * its height.
247
+ *
248
+ * The centring travels WITH the measure because the two are one decision — a
249
+ * state that fills the screen holds a paragraph of content — and half of this
250
+ * recipe is what produced the defect in the first place.
251
+ */
252
+ exports.screen = {
253
+ minHeight: '100dvh',
254
+ alignItems: 'center',
255
+ justifyContent: 'center',
256
+ };
package/dist/glass.css ADDED
@@ -0,0 +1,140 @@
1
+ /* Glass — the material floating chrome is made of, and the ladder it stands on.
2
+ *
3
+ * import '@hanzo/ui/glass.css' // this slice alone
4
+ * import '@hanzo/ui/theme.css' // the whole sheet; this is inlined in it
5
+ *
6
+ * Menus, dialogs, popovers, toasts, sticky bars: things with page UNDER them.
7
+ * That last clause is the whole rule. Vibrancy is a lens — translucent ground
8
+ * plus a backdrop blur — so it shows a softened version of whatever it covers,
9
+ * and covering nothing but the flat canvas it has nothing to soften. An in-flow
10
+ * card wearing this is not glass; it is the page with extra steps. So: over
11
+ * content -> glass, in the flow -> the surface ladder. There is no third case.
12
+ *
13
+ * ── Values come from @hanzo/design ──────────────────────────────────────────
14
+ * Every colour, shadow and light below is a design token. The fallbacks are
15
+ * design's OWN published values, present so a host that imports this file
16
+ * without design's sheet still gets a ladder instead of a silently-dropped
17
+ * declaration (an undefined var() invalidates the whole property). They are a
18
+ * mirror, not a second opinion — glass.test.ts reads @hanzo/design/styles.css
19
+ * and fails if any of them stops matching, so the copy cannot drift.
20
+ */
21
+
22
+ /* ── The material ───────────────────────────────────────────────────────────
23
+ Applied BY SLOT, not by call site. Every surface below is floating chrome by
24
+ construction — a popover is over the page or it is nothing — so which of them
25
+ is glass was never a decision a call site should get to make. It was making
26
+ it anyway: in the console, of 135 floating surfaces exactly ONE had reached
27
+ for the material by hand and a dozen dialogs had gone the other way and
28
+ pinned an opaque fill. Reaching them one by one is 134 edits that agree today
29
+ and drift apart on the next one somebody adds. A new dialog is glass because
30
+ it is a dialog.
31
+
32
+ `.glass` is here for the same reason `glass()` exists in the recipes module:
33
+ a hand-rolled bar or a custom menu is floating chrome that no slot names.
34
+
35
+ The ground is derived from --background rather than fixed, so the material
36
+ follows the theme instead of assuming a dark one.
37
+
38
+ The opaque `background-color` outside the @supports block is load-bearing
39
+ rather than belt-and-braces: a browser that cannot blur gets no rule at all
40
+ from inside it, and a menu with no background is a menu you read the page
41
+ through. !important beats the compiled atomic background class, which lands
42
+ in an inline <style> a bundler orders after this sheet, and it beats the call
43
+ sites that pin a fill by hand — those become no-ops instead of holes. */
44
+ @supports (backdrop-filter: blur(8px)) {
45
+ .glass,
46
+ [data-slot="glass"],
47
+ [data-slot="dialog-content"],
48
+ [data-slot="popover-content"],
49
+ [data-slot="select-content"],
50
+ [data-slot="dropdown-menu-content"],
51
+ [data-slot="dropdown-menu-sub-content"],
52
+ [data-slot="tooltip-content"] {
53
+ background-color: color-mix(in oklab, var(--background) 72%, transparent) !important;
54
+ -webkit-backdrop-filter: blur(20px) saturate(1.8);
55
+ backdrop-filter: blur(20px) saturate(1.8);
56
+ }
57
+ }
58
+
59
+ /* ── Depth — three rungs, and the lit edge is the load-bearing half ──────────
60
+ A cast shadow is how a light page shows lift. On a near-black canvas it is
61
+ nearly free of information: black on near-black moves no pixels, so a raised
62
+ panel wearing only a shadow reads exactly as flat as one wearing none. What
63
+ says "this surface is above that one" in a dark UI is the LIT EDGE — the top
64
+ lip catching light, which is why every macOS/iOS glass panel has one. Design
65
+ spends the same two tokens for the same reason and zeroes --edge-highlight in
66
+ `.light`, where a white line on a white card is nothing and the drop does all
67
+ the work: one composition, both themes, no branch here.
68
+
69
+ Levels are a LADDER, not a palette — 1 rests in the flow, 2 lifts off it, 3
70
+ floats over the page. Nothing needs a fourth; if something seems to, it is
71
+ sitting at the wrong level.
72
+
73
+ Anchored chrome — a menu pinned to the control that opened it, a tooltip to
74
+ its target — is rung 2. A modal has no anchor and a scrim of its own, so it
75
+ floats free at 3. Those slots share the rung's rule rather than restating its
76
+ value, so the ladder cannot come apart from the surfaces standing on it.
77
+
78
+ Glass over glass (a select opened inside a dialog) is deliberately left to
79
+ stack: two 72% grounds land near 92%, so the nested surface reads as MORE
80
+ solid than its parent, which is the right answer and the one macOS gives. */
81
+ .elevation-1 {
82
+ box-shadow:
83
+ var(--edge-highlight, inset 0 1px 0 0 rgb(255 255 255 / .10)),
84
+ var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / .40)) !important;
85
+ }
86
+
87
+ .elevation-2,
88
+ [data-slot="popover-content"],
89
+ [data-slot="select-content"],
90
+ [data-slot="dropdown-menu-content"],
91
+ [data-slot="dropdown-menu-sub-content"],
92
+ [data-slot="tooltip-content"] {
93
+ box-shadow:
94
+ var(--edge-highlight, inset 0 1px 0 0 rgb(255 255 255 / .10)),
95
+ var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)) !important;
96
+ }
97
+
98
+ .elevation-3,
99
+ [data-slot="dialog-content"] {
100
+ box-shadow:
101
+ var(--edge-highlight, inset 0 1px 0 0 rgb(255 255 255 / .10)),
102
+ var(--shadow-floating, 0 24px 60px -16px rgb(0 0 0 / .75)) !important;
103
+ }
104
+
105
+ /* ── The scrim ──────────────────────────────────────────────────────────────
106
+ The dim that makes a floating panel read as floating rather than as more
107
+ page. gui's overlay dims TWICE — a translucent ground under a further
108
+ `opacity` — landing at about a quarter black. Opaque chrome gets away with
109
+ it; glass does not, because the blur behind a translucent panel only reads as
110
+ depth when the ground behind it is genuinely darker.
111
+
112
+ One value, in one place: the recipes module reads the same token, so the dim
113
+ behind a component dialog and the dim behind a hand-rolled one are the same
114
+ dim by construction rather than by two people remembering a number. */
115
+ [data-slot="dialog-overlay"] {
116
+ opacity: 1;
117
+ background-color: var(--surface-scrim, rgb(0 0 0 / .8));
118
+ }
119
+
120
+ /* ── Grouped rows ───────────────────────────────────────────────────────────
121
+ One card, hairlines between its rows — the macOS settings group. Four
122
+ separate cards for four toggles reads as four unrelated decisions, each with
123
+ its own four edges; the same four inside one card reads as one set and drops
124
+ twelve edges of noise.
125
+
126
+ The separator is drawn by the PARENT, because only the parent knows which
127
+ child is last. Every by-hand version either left a trailing hairline sitting
128
+ against the card's own bottom border (two parallel lines, 1px apart) or made
129
+ each row take a `first` flag it should never have needed. `> * + *` has
130
+ neither failure mode and needs nothing from the call site.
131
+
132
+ Pair with `overflow: hidden` on the card (the `rows` recipe does) so the
133
+ separators stop at the radius instead of poking through the rounded corner.
134
+
135
+ --borderColor first: that is the variable the gui theme scope re-bases, and
136
+ it is what a card's own edge is drawn with. Falling through to --border keeps
137
+ a host that has design's tokens but no gui scope from losing the line. */
138
+ [data-slot="rows"] > * + * {
139
+ border-top: 1px solid var(--borderColor, var(--border));
140
+ }