@deskwork/studio 0.21.0 → 0.22.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/dist/pages/dashboard/adjacent-section.d.ts +38 -0
- package/dist/pages/dashboard/adjacent-section.d.ts.map +1 -0
- package/dist/pages/dashboard/adjacent-section.js +61 -0
- package/dist/pages/dashboard/adjacent-section.js.map +1 -0
- package/dist/pages/dashboard/adjacent-section.ts +71 -0
- package/dist/pages/dashboard/data.d.ts +35 -4
- package/dist/pages/dashboard/data.d.ts.map +1 -1
- package/dist/pages/dashboard/data.js +69 -5
- package/dist/pages/dashboard/data.js.map +1 -1
- package/dist/pages/dashboard/data.ts +84 -5
- package/dist/pages/dashboard/press-queue.d.ts +16 -0
- package/dist/pages/dashboard/press-queue.d.ts.map +1 -0
- package/dist/pages/dashboard/press-queue.js +91 -0
- package/dist/pages/dashboard/press-queue.js.map +1 -0
- package/dist/pages/dashboard/press-queue.ts +112 -0
- package/dist/pages/dashboard/section.d.ts.map +1 -1
- package/dist/pages/dashboard/section.js +7 -0
- package/dist/pages/dashboard/section.js.map +1 -1
- package/dist/pages/dashboard/section.ts +7 -0
- package/dist/pages/dashboard/shortform-section.d.ts +86 -0
- package/dist/pages/dashboard/shortform-section.d.ts.map +1 -0
- package/dist/pages/dashboard/shortform-section.js +189 -0
- package/dist/pages/dashboard/shortform-section.js.map +1 -0
- package/dist/pages/dashboard/shortform-section.ts +228 -0
- package/dist/pages/dashboard.d.ts.map +1 -1
- package/dist/pages/dashboard.js +34 -1
- package/dist/pages/dashboard.js.map +1 -1
- package/dist/pages/dashboard.ts +40 -1
- package/dist/pages/entry-review/index.d.ts.map +1 -1
- package/dist/pages/entry-review/index.js +24 -2
- package/dist/pages/entry-review/index.js.map +1 -1
- package/dist/pages/entry-review/mobile-sheet.d.ts +65 -0
- package/dist/pages/entry-review/mobile-sheet.d.ts.map +1 -0
- package/dist/pages/entry-review/mobile-sheet.js +170 -0
- package/dist/pages/entry-review/mobile-sheet.js.map +1 -0
- package/dist/pages/masthead-menu.d.ts +38 -0
- package/dist/pages/masthead-menu.d.ts.map +1 -0
- package/dist/pages/masthead-menu.js +126 -0
- package/dist/pages/masthead-menu.js.map +1 -0
- package/dist/pages/masthead-menu.ts +128 -0
- package/dist/pages/masthead.d.ts +85 -0
- package/dist/pages/masthead.d.ts.map +1 -0
- package/dist/pages/masthead.js +99 -0
- package/dist/pages/masthead.js.map +1 -0
- package/dist/pages/masthead.ts +155 -0
- package/dist/pages/mobile-bar.d.ts +72 -0
- package/dist/pages/mobile-bar.d.ts.map +1 -0
- package/dist/pages/mobile-bar.js +88 -0
- package/dist/pages/mobile-bar.js.map +1 -0
- package/dist/pages/mobile-bar.ts +129 -0
- package/dist/pages/shortform-review-mobile-sheet.d.ts +76 -0
- package/dist/pages/shortform-review-mobile-sheet.d.ts.map +1 -0
- package/dist/pages/shortform-review-mobile-sheet.js +159 -0
- package/dist/pages/shortform-review-mobile-sheet.js.map +1 -0
- package/dist/pages/shortform-review-mobile-sheet.ts +185 -0
- package/dist/pages/shortform-review.d.ts +18 -0
- package/dist/pages/shortform-review.d.ts.map +1 -1
- package/dist/pages/shortform-review.js +62 -111
- package/dist/pages/shortform-review.js.map +1 -1
- package/dist/pages/shortform-review.ts +68 -140
- package/dist/pages/shortform.d.ts.map +1 -1
- package/dist/pages/shortform.js +0 -1
- package/dist/pages/shortform.js.map +1 -1
- package/dist/pages/shortform.ts +0 -1
- package/dist/server.js +41 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal masthead chrome for studio surfaces (v7 architecture).
|
|
3
|
+
*
|
|
4
|
+
* Per `DESIGN-STANDARDS.md § Studio navigation model`, every studio
|
|
5
|
+
* surface carries a three-column masthead:
|
|
6
|
+
*
|
|
7
|
+
* `24px (← back) | 1fr (center stack) | 24px (⋮ menu)`
|
|
8
|
+
*
|
|
9
|
+
* On the Desk (`isHub: true`) the back-link is absent — you're already
|
|
10
|
+
* home — and the grid collapses to `1fr | 24px`. Every cell is
|
|
11
|
+
* `align-items: center`; the masthead's bottom-border rule clears
|
|
12
|
+
* every content baseline so nothing is struck through.
|
|
13
|
+
*
|
|
14
|
+
* Center stack composition:
|
|
15
|
+
* - Top row: kicker (mono caps, red, with optional inline meta after a
|
|
16
|
+
* paper-3 separator). The kicker is always present.
|
|
17
|
+
* - Bottom row: either `slug` (mono, slug-shaped surfaces) OR `title`
|
|
18
|
+
* (italic display, hub-shaped surfaces). Callers pass exactly one
|
|
19
|
+
* of the two — passing both throws.
|
|
20
|
+
*
|
|
21
|
+
* Glyph contracts (per design-standards):
|
|
22
|
+
* - `←` italic-display 1.35rem in `--er-proof-blue` (8.13:1 vs paper),
|
|
23
|
+
* navigates to `/dev/editorial-studio`.
|
|
24
|
+
* - `⋮` mono 1.2rem in `--er-ink-soft` (8.92:1 vs paper). The button
|
|
25
|
+
* does nothing yet; Step 2.2.7 wires the popover menu.
|
|
26
|
+
* - Each glyph has a ≥44×44px tap target via padding+margin (visual
|
|
27
|
+
* remains 24px square).
|
|
28
|
+
*
|
|
29
|
+
* The masthead is mobile-only (≤600px). CSS hides it on desktop so
|
|
30
|
+
* existing desktop chrome (folio + er-strip + er-pagehead) keeps its
|
|
31
|
+
* exact present-day appearance. Desktop refinement is out-of-scope for
|
|
32
|
+
* this feature branch (per the design-standards "separate feature
|
|
33
|
+
* branch" note).
|
|
34
|
+
*
|
|
35
|
+
* This file ships the helper + the markup contract. The companion
|
|
36
|
+
* stylesheet is `plugins/deskwork-studio/public/css/mobile-shell.css`.
|
|
37
|
+
*/
|
|
38
|
+
import { html, unsafe, escapeHtml } from "./html.js";
|
|
39
|
+
const DESK_HREF = '/dev/editorial-studio';
|
|
40
|
+
function renderKicker(opts) {
|
|
41
|
+
if (opts.kicker !== undefined && opts.kickerHtml !== undefined) {
|
|
42
|
+
throw new Error('renderMasthead: pass exactly one of { kicker, kickerHtml }, not both. ' +
|
|
43
|
+
'See packages/studio/src/pages/masthead.ts for the MastheadOpts contract.');
|
|
44
|
+
}
|
|
45
|
+
const meta = opts.metaInline
|
|
46
|
+
? html `<span class="er-masthead-kicker-sep">·</span><span class="er-masthead-meta-inline">${opts.metaInline}</span>`
|
|
47
|
+
: '';
|
|
48
|
+
if (opts.kickerHtml !== undefined) {
|
|
49
|
+
return unsafe(html `<div class="er-masthead-kicker">${opts.kickerHtml}${unsafe(meta)}</div>`);
|
|
50
|
+
}
|
|
51
|
+
if (opts.kicker !== undefined) {
|
|
52
|
+
return unsafe(html `<div class="er-masthead-kicker">${opts.kicker}${unsafe(meta)}</div>`);
|
|
53
|
+
}
|
|
54
|
+
return unsafe('<div class="er-masthead-kicker" aria-hidden="true"></div>');
|
|
55
|
+
}
|
|
56
|
+
function renderBody(opts) {
|
|
57
|
+
if (opts.slug !== undefined && opts.title !== undefined) {
|
|
58
|
+
throw new Error('renderMasthead: pass exactly one of { slug, title }, not both.');
|
|
59
|
+
}
|
|
60
|
+
if (opts.slug !== undefined) {
|
|
61
|
+
return unsafe(`<div class="er-masthead-slug">${escapeHtml(opts.slug)}</div>`);
|
|
62
|
+
}
|
|
63
|
+
if (opts.title !== undefined) {
|
|
64
|
+
return unsafe(`<div class="er-masthead-title">${escapeHtml(opts.title)}</div>`);
|
|
65
|
+
}
|
|
66
|
+
return unsafe('');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Render the universal masthead. Returns RawHtml so callers can embed
|
|
70
|
+
* the result directly in `html\`…${renderMasthead(...)}…\`` templates.
|
|
71
|
+
*/
|
|
72
|
+
export function renderMasthead(opts) {
|
|
73
|
+
const triggerId = opts.menuTriggerId ?? 'masthead-menu-trigger';
|
|
74
|
+
const hubClass = opts.isHub ? ' er-masthead--hub' : '';
|
|
75
|
+
const backLink = opts.isHub
|
|
76
|
+
? ''
|
|
77
|
+
: html `<a class="er-masthead-back" href="${DESK_HREF}" aria-label="Back to the Desk" title="Back to the Desk">←</a>`;
|
|
78
|
+
const kicker = renderKicker(opts);
|
|
79
|
+
const body = renderBody(opts);
|
|
80
|
+
return unsafe(html `
|
|
81
|
+
<header class="er-masthead${unsafe(hubClass)}" data-er-masthead role="banner">
|
|
82
|
+
${unsafe(backLink)}
|
|
83
|
+
<div class="er-masthead-center">
|
|
84
|
+
${kicker}
|
|
85
|
+
${body}
|
|
86
|
+
</div>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="er-masthead-menu"
|
|
90
|
+
id="${triggerId}"
|
|
91
|
+
data-er-masthead-menu
|
|
92
|
+
aria-haspopup="true"
|
|
93
|
+
aria-expanded="false"
|
|
94
|
+
aria-label="Studio menu"
|
|
95
|
+
title="Studio menu"
|
|
96
|
+
>⋮</button>
|
|
97
|
+
</header>`);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=masthead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masthead.js","sourceRoot":"","sources":["../../src/pages/masthead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAgB,MAAM,WAAW,CAAC;AAkDnE,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,SAAS,YAAY,CAAC,IAAkB;IACtC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CACb,wEAAwE;YACtE,0EAA0E,CAC7E,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU;QAC1B,CAAC,CAAC,IAAI,CAAA,sFAAsF,IAAI,CAAC,UAAU,SAAS;QACpH,CAAC,CAAC,EAAE,CAAC;IACP,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAClC,OAAO,MAAM,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,MAAM,CAAC,2DAA2D,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,UAAU,CAAC,IAAkB;IACpC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,iCAAiC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,kCAAkC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,IAAI,uBAAuB,CAAC;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK;QACzB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,CAAA,qCAAqC,SAAS,gEAAgE,CAAC;IACvH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC,IAAI,CAAA;gCACY,MAAM,CAAC,QAAQ,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC;;UAEd,MAAM;UACN,IAAI;;;;;cAKA,SAAS;;;;;;;cAOT,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal masthead chrome for studio surfaces (v7 architecture).
|
|
3
|
+
*
|
|
4
|
+
* Per `DESIGN-STANDARDS.md § Studio navigation model`, every studio
|
|
5
|
+
* surface carries a three-column masthead:
|
|
6
|
+
*
|
|
7
|
+
* `24px (← back) | 1fr (center stack) | 24px (⋮ menu)`
|
|
8
|
+
*
|
|
9
|
+
* On the Desk (`isHub: true`) the back-link is absent — you're already
|
|
10
|
+
* home — and the grid collapses to `1fr | 24px`. Every cell is
|
|
11
|
+
* `align-items: center`; the masthead's bottom-border rule clears
|
|
12
|
+
* every content baseline so nothing is struck through.
|
|
13
|
+
*
|
|
14
|
+
* Center stack composition:
|
|
15
|
+
* - Top row: kicker (mono caps, red, with optional inline meta after a
|
|
16
|
+
* paper-3 separator). The kicker is always present.
|
|
17
|
+
* - Bottom row: either `slug` (mono, slug-shaped surfaces) OR `title`
|
|
18
|
+
* (italic display, hub-shaped surfaces). Callers pass exactly one
|
|
19
|
+
* of the two — passing both throws.
|
|
20
|
+
*
|
|
21
|
+
* Glyph contracts (per design-standards):
|
|
22
|
+
* - `←` italic-display 1.35rem in `--er-proof-blue` (8.13:1 vs paper),
|
|
23
|
+
* navigates to `/dev/editorial-studio`.
|
|
24
|
+
* - `⋮` mono 1.2rem in `--er-ink-soft` (8.92:1 vs paper). The button
|
|
25
|
+
* does nothing yet; Step 2.2.7 wires the popover menu.
|
|
26
|
+
* - Each glyph has a ≥44×44px tap target via padding+margin (visual
|
|
27
|
+
* remains 24px square).
|
|
28
|
+
*
|
|
29
|
+
* The masthead is mobile-only (≤600px). CSS hides it on desktop so
|
|
30
|
+
* existing desktop chrome (folio + er-strip + er-pagehead) keeps its
|
|
31
|
+
* exact present-day appearance. Desktop refinement is out-of-scope for
|
|
32
|
+
* this feature branch (per the design-standards "separate feature
|
|
33
|
+
* branch" note).
|
|
34
|
+
*
|
|
35
|
+
* This file ships the helper + the markup contract. The companion
|
|
36
|
+
* stylesheet is `plugins/deskwork-studio/public/css/mobile-shell.css`.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { html, unsafe, escapeHtml, type RawHtml } from './html.ts';
|
|
40
|
+
|
|
41
|
+
export interface MastheadOpts {
|
|
42
|
+
/**
|
|
43
|
+
* Top kicker line. Mono caps, red. Example: `entry · drafting · № 12`.
|
|
44
|
+
* Pre-rendered HTML may be included via `kickerHtml` instead when the
|
|
45
|
+
* kicker carries inline markup (e.g. a `<span class="platform">`).
|
|
46
|
+
*/
|
|
47
|
+
readonly kicker?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Raw HTML alternative to `kicker`. Used when the kicker carries
|
|
51
|
+
* inline ornament (platform tag, dim spans, etc.). Caller is
|
|
52
|
+
* responsible for any escaping; pass through `escapeHtml` for any
|
|
53
|
+
* user-supplied substring.
|
|
54
|
+
*/
|
|
55
|
+
readonly kickerHtml?: RawHtml;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Bottom-row body when the surface is slug-shaped (entry-review,
|
|
59
|
+
* shortform-review). Rendered as mono. Mutually exclusive with
|
|
60
|
+
* `title`.
|
|
61
|
+
*/
|
|
62
|
+
readonly slug?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Bottom-row body when the surface is hub-shaped (Desk). Rendered as
|
|
66
|
+
* italic display. Mutually exclusive with `slug`.
|
|
67
|
+
*/
|
|
68
|
+
readonly title?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optional inline meta appended to the kicker line after a paper-3
|
|
72
|
+
* `·` separator. Example: `v3 · 2h`, `№ 03`. Mono, dim color.
|
|
73
|
+
*/
|
|
74
|
+
readonly metaInline?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `true` on the Desk only. Suppresses the `←` back-link and collapses
|
|
78
|
+
* the grid to `1fr | 24px`.
|
|
79
|
+
*/
|
|
80
|
+
readonly isHub: boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Optional id for the `⋮` button element. Step 2.2.7 will look this
|
|
84
|
+
* up to wire the popover. Defaults to `masthead-menu-trigger`.
|
|
85
|
+
*/
|
|
86
|
+
readonly menuTriggerId?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const DESK_HREF = '/dev/editorial-studio';
|
|
90
|
+
|
|
91
|
+
function renderKicker(opts: MastheadOpts): RawHtml {
|
|
92
|
+
if (opts.kicker !== undefined && opts.kickerHtml !== undefined) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
'renderMasthead: pass exactly one of { kicker, kickerHtml }, not both. ' +
|
|
95
|
+
'See packages/studio/src/pages/masthead.ts for the MastheadOpts contract.',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const meta = opts.metaInline
|
|
99
|
+
? html`<span class="er-masthead-kicker-sep">·</span><span class="er-masthead-meta-inline">${opts.metaInline}</span>`
|
|
100
|
+
: '';
|
|
101
|
+
if (opts.kickerHtml !== undefined) {
|
|
102
|
+
return unsafe(html`<div class="er-masthead-kicker">${opts.kickerHtml}${unsafe(meta)}</div>`);
|
|
103
|
+
}
|
|
104
|
+
if (opts.kicker !== undefined) {
|
|
105
|
+
return unsafe(html`<div class="er-masthead-kicker">${opts.kicker}${unsafe(meta)}</div>`);
|
|
106
|
+
}
|
|
107
|
+
return unsafe('<div class="er-masthead-kicker" aria-hidden="true"></div>');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function renderBody(opts: MastheadOpts): RawHtml {
|
|
111
|
+
if (opts.slug !== undefined && opts.title !== undefined) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'renderMasthead: pass exactly one of { slug, title }, not both.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (opts.slug !== undefined) {
|
|
117
|
+
return unsafe(`<div class="er-masthead-slug">${escapeHtml(opts.slug)}</div>`);
|
|
118
|
+
}
|
|
119
|
+
if (opts.title !== undefined) {
|
|
120
|
+
return unsafe(`<div class="er-masthead-title">${escapeHtml(opts.title)}</div>`);
|
|
121
|
+
}
|
|
122
|
+
return unsafe('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render the universal masthead. Returns RawHtml so callers can embed
|
|
127
|
+
* the result directly in `html\`…${renderMasthead(...)}…\`` templates.
|
|
128
|
+
*/
|
|
129
|
+
export function renderMasthead(opts: MastheadOpts): RawHtml {
|
|
130
|
+
const triggerId = opts.menuTriggerId ?? 'masthead-menu-trigger';
|
|
131
|
+
const hubClass = opts.isHub ? ' er-masthead--hub' : '';
|
|
132
|
+
const backLink = opts.isHub
|
|
133
|
+
? ''
|
|
134
|
+
: html`<a class="er-masthead-back" href="${DESK_HREF}" aria-label="Back to the Desk" title="Back to the Desk">←</a>`;
|
|
135
|
+
const kicker = renderKicker(opts);
|
|
136
|
+
const body = renderBody(opts);
|
|
137
|
+
return unsafe(html`
|
|
138
|
+
<header class="er-masthead${unsafe(hubClass)}" data-er-masthead role="banner">
|
|
139
|
+
${unsafe(backLink)}
|
|
140
|
+
<div class="er-masthead-center">
|
|
141
|
+
${kicker}
|
|
142
|
+
${body}
|
|
143
|
+
</div>
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
class="er-masthead-menu"
|
|
147
|
+
id="${triggerId}"
|
|
148
|
+
data-er-masthead-menu
|
|
149
|
+
aria-haspopup="true"
|
|
150
|
+
aria-expanded="false"
|
|
151
|
+
aria-label="Studio menu"
|
|
152
|
+
title="Studio menu"
|
|
153
|
+
>⋮</button>
|
|
154
|
+
</header>`);
|
|
155
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal mobile bottom-bar.
|
|
3
|
+
*
|
|
4
|
+
* Renders the `<nav class="er-mobile-bar">` chrome that the studio's
|
|
5
|
+
* phone-width surfaces share. The existing `editorial-review.css` rules
|
|
6
|
+
* (`.er-mobile-bar`, `.er-mobile-tab*`, `.er-mobile-tab--review`,
|
|
7
|
+
* `.er-mobile-tab--edit`) cascade against the markup this helper emits
|
|
8
|
+
* unchanged — the bar is contextual chrome the caller composes by
|
|
9
|
+
* supplying a list of `Cell`s that name each tab's surface affordance.
|
|
10
|
+
*
|
|
11
|
+
* Design contract (DESIGN-STANDARDS.md § Studio navigation model):
|
|
12
|
+
* the mobile bar carries ONLY contextual cells — no global nav. The
|
|
13
|
+
* star nav (Desk hub, ← back, ⋮ menu) lives in the masthead. This
|
|
14
|
+
* helper is intentionally narrow: it cannot emit nav-region cells; it
|
|
15
|
+
* only emits the contextual ribbon the caller supplied.
|
|
16
|
+
*
|
|
17
|
+
* THESIS Consequence 2: the bar is server-rendered chrome. None of the
|
|
18
|
+
* buttons rendered here mutate state directly on render — sheet-kind
|
|
19
|
+
* cells open a sheet (client-side state change, no POST); the single
|
|
20
|
+
* direct-kind cell defined today (Save) triggers the existing
|
|
21
|
+
* `[data-action="save-version"]` handler, the one allowed file
|
|
22
|
+
* mutation.
|
|
23
|
+
*
|
|
24
|
+
* The mode tag (`'review' | 'edit' | 'both'`) maps to the existing
|
|
25
|
+
* `er-mobile-tab--review` / `er-mobile-tab--edit` modifier classes;
|
|
26
|
+
* the `body[data-edit-mode="editing"]` CSS rules swap the grid layout
|
|
27
|
+
* between modes by hiding the off-mode cells.
|
|
28
|
+
*/
|
|
29
|
+
import { type RawHtml } from './html.ts';
|
|
30
|
+
/** Discriminated union of bar cell actions. A cell either opens a
|
|
31
|
+
* bottom-sheet (the dominant pattern) or fires a direct client-side
|
|
32
|
+
* action (the Save tab is the only direct-kind today). */
|
|
33
|
+
export type CellAction = {
|
|
34
|
+
readonly kind: 'sheet';
|
|
35
|
+
readonly name: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: 'direct';
|
|
38
|
+
readonly action: string;
|
|
39
|
+
};
|
|
40
|
+
/** Optional count badge rendered after the label. Default tone is
|
|
41
|
+
* red-pencil; `kraft` tone is the kraft-coloured variant used by the
|
|
42
|
+
* Scrapbook tab to distinguish folio context from action peers. */
|
|
43
|
+
export interface CellCount {
|
|
44
|
+
readonly dataAttr: string;
|
|
45
|
+
readonly tone?: 'red' | 'kraft';
|
|
46
|
+
}
|
|
47
|
+
/** A single bar cell. Caller-supplied; the helper renders the cell
|
|
48
|
+
* into the bar's flex row. */
|
|
49
|
+
export interface Cell {
|
|
50
|
+
readonly glyph: string;
|
|
51
|
+
readonly label: string;
|
|
52
|
+
/** Which edit-mode the cell is visible in. `both` renders no modifier
|
|
53
|
+
* (the cell shows in both review and edit). */
|
|
54
|
+
readonly mode: 'review' | 'edit' | 'both';
|
|
55
|
+
readonly action: CellAction;
|
|
56
|
+
readonly count?: CellCount;
|
|
57
|
+
/** Additional class modifier appended to the base class string
|
|
58
|
+
* (e.g. `er-mobile-tab--scrapbook`). */
|
|
59
|
+
readonly modifierClass?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface RenderMobileBarOptions {
|
|
62
|
+
readonly contextual: readonly Cell[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the mobile bottom-bar chrome from a list of contextual cells.
|
|
66
|
+
*
|
|
67
|
+
* Throws when `contextual` is empty (a bar with zero cells is
|
|
68
|
+
* malformed) or when it carries more than `MAX_CELLS` (overflows the
|
|
69
|
+
* CSS grid layout).
|
|
70
|
+
*/
|
|
71
|
+
export declare function renderMobileBar(opts: RenderMobileBarOptions): RawHtml;
|
|
72
|
+
//# sourceMappingURL=mobile-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-bar.d.ts","sourceRoot":"","sources":["../../src/pages/mobile-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAQnE;;2DAE2D;AAC3D,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;oEAEoE;AACpE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CACjC;AAED;+BAC+B;AAC/B,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;oDACgD;IAChD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;6CACyC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;CACtC;AAuCD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAarE"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal mobile bottom-bar.
|
|
3
|
+
*
|
|
4
|
+
* Renders the `<nav class="er-mobile-bar">` chrome that the studio's
|
|
5
|
+
* phone-width surfaces share. The existing `editorial-review.css` rules
|
|
6
|
+
* (`.er-mobile-bar`, `.er-mobile-tab*`, `.er-mobile-tab--review`,
|
|
7
|
+
* `.er-mobile-tab--edit`) cascade against the markup this helper emits
|
|
8
|
+
* unchanged — the bar is contextual chrome the caller composes by
|
|
9
|
+
* supplying a list of `Cell`s that name each tab's surface affordance.
|
|
10
|
+
*
|
|
11
|
+
* Design contract (DESIGN-STANDARDS.md § Studio navigation model):
|
|
12
|
+
* the mobile bar carries ONLY contextual cells — no global nav. The
|
|
13
|
+
* star nav (Desk hub, ← back, ⋮ menu) lives in the masthead. This
|
|
14
|
+
* helper is intentionally narrow: it cannot emit nav-region cells; it
|
|
15
|
+
* only emits the contextual ribbon the caller supplied.
|
|
16
|
+
*
|
|
17
|
+
* THESIS Consequence 2: the bar is server-rendered chrome. None of the
|
|
18
|
+
* buttons rendered here mutate state directly on render — sheet-kind
|
|
19
|
+
* cells open a sheet (client-side state change, no POST); the single
|
|
20
|
+
* direct-kind cell defined today (Save) triggers the existing
|
|
21
|
+
* `[data-action="save-version"]` handler, the one allowed file
|
|
22
|
+
* mutation.
|
|
23
|
+
*
|
|
24
|
+
* The mode tag (`'review' | 'edit' | 'both'`) maps to the existing
|
|
25
|
+
* `er-mobile-tab--review` / `er-mobile-tab--edit` modifier classes;
|
|
26
|
+
* the `body[data-edit-mode="editing"]` CSS rules swap the grid layout
|
|
27
|
+
* between modes by hiding the off-mode cells.
|
|
28
|
+
*/
|
|
29
|
+
import { html, unsafe, escapeHtml } from "./html.js";
|
|
30
|
+
/** Maximum cells the bar can carry. CSS grid swaps between 3 and 4
|
|
31
|
+
* visible columns by mode; the bar must hold all mode-keyed cells the
|
|
32
|
+
* caller defines, but more than 6 would overflow the layout the CSS
|
|
33
|
+
* assumes. */
|
|
34
|
+
const MAX_CELLS = 6;
|
|
35
|
+
function modeClass(mode) {
|
|
36
|
+
if (mode === 'review')
|
|
37
|
+
return ' er-mobile-tab--review';
|
|
38
|
+
if (mode === 'edit')
|
|
39
|
+
return ' er-mobile-tab--edit';
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
function renderCell(cell) {
|
|
43
|
+
const baseClass = 'er-mobile-tab' + modeClass(cell.mode)
|
|
44
|
+
+ (cell.modifierClass !== undefined ? ' ' + cell.modifierClass : '');
|
|
45
|
+
let actionAttr;
|
|
46
|
+
let ariaAttrs;
|
|
47
|
+
if (cell.action.kind === 'sheet') {
|
|
48
|
+
actionAttr = `data-mobile-sheet="${escapeHtml(cell.action.name)}"`;
|
|
49
|
+
ariaAttrs = ' aria-controls="er-mobile-sheet" aria-expanded="false"';
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
actionAttr = `data-mobile-action="${escapeHtml(cell.action.action)}"`;
|
|
53
|
+
ariaAttrs = '';
|
|
54
|
+
}
|
|
55
|
+
const count = cell.count;
|
|
56
|
+
let countMarkup = '';
|
|
57
|
+
if (count !== undefined) {
|
|
58
|
+
const tone = count.tone ?? 'red';
|
|
59
|
+
const countClass = tone === 'kraft'
|
|
60
|
+
? 'er-mobile-tab-count er-mobile-tab-count--kraft'
|
|
61
|
+
: 'er-mobile-tab-count';
|
|
62
|
+
countMarkup = `<span class="${countClass}" ${escapeHtml(count.dataAttr)} hidden>0</span>`;
|
|
63
|
+
}
|
|
64
|
+
return `<button class="${escapeHtml(baseClass)}" ${actionAttr} type="button"${ariaAttrs}>`
|
|
65
|
+
+ `<span class="er-mobile-tab-glyph" aria-hidden="true">${escapeHtml(cell.glyph)}</span>`
|
|
66
|
+
+ `<span class="er-mobile-tab-label">${escapeHtml(cell.label)}</span>`
|
|
67
|
+
+ countMarkup
|
|
68
|
+
+ `</button>`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Render the mobile bottom-bar chrome from a list of contextual cells.
|
|
72
|
+
*
|
|
73
|
+
* Throws when `contextual` is empty (a bar with zero cells is
|
|
74
|
+
* malformed) or when it carries more than `MAX_CELLS` (overflows the
|
|
75
|
+
* CSS grid layout).
|
|
76
|
+
*/
|
|
77
|
+
export function renderMobileBar(opts) {
|
|
78
|
+
const cells = opts.contextual;
|
|
79
|
+
if (cells.length === 0) {
|
|
80
|
+
throw new Error('renderMobileBar: contextual must contain at least one cell; an empty bar is malformed.');
|
|
81
|
+
}
|
|
82
|
+
if (cells.length > MAX_CELLS) {
|
|
83
|
+
throw new Error(`renderMobileBar: contextual.length=${cells.length} exceeds the bar's MAX_CELLS=${MAX_CELLS}.`);
|
|
84
|
+
}
|
|
85
|
+
const cellsMarkup = cells.map(renderCell).join('');
|
|
86
|
+
return unsafe(html `<nav class="er-mobile-bar" data-mobile-bar aria-label="Surface tabs">${unsafe(cellsMarkup)}</nav>`);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=mobile-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-bar.js","sourceRoot":"","sources":["../../src/pages/mobile-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAgB,MAAM,WAAW,CAAC;AAEnE;;;eAGe;AACf,MAAM,SAAS,GAAG,CAAC,CAAC;AAoCpB,SAAS,SAAS,CAAC,IAAkB;IACnC,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,wBAAwB,CAAC;IACvD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,sBAAsB,CAAC;IACnD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,UAAU,CAAC,IAAU;IAC5B,MAAM,SAAS,GAAG,eAAe,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;UACpD,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvE,IAAI,UAAkB,CAAC;IACvB,IAAI,SAAiB,CAAC;IACtB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACjC,UAAU,GAAG,sBAAsB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACnE,SAAS,GAAG,wDAAwD,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,uBAAuB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;QACtE,SAAS,GAAG,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC;QACjC,MAAM,UAAU,GAAG,IAAI,KAAK,OAAO;YACjC,CAAC,CAAC,gDAAgD;YAClD,CAAC,CAAC,qBAAqB,CAAC;QAC1B,WAAW,GAAG,gBAAgB,UAAU,KAAK,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAC5F,CAAC;IAED,OAAO,kBAAkB,UAAU,CAAC,SAAS,CAAC,KAAK,UAAU,iBAAiB,SAAS,GAAG;UACtF,wDAAwD,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;UACvF,qCAAqC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;UACpE,WAAW;UACX,WAAW,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAA4B;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;IAC5G,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,sCAAsC,KAAK,CAAC,MAAM,gCAAgC,SAAS,GAAG,CAC/F,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,IAAI,CAAA,wEAAwE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AACzH,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal mobile bottom-bar.
|
|
3
|
+
*
|
|
4
|
+
* Renders the `<nav class="er-mobile-bar">` chrome that the studio's
|
|
5
|
+
* phone-width surfaces share. The existing `editorial-review.css` rules
|
|
6
|
+
* (`.er-mobile-bar`, `.er-mobile-tab*`, `.er-mobile-tab--review`,
|
|
7
|
+
* `.er-mobile-tab--edit`) cascade against the markup this helper emits
|
|
8
|
+
* unchanged — the bar is contextual chrome the caller composes by
|
|
9
|
+
* supplying a list of `Cell`s that name each tab's surface affordance.
|
|
10
|
+
*
|
|
11
|
+
* Design contract (DESIGN-STANDARDS.md § Studio navigation model):
|
|
12
|
+
* the mobile bar carries ONLY contextual cells — no global nav. The
|
|
13
|
+
* star nav (Desk hub, ← back, ⋮ menu) lives in the masthead. This
|
|
14
|
+
* helper is intentionally narrow: it cannot emit nav-region cells; it
|
|
15
|
+
* only emits the contextual ribbon the caller supplied.
|
|
16
|
+
*
|
|
17
|
+
* THESIS Consequence 2: the bar is server-rendered chrome. None of the
|
|
18
|
+
* buttons rendered here mutate state directly on render — sheet-kind
|
|
19
|
+
* cells open a sheet (client-side state change, no POST); the single
|
|
20
|
+
* direct-kind cell defined today (Save) triggers the existing
|
|
21
|
+
* `[data-action="save-version"]` handler, the one allowed file
|
|
22
|
+
* mutation.
|
|
23
|
+
*
|
|
24
|
+
* The mode tag (`'review' | 'edit' | 'both'`) maps to the existing
|
|
25
|
+
* `er-mobile-tab--review` / `er-mobile-tab--edit` modifier classes;
|
|
26
|
+
* the `body[data-edit-mode="editing"]` CSS rules swap the grid layout
|
|
27
|
+
* between modes by hiding the off-mode cells.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { html, unsafe, escapeHtml, type RawHtml } from './html.ts';
|
|
31
|
+
|
|
32
|
+
/** Maximum cells the bar can carry. CSS grid swaps between 3 and 4
|
|
33
|
+
* visible columns by mode; the bar must hold all mode-keyed cells the
|
|
34
|
+
* caller defines, but more than 6 would overflow the layout the CSS
|
|
35
|
+
* assumes. */
|
|
36
|
+
const MAX_CELLS = 6;
|
|
37
|
+
|
|
38
|
+
/** Discriminated union of bar cell actions. A cell either opens a
|
|
39
|
+
* bottom-sheet (the dominant pattern) or fires a direct client-side
|
|
40
|
+
* action (the Save tab is the only direct-kind today). */
|
|
41
|
+
export type CellAction =
|
|
42
|
+
| { readonly kind: 'sheet'; readonly name: string }
|
|
43
|
+
| { readonly kind: 'direct'; readonly action: string };
|
|
44
|
+
|
|
45
|
+
/** Optional count badge rendered after the label. Default tone is
|
|
46
|
+
* red-pencil; `kraft` tone is the kraft-coloured variant used by the
|
|
47
|
+
* Scrapbook tab to distinguish folio context from action peers. */
|
|
48
|
+
export interface CellCount {
|
|
49
|
+
readonly dataAttr: string;
|
|
50
|
+
readonly tone?: 'red' | 'kraft';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A single bar cell. Caller-supplied; the helper renders the cell
|
|
54
|
+
* into the bar's flex row. */
|
|
55
|
+
export interface Cell {
|
|
56
|
+
readonly glyph: string;
|
|
57
|
+
readonly label: string;
|
|
58
|
+
/** Which edit-mode the cell is visible in. `both` renders no modifier
|
|
59
|
+
* (the cell shows in both review and edit). */
|
|
60
|
+
readonly mode: 'review' | 'edit' | 'both';
|
|
61
|
+
readonly action: CellAction;
|
|
62
|
+
readonly count?: CellCount;
|
|
63
|
+
/** Additional class modifier appended to the base class string
|
|
64
|
+
* (e.g. `er-mobile-tab--scrapbook`). */
|
|
65
|
+
readonly modifierClass?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface RenderMobileBarOptions {
|
|
69
|
+
readonly contextual: readonly Cell[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function modeClass(mode: Cell['mode']): string {
|
|
73
|
+
if (mode === 'review') return ' er-mobile-tab--review';
|
|
74
|
+
if (mode === 'edit') return ' er-mobile-tab--edit';
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderCell(cell: Cell): string {
|
|
79
|
+
const baseClass = 'er-mobile-tab' + modeClass(cell.mode)
|
|
80
|
+
+ (cell.modifierClass !== undefined ? ' ' + cell.modifierClass : '');
|
|
81
|
+
|
|
82
|
+
let actionAttr: string;
|
|
83
|
+
let ariaAttrs: string;
|
|
84
|
+
if (cell.action.kind === 'sheet') {
|
|
85
|
+
actionAttr = `data-mobile-sheet="${escapeHtml(cell.action.name)}"`;
|
|
86
|
+
ariaAttrs = ' aria-controls="er-mobile-sheet" aria-expanded="false"';
|
|
87
|
+
} else {
|
|
88
|
+
actionAttr = `data-mobile-action="${escapeHtml(cell.action.action)}"`;
|
|
89
|
+
ariaAttrs = '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const count = cell.count;
|
|
93
|
+
let countMarkup = '';
|
|
94
|
+
if (count !== undefined) {
|
|
95
|
+
const tone = count.tone ?? 'red';
|
|
96
|
+
const countClass = tone === 'kraft'
|
|
97
|
+
? 'er-mobile-tab-count er-mobile-tab-count--kraft'
|
|
98
|
+
: 'er-mobile-tab-count';
|
|
99
|
+
countMarkup = `<span class="${countClass}" ${escapeHtml(count.dataAttr)} hidden>0</span>`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return `<button class="${escapeHtml(baseClass)}" ${actionAttr} type="button"${ariaAttrs}>`
|
|
103
|
+
+ `<span class="er-mobile-tab-glyph" aria-hidden="true">${escapeHtml(cell.glyph)}</span>`
|
|
104
|
+
+ `<span class="er-mobile-tab-label">${escapeHtml(cell.label)}</span>`
|
|
105
|
+
+ countMarkup
|
|
106
|
+
+ `</button>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Render the mobile bottom-bar chrome from a list of contextual cells.
|
|
111
|
+
*
|
|
112
|
+
* Throws when `contextual` is empty (a bar with zero cells is
|
|
113
|
+
* malformed) or when it carries more than `MAX_CELLS` (overflows the
|
|
114
|
+
* CSS grid layout).
|
|
115
|
+
*/
|
|
116
|
+
export function renderMobileBar(opts: RenderMobileBarOptions): RawHtml {
|
|
117
|
+
const cells = opts.contextual;
|
|
118
|
+
if (cells.length === 0) {
|
|
119
|
+
throw new Error('renderMobileBar: contextual must contain at least one cell; an empty bar is malformed.');
|
|
120
|
+
}
|
|
121
|
+
if (cells.length > MAX_CELLS) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`renderMobileBar: contextual.length=${cells.length} exceeds the bar's MAX_CELLS=${MAX_CELLS}.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const cellsMarkup = cells.map(renderCell).join('');
|
|
128
|
+
return unsafe(html`<nav class="er-mobile-bar" data-mobile-bar aria-label="Surface tabs">${unsafe(cellsMarkup)}</nav>`);
|
|
129
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortform-review-specific mobile sheet host + bar cell composition.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `entry-review/mobile-sheet.ts` for the shortform review
|
|
5
|
+
* surface. The mobile-bar primitive (`../mobile-bar.ts`) is universal;
|
|
6
|
+
* each surface composes its `Cell[]` and the corresponding slot host
|
|
7
|
+
* here.
|
|
8
|
+
*
|
|
9
|
+
* Design contract (DESIGN-STANDARDS.md § Universal bar contract,
|
|
10
|
+
* settled 2026-05-13):
|
|
11
|
+
* - One bar primitive, one shape. The per-surface design variable
|
|
12
|
+
* is the cells, not the chrome.
|
|
13
|
+
* - 1-6 contextual cells. The Actions cell is unconditional so the
|
|
14
|
+
* bar is never empty (renderMobileBar throws on empty cells).
|
|
15
|
+
* - The TOC and Versions cells are conditional (hidden when their
|
|
16
|
+
* content has fewer than 2 items).
|
|
17
|
+
*
|
|
18
|
+
* State-machine compliance (DESKWORK-STATE-MACHINE.md):
|
|
19
|
+
* - Commandment II: verbs are universal. Approve/Iterate/Cancel are
|
|
20
|
+
* rendered unconditionally inside the Actions slot. The shortform
|
|
21
|
+
* surface header (shortform-review.ts:1-18) explicitly defers the
|
|
22
|
+
* legacy `DraftWorkflowState` migration; for this step we surface
|
|
23
|
+
* the three universal verbs and trust the client clipboard handlers
|
|
24
|
+
* to compose the right slash command from the workflow context.
|
|
25
|
+
* - Commandment III: no review-state surfacing. No "IN REVIEW" /
|
|
26
|
+
* "ITERATING" / "APPROVED" pills. No `.er-stamp`. No
|
|
27
|
+
* `.er-pending-state` markup.
|
|
28
|
+
* - Commandment IV/V/VII: clipboard-copy is the only action. The
|
|
29
|
+
* three buttons carry `data-action="approve|iterate|cancel"`; the
|
|
30
|
+
* existing client handlers in `editorial-review-client.ts` wire
|
|
31
|
+
* them to clipboard-only handlers (post-G.3 refactor).
|
|
32
|
+
* - G.4 (issue #260): the destructive verb is `cancel`, NOT `reject`.
|
|
33
|
+
* The state machine has no `reject` verb.
|
|
34
|
+
*/
|
|
35
|
+
import type { TocEntry } from '@deskwork/core/review/toc';
|
|
36
|
+
import type { DraftVersion, DraftWorkflowItem } from '@deskwork/core/review/types';
|
|
37
|
+
import { type RawHtml } from './html.ts';
|
|
38
|
+
import type { Cell } from './mobile-bar.ts';
|
|
39
|
+
export interface ShortformBarCellOptions {
|
|
40
|
+
readonly tocEntries: readonly TocEntry[];
|
|
41
|
+
readonly versions: readonly DraftVersion[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the shortform review bar's contextual cell list.
|
|
45
|
+
*
|
|
46
|
+
* Cell ordering (when present): TOC → Versions → Actions. The Actions
|
|
47
|
+
* cell is always last and always present; the bar is never empty.
|
|
48
|
+
*
|
|
49
|
+
* Conditional-omission rules:
|
|
50
|
+
* - TOC cell: omitted when fewer than 2 heading entries exist.
|
|
51
|
+
* Shortform drafts (social-platform copy) are often headingless;
|
|
52
|
+
* surfacing an empty TOC adds noise without value per the
|
|
53
|
+
* "structure over scrolling" principle.
|
|
54
|
+
* - Versions cell: omitted when only one revision exists. With one
|
|
55
|
+
* revision there is no history to navigate.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getShortformBarCells(opts: ShortformBarCellOptions): readonly Cell[];
|
|
58
|
+
export interface ShortformMobileSheetOptions {
|
|
59
|
+
readonly tocEntries: readonly TocEntry[];
|
|
60
|
+
readonly versions: readonly DraftVersion[];
|
|
61
|
+
readonly workflow: DraftWorkflowItem;
|
|
62
|
+
readonly currentVersion: DraftVersion;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the shortform mobile sheet host with the three named slots
|
|
66
|
+
* (toc / versions / actions). Each slot body is server-rendered so
|
|
67
|
+
* the client doesn't need to clone DOM from elsewhere on the page —
|
|
68
|
+
* the slot is self-contained.
|
|
69
|
+
*
|
|
70
|
+
* The sheet host is always rendered (matches the entry-review pattern
|
|
71
|
+
* where the host is present and the client toggles `hidden` on
|
|
72
|
+
* dispatch). Individual slots are hidden until the corresponding bar
|
|
73
|
+
* cell opens them via the shared `sheet-controller` primitive.
|
|
74
|
+
*/
|
|
75
|
+
export declare function renderShortformMobileSheet(opts: ShortformMobileSheetOptions): RawHtml;
|
|
76
|
+
//# sourceMappingURL=shortform-review-mobile-sheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortform-review-mobile-sheet.d.ts","sourceRoot":"","sources":["../../src/pages/shortform-review-mobile-sheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,IAAI,EAAE,CAyBnF;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC;CACvC;AAiDD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,2BAA2B,GAChC,OAAO,CA4BT"}
|