one list of roles, named for what they are
Fonts and Font Sizes are the same four things, so they now read in the same order under the same names — Menu, Titles, Headings, Body — from one list. font_display is the largest statements and font_title is a section heading, which is the reverse of what the words suggest. The labels say what the reader sees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useDevice } from '../DeviceToggle';
|
import { useDevice } from '../DeviceToggle';
|
||||||
|
import { ROLES } from './roles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How big each role is, at the width being edited.
|
* How big each role is, at the width being edited.
|
||||||
@@ -20,14 +21,6 @@ export type FontSizes = Partial<Record<'title' | 'body' | 'sub' | 'display', Par
|
|||||||
|
|
||||||
const BREAKPOINT = { desktop: 'lg', tablet: 'md', mobile: 'base' } as const;
|
const BREAKPOINT = { desktop: 'lg', tablet: 'md', mobile: 'base' } as const;
|
||||||
|
|
||||||
/** The order they are read in: biggest first, which is how a type scale is judged. */
|
|
||||||
const ROLES = [
|
|
||||||
{ key: 'display' as const, label: 'Display' },
|
|
||||||
{ key: 'title' as const, label: 'Headings' },
|
|
||||||
{ key: 'body' as const, label: 'Body' },
|
|
||||||
{ key: 'sub' as const, label: 'Menu' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const inherited = (
|
const inherited = (
|
||||||
steps: Partial<Record<'base' | 'md' | 'lg', string>> | undefined,
|
steps: Partial<Record<'base' | 'md' | 'lg', string>> | undefined,
|
||||||
key: 'base' | 'md' | 'lg',
|
key: 'base' | 'md' | 'lg',
|
||||||
@@ -69,16 +62,16 @@ export const FontSizesTab = ({
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{ROLES.map((role) => {
|
{ROLES.map((role) => {
|
||||||
const current = sizes[role.key];
|
const current = sizes[role.size];
|
||||||
const own = current?.[key];
|
const own = current?.[key];
|
||||||
const from = inherited(current, key);
|
const from = inherited(current, key);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={role.key} className="flex flex-col gap-1">
|
<div key={role.size} className="flex flex-col gap-1">
|
||||||
<label className="text-xs">{role.label}</label>
|
<label className="text-xs">{role.label}</label>
|
||||||
<select
|
<select
|
||||||
value={own ?? ''}
|
value={own ?? ''}
|
||||||
onChange={(event) => set(role.key, event.currentTarget.value)}
|
onChange={(event) => set(role.size, event.currentTarget.value)}
|
||||||
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
|
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
|
||||||
>
|
>
|
||||||
{/* Mobile is the smallest, so there is nothing below it to inherit from — and nothing
|
{/* Mobile is the smallest, so there is nothing below it to inherit from — and nothing
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useEditorSlot } from '../useEditorSlot';
|
|||||||
import { type LayoutBand } from './PaddingTab';
|
import { type LayoutBand } from './PaddingTab';
|
||||||
import { SpacingTab } from './SpacingTab';
|
import { SpacingTab } from './SpacingTab';
|
||||||
import { FontSizesTab, type FontSizes } from './FontSizesTab';
|
import { FontSizesTab, type FontSizes } from './FontSizesTab';
|
||||||
|
import { ROLES } from './roles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The shell every page renders inside: the menu, the footer, the colours and the typefaces.
|
* The shell every page renders inside: the menu, the footer, the colours and the typefaces.
|
||||||
@@ -76,14 +77,6 @@ type Layout = {
|
|||||||
font_defaults: Record<string, string>;
|
font_defaults: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The four roles styles.css declares. A template asks for a role; this decides which face plays it. */
|
|
||||||
const ROLES = [
|
|
||||||
{ key: 'font_title' as const, label: 'Headings' },
|
|
||||||
{ key: 'font_body' as const, label: 'Body' },
|
|
||||||
{ key: 'font_sub' as const, label: 'Menu' },
|
|
||||||
{ key: 'font_display' as const, label: 'Display' },
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The draft, as the CSS custom properties the page reads.
|
* The draft, as the CSS custom properties the page reads.
|
||||||
*
|
*
|
||||||
@@ -362,18 +355,18 @@ export const LayoutPanel = () => {
|
|||||||
|
|
||||||
*/}
|
*/}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{ROLES.map(({ key, label }) => (
|
{ROLES.map(({ font, label }) => (
|
||||||
<div key={key} className="flex flex-col gap-1">
|
<div key={font} className="flex flex-col gap-1">
|
||||||
<label className="text-xs">{label}</label>
|
<label className="text-xs">{label}</label>
|
||||||
<select
|
<select
|
||||||
value={draft[key] ?? ''}
|
value={draft[font] ?? ''}
|
||||||
onChange={(event) => patch({ [key]: event.currentTarget.value || null } as Partial<Layout>)}
|
onChange={(event) => patch({ [font]: event.currentTarget.value || null } as Partial<Layout>)}
|
||||||
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
|
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
|
||||||
>
|
>
|
||||||
{/* Not "Default" — the face the stylesheet actually uses, named. Choosing it puts
|
{/* Not "Default" — the face the stylesheet actually uses, named. Choosing it puts
|
||||||
things back rather than setting something new, and now you can see what back is. */}
|
things back rather than setting something new, and now you can see what back is. */}
|
||||||
<option value="">
|
<option value="">
|
||||||
{draft.font_defaults[key] ? `${draft.font_defaults[key]} (site default)` : 'Default'}
|
{draft.font_defaults[font] ? `${draft.font_defaults[font]} (site default)` : 'Default'}
|
||||||
</option>
|
</option>
|
||||||
{draft.fonts.map((font) => (
|
{draft.fonts.map((font) => (
|
||||||
<option key={font} value={font}>
|
<option key={font} value={font}>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* The four type roles, named and ordered once.
|
||||||
|
*
|
||||||
|
* ── One list, two sections ──
|
||||||
|
*
|
||||||
|
* Fonts picks the FACE for a role and Font Sizes picks its SIZE. They are the same four things, so a
|
||||||
|
* reader moving between the two sections is entitled to find them in the same order under the same
|
||||||
|
* names — and that only holds if there is one list.
|
||||||
|
*
|
||||||
|
* ── The names are the site's, not the stylesheet's ──
|
||||||
|
*
|
||||||
|
* `font_display` is the largest statements and `font_title` is a section heading, which is the reverse
|
||||||
|
* of what those two words suggest. So the labels say Titles and Headings, in the order somebody reads
|
||||||
|
* a page: the menu above it, the title, the headings under that, the words.
|
||||||
|
*/
|
||||||
|
export const ROLES = [
|
||||||
|
{ size: 'sub' as const, font: 'font_sub' as const, label: 'Menu' },
|
||||||
|
{ size: 'display' as const, font: 'font_display' as const, label: 'Titles' },
|
||||||
|
{ size: 'title' as const, font: 'font_title' as const, label: 'Headings' },
|
||||||
|
{ size: 'body' as const, font: 'font_body' as const, label: 'Body' },
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user