@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 +9 -0
- package/README.md +148 -0
- package/fesm2022/frame-ui-ng-foundation.mjs +155 -0
- package/fesm2022/frame-ui-ng-foundation.mjs.map +1 -0
- package/package.json +55 -0
- package/styles/foundation.css +82 -0
- package/styles.css +1 -0
- package/types/frame-ui-ng-foundation.d.ts +51 -0
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 };
|