@lotics/ui 43.5.0 → 43.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/docs/composition.md +15 -0
- package/package.json +1 -1
- package/src/markdown.css +1 -1
- package/src/metric.tsx +12 -6
- package/src/table.tsx +13 -3
- package/src/text.css +7 -7
- package/src/text_utils.ts +10 -12
- package/src/type_ramp.ts +33 -20
package/AGENTS.md
CHANGED
|
@@ -17,7 +17,7 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
|
|
|
17
17
|
| [docs/catalog.md](./docs/catalog.md) | **The complete inventory** — Reach-by-role (each data role → the ONE canonical component) + every `@lotics/ui/<module>` entry point (incl. `@lotics/ui/vite`'s `loticsOptimizeDeps` + `loticsResolve()` — the pre-bundle list and the whole `resolve` block a custom-code app's `vite.config.ts` imports rather than hand-carries, dev-link included). Read before building any screen; reuse first. |
|
|
18
18
|
| [docs/data_entry.md](./docs/data_entry.md) | **a picker over a select field keeps the field's colours** (`optionPicker` — the hand-rolled `map` to `{value,label}` drops `color`, so a value reads coloured in a register and grey in the editor); Which editing pattern for which job — inline edit, fieldset forms, browser-autofill suppression (search controls only), find-or-create (`Combobox`), line items, handoffs, phased records, billing, tags, dispositions, attachments (`InlineFiles` for a record ROW — list + one CTA, `multiple` decides add-vs-replace; the `FilesEditor` COMPOUND for a whole section — root owns selection/gallery/confirm, you compose the bar, a HOST verb reads `useFilesEditorSelection` — plus the three-way file INTAKE: CTA + `FileDropTarget` + `usePasteFiles`), stage gates, whether a multiline value keeps its fixed reserve or `autoGrow`s (who decides the length — the field, or whoever is typing), the commit-on-blur vs action-press ordering law (the kit gates the press — `pending_commits`). |
|
|
19
19
|
| [docs/ai_patterns.md](./docs/ai_patterns.md) | AI acts, the human stays in charge — composer, live run feed (`AgentRun`), the one law's split — it turns on WHO supplied the values (machine → a gate: a diff when something is being replaced, a full editable preview when records are being created from a document; human-typed → save-direct + the `ResultHeader` receipt), findings, provenance, confidence; **after the run** — a stored record that fills up from several writers (a person, a chat agent, an extraction, an automation) and remembers none of them: an unwritten value must not render like a written one, a machine's prose and a person's must not share a treatment, model markdown goes in `variant="embedded"`, and the read path must project every field the write paths set; the UI half of the SDK's [ai doc](../app-sdk/docs/ai.md), the whole run in a dialog (`AgentRunScope`/`AgentRunPane`/`AgentRunActions` — a parked question REPLACES the feed, actions in the footer, **Stop** while streaming), **stopping** (`cancel` stops the run, `abort` only stops listening — so closing a dialog must `cancel` or it keeps billing); **review surfaces compose from atoms** — `DiffValue` (a changed value, droppable in any cell/row/total), `DiffMark` (what happened to the row — ONE circular disc, every surface), `useChangeSet` (accept/reject/undo bookkeeping, no layout) — see [MIGRATION.md](./MIGRATION.md) for the `ChangeReview` family they replace |
|
|
20
|
-
| [docs/composition.md](./docs/composition.md) | The design-language contract — canvas + content column, heading altitude (incl. eyebrow vs group lead — a label is one or the other), banded cards, register vs inset rows (incl. the register laws a row centres its cells by: every cell a FIXED height, a pressable cell on the shared hover token, a column sized by what it carries), the button ladder and **what underlined text may mean** (it GOES somewhere or REVEALS something — never mutates; blue leaves the surface, muted stays on it, and the one in-prose disclosure exception is scoped there), master-detail `Drawer` on a LIST screen vs a child collection's row EXPANDING inside a record, view controls, RECORD EXTENT (one page, sections scrolled to and never routed to), **the register's rhythm** (no hairline between rows — their own height and the hover wash separate them; the register's one line is the band capping the columns, because that is the break whitespace cannot state. Density is `TableRow`'s `minHeight`, never a mode), **a register that TRIAGES** (`TableGroup` — titled bands ordered by what needs the reader now, grouped by what implies a different ACTION rather than by a category already in a column), **the register's own craft** (a column header treated as CHROME a step lighter than row metadata, sentence case in the table header too, and a supporting line one RUNG BELOW the line it supports rather than a fixed size), **identity marks** (a mark that looks the same on every row carries nothing — `Avatar` derives its hue from the name and takes circle/square for person vs organization; the palette is ordered by contrast, not spectrum, and never paints a person red or amber), **where the accent goes** ("which of these" — a selected row, an active filter; never DATA and never the focus ring, while a page-scale position marker like an active TAB takes `primary`, the ink the kit already spends on "this is on"; and selection must not share a wash with hover), **theming is an app-level ESCAPE HATCH, not a product surface** (`applyLoticsTheme({ … })` once at boot, before `mount`, writing the `--lotics-*` roles on the document root; nothing in the platform writes a theme, and `apps.theme.color` is the launcher icon only — the five `var()` roles plus the three font rungs ARE the whole mechanism, and wrapping a component cannot replace them because RN-Web writes `fontFamily` per element and background/border do not inherit), color discipline, **typography** (the ramp is DATA in `type_ramp`, one table both platforms derive from; leading and tracking are baked per rung and a hand-set `letterSpacing` is always a second copy of the curve), whitespace, and how to TEST an overlay component (a `Popover`-backed surface never mounts under jsdom). |
|
|
20
|
+
| [docs/composition.md](./docs/composition.md) | The design-language contract — canvas + content column, heading altitude (incl. eyebrow vs group lead — a label is one or the other), banded cards, register vs inset rows (incl. the register laws a row centres its cells by: every cell a FIXED height, a pressable cell on the shared hover token, a column sized by what it carries), the button ladder and **what underlined text may mean** (it GOES somewhere or REVEALS something — never mutates; blue leaves the surface, muted stays on it, and the one in-prose disclosure exception is scoped there), master-detail `Drawer` on a LIST screen vs a child collection's row EXPANDING inside a record, view controls, RECORD EXTENT (one page, sections scrolled to and never routed to), **the register's rhythm** (no hairline between rows — their own height and the hover wash separate them; the register's one line is the band capping the columns, because that is the break whitespace cannot state. Density is `TableRow`'s `minHeight`, never a mode), **a register that TRIAGES** (`TableGroup` — titled bands ordered by what needs the reader now, grouped by what implies a different ACTION rather than by a category already in a column), **the register's own craft** (a column header treated as CHROME a step lighter than row metadata, sentence case in the table header too, and a supporting line one RUNG BELOW the line it supports rather than a fixed size — plus the half that rule was missing: every pair on ONE ROW starts at the SAME rung, a register's subject is `sm` over `xs` with values `sm`, and the subject leads by WEIGHT and its identity mark rather than by size), **identity marks** (a mark that looks the same on every row carries nothing — `Avatar` derives its hue from the name and takes circle/square for person vs organization; the palette is ordered by contrast, not spectrum, and never paints a person red or amber), **where the accent goes** ("which of these" — a selected row, an active filter; never DATA and never the focus ring, while a page-scale position marker like an active TAB takes `primary`, the ink the kit already spends on "this is on"; and selection must not share a wash with hover), **theming is an app-level ESCAPE HATCH, not a product surface** (`applyLoticsTheme({ … })` once at boot, before `mount`, writing the `--lotics-*` roles on the document root; nothing in the platform writes a theme, and `apps.theme.color` is the launcher icon only — the five `var()` roles plus the three font rungs ARE the whole mechanism, and wrapping a component cannot replace them because RN-Web writes `fontFamily` per element and background/border do not inherit), color discipline, **typography** (the ramp is DATA in `type_ramp`, one table both platforms derive from; leading and tracking are baked per rung and a hand-set `letterSpacing` is always a second copy of the curve), whitespace, and how to TEST an overlay component (a `Popover`-backed surface never mounts under jsdom). |
|
|
21
21
|
| [docs/reviewing.md](./docs/reviewing.md) | **Reviewing a screen you built** — the other docs say what good looks like; this one says how to find out whether you achieved it, because "it looks fine" is the same claim as "it should work" made with the same evidence. Measure, never eyeball: render it, extract computed values, let the table show the defect. Two GATES that outrank every treatment question (**SUBTRACT** — what question does this element answer, and count FACTS not elements; **RIGHT CONTROL** — name the species before treating it), then 10 probes each naming what to COLLECT and the SIGNATURE in the numbers: type inventory (range, singletons, the label/value shrink war), ground+border+radius across siblings, what each RULE separates, gap RATIOS, the state diff (paint may change, geometry may not), alignment drift (incl. a centred child that moves while its container measures identical), the surface walk (open it, press it, read the settled string), the DATA probe (what is really in the table — incl. grepping rendered text for internal ids), the ABSENCE pass that catches "bland", and microcopy. Plus the extraction snippets, what measurement cannot see, and how to act on a finding (fix the CLASS, and never resolve one with "the docs say X"). |
|
|
22
22
|
| [docs/testing.md](./docs/testing.md) | Driving the kit in a browser — the three anatomies where the a11y tree says one thing and a driver must do another: a `PressDoor` row whose named button always intercepts pointer events (by design), portalled overlays that render at the top of the DOM, and custom pointer drag that `dragTo` cannot move. |
|
|
23
23
|
| [docs/templates.md](./docs/templates.md) | The map of `examples/tpl_*.tsx` — what shape each template solves and which to start from (copy + adapt, never import) — plus the record-surface composition rules (pipeline order, static shape, decision budget) and the ACTIVITY shape — a communications feed where the row's label is the GIST and the body varies by medium, one anatomy rather than a row type per kind. |
|
package/docs/composition.md
CHANGED
|
@@ -1165,6 +1165,21 @@ wider pair then reads as though its second line shrank rather than stepped. Pick
|
|
|
1165
1165
|
relative to the primary and the pair scales together. The probe: collect every primary/supporting
|
|
1166
1166
|
pair on the screen and divide. A spread in the RATIOS is the finding, not a spread in the sizes.
|
|
1167
1167
|
|
|
1168
|
+
**And every pair ON ONE ROW sits at the SAME rungs — a register's row subject is `sm`, its
|
|
1169
|
+
supporting line `xs`, and a value in any other column is `sm` too.** "One rung below" is only half
|
|
1170
|
+
a rule: it constrains the STEP and says nothing about where the pair starts, so a subject placed a
|
|
1171
|
+
rung high stays compliant while dragging its whole column above the rest. Measured on a register
|
|
1172
|
+
that read as disproportionate: the identity column ran 16/14 while the column beside it ran 14/12
|
|
1173
|
+
— both correctly "one rung apart", one whole rung out of step with each other, and the first column
|
|
1174
|
+
shouted while everything else receded.
|
|
1175
|
+
|
|
1176
|
+
**The subject leads by WEIGHT and by its identity mark, not by size.** `medium` against `regular`,
|
|
1177
|
+
plus the avatar or tile that answers "which row is this", is enough — and it is what
|
|
1178
|
+
`tpl_item_list` does. Money is the case that most often argues for a size step and should not get
|
|
1179
|
+
one: tabular figures and right alignment already mark it, and a lone larger value on a row reads as
|
|
1180
|
+
a mistake rather than as emphasis. One larger thing among peers is not emphasis; it is the thing
|
|
1181
|
+
that looks broken.
|
|
1182
|
+
|
|
1168
1183
|
**A CONTROL riding a text line must not resize that line.** A control is taller than the text it
|
|
1169
1184
|
sits beside — a 24px `CopyButton` on an 18px `xs` line — so a flex row containing both is sized by
|
|
1170
1185
|
the CONTROL, and `alignItems: "center"` then spends the difference above the text. The line's text
|
package/package.json
CHANGED
package/src/markdown.css
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
carried before, written as an em so it scales with the rung instead of being
|
|
9
9
|
a flat pixel value that drifts at every other size — and now ONE number
|
|
10
10
|
shared with `Text` and the inputs rather than markdown's private one. */
|
|
11
|
-
letter-spacing: -0.
|
|
11
|
+
letter-spacing: -0.006em;
|
|
12
12
|
color: rgba(24, 24, 27, 1);
|
|
13
13
|
}
|
|
14
14
|
|
package/src/metric.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { View, StyleSheet, type TextStyle } from "react-native";
|
|
2
2
|
import { Text } from "./text";
|
|
3
3
|
import { colors } from "./colors";
|
|
4
|
+
import { getTextColor } from "./text_utils";
|
|
4
5
|
import { formatCompactNumber, formatMoney } from "./format_money";
|
|
5
6
|
import { useMemo } from "react";
|
|
6
7
|
|
|
@@ -46,11 +47,6 @@ const TREND_UP = "↑";
|
|
|
46
47
|
const TREND_DOWN = "↓";
|
|
47
48
|
const TREND_FLAT = "→";
|
|
48
49
|
|
|
49
|
-
const TONE_COLOR: Record<Exclude<MetricTone, "default">, string> = {
|
|
50
|
-
warning: colors.amber[600],
|
|
51
|
-
danger: colors.red[600],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
50
|
export function Metric(props: MetricProps) {
|
|
55
51
|
const {
|
|
56
52
|
value,
|
|
@@ -107,7 +103,17 @@ export function Metric(props: MetricProps) {
|
|
|
107
103
|
// none. (Tabular figures do not need it reset either — letter-spacing
|
|
108
104
|
// adds a constant to every advance, so a tabular column stays aligned.)
|
|
109
105
|
{ fontVariantNumeric: "tabular-nums" } as TextStyle,
|
|
110
|
-
|
|
106
|
+
// THE VALENCE INK COMES FROM `Text`, not a second map here. This kept
|
|
107
|
+
// its own — amber-600 and red-600 — while `getTextColor` answers
|
|
108
|
+
// amber-700 and red-900 for the same two words, so one `tone="warning"`
|
|
109
|
+
// rendered two different ambers depending on whether the number was a
|
|
110
|
+
// `Metric` or the sentence beside it.
|
|
111
|
+
//
|
|
112
|
+
// And the divergence was not cosmetic: amber-600 measures 3.19:1 on
|
|
113
|
+
// white, under the 4.5:1 AA floor for normal text, which is exactly
|
|
114
|
+
// what `getTextColor`'s own comment says the 700 rung exists to clear.
|
|
115
|
+
// A summary figure is the smallest, most glanced-at text on a register.
|
|
116
|
+
tone === "default" ? undefined : { color: getTextColor(tone) },
|
|
111
117
|
]}
|
|
112
118
|
>
|
|
113
119
|
{displayValue}
|
package/src/table.tsx
CHANGED
|
@@ -379,9 +379,19 @@ export type TableRowProps = {
|
|
|
379
379
|
* before `trailing`; stacked mode: the content's bottom line, right-aligned — an action
|
|
380
380
|
* reads at content scale, not as row chrome. Reserve Table `trailing` for it. */
|
|
381
381
|
action?: ReactNode;
|
|
382
|
-
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
382
|
+
/**
|
|
383
|
+
* Min row height — a FLOOR, not a fixed height: a taller cell grows the row,
|
|
384
|
+
* so a two-line identity cell or a figure with a bar under it needs no
|
|
385
|
+
* override to fit. (Stacked rows size to their content and ignore this.)
|
|
386
|
+
*
|
|
387
|
+
* Defaults to `ROW_HEIGHT` (72), the register's rhythm. **Reach for this only
|
|
388
|
+
* when a surface genuinely needs more DENSITY than the beat** — and know that
|
|
389
|
+
* a number written against an older default silently becomes a shrink: an app
|
|
390
|
+
* carrying `minHeight={64}` from when the default was 52 now pulls that one
|
|
391
|
+
* register below every other on the screen, and it reads as cramped rather
|
|
392
|
+
* than as dense. If the reason you overrode it was "the content did not fit",
|
|
393
|
+
* delete the override.
|
|
394
|
+
*/
|
|
385
395
|
minHeight?: number;
|
|
386
396
|
/**
|
|
387
397
|
* The row's detail, revealed BENEATH it when `expanded` — the alternative to
|
package/src/text.css
CHANGED
|
@@ -46,39 +46,39 @@
|
|
|
46
46
|
[data-text-size="xs"] {
|
|
47
47
|
font-size: 12px;
|
|
48
48
|
line-height: 18px;
|
|
49
|
-
letter-spacing:
|
|
49
|
+
letter-spacing: 0em;
|
|
50
50
|
}
|
|
51
51
|
[data-text-size="sm"] {
|
|
52
52
|
font-size: 14px;
|
|
53
53
|
line-height: 24px;
|
|
54
|
-
letter-spacing: -0.
|
|
54
|
+
letter-spacing: -0.006em;
|
|
55
55
|
}
|
|
56
56
|
[data-text-size="md"] {
|
|
57
57
|
font-size: 16px;
|
|
58
58
|
line-height: 26px;
|
|
59
|
-
letter-spacing: -0.
|
|
59
|
+
letter-spacing: -0.011em;
|
|
60
60
|
}
|
|
61
61
|
[data-text-size="lg"] {
|
|
62
62
|
font-size: 18px;
|
|
63
63
|
line-height: 28px;
|
|
64
|
-
letter-spacing: -0.
|
|
64
|
+
letter-spacing: -0.014em;
|
|
65
65
|
}
|
|
66
66
|
/* Display ramp — the heading ladder: xl = `##` (section), xxl = `#`
|
|
67
67
|
(page/record title), xxxl = hero numbers. */
|
|
68
68
|
[data-text-size="xl"] {
|
|
69
69
|
font-size: 22px;
|
|
70
70
|
line-height: 28px;
|
|
71
|
-
letter-spacing: -0.
|
|
71
|
+
letter-spacing: -0.018em;
|
|
72
72
|
}
|
|
73
73
|
[data-text-size="xxl"] {
|
|
74
74
|
font-size: 28px;
|
|
75
75
|
line-height: 34px;
|
|
76
|
-
letter-spacing: -0.
|
|
76
|
+
letter-spacing: -0.021em;
|
|
77
77
|
}
|
|
78
78
|
[data-text-size="xxxl"] {
|
|
79
79
|
font-size: 32px;
|
|
80
80
|
line-height: 38px;
|
|
81
|
-
letter-spacing: -0.
|
|
81
|
+
letter-spacing: -0.022em;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/* Refer to `use_screen_size` for breakpoints */
|
package/src/text_utils.ts
CHANGED
|
@@ -53,24 +53,22 @@ export function getTextColor(color?: TextColor): string {
|
|
|
53
53
|
export { fontFamilyRegular, fontFamilyMedium, fontFamilySemiBold } from "./font_family";
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Tracking for input text — the
|
|
57
|
-
*
|
|
58
|
-
* at 14px; the ramp was anchored on this value rather than the other way round.
|
|
56
|
+
* Tracking for input text — the `sm` rung of Inter's curve (`TYPE_TRACKING.sm`,
|
|
57
|
+
* -0.006em) as px, because React Native's `letterSpacing` has no em unit.
|
|
59
58
|
*
|
|
60
|
-
* Six inputs each hard-coded
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* body rhythm on both axes.
|
|
59
|
+
* Six inputs each hard-coded their own value before this; there is one of it now,
|
|
60
|
+
* next to the line height, because they are the same contract — an input is
|
|
61
|
+
* running text and sits at the body rhythm on both axes.
|
|
64
62
|
*
|
|
65
63
|
* NOT derived, deliberately, unlike the line heights beside it. An input is 14px
|
|
66
64
|
* on desktop and 16px on mobile, so no single px value is the em at both sizes —
|
|
67
65
|
* computing one from a rung would pick a size and call the result exact. Written
|
|
68
|
-
* out, it stays honestly an approximation: right at 14px, a hair light at 16px
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
66
|
+
* out, it stays honestly an approximation: right at 14px, a hair light at 16px.
|
|
67
|
+
* `type_ramp.test.ts` pins it to within a hundredth of the `sm` rung so it cannot
|
|
68
|
+
* drift away from the curve it belongs to; the exact form (an `em` string through
|
|
69
|
+
* RN's style types) is not portable.
|
|
72
70
|
*/
|
|
73
|
-
export const INPUT_LETTER_SPACING = -0.
|
|
71
|
+
export const INPUT_LETTER_SPACING = -0.08;
|
|
74
72
|
|
|
75
73
|
/**
|
|
76
74
|
* Line height for mobile web — the input is 16px there (to defeat iOS-Safari
|
package/src/type_ramp.ts
CHANGED
|
@@ -24,18 +24,9 @@
|
|
|
24
24
|
* every label, value and control gained about 4px of line box. Density was
|
|
25
25
|
* traded for rhythm.
|
|
26
26
|
*
|
|
27
|
-
* TRACKING:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* product's own, arrived at by reading it in Vietnamese every day, and the rest
|
|
31
|
-
* of the ramp is derived FROM it rather than imported from another design
|
|
32
|
-
* system. Inter is drawn generously at default tracking, so untracked body reads
|
|
33
|
-
* loose beside it, and what looks right at 14px looks slack at 32px.
|
|
34
|
-
*
|
|
35
|
-
* `xs` is the one rung held back (-0.02em rather than the full body value). 12px
|
|
36
|
-
* is where Vietnamese stacked diacritics (ự, ộ, ế, ệ) and Inter's `1`/`l`/`i`
|
|
37
|
-
* come closest to colliding, and tightening is the direction that makes both
|
|
38
|
-
* worse.
|
|
27
|
+
* TRACKING: Inter's own dynamic-metrics curve, evaluated per rung — see
|
|
28
|
+
* `TYPE_TRACKING`. Near zero at 12px and steepening as the type grows, which is
|
|
29
|
+
* the shape the face is drawn for. It is derived, not chosen.
|
|
39
30
|
*
|
|
40
31
|
* Tracking is stated ONCE, in em, and is not repeated per breakpoint: an em
|
|
41
32
|
* scales with the font size, so the same value is correct at both. Only the
|
|
@@ -75,15 +66,37 @@ export const TYPE_RAMP_DESKTOP = {
|
|
|
75
66
|
xxxl: { size: 48, leading: 52 },
|
|
76
67
|
} as const satisfies Record<keyof typeof TYPE_RAMP_MOBILE, TypeRung>;
|
|
77
68
|
|
|
78
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Tracking per rung, in em — INTER'S OWN dynamic metrics, not a house curve.
|
|
71
|
+
*
|
|
72
|
+
* Inter publishes the tracking it is drawn for as a function of size:
|
|
73
|
+
*
|
|
74
|
+
* tracking(z) = a + b · e^(c · z) a = -0.0223, b = 0.185, c = -0.1745
|
|
75
|
+
*
|
|
76
|
+
* evaluated at each rung's base size and rounded to a thousandth. The values
|
|
77
|
+
* below ARE that function; they are not tuned by eye and should not be. If a
|
|
78
|
+
* screen reads too tight or too loose, the answer is a size or a leading change,
|
|
79
|
+
* not a nudge here — the typeface's designer has already answered this question
|
|
80
|
+
* for this typeface, and second-guessing it per-rung is how a ramp ends up with
|
|
81
|
+
* seven independent opinions.
|
|
82
|
+
*
|
|
83
|
+
* The shape: near zero at 12px, steepening as the type grows, asymptotic to the
|
|
84
|
+
* -0.0223 the face is drawn to at display sizes. `xs` lands at 0.000, which is
|
|
85
|
+
* also the right answer for Vietnamese stacked diacritics (ự, ộ, ế, ệ) and for
|
|
86
|
+
* Inter's `1`/`l`/`i` at 12px — the sizes where tightening does the most damage
|
|
87
|
+
* are exactly the ones the curve leaves alone.
|
|
88
|
+
*
|
|
89
|
+
* One value per rung, not per breakpoint: an em already scales with the font
|
|
90
|
+
* size, and the curve's difference across a rung's two sizes is under 0.002em.
|
|
91
|
+
*/
|
|
79
92
|
export const TYPE_TRACKING = {
|
|
80
|
-
xs:
|
|
81
|
-
sm: -0.
|
|
82
|
-
md: -0.
|
|
83
|
-
lg: -0.
|
|
84
|
-
xl: -0.
|
|
85
|
-
xxl: -0.
|
|
86
|
-
xxxl: -0.
|
|
93
|
+
xs: 0.000,
|
|
94
|
+
sm: -0.006,
|
|
95
|
+
md: -0.011,
|
|
96
|
+
lg: -0.014,
|
|
97
|
+
xl: -0.018,
|
|
98
|
+
xxl: -0.021,
|
|
99
|
+
xxxl: -0.022,
|
|
87
100
|
} as const satisfies Record<keyof typeof TYPE_RAMP_MOBILE, number>;
|
|
88
101
|
|
|
89
102
|
/** The rungs, in ladder order. `TextSize` in `text.tsx` is this set. */
|