@muten/shadcn 0.1.0 → 0.1.2
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 +64 -1
- package/README.md +7 -0
- package/kit.css +2289 -25
- package/motion.css +16 -1
- package/package.json +3 -2
- package/registry/accordion.muten +10 -0
- package/registry/agenda.js +72 -24
- package/registry/agenda.muten +30 -2
- package/registry/band.muten +1 -1
- package/registry/bio-play.muten +16 -0
- package/registry/booking-play.muten +43 -0
- package/registry/button.muten +3 -2
- package/registry/chat-play.muten +77 -0
- package/registry/day-plan.muten +40 -0
- package/registry/device-frame.muten +25 -7
- package/registry/drawer.muten +1 -1
- package/registry/gradient-blinds.js +88 -89
- package/registry/gradient-blinds.muten +11 -4
- package/registry/inbox-play.muten +56 -0
- package/registry/input-group.muten +3 -1
- package/registry/kit/board.js +1 -1
- package/registry/kit/collections.js +10 -2
- package/registry/kit/core.js +519 -99
- package/registry/kit/structure.js +7 -5
- package/registry/kit-widget.js +1 -1
- package/registry/legal-doc.muten +27 -0
- package/registry/modal-behavior.js +11 -0
- package/registry/modal.muten +2 -2
- package/registry/order-play.muten +33 -0
- package/registry/permission-matrix.muten +31 -5
- package/registry/phone-bar.muten +18 -0
- package/registry/phone-shell.muten +1 -1
- package/registry/product-play.muten +20 -0
- package/registry/screen-tour.js +59 -0
- package/registry/screen-tour.muten +10 -0
- package/registry/sidebar.muten +9 -0
- package/registry/site-footer.muten +37 -0
- package/registry/stamp-card.muten +5 -2
- package/registry/zoom-control.muten +3 -3
- package/registry.json +78 -0
- package/scripts/reuse-audit.mjs +37 -0
|
@@ -1,129 +1,128 @@
|
|
|
1
|
-
// GradientBlinds - ported from React Bits (reactbits.dev)
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// GradientBlinds - first ported from React Bits (reactbits.dev), then made calm: a WebGL fragment shader (via `ogl`)
|
|
2
|
+
// lays a gradient that is born on one side of the hero (`from`) and dissolves into the page over a long, eased fall,
|
|
3
|
+
// with soft blinds that only shade it (never down to black), a light that follows the pointer and only adds, and a
|
|
4
|
+
// fine grain. The fall is the canvas's own alpha, so it melts into any page background, light or dark.
|
|
5
|
+
// A Custom (graphics = the vanilla-JS escape); ogl loads via dynamic import() (a Custom is inlined into an IIFE, so a
|
|
6
|
+
// top-level import is illegal). Inputs: colors, from (top | bottom | left | right | center), angle, blindCount,
|
|
7
|
+
// blindMinWidth, strength (how much the blinds shade, 0..1), noise. It draws only while on screen; with reduced
|
|
8
|
+
// motion it is still (no drift, the light stays put). Needs `ogl`.
|
|
6
9
|
const MAX_COLORS = 8;
|
|
10
|
+
const FROM = { top: 0, bottom: 1, left: 2, right: 3, center: 4 };
|
|
7
11
|
const hexToRGB = (hex) => {
|
|
8
12
|
const c = String(hex).replace("#", "").padEnd(6, "0");
|
|
9
13
|
return [parseInt(c.slice(0, 2), 16) / 255, parseInt(c.slice(2, 4), 16) / 255, parseInt(c.slice(4, 6), 16) / 255];
|
|
10
14
|
};
|
|
11
15
|
const prepStops = (stops) => {
|
|
12
|
-
const base = (stops
|
|
16
|
+
const base = (stops.length ? stops : ["#9B80FF", "#6B46F2"]).slice(0, MAX_COLORS);
|
|
13
17
|
if (base.length === 1) base.push(base[0]);
|
|
18
|
+
const count = base.length;
|
|
14
19
|
while (base.length < MAX_COLORS) base.push(base[base.length - 1]);
|
|
15
|
-
return { arr: base.map(hexToRGB), count
|
|
20
|
+
return { arr: base.map(hexToRGB), count };
|
|
16
21
|
};
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const I = inputs || {};
|
|
21
|
-
const colors = (I.colors ? String(I.colors) : "#FF9FFC,#5227FF").split(",").map((s) => s.trim()).filter(Boolean);
|
|
22
|
-
const angle = num(I.angle, -25), noise = num(I.noise, 0.22);
|
|
23
|
-
const blindCount = num(I.blindCount, 16), blindMinWidth = num(I.blindMinWidth, 55);
|
|
24
|
-
const spotRadius = num(I.spotlightRadius, 0.6), spotSoft = num(I.spotlightSoftness, 1), spotOpacity = num(I.spotlightOpacity, 0.75);
|
|
25
|
-
const damp = num(I.mouseDampening, 0.15), distort = num(I.distort, 0);
|
|
26
|
-
const shineFlip = I.shineDirection === "right" ? 1 : 0, mirror = I.mirror ? 1 : 0;
|
|
27
|
-
|
|
28
|
-
import("ogl").then(({ Renderer, Program, Mesh, Triangle }) => {
|
|
29
|
-
const renderer = new Renderer({ dpr: Math.min(2, window.devicePixelRatio || 1), alpha: true, antialias: true });
|
|
30
|
-
const gl = renderer.gl;
|
|
31
|
-
const canvas = gl.canvas;
|
|
32
|
-
canvas.style.cssText = "position:absolute;inset:0;width:100%;height:100%;display:block;-webkit-mask-image:linear-gradient(to bottom,#000 42%,transparent 94%);mask-image:linear-gradient(to bottom,#000 42%,transparent 94%)";
|
|
33
|
-
el.appendChild(canvas);
|
|
34
|
-
|
|
35
|
-
const vertex = "attribute vec2 position;attribute vec2 uv;varying vec2 vUv;void main(){vUv=uv;gl_Position=vec4(position,0.0,1.0);}";
|
|
36
|
-
const fragment = `#ifdef GL_ES
|
|
37
|
-
precision mediump float;
|
|
38
|
-
#endif
|
|
23
|
+
const vertex = "attribute vec2 position;attribute vec2 uv;varying vec2 vUv;void main(){vUv=uv;gl_Position=vec4(position,0.0,1.0);}";
|
|
24
|
+
const fragment = `precision mediump float;
|
|
39
25
|
uniform vec3 iResolution; uniform vec2 iMouse; uniform float iTime;
|
|
40
|
-
uniform float uAngle, uNoise, uBlindCount,
|
|
26
|
+
uniform float uAngle, uNoise, uBlindCount, uStrength, uFrom;
|
|
41
27
|
uniform vec3 uColor0,uColor1,uColor2,uColor3,uColor4,uColor5,uColor6,uColor7; uniform int uColorCount;
|
|
42
28
|
varying vec2 vUv;
|
|
43
29
|
float rand(vec2 co){ return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453); }
|
|
44
30
|
vec2 rotate2D(vec2 p, float a){ float c=cos(a), s=sin(a); return mat2(c,-s,s,c)*p; }
|
|
45
|
-
vec3
|
|
46
|
-
float
|
|
47
|
-
float scaled=tt*float(count-1); float seg=floor(scaled); float f=fract(scaled);
|
|
31
|
+
vec3 gradient(float t){
|
|
32
|
+
float scaled = clamp(t,0.0,1.0) * float(uColorCount-1); float seg = floor(scaled); float f = smoothstep(0.0,1.0,fract(scaled));
|
|
48
33
|
if(seg<1.0) return mix(uColor0,uColor1,f);
|
|
49
|
-
if(seg<2.0
|
|
50
|
-
if(seg<3.0
|
|
51
|
-
if(seg<4.0
|
|
52
|
-
if(seg<5.0
|
|
53
|
-
if(seg<6.0
|
|
54
|
-
|
|
55
|
-
if(count>7) return uColor7; if(count>6) return uColor6; if(count>5) return uColor5;
|
|
56
|
-
if(count>4) return uColor4; if(count>3) return uColor3; if(count>2) return uColor2; return uColor1;
|
|
34
|
+
if(seg<2.0) return mix(uColor1,uColor2,f);
|
|
35
|
+
if(seg<3.0) return mix(uColor2,uColor3,f);
|
|
36
|
+
if(seg<4.0) return mix(uColor3,uColor4,f);
|
|
37
|
+
if(seg<5.0) return mix(uColor4,uColor5,f);
|
|
38
|
+
if(seg<6.0) return mix(uColor5,uColor6,f);
|
|
39
|
+
return mix(uColor6,uColor7,f);
|
|
57
40
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if(
|
|
64
|
-
|
|
65
|
-
vec3 base = getGradientColor(t);
|
|
66
|
-
vec2 offset = vec2(iMouse.x/iResolution.x, iMouse.y/iResolution.y);
|
|
67
|
-
float d = length(uv0-offset); float r = max(uSpotlightRadius, 1e-4); float dn = d/r;
|
|
68
|
-
float spot = (1.0 - 2.0*pow(dn, uSpotlightSoftness)) * uSpotlightOpacity;
|
|
69
|
-
vec3 cir = vec3(spot);
|
|
70
|
-
float stripe = fract(uvMod.x * max(uBlindCount,1.0)); if(uShineFlip>0.5) stripe = 1.0-stripe;
|
|
71
|
-
vec3 ran = vec3(stripe);
|
|
72
|
-
vec3 col = cir + base - ran;
|
|
73
|
-
col += (rand(gl_FragCoord.xy + iTime) - 0.5) * uNoise;
|
|
74
|
-
fragColor = vec4(col, 1.0);
|
|
41
|
+
// how far a point is from the side the colour is born on: 0 there, 1 at the far side
|
|
42
|
+
float away(vec2 uv){
|
|
43
|
+
if(uFrom<0.5) return 1.0-uv.y;
|
|
44
|
+
if(uFrom<1.5) return uv.y;
|
|
45
|
+
if(uFrom<2.5) return uv.x;
|
|
46
|
+
if(uFrom<3.5) return 1.0-uv.x;
|
|
47
|
+
vec2 d = (uv-0.5)*vec2(iResolution.x/iResolution.y,1.0); return clamp(length(d)*0.9,0.0,1.0);
|
|
75
48
|
}
|
|
76
|
-
void main(){
|
|
49
|
+
void main(){
|
|
50
|
+
vec2 uv0 = vUv;
|
|
51
|
+
float aspect = iResolution.x / iResolution.y;
|
|
52
|
+
vec2 p = uv0*2.0-1.0; p.x *= aspect; vec2 pr = rotate2D(p, uAngle); pr.x /= aspect; vec2 uv = pr*0.5+0.5;
|
|
53
|
+
float t = uv.x + iTime*0.012;
|
|
54
|
+
float wave = fract(t*0.5)*2.0; wave = wave > 1.0 ? 2.0-wave : wave; // there and back, so the colours never jump
|
|
55
|
+
vec3 col = gradient(wave);
|
|
56
|
+
// blinds: a rounded light-to-shade profile per blind, shading the colour by at most uStrength
|
|
57
|
+
float stripe = fract(t * uBlindCount);
|
|
58
|
+
float shade = 0.5 - 0.5*cos(stripe*6.28318);
|
|
59
|
+
col *= 1.0 - uStrength*shade;
|
|
60
|
+
// the light under the pointer: a wide, soft lift
|
|
61
|
+
vec2 m = iMouse / iResolution.xy; vec2 dm = (uv0-m)*vec2(aspect,1.0);
|
|
62
|
+
col += exp(-dot(dm,dm)*3.5) * 0.18;
|
|
63
|
+
col += (rand(gl_FragCoord.xy + floor(iTime*24.0)) - 0.5) * uNoise;
|
|
64
|
+
// the fall into the page: full near the side it comes from, gone by the far side, eased so there is no edge
|
|
65
|
+
float a = 1.0 - smoothstep(0.0, 1.0, away(uv0));
|
|
66
|
+
a = a*a*(3.0-2.0*a);
|
|
67
|
+
gl_FragColor = vec4(col*a, a);
|
|
68
|
+
}`;
|
|
69
|
+
|
|
70
|
+
export function mount(el, inputs) {
|
|
71
|
+
const num = (v, d) => { const n = Number(v); return Number.isFinite(n) ? n : d; };
|
|
72
|
+
const I = inputs || {};
|
|
73
|
+
const colors = String(I.colors || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
74
|
+
const from = FROM[I.from] ?? FROM.top;
|
|
75
|
+
const angle = num(I.angle, -25), noise = num(I.noise, 0.035), strength = num(I.strength, 0.22);
|
|
76
|
+
const blindCount = num(I.blindCount, 12), blindMinWidth = num(I.blindMinWidth, 80);
|
|
77
|
+
const still = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
78
|
+
|
|
79
|
+
import("ogl").then(({ Renderer, Program, Mesh, Triangle }) => {
|
|
80
|
+
const renderer = new Renderer({ dpr: Math.min(2, window.devicePixelRatio || 1), alpha: true, premultipliedAlpha: true });
|
|
81
|
+
const gl = renderer.gl;
|
|
82
|
+
const canvas = gl.canvas;
|
|
83
|
+
canvas.style.cssText = "position:absolute;inset:0;width:100%;height:100%;display:block";
|
|
84
|
+
el.appendChild(canvas);
|
|
77
85
|
|
|
78
86
|
const { arr, count } = prepStops(colors);
|
|
79
87
|
const uniforms = {
|
|
80
|
-
iResolution: { value: [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
uBlindCount: { value: Math.max(1, blindCount) },
|
|
84
|
-
uSpotlightRadius: { value: spotRadius }, uSpotlightSoftness: { value: spotSoft }, uSpotlightOpacity: { value: spotOpacity },
|
|
85
|
-
uMirror: { value: mirror }, uDistort: { value: distort }, uShineFlip: { value: shineFlip },
|
|
88
|
+
iResolution: { value: [1, 1, 1] }, iMouse: { value: [0, 0] }, iTime: { value: 0 },
|
|
89
|
+
uAngle: { value: (angle * Math.PI) / 180 }, uNoise: { value: noise }, uStrength: { value: strength },
|
|
90
|
+
uBlindCount: { value: blindCount }, uFrom: { value: from }, uColorCount: { value: count },
|
|
86
91
|
uColor0: { value: arr[0] }, uColor1: { value: arr[1] }, uColor2: { value: arr[2] }, uColor3: { value: arr[3] },
|
|
87
92
|
uColor4: { value: arr[4] }, uColor5: { value: arr[5] }, uColor6: { value: arr[6] }, uColor7: { value: arr[7] },
|
|
88
|
-
uColorCount: { value: count },
|
|
89
93
|
};
|
|
90
|
-
const
|
|
91
|
-
const mesh = new Mesh(gl, { geometry: new Triangle(gl), program });
|
|
94
|
+
const mesh = new Mesh(gl, { geometry: new Triangle(gl), program: new Program(gl, { vertex, fragment, uniforms, transparent: true }) });
|
|
92
95
|
|
|
93
|
-
let
|
|
96
|
+
let tx = 0, ty = 0, last = 0, seen = true, placed = false;
|
|
97
|
+
const draw = () => { try { renderer.render({ scene: mesh }); } catch (_) { /* context lost */ } };
|
|
94
98
|
const resize = () => {
|
|
95
99
|
const rect = el.getBoundingClientRect();
|
|
96
100
|
renderer.setSize(rect.width || 1, rect.height || 1);
|
|
97
101
|
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
uniforms.uBlindCount.value = Math.max(1, Math.min(blindCount, Math.floor((rect.width || 1) / blindMinWidth)));
|
|
103
|
+
if (!placed) { placed = true; tx = gl.drawingBufferWidth / 2; ty = gl.drawingBufferHeight * 0.7; uniforms.iMouse.value = [tx, ty]; }
|
|
104
|
+
draw();
|
|
101
105
|
};
|
|
102
|
-
let tx = 0, ty = 0, last = 0;
|
|
103
106
|
resize();
|
|
104
107
|
try { new ResizeObserver(resize).observe(el); } catch (_) {}
|
|
108
|
+
try { new IntersectionObserver(([e]) => { seen = e.isIntersecting; }).observe(el); } catch (_) {}
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
if (still) return;
|
|
111
|
+
// on window, so the light follows the pointer even over the hero's content
|
|
107
112
|
window.addEventListener("pointermove", (e) => {
|
|
108
|
-
const rect = canvas.getBoundingClientRect();
|
|
109
|
-
const scale = renderer.dpr || 1;
|
|
113
|
+
const rect = canvas.getBoundingClientRect(), scale = renderer.dpr || 1;
|
|
110
114
|
tx = (e.clientX - rect.left) * scale;
|
|
111
115
|
ty = (rect.height - (e.clientY - rect.top)) * scale;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const loop = (t) => {
|
|
116
|
+
}, { passive: true });
|
|
117
|
+
const loop = (time) => {
|
|
116
118
|
requestAnimationFrame(loop);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
cur[0] += (tx - cur[0]) * factor; cur[1] += (ty - cur[1]) * factor;
|
|
124
|
-
}
|
|
125
|
-
try { renderer.render({ scene: mesh }); } catch (e) { /* context lost */ }
|
|
119
|
+
if (!seen || document.hidden) { last = time; return; }
|
|
120
|
+
const dt = last ? (time - last) / 1000 : 0; last = time;
|
|
121
|
+
const ease = 1 - Math.exp(-dt / 0.35), cur = uniforms.iMouse.value;
|
|
122
|
+
cur[0] += (tx - cur[0]) * ease; cur[1] += (ty - cur[1]) * ease;
|
|
123
|
+
uniforms.iTime.value = time * 0.001;
|
|
124
|
+
draw();
|
|
126
125
|
};
|
|
127
126
|
requestAnimationFrame(loop);
|
|
128
|
-
}).catch(() => { /* no ogl
|
|
127
|
+
}).catch(() => { /* no ogl: the hero keeps its flat background */ });
|
|
129
128
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
# GradientBlinds - an animated gradient behind a hero
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# GradientBlinds - an animated gradient behind a hero, drawn on the GPU (WebGL, through `ogl`: npm i ogl): the colour is
|
|
2
|
+
# born on one side (`from`) and melts into the page over a long, eased fall, with soft diagonal blinds, a light that
|
|
3
|
+
# follows the pointer and a fine grain. Make it the first child of the hero (`cx-hero-blinds`) and it fills it; the
|
|
4
|
+
# content goes on top. It melts into any page background, light or dark, and is still under reduced motion.
|
|
4
5
|
# Stack class("cx-hero-blinds") { GradientBlinds(colors: "#9B80FF,#6B46F2") Stack class("…") { … } }
|
|
6
|
+
# GradientBlinds(colors: "#FFB38A,#FF6F91,#9B80FF", from: "left", angle: 30, strength: 0.4, blinds: 8)
|
|
7
|
+
# colors: hex stops separated by commas. from: top | bottom | left | right | center. angle: the blinds' tilt in degrees
|
|
8
|
+
# (-25). strength: how much they shade, 0 (none) to 1 (0.22). blinds: how many at most (12; fewer on a narrow hero).
|
|
9
|
+
# noise: the grain, 0 to 0.2 (0.035).
|
|
5
10
|
# shadcn-for-muten · Custom · needs ogl · host JS gradient-blinds.js.
|
|
6
|
-
part GradientBlinds(colors: text = "#9B80FF,#6B46F2"
|
|
11
|
+
part GradientBlinds(colors: text = "#9B80FF,#6B46F2", from: text = "top", angle: number = -25, strength: number = 0.22, blinds: number = 12, noise: number = 0.035) {
|
|
12
|
+
Custom GradientBlinds inputs(colors: $colors, from: $from, angle: $angle, strength: $strength, blindCount: $blinds, noise: $noise) class("cx-blinds")
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# InboxPlay - a messages list shown, not used: the shop's inbox as the channel's app draws it (skin: instagram |
|
|
2
|
+
# whatsapp), with the unread ones in bold and their dot. For a landing, beside «nobody answered them»; the real inbox
|
|
3
|
+
# is Messaging. The rows are a picture (aria-hidden); `label` says what it shows.
|
|
4
|
+
# InboxPlay(title: "brumabarberia", tabs: "Principal", requests: "Solicitudes (12)", label: "…") {
|
|
5
|
+
# InboxPlayRow(name: "lau.mtz", text: "precio??", time: "2 h", unread: "yes")
|
|
6
|
+
# InboxPlayRow(name: "carlos.r", text: "Visto", time: "1 d", photo: "/face.webp")
|
|
7
|
+
# }
|
|
8
|
+
# search (a placeholder) draws the app's search bar; InboxPlayNotes, InboxPlayTabs and InboxPlayFilters go in the list
|
|
9
|
+
# in that order. A row takes ring (yes: a story ring), badge (WhatsApp's unread count), tag (who answered: «Asistente»);
|
|
10
|
+
# and Instagram rows show the camera at the end.
|
|
11
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-ip).
|
|
12
|
+
part InboxPlay(title: text, tabs: text = "", requests: text = "", skin: text = "instagram", label: text = "", search: text = "") {
|
|
13
|
+
Stack class("cx-ip cx-play") data(skin: $skin) aria(role: "img", label: $label) {
|
|
14
|
+
Stack class("cx-ip-head") aria(hidden: true) {
|
|
15
|
+
Span "{$title}" class("cx-ip-t")
|
|
16
|
+
Icon "lucide:chevron-down"
|
|
17
|
+
Stack class("cx-ip-ic") { Icon "lucide:square-pen" }
|
|
18
|
+
}
|
|
19
|
+
when $search != "" { Stack class("cx-ip-search") aria(hidden: true) { Icon "lucide:search" Span "{$search}" } }
|
|
20
|
+
when $tabs != "" { Stack class("cx-ip-tabs") aria(hidden: true) { Span "{$tabs}" data(on: "yes") Span "{$requests}" } }
|
|
21
|
+
Stack class("cx-ip-list") aria(hidden: true) { slot }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
part InboxPlayRow(name: text, text: text, time: text = "", photo: text = "", unread: text = "", ring: text = "", badge: text = "", tag: text = "") {
|
|
25
|
+
Stack class("cx-ip-row") data(unread: $unread, ring: $ring) {
|
|
26
|
+
Stack class("cx-ip-avw") {
|
|
27
|
+
when $photo != "" { Image "{$photo}" alt("") class("cx-ip-av") }
|
|
28
|
+
when $photo == "" { Span "{initial($name)}" class("cx-ip-av cx-ip-init") }
|
|
29
|
+
}
|
|
30
|
+
Stack class("cx-ip-tx") {
|
|
31
|
+
Stack class("cx-ip-top") { Span "{$name}" class("cx-ip-n") Span "{$time}" class("cx-ip-tr") }
|
|
32
|
+
Stack class("cx-ip-l") {
|
|
33
|
+
when $tag != "" { Span "{$tag}" class("cx-ip-tag") }
|
|
34
|
+
Span "{$text}" class("cx-ip-m")
|
|
35
|
+
Span "· {$time}" class("cx-ip-time")
|
|
36
|
+
when $badge != "" { Span "{$badge}" class("cx-ip-badge") }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
Span "" class("cx-ip-dot")
|
|
40
|
+
Stack class("cx-ip-cam") { Icon "lucide:camera" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
# Instagram's notes over the list: a face with its short note above it.
|
|
44
|
+
part InboxPlayNotes() { Stack class("cx-ip-notes") { slot } }
|
|
45
|
+
part InboxPlayNote(name: text, note: text = "", photo: text = "") {
|
|
46
|
+
Stack class("cx-ip-note") {
|
|
47
|
+
when $note != "" { Span "{$note}" class("cx-ip-bub") }
|
|
48
|
+
when $photo != "" { Image "{$photo}" alt("") class("cx-ip-av") }
|
|
49
|
+
when $photo == "" { Span "{initial($name)}" class("cx-ip-av cx-ip-init") }
|
|
50
|
+
Span "{$name}" class("cx-ip-nn")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
# Tabs as the app draws them (Instagram: Principal · General … Solicitudes) and WhatsApp's filter chips.
|
|
54
|
+
part InboxPlayTabs(primary: text, general: text = "", requests: text = "") { Stack class("cx-ip-tabs") { Span "{$primary}" data(on: "yes") when $general != "" { Span "{$general}" } when $requests != "" { Span "{$requests}" class("cx-ip-req") } } }
|
|
55
|
+
part InboxPlayFilters() { Stack class("cx-ip-filters") { slot } }
|
|
56
|
+
part InboxPlayFilter(label: text, on: text = "") { Span "{$label}" class("cx-ip-filter") data(on: $on) }
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
# shadcn-for-muten · part · no deps · Nova (.cn-input-group).
|
|
10
10
|
part InputGroup(size: text = "default", variant: text = "default") { Stack class("cn-input-group group/input-group") data(slot: "input-group", size: $size, variant: $variant) aria(role: "group") { slot } }
|
|
11
11
|
part InputAddon(align: text = "inline-start") { Stack class("cn-input-group-addon cn-input-group-addon-align-{$align}") data(slot: "input-group-addon", align: $align) { slot } }
|
|
12
|
-
|
|
12
|
+
# GroupInput - the field inside an InputGroup. `label` names it for a screen reader when the group shows no label of its own
|
|
13
|
+
# (a literal: an interpolated arg cannot reach aria(); without it the placeholder names the field).
|
|
14
|
+
part GroupInput(value: text, placeholder: text = "", label: text = "") { SearchField bind($value) "{$placeholder}" class("cn-input cn-input-group-input cx-num") data(slot: "input-group-control") aria(label: $label) }
|
|
13
15
|
part SearchInput(value: text, placeholder: text = "Buscar", onClear: action, size: text = "default", variant: text = "default") {
|
|
14
16
|
Stack class("cn-input-group group/input-group") data(slot: "input-group", size: $size, variant: $variant) aria(role: "group") {
|
|
15
17
|
Stack class("cn-input-group-addon cn-input-group-addon-align-inline-start") data(slot: "input-group-addon", align: "inline-start") { Icon "lucide:search" }
|
package/registry/kit/board.js
CHANGED
|
@@ -91,7 +91,7 @@ export function orderBoard(root, i, core, on) {
|
|
|
91
91
|
const groups = GROUP_ORDER.filter((k) => c[k]).map((k) => GROUPS[k](c[k], c));
|
|
92
92
|
const next = c.actions.find(([a]) => a === 'advance');
|
|
93
93
|
return `<div data-slot="card-header" class="cn-card-header">${person}</div>`
|
|
94
|
-
+ (groups.length ? `<div data-slot="card-content" class="cn-card-content"><div data-slot="item-group" class="cn-item-group"
|
|
94
|
+
+ (groups.length ? `<div data-slot="card-content" class="cn-card-content"><div data-slot="item-group" class="cn-item-group">${groups.join(ui.itemSeparator())}</div></div>` : '')
|
|
95
95
|
+ (next ? `<div data-slot="card-footer" class="cn-card-footer">${ui.button({ label: `${next[1]}<svg><use href="#right"/></svg>`, variant: 'default', size: 'default', attrs: 'data-bact="advance"', cls: 'cx-fill' })}</div>` : '');
|
|
96
96
|
};
|
|
97
97
|
|
|
@@ -115,7 +115,8 @@ export function carouselBehavior(root, i, core) {
|
|
|
115
115
|
const thumbOf = (k) => { const media = cards[k].querySelector('img, [role=img], .cx-gallery-img'); const bg = media ? getComputedStyle(media).backgroundImage : ''; return media?.tagName === 'IMG' ? `<img src="${media.src}" alt="">` : `<span class="cx-thumb-fill" style="background-image:${bg}"></span>`; };
|
|
116
116
|
const paint = () => {
|
|
117
117
|
const n = pages(), cur = current();
|
|
118
|
-
|
|
118
|
+
// the dots are tabs, so their holder is the tablist (a tab without one fails the a11y check)
|
|
119
|
+
if (indicator === 'dots') { ind.setAttribute('role', 'tablist'); ind.setAttribute('aria-label', 'Diapositivas'); if (ind.childElementCount !== n) ind.innerHTML = Array.from({ length: n }, (_, k) => ui.button({ variant: 'ghost', size: 'icon-xs', cls: 'cx-dotb', attrs: `role="tab" data-go="${k}" aria-label="Ir a ${k + 1} de ${n}"`, label: '<span class="cx-dot-mark"></span>' })).join(''); $$('[data-go]', ind).forEach((d, k) => d.setAttribute('aria-selected', k === cur)); }
|
|
119
120
|
if (indicator === 'progress') ind.innerHTML = `<div class="cx-hstack" style="gap:10px;flex-wrap:nowrap">${ui.progress(((cur + 1) / n) * 100, `Diapositiva ${cur + 1} de ${n}`)}${ui.text(`${cur + 1} / ${n}`, 'caption')}</div>`;
|
|
120
121
|
if (indicator === 'count') ind.innerHTML = ui.text(`${cur + 1} de ${n}`, 'caption');
|
|
121
122
|
if (indicator === 'thumbs') { if (!ind.childElementCount) ind.innerHTML = `<div class="cx-hstack" role="tablist" aria-label="Miniaturas">${cards.map((_, k) => ui.button({ variant: 'ghost', size: 'icon-lg', cls: 'cx-thumb', attrs: `role="tab" data-go="${k}" aria-label="Foto ${k + 1}"`, label: thumbOf(k) })).join('')}</div>`; $$('[data-go]', ind).forEach((d, k) => { d.setAttribute('aria-selected', k === cur); d.setAttribute('aria-pressed', k === cur); }); }
|
|
@@ -135,7 +136,14 @@ export function carouselBehavior(root, i, core) {
|
|
|
135
136
|
let drag = null;
|
|
136
137
|
track.addEventListener('pointerdown', (e) => { if (e.pointerType !== 'mouse' || e.button > 0) return; drag = { x: e.clientX, left: track.scrollLeft, moved: false }; track.classList.add('dragging'); });
|
|
137
138
|
addEventListener('pointermove', (e) => { if (!drag) return; const dx = e.clientX - drag.x; if (Math.abs(dx) > 4) drag.moved = true; track.scrollLeft = drag.left - dx; });
|
|
138
|
-
|
|
139
|
+
// A drag lets go between two pages (snapping is off while dragging): settle on the nearest one, as a swipe does,
|
|
140
|
+
// or the track rests half-way and the dots and arrows disagree about where it is.
|
|
141
|
+
const nearest = () => {
|
|
142
|
+
if (center) return current();
|
|
143
|
+
if (track.scrollLeft >= track.scrollWidth - track.clientWidth - 4) return pages() - 1;
|
|
144
|
+
return Math.max(0, Math.min(pages() - 1, Math.round(track.scrollLeft / (step() * perPage()))));
|
|
145
|
+
};
|
|
146
|
+
addEventListener('pointerup', () => { if (!drag) return; const moved = drag.moved; drag = null; track.classList.remove('dragging'); if (moved) { go(nearest()); track.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); }, { capture: true, once: true }); } });
|
|
139
147
|
let playing = !!autoplay && !matchMedia('(prefers-reduced-motion: reduce)').matches, hold = false;
|
|
140
148
|
if (autoplay) {
|
|
141
149
|
if (!playing) { const b = $('[data-car="play"]', root); b.innerHTML = ui.icon('play'); b.setAttribute('aria-label', 'Reproducir'); }
|