@exegia/corpora-ui 0.5.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 +21 -0
- package/dist-lib/components/blocks/auth/auth-shell.d.ts +56 -0
- package/dist-lib/components/blocks/auth/code-auth-block.d.ts +22 -0
- package/dist-lib/components/blocks/auth/forgot-password-block.d.ts +15 -0
- package/dist-lib/components/blocks/auth/login-block.d.ts +25 -0
- package/dist-lib/components/blocks/auth/signup-block.d.ts +26 -0
- package/dist-lib/components/composed/password-input.d.ts +30 -0
- package/dist-lib/components/composed/social-providers.d.ts +22 -0
- package/dist-lib/components/docs/code-block.d.ts +4 -0
- package/dist-lib/components/docs/component-preview.d.ts +10 -0
- package/dist-lib/components/docs/demo-controls.d.ts +28 -0
- package/dist-lib/components/docs/props-table.d.ts +4 -0
- package/dist-lib/components/theme-provider.d.ts +15 -0
- package/dist-lib/components/ui/button.d.ts +28 -0
- package/dist-lib/components/ui/card.d.ts +16 -0
- package/dist-lib/components/ui/checkbox.d.ts +7 -0
- package/dist-lib/components/ui/field.d.ts +10 -0
- package/dist-lib/components/ui/frame.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-button.d.ts +6 -0
- package/dist-lib/components/ui/glasscn/liquid-glass.d.ts +31 -0
- package/dist-lib/components/ui/input-group.d.ts +13 -0
- package/dist-lib/components/ui/input.d.ts +9 -0
- package/dist-lib/components/ui/label.d.ts +11 -0
- package/dist-lib/components/ui/otp-field.d.ts +11 -0
- package/dist-lib/components/ui/separator.d.ts +4 -0
- package/dist-lib/components/ui/spinner.d.ts +3 -0
- package/dist-lib/components/ui/textarea.d.ts +8 -0
- package/dist-lib/index.d.ts +29 -0
- package/dist-lib/index.js +1617 -0
- package/dist-lib/index.js.map +1 -0
- package/dist-lib/lib/auth-accent.d.ts +33 -0
- package/dist-lib/lib/glass-variants.d.ts +6 -0
- package/dist-lib/lib/sound.d.ts +13 -0
- package/dist-lib/lib/utils.d.ts +2 -0
- package/dist-lib/lib/utils.test.d.ts +1 -0
- package/package.json +80 -0
- package/src/components/blocks/.gitkeep +0 -0
- package/src/components/blocks/auth/auth-shell.tsx +266 -0
- package/src/components/blocks/auth/code-auth-block.tsx +190 -0
- package/src/components/blocks/auth/forgot-password-block.tsx +123 -0
- package/src/components/blocks/auth/login-block.tsx +239 -0
- package/src/components/blocks/auth/signup-block.tsx +260 -0
- package/src/components/composed/.gitkeep +0 -0
- package/src/components/composed/password-input.tsx +179 -0
- package/src/components/composed/social-providers.tsx +89 -0
- package/src/components/docs/code-block.tsx +8 -0
- package/src/components/docs/component-preview.tsx +28 -0
- package/src/components/docs/demo-controls.tsx +118 -0
- package/src/components/docs/props-table.tsx +35 -0
- package/src/components/theme-provider.tsx +230 -0
- package/src/components/ui/button.tsx +199 -0
- package/src/components/ui/card.tsx +253 -0
- package/src/components/ui/checkbox.tsx +83 -0
- package/src/components/ui/field.tsx +89 -0
- package/src/components/ui/frame.tsx +87 -0
- package/src/components/ui/glasscn/glass-button.tsx +25 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +276 -0
- package/src/components/ui/input-group.tsx +105 -0
- package/src/components/ui/input.tsx +70 -0
- package/src/components/ui/label.tsx +36 -0
- package/src/components/ui/otp-field.tsx +88 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/spinner.tsx +17 -0
- package/src/components/ui/textarea.tsx +58 -0
- package/src/index.css +171 -0
- package/src/index.ts +37 -0
- package/src/lib/auth-accent.ts +49 -0
- package/src/lib/glass-variants.ts +57 -0
- package/src/lib/sound.ts +39 -0
- package/src/lib/utils.test.ts +22 -0
- package/src/lib/utils.ts +6 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export function Frame({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={cn(
|
|
11
|
+
"relative flex flex-col rounded-2xl bg-muted/72 p-1",
|
|
12
|
+
"*:[[data-slot=frame-panel]+[data-slot=frame-panel]]:mt-1",
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
data-slot="frame"
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function FramePanel({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
"relative rounded-xl border bg-background bg-clip-padding p-5 shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
data-slot="frame-panel"
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function FrameHeader({
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<"header">): React.ReactElement {
|
|
41
|
+
return (
|
|
42
|
+
<header
|
|
43
|
+
className={cn("flex flex-col px-5 py-4", className)}
|
|
44
|
+
data-slot="frame-panel-header"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function FrameTitle({
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={cn("font-semibold text-sm", className)}
|
|
57
|
+
data-slot="frame-panel-title"
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function FrameDescription({
|
|
64
|
+
className,
|
|
65
|
+
...props
|
|
66
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
70
|
+
data-slot="frame-panel-description"
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function FrameFooter({
|
|
77
|
+
className,
|
|
78
|
+
...props
|
|
79
|
+
}: React.ComponentProps<"footer">): React.ReactElement {
|
|
80
|
+
return (
|
|
81
|
+
<footer
|
|
82
|
+
className={cn("px-5 py-4", className)}
|
|
83
|
+
data-slot="frame-panel-footer"
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { FrostGlassVariantProp } from "@/lib/glass-variants";
|
|
4
|
+
|
|
5
|
+
import { Button, type ButtonProps } from "../button";
|
|
6
|
+
|
|
7
|
+
type GlassButtonProps = Omit<ButtonProps, "variant" | "glassVariant"> &
|
|
8
|
+
FrostGlassVariantProp;
|
|
9
|
+
|
|
10
|
+
/** @deprecated Use `<Button variant="glass" glassVariant="…">` instead. */
|
|
11
|
+
function GlassButton({
|
|
12
|
+
glassVariant = "liquid-refract",
|
|
13
|
+
...props
|
|
14
|
+
}: GlassButtonProps) {
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
data-slot="glass-button"
|
|
18
|
+
variant="glass"
|
|
19
|
+
glassVariant={glassVariant}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { GlassButton };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type CSSProperties,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
forwardRef,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useId,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from "react";
|
|
14
|
+
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
|
|
17
|
+
type MapGeometry = {
|
|
18
|
+
width: number; // element width, CSS px, integer
|
|
19
|
+
height: number; // element height, CSS px, integer
|
|
20
|
+
radius: number; // corner radius, CSS px, already clamped to min(width, height) / 2
|
|
21
|
+
bezel: number; // fraction (0..1] of the half-min-dimension used as the refractive band
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const MAX_TEXTURE_SIZE = 480;
|
|
25
|
+
const EDGE_TAPER_PX = 1.25;
|
|
26
|
+
|
|
27
|
+
const displacementMapCache = new Map<string, string>();
|
|
28
|
+
|
|
29
|
+
// CSS.supports("backdrop-filter", "url(#f)") returns true in Safari and
|
|
30
|
+
// Firefox (they parse the value) but neither browser actually renders SVG
|
|
31
|
+
// filters through backdrop-filter. UA sniffing is the only reliable approach.
|
|
32
|
+
// When WebKit ships support (track WebKit bug 245510), loosen this function.
|
|
33
|
+
function supportsSvgBackdropFilter() {
|
|
34
|
+
if (typeof navigator === "undefined") return false;
|
|
35
|
+
const ua = navigator.userAgent;
|
|
36
|
+
const isChromium = /Chrom(e|ium)/.test(ua) || /Edg\//.test(ua);
|
|
37
|
+
return isChromium && !/Firefox/.test(ua);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// NOTE: Per-corner radii and percentage radii are NOT supported. The map
|
|
41
|
+
// reads only borderTopLeftRadius in px. If a future consumer needs
|
|
42
|
+
// asymmetric corners, extend the corner SDF branch to accept four radii.
|
|
43
|
+
//
|
|
44
|
+
// NOTE: Map regeneration renders a ≤480 px texture and encodes a PNG for
|
|
45
|
+
// each unique (width, height, radius, bezel). Continuously animating the
|
|
46
|
+
// element's SIZE will thrash the cache; animate transform: scale instead.
|
|
47
|
+
function createDisplacementMap({ width, height, radius, bezel }: MapGeometry) {
|
|
48
|
+
const cacheKey = `${width}:${height}:${radius}:${bezel}`;
|
|
49
|
+
const cached = displacementMapCache.get(cacheKey);
|
|
50
|
+
if (cached !== undefined) return cached;
|
|
51
|
+
|
|
52
|
+
const downscale = Math.min(1, MAX_TEXTURE_SIZE / Math.max(width, height));
|
|
53
|
+
const texWidth = Math.max(2, Math.round(width * downscale));
|
|
54
|
+
const texHeight = Math.max(2, Math.round(height * downscale));
|
|
55
|
+
|
|
56
|
+
const canvas = document.createElement("canvas");
|
|
57
|
+
canvas.width = texWidth;
|
|
58
|
+
canvas.height = texHeight;
|
|
59
|
+
const ctx = canvas.getContext("2d");
|
|
60
|
+
if (!ctx) return "";
|
|
61
|
+
|
|
62
|
+
const image = ctx.createImageData(texWidth, texHeight);
|
|
63
|
+
const data = image.data;
|
|
64
|
+
const halfWidth = width / 2;
|
|
65
|
+
const halfHeight = height / 2;
|
|
66
|
+
const r = Math.min(radius, halfWidth, halfHeight);
|
|
67
|
+
const bezelPx = Math.max(1, bezel * Math.min(halfWidth, halfHeight));
|
|
68
|
+
|
|
69
|
+
for (let ty = 0; ty < texHeight; ty += 1) {
|
|
70
|
+
for (let tx = 0; tx < texWidth; tx += 1) {
|
|
71
|
+
// Texel center in element coordinates, origin at element center.
|
|
72
|
+
const px = ((tx + 0.5) / texWidth) * width - halfWidth;
|
|
73
|
+
const py = ((ty + 0.5) / texHeight) * height - halfHeight;
|
|
74
|
+
|
|
75
|
+
// Signed distance + outward normal of the rounded-rectangle contour.
|
|
76
|
+
const qx = Math.abs(px) - (halfWidth - r);
|
|
77
|
+
const qy = Math.abs(py) - (halfHeight - r);
|
|
78
|
+
let signedDistance: number;
|
|
79
|
+
let dirX = 0;
|
|
80
|
+
let dirY = 0;
|
|
81
|
+
if (qx > 0 && qy > 0) {
|
|
82
|
+
const len = Math.hypot(qx, qy);
|
|
83
|
+
signedDistance = len - r;
|
|
84
|
+
dirX = (Math.sign(px) * qx) / len;
|
|
85
|
+
dirY = (Math.sign(py) * qy) / len;
|
|
86
|
+
} else if (qx > qy) {
|
|
87
|
+
signedDistance = qx - r;
|
|
88
|
+
dirX = Math.sign(px);
|
|
89
|
+
} else {
|
|
90
|
+
signedDistance = qy - r;
|
|
91
|
+
dirY = Math.sign(py);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const index = (ty * texWidth + tx) * 4;
|
|
95
|
+
const inside = -signedDistance; // px from the contour, positive inside
|
|
96
|
+
|
|
97
|
+
if (inside <= 0) {
|
|
98
|
+
data[index] = 128;
|
|
99
|
+
data[index + 1] = 128;
|
|
100
|
+
data[index + 2] = 128;
|
|
101
|
+
data[index + 3] = 255;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const t = Math.min(1, inside / bezelPx); // 0 at the edge → 1 at the bezel's inner end
|
|
106
|
+
let magnitude = 1 - convexSquircle(t); // max refraction at the edge, optically flat interior
|
|
107
|
+
magnitude *= Math.min(1, inside / EDGE_TAPER_PX); // avoid sampling artifacts on the outermost pixels
|
|
108
|
+
|
|
109
|
+
data[index] = Math.round(128 + dirX * magnitude * 127);
|
|
110
|
+
data[index + 1] = Math.round(128 + dirY * magnitude * 127);
|
|
111
|
+
data[index + 2] = 128;
|
|
112
|
+
data[index + 3] = 255;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ctx.putImageData(image, 0, 0);
|
|
117
|
+
const mapUrl = canvas.toDataURL("image/png");
|
|
118
|
+
displacementMapCache.set(cacheKey, mapUrl);
|
|
119
|
+
return mapUrl;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function convexSquircle(x: number) {
|
|
123
|
+
return Math.pow(1 - Math.pow(1 - x, 4), 0.25);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type LiquidGlassProps = HTMLAttributes<HTMLDivElement> & {
|
|
127
|
+
/** Extra blur mixed into the backdrop-filter after the SVG refraction. */
|
|
128
|
+
blur?: number;
|
|
129
|
+
/** SVG displacement strength. Higher values bend the sampled backdrop more. */
|
|
130
|
+
refraction?: number;
|
|
131
|
+
/**
|
|
132
|
+
* @deprecated Texture size now derives from element size. Accepted for
|
|
133
|
+
* backwards-compatibility but has no effect.
|
|
134
|
+
*/
|
|
135
|
+
mapSize?: number;
|
|
136
|
+
/** Fraction of the radius used as the curved edge where most refraction happens. */
|
|
137
|
+
bezel?: number;
|
|
138
|
+
/** Backdrop saturation. */
|
|
139
|
+
saturation?: number;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const LiquidGlass = forwardRef<HTMLDivElement, LiquidGlassProps>(function LiquidGlass(
|
|
143
|
+
{
|
|
144
|
+
blur = 2,
|
|
145
|
+
refraction = 15,
|
|
146
|
+
mapSize: _mapSize,
|
|
147
|
+
bezel = 0.34,
|
|
148
|
+
saturation = 1.28,
|
|
149
|
+
className,
|
|
150
|
+
style,
|
|
151
|
+
children,
|
|
152
|
+
...props
|
|
153
|
+
},
|
|
154
|
+
ref,
|
|
155
|
+
) {
|
|
156
|
+
const rawId = useId();
|
|
157
|
+
const filterId = useMemo(() => `liquid-glass-${rawId.replace(/:/g, "")}`, [rawId]);
|
|
158
|
+
console.log(_mapSize)
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// Ref merge — we need the DOM node to measure geometry while still
|
|
161
|
+
// forwarding the ref to the consumer.
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
const localRef = useRef<HTMLDivElement | null>(null);
|
|
165
|
+
const setRefs = useCallback(
|
|
166
|
+
(node: HTMLDivElement | null) => {
|
|
167
|
+
localRef.current = node;
|
|
168
|
+
if (typeof ref === "function") ref(node);
|
|
169
|
+
else if (ref) ref.current = node;
|
|
170
|
+
},
|
|
171
|
+
[ref],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// -------------------------------------------------------------------------
|
|
175
|
+
// Geometry measurement via ResizeObserver
|
|
176
|
+
// -------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
const [geometry, setGeometry] = useState<MapGeometry | null>(null);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const el = localRef.current;
|
|
182
|
+
if (!el) return;
|
|
183
|
+
const measure = () => {
|
|
184
|
+
const rect = el.getBoundingClientRect();
|
|
185
|
+
const width = Math.round(rect.width);
|
|
186
|
+
const height = Math.round(rect.height);
|
|
187
|
+
if (!width || !height) return;
|
|
188
|
+
const parsed = Number.parseFloat(getComputedStyle(el).borderTopLeftRadius);
|
|
189
|
+
const radius = Math.min(Number.isFinite(parsed) ? parsed : Math.min(width, height) / 2, width / 2, height / 2);
|
|
190
|
+
setGeometry((prev) =>
|
|
191
|
+
prev && prev.width === width && prev.height === height && prev.radius === radius && prev.bezel === bezel
|
|
192
|
+
? prev
|
|
193
|
+
: { width, height, radius, bezel },
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
measure();
|
|
197
|
+
const observer = new ResizeObserver(measure);
|
|
198
|
+
observer.observe(el);
|
|
199
|
+
return () => observer.disconnect();
|
|
200
|
+
}, [bezel]);
|
|
201
|
+
|
|
202
|
+
const mapUrl = useMemo(() => (geometry ? createDisplacementMap(geometry) : ""), [geometry]);
|
|
203
|
+
|
|
204
|
+
// -------------------------------------------------------------------------
|
|
205
|
+
// Browser capability gate
|
|
206
|
+
//
|
|
207
|
+
// Both SSR and the first client render take the fallback branch so hydration
|
|
208
|
+
// never mismatches. The state is set in an effect (client-only).
|
|
209
|
+
// -------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
const [supported, setSupported] = useState(false);
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
setSupported(supportsSvgBackdropFilter());
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
const refractionActive = supported && mapUrl !== "";
|
|
218
|
+
const backdropFilter = refractionActive
|
|
219
|
+
? `url(#${filterId}) blur(${blur}px) saturate(${saturation})`
|
|
220
|
+
: `blur(${blur + 2}px) saturate(${saturation})`;
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<div
|
|
225
|
+
ref={setRefs}
|
|
226
|
+
className={cn(
|
|
227
|
+
"relative overflow-hidden rounded-full bg-white/[0.08]",
|
|
228
|
+
className,
|
|
229
|
+
)}
|
|
230
|
+
style={{ ...style, backdropFilter, WebkitBackdropFilter: backdropFilter } as CSSProperties}
|
|
231
|
+
{...props}
|
|
232
|
+
>
|
|
233
|
+
{children}
|
|
234
|
+
<span
|
|
235
|
+
aria-hidden
|
|
236
|
+
className="pointer-events-none absolute inset-0 rounded-[inherit]"
|
|
237
|
+
style={{
|
|
238
|
+
// Rim thickness, overridable per-consumer via --liquid-glass-rim-width.
|
|
239
|
+
padding: "var(--liquid-glass-rim-width, 0.5px)",
|
|
240
|
+
background:
|
|
241
|
+
// iOS 27 liquid-glass rim. Each gradient runs ALONG its edges and
|
|
242
|
+
// fades to nothing at the corners, uniform in between:
|
|
243
|
+
// - to right → white streak on the top + bottom runs
|
|
244
|
+
// - to bottom → dark streak on the left + right runs
|
|
245
|
+
// Streak colors are overridable per-consumer via the
|
|
246
|
+
// --liquid-glass-rim-light / --liquid-glass-rim-dark variables;
|
|
247
|
+
// --liquid-glass-rim-fade sets how far from each corner the
|
|
248
|
+
// streak takes to reach full strength (smaller = longer streak).
|
|
249
|
+
"linear-gradient(to right, rgba(255,255,255,0), var(--liquid-glass-rim-light, rgba(255,255,255,0.25)) var(--liquid-glass-rim-fade, 18%), var(--liquid-glass-rim-light, rgba(255,255,255,0.25)) calc(100% - var(--liquid-glass-rim-fade, 18%)), rgba(255,255,255,0)), " +
|
|
250
|
+
"linear-gradient(to bottom, rgba(0,0,0,0), var(--liquid-glass-rim-dark, rgba(0,0,0,0.2)) var(--liquid-glass-rim-fade, 18%), var(--liquid-glass-rim-dark, rgba(0,0,0,0.2)) calc(100% - var(--liquid-glass-rim-fade, 18%)), rgba(0,0,0,0))",
|
|
251
|
+
WebkitMask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
|
|
252
|
+
WebkitMaskComposite: "xor",
|
|
253
|
+
mask: "linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)",
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{refractionActive && (
|
|
259
|
+
<svg className="absolute size-0 overflow-hidden" aria-hidden>
|
|
260
|
+
<filter id={filterId} x="0" y="0" width="100%" height="100%" colorInterpolationFilters="sRGB">
|
|
261
|
+
<feImage href={mapUrl} x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map" />
|
|
262
|
+
<feDisplacementMap
|
|
263
|
+
in="SourceGraphic"
|
|
264
|
+
in2="map"
|
|
265
|
+
scale={refraction}
|
|
266
|
+
xChannelSelector="R"
|
|
267
|
+
yChannelSelector="G"
|
|
268
|
+
result="displaced"
|
|
269
|
+
/>
|
|
270
|
+
<feGaussianBlur in="displaced" stdDeviation="0.15" />
|
|
271
|
+
</filter>
|
|
272
|
+
</svg>
|
|
273
|
+
)}
|
|
274
|
+
</>
|
|
275
|
+
);
|
|
276
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { Input, type InputProps } from "@/components/ui/input";
|
|
7
|
+
import { Textarea, type TextareaProps } from "@/components/ui/textarea";
|
|
8
|
+
|
|
9
|
+
const inputGroupAddonVariants = cva(
|
|
10
|
+
"flex h-auto cursor-text select-none items-center justify-center gap-2 leading-none [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:-mx-0.5 not-has-[button]:**:[svg:not([class*='opacity-'])]:opacity-80",
|
|
11
|
+
{
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
align: "inline-start",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
align: {
|
|
17
|
+
"block-end":
|
|
18
|
+
"order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
19
|
+
"block-start":
|
|
20
|
+
"order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
21
|
+
"inline-end":
|
|
22
|
+
"order-last pe-[calc(--spacing(3)-1px)] has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]",
|
|
23
|
+
"inline-start":
|
|
24
|
+
"order-first ps-[calc(--spacing(3)-1px)] has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export function InputGroup({
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<"div">): React.ReactElement {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
"relative inline-flex w-full min-w-0 items-center rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)] has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-slot="input-group"
|
|
41
|
+
role="group"
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function InputGroupAddon({
|
|
48
|
+
className,
|
|
49
|
+
align = "inline-start",
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<"div"> &
|
|
52
|
+
VariantProps<typeof inputGroupAddonVariants>): React.ReactElement {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
56
|
+
data-align={align}
|
|
57
|
+
data-slot="input-group-addon"
|
|
58
|
+
onMouseDown={(e: React.MouseEvent<HTMLDivElement>) => {
|
|
59
|
+
const target = e.target as HTMLElement;
|
|
60
|
+
const isInteractive = target.closest(
|
|
61
|
+
"button, a, input, select, textarea, [role='button'], [role='combobox'], [role='listbox'], [data-slot='select-trigger']",
|
|
62
|
+
);
|
|
63
|
+
if (isInteractive) return;
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
const parent = e.currentTarget.parentElement;
|
|
66
|
+
const input = parent?.querySelector<
|
|
67
|
+
HTMLInputElement | HTMLTextAreaElement
|
|
68
|
+
>("input, textarea");
|
|
69
|
+
if (input && !parent?.querySelector("input:focus, textarea:focus")) {
|
|
70
|
+
input.focus();
|
|
71
|
+
}
|
|
72
|
+
}}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function InputGroupText({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<"span">): React.ReactElement {
|
|
82
|
+
return (
|
|
83
|
+
<span
|
|
84
|
+
className={cn(
|
|
85
|
+
"line-clamp-1 flex items-center gap-2 whitespace-nowrap text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-mx-0.5",
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function InputGroupInput({
|
|
94
|
+
className,
|
|
95
|
+
...props
|
|
96
|
+
}: InputProps): React.ReactElement {
|
|
97
|
+
return <Input className={className} unstyled {...props} />;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function InputGroupTextarea({
|
|
101
|
+
className,
|
|
102
|
+
...props
|
|
103
|
+
}: TextareaProps): React.ReactElement {
|
|
104
|
+
return <Textarea className={className} unstyled {...props} />;
|
|
105
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
export type InputProps = Omit<
|
|
8
|
+
InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,
|
|
9
|
+
"size"
|
|
10
|
+
> & {
|
|
11
|
+
size?: "sm" | "default" | "lg" | number;
|
|
12
|
+
unstyled?: boolean;
|
|
13
|
+
nativeInput?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function Input({
|
|
17
|
+
className,
|
|
18
|
+
size = "default",
|
|
19
|
+
unstyled = false,
|
|
20
|
+
nativeInput = false,
|
|
21
|
+
style,
|
|
22
|
+
...props
|
|
23
|
+
}: InputProps): React.ReactElement {
|
|
24
|
+
const inputClassName = cn(
|
|
25
|
+
// The 5000000s background-color entry is the autofill-flash suppression
|
|
26
|
+
// hack; height/line-height/padding tween size-prop changes.
|
|
27
|
+
"h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none [transition:background-color_5000000s_ease-in-out_0s,height_300ms_var(--ease-smooth-out),line-height_300ms_var(--ease-smooth-out),padding_300ms_var(--ease-smooth-out),font-size_300ms_var(--ease-smooth-out)] placeholder:text-muted-foreground/72 motion-reduce:[transition:background-color_5000000s_ease-in-out_0s] sm:h-7.5 sm:leading-7.5",
|
|
28
|
+
size === "sm" &&
|
|
29
|
+
"h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5",
|
|
30
|
+
size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5",
|
|
31
|
+
props.type === "search" &&
|
|
32
|
+
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
|
|
33
|
+
props.type === "file" &&
|
|
34
|
+
"text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<span
|
|
39
|
+
className={
|
|
40
|
+
cn(
|
|
41
|
+
!unstyled &&
|
|
42
|
+
"relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-[box-shadow,border-color] duration-150 ease-smooth-out has-aria-invalid:animate-input-shake motion-reduce:animate-none motion-reduce:transition-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
43
|
+
className,
|
|
44
|
+
) || undefined
|
|
45
|
+
}
|
|
46
|
+
data-size={size}
|
|
47
|
+
data-slot="input-control"
|
|
48
|
+
>
|
|
49
|
+
{nativeInput ? (
|
|
50
|
+
<input
|
|
51
|
+
className={inputClassName}
|
|
52
|
+
data-slot="input"
|
|
53
|
+
size={typeof size === "number" ? size : undefined}
|
|
54
|
+
style={typeof style === "function" ? undefined : style}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<InputPrimitive
|
|
59
|
+
className={inputClassName}
|
|
60
|
+
data-slot="input"
|
|
61
|
+
size={typeof size === "number" ? size : undefined}
|
|
62
|
+
style={style}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
</span>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { InputPrimitive };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export function Label({
|
|
9
|
+
className,
|
|
10
|
+
render,
|
|
11
|
+
sound = false,
|
|
12
|
+
...props
|
|
13
|
+
}: useRender.ComponentProps<"label"> & {
|
|
14
|
+
/**
|
|
15
|
+
* Emit the cuelume hover tick. Opt-in: a label is a click target for its
|
|
16
|
+
* control, and that control already owns the interaction cue (Checkbox
|
|
17
|
+
* plays `toggle` on check/uncheck), so hover-ticking here double-sounds.
|
|
18
|
+
* Inert until the app calls bindSounds().
|
|
19
|
+
*/
|
|
20
|
+
sound?: boolean;
|
|
21
|
+
}): React.ReactElement {
|
|
22
|
+
const defaultProps = {
|
|
23
|
+
className: cn(
|
|
24
|
+
"inline-flex cursor-pointer items-center gap-2 font-medium text-base/4.5 text-foreground sm:text-sm/4",
|
|
25
|
+
className,
|
|
26
|
+
),
|
|
27
|
+
"data-cuelume-hover": sound ? "tick" : undefined,
|
|
28
|
+
"data-slot": "label",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return useRender({
|
|
32
|
+
defaultTagName: "label",
|
|
33
|
+
props: mergeProps<"label">(defaultProps, props),
|
|
34
|
+
render,
|
|
35
|
+
});
|
|
36
|
+
}
|