@humanforest/slidev-theme 0.1.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/LICENSE +67 -0
- package/README.md +577 -0
- package/assetUrl.js +38 -0
- package/components/Alert.vue +209 -0
- package/components/AreaBox.vue +152 -0
- package/components/AreaChart.vue +145 -0
- package/components/Avatar.vue +267 -0
- package/components/AvatarGroup.vue +94 -0
- package/components/BadgeStrip.vue +203 -0
- package/components/Bars.vue +207 -0
- package/components/BoroughShape.vue +127 -0
- package/components/BrowserFrame.vue +484 -0
- package/components/CalendarGrid.vue +480 -0
- package/components/Card.vue +70 -0
- package/components/Carousel.vue +266 -0
- package/components/ChartFrame.vue +426 -0
- package/components/ChordChart.vue +264 -0
- package/components/ComposedChart.vue +187 -0
- package/components/Contents.vue +86 -0
- package/components/DataTable.vue +244 -0
- package/components/DeckMap.vue +409 -0
- package/components/DeviceFrame.vue +331 -0
- package/components/DonutChart.vue +103 -0
- package/components/DottedMap.vue +126 -0
- package/components/Endorsement.vue +76 -0
- package/components/Grid.vue +169 -0
- package/components/GroupedBarChart.vue +141 -0
- package/components/IconTile.vue +116 -0
- package/components/Kpi.vue +188 -0
- package/components/LineChart.vue +133 -0
- package/components/Logo.vue +203 -0
- package/components/LondonMap.vue +348 -0
- package/components/MarkPair.vue +109 -0
- package/components/Marquee.vue +263 -0
- package/components/NestedDonutChart.vue +295 -0
- package/components/Placeholder.vue +88 -0
- package/components/SankeyChart.vue +223 -0
- package/components/ScatterChart.vue +142 -0
- package/components/StackedBarChart.vue +143 -0
- package/components/StatCard.vue +134 -0
- package/components/StatusTrack.vue +334 -0
- package/components/Timeline.vue +249 -0
- package/components/TimelineChart.vue +329 -0
- package/components/TreemapChart.vue +267 -0
- package/components/backgrounds.js +135 -0
- package/components/boroughGeometry.ts +59 -0
- package/components/chartData.ts +277 -0
- package/components/chartTip.ts +201 -0
- package/components/codeTheme.ts +239 -0
- package/components/deckPalette.ts +157 -0
- package/components/dottedMapGeometry.js +60 -0
- package/components/iconSlugs.js +55 -0
- package/components/iconUrl.js +51 -0
- package/components/mermaidTheme.ts +972 -0
- package/components/motionDuration.ts +41 -0
- package/components/tableRules.ts +257 -0
- package/confidential-mark.vue +151 -0
- package/custom-nav-controls.vue +79 -0
- package/endorsements/b-corp-black.svg +30 -0
- package/endorsements/b-corp-white.svg +30 -0
- package/global-top.vue +1662 -0
- package/icons/forest/bike-asterisk.svg +21 -0
- package/icons/forest/bike-down.svg +19 -0
- package/icons/forest/bike-off.svg +20 -0
- package/icons/forest/bike-up.svg +19 -0
- package/icons/forest/bike-x.svg +19 -0
- package/icons/forest/bike.svg +18 -0
- package/icons/forest/mark-fill.svg +6 -0
- package/icons/forest/mark.svg +6 -0
- package/icons/forest/parking.svg +3 -0
- package/icons/forest/star-fill.svg +4 -0
- package/icons/forest/star.svg +4 -0
- package/icons/forest/traffic-light-caution.svg +8 -0
- package/icons/forest/traffic-light-go.svg +8 -0
- package/icons/forest/traffic-light-lit.svg +8 -0
- package/icons/forest/traffic-light-stop.svg +8 -0
- package/icons/forest/traffic-light.svg +8 -0
- package/icons/lucide/battery.svg +1 -0
- package/icons/lucide/bike.svg +1 -0
- package/icons/lucide/chart-line.svg +1 -0
- package/icons/lucide/check.svg +1 -0
- package/icons/lucide/circle-check.svg +1 -0
- package/icons/lucide/clock.svg +1 -0
- package/icons/lucide/code.svg +1 -0
- package/icons/lucide/component.svg +1 -0
- package/icons/lucide/frame.svg +1 -0
- package/icons/lucide/layout-grid.svg +1 -0
- package/icons/lucide/map-pin.svg +1 -0
- package/icons/lucide/map.svg +1 -0
- package/icons/lucide/palette.svg +1 -0
- package/icons/lucide/table.svg +1 -0
- package/icons/lucide/triangle-alert.svg +1 -0
- package/icons/lucide/type.svg +1 -0
- package/icons/lucide/wrench.svg +1 -0
- package/layouts/bleed.vue +317 -0
- package/layouts/cover.vue +1514 -0
- package/layouts/default.vue +187 -0
- package/layouts/divider.vue +286 -0
- package/layouts/end.vue +384 -0
- package/layouts/quote.vue +218 -0
- package/layouts/split.vue +1875 -0
- package/layouts/stack.vue +944 -0
- package/layouts/statement.vue +67 -0
- package/manifest/chrome.ts +36 -0
- package/manifest/components.ts +49 -0
- package/manifest/index.ts +57 -0
- package/manifest/layouts.ts +37 -0
- package/manifest/manifest.json +6514 -0
- package/manifest/presets.ts +635 -0
- package/notch.js +458 -0
- package/notchPath.js +188 -0
- package/package.json +77 -0
- package/section.js +110 -0
- package/setup/mermaid-renderer.ts +2120 -0
- package/setup/mermaid.ts +30 -0
- package/setup/shiki.ts +44 -0
- package/setup/shortcuts.ts +28 -0
- package/slide-bottom.vue +305 -0
- package/slide-top.vue +268 -0
- package/styles/base.css +2611 -0
- package/styles/index.ts +71 -0
- package/styles/tokens.css +144 -0
- package/uno.config.ts +231 -0
- package/useNotchCuts.js +200 -0
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@humanforest/slidev-theme",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Forest brand theme for Slidev — the guardrail. Layouts + components wired to @humanforest/tokens, so any deck is on-brand by construction. Do not hand-pick colours/fonts here; everything aliases the engine tokens.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"slidev-theme",
|
|
7
|
+
"slidev",
|
|
8
|
+
"theme",
|
|
9
|
+
"forest"
|
|
10
|
+
],
|
|
11
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "vitest run"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0",
|
|
17
|
+
"slidev": ">=0.50.0"
|
|
18
|
+
},
|
|
19
|
+
"slidev": {
|
|
20
|
+
"colorSchema": "light",
|
|
21
|
+
"defaults": {
|
|
22
|
+
"transition": "view-transition",
|
|
23
|
+
"aspectRatio": "16/9",
|
|
24
|
+
"canvasWidth": 1920,
|
|
25
|
+
"fonts": {
|
|
26
|
+
"sans": "GT Haptik",
|
|
27
|
+
"mono": "JetBrains Mono",
|
|
28
|
+
"provider": "none",
|
|
29
|
+
"local": "GT Haptik,JetBrains Mono,MohrAlt"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@humanforest/fonts": "^0.1.0",
|
|
35
|
+
"@humanforest/frames": "^0.1.0",
|
|
36
|
+
"@humanforest/tokens": "^0.1.0",
|
|
37
|
+
"@humanforest/ui": "^0.1.0",
|
|
38
|
+
"@iconify-json/lucide": "^1.2.0",
|
|
39
|
+
"@iconify-json/vscode-icons": "^1.2.75",
|
|
40
|
+
"@unovis/ts": "^1.6.7",
|
|
41
|
+
"@unovis/vue": "^1.6.7",
|
|
42
|
+
"mapbox-gl": "^3.28.1",
|
|
43
|
+
"mermaid": "^11.17.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@unocss/preset-wind3": "^66.7.5",
|
|
47
|
+
"unocss": "^66.7.5",
|
|
48
|
+
"vitest": "^4.1.10"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"unocss": ">=66"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"layouts",
|
|
55
|
+
"components",
|
|
56
|
+
"styles",
|
|
57
|
+
"setup",
|
|
58
|
+
"global-top.vue",
|
|
59
|
+
"slide-top.vue",
|
|
60
|
+
"slide-bottom.vue",
|
|
61
|
+
"custom-nav-controls.vue",
|
|
62
|
+
"confidential-mark.vue",
|
|
63
|
+
"assetUrl.js",
|
|
64
|
+
"notch.js",
|
|
65
|
+
"notchPath.js",
|
|
66
|
+
"section.js",
|
|
67
|
+
"useNotchCuts.js",
|
|
68
|
+
"uno.config.ts",
|
|
69
|
+
"icons",
|
|
70
|
+
"endorsements",
|
|
71
|
+
"manifest",
|
|
72
|
+
"README.md"
|
|
73
|
+
],
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
}
|
|
77
|
+
}
|
package/section.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The section pill's logic, split out of slide-top.vue so it is unit-testable at all.
|
|
2
|
+
//
|
|
3
|
+
// packages/slidev-theme's own suite (test/frontmatter.static.test.ts) runs under plain Node, no
|
|
4
|
+
// DOM (see vitest.config.ts: `environment: 'node'`) — there is no @vue/test-utils here and no
|
|
5
|
+
// component ever gets mounted. A computed buried inside slide-top.vue's <script setup> would
|
|
6
|
+
// therefore be unreachable from a test. Pulling the derivation out into a plain, exported function
|
|
7
|
+
// is the same move backgrounds.js already made for the background lookup, and the same shape the
|
|
8
|
+
// suite already tests directly for contrast() (packages/tokens/src/colourEngine.ts) — a pure
|
|
9
|
+
// function, imported and called with plain data, no framework runtime required.
|
|
10
|
+
//
|
|
11
|
+
// A slide's section is the nearest PRECEDING divider's title — not a value the slide carries
|
|
12
|
+
// itself. Two traps, both load-bearing (see task-5-api-notes.md, which this was checked against):
|
|
13
|
+
//
|
|
14
|
+
// 1. Indexing. `useNav()`'s `slides` is a zero-indexed array; `currentPage` is Slidev's own
|
|
15
|
+
// ONE-based page number. `currentPage - 1` is the current slide's own array index. Walking
|
|
16
|
+
// back from `currentPage` directly (forgetting the -1) starts one slide too far forward and
|
|
17
|
+
// lets a divider claim the slide AFTER it as "its own" section — while the divider itself
|
|
18
|
+
// would report whatever divider preceded IT, or nothing. The walk here starts at
|
|
19
|
+
// `currentPage - 1` specifically so a divider slide reports ITS OWN title as its section
|
|
20
|
+
// (there is nothing wrong with a divider naming itself — the audience is looking at "The
|
|
21
|
+
// seam" while "The seam" is the current slide), and the slide immediately before it must not.
|
|
22
|
+
// 2. Precedence. `frontmatter.section` is the author's explicit override (a plain custom key,
|
|
23
|
+
// not reserved anywhere in Slidev's frontmatter). `meta.slide.title` is Slidev's OWN
|
|
24
|
+
// first-heading extraction — reading it is fine, but a layout must never be told to WRITE
|
|
25
|
+
// `title:` in its own frontmatter to name a section, because `title` is reserved on slide 1
|
|
26
|
+
// (RESERVED_ON_SLIDE_ONE in test/props.ts) and would silently be dropped there.
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The minimal shape this needs out of a `useNav()` slide route — `SlideRoute` from
|
|
30
|
+
* `@slidev/types`, narrowed to the two fields actually read.
|
|
31
|
+
* @typedef {{ meta: { slide: { frontmatter: Record<string, any>, title?: string } } }} MinimalSlideRoute
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Derives the section pill's text for the slide at `currentPage` (Slidev's own one-based page
|
|
36
|
+
* number) by walking backwards through the deck's own slide list to the first slide — inclusive
|
|
37
|
+
* of the current one — whose `layout` is `'divider'`.
|
|
38
|
+
*
|
|
39
|
+
* @param {MinimalSlideRoute[]} slides `useNav()`'s own `slides.value` — zero-indexed.
|
|
40
|
+
* @param {number} currentPage `useNav()`'s own `currentPage.value` — one-based.
|
|
41
|
+
* @returns {string} the nearest preceding (or the current) divider's `section:` override, falling
|
|
42
|
+
* back to its own extracted title, or `''` when no divider precedes this slide at all — the
|
|
43
|
+
* empty string is the caller's own signal to render no pill.
|
|
44
|
+
*
|
|
45
|
+
* Three ways to get that empty string on purpose, narrowest first:
|
|
46
|
+
* - `section: false` on a SLIDE — this slide only, chrome otherwise untouched
|
|
47
|
+
* - `section: false` on a DIVIDER — that divider and every slide until the next one
|
|
48
|
+
* - `sectionPill: false` in HEADMATTER — the whole deck, and nothing can out-rank it
|
|
49
|
+
*/
|
|
50
|
+
export const deriveSection = (slides, currentPage) => {
|
|
51
|
+
/*
|
|
52
|
+
★★ `sectionPill: false` — THE DECK-WIDE SWITCH, AND IT IS CHECKED BEFORE EVERYTHING ELSE.
|
|
53
|
+
|
|
54
|
+
Read off slide ONE's frontmatter, which in Slidev is the deck's headmatter. Not merged, not
|
|
55
|
+
inherited: read directly, once, from the only slide that can carry a deck-level statement.
|
|
56
|
+
|
|
57
|
+
★ WHY NOT `defaults: { section: false }`, WHICH ALREADY ALMOST WORKS. Slidev's `defaults:` block
|
|
58
|
+
merges into every slide's own frontmatter, so it looks like the deck-wide lever and behaves like
|
|
59
|
+
one until it doesn't: a slide's OWN frontmatter outranks a default, and every divider now
|
|
60
|
+
declares `section:` because a divider that names nothing takes the pill off every slide behind
|
|
61
|
+
it (see the walk below, and the gate in test/frontmatter.static.test.ts). So dividers — and only
|
|
62
|
+
dividers — would out-rank the default and keep their pill.
|
|
63
|
+
|
|
64
|
+
Measured before this existed: `defaults: { section: false }` cleaned 224 slides across the two
|
|
65
|
+
decks and left a pill on three, all of them dividers drawn as SPECIMENS of the divider layout
|
|
66
|
+
rather than as section openers. That is a rule nobody could predict from the outside, failing on
|
|
67
|
+
the category hardest to guess. A switch that works by accident of precedence is not a switch.
|
|
68
|
+
|
|
69
|
+
This one cannot be out-ranked, because nothing else is consulted once it is set.
|
|
70
|
+
|
|
71
|
+
NOT the same key as `section:`, deliberately. `sections` would have been the natural plural and
|
|
72
|
+
is one keystroke from `section` — a typo that silently changes SCOPE rather than erroring is
|
|
73
|
+
exactly the class of defect this file's own history is made of.
|
|
74
|
+
*/
|
|
75
|
+
if (slides[0]?.meta?.slide?.frontmatter?.sectionPill === false) return '';
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
★ `section: false` — the PER-SLIDE opt-out, and the only thing a non-divider slide may say about
|
|
79
|
+
its own section.
|
|
80
|
+
|
|
81
|
+
Two levers already existed and neither was per-slide: `section: '…'` on a DIVIDER renames the
|
|
82
|
+
pill for that whole run of slides, and `chrome: false` removes all the chrome at once, mark
|
|
83
|
+
included. A slide that wants its band's top-left corner back — and nothing else changed — had no
|
|
84
|
+
way to ask, because this function only ever read divider frontmatter.
|
|
85
|
+
|
|
86
|
+
FALSE, not empty string. `section: ''` on a divider already means "this section has no name",
|
|
87
|
+
which is a statement about the SECTION; `section: false` on any slide means "not here", which is
|
|
88
|
+
a statement about the SLIDE. Keeping them distinct matters because the first propagates forward
|
|
89
|
+
to every slide under that divider and the second does not.
|
|
90
|
+
|
|
91
|
+
It composes with the notch for free and that is not a coincidence: the cut is measured from
|
|
92
|
+
whatever chrome is actually on the panel (../useNotchCuts.js), so a slide that draws no pill
|
|
93
|
+
simply has no box in that corner and gets its plain --deck-radius-card corner back. Nothing
|
|
94
|
+
tells the panel; there is nothing to tell.
|
|
95
|
+
*/
|
|
96
|
+
if (slides[currentPage - 1]?.meta?.slide?.frontmatter?.section === false) return '';
|
|
97
|
+
|
|
98
|
+
for (let i = currentPage - 1; i >= 0; i--) {
|
|
99
|
+
const frontmatter = slides[i]?.meta?.slide?.frontmatter;
|
|
100
|
+
if (frontmatter?.layout === 'divider') {
|
|
101
|
+
// A divider may opt its whole section out the same way. Checked explicitly rather than left
|
|
102
|
+
// to `?? title`, because `false` is not nullish — it would fall through and be returned AS a
|
|
103
|
+
// boolean, which happens to render as no pill today only because the caller filters falsy
|
|
104
|
+
// values. Relying on that would be a contract nobody wrote down.
|
|
105
|
+
if (frontmatter.section === false) return '';
|
|
106
|
+
return frontmatter.section ?? slides[i].meta.slide.title ?? '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return '';
|
|
110
|
+
};
|