@frame-ui-ng/foundation 0.1.0-beta.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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FrameUI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # Foundation
2
+
3
+ `@frame-ui-ng/foundation` is the stable base layer for FrameUI.
4
+
5
+ Documentation: https://frame-ui.com
6
+
7
+ Current scope:
8
+
9
+ - CSS-variable token contract
10
+ - Angular theme switching via `data-theme` or a shared `.dark` class
11
+ - class merge helpers for future slot-based primitives
12
+ - Vitest unit tests
13
+
14
+ No primitives or complex components are included here.
15
+
16
+ ## Token Contract
17
+
18
+ The foundation token contract is intentionally semantic and small.
19
+
20
+ ### Color tokens
21
+
22
+ - `--frame-background`: app/page background
23
+ - `--frame-foreground`: default readable text on `background`
24
+ - `--frame-surface`: raised or contained surfaces such as cards, panels, menus
25
+ - `--frame-surface-foreground`: readable text on `surface`
26
+ - `--frame-muted`: low-emphasis surfaces and fills
27
+ - `--frame-muted-foreground`: low-emphasis text
28
+ - `--frame-primary`: primary emphasis, usually the main action color
29
+ - `--frame-primary-foreground`: readable text on `primary`
30
+ - `--frame-accent`: secondary emphasis or selection highlight
31
+ - `--frame-accent-foreground`: readable text on `accent`
32
+ - `--frame-border`: default border color
33
+ - `--frame-input`: input field chrome
34
+ - `--frame-ring`: focus ring color
35
+
36
+ ### Shape tokens
37
+
38
+ - `--frame-radius-sm`
39
+ - `--frame-radius-md`
40
+ - `--frame-radius-lg`
41
+
42
+ ### Elevation tokens
43
+
44
+ - `--frame-shadow-sm`
45
+ - `--frame-shadow-md`
46
+
47
+ ## Contract Rules
48
+
49
+ - Tokens must describe purpose, not implementation.
50
+ - Components should consume semantic tokens, never hardcoded theme colors.
51
+ - New tokens should be added only when multiple primitives need the same concept.
52
+ - Component-specific tokens should not be added to the foundation layer yet.
53
+
54
+ Good:
55
+
56
+ ```css
57
+ color: var(--frame-foreground);
58
+ border-color: var(--frame-border);
59
+ ```
60
+
61
+ Bad:
62
+
63
+ ```css
64
+ color: #111827;
65
+ border-color: #e5e7eb;
66
+ ```
67
+
68
+ Good token naming:
69
+
70
+ ```css
71
+ --frame-primary
72
+ --frame-surface
73
+ ```
74
+
75
+ Bad token naming:
76
+
77
+ ```css
78
+ --frame-blue-500
79
+ --frame-card-border-hover
80
+ ```
81
+
82
+ ## Light And Dark
83
+
84
+ The foundation layer only models `light` and `dark`.
85
+
86
+ Brand, product, or campaign differences should be handled by the host app's tokens or by scoped CSS-variable overrides. They should not become additional registered theme names.
87
+
88
+ ## Theme Ownership
89
+
90
+ The foundation layer should expose a token contract, not force itself to be the only dark mode owner.
91
+
92
+ There are two recommended ownership models:
93
+
94
+ - library-managed: the FrameUI writes the active theme to the root element
95
+ - externally managed: another system such as Tailwind owns the root selector and the FrameUI follows it
96
+
97
+ ### Library-managed with `data-theme`
98
+
99
+ This is the current default:
100
+
101
+ ```ts
102
+ provideFrameUI({
103
+ defaultTheme: 'light',
104
+ });
105
+ ```
106
+
107
+ ### Library-managed with Tailwind's `.dark` class
108
+
109
+ If you want the FrameUI service to be the single source of truth, but Tailwind utilities should respond too, switch to class strategy:
110
+
111
+ ```ts
112
+ provideFrameUI({
113
+ strategy: 'class',
114
+ className: 'dark',
115
+ });
116
+ ```
117
+
118
+ `ThemeService.setTheme('dark')` now adds `.dark` to the root element, so both the FrameUI tokens and Tailwind `dark:` utilities react to the same switch.
119
+
120
+ ### Externally managed by Tailwind or another app shell
121
+
122
+ If the host app already owns dark mode, let the FrameUI observe instead of write:
123
+
124
+ ```ts
125
+ provideFrameUI({
126
+ strategy: 'class',
127
+ mode: 'observe',
128
+ className: 'dark',
129
+ });
130
+ ```
131
+
132
+ In this mode the library does not write to the DOM. It reads the current root class and keeps `ThemeService.theme()` in sync with that external source of truth.
133
+
134
+ Use scoped overrides for local brand moments:
135
+
136
+ ```css
137
+ .marketing-hero {
138
+ --frame-primary: oklch(0.69 0.19 38);
139
+ --frame-primary-foreground: oklch(0.99 0.01 95);
140
+ --frame-radius-lg: 1rem;
141
+ }
142
+ ```
143
+
144
+ ## Commands
145
+
146
+ ```bash
147
+ npm install @frame-ui-ng/foundation
148
+ ```
@@ -0,0 +1,155 @@
1
+ import { DOCUMENT } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, makeEnvironmentProviders, inject, signal, computed, Injectable } from '@angular/core';
4
+
5
+ function cx(...values) {
6
+ const classNames = [];
7
+ for (const value of values) {
8
+ if (!value) {
9
+ continue;
10
+ }
11
+ if (typeof value === 'string') {
12
+ classNames.push(value);
13
+ continue;
14
+ }
15
+ if (Array.isArray(value)) {
16
+ const nested = cx(...value);
17
+ if (nested) {
18
+ classNames.push(nested);
19
+ }
20
+ continue;
21
+ }
22
+ for (const [className, enabled] of Object.entries(value)) {
23
+ if (enabled) {
24
+ classNames.push(className);
25
+ }
26
+ }
27
+ }
28
+ return classNames.join(' ');
29
+ }
30
+ function withClassOverrides(slots, overrides) {
31
+ const mergedSlots = {};
32
+ for (const slot of Object.keys(slots)) {
33
+ mergedSlots[slot] = cx(slots[slot], overrides?.[slot]);
34
+ }
35
+ return mergedSlots;
36
+ }
37
+
38
+ const DEFAULT_CONFIG = {
39
+ attribute: 'data-theme',
40
+ className: 'dark',
41
+ defaultTheme: 'light',
42
+ mode: 'managed',
43
+ strategy: 'attribute',
44
+ };
45
+ const FRAME_UI_CONFIG = new InjectionToken('FRAME_UI_CONFIG', {
46
+ factory: () => DEFAULT_CONFIG,
47
+ });
48
+ function provideFrameUI(options = {}) {
49
+ return makeEnvironmentProviders([
50
+ {
51
+ provide: FRAME_UI_CONFIG,
52
+ useValue: createFrameUIConfig(options),
53
+ },
54
+ ThemeService,
55
+ ]);
56
+ }
57
+ function createFrameUIConfig(options = {}) {
58
+ const defaultTheme = options.defaultTheme ?? DEFAULT_CONFIG.defaultTheme;
59
+ return {
60
+ attribute: options.attribute ?? DEFAULT_CONFIG.attribute,
61
+ className: options.className ?? DEFAULT_CONFIG.className,
62
+ defaultTheme,
63
+ mode: options.mode ?? DEFAULT_CONFIG.mode,
64
+ strategy: options.strategy ?? DEFAULT_CONFIG.strategy,
65
+ };
66
+ }
67
+ class ThemeService {
68
+ document = inject(DOCUMENT);
69
+ config = inject(FRAME_UI_CONFIG);
70
+ activeTheme = signal(this.config.defaultTheme, ...(ngDevMode ? [{ debugName: "activeTheme" }] : /* istanbul ignore next */ []));
71
+ observer = null;
72
+ theme = this.activeTheme.asReadonly();
73
+ isDark = computed(() => this.activeTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
74
+ constructor() {
75
+ if (this.config.mode === 'observe') {
76
+ this.syncFromDom();
77
+ this.observeThemeChanges();
78
+ return;
79
+ }
80
+ this.applyTheme(this.activeTheme());
81
+ }
82
+ setTheme(name) {
83
+ if (!isFrameUITheme(name)) {
84
+ throw new Error(`Unknown theme "${name}".`);
85
+ }
86
+ if (this.config.mode === 'observe') {
87
+ throw new Error('ThemeService is configured to observe external theme state and cannot set the theme.');
88
+ }
89
+ this.activeTheme.set(name);
90
+ this.applyTheme(name);
91
+ }
92
+ toggleTheme() {
93
+ const nextTheme = this.activeTheme() === 'dark' ? 'light' : 'dark';
94
+ this.setTheme(nextTheme);
95
+ return nextTheme;
96
+ }
97
+ ngOnDestroy() {
98
+ this.observer?.disconnect();
99
+ this.observer = null;
100
+ }
101
+ applyTheme(name) {
102
+ const root = this.document?.documentElement;
103
+ if (!root) {
104
+ return;
105
+ }
106
+ if (this.config.strategy === 'class') {
107
+ root.classList.toggle(this.config.className, name === 'dark');
108
+ return;
109
+ }
110
+ root.setAttribute(this.config.attribute, name);
111
+ }
112
+ observeThemeChanges() {
113
+ const root = this.document?.documentElement;
114
+ if (!root || typeof MutationObserver === 'undefined') {
115
+ return;
116
+ }
117
+ const attributeFilter = this.config.strategy === 'class' ? ['class'] : [this.config.attribute];
118
+ this.observer = new MutationObserver(() => {
119
+ this.syncFromDom();
120
+ });
121
+ this.observer.observe(root, {
122
+ attributeFilter,
123
+ attributes: true,
124
+ });
125
+ }
126
+ syncFromDom() {
127
+ const root = this.document?.documentElement;
128
+ if (!root) {
129
+ return;
130
+ }
131
+ this.activeTheme.set(this.readThemeFromDom(root));
132
+ }
133
+ readThemeFromDom(root) {
134
+ if (this.config.strategy === 'class') {
135
+ return root.classList.contains(this.config.className) ? 'dark' : 'light';
136
+ }
137
+ const theme = root.getAttribute(this.config.attribute);
138
+ return isFrameUITheme(theme) ? theme : this.config.defaultTheme;
139
+ }
140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
141
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ThemeService });
142
+ }
143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ThemeService, decorators: [{
144
+ type: Injectable
145
+ }], ctorParameters: () => [] });
146
+ function isFrameUITheme(value) {
147
+ return value === 'light' || value === 'dark';
148
+ }
149
+
150
+ /**
151
+ * Generated bundle index. Do not edit.
152
+ */
153
+
154
+ export { FRAME_UI_CONFIG, ThemeService, createFrameUIConfig, cx, provideFrameUI, withClassOverrides };
155
+ //# sourceMappingURL=frame-ui-ng-foundation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-ui-ng-foundation.mjs","sources":["../../../projects/foundation/src/lib/class-names.ts","../../../projects/foundation/src/lib/frame-ui.ts","../../../projects/foundation/src/frame-ui-ng-foundation.ts"],"sourcesContent":["export type ClassDictionary = Readonly<Record<string, boolean | null | undefined>>;\nexport type ClassValue =\n | ClassDictionary\n | ClassValue[]\n | false\n | null\n | string\n | undefined;\n\nexport type SlotClasses<TSlot extends string> = Readonly<Record<TSlot, string>>;\nexport type SlotClassOverrides<TSlot extends string> = Partial<Record<TSlot, ClassValue>>;\n\nexport function cx(...values: readonly ClassValue[]): string {\n const classNames: string[] = [];\n\n for (const value of values) {\n if (!value) {\n continue;\n }\n\n if (typeof value === 'string') {\n classNames.push(value);\n continue;\n }\n\n if (Array.isArray(value)) {\n const nested = cx(...value);\n\n if (nested) {\n classNames.push(nested);\n }\n\n continue;\n }\n\n for (const [className, enabled] of Object.entries(value)) {\n if (enabled) {\n classNames.push(className);\n }\n }\n }\n\n return classNames.join(' ');\n}\n\nexport function withClassOverrides<TSlot extends string>(\n slots: SlotClasses<TSlot>,\n overrides?: SlotClassOverrides<TSlot>,\n): Record<TSlot, string> {\n const mergedSlots = {} as Record<TSlot, string>;\n\n for (const slot of Object.keys(slots) as TSlot[]) {\n mergedSlots[slot] = cx(slots[slot], overrides?.[slot]);\n }\n\n return mergedSlots;\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n EnvironmentProviders,\n Injectable,\n InjectionToken,\n OnDestroy,\n Signal,\n computed,\n inject,\n makeEnvironmentProviders,\n signal,\n} from '@angular/core';\n\nexport type ThemeBindingStrategy = 'attribute' | 'class';\nexport type ThemeSyncMode = 'managed' | 'observe';\nexport type FrameUITheme = 'light' | 'dark';\n\nexport interface FrameUIConfig {\n attribute: string;\n className: string;\n defaultTheme: FrameUITheme;\n mode: ThemeSyncMode;\n strategy: ThemeBindingStrategy;\n}\n\nconst DEFAULT_CONFIG: FrameUIConfig = {\n attribute: 'data-theme',\n className: 'dark',\n defaultTheme: 'light',\n mode: 'managed',\n strategy: 'attribute',\n};\n\nexport const FRAME_UI_CONFIG = new InjectionToken<FrameUIConfig>(\n 'FRAME_UI_CONFIG',\n {\n factory: () => DEFAULT_CONFIG,\n },\n);\n\nexport interface FrameUIOptions {\n attribute?: string;\n className?: string;\n defaultTheme?: FrameUITheme;\n mode?: ThemeSyncMode;\n strategy?: ThemeBindingStrategy;\n}\n\nexport function provideFrameUI(\n options: FrameUIOptions = {},\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: FRAME_UI_CONFIG,\n useValue: createFrameUIConfig(options),\n },\n ThemeService,\n ]);\n}\n\nexport function createFrameUIConfig(\n options: FrameUIOptions = {},\n): FrameUIConfig {\n const defaultTheme = options.defaultTheme ?? DEFAULT_CONFIG.defaultTheme;\n\n return {\n attribute: options.attribute ?? DEFAULT_CONFIG.attribute,\n className: options.className ?? DEFAULT_CONFIG.className,\n defaultTheme,\n mode: options.mode ?? DEFAULT_CONFIG.mode,\n strategy: options.strategy ?? DEFAULT_CONFIG.strategy,\n };\n}\n\n@Injectable()\nexport class ThemeService implements OnDestroy {\n private readonly document = inject(DOCUMENT);\n private readonly config = inject(FRAME_UI_CONFIG);\n private readonly activeTheme = signal(this.config.defaultTheme);\n private observer: MutationObserver | null = null;\n\n readonly theme: Signal<FrameUITheme> = this.activeTheme.asReadonly();\n\n readonly isDark = computed(() => this.activeTheme() === 'dark');\n\n constructor() {\n if (this.config.mode === 'observe') {\n this.syncFromDom();\n this.observeThemeChanges();\n return;\n }\n\n this.applyTheme(this.activeTheme());\n }\n\n setTheme(name: FrameUITheme): void {\n if (!isFrameUITheme(name)) {\n throw new Error(`Unknown theme \"${name}\".`);\n }\n\n if (this.config.mode === 'observe') {\n throw new Error(\n 'ThemeService is configured to observe external theme state and cannot set the theme.',\n );\n }\n\n this.activeTheme.set(name);\n this.applyTheme(name);\n }\n\n toggleTheme(): FrameUITheme {\n const nextTheme = this.activeTheme() === 'dark' ? 'light' : 'dark';\n\n this.setTheme(nextTheme);\n\n return nextTheme;\n }\n\n ngOnDestroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n }\n\n private applyTheme(name: FrameUITheme): void {\n const root = this.document?.documentElement;\n\n if (!root) {\n return;\n }\n\n if (this.config.strategy === 'class') {\n root.classList.toggle(this.config.className, name === 'dark');\n return;\n }\n\n root.setAttribute(this.config.attribute, name);\n }\n\n private observeThemeChanges(): void {\n const root = this.document?.documentElement;\n\n if (!root || typeof MutationObserver === 'undefined') {\n return;\n }\n\n const attributeFilter =\n this.config.strategy === 'class' ? ['class'] : [this.config.attribute];\n\n this.observer = new MutationObserver(() => {\n this.syncFromDom();\n });\n this.observer.observe(root, {\n attributeFilter,\n attributes: true,\n });\n }\n\n private syncFromDom(): void {\n const root = this.document?.documentElement;\n\n if (!root) {\n return;\n }\n\n this.activeTheme.set(this.readThemeFromDom(root));\n }\n\n private readThemeFromDom(root: HTMLElement): FrameUITheme {\n if (this.config.strategy === 'class') {\n return root.classList.contains(this.config.className) ? 'dark' : 'light';\n }\n\n const theme = root.getAttribute(this.config.attribute);\n\n return isFrameUITheme(theme) ? theme : this.config.defaultTheme;\n }\n}\n\nfunction isFrameUITheme(value: unknown): value is FrameUITheme {\n return value === 'light' || value === 'dark';\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@frame-ui-ng/foundation",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Foundation utilities, tokens, and providers for FrameUI.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/gamekohl/frame-ui.git"
9
+ },
10
+ "homepage": "https://frame-ui.com",
11
+ "bugs": {
12
+ "url": "https://github.com/gamekohl/frame-ui/issues"
13
+ },
14
+ "keywords": [
15
+ "angular",
16
+ "design-tokens",
17
+ "theme",
18
+ "design-system",
19
+ "frame-ui"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "peerDependencies": {
25
+ "@angular/common": ">=21.0.0 <22.0.0",
26
+ "@angular/core": ">=21.0.0 <22.0.0"
27
+ },
28
+ "dependencies": {
29
+ "tslib": "^2.3.0"
30
+ },
31
+ "exports": {
32
+ "./styles.css": {
33
+ "style": "./styles.css",
34
+ "default": "./styles.css"
35
+ },
36
+ "./styles/foundation.css": {
37
+ "style": "./styles/foundation.css",
38
+ "default": "./styles/foundation.css"
39
+ },
40
+ "./package.json": {
41
+ "default": "./package.json"
42
+ },
43
+ ".": {
44
+ "types": "./types/frame-ui-ng-foundation.d.ts",
45
+ "default": "./fesm2022/frame-ui-ng-foundation.mjs"
46
+ }
47
+ },
48
+ "sideEffects": [
49
+ "./styles.css",
50
+ "./styles/foundation.css"
51
+ ],
52
+ "module": "fesm2022/frame-ui-ng-foundation.mjs",
53
+ "typings": "types/frame-ui-ng-foundation.d.ts",
54
+ "type": "module"
55
+ }
@@ -0,0 +1,82 @@
1
+ :root,
2
+ [data-theme='light'] {
3
+ /* Theme metadata */
4
+ color-scheme: light;
5
+
6
+ /* Surfaces */
7
+ --frame-background: oklch(1 0 0);
8
+ --frame-foreground: oklch(0.145 0 0);
9
+ --frame-surface: oklch(1 0 0);
10
+ --frame-surface-foreground: oklch(0.145 0 0);
11
+
12
+ /* Supporting surfaces */
13
+ --frame-muted: oklch(0.97 0 0);
14
+ --frame-muted-foreground: oklch(0.45 0 0);
15
+
16
+ /* Actions */
17
+ --frame-primary: #161b27; /*oklch(0.205 0 0);*/
18
+ --frame-primary-foreground: oklch(0.985 0 0);
19
+ --frame-accent: oklch(0.97 0 0);
20
+ --frame-accent-foreground: oklch(0.205 0 0);
21
+ --frame-destructive: oklch(55.205% 0.18114 25.384);
22
+ --frame-destructive-foreground: oklch(100% 0.00011 271.152);
23
+
24
+ /* Structure and focus */
25
+ --frame-border: oklch(0.922 0 0);
26
+ --frame-input: oklch(0.922 0 0);
27
+ --frame-ring: oklch(0.708 0 0);
28
+
29
+ /* Spacing */
30
+ --frame-spacing-xs: 0.25rem;
31
+ --frame-spacing-sm: 0.5rem;
32
+ --frame-spacing-md: 0.75rem;
33
+ --frame-spacing-lg: 1rem;
34
+ --frame-spacing-xl: 1.25rem;
35
+
36
+ /* Shape */
37
+ --frame-radius-xs: 0.125rem;
38
+ --frame-radius-sm: 0.375rem;
39
+ --frame-radius-md: 0.5rem;
40
+ --frame-radius-lg: 0.75rem;
41
+ --frame-radius-full: 9999px;
42
+
43
+ /* Elevation */
44
+ --frame-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
45
+ --frame-shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1);
46
+ }
47
+
48
+ [data-theme='dark'],
49
+ .dark {
50
+ /* Theme metadata */
51
+ color-scheme: dark;
52
+
53
+ /* Surfaces */
54
+ --frame-background: oklch(0.145 0 0);
55
+ --frame-foreground: oklch(0.985 0 0);
56
+ --frame-surface: oklch(0.205 0 0);
57
+ --frame-surface-foreground: oklch(0.985 0 0);
58
+
59
+ /* Supporting surfaces */
60
+ --frame-muted: oklch(0.269 0 0);
61
+ --frame-muted-foreground: oklch(0.708 0 0);
62
+
63
+ /* Actions */
64
+ --frame-primary: oklch(0.922 0 0);
65
+ --frame-primary-foreground: oklch(0.205 0 0);
66
+ --frame-accent: oklch(0.269 0 0);
67
+ --frame-accent-foreground: oklch(0.985 0 0);
68
+
69
+ /* Structure and focus */
70
+ --frame-border: oklch(1 0 0 / 0.1);
71
+ --frame-input: oklch(1 0 0 / 0.15);
72
+ --frame-ring: oklch(0.556 0 0);
73
+
74
+ /* Shape */
75
+ --frame-radius-sm: 0.375rem;
76
+ --frame-radius-md: 0.5rem;
77
+ --frame-radius-lg: 0.75rem;
78
+
79
+ /* Elevation */
80
+ --frame-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
81
+ --frame-shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.35);
82
+ }
package/styles.css ADDED
@@ -0,0 +1 @@
1
+ @import './styles/foundation.css';
@@ -0,0 +1,51 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, OnDestroy, Signal, EnvironmentProviders } from '@angular/core';
3
+
4
+ type ClassDictionary = Readonly<Record<string, boolean | null | undefined>>;
5
+ type ClassValue = ClassDictionary | ClassValue[] | false | null | string | undefined;
6
+ type SlotClasses<TSlot extends string> = Readonly<Record<TSlot, string>>;
7
+ type SlotClassOverrides<TSlot extends string> = Partial<Record<TSlot, ClassValue>>;
8
+ declare function cx(...values: readonly ClassValue[]): string;
9
+ declare function withClassOverrides<TSlot extends string>(slots: SlotClasses<TSlot>, overrides?: SlotClassOverrides<TSlot>): Record<TSlot, string>;
10
+
11
+ type ThemeBindingStrategy = 'attribute' | 'class';
12
+ type ThemeSyncMode = 'managed' | 'observe';
13
+ type FrameUITheme = 'light' | 'dark';
14
+ interface FrameUIConfig {
15
+ attribute: string;
16
+ className: string;
17
+ defaultTheme: FrameUITheme;
18
+ mode: ThemeSyncMode;
19
+ strategy: ThemeBindingStrategy;
20
+ }
21
+ declare const FRAME_UI_CONFIG: InjectionToken<FrameUIConfig>;
22
+ interface FrameUIOptions {
23
+ attribute?: string;
24
+ className?: string;
25
+ defaultTheme?: FrameUITheme;
26
+ mode?: ThemeSyncMode;
27
+ strategy?: ThemeBindingStrategy;
28
+ }
29
+ declare function provideFrameUI(options?: FrameUIOptions): EnvironmentProviders;
30
+ declare function createFrameUIConfig(options?: FrameUIOptions): FrameUIConfig;
31
+ declare class ThemeService implements OnDestroy {
32
+ private readonly document;
33
+ private readonly config;
34
+ private readonly activeTheme;
35
+ private observer;
36
+ readonly theme: Signal<FrameUITheme>;
37
+ readonly isDark: Signal<boolean>;
38
+ constructor();
39
+ setTheme(name: FrameUITheme): void;
40
+ toggleTheme(): FrameUITheme;
41
+ ngOnDestroy(): void;
42
+ private applyTheme;
43
+ private observeThemeChanges;
44
+ private syncFromDom;
45
+ private readThemeFromDom;
46
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
47
+ static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
48
+ }
49
+
50
+ export { FRAME_UI_CONFIG, ThemeService, createFrameUIConfig, cx, provideFrameUI, withClassOverrides };
51
+ export type { ClassDictionary, ClassValue, FrameUIConfig, FrameUIOptions, FrameUITheme, SlotClassOverrides, SlotClasses, ThemeBindingStrategy, ThemeSyncMode };