@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/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.34",
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.34",
57
- "@flux-ui/types": "3.0.0-next.34",
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
- <FluxActions :class="$style.tableActions">
2
+ <FluxActionStack :class="$style.tableActions">
3
3
  <slot/>
4
- </FluxActions>
4
+ </FluxActionStack>
5
5
  </template>
6
6
 
7
7
  <script
8
8
  lang="ts"
9
9
  setup>
10
- import FluxActions from './FluxActions.vue';
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;
@@ -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 FluxActions } from './FluxActions.vue';
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';