@waveso/ui 0.19.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
 
@@ -85,8 +84,8 @@ Wave pays close attention to token taxonomy. Surfaces encode elevation; borders
85
84
  ### Text — contrast
86
85
 
87
86
  - `--contrast` — titles, primary text
88
- - `--muted` — body text, icons, any mark
89
- - `--subtle` — placeholders, hints
87
+ - `--muted` — body text, placeholders, icons, any mark
88
+ - `--subtle` — hints
90
89
 
91
90
  ### Brand — colour
92
91
 
@@ -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/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/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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveso/ui",
3
- "version": "0.19.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": [