@waveso/ui 0.18.0 → 0.20.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/README.md CHANGED
@@ -10,8 +10,7 @@
10
10
 
11
11
  <br />
12
12
 
13
- <!-- TODO: replace assets/showcase-placeholder.svg with a real screenshot of the Showcase story across the three themes -->
14
- <img src="assets/showcase-placeholder.svg" alt="Wave UI across its three themes — Graphite, Ink, and Paper" width="760" />
13
+ <img src="assets/showcase.svg" alt="Wave UI across its three themes — Graphite, Ink, and Paper" width="760" />
15
14
 
16
15
  <br /><br />
17
16
 
@@ -47,11 +46,12 @@ Wave builds around an intuitive, intentional **3-tier hierarchy** — for almost
47
46
  - 3 offset amounts for transforms
48
47
  - …and more
49
48
 
50
- It also builds around 3 semantic intents:
49
+ It builds around semantic intents:
51
50
 
52
51
  - **Background** colors focus on elevation — how deep a surface sits
53
52
  - **Content** colors focus on emphasis — how strongly something reads
54
53
  - **Border** colors focus on structure — how functional a boundary is
54
+ - **Control** colors focus on state — whether a control is at rest, pointed at or chosen
55
55
 
56
56
  This is the right shape for a system centered on an emphasis ladder: clean and minimal, but flexible.
57
57
 
@@ -71,49 +71,48 @@ A pinned class always beats the OS — put `.paper`, `.graphite`, or `.ink` on y
71
71
 
72
72
  ## Taxonomy
73
73
 
74
- Wave pays close attention to token taxonomy. Surfaces encode elevation; borders act as _light interference rather than geometry_.
74
+ Wave pays close attention to token taxonomy. Surfaces encode elevation; borders act as _light interference rather than geometry_. Each of the five families below has three members, each with one job.
75
75
 
76
- ### Background
76
+ **Surfaces are solid: they contain. Borders and control fills are see-through:** `line`, `edge`, `contour`, `neutral` and `highlighted` are the theme's text colour made translucent, so they blend into whatever is underneath, for depth and integration. An input inside a primary-coloured banner, or on an accent card, only works if its fill is see-through; a solid control colour can't follow every background. And because controls don't borrow `secondary`, it's free to be a real brand colour.
77
+
78
+ ### Surfaces — solid, to contain
77
79
 
78
80
  - `--foundation` — the deepest layer, the base where elevation starts
79
81
  - `--surface` — the middle layer: cards, sidebars, content boxes
80
82
  - `--elevated` — the highest layer: floating windows, modals, dropdowns, dialogs
81
83
 
82
- ### Foreground
84
+ ### Text — contrast
83
85
 
84
86
  - `--contrast` — titles, primary text
85
- - `--muted` — body text, icons, any mark
86
- - `--subtle` — placeholders, hints
87
+ - `--muted` — body text, placeholders, icons, any mark
88
+ - `--subtle` — hints
89
+
90
+ ### Brand — colour
87
91
 
88
- ### Lines and fills
92
+ - `--primary` — wired to the Wave ramp
93
+ - `--secondary` — the second brand colour, a quiet neutral by default
94
+ - `--accent` — alternative emphasis
89
95
 
90
- The theme's foreground made translucent, so each reads the same on any surface, and each keeps one job.
96
+ ### Borders — boundary
91
97
 
92
- Lines:
98
+ The theme's text colour at three strengths, one job each:
93
99
 
94
100
  - `--line` (5%) — what divides: separators, dividers, hairlines
95
101
  - `--edge` (10%) — what bounds a surface: cards, dialogs, popups, toasts
96
102
  - `--contour` (15%) — what outlines a control or a region: outline buttons, badges, toggles, layout regions
97
103
 
98
- Fills:
104
+ ### Controls — depth
105
+
106
+ A control's state, from resting to chosen:
99
107
 
100
- - `--highlighted` (10%) — where attention lands: hovered, keyboard-highlighted and open items
101
- - `--neutral` (15%) — what gives a control its body, its look before anything is chosen: field and track fills, unchecked boxes. Not Tailwind's `neutral-*` grey palette, which Wave doesn't use
108
+ - `--neutral` (10%) — the look before anything is chosen or pointed at: field and track fills, unchecked boxes, table footers, muted items. Not Tailwind's `neutral-*` grey palette, which Wave doesn't use
109
+ - `--highlighted` (15%) — where attention lands: hovered, keyboard-highlighted and open items, a step stronger than `neutral`
110
+ - `--selected` — chosen, current or on: the current tab, a pressed toggle, a checked box, a selected row. `--primary` by default; set it to restyle every chosen state at once. Chosen items keep white text, so pick a colour it reads on, such as a darker shade of your brand colour
102
111
 
103
112
  ### Ring
104
113
 
105
114
  - `--focus` — active / focus states
106
115
 
107
- ### Selected
108
-
109
- - `--selected` — chosen, current or on: the current tab, a pressed toggle, a checked box, a selected row. Primary by default; set it to restyle every chosen state at once
110
-
111
- ### Brand
112
-
113
- - `--primary` — wired to the Wave ramp
114
- - `--secondary` — neutral fill
115
- - `--accent` — alternative emphasis
116
-
117
116
  ### Border strategy
118
117
 
119
118
  This is the sophisticated move most libraries skip.
package/dist/checkbox.js CHANGED
@@ -7,7 +7,7 @@ import { Checkbox as Checkbox$1 } from "@base-ui/react/checkbox";
7
7
  function Checkbox({ className, ...props }) {
8
8
  return /* @__PURE__ */ jsx(Checkbox$1.Root, {
9
9
  "data-slot": "checkbox",
10
- className: cn("cursor-clickable bg-neutral border-transparent not-data-disabled:data-unchecked:hover:border-focus not-data-disabled:data-unchecked:group-hover/field:border-focus data-checked:bg-primary data-indeterminate:bg-primary data-checked:text-white data-indeterminate:text-white data-checked:border-primary data-indeterminate:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-danger focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 peer relative flex size-6 shrink-0 items-center justify-center rounded-sm border motion-color focus-visible:outline-hidden group-has-disabled/field:opacity-30 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", className),
10
+ className: cn("cursor-clickable bg-neutral border-transparent not-data-disabled:data-unchecked:hover:border-focus not-data-disabled:data-unchecked:group-hover/field:border-focus data-checked:bg-selected data-indeterminate:bg-selected data-checked:text-white data-indeterminate:text-white data-checked:border-selected data-indeterminate:border-selected aria-invalid:aria-checked:border-selected aria-invalid:border-danger focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 peer relative flex size-6 shrink-0 items-center justify-center rounded-sm border motion-color focus-visible:outline-hidden group-has-disabled/field:opacity-30 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", className),
11
11
  ...props,
12
12
  children: /* @__PURE__ */ jsxs(Checkbox$1.Indicator, {
13
13
  "data-slot": "checkbox-indicator",
@@ -1,4 +1,4 @@
1
1
  //#region src/film-grain-shader.d.ts
2
- export declare const vertexShader = "\nattribute vec2 position;\nvarying vec2 vUv;\n\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n";
3
- export declare const fragmentShader = "\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uDensity;\nuniform float uOpacity;\nuniform float uFps;\nuniform vec3 uColor;\n\nvarying vec2 vUv;\n\n// Stable hash\nfloat hash(vec2 p, float seed) {\n return fract(sin(dot(p + seed, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\n// Film response curve (soft toe + shoulder rolloff)\nfloat filmCurve(float x) {\n return smoothstep(0.0, 0.2, x) * (1.0 - smoothstep(0.8, 1.0, x));\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy;\n vec2 p = floor(uv);\n\n // Temporal coherence: blend between current and next frame seed\n float frame = floor(uTime * uFps);\n float t = fract(uTime * uFps);\n float seed = frame * 0.1731;\n\n // Multi-scale grain with inter-frame blending on fine layer\n float fineA = hash(p, seed);\n float fineB = hash(p, seed + 0.1731);\n float fine = mix(fineA, fineB, t);\n\n float medium = hash(floor(uv * 0.5), seed + 3.1);\n float coarse = hash(floor(uv * 0.25), seed + 7.93);\n\n float grain = fine * 0.65 + medium * 0.25 + coarse * 0.10;\n\n // Emulsion clumping (organic structure)\n float cluster = hash(floor(uv * 0.18), seed + 9.2);\n grain *= mix(0.75, 1.35, cluster);\n\n // Density threshold\n grain = smoothstep(1.0 - uDensity, 1.0, grain);\n\n // Film response curve (replaces raw pow gamma)\n grain = filmCurve(grain);\n\n // Micro-blur: sample neighbors for optical diffusion\n float n1 = hash(p + vec2(1.0, 0.0), seed);\n float n2 = hash(p - vec2(1.0, 0.0), seed);\n float n3 = hash(p + vec2(0.0, 1.0), seed);\n float n4 = hash(p - vec2(0.0, 1.0), seed);\n float neighbors = (n1 + n2 + n3 + n4) * 0.25;\n grain = mix(grain, neighbors, 0.15);\n\n // Chromatic aberration (reuse neighbor hashes)\n float rShift = n1 * 0.015;\n float bShift = n2 * 0.015;\n\n vec3 color = uColor * vec3(\n grain + rShift,\n grain,\n grain + bShift\n );\n\n color = clamp(color, 0.0, 1.0);\n\n gl_FragColor = vec4(color, grain * uOpacity);\n}\n";
2
+ export declare const vertexShader = "#version 300 es\nin vec2 position;\nout vec2 vUv;\n\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n";
3
+ export declare const fragmentShader = "#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uDensity;\nuniform float uOpacity;\nuniform float uFps;\nuniform vec3 uColor;\n\nin vec2 vUv;\nout vec4 fragColor;\n\n// Integer hash (two rounds of a permuted congruential mix): three\n// independent values from three inputs, identical on every GPU. No sine:\n// a sine hash loses precision on some mobile GPUs and draws faint stripes.\nuvec3 hash3(uvec3 v) {\n v = v * 1664525u + 1013904223u;\n v.x += v.y * v.z;\n v.y += v.z * v.x;\n v.z += v.x * v.y;\n v ^= v >> 16u;\n v.x += v.y * v.z;\n v.y += v.z * v.x;\n v.z += v.x * v.y;\n return v;\n}\n\nconst float UNIT = 1.0 / 4294967295.0;\n\n// A pixel's fine speck: the average of two values, so it gathers round the\n// middle as film grain does, with fewer pure darks and lights.\nfloat fine(uvec2 pixel, uint frame) {\n uvec3 h = hash3(uvec3(pixel, frame));\n return (float(h.x) + float(h.y)) * (0.5 * UNIT);\n}\n\n// Film response curve (soft toe + shoulder rolloff)\nfloat filmCurve(float x) {\n return smoothstep(0.0, 0.2, x) * (1.0 - smoothstep(0.8, 1.0, x));\n}\n\nvoid main() {\n uvec2 p = uvec2(gl_FragCoord.xy);\n // The grain changes once per frame of its fps; the component only draws\n // when this changes.\n uint frame = uint(floor(uTime * uFps));\n\n // Multi-scale grain. One hash per 4x4 block gives its coarse speck, its\n // clumping and, a byte for each 2x2 quarter, its medium specks.\n uvec3 block = hash3(uvec3(p >> 2u, frame ^ 0x9e3779b9u));\n uint quarter = ((p.y >> 1u) & 1u) * 2u + ((p.x >> 1u) & 1u);\n float medium = float((block.z >> (quarter * 8u)) & 255u) / 255.0;\n float coarse = float(block.x) * UNIT;\n float grain = fine(p, frame) * 0.65 + medium * 0.25 + coarse * 0.10;\n\n // Emulsion clumping (organic structure)\n grain *= mix(0.75, 1.35, float(block.y) * UNIT);\n\n // Density threshold\n grain = smoothstep(1.0 - uDensity, 1.0, grain);\n\n // Film response curve (replaces raw pow gamma)\n grain = filmCurve(grain);\n\n // Micro-blur: sample neighbors for optical diffusion\n float n1 = fine(p + uvec2(1u, 0u), frame);\n float n2 = fine(p - uvec2(1u, 0u), frame);\n float n3 = fine(p + uvec2(0u, 1u), frame);\n float n4 = fine(p - uvec2(0u, 1u), frame);\n float neighbors = (n1 + n2 + n3 + n4) * 0.25;\n grain = mix(grain, neighbors, 0.15);\n\n // Chromatic aberration (reuse neighbor hashes)\n float rShift = n1 * 0.015;\n float bShift = n2 * 0.015;\n\n vec3 color = uColor * vec3(\n grain + rShift,\n grain,\n grain + bShift\n );\n\n color = clamp(color, 0.0, 1.0);\n\n fragColor = vec4(color, grain * uOpacity);\n}\n";
4
4
  //#endregion
@@ -1,14 +1,14 @@
1
1
  //#region src/film-grain-shader.ts
2
- const vertexShader = `
3
- attribute vec2 position;
4
- varying vec2 vUv;
2
+ const vertexShader = `#version 300 es
3
+ in vec2 position;
4
+ out vec2 vUv;
5
5
 
6
6
  void main() {
7
7
  vUv = position * 0.5 + 0.5;
8
8
  gl_Position = vec4(position, 0.0, 1.0);
9
9
  }
10
10
  `;
11
- const fragmentShader = `
11
+ const fragmentShader = `#version 300 es
12
12
  precision highp float;
13
13
 
14
14
  uniform float uTime;
@@ -18,11 +18,31 @@ uniform float uOpacity;
18
18
  uniform float uFps;
19
19
  uniform vec3 uColor;
20
20
 
21
- varying vec2 vUv;
21
+ in vec2 vUv;
22
+ out vec4 fragColor;
23
+
24
+ // Integer hash (two rounds of a permuted congruential mix): three
25
+ // independent values from three inputs, identical on every GPU. No sine:
26
+ // a sine hash loses precision on some mobile GPUs and draws faint stripes.
27
+ uvec3 hash3(uvec3 v) {
28
+ v = v * 1664525u + 1013904223u;
29
+ v.x += v.y * v.z;
30
+ v.y += v.z * v.x;
31
+ v.z += v.x * v.y;
32
+ v ^= v >> 16u;
33
+ v.x += v.y * v.z;
34
+ v.y += v.z * v.x;
35
+ v.z += v.x * v.y;
36
+ return v;
37
+ }
38
+
39
+ const float UNIT = 1.0 / 4294967295.0;
22
40
 
23
- // Stable hash
24
- float hash(vec2 p, float seed) {
25
- return fract(sin(dot(p + seed, vec2(127.1, 311.7))) * 43758.5453123);
41
+ // A pixel's fine speck: the average of two values, so it gathers round the
42
+ // middle as film grain does, with fewer pure darks and lights.
43
+ float fine(uvec2 pixel, uint frame) {
44
+ uvec3 h = hash3(uvec3(pixel, frame));
45
+ return (float(h.x) + float(h.y)) * (0.5 * UNIT);
26
46
  }
27
47
 
28
48
  // Film response curve (soft toe + shoulder rolloff)
@@ -31,27 +51,21 @@ float filmCurve(float x) {
31
51
  }
32
52
 
33
53
  void main() {
34
- vec2 uv = gl_FragCoord.xy;
35
- vec2 p = floor(uv);
36
-
37
- // Temporal coherence: blend between current and next frame seed
38
- float frame = floor(uTime * uFps);
39
- float t = fract(uTime * uFps);
40
- float seed = frame * 0.1731;
41
-
42
- // Multi-scale grain with inter-frame blending on fine layer
43
- float fineA = hash(p, seed);
44
- float fineB = hash(p, seed + 0.1731);
45
- float fine = mix(fineA, fineB, t);
46
-
47
- float medium = hash(floor(uv * 0.5), seed + 3.1);
48
- float coarse = hash(floor(uv * 0.25), seed + 7.93);
49
-
50
- float grain = fine * 0.65 + medium * 0.25 + coarse * 0.10;
54
+ uvec2 p = uvec2(gl_FragCoord.xy);
55
+ // The grain changes once per frame of its fps; the component only draws
56
+ // when this changes.
57
+ uint frame = uint(floor(uTime * uFps));
58
+
59
+ // Multi-scale grain. One hash per 4x4 block gives its coarse speck, its
60
+ // clumping and, a byte for each 2x2 quarter, its medium specks.
61
+ uvec3 block = hash3(uvec3(p >> 2u, frame ^ 0x9e3779b9u));
62
+ uint quarter = ((p.y >> 1u) & 1u) * 2u + ((p.x >> 1u) & 1u);
63
+ float medium = float((block.z >> (quarter * 8u)) & 255u) / 255.0;
64
+ float coarse = float(block.x) * UNIT;
65
+ float grain = fine(p, frame) * 0.65 + medium * 0.25 + coarse * 0.10;
51
66
 
52
67
  // Emulsion clumping (organic structure)
53
- float cluster = hash(floor(uv * 0.18), seed + 9.2);
54
- grain *= mix(0.75, 1.35, cluster);
68
+ grain *= mix(0.75, 1.35, float(block.y) * UNIT);
55
69
 
56
70
  // Density threshold
57
71
  grain = smoothstep(1.0 - uDensity, 1.0, grain);
@@ -60,10 +74,10 @@ void main() {
60
74
  grain = filmCurve(grain);
61
75
 
62
76
  // Micro-blur: sample neighbors for optical diffusion
63
- float n1 = hash(p + vec2(1.0, 0.0), seed);
64
- float n2 = hash(p - vec2(1.0, 0.0), seed);
65
- float n3 = hash(p + vec2(0.0, 1.0), seed);
66
- float n4 = hash(p - vec2(0.0, 1.0), seed);
77
+ float n1 = fine(p + uvec2(1u, 0u), frame);
78
+ float n2 = fine(p - uvec2(1u, 0u), frame);
79
+ float n3 = fine(p + uvec2(0u, 1u), frame);
80
+ float n4 = fine(p - uvec2(0u, 1u), frame);
67
81
  float neighbors = (n1 + n2 + n3 + n4) * 0.25;
68
82
  grain = mix(grain, neighbors, 0.15);
69
83
 
@@ -79,7 +93,7 @@ void main() {
79
93
 
80
94
  color = clamp(color, 0.0, 1.0);
81
95
 
82
- gl_FragColor = vec4(color, grain * uOpacity);
96
+ fragColor = vec4(color, grain * uOpacity);
83
97
  }
84
98
  `;
85
99
  //#endregion
@@ -125,7 +125,7 @@ function useFilmGrain({ density, fps = 18, color = "#ffffff" }) {
125
125
  dpr
126
126
  };
127
127
  };
128
- const canGetWebGL = (c) => (c.getContext("webgl") || c.getContext("experimental-webgl")) ?? null;
128
+ const canGetWebGL = (c) => c.getContext("webgl2");
129
129
  const probeGl = canGetWebGL(document.createElement("canvas"));
130
130
  const webglSupported = probeGl ? initWebGL(probeGl) !== null : false;
131
131
  probeGl?.getExtension("WEBGL_lose_context")?.loseContext();
package/dist/input.js CHANGED
@@ -3,7 +3,7 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { Input as Input$1 } from "@base-ui/react/input";
5
5
  //#region src/input.tsx
6
- const inputVariants = cva("bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger placeholder:text-subtle flex w-full min-w-0 items-center overflow-hidden rounded-md border border-transparent motion-color focus-visible:outline-hidden file:inline-flex file:my-[3px] file:items-center file:me-3 file:cursor-clickable file:rounded-md file:border-0 file:bg-primary file:px-2.5 file:leading-none file:font-medium file:text-white file:motion-color file:hover:bg-primary/80 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", {
6
+ const inputVariants = cva("bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger placeholder:text-muted flex w-full min-w-0 items-center overflow-hidden rounded-md border border-transparent motion-color focus-visible:outline-hidden file:inline-flex file:my-[3px] file:items-center file:me-3 file:cursor-clickable file:rounded-md file:border-0 file:bg-primary file:px-2.5 file:leading-none file:font-medium file:text-white file:motion-color file:hover:bg-primary/80 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", {
7
7
  variants: { size: {
8
8
  xs: "h-6 rounded-sm px-2 text-xs file:-ms-[5px] file:h-4",
9
9
  sm: "h-8 px-3 text-sm file:-ms-[9px] file:h-6",
package/dist/item.js CHANGED
@@ -12,7 +12,7 @@ const itemVariants = cva("[a,button]:hover:bg-highlighted [a,button]:cursor-clic
12
12
  variant: {
13
13
  default: "border-transparent",
14
14
  outline: "border-line",
15
- muted: "bg-secondary/50 border-transparent [a,button]:hover:bg-secondary"
15
+ muted: "bg-neutral border-transparent"
16
16
  },
17
17
  size: {
18
18
  default: "gap-3.5 px-4 py-3.5",
package/dist/radio.js CHANGED
@@ -6,7 +6,7 @@ import { Radio as Radio$1 } from "@base-ui/react/radio";
6
6
  function Radio({ className, ...props }) {
7
7
  return /* @__PURE__ */ jsx(Radio$1.Root, {
8
8
  "data-slot": "radio",
9
- className: cn("cursor-clickable bg-neutral border-transparent not-data-disabled:data-unchecked:hover:border-focus not-data-disabled:data-unchecked:group-hover/field:border-focus data-checked:bg-primary data-checked:text-white data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-danger focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 group/radio peer relative flex aspect-square size-6 shrink-0 rounded-full border motion-color focus-visible:outline-hidden group-has-disabled/field:opacity-30 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", className),
9
+ className: cn("cursor-clickable bg-neutral border-transparent not-data-disabled:data-unchecked:hover:border-focus not-data-disabled:data-unchecked:group-hover/field:border-focus data-checked:bg-selected data-checked:text-white data-checked:border-selected aria-invalid:aria-checked:border-selected aria-invalid:border-danger focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 group/radio peer relative flex aspect-square size-6 shrink-0 rounded-full border motion-color focus-visible:outline-hidden group-has-disabled/field:opacity-30 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3", className),
10
10
  ...props,
11
11
  children: /* @__PURE__ */ jsx(RadioIndicator, {})
12
12
  });
package/dist/select.js CHANGED
@@ -5,7 +5,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { Select as Select$1 } from "@base-ui/react/select";
7
7
  //#region src/select.tsx
8
- const selectTriggerVariants = cva("data-placeholder:text-subtle bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger flex w-fit items-center justify-between gap-1.5 rounded-md border border-transparent whitespace-nowrap motion-color focus-visible:outline-hidden select-none focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
8
+ const selectTriggerVariants = cva("data-placeholder:text-muted bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger flex w-fit items-center justify-between gap-1.5 rounded-md border border-transparent whitespace-nowrap motion-color focus-visible:outline-hidden select-none focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 aria-invalid:ring-3 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
9
9
  variants: { size: {
10
10
  xs: "h-6 rounded-sm px-2 text-xs",
11
11
  sm: "h-8 px-3 text-sm",
package/dist/sidebar.js CHANGED
@@ -299,7 +299,7 @@ function SidebarMenuItem({ className, ...props }) {
299
299
  ...props
300
300
  });
301
301
  }
302
- const sidebarMenuButtonVariants = cva("ring-focus/50 hover:bg-highlighted active:bg-highlighted data-active:bg-primary data-active:text-white gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[slot=sidebar-menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:w-full group-data-[collapsible=icon]:aspect-square group-data-[collapsible=icon]:h-11 focus-visible:ring-3 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden focus-visible:outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-30 aria-disabled:pointer-events-none aria-disabled:opacity-30 [&>span:last-child]:truncate [&_svg]:shrink-0", {
302
+ const sidebarMenuButtonVariants = cva("ring-focus/50 hover:bg-highlighted active:bg-highlighted data-active:bg-selected data-active:text-white gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[slot=sidebar-menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:w-full group-data-[collapsible=icon]:aspect-square group-data-[collapsible=icon]:h-11 focus-visible:ring-3 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden focus-visible:outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-30 aria-disabled:pointer-events-none aria-disabled:opacity-30 [&>span:last-child]:truncate [&_svg]:shrink-0", {
303
303
  variants: {
304
304
  variant: {
305
305
  default: "",
@@ -387,7 +387,7 @@ function SidebarMenuSubItem({ className, ...props }) {
387
387
  function SidebarMenuSubButton({ render, size = "md", isActive = false, className, ...props }) {
388
388
  return useRender({
389
389
  defaultTagName: "a",
390
- props: mergeProps({ className: cn("text-contrast ring-focus/50 hover:bg-highlighted active:bg-highlighted data-active:bg-primary data-active:text-white h-7 gap-2 rounded-md px-2 focus-visible:ring-3 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden focus-visible:outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-30 aria-disabled:pointer-events-none aria-disabled:opacity-30 [&>span:last-child]:truncate [&>svg]:shrink-0", className) }, props),
390
+ props: mergeProps({ className: cn("text-contrast ring-focus/50 hover:bg-highlighted active:bg-highlighted data-active:bg-selected data-active:text-white h-7 gap-2 rounded-md px-2 focus-visible:ring-3 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden focus-visible:outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-30 aria-disabled:pointer-events-none aria-disabled:opacity-30 [&>span:last-child]:truncate [&>svg]:shrink-0", className) }, props),
391
391
  render,
392
392
  state: {
393
393
  slot: "sidebar-menu-sub-button",
package/dist/styles.css CHANGED
@@ -279,22 +279,21 @@
279
279
  --ink-900: oklch(0.201 0.038 257); /* #0B1627 */
280
280
  --ink-950: oklch(0.132 0.027 252); /* #020812 */
281
281
 
282
- /* Paper — warm stone on a white page. Built around its own colours: the
283
- * cards (50), the neutral fill (100), a warm 300, the secondary text (600)
284
- * and the text (950). The steps between are filled with even contrast,
285
- * about 1.37 a step from the 100 down. The warmth peaks at the 300 and eases
286
- * into the text greys. The page is white, so it isn't a step; these are
287
- * sRGB colours, the same on Display-P3 screens. */
282
+ /* Paper — warm stone on a white page. Its ends are its own colours, the
283
+ * cards (50) and the text (950); the steps between follow Ink's spacing, so
284
+ * the ramp falls smoothly all the way into the text colour. The warmth
285
+ * peaks at the 300 and eases into the text greys. The page is white, so it
286
+ * isn't a step; these are sRGB colours, the same on Display-P3 screens. */
288
287
  --paper-50: oklch(0.989 0.002 67.8); /* #FCFBFA */
289
- --paper-100: oklch(0.959 0.002 67.8); /* #F2F1F0 */
290
- --paper-200: oklch(0.860 0.007 67.7); /* #D4D0CC */
291
- --paper-300: oklch(0.769 0.010 72.6); /* #B8B3AD */
292
- --paper-400: oklch(0.683 0.008 64.9); /* #9D9894 */
293
- --paper-500: oklch(0.603 0.006 52.9); /* #84807E */
294
- --paper-600: oklch(0.528 0.005 34.3); /* #6E6A69 */
295
- --paper-700: oklch(0.452 0.004 34.3); /* #585554 */
296
- --paper-800: oklch(0.376 0.003 34.3); /* #434140 */
297
- --paper-900: oklch(0.291 0.001 34.3); /* #2C2B2B */
288
+ --paper-100: oklch(0.929 0.002 67.8); /* #E8E7E6 */
289
+ --paper-200: oklch(0.850 0.007 67.7); /* #D1CDC9 */
290
+ --paper-300: oklch(0.756 0.010 72.6); /* #B4AFA9 */
291
+ --paper-400: oklch(0.668 0.008 64.9); /* #989490 */
292
+ --paper-500: oklch(0.567 0.006 52.9); /* #797673 */
293
+ --paper-600: oklch(0.465 0.005 34.3); /* #5C5857 */
294
+ --paper-700: oklch(0.372 0.004 34.3); /* #423F3F */
295
+ --paper-800: oklch(0.296 0.003 34.3); /* #2E2C2C */
296
+ --paper-900: oklch(0.228 0.001 34.3); /* #1D1C1C */
298
297
  --paper-950: oklch(0.159 0 0); /* #0D0D0D */
299
298
 
300
299
  /* ========================================================================
@@ -566,8 +565,8 @@
566
565
  --line: color-mix(in srgb, var(--paper-950) 5%, transparent);
567
566
  --edge: color-mix(in srgb, var(--paper-950) 10%, transparent);
568
567
  --contour: color-mix(in srgb, var(--paper-950) 15%, transparent);
569
- --highlighted: color-mix(in srgb, var(--paper-950) 10%, transparent);
570
- --neutral: color-mix(in srgb, var(--paper-950) 15%, transparent);
568
+ --highlighted: color-mix(in srgb, var(--paper-950) 15%, transparent);
569
+ --neutral: color-mix(in srgb, var(--paper-950) 10%, transparent);
571
570
  --shadow-strength: 0.055;
572
571
  --danger: var(--wave-red-500);
573
572
  --danger-text: var(--wave-red-600);
@@ -601,8 +600,8 @@
601
600
  --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
602
601
  --edge: color-mix(in srgb, var(--graphite-50) 10%, transparent);
603
602
  --contour: color-mix(in srgb, var(--graphite-50) 15%, transparent);
604
- --highlighted: color-mix(in srgb, var(--graphite-50) 10%, transparent);
605
- --neutral: color-mix(in srgb, var(--graphite-50) 15%, transparent);
603
+ --highlighted: color-mix(in srgb, var(--graphite-50) 15%, transparent);
604
+ --neutral: color-mix(in srgb, var(--graphite-50) 10%, transparent);
606
605
  --shadow-strength: 0.045;
607
606
  --danger: var(--wave-red-500);
608
607
  --danger-text: oklch(0.749 0.152 16.8); /* #FF828B, see below */
@@ -626,8 +625,8 @@
626
625
  --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
627
626
  --edge: color-mix(in srgb, var(--graphite-50) 10%, transparent);
628
627
  --contour: color-mix(in srgb, var(--graphite-50) 15%, transparent);
629
- --highlighted: color-mix(in srgb, var(--graphite-50) 10%, transparent);
630
- --neutral: color-mix(in srgb, var(--graphite-50) 15%, transparent);
628
+ --highlighted: color-mix(in srgb, var(--graphite-50) 15%, transparent);
629
+ --neutral: color-mix(in srgb, var(--graphite-50) 10%, transparent);
631
630
  --shadow-strength: 0.045;
632
631
  --danger: var(--wave-red-500);
633
632
  --danger-text: oklch(0.749 0.152 16.8); /* #FF828B, see below */
@@ -680,8 +679,8 @@
680
679
  --line: color-mix(in srgb, var(--ink-50) 5%, transparent);
681
680
  --edge: color-mix(in srgb, var(--ink-50) 10%, transparent);
682
681
  --contour: color-mix(in srgb, var(--ink-50) 15%, transparent);
683
- --highlighted: color-mix(in srgb, var(--ink-50) 10%, transparent);
684
- --neutral: color-mix(in srgb, var(--ink-50) 15%, transparent);
682
+ --highlighted: color-mix(in srgb, var(--ink-50) 15%, transparent);
683
+ --neutral: color-mix(in srgb, var(--ink-50) 10%, transparent);
685
684
  --shadow-strength: 0.045;
686
685
  --danger: var(--wave-red-500);
687
686
  --danger-text: var(--wave-red-400);
@@ -729,12 +728,13 @@
729
728
  * always as a border (rings are kept for focus and invalid halos).
730
729
  * `contour` (15%) outlines a control or a region: outline buttons, badges,
731
730
  * toggles, the slider thumb, menubars.
732
- * Fills, one job each: `highlighted` (10%) marks where attention lands:
733
- * hovered, keyboard-highlighted and open items. `neutral` (15%) gives a
734
- * control its body, its look before anything is chosen: field and track
735
- * fills, unchecked boxes. (Tailwind's own `neutral-50` … `neutral-950`
736
- * greys are a separate palette Wave doesn't use.) `focus` is the
737
- * brand-themed ring (= primary). */
731
+ * Fills, one job each: `neutral` (10%) is the look before anything is
732
+ * chosen or pointed at: a control's body (field and track fills, unchecked
733
+ * boxes) and a quiet region (a table footer, a muted item). `highlighted`
734
+ * (15%) marks where attention lands: hovered, keyboard-highlighted and
735
+ * open items, always a step stronger than resting. (Tailwind's own
736
+ * `neutral-50` … `neutral-950` greys are a separate palette Wave doesn't
737
+ * use.) `focus` is the brand-themed ring (= primary). */
738
738
  --color-line: var(--line);
739
739
  --color-edge: var(--edge);
740
740
  --color-contour: var(--contour);
package/dist/switch.js CHANGED
@@ -4,7 +4,7 @@ import { jsx } from "react/jsx-runtime";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { Switch as Switch$1 } from "@base-ui/react/switch";
6
6
  //#region src/switch.tsx
7
- const switchVariants = cva("@container cursor-clickable data-checked:bg-primary data-unchecked:bg-neutral data-unchecked:hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger peer relative inline-flex shrink-0 items-center rounded-full border border-transparent motion-color focus-visible:outline-hidden after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 aria-invalid:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30", {
7
+ const switchVariants = cva("@container cursor-clickable data-checked:bg-selected data-unchecked:bg-neutral data-unchecked:hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger peer relative inline-flex shrink-0 items-center rounded-full border border-transparent motion-color focus-visible:outline-hidden after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 aria-invalid:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30", {
8
8
  variants: { size: {
9
9
  default: "h-6 w-11 p-1",
10
10
  sm: "h-4 w-7 p-0.5"
package/dist/table.js CHANGED
@@ -61,14 +61,14 @@ function TableBody({ className, ...props }) {
61
61
  function TableFooter({ className, ...props }) {
62
62
  return /* @__PURE__ */ jsx("tfoot", {
63
63
  "data-slot": "table-footer",
64
- className: cn("bg-secondary/50 border-t border-line font-medium [&>tr]:last:border-b-0", className),
64
+ className: cn("bg-neutral border-t border-line font-medium [&>tr]:last:border-b-0", className),
65
65
  ...props
66
66
  });
67
67
  }
68
68
  function TableRow({ className, ...props }) {
69
69
  return /* @__PURE__ */ jsx("tr", {
70
70
  "data-slot": "table-row",
71
- className: cn("hover:bg-highlighted data-selected:bg-primary data-selected:text-white data-selected:**:data-[slot=checkbox]:border-white border-b border-line motion-color", className),
71
+ className: cn("hover:bg-highlighted data-selected:bg-selected data-selected:text-white data-selected:**:data-[slot=checkbox]:border-white border-b border-line motion-color", className),
72
72
  ...props
73
73
  });
74
74
  }
package/dist/tabs.js CHANGED
@@ -30,7 +30,7 @@ function TabsList({ className, variant = "default", ...props }) {
30
30
  function TabsTrigger({ className, ...props }) {
31
31
  return /* @__PURE__ */ jsx(Tabs$1.Tab, {
32
32
  "data-slot": "tabs-trigger",
33
- className: cn("cursor-clickable text-muted hover:text-contrast relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-2 rounded-sm border border-transparent px-3 py-1 has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 text-sm font-medium whitespace-nowrap motion-color focus-visible:outline-hidden focus-visible:border-focus focus-visible:ring-focus/50 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[orientation=vertical]/tabs:py-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "data-active:bg-primary data-active:text-white", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:text-contrast", "after:bg-contrast after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-end-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className),
33
+ className: cn("cursor-clickable text-muted hover:text-contrast relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-2 rounded-sm border border-transparent px-3 py-1 has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 text-sm font-medium whitespace-nowrap motion-color focus-visible:outline-hidden focus-visible:border-focus focus-visible:ring-focus/50 focus-visible:ring-3 data-disabled:pointer-events-none data-disabled:opacity-30 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[orientation=vertical]/tabs:py-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "data-active:bg-selected data-active:text-white", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:text-contrast", "after:bg-contrast after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-end-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className),
34
34
  ...props
35
35
  });
36
36
  }
package/dist/textarea.js CHANGED
@@ -3,7 +3,7 @@ import { cn } from "./lib/utils.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { cva } from "class-variance-authority";
5
5
  //#region src/textarea.tsx
6
- const textareaVariants = cva("bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger resize-y resize-handle disabled:pointer-events-none placeholder:text-subtle flex field-sizing-content w-full rounded-md border border-transparent motion-color focus-visible:outline-hidden focus-visible:ring-3 disabled:opacity-30 aria-invalid:ring-3", {
6
+ const textareaVariants = cva("bg-neutral hover:border-focus focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger resize-y resize-handle disabled:pointer-events-none placeholder:text-muted flex field-sizing-content w-full rounded-md border border-transparent motion-color focus-visible:outline-hidden focus-visible:ring-3 disabled:opacity-30 aria-invalid:ring-3", {
7
7
  variants: { size: {
8
8
  xs: "min-h-12 rounded-sm px-2 py-1 text-xs",
9
9
  sm: "min-h-14 px-3 py-1.5 text-sm",
package/dist/toggle.js CHANGED
@@ -7,8 +7,8 @@ import { Toggle as Toggle$1 } from "@base-ui/react/toggle";
7
7
  const toggleVariants = cva("cursor-clickable focus-visible:border-focus focus-visible:ring-focus/50 aria-invalid:ring-danger/30 aria-invalid:border-danger rounded-md border border-transparent bg-clip-padding font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap motion-color data-disabled:pointer-events-none data-disabled:opacity-30 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 focus-visible:outline-hidden group/toggle select-none active:scale-[0.97]", {
8
8
  variants: {
9
9
  variant: {
10
- default: "text-contrast hover:bg-highlighted data-pressed:bg-primary data-pressed:text-white data-pressed:hover:bg-primary/80 not-supports-backdrop-filter:data-pressed:hover:bg-primary/90 data-pressed:hover:backdrop-blur-xl data-pressed:hover:backdrop-saturate-150",
11
- outline: "border-contour bg-foundation hover:bg-highlighted hover:text-contrast data-pressed:border-primary data-pressed:bg-primary data-pressed:text-white data-pressed:hover:bg-primary/80 not-supports-backdrop-filter:data-pressed:hover:bg-primary/90 data-pressed:hover:backdrop-blur-xl data-pressed:hover:backdrop-saturate-150"
10
+ default: "text-contrast hover:bg-highlighted data-pressed:bg-selected data-pressed:text-white data-pressed:hover:bg-selected/80 not-supports-backdrop-filter:data-pressed:hover:bg-selected/90 data-pressed:hover:backdrop-blur-xl data-pressed:hover:backdrop-saturate-150",
11
+ outline: "border-contour bg-foundation hover:bg-highlighted hover:text-contrast data-pressed:border-selected data-pressed:bg-selected data-pressed:text-white data-pressed:hover:bg-selected/80 not-supports-backdrop-filter:data-pressed:hover:bg-selected/90 data-pressed:hover:backdrop-blur-xl data-pressed:hover:backdrop-saturate-150"
12
12
  },
13
13
  size: {
14
14
  default: "h-9 min-w-9 gap-1.5 px-3 text-base has-data-[icon=inline-end]:pe-2.5 has-data-[icon=inline-start]:ps-2.5",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveso/ui",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Wave UI component library built on Base UI and Tailwind CSS",
5
5
  "type": "module",
6
6
  "sideEffects": [