@skewedaspect/sleekspace-ui 0.3.0 → 0.5.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/dist/components/Alert/SkAlert.vue.d.ts +18 -8
- package/dist/components/Alert/types.d.ts +3 -2
- package/dist/components/Page/SkPage.vue.d.ts +9 -0
- package/dist/components/Page/types.d.ts +3 -0
- package/dist/sleekspace-ui.css +47 -24
- package/dist/sleekspace-ui.es.js +67 -41
- package/dist/sleekspace-ui.umd.js +67 -41
- package/dist/tokens.css +23 -0
- 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 +262 -8
- package/package.json +4 -4
- package/src/components/Alert/SkAlert.vue +50 -17
- package/src/components/Alert/types.ts +7 -2
- package/src/components/Page/SkPage.vue +36 -2
- package/src/components/Page/types.ts +6 -0
- package/src/styles/_scrollbar.scss +2 -2
- package/src/styles/base/_global.scss +3 -3
- package/src/styles/components/_alert.scss +5 -5
- package/src/styles/components/_page.scss +2 -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 +24 -4
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,264 @@ 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:** The `theme` prop is typed as `SkThemeName` which is `'greyscale' | 'colorful'`. For custom theme names, cast the value: `:theme="'ocean' as any"`. If you're building a library that adds themes, you can augment the type via module declaration.
|
|
269
|
+
|
|
270
|
+
### Using Foundation Colors
|
|
271
|
+
|
|
272
|
+
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):
|
|
273
|
+
|
|
274
|
+
```css
|
|
275
|
+
[data-scheme="mytheme"] {
|
|
276
|
+
/* Reference foundation palette variables */
|
|
277
|
+
--sk-primary-base: var(--sk-color-purple-50);
|
|
278
|
+
--sk-accent-base: var(--sk-color-pink-50);
|
|
279
|
+
--sk-neutral-base: var(--sk-color-gray-60);
|
|
280
|
+
|
|
281
|
+
/* hover/active states auto-derived the same way */
|
|
282
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
283
|
+
/* ... */
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Available foundation color families: `gray`, `blue`, `red`, `orange`, `yellow`, `green`, `mint`, `cyan`, `purple`, `pink`.
|
|
288
|
+
|
|
289
|
+
### Quick Reference: Minimal Theme
|
|
290
|
+
|
|
291
|
+
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:
|
|
292
|
+
|
|
293
|
+
```css
|
|
294
|
+
[data-scheme="mytheme"] {
|
|
295
|
+
--sk-neutral-base: /* your color */;
|
|
296
|
+
--sk-neutral-hover: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
297
|
+
--sk-neutral-active: color-mix(in oklch, var(--sk-neutral-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
298
|
+
--sk-neutral-text: oklch(1 0 0);
|
|
299
|
+
--sk-neutral-text-contrast: var(--sk-color-gray-95);
|
|
300
|
+
|
|
301
|
+
--sk-primary-base: /* your color */;
|
|
302
|
+
--sk-primary-hover: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
303
|
+
--sk-primary-active: color-mix(in oklch, var(--sk-primary-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
304
|
+
--sk-primary-text: oklch(1 0 0);
|
|
305
|
+
--sk-primary-text-contrast: var(--sk-color-gray-95);
|
|
306
|
+
|
|
307
|
+
--sk-accent-base: /* your color */;
|
|
308
|
+
--sk-accent-hover: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
309
|
+
--sk-accent-active: color-mix(in oklch, var(--sk-accent-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
310
|
+
--sk-accent-text: oklch(1 0 0);
|
|
311
|
+
--sk-accent-text-contrast: var(--sk-color-gray-95);
|
|
312
|
+
|
|
313
|
+
--sk-success-base: /* your color */;
|
|
314
|
+
--sk-success-hover: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
315
|
+
--sk-success-active: color-mix(in oklch, var(--sk-success-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
316
|
+
--sk-success-text: oklch(1 0 0);
|
|
317
|
+
--sk-success-text-contrast: var(--sk-color-gray-95);
|
|
318
|
+
|
|
319
|
+
--sk-warning-base: /* your color */;
|
|
320
|
+
--sk-warning-hover: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
321
|
+
--sk-warning-active: color-mix(in oklch, var(--sk-warning-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
322
|
+
--sk-warning-text: oklch(1 0 0);
|
|
323
|
+
--sk-warning-text-contrast: var(--sk-color-gray-95);
|
|
324
|
+
|
|
325
|
+
--sk-danger-base: /* your color */;
|
|
326
|
+
--sk-danger-hover: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
327
|
+
--sk-danger-active: color-mix(in oklch, var(--sk-danger-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
328
|
+
--sk-danger-text: oklch(1 0 0);
|
|
329
|
+
--sk-danger-text-contrast: var(--sk-color-gray-95);
|
|
330
|
+
|
|
331
|
+
--sk-info-base: /* your color */;
|
|
332
|
+
--sk-info-hover: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-moderate));
|
|
333
|
+
--sk-info-active: color-mix(in oklch, var(--sk-info-base), var(--sk-color-gray-10) var(--sk-mix-amount-strong));
|
|
334
|
+
--sk-info-text: oklch(1 0 0);
|
|
335
|
+
--sk-info-text-contrast: var(--sk-color-gray-95);
|
|
336
|
+
}
|
|
85
337
|
```
|
|
86
338
|
|
|
339
|
+
Only the 7 `-base` values need to change. Everything else is boilerplate that can be copied as-is.
|
|
340
|
+
|
|
87
341
|
## Semantic Kinds
|
|
88
342
|
|
|
89
343
|
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.0",
|
|
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,11 +49,11 @@
|
|
|
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/
|
|
56
|
-
"homepage": "https://
|
|
55
|
+
"bugs": "https://gitlab.com/skewed-aspect/sleekspace-ui/-/issues",
|
|
56
|
+
"homepage": "https://sleekspace.skewedaspect.com",
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"vue": "^3.3.0"
|
|
59
59
|
},
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
6
|
<div :class="classes" :style="customColorStyles" role="alert">
|
|
7
|
-
<div class="sk-alert-icon">
|
|
7
|
+
<div v-if="shouldShowIcon" class="sk-alert-icon">
|
|
8
8
|
<slot name="icon">
|
|
9
|
-
<!-- Default icons for
|
|
9
|
+
<!-- Default icons for feedback kinds -->
|
|
10
10
|
<svg
|
|
11
11
|
v-if="kind === 'info'"
|
|
12
12
|
viewBox="0 0 24 24"
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
/**
|
|
67
67
|
* @component SkAlert
|
|
68
68
|
* @description A contextual feedback component for displaying informational, success, warning, or error messages.
|
|
69
|
-
* Features automatic icon selection based on alert kind and supports
|
|
69
|
+
* Features automatic icon selection based on alert kind and supports prominent (default) and subtle visual styles.
|
|
70
70
|
* Use alerts to communicate important status updates, validation results, or system messages to users.
|
|
71
71
|
*
|
|
72
72
|
* @example
|
|
@@ -74,11 +74,10 @@
|
|
|
74
74
|
* <SkAlert kind="success">Your changes have been saved!</SkAlert>
|
|
75
75
|
* ```
|
|
76
76
|
*
|
|
77
|
-
* @example
|
|
77
|
+
* @example Subtle info alert
|
|
78
78
|
* ```vue
|
|
79
|
-
* <SkAlert kind="
|
|
80
|
-
*
|
|
81
|
-
* This action cannot be undone.
|
|
79
|
+
* <SkAlert kind="info" subtle>
|
|
80
|
+
* A helpful tip for the user.
|
|
82
81
|
* </SkAlert>
|
|
83
82
|
* ```
|
|
84
83
|
*
|
|
@@ -86,7 +85,7 @@
|
|
|
86
85
|
* @slot icon - Custom icon to override the default kind-based icon. Receives no slot props.
|
|
87
86
|
*/
|
|
88
87
|
|
|
89
|
-
import { computed, toRef } from 'vue';
|
|
88
|
+
import { computed, toRef, useSlots } from 'vue';
|
|
90
89
|
|
|
91
90
|
// Types
|
|
92
91
|
import type { SkAlertKind } from './types';
|
|
@@ -97,32 +96,50 @@
|
|
|
97
96
|
|
|
98
97
|
//------------------------------------------------------------------------------------------------------------------
|
|
99
98
|
|
|
99
|
+
// Kinds that have default icons
|
|
100
|
+
const FEEDBACK_KINDS = [ 'info', 'success', 'warning', 'danger' ] as const;
|
|
101
|
+
|
|
102
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
103
|
+
|
|
100
104
|
export interface SkAlertComponentProps extends ComponentCustomColors
|
|
101
105
|
{
|
|
102
106
|
/**
|
|
103
|
-
* Semantic kind that determines the alert's color scheme and default icon.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
107
|
+
* Semantic kind that determines the alert's color scheme and default icon. Supports all
|
|
108
|
+
* semantic kinds (neutral, primary, accent, info, success, warning, danger) as well as
|
|
109
|
+
* direct color kinds (neon-blue, neon-purple, etc.). Default icons are provided for the
|
|
110
|
+
* four feedback kinds (info, success, warning, danger).
|
|
106
111
|
* @default 'info'
|
|
107
112
|
*/
|
|
108
113
|
kind ?: SkAlertKind;
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
|
-
* When true, applies
|
|
112
|
-
*
|
|
113
|
-
*
|
|
116
|
+
* When true, applies subdued styling with a lighter, semi-transparent background.
|
|
117
|
+
* Use subtle alerts for less critical messages that shouldn't dominate the visual
|
|
118
|
+
* hierarchy, such as tips or supplementary information.
|
|
114
119
|
* @default false
|
|
115
120
|
*/
|
|
116
|
-
|
|
121
|
+
subtle ?: boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Controls visibility of the icon container. When undefined (default), the icon is shown
|
|
125
|
+
* automatically if there's a default icon for the kind (info, success, warning, danger)
|
|
126
|
+
* or if custom content is provided via the icon slot. Set to false to force-hide the icon,
|
|
127
|
+
* or true to force-show the container even when empty.
|
|
128
|
+
* @default undefined
|
|
129
|
+
*/
|
|
130
|
+
showIcon ?: boolean;
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
//------------------------------------------------------------------------------------------------------------------
|
|
120
134
|
|
|
121
135
|
const props = withDefaults(defineProps<SkAlertComponentProps>(), {
|
|
122
136
|
kind: 'info',
|
|
123
|
-
|
|
137
|
+
subtle: false,
|
|
138
|
+
showIcon: undefined,
|
|
124
139
|
});
|
|
125
140
|
|
|
141
|
+
const slots = useSlots();
|
|
142
|
+
|
|
126
143
|
//------------------------------------------------------------------------------------------------------------------
|
|
127
144
|
|
|
128
145
|
const classes = computed(() =>
|
|
@@ -130,10 +147,26 @@
|
|
|
130
147
|
return {
|
|
131
148
|
'sk-alert': true,
|
|
132
149
|
[`sk-${ props.kind }`]: true,
|
|
133
|
-
'sk-
|
|
150
|
+
'sk-subtle': props.subtle,
|
|
134
151
|
};
|
|
135
152
|
});
|
|
136
153
|
|
|
154
|
+
// Determine if we should show the icon container
|
|
155
|
+
const shouldShowIcon = computed(() =>
|
|
156
|
+
{
|
|
157
|
+
// Explicit override takes precedence
|
|
158
|
+
if(props.showIcon !== undefined)
|
|
159
|
+
{
|
|
160
|
+
return props.showIcon;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Auto-show if kind has a default icon or if slot content is provided
|
|
164
|
+
const hasDefaultIcon = FEEDBACK_KINDS.includes(props.kind as typeof FEEDBACK_KINDS[number]);
|
|
165
|
+
const hasSlotContent = !!slots.icon;
|
|
166
|
+
|
|
167
|
+
return hasDefaultIcon || hasSlotContent;
|
|
168
|
+
});
|
|
169
|
+
|
|
137
170
|
//------------------------------------------------------------------------------------------------------------------
|
|
138
171
|
|
|
139
172
|
// Custom color styles
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
// Alert Component Types
|
|
3
3
|
//----------------------------------------------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
// Types
|
|
6
|
+
import type { ComponentKind } from '@/types';
|
|
7
|
+
|
|
8
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
|
-
* Alert semantic
|
|
11
|
+
* Alert kind - supports all semantic and color kinds
|
|
7
12
|
*/
|
|
8
|
-
export type SkAlertKind =
|
|
13
|
+
export type SkAlertKind = ComponentKind;
|
|
9
14
|
|
|
10
15
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -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
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -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
|
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
/// Glow effect opacity
|
|
38
38
|
--sk-alert-glow-opacity: 0.3;
|
|
39
39
|
|
|
40
|
-
/// Subtle background opacity (for non-prominent alerts)
|
|
40
|
+
/// Subtle background opacity (for default non-prominent alerts)
|
|
41
41
|
--sk-alert-subtle-opacity: var(--sk-opacity-subtle);
|
|
42
42
|
|
|
43
43
|
//------------------------------------------------------------------------------------------------------------------
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
color: var(--sk-alert-fg);
|
|
108
108
|
border-color: var(--sk-alert-border-color);
|
|
109
109
|
|
|
110
|
-
//
|
|
111
|
-
|
|
110
|
+
// Non-subtle variants get the dark foreground color
|
|
111
|
+
&:not(.sk-subtle)
|
|
112
112
|
{
|
|
113
113
|
--sk-alert-fg: var(--sk-#{ $kind }-text);
|
|
114
114
|
}
|
|
@@ -185,10 +185,10 @@
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
//------------------------------------------------------------------------------------------------------------------
|
|
188
|
-
//
|
|
188
|
+
// Non-subtle (prominent) variant - applied when NOT subtle
|
|
189
189
|
//------------------------------------------------------------------------------------------------------------------
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
&:not(.sk-subtle)
|
|
192
192
|
{
|
|
193
193
|
--sk-alert-padding: 1rem;
|
|
194
194
|
--sk-alert-icon-size: 1.5rem;
|
|
@@ -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
|
|
@@ -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
|