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:
@@ -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 {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
97
tests/smoke/title-emoji-inline.spec.ts
Normal file
97
tests/smoke/title-emoji-inline.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user