@uniflowed/stylex 0.0.0-alpha.9 → 0.2.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/index.js CHANGED
@@ -21,7 +21,7 @@
21
21
  //
22
22
  // 1. **A token set that already exists.** `@uniflowed/stylex/tokens.stylex.js`
23
23
  // is a real `stylex.defineVars` module — colour roles, a type scale,
24
- // spacing, radii, elevation and motion — so a project has a coherent palette
24
+ // spacing, radii and motion — so a project has a coherent palette
25
25
  // without authoring one, and gets it as `:root` custom properties the build
26
26
  // inlined rather than as anything computed in a browser.
27
27
  // 2. **A base layer over those tokens.** `@uniflowed/stylex/preset` is
@@ -39,21 +39,52 @@
39
39
  // configuration flag. The preset is reached by importing it, so a project that
40
40
  // does not import it pays nothing, and one that imports half of it keeps half.
41
41
  //
42
+ // # The half of a component catalogue that lives here
43
+ //
44
+ // "Not a component library" is a rule about what this package exports, and it
45
+ // was read for a while as a rule about what it is responsible for. It is not.
46
+ // About twenty of the catalogue `@uniflowed/ui` is measured against — Badge,
47
+ // Card, Button, Input, Textarea, Label, Aspect Ratio and the rest — have no
48
+ // behaviour at all: no role, no state machine, no keyboard map. A headless
49
+ // package cannot ship them, because with the styles taken out a `Badge` is a
50
+ // `<span>`. **They are this module's half of the split, and the answer is a
51
+ // style function rather than a component** (ubugeeei-prod/uf#298).
52
+ //
53
+ // `buttonStyles({ tone, size })`, `cardStyles()`, `textStyles({ size, tone })`
54
+ // and `fieldStyles()` are already that answer for four of them, and the shape
55
+ // is the point: a caller spreads `{ className }` onto their own `<button>`, so
56
+ // they keep `type="submit"`, `formAction`, the ref and every attribute a
57
+ // wrapper would have had to remember to forward. Nothing here appears in
58
+ // `@uniflowed/ui`'s types and nothing here renders an element — the rule holds
59
+ // exactly as written; what changed is that the gap is now assigned rather than
60
+ // left between the two packages.
61
+ //
62
+ // `crates/uf_lib/src/ui.rs` records the assignment component by component, and
63
+ // `cargo test -p uf_lib` holds it to this file in both directions: an entry
64
+ // there may not name a style `./preset.js` does not export, and a style
65
+ // exported here must be named by some entry. A `badgeStyles` is the way to
66
+ // close the Badge gap; a `<Badge>` in the other package is not.
67
+ //
42
68
  // # Where the token values come from
43
69
  //
44
- // `@uniflowed/brand` owns uf's visual identity — the palette, the type,
45
- // spacing and radius scales — and it stays there. It cannot own the token
46
- // module: a StyleX token's name is computed by the compiler from the binding
47
- // and key it was declared under, `defineVars` accepts only literals, and
48
- // brand's `--uf-*` names are hand-written for a different consumer. So brand
49
- // holds the identity values and `./tokens.stylex.js` is their StyleX-shaped
50
- // projection into semantic roles — `accent`, `ink`, `canvas` — which is the
51
- // layer a design system needs and an identity does not have.
70
+ // `@uniflowed/brand` owns uf's visual identity — the logo, its spectrum, the
71
+ // type and spacing scales — and it stays there. The interface borrows brand's
72
+ // type and spacing, and chooses its own colours and corners: a UI dressed in
73
+ // the logo's cyan-to-magenta spectrum and round corners looks generated, so
74
+ // `./tokens.stylex.js` holds a neutral-first palette of its own
75
+ // (`docs/ui-visual-language.md` says why). Brand could not own the token
76
+ // module anyway: a StyleX token's name is computed by the compiler from the
77
+ // binding and key it was declared under, and `defineVars` accepts only
78
+ // literals. So `./tokens.stylex.js` is where the semantic roles — `accent`,
79
+ // `ink`, `canvas` — get their values, which is the layer a design system needs
80
+ // and an identity does not have.
52
81
  //
53
82
  // # Readiness
54
83
  //
55
84
  // **Implemented.** `props`, `create`, `defineVars`, `createTheme`, pseudo-class
56
- // and pseudo-element conditions, at-rule conditions (`@media`, `@supports`),
85
+ // and pseudo-element conditions, the state a headless part announces
86
+ // (`:is([aria-selected=true])` — `crates/uf_stylex/src/condition.rs` says why
87
+ // that shape and no wider), at-rule conditions (`@media`, `@supports`),
57
88
  // shorthand-versus-longhand ordering, unit inference, the preset tokens, the
58
89
  // base layer, and the shipped themes. Every one of them is compiled: the
59
90
  // runtime half of this package is `props` and three functions that throw.
@@ -0,0 +1,40 @@
1
+ // @flow
2
+
3
+ export type NativeStyleProps = { readonly style: { readonly [string]: string | number } };
4
+
5
+ /** Return null for web namespaces; mixing renderer outputs is an error. */
6
+ export function nativeProps(
7
+ styles: $ReadOnlyArray<mixed>,
8
+ nativeOnly: boolean = false,
9
+ ): NativeStyleProps | null {
10
+ let native = false;
11
+ let web = false;
12
+ const merged: { [string]: string | number } = {};
13
+ const pending = [...styles];
14
+ for (let index = 0; index < pending.length; index += 1) {
15
+ const value = pending[index];
16
+ if (Array.isArray(value)) {
17
+ pending.splice(index + 1, 0, ...value);
18
+ continue;
19
+ }
20
+ if (value == null || typeof value !== "object") continue;
21
+ // Static objects written by the compiler; validate their scalar fields.
22
+ const namespace: { [string]: mixed } = value as $FlowFixMe;
23
+ if (namespace.$$css === true) web = true;
24
+ if (namespace.$$native !== true) {
25
+ if (nativeOnly) throw new Error("StyleX native: expected a compiled native namespace");
26
+ continue;
27
+ }
28
+ native = true;
29
+ for (const property of Object.keys(namespace)) {
30
+ if (property === "$$native") continue;
31
+ const entry = namespace[property];
32
+ if (typeof entry !== "string" && typeof entry !== "number") {
33
+ throw new Error(`StyleX native: invalid compiled value for ${property}`);
34
+ }
35
+ merged[property] = entry;
36
+ }
37
+ }
38
+ if (native && web) throw new Error("StyleX: cannot mix native style objects with web classes");
39
+ return native ? { style: merged } : null;
40
+ }
package/native.js ADDED
@@ -0,0 +1,40 @@
1
+ // @flow
2
+ import type { ViewStyle, TextStyle, ImageStyle } from "react-native";
3
+ import { nativeRuntimeRequired } from "@uniflowed/core/native";
4
+ import { nativeProps } from "./native-props.js";
5
+
6
+ // Use one property map: a ViewStyle | TextStyle union would accept an invalid
7
+ // fontWeight through its ViewStyle branch, whose optional fields all match.
8
+ export type NativeStyle = $ReadOnly<{
9
+ ...$Exact<ViewStyle>,
10
+ ...$Exact<TextStyle>,
11
+ ...$Exact<ImageStyle>,
12
+ }>;
13
+ export type NativeStyleArgument<Style> =
14
+ | Style
15
+ | false
16
+ | null
17
+ | void
18
+ | $ReadOnlyArray<NativeStyleArgument<Style>>;
19
+ type StyleFields<Value> = Value extends $ReadOnlyArray<infer Item>
20
+ ? StyleFields<Item>
21
+ : Value extends (false | null | void)
22
+ ? {}
23
+ : Value;
24
+
25
+ /** Compile-time namespaces, checked against the app's React Native styles. */
26
+ export function create<Styles extends { readonly [string]: NativeStyle }>(styles: Styles): Styles {
27
+ return nativeRuntimeRequired("@uniflowed/stylex/native", "stylex.create");
28
+ }
29
+
30
+ /** Native-only props retain the authored property types and never add className. */
31
+ export function props<Args extends $ReadOnlyArray<NativeStyleArgument<NativeStyle>>>(
32
+ ...styles: Args
33
+ ): { readonly style: Partial<StyleFields<Args>> } {
34
+ const result = nativeProps(styles, true);
35
+ // The compiler validates the native subset and preserves every scalar value.
36
+ // The merger copies those fields, but its dynamic keys cannot retain Style.
37
+ return (result ?? { style: {} }) as $FlowFixMe;
38
+ }
39
+
40
+ export const stylex = { create, props };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniflowed/stylex",
3
- "version": "0.0.0-alpha.9",
3
+ "version": "0.2.0",
4
4
  "description": "Flow declarations for @uniflowed/stylex, part of the Unified Toolchain for Flow.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,12 +15,22 @@
15
15
  "./props": "./props.js",
16
16
  "./preset": "./preset.js",
17
17
  "./theme": "./theme.js",
18
- "./tokens.stylex.js": "./tokens.stylex.js"
18
+ "./tokens.stylex.js": "./tokens.stylex.js",
19
+ "./native": "./native.js"
19
20
  },
20
21
  "files": [
21
- "*.js"
22
+ "*.js",
23
+ "!*.test.js"
22
24
  ],
23
25
  "dependencies": {
24
- "@uniflowed/core": "0.0.0-alpha.9"
26
+ "@uniflowed/core": "0.2.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react-native": ">=0.86.0"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "react-native": {
33
+ "optional": true
34
+ }
25
35
  }
26
36
  }
package/preset.js CHANGED
@@ -20,9 +20,9 @@
20
20
  //
21
21
  // It is the only module in the package that has opinions. `props` is a merge,
22
22
  // the tokens are names, the themes are values; this is where uf says a card has
23
- // a 16px radius. A project that wants its own look drops this import and keeps
24
- // everything else, and a bundler drops the rules with it because nothing else
25
- // references them.
23
+ // a 6px radius and a 1px border rather than a shadow. A project that wants its
24
+ // own look drops this import and keeps everything else, and a bundler drops
25
+ // the rules with it because nothing else references them.
26
26
  //
27
27
  // # Why functions rather than exported namespaces
28
28
  //
@@ -84,13 +84,14 @@ const surfaces = stylex.create({
84
84
  borderColor: ufTokens.border,
85
85
  borderRadius: ufTokens.radiusLg,
86
86
  padding: ufTokens.space6,
87
- boxShadow: ufTokens.shadowCard,
88
87
  },
89
88
  panel: {
90
89
  backgroundColor: ufTokens.surface,
91
- borderRadius: ufTokens.radiusXl,
90
+ borderWidth: "1px",
91
+ borderStyle: "solid",
92
+ borderColor: ufTokens.border,
93
+ borderRadius: ufTokens.radiusLg,
92
94
  padding: ufTokens.space6,
93
- boxShadow: ufTokens.shadowPanel,
94
95
  },
95
96
  sunken: {
96
97
  backgroundColor: ufTokens.sunken,
@@ -132,7 +133,10 @@ const buttons = stylex.create({
132
133
  borderColor: "transparent",
133
134
  cursor: "pointer",
134
135
  transitionProperty: "background-color, border-color, color",
135
- transitionDuration: ufTokens.durationFast,
136
+ transitionDuration: {
137
+ default: ufTokens.durationFast,
138
+ "@media (prefers-reduced-motion: reduce)": "0s",
139
+ },
136
140
  transitionTimingFunction: ufTokens.easing,
137
141
  // The ring is drawn only for a keyboard focus, which is the whole reason
138
142
  // `:focus-visible` exists: a mouse click should not light the control up.
@@ -191,7 +195,7 @@ const fields = stylex.create({
191
195
  borderWidth: "1px",
192
196
  borderStyle: "solid",
193
197
  borderColor: ufTokens.border,
194
- borderRadius: ufTokens.radiusSm,
198
+ borderRadius: ufTokens.radiusMd,
195
199
  paddingBlock: ufTokens.space2,
196
200
  paddingInline: ufTokens.space3,
197
201
  outlineWidth: { default: "0", ":focus-visible": "2px" },
@@ -226,9 +230,11 @@ const overlays = stylex.create({
226
230
  backgroundColor: ufTokens.surface,
227
231
  color: ufTokens.ink,
228
232
  fontFamily: ufTokens.fontSans,
229
- borderRadius: ufTokens.radiusXl,
233
+ borderWidth: "1px",
234
+ borderStyle: "solid",
235
+ borderColor: ufTokens.border,
236
+ borderRadius: ufTokens.radiusLg,
230
237
  padding: ufTokens.space6,
231
- boxShadow: ufTokens.shadowPanel,
232
238
  },
233
239
  });
234
240
 
@@ -243,7 +249,6 @@ const menus = stylex.create({
243
249
  borderStyle: "solid",
244
250
  borderColor: ufTokens.border,
245
251
  borderRadius: ufTokens.radiusMd,
246
- boxShadow: ufTokens.shadowPanel,
247
252
  },
248
253
  item: {
249
254
  display: "flex",
@@ -315,7 +320,10 @@ const controls = stylex.create({
315
320
  color: ufTokens.accentInk,
316
321
  cursor: "pointer",
317
322
  transitionProperty: "background-color, border-color",
318
- transitionDuration: ufTokens.durationFast,
323
+ transitionDuration: {
324
+ default: ufTokens.durationFast,
325
+ "@media (prefers-reduced-motion: reduce)": "0s",
326
+ },
319
327
  transitionTimingFunction: ufTokens.easing,
320
328
  outlineWidth: { default: "0", ":focus-visible": "2px" },
321
329
  outlineStyle: "solid",
@@ -325,7 +333,7 @@ const controls = stylex.create({
325
333
  box: {
326
334
  width: ufTokens.sizeControl,
327
335
  height: ufTokens.sizeControl,
328
- borderRadius: "5px",
336
+ borderRadius: ufTokens.radiusSm,
329
337
  },
330
338
  // A switch's track is the one piece of geometry here that is not a token:
331
339
  // nothing but a switch reads it, and a theme that changed it would leave the
package/props.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @flow
2
+
3
+ import { nativeProps } from "./native-props.js";
2
4
  //
3
5
  // `@uniflowed/stylex/props`: the merge, and the only part of StyleX that runs.
4
6
  //
@@ -56,7 +58,10 @@ export type CompiledStyle = {
56
58
  export type StyleArgument = mixed;
57
59
 
58
60
  /** What `props` hands to an element. */
59
- export type StyleProps = { readonly className?: string };
61
+ export type StyleProps = {
62
+ readonly className?: string,
63
+ readonly style?: { readonly [string]: string | number },
64
+ };
60
65
 
61
66
  /** What one property contributed, once the merge has picked a winner. */
62
67
  type Winner = string | null | CompiledClasses;
@@ -73,6 +78,8 @@ type Winner = string | null | CompiledClasses;
73
78
  * survived.
74
79
  */
75
80
  export function props(...styles: $ReadOnlyArray<StyleArgument>): StyleProps {
81
+ const native = nativeProps(styles);
82
+ if (native != null) return native;
76
83
  const winners: { [string]: Winner } = {};
77
84
  collect(styles, winners);
78
85
 
package/theme.js CHANGED
@@ -62,25 +62,23 @@ import { ufTokens } from "./tokens.stylex.js";
62
62
  * exactly one `@media (prefers-color-scheme: dark)` block.
63
63
  */
64
64
  export const ufAutoTheme = stylex.createTheme(ufTokens, {
65
- canvas: { "@media (prefers-color-scheme: dark)": "#0b1220" },
66
- sunken: { "@media (prefers-color-scheme: dark)": "#0f1726" },
67
- surface: { "@media (prefers-color-scheme: dark)": "#131c2e" },
68
- surfaceHover: { "@media (prefers-color-scheme: dark)": "#1b2540" },
69
- border: { "@media (prefers-color-scheme: dark)": "#26324a" },
70
- ink: { "@media (prefers-color-scheme: dark)": "#e8eefc" },
71
- muted: { "@media (prefers-color-scheme: dark)": "#9fb0cc" },
72
- accent: { "@media (prefers-color-scheme: dark)": "#8f8bff" },
73
- accentHover: { "@media (prefers-color-scheme: dark)": "#a49fff" },
74
- accentInk: { "@media (prefers-color-scheme: dark)": "#0b1220" },
75
- accentSoft: { "@media (prefers-color-scheme: dark)": "#1e2547" },
76
- danger: { "@media (prefers-color-scheme: dark)": "#ff6b5e" },
77
- dangerHover: { "@media (prefers-color-scheme: dark)": "#ff8378" },
78
- dangerInk: { "@media (prefers-color-scheme: dark)": "#2a0b08" },
79
- dangerSoft: { "@media (prefers-color-scheme: dark)": "#2a1512" },
80
- focus: { "@media (prefers-color-scheme: dark)": "#7fb0ff" },
81
- scrim: { "@media (prefers-color-scheme: dark)": "rgba(2, 6, 16, 0.62)" },
82
- shadowCard: { "@media (prefers-color-scheme: dark)": "0 1px 2px rgba(0, 0, 0, 0.40)" },
83
- shadowPanel: { "@media (prefers-color-scheme: dark)": "0 16px 48px rgba(0, 0, 0, 0.55)" },
65
+ canvas: { "@media (prefers-color-scheme: dark)": "#141413" },
66
+ sunken: { "@media (prefers-color-scheme: dark)": "#0f0f0e" },
67
+ surface: { "@media (prefers-color-scheme: dark)": "#1c1c1a" },
68
+ surfaceHover: { "@media (prefers-color-scheme: dark)": "#262623" },
69
+ border: { "@media (prefers-color-scheme: dark)": "#363632" },
70
+ ink: { "@media (prefers-color-scheme: dark)": "#ecece7" },
71
+ muted: { "@media (prefers-color-scheme: dark)": "#a5a59e" },
72
+ accent: { "@media (prefers-color-scheme: dark)": "#8eaee8" },
73
+ accentHover: { "@media (prefers-color-scheme: dark)": "#a8c1ee" },
74
+ accentInk: { "@media (prefers-color-scheme: dark)": "#101318" },
75
+ accentSoft: { "@media (prefers-color-scheme: dark)": "#1f2838" },
76
+ danger: { "@media (prefers-color-scheme: dark)": "#ee8a80" },
77
+ dangerHover: { "@media (prefers-color-scheme: dark)": "#f3a49c" },
78
+ dangerInk: { "@media (prefers-color-scheme: dark)": "#1d0f0d" },
79
+ dangerSoft: { "@media (prefers-color-scheme: dark)": "#35201d" },
80
+ focus: { "@media (prefers-color-scheme: dark)": "#8eaee8" },
81
+ scrim: { "@media (prefers-color-scheme: dark)": "rgba(0, 0, 0, 0.6)" },
84
82
  });
85
83
 
86
84
  /**
@@ -90,23 +88,21 @@ export const ufAutoTheme = stylex.createTheme(ufTokens, {
90
88
  * preference a reader chose.
91
89
  */
92
90
  export const ufDarkTheme = stylex.createTheme(ufTokens, {
93
- canvas: "#0b1220",
94
- sunken: "#0f1726",
95
- surface: "#131c2e",
96
- surfaceHover: "#1b2540",
97
- border: "#26324a",
98
- ink: "#e8eefc",
99
- muted: "#9fb0cc",
100
- accent: "#8f8bff",
101
- accentHover: "#a49fff",
102
- accentInk: "#0b1220",
103
- accentSoft: "#1e2547",
104
- danger: "#ff6b5e",
105
- dangerHover: "#ff8378",
106
- dangerInk: "#2a0b08",
107
- dangerSoft: "#2a1512",
108
- focus: "#7fb0ff",
109
- scrim: "rgba(2, 6, 16, 0.62)",
110
- shadowCard: "0 1px 2px rgba(0, 0, 0, 0.40)",
111
- shadowPanel: "0 16px 48px rgba(0, 0, 0, 0.55)",
91
+ canvas: "#141413",
92
+ sunken: "#0f0f0e",
93
+ surface: "#1c1c1a",
94
+ surfaceHover: "#262623",
95
+ border: "#363632",
96
+ ink: "#ecece7",
97
+ muted: "#a5a59e",
98
+ accent: "#8eaee8",
99
+ accentHover: "#a8c1ee",
100
+ accentInk: "#101318",
101
+ accentSoft: "#1f2838",
102
+ danger: "#ee8a80",
103
+ dangerHover: "#f3a49c",
104
+ dangerInk: "#1d0f0d",
105
+ dangerSoft: "#35201d",
106
+ focus: "#8eaee8",
107
+ scrim: "rgba(0, 0, 0, 0.6)",
112
108
  });
package/tokens.stylex.js CHANGED
@@ -11,18 +11,37 @@
11
11
  // # Roles, not colours
12
12
  //
13
13
  // The names are what a token is *for*, never what it looks like. `accent` is
14
- // the colour a primary action wears; it is indigo today and a theme can make it
15
- // anything, and no rule in `preset.js` has to change when it does. A token
16
- // named `indigo500` would have made the preset unthemeable the moment someone
17
- // wanted a green product.
14
+ // the colour a primary action wears; it is a quiet cobalt today and a theme
15
+ // can make it anything, and no rule in `preset.js` has to change when it does.
16
+ // A token named `blue600` would have made the preset unthemeable the moment
17
+ // someone wanted a green product.
18
18
  //
19
- // The values are uf's identity, and `@uniflowed/brand` is where that identity
20
- // is decided: `accent`, `ink`, `canvas` and `muted` are brand's Indigo, Ink,
21
- // Mist and Slate, and the type, spacing and radius steps are brand's scales.
22
- // Brand cannot hold the token module itself — a StyleX token's name is computed
23
- // by the compiler, and `defineVars` takes literals, not an imported array — so
24
- // this module is brand's projection into the semantic roles a design system
25
- // needs and an identity does not have.
19
+ // # The palette
20
+ //
21
+ // `docs/ui-visual-language.md` explains it; in short: the interface is
22
+ // neutral first — warm greys with no blue cast, near-black ink on off-white
23
+ // paper — and colour is spent in one place, a single accent for what is
24
+ // selected, current or primary. Danger is a brick red rather than a signal
25
+ // red. Dark is designed as its own palette (charcoal, not navy; a lighter,
26
+ // desaturated accent that does not glow), not the light one inverted.
27
+ //
28
+ // The type and spacing steps are `@uniflowed/brand`'s scales. The colours and
29
+ // the corners are not: brand's cyan-to-magenta spectrum and its round corners
30
+ // are uf's logo and marketing identity, and an interface built from them looks
31
+ // like every other generated one, so this module's values are its own. Brand
32
+ // cannot hold the token module itself anyway — a StyleX token's name is
33
+ // computed by the compiler, and `defineVars` takes literals, not an imported
34
+ // array.
35
+ //
36
+ // # No shadows, no gradients
37
+ //
38
+ // The defaults draw depth with a rule and a change of surface, and nothing
39
+ // else. A shadow is the first thing a theme has to fight when the product's
40
+ // look is flat, and a gradient is a decision about a brand this module does
41
+ // not have, so neither is a token and neither appears in a default style.
42
+ // `crates/uf_stylex/src/tests/defaults.rs` compiles every default style —
43
+ // this module, `preset.js`, `theme.js` and every file in `registry/ui/` — and
44
+ // fails on a `box-shadow`, a `text-shadow`, a `drop-shadow()` or a gradient.
26
45
  //
27
46
  // # What is not a token
28
47
  //
@@ -45,31 +64,31 @@ import { stylex } from "@uniflowed/stylex";
45
64
  // `ThemeOverrides<typeof ufTokens>` reject a token this module does not declare.
46
65
  export const ufTokens = stylex.defineVars({
47
66
  // Surfaces, from furthest back to nearest front.
48
- canvas: "#f8fafc",
49
- sunken: "#eef2f7",
67
+ canvas: "#f6f6f4",
68
+ sunken: "#eeeeeb",
50
69
  surface: "#ffffff",
51
- surfaceHover: "#f1f5f9",
52
- border: "#dbe3ec",
70
+ surfaceHover: "#f1f1ee",
71
+ border: "#d9d9d4",
53
72
 
54
73
  // Text.
55
- ink: "#0f172a",
56
- muted: "#475569",
74
+ ink: "#1b1b19",
75
+ muted: "#5c5c57",
57
76
 
58
77
  // The colour a primary action wears, and what is legible on it.
59
- accent: "#5c49ff",
60
- accentHover: "#4a37f0",
78
+ accent: "#2b58b5",
79
+ accentHover: "#234996",
61
80
  accentInk: "#ffffff",
62
- accentSoft: "#eeecff",
81
+ accentSoft: "#e8eef9",
63
82
 
64
83
  // The colour a destructive action wears.
65
- danger: "#b42318",
66
- dangerHover: "#9a1c12",
84
+ danger: "#b1271d",
85
+ dangerHover: "#931f17",
67
86
  dangerInk: "#ffffff",
68
- dangerSoft: "#fef3f2",
87
+ dangerSoft: "#fbedeb",
69
88
 
70
89
  // Focus ring, and the wash behind a modal surface.
71
- focus: "#2677ff",
72
- scrim: "rgba(15, 23, 42, 0.48)",
90
+ focus: "#2b58b5",
91
+ scrim: "rgba(27, 27, 25, 0.45)",
73
92
 
74
93
  // Type.
75
94
  fontSans: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
@@ -95,20 +114,30 @@ export const ufTokens = stylex.defineVars({
95
114
  space8: "32px",
96
115
  space12: "48px",
97
116
 
98
- // Shape.
99
- radiusSm: "8px",
100
- radiusMd: "12px",
101
- radiusLg: "16px",
102
- radiusXl: "24px",
117
+ // Shape. Three small steps and a pill, and nothing rounder: a control is
118
+ // 2px, a button, field group or popover 4px, and the largest surface — a
119
+ // card, a dialog — 6px. `radiusPill` is for the few things that are round
120
+ // by what they are, such as a switch's track or a radio's dot, and not a
121
+ // way to soften a rectangle.
122
+ radiusSm: "2px",
123
+ radiusMd: "4px",
124
+ radiusLg: "6px",
103
125
  radiusPill: "999px",
104
126
 
105
127
  // The size of a control a finger or a pointer aims at.
106
128
  sizeControl: "18px",
107
129
 
108
- // Elevation and motion.
109
- shadowCard: "0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.10)",
110
- shadowPanel: "0 16px 48px rgba(15, 23, 42, 0.24)",
130
+ // Motion. Two short durations and one easing: `durationFast` for a colour
131
+ // or a chevron, `durationBase` for something that travels, such as a
132
+ // switch's thumb or a drawer. The easing decelerates and never overshoots,
133
+ // so nothing bounces. A transition names the properties it moves, never
134
+ // `all`, and is `0s` under `prefers-reduced-motion: reduce`;
135
+ // `crates/uf_stylex/src/tests/defaults.rs` holds every default style to
136
+ // that.
137
+ //
138
+ // There is no elevation either: a surface in front of another is told
139
+ // apart by a 1px `border` and its own background, never by a shadow.
111
140
  durationFast: "120ms",
112
- durationBase: "200ms",
141
+ durationBase: "160ms",
113
142
  easing: "cubic-bezier(0.2, 0, 0, 1)",
114
143
  });