Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions apps/desktop/src/components/settings/FontFamilyRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import { IconCheck, IconChevronDown, IconSearch } from "../icons";
* Global UI font picker (Settings → Basics → Appearance). Offers the
* system default, bundled open-licensed families, and installed system
* families; the selected stack is persisted as `AppSettings.fontFamily`
* and applied to `--font-sans` by App. Selecting System default persists an
* empty stack, which every consumer treats as the built-in token stack.
* and applied to `--font-sans` by App. Selecting the localized system-default
* option persists an empty stack, which every consumer treats as the built-in
* token stack. The closed trigger and search haystack use `settings.fontSystemDefault`
* so the English catalog label in `fonts.ts` never reaches the UI.
*/
export function FontFamilyRow({
settings,
Expand Down Expand Up @@ -153,17 +155,24 @@ export function FontFamilyRow({
const selectedValue = settings.fontFamily ?? "";
const selectedOption =
options.find((option) => option.value === selectedValue) ?? null;
const defaultLabel = t("settings.fontSystemDefault");
const selectedLabel =
selectedOption?.label ?? readableFontFamily(settings.fontFamily ?? "");
selectedOption?.group === "default" || selectedValue === ""
? defaultLabel
: selectedOption?.label ?? readableFontFamily(settings.fontFamily ?? "");
const selectedFamily = selectedOption?.family ?? readableFontFamily(selectedValue);

const filtered = useMemo(() => {
const needle = query.trim().toLowerCase();
if (!needle) return options;
return options.filter((option) =>
option.label.toLowerCase().includes(needle),
);
}, [options, query]);
return options.filter((option) => {
const localized =
option.group === "default" ? defaultLabel.toLowerCase() : option.label.toLowerCase();
return (
localized.includes(needle) || option.label.toLowerCase().includes(needle)
);
});
}, [defaultLabel, options, query]);

const groupLabel = useCallback((group: string) => {
if (group === "bundled") return t("settings.fontBundled");
Expand Down
21 changes: 13 additions & 8 deletions apps/desktop/src/styles/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -760,21 +760,26 @@
font-size: var(--text-sm-plus);
}

/* Global UI font picker (Appearance card). Pill trigger mirrors the
shared settings select; the menu portals to document.body as a fixed
floating layer so the settings card's overflow cannot clip it. */
/* Global UI font picker (Appearance card). Pill trigger mirrors language /
theme: hug the current label, capped by the settings control column. The
menu portals to document.body as a fixed floating layer so the settings
card's overflow cannot clip it. */
.settings-font {
position: relative;
width: max-content;
max-width: 100%;
min-width: 0;
}

.settings-font-trigger {
display: flex;
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 200px;
max-width: 280px;
width: max-content;
max-width: 100%;
min-width: 0;
height: 30px;
padding: 0 12px;
padding: 0 10px 0 12px;
border-radius: var(--radius-full);
background: var(--ds-raised);
box-shadow: var(--ds-raised-shadow);
Expand All @@ -788,7 +793,7 @@

.settings-font-trigger-label {
min-width: 0;
flex: 1;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Expand Down
16 changes: 16 additions & 0 deletions apps/desktop/test/settings-font-picker.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ test("selecting System default persists an empty stack so the override clears",
assert.doesNotMatch(rowSource, /fontFamily: undefined/);
});

test("the closed trigger and search use the localized system-default label", () => {
assert.match(rowSource, /const defaultLabel = t\("settings\.fontSystemDefault"\)/);
assert.match(
rowSource,
/selectedOption\?\.group === "default" \|\| selectedValue === ""/,
);
assert.match(rowSource, /option\.group === "default" \? defaultLabel\.toLowerCase\(\)/);
});

test("the font trigger hugs the current label like language and theme", () => {
assert.match(styles, /\.settings-font\s*\{[^}]*width:\s*max-content;/s);
assert.match(styles, /\.settings-font-trigger\s*\{[^}]*width:\s*max-content;/s);
assert.match(styles, /\.settings-font-trigger-label\s*\{[^}]*flex:\s*0 1 auto;/s);
assert.doesNotMatch(styles, /\.settings-font-trigger\s*\{[^}]*min-width:\s*200px;/s);
});

test("font list windows the rows so only the visible slice is in the DOM", () => {
assert.match(rowSource, /visibleRowRange\(layout, scrollTop/);
assert.match(rowSource, /layout\.rows\.slice\(start, end\)/);
Expand Down