@flux-ui/components 3.0.0-next.34 → 3.0.0-next.35
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/component/FluxCommandPalette.vue.d.ts +52 -0
- package/dist/component/FluxCommandPaletteGroup.vue.d.ts +8 -0
- package/dist/component/FluxCommandPaletteItem.vue.d.ts +18 -0
- package/dist/component/FluxTag.vue.d.ts +1 -0
- package/dist/component/index.d.ts +4 -1
- package/dist/composable/private/useCommandPalette.d.ts +38 -0
- package/dist/index.css +320 -0
- package/dist/index.js +633 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/component/FluxCommandPalette.vue +290 -0
- package/src/component/FluxCommandPaletteGroup.vue +23 -0
- package/src/component/FluxCommandPaletteItem.vue +60 -0
- package/src/component/FluxTableActions.vue +3 -3
- package/src/component/FluxTag.vue +3 -1
- package/src/component/index.ts +4 -1
- package/src/composable/private/useCommandPalette.ts +405 -0
- package/src/css/component/Badge.module.scss +7 -0
- package/src/css/component/CommandPalette.module.scss +332 -0
- /package/dist/component/{FluxActions.vue.d.ts → FluxActionStack.vue.d.ts} +0 -0
- /package/src/component/{FluxActions.vue → FluxActionStack.vue} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flux-ui/components",
|
|
3
3
|
"description": "A set of opiniated UI components.",
|
|
4
|
-
"version": "3.0.0-next.
|
|
4
|
+
"version": "3.0.0-next.35",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/basmilius",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@basmilius/common": "^3.12.1",
|
|
55
55
|
"@basmilius/utils": "^3.12.1",
|
|
56
|
-
"@flux-ui/internals": "3.0.0-next.
|
|
57
|
-
"@flux-ui/types": "3.0.0-next.
|
|
56
|
+
"@flux-ui/internals": "3.0.0-next.35",
|
|
57
|
+
"@flux-ui/types": "3.0.0-next.35",
|
|
58
58
|
"@fortawesome/fontawesome-common-types": "^7.2.0",
|
|
59
59
|
"clsx": "^2.1.1",
|
|
60
60
|
"imask": "^7.6.1",
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isOpen || isClosing"
|
|
5
|
+
:class="[$style.commandPaletteBackdrop, isClosing && $style.isClosing]"
|
|
6
|
+
@click="close"/>
|
|
7
|
+
|
|
8
|
+
<div
|
|
9
|
+
v-if="isOpen || isClosing"
|
|
10
|
+
ref="dialogRef"
|
|
11
|
+
:class="[$style.commandPaletteDialog, isClosing && $style.isClosing]"
|
|
12
|
+
@click.self="close"
|
|
13
|
+
@keydown="onKeyDown">
|
|
14
|
+
<div
|
|
15
|
+
v-height-transition
|
|
16
|
+
:class="$style.commandPalette"
|
|
17
|
+
@mousedown.prevent>
|
|
18
|
+
<div :class="$style.commandPaletteSearch">
|
|
19
|
+
<FluxIcon
|
|
20
|
+
:class="$style.commandPaletteSearchIcon"
|
|
21
|
+
name="magnifying-glass"/>
|
|
22
|
+
|
|
23
|
+
<template v-if="activeTabSource">
|
|
24
|
+
<button
|
|
25
|
+
:class="$style.commandPaletteBreadcrumb"
|
|
26
|
+
tabindex="-1"
|
|
27
|
+
type="button"
|
|
28
|
+
@click="clearSubActionTarget"
|
|
29
|
+
@mousedown.prevent>
|
|
30
|
+
{{ activeTabSource.label }}
|
|
31
|
+
</button>
|
|
32
|
+
|
|
33
|
+
<span :class="$style.commandPaletteBreadcrumbSeparator">/</span>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<template v-if="subActionTarget">
|
|
37
|
+
<button
|
|
38
|
+
:class="$style.commandPaletteBreadcrumb"
|
|
39
|
+
tabindex="-1"
|
|
40
|
+
type="button"
|
|
41
|
+
@mousedown.prevent>
|
|
42
|
+
{{ subActionTarget.label }}
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<span :class="$style.commandPaletteBreadcrumbSeparator">/</span>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<input
|
|
49
|
+
ref="inputRef"
|
|
50
|
+
:class="$style.commandPaletteSearchInput"
|
|
51
|
+
:placeholder="placeholder ?? 'Search...'"
|
|
52
|
+
:value="search"
|
|
53
|
+
type="text"
|
|
54
|
+
@input="setSearch(($event.target as HTMLInputElement).value)"/>
|
|
55
|
+
|
|
56
|
+
<FluxTag
|
|
57
|
+
is-keyboard-shortcut
|
|
58
|
+
label="Esc"/>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
v-if="tabs.length > 0 && !subActionTarget"
|
|
63
|
+
:class="$style.commandPaletteTabs">
|
|
64
|
+
<button
|
|
65
|
+
:class="activeTab === null ? $style.commandPaletteTabActive : $style.commandPaletteTab"
|
|
66
|
+
tabindex="-1"
|
|
67
|
+
type="button"
|
|
68
|
+
@click="setActiveTab(null)"
|
|
69
|
+
@mousedown.prevent>
|
|
70
|
+
All
|
|
71
|
+
</button>
|
|
72
|
+
|
|
73
|
+
<button
|
|
74
|
+
v-for="tab of tabs"
|
|
75
|
+
:key="tab.key"
|
|
76
|
+
:class="activeTab === tab.key ? $style.commandPaletteTabActive : $style.commandPaletteTab"
|
|
77
|
+
tabindex="-1"
|
|
78
|
+
type="button"
|
|
79
|
+
@click="setActiveTab(tab.key)"
|
|
80
|
+
@mousedown.prevent>
|
|
81
|
+
<FluxIcon
|
|
82
|
+
v-if="tab.icon"
|
|
83
|
+
:class="$style.commandPaletteTabIcon"
|
|
84
|
+
:name="tab.icon"/>
|
|
85
|
+
|
|
86
|
+
{{ tab.label }}
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<FluxWindowTransition :is-back="isTransitioningBack">
|
|
91
|
+
<div
|
|
92
|
+
:key="activeTab"
|
|
93
|
+
:class="$style.commandPaletteResults">
|
|
94
|
+
<template v-if="subActionTarget">
|
|
95
|
+
<FluxCommandPaletteItem
|
|
96
|
+
v-for="(action, index) of subActions"
|
|
97
|
+
:key="index"
|
|
98
|
+
ref="itemRefs"
|
|
99
|
+
:icon="action.icon"
|
|
100
|
+
:is-highlighted="highlightedIndex === index"
|
|
101
|
+
:label="action.label"
|
|
102
|
+
@activate="activateSubAction(action)"
|
|
103
|
+
@highlight="highlightedIndex = index"/>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<template v-else-if="groupedItems.length > 0">
|
|
107
|
+
<template
|
|
108
|
+
v-for="group of groupedItems"
|
|
109
|
+
:key="group.sourceKey">
|
|
110
|
+
<FluxCommandPaletteGroup
|
|
111
|
+
v-if="group.sourceLabel"
|
|
112
|
+
:label="group.sourceLabel"/>
|
|
113
|
+
|
|
114
|
+
<FluxCommandPaletteItem
|
|
115
|
+
v-for="result of group.items"
|
|
116
|
+
:key="result.item.id"
|
|
117
|
+
ref="itemRefs"
|
|
118
|
+
:command="result.item.command"
|
|
119
|
+
:has-sub-actions="!!result.item.subActions?.length"
|
|
120
|
+
:icon="result.item.icon"
|
|
121
|
+
:is-highlighted="highlightedIndex === result.globalIndex"
|
|
122
|
+
:label="result.item.label"
|
|
123
|
+
:sub-label="result.item.subLabel"
|
|
124
|
+
@activate="activateItem(result.item)"
|
|
125
|
+
@highlight="highlightedIndex = result.globalIndex"/>
|
|
126
|
+
</template>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<div
|
|
130
|
+
v-else-if="isLoading"
|
|
131
|
+
:class="$style.commandPaletteLoading">
|
|
132
|
+
<FluxSpinner :size="22"/>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div
|
|
136
|
+
v-else
|
|
137
|
+
:class="$style.commandPaletteEmpty">
|
|
138
|
+
No results found.
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</FluxWindowTransition>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</Teleport>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<script
|
|
148
|
+
lang="ts"
|
|
149
|
+
setup>
|
|
150
|
+
import type { FluxCommandSource, FluxCommandSourceItem } from '@flux-ui/types';
|
|
151
|
+
import { isSSR, vHeightTransition } from '@flux-ui/internals';
|
|
152
|
+
import { onMounted, onUnmounted, ref, toRef, unref, useTemplateRef } from 'vue';
|
|
153
|
+
import { useCommandPalette } from '$flux/composable/private/useCommandPalette';
|
|
154
|
+
import FluxCommandPaletteGroup from './FluxCommandPaletteGroup.vue';
|
|
155
|
+
import FluxCommandPaletteItem from './FluxCommandPaletteItem.vue';
|
|
156
|
+
import FluxIcon from './FluxIcon.vue';
|
|
157
|
+
import FluxSpinner from './FluxSpinner.vue';
|
|
158
|
+
import FluxTag from './FluxTag.vue';
|
|
159
|
+
import FluxWindowTransition from '$flux/transition/FluxWindowTransition.vue';
|
|
160
|
+
import $style from '$flux/css/component/CommandPalette.module.scss';
|
|
161
|
+
|
|
162
|
+
const props = defineProps<{
|
|
163
|
+
readonly hasKeyboardShortcut?: boolean;
|
|
164
|
+
readonly placeholder?: string;
|
|
165
|
+
readonly sources: FluxCommandSource[];
|
|
166
|
+
}>();
|
|
167
|
+
|
|
168
|
+
const emit = defineEmits<{
|
|
169
|
+
select: [item: FluxCommandSourceItem];
|
|
170
|
+
}>();
|
|
171
|
+
|
|
172
|
+
const dialogRef = useTemplateRef<HTMLDivElement>('dialogRef');
|
|
173
|
+
const inputRef = useTemplateRef<HTMLInputElement>('inputRef');
|
|
174
|
+
const itemRefs = ref<InstanceType<typeof FluxCommandPaletteItem>[]>();
|
|
175
|
+
|
|
176
|
+
const isOpen = ref(false);
|
|
177
|
+
const isClosing = ref(false);
|
|
178
|
+
|
|
179
|
+
const {
|
|
180
|
+
search,
|
|
181
|
+
activeTab,
|
|
182
|
+
activeTabSource,
|
|
183
|
+
highlightedIndex,
|
|
184
|
+
isLoading,
|
|
185
|
+
isTransitioningBack,
|
|
186
|
+
subActionTarget,
|
|
187
|
+
groupedItems,
|
|
188
|
+
subActions,
|
|
189
|
+
tabs,
|
|
190
|
+
setSearch,
|
|
191
|
+
setActiveTab,
|
|
192
|
+
enterSubActions,
|
|
193
|
+
onKeyNavigate,
|
|
194
|
+
reset
|
|
195
|
+
} = useCommandPalette({
|
|
196
|
+
sources: toRef(() => props.sources),
|
|
197
|
+
itemRefs
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
function open(): void {
|
|
201
|
+
if (unref(isOpen)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
isOpen.value = true;
|
|
206
|
+
|
|
207
|
+
requestAnimationFrame(() => unref(inputRef)?.focus());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function close(): void {
|
|
211
|
+
if (!unref(isOpen)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const dialog = unref(dialogRef);
|
|
216
|
+
|
|
217
|
+
if (!dialog) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
isClosing.value = true;
|
|
222
|
+
|
|
223
|
+
const finishClose = () => {
|
|
224
|
+
isClosing.value = false;
|
|
225
|
+
isOpen.value = false;
|
|
226
|
+
reset();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const fallbackTimeout = setTimeout(finishClose, 250);
|
|
230
|
+
|
|
231
|
+
dialog.addEventListener('animationend', () => {
|
|
232
|
+
clearTimeout(fallbackTimeout);
|
|
233
|
+
finishClose();
|
|
234
|
+
}, {once: true});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function clearSubActionTarget(): void {
|
|
238
|
+
subActionTarget.value = null;
|
|
239
|
+
highlightedIndex.value = -1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function activateItem(item: FluxCommandSourceItem): void {
|
|
243
|
+
if (item.subActions?.length) {
|
|
244
|
+
enterSubActions(item);
|
|
245
|
+
} else {
|
|
246
|
+
item.onActivate();
|
|
247
|
+
emit('select', item);
|
|
248
|
+
close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function activateSubAction(action: { readonly onActivate: () => void; }): void {
|
|
253
|
+
action.onActivate();
|
|
254
|
+
close();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function onKeyDown(evt: KeyboardEvent): void {
|
|
258
|
+
onKeyNavigate(evt, close, (item) => {
|
|
259
|
+
emit('select', item);
|
|
260
|
+
close();
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function onGlobalKeyDown(evt: KeyboardEvent): void {
|
|
265
|
+
if (evt.key === 'k' && (evt.metaKey || evt.ctrlKey)) {
|
|
266
|
+
evt.preventDefault();
|
|
267
|
+
|
|
268
|
+
if (unref(isOpen)) {
|
|
269
|
+
close();
|
|
270
|
+
} else {
|
|
271
|
+
open();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!isSSR && props.hasKeyboardShortcut) {
|
|
277
|
+
onMounted(() => {
|
|
278
|
+
window.addEventListener('keydown', onGlobalKeyDown);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
onUnmounted(() => {
|
|
282
|
+
window.removeEventListener('keydown', onGlobalKeyDown);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
defineExpose({
|
|
287
|
+
close,
|
|
288
|
+
open
|
|
289
|
+
});
|
|
290
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.commandPaletteGroup">
|
|
3
|
+
<FluxIcon
|
|
4
|
+
v-if="icon"
|
|
5
|
+
:class="$style.commandPaletteGroupIcon"
|
|
6
|
+
:name="icon"/>
|
|
7
|
+
|
|
8
|
+
<span>{{ label }}</span>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script
|
|
13
|
+
lang="ts"
|
|
14
|
+
setup>
|
|
15
|
+
import type { FluxIconName } from '@flux-ui/types';
|
|
16
|
+
import FluxIcon from './FluxIcon.vue';
|
|
17
|
+
import $style from '$flux/css/component/CommandPalette.module.scss';
|
|
18
|
+
|
|
19
|
+
defineProps<{
|
|
20
|
+
readonly icon?: FluxIconName;
|
|
21
|
+
readonly label: string;
|
|
22
|
+
}>();
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
:class="isHighlighted ? $style.commandPaletteItemHighlighted : $style.commandPaletteItem"
|
|
4
|
+
tabindex="-1"
|
|
5
|
+
type="button"
|
|
6
|
+
@click="$emit('activate')"
|
|
7
|
+
@mousedown.prevent
|
|
8
|
+
@mouseenter="$emit('highlight')">
|
|
9
|
+
<div
|
|
10
|
+
v-if="icon"
|
|
11
|
+
:class="$style.commandPaletteItemIcon">
|
|
12
|
+
<FluxIcon :name="icon"/>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div :class="$style.commandPaletteItemContent">
|
|
16
|
+
<div :class="$style.commandPaletteItemLabel">
|
|
17
|
+
{{ label }}
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div
|
|
21
|
+
v-if="subLabel"
|
|
22
|
+
:class="$style.commandPaletteItemSubLabel">
|
|
23
|
+
{{ subLabel }}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<FluxTag
|
|
28
|
+
v-if="command"
|
|
29
|
+
is-keyboard-shortcut
|
|
30
|
+
:label="command"/>
|
|
31
|
+
|
|
32
|
+
<FluxIcon
|
|
33
|
+
v-if="hasSubActions"
|
|
34
|
+
:class="$style.commandPaletteItemSubActionIndicator"
|
|
35
|
+
name="chevron-right"/>
|
|
36
|
+
</button>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script
|
|
40
|
+
lang="ts"
|
|
41
|
+
setup>
|
|
42
|
+
import type { FluxIconName } from '@flux-ui/types';
|
|
43
|
+
import FluxIcon from './FluxIcon.vue';
|
|
44
|
+
import FluxTag from './FluxTag.vue';
|
|
45
|
+
import $style from '$flux/css/component/CommandPalette.module.scss';
|
|
46
|
+
|
|
47
|
+
defineEmits<{
|
|
48
|
+
activate: [];
|
|
49
|
+
highlight: [];
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
defineProps<{
|
|
53
|
+
readonly command?: string;
|
|
54
|
+
readonly hasSubActions?: boolean;
|
|
55
|
+
readonly icon?: FluxIconName;
|
|
56
|
+
readonly isHighlighted?: boolean;
|
|
57
|
+
readonly label: string;
|
|
58
|
+
readonly subLabel?: string;
|
|
59
|
+
}>();
|
|
60
|
+
</script>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<FluxActionStack :class="$style.tableActions">
|
|
3
3
|
<slot/>
|
|
4
|
-
</
|
|
4
|
+
</FluxActionStack>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script
|
|
8
8
|
lang="ts"
|
|
9
9
|
setup>
|
|
10
|
-
import
|
|
10
|
+
import FluxActionStack from './FluxActionStack.vue';
|
|
11
11
|
import $style from '$flux/css/component/Table.module.scss';
|
|
12
12
|
|
|
13
13
|
defineSlots<{
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
color === 'danger' && $style.tagDanger,
|
|
7
7
|
color === 'info' && $style.tagInfo,
|
|
8
8
|
color === 'success' && $style.tagSuccess,
|
|
9
|
-
color === 'warning' && $style.tagWarning
|
|
9
|
+
color === 'warning' && $style.tagWarning,
|
|
10
|
+
isKeyboardShortcut && $style.tagKeyboardShortcut
|
|
10
11
|
)"
|
|
11
12
|
:component-type="type"
|
|
12
13
|
:tabindex="tabindex"
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
readonly icon?: FluxIconName;
|
|
70
71
|
readonly isClickable?: boolean;
|
|
71
72
|
readonly isDeletable?: boolean;
|
|
73
|
+
readonly isKeyboardShortcut?: boolean;
|
|
72
74
|
readonly isLoading?: boolean;
|
|
73
75
|
readonly label: string;
|
|
74
76
|
readonly type?: FluxPressableType;
|
package/src/component/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as FluxAction } from './FluxAction.vue';
|
|
2
2
|
export { default as FluxActionBar } from './FluxActionBar.vue';
|
|
3
3
|
export { default as FluxActionPane } from './FluxActionPane.vue';
|
|
4
|
-
export { default as
|
|
4
|
+
export { default as FluxActionStack } from './FluxActionStack.vue';
|
|
5
5
|
export { default as FluxAnimatedColors } from './FluxAnimatedColors.vue';
|
|
6
6
|
export { default as FluxAspectRatio } from './FluxAspectRatio.vue';
|
|
7
7
|
export { default as FluxAutoGrid } from './FluxAutoGrid.vue';
|
|
@@ -18,6 +18,9 @@ export { default as FluxCalendarEvent } from './FluxCalendarEvent.vue';
|
|
|
18
18
|
export { default as FluxCheckbox } from './FluxCheckbox.vue';
|
|
19
19
|
export { default as FluxChip } from './FluxChip.vue';
|
|
20
20
|
export { default as FluxClickablePane } from './FluxClickablePane.vue';
|
|
21
|
+
export { default as FluxCommandPalette } from './FluxCommandPalette.vue';
|
|
22
|
+
export { default as FluxCommandPaletteGroup } from './FluxCommandPaletteGroup.vue';
|
|
23
|
+
export { default as FluxCommandPaletteItem } from './FluxCommandPaletteItem.vue';
|
|
21
24
|
export { default as FluxComment } from './FluxComment.vue';
|
|
22
25
|
export { default as FluxColorPicker } from './FluxColorPicker.vue';
|
|
23
26
|
export { default as FluxColorSelect } from './FluxColorSelect.vue';
|