@beatzball/create-litro 0.1.4 → 0.2.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/CHANGELOG.md +15 -0
- package/dist/recipes/starlight/recipe.config.d.ts +4 -0
- package/dist/recipes/starlight/recipe.config.d.ts.map +1 -0
- package/dist/recipes/starlight/recipe.config.js +9 -0
- package/dist/recipes/starlight/recipe.config.js.map +1 -0
- package/dist/recipes/starlight/recipe.config.ts +11 -0
- package/dist/recipes/starlight/template/_data/metadata.js +10 -0
- package/dist/recipes/starlight/template/app.ts +18 -0
- package/dist/recipes/starlight/template/content/blog/.11tydata.json +1 -0
- package/dist/recipes/starlight/template/content/blog/release-notes.md +44 -0
- package/dist/recipes/starlight/template/content/blog/welcome.md +44 -0
- package/dist/recipes/starlight/template/content/docs/.11tydata.json +1 -0
- package/dist/recipes/starlight/template/content/docs/configuration.md +77 -0
- package/dist/recipes/starlight/template/content/docs/getting-started.md +53 -0
- package/dist/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
- package/dist/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
- package/dist/recipes/starlight/template/content/docs/installation.md +54 -0
- package/dist/recipes/starlight/template/litro.recipe.json +7 -0
- package/dist/recipes/starlight/template/nitro.config.ts +57 -0
- package/dist/recipes/starlight/template/package.json +26 -0
- package/dist/recipes/starlight/template/pages/blog/[slug].ts +125 -0
- package/dist/recipes/starlight/template/pages/blog/index.ts +114 -0
- package/dist/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
- package/dist/recipes/starlight/template/pages/docs/[slug].ts +147 -0
- package/dist/recipes/starlight/template/pages/index.ts +135 -0
- package/dist/recipes/starlight/template/public/styles/starlight.css +215 -0
- package/dist/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
- package/dist/recipes/starlight/template/server/routes/[...].ts +57 -0
- package/dist/recipes/starlight/template/server/starlight.config.js +29 -0
- package/dist/recipes/starlight/template/src/components/sl-aside.ts +91 -0
- package/dist/recipes/starlight/template/src/components/sl-badge.ts +76 -0
- package/dist/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
- package/dist/recipes/starlight/template/src/components/sl-card.ts +91 -0
- package/dist/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
- package/dist/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
- package/dist/recipes/starlight/template/src/components/starlight-header.ts +152 -0
- package/dist/recipes/starlight/template/src/components/starlight-page.ts +168 -0
- package/dist/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
- package/dist/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
- package/dist/recipes/starlight/template/src/date-utils.ts +20 -0
- package/dist/recipes/starlight/template/src/extract-headings.ts +68 -0
- package/dist/recipes/starlight/template/src/route-meta.ts +16 -0
- package/dist/recipes/starlight/template/tsconfig.json +14 -0
- package/dist/recipes/starlight/template/vite.config.ts +19 -0
- package/dist/src/scaffold.test.js +134 -0
- package/dist/src/scaffold.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/recipes/starlight/recipe.config.ts +11 -0
- package/recipes/starlight/template/_data/metadata.js +10 -0
- package/recipes/starlight/template/app.ts +18 -0
- package/recipes/starlight/template/content/blog/.11tydata.json +1 -0
- package/recipes/starlight/template/content/blog/release-notes.md +44 -0
- package/recipes/starlight/template/content/blog/welcome.md +44 -0
- package/recipes/starlight/template/content/docs/.11tydata.json +1 -0
- package/recipes/starlight/template/content/docs/configuration.md +77 -0
- package/recipes/starlight/template/content/docs/getting-started.md +53 -0
- package/recipes/starlight/template/content/docs/guides-deploying.md +79 -0
- package/recipes/starlight/template/content/docs/guides-first-page.md +64 -0
- package/recipes/starlight/template/content/docs/installation.md +54 -0
- package/recipes/starlight/template/litro.recipe.json +7 -0
- package/recipes/starlight/template/nitro.config.ts +57 -0
- package/recipes/starlight/template/package.json +26 -0
- package/recipes/starlight/template/pages/blog/[slug].ts +125 -0
- package/recipes/starlight/template/pages/blog/index.ts +114 -0
- package/recipes/starlight/template/pages/blog/tags/[tag].ts +110 -0
- package/recipes/starlight/template/pages/docs/[slug].ts +147 -0
- package/recipes/starlight/template/pages/index.ts +135 -0
- package/recipes/starlight/template/public/styles/starlight.css +215 -0
- package/recipes/starlight/template/server/middleware/vite-dev.ts +29 -0
- package/recipes/starlight/template/server/routes/[...].ts +57 -0
- package/recipes/starlight/template/server/starlight.config.js +29 -0
- package/recipes/starlight/template/src/components/sl-aside.ts +91 -0
- package/recipes/starlight/template/src/components/sl-badge.ts +76 -0
- package/recipes/starlight/template/src/components/sl-card-grid.ts +34 -0
- package/recipes/starlight/template/src/components/sl-card.ts +91 -0
- package/recipes/starlight/template/src/components/sl-tab-item.ts +35 -0
- package/recipes/starlight/template/src/components/sl-tabs.ts +108 -0
- package/recipes/starlight/template/src/components/starlight-header.ts +152 -0
- package/recipes/starlight/template/src/components/starlight-page.ts +168 -0
- package/recipes/starlight/template/src/components/starlight-sidebar.ts +125 -0
- package/recipes/starlight/template/src/components/starlight-toc.ts +133 -0
- package/recipes/starlight/template/src/date-utils.ts +20 -0
- package/recipes/starlight/template/src/extract-headings.ts +68 -0
- package/recipes/starlight/template/src/route-meta.ts +16 -0
- package/recipes/starlight/template/tsconfig.json +14 -0
- package/recipes/starlight/template/vite.config.ts +19 -0
- package/src/scaffold.test.ts +148 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
|
|
4
|
+
type BadgeVariant = 'note' | 'tip' | 'caution' | 'danger' | 'default';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* <sl-badge variant="tip" text="New">
|
|
8
|
+
* Inline color-coded chip. Use `text` attribute or slot content.
|
|
9
|
+
* </sl-badge>
|
|
10
|
+
*/
|
|
11
|
+
@customElement('sl-badge')
|
|
12
|
+
export class SlBadge extends LitElement {
|
|
13
|
+
static override properties = {
|
|
14
|
+
variant: { type: String },
|
|
15
|
+
text: { type: String },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
static override styles = css`
|
|
19
|
+
:host {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.badge {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
padding: 0.15em 0.55em;
|
|
27
|
+
border-radius: 9999px;
|
|
28
|
+
font-size: var(--sl-text-xs, 0.75rem);
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
line-height: 1.5;
|
|
31
|
+
text-transform: uppercase;
|
|
32
|
+
letter-spacing: 0.04em;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.badge.note {
|
|
36
|
+
background-color: color-mix(in srgb, var(--sl-color-note, #1d4ed8) 15%, transparent);
|
|
37
|
+
color: var(--sl-color-note, #1d4ed8);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.badge.tip {
|
|
41
|
+
background-color: color-mix(in srgb, var(--sl-color-tip, #15803d) 15%, transparent);
|
|
42
|
+
color: var(--sl-color-tip, #15803d);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.badge.caution {
|
|
46
|
+
background-color: color-mix(in srgb, var(--sl-color-caution, #b45309) 15%, transparent);
|
|
47
|
+
color: var(--sl-color-caution, #b45309);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.badge.danger {
|
|
51
|
+
background-color: color-mix(in srgb, var(--sl-color-danger, #b91c1c) 15%, transparent);
|
|
52
|
+
color: var(--sl-color-danger, #b91c1c);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.badge.default {
|
|
56
|
+
background-color: var(--sl-color-accent-low, #ede9fe);
|
|
57
|
+
color: var(--sl-color-accent-high, #5b21b6);
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
variant: BadgeVariant = 'default';
|
|
62
|
+
text = '';
|
|
63
|
+
|
|
64
|
+
override render() {
|
|
65
|
+
const cls = ['note', 'tip', 'caution', 'danger'].includes(this.variant)
|
|
66
|
+
? this.variant
|
|
67
|
+
: 'default';
|
|
68
|
+
return html`
|
|
69
|
+
<span class="badge ${cls}">
|
|
70
|
+
${this.text || html`<slot></slot>`}
|
|
71
|
+
</span>
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default SlBadge;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <sl-card-grid>
|
|
6
|
+
* Responsive auto-fit grid for <sl-card> elements.
|
|
7
|
+
* Single slot — place <sl-card> elements directly inside.
|
|
8
|
+
* </sl-card-grid>
|
|
9
|
+
*/
|
|
10
|
+
@customElement('sl-card-grid')
|
|
11
|
+
export class SlCardGrid extends LitElement {
|
|
12
|
+
static override styles = css`
|
|
13
|
+
:host {
|
|
14
|
+
display: block;
|
|
15
|
+
counter-reset: card;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.grid {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
|
21
|
+
gap: 1.25rem;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
override render() {
|
|
26
|
+
return html`
|
|
27
|
+
<div class="grid">
|
|
28
|
+
<slot></slot>
|
|
29
|
+
</div>
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default SlCardGrid;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <sl-card title="Feature" description="Short desc" href="/docs/feature">
|
|
6
|
+
* Renders as an <a> when `href` is set, otherwise a <div>.
|
|
7
|
+
* Rotating accent color per nth-card via CSS counter.
|
|
8
|
+
* </sl-card>
|
|
9
|
+
*/
|
|
10
|
+
@customElement('sl-card')
|
|
11
|
+
export class SlCard extends LitElement {
|
|
12
|
+
static override properties = {
|
|
13
|
+
title: { type: String },
|
|
14
|
+
description: { type: String },
|
|
15
|
+
icon: { type: String },
|
|
16
|
+
href: { type: String },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static override styles = css`
|
|
20
|
+
:host {
|
|
21
|
+
display: block;
|
|
22
|
+
counter-increment: card;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.card {
|
|
26
|
+
display: block;
|
|
27
|
+
padding: 1.25rem 1.5rem;
|
|
28
|
+
border: 1px solid var(--sl-color-border, #e8e8e8);
|
|
29
|
+
border-radius: var(--sl-border-radius, 0.375rem);
|
|
30
|
+
background-color: var(--sl-color-bg, #fff);
|
|
31
|
+
border-top: 4px solid;
|
|
32
|
+
text-decoration: none;
|
|
33
|
+
color: inherit;
|
|
34
|
+
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Rotate accent colors using counter — cycles through 4 variants */
|
|
38
|
+
:host(:nth-child(4n+1)) .card { border-top-color: var(--sl-color-accent, #7c3aed); }
|
|
39
|
+
:host(:nth-child(4n+2)) .card { border-top-color: var(--sl-color-note, #1d4ed8); }
|
|
40
|
+
:host(:nth-child(4n+3)) .card { border-top-color: var(--sl-color-tip, #15803d); }
|
|
41
|
+
:host(:nth-child(4n+0)) .card { border-top-color: var(--sl-color-caution, #b45309); }
|
|
42
|
+
|
|
43
|
+
a.card:hover {
|
|
44
|
+
box-shadow: var(--sl-shadow-md, 0 4px 16px rgba(0,0,0,.12));
|
|
45
|
+
transform: translateY(-2px);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.card-icon {
|
|
49
|
+
font-size: 1.75rem;
|
|
50
|
+
margin-bottom: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.card-title {
|
|
54
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
color: var(--sl-color-text, #23262f);
|
|
57
|
+
margin: 0 0 0.4rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.card-desc {
|
|
61
|
+
font-size: var(--sl-text-sm, 0.875rem);
|
|
62
|
+
color: var(--sl-color-gray-4, #757575);
|
|
63
|
+
margin: 0;
|
|
64
|
+
line-height: 1.6;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.card-slot {
|
|
68
|
+
margin-top: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
title = '';
|
|
73
|
+
description = '';
|
|
74
|
+
icon = '';
|
|
75
|
+
href = '';
|
|
76
|
+
|
|
77
|
+
override render() {
|
|
78
|
+
const inner = html`
|
|
79
|
+
${this.icon ? html`<div class="card-icon">${this.icon}</div>` : ''}
|
|
80
|
+
<p class="card-title">${this.title}</p>
|
|
81
|
+
${this.description ? html`<p class="card-desc">${this.description}</p>` : ''}
|
|
82
|
+
<div class="card-slot"><slot></slot></div>
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
return this.href
|
|
86
|
+
? html`<a class="card" href="${this.href}">${inner}</a>`
|
|
87
|
+
: html`<div class="card">${inner}</div>`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default SlCard;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <sl-tab-item label="Tab Label">
|
|
6
|
+
* A single tab panel managed by a parent <sl-tabs> element.
|
|
7
|
+
* Hidden automatically when not selected.
|
|
8
|
+
* </sl-tab-item>
|
|
9
|
+
*/
|
|
10
|
+
@customElement('sl-tab-item')
|
|
11
|
+
export class SlTabItem extends LitElement {
|
|
12
|
+
static override properties = {
|
|
13
|
+
label: { type: String },
|
|
14
|
+
selected: { type: Boolean, reflect: true },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
static override styles = css`
|
|
18
|
+
:host {
|
|
19
|
+
display: block;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:host(:not([selected])) {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
label = '';
|
|
28
|
+
selected = false;
|
|
29
|
+
|
|
30
|
+
override render() {
|
|
31
|
+
return html`<slot></slot>`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default SlTabItem;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
import type { SlTabItem } from './sl-tab-item.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* <sl-tabs>
|
|
7
|
+
* <sl-tab-item label="First">Content A</sl-tab-item>
|
|
8
|
+
* <sl-tab-item label="Second">Content B</sl-tab-item>
|
|
9
|
+
* </sl-tabs>
|
|
10
|
+
*
|
|
11
|
+
* Reads slotted <sl-tab-item> elements via the slotchange event.
|
|
12
|
+
* Renders a tab bar in Shadow DOM; clicking selects the tab.
|
|
13
|
+
*/
|
|
14
|
+
@customElement('sl-tabs')
|
|
15
|
+
export class SlTabs extends LitElement {
|
|
16
|
+
static override properties = {
|
|
17
|
+
_labels: { type: Array, state: true },
|
|
18
|
+
_selectedIndex: { type: Number, state: true },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static override styles = css`
|
|
22
|
+
:host {
|
|
23
|
+
display: block;
|
|
24
|
+
margin: 1.5rem 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.tab-bar {
|
|
28
|
+
display: flex;
|
|
29
|
+
gap: 0;
|
|
30
|
+
border-bottom: 2px solid var(--sl-color-border, #e8e8e8);
|
|
31
|
+
overflow-x: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.tab-btn {
|
|
35
|
+
appearance: none;
|
|
36
|
+
background: none;
|
|
37
|
+
border: none;
|
|
38
|
+
padding: 0.5rem 1rem;
|
|
39
|
+
font: inherit;
|
|
40
|
+
font-size: var(--sl-text-sm, 0.875rem);
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
color: var(--sl-color-gray-4, #757575);
|
|
44
|
+
border-bottom: 2px solid transparent;
|
|
45
|
+
margin-bottom: -2px;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
transition: color 0.15s, border-color 0.15s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tab-btn:hover {
|
|
51
|
+
color: var(--sl-color-text, #23262f);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tab-btn[aria-selected='true'] {
|
|
55
|
+
color: var(--sl-color-accent, #7c3aed);
|
|
56
|
+
border-bottom-color: var(--sl-color-accent, #7c3aed);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.tab-content {
|
|
60
|
+
padding-top: 1rem;
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
_labels: string[] = [];
|
|
65
|
+
_selectedIndex = 0;
|
|
66
|
+
|
|
67
|
+
private _items(): SlTabItem[] {
|
|
68
|
+
const slot = this.shadowRoot?.querySelector('slot');
|
|
69
|
+
if (!slot) return [];
|
|
70
|
+
return slot.assignedElements().filter(
|
|
71
|
+
(el): el is SlTabItem => el.tagName.toLowerCase() === 'sl-tab-item',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private _onSlotChange() {
|
|
76
|
+
const items = this._items();
|
|
77
|
+
this._labels = items.map((item) => item.label || `Tab ${items.indexOf(item) + 1}`);
|
|
78
|
+
this._selectIndex(this._selectedIndex < items.length ? this._selectedIndex : 0, items);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _selectIndex(index: number, items?: SlTabItem[]) {
|
|
82
|
+
const all = items ?? this._items();
|
|
83
|
+
this._selectedIndex = index;
|
|
84
|
+
all.forEach((item, i) => {
|
|
85
|
+
item.selected = i === index;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override render() {
|
|
90
|
+
return html`
|
|
91
|
+
<div class="tab-bar" role="tablist">
|
|
92
|
+
${this._labels.map((label, i) => html`
|
|
93
|
+
<button
|
|
94
|
+
class="tab-btn"
|
|
95
|
+
role="tab"
|
|
96
|
+
aria-selected="${this._selectedIndex === i ? 'true' : 'false'}"
|
|
97
|
+
@click=${() => this._selectIndex(i)}
|
|
98
|
+
>${label}</button>
|
|
99
|
+
`)}
|
|
100
|
+
</div>
|
|
101
|
+
<div class="tab-content">
|
|
102
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default SlTabs;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
|
|
4
|
+
export interface NavItem {
|
|
5
|
+
label: string;
|
|
6
|
+
href: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* <starlight-header siteTitle="My Docs" .nav=${nav} currentPath="/docs/getting-started">
|
|
11
|
+
* Top navigation bar with site title, nav links, and dark/light theme toggle.
|
|
12
|
+
*/
|
|
13
|
+
@customElement('starlight-header')
|
|
14
|
+
export class StarlightHeader extends LitElement {
|
|
15
|
+
static override properties = {
|
|
16
|
+
siteTitle: { type: String },
|
|
17
|
+
nav: { type: Array },
|
|
18
|
+
currentPath: { type: String },
|
|
19
|
+
_theme: { type: String, state: true },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static override styles = css`
|
|
23
|
+
:host {
|
|
24
|
+
display: block;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
header {
|
|
28
|
+
position: sticky;
|
|
29
|
+
top: 0;
|
|
30
|
+
z-index: 100;
|
|
31
|
+
height: var(--sl-nav-height, 3.5rem);
|
|
32
|
+
background-color: var(--sl-color-bg-nav, #fff);
|
|
33
|
+
border-bottom: 1px solid var(--sl-color-border, #e8e8e8);
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
padding: 0 var(--sl-content-pad-x, 1.5rem);
|
|
37
|
+
gap: 1.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.site-title {
|
|
41
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
color: var(--sl-color-text, #23262f);
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.site-title:hover { opacity: 0.85; }
|
|
49
|
+
|
|
50
|
+
nav {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 0.25rem;
|
|
54
|
+
flex: 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
nav a {
|
|
58
|
+
padding: 0.35rem 0.75rem;
|
|
59
|
+
font-size: var(--sl-text-sm, 0.875rem);
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
color: var(--sl-color-gray-5, #4b4b4b);
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
border-radius: var(--sl-border-radius, 0.375rem);
|
|
64
|
+
transition: color 0.15s, background-color 0.15s;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
nav a:hover {
|
|
68
|
+
color: var(--sl-color-text, #23262f);
|
|
69
|
+
background-color: var(--sl-color-gray-2, #e8e8e8);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
nav a[aria-current='page'] {
|
|
73
|
+
color: var(--sl-color-accent, #7c3aed);
|
|
74
|
+
background-color: var(--sl-color-accent-low, #ede9fe);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.theme-toggle {
|
|
78
|
+
margin-left: auto;
|
|
79
|
+
appearance: none;
|
|
80
|
+
background: none;
|
|
81
|
+
border: 1px solid var(--sl-color-border, #e8e8e8);
|
|
82
|
+
border-radius: var(--sl-border-radius, 0.375rem);
|
|
83
|
+
width: 2.25rem;
|
|
84
|
+
height: 2.25rem;
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
font-size: 1rem;
|
|
90
|
+
color: var(--sl-color-text, #23262f);
|
|
91
|
+
transition: background-color 0.15s;
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.theme-toggle:hover {
|
|
96
|
+
background-color: var(--sl-color-gray-2, #e8e8e8);
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
siteTitle = '';
|
|
101
|
+
nav: NavItem[] = [];
|
|
102
|
+
currentPath = '';
|
|
103
|
+
|
|
104
|
+
_theme = 'light';
|
|
105
|
+
|
|
106
|
+
override firstUpdated() {
|
|
107
|
+
const stored = (typeof localStorage !== 'undefined'
|
|
108
|
+
? localStorage.getItem('sl-theme')
|
|
109
|
+
: null) ?? 'light';
|
|
110
|
+
this._theme = stored;
|
|
111
|
+
if (typeof document !== 'undefined') {
|
|
112
|
+
document.documentElement.setAttribute('data-theme', stored);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private _toggleTheme() {
|
|
117
|
+
const next = this._theme === 'light' ? 'dark' : 'light';
|
|
118
|
+
this._theme = next;
|
|
119
|
+
if (typeof localStorage !== 'undefined') {
|
|
120
|
+
localStorage.setItem('sl-theme', next);
|
|
121
|
+
}
|
|
122
|
+
if (typeof document !== 'undefined') {
|
|
123
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override render() {
|
|
128
|
+
const icon = this._theme === 'dark' ? '☀️' : '🌙';
|
|
129
|
+
const label = this._theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
|
130
|
+
|
|
131
|
+
return html`
|
|
132
|
+
<header>
|
|
133
|
+
<a class="site-title" href="/">${this.siteTitle}</a>
|
|
134
|
+
<nav aria-label="Main navigation">
|
|
135
|
+
${this.nav.map(item => html`
|
|
136
|
+
<a
|
|
137
|
+
href="${item.href}"
|
|
138
|
+
aria-current="${this.currentPath.startsWith(item.href) ? 'page' : 'false'}"
|
|
139
|
+
>${item.label}</a>
|
|
140
|
+
`)}
|
|
141
|
+
</nav>
|
|
142
|
+
<button
|
|
143
|
+
class="theme-toggle"
|
|
144
|
+
aria-label="${label}"
|
|
145
|
+
@click="${this._toggleTheme}"
|
|
146
|
+
>${icon}</button>
|
|
147
|
+
</header>
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default StarlightHeader;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
import type { NavItem } from './starlight-header.js';
|
|
4
|
+
import type { SidebarGroup } from './starlight-sidebar.js';
|
|
5
|
+
import type { TocEntry } from '../extract-headings.js';
|
|
6
|
+
|
|
7
|
+
// Side-effect imports — registers child custom elements
|
|
8
|
+
import './starlight-header.js';
|
|
9
|
+
import './starlight-sidebar.js';
|
|
10
|
+
import './starlight-toc.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* <starlight-page
|
|
14
|
+
* siteTitle="My Docs"
|
|
15
|
+
* pageTitle="Getting Started"
|
|
16
|
+
* .nav=${nav}
|
|
17
|
+
* .sidebar=${sidebar}
|
|
18
|
+
* .toc=${toc}
|
|
19
|
+
* currentSlug="getting-started"
|
|
20
|
+
* currentPath="/docs/getting-started"
|
|
21
|
+
* >
|
|
22
|
+
* <div slot="content">…rendered HTML…</div>
|
|
23
|
+
* </starlight-page>
|
|
24
|
+
*
|
|
25
|
+
* Three-column grid layout: sidebar | content | TOC.
|
|
26
|
+
* Responsive: single column below 768px, sidebar/TOC collapsed.
|
|
27
|
+
*/
|
|
28
|
+
@customElement('starlight-page')
|
|
29
|
+
export class StarlightPage extends LitElement {
|
|
30
|
+
static override properties = {
|
|
31
|
+
siteTitle: { type: String },
|
|
32
|
+
pageTitle: { type: String },
|
|
33
|
+
nav: { type: Array },
|
|
34
|
+
sidebar: { type: Array },
|
|
35
|
+
toc: { type: Array },
|
|
36
|
+
currentSlug: { type: String },
|
|
37
|
+
currentPath: { type: String },
|
|
38
|
+
noSidebar: { type: Boolean },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
static override styles = css`
|
|
42
|
+
:host {
|
|
43
|
+
display: block;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.page-wrap {
|
|
47
|
+
min-height: 100vh;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.body {
|
|
53
|
+
display: grid;
|
|
54
|
+
grid-template-columns: var(--sl-sidebar-width, 16rem) 1fr var(--sl-toc-width, 14rem);
|
|
55
|
+
grid-template-areas: 'sidebar content toc';
|
|
56
|
+
flex: 1;
|
|
57
|
+
max-width: 90rem;
|
|
58
|
+
margin: 0 auto;
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.body.no-sidebar {
|
|
63
|
+
grid-template-columns: 1fr;
|
|
64
|
+
grid-template-areas: 'content';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.sidebar-wrap {
|
|
68
|
+
grid-area: sidebar;
|
|
69
|
+
border-right: 1px solid var(--sl-color-border, #e8e8e8);
|
|
70
|
+
background-color: var(--sl-color-bg-sidebar, #f6f6f6);
|
|
71
|
+
position: sticky;
|
|
72
|
+
top: var(--sl-nav-height, 3.5rem);
|
|
73
|
+
height: calc(100vh - var(--sl-nav-height, 3.5rem));
|
|
74
|
+
overflow-y: auto;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.content-wrap {
|
|
78
|
+
grid-area: content;
|
|
79
|
+
padding: var(--sl-content-pad-y, 2rem) var(--sl-content-pad-x, 1.5rem);
|
|
80
|
+
min-width: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.content-inner {
|
|
84
|
+
max-width: var(--sl-content-width, 48rem);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.toc-wrap {
|
|
88
|
+
grid-area: toc;
|
|
89
|
+
padding: var(--sl-content-pad-y, 2rem) 0 var(--sl-content-pad-y, 2rem) var(--sl-content-pad-x, 1.5rem);
|
|
90
|
+
border-left: 1px solid var(--sl-color-border, #e8e8e8);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.page-title {
|
|
94
|
+
font-size: var(--sl-text-4xl, 2.25rem);
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
color: var(--sl-color-text, #23262f);
|
|
97
|
+
margin: 0 0 1.5rem;
|
|
98
|
+
line-height: 1.15;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Responsive: hide sidebar and TOC on narrow screens */
|
|
102
|
+
@media (max-width: 72rem) {
|
|
103
|
+
.body {
|
|
104
|
+
grid-template-columns: 1fr var(--sl-toc-width, 14rem);
|
|
105
|
+
grid-template-areas: 'content toc';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.sidebar-wrap {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@media (max-width: 48rem) {
|
|
114
|
+
.body {
|
|
115
|
+
grid-template-columns: 1fr;
|
|
116
|
+
grid-template-areas: 'content';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.toc-wrap {
|
|
120
|
+
display: none;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
siteTitle = '';
|
|
126
|
+
pageTitle = '';
|
|
127
|
+
nav: NavItem[] = [];
|
|
128
|
+
sidebar: SidebarGroup[] = [];
|
|
129
|
+
toc: TocEntry[] = [];
|
|
130
|
+
currentSlug = '';
|
|
131
|
+
currentPath = '';
|
|
132
|
+
noSidebar = false;
|
|
133
|
+
|
|
134
|
+
override render() {
|
|
135
|
+
return html`
|
|
136
|
+
<div class="page-wrap">
|
|
137
|
+
<starlight-header
|
|
138
|
+
siteTitle="${this.siteTitle}"
|
|
139
|
+
.nav="${this.nav}"
|
|
140
|
+
currentPath="${this.currentPath}"
|
|
141
|
+
></starlight-header>
|
|
142
|
+
<div class="body${this.noSidebar ? ' no-sidebar' : ''}">
|
|
143
|
+
${!this.noSidebar ? html`
|
|
144
|
+
<aside class="sidebar-wrap">
|
|
145
|
+
<starlight-sidebar
|
|
146
|
+
.groups="${this.sidebar}"
|
|
147
|
+
currentSlug="${this.currentSlug}"
|
|
148
|
+
></starlight-sidebar>
|
|
149
|
+
</aside>
|
|
150
|
+
` : ''}
|
|
151
|
+
<main class="content-wrap">
|
|
152
|
+
<div class="content-inner">
|
|
153
|
+
${this.pageTitle ? html`<h1 class="page-title">${this.pageTitle}</h1>` : ''}
|
|
154
|
+
<slot name="content"></slot>
|
|
155
|
+
</div>
|
|
156
|
+
</main>
|
|
157
|
+
${!this.noSidebar ? html`
|
|
158
|
+
<aside class="toc-wrap">
|
|
159
|
+
<starlight-toc .entries="${this.toc}"></starlight-toc>
|
|
160
|
+
</aside>
|
|
161
|
+
` : ''}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default StarlightPage;
|