anentrypoint-design 0.0.381 → 0.0.382
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/dist/247420.css +228 -162
- package/dist/247420.js +24 -24
- package/package.json +1 -1
- package/src/components/content/avatar.js +31 -0
- package/src/components/content/charts.js +50 -0
- package/src/components/content/cli.js +46 -0
- package/src/components/content/feedback.js +77 -0
- package/src/components/content/fields.js +136 -0
- package/src/components/content/hero.js +146 -0
- package/src/components/content/lists.js +85 -0
- package/src/components/content/panel.js +79 -0
- package/src/components/content/row.js +115 -0
- package/src/components/content/table.js +97 -0
- package/src/components/content/views.js +81 -0
- package/src/components/content.js +29 -861
- package/src/components/editor-primitives/batch.js +60 -0
- package/src/components/editor-primitives/chrome.js +146 -0
- package/src/components/editor-primitives/collapse.js +46 -0
- package/src/components/editor-primitives/context-menu.js +127 -0
- package/src/components/editor-primitives/diagnostics.js +44 -0
- package/src/components/editor-primitives/focus-trap.js +26 -0
- package/src/components/editor-primitives/json-viewer.js +123 -0
- package/src/components/editor-primitives/layout.js +89 -0
- package/src/components/editor-primitives/modals.js +89 -0
- package/src/components/editor-primitives/pager.js +74 -0
- package/src/components/editor-primitives/property-grid.js +57 -0
- package/src/components/editor-primitives/shared.js +18 -0
- package/src/components/editor-primitives/split-panel.js +101 -0
- package/src/components/editor-primitives/toast.js +59 -0
- package/src/components/editor-primitives/tree.js +75 -0
- package/src/components/editor-primitives.js +35 -1048
- package/src/components/overlay-primitives/approval-prompt.js +38 -0
- package/src/components/overlay-primitives/auth-modal.js +89 -0
- package/src/components/overlay-primitives/command-palette.js +101 -0
- package/src/components/overlay-primitives/emoji-picker.js +103 -0
- package/src/components/overlay-primitives/floating.js +146 -0
- package/src/components/overlay-primitives/full-screen.js +45 -0
- package/src/components/overlay-primitives/menus.js +131 -0
- package/src/components/overlay-primitives/popover.js +51 -0
- package/src/components/overlay-primitives/roving-menu.js +73 -0
- package/src/components/overlay-primitives/settings-popover.js +85 -0
- package/src/components/overlay-primitives/tooltip.js +55 -0
- package/src/components/overlay-primitives.js +33 -855
- package/src/css/app-shell/chat-polish.css +39 -32
- package/src/css/app-shell/data-density.css +25 -21
- package/src/css/app-shell/files.css +37 -29
- package/src/css/app-shell/kits-appended.css +73 -50
- package/src/css/app-shell/responsive.css +47 -29
- package/src/kits/os/freddie-dashboard.css +2 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Panel family — the bordered content container (`Panel`, aliased `Card`),
|
|
2
|
+
// the section wrapper (`Section`), the items[]-to-rows mapper
|
|
3
|
+
// (`PanelFromItems`), and the two tabular panel bodies that only ever appear
|
|
4
|
+
// inside one (`Receipt` key/value table, `Changelog` release list).
|
|
5
|
+
|
|
6
|
+
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
7
|
+
import { RowLink } from './row.js';
|
|
8
|
+
const h = webjsx.createElement;
|
|
9
|
+
|
|
10
|
+
export function Panel({ title, count, right, style = '', class: className = '', children, kind, id }) {
|
|
11
|
+
const cls = 'panel' + (kind ? ' panel-' + kind : '') + (className ? ' ' + className : '');
|
|
12
|
+
return h('div', { class: cls, style, id: id || null },
|
|
13
|
+
title != null ? h('div', { class: 'panel-head' },
|
|
14
|
+
h('span', {}, title),
|
|
15
|
+
right != null ? right : (count != null ? h('span', {}, String(count)) : null)
|
|
16
|
+
) : null,
|
|
17
|
+
h('div', { class: 'panel-body' }, ...(Array.isArray(children) ? children : [children]))
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Card — semantic alias of Panel; behaves identically.
|
|
22
|
+
export const Card = Panel;
|
|
23
|
+
|
|
24
|
+
// PanelFromItems — the shared 'items[] -> RowLink wrapped in Panel' mapper
|
|
25
|
+
// every portfolio consumer theme.mjs (zellous/wireweave/thebird/247420) had
|
|
26
|
+
// hand-rolled identically: items.map((it,i) => RowLink({code, title, sub,
|
|
27
|
+
// meta, href})) inside a titled Panel. `keyPrefix` seeds each row's stable
|
|
28
|
+
// key (`${keyPrefix}${i}`), matching the consumer convention of a
|
|
29
|
+
// one-letter-per-section prefix (e.g. 'f' for features, 'm' for modules).
|
|
30
|
+
// Field aliasing mirrors the union of shapes actually hand-rolled downstream:
|
|
31
|
+
// title reads `title` then `name`; sub reads `sub` then `desc`; code falls
|
|
32
|
+
// back to a zero-padded 1-based index when the item carries none. `heading`/
|
|
33
|
+
// `count`/`style`/`kind` pass through to Panel unchanged.
|
|
34
|
+
export function PanelFromItems({ heading, items = [], keyPrefix = 'i', count, style, kind, emptyText } = {}) {
|
|
35
|
+
if (!items || !items.length) return emptyText != null ? h('div', { class: 'empty' }, emptyText) : null;
|
|
36
|
+
const rows = items.map((it, i) => {
|
|
37
|
+
const codeVal = it.code != null ? it.code : (it.rank != null ? it.rank : String(i + 1).padStart(2, '0'));
|
|
38
|
+
return RowLink({
|
|
39
|
+
key: keyPrefix + i,
|
|
40
|
+
code: codeVal,
|
|
41
|
+
title: it.title != null ? it.title : it.name,
|
|
42
|
+
sub: it.sub != null ? it.sub : (it.desc != null ? it.desc : ''),
|
|
43
|
+
meta: it.meta != null ? it.meta : '',
|
|
44
|
+
href: it.href || '#'
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return Panel({ title: heading, count, style, kind, children: rows });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Section({ title, eyebrow, children, id }) {
|
|
51
|
+
return h('section', { class: 'ds-section', id: id || null },
|
|
52
|
+
eyebrow ? h('span', { class: 'eyebrow' }, eyebrow) : null,
|
|
53
|
+
title ? h('h3', {}, title) : null,
|
|
54
|
+
...(Array.isArray(children) ? children : [children])
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Receipt({ rows = [], emptyText = 'nothing here yet' }) {
|
|
59
|
+
if (!rows.length) return h('div', { class: 'empty' }, emptyText);
|
|
60
|
+
return h('table', { class: 'kv' },
|
|
61
|
+
h('tbody', {}, ...rows.map(([k, v], i) =>
|
|
62
|
+
h('tr', { key: i }, h('td', {}, k), h('td', {}, v))
|
|
63
|
+
))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function Changelog({ entries = [], emptyText = 'no changelog entries yet' }) {
|
|
68
|
+
if (!entries.length) return h('div', { class: 'empty' }, emptyText);
|
|
69
|
+
return Panel({
|
|
70
|
+
kind: 'wide',
|
|
71
|
+
children: entries.map((e, i) =>
|
|
72
|
+
h('div', { key: i, class: 'row ds-changelog-row' },
|
|
73
|
+
h('span', { class: 'code' }, e.date),
|
|
74
|
+
h('span', { class: 'ds-changelog-ver' }, e.ver),
|
|
75
|
+
h('span', { class: 'title' }, e.msg)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Row primitives — the single parameterized list row (`Row`) plus its
|
|
2
|
+
// link-flavoured wrapper (`RowLink`), and the search-term highlighter both
|
|
3
|
+
// use. Row covers every list-row shape in the kit: static, clickable
|
|
4
|
+
// (button semantics + keyboard activation), link, grid-columned, railed,
|
|
5
|
+
// expandable-with-actions, and search-highlighted.
|
|
6
|
+
|
|
7
|
+
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
8
|
+
const h = webjsx.createElement;
|
|
9
|
+
|
|
10
|
+
// Split a title string around case-insensitive matches of `highlight`, wrapping
|
|
11
|
+
// hits in <mark class="ds-hl">. Every segment is a keyed span so the children
|
|
12
|
+
// array never mixes keyed VElements with bare strings (webjsx applyDiff crashes
|
|
13
|
+
// on mixed keying).
|
|
14
|
+
function highlightTitle(title, highlight) {
|
|
15
|
+
const text = String(title);
|
|
16
|
+
const needle = String(highlight).toLowerCase();
|
|
17
|
+
if (!needle) return text;
|
|
18
|
+
const lower = text.toLowerCase();
|
|
19
|
+
const segs = [];
|
|
20
|
+
let pos = 0, n = 0;
|
|
21
|
+
while (pos <= text.length) {
|
|
22
|
+
const hit = lower.indexOf(needle, pos);
|
|
23
|
+
if (hit === -1) break;
|
|
24
|
+
if (hit > pos) segs.push(h('span', { key: 'hs' + n++ }, text.slice(pos, hit)));
|
|
25
|
+
segs.push(h('mark', { key: 'hs' + n++, class: 'ds-hl' }, text.slice(hit, hit + needle.length)));
|
|
26
|
+
pos = hit + needle.length;
|
|
27
|
+
}
|
|
28
|
+
if (!segs.length) return text;
|
|
29
|
+
if (pos < text.length) segs.push(h('span', { key: 'hs' + n++ }, text.slice(pos)));
|
|
30
|
+
return segs;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function Row({ code, rank, title, sub, meta, active, state = 'default', onClick, key, style, href, kind, cols, leading, trailing, target, selected, rail, expanded, highlight, actions, detail }) {
|
|
34
|
+
// `rank` is an alias for `code` (the leading monospace index); callers use
|
|
35
|
+
// either name. `rail` renders a thin colour bar at the row's leading edge as
|
|
36
|
+
// a status indicator (tone: green | purple | flame | <any token>).
|
|
37
|
+
const codeVal = code != null ? code : rank;
|
|
38
|
+
// Support legacy active/selected props for backward compatibility
|
|
39
|
+
const isActive = state === 'active' || (state === 'default' && (active || selected));
|
|
40
|
+
const isLink = kind === 'link' || (href != null && !onClick);
|
|
41
|
+
const isButton = !isLink && !!onClick;
|
|
42
|
+
const stateCls = state === 'disabled' ? ' row-state-disabled' : (state === 'error' ? ' row-state-error' : '');
|
|
43
|
+
// With no leading/code, the title would otherwise land in the narrow code
|
|
44
|
+
// column and wrap; `row-nocode` collapses that column so the title gets the
|
|
45
|
+
// full width (meta still pinned right).
|
|
46
|
+
const noLead = codeVal == null && leading == null;
|
|
47
|
+
const cls = 'row' + (isActive ? ' active' : '') + stateCls + (cols ? ' row-grid' : '') + (noLead && !cols ? ' row-nocode' : '') + (rail ? ' rail-' + rail : '');
|
|
48
|
+
const isDisabled = state === 'disabled';
|
|
49
|
+
const props = { key, class: cls, style: cols ? `${style ? style + ';' : ''}grid-template-columns:${cols}` : style };
|
|
50
|
+
if (isLink) {
|
|
51
|
+
props.href = href || '#';
|
|
52
|
+
if (target) props.target = target;
|
|
53
|
+
} else if (isButton && !isDisabled) {
|
|
54
|
+
// Clickable div needs button semantics + keyboard activation for a11y parity.
|
|
55
|
+
// A disabled row is inert: no click, no button role, no tab stop.
|
|
56
|
+
props.onclick = onClick;
|
|
57
|
+
props.role = 'button';
|
|
58
|
+
props.tabindex = '0';
|
|
59
|
+
props.onkeydown = (e) => {
|
|
60
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(e); }
|
|
61
|
+
};
|
|
62
|
+
// When the row is a disclosure toggle (host passes a boolean `expanded`),
|
|
63
|
+
// announce its open/closed state so AT users hear "expanded/collapsed".
|
|
64
|
+
// Omitted entirely for plain action buttons (expanded === undefined).
|
|
65
|
+
if (expanded === true || expanded === false) props['aria-expanded'] = expanded ? 'true' : 'false';
|
|
66
|
+
}
|
|
67
|
+
if (isDisabled) props['aria-disabled'] = 'true';
|
|
68
|
+
if (isActive && (isLink || isButton)) props['aria-current'] = isActive ? 'page' : null;
|
|
69
|
+
// `highlight` wraps case-insensitive matches in the title in <mark class="ds-hl">.
|
|
70
|
+
// The segments live inside a single wrapper span so the title's child list
|
|
71
|
+
// never mixes keyed and unkeyed siblings.
|
|
72
|
+
const titleNode = (highlight && typeof title === 'string')
|
|
73
|
+
? h('span', {}, ...[].concat(highlightTitle(title, highlight)))
|
|
74
|
+
: title;
|
|
75
|
+
// `actions` render ONLY when the row is expanded, as a sibling action strip
|
|
76
|
+
// inside the row container; each button stops propagation so it never fires
|
|
77
|
+
// the row onClick.
|
|
78
|
+
const actionRow = (expanded === true && Array.isArray(actions) && actions.length)
|
|
79
|
+
? h('span', { class: 'row-actions', role: 'group', 'aria-label': 'row actions' },
|
|
80
|
+
...actions.map((a, i) => h('button', {
|
|
81
|
+
key: 'ract' + i,
|
|
82
|
+
type: 'button',
|
|
83
|
+
class: 'row-act',
|
|
84
|
+
title: a.title || a.label,
|
|
85
|
+
'aria-label': a.title || a.label,
|
|
86
|
+
onclick: (e) => { e.stopPropagation(); a.onClick && a.onClick(e); },
|
|
87
|
+
onkeydown: (e) => { e.stopPropagation(); },
|
|
88
|
+
}, a.label)))
|
|
89
|
+
: null;
|
|
90
|
+
// Color is not the only status channel: emit a visually-hidden word for the
|
|
91
|
+
// meaningful rail tones (error/subagent) so AT and color-blind users get the
|
|
92
|
+
// state. green is the unremarkable default - announcing "ok" everywhere would
|
|
93
|
+
// be AT noise - so it emits nothing.
|
|
94
|
+
const railWord = rail === 'flame' ? 'error' : rail === 'purple' ? 'subagent' : null;
|
|
95
|
+
// `detail` renders as a sibling block AFTER the title/meta children (its own
|
|
96
|
+
// line via flex-basis:100% in .ds-row-detail), not inside the title span.
|
|
97
|
+
// The same `highlight` search term that marks matches in the collapsed
|
|
98
|
+
// title previously stopped applying the moment a row expanded - the
|
|
99
|
+
// expanded body (often the ONLY place a match beyond the 220-char title
|
|
100
|
+
// window is actually visible) rendered as plain unmarked text.
|
|
101
|
+
const detailNode = (highlight && typeof detail === 'string')
|
|
102
|
+
? h('span', {}, ...[].concat(highlightTitle(detail, highlight)))
|
|
103
|
+
: detail;
|
|
104
|
+
return h(isLink ? 'a' : 'div', props,
|
|
105
|
+
railWord ? h('span', { class: 'sr-only' }, railWord) : null,
|
|
106
|
+
leading != null ? leading : (codeVal != null ? h('span', { class: 'code' }, codeVal) : null),
|
|
107
|
+
h('span', { class: 'title', title: typeof title === 'string' ? title : undefined }, titleNode, sub ? h('span', { class: 'sub', title: typeof sub === 'string' ? sub : undefined }, sub) : null),
|
|
108
|
+
trailing != null ? trailing : (meta != null ? h('span', { class: 'meta' }, meta) : null),
|
|
109
|
+
actionRow,
|
|
110
|
+
detail != null ? h('pre', { class: 'ds-row-detail' }, detailNode) : null);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function RowLink({ code, title, sub, meta, href = '#', key, target }) {
|
|
114
|
+
return Row({ code, title, sub, meta, href, kind: 'link', key, target });
|
|
115
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Tables — the generic `Table` primitive plus the two domain tables built
|
|
2
|
+
// directly on it: HealthTable (a health-check map with per-value tone
|
|
3
|
+
// inference) and ProcessRegistryTable (a live long-lived-process census).
|
|
4
|
+
|
|
5
|
+
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
6
|
+
import { Icon, Chip } from '../shell.js';
|
|
7
|
+
const h = webjsx.createElement;
|
|
8
|
+
|
|
9
|
+
export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet', rowLabels, striped = false, compact = false, sortable = false, sortKey, sortDir = 'asc', onSort }) {
|
|
10
|
+
if (!rows || rows.length === 0) return h('div', { class: 'empty' }, emptyText);
|
|
11
|
+
// rowLabels lets callers supply a plain-text label per row when the first
|
|
12
|
+
// cell is a vnode (so the aria-label is meaningful, not the literal 'row').
|
|
13
|
+
const labelFor = (row, i) => {
|
|
14
|
+
if (Array.isArray(rowLabels) && rowLabels[i] != null) return String(rowLabels[i]);
|
|
15
|
+
const c = row[0];
|
|
16
|
+
return c == null ? 'row' : (typeof c === 'object' ? 'row' : String(c));
|
|
17
|
+
};
|
|
18
|
+
// Native <table>/<tr>/<th>/<td> already carry the correct implicit ARIA
|
|
19
|
+
// roles — explicit role="table"/row/columnheader/cell is redundant and only
|
|
20
|
+
// risks overriding native semantics, so it is omitted.
|
|
21
|
+
// Scroll containment lives on the component itself: a wide table used
|
|
22
|
+
// outside a Panel must never force page-level horizontal scroll.
|
|
23
|
+
// striped/compact are opt-in density modifiers (webgeist g-table parity) —
|
|
24
|
+
// default false so the existing contract/visual is byte-unchanged.
|
|
25
|
+
const wrapClass = 'ds-table-wrap' + (striped ? ' is-striped' : '') + (compact ? ' is-compact' : '');
|
|
26
|
+
// sortable is opt-in (default false, byte-unchanged for existing callers):
|
|
27
|
+
// a header becomes a real <button> announcing aria-sort, dispatching
|
|
28
|
+
// onSort(headerIndex) so the HOST owns the actual row-ordering logic (this
|
|
29
|
+
// component has no opinion on comparator/locale/type - it only renders the
|
|
30
|
+
// control and current state). A docstudio-style dense admin table needs
|
|
31
|
+
// sortable columns; Table previously had no way to express that at all.
|
|
32
|
+
const thFor = (hd, i) => {
|
|
33
|
+
if (!sortable || !onSort) return h('th', { key: i, scope: 'col' }, hd);
|
|
34
|
+
const isActive = sortKey === i;
|
|
35
|
+
const ariaSort = isActive ? (sortDir === 'desc' ? 'descending' : 'ascending') : 'none';
|
|
36
|
+
return h('th', { key: i, scope: 'col', 'aria-sort': ariaSort },
|
|
37
|
+
h('button', { type: 'button', class: 'ds-table-sort-btn' + (isActive ? ' is-active' : ''), onclick: () => onSort(i) },
|
|
38
|
+
h('span', { class: 'ds-table-sort-label' }, hd),
|
|
39
|
+
isActive ? Icon(sortDir === 'desc' ? 'chevron-down' : 'chevron-up', { size: 12 }) : null));
|
|
40
|
+
};
|
|
41
|
+
return h('div', { class: wrapClass }, h('table', {},
|
|
42
|
+
h('thead', {}, h('tr', {}, ...headers.map((hd, i) => thFor(hd, i)))),
|
|
43
|
+
h('tbody', {}, ...rows.map((row, i) => h('tr', {
|
|
44
|
+
key: i,
|
|
45
|
+
class: onRowClick ? 'clickable' : '',
|
|
46
|
+
onclick: onRowClick ? () => onRowClick(i) : null,
|
|
47
|
+
// Space scrolls by default — preventDefault on Space (and Enter) so
|
|
48
|
+
// keyboard activation matches click without page jump.
|
|
49
|
+
...(onRowClick ? { tabindex: '0', role: 'button', 'aria-label': 'open ' + labelFor(row, i), onkeydown: (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); onRowClick(i); } } } : {})
|
|
50
|
+
}, ...row.map((c, j) => h('td', { key: j }, c == null ? '' : (typeof c === 'object' ? c : String(c)))))))));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// HealthTable — generic health-check table: given an arbitrary
|
|
54
|
+
// `{checkName: value}` object, infers a Chip tone per row from the value's
|
|
55
|
+
// own type (boolean true/false -> ok/miss Chip, object -> truncated JSON
|
|
56
|
+
// string, else the raw value stringified) so a new backend health check
|
|
57
|
+
// appears with zero per-check hardcoding at the call site. Ported from
|
|
58
|
+
// freddie's health page (src/components/freddie.js's `health` page), which
|
|
59
|
+
// hand-rolled this exact inference inline; promoted here as a reusable
|
|
60
|
+
// primitive for any dashboard/monitoring surface with a health-check map
|
|
61
|
+
// (agentgui's Live tab included). `okLabel`/`missLabel` let a caller
|
|
62
|
+
// localize the two Chip strings; `jsonTruncate` caps the JSON.stringify
|
|
63
|
+
// length for object-shaped values (matches freddie's own truncation width).
|
|
64
|
+
export function HealthTable({ checks = {}, emptyText = 'no health data', okLabel = 'ok', missLabel = 'no', jsonTruncate = 60 } = {}) {
|
|
65
|
+
const entries = Object.entries(checks);
|
|
66
|
+
if (!entries.length) return h('div', { class: 'empty' }, emptyText);
|
|
67
|
+
const rows = entries.map(([name, v]) => {
|
|
68
|
+
let cell;
|
|
69
|
+
if (typeof v === 'object' && v !== null) {
|
|
70
|
+
const s = JSON.stringify(v);
|
|
71
|
+
cell = h('span', { title: s.length > jsonTruncate ? s : null }, s.length > jsonTruncate ? s.slice(0, jsonTruncate) + '…' : s);
|
|
72
|
+
} else if (v === true) cell = Chip({ tone: 'ok', children: okLabel });
|
|
73
|
+
else if (v === false) cell = Chip({ tone: 'miss', children: missLabel });
|
|
74
|
+
else cell = String(v);
|
|
75
|
+
return [name, cell];
|
|
76
|
+
});
|
|
77
|
+
return Table({ headers: ['check', 'status'], rows });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ProcessRegistryTable — generic long-lived-process registry table: given a
|
|
81
|
+
// list of `{kind, key, state}`-shaped rows (any in-flight process the host
|
|
82
|
+
// tracks — an xstate machine, an ACP/direct-runner session, a background
|
|
83
|
+
// job), renders a uniform table. Ported from freddie's `machines` page
|
|
84
|
+
// (src/components/freddie.js), which used this exact shape for its xstate
|
|
85
|
+
// machine census; generalized here since the shape (kind/key/state) applies
|
|
86
|
+
// equally to agentgui's Live tab listing in-flight agent runner processes.
|
|
87
|
+
// `extraColumns` lets a caller append columns beyond the base three (e.g. a
|
|
88
|
+
// stop-action button) without forking the whole table.
|
|
89
|
+
export function ProcessRegistryTable({ processes = [], emptyText = 'no live processes', extraColumns = [] } = {}) {
|
|
90
|
+
if (!processes.length) return h('div', { class: 'empty' }, emptyText);
|
|
91
|
+
const headers = ['kind', 'key', 'state', ...extraColumns.map(c => c.header)];
|
|
92
|
+
const rows = processes.map(p => [
|
|
93
|
+
p.kind || '—', p.key || '—', p.state || '—',
|
|
94
|
+
...extraColumns.map(c => c.render(p))
|
|
95
|
+
]);
|
|
96
|
+
return Table({ headers, rows });
|
|
97
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Composed page views — the two whole-page compositions assembled from the
|
|
2
|
+
// primitives in this group: HomeView (hero + shipping/works/writing/manifesto
|
|
3
|
+
// sections) and ProjectView (project prose + install + receipt + changelog).
|
|
4
|
+
// Both return a flat array of blocks for a host to render directly.
|
|
5
|
+
|
|
6
|
+
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
7
|
+
import { Heading, Lede, Dot } from '../shell.js';
|
|
8
|
+
import { Row } from './row.js';
|
|
9
|
+
import { Panel, Section, Receipt, Changelog } from './panel.js';
|
|
10
|
+
import { Hero, Manifesto } from './hero.js';
|
|
11
|
+
import { Install } from './cli.js';
|
|
12
|
+
import { WorksList, WritingList } from './lists.js';
|
|
13
|
+
const h = webjsx.createElement;
|
|
14
|
+
|
|
15
|
+
export function HomeView({ state = {}, onNav, onToggleWork, works = [], posts = [], manifesto = [], currentlyShipping } = {}) {
|
|
16
|
+
return [
|
|
17
|
+
// The page's one deliberate kicker. 'an entrypoint' is the collective's
|
|
18
|
+
// name-as-tagline — it says something the <h1> does not, and it is the
|
|
19
|
+
// masthead position where a print eyebrow actually belongs. The sections
|
|
20
|
+
// below intentionally carry NO eyebrow: each has a heading that already
|
|
21
|
+
// names it, so a kicker there would only restate the <h3> one word
|
|
22
|
+
// shorter. Do not add eyebrows to the sibling sections to "match" this.
|
|
23
|
+
Hero({
|
|
24
|
+
eyebrow: 'an entrypoint',
|
|
25
|
+
title: 'Small, weird, useful tools — built in public.',
|
|
26
|
+
body: '247420 is a creative collective of eight, scattered across three timezones. We have been shipping open-source tools for the web since 2018.',
|
|
27
|
+
accent: 'Some become the future. Most don\'t. That\'s the deal.'
|
|
28
|
+
}),
|
|
29
|
+
// Titleless section: promoted its former eyebrow to the actual heading
|
|
30
|
+
// rather than leaving a kicker hovering over an unnamed panel. The label
|
|
31
|
+
// was carrying the section's only name, so it became the <h3>.
|
|
32
|
+
currentlyShipping ? Section({
|
|
33
|
+
title: 'currently shipping',
|
|
34
|
+
children: Panel({
|
|
35
|
+
kind: 'wide',
|
|
36
|
+
children: currentlyShipping.map((row, i) => {
|
|
37
|
+
const dotNode = Dot({ tone: row.live ? 'live' : 'idle' });
|
|
38
|
+
dotNode.props = { ...dotNode.props, 'aria-label': row.live ? 'live status' : 'idle status' };
|
|
39
|
+
return Row({
|
|
40
|
+
key: i,
|
|
41
|
+
code: dotNode,
|
|
42
|
+
title: row.title, sub: row.sub, meta: row.meta
|
|
43
|
+
});
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}) : null,
|
|
47
|
+
works.length ? Section({
|
|
48
|
+
title: 'Everything else.',
|
|
49
|
+
children: WorksList({ works, openedIndex: state.opened ?? -1, onToggle: onToggleWork })
|
|
50
|
+
}) : null,
|
|
51
|
+
posts.length ? Section({
|
|
52
|
+
title: 'When we have something to say.',
|
|
53
|
+
children: WritingList({ posts })
|
|
54
|
+
}) : null,
|
|
55
|
+
manifesto.length ? Section({
|
|
56
|
+
title: 'Eight people, three timezones, one ongoing conversation.',
|
|
57
|
+
children: Manifesto({ paragraphs: manifesto })
|
|
58
|
+
}) : null
|
|
59
|
+
].filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function ProjectView({ project = {}, copied, onCopy } = {}) {
|
|
63
|
+
return [
|
|
64
|
+
h('div', { class: 'ds-prose' },
|
|
65
|
+
Heading({ level: 1, children: project.name }),
|
|
66
|
+
Lede({ children: project.tagline })
|
|
67
|
+
),
|
|
68
|
+
project.install ? [
|
|
69
|
+
Heading({ level: 3, children: 'install' }),
|
|
70
|
+
Install({ cmd: project.install, copied, onCopy }),
|
|
71
|
+
] : null,
|
|
72
|
+
project.receipt ? [
|
|
73
|
+
Heading({ level: 3, children: 'by the numbers' }),
|
|
74
|
+
Receipt({ rows: project.receipt }),
|
|
75
|
+
] : null,
|
|
76
|
+
project.changelog ? [
|
|
77
|
+
Heading({ level: 3, children: 'recent releases' }),
|
|
78
|
+
Changelog({ entries: project.changelog })
|
|
79
|
+
] : null
|
|
80
|
+
].filter(Boolean).flat();
|
|
81
|
+
}
|