fix: title H1 style with inline emoji layout

Wrap NoteIcon + TitleField in a flex row container so the emoji
renders inline-left of the title instead of stacked vertically.
Add fallback values for H1 CSS variables (28px, 700 weight).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-29 14:31:35 +02:00
parent a42a15c30c
commit fc4427750d
3 changed files with 127 additions and 21 deletions

View File

@@ -179,6 +179,13 @@
flex-shrink: 0;
}
.title-section__row {
display: flex;
flex-direction: row;
align-items: flex-start;
padding-top: 16px;
}
.title-section__separator {
border-bottom: 1px solid var(--border-primary, rgba(0, 0, 0, 0.08));
margin-top: 12px;
@@ -186,7 +193,6 @@
/* --- Note Icon Area --- */
.note-icon-area {
padding: 16px 0 0;
flex-shrink: 0;
}
@@ -203,8 +209,10 @@
}
.note-icon-button--active {
font-size: 40px;
font-size: 32px;
line-height: 1.2;
transition: transform 0.1s;
margin-right: 8px;
}
.note-icon-button--active:hover:not(:disabled) {
@@ -272,8 +280,8 @@
/* --- Title Field --- */
.title-field {
padding: 4px 0 0;
flex-shrink: 0;
flex: 1;
min-width: 0;
}
.title-field__input {
@@ -282,13 +290,12 @@
border: none;
outline: none;
background: transparent;
font-size: var(--headings-h1-font-size);
font-weight: var(--headings-h1-font-weight);
line-height: var(--headings-h1-line-height);
letter-spacing: var(--headings-h1-letter-spacing);
font-size: var(--headings-h1-font-size, 28px);
font-weight: var(--headings-h1-font-weight, 700);
line-height: var(--headings-h1-line-height, 1.2);
letter-spacing: var(--headings-h1-letter-spacing, -0.015em);
color: var(--foreground);
padding: 0;
margin-left: 8px;
}
.title-field__input::placeholder {

View File

@@ -203,18 +203,20 @@ export function EditorContent({
{showEditor && activeTab && (
<div className="editor-scroll-area">
<div className="title-section">
<NoteIcon
icon={emojiIcon}
editable={!isTrashed}
onSetIcon={handleSetIcon}
onRemoveIcon={handleRemoveIcon}
/>
<TitleField
title={activeTab.entry.title}
filename={activeTab.entry.filename}
editable={!isTrashed}
onTitleChange={(newTitle) => onTitleChange?.(activeTab.entry.path, newTitle)}
/>
<div className="title-section__row">
<NoteIcon
icon={emojiIcon}
editable={!isTrashed}
onSetIcon={handleSetIcon}
onRemoveIcon={handleRemoveIcon}
/>
<TitleField
title={activeTab.entry.title}
filename={activeTab.entry.filename}
editable={!isTrashed}
onTitleChange={(newTitle) => onTitleChange?.(activeTab.entry.path, newTitle)}
/>
</div>
<div className="title-section__separator" />
</div>
<SingleEditorView editor={editor} entries={entries} onNavigateWikilink={onNavigateWikilink} onChange={onEditorChange} vaultPath={vaultPath} editable={!isTrashed} />

View File

@@ -0,0 +1,97 @@
import { test, expect } from '@playwright/test'
test.describe('Title H1 with inline emoji', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForTimeout(500)
// Open a note so TitleField is visible
const noteItem = page.locator('.app__note-list .cursor-pointer').first()
await noteItem.click()
await page.waitForTimeout(500)
})
test('title-section__row is a horizontal flex container', async ({ page }) => {
const row = page.locator('.title-section__row')
await expect(row).toBeVisible({ timeout: 3000 })
const display = await row.evaluate(el => getComputedStyle(el).display)
const flexDir = await row.evaluate(el => getComputedStyle(el).flexDirection)
expect(display).toBe('flex')
expect(flexDir).toBe('row')
})
test('title field renders with large H1 font size', async ({ page }) => {
const input = page.locator('.title-field__input')
await expect(input).toBeVisible({ timeout: 3000 })
const fontSize = await input.evaluate(el => {
const computed = getComputedStyle(el)
return parseFloat(computed.fontSize)
})
// Title must be at least 24px (H1 style, significantly larger than body ~16px)
expect(fontSize).toBeGreaterThanOrEqual(24)
})
test('title field renders with bold font weight', async ({ page }) => {
const input = page.locator('.title-field__input')
await expect(input).toBeVisible({ timeout: 3000 })
const fontWeight = await input.evaluate(el => {
const computed = getComputedStyle(el)
return parseInt(computed.fontWeight, 10)
})
// Font weight 700+ (bold or heavier)
expect(fontWeight).toBeGreaterThanOrEqual(700)
})
test('emoji icon and title are on the same horizontal line when icon present', async ({ page }) => {
// Add an icon via the "Add icon" button
const iconArea = page.locator('[data-testid="note-icon-area"]')
await iconArea.hover()
await page.waitForTimeout(200)
const addBtn = page.locator('[data-testid="note-icon-add"]')
if (await addBtn.isVisible()) {
await addBtn.click()
await page.waitForTimeout(300)
// Pick the first emoji in the picker
const emojiBtn = page.locator('.emoji-picker-grid button').first()
if (await emojiBtn.isVisible()) {
await emojiBtn.click()
await page.waitForTimeout(300)
}
}
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
const titleInput = page.locator('[data-testid="title-field-input"]')
if (await iconDisplay.isVisible()) {
const iconBox = await iconDisplay.boundingBox()
const titleBox = await titleInput.boundingBox()
expect(iconBox).not.toBeNull()
expect(titleBox).not.toBeNull()
// Emoji and title must be on the same row: their vertical centers overlap
const iconCenter = iconBox!.y + iconBox!.height / 2
const titleTop = titleBox!.y
const titleBottom = titleBox!.y + titleBox!.height
expect(iconCenter).toBeGreaterThanOrEqual(titleTop - 8)
expect(iconCenter).toBeLessThanOrEqual(titleBottom + 8)
// Emoji must be to the left of the title
expect(iconBox!.x).toBeLessThan(titleBox!.x)
}
})
test('clicking title still enters edit mode', async ({ page }) => {
const titleInput = page.locator('[data-testid="title-field-input"]')
await expect(titleInput).toBeVisible({ timeout: 3000 })
await titleInput.click()
await expect(titleInput).toBeFocused()
})
})