fix(editor): adjust callout emoji spacing based on first child block type (#13712)

- Remove fixed marginTop from emoji container style
- Dynamically calculate emoji marginTop based on first child block type
(h1-h6)
- Use model signal to reactively update spacing when children change
- Default to 10px for non-heading blocks

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Style
- Improved emoji alignment in callout blocks. The emoji now adjusts its
top spacing based on the first line’s heading level, ensuring better
vertical alignment with headings (H1–H6) and more consistent visual
balance across different callout contents.
- Maintains existing margins and layout behavior otherwise, resulting in
a cleaner, more polished appearance without affecting functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
3720
2025-10-09 17:04:14 +08:00
committed by GitHub
parent 1b859a37c5
commit 3c7461a5ce
2 changed files with 37 additions and 2 deletions
@@ -20,7 +20,7 @@ export const calloutEmojiContainerStyles = css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginTop: '10px',
// marginTop is dynamically set by JavaScript based on first child's height
marginBottom: '10px',
flexShrink: 0,
position: 'relative',
@@ -4,7 +4,10 @@ import {
popupTargetFromElement,
} from '@blocksuite/affine-components/context-menu';
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
import { type CalloutBlockModel } from '@blocksuite/affine-model';
import {
type CalloutBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
import {
@@ -69,6 +72,35 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
this.classList.add(calloutHostStyles);
}
private _getEmojiMarginTop(): string {
if (this.model.children.length === 0) {
return '10px';
}
const firstChild = this.model.children[0];
const flavour = firstChild.flavour;
const marginTopMap: Record<string, string> = {
'affine:paragraph:h1': '23px',
'affine:paragraph:h2': '20px',
'affine:paragraph:h3': '16px',
'affine:paragraph:h4': '15px',
'affine:paragraph:h5': '14px',
'affine:paragraph:h6': '13px',
};
// For heading blocks, use the type to determine margin
if (flavour === 'affine:paragraph') {
const paragraph = firstChild as ParagraphBlockModel;
const type = paragraph.props.type$.value;
const key = `${flavour}:${type}`;
return marginTopMap[key] || '10px';
}
// Default for all other block types
return '10px';
}
private _closeIconPicker() {
if (this._popupCloseHandler) {
this._popupCloseHandler();
@@ -204,6 +236,9 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
@click=${this._toggleIconPicker}
contenteditable="false"
class="${calloutEmojiContainerStyles}"
style=${styleMap({
marginTop: this._getEmojiMarginTop(),
})}
>
<span class="${calloutEmojiStyles}" data-testid="callout-emoji"
>${iconContent}</span