@keenmate/web-components-devkit 0.0.0-stage → 1.0.0-rc02
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 +126 -0
- package/LICENSE +21 -0
- package/README.md +434 -2
- package/components.json +206 -0
- package/package.json +77 -4
- package/src/cli/args.js +16 -0
- package/src/cli/commands/check-release.js +66 -0
- package/src/cli/commands/check-vars.js +116 -0
- package/src/cli/commands/doctor.js +289 -0
- package/src/cli/commands/new.js +323 -0
- package/src/cli/commands/readme.js +111 -0
- package/src/cli/commands/sync.js +48 -0
- package/src/cli/kmwc.js +73 -0
- package/src/cli/sync-plan.js +163 -0
- package/src/config.d.ts +149 -0
- package/src/config.js +279 -0
- package/src/examples/auto.js +20 -0
- package/src/examples/chapter-nav.js +246 -0
- package/src/examples/constants.js +80 -0
- package/src/examples/controls-persistence.js +97 -0
- package/src/examples/copy-link.js +89 -0
- package/src/examples/css/base.css +215 -0
- package/src/examples/css/chapter-nav.css +179 -0
- package/src/examples/css/content.css +251 -0
- package/src/examples/css/controls.css +205 -0
- package/src/examples/css/copy-link.css +82 -0
- package/src/examples/css/demo.css +50 -0
- package/src/examples/css/index-page.css +118 -0
- package/src/examples/css/style.css +30 -0
- package/src/examples/css/theme-switch.css +38 -0
- package/src/examples/css/tokens.css +124 -0
- package/src/examples/css/widgets.css +133 -0
- package/src/examples/demo-code.js +30 -0
- package/src/examples/index-page.js +78 -0
- package/src/examples/index.d.ts +107 -0
- package/src/examples/index.js +18 -0
- package/src/examples/kit.js +22 -0
- package/src/examples/page-config.js +30 -0
- package/src/examples/sections.js +53 -0
- package/src/examples/theme-switch.js +72 -0
- package/src/examples/version-badge.js +23 -0
- package/src/playwright/index.d.ts +15 -0
- package/src/playwright/index.js +66 -0
- package/src/template.js +11 -0
- package/src/vite/index.d.ts +28 -0
- package/src/vite/index.js +243 -0
- package/src/vite/page-composer.js +216 -0
- package/templates/Dockerfile.tmpl +39 -0
- package/templates/Makefile.tmpl +136 -0
- package/templates/claude-block.md +40 -0
- package/templates/dockerignore.tmpl +10 -0
- package/templates/examples-layout.html +13 -0
- package/templates/new/.claude/commands/publish.md +226 -0
- package/templates/new/CHANGELOG.md +13 -0
- package/templates/new/LICENSE +21 -0
- package/templates/new/README.md +71 -0
- package/templates/new/_gitignore +7 -0
- package/templates/new/component-variables.manifest.json +32 -0
- package/templates/new/custom-elements-manifest.config.mjs +23 -0
- package/templates/new/docs/accessibility.md +24 -0
- package/templates/new/docs/examples.md +33 -0
- package/templates/new/docs/theming.md +54 -0
- package/templates/new/docs/usage.md +51 -0
- package/templates/new/e2e/dark-mode.spec.ts +30 -0
- package/templates/new/e2e/smoke.spec.ts +26 -0
- package/templates/new/examples-basic.html +57 -0
- package/templates/new/examples-theming.html +28 -0
- package/templates/new/index.html +16 -0
- package/templates/new/partials/note.html +5 -0
- package/templates/new/playwright.config.ts +4 -0
- package/templates/new/src/__FEATURE__.ts +55 -0
- package/templates/new/src/css/animations.css +1 -0
- package/templates/new/src/css/base.css +43 -0
- package/templates/new/src/css/controls.css +20 -0
- package/templates/new/src/css/dark-mode.css +36 -0
- package/templates/new/src/css/floating.css +1 -0
- package/templates/new/src/css/main.css +27 -0
- package/templates/new/src/css/states.css +22 -0
- package/templates/new/src/css/variables.css +42 -0
- package/templates/new/src/env.d.ts +7 -0
- package/templates/new/src/index.ts +17 -0
- package/templates/new/src/logger.ts +11 -0
- package/templates/new/src/types.ts +25 -0
- package/templates/new/src/web-component.ts +73 -0
- package/templates/new/test/dark-mode.html +18 -0
- package/templates/new/test/fixture.html +11 -0
- package/templates/new/tsconfig.json +15 -0
- package/templates/new/vite.config.ts +5 -0
- package/templates/new-minimal/CHANGELOG.md +13 -0
- package/templates/new-minimal/README.md +71 -0
- package/templates/new-minimal/component-variables.manifest.json +24 -0
- package/templates/new-minimal/docs/accessibility.md +20 -0
- package/templates/new-minimal/docs/examples.md +17 -0
- package/templates/new-minimal/docs/theming.md +53 -0
- package/templates/new-minimal/docs/usage.md +43 -0
- package/templates/new-minimal/e2e/smoke.spec.ts +10 -0
- package/templates/new-minimal/examples-basic.html +23 -0
- package/templates/new-minimal/examples-theming.html +28 -0
- package/templates/new-minimal/src/__FEATURE__.ts +37 -0
- package/templates/new-minimal/src/css/base.css +48 -0
- package/templates/new-minimal/src/css/controls.css +1 -0
- package/templates/new-minimal/src/css/states.css +1 -0
- package/templates/new-minimal/src/css/variables.css +28 -0
- package/templates/new-minimal/src/index.ts +17 -0
- package/templates/new-minimal/src/types.ts +10 -0
- package/templates/new-minimal/src/web-component.ts +48 -0
- package/templates/new-minimal/test/dark-mode.html +18 -0
- package/templates/new-minimal/test/fixture.html +11 -0
- package/templates/nginx.conf.tmpl +43 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* ==============================================================================
|
|
2
|
+
KMWC EXAMPLES KIT — MAIN ENTRY POINT
|
|
3
|
+
==============================================================================
|
|
4
|
+
Loaded automatically on every example page by the kmwc Vite plugin.
|
|
5
|
+
|
|
6
|
+
Everything lives in `@layer kmwc`, so ANY unlayered rule — a page's own
|
|
7
|
+
<style>, a repo's `examples-local.css`, a light-DOM component's styles —
|
|
8
|
+
wins regardless of load order or specificity. The universal reset is
|
|
9
|
+
layered too, so it can never clobber a component (css-structure.md "The
|
|
10
|
+
unlayered-reset footgun").
|
|
11
|
+
|
|
12
|
+
• colours → tokens (tokens.css) read through --base-*; per-site accent via
|
|
13
|
+
package.json#kmwc.theme
|
|
14
|
+
• a rule → write the selector unlayered in examples-local.css
|
|
15
|
+
• missing → if two components need it, add it HERE, not locally
|
|
16
|
+
|
|
17
|
+
Page vocabulary (.card, .controls, .note, …) is authored by pages and stays
|
|
18
|
+
unprefixed; DOM the kit generates is prefixed BEM (kmwc-chapter-nav__item…). */
|
|
19
|
+
@layer kmwc;
|
|
20
|
+
|
|
21
|
+
@import './tokens.css';
|
|
22
|
+
@import './base.css';
|
|
23
|
+
@import './controls.css';
|
|
24
|
+
@import './content.css';
|
|
25
|
+
@import './widgets.css';
|
|
26
|
+
@import './demo.css';
|
|
27
|
+
@import './copy-link.css';
|
|
28
|
+
@import './chapter-nav.css';
|
|
29
|
+
@import './theme-switch.css';
|
|
30
|
+
@import './index-page.css';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* ==============================================================================
|
|
2
|
+
EXAMPLES KIT — THEME SWITCH
|
|
3
|
+
==============================================================================
|
|
4
|
+
Fixed light / dark / auto button (theme-switch.js), top-right corner. */
|
|
5
|
+
@layer kmwc {
|
|
6
|
+
.kmwc-theme-switch {
|
|
7
|
+
position: fixed;
|
|
8
|
+
top: 0.75rem;
|
|
9
|
+
right: 0.75rem;
|
|
10
|
+
z-index: 9000;
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
width: 36px;
|
|
15
|
+
height: 36px;
|
|
16
|
+
padding: 0;
|
|
17
|
+
border-radius: 50%;
|
|
18
|
+
border: 1px solid var(--kmwc-border-color);
|
|
19
|
+
background: var(--kmwc-main-bg);
|
|
20
|
+
color: var(--kmwc-text-color-1);
|
|
21
|
+
box-shadow: 0 2px 6px var(--kmwc-shadow-color);
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.kmwc-theme-switch:hover {
|
|
26
|
+
background: var(--kmwc-hover-bg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.kmwc-theme-switch:focus-visible {
|
|
30
|
+
outline: 2px solid var(--kmwc-accent-color);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.kmwc-theme-switch__icon {
|
|
35
|
+
font-size: 1.1rem;
|
|
36
|
+
line-height: 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* ==============================================================================
|
|
2
|
+
EXAMPLES KIT — TOKENS
|
|
3
|
+
==============================================================================
|
|
4
|
+
The page-chrome counterpart of a component's variables.css. Same two-layer
|
|
5
|
+
model as the rulebook (base-variables.md): every --kmwc-* token reads through
|
|
6
|
+
the cross-component --base-* hook with a light-dark() fallback, so
|
|
7
|
+
|
|
8
|
+
• a theme-designer theme (:root --base-*) restyles the demo page AND the
|
|
9
|
+
component together — demo pages demonstrate cross-component theming;
|
|
10
|
+
• with no --base-* set, the page still looks right, in light and dark.
|
|
11
|
+
|
|
12
|
+
Fallbacks are the contract's own (@keenmate/base-css-variables): an input
|
|
13
|
+
falls back to its default, a derived token to the contract formula on the
|
|
14
|
+
--kmwc-* tokens, so a page looks the same with and without the contract.
|
|
15
|
+
`npm run check:contract` (base-contract-check) enforces it.
|
|
16
|
+
|
|
17
|
+
Unlike component variables (theme-container.md: never on :root), page
|
|
18
|
+
tokens DO live on :root — the page is the scope here. Feature files read
|
|
19
|
+
only --kmwc-* tokens; colour literals live in this file alone.
|
|
20
|
+
|
|
21
|
+
Re-theme a component's example site via package.json#kmwc.theme (injected
|
|
22
|
+
as --kmwc-accent-color / --kmwc-accent-end-color) — never by copying CSS. */
|
|
23
|
+
@layer kmwc {
|
|
24
|
+
/* ==========================================================================
|
|
25
|
+
COLOR SCHEME — driven by <html data-theme> (theme switch)
|
|
26
|
+
==========================================================================
|
|
27
|
+
light (default) / dark force the scheme; auto follows the OS. Components
|
|
28
|
+
on the page inherit color-scheme and see the same data-theme signal via
|
|
29
|
+
:host-context([data-theme="…"]). */
|
|
30
|
+
:root {
|
|
31
|
+
color-scheme: light;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:root[data-theme="dark"] {
|
|
35
|
+
color-scheme: dark;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:root[data-theme="auto"] {
|
|
39
|
+
color-scheme: light dark;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:root {
|
|
43
|
+
/* ======================================================================
|
|
44
|
+
COLORS — ACCENT
|
|
45
|
+
====================================================================== */
|
|
46
|
+
--kmwc-accent-color: var(--base-primary-color, #3b82f6);
|
|
47
|
+
--kmwc-accent-end-color: oklch(from var(--kmwc-accent-color) calc(l - 0.12) c calc(h + 25)); /* header gradient end */
|
|
48
|
+
--kmwc-accent-color-hover: var(--base-primary-color-hover, light-dark(color-mix(in srgb, black 15%, var(--kmwc-accent-color)), color-mix(in srgb, white 15%, var(--kmwc-accent-color))));
|
|
49
|
+
--kmwc-accent-color-active: var(--base-primary-color-active, light-dark(color-mix(in srgb, black 30%, var(--kmwc-accent-color)), color-mix(in srgb, white 30%, var(--kmwc-accent-color))));
|
|
50
|
+
--kmwc-accent-light-bg: var(--base-primary-bg-subtle, light-dark(color-mix(in srgb, var(--kmwc-accent-color) 10%, transparent), color-mix(in srgb, var(--kmwc-accent-color) 20%, transparent)));
|
|
51
|
+
--kmwc-accent-light-bg-hover: var(--base-primary-bg-subtle-hover, light-dark(color-mix(in srgb, var(--kmwc-accent-color) 15%, transparent), color-mix(in srgb, var(--kmwc-accent-color) 30%, transparent)));
|
|
52
|
+
--kmwc-accent-border-color: color-mix(in srgb, var(--kmwc-accent-color) 40%, transparent);
|
|
53
|
+
--kmwc-accent-shadow-color: color-mix(in srgb, var(--kmwc-accent-color) 35%, transparent);
|
|
54
|
+
--kmwc-text-color-on-accent: var(--base-on-primary-text-color, color(from color(from var(--kmwc-accent-color) srgb calc(0.21 * r * r * (0.4 + r * (0.82 - 0.22 * r)) + 0.72 * g * g * (0.4 + g * (0.82 - 0.22 * g)) + 0.07 * b * b * (0.4 + b * (0.82 - 0.22 * b))) 0 0) srgb clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1)));
|
|
55
|
+
--kmwc-header-bg: linear-gradient(135deg, var(--kmwc-accent-color) 0%, var(--kmwc-accent-end-color) 100%);
|
|
56
|
+
|
|
57
|
+
/* ======================================================================
|
|
58
|
+
COLORS — TEXT
|
|
59
|
+
====================================================================== */
|
|
60
|
+
--kmwc-text-color-1: var(--base-text-color-1, light-dark(#334155, #f1f5f9));
|
|
61
|
+
--kmwc-text-color-2: var(--base-text-color-2, color-mix(in srgb, var(--kmwc-text-color-1) 75%, var(--kmwc-main-bg)));
|
|
62
|
+
--kmwc-text-color-3: var(--base-text-color-3, color-mix(in srgb, var(--kmwc-text-color-1) 55%, var(--kmwc-main-bg)));
|
|
63
|
+
--kmwc-text-color-4: var(--base-text-color-4, color-mix(in srgb, var(--kmwc-text-color-1) 40%, var(--kmwc-main-bg)));
|
|
64
|
+
|
|
65
|
+
/* ======================================================================
|
|
66
|
+
COLORS — SURFACE
|
|
67
|
+
====================================================================== */
|
|
68
|
+
--kmwc-page-bg: var(--base-page-bg, light-dark(#f8f9fa, #0f172a));
|
|
69
|
+
--kmwc-main-bg: var(--base-main-bg, light-dark(#ffffff, #1e293b));
|
|
70
|
+
--kmwc-elevated-bg: var(--base-elevated-bg, light-dark(oklch(from var(--kmwc-main-bg) calc(l - 0.03) c h), oklch(from var(--kmwc-main-bg) calc(l + 0.06) c h)));
|
|
71
|
+
--kmwc-hover-bg: var(--base-hover-bg, light-dark(color-mix(in oklch, oklch(from var(--kmwc-main-bg) calc(l - 0.45) c h) 14%, transparent), color-mix(in oklch, oklch(from var(--kmwc-main-bg) calc(l + 0.45) c h) 28%, transparent)));
|
|
72
|
+
--kmwc-border-color: var(--base-border-color, light-dark(oklch(from var(--kmwc-main-bg) calc(l - 0.096) c h), oklch(from var(--kmwc-main-bg) calc(l + 0.192) c h)));
|
|
73
|
+
--kmwc-border-color-strong: var(--base-input-border-color, light-dark(color-mix(in srgb, var(--kmwc-text-color-1) 45%, var(--kmwc-main-bg)), color-mix(in srgb, var(--kmwc-text-color-1) 35%, var(--kmwc-main-bg)))); /* control outline */
|
|
74
|
+
--kmwc-shadow-color: var(--base-shadow-color, light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.4)));
|
|
75
|
+
|
|
76
|
+
/* ======================================================================
|
|
77
|
+
COLORS — INPUTS & CODE
|
|
78
|
+
====================================================================== */
|
|
79
|
+
--kmwc-input-bg: var(--base-input-bg, var(--kmwc-main-bg));
|
|
80
|
+
--kmwc-input-color: var(--base-input-text-color, var(--kmwc-text-color-1));
|
|
81
|
+
--kmwc-input-border: var(--base-input-border, 1px solid var(--kmwc-border-color-strong));
|
|
82
|
+
--kmwc-code-bg: light-dark(#2d3748, #0d1117);
|
|
83
|
+
--kmwc-code-text-color: #e2e8f0;
|
|
84
|
+
--kmwc-code-comment-color: #8b949e;
|
|
85
|
+
--kmwc-code-keyword-color: #c678dd;
|
|
86
|
+
--kmwc-code-string-color: #98c379;
|
|
87
|
+
--kmwc-code-function-color: #61afef;
|
|
88
|
+
--kmwc-code-property-color: #d19a66;
|
|
89
|
+
--kmwc-console-bg: light-dark(#0f172a, #05070d);
|
|
90
|
+
--kmwc-console-text-color: #e2e8f0;
|
|
91
|
+
--kmwc-console-muted-color: #94a3b8;
|
|
92
|
+
--kmwc-console-highlight-color: #68d391;
|
|
93
|
+
--kmwc-inline-code-bg: light-dark(#edf2f7, #2b2b2b);
|
|
94
|
+
|
|
95
|
+
/* ======================================================================
|
|
96
|
+
COLORS — SEMANTIC
|
|
97
|
+
====================================================================== */
|
|
98
|
+
/* -color = the role as text on a surface (notes, feedback); the danger
|
|
99
|
+
button fills with --kmwc-danger-fill-color. */
|
|
100
|
+
--kmwc-success-color: var(--base-success-text-color, light-dark(#155724, #34d399));
|
|
101
|
+
--kmwc-warning-color: var(--base-warning-text-color, light-dark(#856404, #fbbf24));
|
|
102
|
+
--kmwc-danger-color: var(--base-danger-text-color, light-dark(#721c24, #f87171));
|
|
103
|
+
--kmwc-info-color: var(--base-info-text-color, light-dark(#0c5460, #22d3ee));
|
|
104
|
+
--kmwc-danger-fill-color: var(--base-danger-color, #dc2626);
|
|
105
|
+
--kmwc-danger-fill-color-hover: var(--base-danger-color-hover, light-dark(color-mix(in srgb, black 15%, var(--kmwc-danger-fill-color)), color-mix(in srgb, white 15%, var(--kmwc-danger-fill-color))));
|
|
106
|
+
--kmwc-text-color-on-danger: var(--base-on-danger-text-color, color(from color(from var(--kmwc-danger-fill-color) srgb calc(0.21 * r * r * (0.4 + r * (0.82 - 0.22 * r)) + 0.72 * g * g * (0.4 + g * (0.82 - 0.22 * g)) + 0.07 * b * b * (0.4 + b * (0.82 - 0.22 * b))) 0 0) srgb clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1)));
|
|
107
|
+
--kmwc-neutral-color: light-dark(#6b7280, #9ca3af);
|
|
108
|
+
--kmwc-badge-new-bg: light-dark(#e53e3e, #f56565);
|
|
109
|
+
/* Text on the neutral / badge fills: the contract's on-colour formula on
|
|
110
|
+
their own background, not on the accent (a light primary would turn it black). */
|
|
111
|
+
--kmwc-text-color-on-neutral: color(from color(from var(--kmwc-neutral-color) srgb calc(0.21 * r * r * (0.4 + r * (0.82 - 0.22 * r)) + 0.72 * g * g * (0.4 + g * (0.82 - 0.22 * g)) + 0.07 * b * b * (0.4 + b * (0.82 - 0.22 * b))) 0 0) srgb clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1));
|
|
112
|
+
--kmwc-text-color-on-badge-new: color(from color(from var(--kmwc-badge-new-bg) srgb calc(0.21 * r * r * (0.4 + r * (0.82 - 0.22 * r)) + 0.72 * g * g * (0.4 + g * (0.82 - 0.22 * g)) + 0.07 * b * b * (0.4 + b * (0.82 - 0.22 * b))) 0 0) srgb clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1) clamp(0, (0.34 - r) * 1e9, 1));
|
|
113
|
+
|
|
114
|
+
/* ======================================================================
|
|
115
|
+
TYPOGRAPHY & SHAPE
|
|
116
|
+
====================================================================== */
|
|
117
|
+
--kmwc-font-family: var(--base-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
|
|
118
|
+
--kmwc-mono-font-family: var(--base-font-family-mono, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
|
|
119
|
+
--kmwc-radius-lg: 12px;
|
|
120
|
+
--kmwc-radius-md: 8px;
|
|
121
|
+
--kmwc-radius-sm: 6px;
|
|
122
|
+
--kmwc-max-width: 1400px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* ==============================================================================
|
|
2
|
+
EXAMPLES KIT — WIDGETS
|
|
3
|
+
==============================================================================
|
|
4
|
+
Opt-in helpers more than one component's examples use. Add here (not to a
|
|
5
|
+
repo-local stylesheet) once a second component needs a pattern. */
|
|
6
|
+
@layer kmwc {
|
|
7
|
+
/* ==========================================================================
|
|
8
|
+
LOG PANELS
|
|
9
|
+
==========================================================================
|
|
10
|
+
`.log-panel` — monospace, scrollable box for streaming output; light by
|
|
11
|
+
default, `.log-panel--dark` for the console look; height cap via
|
|
12
|
+
`--log-max-height`. `.event-log` is the legacy dark console with
|
|
13
|
+
`.event-*` entries — prefer `.log-panel` for new pages. */
|
|
14
|
+
.log-panel {
|
|
15
|
+
font-family: var(--kmwc-mono-font-family);
|
|
16
|
+
font-size: 0.85rem;
|
|
17
|
+
line-height: 1.5;
|
|
18
|
+
padding: 1rem 1.25rem;
|
|
19
|
+
border-radius: var(--kmwc-radius-md);
|
|
20
|
+
max-height: var(--log-max-height, 220px);
|
|
21
|
+
overflow: auto;
|
|
22
|
+
background: var(--kmwc-page-bg);
|
|
23
|
+
color: var(--kmwc-text-color-1);
|
|
24
|
+
border: 1px solid var(--kmwc-border-color);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.log-panel--dark {
|
|
28
|
+
background: var(--kmwc-console-bg);
|
|
29
|
+
color: var(--kmwc-console-text-color);
|
|
30
|
+
border-color: transparent;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.event-log {
|
|
34
|
+
background: var(--kmwc-console-bg);
|
|
35
|
+
color: var(--kmwc-console-muted-color);
|
|
36
|
+
padding: 1rem;
|
|
37
|
+
border-radius: var(--kmwc-radius-md);
|
|
38
|
+
font-family: var(--kmwc-mono-font-family);
|
|
39
|
+
font-size: 0.8rem;
|
|
40
|
+
max-height: 200px;
|
|
41
|
+
overflow-y: auto;
|
|
42
|
+
line-height: 1.5;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.event-log .event-entry {
|
|
46
|
+
padding: 0.15rem 0;
|
|
47
|
+
border-bottom: 1px solid color-mix(in srgb, var(--kmwc-console-text-color) 6%, transparent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.event-log .event-time { color: var(--kmwc-console-muted-color); }
|
|
51
|
+
.event-log .event-name { color: var(--kmwc-console-highlight-color); font-weight: 600; }
|
|
52
|
+
|
|
53
|
+
/* ==========================================================================
|
|
54
|
+
CSS-VARIABLE DEMO HELPERS
|
|
55
|
+
==========================================================================
|
|
56
|
+
`.display-sliders` — (name, range, value) rows for live CSS-var demos;
|
|
57
|
+
`.css-readout` — chips showing measured values (`.is-gap` accents a
|
|
58
|
+
derived figure). */
|
|
59
|
+
.display-sliders {
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-columns: auto 1fr auto;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 0.4rem 0.75rem;
|
|
64
|
+
margin: 0.25rem 0 0.75rem;
|
|
65
|
+
max-width: 460px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.display-sliders label {
|
|
69
|
+
font-family: var(--kmwc-mono-font-family);
|
|
70
|
+
font-size: 0.78rem;
|
|
71
|
+
color: var(--kmwc-text-color-2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.display-sliders input[type="range"] {
|
|
75
|
+
width: 100%;
|
|
76
|
+
accent-color: var(--kmwc-accent-color);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.display-sliders output {
|
|
80
|
+
font-family: var(--kmwc-mono-font-family);
|
|
81
|
+
font-size: 0.78rem;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
color: var(--kmwc-info-color);
|
|
84
|
+
min-width: 3.2em;
|
|
85
|
+
text-align: right;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.css-readout {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-wrap: wrap;
|
|
91
|
+
gap: 0.4rem;
|
|
92
|
+
margin: 0.25rem 0 1rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.css-readout code { font-size: 0.78rem; }
|
|
96
|
+
.css-readout code b { color: var(--kmwc-info-color); font-weight: 700; }
|
|
97
|
+
.css-readout code.is-gap b { color: var(--kmwc-warning-color); }
|
|
98
|
+
|
|
99
|
+
/* ==========================================================================
|
|
100
|
+
STATUS DOTS & PILLS
|
|
101
|
+
========================================================================== */
|
|
102
|
+
.status-dot {
|
|
103
|
+
--kmwc-status-color: var(--kmwc-neutral-color);
|
|
104
|
+
display: inline-block;
|
|
105
|
+
width: 8px;
|
|
106
|
+
height: 8px;
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
background: var(--kmwc-status-color);
|
|
109
|
+
margin-right: 0.4rem;
|
|
110
|
+
vertical-align: middle;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.status-dot--ok { --kmwc-status-color: var(--kmwc-success-color); }
|
|
114
|
+
.status-dot--warn { --kmwc-status-color: var(--kmwc-warning-color); }
|
|
115
|
+
.status-dot--err { --kmwc-status-color: var(--kmwc-danger-color); }
|
|
116
|
+
|
|
117
|
+
.pill {
|
|
118
|
+
--kmwc-status-color: var(--kmwc-neutral-color);
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
padding: 0.15rem 0.55rem;
|
|
122
|
+
border-radius: 999px;
|
|
123
|
+
font-size: 0.75rem;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
font-family: var(--kmwc-mono-font-family);
|
|
126
|
+
background: color-mix(in srgb, var(--kmwc-status-color) 16%, transparent);
|
|
127
|
+
color: var(--kmwc-status-color);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.pill--ok { --kmwc-status-color: var(--kmwc-success-color); }
|
|
131
|
+
.pill--warn { --kmwc-status-color: var(--kmwc-warning-color); }
|
|
132
|
+
.pill--err { --kmwc-status-color: var(--kmwc-danger-color); }
|
|
133
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy buttons for the code blocks <kmwc-demo> generates. The code itself is
|
|
3
|
+
* rendered at build time (page-composer.js) from the demo's live markup, so it
|
|
4
|
+
* can't drift; this only adds the "Copy" affordance. Styles: css/demo.css.
|
|
5
|
+
*/
|
|
6
|
+
import { copyText } from './copy-link.js';
|
|
7
|
+
import { COPY_FEEDBACK_DURATION_MS, DEMO_CLASSES } from './constants.js';
|
|
8
|
+
|
|
9
|
+
export function attachDemoCopyButtons(root = document) {
|
|
10
|
+
for (const codeBlock of root.querySelectorAll(`.${DEMO_CLASSES.code}`)) {
|
|
11
|
+
if (codeBlock.querySelector(`.${DEMO_CLASSES.copyButton}`)) continue;
|
|
12
|
+
const button = document.createElement('button');
|
|
13
|
+
button.type = 'button';
|
|
14
|
+
button.className = DEMO_CLASSES.copyButton;
|
|
15
|
+
button.textContent = 'Copy';
|
|
16
|
+
let revertTimer = null;
|
|
17
|
+
button.addEventListener('click', () => {
|
|
18
|
+
copyText(codeBlock.querySelector('code')?.textContent ?? '').then((isCopied) => {
|
|
19
|
+
button.textContent = isCopied ? 'Copied!' : 'Ctrl/Cmd+C';
|
|
20
|
+
button.classList.toggle(DEMO_CLASSES.copyButtonCopied, isCopied);
|
|
21
|
+
clearTimeout(revertTimer);
|
|
22
|
+
revertTimer = setTimeout(() => {
|
|
23
|
+
button.textContent = 'Copy';
|
|
24
|
+
button.classList.remove(DEMO_CLASSES.copyButtonCopied);
|
|
25
|
+
}, COPY_FEEDBACK_DURATION_MS);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
codeBlock.appendChild(button);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The examples landing page, rendered from a page registry. A component's
|
|
3
|
+
* index.html shrinks to:
|
|
4
|
+
*
|
|
5
|
+
* <body>
|
|
6
|
+
* <script type="module">
|
|
7
|
+
* import { renderExamplesIndex } from '@keenmate/web-components-devkit/examples';
|
|
8
|
+
* renderExamplesIndex({
|
|
9
|
+
* pages: [
|
|
10
|
+
* { href: 'examples-basic.html', icon: '🧩', title: 'Basic Usage', description: '…' },
|
|
11
|
+
* ],
|
|
12
|
+
* });
|
|
13
|
+
* </script>
|
|
14
|
+
* </body>
|
|
15
|
+
*
|
|
16
|
+
* Title, tagline, version and the GitHub link come from package.json#kmwc via
|
|
17
|
+
* the injected page config. The registry also feeds the cross-page chapter nav.
|
|
18
|
+
*/
|
|
19
|
+
import { getPageConfig } from './page-config.js';
|
|
20
|
+
import { getVersionLabel } from './version-badge.js';
|
|
21
|
+
import { INDEX_PAGE_CLASSES } from './constants.js';
|
|
22
|
+
|
|
23
|
+
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
24
|
+
const mapToEscapedHtml = (text) => String(text ?? '').replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {import('./index.js').ExamplesIndexConfig} indexConfig
|
|
28
|
+
* @returns {HTMLElement} the rendered page wrapper
|
|
29
|
+
*/
|
|
30
|
+
export function renderExamplesIndex(indexConfig) {
|
|
31
|
+
const pageConfig = getPageConfig();
|
|
32
|
+
const pages = indexConfig.pages || [];
|
|
33
|
+
const title = indexConfig.title ?? pageConfig.title;
|
|
34
|
+
const tagline = indexConfig.tagline ?? pageConfig.tagline;
|
|
35
|
+
const footerLinks = [
|
|
36
|
+
...(pageConfig.repository ? [{ label: 'GitHub', href: pageConfig.repository }] : []),
|
|
37
|
+
...(indexConfig.links || []),
|
|
38
|
+
];
|
|
39
|
+
const classes = INDEX_PAGE_CLASSES;
|
|
40
|
+
|
|
41
|
+
document.body.classList.add(classes.body);
|
|
42
|
+
if (!document.title) document.title = `${title} — Examples`;
|
|
43
|
+
|
|
44
|
+
const containerEl = indexConfig.containerEl || document.body;
|
|
45
|
+
const pageElement = document.createElement('div');
|
|
46
|
+
pageElement.className = classes.page;
|
|
47
|
+
pageElement.innerHTML = `
|
|
48
|
+
<header class="${classes.header}">
|
|
49
|
+
<h1>${mapToEscapedHtml(title)}</h1>
|
|
50
|
+
${tagline ? `<p class="${classes.tagline}">${mapToEscapedHtml(tagline)}</p>` : ''}
|
|
51
|
+
<span class="${classes.version}" data-kmwc-version>${mapToEscapedHtml(getVersionLabel(pageConfig))}</span>
|
|
52
|
+
</header>
|
|
53
|
+
<div class="${classes.cards}">
|
|
54
|
+
${pages
|
|
55
|
+
.map(
|
|
56
|
+
(page) => `
|
|
57
|
+
<a href="${mapToEscapedHtml(page.href)}" class="${classes.card}">
|
|
58
|
+
${page.icon ? `<div class="${classes.icon}">${mapToEscapedHtml(page.icon)}</div>` : ''}
|
|
59
|
+
<h2 class="${classes.cardTitle}">${mapToEscapedHtml(page.title)}${page.badge ? ` <span class="${classes.badge}">${mapToEscapedHtml(page.badge)}</span>` : ''}</h2>
|
|
60
|
+
${page.description ? `<p class="${classes.cardDescription}">${mapToEscapedHtml(page.description)}</p>` : ''}
|
|
61
|
+
</a>`
|
|
62
|
+
)
|
|
63
|
+
.join('')}
|
|
64
|
+
</div>
|
|
65
|
+
<footer class="${classes.footer}">
|
|
66
|
+
<p>Created by <a href="https://github.com/keenmate" target="_blank" rel="noopener">Keenmate s.r.o.</a>${footerLinks
|
|
67
|
+
.map((link) => ` | <a href="${mapToEscapedHtml(link.href)}" target="_blank" rel="noopener">${mapToEscapedHtml(link.label)}</a>`)
|
|
68
|
+
.join('')}${
|
|
69
|
+
pageConfig.devkitVersion
|
|
70
|
+
? ` | <a class="${classes.devkit}" href="${mapToEscapedHtml(pageConfig.devkitHomepage)}" target="_blank" rel="noopener">web-components-devkit v${mapToEscapedHtml(pageConfig.devkitVersion)}</a>`
|
|
71
|
+
: ''
|
|
72
|
+
}</p>
|
|
73
|
+
</footer>`;
|
|
74
|
+
containerEl.prepend(pageElement);
|
|
75
|
+
|
|
76
|
+
window.CHAPTER_NAV_ITEMS = pages.map((page) => ({ label: page.title, href: page.href }));
|
|
77
|
+
return pageElement;
|
|
78
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface KmwcPageConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
tagline: string;
|
|
5
|
+
version: string;
|
|
6
|
+
packageName: string;
|
|
7
|
+
buildTimestamp: string | null;
|
|
8
|
+
mode: 'dev' | 'build';
|
|
9
|
+
canonicalOrigin: string;
|
|
10
|
+
storagePrefix: string;
|
|
11
|
+
repository: string;
|
|
12
|
+
homepage: string;
|
|
13
|
+
/** The @keenmate/web-components-devkit version that built the page (shown in the index footer). */
|
|
14
|
+
devkitVersion: string;
|
|
15
|
+
devkitHomepage: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** One card on the examples index (plain data model). */
|
|
19
|
+
export interface ExamplePage {
|
|
20
|
+
href: string;
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
icon?: string;
|
|
24
|
+
badge?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExamplesIndexConfig {
|
|
28
|
+
pages: ExamplePage[];
|
|
29
|
+
title?: string;
|
|
30
|
+
tagline?: string;
|
|
31
|
+
links?: Array<{ label: string; href: string }>;
|
|
32
|
+
/** Where the page is rendered; defaults to <body>. */
|
|
33
|
+
containerEl?: Element;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ChapterNavConfig {
|
|
37
|
+
/** Cross-page jump list (the index passes its registry). */
|
|
38
|
+
items?: Array<{ label: string; href: string }>;
|
|
39
|
+
/** "Back to Examples" target; '' hides it. */
|
|
40
|
+
indexHref?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Section {
|
|
44
|
+
heading: HTMLElement;
|
|
45
|
+
section: HTMLElement;
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type Theme = 'light' | 'dark' | 'auto';
|
|
51
|
+
|
|
52
|
+
export function attachControlsPersistence(pageId: string, root?: ParentNode): { clear(): void };
|
|
53
|
+
export function getControlsStorageKey(pageId: string, storagePrefix?: string): string;
|
|
54
|
+
|
|
55
|
+
export function renderExamplesIndex(indexConfig: ExamplesIndexConfig): HTMLElement;
|
|
56
|
+
|
|
57
|
+
/** Re-mount copy-links + chapter nav + version badges + theme switch after rendering sections late. */
|
|
58
|
+
export function mountExamplesKit(): void;
|
|
59
|
+
export function mountChapterNav(navConfig?: ChapterNavConfig): { unmount(): void } | null;
|
|
60
|
+
|
|
61
|
+
export function attachCopyLinks(): void;
|
|
62
|
+
export function copyText(text: string): Promise<boolean>;
|
|
63
|
+
export function getSectionUrl(sectionId: string, canonicalOrigin?: string): string;
|
|
64
|
+
|
|
65
|
+
export function renderVersionBadges(root?: ParentNode): void;
|
|
66
|
+
export function attachDemoCopyButtons(root?: ParentNode): void;
|
|
67
|
+
export function getVersionLabel(pageConfig?: KmwcPageConfig): string;
|
|
68
|
+
|
|
69
|
+
export function mountThemeSwitch(): { unmount(): void };
|
|
70
|
+
export function applyTheme(theme: Theme): void;
|
|
71
|
+
export function getSavedTheme(): Theme;
|
|
72
|
+
|
|
73
|
+
export function ensureSectionAnchors(root?: Document): Section[];
|
|
74
|
+
export function generateSlug(text: string): string;
|
|
75
|
+
export function getHeadingLabel(heading: Element, fallbackLabel: string): string;
|
|
76
|
+
|
|
77
|
+
export function getPageConfig(): KmwcPageConfig;
|
|
78
|
+
export function attachDomReadyHandler(handler: () => void): void;
|
|
79
|
+
|
|
80
|
+
export const CHAPTER_NAV_CLASSES: Record<string, string>;
|
|
81
|
+
export const COPY_LINK_CLASSES: Record<string, string>;
|
|
82
|
+
export const INDEX_PAGE_CLASSES: Record<string, string>;
|
|
83
|
+
export const THEME_SWITCH_CLASSES: Record<string, string>;
|
|
84
|
+
export const DEMO_CLASSES: Record<string, string>;
|
|
85
|
+
export const THEMES: Theme[];
|
|
86
|
+
export const DEFAULT_THEME: Theme;
|
|
87
|
+
/** id of the chapter-nav panel (the toggle's `aria-controls`). */
|
|
88
|
+
export const CHAPTER_NAV_PANEL_ID: string;
|
|
89
|
+
/** Where sections live, and what counts as one (one `<h2>` each = one chapter). */
|
|
90
|
+
export const SECTION_CONTAINER_SELECTOR: string;
|
|
91
|
+
export const SECTION_SELECTOR: string;
|
|
92
|
+
/** Heading children left out of a chapter label (badges, the copy-link button). */
|
|
93
|
+
export const HEADING_CHIP_SELECTOR: string;
|
|
94
|
+
export const VERSION_BADGE_SELECTOR: string;
|
|
95
|
+
export const DEFAULT_INDEX_HREF: string;
|
|
96
|
+
/** `data-<THEME_ATTRIBUTE>` on `<html>`, and the localStorage key of the saved choice. */
|
|
97
|
+
export const THEME_ATTRIBUTE: string;
|
|
98
|
+
export const THEME_STORAGE_KEY: string;
|
|
99
|
+
export const ACTIVE_CHAPTER_OFFSET_PX: number;
|
|
100
|
+
export const COPY_FEEDBACK_DURATION_MS: number;
|
|
101
|
+
|
|
102
|
+
declare global {
|
|
103
|
+
interface Window {
|
|
104
|
+
__KMWC__?: Partial<KmwcPageConfig>;
|
|
105
|
+
CHAPTER_NAV_ITEMS?: Array<{ label: string; href: string }>;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@keenmate/web-components-devkit/examples` — named exports for example pages.
|
|
3
|
+
* The page-level helpers (styles, chapter nav, copy-links, version badge,
|
|
4
|
+
* theme switch) are mounted automatically by the Vite plugin; import from here
|
|
5
|
+
* only for the opt-in bits (controls persistence, index page) or to drive a
|
|
6
|
+
* helper manually.
|
|
7
|
+
*/
|
|
8
|
+
export { attachControlsPersistence, getControlsStorageKey } from './controls-persistence.js';
|
|
9
|
+
export { renderExamplesIndex } from './index-page.js';
|
|
10
|
+
export { mountExamplesKit } from './kit.js';
|
|
11
|
+
export { mountChapterNav } from './chapter-nav.js';
|
|
12
|
+
export { attachCopyLinks, copyText, getSectionUrl } from './copy-link.js';
|
|
13
|
+
export { renderVersionBadges, getVersionLabel } from './version-badge.js';
|
|
14
|
+
export { attachDemoCopyButtons } from './demo-code.js';
|
|
15
|
+
export { mountThemeSwitch, applyTheme, getSavedTheme } from './theme-switch.js';
|
|
16
|
+
export { ensureSectionAnchors, generateSlug, getHeadingLabel } from './sections.js';
|
|
17
|
+
export { getPageConfig, attachDomReadyHandler } from './page-config.js';
|
|
18
|
+
export * from './constants.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mountChapterNav } from './chapter-nav.js';
|
|
2
|
+
import { attachCopyLinks } from './copy-link.js';
|
|
3
|
+
import { renderVersionBadges } from './version-badge.js';
|
|
4
|
+
import { mountThemeSwitch } from './theme-switch.js';
|
|
5
|
+
import { attachDemoCopyButtons } from './demo-code.js';
|
|
6
|
+
import { INDEX_PAGE_CLASSES } from './constants.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* (Re)mount the page-level helpers on the current DOM. Runs automatically once
|
|
10
|
+
* per page; call it again after rendering sections asynchronously. Idempotent:
|
|
11
|
+
* copy-link / demo copy buttons skip elements that already have one, the nav
|
|
12
|
+
* and the theme switch are remounted.
|
|
13
|
+
*/
|
|
14
|
+
export function mountExamplesKit() {
|
|
15
|
+
// Copy-links before the nav: the nav strips the buttons back out when
|
|
16
|
+
// reading labels, and this order avoids a relayout after the nav is built.
|
|
17
|
+
if (!document.body.classList.contains(INDEX_PAGE_CLASSES.body)) attachCopyLinks();
|
|
18
|
+
mountChapterNav();
|
|
19
|
+
renderVersionBadges();
|
|
20
|
+
attachDemoCopyButtons();
|
|
21
|
+
mountThemeSwitch();
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-level config. The Vite plugin injects `window.__KMWC__` into every
|
|
3
|
+
* example page's <head> (id, title, version, build timestamp, canonical
|
|
4
|
+
* origin, …). Reading it through here gives sane defaults when a page is
|
|
5
|
+
* opened outside the plugin (e.g. a fixture page or a plain static copy).
|
|
6
|
+
*/
|
|
7
|
+
export function getPageConfig() {
|
|
8
|
+
const injectedConfig = (typeof window !== 'undefined' && window.__KMWC__) || {};
|
|
9
|
+
return {
|
|
10
|
+
id: injectedConfig.id || 'component',
|
|
11
|
+
title: injectedConfig.title || document.title || '',
|
|
12
|
+
tagline: injectedConfig.tagline || '',
|
|
13
|
+
version: injectedConfig.version || '',
|
|
14
|
+
packageName: injectedConfig.packageName || '',
|
|
15
|
+
buildTimestamp: injectedConfig.buildTimestamp || null,
|
|
16
|
+
mode: injectedConfig.mode || 'dev',
|
|
17
|
+
canonicalOrigin: injectedConfig.canonicalOrigin || '',
|
|
18
|
+
storagePrefix: injectedConfig.storagePrefix || injectedConfig.id || 'component',
|
|
19
|
+
repository: injectedConfig.repository || '',
|
|
20
|
+
homepage: injectedConfig.homepage || '',
|
|
21
|
+
devkitVersion: injectedConfig.devkitVersion || '',
|
|
22
|
+
devkitHomepage: injectedConfig.devkitHomepage || '',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Run `handler` once the DOM is parsed (immediately if it already is). */
|
|
27
|
+
export function attachDomReadyHandler(handler) {
|
|
28
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', handler, { once: true });
|
|
29
|
+
else handler();
|
|
30
|
+
}
|