@skewedaspect/sleekspace-ui 0.4.0 → 0.5.1
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/dist/components/Page/SkPage.vue.d.ts +9 -0
- package/dist/components/Page/types.d.ts +3 -0
- package/dist/components/Theme/types.d.ts +18 -3
- package/dist/sleekspace-ui.css +79 -46
- package/dist/sleekspace-ui.es.js +48 -36
- package/dist/sleekspace-ui.umd.js +48 -36
- package/dist/tokens.css +23 -0
- package/docs/guides/design-tokens/advanced.md +6 -1
- package/docs/guides/design-tokens/foundation-other.md +16 -3
- package/docs/guides/design-tokens/semantic-states.md +26 -11
- package/docs/guides/design-tokens/themes.md +13 -2
- package/docs/guides/getting-started.md +31 -30
- package/docs/guides/theming.md +272 -8
- package/package.json +3 -3
- package/src/components/Page/SkPage.vue +36 -2
- package/src/components/Page/types.ts +6 -0
- package/src/components/Theme/types.ts +20 -3
- package/src/styles/_scrollbar.scss +2 -2
- package/src/styles/base/_global.scss +3 -3
- package/src/styles/components/_button.scss +45 -9
- package/src/styles/components/_page.scss +2 -2
- package/src/styles/themes/README.md +6 -2
- package/src/styles/tokens/_foundation-colors.scss +4 -0
- package/src/styles/tokens/_foundation-transitions.scss +8 -0
- package/src/styles/tokens/_semantic-surfaces.scss +15 -0
- package/web-types.json +11 -1
package/docs/guides/theming.md
CHANGED
|
@@ -42,9 +42,29 @@ const { currentTheme, setTheme } = useTheme();
|
|
|
42
42
|
</template>
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## SkPage as Theme Provider
|
|
46
|
+
|
|
47
|
+
If your app already uses `SkPage`, you can skip the `SkTheme` wrapper and pass a `theme` prop directly:
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<template>
|
|
51
|
+
<SkPage theme="colorful" fixed-header>
|
|
52
|
+
<template #header>
|
|
53
|
+
<SkNavBar kind="primary">
|
|
54
|
+
<template #brand>My App</template>
|
|
55
|
+
</SkNavBar>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<SkPanel kind="accent">Fully themed — no SkTheme wrapper needed.</SkPanel>
|
|
59
|
+
</SkPage>
|
|
60
|
+
</template>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This establishes the same theme context that `SkTheme` does (including portal component support). The `useTheme` composable works identically inside a themed `SkPage`.
|
|
64
|
+
|
|
45
65
|
## How It Works
|
|
46
66
|
|
|
47
|
-
SkTheme sets a `data-scheme` attribute on its wrapper element. CSS rules scoped to each scheme override semantic token values:
|
|
67
|
+
SkTheme (or SkPage with a `theme` prop) sets a `data-scheme` attribute on its wrapper element. This cascades the correct text color (`--sk-text-primary`) to all descendants. SkPage also sets the page background (`--sk-surface-base`). CSS rules scoped to each scheme override semantic token values:
|
|
48
68
|
|
|
49
69
|
```css
|
|
50
70
|
[data-scheme="greyscale"] {
|
|
@@ -60,30 +80,274 @@ All components reference these semantic tokens, so switching the scheme instantl
|
|
|
60
80
|
|
|
61
81
|
## Creating Custom Themes
|
|
62
82
|
|
|
63
|
-
|
|
83
|
+
Themes are pure CSS. You define a `[data-scheme="name"]` block with 35 custom properties (7 semantic kinds × 5 tokens each) and you're done. No library modifications, no build steps — just a stylesheet.
|
|
84
|
+
|
|
85
|
+
> See the [SkTheme component page](/components/theme) for live demos of custom themes compared side-by-side with the built-in ones.
|
|
86
|
+
|
|
87
|
+
### Token Structure
|
|
88
|
+
|
|
89
|
+
Each of the 7 semantic kinds requires 5 tokens:
|
|
90
|
+
|
|
91
|
+
| Token | Purpose |
|
|
92
|
+
|-------|---------|
|
|
93
|
+
| `--sk-{kind}-base` | Main background color |
|
|
94
|
+
| `--sk-{kind}-hover` | Hover state background |
|
|
95
|
+
| `--sk-{kind}-active` | Active/pressed state background |
|
|
96
|
+
| `--sk-{kind}-text` | Text color on the base background |
|
|
97
|
+
| `--sk-{kind}-text-contrast` | Alternative text for subtle/ghost variants |
|
|
98
|
+
|
|
99
|
+
The 7 kinds are: `neutral`, `primary`, `accent`, `success`, `warning`, `danger`, `info`.
|
|
100
|
+
|
|
101
|
+
### Surface Tokens (Optional)
|
|
102
|
+
|
|
103
|
+
Themes can also override the page surface tokens. These have sensible defaults (dark gray background, white text) so you only need to override them if your theme wants a different page feel:
|
|
104
|
+
|
|
105
|
+
| Token | Default | Purpose |
|
|
106
|
+
|-------|---------|---------|
|
|
107
|
+
| `--sk-surface-base` | `--sk-color-gray-90` | Page/app background |
|
|
108
|
+
| `--sk-surface-raised` | `--sk-color-gray-95` | Elevated surfaces (cards, panels) |
|
|
109
|
+
| `--sk-surface-overlay` | `--sk-color-gray-80` | Overlays (modals, dropdowns) |
|
|
110
|
+
| `--sk-text-primary` | white | Primary body text |
|
|
111
|
+
|
|
112
|
+
`SkPage` applies `--sk-surface-base` as its background automatically. If you use `SkTheme` without `SkPage`, set your body background to `var(--sk-surface-base)` for the dark page feel.
|
|
113
|
+
|
|
114
|
+
### Deriving Hover and Active States
|
|
115
|
+
|
|
116
|
+
You don't need to hand-pick hover/active colors. The `color-mix()` function lightens the base color automatically, which is how the built-in themes do it:
|
|
64
117
|
|
|
65
118
|
```css
|
|
119
|
+
--sk-primary-hover: color-mix(
|
|
120
|
+
in oklch,
|
|
121
|
+
var(--sk-primary-base),
|
|
122
|
+
var(--sk-color-gray-10) var(--sk-mix-amount-moderate) /* 15% lighter */
|
|
123
|
+
);
|
|
124
|
+
--sk-primary-active: color-mix(
|
|
125
|
+
in oklch,
|
|
126
|
+
var(--sk-primary-base),
|
|
127
|
+
var(--sk-color-gray-10) var(--sk-mix-amount-strong) /* 20% lighter */
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This pattern references the `--sk-color-gray-10` foundation token (a light gray) and mixes it in at either 15% (`moderate`) or 20% (`strong`). You can also use hardcoded OKLCH values if you prefer full control.
|
|
132
|
+
|
|
133
|
+
### Complete Example: "Ocean" Theme
|
|
134
|
+
|
|
135
|
+
Here's a full theme definition using OKLCH values. This theme uses deep blues for neutral elements, teal for primary, coral for accent, and keeps the standard semantic colors for success/warning/danger/info:
|
|
136
|
+
|
|
137
|
+
```css
|
|
138
|
+
/* ocean-theme.css — import this after SleekSpace UI styles */
|
|
139
|
+
|
|
66
140
|
[data-scheme="ocean"] {
|
|
67
|
-
|
|
141
|
+
/* Neutral — deep slate blue */
|
|
142
|
+
--sk-neutral-base: oklch(0.35 0.04 250);
|
|
143
|
+
--sk-neutral-hover: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
144
|
+
--sk-neutral-active: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
145
|
+
--sk-neutral-text: oklch(1 0 0);
|
|
146
|
+
--sk-neutral-text-contrast: oklch(0.15 0.02 250);
|
|
147
|
+
|
|
148
|
+
/* Primary — bright teal */
|
|
149
|
+
--sk-primary-base: oklch(0.72 0.15 195);
|
|
150
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
151
|
+
--sk-primary-active: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
68
152
|
--sk-primary-text: oklch(1 0 0);
|
|
69
|
-
--sk-primary-
|
|
153
|
+
--sk-primary-text-contrast: oklch(0.15 0.02 195);
|
|
70
154
|
|
|
71
|
-
|
|
155
|
+
/* Accent — warm coral */
|
|
156
|
+
--sk-accent-base: oklch(0.68 0.18 25);
|
|
157
|
+
--sk-accent-hover: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
158
|
+
--sk-accent-active: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
72
159
|
--sk-accent-text: oklch(1 0 0);
|
|
73
|
-
--sk-accent-
|
|
160
|
+
--sk-accent-text-contrast: oklch(0.15 0.02 25);
|
|
74
161
|
|
|
75
|
-
/*
|
|
162
|
+
/* Success — emerald green */
|
|
163
|
+
--sk-success-base: oklch(0.70 0.18 155);
|
|
164
|
+
--sk-success-hover: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
165
|
+
--sk-success-active: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
166
|
+
--sk-success-text: oklch(1 0 0);
|
|
167
|
+
--sk-success-text-contrast: oklch(0.15 0.02 155);
|
|
168
|
+
|
|
169
|
+
/* Warning — amber */
|
|
170
|
+
--sk-warning-base: oklch(0.85 0.16 85);
|
|
171
|
+
--sk-warning-hover: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
172
|
+
--sk-warning-active: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
173
|
+
--sk-warning-text: oklch(1 0 0);
|
|
174
|
+
--sk-warning-text-contrast: oklch(0.15 0.02 85);
|
|
175
|
+
|
|
176
|
+
/* Danger — crimson red */
|
|
177
|
+
--sk-danger-base: oklch(0.60 0.22 25);
|
|
178
|
+
--sk-danger-hover: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
179
|
+
--sk-danger-active: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
180
|
+
--sk-danger-text: oklch(1 0 0);
|
|
181
|
+
--sk-danger-text-contrast: oklch(0.15 0.02 25);
|
|
182
|
+
|
|
183
|
+
/* Info — sky blue */
|
|
184
|
+
--sk-info-base: oklch(0.75 0.12 230);
|
|
185
|
+
--sk-info-hover: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
186
|
+
--sk-info-active: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
187
|
+
--sk-info-text: oklch(1 0 0);
|
|
188
|
+
--sk-info-text-contrast: oklch(0.15 0.02 230);
|
|
76
189
|
}
|
|
77
190
|
```
|
|
78
191
|
|
|
79
|
-
|
|
192
|
+
### Complete Example: "Cyberpunk" Theme
|
|
193
|
+
|
|
194
|
+
Neon pink primary, electric cyan accent, deep violet neutrals, and screaming neon green for success. High chroma across the board.
|
|
195
|
+
|
|
196
|
+
```css
|
|
197
|
+
/* cyberpunk-theme.css */
|
|
198
|
+
|
|
199
|
+
[data-scheme="cyberpunk"] {
|
|
200
|
+
/* Neutral — deep violet */
|
|
201
|
+
--sk-neutral-base: oklch(0.25 0.08 300);
|
|
202
|
+
--sk-neutral-hover: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
203
|
+
--sk-neutral-active: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
204
|
+
--sk-neutral-text: oklch(1 0 0);
|
|
205
|
+
--sk-neutral-text-contrast: oklch(0.12 0.04 300);
|
|
206
|
+
|
|
207
|
+
/* Primary — hot pink */
|
|
208
|
+
--sk-primary-base: oklch(0.65 0.27 355);
|
|
209
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
210
|
+
--sk-primary-active: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
211
|
+
--sk-primary-text: oklch(1 0 0);
|
|
212
|
+
--sk-primary-text-contrast: oklch(0.12 0.04 355);
|
|
213
|
+
|
|
214
|
+
/* Accent — electric cyan */
|
|
215
|
+
--sk-accent-base: oklch(0.78 0.15 195);
|
|
216
|
+
--sk-accent-hover: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
217
|
+
--sk-accent-active: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
218
|
+
--sk-accent-text: oklch(1 0 0);
|
|
219
|
+
--sk-accent-text-contrast: oklch(0.12 0.04 195);
|
|
220
|
+
|
|
221
|
+
/* Success — neon green */
|
|
222
|
+
--sk-success-base: oklch(0.82 0.25 145);
|
|
223
|
+
--sk-success-hover: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
224
|
+
--sk-success-active: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
225
|
+
--sk-success-text: oklch(1 0 0);
|
|
226
|
+
--sk-success-text-contrast: oklch(0.12 0.04 145);
|
|
227
|
+
|
|
228
|
+
/* Warning — electric yellow */
|
|
229
|
+
--sk-warning-base: oklch(0.90 0.18 100);
|
|
230
|
+
--sk-warning-hover: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
231
|
+
--sk-warning-active: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
232
|
+
--sk-warning-text: oklch(1 0 0);
|
|
233
|
+
--sk-warning-text-contrast: oklch(0.12 0.04 100);
|
|
234
|
+
|
|
235
|
+
/* Danger — hot red */
|
|
236
|
+
--sk-danger-base: oklch(0.62 0.25 30);
|
|
237
|
+
--sk-danger-hover: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
238
|
+
--sk-danger-active: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
239
|
+
--sk-danger-text: oklch(1 0 0);
|
|
240
|
+
--sk-danger-text-contrast: oklch(0.12 0.04 30);
|
|
241
|
+
|
|
242
|
+
/* Info — neon purple */
|
|
243
|
+
--sk-info-base: oklch(0.60 0.25 305);
|
|
244
|
+
--sk-info-hover: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
245
|
+
--sk-info-active: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
246
|
+
--sk-info-text: oklch(1 0 0);
|
|
247
|
+
--sk-info-text-contrast: oklch(0.12 0.04 305);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Using a Custom Theme
|
|
252
|
+
|
|
253
|
+
Apply it with `SkTheme` or `SkPage`:
|
|
80
254
|
|
|
81
255
|
```vue
|
|
256
|
+
<!-- With SkTheme -->
|
|
82
257
|
<SkTheme theme="ocean">
|
|
83
258
|
<YourContent />
|
|
84
259
|
</SkTheme>
|
|
260
|
+
|
|
261
|
+
<!-- Or directly on SkPage -->
|
|
262
|
+
<SkPage theme="ocean" fixed-header>
|
|
263
|
+
<template #header>...</template>
|
|
264
|
+
<YourContent />
|
|
265
|
+
</SkPage>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
> **TypeScript note:** To get type-safe autocomplete for custom theme names, augment the `SkThemeNameMap` interface:
|
|
269
|
+
>
|
|
270
|
+
> ```ts
|
|
271
|
+
> declare module '@skewedaspect/sleekspace-ui' {
|
|
272
|
+
> interface SkThemeNameMap {
|
|
273
|
+
> ocean : true;
|
|
274
|
+
> }
|
|
275
|
+
> }
|
|
276
|
+
> ```
|
|
277
|
+
>
|
|
278
|
+
> This registers `'ocean'` as a valid `SkThemeName` throughout your project.
|
|
279
|
+
|
|
280
|
+
### Using Foundation Colors
|
|
281
|
+
|
|
282
|
+
Instead of raw OKLCH values, you can reference the built-in foundation color palette. Each color family has 11 shades (05 through 95, where 50 is the most saturated):
|
|
283
|
+
|
|
284
|
+
```css
|
|
285
|
+
[data-scheme="mytheme"] {
|
|
286
|
+
/* Reference foundation palette variables */
|
|
287
|
+
--sk-primary-base: var(--sk-color-purple-50);
|
|
288
|
+
--sk-accent-base: var(--sk-color-pink-50);
|
|
289
|
+
--sk-neutral-base: var(--sk-color-gray-60);
|
|
290
|
+
|
|
291
|
+
/* hover/active states auto-derived the same way */
|
|
292
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
293
|
+
/* ... */
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Available foundation color families: `gray`, `blue`, `red`, `orange`, `yellow`, `green`, `mint`, `cyan`, `purple`, `pink`.
|
|
298
|
+
|
|
299
|
+
### Quick Reference: Minimal Theme
|
|
300
|
+
|
|
301
|
+
If you just want to remap which colors go where, copy this template and change the `-base` values. The `color-mix()` lines can be copied verbatim — they derive from the base automatically:
|
|
302
|
+
|
|
303
|
+
```css
|
|
304
|
+
[data-scheme="mytheme"] {
|
|
305
|
+
--sk-neutral-base: /* your color */;
|
|
306
|
+
--sk-neutral-hover: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
307
|
+
--sk-neutral-active: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
308
|
+
--sk-neutral-text: oklch(1 0 0);
|
|
309
|
+
--sk-neutral-text-contrast: var(--sk-color-gray-95);
|
|
310
|
+
|
|
311
|
+
--sk-primary-base: /* your color */;
|
|
312
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
313
|
+
--sk-primary-active: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
314
|
+
--sk-primary-text: oklch(1 0 0);
|
|
315
|
+
--sk-primary-text-contrast: var(--sk-color-gray-95);
|
|
316
|
+
|
|
317
|
+
--sk-accent-base: /* your color */;
|
|
318
|
+
--sk-accent-hover: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
319
|
+
--sk-accent-active: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
320
|
+
--sk-accent-text: oklch(1 0 0);
|
|
321
|
+
--sk-accent-text-contrast: var(--sk-color-gray-95);
|
|
322
|
+
|
|
323
|
+
--sk-success-base: /* your color */;
|
|
324
|
+
--sk-success-hover: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
325
|
+
--sk-success-active: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
326
|
+
--sk-success-text: oklch(1 0 0);
|
|
327
|
+
--sk-success-text-contrast: var(--sk-color-gray-95);
|
|
328
|
+
|
|
329
|
+
--sk-warning-base: /* your color */;
|
|
330
|
+
--sk-warning-hover: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
331
|
+
--sk-warning-active: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
332
|
+
--sk-warning-text: oklch(1 0 0);
|
|
333
|
+
--sk-warning-text-contrast: var(--sk-color-gray-95);
|
|
334
|
+
|
|
335
|
+
--sk-danger-base: /* your color */;
|
|
336
|
+
--sk-danger-hover: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
337
|
+
--sk-danger-active: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
338
|
+
--sk-danger-text: oklch(1 0 0);
|
|
339
|
+
--sk-danger-text-contrast: var(--sk-color-gray-95);
|
|
340
|
+
|
|
341
|
+
--sk-info-base: /* your color */;
|
|
342
|
+
--sk-info-hover: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
343
|
+
--sk-info-active: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
344
|
+
--sk-info-text: oklch(1 0 0);
|
|
345
|
+
--sk-info-text-contrast: var(--sk-color-gray-95);
|
|
346
|
+
}
|
|
85
347
|
```
|
|
86
348
|
|
|
349
|
+
Only the 7 `-base` values need to change. Everything else is boilerplate that can be copied as-is.
|
|
350
|
+
|
|
87
351
|
## Semantic Kinds
|
|
88
352
|
|
|
89
353
|
All themed components accept a `kind` prop with these values:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skewedaspect/sleekspace-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A Vue 3 component library with a cyberpunk aesthetic, featuring OKLCH colors, beveled corners, and a powerful design token system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/sleekspace-ui.umd.js",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
52
|
-
"url": "git+https://gitlab.com/
|
|
52
|
+
"url": "git+https://gitlab.com/skewed-aspect/sleekspace-ui.git",
|
|
53
53
|
"directory": "packages/sleekspace-ui"
|
|
54
54
|
},
|
|
55
|
-
"bugs": "https://gitlab.com/
|
|
55
|
+
"bugs": "https://gitlab.com/skewed-aspect/sleekspace-ui/-/issues",
|
|
56
56
|
"homepage": "https://sleekspace.skewedaspect.com",
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"vue": "^3.3.0"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="classes" :style="customStyles">
|
|
2
|
+
<div :class="classes" :style="customStyles" :data-scheme="theme">
|
|
3
3
|
<header v-if="$slots.header" class="sk-page-header">
|
|
4
4
|
<slot name="header" />
|
|
5
5
|
</header>
|
|
@@ -62,11 +62,15 @@
|
|
|
62
62
|
* width below the sidebar and content areas.
|
|
63
63
|
*/
|
|
64
64
|
|
|
65
|
-
import { computed } from 'vue';
|
|
65
|
+
import { computed, provide, watch } from 'vue';
|
|
66
66
|
|
|
67
67
|
// Types
|
|
68
|
+
import type { SkThemeName } from '../Theme/types';
|
|
68
69
|
import type { SkPageSidebarPosition } from './types';
|
|
69
70
|
|
|
71
|
+
// Composables
|
|
72
|
+
import { provideTheme } from '../Theme/useTheme';
|
|
73
|
+
|
|
70
74
|
//------------------------------------------------------------------------------------------------------------------
|
|
71
75
|
|
|
72
76
|
export interface SkPageComponentProps
|
|
@@ -101,6 +105,14 @@
|
|
|
101
105
|
* Only applies when the sidebar slot is provided.
|
|
102
106
|
*/
|
|
103
107
|
sidebarWidth ?: string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Optional theme name. When provided, SkPage acts as a theme provider — setting
|
|
111
|
+
* `data-scheme` on the root element and providing theme context for descendant
|
|
112
|
+
* components (including portal components like dropdowns and modals).
|
|
113
|
+
* When omitted, SkPage has no theme behavior.
|
|
114
|
+
*/
|
|
115
|
+
theme ?: SkThemeName;
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -110,6 +122,7 @@
|
|
|
110
122
|
fixedHeader: false,
|
|
111
123
|
fixedFooter: false,
|
|
112
124
|
sidebarWidth: undefined,
|
|
125
|
+
theme: undefined,
|
|
113
126
|
});
|
|
114
127
|
|
|
115
128
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -138,6 +151,27 @@
|
|
|
138
151
|
'--sk-page-sidebar-width': props.sidebarWidth,
|
|
139
152
|
};
|
|
140
153
|
});
|
|
154
|
+
|
|
155
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
156
|
+
// Theme Provider
|
|
157
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
if(props.theme)
|
|
160
|
+
{
|
|
161
|
+
const { currentTheme, setTheme } = provideTheme(props.theme);
|
|
162
|
+
|
|
163
|
+
// Provide theme for portal components (dropdown, modal, tooltip, etc.)
|
|
164
|
+
provide('sk-theme', currentTheme);
|
|
165
|
+
|
|
166
|
+
// Watch for external theme prop changes
|
|
167
|
+
watch(() => props.theme, (newTheme) =>
|
|
168
|
+
{
|
|
169
|
+
if(newTheme)
|
|
170
|
+
{
|
|
171
|
+
setTheme(newTheme);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
141
175
|
</script>
|
|
142
176
|
|
|
143
177
|
<!--------------------------------------------------------------------------------------------------------------------->
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// Page Types
|
|
3
3
|
//----------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
import type { SkThemeName } from '../Theme/types';
|
|
6
|
+
|
|
7
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
8
|
+
|
|
5
9
|
/** Sidebar position */
|
|
6
10
|
export type SkPageSidebarPosition = 'left' | 'right';
|
|
7
11
|
|
|
@@ -16,6 +20,8 @@ export interface SkPageProps
|
|
|
16
20
|
fixedFooter ?: boolean;
|
|
17
21
|
/** Custom sidebar width */
|
|
18
22
|
sidebarWidth ?: string;
|
|
23
|
+
/** Optional theme name — when provided, SkPage acts as a theme provider */
|
|
24
|
+
theme ?: SkThemeName;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -2,12 +2,29 @@
|
|
|
2
2
|
// Theme Component Types
|
|
3
3
|
//----------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Registry of available theme names. Augment this interface to register custom themes:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* declare module '@skewedaspect/sleekspace-ui' {
|
|
10
|
+
* interface SkThemeNameMap {
|
|
11
|
+
* ocean : true;
|
|
12
|
+
* midnight : true;
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface SkThemeNameMap
|
|
18
|
+
{
|
|
19
|
+
greyscale : true;
|
|
20
|
+
colorful : true;
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
/**
|
|
6
24
|
* Available theme names that control the overall color scheme of the application.
|
|
7
|
-
*
|
|
8
|
-
* - 'colorful': A vibrant theme with saturated colors and stronger contrasts
|
|
25
|
+
* Derived from {@link SkThemeNameMap} — augment that interface to add custom themes.
|
|
9
26
|
*/
|
|
10
|
-
export type SkThemeName =
|
|
27
|
+
export type SkThemeName = keyof SkThemeNameMap;
|
|
11
28
|
|
|
12
29
|
//----------------------------------------------------------------------------------------------------------------------
|
|
13
30
|
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
|
|
57
57
|
/* Smooth transitions for interactive states */
|
|
58
58
|
transition:
|
|
59
|
-
background-color var(--sk-transition-duration-fast) var(--sk-transition-easing-
|
|
60
|
-
border-color var(--sk-transition-duration-fast) var(--sk-transition-easing-
|
|
59
|
+
background-color var(--sk-transition-duration-fast) var(--sk-transition-easing-ease-out),
|
|
60
|
+
border-color var(--sk-transition-duration-fast) var(--sk-transition-easing-ease-out);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/* Hover State */
|
|
@@ -21,7 +21,7 @@ body
|
|
|
21
21
|
{
|
|
22
22
|
margin: 0;
|
|
23
23
|
min-height: 100svh;
|
|
24
|
-
color: var(--sk-
|
|
24
|
+
color: var(--sk-text-primary);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
img,
|
|
@@ -40,8 +40,8 @@ canvas
|
|
|
40
40
|
|
|
41
41
|
:focus-visible
|
|
42
42
|
{
|
|
43
|
-
outline: var(--sk-focus-
|
|
44
|
-
outline-offset: var(--sk-focus-
|
|
43
|
+
outline: var(--sk-focus-ring-width) solid currentColor;
|
|
44
|
+
outline-offset: var(--sk-focus-ring-offset);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -103,10 +103,10 @@
|
|
|
103
103
|
--sk-button-outline-bg: rgba(5, 5, 5, 0.15);
|
|
104
104
|
|
|
105
105
|
/// Subtle variant background opacity (references global foundation token)
|
|
106
|
-
--sk-button-subtle-opacity:
|
|
106
|
+
--sk-button-subtle-opacity: 0.35;
|
|
107
107
|
|
|
108
108
|
/// Subtle variant hover background opacity (references global foundation token)
|
|
109
|
-
--sk-button-subtle-opacity-hover:
|
|
109
|
+
--sk-button-subtle-opacity-hover: 0.65;
|
|
110
110
|
|
|
111
111
|
/// Subtle variant border opacity (references global foundation token)
|
|
112
112
|
--sk-button-subtle-border-opacity: var(--sk-opacity-subtle-border);
|
|
@@ -260,10 +260,9 @@
|
|
|
260
260
|
|
|
261
261
|
&:hover:not(:disabled)
|
|
262
262
|
{
|
|
263
|
-
--sk-button-border-color: oklch(from var(--sk-button-border-base) l c h / var(--sk-button-subtle-border-opacity-hover));
|
|
264
263
|
--sk-button-glow-shadow: 0 0 var(--sk-button-glow-size) oklch(from var(--sk-button-border-base) l c h / var(--sk-button-glow-opacity));
|
|
265
264
|
|
|
266
|
-
background-color:
|
|
265
|
+
background-color: var(--sk-button-bg-hover);
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
&:active:not(:disabled)
|
|
@@ -534,6 +533,12 @@
|
|
|
534
533
|
@include button-subtle-kind($kind);
|
|
535
534
|
}
|
|
536
535
|
|
|
536
|
+
&:disabled,
|
|
537
|
+
&.sk-loading
|
|
538
|
+
{
|
|
539
|
+
background-color: oklch(from var(--sk-button-bg) l c h / 0.45);
|
|
540
|
+
}
|
|
541
|
+
|
|
537
542
|
// Special case: neutral text and border are lighter (halfway to white) for better differentiation
|
|
538
543
|
&.sk-neutral
|
|
539
544
|
{
|
|
@@ -624,20 +629,51 @@
|
|
|
624
629
|
|
|
625
630
|
&.sk-pressed
|
|
626
631
|
{
|
|
627
|
-
&.sk-
|
|
628
|
-
&.sk-ghost
|
|
632
|
+
&.sk-solid
|
|
629
633
|
{
|
|
630
|
-
|
|
634
|
+
--sk-button-border-color: var(--sk-button-border-hover);
|
|
635
|
+
background-color: var(--sk-button-bg-hover);
|
|
636
|
+
color: var(--sk-button-text);
|
|
637
|
+
|
|
638
|
+
&:hover:not(:disabled)
|
|
639
|
+
{
|
|
640
|
+
--sk-button-glow-shadow: 0 0 var(--sk-button-glow-size) oklch(from var(--sk-button-border-base) l c h / var(--sk-button-glow-opacity));
|
|
641
|
+
background-color: color-mix(in oklch, var(--sk-button-bg) 70%, white 30%);
|
|
642
|
+
}
|
|
631
643
|
}
|
|
632
644
|
|
|
633
|
-
&.sk-
|
|
645
|
+
&.sk-outline
|
|
634
646
|
{
|
|
635
|
-
|
|
647
|
+
--sk-button-border-color: var(--sk-button-border-hover);
|
|
648
|
+
background-color: var(--sk-button-bg);
|
|
649
|
+
color: var(--sk-button-text);
|
|
636
650
|
}
|
|
637
651
|
|
|
638
652
|
&.sk-subtle
|
|
639
653
|
{
|
|
654
|
+
--sk-button-border-color: transparent;
|
|
640
655
|
background-color: var(--sk-button-bg);
|
|
656
|
+
color: var(--sk-button-text);
|
|
657
|
+
|
|
658
|
+
&:hover:not(:disabled)
|
|
659
|
+
{
|
|
660
|
+
--sk-button-border-color: transparent;
|
|
661
|
+
--sk-button-glow-shadow: 0 0 var(--sk-button-glow-size) oklch(from var(--sk-button-border-base) l c h / var(--sk-button-glow-opacity));
|
|
662
|
+
background-color: var(--sk-button-bg-hover);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
&.sk-ghost
|
|
667
|
+
{
|
|
668
|
+
--sk-button-border-color: color-mix(in oklch, var(--sk-button-border-base) 50%, transparent 50%);
|
|
669
|
+
border-color: color-mix(in oklch, var(--sk-button-border-base) 50%, transparent 50%);
|
|
670
|
+
background-color: var(--sk-button-outline-bg);
|
|
671
|
+
color: var(--sk-button-text);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
&.sk-link
|
|
675
|
+
{
|
|
676
|
+
color: var(--sk-button-text);
|
|
641
677
|
}
|
|
642
678
|
}
|
|
643
679
|
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
display: flex;
|
|
13
13
|
flex-direction: column;
|
|
14
14
|
min-height: 100vh;
|
|
15
|
-
background: var(--sk-
|
|
16
|
-
color: var(--sk-
|
|
15
|
+
background: var(--sk-surface-base);
|
|
16
|
+
color: var(--sk-text-primary);
|
|
17
17
|
|
|
18
18
|
//------------------------------------------------------------------------------------------------------------------
|
|
19
19
|
// Header
|
|
@@ -83,10 +83,14 @@ To create a new theme:
|
|
|
83
83
|
@forward 'mytheme'; // Add your theme
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
4. **
|
|
86
|
+
4. **Register the TypeScript type** via module augmentation (e.g. in a `themes.d.ts` or your app's entry file):
|
|
87
87
|
|
|
88
88
|
```typescript
|
|
89
|
-
|
|
89
|
+
declare module '@skewedaspect/sleekspace-ui' {
|
|
90
|
+
interface SkThemeNameMap {
|
|
91
|
+
mytheme : true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
90
94
|
```
|
|
91
95
|
|
|
92
96
|
5. **Use your theme**:
|
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
--sk-opacity-strong: 0.60;
|
|
157
157
|
--sk-opacity-opaque: 1;
|
|
158
158
|
|
|
159
|
+
/* Semantic opacity aliases */
|
|
160
|
+
--sk-opacity-disabled: 0.5;
|
|
161
|
+
--sk-opacity-loading: 0.6;
|
|
162
|
+
|
|
159
163
|
/* ===================================================================
|
|
160
164
|
* Color Mixing Percentages
|
|
161
165
|
* Tokenized mixing amounts for color-mix() function
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
--sk-transition-easing-ease-out: ease-out;
|
|
27
27
|
--sk-transition-easing-ease-in-out: ease-in-out;
|
|
28
28
|
|
|
29
|
+
/* ===================================================================
|
|
30
|
+
* Default Aliases
|
|
31
|
+
* Generic names that resolve to the most common values
|
|
32
|
+
* =================================================================== */
|
|
33
|
+
|
|
34
|
+
--sk-transition-duration-base: var(--sk-transition-duration-normal);
|
|
35
|
+
--sk-transition-easing: var(--sk-transition-easing-ease-out);
|
|
36
|
+
|
|
29
37
|
/* ===================================================================
|
|
30
38
|
* Common Transition Combinations
|
|
31
39
|
* Convenience tokens for frequently used transitions
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Background and text colors for surfaces and general use.
|
|
5
5
|
* These provide a consistent color system independent of component kinds.
|
|
6
|
+
*
|
|
7
|
+
* Defaults are defined on :root as fallbacks. Themes can override any of these
|
|
8
|
+
* via [data-scheme] selectors. The [data-scheme] rule also applies background
|
|
9
|
+
* and text color so that any themed element gets the correct surface treatment.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
:root
|
|
@@ -34,3 +38,14 @@
|
|
|
34
38
|
--sk-border-normal: var(--sk-color-gray-70); /* Standard borders */
|
|
35
39
|
--sk-border-subtle: var(--sk-color-gray-80); /* Subtle borders/dividers */
|
|
36
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Apply text color to any themed element so descendants inherit the correct
|
|
44
|
+
* color without needing to set it themselves. Background is intentionally NOT
|
|
45
|
+
* set here — page-level containers like SkPage handle their own background
|
|
46
|
+
* via the surface tokens.
|
|
47
|
+
*/
|
|
48
|
+
[data-scheme]
|
|
49
|
+
{
|
|
50
|
+
color: var(--sk-text-primary);
|
|
51
|
+
}
|