mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
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.
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="svg2" version="1.1" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="g2" transform="matrix(1 0 0 1 -4 -4)" fill="#000">
|
||||
<path id="arrow1" class="arrow"
|
||||
d="m10.208 13.003c0.4262 0 0.7723 0.34592 0.7723 0.77195v5.4527c0 0.42604-0.3461 0.77195-0.7723 0.77195-0.42619 0-0.77227-0.34592-0.77227-0.77195v-4.6825l-4.6832-0.0017c-0.4262 0-0.77228-0.34592-0.77228-0.77185 0-0.42604 0.34608-0.77195 0.77228-0.77195h5.4554v0.0034z" />
|
||||
<path id="arrow2" class="arrow"
|
||||
d="m13.772 10.997c-0.42624 0-0.77233-0.34581-0.77233-0.77184v-5.4527c0-0.426 0.34609-0.77191 0.77233-0.77191 0.42614 0 0.77223 0.34591 0.77223 0.77191v4.6826l4.6832 0.0017c0.42625 0 0.77223 0.34591 0.77223 0.77187 0 0.42603-0.34599 0.77195-0.77223 0.77195h-5.4554v-0.0035z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 831 B |
@@ -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;
|
||||
|
||||
@@ -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<Tab>(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: <MagnifyIcon enabled={isMagnified} />,
|
||||
title: isMagnified ? "Minimize" : "Magnify",
|
||||
click: layoutModel?.onMagnifyToggle,
|
||||
};
|
||||
return <IconButton key="magnify" decl={magnifyDecl} className="block-frame-magnify" />;
|
||||
|
||||
@@ -163,7 +163,7 @@ export const IconButton = React.memo(({ decl, className }: { decl: HeaderIconBut
|
||||
useLongClick(buttonRef, decl.click, decl.longClick);
|
||||
return (
|
||||
<div ref={buttonRef} className={clsx("iconbutton", className)} title={decl.title}>
|
||||
<i className={util.makeIconClass(decl.icon, true)} />
|
||||
{typeof decl.icon === "string" ? <i className={util.makeIconClass(decl.icon, true)} /> : decl.icon}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<typeof MagnifyIcon>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Enabled: Story = {
|
||||
args: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
@@ -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 (
|
||||
<div className={clsx("magnify-icon", { enabled })}>
|
||||
<MagnifySVG />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -123,7 +123,7 @@ declare global {
|
||||
|
||||
type HeaderIconButton = {
|
||||
elemtype: "iconbutton";
|
||||
icon: string;
|
||||
icon: string | React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
click?: (e: React.MouseEvent<any>) => void;
|
||||
|
||||
Reference in New Issue
Block a user