feat: drag the sidebar buttons into order

This commit is contained in:
Felitendo committed 2026-09-18 11:41:20 +02:00
1 parent c53037fac8
commit 5234208662
2 files changed
+331 -374

No files matched your search

+58 -50
View File
@@ -20,8 +20,8 @@ where they belong; only the number of instances stays there.
.../components/ui/modal/AppSettingsModal.vue | 22 ++-
.../ui/settings/display/FeaturesSettings.vue | 70 ---------
.../ui/settings/display/SidebarSettings.vue | 125 ++++++++++++++++
.../src/composables/use-sidebar-layout.ts | 64 ++++++++
5 files changed, 302 insertions(+), 117 deletions(-)
.../src/composables/use-sidebar-layout.ts | 72 +++++++++
5 files changed, 310 insertions(+), 117 deletions(-)
create mode 100644 apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
create mode 100644 apps/app-frontend/src/composables/use-sidebar-layout.ts
@@ -382,7 +382,7 @@ index 9a9e8ef..7636c46 100644
{{ formatMessage(messages.quickInstancesTitle) }}
diff --git a/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
new file mode 100644
index 0000000..54e155a
index 0000000..dc0cd1f
--- /dev/null
+++ b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
@@ -0,0 +1,125 @@
@@ -390,45 +390,41 @@ index 0000000..54e155a
+ <h2 class="m-0 text-xl font-semibold text-contrast">{{ formatMessage(messages.title) }}</h2>
+ <p class="mt-1 mb-0">{{ formatMessage(messages.description) }}</p>
+
+ <div class="mt-4 flex flex-col gap-2">
+ <div
+ v-for="(item, index) in items"
+ :key="item.id"
+ class="flex items-center gap-3 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-3"
+ >
+ <component :is="item.icon" class="size-5 shrink-0 text-secondary" />
+ <span class="font-semibold text-contrast">{{ item.label }}</span>
+ <div class="ml-auto flex items-center gap-1">
+ <IconButton
+ v-tooltip="formatMessage(messages.moveUp)"
+ type="quiet"
+ size="sm"
+ :label="formatMessage(messages.moveUp)"
+ :disabled="index === 0"
+ @click="layout.move(item.id, -1)"
+ <Draggable
+ v-model="orderedItems"
+ class="mt-4 flex flex-col gap-2"
+ item-key="id"
+ :animation="200"
+ filter=".sidebar-item-toggle"
+ :prevent-on-filter="false"
+ ghost-class="sidebar-item-ghost"
+ >
+ <template #item="{ element: item }">
+ <div
+ class="flex cursor-grab items-center gap-3 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-3 active:cursor-grabbing"
+ >
+ <button
+ v-tooltip="formatMessage(messages.reorder)"
+ type="button"
+ class="flex cursor-grab border-none bg-transparent p-0 text-secondary active:cursor-grabbing"
+ :aria-label="formatMessage(messages.reorder)"
+ @keydown.up.prevent="layout.move(item.id, -1)"
+ @keydown.down.prevent="layout.move(item.id, 1)"
+ >
+ <ArrowUpIcon />
+ </IconButton>
+ <IconButton
+ v-tooltip="formatMessage(messages.moveDown)"
+ type="quiet"
+ size="sm"
+ :label="formatMessage(messages.moveDown)"
+ :disabled="index === items.length - 1"
+ @click="layout.move(item.id, 1)"
+ >
+ <ArrowDownIcon />
+ </IconButton>
+ <GripVerticalIcon class="size-5" />
+ </button>
+ <component :is="item.icon" class="size-5 shrink-0 text-secondary" />
+ <span class="font-semibold text-contrast">{{ item.label }}</span>
+ <Toggle
+ :id="`sidebar-${item.id}`"
+ :model-value="!layout.isHidden(item.id)"
+ :aria-label="formatMessage(messages.show, { name: item.label })"
+ class="ml-2"
+ class="sidebar-item-toggle ml-auto"
+ @update:model-value="(shown: boolean) => layout.setHidden(item.id, !shown)"
+ />
+ </div>
+ </div>
+ </div>
+ </template>
+ </Draggable>
+
+ <Button class="mt-4" @click="layout.reset()">
+ <RotateCounterClockwiseIcon />
@@ -438,17 +434,17 @@ index 0000000..54e155a
+
+<script setup lang="ts">
+import {
+ ArrowDownIcon,
+ ArrowUpIcon,
+ CompassIcon,
+ GripVerticalIcon,
+ ImageIcon,
+ PlayIcon,
+ RotateCounterClockwiseIcon,
+ ServerStackIcon,
+ ShirtIcon,
+} from '@modrinth/assets'
+import { Button, commonMessages, defineMessages, IconButton, Toggle, useVIntl } from '@modrinth/ui'
+import { Button, commonMessages, defineMessages, Toggle, useVIntl } from '@modrinth/ui'
+import { computed } from 'vue'
+import Draggable from 'vuedraggable'
+
+import { type SidebarItemId, useSidebarLayout } from '@/composables/use-sidebar-layout'
+import { appMessages } from '@/utils/app-messages'
@@ -466,13 +462,9 @@ index 0000000..54e155a
+ defaultMessage:
+ 'The buttons above the instances in the left sidebar. Right-clicking one of them hides it as well.',
+ },
+ moveUp: {
+ id: 'app.sidebar-settings.move-up',
+ defaultMessage: 'Move up',
+ },
+ moveDown: {
+ id: 'app.sidebar-settings.move-down',
+ defaultMessage: 'Move down',
+ reorder: {
+ id: 'app.sidebar-settings.reorder',
+ defaultMessage: 'Drag to reorder, or use the arrow keys',
+ },
+ show: {
+ id: 'app.sidebar-settings.show',
@@ -507,16 +499,24 @@ index 0000000..54e155a
+ hosting: { icon: ServerStackIcon, label: () => formatMessage(messages.hosting) },
+}
+
+const items = computed(() =>
+ layout.order.value.map((id) => ({ id, icon: ITEMS[id].icon, label: ITEMS[id].label() })),
+)
+const orderedItems = computed({
+ get: () =>
+ layout.order.value.map((id) => ({ id, icon: ITEMS[id].icon, label: ITEMS[id].label() })),
+ set: (items: { id: SidebarItemId }[]) => layout.setOrder(items.map((item) => item.id)),
+})
+</script>
+
+<style scoped>
+.sidebar-item-ghost {
+ opacity: 0.4;
+}
+</style>
diff --git a/apps/app-frontend/src/composables/use-sidebar-layout.ts b/apps/app-frontend/src/composables/use-sidebar-layout.ts
new file mode 100644
index 0000000..8d12b8d
index 0000000..7225c21
--- /dev/null
+++ b/apps/app-frontend/src/composables/use-sidebar-layout.ts
@@ -0,0 +1,64 @@
@@ -0,0 +1,72 @@
+/**
+ * The order of the buttons above the instances in the left sidebar, and which
+ * of them are shown.
@@ -575,9 +575,17 @@ index 0000000..8d12b8d
+ layout.value = { order: ids, hidden: hidden.value }
+ }
+
+ /** The order the buttons were dragged into, anything unknown dropped. */
+ function setOrder(ids: SidebarItemId[]) {
+ layout.value = {
+ order: ids.filter((id) => SIDEBAR_ITEMS.includes(id)),
+ hidden: hidden.value,
+ }
+ }
+
+ function reset() {
+ layout.value = { order: [...SIDEBAR_ITEMS], hidden: [] }
+ }
+
+ return { order, hidden, isHidden, setHidden, move, reset }
+ return { order, hidden, isHidden, setHidden, move, setOrder, reset }
+}