diff --git a/packages/studio/src/components/editor/PropertyPanel.tsx b/packages/studio/src/components/editor/PropertyPanel.tsx index 71cce70a0..1ff52810a 100644 --- a/packages/studio/src/components/editor/PropertyPanel.tsx +++ b/packages/studio/src/components/editor/PropertyPanel.tsx @@ -769,8 +769,8 @@ function fontSourceRank(source: FontSource): number { if (source === "Current") return 0; if (source === "Document") return 1; if (source === "Imported") return 2; - if (source === "Local") return 3; - if (source === "Google") return 4; + if (source === "Google") return 3; + if (source === "Local") return 4; return 5; } @@ -841,16 +841,44 @@ function loadImportedFontStylesheet(asset: ImportedFontAsset): void { document.head.appendChild(style); } +const ALL_WEIGHTS = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]; +const WEIGHT_LABELS: Record = { + "100": "100 · Thin", + "200": "200 · Extra Light", + "300": "300 · Light", + "400": "400 · Regular", + "500": "500 · Medium", + "600": "600 · Semi Bold", + "700": "700 · Bold", + "800": "800 · Extra Bold", + "900": "900 · Black", +}; + +function detectAvailableWeights(fontFamily: string): string[] { + const fonts = document.fonts; + if (!fonts) return ALL_WEIGHTS; + const family = fontFamily.split(",")[0]?.trim().replace(/['"]/g, ""); + if (!family) return ALL_WEIGHTS; + const available: string[] = []; + for (const w of ALL_WEIGHTS) { + if (fonts.check(`${w} 16px "${family}"`)) available.push(w); + } + return available.length > 0 ? available : ALL_WEIGHTS; +} + function FontWeightField({ value, disabled, + fontFamily, onCommit, }: { value: string; disabled?: boolean; + fontFamily?: string; onCommit: (nextValue: string) => void; }) { - const options = ["300", "400", "500", "600", "700", "800"]; + const options = fontFamily ? detectAvailableWeights(fontFamily) : ALL_WEIGHTS; + const displayOptions = value && !options.includes(value) ? [value, ...options] : options; return (
@@ -861,9 +889,9 @@ function FontWeightField({ onChange={(e) => onCommit(e.target.value)} className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-none disabled:cursor-not-allowed disabled:text-neutral-600" > - {options.map((option) => ( + {displayOptions.map((option) => ( ))} @@ -1022,13 +1050,20 @@ function FontFamilyField({ const options = useMemo(() => { const documentFonts = collectDocumentFontFamilies(); + const googleSet = new Set(googleFonts.map((f) => f.toLowerCase())); + const taggedLocalFonts = localFonts.map( + (family): FontOption => ({ + family, + source: googleSet.has(family.toLowerCase()) ? "Google" : "Local", + }), + ); return sortFontOptions( uniqueFontOptions([ { family: currentFamily, source: "Current" }, ...documentFonts.map((family): FontOption => ({ family, source: "Document" })), ...projectFontAssets, - ...localFonts.map((family): FontOption => ({ family, source: "Local" })), ...googleFonts.map((family): FontOption => ({ family, source: "Google" })), + ...taggedLocalFonts, ...DEFAULT_FONT_FAMILIES.map((family): FontOption => ({ family, source: "System" })), ]), ); @@ -1036,7 +1071,21 @@ function FontFamilyField({ const filteredOptions = useMemo(() => { const matches = options.filter((option) => fontMatchesQuery(option.family, query)); - return matches.slice(0, query.trim() ? 120 : 160); + if (query.trim()) return matches.slice(0, 200); + const bySource = new Map(); + for (const m of matches) { + const list = bySource.get(m.source) ?? []; + list.push(m); + bySource.set(m.source, list); + } + const result: FontOption[] = []; + for (const s of ["Current", "Document", "Imported"]) { + result.push(...(bySource.get(s) ?? [])); + } + result.push(...(bySource.get("Google") ?? []).slice(0, 100)); + result.push(...(bySource.get("Local") ?? []).slice(0, 80)); + result.push(...(bySource.get("System") ?? [])); + return result; }, [options, query]); const importLocalFont = async (family: string): Promise => { @@ -1226,21 +1275,36 @@ function AdvancedTextControls({ return (
- + options={["normal", "1", "1.1", "1.2", "1.25", "1.3", "1.4", "1.5", "1.6", "1.75", "2"]} + onChange={(next) => onCommit("line-height", normalizeTextMetricValue("line-height", next)) } /> - + options={[ + "0px", + "-0.05em", + "-0.04em", + "-0.03em", + "-0.02em", + "-0.01em", + "0em", + "0.01em", + "0.02em", + "0.03em", + "0.05em", + "0.1em", + "0.15em", + "0.2em", + ]} + onChange={(next) => onCommit("letter-spacing", normalizeTextMetricValue("letter-spacing", next)) } /> @@ -1266,7 +1330,7 @@ function AdvancedTextControls({ value={getTextStyleValue(field, inheritedStyles, "font-style", "normal")} disabled={disabled} onChange={(next) => onCommit("font-style", next)} - options={["normal", "italic", "oblique"]} + options={["normal", "italic"]} />
); @@ -2402,6 +2466,9 @@ export const PropertyPanel = memo(function PropertyPanel({ styles["font-weight"] || "400" } + fontFamily={ + activeField.computedStyles["font-family"] || styles["font-family"] + } disabled={false} onCommit={(next) => onSetTextFieldStyle(activeField.key, "font-weight", next) @@ -2530,6 +2597,7 @@ export const PropertyPanel = memo(function PropertyPanel({ /> onSetTextFieldStyle(activeField.key, "font-weight", next)