@kolkrabbi/kol-workshop 0.3.6 → 0.5.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/package.json +4 -4
- package/src/compositions/WorkshopDefaultSidebar.jsx +7 -7
- package/src/compositions/WorkshopSidebar.jsx +3 -3
- package/src/docs/DocsFrontmatter.jsx +51 -6
- package/src/docs/DocumentationReader.jsx +50 -18
- package/src/engine/doc-helpers.js +31 -4
- package/src/shell/ShellLayout.jsx +26 -2
- package/src/shell/ShellSidebar.jsx +47 -18
- package/src/tags/TagGraph.jsx +11 -11
- package/src/tags/TagModeOverlay.jsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-workshop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "KOL workshop/docs system — a handrolled markdown engine, search, tag system (incl. a d3 tag graph), docs viewer, and docs shell. Sits above @kolkrabbi/kol-{theme,icons,component,framework}; content is consumer-injected (the package never globs docs).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"d3": "^7.9.0",
|
|
16
|
-
"@kolkrabbi/kol-brand": "^0.1.2",
|
|
17
|
-
"@kolkrabbi/kol-component": "0.14.1",
|
|
18
16
|
"@kolkrabbi/kol-icons": "0.8.11",
|
|
17
|
+
"@kolkrabbi/kol-component": "0.14.3",
|
|
18
|
+
"@kolkrabbi/kol-brand": "^0.1.2",
|
|
19
19
|
"@kolkrabbi/kol-theme": "0.13.3",
|
|
20
|
-
"@kolkrabbi/kol-framework": "0.
|
|
20
|
+
"@kolkrabbi/kol-framework": "0.9.1"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": "^18.3.0 || ^19.0.0",
|
|
@@ -33,7 +33,7 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
|
|
|
33
33
|
<Link
|
|
34
34
|
key={child.id}
|
|
35
35
|
to={`${basePath}/${child.path}`}
|
|
36
|
-
className={`shell-
|
|
36
|
+
className={`shell-nav-item block kol-mono-14 ${
|
|
37
37
|
location.pathname === `${basePath}/${child.path}` ? 'text-emphasis' : 'text-body'
|
|
38
38
|
}`}
|
|
39
39
|
>
|
|
@@ -47,15 +47,15 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
|
|
|
47
47
|
{links && (
|
|
48
48
|
<div>
|
|
49
49
|
<div className="shell-sidebar-label kol-doc-eyebrow">Repository</div>
|
|
50
|
-
<div
|
|
50
|
+
<div>
|
|
51
51
|
{links.live && (
|
|
52
|
-
<a href={links.live} target="_blank" rel="noopener noreferrer" className="shell-
|
|
52
|
+
<a href={links.live} target="_blank" rel="noopener noreferrer" className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis">
|
|
53
53
|
<Icon name="external-link" size={14} />
|
|
54
54
|
Live site
|
|
55
55
|
</a>
|
|
56
56
|
)}
|
|
57
57
|
{links.repo && (
|
|
58
|
-
<a href={links.repo} target="_blank" rel="noopener noreferrer" className="shell-
|
|
58
|
+
<a href={links.repo} target="_blank" rel="noopener noreferrer" className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis">
|
|
59
59
|
<Icon name="external-link" size={14} />
|
|
60
60
|
GitHub
|
|
61
61
|
</a>
|
|
@@ -66,14 +66,14 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
|
|
|
66
66
|
|
|
67
67
|
<div>
|
|
68
68
|
<div className="shell-sidebar-label kol-doc-eyebrow">Quick actions</div>
|
|
69
|
-
<div
|
|
70
|
-
<Link to={basePath} className="shell-
|
|
69
|
+
<div>
|
|
70
|
+
<Link to={basePath} className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis">
|
|
71
71
|
<Icon name="layout" size={14} />
|
|
72
72
|
Workshop home
|
|
73
73
|
</Link>
|
|
74
74
|
<button
|
|
75
75
|
type="button"
|
|
76
|
-
className="shell-
|
|
76
|
+
className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis"
|
|
77
77
|
onClick={() => navigator.clipboard.writeText(window.location.pathname)}
|
|
78
78
|
title="Copy page path to clipboard"
|
|
79
79
|
>
|
|
@@ -96,7 +96,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
|
|
|
96
96
|
<div key={major} className="shell-nav-group">
|
|
97
97
|
<button
|
|
98
98
|
type="button"
|
|
99
|
-
className="shell-nav-group-header w-full text-left kol-
|
|
99
|
+
className="shell-nav-group-header w-full text-left kol-mono-14 text-body"
|
|
100
100
|
onClick={() => toggleGroup(major)}
|
|
101
101
|
>
|
|
102
102
|
<span className="flex items-center gap-2">
|
|
@@ -107,7 +107,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
|
|
|
107
107
|
/>
|
|
108
108
|
{categoryLabels[major] || 'Other'}
|
|
109
109
|
</span>
|
|
110
|
-
<span className="kol-
|
|
110
|
+
<span className="kol-mono-14 text-subtle">({docs.length})</span>
|
|
111
111
|
</button>
|
|
112
112
|
{!isCollapsed && (
|
|
113
113
|
<div className="shell-nav-items">
|
|
@@ -121,7 +121,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
|
|
|
121
121
|
onClick={onNavigate}
|
|
122
122
|
>
|
|
123
123
|
<span className="shell-nav-item-title">{cleanTitle(d.title, d.id)}</span>
|
|
124
|
-
<span className="shell-nav-item-id kol-
|
|
124
|
+
<span className="shell-nav-item-id kol-mono-14 text-subtle">{extractDocNumber(d.id)}</span>
|
|
125
125
|
</Link>
|
|
126
126
|
)
|
|
127
127
|
})}
|
|
@@ -5,16 +5,59 @@ import { useTagMode } from '../tags'
|
|
|
5
5
|
const FIELD_ICONS = {
|
|
6
6
|
file: 'file',
|
|
7
7
|
title: null,
|
|
8
|
+
type: 'folder',
|
|
9
|
+
status: 'check',
|
|
10
|
+
updated: null,
|
|
11
|
+
created: null,
|
|
12
|
+
verified: null,
|
|
13
|
+
description: null,
|
|
14
|
+
audience: null,
|
|
15
|
+
aliases: null,
|
|
8
16
|
version: 'hash-01',
|
|
17
|
+
/* legacy dialect (workshop-sample docs) — kept so those still render */
|
|
9
18
|
date: null,
|
|
10
|
-
status: 'check',
|
|
11
|
-
'content-type': null,
|
|
12
19
|
category: 'folder',
|
|
13
20
|
tags: null,
|
|
14
|
-
modified: null
|
|
21
|
+
modified: null,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Reading ORDER, not an allowlist. The kol-docs contract first
|
|
25
|
+
* (`.kol/docs-framework/01-conventions.md`: required title/type/status/updated/
|
|
26
|
+
* tags, then recommended description/aliases, then optional), legacy
|
|
27
|
+
* sample-dialect keys after, and ANY key not named here after that — so a field
|
|
28
|
+
* can never be silently dropped for being unknown.
|
|
29
|
+
*
|
|
30
|
+
* This was a filter over ['title','category','date','tags','modified'] — the
|
|
31
|
+
* workshop-SAMPLE dialect. No kol-docs document carries category/date/modified,
|
|
32
|
+
* so it admitted title + tags and discarded the seven fields the parser had
|
|
33
|
+
* already handed it. The panel wasn't missing data; it was screening for the
|
|
34
|
+
* wrong schema, and screening is now the thing it doesn't do.
|
|
35
|
+
*
|
|
36
|
+
* `related` is the one deliberate omission — the rail renders it as live links,
|
|
37
|
+
* so printing the raw wikilinks here would be the same thing twice. */
|
|
38
|
+
const FIELD_ORDER = [
|
|
39
|
+
'title', 'type', 'status', 'updated', 'tags',
|
|
40
|
+
'description', 'aliases',
|
|
41
|
+
'created', 'verified', 'audience', 'superseded_by', 'drift',
|
|
42
|
+
'category', 'date', 'modified', 'version',
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
const HIDDEN = new Set(['related'])
|
|
46
|
+
|
|
47
|
+
/** Contract order first, then anything else the doc carries, alphabetically. */
|
|
48
|
+
const orderFields = (metadata) => {
|
|
49
|
+
const present = Object.keys(metadata).filter(
|
|
50
|
+
(k) => !HIDDEN.has(k) && metadata[k] != null && metadata[k] !== '' &&
|
|
51
|
+
!(Array.isArray(metadata[k]) && metadata[k].length === 0)
|
|
52
|
+
)
|
|
53
|
+
const known = FIELD_ORDER.filter((k) => present.includes(k))
|
|
54
|
+
const rest = present.filter((k) => !FIELD_ORDER.includes(k)).sort()
|
|
55
|
+
return [...known, ...rest]
|
|
15
56
|
}
|
|
16
57
|
|
|
17
|
-
|
|
58
|
+
/* Every field rendered as a date. `updated`/`created`/`verified` are the
|
|
59
|
+
* kol-docs names; `date`/`modified` are the sample dialect's. */
|
|
60
|
+
const DATE_FIELDS = new Set(['updated', 'created', 'verified', 'date', 'modified'])
|
|
18
61
|
|
|
19
62
|
const formatDate = (dateStr) => {
|
|
20
63
|
if (!dateStr) return null
|
|
@@ -27,7 +70,7 @@ const DocsFrontmatter = ({ metadata, docId }) => {
|
|
|
27
70
|
const { openTagMode } = useTagMode()
|
|
28
71
|
if (!metadata || Object.keys(metadata).length === 0) return null
|
|
29
72
|
|
|
30
|
-
const fields =
|
|
73
|
+
const fields = orderFields(metadata)
|
|
31
74
|
|
|
32
75
|
if (fields.length === 0) return null
|
|
33
76
|
|
|
@@ -58,8 +101,10 @@ const DocsFrontmatter = ({ metadata, docId }) => {
|
|
|
58
101
|
</Tag>
|
|
59
102
|
))}
|
|
60
103
|
</span>
|
|
61
|
-
) : key
|
|
104
|
+
) : DATE_FIELDS.has(key) ? (
|
|
62
105
|
formatDate(String(value))
|
|
106
|
+
) : Array.isArray(value) ? (
|
|
107
|
+
value.join(' · ')
|
|
63
108
|
) : (
|
|
64
109
|
String(value)
|
|
65
110
|
)}
|
|
@@ -10,18 +10,41 @@ import DocsFrontmatter from './DocsFrontmatter.jsx'
|
|
|
10
10
|
import { DocSection } from './DocKit.jsx'
|
|
11
11
|
import { renderInlineTokens } from './render-tokens.jsx'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
13
|
+
/**
|
|
14
|
+
* A rail section header. Same anatomy as the LEFT tree's group header — a
|
|
15
|
+
* rotating chevron, the label, and the item count pushed right — because the
|
|
16
|
+
* two rails are one system and a reader shouldn't have to learn each side
|
|
17
|
+
* separately.
|
|
18
|
+
*
|
|
19
|
+
* These were bare text. They collapsed on click with no chevron, no count and
|
|
20
|
+
* no hover, so the only way to discover they were interactive was to click
|
|
21
|
+
* something that looked inert and watch the panel vanish. An affordance that
|
|
22
|
+
* only announces itself after you've used it isn't one.
|
|
23
|
+
*/
|
|
24
|
+
const SidebarSection = ({ sectionKey, label, count, collapsedSections, toggleSection, children }) => {
|
|
25
|
+
const isOpen = !collapsedSections[sectionKey]
|
|
26
|
+
return (
|
|
27
|
+
<div>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
className="shell-nav-group-header w-full text-left shell-sidebar-label kol-doc-eyebrow"
|
|
31
|
+
onClick={() => toggleSection(sectionKey)}
|
|
32
|
+
aria-expanded={isOpen}
|
|
33
|
+
>
|
|
34
|
+
<span className="flex items-center gap-2">
|
|
35
|
+
<Icon
|
|
36
|
+
name="chevron-right"
|
|
37
|
+
size={12}
|
|
38
|
+
className={`transition-transform ${isOpen ? 'rotate-90' : ''}`}
|
|
39
|
+
/>
|
|
40
|
+
{label}
|
|
41
|
+
</span>
|
|
42
|
+
{count > 0 && <span className="kol-mono-14 text-subtle">({count})</span>}
|
|
43
|
+
</button>
|
|
44
|
+
{isOpen && children}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
25
48
|
|
|
26
49
|
const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, componentsHref, docFilePath }) => {
|
|
27
50
|
const navigate = useNavigate()
|
|
@@ -34,6 +57,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
|
|
|
34
57
|
<SidebarSection
|
|
35
58
|
sectionKey="toc"
|
|
36
59
|
label="On this page"
|
|
60
|
+
count={toc?.length}
|
|
37
61
|
collapsedSections={collapsedSections}
|
|
38
62
|
toggleSection={toggleSection}
|
|
39
63
|
>
|
|
@@ -44,6 +68,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
|
|
|
44
68
|
<SidebarSection
|
|
45
69
|
sectionKey="related"
|
|
46
70
|
label="Related"
|
|
71
|
+
count={related.length}
|
|
47
72
|
collapsedSections={collapsedSections}
|
|
48
73
|
toggleSection={toggleSection}
|
|
49
74
|
>
|
|
@@ -64,26 +89,30 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
|
|
|
64
89
|
collapsedSections={collapsedSections}
|
|
65
90
|
toggleSection={toggleSection}
|
|
66
91
|
>
|
|
67
|
-
|
|
92
|
+
{/* Quick actions wear the SAME row as On-this-page and Related. They
|
|
93
|
+
* were `.shell-sidebar-action`, a third geometry with no left indent,
|
|
94
|
+
* so this rail carried three different row idioms — under the
|
|
95
|
+
* "ONE rail voice" comment above. */}
|
|
96
|
+
<div>
|
|
68
97
|
<button
|
|
69
98
|
type="button"
|
|
70
|
-
className="shell-
|
|
99
|
+
className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis"
|
|
71
100
|
onClick={() => navigate(-1)}
|
|
72
101
|
>
|
|
73
102
|
<Icon name="arrow-left" size={14} />
|
|
74
103
|
Back
|
|
75
104
|
</button>
|
|
76
|
-
<Link to={docsIndexHref} className="shell-
|
|
105
|
+
<Link to={docsIndexHref} className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis">
|
|
77
106
|
<Icon name="book-open" size={14} />
|
|
78
107
|
All documentation
|
|
79
108
|
</Link>
|
|
80
|
-
<Link to={componentsHref} className="shell-
|
|
109
|
+
<Link to={componentsHref} className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis">
|
|
81
110
|
<Icon name="grid" size={14} />
|
|
82
111
|
View components
|
|
83
112
|
</Link>
|
|
84
113
|
<button
|
|
85
114
|
type="button"
|
|
86
|
-
className="shell-
|
|
115
|
+
className="shell-nav-item flex items-center gap-2 kol-mono-14 text-body transition-colors hover:text-emphasis"
|
|
87
116
|
onClick={() => navigator.clipboard.writeText(docFilePath(docId))}
|
|
88
117
|
title="Copy file path to clipboard"
|
|
89
118
|
>
|
|
@@ -96,6 +125,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
|
|
|
96
125
|
{allTags.length > 0 && (
|
|
97
126
|
<SidebarSection
|
|
98
127
|
sectionKey="tags"
|
|
128
|
+
count={allTags?.length}
|
|
99
129
|
label="Tags"
|
|
100
130
|
collapsedSections={collapsedSections}
|
|
101
131
|
toggleSection={toggleSection}
|
|
@@ -376,7 +406,9 @@ const DocumentationReader = ({
|
|
|
376
406
|
|
|
377
407
|
if (!doc) {
|
|
378
408
|
return (
|
|
379
|
-
|
|
409
|
+
/* framed to a 1400px tier that was removed from the scale at theme
|
|
410
|
+
* 0.11.22 and left behind here — a width nothing else still uses */
|
|
411
|
+
<div className="mx-auto max-w-[var(--kol-content-panel)] py-16">
|
|
380
412
|
<DocsHeader title="Document Not Found" subtitle={`Could not find document: ${docId}`} />
|
|
381
413
|
<p className="kol-mono-12 mt-6">
|
|
382
414
|
<Link to={docsIndex} className="text-accent-primary">
|
|
@@ -85,14 +85,41 @@ export const cleanTitle = (title, id) => {
|
|
|
85
85
|
|
|
86
86
|
const TAG_COLORS = ['blue', 'teal', 'green', 'yellow', 'red', 'orange', 'purple', 'dark']
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
/* Colour BY NAMESPACE, from the closed taxonomy in `.kol/docs-framework/
|
|
89
|
+
* 03-tag-taxonomy.md`. Ten top-level namespaces, ten stable assignments —
|
|
90
|
+
* so `domain/*` is always one colour and the eye can group by prefix.
|
|
91
|
+
*
|
|
92
|
+
* This used to hash the whole tag string into TAG_COLORS. That made the colour
|
|
93
|
+
* a function of spelling rather than meaning: `#domain/design-system` and
|
|
94
|
+
* `#domain/iconography` landed on different hues for no reason a reader could
|
|
95
|
+
* see, and renaming a tag silently recoloured it. The taxonomy existed the
|
|
96
|
+
* whole time and nothing consulted it. */
|
|
97
|
+
const NAMESPACE_COLORS = {
|
|
98
|
+
domain: 'blue',
|
|
99
|
+
pattern: 'purple',
|
|
100
|
+
provider: 'orange',
|
|
101
|
+
integration: 'teal',
|
|
102
|
+
project: 'green',
|
|
103
|
+
audience: 'yellow',
|
|
104
|
+
brand: 'red',
|
|
105
|
+
editor: 'teal',
|
|
106
|
+
framework: 'dark',
|
|
107
|
+
archive: 'dark',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Stable fallback for tags outside the taxonomy — still deterministic, but
|
|
111
|
+
* only reached when a tag carries no known namespace. */
|
|
112
|
+
const hashColor = (s) => {
|
|
89
113
|
let hash = 0
|
|
90
|
-
for (let i = 0; i <
|
|
91
|
-
hash = ((hash << 5) - hash + tag.charCodeAt(i)) | 0
|
|
92
|
-
}
|
|
114
|
+
for (let i = 0; i < s.length; i++) hash = ((hash << 5) - hash + s.charCodeAt(i)) | 0
|
|
93
115
|
return TAG_COLORS[Math.abs(hash) % TAG_COLORS.length]
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
export const getTagColor = (tag) => {
|
|
119
|
+
const namespace = String(tag).replace(/^#/, '').split('/')[0]
|
|
120
|
+
return NAMESPACE_COLORS[namespace] ?? hashColor(namespace)
|
|
121
|
+
}
|
|
122
|
+
|
|
96
123
|
/** Group docs by major version number.
|
|
97
124
|
*
|
|
98
125
|
* Three id dialects are grouped: dotted (`0.0.1-slug`), folder-index
|
|
@@ -44,8 +44,14 @@ const MainColumn = ({ children, fullHeight }) => (
|
|
|
44
44
|
</main>
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
/* `xl`, not `lg` — the grid only declares a third column at xl (gridCols
|
|
48
|
+
* below). Rendering this at lg put THREE children in a TWO-column grid between
|
|
49
|
+
* 1024 and 1279px: the rail wrapped to an implicit second row and `h-full`
|
|
50
|
+
* split the height between them, so main got ~373px of a 900px window and the
|
|
51
|
+
* page read as empty. The breakpoint here and the one in gridCols are one
|
|
52
|
+
* decision and must not be stated twice differently. */
|
|
47
53
|
const TocColumn = ({ children }) => (
|
|
48
|
-
<aside aria-label="Table of contents" className="shell-sidebar-sticky hidden
|
|
54
|
+
<aside aria-label="Table of contents" className="shell-sidebar-sticky hidden xl:block shrink-0 h-full min-h-0 overflow-y-auto overflow-x-hidden overscroll-none pt-6 md:pt-6 lg:pt-8 pb-8">
|
|
49
55
|
{children}
|
|
50
56
|
</aside>
|
|
51
57
|
)
|
|
@@ -76,6 +82,14 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
|
|
|
76
82
|
}, [])
|
|
77
83
|
|
|
78
84
|
const effectiveTocContent = tocContent ?? defaultTocContent
|
|
85
|
+
/* KNOWN, deliberately not fixed here: an element is always truthy, so
|
|
86
|
+
* `<AutoToc/>` reserves a rail even on pages where it renders null. The
|
|
87
|
+
* obvious fix — measure the mounted column — deadlocks: once the column
|
|
88
|
+
* unmounts the probe is gone and can never report content again. The real
|
|
89
|
+
* fix is a consumer-side signal (a render-prop returning null, or an
|
|
90
|
+
* explicit `hasToc` prop), which is an API change. Filed, not improvised.
|
|
91
|
+
* The bug this caused — a half-height main column at 1024–1279px — was the
|
|
92
|
+
* `lg` vs `xl` mismatch on TocColumn above, and that IS fixed. */
|
|
79
93
|
const hasToc = Boolean(effectiveTocContent)
|
|
80
94
|
const showNav = !navCollapsed
|
|
81
95
|
const showToc = hasToc && !tocCollapsed
|
|
@@ -183,7 +197,17 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
|
|
|
183
197
|
* main scrolls unless a page locks it (ShellFullHeightContext — embeds).
|
|
184
198
|
* overscroll-none stops chaining between regions and the edge bounce. */}
|
|
185
199
|
<div className="flex-1 overflow-hidden">
|
|
186
|
-
|
|
200
|
+
{/* THE frame (kol-theme "Content widths"): ONE cap at the shell
|
|
201
|
+
* token, centred, on the framework padding ramp. This wrapper
|
|
202
|
+
* carried `w-full px-4 md:px-5 lg:px-6` — no cap at all, and
|
|
203
|
+
* Tailwind's 16/20/24 steps instead of the ramp's 20/32/48 — so
|
|
204
|
+
* every consumer page inherited the raw viewport (measured 2152px
|
|
205
|
+
* of frame at a 2200px window against an 1800px law). Every
|
|
206
|
+
* page-level width fix is downstream of this line. */}
|
|
207
|
+
<div
|
|
208
|
+
className="mx-auto h-full w-full max-w-[var(--kol-content-shell)]"
|
|
209
|
+
style={{ paddingInline: 'var(--kol-pad-section-x)' }}
|
|
210
|
+
>
|
|
187
211
|
{/* gap lives in .shell-content-grid (theme) — a gap-8 utility here
|
|
188
212
|
* outranks the layered theme rule at every width and killed the
|
|
189
213
|
* 48px wide step (ARCHITECTURE §5: component geometry in its own
|
|
@@ -75,7 +75,12 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
|
|
|
75
75
|
{label}
|
|
76
76
|
</Link>
|
|
77
77
|
) : (
|
|
78
|
-
|
|
78
|
+
/* Same voice as the labelTo branch. This read `kol-helper-10
|
|
79
|
+
* text-meta`, so the one rail mounted without a labelTo (the vault's
|
|
80
|
+
* "Documentation") rendered its label at a different size and colour
|
|
81
|
+
* from its two siblings — a type fork caused purely by which props
|
|
82
|
+
* the consumer happened to pass. */
|
|
83
|
+
<button type="button" className="shell-sidebar-label kol-doc-eyebrow" onClick={handleToggle}>{label}</button>
|
|
79
84
|
)}
|
|
80
85
|
<Tooltip label={navCollapsed ? `Expand ${label}` : `Collapse ${label}`}>
|
|
81
86
|
<button
|
|
@@ -99,25 +104,49 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
|
|
|
99
104
|
{routes.map((route) => {
|
|
100
105
|
const isExpanded = !collapsedSections[route.id]
|
|
101
106
|
|
|
107
|
+
/* A group with no children is not a group — it is a link. It used to
|
|
108
|
+
* render as a header anyway: a chevron that rotated over an empty
|
|
109
|
+
* body, no count, and no navigation, so clicking "Icons" or
|
|
110
|
+
* "Components" in the tree did nothing at all while looking like it
|
|
111
|
+
* should. Now the chevron and the toggle only appear when there is
|
|
112
|
+
* something to expand, and a childless row goes where it says. */
|
|
113
|
+
const hasChildren = route.children?.length > 0
|
|
114
|
+
const headerClass = 'shell-nav-group-header w-full text-left kol-mono-14 text-body'
|
|
115
|
+
|
|
102
116
|
return (
|
|
103
117
|
<div key={route.id} className="shell-nav-group">
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
{hasChildren ? (
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
className={headerClass}
|
|
122
|
+
onClick={() => handleSectionClick(route)}
|
|
123
|
+
aria-expanded={isExpanded}
|
|
124
|
+
>
|
|
125
|
+
<span className="flex items-center gap-2">
|
|
126
|
+
<Icon
|
|
127
|
+
name="chevron-right"
|
|
128
|
+
size={12}
|
|
129
|
+
className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
|
130
|
+
/>
|
|
131
|
+
{route.label}
|
|
132
|
+
</span>
|
|
133
|
+
<span className="kol-mono-14 text-subtle">({route.children.length})</span>
|
|
134
|
+
</button>
|
|
135
|
+
) : (
|
|
136
|
+
<Link
|
|
137
|
+
to={getSectionRootPath(route, basePath)}
|
|
138
|
+
className={headerClass}
|
|
139
|
+
onClick={onNavigate}
|
|
140
|
+
>
|
|
141
|
+
{/* aligned with the expandable rows' label column — the
|
|
142
|
+
* chevron's width, kept as space so the two kinds of row
|
|
143
|
+
* share a left edge */}
|
|
144
|
+
<span className="flex items-center gap-2">
|
|
145
|
+
<span aria-hidden="true" style={{ width: 12 }} />
|
|
146
|
+
{route.label}
|
|
147
|
+
</span>
|
|
148
|
+
</Link>
|
|
149
|
+
)}
|
|
121
150
|
|
|
122
151
|
{isExpanded && route.children?.length > 0 && (
|
|
123
152
|
<div className="shell-nav-items">
|
package/src/tags/TagGraph.jsx
CHANGED
|
@@ -119,18 +119,18 @@ const TagGraph = ({ docs, activeTag, onTagClick, allDocs, tagHref = defaultTagHr
|
|
|
119
119
|
// Node circles
|
|
120
120
|
node.append('circle')
|
|
121
121
|
.attr('r', d => Math.max(8, Math.min(16, 6 + d.count * 2)))
|
|
122
|
+
/* Palette tokens, not copies. This held eight hardcoded hexes that were
|
|
123
|
+
* transcriptions of --kol-palette-* (#3740D3 IS palette-blue) — correct
|
|
124
|
+
* the day they were typed and frozen ever since, so a retuned palette
|
|
125
|
+
* would move every chip and leave the graph behind. The chip rules in
|
|
126
|
+
* kol-components-molecules.css resolve the variables; the graph now
|
|
127
|
+
* reads the same ones. `dark` has no palette entry — it is a surface
|
|
128
|
+
* role — so it falls back rather than emitting an invalid var(). */
|
|
122
129
|
.attr('fill', d => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
yellow: '#ffe32e',
|
|
128
|
-
red: '#ce4646',
|
|
129
|
-
orange: '#db8000',
|
|
130
|
-
purple: '#9437FF',
|
|
131
|
-
dark: '#3a3a40'
|
|
132
|
-
}
|
|
133
|
-
return colorMap[getTagColor(d.id)] || '#49a0a2'
|
|
130
|
+
const key = getTagColor(d.id)
|
|
131
|
+
return key === 'dark'
|
|
132
|
+
? 'var(--kol-surface-on-primary)'
|
|
133
|
+
: `var(--kol-palette-${key}, var(--kol-palette-teal))`
|
|
134
134
|
})
|
|
135
135
|
.attr('opacity', d => activeTag && d.id !== activeTag ? 0.4 : 0.9)
|
|
136
136
|
.attr('stroke', d => d.id === activeTag ? 'var(--kol-accent-primary)' : 'none')
|
|
@@ -34,7 +34,9 @@ const TagModeOverlay = () => {
|
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<article className="docs-article">
|
|
37
|
-
|
|
37
|
+
{/* was max-w-[864px] — an improvised value in the ~900–1200 band the
|
|
38
|
+
* panel token exists to settle (kol-theme "Content widths"). */}
|
|
39
|
+
<div className="mx-auto max-w-[var(--kol-content-panel)]">
|
|
38
40
|
<div className="flex items-center justify-start gap-1 mb-3">
|
|
39
41
|
{hasFilters && (
|
|
40
42
|
<Tooltip label={viewMode === 'graph' ? 'List view' : 'Graph view'}>
|