@cueplusplus/ui 0.5.0 → 0.6.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/CHANGELOG.md +34 -0
- package/dist/chrome/_drag.js +59 -0
- package/dist/chrome/_status-bar-item.js +85 -0
- package/dist/chrome/_status-bar.d.ts +15 -0
- package/dist/chrome/_status-bar.js +171 -0
- package/dist/chrome/app-shell.d.ts +69 -1
- package/dist/chrome/app-shell.js +38 -11
- package/dist/chrome/index.d.ts +3 -2
- package/dist/chrome/status-bar.d.ts +87 -4
- package/dist/chrome/status-bar.js +80 -16
- package/dist/chrome/title-bar.d.ts +11 -1
- package/dist/chrome/title-bar.js +1 -3
- package/dist/date/_segments.js +15 -4
- package/dist/forms/_chassis.d.ts +104 -4
- package/dist/forms/_chassis.js +104 -4
- package/dist/forms/input-group.d.ts +8 -3
- package/dist/forms/input-group.js +8 -3
- package/dist/forms/number-field.d.ts +3 -2
- package/dist/forms/number-field.js +3 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -1
- package/dist/instruments/_data-row.js +118 -0
- package/dist/instruments/data-row.d.ts +94 -0
- package/dist/instruments/data-row.js +151 -0
- package/dist/instruments/data-tree.d.ts +126 -0
- package/dist/instruments/data-tree.js +303 -0
- package/dist/instruments/index.d.ts +5 -2
- package/dist/instruments/index.js +4 -1
- package/dist/instruments/ledger.d.ts +105 -0
- package/dist/instruments/ledger.js +114 -0
- package/dist/instruments/table.d.ts +70 -2
- package/dist/instruments/table.js +133 -39
- package/dist/theming/_presets.js +3 -3
- package/manifest/components/app-shell.json +45 -4
- package/manifest/components/autocomplete.json +2 -0
- package/manifest/components/color-field.json +2 -0
- package/manifest/components/color-picker.json +2 -0
- package/manifest/components/combobox.json +2 -0
- package/manifest/components/composer.json +2 -0
- package/manifest/components/data-row.json +203 -0
- package/manifest/components/data-tree.json +151 -0
- package/manifest/components/date-field.json +2 -0
- package/manifest/components/date-picker.json +2 -0
- package/manifest/components/date-range-picker.json +2 -0
- package/manifest/components/env-var-input.json +2 -0
- package/manifest/components/input-group.json +4 -2
- package/manifest/components/input.json +3 -1
- package/manifest/components/ledger.json +187 -0
- package/manifest/components/multi-select.json +2 -0
- package/manifest/components/musical-time-input.json +2 -0
- package/manifest/components/number-field.json +3 -1
- package/manifest/components/otp-field.json +2 -0
- package/manifest/components/password-input.json +2 -0
- package/manifest/components/scrub-input.json +2 -0
- package/manifest/components/search-input.json +2 -0
- package/manifest/components/select.json +1 -0
- package/manifest/components/status-bar.json +146 -12
- package/manifest/components/table-scroll-region.json +6 -0
- package/manifest/components/table.json +88 -4
- package/manifest/components/tags-input.json +2 -0
- package/manifest/components/textarea.json +3 -1
- package/manifest/components/time-field.json +2 -0
- package/manifest/components/title-bar.json +1 -1
- package/manifest/components/toggle-group.json +2 -0
- package/manifest/components/toggle.json +2 -0
- package/manifest/manifest.json +72 -33
- package/manifest/tokens.json +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @cueplusplus/ui
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bf116b5: `AppShell.Bar` gains the desktop drag contract `TitleBar` already had, plus a truly bar-centred `center` slot and a trailing `actions` cluster, so a Tauri or Electron console spends one chrome row on window furniture instead of two.
|
|
8
|
+
|
|
9
|
+
`StatusBar.Item` gains `interactive`, `prominence` and `render`, so a reading can become a real button or link with hover, press and inset keyboard-focus states, and the new `StatusBar.Group` fuses adjacent items into one compact control. The published manifest now lists `StatusBar.Item` and `StatusBar.Group` with their props, so a props table or lookup tool reads them the way it already reads `AppShell`'s parts.
|
|
10
|
+
|
|
11
|
+
## Migrating
|
|
12
|
+
|
|
13
|
+
Nothing to change. Both additions are opt-in: without `platformDrag`, `center` or `actions` the bar emits no drag attributes at all, and a `StatusBar.Item` with none of the new props is the same inert `<div>` with the same classes it always was.
|
|
14
|
+
|
|
15
|
+
Two shapes moved underneath, in case your own CSS reaches into them. `AppShell.Bar` now wraps its brand, its leading children and its actions in `[data-slot="app-shell-brand-content"]`, `[data-slot="app-shell-bar-leading"]` and `[data-slot="app-shell-bar-actions"]` respectively — each a `<div>` laid out as a flex row with the bar's own gap, so the rhythm is unchanged and `brandClassName` still lands on the cell. Under `platformDrag` those clusters take their content's width rather than filling their cell, which is what leaves the bar's dead space to the window manager; an `ms-auto` that needs to reach the cell's rim belongs on a box inside the cluster there. And `StatusBar`'s root is now `overflow-x-clip`: the bar has always promised to be one row tall, and now it keeps that promise when it is overfilled.
|
|
16
|
+
|
|
17
|
+
- fb20c71: Dense data surfaces: `DataRow` is the shared row anatomy (marker, label, description, tags, figure and an action track hidden until hover, focus or selection, with a touch fallback and no reflow when it appears); `Ledger` groups rows under sticky tier headings and gutter slugs with no disclosure semantics; `DataTree` draws the same rows as a hierarchy under `role="tree"` with the APG keyboard map and CSS depth guides; and `Table` gains `Table.GroupRow` (a `<th scope="rowgroup">` divider so a grouped list stays one table with its columns named once), `sticky` on `Table.Head`, and `interactive`/`selected` on `Table.Row`. Existing tables are untouched: every addition is opt-in.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 778374d: The keyboard focus ring is now visible in the luma, venu and hivehub light
|
|
22
|
+
themes. Each set `--cue-focus` to its brand accent, and on paper that accent
|
|
23
|
+
read as low as 2.45:1 against the recessed fill a focused field paints it
|
|
24
|
+
over — under the 3:1 WCAG 1.4.11 asks of a focus indicator. Each light block
|
|
25
|
+
now carries its own focus colour: the same accent hue, stepped down in
|
|
26
|
+
lightness until it clears every surface a field can sit on. `--cue-accent` is
|
|
27
|
+
unchanged, so no button, chip or link moves.
|
|
28
|
+
- 013960d: Form controls now show one quiet focus signal instead of two. A focused field strengthens its rim by one rung (`--cue-border` → `--cue-border-strong`) and draws a single 2px ring in the theme's own `--cue-focus`, pulled flush against the rim rather than floating outside it on a gap. Grouped controls — `InputGroup`, `SearchInput`, `Combobox`, `MultiSelect`, `Autocomplete`, `TagsInput`, `NumberField`, `ScrubInput`, `Composer`, `MusicalTimeInput` and the segmented date and time fields — strengthen their rim for any focus inside but wait for a keyboard-visible descendant before drawing the ring, so a mouse click no longer lights the whole cluster up. An invalid field stays red while it is focused: because a flush ring is drawn over the rim it hugs, the ring itself turns `--cue-danger` rather than leaving a border nobody can see underneath it — so focus keeps its own indicator and invalid keeps its colour, on the plain controls and on the grouped ones alike.
|
|
29
|
+
|
|
30
|
+
## Migrating
|
|
31
|
+
|
|
32
|
+
No API changed, and no consumer edit is required. Anyone who added local overrides to mute the old accent-rim-plus-outer-ring treatment can delete them: the defaults are now the quiet version, and a local override will fight the theme's focus colour rather than help it.
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [778374d]
|
|
35
|
+
- @cueplusplus/tokens@0.6.0
|
|
36
|
+
|
|
3
37
|
## 0.5.0
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/chrome/_drag.ts
|
|
2
|
+
/**
|
|
3
|
+
* The desktop drag contract, in one place because two bars now emit it.
|
|
4
|
+
*
|
|
5
|
+
* A window has exactly one way to be dragged by its chrome, and this library
|
|
6
|
+
* ships into two shells that disagree about what that way is:
|
|
7
|
+
*
|
|
8
|
+
* - **Tauri** listens for `mousedown` and walks the event's `composedPath()`,
|
|
9
|
+
* looking for `data-tauri-drag-region`. The bare form of the attribute means
|
|
10
|
+
* *this element and only this element* — the innermost element under the
|
|
11
|
+
* cursor has to be the attributed one — so a container carrying it does not
|
|
12
|
+
* hand the behaviour to its children. It also excludes anything that reads as
|
|
13
|
+
* a control (`a`, `button`, `input`, `select`, `textarea`, `label`,
|
|
14
|
+
* `summary`, `[contenteditable]`, `[tabindex]`, and the button/link/menuitem
|
|
15
|
+
* ARIA roles) for free, without being asked.
|
|
16
|
+
* - **Electron and Chromium** read the `app-region` CSS property and build a
|
|
17
|
+
* *geometric* hit-test mask from it, below the DOM's pointer-event model
|
|
18
|
+
* entirely: every painted pixel of a `drag` box drags, minus the pixels a
|
|
19
|
+
* `no-drag` box covers. It knows nothing about ancestry and excludes nothing
|
|
20
|
+
* on its own — every interactive descendant has to opt out by name.
|
|
21
|
+
*
|
|
22
|
+
* The two mechanisms fail in opposite directions, so the contract a shared
|
|
23
|
+
* component can honour is the union of both: **attribute every structural
|
|
24
|
+
* surface, carve every interactive cluster.** A bar that only attributes its
|
|
25
|
+
* root is undraggable in Tauri wherever a cell covers the root; a bar that only
|
|
26
|
+
* drags and never carves is a bar whose buttons move the window in Electron.
|
|
27
|
+
*
|
|
28
|
+
* Both spellings of the CSS property are emitted. `app-region` is the current
|
|
29
|
+
* canonical name in Electron's own documentation; `-webkit-app-region` is the
|
|
30
|
+
* legacy alias older Chromium builds still want, and neither shell minds
|
|
31
|
+
* reading a property it already understands twice.
|
|
32
|
+
*
|
|
33
|
+
* These are class strings rather than a `<div>` of their own on purpose: the
|
|
34
|
+
* surfaces that have to carry them — {@link TitleBar}'s root,
|
|
35
|
+
* `AppShell.Bar`'s root, its brand cell, its rest cell — already exist for
|
|
36
|
+
* layout reasons, and wrapping each one in a drag element would put a box in
|
|
37
|
+
* the flex arithmetic that is there for nothing but a CSS property.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Make a painted surface a window drag handle.
|
|
41
|
+
*
|
|
42
|
+
* Pair it with a bare `data-tauri-drag-region` attribute on the same element —
|
|
43
|
+
* the CSS half is Electron's and the attribute half is Tauri's, and an element
|
|
44
|
+
* with only one of them is draggable in only one shell.
|
|
45
|
+
*/
|
|
46
|
+
const DRAG_CLASSES = "[app-region:drag] [-webkit-app-region:drag]";
|
|
47
|
+
/**
|
|
48
|
+
* Carve an interactive cluster back out of the surrounding drag region.
|
|
49
|
+
*
|
|
50
|
+
* Required for Electron, where a button that merely overlaps a drag box stops
|
|
51
|
+
* receiving clicks at all; harmless in Tauri, which had already excluded a
|
|
52
|
+
* real `<button>` on its own. Anything custom enough that Tauri's tag/role
|
|
53
|
+
* heuristic would miss it — an icon "button" built from a `<div>`, a bespoke
|
|
54
|
+
* menu trigger — additionally wants `data-tauri-drag-region="false"`, which is
|
|
55
|
+
* the caller's to add on the control itself.
|
|
56
|
+
*/
|
|
57
|
+
const NO_DRAG_CLASSES = "[app-region:no-drag] [-webkit-app-region:no-drag]";
|
|
58
|
+
//#endregion
|
|
59
|
+
export { DRAG_CLASSES, NO_DRAG_CLASSES };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { statusBarItemClasses } from "./_status-bar.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
7
|
+
//#region src/chrome/_status-bar-item.tsx
|
|
8
|
+
/**
|
|
9
|
+
* The elements for which `disabled` is a real HTML attribute.
|
|
10
|
+
*
|
|
11
|
+
* Everything else — an anchor above all — has to be told it is inert some other
|
|
12
|
+
* way. Setting `disabled` on an `<a>` produces a stray attribute the parser
|
|
13
|
+
* keeps and nothing reads: not a validity error, but not a disabled link
|
|
14
|
+
* either, because there is no such thing.
|
|
15
|
+
*/
|
|
16
|
+
const DISABLEABLE_TAGS = /* @__PURE__ */ new Set([
|
|
17
|
+
"button",
|
|
18
|
+
"input",
|
|
19
|
+
"select",
|
|
20
|
+
"textarea",
|
|
21
|
+
"fieldset",
|
|
22
|
+
"optgroup"
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Whether `disabled` can be handed to whatever this renders as.
|
|
26
|
+
*
|
|
27
|
+
* A `render` that is a component rather than an intrinsic tag decides for
|
|
28
|
+
* itself — Base UI's own triggers take a `disabled` prop, and refusing to pass
|
|
29
|
+
* it would break the composition this prop exists for — so only a string tag
|
|
30
|
+
* outside the set above is refused.
|
|
31
|
+
*/
|
|
32
|
+
function takesDisabledAttribute(render) {
|
|
33
|
+
if (render === void 0) return true;
|
|
34
|
+
if (!React.isValidElement(render)) return true;
|
|
35
|
+
return typeof render.type !== "string" || DISABLEABLE_TAGS.has(render.type);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A status-bar item that is a real control: a `<button>`, or whatever `render`
|
|
39
|
+
* replaces it with.
|
|
40
|
+
*
|
|
41
|
+
* `type="button"` is supplied only when this renders its own `<button>`. A
|
|
42
|
+
* `render={<a href="…" />}` must not be handed a `type`, which on an anchor is
|
|
43
|
+
* a MIME hint and not a submit-behaviour switch — the same rule
|
|
44
|
+
* {@link IconButton} follows, and for the same reason.
|
|
45
|
+
*
|
|
46
|
+
* `disabled` is honoured on every interactive shape, by two routes. The
|
|
47
|
+
* attribute itself goes only where it means something ({@link DISABLEABLE_TAGS},
|
|
48
|
+
* plus any component `render`, which takes its own `disabled` prop); the state
|
|
49
|
+
* is stated everywhere with `aria-disabled` and `data-disabled`, which is what
|
|
50
|
+
* an anchor gets instead. `data-disabled` is not decoration — it is what greys
|
|
51
|
+
* the item out and lifts it out of the pointer's reach in `_status-bar.ts`,
|
|
52
|
+
* since `:disabled` never matches an `<a>`. The gap this closes was a
|
|
53
|
+
* `render={<button/>} disabled` that shipped enabled, clickable and undimmed.
|
|
54
|
+
*/
|
|
55
|
+
const StatusBarInteractiveItem = React.forwardRef(function StatusBarInteractiveItem({ tone, prominence, className, push, icon: Icon, render, disabled, children, elementProps }, ref) {
|
|
56
|
+
const tagProps = {
|
|
57
|
+
...render === void 0 ? { type: "button" } : {},
|
|
58
|
+
...disabled === true && takesDisabledAttribute(render) ? { disabled: true } : {},
|
|
59
|
+
...disabled === true ? {
|
|
60
|
+
"aria-disabled": true,
|
|
61
|
+
"data-disabled": ""
|
|
62
|
+
} : {}
|
|
63
|
+
};
|
|
64
|
+
return useRender({
|
|
65
|
+
render,
|
|
66
|
+
ref,
|
|
67
|
+
defaultTagName: "button",
|
|
68
|
+
props: {
|
|
69
|
+
"data-slot": "status-bar-item",
|
|
70
|
+
...tagProps,
|
|
71
|
+
...elementProps,
|
|
72
|
+
className: cn(statusBarItemClasses({
|
|
73
|
+
tone,
|
|
74
|
+
interactive: true,
|
|
75
|
+
prominence
|
|
76
|
+
}), push ? "ml-auto" : void 0, className),
|
|
77
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [Icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
|
|
78
|
+
"aria-hidden": "true",
|
|
79
|
+
className: "size-icon-sm shrink-0"
|
|
80
|
+
}), children] })
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
//#endregion
|
|
85
|
+
export { StatusBarInteractiveItem };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "./status-bar.js";
|
|
2
|
+
//#region src/chrome/_status-bar.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* How loudly a status-bar item states itself.
|
|
5
|
+
*
|
|
6
|
+
* `quiet` is a reading — tone-coloured ink on the bar's own ground, which is
|
|
7
|
+
* what a status bar is for. `filled` is a chip: a full-height tone wash behind
|
|
8
|
+
* tone ink, for the handful of states VS Code reserves its coloured categories
|
|
9
|
+
* for (a warning, an error, the remote you are attached to). It is loud on
|
|
10
|
+
* purpose and it is meant to be rare; a bar of five filled items is a bar
|
|
11
|
+
* nobody reads.
|
|
12
|
+
*/
|
|
13
|
+
type StatusBarProminence = "quiet" | "filled";
|
|
14
|
+
//#endregion
|
|
15
|
+
export { StatusBarProminence };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
//#region src/chrome/_status-bar.ts
|
|
3
|
+
/**
|
|
4
|
+
* The class half of `StatusBar` (see `./status-bar.tsx`).
|
|
5
|
+
*
|
|
6
|
+
* It lives on its own for the same reason `forms/_chassis.ts` does — the item's
|
|
7
|
+
* appearance is a pure function of three enumerated inputs and nothing else —
|
|
8
|
+
* and for one reason `_chassis.ts` does not have: a status bar is two kinds of
|
|
9
|
+
* thing wearing one skin. A static reading is markup, and markup is the whole
|
|
10
|
+
* point of `StatusBar` being renderable from a server component with no
|
|
11
|
+
* JavaScript at all. An *interactive* item is a control, and a control needs
|
|
12
|
+
* the render prop, which needs a hook, which needs `"use client"`. Putting the
|
|
13
|
+
* shared class strings here lets the static half stay static and the
|
|
14
|
+
* interactive half cross the boundary alone (`./_status-bar-item.tsx`), instead
|
|
15
|
+
* of the whole bar crossing it because one of its items might be a button.
|
|
16
|
+
*
|
|
17
|
+
* Everything below is token-backed and enumerated per tone. Nothing is derived
|
|
18
|
+
* at runtime from a tone name, because a class string assembled from a template
|
|
19
|
+
* literal is a class string Tailwind's scanner never sees.
|
|
20
|
+
*/
|
|
21
|
+
/** Ink for each tone; `neutral` inherits the bar's own subtle grey. */
|
|
22
|
+
const ITEM_TONES = {
|
|
23
|
+
neutral: "",
|
|
24
|
+
accent: "text-accent",
|
|
25
|
+
ok: "text-ok",
|
|
26
|
+
busy: "text-busy",
|
|
27
|
+
warn: "text-warn",
|
|
28
|
+
danger: "text-danger",
|
|
29
|
+
info: "text-info"
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Every item, static or not: one line, never wrapping, never growing the bar.
|
|
33
|
+
*
|
|
34
|
+
* This exact string is what a plain `<StatusBar.Item>` rendered before any of
|
|
35
|
+
* the interactive vocabulary existed, and it still is — the appearance function
|
|
36
|
+
* adds to it and never rewrites it, so a console that upgrades sees the same
|
|
37
|
+
* readouts it had.
|
|
38
|
+
*/
|
|
39
|
+
const ITEM_BASE = "inline-flex min-w-0 shrink-0 items-center gap-(--cue-space-2) whitespace-nowrap";
|
|
40
|
+
/**
|
|
41
|
+
* What an item gains when it becomes a real control.
|
|
42
|
+
*
|
|
43
|
+
* The reset first: a `<button>` arrives with a border, a ground, a gutter and a
|
|
44
|
+
* font of the user agent's choosing, and none of them belong in a 24px strip.
|
|
45
|
+
* `text-(length:--cue-text-label)` and `font-mono` are re-declared rather than
|
|
46
|
+
* inherited for the same reason — form controls do not inherit type.
|
|
47
|
+
*
|
|
48
|
+
* `h-full` is the VS Code reading of a status item and the one that makes hover
|
|
49
|
+
* legible: the fill is the height of the bar, not the height of the words, so a
|
|
50
|
+
* pointer anywhere in the strip is over the target rather than beside it.
|
|
51
|
+
*
|
|
52
|
+
* The focus ring is inset (`-outline-offset-1`). An outset ring on a bar this
|
|
53
|
+
* low paints across the item's neighbours and the hairline above them, which
|
|
54
|
+
* reads as the whole bar being focused rather than one reading in it.
|
|
55
|
+
*
|
|
56
|
+
* The disabled treatment is declared twice, once for `:disabled` and once for
|
|
57
|
+
* `[data-disabled]`. An item that a `render` prop turned into an anchor can
|
|
58
|
+
* never match `:disabled` — the pseudo-class is defined on form controls — so
|
|
59
|
+
* without the data twin a disabled link would read as perfectly live.
|
|
60
|
+
*/
|
|
61
|
+
const INTERACTIVE_CHASSIS = "h-full cursor-pointer border-0 bg-transparent py-0 font-mono text-(length:--cue-text-label) outline-none transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-40 data-[disabled]:pointer-events-none data-[disabled]:opacity-40 motion-reduce:transition-none";
|
|
62
|
+
/**
|
|
63
|
+
* A quiet control's gutter, and the negative margin that pays for it.
|
|
64
|
+
*
|
|
65
|
+
* The bar sets `gap-(--cue-space-4)` between items. Padding an item to give its
|
|
66
|
+
* hover fill somewhere to live would push its text `--cue-space-2` further from
|
|
67
|
+
* its neighbour, so the same length is taken straight back off the margin: the
|
|
68
|
+
* words stay exactly where a static reading's words would be, and the fill
|
|
69
|
+
* bleeds halfway into the gap either side. VS Code does the identical trick
|
|
70
|
+
* with a 5px label padding inside a 3px item margin.
|
|
71
|
+
*/
|
|
72
|
+
const QUIET_GEOMETRY = "px-(--cue-space-2) -mx-(--cue-space-2)";
|
|
73
|
+
/**
|
|
74
|
+
* A filled item's gutter. Wider, and with no negative margin at all.
|
|
75
|
+
*
|
|
76
|
+
* A filled item is a chip rather than a reading, and a chip whose ground bleeds
|
|
77
|
+
* into the gap on both sides has no gap left to be a chip in. VS Code makes the
|
|
78
|
+
* same split — its coloured items take the wider 8px padding while a plain one
|
|
79
|
+
* keeps 5px — so this is the next space token up and the margin stays put.
|
|
80
|
+
*/
|
|
81
|
+
const FILLED_GEOMETRY = "h-full px-(--cue-space-3)";
|
|
82
|
+
/**
|
|
83
|
+
* The ground a `quiet` control paints, hovered, pressed, and as a member of a
|
|
84
|
+
* hovered {@link STATUS_BAR_GROUP_CLASSES cluster}.
|
|
85
|
+
*
|
|
86
|
+
* Three rungs of one ladder: the bar sits on `surface-1`, a hovered item lifts
|
|
87
|
+
* to `surface-2`, a pressed one to `surface-3`. The cluster rule is the VS Code
|
|
88
|
+
* pairing exactly — hovering any segment grounds every segment at the ordinary
|
|
89
|
+
* hover rung, and the segment actually under the cursor goes one rung further,
|
|
90
|
+
* so a two-item group reads as one control without hiding which half is live.
|
|
91
|
+
* The compound `group-hover/status-group:hover:` outranks the plain `hover:` on
|
|
92
|
+
* specificity rather than on source order, so which of them wins is decided by
|
|
93
|
+
* the cascade and not by how Tailwind happened to sort that day.
|
|
94
|
+
*/
|
|
95
|
+
const QUIET_GROUNDS = "hover:bg-surface-2 active:bg-surface-3 group-hover/status-group:bg-surface-2 group-hover/status-group:hover:bg-surface-3";
|
|
96
|
+
/**
|
|
97
|
+
* The fill a `filled` item paints, per tone.
|
|
98
|
+
*
|
|
99
|
+
* `bg-<tone>/10` is the tinted-`Chip` recipe, deliberately: "warn" has to be
|
|
100
|
+
* the same wash in the status bar that it is on the chip that raised it. The
|
|
101
|
+
* derived `--cue-<tone>-soft` properties are not used here because only
|
|
102
|
+
* `accent-soft` is bridged into Tailwind's colour namespace — `bg-warn-soft`
|
|
103
|
+
* compiles to nothing at all — while the alpha modifier reaches every tone and
|
|
104
|
+
* lands on the same 10–12% wash the token was derived at.
|
|
105
|
+
*/
|
|
106
|
+
const FILLED_TONES = {
|
|
107
|
+
neutral: "bg-surface-2 text-fg",
|
|
108
|
+
accent: "bg-accent/10",
|
|
109
|
+
ok: "bg-ok/10",
|
|
110
|
+
busy: "bg-busy/10",
|
|
111
|
+
warn: "bg-warn/10",
|
|
112
|
+
danger: "bg-danger/10",
|
|
113
|
+
info: "bg-info/10"
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Where a filled control's fill steps to under the pointer, per tone.
|
|
117
|
+
*
|
|
118
|
+
* The tint deepens rather than being swapped for a grey: a warning that turns
|
|
119
|
+
* neutral on hover has stopped warning at the exact moment somebody reached for
|
|
120
|
+
* it. `/20` is `Chip`'s own interactive step, and `/30` is one further for the
|
|
121
|
+
* press. Neutral has only the surface ladder to climb and its top rung is
|
|
122
|
+
* `surface-3`, so hover and press share it.
|
|
123
|
+
*/
|
|
124
|
+
const FILLED_GROUNDS = {
|
|
125
|
+
neutral: "hover:bg-surface-3 active:bg-surface-3",
|
|
126
|
+
accent: "hover:bg-accent/20 active:bg-accent/30",
|
|
127
|
+
ok: "hover:bg-ok/20 active:bg-ok/30",
|
|
128
|
+
busy: "hover:bg-busy/20 active:bg-busy/30",
|
|
129
|
+
warn: "hover:bg-warn/20 active:bg-warn/30",
|
|
130
|
+
danger: "hover:bg-danger/20 active:bg-danger/30",
|
|
131
|
+
info: "hover:bg-info/20 active:bg-info/30"
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* The compact-adjacency cluster: children one pixel apart, whatever the bar's
|
|
135
|
+
* own gap is.
|
|
136
|
+
*
|
|
137
|
+
* `gap-px` is the seam. `*:mx-0` is what makes the seam survive an interactive
|
|
138
|
+
* member, whose `-mx-(--cue-space-2)` would otherwise pull its neighbours a
|
|
139
|
+
* whole space token *over* it — the negative margin exists to bleed a lone
|
|
140
|
+
* item's fill into the bar's gap, and inside a cluster there is no gap to bleed
|
|
141
|
+
* into. It lands on the group because that is where the decision is: these are
|
|
142
|
+
* segments of one control, not readings that happen to be adjacent.
|
|
143
|
+
*
|
|
144
|
+
* `group/status-group` is the handle the members' cluster-hover rules read, and
|
|
145
|
+
* it is left on the element whether or not any member is interactive so a
|
|
146
|
+
* consumer can hang `group-hover/status-group:` rules of their own on it.
|
|
147
|
+
*/
|
|
148
|
+
const STATUS_BAR_GROUP_CLASSES = "group/status-group flex h-full min-w-0 shrink-0 items-center gap-px *:mx-0";
|
|
149
|
+
/**
|
|
150
|
+
* Every class a status-bar item wears, from the three things that vary.
|
|
151
|
+
*
|
|
152
|
+
* One function rather than a class per shape, because the shapes multiply:
|
|
153
|
+
* seven tones times two prominences times interactive-or-not is 28 items, and
|
|
154
|
+
* 28 hand-written strings drift. Ordering is load-bearing — the merge is
|
|
155
|
+
* last-one-wins, so the reset comes before the gutter that overrides it and the
|
|
156
|
+
* tone comes after the `text-inherit` that would otherwise flatten it.
|
|
157
|
+
*
|
|
158
|
+
* @param appearance - Tone, interactivity and prominence; every field optional.
|
|
159
|
+
* @returns The merged class string, with no duplicate or cancelled utilities.
|
|
160
|
+
* @example
|
|
161
|
+
* statusBarItemClasses({}) // exactly the static item's own chassis
|
|
162
|
+
* @example
|
|
163
|
+
* statusBarItemClasses({ tone: "danger", interactive: true, prominence: "filled" })
|
|
164
|
+
*/
|
|
165
|
+
function statusBarItemClasses(appearance) {
|
|
166
|
+
const { tone = "neutral", interactive = false, prominence = "quiet" } = appearance;
|
|
167
|
+
const filled = prominence === "filled";
|
|
168
|
+
return cn(ITEM_BASE, interactive ? INTERACTIVE_CHASSIS : void 0, interactive && tone === "neutral" ? "text-inherit" : void 0, interactive && !filled ? QUIET_GEOMETRY : void 0, filled ? FILLED_GEOMETRY : void 0, ITEM_TONES[tone], filled ? FILLED_TONES[tone] : void 0, interactive && !filled ? QUIET_GROUNDS : void 0, interactive && filled ? FILLED_GROUNDS[tone] : void 0);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { ITEM_BASE, STATUS_BAR_GROUP_CLASSES, statusBarItemClasses };
|
|
@@ -47,8 +47,76 @@ interface AppShellBarProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
|
47
47
|
* is worse than no cell.
|
|
48
48
|
*/
|
|
49
49
|
brand?: React.ReactNode;
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Extra classes for the header cell — the bordered box that spans the
|
|
52
|
+
* navigation column, not the row of brand content inside it. Anything about
|
|
53
|
+
* the cell's width, rim or gutter belongs here; anything about how the mark
|
|
54
|
+
* and its controls sit belongs on the nodes passed as `brand`.
|
|
55
|
+
*/
|
|
51
56
|
brandClassName?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Make the bar's dead space a window drag handle. Defaults to `false`.
|
|
59
|
+
*
|
|
60
|
+
* This is the same contract {@link TitleBar} has always offered, on the bar
|
|
61
|
+
* a console with a navigation column actually uses — a desktop app should
|
|
62
|
+
* not need a `TitleBar` *and* an `AppShell.Bar` stacked on top of each other
|
|
63
|
+
* to get one draggable row, which is two chrome rungs of a small window
|
|
64
|
+
* spent on furniture.
|
|
65
|
+
*
|
|
66
|
+
* Every structural surface is attributed and every interactive cluster is
|
|
67
|
+
* carved back out, because the two shells this ships into fail in opposite
|
|
68
|
+
* directions. Tauri matches the *exact* element under the cursor, so the
|
|
69
|
+
* brand cell and the rest cell carry the attribute themselves — a click on
|
|
70
|
+
* the cell's own dead space targets the cell, never the root beneath it, and
|
|
71
|
+
* a bar that only attributed its root would be draggable nowhere. Electron
|
|
72
|
+
* builds a geometric mask from painted boxes instead and excludes nothing on
|
|
73
|
+
* its own, so the brand content, the leading children and `actions` each sit
|
|
74
|
+
* in a cluster marked `app-region: no-drag` — without it a button that merely
|
|
75
|
+
* overlaps the bar stops receiving clicks at all. See `./_drag.ts`.
|
|
76
|
+
*
|
|
77
|
+
* **It changes how the clusters are sized, and that is a trade.** Off, each
|
|
78
|
+
* cluster grows into its cell, so a caller's own `ms-auto` reaches the cell's
|
|
79
|
+
* trailing rim. On, the clusters take their content's width and `actions` is
|
|
80
|
+
* pushed over with `ms-auto` instead, because a cluster that fills its cell
|
|
81
|
+
* is a `no-drag` rectangle painted over every dead pixel the cell just
|
|
82
|
+
* attributed — the bar would be draggable nowhere but its paddings and its
|
|
83
|
+
* one gap. The dead area belongs to the window in drag mode, and a caller who
|
|
84
|
+
* needs content to reach the rim there should say so on a box of their own
|
|
85
|
+
* inside the cluster.
|
|
86
|
+
*
|
|
87
|
+
* `select-none` comes with it: dragging a window and selecting the text you
|
|
88
|
+
* started the drag on are the same gesture, and the shell wins.
|
|
89
|
+
*
|
|
90
|
+
* Not wired here: double-click-to-maximise. Tauri's injected script already
|
|
91
|
+
* does it for any drag region, macOS's cancel-by-dragging-away included.
|
|
92
|
+
* Electron does not, and an Electron app has to listen for `dblclick` and
|
|
93
|
+
* toggle the window itself. Which of those an app is, is not something a
|
|
94
|
+
* layout component can know.
|
|
95
|
+
*/
|
|
96
|
+
platformDrag?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* A title centred on the **bar**, not between the cells beside it.
|
|
99
|
+
*
|
|
100
|
+
* Absolutely positioned against the bar's own box, so it stays put when a
|
|
101
|
+
* button appears in `actions` or the column rails — the whole reason a
|
|
102
|
+
* document name in desktop chrome is not just another flex child.
|
|
103
|
+
* `pointer-events-none` so the dead space around a short title still drags,
|
|
104
|
+
* which is also what makes it click through to the drag surface underneath
|
|
105
|
+
* in Tauri, whose hit-testing is the browser's own.
|
|
106
|
+
*
|
|
107
|
+
* Inert by construction. Anything the reader has to click belongs in
|
|
108
|
+
* `actions` or in `children`, where it can be carved out of the drag region.
|
|
109
|
+
*/
|
|
110
|
+
center?: React.ReactNode;
|
|
111
|
+
/**
|
|
112
|
+
* The trailing cluster: connection state, a run indicator, an overflow menu.
|
|
113
|
+
*
|
|
114
|
+
* Pinned to the bar's trailing edge and never squeezed, so it reads as window
|
|
115
|
+
* furniture rather than the end of a sentence. `children` keeps flowing into
|
|
116
|
+
* the same cell ahead of it, which is what lets an existing web-shell caller
|
|
117
|
+
* pass free-form content and gain a cluster without rearranging anything.
|
|
118
|
+
*/
|
|
119
|
+
actions?: React.ReactNode;
|
|
52
120
|
}
|
|
53
121
|
type AppShellBodyProps = React.ComponentPropsWithoutRef<"div">;
|
|
54
122
|
interface AppShellSidebarProps extends SidebarRootProps {
|
package/dist/chrome/app-shell.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Sidebar } from "../layout/sidebar.js";
|
|
|
5
5
|
import { CrossGlyph } from "../overlays/_glyphs.js";
|
|
6
6
|
import { Drawer } from "../overlays/drawer.js";
|
|
7
7
|
import { IconButton } from "../primitives/icon-button.js";
|
|
8
|
+
import { DRAG_CLASSES, NO_DRAG_CLASSES } from "./_drag.js";
|
|
8
9
|
import * as React from "react";
|
|
9
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
//#region src/chrome/app-shell.tsx
|
|
@@ -121,21 +122,47 @@ const AppShell = {
|
|
|
121
122
|
})
|
|
122
123
|
});
|
|
123
124
|
}),
|
|
124
|
-
Bar: React.forwardRef(function AppShellBar({ className, brand, brandClassName, children, ...elementProps }, ref) {
|
|
125
|
+
Bar: React.forwardRef(function AppShellBar({ className, brand, brandClassName, platformDrag = false, center, actions, children, ...elementProps }, ref) {
|
|
126
|
+
const dragProps = platformDrag ? { "data-tauri-drag-region": "" } : void 0;
|
|
127
|
+
const dragClasses = platformDrag ? DRAG_CLASSES : void 0;
|
|
128
|
+
const clusterClasses = cn("flex min-w-0 items-center gap-(--cue-space-3)", platformDrag ? NO_DRAG_CLASSES : "grow");
|
|
125
129
|
return /* @__PURE__ */ jsxs("div", {
|
|
126
130
|
ref,
|
|
127
131
|
"data-slot": "app-shell-bar",
|
|
128
|
-
|
|
132
|
+
...dragProps,
|
|
133
|
+
className: cn("relative flex h-(--cue-chrome-titlebar) w-full shrink-0 items-stretch border-b border-border bg-surface-1", dragClasses, platformDrag ? "select-none" : void 0, className),
|
|
129
134
|
...elementProps,
|
|
130
|
-
children: [
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
children: [
|
|
136
|
+
brand === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
137
|
+
"data-slot": "app-shell-brand",
|
|
138
|
+
...dragProps,
|
|
139
|
+
className: cn("flex w-auto min-w-0 shrink-0 items-center gap-(--cue-space-3) border-r border-border px-(--cue-pad-row-x) transition-[width] duration-150 ease-out md:w-(--cue-nav-width) motion-reduce:transition-none", dragClasses, brandClassName),
|
|
140
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
141
|
+
"data-slot": "app-shell-brand-content",
|
|
142
|
+
className: clusterClasses,
|
|
143
|
+
children: brand
|
|
144
|
+
})
|
|
145
|
+
}),
|
|
146
|
+
center === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
147
|
+
"data-slot": "app-shell-bar-center",
|
|
148
|
+
className: "pointer-events-none absolute top-1/2 left-1/2 max-w-[50%] -translate-x-1/2 -translate-y-1/2 truncate font-mono text-(length:--cue-text-label) text-fg-muted",
|
|
149
|
+
children: center
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ jsxs("div", {
|
|
152
|
+
"data-slot": "app-shell-bar-rest",
|
|
153
|
+
...dragProps,
|
|
154
|
+
className: cn("flex min-w-0 flex-1 items-center gap-(--cue-space-3) px-(--cue-pad-row-x)", dragClasses),
|
|
155
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
156
|
+
"data-slot": "app-shell-bar-leading",
|
|
157
|
+
className: clusterClasses,
|
|
158
|
+
children
|
|
159
|
+
}), actions === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
160
|
+
"data-slot": "app-shell-bar-actions",
|
|
161
|
+
className: cn(clusterClasses, "grow-0 shrink-0", platformDrag ? "ms-auto" : void 0),
|
|
162
|
+
children: actions
|
|
163
|
+
})]
|
|
164
|
+
})
|
|
165
|
+
]
|
|
139
166
|
});
|
|
140
167
|
}),
|
|
141
168
|
Body: React.forwardRef(function AppShellBody({ className, ...elementProps }, ref) {
|
package/dist/chrome/index.d.ts
CHANGED
|
@@ -13,9 +13,10 @@ import { Row, RowProps, SeamList, SeamListProps, rowVariants } from "./row.js";
|
|
|
13
13
|
import { ScrollArea, ScrollAreaOrientation, ScrollAreaProps } from "./scroll-area.js";
|
|
14
14
|
import { SectionHeader, SectionHeaderProps } from "./section-header.js";
|
|
15
15
|
import { SegmentedControl, SegmentedControlItem, SegmentedControlProps, SegmentedControlSize } from "./segmented-control.js";
|
|
16
|
-
import {
|
|
16
|
+
import { StatusBarProminence } from "./_status-bar.js";
|
|
17
|
+
import { StatusBar, StatusBarGroupProps, StatusBarItemProps, StatusBarProps, StatusBarTone } from "./status-bar.js";
|
|
17
18
|
import { ScrollableTabsList, ScrollableTabsListProps, Tabs, TabsListProps, TabsPanelProps, TabsRootProps, TabsTabProps } from "./tabs.js";
|
|
18
19
|
import { TitleBar, TitleBarProps } from "./title-bar.js";
|
|
19
20
|
import { Tree, TreeProps, TreeVisibility, TreeVisibilityToggle, TreeVisibilityToggleProps } from "./tree.js";
|
|
20
21
|
import { Toolbar, ToolbarButtonProps, ToolbarGroupProps, ToolbarRootProps, ToolbarSeparatorProps } from "./toolbar.js";
|
|
21
|
-
export { AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBarProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatusBar, type StatusBarItemProps, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
|
|
22
|
+
export { AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBarProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatusBar, type StatusBarGroupProps, type StatusBarItemProps, type StatusBarProminence, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
|