mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
Allow users to reorder favorite friend groups in the sidebar
This commit is contained in:
130
package-lock.json
generated
130
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"node-api-dotnet": "^0.9.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/vue": "^0.3.2",
|
||||
"@electron/rebuild": "^4.0.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
@@ -741,6 +742,124 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/abstract": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.3.2.tgz",
|
||||
"integrity": "sha512-uvPVK+SZYD6Viddn9M0K0JQdXknuVSxA/EbMlFRanve3P/XTc18oLa5zGftKSGjfQGmuzkZ34E26DSbly1zi3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/geometry": "^0.3.2",
|
||||
"@dnd-kit/state": "^0.3.2",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/abstract/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@dnd-kit/collision": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.3.2.tgz",
|
||||
"integrity": "sha512-pNmNSLCI8S9fNQ7QJ3fBCDjiT0sqBhUFcKgmyYaGvGCAU+kq0AP8OWlh0JSisc9k5mFyxmRpmFQcnJpILz/RPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "^0.3.2",
|
||||
"@dnd-kit/geometry": "^0.3.2",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/collision/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@dnd-kit/dom": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.3.2.tgz",
|
||||
"integrity": "sha512-cIUAVgt2szQyz6JRy7I+0r+xeyOAGH21Y15hb5bIyHoDEaZBvIDH+OOlD9eoLjCbsxDLN9WloU2CBi3OE6LYDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "^0.3.2",
|
||||
"@dnd-kit/collision": "^0.3.2",
|
||||
"@dnd-kit/geometry": "^0.3.2",
|
||||
"@dnd-kit/state": "^0.3.2",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/dom/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@dnd-kit/geometry": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.3.2.tgz",
|
||||
"integrity": "sha512-3UBPuIS7E3oGiHxOE8h810QA+0pnrnCtGxl4Os1z3yy5YkC/BEYGY+TxWPTQaY1/OMV7GCX7ZNMlama2QN3n3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/state": "^0.3.2",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/geometry/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@dnd-kit/state": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.3.2.tgz",
|
||||
"integrity": "sha512-dLUIkoYrIJhGXfF2wGLTfb46vUokEsO/OoE21TSfmahYrx7ysTmnwbePsznFaHlwgZhQEh6AlLvthLCeY21b1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@preact/signals-core": "^1.10.0",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/state/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@dnd-kit/vue": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/vue/-/vue-0.3.2.tgz",
|
||||
"integrity": "sha512-tYX0YzylmkPyjTM8Kv//o90sWNVRCFBtzSu7/Ung2aFMJhfEEUKJoEYRL2V4Cz+fLkOPQFaFqNxSDpNKTeWNxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "^0.3.2",
|
||||
"@dnd-kit/dom": "^0.3.2",
|
||||
"@dnd-kit/state": "^0.3.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/vue/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@electron/asar": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
||||
@@ -2874,6 +2993,17 @@
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/signals-core": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz",
|
||||
"integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
||||
"devDependencies": {
|
||||
"@dnd-kit/vue": "^0.3.2",
|
||||
"@electron/rebuild": "^4.0.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"actions": {
|
||||
"open": "Open",
|
||||
"confirm": "Confirm",
|
||||
"clear": "Clear"
|
||||
"clear": "Clear",
|
||||
"reset": "Reset"
|
||||
},
|
||||
"time_units": {
|
||||
"y": "y",
|
||||
@@ -93,7 +94,8 @@
|
||||
"sort_secondary": "Then by",
|
||||
"sort_tertiary": "Then by",
|
||||
"favorite_groups": "Favorite Groups",
|
||||
"favorite_groups_placeholder": "All Groups"
|
||||
"favorite_groups_placeholder": "All Groups",
|
||||
"edit_group_order": "Edit Group Order"
|
||||
},
|
||||
"notifications": "Notifications",
|
||||
"notification_center": {
|
||||
|
||||
@@ -78,6 +78,7 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
const isHideFriendsInSameInstance = ref(false);
|
||||
const isSidebarDivideByFriendGroup = ref(false);
|
||||
const sidebarFavoriteGroups = ref([]);
|
||||
const sidebarFavoriteGroupOrder = ref([]);
|
||||
const hideUserNotes = ref(false);
|
||||
const hideUserMemos = ref(false);
|
||||
const hideUnfriends = ref(false);
|
||||
@@ -152,6 +153,7 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
isHideFriendsInSameInstanceConfig,
|
||||
isSidebarDivideByFriendGroupConfig,
|
||||
sidebarFavoriteGroupsConfig,
|
||||
sidebarFavoriteGroupOrderConfig,
|
||||
hideUserNotesConfig,
|
||||
hideUserMemosConfig,
|
||||
hideUnfriendsConfig,
|
||||
@@ -208,6 +210,10 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
true
|
||||
),
|
||||
configRepository.getString('VRCX_sidebarFavoriteGroups', '[]'),
|
||||
configRepository.getString(
|
||||
'VRCX_sidebarFavoriteGroupOrder',
|
||||
'[]'
|
||||
),
|
||||
configRepository.getBool('VRCX_hideUserNotes', false),
|
||||
configRepository.getBool('VRCX_hideUserMemos', false),
|
||||
configRepository.getBool('VRCX_hideUnfriends', false),
|
||||
@@ -299,6 +305,9 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
sidebarFavoriteGroups.value = JSON.parse(
|
||||
sidebarFavoriteGroupsConfig
|
||||
);
|
||||
sidebarFavoriteGroupOrder.value = JSON.parse(
|
||||
sidebarFavoriteGroupOrderConfig
|
||||
);
|
||||
hideUserNotes.value = hideUserNotesConfig;
|
||||
hideUserMemos.value = hideUserMemosConfig;
|
||||
hideUnfriends.value = hideUnfriendsConfig;
|
||||
@@ -717,6 +726,16 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
JSON.stringify(value)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {string[]} value
|
||||
*/
|
||||
function setSidebarFavoriteGroupOrder(value) {
|
||||
sidebarFavoriteGroupOrder.value = value;
|
||||
configRepository.setString(
|
||||
'VRCX_sidebarFavoriteGroupOrder',
|
||||
JSON.stringify(value)
|
||||
);
|
||||
}
|
||||
function setHideUserNotes() {
|
||||
hideUserNotes.value = !hideUserNotes.value;
|
||||
configRepository.setBool('VRCX_hideUserNotes', hideUserNotes.value);
|
||||
@@ -975,6 +994,7 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
isHideFriendsInSameInstance,
|
||||
isSidebarDivideByFriendGroup,
|
||||
sidebarFavoriteGroups,
|
||||
sidebarFavoriteGroupOrder,
|
||||
hideUserNotes,
|
||||
hideUserMemos,
|
||||
hideUnfriends,
|
||||
@@ -1013,6 +1033,7 @@ export const useAppearanceSettingsStore = defineStore(
|
||||
setIsHideFriendsInSameInstance,
|
||||
setIsSidebarDivideByFriendGroup,
|
||||
setSidebarFavoriteGroups,
|
||||
setSidebarFavoriteGroupOrder,
|
||||
setHideUserNotes,
|
||||
setHideUserMemos,
|
||||
setHideUnfriends,
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
class="absolute top-1 right-1.25 size-1.5 rounded-full bg-red-500" />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<Popover>
|
||||
<Popover v-model:open="isSettingsPopoverOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button class="rounded-full" variant="ghost" size="icon-sm">
|
||||
<Settings />
|
||||
@@ -112,6 +112,17 @@
|
||||
:model-value="isSidebarDivideByFriendGroup"
|
||||
@update:modelValue="setIsSidebarDivideByFriendGroup" />
|
||||
</div>
|
||||
<Button
|
||||
v-if="isSidebarDivideByFriendGroup"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full text-xs"
|
||||
@click="
|
||||
isSettingsPopoverOpen = false;
|
||||
isGroupOrderSheetOpen = true;
|
||||
">
|
||||
{{ t('side_panel.settings.edit_group_order') }}
|
||||
</Button>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span>{{ t('side_panel.settings.favorite_groups') }}</span>
|
||||
<Select
|
||||
@@ -248,6 +259,7 @@
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
<NotificationCenterSheet />
|
||||
<GroupOrderSheet v-model:open="isGroupOrderSheetOpen" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -285,6 +297,7 @@
|
||||
import { debounce, userImage } from '../../shared/utils';
|
||||
|
||||
import FriendsSidebar from './components/FriendsSidebar.vue';
|
||||
import GroupOrderSheet from './components/GroupOrderSheet.vue';
|
||||
import GroupsSidebar from './components/GroupsSidebar.vue';
|
||||
import NotificationCenterSheet from './components/NotificationCenterSheet.vue';
|
||||
|
||||
@@ -357,6 +370,8 @@
|
||||
});
|
||||
|
||||
const CLEAR_VALUE = '__clear__';
|
||||
const isGroupOrderSheetOpen = ref(false);
|
||||
const isSettingsPopoverOpen = ref(false);
|
||||
|
||||
const sortOptions = computed(() => [
|
||||
{ value: 'Sort Alphabetically', label: t('view.settings.appearance.side_panel.sorting.alphabetical') },
|
||||
|
||||
@@ -120,7 +120,8 @@
|
||||
isSidebarGroupByInstance,
|
||||
isHideFriendsInSameInstance,
|
||||
isSidebarDivideByFriendGroup,
|
||||
sidebarFavoriteGroups
|
||||
sidebarFavoriteGroups,
|
||||
sidebarFavoriteGroupOrder
|
||||
} = storeToRefs(useAppearanceSettingsStore());
|
||||
const { gameLogDisabled } = storeToRefs(useAdvancedSettingsStore());
|
||||
const { showUserDialog } = useUserStore();
|
||||
@@ -228,7 +229,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
return result.sort((a, b) => a[0].key.localeCompare(b[0].key));
|
||||
const order = sidebarFavoriteGroupOrder.value;
|
||||
return result.sort((a, b) => {
|
||||
const idxA = order.indexOf(a[0]?.key);
|
||||
const idxB = order.indexOf(b[0]?.key);
|
||||
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
||||
if (idxA !== -1) return -1;
|
||||
if (idxB !== -1) return 1;
|
||||
return (a[0]?.key ?? '').localeCompare(b[0]?.key ?? '');
|
||||
});
|
||||
});
|
||||
|
||||
const buildToggleRow = ({
|
||||
|
||||
132
src/views/Sidebar/components/GroupOrderSheet.vue
Normal file
132
src/views/Sidebar/components/GroupOrderSheet.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<Sheet v-model:open="isOpen">
|
||||
<SheetContent side="right" class="w-80 flex flex-col" @open-auto-focus.prevent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{{ t('side_panel.settings.edit_group_order') }}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div class="flex-1 overflow-auto p-3">
|
||||
<DragDropProvider @dragEnd="onDragEnd">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<SortableGroupItem
|
||||
v-for="(item, index) in localOrder"
|
||||
:key="item.key"
|
||||
:id="item.key"
|
||||
:index="index"
|
||||
:label="item.displayName" />
|
||||
</div>
|
||||
</DragDropProvider>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 border-t p-3">
|
||||
<Button variant="secondary" size="sm" @click="resetOrder">
|
||||
{{ t('common.actions.reset') }}
|
||||
</Button>
|
||||
<Button size="sm" @click="confirmOrder">
|
||||
{{ t('common.actions.confirm') }}
|
||||
</Button>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DragDropProvider } from '@dnd-kit/vue';
|
||||
import { isSortable } from '@dnd-kit/vue/sortable';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useAppearanceSettingsStore, useFavoriteStore } from '../../../stores';
|
||||
|
||||
import SortableGroupItem from './SortableGroupItem.vue';
|
||||
|
||||
const isOpen = defineModel('open', { type: Boolean, default: false });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const appearanceStore = useAppearanceSettingsStore();
|
||||
const { sidebarFavoriteGroups, sidebarFavoriteGroupOrder } = storeToRefs(appearanceStore);
|
||||
const { setSidebarFavoriteGroupOrder } = appearanceStore;
|
||||
|
||||
const favoriteStore = useFavoriteStore();
|
||||
const { favoriteFriendGroups, localFriendFavoriteGroups } = storeToRefs(favoriteStore);
|
||||
|
||||
const allGroupItems = computed(() => {
|
||||
const items = [];
|
||||
for (const group of favoriteFriendGroups.value) {
|
||||
items.push({ key: group.key, displayName: group.displayName });
|
||||
}
|
||||
for (const name of localFriendFavoriteGroups.value) {
|
||||
items.push({ key: `local:${name}`, displayName: name });
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
const selectedGroupKeys = computed(() => {
|
||||
if (sidebarFavoriteGroups.value.length === 0) {
|
||||
return allGroupItems.value.map((g) => g.key);
|
||||
}
|
||||
return sidebarFavoriteGroups.value;
|
||||
});
|
||||
|
||||
const localOrder = ref([]);
|
||||
|
||||
function buildOrderedList() {
|
||||
const selected = new Set(selectedGroupKeys.value);
|
||||
const persistedOrder = sidebarFavoriteGroupOrder.value;
|
||||
const itemMap = new Map(allGroupItems.value.map((g) => [g.key, g]));
|
||||
|
||||
const ordered = [];
|
||||
for (const key of persistedOrder) {
|
||||
if (selected.has(key) && itemMap.has(key)) {
|
||||
ordered.push(itemMap.get(key));
|
||||
selected.delete(key);
|
||||
}
|
||||
}
|
||||
for (const key of selectedGroupKeys.value) {
|
||||
if (selected.has(key) && itemMap.has(key)) {
|
||||
ordered.push(itemMap.get(key));
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
watch(isOpen, (open) => {
|
||||
if (open) {
|
||||
localOrder.value = buildOrderedList();
|
||||
}
|
||||
});
|
||||
|
||||
function onDragEnd(event) {
|
||||
if (event.canceled) return;
|
||||
const { source } = event.operation;
|
||||
if (isSortable(source)) {
|
||||
const { initialIndex, index } = source;
|
||||
if (initialIndex !== index) {
|
||||
const newOrder = [...localOrder.value];
|
||||
const [removed] = newOrder.splice(initialIndex, 1);
|
||||
newOrder.splice(index, 0, removed);
|
||||
localOrder.value = newOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function confirmOrder() {
|
||||
const currentKeys = localOrder.value.map((g) => g.key);
|
||||
const persistedOrder = sidebarFavoriteGroupOrder.value;
|
||||
const merged = [...currentKeys];
|
||||
for (const key of persistedOrder) {
|
||||
if (!merged.includes(key)) {
|
||||
merged.push(key);
|
||||
}
|
||||
}
|
||||
setSidebarFavoriteGroupOrder(merged);
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
function resetOrder() {
|
||||
setSidebarFavoriteGroupOrder([]);
|
||||
localOrder.value = buildOrderedList();
|
||||
}
|
||||
</script>
|
||||
29
src/views/Sidebar/components/SortableGroupItem.vue
Normal file
29
src/views/Sidebar/components/SortableGroupItem.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { GripVertical } from 'lucide-vue-next';
|
||||
import { useSortable } from '@dnd-kit/vue/sortable';
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: String, required: true },
|
||||
index: { type: Number, required: true },
|
||||
label: { type: String, required: true }
|
||||
});
|
||||
|
||||
const element = ref(null);
|
||||
|
||||
const { isDragSource } = useSortable({
|
||||
id: computed(() => props.id),
|
||||
index: computed(() => props.index),
|
||||
element
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="element"
|
||||
class="flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm select-none cursor-grab active:cursor-grabbing"
|
||||
:class="{ 'opacity-50': isDragSource }">
|
||||
<GripVertical class="size-4 shrink-0 text-muted-foreground" />
|
||||
<span class="truncate">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user