From 33ac14c3b62989fd16daaeaea78a9cddd33068b7 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 09:02:30 +0800 Subject: [PATCH 1/7] chore: adjust toolbar size --- packages/blocky-react/src/defaultToolbar/style.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/blocky-react/src/defaultToolbar/style.tsx b/packages/blocky-react/src/defaultToolbar/style.tsx index 85ffdfa..03bd07d 100644 --- a/packages/blocky-react/src/defaultToolbar/style.tsx +++ b/packages/blocky-react/src/defaultToolbar/style.tsx @@ -1,6 +1,8 @@ import { css } from "@emotion/react"; export const toolbarContainerStyle = css({ + fontFamily: `var(--blocky-font)`, + fontWeight: 400, backgroundColor: "var(--bg-color)", boxShadow: "0px 0px 4px rgba(0, 0, 0, 0.2)", padding: 0, @@ -22,15 +24,15 @@ export const toolbarMenuButton = css({ backgroundColor: "rgba(0, 0, 0, 0.1)", }, "&.bold": { - fontFamily: `'Times New Roman', Times, serif`, + // fontFamily: `'Times New Roman', Times, serif`, fontWeight: 600, }, "&.italic": { - fontFamily: `'Times New Roman', Times, serif`, + // fontFamily: `'Times New Roman', Times, serif`, fontStyle: "italic", }, "&.underline": { - fontFamily: `'Times New Roman', Times, serif`, + // fontFamily: `'Times New Roman', Times, serif`, textDecoration: "underline", }, }); From 191af2bb6f73dfa4984fac64c84aae7e977dcdae Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 09:08:37 +0800 Subject: [PATCH 2/7] feat: adjust grab style --- packages/blocky-example/app/spannerMenu.scss | 9 +++++++-- packages/blocky-example/app/spannerMenu.tsx | 6 +----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/blocky-example/app/spannerMenu.scss b/packages/blocky-example/app/spannerMenu.scss index e426a48..e670d5d 100644 --- a/packages/blocky-example/app/spannerMenu.scss +++ b/packages/blocky-example/app/spannerMenu.scss @@ -3,11 +3,16 @@ border-color: rgb(202, 196, 196); border-style: solid; border-width: 1px; - width: 18px; - height: 18px; + width: 16px; border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + padding-top: 3px; + padding-bottom: 3px; &:hover { + cursor: grab; box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2); } diff --git a/packages/blocky-example/app/spannerMenu.tsx b/packages/blocky-example/app/spannerMenu.tsx index f2392d9..c8c394b 100644 --- a/packages/blocky-example/app/spannerMenu.tsx +++ b/packages/blocky-example/app/spannerMenu.tsx @@ -19,11 +19,7 @@ interface SpannerState { } const SpannerIcon = ` - - - - - + `; class SpannerMenu extends Component { From af50f43dc5d506a21b1263175d0bb634e40d8a36 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 09:25:14 +0800 Subject: [PATCH 3/7] feat: spannar style --- packages/blocky-core/src/view/editor.ts | 4 ++ packages/blocky-example/app/app.tsx | 3 +- packages/blocky-example/app/spannerMenu.scss | 32 --------- .../components/dropdown/dropdown.tsx | 29 -------- .../blocky-example/components/menu/menu.scss | 28 -------- .../blocky-example/components/menu/menu.tsx | 43 ------------ .../src/components/dropdown/dropdown.tsx | 25 +++++++ .../src}/components/dropdown/index.ts | 0 .../src}/components/menu/index.ts | 0 .../blocky-react/src/components/menu/menu.tsx | 66 +++++++++++++++++++ .../defaultSpannerMenu.tsx} | 34 ++++++++-- .../src/defaultSpannerMenu/index.ts | 3 + .../src/defaultToolbar/defaultToolbar.tsx | 2 +- .../blocky-react/src/defaultToolbar/index.ts | 6 +- packages/blocky-react/src/index.ts | 1 + 15 files changed, 135 insertions(+), 141 deletions(-) delete mode 100644 packages/blocky-example/app/spannerMenu.scss delete mode 100644 packages/blocky-example/components/dropdown/dropdown.tsx delete mode 100644 packages/blocky-example/components/menu/menu.scss delete mode 100644 packages/blocky-example/components/menu/menu.tsx create mode 100644 packages/blocky-react/src/components/dropdown/dropdown.tsx rename packages/{blocky-example => blocky-react/src}/components/dropdown/index.ts (100%) rename packages/{blocky-example => blocky-react/src}/components/menu/index.ts (100%) create mode 100644 packages/blocky-react/src/components/menu/menu.tsx rename packages/{blocky-example/app/spannerMenu.tsx => blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx} (88%) create mode 100644 packages/blocky-react/src/defaultSpannerMenu/index.ts diff --git a/packages/blocky-core/src/view/editor.ts b/packages/blocky-core/src/view/editor.ts index 3318848..334cb58 100644 --- a/packages/blocky-core/src/view/editor.ts +++ b/packages/blocky-core/src/view/editor.ts @@ -1894,6 +1894,10 @@ export class Editor { return undefined; } + get fontFamily(): string { + return this.themeData?.font ?? blockyDefaultFonts; + } + handleBlocksContainerMouseMove(e: MouseEvent) { const y = e.clientY; const blockSizes = this.#collectBlocksSize(); diff --git a/packages/blocky-example/app/app.tsx b/packages/blocky-example/app/app.tsx index 6b5dd9c..c2a9cf9 100644 --- a/packages/blocky-example/app/app.tsx +++ b/packages/blocky-example/app/app.tsx @@ -9,13 +9,12 @@ import { makeImageBlockPlugin, type SpannerRenderProps, DefaultToolbarMenu, + SpannerMenu, } from "blocky-react"; import SearchBox from "@pkg/components/searchBox"; import ImagePlaceholder from "@pkg/components/imagePlaceholder"; import { makeCommandPanelPlugin } from "./plugins/commandPanel"; import { makeAtPanelPlugin } from "./plugins/atPanel"; -import SpannerMenu from "./spannerMenu"; -// import ToolbarMenu from "./toolbarMenu"; import TianShuiWeiImage from "./tianshuiwei.jpg"; import Image from "next/image"; import { blockyExampleFont, Theme } from "./themeSwitch"; diff --git a/packages/blocky-example/app/spannerMenu.scss b/packages/blocky-example/app/spannerMenu.scss deleted file mode 100644 index e670d5d..0000000 --- a/packages/blocky-example/app/spannerMenu.scss +++ /dev/null @@ -1,32 +0,0 @@ - -.blocky-example-banner-button { - border-color: rgb(202, 196, 196); - border-style: solid; - border-width: 1px; - width: 16px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - padding-top: 3px; - padding-bottom: 3px; - - &:hover { - cursor: grab; - box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2); - } - - svg { - width: 100%; - height: 100%; - } -} - -.blocky-example-image { - margin-bottom: 16px; - width: 100%; - - img { - width: 100%; - } -} diff --git a/packages/blocky-example/components/dropdown/dropdown.tsx b/packages/blocky-example/components/dropdown/dropdown.tsx deleted file mode 100644 index 3e655e4..0000000 --- a/packages/blocky-example/components/dropdown/dropdown.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Component } from "react"; -import { createPortal } from "react-dom"; -import Mask from "@pkg/components/mask"; - -export interface DropdownProps { - show?: boolean; - onMaskClicked?: () => void; - overlay?: any; - children?: any; -} - -class Dropdown extends Component { - - override render() { - const { children, show, onMaskClicked, overlay } = this.props; - return ( - <> - {children} - {show && - createPortal( - {overlay}, - document.body - )} - - ); - } -} - -export default Dropdown; diff --git a/packages/blocky-example/components/menu/menu.scss b/packages/blocky-example/components/menu/menu.scss deleted file mode 100644 index 7e24807..0000000 --- a/packages/blocky-example/components/menu/menu.scss +++ /dev/null @@ -1,28 +0,0 @@ - -.blocky-menu { - font-family: var(--blocky-example-font); - background-color: var(--bg-color); - box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2); - border-radius: 8px; - overflow: hidden; -} - -.blocky-menu-item { - width: 240px; - padding: 8px 12px; - font-size: 14px; - color: var(--primary-text-color); - color: rgb(72, 72, 72); - - &:hover { - background-color: rgba(0, 0, 0, 0.1); - } -} - -.blocky-menu-divider { - margin-top: 4px; - margin-bottom: 4px; - width: 100%; - height: 1px; - background-color: rgb(236, 236, 236); -} diff --git a/packages/blocky-example/components/menu/menu.tsx b/packages/blocky-example/components/menu/menu.tsx deleted file mode 100644 index a488d29..0000000 --- a/packages/blocky-example/components/menu/menu.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component } from "react"; -import "./menu.scss"; - -export interface MenuProps { - style?: React.CSSProperties; - children?: any; -} - -export class Menu extends Component { - override render() { - const { children, style } = this.props; - return ( -
- {children} -
- ); - } -} - -export interface MenuItemProps { - style?: React.CSSProperties; - onClick?: () => void; - children?: any; -} - -export class MenuItem extends Component { - override render() { - const { style, onClick, children } = this.props; - return ( -
- {children} -
- ); - } -} - -export function Divider() { - return
; -} diff --git a/packages/blocky-react/src/components/dropdown/dropdown.tsx b/packages/blocky-react/src/components/dropdown/dropdown.tsx new file mode 100644 index 0000000..6d8e988 --- /dev/null +++ b/packages/blocky-react/src/components/dropdown/dropdown.tsx @@ -0,0 +1,25 @@ +import { createPortal } from "react-dom"; +import Mask from "@pkg/components/mask"; + +export interface DropdownProps { + show?: boolean; + onMaskClicked?: () => void; + overlay?: any; + children?: any; +} + +function Dropdown(props: DropdownProps) { + const { children, show, onMaskClicked, overlay } = props; + return ( + <> + {children} + {show && + createPortal( + {overlay}, + document.body + )} + + ); +} + +export default Dropdown; diff --git a/packages/blocky-example/components/dropdown/index.ts b/packages/blocky-react/src/components/dropdown/index.ts similarity index 100% rename from packages/blocky-example/components/dropdown/index.ts rename to packages/blocky-react/src/components/dropdown/index.ts diff --git a/packages/blocky-example/components/menu/index.ts b/packages/blocky-react/src/components/menu/index.ts similarity index 100% rename from packages/blocky-example/components/menu/index.ts rename to packages/blocky-react/src/components/menu/index.ts diff --git a/packages/blocky-react/src/components/menu/menu.tsx b/packages/blocky-react/src/components/menu/menu.tsx new file mode 100644 index 0000000..a7cb208 --- /dev/null +++ b/packages/blocky-react/src/components/menu/menu.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { css } from "@emotion/react"; + +export interface MenuProps { + style?: React.CSSProperties; + children?: any; +} + +const menuStyle = css({ + fontFamily: "var(--blocky-font)", + backgroundColor: "var(--bg-color)", + boxShadow: "0px 0px 4px rgba(0, 0, 0, 0.2)", + borderRadius: "8px", + overflow: "hidden", +}); + +export function Menu(props: MenuProps) { + const { children, style } = props; + return ( +
+ {children} +
+ ); +} + +export interface MenuItemProps { + style?: React.CSSProperties; + onClick?: () => void; + children?: any; +} + +const menuItemStyle = css({ + width: "240px", + padding: "8px 12px", + fontSize: "14px", + color: "rgb(72, 72, 72)", + "&:hover": { + backgroundColor: "rgba(0, 0, 0, 0.1)", + }, +}); + +export function MenuItem(props: MenuItemProps) { + const { style, onClick, children } = props; + return ( +
+ {children} +
+ ); +} + +const dividerStyle = css({ + marginTop: "4px", + marginBottom: "4px", + width: "100%", + height: "1px", + backgroundColor: "rgb(236, 236, 236)", +}); + +export function Divider() { + return
; +} diff --git a/packages/blocky-example/app/spannerMenu.tsx b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx similarity index 88% rename from packages/blocky-example/app/spannerMenu.tsx rename to packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx index c8c394b..b117b8e 100644 --- a/packages/blocky-example/app/spannerMenu.tsx +++ b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx @@ -2,9 +2,9 @@ import { Component, type RefObject, createRef } from "react"; import { type EditorController, BlockDataElement, TextType } from "blocky-core"; import Dropdown from "@pkg/components/dropdown"; import { Menu, MenuItem, Divider } from "@pkg/components/menu"; -import { ImageBlockName } from "blocky-react"; +import { ImageBlockName } from "../"; import { Subject, takeUntil } from "rxjs"; -import "./spannerMenu.scss"; +import { css } from "@emotion/react"; export interface SpannerProps { editorController: EditorController; @@ -22,6 +22,25 @@ const SpannerIcon = ` `; +const buttonStyle = css({ + width: "16px", + height: "16px", + borderRadius: "4px", + display: "flex", + alignItems: "center", + justifyContent: "center", + paddingTop: "3px", + paddingBottom: "3px", + "&:hover": { + cursor: "grab", + boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)", + }, + svg: { + width: "100%", + height: "100%", + }, +}); + class SpannerMenu extends Component { private bannerRef: RefObject = createRef(); private dispose$ = new Subject(); @@ -121,7 +140,14 @@ class SpannerMenu extends Component { menuY += 36; return ( Text @@ -162,7 +188,7 @@ class SpannerMenu extends Component { >
diff --git a/packages/blocky-react/src/defaultSpannerMenu/index.ts b/packages/blocky-react/src/defaultSpannerMenu/index.ts new file mode 100644 index 0000000..f5485d6 --- /dev/null +++ b/packages/blocky-react/src/defaultSpannerMenu/index.ts @@ -0,0 +1,3 @@ +import SpannerMenu from "./defaultSpannerMenu"; + +export { SpannerMenu }; diff --git a/packages/blocky-react/src/defaultToolbar/defaultToolbar.tsx b/packages/blocky-react/src/defaultToolbar/defaultToolbar.tsx index 8e8e31c..aabd38f 100644 --- a/packages/blocky-react/src/defaultToolbar/defaultToolbar.tsx +++ b/packages/blocky-react/src/defaultToolbar/defaultToolbar.tsx @@ -18,7 +18,7 @@ const ToolbarMenuItem = memo( } ); -interface DefaultToolbarMenuProps { +export interface DefaultToolbarMenuProps { editorController: EditorController; } diff --git a/packages/blocky-react/src/defaultToolbar/index.ts b/packages/blocky-react/src/defaultToolbar/index.ts index d2fcb7c..a281c0e 100644 --- a/packages/blocky-react/src/defaultToolbar/index.ts +++ b/packages/blocky-react/src/defaultToolbar/index.ts @@ -1,3 +1,5 @@ -import DefaultToolbarMenu from "./defaultToolbar"; +import DefaultToolbarMenu, { + type DefaultToolbarMenuProps, +} from "./defaultToolbar"; -export { DefaultToolbarMenu }; +export { DefaultToolbarMenu, type DefaultToolbarMenuProps }; diff --git a/packages/blocky-react/src/index.ts b/packages/blocky-react/src/index.ts index 9ab0c49..855a954 100644 --- a/packages/blocky-react/src/index.ts +++ b/packages/blocky-react/src/index.ts @@ -9,6 +9,7 @@ export * from "./blocks"; export * from "./reactFollowerWidget"; export * from "./blockActiveDetector"; export * from "./defaultToolbar"; +export * from "./defaultSpannerMenu"; export { makeReactSpanner, type RenderProps as SpannerRenderProps, From 32631050ea34012e0c2e711648ce885b5f7c953e Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 09:45:09 +0800 Subject: [PATCH 4/7] refactor: spanner menu --- .../defaultSpannerMenu/defaultSpannerMenu.tsx | 187 ++++++------------ .../src/defaultSpannerMenu/style.ts | 24 +++ 2 files changed, 89 insertions(+), 122 deletions(-) create mode 100644 packages/blocky-react/src/defaultSpannerMenu/style.ts diff --git a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx index b117b8e..1d67b4d 100644 --- a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx +++ b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx @@ -1,108 +1,70 @@ -import { Component, type RefObject, createRef } from "react"; +import { useState, useRef, useCallback, useEffect } from "react"; +import ReactDOM from "react-dom"; import { type EditorController, BlockDataElement, TextType } from "blocky-core"; import Dropdown from "@pkg/components/dropdown"; import { Menu, MenuItem, Divider } from "@pkg/components/menu"; import { ImageBlockName } from "../"; import { Subject, takeUntil } from "rxjs"; -import { css } from "@emotion/react"; +import { SpannerIcon, buttonStyle } from "./style"; export interface SpannerProps { editorController: EditorController; focusedNode?: BlockDataElement; } -interface SpannerState { - showDropdown: boolean; - menuX: number; - menuY: number; - showDelete: boolean; +interface Coord { + x: number; + y: number; } -const SpannerIcon = ` - -`; - -const buttonStyle = css({ - width: "16px", - height: "16px", - borderRadius: "4px", - display: "flex", - alignItems: "center", - justifyContent: "center", - paddingTop: "3px", - paddingBottom: "3px", - "&:hover": { - cursor: "grab", - boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)", - }, - svg: { - width: "100%", - height: "100%", - }, -}); - -class SpannerMenu extends Component { - private bannerRef: RefObject = createRef(); - private dispose$ = new Subject(); - - constructor(props: SpannerProps) { - super(props); - this.state = { - showDropdown: false, - menuX: 0, - menuY: 0, - showDelete: false, +function SpannerMenu(props: SpannerProps) { + const { editorController, focusedNode } = props; + const [showDropdown, setShowDropdown] = useState(false); + const [showDelete, setShowDelete] = useState(false); + const [menuCoord, setMenuCoord] = useState({ x: 0, y: 0 }); + const bannerRef = useRef(null); + + useEffect(() => { + const dispose$ = new Subject(); + + const handleBlocksChanged = () => { + const blockCount = editorController.state.blocks.size; + + const showDelete = blockCount > 1; + setShowDelete(showDelete); }; - } - override componentDidMount() { - const { editorController } = this.props; const { state } = editorController; state.newBlockCreated - .pipe(takeUntil(this.dispose$)) - .subscribe(this.handleBlocksChanged); + .pipe(takeUntil(dispose$)) + .subscribe(handleBlocksChanged); state.blockWillDelete - .pipe(takeUntil(this.dispose$)) - .subscribe(this.handleBlocksChanged); - - this.handleBlocksChanged(); - - this.bannerRef.current!.innerHTML = SpannerIcon; - } + .pipe(takeUntil(dispose$)) + .subscribe(handleBlocksChanged); - override componentWillUnmount() { - this.dispose$.next(); - } + handleBlocksChanged(); - private handleBlocksChanged = () => { - const { editorController } = this.props; + bannerRef.current!.innerHTML = SpannerIcon; - const blockCount = editorController.state.blocks.size; - - const showDelete = blockCount > 1; - if (showDelete === this.state.showDelete) { - return; - } - this.setState({ showDelete }); - }; + return () => { + dispose$.next(); + dispose$.complete(); + }; + }, [editorController]); - private handleClick = () => { - const rect = this.bannerRef.current!.getBoundingClientRect(); - this.setState({ - showDropdown: true, - menuX: rect.x, - menuY: rect.y, + const handleClick = useCallback(() => { + const rect = bannerRef.current!.getBoundingClientRect(); + ReactDOM.unstable_batchedUpdates(() => { + setShowDropdown(true); + setMenuCoord({ x: rect.x, y: rect.y }); }); - }; + }, []); - private handleMaskClicked = () => { - this.setState({ - showDropdown: false, - }); - }; + const handleMaskClicked = useCallback(() => { + setShowDropdown(false); + }, []); - private insertText = (textType: TextType) => () => { - const { editorController, focusedNode } = this.props; + const insertText = (textType: TextType) => () => { if (!focusedNode) { return; } @@ -114,8 +76,7 @@ class SpannerMenu extends Component { }); }; - private insertImage = () => { - const { editorController, focusedNode } = this.props; + const insertImage = () => { if (!focusedNode) { return; } @@ -126,49 +87,38 @@ class SpannerMenu extends Component { }); }; - private deleteBlock = () => { - const { editorController, focusedNode } = this.props; + const deleteBlock = () => { if (!focusedNode) { return; } editorController.deleteBlock(focusedNode.id); }; - private renderMenu() { - const { menuX, showDelete } = this.state; - let { menuY } = this.state; - menuY += 36; + const renderMenu = () => { + const menuY = menuCoord.y + 36; return ( - Text - - Heading1 - - - Heading2 - - - Heading3 - - - Checkbox - - Image + Text + Heading1 + Heading2 + Heading3 + Checkbox + Image {showDelete && ( <> Delete @@ -176,24 +126,17 @@ class SpannerMenu extends Component { )} ); - } + }; - render() { - const { showDropdown } = this.state; - return ( - -
-
- ); - } + return ( + +
+
+ ); } export default SpannerMenu; diff --git a/packages/blocky-react/src/defaultSpannerMenu/style.ts b/packages/blocky-react/src/defaultSpannerMenu/style.ts new file mode 100644 index 0000000..3e3349d --- /dev/null +++ b/packages/blocky-react/src/defaultSpannerMenu/style.ts @@ -0,0 +1,24 @@ +import { css } from "@emotion/react"; + +export const SpannerIcon = ` + +`; + +export const buttonStyle = css({ + width: "16px", + height: "16px", + borderRadius: "4px", + display: "flex", + alignItems: "center", + justifyContent: "center", + paddingTop: "3px", + paddingBottom: "3px", + "&:hover": { + cursor: "grab", + boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)", + }, + svg: { + width: "100%", + height: "100%", + }, +}); From 6cdf5253307309d5227cc889f2a8a6eebb741e20 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 10:14:56 +0800 Subject: [PATCH 5/7] feat: add transition --- packages/blocky-core/css/blocky-core.css | 3 +++ .../blocky-core/src/view/toolbarDelegate.ts | 2 ++ .../blocky-react/src/components/menu/menu.tsx | 17 +++++++++++++++-- .../blocky-react/src/defaultToolbar/style.tsx | 3 +++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/blocky-core/css/blocky-core.css b/packages/blocky-core/css/blocky-core.css index b0508bd..550f176 100644 --- a/packages/blocky-core/css/blocky-core.css +++ b/packages/blocky-core/css/blocky-core.css @@ -24,6 +24,9 @@ .blocky-editor-toolbar-delegate { position: absolute; z-index: 11; + transition-property: opacity, transform; + transition-duration: 200ms; + transition-timing-function: ease; } /* https://stackoverflow.com/questions/826782/how-to-disable-text-selection-highlighting */ diff --git a/packages/blocky-core/src/view/toolbarDelegate.ts b/packages/blocky-core/src/view/toolbarDelegate.ts index fb75276..ae4f6f8 100644 --- a/packages/blocky-core/src/view/toolbarDelegate.ts +++ b/packages/blocky-core/src/view/toolbarDelegate.ts @@ -58,6 +58,7 @@ export class ToolbarDelegate extends UIDelegate { this.#debounced = undefined; } super.hide(); + this.container.style.opacity = "0"; } override show() { @@ -77,6 +78,7 @@ export class ToolbarDelegate extends UIDelegate { } this.#debounced = undefined; this.container.style.display = ""; + this.container.style.opacity = "1"; this.shown = true; if (this.shown) { this.container.style.top = this.#y + "px"; diff --git a/packages/blocky-react/src/components/menu/menu.tsx b/packages/blocky-react/src/components/menu/menu.tsx index a7cb208..782a84e 100644 --- a/packages/blocky-react/src/components/menu/menu.tsx +++ b/packages/blocky-react/src/components/menu/menu.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { css } from "@emotion/react"; export interface MenuProps { @@ -12,12 +12,25 @@ const menuStyle = css({ boxShadow: "0px 0px 4px rgba(0, 0, 0, 0.2)", borderRadius: "8px", overflow: "hidden", + transitionProperty: "opacity, transform", + transitionDuration: "200ms", + transitionTimingFunction: "ease", }); export function Menu(props: MenuProps) { const { children, style } = props; + const [opacity, setOpacity] = useState(0); + useEffect(() => { + setOpacity(1); + }, []); return ( -
+
{children}
); diff --git a/packages/blocky-react/src/defaultToolbar/style.tsx b/packages/blocky-react/src/defaultToolbar/style.tsx index 03bd07d..9a351fc 100644 --- a/packages/blocky-react/src/defaultToolbar/style.tsx +++ b/packages/blocky-react/src/defaultToolbar/style.tsx @@ -9,6 +9,9 @@ export const toolbarContainerStyle = css({ borderRadius: 4, overflow: "hidden", boxSizing: "border-box", + transitionProperty: "opacity, transform", + transitionDuration: "200ms", + transitionTimingFunction: "ease", }); export const toolbarMenuButton = css({ From 7ec3826faba593aa389c388985ef8c11955b585f Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 10:39:50 +0800 Subject: [PATCH 6/7] feat: smart dropdown --- .../src/components/dropdown/dropdown.tsx | 82 ++++++++++++++++++- .../defaultSpannerMenu/defaultSpannerMenu.tsx | 37 +++------ 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/packages/blocky-react/src/components/dropdown/dropdown.tsx b/packages/blocky-react/src/components/dropdown/dropdown.tsx index 6d8e988..4faee3b 100644 --- a/packages/blocky-react/src/components/dropdown/dropdown.tsx +++ b/packages/blocky-react/src/components/dropdown/dropdown.tsx @@ -1,21 +1,95 @@ -import { createPortal } from "react-dom"; +import { RefObject, useEffect, useRef, useState } from "react"; +import ReactDOM, { createPortal } from "react-dom"; import Mask from "@pkg/components/mask"; export interface DropdownProps { show?: boolean; onMaskClicked?: () => void; - overlay?: any; + overlay: () => React.ReactNode; children?: any; + anchorRef: RefObject; +} + +interface Coord { + x: number; + y: number; +} + +const zero: Coord = { x: 0, y: 0 }; +const margin = 16; + +function fixMenuCoord( + coord: Coord, + winWidth: number, + winHeight: number, + menuWidth: number, + menuHeight: number +): Coord { + const { x, y } = coord; + if (x + menuWidth + margin > winWidth) { + coord.x = winWidth - menuWidth - margin; + } + if (y + menuHeight + margin > winHeight) { + coord.y = winHeight - menuHeight - margin; + } + return coord; } function Dropdown(props: DropdownProps) { - const { children, show, onMaskClicked, overlay } = props; + const { children, show, onMaskClicked, overlay, anchorRef } = props; + const [menuCoord, setMenuCoord] = useState(zero); + const [shown, setShown] = useState(false); + const contentRef = useRef(null); + + useEffect(() => { + if (show) { + setMenuCoord({ x: -1000, y: -1000 }); + window.requestAnimationFrame(() => { + setShown(true); + }); + } else { + ReactDOM.unstable_batchedUpdates(() => { + setMenuCoord({ x: -1000, y: -1000 }); + setShown(false); + }); + } + }, [show, anchorRef]); + + useEffect(() => { + if (shown) { + const rect = anchorRef.current!.getBoundingClientRect(); + const contentRect = contentRef.current!.getBoundingClientRect(); + const x = rect.x - rect.width - contentRect.width; + const y = rect.y + rect.height / 2 - contentRect.height / 2; + + const fixedCoord = fixMenuCoord( + { x, y }, + window.innerWidth, + window.innerHeight, + contentRect.width, + contentRect.height + ); + setMenuCoord(fixedCoord); + } + }, [shown]); + return ( <> {children} {show && createPortal( - {overlay}, + +
+ {overlay()} +
+
, document.body )} diff --git a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx index 1d67b4d..34e4a8d 100644 --- a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx +++ b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx @@ -1,5 +1,4 @@ import { useState, useRef, useCallback, useEffect } from "react"; -import ReactDOM from "react-dom"; import { type EditorController, BlockDataElement, TextType } from "blocky-core"; import Dropdown from "@pkg/components/dropdown"; import { Menu, MenuItem, Divider } from "@pkg/components/menu"; @@ -12,16 +11,10 @@ export interface SpannerProps { focusedNode?: BlockDataElement; } -interface Coord { - x: number; - y: number; -} - function SpannerMenu(props: SpannerProps) { const { editorController, focusedNode } = props; const [showDropdown, setShowDropdown] = useState(false); const [showDelete, setShowDelete] = useState(false); - const [menuCoord, setMenuCoord] = useState({ x: 0, y: 0 }); const bannerRef = useRef(null); useEffect(() => { @@ -44,8 +37,6 @@ function SpannerMenu(props: SpannerProps) { handleBlocksChanged(); - bannerRef.current!.innerHTML = SpannerIcon; - return () => { dispose$.next(); dispose$.complete(); @@ -53,11 +44,7 @@ function SpannerMenu(props: SpannerProps) { }, [editorController]); const handleClick = useCallback(() => { - const rect = bannerRef.current!.getBoundingClientRect(); - ReactDOM.unstable_batchedUpdates(() => { - setShowDropdown(true); - setMenuCoord({ x: rect.x, y: rect.y }); - }); + setShowDropdown(true); }, []); const handleMaskClicked = useCallback(() => { @@ -95,17 +82,13 @@ function SpannerMenu(props: SpannerProps) { }; const renderMenu = () => { - const menuY = menuCoord.y + 36; return ( Text Heading1 @@ -131,10 +114,16 @@ function SpannerMenu(props: SpannerProps) { return ( -
+
); } From 4fbf879fdcdd1aad044747ee0eaa498fee1abf5e Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 27 Nov 2023 10:42:20 +0800 Subject: [PATCH 7/7] feat: add default spanner menu --- packages/blocky-example/app/app.tsx | 4 ++-- .../src/defaultSpannerMenu/defaultSpannerMenu.tsx | 4 ++-- packages/blocky-react/src/defaultSpannerMenu/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/blocky-example/app/app.tsx b/packages/blocky-example/app/app.tsx index c2a9cf9..491d6e8 100644 --- a/packages/blocky-example/app/app.tsx +++ b/packages/blocky-example/app/app.tsx @@ -9,7 +9,7 @@ import { makeImageBlockPlugin, type SpannerRenderProps, DefaultToolbarMenu, - SpannerMenu, + DefaultSpannerMenu, } from "blocky-react"; import SearchBox from "@pkg/components/searchBox"; import ImagePlaceholder from "@pkg/components/imagePlaceholder"; @@ -62,7 +62,7 @@ function makeController(userId: string, title: string): EditorController { */ spannerFactory: makeReactSpanner( ({ editorController, focusedNode }: SpannerRenderProps) => ( - diff --git a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx index 34e4a8d..c4e4704 100644 --- a/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx +++ b/packages/blocky-react/src/defaultSpannerMenu/defaultSpannerMenu.tsx @@ -11,7 +11,7 @@ export interface SpannerProps { focusedNode?: BlockDataElement; } -function SpannerMenu(props: SpannerProps) { +function DefaultSpannerMenu(props: SpannerProps) { const { editorController, focusedNode } = props; const [showDropdown, setShowDropdown] = useState(false); const [showDelete, setShowDelete] = useState(false); @@ -128,4 +128,4 @@ function SpannerMenu(props: SpannerProps) { ); } -export default SpannerMenu; +export default DefaultSpannerMenu; diff --git a/packages/blocky-react/src/defaultSpannerMenu/index.ts b/packages/blocky-react/src/defaultSpannerMenu/index.ts index f5485d6..d30028f 100644 --- a/packages/blocky-react/src/defaultSpannerMenu/index.ts +++ b/packages/blocky-react/src/defaultSpannerMenu/index.ts @@ -1,3 +1,3 @@ -import SpannerMenu from "./defaultSpannerMenu"; +import DefaultSpannerMenu from "./defaultSpannerMenu"; -export { SpannerMenu }; +export { DefaultSpannerMenu };