feat(editor): inner toolbar layout for block (#11243)

Close [BS-2808](https://linear.app/affine-design/issue/BS-2808/组件内-toolbar-重构)
This commit is contained in:
L-Sun
2025-03-28 03:47:37 +00:00
parent 7193393a06
commit e2c752d56f
8 changed files with 150 additions and 62 deletions
@@ -7,12 +7,13 @@ import {
type ToolbarAction,
type ToolbarActions,
type ToolbarContext,
type ToolbarPlacement,
} from '@blocksuite/affine-shared/services';
import { nextTick } from '@blocksuite/global/utils';
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
import type {
AutoUpdateOptions,
Placement,
ComputePositionConfig,
ReferenceElement,
SideObject,
} from '@floating-ui/dom';
@@ -25,8 +26,9 @@ import {
limitShift,
offset,
shift,
size,
} from '@floating-ui/dom';
import { html, render } from 'lit';
import { html, nothing, render } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { join } from 'lit/directives/join.js';
import { keyed } from 'lit/directives/keyed.js';
@@ -51,34 +53,54 @@ export function autoUpdatePosition(
toolbar: EditorToolbar,
referenceElement: ReferenceElement,
flavour: string,
placement: Placement,
placement: ToolbarPlacement,
sideOptions: Partial<SideObject> | null,
options: AutoUpdateOptions = { elementResize: false, animationFrame: true }
) {
const isInline = flavour === 'affine:note';
const hasSurfaceScope = flavour.includes('surface');
const isInner = placement === 'inner';
const offsetTop = sideOptions?.top ?? 0;
const offsetBottom = sideOptions?.bottom ?? 0;
const offsetY = offsetTop + (hasSurfaceScope ? 2 : 0);
const config = {
placement,
middleware: [
offset(10 + offsetY),
isInline ? inline() : undefined,
shift(state => ({
padding: {
top: 10,
right: 10,
bottom: 150,
left: 10,
},
crossAxis: state.placement.includes('bottom'),
limiter: limitShift(),
})),
flip({ padding: 10 }),
hide(),
],
};
const config: Partial<ComputePositionConfig> = isInner
? {
placement: 'top-start',
middleware: [
offset(({ rects }) => -rects.floating.height),
size({
apply: ({ elements }) => {
elements.floating.style.width = `${
elements.reference.getBoundingClientRect().width
}px`;
},
}),
],
}
: {
placement,
middleware: [
offset(10 + offsetY),
size({
apply: ({ elements }) => {
elements.floating.style.width = 'fit-content';
},
}),
isInline ? inline() : undefined,
shift(state => ({
padding: {
top: 10,
right: 10,
bottom: 150,
left: 10,
},
crossAxis: state.placement.includes('bottom'),
limiter: limitShift(),
})),
flip({ padding: 10 }),
hide(),
],
};
const update = async () => {
await Promise.race([
new Promise(resolve => {
@@ -217,6 +239,8 @@ export function renderToolbar(
a => a.placement === ActionPlacement.More
);
const innerToolbar = context.placement$.value === 'inner';
if (moreActionGroup.length) {
const moreMenuItems = renderActions(
moreActionGroup,
@@ -235,12 +259,20 @@ export function renderToolbar(
aria-label="More menu"
.contentPadding="${'8px'}"
.button=${html`
<editor-icon-button aria-label="More" .tooltip="${'More'}">
<editor-icon-button
aria-label="More"
.tooltip="${'More'}"
.iconContainerPadding=${innerToolbar ? 4 : 2}
.iconSize=${innerToolbar ? '16px' : undefined}
>
${MoreVerticalIcon()}
</editor-icon-button>
`}
>
<div data-size="large" data-orientation="vertical">
<div
data-size=${innerToolbar ? '' : 'large'}
data-orientation="vertical"
>
${join(moreMenuItems, renderToolbarSeparator('horizontal'))}
</div>
</editor-menu-button>
@@ -251,7 +283,10 @@ export function renderToolbar(
}
render(
join(renderActions(primaryActionGroup, context), renderToolbarSeparator()),
join(
renderActions(primaryActionGroup, context),
innerToolbar ? nothing : renderToolbarSeparator()
),
toolbar
);
}
@@ -305,6 +340,7 @@ function renderActions(
// TODO(@fundon): supports templates
function renderActionItem(action: ToolbarAction, context: ToolbarContext) {
const innerToolbar = context.placement$.value === 'inner';
const ids = action.id.split('.');
const id = ids[ids.length - 1];
return html`
@@ -316,6 +352,8 @@ function renderActionItem(action: ToolbarAction, context: ToolbarContext) {
: action.active}
?disabled=${action.disabled}
.tooltip=${action.tooltip}
.iconContainerPadding=${innerToolbar ? 4 : 2}
.iconSize=${innerToolbar ? '16px' : undefined}
@click=${() => action.run?.(context)}
>
${action.icon}
@@ -327,6 +365,7 @@ function renderActionItem(action: ToolbarAction, context: ToolbarContext) {
}
function renderMenuActionItem(action: ToolbarAction, context: ToolbarContext) {
const innerToolbar = context.placement$.value === 'inner';
const ids = action.id.split('.');
const id = ids[ids.length - 1];
return html`
@@ -341,6 +380,8 @@ function renderMenuActionItem(action: ToolbarAction, context: ToolbarContext) {
: action.active}
?disabled=${action.disabled}
.tooltip=${ifDefined(action.tooltip)}
.iconContainerPadding=${innerToolbar ? 4 : 2}
.iconSize=${innerToolbar ? '16px' : undefined}
@click=${() => action.run?.(context)}
>
${action.icon}