@leavepulse/ui 0.46.0 → 0.47.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/assets/HighlightText-BwC5xbqn.css +6 -0
- package/dist/assets/{LpCommandPalette-5miKYsb-.css → LpCommandPalette-DmQUsJqA.css} +21 -21
- package/dist/assets/LpKeys-BxRhfQda.css +36 -0
- package/dist/assets/LpPaletteFacts-B9FdVmIO.css +93 -0
- package/dist/assets/LpPaletteHeader-BVOu1UX5.css +70 -0
- package/dist/assets/LpPalettePreview-CJmAKgYW.css +43 -0
- package/dist/assets/LpPaletteStats-ClE-37MK.css +33 -0
- package/dist/assets/LpPaletteSwatches-CA0B8y2E.css +21 -0
- package/dist/assets/{LpShortcutsDialog-CqYX3yy6.css → LpShortcutsDialog-BUp_BX4s.css} +7 -28
- package/dist/assets/{LpTable-BoeA_TgK.css → LpTable-xWwI4KPZ.css} +17 -17
- package/dist/chunks/HighlightText-BvnYZkhi.js +27 -0
- package/dist/chunks/{LpAvatar-DeSb6H0c.js → LpAvatar-Cb1jtOVv.js} +21 -3
- package/dist/chunks/{LpLightbox-DSDahF23.js → LpLightbox-EVXzAq62.js} +3 -3
- package/dist/chunks/{LpMediaGallery-YKr-cQPN.js → LpMediaGallery-BnlkN_1f.js} +1 -1
- package/dist/chunks/LpPalettePreview-CFav_7co.js +19 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/HighlightText.vue.d.ts +7 -0
- package/dist/components/HighlightText.vue.js +4 -0
- package/dist/components/LpAvatar.vue.d.ts +12 -0
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +152 -15
- package/dist/components/LpCommandPalette.vue.js +178 -92
- package/dist/components/LpKeys.vue.d.ts +22 -0
- package/dist/components/LpKeys.vue.js +52 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.d.ts +25 -0
- package/dist/components/LpPaletteFacts.vue.js +85 -0
- package/dist/components/LpPaletteHeader.vue.d.ts +32 -0
- package/dist/components/LpPaletteHeader.vue.js +103 -0
- package/dist/components/LpPalettePreview.vue.d.ts +19 -0
- package/dist/components/LpPalettePreview.vue.js +4 -0
- package/dist/components/LpPaletteStats.vue.d.ts +11 -0
- package/dist/components/LpPaletteStats.vue.js +47 -0
- package/dist/components/LpPaletteSwatches.vue.d.ts +10 -0
- package/dist/components/LpPaletteSwatches.vue.js +34 -0
- package/dist/components/LpShortcutsDialog.vue.js +8 -28
- package/dist/components/LpTable.vue.js +5 -3
- package/dist/components/commandPalette.d.ts +157 -0
- package/dist/components/commandPalette.js +103 -0
- package/dist/components/commandSearch.d.ts +32 -0
- package/dist/components/commandSearch.js +48 -0
- package/dist/components/keys.d.ts +2 -0
- package/dist/components/keys.js +11 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +157 -134
- package/package.json +1 -1
- package/src/component-names.ts +6 -0
- package/src/components/HighlightText.vue +28 -0
- package/src/components/LpAvatar.vue +21 -2
- package/src/components/LpCommandPalette.vue +387 -87
- package/src/components/LpKeys.vue +79 -0
- package/src/components/LpLightbox.vue +4 -1
- package/src/components/LpPaletteFacts.vue +165 -0
- package/src/components/LpPaletteHeader.vue +155 -0
- package/src/components/LpPalettePreview.vue +77 -0
- package/src/components/LpPaletteStats.vue +81 -0
- package/src/components/LpPaletteSwatches.vue +56 -0
- package/src/components/LpShortcutsDialog.vue +2 -45
- package/src/components/LpTable.vue +6 -1
- package/src/components/commandPalette.ts +264 -0
- package/src/components/commandSearch.ts +79 -0
- package/src/components/keys.ts +13 -0
- package/src/index.ts +12 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* A key combination drawn as keycaps: "Ctrl+K" is two caps, "G I" is G then
|
|
4
|
+
* I. Written the way LpShortcutsDialog reads combos, and drawn the same way,
|
|
5
|
+
* so a hint in a palette preview, a tooltip or a menu matches the reference.
|
|
6
|
+
*
|
|
7
|
+
* <LpKeys combo="Ctrl+Shift+P" label="from anywhere" />
|
|
8
|
+
*
|
|
9
|
+
* On an Apple keyboard Ctrl and Alt read as the symbols printed on its keys.
|
|
10
|
+
*/
|
|
11
|
+
import { computed } from "vue"
|
|
12
|
+
import { appleModifierLabel, useAppleKeyboard } from "../composables/useHotkeys"
|
|
13
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
14
|
+
import { comboSteps } from "./keys"
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(
|
|
17
|
+
defineProps<{
|
|
18
|
+
/** "Ctrl+K", "Ctrl++", or a sequence "G I". Or keys already split. */
|
|
19
|
+
combo: string | string[]
|
|
20
|
+
/** Text after the caps, e.g. what the keys do. */
|
|
21
|
+
label?: string
|
|
22
|
+
size?: "sm" | "md"
|
|
23
|
+
}>(),
|
|
24
|
+
{ size: "md" },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const text = useLabels("shortcuts", { then: "then" })
|
|
28
|
+
const apple = useAppleKeyboard()
|
|
29
|
+
|
|
30
|
+
const steps = computed(() => (Array.isArray(props.combo) ? [props.combo] : comboSteps(props.combo)))
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<span class="lp-keys" :class="`lp-keys--${size}`">
|
|
35
|
+
<template v-for="(step, stepIndex) in steps" :key="stepIndex">
|
|
36
|
+
<span v-if="stepIndex" class="lp-keys__then">{{ text.then }}</span>
|
|
37
|
+
<kbd v-for="(key, index) in step" :key="`${key}-${index}`">{{ appleModifierLabel(key.trim(), apple) }}</kbd>
|
|
38
|
+
</template>
|
|
39
|
+
<span v-if="label || $slots.default" class="lp-keys__label"><slot>{{ label }}</slot></span>
|
|
40
|
+
</span>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<style scoped>
|
|
44
|
+
.lp-keys {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 3px;
|
|
49
|
+
color: var(--color-muted);
|
|
50
|
+
}
|
|
51
|
+
.lp-keys__then {
|
|
52
|
+
padding: 0 2px;
|
|
53
|
+
font-size: 0.68rem;
|
|
54
|
+
}
|
|
55
|
+
.lp-keys__label {
|
|
56
|
+
margin-left: 5px;
|
|
57
|
+
font-size: 0.75rem;
|
|
58
|
+
}
|
|
59
|
+
kbd {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
min-width: 20px;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
padding: 2px 6px;
|
|
65
|
+
border: 1px solid var(--color-line-strong);
|
|
66
|
+
border-bottom-width: 2px;
|
|
67
|
+
border-radius: 5px;
|
|
68
|
+
background: color-mix(in srgb, var(--color-ink) 6%, transparent);
|
|
69
|
+
color: var(--color-muted-strong);
|
|
70
|
+
font-family: inherit;
|
|
71
|
+
font-size: 0.7rem;
|
|
72
|
+
line-height: 1.5;
|
|
73
|
+
}
|
|
74
|
+
.lp-keys--sm kbd {
|
|
75
|
+
min-width: 16px;
|
|
76
|
+
padding: 0 4px;
|
|
77
|
+
font-size: 0.65rem;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -435,9 +435,12 @@ const ARROW =
|
|
|
435
435
|
<template>
|
|
436
436
|
<DialogRoot :open="open" @update:open="(v) => emit('update:open', v)">
|
|
437
437
|
<DialogPortal>
|
|
438
|
+
<!-- Not `lp-scrim--nested` even over another layer: that variant drops
|
|
439
|
+
the scrim's blur because a nested dialog brings its own glass panel,
|
|
440
|
+
and a lightbox has none, so the drawer or dialog it opened from read
|
|
441
|
+
sharp around the picture. -->
|
|
438
442
|
<DialogOverlay
|
|
439
443
|
class="lp-scrim fixed inset-0 data-[state=open]:animate-[fade-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[fade-out_120ms_ease]"
|
|
440
|
-
:class="{ 'lp-scrim--nested': layer.nested }"
|
|
441
444
|
:style="{ zIndex: layer.scrim }"
|
|
442
445
|
/>
|
|
443
446
|
<DialogContent
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* A short list of facts in a palette preview: a label, and what it is set to
|
|
4
|
+
* now. The tabs of a page, the fields of a settings section, a person's
|
|
5
|
+
* standing, the instance a world belongs to. An icon or a colour dot leads
|
|
6
|
+
* each row; `current` marks the one in effect.
|
|
7
|
+
*
|
|
8
|
+
* <LpPaletteFacts
|
|
9
|
+
* title="Now"
|
|
10
|
+
* :items="[
|
|
11
|
+
* { icon: 'lucide:server', label: 'Instance', value: 'Survival' },
|
|
12
|
+
* { color: '#5865f2', label: 'Role colour', value: '#5865F2', mono: true },
|
|
13
|
+
* ]"
|
|
14
|
+
* />
|
|
15
|
+
*
|
|
16
|
+
* Values roll when they change while the row is on screen (a theme picked
|
|
17
|
+
* with keepOpen, a count that refreshed), through LpRollingText. The `value`
|
|
18
|
+
* slot draws a value of the app's own (chips, a switch) instead.
|
|
19
|
+
*/
|
|
20
|
+
import HighlightText from "./HighlightText.vue"
|
|
21
|
+
import LpIcon from "./LpIcon.vue"
|
|
22
|
+
import LpRollingText from "./LpRollingText.vue"
|
|
23
|
+
import type { PaletteFact } from "./commandPalette"
|
|
24
|
+
|
|
25
|
+
withDefaults(
|
|
26
|
+
defineProps<{
|
|
27
|
+
items: PaletteFact[]
|
|
28
|
+
/** Small heading over the list. */
|
|
29
|
+
title?: string
|
|
30
|
+
/** Plain rows instead of soft tiles, for a list read as text. */
|
|
31
|
+
plain?: boolean
|
|
32
|
+
}>(),
|
|
33
|
+
{ plain: false },
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
defineSlots<{
|
|
37
|
+
value?: (props: { item: PaletteFact; index: number }) => unknown
|
|
38
|
+
}>()
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<section class="lp-palette-facts" :class="{ 'lp-palette-facts--plain': plain }">
|
|
43
|
+
<p v-if="title" class="lp-palette-facts__title">{{ title }}</p>
|
|
44
|
+
<ul class="lp-palette-facts__list">
|
|
45
|
+
<li
|
|
46
|
+
v-for="(item, index) in items"
|
|
47
|
+
:key="index"
|
|
48
|
+
class="lp-palette-facts__row"
|
|
49
|
+
:class="{ 'is-current': item.current }"
|
|
50
|
+
:aria-current="item.current || undefined"
|
|
51
|
+
>
|
|
52
|
+
<span v-if="item.color" class="lp-palette-facts__dot" :style="{ background: item.color }" />
|
|
53
|
+
<LpIcon v-else-if="item.icon" :name="item.icon" :size="15" class="lp-palette-facts__icon" />
|
|
54
|
+
<span class="lp-palette-facts__label">
|
|
55
|
+
<span class="lp-palette-facts__name"><HighlightText :text="item.label" /></span>
|
|
56
|
+
<span v-if="item.hint?.length" class="lp-palette-facts__hint"><HighlightText :text="item.hint" /></span>
|
|
57
|
+
</span>
|
|
58
|
+
<span
|
|
59
|
+
v-if="item.value !== undefined && item.value !== '' || $slots.value"
|
|
60
|
+
class="lp-palette-facts__value"
|
|
61
|
+
:class="{ 'lp-palette-facts__value--mono': item.mono }"
|
|
62
|
+
>
|
|
63
|
+
<slot name="value" :item="item" :index="index">
|
|
64
|
+
<LpRollingText v-if="item.value !== undefined" :value="item.value" align="end" />
|
|
65
|
+
</slot>
|
|
66
|
+
</span>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</section>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<style scoped>
|
|
73
|
+
.lp-palette-facts {
|
|
74
|
+
min-width: 0;
|
|
75
|
+
}
|
|
76
|
+
.lp-palette-facts__title {
|
|
77
|
+
margin: 0 0 0.4rem;
|
|
78
|
+
color: var(--color-muted);
|
|
79
|
+
font-size: 11px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
letter-spacing: 0.08em;
|
|
82
|
+
text-transform: uppercase;
|
|
83
|
+
}
|
|
84
|
+
.lp-palette-facts__list {
|
|
85
|
+
display: grid;
|
|
86
|
+
gap: 2px;
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 0;
|
|
89
|
+
list-style: none;
|
|
90
|
+
}
|
|
91
|
+
.lp-palette-facts__row {
|
|
92
|
+
display: flex;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 0.6rem;
|
|
96
|
+
padding: 0.4rem 0.6rem;
|
|
97
|
+
border-radius: var(--radius-control);
|
|
98
|
+
background: color-mix(in srgb, var(--color-ink) 3%, transparent);
|
|
99
|
+
color: var(--color-ink);
|
|
100
|
+
font-size: 0.8125rem;
|
|
101
|
+
transition: background-color var(--duration-fast, 150ms) ease;
|
|
102
|
+
}
|
|
103
|
+
.lp-palette-facts--plain .lp-palette-facts__row {
|
|
104
|
+
padding: 0.15rem 0;
|
|
105
|
+
background: transparent;
|
|
106
|
+
}
|
|
107
|
+
.lp-palette-facts__row.is-current {
|
|
108
|
+
background: var(--color-brand-soft);
|
|
109
|
+
}
|
|
110
|
+
.lp-palette-facts--plain .lp-palette-facts__row.is-current {
|
|
111
|
+
background: transparent;
|
|
112
|
+
color: var(--color-brand);
|
|
113
|
+
}
|
|
114
|
+
.lp-palette-facts__icon {
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
color: var(--color-muted);
|
|
117
|
+
transition: color var(--duration-fast, 150ms) ease;
|
|
118
|
+
}
|
|
119
|
+
.is-current .lp-palette-facts__icon {
|
|
120
|
+
color: var(--color-brand);
|
|
121
|
+
}
|
|
122
|
+
.lp-palette-facts__dot {
|
|
123
|
+
width: 0.55rem;
|
|
124
|
+
height: 0.55rem;
|
|
125
|
+
flex-shrink: 0;
|
|
126
|
+
margin: 0 0.15rem;
|
|
127
|
+
border-radius: 9999px;
|
|
128
|
+
box-shadow: 0 0 0 1px var(--color-line);
|
|
129
|
+
}
|
|
130
|
+
.lp-palette-facts__label {
|
|
131
|
+
display: flex;
|
|
132
|
+
min-width: 0;
|
|
133
|
+
flex: 1;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
}
|
|
136
|
+
.lp-palette-facts__name {
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
text-overflow: ellipsis;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
}
|
|
141
|
+
.lp-palette-facts__hint {
|
|
142
|
+
margin-top: 1px;
|
|
143
|
+
color: var(--color-muted);
|
|
144
|
+
font-size: 0.75rem;
|
|
145
|
+
line-height: 1.45;
|
|
146
|
+
}
|
|
147
|
+
.lp-palette-facts__value {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
min-width: 0;
|
|
150
|
+
max-width: 55%;
|
|
151
|
+
flex-shrink: 0;
|
|
152
|
+
justify-content: flex-end;
|
|
153
|
+
overflow: hidden;
|
|
154
|
+
color: var(--color-muted);
|
|
155
|
+
font-size: 0.75rem;
|
|
156
|
+
font-variant-numeric: tabular-nums;
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
}
|
|
159
|
+
.lp-palette-facts__value :deep(.lp-roll) {
|
|
160
|
+
max-width: 100%;
|
|
161
|
+
}
|
|
162
|
+
.lp-palette-facts__value--mono {
|
|
163
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* The head of a palette preview: what kind of thing this is (eyebrow), its
|
|
4
|
+
* name, a line under the name, and a lead paragraph. A picture or an icon
|
|
5
|
+
* sits beside the name; badges and other extras go in the default slot,
|
|
6
|
+
* between the name and the lead.
|
|
7
|
+
*
|
|
8
|
+
* <LpPaletteHeader
|
|
9
|
+
* eyebrow="Modrinth"
|
|
10
|
+
* :title="hit.title"
|
|
11
|
+
* :subtitle="hit.author"
|
|
12
|
+
* :image="{ src: hit.iconUrl, fallback: initials }"
|
|
13
|
+
* :lead="hit.description"
|
|
14
|
+
* >
|
|
15
|
+
* <LpBadge>{{ hit.version }}</LpBadge>
|
|
16
|
+
* </LpPaletteHeader>
|
|
17
|
+
*
|
|
18
|
+
* Title, subtitle and lead take the preview slot's `highlight(text)` as well
|
|
19
|
+
* as a string, to mark what was typed:
|
|
20
|
+
*
|
|
21
|
+
* <LpPaletteHeader :title="highlight(command.label)" />
|
|
22
|
+
*
|
|
23
|
+
* The picture goes through LpAvatar: it fades in once loaded and falls back
|
|
24
|
+
* to the initials or the icon when the link is dead.
|
|
25
|
+
*/
|
|
26
|
+
import { computed } from "vue"
|
|
27
|
+
import LpAvatar from "./LpAvatar.vue"
|
|
28
|
+
import LpIcon from "./LpIcon.vue"
|
|
29
|
+
import HighlightText from "./HighlightText.vue"
|
|
30
|
+
import type { HighlightPart } from "./commandSearch"
|
|
31
|
+
import type { CommandImage } from "./LpCommandPalette.vue"
|
|
32
|
+
|
|
33
|
+
const props = defineProps<{
|
|
34
|
+
eyebrow?: string
|
|
35
|
+
/** Text, or the preview slot's `highlight(text)` to mark the query. */
|
|
36
|
+
title?: string | HighlightPart[]
|
|
37
|
+
subtitle?: string | HighlightPart[]
|
|
38
|
+
lead?: string | HighlightPart[]
|
|
39
|
+
/** Icon beside the title, on a soft tile. */
|
|
40
|
+
icon?: string
|
|
41
|
+
/** Picture beside the title; wins over `icon`, which becomes its fallback. */
|
|
42
|
+
image?: string | CommandImage | null
|
|
43
|
+
}>()
|
|
44
|
+
|
|
45
|
+
const picture = computed<CommandImage | null>(() => {
|
|
46
|
+
if (!props.image) return null
|
|
47
|
+
const image = typeof props.image === "string" ? { src: props.image } : props.image
|
|
48
|
+
return { ...image, icon: image.icon ?? props.icon }
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<header class="lp-palette-header">
|
|
54
|
+
<div class="lp-palette-header__row">
|
|
55
|
+
<slot name="media">
|
|
56
|
+
<LpAvatar
|
|
57
|
+
v-if="picture"
|
|
58
|
+
:src="picture.src ?? undefined"
|
|
59
|
+
:alt="picture.alt ?? ''"
|
|
60
|
+
:fallback="picture.fallback"
|
|
61
|
+
:icon="picture.icon"
|
|
62
|
+
:shape="picture.shape ?? 'soft'"
|
|
63
|
+
:pixel-size="44"
|
|
64
|
+
class="lp-palette-header__media"
|
|
65
|
+
/>
|
|
66
|
+
<span v-else-if="icon" class="lp-palette-header__tile">
|
|
67
|
+
<LpIcon :name="icon" :size="18" />
|
|
68
|
+
</span>
|
|
69
|
+
</slot>
|
|
70
|
+
<div class="lp-palette-header__text">
|
|
71
|
+
<p v-if="eyebrow" class="lp-palette-header__eyebrow">{{ eyebrow }}</p>
|
|
72
|
+
<h3 v-if="title?.length || $slots.title" class="lp-palette-header__title">
|
|
73
|
+
<slot name="title"><HighlightText v-if="title" :text="title" /></slot>
|
|
74
|
+
</h3>
|
|
75
|
+
<p v-if="subtitle?.length" class="lp-palette-header__subtitle"><HighlightText :text="subtitle" /></p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div v-if="$slots.default" class="lp-palette-header__extra"><slot /></div>
|
|
79
|
+
<p v-if="lead?.length || $slots.lead" class="lp-palette-header__lead">
|
|
80
|
+
<slot name="lead"><HighlightText v-if="lead" :text="lead" /></slot>
|
|
81
|
+
</p>
|
|
82
|
+
</header>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<style scoped>
|
|
86
|
+
.lp-palette-header {
|
|
87
|
+
display: flex;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 0.6rem;
|
|
91
|
+
}
|
|
92
|
+
.lp-palette-header__row {
|
|
93
|
+
display: flex;
|
|
94
|
+
min-width: 0;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 0.75rem;
|
|
97
|
+
}
|
|
98
|
+
.lp-palette-header__media {
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
}
|
|
101
|
+
.lp-palette-header__tile {
|
|
102
|
+
display: grid;
|
|
103
|
+
width: 2.25rem;
|
|
104
|
+
height: 2.25rem;
|
|
105
|
+
flex-shrink: 0;
|
|
106
|
+
place-items: center;
|
|
107
|
+
border-radius: var(--radius-control);
|
|
108
|
+
background: var(--color-brand-soft);
|
|
109
|
+
color: var(--color-brand);
|
|
110
|
+
}
|
|
111
|
+
.lp-palette-header__text {
|
|
112
|
+
min-width: 0;
|
|
113
|
+
flex: 1;
|
|
114
|
+
}
|
|
115
|
+
.lp-palette-header__eyebrow {
|
|
116
|
+
margin: 0 0 2px;
|
|
117
|
+
color: var(--color-muted);
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
letter-spacing: 0.08em;
|
|
121
|
+
text-transform: uppercase;
|
|
122
|
+
}
|
|
123
|
+
.lp-palette-header__title {
|
|
124
|
+
display: -webkit-box;
|
|
125
|
+
margin: 0;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
-webkit-box-orient: vertical;
|
|
128
|
+
-webkit-line-clamp: 2;
|
|
129
|
+
color: var(--color-ink);
|
|
130
|
+
font-size: 1rem;
|
|
131
|
+
font-weight: 600;
|
|
132
|
+
line-height: 1.3;
|
|
133
|
+
overflow-wrap: anywhere;
|
|
134
|
+
}
|
|
135
|
+
.lp-palette-header__subtitle {
|
|
136
|
+
margin: 2px 0 0;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
color: var(--color-muted);
|
|
139
|
+
font-size: 0.75rem;
|
|
140
|
+
text-overflow: ellipsis;
|
|
141
|
+
white-space: nowrap;
|
|
142
|
+
}
|
|
143
|
+
.lp-palette-header__extra {
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-wrap: wrap;
|
|
146
|
+
gap: 0.375rem;
|
|
147
|
+
}
|
|
148
|
+
.lp-palette-header__lead {
|
|
149
|
+
margin: 0;
|
|
150
|
+
color: var(--color-muted-strong);
|
|
151
|
+
font-size: 0.8125rem;
|
|
152
|
+
line-height: 1.55;
|
|
153
|
+
white-space: pre-line;
|
|
154
|
+
}
|
|
155
|
+
</style>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* The body of LpCommandPalette's `#preview` slot. It spaces the blocks inside
|
|
4
|
+
* it (LpPaletteHeader, LpPaletteFacts, LpPaletteStats, LpKeys, anything of the
|
|
5
|
+
* app's own) and brings them in one after another, the way the palette's own
|
|
6
|
+
* pane brings in paragraphs. Every app that filled the slot wrote this same
|
|
7
|
+
* wrapper with the same stagger; it is one element here.
|
|
8
|
+
*
|
|
9
|
+
* <template #preview="{ command, highlight }">
|
|
10
|
+
* <LpPalettePreview>
|
|
11
|
+
* <LpPaletteHeader :eyebrow="command.group" :title="highlight(command.label)" />
|
|
12
|
+
* <LpPaletteFacts :items="rows" />
|
|
13
|
+
* </LpPalettePreview>
|
|
14
|
+
* </template>
|
|
15
|
+
*
|
|
16
|
+
* The pane remounts its body for every highlighted row, so the entrance plays
|
|
17
|
+
* per row; reduced motion shows the blocks at once.
|
|
18
|
+
*/
|
|
19
|
+
withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
/** Space between blocks. */
|
|
22
|
+
gap?: "sm" | "md" | "lg"
|
|
23
|
+
}>(),
|
|
24
|
+
{ gap: "md" },
|
|
25
|
+
)
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div class="lp-palette-preview" :class="`lp-palette-preview--${gap}`">
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
.lp-palette-preview {
|
|
36
|
+
display: flex;
|
|
37
|
+
min-width: 0;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
color: var(--color-ink);
|
|
40
|
+
}
|
|
41
|
+
.lp-palette-preview--sm {
|
|
42
|
+
gap: 0.6rem;
|
|
43
|
+
}
|
|
44
|
+
.lp-palette-preview--md {
|
|
45
|
+
gap: 0.9rem;
|
|
46
|
+
}
|
|
47
|
+
.lp-palette-preview--lg {
|
|
48
|
+
gap: 1.25rem;
|
|
49
|
+
}
|
|
50
|
+
.lp-palette-preview > * {
|
|
51
|
+
min-width: 0;
|
|
52
|
+
animation: lp-palette-in 260ms var(--ease-emphasized, ease-out) both;
|
|
53
|
+
}
|
|
54
|
+
.lp-palette-preview > :nth-child(2) {
|
|
55
|
+
animation-delay: 40ms;
|
|
56
|
+
}
|
|
57
|
+
.lp-palette-preview > :nth-child(3) {
|
|
58
|
+
animation-delay: 80ms;
|
|
59
|
+
}
|
|
60
|
+
.lp-palette-preview > :nth-child(4) {
|
|
61
|
+
animation-delay: 120ms;
|
|
62
|
+
}
|
|
63
|
+
.lp-palette-preview > :nth-child(n + 5) {
|
|
64
|
+
animation-delay: 160ms;
|
|
65
|
+
}
|
|
66
|
+
@keyframes lp-palette-in {
|
|
67
|
+
from {
|
|
68
|
+
opacity: 0;
|
|
69
|
+
transform: translateY(4px);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
.lp-palette-preview > * {
|
|
74
|
+
animation: none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* A few figures in a palette preview, as tiles: mods and worlds in an
|
|
4
|
+
* instance, downloads and follows of a project, members online. A number
|
|
5
|
+
* turns to a new value through LpNumberFlow, text rolls through
|
|
6
|
+
* LpRollingText, so a figure that refreshes while the row is highlighted
|
|
7
|
+
* moves instead of blinking.
|
|
8
|
+
*
|
|
9
|
+
* <LpPaletteStats :items="[
|
|
10
|
+
* { label: 'Downloads', value: 1250000, compact: true },
|
|
11
|
+
* { label: 'Latest', value: '1.21.4' },
|
|
12
|
+
* ]" />
|
|
13
|
+
*/
|
|
14
|
+
import LpNumberFlow from "./LpNumberFlow.vue"
|
|
15
|
+
import LpRollingText from "./LpRollingText.vue"
|
|
16
|
+
import type { PaletteStat } from "./commandPalette"
|
|
17
|
+
|
|
18
|
+
withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
items: PaletteStat[]
|
|
21
|
+
/** Tiles per row. */
|
|
22
|
+
columns?: 2 | 3 | 4
|
|
23
|
+
}>(),
|
|
24
|
+
{ columns: 2 },
|
|
25
|
+
)
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<dl class="lp-palette-stats" :style="{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }">
|
|
30
|
+
<div v-for="(item, index) in items" :key="`${item.label}-${index}`" class="lp-palette-stats__tile">
|
|
31
|
+
<dt>{{ item.label }}</dt>
|
|
32
|
+
<dd>
|
|
33
|
+
<LpNumberFlow
|
|
34
|
+
v-if="typeof item.value === 'number'"
|
|
35
|
+
:value="item.value"
|
|
36
|
+
:compact="item.compact"
|
|
37
|
+
:suffix="item.suffix"
|
|
38
|
+
:format="item.format"
|
|
39
|
+
:animate-on-mount="false"
|
|
40
|
+
grouped
|
|
41
|
+
/>
|
|
42
|
+
<LpRollingText v-else :value="item.value" align="start" />
|
|
43
|
+
</dd>
|
|
44
|
+
</div>
|
|
45
|
+
</dl>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<style scoped>
|
|
49
|
+
.lp-palette-stats {
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
margin: 0;
|
|
53
|
+
}
|
|
54
|
+
.lp-palette-stats__tile {
|
|
55
|
+
min-width: 0;
|
|
56
|
+
padding: 0.5rem 0.65rem;
|
|
57
|
+
border: 1px solid var(--color-line);
|
|
58
|
+
border-radius: var(--radius-control);
|
|
59
|
+
background: color-mix(in srgb, var(--color-surface-soft) 55%, transparent);
|
|
60
|
+
}
|
|
61
|
+
dt {
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
color: var(--color-muted);
|
|
64
|
+
font-size: 11px;
|
|
65
|
+
text-overflow: ellipsis;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
dd {
|
|
69
|
+
display: flex;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
margin: 2px 0 0;
|
|
72
|
+
color: var(--color-ink);
|
|
73
|
+
font-size: 1.125rem;
|
|
74
|
+
font-weight: 650;
|
|
75
|
+
font-variant-numeric: tabular-nums;
|
|
76
|
+
line-height: 1.3;
|
|
77
|
+
}
|
|
78
|
+
dd :deep(.lp-roll) {
|
|
79
|
+
max-width: 100%;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* A theme's colours as a strip of swatches on its own surface, for a palette
|
|
4
|
+
* preview of a theme picker: the row says the name, this shows what picking
|
|
5
|
+
* it would look like. Any CSS colour works.
|
|
6
|
+
*
|
|
7
|
+
* <LpPaletteSwatches
|
|
8
|
+
* :background="theme.colors.surface"
|
|
9
|
+
* :colors="[theme.colors.ink, theme.colors.brand, theme.colors.accent]"
|
|
10
|
+
* />
|
|
11
|
+
*
|
|
12
|
+
* A swatch that changes (the same row after a keepOpen pick) eases to its
|
|
13
|
+
* new colour.
|
|
14
|
+
*/
|
|
15
|
+
defineProps<{
|
|
16
|
+
colors: string[]
|
|
17
|
+
/** The surface the swatches sit on, usually the theme's own. */
|
|
18
|
+
background?: string
|
|
19
|
+
/** Accessible name of the strip, e.g. the theme's name. */
|
|
20
|
+
label?: string
|
|
21
|
+
}>()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div
|
|
26
|
+
class="lp-palette-swatches"
|
|
27
|
+
role="img"
|
|
28
|
+
:aria-label="label"
|
|
29
|
+
:style="{ background, gridTemplateColumns: `repeat(${Math.max(colors.length, 1)}, minmax(0, 1fr))` }"
|
|
30
|
+
>
|
|
31
|
+
<span v-for="(color, index) in colors" :key="index" :title="color" :style="{ background: color }" />
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
.lp-palette-swatches {
|
|
37
|
+
display: grid;
|
|
38
|
+
gap: 0.4rem;
|
|
39
|
+
padding: 0.7rem;
|
|
40
|
+
border: 1px solid var(--color-line);
|
|
41
|
+
border-radius: var(--radius-card);
|
|
42
|
+
transition: background-color var(--duration-medium, 240ms) ease;
|
|
43
|
+
}
|
|
44
|
+
.lp-palette-swatches span {
|
|
45
|
+
height: 2rem;
|
|
46
|
+
border-radius: var(--radius-control);
|
|
47
|
+
box-shadow: inset 0 0 0 1px rgb(128 128 128 / 0.25);
|
|
48
|
+
transition: background-color var(--duration-medium, 240ms) ease;
|
|
49
|
+
}
|
|
50
|
+
@media (prefers-reduced-motion: reduce) {
|
|
51
|
+
.lp-palette-swatches,
|
|
52
|
+
.lp-palette-swatches span {
|
|
53
|
+
transition: none;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</style>
|