From e29cb2debe182ca7ee7d1ad47b42240603b5fa7b Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Mon, 5 Aug 2024 16:13:26 -0700 Subject: [PATCH] Make magnify icon always visible, transition from magnify to minimize (#197) I'm updating the magnify button to be always visible and animate a transition between being a "Magnify" button and a "Minimize" button. This also cleans up some text shrinking behavior in the block frame header so the end icons are always visible. Also fixes some height discrepancies in the block frame header. Also implements a `prefers-reduced-motion` query for the tilelayout and block frame to ensure transitions are not set if the user does not want them. --- .storybook/preview.tsx | 1 - electron.vite.config.ts | 8 +++--- frontend/app/asset/magnify.svg | 9 ++++++ frontend/app/block/block.less | 26 ++++++++++++++--- frontend/app/block/blockframe.tsx | 9 +++--- frontend/app/block/blockutil.tsx | 2 +- frontend/app/element/magnify.less | 36 ++++++++++++++++++++++++ frontend/app/element/magnify.stories.tsx | 25 ++++++++++++++++ frontend/app/element/magnify.tsx | 15 ++++++++++ frontend/app/theme.less | 1 + frontend/layout/lib/tilelayout.less | 13 +++++++++ frontend/types/custom.d.ts | 2 +- 12 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 frontend/app/asset/magnify.svg create mode 100644 frontend/app/element/magnify.less create mode 100644 frontend/app/element/magnify.stories.tsx create mode 100644 frontend/app/element/magnify.tsx diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index c300c63f2..a6c2c09bb 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -3,7 +3,6 @@ import type { Preview } from "@storybook/react"; import React from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; - import "./global.css"; const preview: Preview = { diff --git a/electron.vite.config.ts b/electron.vite.config.ts index c53d4c87a..2a7ab6fe0 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -61,15 +61,15 @@ export default defineConfig({ plugins: [ ViteImageOptimizer(), tsconfigPaths(), + svgr({ + svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true }, + include: "**/*.svg", + }), react({}), flow(), viteStaticCopy({ targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }], }), - svgr({ - svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true }, - include: "**/*.svg", - }), ], }, }); diff --git a/frontend/app/asset/magnify.svg b/frontend/app/asset/magnify.svg new file mode 100644 index 000000000..09a4919ca --- /dev/null +++ b/frontend/app/asset/magnify.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index a0960175e..956ecea0a 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -70,6 +70,8 @@ flex-direction: column; .block-frame-default-header { + max-height: var(--header-height); + min-height: var(--header-height); display: flex; padding: 4px 5px 4px 10px; align-items: center; @@ -90,8 +92,10 @@ .block-frame-default-header-iconview { display: flex; + flex-shrink: 1; align-items: center; gap: 8px; + overflow-x: hidden; .block-frame-view-icon { font-size: var(--header-icon-size); @@ -103,10 +107,9 @@ } .block-frame-view-type { - line-height: 12px; font-weight: 700; - text-wrap: nowrap; overflow-x: hidden; + text-wrap: nowrap; text-overflow: ellipsis; } @@ -119,8 +122,8 @@ font: var(--fixed-font); font-size: 11px; opacity: 0.7; - text-wrap: nowrap; overflow-x: hidden; + text-wrap: nowrap; text-overflow: ellipsis; } @@ -148,6 +151,7 @@ min-width: 0; gap: 8px; align-items: center; + overflow-x: hidden; .block-frame-div { display: flex; @@ -200,6 +204,7 @@ .block-frame-end-icons { display: flex; + flex-shrink: 0; .iconbutton { display: flex; @@ -207,6 +212,19 @@ padding: 4px 6px; align-items: center; } + + .block-frame-magnify { + justify-content: center; + align-items: center; + padding: 0; + + svg { + #arrow1, + #arrow2 { + fill: var(--main-text-color); + } + } + } } } @@ -220,7 +238,7 @@ align-items: center; justify-content: center; - i { + .iconbutton { opacity: 0.7; font-size: 45px; margin: -30px 0 0 0; diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 2e7406f36..60a80daca 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -6,6 +6,7 @@ import { Button } from "@/app/element/button"; import { ContextMenuModel } from "@/app/store/contextmenu"; import { atoms, globalStore, useBlockAtom, WOS } from "@/app/store/global"; import * as services from "@/app/store/services"; +import { MagnifyIcon } from "@/element/magnify"; import { LayoutTreeState } from "@/layout/index"; import { getLayoutStateAtomForTab } from "@/layout/lib/layoutAtom"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; @@ -77,13 +78,11 @@ const OptMagnifyButton = React.memo( const tabId = globalStore.get(atoms.activeTabId); const tabAtom = WOS.getWaveObjectAtom(WOS.makeORef("tab", tabId)); const layoutTreeState = util.useAtomValueSafe(getLayoutStateAtomForTab(tabId, tabAtom)); - if (!isBlockMagnified(layoutTreeState, blockData.oid)) { - return null; - } + const isMagnified = isBlockMagnified(layoutTreeState, blockData.oid); const magnifyDecl: HeaderIconButton = { elemtype: "iconbutton", - icon: "regular@magnifying-glass-minus", - title: "Minimize", + icon: , + title: isMagnified ? "Minimize" : "Magnify", click: layoutModel?.onMagnifyToggle, }; return ; diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 613284534..b991c6317 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -163,7 +163,7 @@ export const IconButton = React.memo(({ decl, className }: { decl: HeaderIconBut useLongClick(buttonRef, decl.click, decl.longClick); return (
- + {typeof decl.icon === "string" ? : decl.icon}
); }); diff --git a/frontend/app/element/magnify.less b/frontend/app/element/magnify.less new file mode 100644 index 000000000..48a402f3b --- /dev/null +++ b/frontend/app/element/magnify.less @@ -0,0 +1,36 @@ +.magnify-icon { + display: inline-block; + width: 15px; + height: 15px; + svg { + #arrow1 { + transform: rotate(180deg); + transform-origin: calc(29.167% + 4px) calc(70.833% + 4px); // account for path offset in the svg itself + } + #arrow2 { + transform: rotate(-180deg); + transform-origin: calc(70.833% + 4px) calc(29.167% + 4px); + } + #arrow1, + #arrow2 { + transition: transform 300ms ease-in; + } + } + &.enabled { + svg { + #arrow1, + #arrow2 { + transform: rotate(0deg); + } + } + } +} + +@media (prefers-reduced-motion) { + .magnify-icon svg { + #arrow1, + #arrow2 { + transition: none; + } + } +} diff --git a/frontend/app/element/magnify.stories.tsx b/frontend/app/element/magnify.stories.tsx new file mode 100644 index 000000000..5e52da4e6 --- /dev/null +++ b/frontend/app/element/magnify.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { MagnifyIcon } from "./magnify"; + +const meta = { + title: "Icons/Magnify", + component: MagnifyIcon, + args: { + enabled: true, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Enabled: Story = { + args: { + enabled: true, + }, +}; + +export const Disabled: Story = { + args: { + enabled: false, + }, +}; diff --git a/frontend/app/element/magnify.tsx b/frontend/app/element/magnify.tsx new file mode 100644 index 000000000..f6ceadeb0 --- /dev/null +++ b/frontend/app/element/magnify.tsx @@ -0,0 +1,15 @@ +import clsx from "clsx"; +import MagnifySVG from "../asset/magnify.svg"; +import "./magnify.less"; + +interface MagnifyIconProps { + enabled: boolean; +} + +export function MagnifyIcon({ enabled }: MagnifyIconProps) { + return ( +
+ +
+ ); +} diff --git a/frontend/app/theme.less b/frontend/app/theme.less index 3ae59f0fe..7d2633ae8 100644 --- a/frontend/app/theme.less +++ b/frontend/app/theme.less @@ -32,6 +32,7 @@ --header-font: 700 11px / normal "Inter", sans-serif; --header-icon-size: 14px; --header-icon-width: 16px; + --header-height: 30px; --tab-green: rgb(88, 193, 66); diff --git a/frontend/layout/lib/tilelayout.less b/frontend/layout/lib/tilelayout.less index 8ef09840e..08a29903c 100644 --- a/frontend/layout/lib/tilelayout.less +++ b/frontend/layout/lib/tilelayout.less @@ -117,3 +117,16 @@ border-radius: calc(var(--block-border-radius) + 2px); } } + +@media (prefers-reduced-motion) { + .tile-layout { + &.animate { + .tile-node, + .placeholder { + transition-duration: none; + transition-timing-function: none; + transition-property: none; + } + } + } +} diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index ba8d797eb..508df4989 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -123,7 +123,7 @@ declare global { type HeaderIconButton = { elemtype: "iconbutton"; - icon: string; + icon: string | React.ReactNode; className?: string; title?: string; click?: (e: React.MouseEvent) => void;