mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01: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:
parent
2f8642db5b
commit
e29cb2debe
@ -3,7 +3,6 @@ import type { Preview } from "@storybook/react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { DndProvider } from "react-dnd";
|
import { DndProvider } from "react-dnd";
|
||||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
|
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
|
@ -61,15 +61,15 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
ViteImageOptimizer(),
|
ViteImageOptimizer(),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
|
svgr({
|
||||||
|
svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true },
|
||||||
|
include: "**/*.svg",
|
||||||
|
}),
|
||||||
react({}),
|
react({}),
|
||||||
flow(),
|
flow(),
|
||||||
viteStaticCopy({
|
viteStaticCopy({
|
||||||
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
|
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
|
||||||
}),
|
}),
|
||||||
svgr({
|
|
||||||
svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true },
|
|
||||||
include: "**/*.svg",
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
9
frontend/app/asset/magnify.svg
Normal file
9
frontend/app/asset/magnify.svg
Normal file
@ -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;
|
flex-direction: column;
|
||||||
|
|
||||||
.block-frame-default-header {
|
.block-frame-default-header {
|
||||||
|
max-height: var(--header-height);
|
||||||
|
min-height: var(--header-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 4px 5px 4px 10px;
|
padding: 4px 5px 4px 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -90,8 +92,10 @@
|
|||||||
|
|
||||||
.block-frame-default-header-iconview {
|
.block-frame-default-header-iconview {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
.block-frame-view-icon {
|
.block-frame-view-icon {
|
||||||
font-size: var(--header-icon-size);
|
font-size: var(--header-icon-size);
|
||||||
@ -103,10 +107,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-frame-view-type {
|
.block-frame-view-type {
|
||||||
line-height: 12px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-wrap: nowrap;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +122,8 @@
|
|||||||
font: var(--fixed-font);
|
font: var(--fixed-font);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
text-wrap: nowrap;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +151,7 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
.block-frame-div {
|
.block-frame-div {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -200,6 +204,7 @@
|
|||||||
|
|
||||||
.block-frame-end-icons {
|
.block-frame-end-icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
.iconbutton {
|
.iconbutton {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -207,6 +212,19 @@
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
align-items: center;
|
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;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
i {
|
.iconbutton {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
font-size: 45px;
|
font-size: 45px;
|
||||||
margin: -30px 0 0 0;
|
margin: -30px 0 0 0;
|
||||||
|
@ -6,6 +6,7 @@ import { Button } from "@/app/element/button";
|
|||||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||||
import { atoms, globalStore, useBlockAtom, WOS } from "@/app/store/global";
|
import { atoms, globalStore, useBlockAtom, WOS } from "@/app/store/global";
|
||||||
import * as services from "@/app/store/services";
|
import * as services from "@/app/store/services";
|
||||||
|
import { MagnifyIcon } from "@/element/magnify";
|
||||||
import { LayoutTreeState } from "@/layout/index";
|
import { LayoutTreeState } from "@/layout/index";
|
||||||
import { getLayoutStateAtomForTab } from "@/layout/lib/layoutAtom";
|
import { getLayoutStateAtomForTab } from "@/layout/lib/layoutAtom";
|
||||||
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
||||||
@ -77,13 +78,11 @@ const OptMagnifyButton = React.memo(
|
|||||||
const tabId = globalStore.get(atoms.activeTabId);
|
const tabId = globalStore.get(atoms.activeTabId);
|
||||||
const tabAtom = WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", tabId));
|
const tabAtom = WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", tabId));
|
||||||
const layoutTreeState = util.useAtomValueSafe(getLayoutStateAtomForTab(tabId, tabAtom));
|
const layoutTreeState = util.useAtomValueSafe(getLayoutStateAtomForTab(tabId, tabAtom));
|
||||||
if (!isBlockMagnified(layoutTreeState, blockData.oid)) {
|
const isMagnified = isBlockMagnified(layoutTreeState, blockData.oid);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const magnifyDecl: HeaderIconButton = {
|
const magnifyDecl: HeaderIconButton = {
|
||||||
elemtype: "iconbutton",
|
elemtype: "iconbutton",
|
||||||
icon: "regular@magnifying-glass-minus",
|
icon: <MagnifyIcon enabled={isMagnified} />,
|
||||||
title: "Minimize",
|
title: isMagnified ? "Minimize" : "Magnify",
|
||||||
click: layoutModel?.onMagnifyToggle,
|
click: layoutModel?.onMagnifyToggle,
|
||||||
};
|
};
|
||||||
return <IconButton key="magnify" decl={magnifyDecl} className="block-frame-magnify" />;
|
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);
|
useLongClick(buttonRef, decl.click, decl.longClick);
|
||||||
return (
|
return (
|
||||||
<div ref={buttonRef} className={clsx("iconbutton", className)} title={decl.title}>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
36
frontend/app/element/magnify.less
Normal file
36
frontend/app/element/magnify.less
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
frontend/app/element/magnify.stories.tsx
Normal file
25
frontend/app/element/magnify.stories.tsx
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
};
|
15
frontend/app/element/magnify.tsx
Normal file
15
frontend/app/element/magnify.tsx
Normal file
@ -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-font: 700 11px / normal "Inter", sans-serif;
|
||||||
--header-icon-size: 14px;
|
--header-icon-size: 14px;
|
||||||
--header-icon-width: 16px;
|
--header-icon-width: 16px;
|
||||||
|
--header-height: 30px;
|
||||||
|
|
||||||
--tab-green: rgb(88, 193, 66);
|
--tab-green: rgb(88, 193, 66);
|
||||||
|
|
||||||
|
@ -117,3 +117,16 @@
|
|||||||
border-radius: calc(var(--block-border-radius) + 2px);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
frontend/types/custom.d.ts
vendored
2
frontend/types/custom.d.ts
vendored
@ -123,7 +123,7 @@ declare global {
|
|||||||
|
|
||||||
type HeaderIconButton = {
|
type HeaderIconButton = {
|
||||||
elemtype: "iconbutton";
|
elemtype: "iconbutton";
|
||||||
icon: string;
|
icon: string | React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
click?: (e: React.MouseEvent<any>) => void;
|
click?: (e: React.MouseEvent<any>) => void;
|
||||||
|
Loading…
Reference in New Issue
Block a user