diff --git a/src/components/Editor.css b/src/components/Editor.css index 8791d5c5..9eab50e5 100644 --- a/src/components/Editor.css +++ b/src/components/Editor.css @@ -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 { diff --git a/src/components/EditorContent.tsx b/src/components/EditorContent.tsx index afc1e5b2..a2076e46 100644 --- a/src/components/EditorContent.tsx +++ b/src/components/EditorContent.tsx @@ -203,18 +203,20 @@ export function EditorContent({ {showEditor && activeTab && (
- - onTitleChange?.(activeTab.entry.path, newTitle)} - /> +
+ + onTitleChange?.(activeTab.entry.path, newTitle)} + /> +
diff --git a/tests/smoke/title-emoji-inline.spec.ts b/tests/smoke/title-emoji-inline.spec.ts new file mode 100644 index 00000000..95f04d25 --- /dev/null +++ b/tests/smoke/title-emoji-inline.spec.ts @@ -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() + }) +})