@moni-labs/moni-ui 0.2.0 → 0.3.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/README.md +52 -194
- package/custom-elements.json +1636 -350
- package/dist/actions/index.d.ts +6 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +6 -0
- package/dist/components/_base/field-styles.d.ts +51 -16
- package/dist/components/_base/field-styles.d.ts.map +1 -1
- package/dist/components/_base/field-styles.js +164 -36
- package/dist/components/_base/index.d.ts +25 -0
- package/dist/components/_base/index.d.ts.map +1 -1
- package/dist/components/_base/index.js +25 -0
- package/dist/components/_base/interaction-styles.d.ts +39 -12
- package/dist/components/_base/interaction-styles.d.ts.map +1 -1
- package/dist/components/_base/interaction-styles.js +85 -33
- package/dist/components/_base/moni-element.d.ts +43 -8
- package/dist/components/_base/moni-element.d.ts.map +1 -1
- package/dist/components/_base/moni-element.js +43 -8
- package/dist/components/_base/shared-styles.d.ts +41 -17
- package/dist/components/_base/shared-styles.d.ts.map +1 -1
- package/dist/components/_base/shared-styles.js +113 -21
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/loading-shapes.d.ts +6 -0
- package/dist/components/loading-shapes.d.ts.map +1 -1
- package/dist/components/loading-shapes.js +6 -0
- package/dist/components/moni-app-bar.d.ts +128 -33
- package/dist/components/moni-app-bar.d.ts.map +1 -1
- package/dist/components/moni-app-bar.js +121 -26
- package/dist/components/moni-badge.d.ts +122 -14
- package/dist/components/moni-badge.d.ts.map +1 -1
- package/dist/components/moni-badge.js +122 -14
- package/dist/components/moni-bottom-sheet.d.ts +120 -15
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +116 -12
- package/dist/components/moni-button-group.d.ts +53 -27
- package/dist/components/moni-button-group.d.ts.map +1 -1
- package/dist/components/moni-button-group.js +49 -23
- package/dist/components/moni-button-segment.d.ts +28 -8
- package/dist/components/moni-button-segment.d.ts.map +1 -1
- package/dist/components/moni-button-segment.js +27 -7
- package/dist/components/moni-button.d.ts +51 -32
- package/dist/components/moni-button.d.ts.map +1 -1
- package/dist/components/moni-button.js +50 -31
- package/dist/components/moni-card.d.ts +91 -31
- package/dist/components/moni-card.d.ts.map +1 -1
- package/dist/components/moni-card.js +86 -26
- package/dist/components/moni-carousel.d.ts +67 -17
- package/dist/components/moni-carousel.d.ts.map +1 -1
- package/dist/components/moni-carousel.js +59 -16
- package/dist/components/moni-checkbox.d.ts +122 -17
- package/dist/components/moni-checkbox.d.ts.map +1 -1
- package/dist/components/moni-checkbox.js +118 -14
- package/dist/components/moni-chip.d.ts +56 -30
- package/dist/components/moni-chip.d.ts.map +1 -1
- package/dist/components/moni-chip.js +51 -25
- package/dist/components/moni-color-field.d.ts +44 -6
- package/dist/components/moni-color-field.d.ts.map +1 -1
- package/dist/components/moni-color-field.js +43 -5
- package/dist/components/moni-context-menu.d.ts +44 -22
- package/dist/components/moni-context-menu.d.ts.map +1 -1
- package/dist/components/moni-context-menu.js +43 -21
- package/dist/components/moni-dialog.d.ts +107 -15
- package/dist/components/moni-dialog.d.ts.map +1 -1
- package/dist/components/moni-dialog.js +105 -14
- package/dist/components/moni-divider.d.ts +50 -15
- package/dist/components/moni-divider.d.ts.map +1 -1
- package/dist/components/moni-divider.js +49 -14
- package/dist/components/moni-expansion.d.ts +44 -8
- package/dist/components/moni-expansion.d.ts.map +1 -1
- package/dist/components/moni-expansion.js +43 -7
- package/dist/components/moni-fab-menu.d.ts +39 -20
- package/dist/components/moni-fab-menu.d.ts.map +1 -1
- package/dist/components/moni-fab-menu.js +38 -19
- package/dist/components/moni-fab.d.ts +49 -23
- package/dist/components/moni-fab.d.ts.map +1 -1
- package/dist/components/moni-fab.js +46 -20
- package/dist/components/moni-file-field.d.ts +54 -14
- package/dist/components/moni-file-field.d.ts.map +1 -1
- package/dist/components/moni-file-field.js +53 -13
- package/dist/components/moni-icon.d.ts +78 -11
- package/dist/components/moni-icon.d.ts.map +1 -1
- package/dist/components/moni-icon.js +77 -10
- package/dist/components/moni-list-item.d.ts +61 -30
- package/dist/components/moni-list-item.d.ts.map +1 -1
- package/dist/components/moni-list-item.js +55 -24
- package/dist/components/moni-list.d.ts +37 -13
- package/dist/components/moni-list.d.ts.map +1 -1
- package/dist/components/moni-list.js +36 -12
- package/dist/components/moni-loading-indicator.d.ts +38 -11
- package/dist/components/moni-loading-indicator.d.ts.map +1 -1
- package/dist/components/moni-loading-indicator.js +37 -10
- package/dist/components/moni-menu-item.d.ts +31 -8
- package/dist/components/moni-menu-item.d.ts.map +1 -1
- package/dist/components/moni-menu-item.js +30 -7
- package/dist/components/moni-menu.d.ts +58 -33
- package/dist/components/moni-menu.d.ts.map +1 -1
- package/dist/components/moni-menu.js +51 -26
- package/dist/components/moni-morph-modal.d.ts +7 -1
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +46 -24
- package/dist/components/moni-nav-item.d.ts +50 -10
- package/dist/components/moni-nav-item.d.ts.map +1 -1
- package/dist/components/moni-nav-item.js +48 -8
- package/dist/components/moni-nav.d.ts +57 -22
- package/dist/components/moni-nav.d.ts.map +1 -1
- package/dist/components/moni-nav.js +53 -18
- package/dist/components/moni-progress.d.ts +108 -20
- package/dist/components/moni-progress.d.ts.map +1 -1
- package/dist/components/moni-progress.js +104 -16
- package/dist/components/moni-radio.d.ts +106 -14
- package/dist/components/moni-radio.d.ts.map +1 -1
- package/dist/components/moni-radio.js +104 -13
- package/dist/components/moni-ripple.d.ts +121 -10
- package/dist/components/moni-ripple.d.ts.map +1 -1
- package/dist/components/moni-ripple.js +120 -9
- package/dist/components/moni-segmented-button.d.ts +31 -11
- package/dist/components/moni-segmented-button.d.ts.map +1 -1
- package/dist/components/moni-segmented-button.js +30 -10
- package/dist/components/moni-select-option.d.ts +43 -9
- package/dist/components/moni-select-option.d.ts.map +1 -1
- package/dist/components/moni-select-option.js +41 -7
- package/dist/components/moni-select.d.ts +59 -2
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +58 -1
- package/dist/components/moni-shape.d.ts +1 -1
- package/dist/components/moni-side-sheet.d.ts +56 -19
- package/dist/components/moni-side-sheet.d.ts.map +1 -1
- package/dist/components/moni-side-sheet.js +53 -16
- package/dist/components/moni-slider.d.ts +56 -25
- package/dist/components/moni-slider.d.ts.map +1 -1
- package/dist/components/moni-slider.js +55 -24
- package/dist/components/moni-snackbar.d.ts +86 -17
- package/dist/components/moni-snackbar.d.ts.map +1 -1
- package/dist/components/moni-snackbar.js +85 -16
- package/dist/components/moni-split-button.d.ts +38 -9
- package/dist/components/moni-split-button.d.ts.map +1 -1
- package/dist/components/moni-split-button.js +37 -8
- package/dist/components/moni-step.d.ts +42 -9
- package/dist/components/moni-step.d.ts.map +1 -1
- package/dist/components/moni-step.js +41 -8
- package/dist/components/moni-stepper.d.ts +43 -6
- package/dist/components/moni-stepper.d.ts.map +1 -1
- package/dist/components/moni-stepper.js +42 -5
- package/dist/components/moni-switch.d.ts +103 -16
- package/dist/components/moni-switch.d.ts.map +1 -1
- package/dist/components/moni-switch.js +99 -13
- package/dist/components/moni-tab.d.ts +35 -8
- package/dist/components/moni-tab.d.ts.map +1 -1
- package/dist/components/moni-tab.js +34 -7
- package/dist/components/moni-tabs.d.ts +51 -13
- package/dist/components/moni-tabs.d.ts.map +1 -1
- package/dist/components/moni-tabs.js +48 -10
- package/dist/components/moni-text-field.d.ts +55 -10
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +54 -9
- package/dist/components/moni-textarea.d.ts +51 -21
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +48 -18
- package/dist/components/moni-time-picker.d.ts +41 -11
- package/dist/components/moni-time-picker.d.ts.map +1 -1
- package/dist/components/moni-time-picker.js +40 -10
- package/dist/components/moni-toolbar.d.ts +43 -15
- package/dist/components/moni-toolbar.d.ts.map +1 -1
- package/dist/components/moni-toolbar.js +42 -14
- package/dist/components/moni-tooltip.d.ts +55 -25
- package/dist/components/moni-tooltip.d.ts.map +1 -1
- package/dist/components/moni-tooltip.js +54 -24
- package/dist/components/moni-typography.d.ts +43 -18
- package/dist/components/moni-typography.d.ts.map +1 -1
- package/dist/components/moni-typography.js +42 -17
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -2
- package/dist/styles/tailwind.css +67 -0
- package/dist/styles/tokens.css +111 -99
- package/dist/utils/color.d.ts +181 -2
- package/dist/utils/color.d.ts.map +1 -1
- package/dist/utils/color.js +182 -4
- package/dist/utils/theme.svelte.d.ts +305 -2
- package/dist/utils/theme.svelte.d.ts.map +1 -1
- package/dist/utils/theme.svelte.js +331 -2
- package/dist/web-components.d.ts +28 -0
- package/dist/web-components.d.ts.map +1 -1
- package/dist/web-components.js +29 -2
- package/package.json +1 -1
- package/src/actions/index.ts +7 -0
- package/src/components/_base/field-styles.ts +165 -37
- package/src/components/_base/index.ts +27 -0
- package/src/components/_base/interaction-styles.ts +86 -33
- package/src/components/_base/moni-element.ts +44 -8
- package/src/components/_base/shared-styles.ts +114 -21
- package/src/components/index.ts +7 -0
- package/src/components/loading-shapes.ts +7 -0
- package/src/components/moni-app-bar.ts +127 -26
- package/src/components/moni-badge.ts +128 -14
- package/src/components/moni-bottom-sheet.ts +125 -13
- package/src/components/moni-button-group.ts +50 -23
- package/src/components/moni-button-segment.ts +28 -7
- package/src/components/moni-button.ts +51 -31
- package/src/components/moni-card.ts +90 -26
- package/src/components/moni-carousel.ts +67 -16
- package/src/components/moni-checkbox.ts +125 -14
- package/src/components/moni-chip.ts +52 -25
- package/src/components/moni-color-field.ts +44 -5
- package/src/components/moni-context-menu.ts +44 -21
- package/src/components/moni-dialog.ts +111 -14
- package/src/components/moni-divider.ts +50 -14
- package/src/components/moni-expansion.ts +44 -7
- package/src/components/moni-fab-menu.ts +39 -19
- package/src/components/moni-fab.ts +47 -20
- package/src/components/moni-file-field.ts +54 -13
- package/src/components/moni-icon.ts +80 -10
- package/src/components/moni-list-item.ts +56 -24
- package/src/components/moni-list.ts +37 -12
- package/src/components/moni-loading-indicator.ts +38 -10
- package/src/components/moni-menu-item.ts +31 -7
- package/src/components/moni-menu.ts +52 -26
- package/src/components/moni-morph-modal.ts +58 -24
- package/src/components/moni-nav-item.ts +49 -8
- package/src/components/moni-nav.ts +54 -18
- package/src/components/moni-progress.ts +109 -16
- package/src/components/moni-radio.ts +111 -13
- package/src/components/moni-ripple.ts +126 -9
- package/src/components/moni-segmented-button.ts +31 -10
- package/src/components/moni-select-option.ts +42 -7
- package/src/components/moni-select.ts +79 -1
- package/src/components/moni-side-sheet.ts +54 -16
- package/src/components/moni-slider.ts +56 -24
- package/src/components/moni-snackbar.ts +90 -16
- package/src/components/moni-split-button.ts +38 -8
- package/src/components/moni-step.ts +42 -8
- package/src/components/moni-stepper.ts +43 -5
- package/src/components/moni-switch.ts +106 -13
- package/src/components/moni-tab.ts +35 -7
- package/src/components/moni-tabs.ts +49 -10
- package/src/components/moni-text-field.ts +55 -9
- package/src/components/moni-textarea.ts +49 -18
- package/src/components/moni-time-picker.ts +41 -10
- package/src/components/moni-toolbar.ts +43 -14
- package/src/components/moni-tooltip.ts +55 -24
- package/src/components/moni-typography.ts +43 -17
- package/src/index.ts +67 -3
- package/src/styles/tailwind.css +67 -0
- package/src/styles/tokens.css +111 -99
- package/src/types/svelte-runes.d.ts +64 -2
- package/src/utils/color.ts +286 -5
- package/src/utils/theme.svelte.ts +411 -2
- package/src/web-components.ts +31 -2
- package/dist/assets/shapes/arch.svg +0 -1
- package/dist/assets/shapes/arrow.svg +0 -1
- package/dist/assets/shapes/boom.svg +0 -1
- package/dist/assets/shapes/burst.svg +0 -1
- package/dist/assets/shapes/circle.svg +0 -1
- package/dist/assets/shapes/clamshell.svg +0 -1
- package/dist/assets/shapes/diamond.svg +0 -1
- package/dist/assets/shapes/fan.svg +0 -1
- package/dist/assets/shapes/flower.svg +0 -1
- package/dist/assets/shapes/gem.svg +0 -1
- package/dist/assets/shapes/ghost-ish.svg +0 -1
- package/dist/assets/shapes/heart.svg +0 -1
- package/dist/assets/shapes/leaf-clover4.svg +0 -1
- package/dist/assets/shapes/leaf-clover8.svg +0 -1
- package/dist/assets/shapes/loading-indicator.svg +0 -1
- package/dist/assets/shapes/oval.svg +0 -1
- package/dist/assets/shapes/pentagon.svg +0 -1
- package/dist/assets/shapes/pill.svg +0 -1
- package/dist/assets/shapes/pixel-circle.svg +0 -1
- package/dist/assets/shapes/pixel-triangle.svg +0 -1
- package/dist/assets/shapes/puffy-diamond.svg +0 -1
- package/dist/assets/shapes/puffy.svg +0 -1
- package/dist/assets/shapes/semicircle.svg +0 -1
- package/dist/assets/shapes/sided-cookie12.svg +0 -1
- package/dist/assets/shapes/sided-cookie4.svg +0 -1
- package/dist/assets/shapes/sided-cookie6.svg +0 -1
- package/dist/assets/shapes/sided-cookie7.svg +0 -1
- package/dist/assets/shapes/sided-cookie9.svg +0 -1
- package/dist/assets/shapes/slanted.svg +0 -1
- package/dist/assets/shapes/soft-boom.svg +0 -1
- package/dist/assets/shapes/soft-burst.svg +0 -1
- package/dist/assets/shapes/square.svg +0 -1
- package/dist/assets/shapes/sunny.svg +0 -1
- package/dist/assets/shapes/triangle.svg +0 -1
- package/dist/assets/shapes/very-sunny.svg +0 -1
- package/dist/assets/shapes/wavy-circle.svg +0 -1
- package/dist/assets/shapes/wavy.svg +0 -1
- package/src/assets/shapes/arch.svg +0 -1
- package/src/assets/shapes/arrow.svg +0 -1
- package/src/assets/shapes/boom.svg +0 -1
- package/src/assets/shapes/burst.svg +0 -1
- package/src/assets/shapes/circle.svg +0 -1
- package/src/assets/shapes/clamshell.svg +0 -1
- package/src/assets/shapes/diamond.svg +0 -1
- package/src/assets/shapes/fan.svg +0 -1
- package/src/assets/shapes/flower.svg +0 -1
- package/src/assets/shapes/gem.svg +0 -1
- package/src/assets/shapes/ghost-ish.svg +0 -1
- package/src/assets/shapes/heart.svg +0 -1
- package/src/assets/shapes/leaf-clover4.svg +0 -1
- package/src/assets/shapes/leaf-clover8.svg +0 -1
- package/src/assets/shapes/loading-indicator.svg +0 -1
- package/src/assets/shapes/oval.svg +0 -1
- package/src/assets/shapes/pentagon.svg +0 -1
- package/src/assets/shapes/pill.svg +0 -1
- package/src/assets/shapes/pixel-circle.svg +0 -1
- package/src/assets/shapes/pixel-triangle.svg +0 -1
- package/src/assets/shapes/puffy-diamond.svg +0 -1
- package/src/assets/shapes/puffy.svg +0 -1
- package/src/assets/shapes/semicircle.svg +0 -1
- package/src/assets/shapes/sided-cookie12.svg +0 -1
- package/src/assets/shapes/sided-cookie4.svg +0 -1
- package/src/assets/shapes/sided-cookie6.svg +0 -1
- package/src/assets/shapes/sided-cookie7.svg +0 -1
- package/src/assets/shapes/sided-cookie9.svg +0 -1
- package/src/assets/shapes/slanted.svg +0 -1
- package/src/assets/shapes/soft-boom.svg +0 -1
- package/src/assets/shapes/soft-burst.svg +0 -1
- package/src/assets/shapes/square.svg +0 -1
- package/src/assets/shapes/sunny.svg +0 -1
- package/src/assets/shapes/triangle.svg +0 -1
- package/src/assets/shapes/very-sunny.svg +0 -1
- package/src/assets/shapes/wavy-circle.svg +0 -1
- package/src/assets/shapes/wavy.svg +0 -1
package/src/utils/color.ts
CHANGED
|
@@ -1,69 +1,218 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @file utils/color.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @module color
|
|
10
|
+
*
|
|
11
|
+
* Color utilities for generating and applying Material Design 3–compliant
|
|
12
|
+
* dynamic color schemes.
|
|
13
|
+
*
|
|
14
|
+
* This module wraps `@material/material-color-utilities` to expose a
|
|
15
|
+
* simplified, opinionated API that covers the full Moni theming pipeline:
|
|
16
|
+
*
|
|
17
|
+
* 1. Convert a user-supplied "seed" hex color to the HCT color space.
|
|
18
|
+
* 2. Generate a complete 27-role `ColorScheme` via `SchemeTonalSpot`.
|
|
19
|
+
* 3. Apply the scheme to the document root as CSS custom properties under
|
|
20
|
+
* three namespaces (`--md-sys-color-*`, `--moni-*`, and bare `--*`)
|
|
21
|
+
* so any component or stylesheet can consume the tokens.
|
|
22
|
+
*
|
|
23
|
+
* **Contrast levels** map to the MD3 contrast spec:
|
|
24
|
+
* - `standard` → 0.0 (WCAG AA baseline)
|
|
25
|
+
* - `medium` → 0.5 (enhanced readability)
|
|
26
|
+
* - `high` → 1.0 (WCAG AAA, maximum legibility)
|
|
27
|
+
*
|
|
28
|
+
* @see https://m3.material.io/styles/color/dynamic-color/overview
|
|
29
|
+
* @see https://github.com/material-foundation/material-color-utilities
|
|
4
30
|
*/
|
|
5
31
|
|
|
6
32
|
import { SchemeTonalSpot, Hct, argbFromHex, hexFromArgb } from '@material/material-color-utilities';
|
|
7
33
|
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
// Types
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* WCAG contrast enhancement level for a generated color scheme.
|
|
40
|
+
*
|
|
41
|
+
* Maps to the `contrastLevel` argument of `SchemeTonalSpot`:
|
|
42
|
+
* - `'standard'` → 0.0 — WCAG AA minimum contrast. Best for everyday UIs.
|
|
43
|
+
* - `'medium'` → 0.5 — Raised contrast. Useful for users with mild low-vision.
|
|
44
|
+
* - `'high'` → 1.0 — WCAG AAA maximum contrast. Required for accessibility-first apps.
|
|
45
|
+
*/
|
|
8
46
|
export type ContrastLevel = 'standard' | 'medium' | 'high';
|
|
9
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Hue–Saturation–Lightness color representation.
|
|
50
|
+
*
|
|
51
|
+
* All values use the CSS `hsl()` convention:
|
|
52
|
+
* - `h` — hue angle in **degrees**, range [0, 360).
|
|
53
|
+
* - `s` — saturation as a **percentage**, range [0, 100].
|
|
54
|
+
* - `l` — lightness as a **percentage**, range [0, 100].
|
|
55
|
+
*/
|
|
10
56
|
export interface HSL {
|
|
57
|
+
/** Hue angle in degrees [0, 360). */
|
|
11
58
|
h: number;
|
|
59
|
+
/** Saturation percentage [0, 100]. */
|
|
12
60
|
s: number;
|
|
61
|
+
/** Lightness percentage [0, 100]. */
|
|
13
62
|
l: number;
|
|
14
63
|
}
|
|
15
64
|
|
|
65
|
+
/**
|
|
66
|
+
* A complete Material Design 3 color scheme with 27 semantic color roles.
|
|
67
|
+
*
|
|
68
|
+
* Every role is expressed as a 7-character hex string (e.g. `'#6750a4'`).
|
|
69
|
+
* The naming convention follows the MD3 spec directly:
|
|
70
|
+
* - `primary` / `onPrimary` — main brand color and its contrast pair.
|
|
71
|
+
* - `primaryContainer` / `onPrimaryContainer` — lower-emphasis filled containers.
|
|
72
|
+
* - `secondary`, `tertiary`, `error` — supporting accent palettes.
|
|
73
|
+
* - `surface*` — background hierarchy (5 tones).
|
|
74
|
+
* - `inverse*` — used in snackbars and tooltips.
|
|
75
|
+
* - `shadow` — drop-shadow tint.
|
|
76
|
+
*
|
|
77
|
+
* @see https://m3.material.io/styles/color/roles
|
|
78
|
+
*/
|
|
16
79
|
export interface ColorScheme {
|
|
80
|
+
// ── Primary ────────────────────────────────────────────────────────────
|
|
81
|
+
/** High-emphasis brand color. Used for filled buttons, FABs, active indicators. */
|
|
17
82
|
primary: string;
|
|
83
|
+
/** Text/icon color that guarantees contrast on top of `primary`. */
|
|
18
84
|
onPrimary: string;
|
|
85
|
+
/** Lower-emphasis container using the primary palette (tone 90 light / tone 30 dark). */
|
|
19
86
|
primaryContainer: string;
|
|
87
|
+
/** Text/icon color that guarantees contrast on top of `primaryContainer`. */
|
|
20
88
|
onPrimaryContainer: string;
|
|
89
|
+
|
|
90
|
+
// ── Secondary ──────────────────────────────────────────────────────────
|
|
91
|
+
/** Complementary accent color, less prominent than primary. */
|
|
21
92
|
secondary: string;
|
|
93
|
+
/** Text/icon color that guarantees contrast on top of `secondary`. */
|
|
22
94
|
onSecondary: string;
|
|
95
|
+
/** Lower-emphasis container using the secondary palette. */
|
|
23
96
|
secondaryContainer: string;
|
|
97
|
+
/** Text/icon color that guarantees contrast on top of `secondaryContainer`. */
|
|
24
98
|
onSecondaryContainer: string;
|
|
99
|
+
|
|
100
|
+
// ── Tertiary ───────────────────────────────────────────────────────────
|
|
101
|
+
/** Optional third accent color for decorative elements. */
|
|
25
102
|
tertiary: string;
|
|
103
|
+
/** Text/icon color that guarantees contrast on top of `tertiary`. */
|
|
26
104
|
onTertiary: string;
|
|
105
|
+
/** Lower-emphasis container using the tertiary palette. */
|
|
27
106
|
tertiaryContainer: string;
|
|
107
|
+
/** Text/icon color that guarantees contrast on top of `tertiaryContainer`. */
|
|
28
108
|
onTertiaryContainer: string;
|
|
109
|
+
|
|
110
|
+
// ── Error ──────────────────────────────────────────────────────────────
|
|
111
|
+
/** Standard error/destructive color (red family by design). */
|
|
29
112
|
error: string;
|
|
113
|
+
/** Text/icon color that guarantees contrast on top of `error`. */
|
|
30
114
|
onError: string;
|
|
115
|
+
/** Lower-emphasis container for error states. */
|
|
31
116
|
errorContainer: string;
|
|
117
|
+
/** Text/icon color that guarantees contrast on top of `errorContainer`. */
|
|
32
118
|
onErrorContainer: string;
|
|
119
|
+
|
|
120
|
+
// ── Background & Surface ───────────────────────────────────────────────
|
|
121
|
+
/** Page-level background (typically equivalent to `surface` in M3). */
|
|
33
122
|
background: string;
|
|
123
|
+
/** Default text/icon color on top of `background`. */
|
|
34
124
|
onBackground: string;
|
|
125
|
+
/** Base surface color used for sheets, cards, and dialogs. */
|
|
35
126
|
surface: string;
|
|
127
|
+
/** Default text/icon color on top of `surface`. */
|
|
36
128
|
onSurface: string;
|
|
129
|
+
/** Subtle variant of surface used for chips, text field backgrounds, etc. */
|
|
37
130
|
surfaceVariant: string;
|
|
131
|
+
/** Text/icon color that guarantees contrast on top of `surfaceVariant`. */
|
|
38
132
|
onSurfaceVariant: string;
|
|
133
|
+
|
|
134
|
+
// ── Outline ────────────────────────────────────────────────────────────
|
|
135
|
+
/** Border color for fields, dividers, and outlined components. */
|
|
39
136
|
outline: string;
|
|
137
|
+
/** Lower-emphasis border color; used for decorative separators. */
|
|
40
138
|
outlineVariant: string;
|
|
139
|
+
|
|
140
|
+
// ── Surface Container Hierarchy (tone scale) ───────────────────────────
|
|
141
|
+
/** Lightest container tone (lowest elevation). */
|
|
41
142
|
surfaceContainerLowest: string;
|
|
143
|
+
/** Second lightest container tone. */
|
|
42
144
|
surfaceContainerLow: string;
|
|
145
|
+
/** Mid-range container tone (default cards, sheets). */
|
|
43
146
|
surfaceContainer: string;
|
|
147
|
+
/** Second darkest container tone. */
|
|
44
148
|
surfaceContainerHigh: string;
|
|
149
|
+
/** Darkest container tone (highest elevation equivalent). */
|
|
45
150
|
surfaceContainerHighest: string;
|
|
151
|
+
|
|
152
|
+
// ── Inverse ────────────────────────────────────────────────────────────
|
|
153
|
+
/** Inverse of `surface`; used for snackbar and tooltip backgrounds. */
|
|
46
154
|
inverseSurface: string;
|
|
155
|
+
/** Text/icon color that guarantees contrast on top of `inverseSurface`. */
|
|
47
156
|
inverseOnSurface: string;
|
|
157
|
+
/** Used for action elements (links, buttons) inside inverse surfaces. */
|
|
48
158
|
inversePrimary: string;
|
|
159
|
+
|
|
160
|
+
// ── Shadow ─────────────────────────────────────────────────────────────
|
|
161
|
+
/** Color tint used as the drop-shadow color for elevation. */
|
|
49
162
|
shadow: string;
|
|
50
163
|
}
|
|
51
164
|
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
// Color conversion utilities
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Converts a 6-digit hex color string to the HSL color model.
|
|
171
|
+
*
|
|
172
|
+
* Normalizes the input (strips optional `#`), parses each 8-bit RGB channel
|
|
173
|
+
* into a [0, 1] float, then applies the standard RGB→HSL algorithm:
|
|
174
|
+
*
|
|
175
|
+
* - Lightness = (max + min) / 2
|
|
176
|
+
* - Saturation = delta / (2 − max − min) if L > 0.5
|
|
177
|
+
* = delta / (max + min) otherwise
|
|
178
|
+
* - Hue = derived from which channel is the maximum
|
|
179
|
+
*
|
|
180
|
+
* When the color is achromatic (r = g = b), hue and saturation are both 0.
|
|
181
|
+
*
|
|
182
|
+
* @param hex - A 6-digit hex color, with or without the `#` prefix.
|
|
183
|
+
* Examples: `'#4f46e5'`, `'4f46e5'`.
|
|
184
|
+
* @returns An {@link HSL} object where `h` ∈ [0, 360), `s` ∈ [0, 100], `l` ∈ [0, 100].
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* hexToHsl('#ff0000') // { h: 0, s: 100, l: 50 }
|
|
188
|
+
* hexToHsl('#ffffff') // { h: 0, s: 0, l: 100 }
|
|
189
|
+
* hexToHsl('#4f46e5') // { h: 243, s: 73, l: 59 } (approx)
|
|
190
|
+
*/
|
|
52
191
|
export function hexToHsl(hex: string): HSL {
|
|
53
192
|
const normalized = hex.replace('#', '');
|
|
193
|
+
|
|
194
|
+
// Parse each 8-bit channel and normalize to [0, 1].
|
|
54
195
|
const r = parseInt(normalized.substring(0, 2), 16) / 255;
|
|
55
196
|
const g = parseInt(normalized.substring(2, 4), 16) / 255;
|
|
56
197
|
const b = parseInt(normalized.substring(4, 6), 16) / 255;
|
|
57
198
|
|
|
58
199
|
const max = Math.max(r, g, b);
|
|
59
200
|
const min = Math.min(r, g, b);
|
|
201
|
+
|
|
202
|
+
// Initialize hue and saturation; they stay 0 for achromatic colors.
|
|
60
203
|
let h = 0;
|
|
61
204
|
let s = 0;
|
|
205
|
+
|
|
206
|
+
// Lightness is always the midpoint between brightest and darkest channel.
|
|
62
207
|
const l = (max + min) / 2;
|
|
63
208
|
|
|
64
209
|
if (max !== min) {
|
|
65
210
|
const d = max - min;
|
|
211
|
+
|
|
212
|
+
// Saturation formula differs above and below the 50% lightness threshold.
|
|
66
213
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
214
|
+
|
|
215
|
+
// Hue is determined by which channel dominates.
|
|
67
216
|
switch (max) {
|
|
68
217
|
case r:
|
|
69
218
|
h = (g - b) / d + (g < b ? 6 : 0);
|
|
@@ -75,37 +224,109 @@ export function hexToHsl(hex: string): HSL {
|
|
|
75
224
|
h = (r - g) / d + 4;
|
|
76
225
|
break;
|
|
77
226
|
}
|
|
227
|
+
// Normalize hue from a 0–6 sector index to degrees [0, 360).
|
|
78
228
|
h /= 6;
|
|
79
229
|
}
|
|
80
230
|
|
|
81
231
|
return { h: h * 360, s: s * 100, l: l * 100 };
|
|
82
232
|
}
|
|
83
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Converts an HSL color to a 6-digit hex string.
|
|
236
|
+
*
|
|
237
|
+
* Uses the CSS Color Level 4 reference algorithm. Internally computes R, G,
|
|
238
|
+
* and B via a piecewise function `f(n)` that maps chroma sector offsets to
|
|
239
|
+
* individual channel values.
|
|
240
|
+
*
|
|
241
|
+
* @param hsl - An {@link HSL} object where `h` ∈ [0, 360), `s` ∈ [0, 100], `l` ∈ [0, 100].
|
|
242
|
+
* @returns A lowercase 7-character hex string including the `#` prefix (e.g. `'#4f46e5'`).
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* hslToHex({ h: 0, s: 100, l: 50 }) // '#ff0000'
|
|
246
|
+
* hslToHex({ h: 0, s: 0, l: 100 }) // '#ffffff'
|
|
247
|
+
*/
|
|
84
248
|
export function hslToHex({ h, s, l }: HSL): string {
|
|
249
|
+
// Normalize percentages to [0, 1] for the algorithm.
|
|
85
250
|
s /= 100;
|
|
86
251
|
l /= 100;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Sector index helper. Maps the offset `n` (0, 4, 8) to a position within
|
|
255
|
+
* the 12-sector hue wheel (each sector = 30°).
|
|
256
|
+
*/
|
|
87
257
|
const k = (n: number) => (n + h / 30) % 12;
|
|
258
|
+
|
|
259
|
+
/** Chroma magnitude: 0 at lightness extremes, max at L=0.5. */
|
|
88
260
|
const a = s * Math.min(l, 1 - l);
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Per-channel computation. Returns a 2-character hex string for the
|
|
264
|
+
* channel corresponding to sector offset `n` (0=R, 8=G, 4=B).
|
|
265
|
+
*/
|
|
89
266
|
const f = (n: number) => {
|
|
90
267
|
const color = l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
91
268
|
return Math.round(color * 255)
|
|
92
269
|
.toString(16)
|
|
93
270
|
.padStart(2, '0');
|
|
94
271
|
};
|
|
272
|
+
|
|
95
273
|
return `#${f(0)}${f(8)}${f(4)}`;
|
|
96
274
|
}
|
|
97
275
|
|
|
276
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
+
// Scheme generation
|
|
278
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
279
|
|
|
99
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Generates a complete Material Design 3 color scheme from a seed hex color.
|
|
282
|
+
*
|
|
283
|
+
* Uses `SchemeTonalSpot` — the standard M3 dynamic color algorithm — which
|
|
284
|
+
* derives all 27 color roles from a single source hue while respecting the
|
|
285
|
+
* target lightness/contrast requirements for each role.
|
|
286
|
+
*
|
|
287
|
+
* **Algorithm pipeline:**
|
|
288
|
+
* 1. Parse `seedHex` → ARGB integer via `argbFromHex`.
|
|
289
|
+
* 2. Convert ARGB → HCT (Hue–Chroma–Tone) color space.
|
|
290
|
+
* 3. Construct a `SchemeTonalSpot` with the requested mode and contrast level.
|
|
291
|
+
* 4. Extract each scheme role via `hexFromArgb` and build the {@link ColorScheme}.
|
|
292
|
+
*
|
|
293
|
+
* @param seedHex - The source/brand color as a hex string. The `#` prefix
|
|
294
|
+
* is optional (e.g. `'#4f46e5'` or `'4f46e5'`).
|
|
295
|
+
* @param mode - Color scheme polarity. `'light'` produces light backgrounds
|
|
296
|
+
* with dark text; `'dark'` inverts the tone mapping.
|
|
297
|
+
* Defaults to `'light'`.
|
|
298
|
+
* @param contrast - WCAG contrast enhancement level. Defaults to `'standard'`.
|
|
299
|
+
*
|
|
300
|
+
* @returns A fully populated {@link ColorScheme} where every value is a
|
|
301
|
+
* lowercase 7-character hex string (e.g. `'#6750a4'`).
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* // Generate a standard light scheme from an indigo seed.
|
|
305
|
+
* const scheme = generateScheme('#4f46e5');
|
|
306
|
+
* console.log(scheme.primary); // e.g. '#5046e4'
|
|
307
|
+
* console.log(scheme.primaryContainer); // e.g. '#e9e7ff'
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* // High-contrast dark scheme.
|
|
311
|
+
* const darkHC = generateScheme('#4f46e5', 'dark', 'high');
|
|
312
|
+
*/
|
|
100
313
|
export function generateScheme(
|
|
101
314
|
seedHex: string,
|
|
102
315
|
mode: 'light' | 'dark' = 'light',
|
|
103
316
|
contrast: ContrastLevel = 'standard'
|
|
104
317
|
): ColorScheme {
|
|
318
|
+
// Ensure the seed always has a leading '#' for argbFromHex.
|
|
105
319
|
const seed = seedHex.startsWith('#') ? seedHex : `#${seedHex}`;
|
|
320
|
+
|
|
321
|
+
// Step 1: Convert hex → ARGB integer.
|
|
106
322
|
const argb = argbFromHex(seed);
|
|
323
|
+
|
|
324
|
+
// Step 2: Convert ARGB → HCT. HCT is the perceptual color space used
|
|
325
|
+
// internally by the MD3 algorithm; it preserves human color perception
|
|
326
|
+
// better than RGB or HSL for palette generation.
|
|
107
327
|
const hct = Hct.fromInt(argb);
|
|
108
328
|
|
|
329
|
+
// Step 3: Map the ContrastLevel union to a numeric value expected by the SDK.
|
|
109
330
|
let contrastLevel = 0.0;
|
|
110
331
|
if (contrast === 'medium') {
|
|
111
332
|
contrastLevel = 0.5;
|
|
@@ -113,8 +334,11 @@ export function generateScheme(
|
|
|
113
334
|
contrastLevel = 1.0;
|
|
114
335
|
}
|
|
115
336
|
|
|
337
|
+
// Step 4: Build the tonal-spot scheme. isDark=true shifts every tone value
|
|
338
|
+
// to keep roles readable on dark backgrounds.
|
|
116
339
|
const scheme = new SchemeTonalSpot(hct, mode === 'dark', contrastLevel);
|
|
117
340
|
|
|
341
|
+
// Step 5: Extract every color role from the scheme and convert back to hex.
|
|
118
342
|
return {
|
|
119
343
|
primary: hexFromArgb(scheme.primary),
|
|
120
344
|
onPrimary: hexFromArgb(scheme.onPrimary),
|
|
@@ -152,19 +376,76 @@ export function generateScheme(
|
|
|
152
376
|
};
|
|
153
377
|
}
|
|
154
378
|
|
|
379
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
380
|
+
// DOM integration
|
|
381
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Writes a {@link ColorScheme} to the document root as CSS custom properties.
|
|
385
|
+
*
|
|
386
|
+
* For each color role in the scheme, **three aliases** are set on
|
|
387
|
+
* `document.documentElement` so every context can consume the tokens:
|
|
388
|
+
*
|
|
389
|
+
* | Namespace prefix | Example | Consumer |
|
|
390
|
+
* |----------------------|----------------------------------|-----------------------|
|
|
391
|
+
* | `--md-sys-color-` | `--md-sys-color-primary` | MD3 spec / third-party|
|
|
392
|
+
* | `--moni-` | `--moni-primary` | Moni components |
|
|
393
|
+
* | `--` (bare) | `--primary` | BeerCSS / legacy |
|
|
394
|
+
*
|
|
395
|
+
* camelCase role names are converted to kebab-case automatically
|
|
396
|
+
* (e.g. `primaryContainer` → `primary-container`).
|
|
397
|
+
*
|
|
398
|
+
* Additionally sets `data-theme="light|dark"` on `<html>` so CSS can use
|
|
399
|
+
* `:root[data-theme='dark']` selectors for any additional overrides.
|
|
400
|
+
*
|
|
401
|
+
* **Side effects:** Mutates `doc.documentElement.style` and attributes.
|
|
402
|
+
* This function should only be called in browser environments.
|
|
403
|
+
*
|
|
404
|
+
* @param scheme - The color scheme to apply, as produced by {@link generateScheme}.
|
|
405
|
+
* @param mode - The resolved mode (`'light'` or `'dark'`); written to `data-theme`.
|
|
406
|
+
* @param doc - The `Document` to target. Pass `document` in normal usage;
|
|
407
|
+
* accepts a custom document for iframe or SSR scenarios.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* const scheme = generateScheme('#4f46e5', 'dark');
|
|
411
|
+
* applySchemeToDocument(scheme, 'dark', document);
|
|
412
|
+
* // <html data-theme="dark" style="--md-sys-color-primary: #...; --moni-primary: #...; --primary: #...">
|
|
413
|
+
*/
|
|
155
414
|
export function applySchemeToDocument(scheme: ColorScheme, mode: 'light' | 'dark', doc: Document) {
|
|
156
415
|
const root = doc.documentElement;
|
|
416
|
+
|
|
417
|
+
// MD3 canonical namespace prefix.
|
|
157
418
|
const prefix = '--md-sys-color-';
|
|
158
419
|
|
|
159
420
|
for (const [key, value] of Object.entries(scheme)) {
|
|
421
|
+
// Convert camelCase → kebab-case (e.g. 'onPrimary' → 'on-primary').
|
|
160
422
|
const kebab = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
161
|
-
|
|
162
|
-
|
|
423
|
+
|
|
424
|
+
// Set all three namespace aliases simultaneously.
|
|
425
|
+
root.style.setProperty(`${prefix}${kebab}`, value); // MD3 spec namespace
|
|
426
|
+
root.style.setProperty(`--moni-${kebab}`, value); // Moni namespace
|
|
427
|
+
root.style.setProperty(`--${kebab}`, value); // Bare/BeerCSS namespace
|
|
163
428
|
}
|
|
164
429
|
|
|
430
|
+
// Mark the document with the resolved mode for CSS `[data-theme]` selectors.
|
|
165
431
|
root.setAttribute('data-theme', mode);
|
|
166
432
|
}
|
|
167
433
|
|
|
434
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
435
|
+
// Defaults
|
|
436
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Returns the default seed color used by the Moni design system.
|
|
440
|
+
*
|
|
441
|
+
* The default seed is a deep indigo (`#4f46e5`) that maps to a material-spec
|
|
442
|
+
* primary hue of roughly 243°. It was chosen because it:
|
|
443
|
+
* - Generates accessible contrast ratios in both light and dark modes.
|
|
444
|
+
* - Feels brand-neutral enough to serve as a placeholder before users
|
|
445
|
+
* customize their color.
|
|
446
|
+
*
|
|
447
|
+
* @returns The default seed hex string `'#4f46e5'`.
|
|
448
|
+
*/
|
|
168
449
|
export function getDefaultSeed(): string {
|
|
169
450
|
return '#4f46e5';
|
|
170
451
|
}
|