@orbytes/astrolab 0.3.0 → 0.4.0-next.2
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 +261 -116
- package/bin/pin-gallery.mjs +53 -19
- package/defaults.mjs +72 -20
- package/dist/core/virtual-module/virtual-routes.js +12 -1
- package/docs/PIN-CONTRACT.md +84 -10
- package/docs/PIN.md +117 -37
- package/index.d.ts +41 -23
- package/index.mjs +46 -91
- package/package.json +7 -3
- package/src/Home.astro +166 -264
- package/src/LabHead.astro +37 -1047
- package/src/chrome/ActionsMenu.astro +97 -0
- package/src/chrome/ComponentCard.astro +76 -0
- package/src/chrome/Icon.astro +21 -0
- package/src/chrome/LICENSE-icons +43 -0
- package/src/chrome/Nav.astro +131 -0
- package/src/chrome/Panel.astro +117 -0
- package/src/chrome/Properties.astro +104 -0
- package/src/chrome/SectionsTree.astro +128 -0
- package/src/chrome/Shell.astro +120 -0
- package/src/chrome/Sprite.astro +23 -0
- package/src/chrome/StoryView.astro +192 -0
- package/src/chrome/Tree.astro +86 -0
- package/src/chrome/ViewportControls.astro +173 -0
- package/src/chrome/ViewportStage.astro +55 -0
- package/src/chrome/fonts/OFL.txt +93 -0
- package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/src/chrome/icons.ts +68 -0
- package/src/chrome/marks-client.ts +75 -0
- package/src/chrome/model.ts +156 -0
- package/src/chrome/navbar-client.ts +324 -0
- package/src/chrome/params-client.ts +434 -0
- package/src/chrome/pins-data.ts +63 -0
- package/src/chrome/shell-client.ts +468 -0
- package/src/chrome/site-data.ts +230 -0
- package/src/chrome/trees.ts +257 -0
- package/src/chrome/viewport-client.ts +405 -0
- package/src/chrome/views/Assets.astro +125 -0
- package/src/chrome/views/Pages.astro +214 -0
- package/src/chrome/views/Placeholder.astro +37 -0
- package/src/chrome/views/Tasks.astro +79 -0
- package/src/core/LICENSE-astrobook +21 -0
- package/src/core/lib/components/home.astro +4 -2
- package/src/core/lib/pages/story.astro +12 -10
- package/src/core/utils/kebab-case.ts +2 -2
- package/src/core/virtual-module/virtual-routes.ts +20 -4
- package/src/pin/board.mjs +414 -190
- package/src/pin/index.mjs +67 -22
- package/src/pin/tickets.mjs +6 -5
- package/src/pin/toolbar.js +82 -4
- package/src/shell/Browse.astro +131 -353
- package/src/shell/Viewport.astro +22 -1315
- package/src/shell/lab-index.ts +28 -18
- package/src/shell/lab-params.ts +113 -6
- package/src/shell/live-files.mjs +212 -10
- package/src/shell/marks.mjs +17 -41
- package/src/ui/components/app.astro +5 -7
- package/src/ui/components/preview-layout.astro +17 -0
- package/src/ui/components/theme-script.astro +17 -5
- package/src/ui/lab.css +3754 -371
- package/virtual.d.ts +13 -4
- package/bin/lab-cull.mjs +0 -401
- package/src/shell/CardGrid.astro +0 -297
- package/src/ui/components/build-path.ts +0 -13
- package/src/ui/components/build-tree.ts +0 -108
- package/src/ui/components/collapse-duration.ts +0 -28
- package/src/ui/components/compress-terms.ts +0 -10
- package/src/ui/components/dashboard-layout.astro +0 -39
- package/src/ui/components/home.astro +0 -65
- package/src/ui/components/layout.astro +0 -110
- package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
- package/src/ui/components/sidebar-button-search.astro +0 -23
- package/src/ui/components/sidebar-button-theme.astro +0 -9
- package/src/ui/components/sidebar-button.astro +0 -24
- package/src/ui/components/sidebar-resize-handle.astro +0 -74
- package/src/ui/components/sidebar-search-panel.astro +0 -41
- package/src/ui/components/sidebar-search-script.ts +0 -103
- package/src/ui/components/sidebar-title.astro +0 -17
- package/src/ui/components/sidebar-tree-node.astro +0 -143
- package/src/ui/components/sidebar-tree.astro +0 -84
- package/src/ui/components/sidebar.astro +0 -29
- package/src/ui/components/theme-toggle.astro +0 -63
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The navbar's ⋯ button and its menu — Figma `Astrolab` › Menu › Type=Actions. Everything that
|
|
3
|
+
// leaves the lab (VS Code, the bare variant full screen, staging, a link at this exact size), then
|
|
4
|
+
// two sections that are switches rather than places:
|
|
5
|
+
//
|
|
6
|
+
// Pins Show pins (on) and Show resolved (off) — they govern the markers drawn on the framed
|
|
7
|
+
// preview. They moved here from the navbar (decided 2026-09-24); the navbar keeps only
|
|
8
|
+
// Comment. Only while the pin board runs, which is `astro dev` with the board enabled.
|
|
9
|
+
// Preview Dark mode — the PREVIEW's own light or dark, separate from the chrome's. Hidden until
|
|
10
|
+
// the framed site turns out to have a dark mode (./viewport-client.ts looks for one).
|
|
11
|
+
//
|
|
12
|
+
// Checkable rows are `menuitemcheckbox` buttons, so the menu stays one list a keyboard can walk.
|
|
13
|
+
// Behaviour: ./navbar-client.ts (pins), ./viewport-client.ts (copy link, dark mode).
|
|
14
|
+
import Icon from "./Icon.astro";
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
editorUrl?: string | null;
|
|
18
|
+
/** The bare variant, alone in a tab. */
|
|
19
|
+
fullScreenUrl?: string | null;
|
|
20
|
+
stagingUrl?: string | null;
|
|
21
|
+
/** Show the Pins section — only where the pin board runs. */
|
|
22
|
+
pins: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { editorUrl, fullScreenUrl, stagingUrl, pins } = Astro.props;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<button
|
|
29
|
+
class="lab-tool lab-tool--icon lab-nb__more"
|
|
30
|
+
type="button"
|
|
31
|
+
popovertarget="lab-actions-menu"
|
|
32
|
+
title="More actions"
|
|
33
|
+
>
|
|
34
|
+
<Icon name="more" size="sm" />
|
|
35
|
+
<span class="lab-visually-hidden">More actions</span>
|
|
36
|
+
</button>
|
|
37
|
+
<div class="lab-menu lab-menu--actions" id="lab-actions-menu" popover role="menu" data-lab-align="end">
|
|
38
|
+
{
|
|
39
|
+
editorUrl && (
|
|
40
|
+
<a class="lab-menu__item" role="menuitem" href={editorUrl}>
|
|
41
|
+
<span class="lab-menu__mark"><Icon name="code" size="sm" /></span>
|
|
42
|
+
<span class="lab-menu__text">Open in VS Code</span>
|
|
43
|
+
</a>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
{
|
|
47
|
+
fullScreenUrl && (
|
|
48
|
+
<a class="lab-menu__item" role="menuitem" href={fullScreenUrl} target="_blank" rel="noopener">
|
|
49
|
+
<span class="lab-menu__mark"><Icon name="expand" size="sm" /></span>
|
|
50
|
+
<span class="lab-menu__text">Open full screen</span>
|
|
51
|
+
<span class="lab-menu__hint">the bare variant</span>
|
|
52
|
+
</a>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
{
|
|
56
|
+
stagingUrl && (
|
|
57
|
+
<a class="lab-menu__item" role="menuitem" href={stagingUrl} target="_blank" rel="noopener">
|
|
58
|
+
<span class="lab-menu__mark"><Icon name="staging" size="sm" /></span>
|
|
59
|
+
<span class="lab-menu__text">View on staging</span>
|
|
60
|
+
</a>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
{(editorUrl || fullScreenUrl || stagingUrl) && <hr />}
|
|
64
|
+
<button class="lab-menu__item" type="button" role="menuitem" data-lab-copy-link>
|
|
65
|
+
<span class="lab-menu__mark"><Icon name="link" size="sm" /></span>
|
|
66
|
+
<span class="lab-menu__text" data-lab-copy-label>Copy link to this exact size</span>
|
|
67
|
+
<span class="lab-menu__hint" data-lab-copy-size></span>
|
|
68
|
+
</button>
|
|
69
|
+
{
|
|
70
|
+
pins && (
|
|
71
|
+
<>
|
|
72
|
+
<hr />
|
|
73
|
+
<p class="lab-menu__label" id="lab-actions-pins">Pins</p>
|
|
74
|
+
<div role="group" aria-labelledby="lab-actions-pins">
|
|
75
|
+
<button class="lab-menu__item" type="button" role="menuitemcheckbox" aria-checked="true" data-lab-pins-toggle="show">
|
|
76
|
+
<span class="lab-menu__mark"><span class="lab-menu__box" aria-hidden="true" /></span>
|
|
77
|
+
<span class="lab-menu__text">Show pins</span>
|
|
78
|
+
</button>
|
|
79
|
+
<button class="lab-menu__item" type="button" role="menuitemcheckbox" aria-checked="false" data-lab-pins-toggle="resolved">
|
|
80
|
+
<span class="lab-menu__mark"><span class="lab-menu__box" aria-hidden="true" /></span>
|
|
81
|
+
<span class="lab-menu__text">Show resolved</span>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
<div data-lab-preview-group hidden>
|
|
88
|
+
<hr />
|
|
89
|
+
<p class="lab-menu__label" id="lab-actions-preview">Preview</p>
|
|
90
|
+
<div role="group" aria-labelledby="lab-actions-preview">
|
|
91
|
+
<button class="lab-menu__item" type="button" role="menuitemcheckbox" aria-checked="false" data-lab-preview-theme>
|
|
92
|
+
<span class="lab-menu__mark"><span class="lab-menu__box" aria-hidden="true" /></span>
|
|
93
|
+
<span class="lab-menu__text">Dark mode</span>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
// One card per COMPONENT (a stories module), not per story — the tree stops at the component and
|
|
3
|
+
// so does the grid. The thumbnail is its first story's bare render (/lab/stories/<id>) in a lazy
|
|
4
|
+
// <iframe> sized to a virtual viewport and scaled to the card by ../chrome/shell-client.ts: page
|
|
5
|
+
// width for section-like tiers, a component stage otherwise. The whole card opens the component;
|
|
6
|
+
// the icon links under it sit above that link.
|
|
7
|
+
//
|
|
8
|
+
// A live component carries the Sections tree's slot badge, not a "live · slot N of M" pill: one sign
|
|
9
|
+
// for "live in slot N" across the lab (decided 2026-09-24, ASTROL-26). The title keeps the long form.
|
|
10
|
+
import Icon from "./Icon.astro";
|
|
11
|
+
import { componentState, type LabComponent } from "./trees";
|
|
12
|
+
import { isSectionLike, livePillText, liveTitle } from "../shell/lab-index";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
mod: LabComponent;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { mod } = Astro.props;
|
|
19
|
+
const first = mod.stories[0]!;
|
|
20
|
+
const liveStory = mod.stories.find((s) => s.live);
|
|
21
|
+
const section = isSectionLike(first);
|
|
22
|
+
const state = componentState(mod);
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<article class="lab-card" data-lab-search={state.search} data-lab-facets={state.facets}>
|
|
26
|
+
<a class="lab-card__main" href={first.dashboardUrl}>
|
|
27
|
+
<span class="lab-card__thumb" data-frame-w={section ? 1440 : 960} data-frame-h={section ? 900 : 540}>
|
|
28
|
+
<iframe src={first.storyUrl} title={`${mod.moduleName} — ${first.storyName}`} loading="lazy" tabindex="-1"></iframe>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="lab-card__body">
|
|
31
|
+
<span class="lab-card__name">
|
|
32
|
+
<span>{mod.moduleName}</span>
|
|
33
|
+
{mod.version && <span class="lab-card__version">{mod.version}</span>}
|
|
34
|
+
</span>
|
|
35
|
+
<span class="lab-card__sub">
|
|
36
|
+
{mod.stories.length} variant{mod.stories.length === 1 ? "" : "s"}{mod.summary ? ` · ${mod.summary}` : ""}
|
|
37
|
+
</span>
|
|
38
|
+
<span class="lab-card__pills">
|
|
39
|
+
{liveStory?.live && (
|
|
40
|
+
<span class="lab-slot" data-state="slotted" title={liveTitle(liveStory) ?? livePillText(liveStory) ?? undefined}>
|
|
41
|
+
<span class="lab-visually-hidden">Live in slot </span>{liveStory.live.slot}
|
|
42
|
+
</span>
|
|
43
|
+
)}
|
|
44
|
+
{!mod.live && mod.usedBy.length > 0 && (
|
|
45
|
+
<span class="lab-pill lab-pill--used" title={mod.usedBy.join("\n")}>used by {mod.usedBy.length}</span>
|
|
46
|
+
)}
|
|
47
|
+
{mod.responsive && (
|
|
48
|
+
<span
|
|
49
|
+
class:list={["lab-pill", mod.responsive.done ? "lab-pill--responsive" : "lab-pill--unresponsive"]}
|
|
50
|
+
title={mod.responsive.approved ? "approved for responsive work" : "not approved for responsive work yet"}
|
|
51
|
+
>
|
|
52
|
+
{mod.responsive.done ? "responsive" : "not responsive"}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
</span>
|
|
56
|
+
</span>
|
|
57
|
+
</a>
|
|
58
|
+
{
|
|
59
|
+
(first.editorUrl || first.stagingUrl) && (
|
|
60
|
+
<span class="lab-card__links">
|
|
61
|
+
{first.editorUrl && (
|
|
62
|
+
<a class="lab-icon-btn lab-card__link" href={first.editorUrl} title="Open in VS Code">
|
|
63
|
+
<Icon name="code" />
|
|
64
|
+
<span class="lab-visually-hidden">Open in VS Code</span>
|
|
65
|
+
</a>
|
|
66
|
+
)}
|
|
67
|
+
{first.stagingUrl && (
|
|
68
|
+
<a class="lab-icon-btn lab-card__link" href={first.stagingUrl} target="_blank" rel="noopener" title="View on staging">
|
|
69
|
+
<Icon name="external" />
|
|
70
|
+
<span class="lab-visually-hidden">View on staging</span>
|
|
71
|
+
</a>
|
|
72
|
+
)}
|
|
73
|
+
</span>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
</article>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
// One icon from the sprite (./Sprite.astro). Decorative by default; pass `label` when the icon is
|
|
3
|
+
// the only thing naming a control.
|
|
4
|
+
import type { IconName } from "./icons";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
name: IconName;
|
|
8
|
+
size?: "sm" | "md";
|
|
9
|
+
class?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { name, size = "md", class: extra, label } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<svg
|
|
17
|
+
class:list={["lab-icon", size === "sm" && "lab-icon--sm", extra]}
|
|
18
|
+
aria-hidden={label ? undefined : "true"}
|
|
19
|
+
role={label ? "img" : undefined}
|
|
20
|
+
aria-label={label}
|
|
21
|
+
><use href={`#lab-i-${name}`}></use></svg>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Level 1 — the main nav (Figma `Sidebar navigation`, the white card). Home, the Site group (Pages,
|
|
3
|
+
// one entry per tier, Assets), the Tasks group when the pin board is running, and Support and
|
|
4
|
+
// Settings at the foot. Collapses to a 64px icon rail (⌘⇧B); in the rail a group's items open as
|
|
5
|
+
// a flyout on hover or focus, under the group's name, drawn by CSS alone (../ui/lab.css › level 1
|
|
6
|
+
// as a rail).
|
|
7
|
+
//
|
|
8
|
+
// The header carries the collapse button and the LAB's own light/dark toggle — a moon, beside the
|
|
9
|
+
// logo, under the logomark in the rail (decided 2026-09-24). It switches the chrome only; the
|
|
10
|
+
// preview's light/dark is a separate setting of its own.
|
|
11
|
+
//
|
|
12
|
+
// The logo and the Support / Settings pages are placeholders until they are designed.
|
|
13
|
+
import Icon from "./Icon.astro";
|
|
14
|
+
import { labConfig, navModel, type NavKey } from "./model";
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
active: NavKey;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { active } = Astro.props;
|
|
21
|
+
const model = navModel();
|
|
22
|
+
const isOpen = (group: { items: { key: NavKey }[] }) => group.items.some((item) => item.key === active);
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<nav class="lab-l1" aria-label="Lab">
|
|
26
|
+
<div class="lab-l1__card">
|
|
27
|
+
<div class="lab-l1__top">
|
|
28
|
+
<div class="lab-l1__header">
|
|
29
|
+
<a class="lab-brand" href={model.home.href} title={labConfig.title}>
|
|
30
|
+
<span class="lab-brand__mark" aria-hidden="true"></span>
|
|
31
|
+
<span class="lab-brand__name">Astrolab</span>
|
|
32
|
+
</a>
|
|
33
|
+
{/* The collapse button's name and tooltip follow the state: "Collapse menu" when level 1
|
|
34
|
+
is open, "Expand menu" in the rail (./shell-client.ts keeps aria-label in step). */}
|
|
35
|
+
<button
|
|
36
|
+
class="lab-icon-btn lab-l1__btn lab-tipped"
|
|
37
|
+
type="button"
|
|
38
|
+
data-lab-toggle="l1"
|
|
39
|
+
data-lab-l1-btn
|
|
40
|
+
aria-label="Collapse menu"
|
|
41
|
+
aria-describedby="lab-tip-l1"
|
|
42
|
+
>
|
|
43
|
+
<Icon name="chevrons-left" size="sm" class="lab-l1-btn__collapse" />
|
|
44
|
+
<Icon name="chevrons-right" size="sm" class="lab-l1-btn__expand" />
|
|
45
|
+
<span class="lab-tip lab-tip--l1" id="lab-tip-l1" role="tooltip">
|
|
46
|
+
<span class="lab-tip__title">
|
|
47
|
+
<span class="lab-l1-btn__collapse">Collapse menu</span><span class="lab-l1-btn__expand">Expand menu</span> · ⌘⇧B
|
|
48
|
+
</span>
|
|
49
|
+
<span class="lab-tip__text">⌘B shows or hides the panel</span>
|
|
50
|
+
<span class="lab-tip__text">⌘. hides both (focus mode)</span>
|
|
51
|
+
</span>
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
class="lab-icon-btn lab-l1__btn lab-theme-btn"
|
|
55
|
+
type="button"
|
|
56
|
+
data-lab-theme-toggle
|
|
57
|
+
aria-pressed="false"
|
|
58
|
+
aria-label="Dark mode for the lab"
|
|
59
|
+
title="Dark mode for the lab"
|
|
60
|
+
>
|
|
61
|
+
<Icon name="moon" size="sm" />
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="lab-l1__nav">
|
|
66
|
+
<a
|
|
67
|
+
class="lab-nav-item"
|
|
68
|
+
href={model.home.href}
|
|
69
|
+
aria-current={active === "home" ? "page" : undefined}
|
|
70
|
+
data-tip={model.home.label}
|
|
71
|
+
data-lab-nav-key="home"
|
|
72
|
+
>
|
|
73
|
+
<Icon name={model.home.icon} />
|
|
74
|
+
<span class="lab-nav-item__label">{model.home.label}</span>
|
|
75
|
+
</a>
|
|
76
|
+
{
|
|
77
|
+
model.groups.map((group) => (
|
|
78
|
+
<>
|
|
79
|
+
<hr class="lab-divider" />
|
|
80
|
+
<div class="lab-nav-group" data-lab-group={group.id} data-lab-holds-current={isOpen(group) ? "" : undefined}>
|
|
81
|
+
<button
|
|
82
|
+
class="lab-nav-item"
|
|
83
|
+
type="button"
|
|
84
|
+
aria-expanded="true"
|
|
85
|
+
aria-current={isOpen(group) ? "true" : undefined}
|
|
86
|
+
data-tip={group.label}
|
|
87
|
+
data-lab-group-toggle
|
|
88
|
+
>
|
|
89
|
+
<Icon name={group.icon} />
|
|
90
|
+
<span class="lab-nav-item__label">{group.label}</span>
|
|
91
|
+
<Icon name="chevron-down" size="sm" class="lab-nav-group__chevron" />
|
|
92
|
+
</button>
|
|
93
|
+
<div class="lab-nav-group__items">
|
|
94
|
+
<p class="lab-nav-group__flyout-label" aria-hidden="true">{group.label}</p>
|
|
95
|
+
{group.items.map((item) => (
|
|
96
|
+
<a
|
|
97
|
+
class="lab-nav-item"
|
|
98
|
+
href={item.href}
|
|
99
|
+
aria-current={item.key === active ? "page" : undefined}
|
|
100
|
+
data-lab-nav-key={item.key}
|
|
101
|
+
>
|
|
102
|
+
<Icon name={item.icon} />
|
|
103
|
+
<span class="lab-nav-item__label">{item.label}</span>
|
|
104
|
+
</a>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</>
|
|
109
|
+
))
|
|
110
|
+
}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="lab-l1__footer">
|
|
115
|
+
{
|
|
116
|
+
model.footer.map((item) => (
|
|
117
|
+
<a
|
|
118
|
+
class="lab-nav-item"
|
|
119
|
+
href={item.href}
|
|
120
|
+
aria-current={item.key === active ? "page" : undefined}
|
|
121
|
+
data-tip={item.label}
|
|
122
|
+
data-lab-nav-key={item.key}
|
|
123
|
+
>
|
|
124
|
+
<Icon name={item.icon} />
|
|
125
|
+
<span class="lab-nav-item__label">{item.label}</span>
|
|
126
|
+
</a>
|
|
127
|
+
))
|
|
128
|
+
}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</nav>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Level 2 — the panel (Figma `Sidebar navigation`, the grey column): breadcrumb, search with its
|
|
3
|
+
// filter menu, the tree in the default slot, and the coffee footer. Closes with ⌘B or its own
|
|
4
|
+
// button; while closed it peeks out over the stage — below the navbar, never over it — when the
|
|
5
|
+
// pointer reaches the stage's left edge.
|
|
6
|
+
//
|
|
7
|
+
// Only the Site views have one. Kanban Board and All Tasks have no level 2 (decided 2026-09-24).
|
|
8
|
+
//
|
|
9
|
+
// The coffee link is a placeholder until it has a destination.
|
|
10
|
+
import Icon from "./Icon.astro";
|
|
11
|
+
import { labBase, type Crumb, type FilterOption } from "./model";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
crumbs: Crumb[];
|
|
15
|
+
/** Label for the panel landmark, e.g. "Pages". */
|
|
16
|
+
label: string;
|
|
17
|
+
search?: boolean;
|
|
18
|
+
searchPlaceholder?: string;
|
|
19
|
+
filters?: FilterOption[];
|
|
20
|
+
filterLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { crumbs, label, search = true, searchPlaceholder = "Search", filters, filterLabel = "Show" } = Astro.props;
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<aside class="lab-l2" id="lab-l2" aria-label={label}>
|
|
27
|
+
<div class="lab-l2__header">
|
|
28
|
+
<div class="lab-l2__crumbs-row">
|
|
29
|
+
<nav aria-label="Breadcrumb">
|
|
30
|
+
<ol class="lab-crumbs">
|
|
31
|
+
<li class="lab-crumbs__item">
|
|
32
|
+
<a href={labBase || "/"} title="Lab home"><Icon name="home" /><span class="lab-visually-hidden">Lab home</span></a>
|
|
33
|
+
</li>
|
|
34
|
+
{
|
|
35
|
+
crumbs.map((crumb, i) => (
|
|
36
|
+
<li class="lab-crumbs__item" aria-current={i === crumbs.length - 1 ? "page" : undefined}>
|
|
37
|
+
{crumb.href && i < crumbs.length - 1 ? <a href={crumb.href}>{crumb.label}</a> : crumb.label}
|
|
38
|
+
</li>
|
|
39
|
+
))
|
|
40
|
+
}
|
|
41
|
+
</ol>
|
|
42
|
+
</nav>
|
|
43
|
+
{/* One button, two actions: docked, it hides the panel; while the panel only peeks, it keeps
|
|
44
|
+
it open. Its name follows the action — ./shell-client.ts swaps the label when a peek
|
|
45
|
+
starts and ends (decided 2026-09-24). */}
|
|
46
|
+
<button
|
|
47
|
+
class="lab-icon-btn lab-l2__close"
|
|
48
|
+
type="button"
|
|
49
|
+
data-lab-toggle="l2"
|
|
50
|
+
data-lab-l2-btn
|
|
51
|
+
aria-label="Hide panel"
|
|
52
|
+
title="Hide panel · ⌘B"
|
|
53
|
+
>
|
|
54
|
+
<Icon name="panel-close" class="lab-l2__close-hide" />
|
|
55
|
+
<Icon name="panel-open" class="lab-l2__close-dock" />
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
search && (
|
|
61
|
+
<div class="lab-input">
|
|
62
|
+
<Icon name="search" size="sm" />
|
|
63
|
+
<input
|
|
64
|
+
class="lab-input__field"
|
|
65
|
+
type="search"
|
|
66
|
+
placeholder={searchPlaceholder}
|
|
67
|
+
aria-label={searchPlaceholder}
|
|
68
|
+
autocomplete="off"
|
|
69
|
+
spellcheck="false"
|
|
70
|
+
data-lab-tree-search
|
|
71
|
+
/>
|
|
72
|
+
<kbd class="lab-input__kbd">⌘K</kbd>
|
|
73
|
+
{filters && filters.length > 0 && (
|
|
74
|
+
<button
|
|
75
|
+
class="lab-icon-btn"
|
|
76
|
+
type="button"
|
|
77
|
+
popovertarget="lab-tree-filter"
|
|
78
|
+
title="Filter"
|
|
79
|
+
data-lab-filter-btn
|
|
80
|
+
>
|
|
81
|
+
<Icon name="filter" size="sm" />
|
|
82
|
+
<span class="lab-visually-hidden">Filter</span>
|
|
83
|
+
</button>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
{
|
|
89
|
+
filters && filters.length > 0 && (
|
|
90
|
+
<div class="lab-menu" id="lab-tree-filter" popover role="menu" data-lab-align="end">
|
|
91
|
+
<p class="lab-menu__label">{filterLabel}</p>
|
|
92
|
+
<button class="lab-menu__item" type="button" role="menuitemradio" aria-checked="true" data-lab-filter="">
|
|
93
|
+
All
|
|
94
|
+
<Icon name="check" size="sm" class="lab-menu__tick" />
|
|
95
|
+
</button>
|
|
96
|
+
{filters.map((option) => (
|
|
97
|
+
<button class="lab-menu__item" type="button" role="menuitemradio" aria-checked="false" data-lab-filter={option.value}>
|
|
98
|
+
{option.label}
|
|
99
|
+
<Icon name="check" size="sm" class="lab-menu__tick" />
|
|
100
|
+
</button>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="lab-l2__body" data-lab-l2-body>
|
|
108
|
+
<slot />
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="lab-l2__footer">
|
|
112
|
+
<span class="lab-nav-item lab-coffee" aria-disabled="true" title="The link is still to come">
|
|
113
|
+
<Icon name="coffee" />
|
|
114
|
+
<span class="lab-nav-item__label">Buy Me A Coffee</span>
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
</aside>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The eye beside a component's (or page's) name, and the Properties panel it opens. It replaces the
|
|
3
|
+
// navbar's status pills and their status menu (decided 2026-09-24: no pills in the identity — a
|
|
4
|
+
// panel that opens when you hover a small eye beside the name). Figma `Astrolab` › Menu ›
|
|
5
|
+
// Type=Properties, and the four cases on the `Lab / Properties` sheet.
|
|
6
|
+
//
|
|
7
|
+
// In order: On the site (live and its slot, used by, or not on any page), Open pins (with a row to
|
|
8
|
+
// the task list), Responsive (the two marks, sections only), Source file. Mark for deletion is not
|
|
9
|
+
// here: it was removed from the lab as a feature (decided 2026-09-24).
|
|
10
|
+
//
|
|
11
|
+
// Not a menu. The panel is a `popover="manual"` dialog: hovering the eye opens it after a short
|
|
12
|
+
// delay and it stays open while the pointer is on the eye or the panel; clicking the eye (or
|
|
13
|
+
// Enter / Space) pins it open until Escape or a click outside. Behaviour: ./navbar-client.ts. The
|
|
14
|
+
// marks are live checkboxes whenever the panel is open (./marks-client.ts).
|
|
15
|
+
import Icon from "./Icon.astro";
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
/** One line per fact under "On the site"; `live` draws the green dot, otherwise it is grey. */
|
|
19
|
+
site: { text: string; live: boolean }[];
|
|
20
|
+
/** Open and resolved pins, and where the row leads. Null when the pin board is not running. */
|
|
21
|
+
pins: { open: number; resolved: number; href: string } | null;
|
|
22
|
+
/** The responsive marks for this file, or null when the lab keeps none for it. */
|
|
23
|
+
responsive: { file: string; approved: boolean; done: boolean } | null;
|
|
24
|
+
source: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { site, pins, responsive, source } = Astro.props;
|
|
28
|
+
|
|
29
|
+
const pinLine = (open: number, resolved: number) =>
|
|
30
|
+
open === 0 && resolved === 0 ? "No pins" : `${open === 0 ? "No open pins" : `${open} open`} · ${resolved} resolved`;
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<button
|
|
34
|
+
class="lab-tool lab-tool--icon"
|
|
35
|
+
type="button"
|
|
36
|
+
aria-expanded="false"
|
|
37
|
+
aria-controls="lab-props"
|
|
38
|
+
title="Properties — hover to look, click to pin open"
|
|
39
|
+
data-lab-props-trigger
|
|
40
|
+
>
|
|
41
|
+
<Icon name="properties" size="sm" />
|
|
42
|
+
<span class="lab-visually-hidden">Properties</span>
|
|
43
|
+
</button>
|
|
44
|
+
<div class="lab-menu lab-props" id="lab-props" popover="manual" role="dialog" aria-label="Properties" data-lab-props>
|
|
45
|
+
<p class="lab-menu__label">On the site</p>
|
|
46
|
+
<div class="lab-props__facts">
|
|
47
|
+
{
|
|
48
|
+
site.map((line) => (
|
|
49
|
+
<p class="lab-props__fact">
|
|
50
|
+
<span class:list={["lab-props__dot", line.live && "lab-props__dot--live"]} aria-hidden="true" />
|
|
51
|
+
{line.text}
|
|
52
|
+
</p>
|
|
53
|
+
))
|
|
54
|
+
}
|
|
55
|
+
</div>
|
|
56
|
+
{
|
|
57
|
+
pins && (
|
|
58
|
+
<>
|
|
59
|
+
<hr />
|
|
60
|
+
<p class="lab-menu__label">Open pins</p>
|
|
61
|
+
<a class="lab-menu__item" href={pins.href}>
|
|
62
|
+
<span class="lab-menu__mark lab-props__pin">
|
|
63
|
+
<Icon name="marker" size="sm" />
|
|
64
|
+
</span>
|
|
65
|
+
<span class="lab-menu__text">{pinLine(pins.open, pins.resolved)}</span>
|
|
66
|
+
<Icon name="chevron-right" size="sm" class="lab-menu__trail" />
|
|
67
|
+
</a>
|
|
68
|
+
</>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
{
|
|
72
|
+
responsive && (
|
|
73
|
+
<>
|
|
74
|
+
<hr />
|
|
75
|
+
<p class="lab-menu__label">Responsive</p>
|
|
76
|
+
<label class="lab-menu__item">
|
|
77
|
+
<span class="lab-menu__mark">
|
|
78
|
+
<input class="lab-checkbox" type="checkbox" data-lab-mark="approved" data-file={responsive.file} checked={responsive.approved} disabled />
|
|
79
|
+
</span>
|
|
80
|
+
<span class="lab-menu__text">Approved for responsive work</span>
|
|
81
|
+
</label>
|
|
82
|
+
<label class="lab-menu__item">
|
|
83
|
+
<span class="lab-menu__mark">
|
|
84
|
+
<input class="lab-checkbox" type="checkbox" data-lab-mark="done" data-file={responsive.file} checked={responsive.done} disabled />
|
|
85
|
+
</span>
|
|
86
|
+
<span class="lab-menu__text">Made responsive</span>
|
|
87
|
+
</label>
|
|
88
|
+
</>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
{
|
|
92
|
+
source && (
|
|
93
|
+
<>
|
|
94
|
+
<hr />
|
|
95
|
+
<p class="lab-menu__label">Source file</p>
|
|
96
|
+
<p class="lab-props__source">{source}</p>
|
|
97
|
+
</>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
<hr />
|
|
101
|
+
<p class="lab-props__note" data-lab-marks-note>
|
|
102
|
+
{responsive ? "Click the eye to pin · marks save to the repo" : "Click the eye to pin"}
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|