@echothink-ui/style 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ /* ========================================================================= *
2
+ * Carbon Bridge.
3
+ *
4
+ * Core controls wrap Carbon React, which reads --cds-* tokens. Presets only
5
+ * overrode --eth-* tokens, so Carbon controls ignored the preset (square,
6
+ * Carbon-blue, and — in studio-dark — white inputs on a dark page). This block
7
+ * maps the active preset's role tokens onto every --cds-* family Carbon
8
+ * consumes. Because the --eth-* values differ per preset, one shared bridge
9
+ * block themes all five presets.
10
+ *
11
+ * Dark mode: StyleScope additionally sets [data-carbon-theme="g100"] on the
12
+ * studio-dark scope, so the ~100 Carbon tokens NOT re-pointed here still get a
13
+ * correct dark base (Part A "theme-bundle base" of the architecture doc). This
14
+ * bridge then tints the subset that should follow the EchoThink palette.
15
+ *
16
+ * Applied to :root by StyleScope (global mode) so PORTALED Carbon content
17
+ * (modals, menus, tooltips, toasts mounted at document.body) inherits the
18
+ * theme instead of falling back to default Carbon.
19
+ * ========================================================================= */
20
+
21
+ .eth-style-carbon-like,
22
+ .eth-style-soft-card,
23
+ .eth-style-glass,
24
+ .eth-style-bright,
25
+ .eth-style-studio-dark {
26
+ /* backgrounds + layers — drive every nested surface, not just the page */
27
+ --cds-background: var(--eth-bridge-bg);
28
+ --cds-background-hover: var(--eth-color-layer-hover);
29
+ --cds-background-active: var(--eth-color-layer-selected);
30
+ --cds-background-selected: var(--eth-color-layer-selected);
31
+ --cds-layer-01: var(--eth-color-card);
32
+ --cds-layer-02: var(--eth-color-layer-02);
33
+ --cds-layer-03: var(--eth-color-layer-03, var(--eth-color-layer-02));
34
+ --cds-layer: var(--cds-layer-01);
35
+ --cds-layer-hover-01: var(--eth-color-layer-hover);
36
+ --cds-layer-hover-02: var(--eth-color-layer-hover);
37
+ --cds-layer-active-01: var(--eth-color-layer-selected);
38
+ --cds-layer-selected-01: var(--eth-table-row-selected-bg);
39
+ --cds-layer-selected-02: var(--eth-table-row-selected-bg);
40
+ --cds-layer-selected-hover-01: var(--eth-table-row-selected-hover-bg);
41
+ --cds-layer-accent-01: var(--eth-table-header-bg);
42
+ --cds-layer-accent-02: var(--eth-table-header-bg);
43
+
44
+ /* fields — the source of the studio-dark white-box bug */
45
+ --cds-field-01: var(--eth-field-bg);
46
+ --cds-field-02: var(--eth-field-bg);
47
+ --cds-field-03: var(--eth-field-bg);
48
+ --cds-field-hover-01: var(--eth-field-bg-hover);
49
+ --cds-field-hover-02: var(--eth-field-bg-hover);
50
+ --cds-field-hover-03: var(--eth-field-bg-hover);
51
+
52
+ /* text + icons (every text role, including on-color and placeholder) */
53
+ --cds-text-primary: var(--eth-color-text-primary);
54
+ --cds-text-secondary: var(--eth-color-text-secondary);
55
+ --cds-text-helper: var(--eth-color-text-helper);
56
+ --cds-text-placeholder: var(--eth-field-placeholder);
57
+ --cds-text-error: var(--eth-status-danger-text);
58
+ --cds-text-on-color: var(--eth-btn-primary-text);
59
+ --cds-text-inverse: var(--eth-bridge-bg);
60
+ --cds-icon-primary: var(--eth-color-text-primary);
61
+ --cds-icon-secondary: var(--eth-color-text-secondary);
62
+ --cds-icon-on-color: var(--eth-btn-primary-text);
63
+
64
+ /* borders */
65
+ --cds-border-subtle: var(--eth-color-border-subtle);
66
+ --cds-border-subtle-00: var(--eth-color-border-subtle);
67
+ --cds-border-subtle-01: var(--eth-color-border-subtle);
68
+ --cds-border-subtle-02: var(--eth-color-border-subtle);
69
+ --cds-border-subtle-03: var(--eth-color-border-subtle);
70
+ --cds-border-strong: var(--eth-color-border-strong);
71
+ --cds-border-strong-01: var(--eth-color-border-strong);
72
+ --cds-border-strong-02: var(--eth-color-border-strong);
73
+ --cds-border-interactive: var(--eth-color-interactive-primary);
74
+
75
+ /* buttons (per variant + state) */
76
+ --cds-button-primary: var(--eth-btn-primary-bg);
77
+ --cds-button-primary-hover: var(--eth-btn-primary-bg-hover);
78
+ --cds-button-primary-active: var(--eth-btn-primary-bg-active);
79
+ --cds-button-secondary: var(--eth-btn-secondary-bg);
80
+ --cds-button-secondary-hover: var(--eth-btn-secondary-bg-hover);
81
+ --cds-button-secondary-active: var(--eth-btn-secondary-bg-active);
82
+ --cds-button-tertiary: var(--eth-btn-tertiary-text);
83
+ --cds-button-tertiary-hover: var(--eth-btn-tertiary-bg-hover);
84
+ --cds-button-danger-primary: var(--eth-btn-danger-bg);
85
+ --cds-button-danger-hover: var(--eth-btn-danger-bg-hover);
86
+ --cds-button-danger-active: var(--eth-btn-danger-bg-active, var(--eth-btn-danger-bg-hover));
87
+ --cds-button-disabled: var(--eth-btn-disabled-bg);
88
+
89
+ /* links + interactive */
90
+ --cds-link-primary: var(--eth-color-link);
91
+ --cds-link-primary-hover: var(--eth-color-interactive-strong);
92
+ --cds-interactive: var(--eth-color-interactive-primary);
93
+
94
+ /* support / status (icon glyph colors + notification fills) */
95
+ --cds-support-error: var(--eth-status-danger-icon);
96
+ --cds-support-success: var(--eth-status-success-icon);
97
+ --cds-support-warning: var(--eth-status-warning-icon);
98
+ --cds-support-info: var(--eth-status-info-icon);
99
+ --cds-notification-background-error: var(--eth-status-danger-bg);
100
+ --cds-notification-background-success: var(--eth-status-success-bg);
101
+ --cds-notification-background-warning: var(--eth-status-warning-bg);
102
+ --cds-notification-background-info: var(--eth-status-info-bg);
103
+
104
+ /* focus + overlay + typography */
105
+ --cds-focus: var(--eth-focus-ring-color);
106
+ --cds-overlay: var(--eth-overlay-bg);
107
+ --cds-font-family: var(--eth-font-family-sans);
108
+ --cds-font-mono: var(--eth-font-family-mono);
109
+ }
@@ -0,0 +1,32 @@
1
+ /* ========================================================================= *
2
+ * Layer 5 — Density sizing axis.
3
+ *
4
+ * Orthogonal to preset. Standard defaults live in base-roles.css :root (so a
5
+ * page with no [data-eth-density] attribute is "standard"). These blocks add
6
+ * the compact and comfortable steps. Composes with the existing spacing-scale
7
+ * density overrides in @echothink-ui/tokens.
8
+ * ========================================================================= */
9
+
10
+ [data-eth-density="compact"] {
11
+ --eth-control-height: 32px;
12
+ --eth-control-padding-inline: 0.75rem;
13
+ --eth-control-padding-block: 0.25rem;
14
+ --eth-row-height: 32px;
15
+ --eth-cell-padding-inline: var(--eth-space-5);
16
+ --eth-cell-padding-block: var(--eth-space-2);
17
+ --eth-field-gap: var(--eth-space-3);
18
+ --eth-section-gap: var(--eth-space-6);
19
+ --eth-card-padding: var(--eth-space-6);
20
+ }
21
+
22
+ [data-eth-density="comfortable"] {
23
+ --eth-control-height: 48px;
24
+ --eth-control-padding-inline: 1.25rem;
25
+ --eth-control-padding-block: 0.75rem;
26
+ --eth-row-height: 56px;
27
+ --eth-cell-padding-inline: var(--eth-space-7);
28
+ --eth-cell-padding-block: var(--eth-space-5);
29
+ --eth-field-gap: var(--eth-space-6);
30
+ --eth-section-gap: var(--eth-space-10);
31
+ --eth-card-padding: var(--eth-space-9);
32
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,207 @@
1
+ import * as React from "react";
2
+ import "./styles.css";
3
+
4
+ export const ethStylePresets = [
5
+ "carbon-like",
6
+ "soft-card",
7
+ "glass",
8
+ "bright",
9
+ "studio-dark"
10
+ ] as const;
11
+
12
+ export type EthStylePreset = (typeof ethStylePresets)[number];
13
+
14
+ export const defaultStylePreset: EthStylePreset = "carbon-like";
15
+
16
+ export const ethStylePresetLabels: Record<EthStylePreset, string> = {
17
+ "carbon-like": "Carbon-like",
18
+ "soft-card": "Soft card",
19
+ glass: "Liquid glass",
20
+ bright: "Bright",
21
+ "studio-dark": "Studio dark"
22
+ };
23
+
24
+ export const ethStylePresetDescriptions: Record<EthStylePreset, string> = {
25
+ "carbon-like": "Square, flat Carbon-inspired surfaces.",
26
+ "soft-card": "Soft radius with quiet card elevation.",
27
+ glass: "Layered translucent surfaces with strong blur, highlights, and depth.",
28
+ bright: "High-key SaaS surfaces with crisp contrast.",
29
+ "studio-dark": "Dark workbench surfaces for dense tools."
30
+ };
31
+
32
+ /** Light/dark mode per preset. Drives the Carbon theme bundle + data-eth-mode. */
33
+ export const ethStylePresetModes: Record<EthStylePreset, "light" | "dark"> = {
34
+ "carbon-like": "light",
35
+ "soft-card": "light",
36
+ glass: "light",
37
+ bright: "light",
38
+ "studio-dark": "dark"
39
+ };
40
+
41
+ /**
42
+ * Carbon theme bundle per preset (Part A "theme-bundle base"). Light presets
43
+ * base on the white bundle; studio-dark bases on g100 so every Carbon token
44
+ * the bridge does not re-point still resolves to a correct dark value.
45
+ */
46
+ export const ethStylePresetCarbonThemes: Record<EthStylePreset, string> = {
47
+ "carbon-like": "white",
48
+ "soft-card": "white",
49
+ glass: "white",
50
+ bright: "white",
51
+ "studio-dark": "g100"
52
+ };
53
+
54
+ export const ethPalettes = ["default"] as const;
55
+ export type EthPalette = (typeof ethPalettes)[number];
56
+ export const defaultPalette: EthPalette = "default";
57
+
58
+ const presetSet = new Set<string>(ethStylePresets);
59
+ const paletteSet = new Set<string>(ethPalettes);
60
+
61
+ function joinClassNames(...values: Array<string | undefined | false>) {
62
+ return values.filter(Boolean).join(" ");
63
+ }
64
+
65
+ export function isEthStylePreset(value: string): value is EthStylePreset {
66
+ return presetSet.has(value);
67
+ }
68
+
69
+ export function isEthPalette(value: string): value is EthPalette {
70
+ return paletteSet.has(value);
71
+ }
72
+
73
+ export function getEthStylePresetClassName(preset: EthStylePreset = defaultStylePreset) {
74
+ return `eth-style-${preset}`;
75
+ }
76
+
77
+ export function getEthPaletteClassName(palette: EthPalette = defaultPalette) {
78
+ return `eth-palette-${palette}`;
79
+ }
80
+
81
+ export function getEthStylePresetDataAttribute(preset: EthStylePreset = defaultStylePreset) {
82
+ return preset;
83
+ }
84
+
85
+ export function getEthStyleMode(preset: EthStylePreset = defaultStylePreset) {
86
+ return ethStylePresetModes[preset];
87
+ }
88
+
89
+ export function getEthStyleCarbonTheme(preset: EthStylePreset = defaultStylePreset) {
90
+ return ethStylePresetCarbonThemes[preset];
91
+ }
92
+
93
+ export function composeEthStylePresetClassName(
94
+ preset: EthStylePreset = defaultStylePreset,
95
+ className?: string
96
+ ) {
97
+ return joinClassNames(getEthStylePresetClassName(preset), className);
98
+ }
99
+
100
+ export const ethDensityModes = ["compact", "standard", "comfortable"] as const;
101
+ export type EthDensityMode = (typeof ethDensityModes)[number];
102
+
103
+ export const ethTypeScales = ["compact", "standard", "large"] as const;
104
+ export type EthTypeScale = (typeof ethTypeScales)[number];
105
+
106
+ export interface StyleScopeProps extends React.HTMLAttributes<HTMLDivElement> {
107
+ preset?: EthStylePreset;
108
+ /** Palette of raw color assets the preset maps from. */
109
+ palette?: EthPalette;
110
+ /** Orthogonal spacing axis. "standard" emits no attribute (token defaults). */
111
+ density?: EthDensityMode;
112
+ /** Orthogonal typography scale axis. "standard" emits no attribute. */
113
+ typeScale?: EthTypeScale;
114
+ /**
115
+ * Also mirror the preset/palette/Carbon-theme onto the document root so
116
+ * PORTALED Carbon content (modals, dropdown menus, tooltips, toasts mounted
117
+ * at document.body, outside this wrapper) inherits the theme instead of
118
+ * falling back to default Carbon. Defaults to true.
119
+ */
120
+ global?: boolean;
121
+ }
122
+
123
+ const useIsomorphicLayoutEffect =
124
+ typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
125
+
126
+ function applyGlobalTheme(
127
+ preset: EthStylePreset,
128
+ palette: EthPalette,
129
+ density: EthDensityMode,
130
+ typeScale: EthTypeScale
131
+ ) {
132
+ if (typeof document === "undefined") return undefined;
133
+ const root = document.documentElement;
134
+
135
+ const presetClass = getEthStylePresetClassName(preset);
136
+ const paletteClass = getEthPaletteClassName(palette);
137
+
138
+ // Desired attributes; a null value means "remove the attribute".
139
+ const desiredAttrs: Record<string, string | null> = {
140
+ "data-eth-palette": palette,
141
+ "data-eth-style-preset": getEthStylePresetDataAttribute(preset),
142
+ "data-eth-mode": getEthStyleMode(preset),
143
+ "data-carbon-theme": getEthStyleCarbonTheme(preset),
144
+ "data-eth-density": density === "standard" ? null : density,
145
+ "data-eth-type": typeScale === "standard" ? null : typeScale
146
+ };
147
+
148
+ // Snapshot prior preset/palette classes and the attributes we touch.
149
+ const removedClasses = Array.from(root.classList).filter(
150
+ (c) => c.startsWith("eth-style-") || c.startsWith("eth-palette-")
151
+ );
152
+ const prevAttrs: Record<string, string | null> = {};
153
+ Object.keys(desiredAttrs).forEach((key) => {
154
+ prevAttrs[key] = root.getAttribute(key);
155
+ });
156
+
157
+ removedClasses.forEach((c) => root.classList.remove(c));
158
+ root.classList.add(paletteClass, presetClass);
159
+ Object.entries(desiredAttrs).forEach(([key, value]) => {
160
+ if (value === null) root.removeAttribute(key);
161
+ else root.setAttribute(key, value);
162
+ });
163
+
164
+ return () => {
165
+ root.classList.remove(paletteClass, presetClass);
166
+ removedClasses.forEach((c) => root.classList.add(c));
167
+ Object.entries(prevAttrs).forEach(([key, value]) => {
168
+ if (value === null) root.removeAttribute(key);
169
+ else root.setAttribute(key, value);
170
+ });
171
+ };
172
+ }
173
+
174
+ export function StyleScope({
175
+ preset = defaultStylePreset,
176
+ palette = defaultPalette,
177
+ density = "standard",
178
+ typeScale = "standard",
179
+ global = true,
180
+ className,
181
+ children,
182
+ ...props
183
+ }: StyleScopeProps) {
184
+ useIsomorphicLayoutEffect(() => {
185
+ if (!global) return undefined;
186
+ return applyGlobalTheme(preset, palette, density, typeScale);
187
+ }, [global, preset, palette, density, typeScale]);
188
+
189
+ return (
190
+ <div
191
+ {...props}
192
+ className={joinClassNames(
193
+ getEthPaletteClassName(palette),
194
+ getEthStylePresetClassName(preset),
195
+ className
196
+ )}
197
+ data-eth-palette={palette}
198
+ data-eth-style-preset={getEthStylePresetDataAttribute(preset)}
199
+ data-eth-mode={getEthStyleMode(preset)}
200
+ data-carbon-theme={getEthStyleCarbonTheme(preset)}
201
+ data-eth-density={density === "standard" ? undefined : density}
202
+ data-eth-type={typeScale === "standard" ? undefined : typeScale}
203
+ >
204
+ {children}
205
+ </div>
206
+ );
207
+ }
@@ -0,0 +1,51 @@
1
+ /* ========================================================================= *
2
+ * Layer 1 — Palette tokens.
3
+ *
4
+ * Raw color assets. No component, preset, or mode meaning. Presets map these
5
+ * into the semantic + role contract. Applied at :root so they always resolve,
6
+ * and under .eth-palette-<name> so multiple palettes can coexist on one page.
7
+ * ========================================================================= */
8
+
9
+ :root,
10
+ .eth-palette-default {
11
+ --eth-palette-neutral-0: #ffffff;
12
+ --eth-palette-neutral-25: #fbfcfe;
13
+ --eth-palette-neutral-50: #f6f7f9;
14
+ --eth-palette-neutral-100: #edf2f8;
15
+ --eth-palette-neutral-200: #dbe1ea;
16
+ --eth-palette-neutral-300: #c2ccd9;
17
+ --eth-palette-neutral-400: #9aa7b8;
18
+ --eth-palette-neutral-500: #6b7889;
19
+ --eth-palette-neutral-600: #5c6a7d;
20
+ --eth-palette-neutral-700: #526176;
21
+ --eth-palette-neutral-800: #33414f;
22
+ --eth-palette-neutral-900: #122033;
23
+ --eth-palette-neutral-950: #0b1623;
24
+
25
+ --eth-palette-blue-50: #edf6ff;
26
+ --eth-palette-blue-100: #dff0ff;
27
+ --eth-palette-blue-300: #7db2ff;
28
+ --eth-palette-blue-400: #4589ff;
29
+ --eth-palette-blue-500: #1768ff;
30
+ --eth-palette-blue-700: #0048d9;
31
+
32
+ --eth-palette-indigo-50: #eef0ff;
33
+ --eth-palette-indigo-300: #a5acff;
34
+ --eth-palette-indigo-500: #4f5bef;
35
+ --eth-palette-indigo-700: #3a3fd0;
36
+
37
+ --eth-palette-red-50: #fff1f1;
38
+ --eth-palette-red-300: #ff9a9f;
39
+ --eth-palette-red-500: #da1e28;
40
+ --eth-palette-red-700: #a2191f;
41
+
42
+ --eth-palette-green-50: #defbe6;
43
+ --eth-palette-green-300: #6fdc8c;
44
+ --eth-palette-green-500: #24a148;
45
+ --eth-palette-green-700: #198038;
46
+
47
+ --eth-palette-yellow-50: #fcf4d6;
48
+ --eth-palette-yellow-300: #f5d76e;
49
+ --eth-palette-yellow-500: #f1c21b;
50
+ --eth-palette-yellow-700: #8a6d00;
51
+ }