@liftkit-vue/core 0.1.0 → 0.2.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/LICENSE +21 -0
- package/package.json +11 -7
- package/src/components/LkBadge.vue +64 -0
- package/src/components/LkButton.vue +102 -0
- package/src/components/LkCard.vue +85 -0
- package/src/components/LkColumn.vue +40 -0
- package/src/components/LkContainer.vue +26 -0
- package/src/components/LkDropdown/LkDropdown.vue +14 -0
- package/src/components/LkDropdown/LkDropdownMenu.vue +99 -0
- package/src/components/LkDropdown/LkDropdownTrigger.vue +39 -0
- package/src/components/LkDropdown/index.ts +3 -0
- package/src/components/LkGrid.vue +58 -0
- package/src/components/LkHeading.vue +43 -0
- package/src/components/LkIcon.vue +56 -0
- package/src/components/LkIconButton.vue +78 -0
- package/src/components/LkImage.vue +62 -0
- package/src/components/LkMaterialLayer.vue +147 -0
- package/src/components/LkMenuItem.vue +51 -0
- package/src/components/LkNavbar.vue +113 -0
- package/src/components/LkPlaceholderBlock.vue +9 -0
- package/src/components/LkRow.vue +40 -0
- package/src/components/LkSection.vue +45 -0
- package/src/components/LkSelect.vue +167 -0
- package/src/components/LkSelectMenu.vue +67 -0
- package/src/components/LkSelectOption.vue +56 -0
- package/src/components/LkSelectTrigger.vue +43 -0
- package/src/components/LkSnackbar.vue +127 -0
- package/src/components/LkStateLayer.vue +33 -0
- package/src/components/LkSticker.vue +42 -0
- package/src/components/LkSwitch.vue +90 -0
- package/src/components/LkTabContent.vue +33 -0
- package/src/components/LkTabLink.vue +50 -0
- package/src/components/LkTabMenu.vue +59 -0
- package/src/components/LkTabs.vue +65 -0
- package/src/components/LkText.vue +37 -0
- package/src/components/LkTextInput.vue +105 -0
- package/src/components/LkTheme.vue +49 -0
- package/src/components/LkThemeController.vue +396 -0
- package/src/css/index.css +3 -1
- package/src/css/liftkit-core.css +5 -464
- package/src/css/liftkit-reset.css +74 -0
- package/src/css/liftkit-tokens.css +375 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, watch } from 'vue'
|
|
3
|
+
import { Teleport } from 'vue'
|
|
4
|
+
import { useTheme, type PaletteState } from '../composables'
|
|
5
|
+
|
|
6
|
+
type LkColorGroup =
|
|
7
|
+
| 'master'
|
|
8
|
+
| 'primary'
|
|
9
|
+
| 'secondary'
|
|
10
|
+
| 'tertiary'
|
|
11
|
+
| 'neutral'
|
|
12
|
+
| 'neutralvariant'
|
|
13
|
+
| 'error'
|
|
14
|
+
| 'warning'
|
|
15
|
+
| 'success'
|
|
16
|
+
| 'info'
|
|
17
|
+
|
|
18
|
+
const { palette, updateTheme, updateThemeFromMaster, colorMode, colorModePreference, setColorMode } = useTheme()
|
|
19
|
+
|
|
20
|
+
const brandPalette: LkColorGroup[] = ['primary', 'secondary', 'tertiary']
|
|
21
|
+
const semanticPalette: LkColorGroup[] = ['error', 'warning', 'success', 'info']
|
|
22
|
+
const layoutPalette: LkColorGroup[] = ['neutral', 'neutralvariant']
|
|
23
|
+
|
|
24
|
+
const paletteArray = computed(() =>
|
|
25
|
+
Object.keys(palette.value).map((key) => ({
|
|
26
|
+
key,
|
|
27
|
+
value: palette.value[key],
|
|
28
|
+
}))
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const isOpen = ref(false)
|
|
32
|
+
|
|
33
|
+
function handleColorChange(key: LkColorGroup, newValue: string) {
|
|
34
|
+
if (key === 'master') {
|
|
35
|
+
updateThemeFromMaster(newValue)
|
|
36
|
+
} else {
|
|
37
|
+
palette.value = {
|
|
38
|
+
...palette.value,
|
|
39
|
+
[key]: newValue,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function handleColorModeChange(mode: 'auto' | 'light' | 'dark') {
|
|
45
|
+
setColorMode(mode)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function handleCopyPalette() {
|
|
49
|
+
try {
|
|
50
|
+
const codeContent = `const palette = ${JSON.stringify(palette.value, null, 2)}\nconst colorMode = '${colorMode.value}'`
|
|
51
|
+
await navigator.clipboard.writeText(codeContent)
|
|
52
|
+
alert('Code copied')
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('Failed to copy palette:', err)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getColorDescription(group: LkColorGroup): string {
|
|
59
|
+
const descriptions: Record<string, string> = {
|
|
60
|
+
primary: 'Main brand color, used for most UI elements.',
|
|
61
|
+
secondary: 'Desaturated variant of primary.',
|
|
62
|
+
tertiary: 'Your accent color. Defaults to complementary hue to primary.',
|
|
63
|
+
error: 'A pink or red, indicating problems.',
|
|
64
|
+
warning: 'An orange or yellow, indicating caution.',
|
|
65
|
+
success: 'A green, indicating success.',
|
|
66
|
+
info: 'A blue, indicating neutral information.',
|
|
67
|
+
neutral: 'Backgrounds, surfaces, outlines, and default text color',
|
|
68
|
+
neutralvariant: 'Surface variant, outline variant, and text color variant',
|
|
69
|
+
}
|
|
70
|
+
return descriptions[group] || ''
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<div v-if="!isOpen" class="position-fixed" :style="{ zIndex: 1000 }">
|
|
76
|
+
<button
|
|
77
|
+
class="lk-theme-controller-trigger shadow-xl"
|
|
78
|
+
:style="{
|
|
79
|
+
position: 'fixed',
|
|
80
|
+
zIndex: 1000,
|
|
81
|
+
top: 'var(--lk-size-xs)',
|
|
82
|
+
left: 'var(--lk-size-xs)',
|
|
83
|
+
}"
|
|
84
|
+
@click="isOpen = true"
|
|
85
|
+
>
|
|
86
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="2 2 20 20" fill="currentColor"><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2.5 2.5 0 0 0 2.5-2.5c0-.61-.23-1.21-.64-1.67a.528.528 0 0 1-.13-.33c0-.28.22-.5.5-.5H16c3.31 0 6-2.69 6-6 0-4.96-4.49-9-10-9m-5.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m3-4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m3 4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/></svg>
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<Teleport to="body">
|
|
91
|
+
<div v-if="isOpen" class="lk-theme-drawer">
|
|
92
|
+
<div class="lk-theme-drawer-card shadow-lg mb-2xl">
|
|
93
|
+
<div class="lk-theme-drawer-scroll">
|
|
94
|
+
<div class="lk-column" style="gap: var(--lk-size-md)">
|
|
95
|
+
<!-- Header -->
|
|
96
|
+
<div class="lk-row" style="align-items: center; justify-content: space-between">
|
|
97
|
+
<h2 class="body-bold">Theme Controller</h2>
|
|
98
|
+
<button
|
|
99
|
+
:style="{
|
|
100
|
+
display: 'inline-flex',
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
justifyContent: 'center',
|
|
103
|
+
background: '#e8e0e5',
|
|
104
|
+
border: 'none',
|
|
105
|
+
borderRadius: '50%',
|
|
106
|
+
width: '36px',
|
|
107
|
+
height: '36px',
|
|
108
|
+
cursor: 'pointer',
|
|
109
|
+
padding: '0',
|
|
110
|
+
}"
|
|
111
|
+
@click="isOpen = false"
|
|
112
|
+
aria-label="Close"
|
|
113
|
+
>
|
|
114
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" style="display: block"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="#1b1b1f"/></svg>
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Config export -->
|
|
119
|
+
<div>
|
|
120
|
+
<h2 class="label mb-xs">Config</h2>
|
|
121
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
122
|
+
Copy and paste this snippet into your theme config to update your project to match
|
|
123
|
+
the current configuration.
|
|
124
|
+
</p>
|
|
125
|
+
<div class="lk-theme-code-block position-relative">
|
|
126
|
+
<pre style="font-size: 0.618em; overflow: auto">
|
|
127
|
+
const palette = {{ JSON.stringify(palette, null, 2) }}
|
|
128
|
+
const colorMode = '{{ colorMode }}'</pre
|
|
129
|
+
>
|
|
130
|
+
<button
|
|
131
|
+
:style="{
|
|
132
|
+
display: 'inline-flex',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
background: '#e8e0e5',
|
|
136
|
+
border: 'none',
|
|
137
|
+
borderRadius: '50%',
|
|
138
|
+
width: '36px',
|
|
139
|
+
height: '36px',
|
|
140
|
+
cursor: 'pointer',
|
|
141
|
+
padding: '0',
|
|
142
|
+
position: 'absolute',
|
|
143
|
+
top: '1em',
|
|
144
|
+
right: '1em',
|
|
145
|
+
}"
|
|
146
|
+
@click="handleCopyPalette"
|
|
147
|
+
>
|
|
148
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" style="display: block"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" fill="#1b1b1f"/></svg>
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<!-- Color Mode -->
|
|
154
|
+
<div>
|
|
155
|
+
<h2 class="capline mb-lg color-onsurfacevariant">Mode</h2>
|
|
156
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
157
|
+
Current: {{ colorMode }} {{ colorModePreference === 'auto' ? '(auto)' : '' }}
|
|
158
|
+
</p>
|
|
159
|
+
<div class="lk-row" style="gap: var(--lk-size-xs)">
|
|
160
|
+
<button
|
|
161
|
+
v-for="mode in (['auto', 'light', 'dark'] as const)"
|
|
162
|
+
:key="mode"
|
|
163
|
+
class="lk-mode-button"
|
|
164
|
+
:class="{ active: colorModePreference === mode }"
|
|
165
|
+
@click="handleColorModeChange(mode)"
|
|
166
|
+
>
|
|
167
|
+
{{ mode }}
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Master color -->
|
|
173
|
+
<div>
|
|
174
|
+
<h2 class="capline mb-lg color-onsurfacevariant">Globals</h2>
|
|
175
|
+
<div class="lk-row" style="align-items: start; gap: var(--lk-size-md)">
|
|
176
|
+
<input
|
|
177
|
+
type="color"
|
|
178
|
+
name="master"
|
|
179
|
+
class="lk-color-input"
|
|
180
|
+
@input="($event) => handleColorChange('master', ($event.target as HTMLInputElement).value)"
|
|
181
|
+
/>
|
|
182
|
+
<div class="lk-column">
|
|
183
|
+
<label class="label mb-xs" for="master">master</label>
|
|
184
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
185
|
+
The seed color.
|
|
186
|
+
<strong class="color-error"
|
|
187
|
+
>If you edit this, all other color tokens will reset.</strong
|
|
188
|
+
>
|
|
189
|
+
</p>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<!-- Brand colors -->
|
|
195
|
+
<div>
|
|
196
|
+
<h2 class="capline mb-lg color-onsurfacevariant">Brand</h2>
|
|
197
|
+
<div
|
|
198
|
+
v-for="colorGroup in brandPalette"
|
|
199
|
+
:key="colorGroup"
|
|
200
|
+
class="lk-row mb-sm"
|
|
201
|
+
style="align-items: start; gap: var(--lk-size-md)"
|
|
202
|
+
>
|
|
203
|
+
<input
|
|
204
|
+
type="color"
|
|
205
|
+
:name="colorGroup"
|
|
206
|
+
:value="palette[colorGroup]"
|
|
207
|
+
class="lk-color-input"
|
|
208
|
+
@input="($event) => handleColorChange(colorGroup, ($event.target as HTMLInputElement).value)"
|
|
209
|
+
/>
|
|
210
|
+
<div class="lk-column">
|
|
211
|
+
<label class="caption-bold mono mb-2xs" :for="colorGroup">{{
|
|
212
|
+
colorGroup
|
|
213
|
+
}}</label>
|
|
214
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
215
|
+
{{ getColorDescription(colorGroup) }}
|
|
216
|
+
</p>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<!-- Semantic colors -->
|
|
222
|
+
<div>
|
|
223
|
+
<h2 class="capline color-onsurfacevariant mb-lg">Semantic</h2>
|
|
224
|
+
<div
|
|
225
|
+
v-for="colorGroup in semanticPalette"
|
|
226
|
+
:key="colorGroup"
|
|
227
|
+
class="lk-row mb-sm"
|
|
228
|
+
style="align-items: start; gap: var(--lk-size-md)"
|
|
229
|
+
>
|
|
230
|
+
<input
|
|
231
|
+
type="color"
|
|
232
|
+
:name="colorGroup"
|
|
233
|
+
:value="palette[colorGroup]"
|
|
234
|
+
class="lk-color-input"
|
|
235
|
+
@input="($event) => handleColorChange(colorGroup, ($event.target as HTMLInputElement).value)"
|
|
236
|
+
/>
|
|
237
|
+
<div class="lk-column">
|
|
238
|
+
<label class="caption-bold mono mb-2xs" :for="colorGroup">{{
|
|
239
|
+
colorGroup
|
|
240
|
+
}}</label>
|
|
241
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
242
|
+
{{ getColorDescription(colorGroup) }}
|
|
243
|
+
</p>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<!-- Layout colors -->
|
|
249
|
+
<div>
|
|
250
|
+
<h2 class="capline color-onsurfacevariant mb-lg">Layout</h2>
|
|
251
|
+
<div
|
|
252
|
+
v-for="colorGroup in layoutPalette"
|
|
253
|
+
:key="colorGroup"
|
|
254
|
+
class="lk-row mb-sm"
|
|
255
|
+
style="align-items: start; gap: var(--lk-size-md)"
|
|
256
|
+
>
|
|
257
|
+
<input
|
|
258
|
+
type="color"
|
|
259
|
+
:name="colorGroup"
|
|
260
|
+
:value="palette[colorGroup]"
|
|
261
|
+
class="lk-color-input"
|
|
262
|
+
@input="($event) => handleColorChange(colorGroup, ($event.target as HTMLInputElement).value)"
|
|
263
|
+
/>
|
|
264
|
+
<div class="lk-column">
|
|
265
|
+
<label class="caption-bold mono mb-xs" :for="colorGroup">{{
|
|
266
|
+
colorGroup
|
|
267
|
+
}}</label>
|
|
268
|
+
<p class="caption color-onsurfacevariant mb-xs">
|
|
269
|
+
{{ getColorDescription(colorGroup) }}
|
|
270
|
+
</p>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</Teleport>
|
|
279
|
+
</template>
|
|
280
|
+
|
|
281
|
+
<style scoped>
|
|
282
|
+
.lk-theme-controller-trigger {
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
justify-content: center;
|
|
286
|
+
background: var(--light__inversesurface_lkv, #303034);
|
|
287
|
+
color: var(--light__inverseonsurface_lkv, #f2f0f4);
|
|
288
|
+
border: none;
|
|
289
|
+
border-radius: 50%;
|
|
290
|
+
width: 48px;
|
|
291
|
+
height: 48px;
|
|
292
|
+
cursor: pointer;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.lk-color-input {
|
|
296
|
+
-webkit-appearance: none;
|
|
297
|
+
-moz-appearance: none;
|
|
298
|
+
appearance: none;
|
|
299
|
+
width: 36px;
|
|
300
|
+
height: 36px;
|
|
301
|
+
flex: 0 0 auto;
|
|
302
|
+
background-color: transparent;
|
|
303
|
+
border: 2px solid var(--light__onsurface_lkv, #1b1b1f);
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
outline: none;
|
|
306
|
+
border-radius: 100em;
|
|
307
|
+
padding: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.lk-color-input::-webkit-color-swatch-wrapper {
|
|
311
|
+
padding: 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.lk-color-input::-webkit-color-swatch {
|
|
315
|
+
border-radius: 100em;
|
|
316
|
+
border: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.lk-color-input::-moz-color-swatch {
|
|
320
|
+
border-radius: 100%;
|
|
321
|
+
border: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.lk-theme-drawer {
|
|
325
|
+
position: fixed;
|
|
326
|
+
top: 0;
|
|
327
|
+
left: 0;
|
|
328
|
+
bottom: 0;
|
|
329
|
+
overflow: hidden;
|
|
330
|
+
width: calc(var(--lk-size-4xl, 320px) * var(--lk-wholestep, 1.5));
|
|
331
|
+
z-index: 1000;
|
|
332
|
+
padding: var(--lk-size-md, 16px);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.lk-theme-drawer-card {
|
|
336
|
+
height: 100%;
|
|
337
|
+
background: var(--light__surfacecontainerlowest_lkv, #ffffff);
|
|
338
|
+
color: var(--light__onsurface_lkv, #1b1b1f);
|
|
339
|
+
border-radius: var(--lk-size-sm, 8px);
|
|
340
|
+
padding: var(--lk-size-md, 16px);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.lk-theme-drawer-scroll {
|
|
344
|
+
height: 100%;
|
|
345
|
+
overflow-y: auto;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.lk-theme-code-block {
|
|
349
|
+
background: var(--lk-surfacecontainerlowest, #ffffff);
|
|
350
|
+
border-radius: var(--lk-size-xs, 4px);
|
|
351
|
+
padding: var(--lk-size-sm, 8px);
|
|
352
|
+
position: relative;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.lk-icon-button {
|
|
356
|
+
display: inline-flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
background: var(--light__surfacecontainerhigh_lkv, #e8e0e5);
|
|
360
|
+
border: none;
|
|
361
|
+
border-radius: 50%;
|
|
362
|
+
width: 36px;
|
|
363
|
+
height: 36px;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
color: var(--light__onsurface_lkv, #1b1b1f);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.lk-column {
|
|
369
|
+
display: flex;
|
|
370
|
+
flex-direction: column;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.lk-row {
|
|
374
|
+
display: flex;
|
|
375
|
+
flex-direction: row;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.lk-mode-button {
|
|
379
|
+
flex: 1;
|
|
380
|
+
padding: 6px 12px;
|
|
381
|
+
border: 1px solid var(--light__outline_lkv, #767680);
|
|
382
|
+
border-radius: 100em;
|
|
383
|
+
background: transparent;
|
|
384
|
+
color: var(--light__onsurface_lkv, #1b1b1f);
|
|
385
|
+
cursor: pointer;
|
|
386
|
+
font-size: 0.75rem;
|
|
387
|
+
font-weight: 500;
|
|
388
|
+
text-transform: capitalize;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.lk-mode-button.active {
|
|
392
|
+
background: var(--light__primary_lkv, #004ee7);
|
|
393
|
+
color: var(--light__onprimary_lkv, #ffffff);
|
|
394
|
+
border-color: var(--light__primary_lkv, #004ee7);
|
|
395
|
+
}
|
|
396
|
+
</style>
|