@refrakt-md/lumina 0.5.1 → 0.6.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/index.css CHANGED
@@ -17,6 +17,7 @@
17
17
  @import './styles/layouts/default.css';
18
18
  @import './styles/layouts/docs.css';
19
19
  @import './styles/layouts/blog.css';
20
+ @import './styles/layouts/on-this-page.css';
20
21
 
21
22
  /* Runes */
22
23
  @import './styles/runes/accordion.css';
package/manifest.json CHANGED
@@ -9,5 +9,17 @@
9
9
  "attribute": "data-theme",
10
10
  "values": { "dark": "dark", "light": "light" },
11
11
  "systemPreference": true
12
+ },
13
+ "layouts": {
14
+ "default": {
15
+ "regions": ["header", "footer"]
16
+ },
17
+ "docs": {
18
+ "regions": ["header", "nav", "sidebar", "footer"],
19
+ "requiredRegions": ["nav"]
20
+ },
21
+ "blog": {
22
+ "regions": ["header", "sidebar", "footer"]
23
+ }
12
24
  }
13
25
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/lumina",
3
3
  "description": "Lumina theme for refrakt.md — design tokens, CSS, identity transform, and framework adapters",
4
- "version": "0.5.1",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -46,10 +46,10 @@
46
46
  "build": "tsc"
47
47
  },
48
48
  "dependencies": {
49
- "@refrakt-md/theme-base": "0.5.1",
50
- "@refrakt-md/transform": "0.5.1",
51
- "@refrakt-md/types": "0.5.1",
52
- "@refrakt-md/svelte": "0.5.1"
49
+ "@refrakt-md/theme-base": "0.6.0",
50
+ "@refrakt-md/transform": "0.6.0",
51
+ "@refrakt-md/types": "0.6.0",
52
+ "@refrakt-md/svelte": "0.6.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "svelte": "^5.0.0",
@@ -88,6 +88,41 @@
88
88
  padding: 0 2.5rem;
89
89
  --rf-content-padding: 2.5rem;
90
90
  }
91
+ .rf-docs-content__inner--has-toc {
92
+ display: flex;
93
+ gap: 0;
94
+ max-width: calc(60rem + 220px);
95
+ }
96
+ .rf-docs-content__inner--has-toc .rf-docs-content__body {
97
+ flex: 1;
98
+ min-width: 0;
99
+ }
100
+
101
+ /* ---- "On this page" sidebar ---- */
102
+ .rf-docs-toc {
103
+ width: 220px;
104
+ flex-shrink: 0;
105
+ position: sticky;
106
+ top: 5.75rem;
107
+ align-self: flex-start;
108
+ max-height: calc(100vh - 5.75rem);
109
+ overflow-y: auto;
110
+ padding: 0 1.25rem;
111
+ }
112
+ .rf-docs-toc::-webkit-scrollbar {
113
+ width: 0;
114
+ }
115
+
116
+ /* ---- Smooth scroll with fixed-header offset ---- */
117
+ html:has(.rf-docs-header) {
118
+ scroll-behavior: smooth;
119
+ scroll-padding-top: 5rem;
120
+ }
121
+ @media (prefers-reduced-motion: reduce) {
122
+ html:has(.rf-docs-header) {
123
+ scroll-behavior: auto;
124
+ }
125
+ }
91
126
 
92
127
  /* ---- Mobile toolbar (hidden on desktop) ---- */
93
128
  .rf-docs-toolbar {
@@ -130,6 +165,17 @@
130
165
  font-weight: 500;
131
166
  }
132
167
 
168
+ /* ---- Hide TOC on medium screens ---- */
169
+ @media (max-width: 1100px) {
170
+ .rf-docs-toc {
171
+ display: none;
172
+ }
173
+ .rf-docs-content__inner--has-toc {
174
+ display: block;
175
+ max-width: 60rem;
176
+ }
177
+ }
178
+
133
179
  /* ---- Mobile overrides ---- */
134
180
  @media (max-width: 768px) {
135
181
  .rf-docs-header {
@@ -74,11 +74,13 @@
74
74
  padding: 1rem 1.5rem;
75
75
  }
76
76
 
77
+ /* Panel opens via data-open attribute (set by mobile-menu behavior) */
78
+ .rf-mobile-panel[data-open] {
79
+ display: block;
80
+ }
81
+
77
82
  @media (max-width: 768px) {
78
83
  .rf-mobile-menu-btn {
79
84
  display: block;
80
85
  }
81
- .rf-mobile-panel {
82
- display: block;
83
- }
84
86
  }
@@ -0,0 +1,48 @@
1
+ /* On This Page — TOC sidebar navigation */
2
+
3
+ .rf-on-this-page {
4
+ font-size: 0.8rem;
5
+ }
6
+
7
+ .rf-on-this-page__title {
8
+ font-size: 0.7rem;
9
+ font-weight: 600;
10
+ text-transform: uppercase;
11
+ letter-spacing: 0.05em;
12
+ color: var(--rf-color-muted, #64748b);
13
+ margin: 0 0 0.75rem;
14
+ }
15
+
16
+ .rf-on-this-page__list {
17
+ list-style: none;
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ .rf-on-this-page__item {
23
+ border-left: 2px solid transparent;
24
+ }
25
+
26
+ .rf-on-this-page__item[data-level="3"] {
27
+ padding-left: 0.75rem;
28
+ }
29
+
30
+ .rf-on-this-page__item a {
31
+ display: block;
32
+ padding: 0.25rem 0.75rem;
33
+ color: var(--rf-color-muted, #64748b);
34
+ text-decoration: none;
35
+ line-height: 1.4;
36
+ }
37
+
38
+ .rf-on-this-page__item a:hover {
39
+ color: var(--rf-color-text, #1a1a2e);
40
+ }
41
+
42
+ .rf-on-this-page__item[data-active] {
43
+ border-left-color: var(--rf-color-primary, #0ea5e9);
44
+ }
45
+
46
+ .rf-on-this-page__item[data-active] a {
47
+ color: var(--rf-color-primary, #0ea5e9);
48
+ }
@@ -1,17 +1,12 @@
1
1
  /* Symbol */
2
2
  .rf-symbol {
3
- border: 1px solid var(--rf-color-border);
4
- border-radius: var(--rf-radius-lg);
5
3
  margin: 1.5rem 0;
6
- overflow: hidden;
7
4
  }
8
5
  .rf-symbol__header {
9
6
  display: flex;
10
7
  align-items: center;
11
8
  gap: 0.5rem;
12
- padding: 0.75rem 1.25rem;
13
- background: var(--rf-color-surface-hover);
14
- border-bottom: 1px solid var(--rf-color-border);
9
+ padding: 0.5rem 0;
15
10
  flex-wrap: wrap;
16
11
  }
17
12
  .rf-symbol__kind-badge {
@@ -56,7 +51,7 @@
56
51
  color: var(--rf-color-primary);
57
52
  }
58
53
  .rf-symbol__body {
59
- padding: 1.25rem;
54
+ padding: 0;
60
55
  }
61
56
  .rf-symbol__body > header {
62
57
  margin-bottom: 1rem;
@@ -100,6 +95,9 @@
100
95
  border-radius: 0 var(--rf-radius-sm) var(--rf-radius-sm) 0;
101
96
  font-size: 0.9375rem;
102
97
  }
98
+ .rf-symbol__body blockquote::before {
99
+ content: none;
100
+ }
103
101
 
104
102
  /* Deprecated symbol */
105
103
  [data-deprecated] .rf-symbol__body > header h2,
@@ -132,10 +130,7 @@
132
130
  /* SymbolMember */
133
131
  .rf-symbol-member {
134
132
  margin: 1.25rem 0;
135
- padding: 1rem;
136
- border: 1px solid var(--rf-color-border);
137
- border-radius: var(--rf-radius-md);
138
- background: var(--rf-color-surface);
133
+ padding: 0;
139
134
  }
140
135
  .rf-symbol-member h4 {
141
136
  margin: 0 0 0.5rem;
@@ -162,3 +157,6 @@
162
157
  border-radius: 0 var(--rf-radius-sm) var(--rf-radius-sm) 0;
163
158
  font-size: 0.875rem;
164
159
  }
160
+ .rf-symbol-member blockquote::before {
161
+ content: none;
162
+ }
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ interface Heading {
3
+ level: number;
4
+ text: string;
5
+ id: string;
6
+ }
7
+
8
+ interface Props {
9
+ headings: Heading[];
10
+ }
11
+
12
+ let { headings }: Props = $props();
13
+
14
+ // Filter to h2 and h3 only
15
+ const filtered = $derived(headings.filter(h => h.level >= 2 && h.level <= 3));
16
+ </script>
17
+
18
+ <nav class="rf-on-this-page" data-scrollspy>
19
+ <p class="rf-on-this-page__title">On this page</p>
20
+ <ul class="rf-on-this-page__list">
21
+ {#each filtered as heading}
22
+ <li class="rf-on-this-page__item" data-level={heading.level}>
23
+ <a href="#{heading.id}">{heading.text}</a>
24
+ </li>
25
+ {/each}
26
+ </ul>
27
+ </nav>
28
+
29
+ <style>
30
+ .rf-on-this-page {
31
+ font-size: 0.8rem;
32
+ }
33
+
34
+ .rf-on-this-page__title {
35
+ font-size: 0.7rem;
36
+ font-weight: 600;
37
+ text-transform: uppercase;
38
+ letter-spacing: 0.05em;
39
+ color: var(--rf-color-muted, #64748b);
40
+ margin: 0 0 0.75rem;
41
+ }
42
+
43
+ .rf-on-this-page__list {
44
+ list-style: none;
45
+ margin: 0;
46
+ padding: 0;
47
+ }
48
+
49
+ .rf-on-this-page__item {
50
+ border-left: 2px solid transparent;
51
+ }
52
+
53
+ .rf-on-this-page__item[data-level="3"] {
54
+ padding-left: 0.75rem;
55
+ }
56
+
57
+ .rf-on-this-page__item a {
58
+ display: block;
59
+ padding: 0.25rem 0.75rem;
60
+ color: var(--rf-color-muted, #64748b);
61
+ text-decoration: none;
62
+ line-height: 1.4;
63
+ }
64
+
65
+ .rf-on-this-page__item a:hover {
66
+ color: var(--rf-color-text, #1a1a2e);
67
+ }
68
+
69
+ .rf-on-this-page__item[data-active] {
70
+ border-left-color: var(--rf-color-primary, #0ea5e9);
71
+ }
72
+
73
+ .rf-on-this-page__item[data-active] a {
74
+ color: var(--rf-color-primary, #0ea5e9);
75
+ }
76
+ </style>
package/svelte/index.ts CHANGED
@@ -1,18 +1,35 @@
1
1
  import type { SvelteTheme } from '@refrakt-md/svelte';
2
- import manifest from './manifest.json';
2
+ import adapterManifest from './manifest.json';
3
3
  import { registry } from './registry.js';
4
4
  import { elements } from './elements.js';
5
- import DocsLayout from './layouts/DocsLayout.svelte';
6
- import DefaultLayout from './layouts/DefaultLayout.svelte';
7
- import BlogLayout from './layouts/BlogLayout.svelte';
5
+ import { defaultLayout, docsLayout, blogArticleLayout } from '@refrakt-md/theme-base';
8
6
 
9
- /** Re-export the raw manifest for server-side use (no Svelte imports) */
10
- export { default as manifest } from './manifest.json';
7
+ // Layout region metadata from the base theme manifest (packages/lumina/manifest.json).
8
+ // Merged with adapter manifest component paths to produce full LayoutDefinitions.
9
+ const layoutRegions: Record<string, { regions: string[]; requiredRegions?: string[] }> = {
10
+ default: { regions: ['header', 'footer'] },
11
+ docs: { regions: ['header', 'nav', 'sidebar', 'footer'], requiredRegions: ['nav'] },
12
+ 'blog-article': { regions: ['header', 'sidebar', 'footer'] },
13
+ };
14
+
15
+ const layouts: Record<string, any> = {};
16
+ for (const [name, adapter] of Object.entries(adapterManifest.layouts)) {
17
+ layouts[name] = { ...layoutRegions[name], ...adapter };
18
+ }
19
+
20
+ const manifest = { ...adapterManifest, layouts };
21
+
22
+ /** Re-export the merged manifest for server-side use (no Svelte imports) */
23
+ export { manifest };
11
24
 
12
25
  /** The structured theme object consumed by ThemeShell */
13
26
  export const theme: SvelteTheme = {
14
27
  manifest: manifest as any,
15
- layouts: { default: DefaultLayout, docs: DocsLayout, blog: BlogLayout },
28
+ layouts: {
29
+ default: defaultLayout,
30
+ docs: docsLayout,
31
+ 'blog-article': blogArticleLayout,
32
+ },
16
33
  components: registry,
17
34
  elements,
18
35
  };
@@ -1,17 +1,24 @@
1
1
  <script lang="ts">
2
2
  import { Renderer } from '@refrakt-md/svelte';
3
+ import OnThisPage from '../components/OnThisPage.svelte';
3
4
 
4
- let { title, regions, renderable, url, pages }: {
5
+ let { title, regions, renderable, url, pages, headings, frontmatter }: {
5
6
  title: string;
6
7
  description: string;
7
8
  regions: Record<string, { name: string; mode: string; content: any[] }>;
8
9
  renderable: any;
9
10
  url: string;
10
11
  pages: Array<{ url: string; title: string; draft: boolean }>;
12
+ headings?: Array<{ level: number; text: string; id: string }>;
13
+ frontmatter?: Record<string, unknown>;
11
14
  } = $props();
12
15
 
13
16
  const hasNav = $derived(!!regions.nav);
14
17
 
18
+ // Show TOC when 2+ qualifying headings (h2/h3) and not opted out via frontmatter
19
+ const tocHeadings = $derived((headings ?? []).filter(h => h.level >= 2 && h.level <= 3));
20
+ const showToc = $derived(tocHeadings.length >= 2 && frontmatter?.toc !== false);
21
+
15
22
  // Mobile panel state
16
23
  let menuOpen = $state(false);
17
24
  let navOpen = $state(false);
@@ -149,7 +156,14 @@
149
156
  {/if}
150
157
 
151
158
  <main class="rf-docs-content" class:rf-docs-content--has-nav={hasNav}>
152
- <div class="rf-docs-content__inner">
153
- <Renderer node={renderable} />
159
+ <div class="rf-docs-content__inner" class:rf-docs-content__inner--has-toc={showToc}>
160
+ <div class="rf-docs-content__body">
161
+ <Renderer node={renderable} />
162
+ </div>
163
+ {#if showToc}
164
+ <aside class="rf-docs-toc">
165
+ <OnThisPage headings={tocHeadings} />
166
+ </aside>
167
+ {/if}
154
168
  </div>
155
169
  </main>
@@ -6,28 +6,11 @@
6
6
  "designTokens": "tokens.css",
7
7
 
8
8
  "layouts": {
9
- "default": {
10
- "component": "layouts/DefaultLayout.svelte",
11
- "regions": ["header", "footer"]
12
- },
13
- "docs": {
14
- "component": "layouts/DocsLayout.svelte",
15
- "regions": ["header", "nav", "sidebar", "footer"],
16
- "requiredRegions": ["nav"]
17
- },
18
- "blog": {
19
- "component": "layouts/BlogLayout.svelte",
20
- "regions": ["header", "sidebar", "footer"]
21
- }
9
+ "default": { "component": "layouts/DefaultLayout.svelte" },
10
+ "docs": { "component": "layouts/DocsLayout.svelte" },
11
+ "blog": { "component": "layouts/BlogLayout.svelte" }
22
12
  },
23
13
 
24
- "routeRules": [
25
- { "pattern": "docs/**", "layout": "docs" },
26
- { "pattern": "blog", "layout": "blog" },
27
- { "pattern": "blog/**", "layout": "blog" },
28
- { "pattern": "**", "layout": "default" }
29
- ],
30
-
31
14
  "components": {
32
15
  "Hint": { "component": "components/Hint.svelte" },
33
16
  "CallToAction": { "component": "components/CallToAction.svelte" },