tutuca 0.9.112 → 0.9.113
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/tutuca-cli.js +43 -5
- package/dist/tutuca-storybook.js +106 -7
- package/package.json +1 -1
- package/skill/tutuca/margaui.md +39 -0
- package/skill/tutuca/storybook.md +26 -0
package/dist/tutuca-cli.js
CHANGED
|
@@ -17368,9 +17368,33 @@ function buildImports(base, { margauiEnabled, margauiJsUrl }) {
|
|
|
17368
17368
|
imports.margaui = margauiJsUrl;
|
|
17369
17369
|
return imports;
|
|
17370
17370
|
}
|
|
17371
|
+
function themesBaseUrl(themeUrl) {
|
|
17372
|
+
return themeUrl.slice(0, themeUrl.lastIndexOf("/") + 1);
|
|
17373
|
+
}
|
|
17374
|
+
function renderNoFlashScript(themesUrl) {
|
|
17375
|
+
return `
|
|
17376
|
+
<script>
|
|
17377
|
+
(() => {
|
|
17378
|
+
const want = new URLSearchParams(location.search).get("theme");
|
|
17379
|
+
const name = /^[a-z]+$/.test(want ?? "")
|
|
17380
|
+
? want
|
|
17381
|
+
: matchMedia("(prefers-color-scheme: dark)").matches
|
|
17382
|
+
? "dark"
|
|
17383
|
+
: "light";
|
|
17384
|
+
document.documentElement.dataset.theme = name;
|
|
17385
|
+
if (name !== "light" && name !== "dark") {
|
|
17386
|
+
const link = document.createElement("link");
|
|
17387
|
+
link.rel = "stylesheet";
|
|
17388
|
+
link.id = "tutuca-theme-" + name;
|
|
17389
|
+
link.href = ${JSON.stringify(themesUrl)} + name + ".css";
|
|
17390
|
+
document.head.append(link);
|
|
17391
|
+
}
|
|
17392
|
+
})();
|
|
17393
|
+
</script>`;
|
|
17394
|
+
}
|
|
17371
17395
|
function renderIndexHtml(imports, { margauiEnabled, margauiThemeUrl, bootstrapUrl }) {
|
|
17372
17396
|
const theme = margauiEnabled ? `
|
|
17373
|
-
<link rel="stylesheet" href="${margauiThemeUrl}"
|
|
17397
|
+
<link rel="stylesheet" href="${margauiThemeUrl}" />${renderNoFlashScript(themesBaseUrl(margauiThemeUrl))}` : "";
|
|
17374
17398
|
return `<!doctype html>
|
|
17375
17399
|
<html lang="en">
|
|
17376
17400
|
<head>
|
|
@@ -17388,7 +17412,7 @@ ${JSON.stringify({ imports }, null, 6)}
|
|
|
17388
17412
|
</html>
|
|
17389
17413
|
`;
|
|
17390
17414
|
}
|
|
17391
|
-
function renderBootstrap(devModuleUrls, { margauiEnabled, check, inspect: inspect3, noCache }) {
|
|
17415
|
+
function renderBootstrap(devModuleUrls, { margauiEnabled, margauiThemeUrl, check, inspect: inspect3, noCache }) {
|
|
17392
17416
|
const lines = ['import { mountStorybook } from "tutuca/storybook";'];
|
|
17393
17417
|
if (margauiEnabled) {
|
|
17394
17418
|
lines.push('import { compileClassesToStyleText } from "tutuca/extra";');
|
|
@@ -17404,8 +17428,10 @@ function renderBootstrap(devModuleUrls, { margauiEnabled, check, inspect: inspec
|
|
|
17404
17428
|
});
|
|
17405
17429
|
const modules = devModuleUrls.map((_, i) => `m${i}`).join(", ");
|
|
17406
17430
|
const optParts = [];
|
|
17407
|
-
if (margauiEnabled)
|
|
17431
|
+
if (margauiEnabled) {
|
|
17408
17432
|
optParts.push("compileCss: (app) => compileClassesToStyleText(app, compile)");
|
|
17433
|
+
optParts.push(`themes: { baseUrl: ${JSON.stringify(themesBaseUrl(margauiThemeUrl))} }`);
|
|
17434
|
+
}
|
|
17409
17435
|
if (inspect3)
|
|
17410
17436
|
optParts.push("dev: { shadowCheckComponent, runTests, expect }");
|
|
17411
17437
|
if (noCache)
|
|
@@ -17617,7 +17643,13 @@ async function run5(argv, opts = {}) {
|
|
|
17617
17643
|
margauiThemeUrl: mg2.themeUrl,
|
|
17618
17644
|
bootstrapUrl: `./${bootstrapName}`
|
|
17619
17645
|
}));
|
|
17620
|
-
writeFileSync(resolve5(outDir, bootstrapName), renderBootstrap(devModuleUrls, {
|
|
17646
|
+
writeFileSync(resolve5(outDir, bootstrapName), renderBootstrap(devModuleUrls, {
|
|
17647
|
+
margauiEnabled,
|
|
17648
|
+
margauiThemeUrl: mg2.themeUrl,
|
|
17649
|
+
check,
|
|
17650
|
+
inspect: inspect3,
|
|
17651
|
+
noCache
|
|
17652
|
+
}));
|
|
17621
17653
|
process.stdout.write(`wrote static storybook → ${relative2(process.cwd(), outDir) || "."}/
|
|
17622
17654
|
index.html + ${bootstrapName} (${devModuleUrls.length} dev modules, CDN import map)
|
|
17623
17655
|
Host it from the project root so /*.dev.js paths resolve.
|
|
@@ -17717,7 +17749,13 @@ async function run5(argv, opts = {}) {
|
|
|
17717
17749
|
margauiThemeUrl: mg.themeUrl,
|
|
17718
17750
|
bootstrapUrl: BOOTSTRAP_URL
|
|
17719
17751
|
});
|
|
17720
|
-
const bootstrapJs = renderBootstrap(devModuleUrls, {
|
|
17752
|
+
const bootstrapJs = renderBootstrap(devModuleUrls, {
|
|
17753
|
+
margauiEnabled,
|
|
17754
|
+
margauiThemeUrl: mg.themeUrl,
|
|
17755
|
+
check,
|
|
17756
|
+
inspect: inspect3,
|
|
17757
|
+
noCache
|
|
17758
|
+
});
|
|
17721
17759
|
const server = createServer((req, res) => {
|
|
17722
17760
|
const path = req.url.split("?")[0];
|
|
17723
17761
|
if (path === "/" || path === "/index.html") {
|
package/dist/tutuca-storybook.js
CHANGED
|
@@ -10,7 +10,9 @@ var Storybook = component({
|
|
|
10
10
|
sectionId: null,
|
|
11
11
|
exampleId: null,
|
|
12
12
|
focusExample: null,
|
|
13
|
-
sidebarCollapsed: false
|
|
13
|
+
sidebarCollapsed: false,
|
|
14
|
+
theme: "",
|
|
15
|
+
themes: []
|
|
14
16
|
},
|
|
15
17
|
statics: {
|
|
16
18
|
withSections(sections) {
|
|
@@ -106,6 +108,11 @@ var Storybook = component({
|
|
|
106
108
|
onFocusClose(ctx) {
|
|
107
109
|
ctx.request("persistState", [this.toUrlState({ example: "" }), this, true]);
|
|
108
110
|
return this.setSectionId(null).setExampleId(null).setFocusExample(null);
|
|
111
|
+
},
|
|
112
|
+
onSelectTheme(value, ctx) {
|
|
113
|
+
ctx.request("applyTheme", [value, this]);
|
|
114
|
+
ctx.request("persistState", [this.toUrlState({ theme: value }), this, false]);
|
|
115
|
+
return this.setTheme(value);
|
|
109
116
|
}
|
|
110
117
|
},
|
|
111
118
|
bubble: {
|
|
@@ -149,7 +156,7 @@ var Storybook = component({
|
|
|
149
156
|
loadState(state, err, ctx) {
|
|
150
157
|
if (err || !state)
|
|
151
158
|
return this;
|
|
152
|
-
const selected = this.selectSectionWithId(state.section).setFilter(state.sectionFilter ?? "").applyFilterToSidebar(state.sectionFilter ?? "").setSelectedSectionFilter(state.exampleFilter ?? "");
|
|
159
|
+
const selected = this.selectSectionWithId(state.section).setTheme(state.theme ?? "").setFilter(state.sectionFilter ?? "").applyFilterToSidebar(state.sectionFilter ?? "").setSelectedSectionFilter(state.exampleFilter ?? "");
|
|
153
160
|
const next = state.example ? selected.focusExampleByIds(state.section, state.example) : selected.setSectionId(null).setExampleId(null).setFocusExample(null);
|
|
154
161
|
transitionSections(ctx, next, this.selectedSectionIndex, next.selectedSectionIndex);
|
|
155
162
|
return next;
|
|
@@ -195,6 +202,15 @@ var Storybook = component({
|
|
|
195
202
|
@on.input="onApplyFilter value"
|
|
196
203
|
@on.keydown.cancel="onClearFilter"
|
|
197
204
|
/>
|
|
205
|
+
<select
|
|
206
|
+
class="select w-auto"
|
|
207
|
+
title="Theme"
|
|
208
|
+
@hide="empty? .themes"
|
|
209
|
+
:value=".theme"
|
|
210
|
+
@on.input="onSelectTheme value"
|
|
211
|
+
>
|
|
212
|
+
<option @each=".themes" :value="@value" @text="@value"></option>
|
|
213
|
+
</select>
|
|
198
214
|
</div>
|
|
199
215
|
<div class="list h-full flex-1 overflow-y-auto">
|
|
200
216
|
<x render-each=".sidebar" @when="groupVisible"></x>
|
|
@@ -729,11 +745,53 @@ async function attachInspectorViews(root, scope, modules, dev = null) {
|
|
|
729
745
|
return root.setSections(sections);
|
|
730
746
|
}
|
|
731
747
|
|
|
748
|
+
// src/storybook/themes.js
|
|
749
|
+
var MARGAUI_THEMES = [
|
|
750
|
+
"light",
|
|
751
|
+
"dark",
|
|
752
|
+
"abyss",
|
|
753
|
+
"acid",
|
|
754
|
+
"aqua",
|
|
755
|
+
"autumn",
|
|
756
|
+
"black",
|
|
757
|
+
"bumblebee",
|
|
758
|
+
"business",
|
|
759
|
+
"caramellatte",
|
|
760
|
+
"cmyk",
|
|
761
|
+
"coffee",
|
|
762
|
+
"corporate",
|
|
763
|
+
"cupcake",
|
|
764
|
+
"cyberpunk",
|
|
765
|
+
"dim",
|
|
766
|
+
"dracula",
|
|
767
|
+
"emerald",
|
|
768
|
+
"fantasy",
|
|
769
|
+
"forest",
|
|
770
|
+
"garden",
|
|
771
|
+
"halloween",
|
|
772
|
+
"lemonade",
|
|
773
|
+
"lofi",
|
|
774
|
+
"luxury",
|
|
775
|
+
"night",
|
|
776
|
+
"nord",
|
|
777
|
+
"pastel",
|
|
778
|
+
"retro",
|
|
779
|
+
"silk",
|
|
780
|
+
"sunset",
|
|
781
|
+
"synthwave",
|
|
782
|
+
"valentine",
|
|
783
|
+
"winter",
|
|
784
|
+
"wireframe"
|
|
785
|
+
];
|
|
786
|
+
var BUNDLED_THEMES = new Set(["light", "dark"]);
|
|
787
|
+
|
|
732
788
|
// src/storybook/mount.js
|
|
733
|
-
async function mountStorybook(selector, modules, { compileCss, root, persistUrl = true, dev = null, noCache = false } = {}) {
|
|
789
|
+
async function mountStorybook(selector, modules, { compileCss, root, persistUrl = true, dev = null, noCache = false, themes = null } = {}) {
|
|
734
790
|
const app = tutuca(selector);
|
|
735
791
|
const built = buildStorybook(modules);
|
|
736
|
-
|
|
792
|
+
const themeBaseUrl = themes?.baseUrl ?? null;
|
|
793
|
+
const base = root ?? built.root;
|
|
794
|
+
app.state.set(themeBaseUrl && base.setThemes ? base.setThemes(MARGAUI_THEMES) : base);
|
|
737
795
|
const rootScope = app.registerComponents(built.engineComponents);
|
|
738
796
|
rootScope.registerMacros(built.macros);
|
|
739
797
|
rootScope.registerRequestHandlers(buildExampleRequestHandlers(built));
|
|
@@ -741,6 +799,9 @@ async function mountStorybook(selector, modules, { compileCss, root, persistUrl
|
|
|
741
799
|
if (persistUrl) {
|
|
742
800
|
rootScope.registerRequestHandlers({ persistState });
|
|
743
801
|
}
|
|
802
|
+
if (themeBaseUrl) {
|
|
803
|
+
rootScope.registerRequestHandlers({ applyTheme });
|
|
804
|
+
}
|
|
744
805
|
const moduleScopes = built.moduleComponents.map((comps) => {
|
|
745
806
|
const scope = rootScope.enter();
|
|
746
807
|
scope.registerComponents(comps);
|
|
@@ -773,17 +834,53 @@ async function mountStorybook(selector, modules, { compileCss, root, persistUrl
|
|
|
773
834
|
}
|
|
774
835
|
window.history[push ? "pushState" : "replaceState"](null, "", url);
|
|
775
836
|
}
|
|
837
|
+
function applyTheme(name, instance) {
|
|
838
|
+
if (instance !== app.state.val)
|
|
839
|
+
return;
|
|
840
|
+
ensureThemeLink(name);
|
|
841
|
+
document.documentElement.dataset.theme = name;
|
|
842
|
+
}
|
|
776
843
|
function loadState() {
|
|
777
844
|
const p = new URLSearchParams(window.location.search);
|
|
778
845
|
return {
|
|
779
846
|
section: p.get("section"),
|
|
780
847
|
example: p.get("example"),
|
|
781
848
|
sectionFilter: p.get("sectionFilter") ?? "",
|
|
782
|
-
exampleFilter: p.get("exampleFilter") ?? ""
|
|
849
|
+
exampleFilter: p.get("exampleFilter") ?? "",
|
|
850
|
+
theme: resolveTheme(p.get("theme"))
|
|
783
851
|
};
|
|
784
852
|
}
|
|
785
853
|
function loadStateBlank() {
|
|
786
|
-
return {
|
|
854
|
+
return {
|
|
855
|
+
section: null,
|
|
856
|
+
example: null,
|
|
857
|
+
sectionFilter: "",
|
|
858
|
+
exampleFilter: "",
|
|
859
|
+
theme: resolveTheme(null)
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function resolveTheme(fromUrl) {
|
|
863
|
+
if (!themeBaseUrl)
|
|
864
|
+
return "";
|
|
865
|
+
const name = MARGAUI_THEMES.includes(fromUrl) ? fromUrl : osTheme();
|
|
866
|
+
ensureThemeLink(name);
|
|
867
|
+
document.documentElement.dataset.theme = name;
|
|
868
|
+
return name;
|
|
869
|
+
}
|
|
870
|
+
function osTheme() {
|
|
871
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
872
|
+
}
|
|
873
|
+
function ensureThemeLink(name) {
|
|
874
|
+
if (BUNDLED_THEMES.has(name) || document.getElementById(themeLinkId(name)))
|
|
875
|
+
return;
|
|
876
|
+
const link = document.createElement("link");
|
|
877
|
+
link.rel = "stylesheet";
|
|
878
|
+
link.id = themeLinkId(name);
|
|
879
|
+
link.href = `${themeBaseUrl}${name}.css`;
|
|
880
|
+
document.head.append(link);
|
|
881
|
+
}
|
|
882
|
+
function themeLinkId(name) {
|
|
883
|
+
return `tutuca-theme-${name}`;
|
|
787
884
|
}
|
|
788
885
|
}
|
|
789
886
|
|
|
@@ -800,5 +897,7 @@ export {
|
|
|
800
897
|
buildExampleRequestHandlers,
|
|
801
898
|
Storybook,
|
|
802
899
|
Section,
|
|
803
|
-
|
|
900
|
+
MARGAUI_THEMES,
|
|
901
|
+
Example,
|
|
902
|
+
BUNDLED_THEMES
|
|
804
903
|
};
|
package/package.json
CHANGED
package/skill/tutuca/margaui.md
CHANGED
|
@@ -68,6 +68,45 @@ that reason):
|
|
|
68
68
|
Trade-off: no runtime network dependency and a frozen version, at the
|
|
69
69
|
cost of updating the vendored files by hand.
|
|
70
70
|
|
|
71
|
+
## Dark mode and the other palettes
|
|
72
|
+
|
|
73
|
+
A margaui theme is a block of CSS custom properties (`--color-*`, `--radius-*`,
|
|
74
|
+
…) under a `[data-theme="<name>"]` selector, and every class it compiles reads
|
|
75
|
+
those through `var(--color-*)`. So switching theme is **one attribute flip on
|
|
76
|
+
`<html>`** — the compiled stylesheet never changes:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
document.documentElement.dataset.theme = "dark";
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Two things to know, both of which bite if you assume otherwise:
|
|
83
|
+
|
|
84
|
+
- **Dark mode never turns itself on.** `dark.css` is keyed on
|
|
85
|
+
`[data-theme="dark"]` alone — there is no `prefers-color-scheme` fallback and
|
|
86
|
+
no `.dark` class. Link `theme.css` and do nothing else and the page is light
|
|
87
|
+
forever, on every machine. Following the OS is your job:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const dark = matchMedia("(prefers-color-scheme: dark)");
|
|
91
|
+
document.documentElement.dataset.theme = dark.matches ? "dark" : "light";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **`theme.css` is only light + dark.** It is literally
|
|
95
|
+
`@import"./light.css";@import"./dark.css";`. margaui ships ~33 more palettes
|
|
96
|
+
(dracula, nord, cyberpunk, …) as sibling files, each linked separately and
|
|
97
|
+
each cheap:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<link rel="stylesheet" href="https://marianoguerra.github.io/margaui/themes/dracula.css" />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Link it **after** `theme.css`: `light.css` claims plain `:root` as well as
|
|
104
|
+
`[data-theme=light]`, which ties on specificity with `[data-theme=dracula]`,
|
|
105
|
+
so a palette only wins by coming later in the cascade.
|
|
106
|
+
|
|
107
|
+
`tutuca storybook` does all of this for you — see the Themes section of
|
|
108
|
+
[storybook.md](./storybook.md).
|
|
109
|
+
|
|
71
110
|
## Wire it into tutuca
|
|
72
111
|
|
|
73
112
|
However you obtained `compile`, the integration is the same: register
|
|
@@ -215,6 +215,32 @@ version-pinned CDN. `--out` always pins the CDN so the artifact is portable —
|
|
|
215
215
|
host it from the project root so `/*.dev.js` paths resolve. See [cli.md](./cli.md)
|
|
216
216
|
for the exhaustive flag list and exit codes.
|
|
217
217
|
|
|
218
|
+
## Themes
|
|
219
|
+
|
|
220
|
+
The page starts in the theme the URL asks for (`?theme=dracula`), else the one
|
|
221
|
+
the OS prefers, and the sidebar has a switcher for margaui's palettes. Picking
|
|
222
|
+
one sets `?theme=`, so a themed storybook is shareable and survives a reload.
|
|
223
|
+
That OS step is on us, not margaui: its `dark.css` is keyed on
|
|
224
|
+
`[data-theme="dark"]` alone, with **no** `prefers-color-scheme` fallback, so a
|
|
225
|
+
page that only links `theme.css` renders light forever. See
|
|
226
|
+
[margaui.md](./margaui.md).
|
|
227
|
+
|
|
228
|
+
Embedding the storybook yourself? The switcher is opt-in through the `themes`
|
|
229
|
+
option, which — like `compileCss` — is injected, so the library still never
|
|
230
|
+
imports margaui:
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
mountStorybook("#app", modules, {
|
|
234
|
+
compileCss: (app) => compileClassesToStyleText(app, compile),
|
|
235
|
+
// The directory your theme.css lives in. Every other palette is a sibling
|
|
236
|
+
// file there; only the one selected is fetched, and only the first time.
|
|
237
|
+
themes: { baseUrl: "https://marianoguerra.github.io/margaui/themes/" },
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Omit `themes` (what `--no-margaui` does) and no switcher renders — there would
|
|
242
|
+
be no theme CSS to switch to.
|
|
243
|
+
|
|
218
244
|
## Footguns
|
|
219
245
|
|
|
220
246
|
- ⚠️ `value` must be a real instance (`Comp.make(...)`), not a plain object or
|