fix(web): keep note titles visible on compact desktop windows
Notebook and tag controls no longer refuse to shrink, so a scaled desktop window cannot collapse the title field to a couple of characters.
This commit is contained in:
1 parent
36cec919ae
commit
b9535e071d
10 files changed
+44
-15
No files matched your search
@@ -444,7 +444,7 @@ describe("diagram editor canvas surface", () => {
|
||||
expect(status).toBeGreaterThan(metadata);
|
||||
expect(headerEnd).toBeGreaterThan(status);
|
||||
expect(toolbar).toBeGreaterThan(headerEnd);
|
||||
expect(source).toContain('rowClassName="shrink-0 flex-nowrap"');
|
||||
expect(source).toContain("rowClassName={MEMO_EDITOR_METADATA_ROW_CLASS_NAME}");
|
||||
expect(source).toContain("nextTitleStatusClearance");
|
||||
expect(source).toContain('cn(MEMO_EDITOR_TOP_ROW_CLASS_NAME, "border-b-0")');
|
||||
expect(source).not.toContain("MEMO_EDITOR_TITLE_REGION_CLASS_NAME");
|
||||
|
||||
@@ -154,6 +154,7 @@ import { MemoEditorMetadataRow } from "@/components/MemoEditorMetadataRow";
|
||||
import { MemoEditorFocusModeButton, MemoEditorTopRowLeading, MemoEditorUpdatedLabel } from "@/components/MemoEditorTopRowLeading";
|
||||
import { MemoEditorToolbarDivider } from "@/components/MemoEditorToolbarChrome";
|
||||
import {
|
||||
MEMO_EDITOR_METADATA_ROW_CLASS_NAME,
|
||||
MEMO_EDITOR_TOP_ROW_CLASS_NAME,
|
||||
nextTitleStatusClearance,
|
||||
} from "@/components/MemoEditorChromeDensity";
|
||||
@@ -3089,7 +3090,6 @@ export const DiagramEditorPane = ({
|
||||
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 px-4 sm:flex-nowrap"
|
||||
>
|
||||
<MemoEditorTopRowLeading
|
||||
className="min-w-0 flex-1"
|
||||
mobileBackButton={(
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -3123,7 +3123,7 @@ export const DiagramEditorPane = ({
|
||||
)}
|
||||
/>
|
||||
<MemoEditorMetadataRow
|
||||
rowClassName="shrink-0 flex-nowrap"
|
||||
rowClassName={MEMO_EDITOR_METADATA_ROW_CLASS_NAME}
|
||||
contentMarkdown={memo.contentMarkdown}
|
||||
disabled={readOnly}
|
||||
mobileNotebookPickerOpen={mobileNotebookSheetOpen}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { MemoEditorMetadataRow } from "@/components/MemoEditorMetadataRow";
|
||||
import { MemoEditorFocusModeButton, MemoEditorTopRowLeading, MemoEditorUpdatedLabel } from "@/components/MemoEditorTopRowLeading";
|
||||
import { MemoEditorToolbarDivider } from "@/components/MemoEditorToolbarChrome";
|
||||
import {
|
||||
MEMO_EDITOR_METADATA_ROW_CLASS_NAME,
|
||||
MEMO_EDITOR_READING_GUTTER_CLASS_NAME,
|
||||
MEMO_EDITOR_READING_GUTTER_PROPERTIES_CLASS_NAME,
|
||||
MEMO_EDITOR_TOP_ROW_CLASS_NAME,
|
||||
@@ -3650,7 +3651,6 @@ const RichEditorPane = ({
|
||||
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 px-4 sm:flex-nowrap"
|
||||
>
|
||||
<MemoEditorTopRowLeading
|
||||
className="min-w-0 flex-1"
|
||||
mobileBackButton={(
|
||||
<Button
|
||||
className="lg:hidden"
|
||||
@@ -3679,7 +3679,7 @@ const RichEditorPane = ({
|
||||
)}
|
||||
/>
|
||||
<MemoEditorMetadataRow
|
||||
rowClassName="shrink-0 flex-nowrap"
|
||||
rowClassName={MEMO_EDITOR_METADATA_ROW_CLASS_NAME}
|
||||
contentMarkdown={currentMarkdownForAi}
|
||||
disabled={effectiveReadOnly}
|
||||
mobileNotebookPickerOpen={mobileNotebookSheetOpen}
|
||||
|
||||
@@ -147,11 +147,11 @@ export const EditorTagPicker = ({ contentMarkdown, disabled, loadTags, title, va
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex min-w-0 max-w-full items-center gap-1">
|
||||
<div className="flex min-w-0 max-w-full flex-1 items-center gap-1 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="flex h-7 min-w-0 max-w-[32rem] items-center gap-1 rounded-md border border-transparent px-1.5 text-left text-xs text-slate-500 outline-none transition hover:border-slate-200 hover:bg-slate-50 focus-visible:border-slate-300 focus-visible:ring-2 focus-visible:ring-slate-400/25 disabled:opacity-50 sm:gap-1.5 sm:px-2"
|
||||
className="flex h-7 min-w-0 flex-1 overflow-hidden max-w-[32rem] items-center gap-1 rounded-md border border-transparent px-1.5 text-left text-xs text-slate-500 outline-none transition hover:border-slate-200 hover:bg-slate-50 focus-visible:border-slate-300 focus-visible:ring-2 focus-visible:ring-slate-400/25 disabled:opacity-50 sm:gap-1.5 sm:px-2"
|
||||
aria-label={t("editor.tagPicker.open")}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IconTooltip } from "@/components/editor/EditorPaneChrome";
|
||||
import { MemoEditorHeaderActions } from "@/components/MemoEditorHeaderActions";
|
||||
import { MemoEditorMetadataRow } from "@/components/MemoEditorMetadataRow";
|
||||
import { MemoEditorFocusModeButton, MemoEditorTopRowLeading, MemoEditorUpdatedLabel } from "@/components/MemoEditorTopRowLeading";
|
||||
import { MEMO_EDITOR_TOP_ROW_CLASS_NAME, nextTitleStatusClearance } from "@/components/MemoEditorChromeDensity";
|
||||
import { MEMO_EDITOR_METADATA_ROW_CLASS_NAME, MEMO_EDITOR_TOP_ROW_CLASS_NAME, nextTitleStatusClearance } from "@/components/MemoEditorChromeDensity";
|
||||
import { MemoEditorToolbarDivider } from "@/components/MemoEditorToolbarChrome";
|
||||
import { MemoTitleInput } from "@/components/MemoTitleInput";
|
||||
import { api } from "@/lib/api";
|
||||
@@ -550,7 +550,6 @@ export default function InfographicEditorPane({
|
||||
<div className="min-w-0 w-full" style={titleStatusClearancePx > 0 ? { paddingRight: titleStatusClearancePx } : undefined}>
|
||||
<div ref={setHeaderTitleSlot} className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 px-4 sm:flex-nowrap">
|
||||
<MemoEditorTopRowLeading
|
||||
className="min-w-0 flex-1"
|
||||
mobileBackButton={(
|
||||
<IconTooltip label={t("common.back")}>
|
||||
<Button variant="ghost" size="icon" className="lg:hidden" onClick={onBackToList} aria-label={t("common.back")}><ChevronLeft className="h-4 w-4" /></Button>
|
||||
@@ -561,7 +560,7 @@ export default function InfographicEditorPane({
|
||||
)}
|
||||
/>
|
||||
<MemoEditorMetadataRow
|
||||
rowClassName="shrink-0 flex-nowrap"
|
||||
rowClassName={MEMO_EDITOR_METADATA_ROW_CLASS_NAME}
|
||||
contentMarkdown={syntax}
|
||||
disabled={readOnly}
|
||||
mobileNotebookPickerOpen={mobileNotebookSheetOpen}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { nextTitleStatusClearance } from "./MemoEditorChromeDensity.ts";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { MEMO_EDITOR_METADATA_ROW_CLASS_NAME, nextTitleStatusClearance } from "./MemoEditorChromeDensity.ts";
|
||||
|
||||
describe("title status clearance", () => {
|
||||
test("pads the title until it clears the status cluster", () => {
|
||||
@@ -16,3 +17,25 @@ describe("title status clearance", () => {
|
||||
expect(nextTitleStatusClearance(480, 2000, 1000)).toBe(480);
|
||||
});
|
||||
});
|
||||
|
||||
describe("title row width", () => {
|
||||
test("keeps the title field ahead of notebook and tags on a compact desktop row", () => {
|
||||
expect(MEMO_EDITOR_METADATA_ROW_CLASS_NAME).toContain("min-w-0");
|
||||
expect(MEMO_EDITOR_METADATA_ROW_CLASS_NAME).toContain("shrink");
|
||||
expect(MEMO_EDITOR_METADATA_ROW_CLASS_NAME).not.toContain("shrink-0");
|
||||
expect(MEMO_EDITOR_METADATA_ROW_CLASS_NAME).not.toContain("max-w-[40%]");
|
||||
const leading = readFileSync(new URL("./MemoEditorTopRowLeading.tsx", import.meta.url), "utf8");
|
||||
expect(leading).toContain("basis-full");
|
||||
expect(leading).toContain("sm:min-w-[min(12rem,45%)]");
|
||||
expect(leading).toContain("sm:flex-1");
|
||||
const metadata = readFileSync(new URL("./MemoEditorMetadataRow.tsx", import.meta.url), "utf8");
|
||||
expect(metadata).toContain("min-w-0 max-w-[18rem] shrink");
|
||||
expect(metadata).toContain("[&>span]:truncate");
|
||||
expect(metadata).not.toContain("min-w-[9rem]");
|
||||
for (const file of ["EditorPane.tsx", "DiagramEditorPane.tsx", "InfographicEditorPane.tsx"]) {
|
||||
const source = readFileSync(new URL(`./${file}`, import.meta.url), "utf8");
|
||||
expect(source).toContain("rowClassName={MEMO_EDITOR_METADATA_ROW_CLASS_NAME}");
|
||||
expect(source).not.toContain('className="min-w-0 flex-1"');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,13 @@ export const MEMO_EDITOR_READING_GUTTER_CLASS_NAME =
|
||||
export const MEMO_EDITOR_TOP_ROW_CLASS_NAME =
|
||||
"relative flex min-h-12 items-center justify-between gap-2 border-b border-slate-100 py-2 sm:min-h-9 sm:py-0.5";
|
||||
|
||||
/**
|
||||
* Notebook and tags share the title row and shrink before the title field.
|
||||
* The title's own minimum width is what keeps typed text visible.
|
||||
*/
|
||||
export const MEMO_EDITOR_METADATA_ROW_CLASS_NAME =
|
||||
"min-w-0 max-w-full shrink overflow-hidden flex-nowrap";
|
||||
|
||||
export const MEMO_EDITOR_TOOLBAR_PADDING_CLASS_NAME =
|
||||
"px-3 py-2 sm:px-4 sm:py-0.5";
|
||||
|
||||
|
||||
@@ -60,13 +60,13 @@ export const MemoEditorMetadataRow = ({
|
||||
<span className="min-w-0 truncate">{currentNotebookLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||
</button>
|
||||
<div className="hidden min-w-[9rem] max-w-[18rem] sm:block">
|
||||
<div className="hidden min-w-0 max-w-[18rem] shrink overflow-hidden sm:block">
|
||||
<Select
|
||||
value={selectedNotebookId}
|
||||
disabled={disabled || notebookUpdatePending}
|
||||
onValueChange={onNotebookChange}
|
||||
>
|
||||
<SelectTrigger className="h-7 min-w-0 whitespace-nowrap border-transparent bg-transparent px-2 text-xs font-medium text-slate-600 hover:border-slate-200 hover:bg-slate-50 hover:text-slate-900">
|
||||
<SelectTrigger className="h-7 w-auto max-w-full min-w-0 overflow-hidden border-transparent bg-transparent px-2 text-xs font-medium text-slate-600 hover:border-slate-200 hover:bg-slate-50 hover:text-slate-900 [&_svg]:shrink-0 [&>span]:min-w-0 [&>span]:truncate">
|
||||
<SelectValue placeholder={t("editor.notebookPlaceholder")}>{currentNotebookLabel}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-60 rounded-md border border-slate-200 bg-card py-1 shadow-md">
|
||||
|
||||
@@ -46,7 +46,7 @@ export const MemoEditorTopRowLeading = ({
|
||||
mobileBackButton?: ReactNode;
|
||||
titleInput?: ReactNode;
|
||||
}) => (
|
||||
<div className={cn("flex min-w-0 flex-1 items-center gap-2 text-sm", className)}>
|
||||
<div className={cn("flex min-w-0 basis-full items-center gap-2 text-sm sm:min-w-[min(12rem,45%)] sm:flex-1", className)}>
|
||||
{mobileBackButton}
|
||||
{titleInput && <div className="min-w-0 flex-1">{titleInput}</div>}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const MemoTitleInput = ({
|
||||
{...props}
|
||||
aria-label={ariaLabel ?? placeholder}
|
||||
className={cn(
|
||||
"block w-full rounded-md border-0 bg-transparent px-2 py-1 text-sm font-semibold leading-tight text-slate-900 outline-none transition placeholder:text-slate-300 hover:bg-slate-100/60 focus-visible:bg-muted focus-visible:shadow-[inset_3px_0_0_rgb(var(--slate-950-rgb))] read-only:text-slate-600 read-only:hover:bg-transparent read-only:focus-visible:bg-transparent read-only:focus-visible:shadow-none sm:text-base",
|
||||
"block w-full min-w-0 rounded-md border-0 bg-transparent px-2 py-1 text-sm font-semibold leading-tight text-slate-900 outline-none transition placeholder:text-slate-300 hover:bg-slate-100/60 focus-visible:bg-muted focus-visible:shadow-[inset_3px_0_0_rgb(var(--slate-950-rgb))] read-only:text-slate-600 read-only:hover:bg-transparent read-only:focus-visible:bg-transparent read-only:focus-visible:shadow-none sm:text-base",
|
||||
className
|
||||
)}
|
||||
maxLength={160}
|
||||
|
||||
Reference in new issue
Block a user