From 99c3bd0f42aac01f968bd7c5409c60f7f4a87f62 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 31 Aug 2026 16:37:51 +0800 Subject: [PATCH] feat(ui): animate sidebar and toolbar interaction feedback --- .../editor/EditorSettingsDialog.vue | 84 ++++++++----- ...torSettingsDialog.choiceCardLayout.spec.ts | 9 +- .../src/components/layout/AppSidebar.vue | 42 ++++--- .../src/components/layout/AppToolbar.vue | 116 +++++++++++++++--- .../components/layout/ToolbarUpdateIcon.vue | 47 +++++++ .../__tests__/ToolbarUpdateIcon.spec.ts | 37 ++++++ .../src/components/sidebar/ConnectionTree.vue | 28 ++--- .../sidebar/SidebarListOptionsIcon.vue | 91 ++++++++++++++ .../sidebar/SidebarLocateButton.vue | 78 ++++++++++++ .../sidebar/SidebarRegexToggleButton.vue | 107 ++++++++++++++++ .../src/components/sidebar/TreeItem.vue | 18 ++- .../SidebarToolbarAnimations.spec.ts | 116 ++++++++++++++++++ .../src/components/ui/LightDropdown.vue | 4 +- .../app-tests/sidebarToolbarTooltip.test.ts | 12 +- 14 files changed, 689 insertions(+), 100 deletions(-) create mode 100644 apps/desktop/src/components/layout/ToolbarUpdateIcon.vue create mode 100644 apps/desktop/src/components/layout/__tests__/ToolbarUpdateIcon.spec.ts create mode 100644 apps/desktop/src/components/sidebar/SidebarListOptionsIcon.vue create mode 100644 apps/desktop/src/components/sidebar/SidebarLocateButton.vue create mode 100644 apps/desktop/src/components/sidebar/SidebarRegexToggleButton.vue create mode 100644 apps/desktop/src/components/sidebar/__tests__/SidebarToolbarAnimations.spec.ts diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index a07fa7dc2d..a66cb0aeb4 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -4800,7 +4800,7 @@ onUnmounted(() => {
-
+
- -
- -
- - - - - - - {{ t("settings.iconThemeDefaultDescription") }} - - - - - - - - - - {{ iconThemeBlackDescriptionText }} - - - -
-
@@ -4987,6 +4955,38 @@ onUnmounted(() => {
+
+ +
+ + +
+
+
@@ -7898,6 +7898,10 @@ onUnmounted(() => { row-gap: 1rem; } +.settings-appearance-section { + container-type: inline-size; +} + .settings-appearance-field > * + * { margin-top: 0.5rem; } @@ -7920,10 +7924,22 @@ onUnmounted(() => { .settings-appearance-theme-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; } +@container (max-width: 34rem) { + .settings-appearance-theme-grid { + grid-template-columns: 1fr; + } +} + +@container (max-width: 36rem) { + .settings-icon-theme-grid { + grid-template-columns: 1fr; + } +} + .settings-option-stack > * + * { margin-top: 0.625rem; } diff --git a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts index a216e38e7d..9caa66b5af 100644 --- a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts +++ b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts @@ -118,12 +118,13 @@ describe("EditorSettingsDialog choice card containment", () => { } }); - it("keeps icon theme choices compact beside the corner style controls", () => { - expect(dialogSource).toContain("grid-template-columns: repeat(3, minmax(0, 1fr));"); + it("restores large icon theme choices below the debug log controls", () => { + expect(sourceIndexForKey("iconTheme")).toBeGreaterThan(sourceIndexForKey("debugLoggingEnabled")); + expect(templateSource).toContain("data-icon-theme-settings"); for (const key of ["iconThemeDefault", "iconThemeBlack"] as const) { const block = buttonBlockForKey(key); - expectClassTokens(classNameFromTag(openingTag(block, "Button")), ["settings-choice-button", "h-8"]); - expect(block).toContain('class="h-7 w-7 shrink-0"'); + expectClassTokens(classNameFromTag(openingTag(block, "Button")), ["settings-choice-card", "h-auto", "min-w-0", "whitespace-normal", "overflow-hidden"]); + expect(block).toContain('class="h-12 w-12 shrink-0"'); } }); }); diff --git a/apps/desktop/src/components/layout/AppSidebar.vue b/apps/desktop/src/components/layout/AppSidebar.vue index aff5733c83..c559beb738 100644 --- a/apps/desktop/src/components/layout/AppSidebar.vue +++ b/apps/desktop/src/components/layout/AppSidebar.vue @@ -268,26 +268,28 @@ defineExpose({ focusSearch, locateTabInSidebar });
diff --git a/apps/desktop/src/components/layout/AppToolbar.vue b/apps/desktop/src/components/layout/AppToolbar.vue index 95a57ad729..f4336db32f 100644 --- a/apps/desktop/src/components/layout/AppToolbar.vue +++ b/apps/desktop/src/components/layout/AppToolbar.vue @@ -2,12 +2,13 @@ import { computed, ref, onMounted, onBeforeUnmount, h, nextTick, watch } from "vue"; import { useI18n } from "vue-i18n"; import { invoke } from "@tauri-apps/api/core"; -import { DatabaseZap, FilePlus2, Loader2, Moon, Sun, SunMoon, History, Bot, ArrowLeftRight, FileCode, BookMarked, GitCompareArrows, TableProperties, Settings, CloudDownload, Package, FileDown, FolderTree } from "@lucide/vue"; +import { DatabaseZap, FilePlus2, Moon, Sun, SunMoon, History, Bot, ArrowLeftRight, FileCode, BookMarked, GitCompareArrows, TableProperties, Settings, CloudDownload, Package, FileDown, FolderTree } from "@lucide/vue"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import LightDropdown from "@/components/ui/LightDropdown.vue"; import WindowControls from "@/components/layout/WindowControls.vue"; import ExportProgressPopover from "@/components/export/ExportProgressPopover.vue"; +import ToolbarUpdateIcon from "@/components/layout/ToolbarUpdateIcon.vue"; import { MAC_TRAFFIC_LIGHT_X, macTrafficLightInsetPaddingForScale, shouldReserveMacTrafficLightInset, useWindowControls } from "@/composables/useWindowControls"; import { useToast } from "@/composables/useToast"; import { useSettingsStore } from "@/stores/settingsStore"; @@ -584,9 +585,8 @@ const toolbarStyle = computed(() => { diff --git a/apps/desktop/src/components/layout/__tests__/ToolbarUpdateIcon.spec.ts b/apps/desktop/src/components/layout/__tests__/ToolbarUpdateIcon.spec.ts new file mode 100644 index 0000000000..8c0330c697 --- /dev/null +++ b/apps/desktop/src/components/layout/__tests__/ToolbarUpdateIcon.spec.ts @@ -0,0 +1,37 @@ +// @vitest-environment happy-dom + +import { createApp, defineComponent, h, nextTick, reactive, type App } from "vue"; +import { afterEach, describe, expect, it } from "vitest"; +import ToolbarUpdateIcon from "../ToolbarUpdateIcon.vue"; + +const mountedApps: App[] = []; + +afterEach(() => { + for (const app of mountedApps.splice(0)) app.unmount(); + document.body.innerHTML = ""; +}); + +describe("ToolbarUpdateIcon", () => { + it("uses cloud download while idle and a cloud outline scan while checking", async () => { + const state = reactive({ loading: false }); + const container = document.createElement("div"); + document.body.append(container); + const app = createApp( + defineComponent({ + setup: () => () => h(ToolbarUpdateIcon, { loading: state.loading }), + }), + ); + mountedApps.push(app); + app.mount(container); + await nextTick(); + + expect(container.querySelector("[data-toolbar-update-idle]")).not.toBeNull(); + expect(container.querySelector("[data-toolbar-update-cloud]")).toBeNull(); + + state.loading = true; + await nextTick(); + expect(container.querySelector("[data-toolbar-update-idle]")).toBeNull(); + expect(container.querySelector("[data-toolbar-update-cloud]")).not.toBeNull(); + expect(container.querySelector("[data-toolbar-update-scan]")).not.toBeNull(); + }); +}); diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index 1ebd5549b0..244f08c8d0 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -1,7 +1,7 @@ + + + + diff --git a/apps/desktop/src/components/sidebar/SidebarLocateButton.vue b/apps/desktop/src/components/sidebar/SidebarLocateButton.vue new file mode 100644 index 0000000000..386b913bde --- /dev/null +++ b/apps/desktop/src/components/sidebar/SidebarLocateButton.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/apps/desktop/src/components/sidebar/SidebarRegexToggleButton.vue b/apps/desktop/src/components/sidebar/SidebarRegexToggleButton.vue new file mode 100644 index 0000000000..ae292b8956 --- /dev/null +++ b/apps/desktop/src/components/sidebar/SidebarRegexToggleButton.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 3ca0375e44..267d6a110c 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -1478,7 +1478,19 @@ function onKeydown(event: KeyboardEvent) { @keydown.escape.prevent="cancelRename" @click.stop /> - {{ visibleLabel(node) }} + {{ visibleLabel(node) }} {{ treeNodeSecondaryValue(node) }}