@exegia/corpora-ui 0.17.0 → 0.18.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/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
- package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
- package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
- package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
- package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
- package/dist-lib/components/blocks/shell/index.d.ts +16 -0
- package/dist-lib/components/blocks/shell/type.d.ts +90 -4
- package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
- package/dist-lib/components/motion/index.d.ts +1 -0
- package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
- package/dist-lib/components/ui/button-group.d.ts +11 -0
- package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
- package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
- package/dist-lib/components/ui/group.d.ts +17 -0
- package/dist-lib/components/ui/separator.d.ts +2 -3
- package/dist-lib/components/ui/sheet.d.ts +14 -0
- package/dist-lib/components/ui/tooltip.d.ts +6 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +1592 -1084
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/ease.d.ts +3 -0
- package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +2 -2
- package/src/components/blocks/nav/sidebar-block.tsx +5 -5
- package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
- package/src/components/blocks/scaffold/constants.ts +28 -0
- package/src/components/blocks/scaffold/index.ts +38 -0
- package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
- package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
- package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
- package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
- package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
- package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
- package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
- package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
- package/src/components/blocks/scaffold/type.ts +171 -0
- package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
- package/src/components/blocks/scaffold/utils.ts +30 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
- package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
- package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
- package/src/components/blocks/shell/animated-panel.tsx +135 -0
- package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
- package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
- package/src/components/blocks/shell/index.ts +18 -0
- package/src/components/blocks/shell/shell-layout.tsx +28 -36
- package/src/components/blocks/shell/type.ts +112 -6
- package/src/components/blocks/shell/use-shell-panels.ts +1 -1
- package/src/components/blocks/shell/utils.ts +142 -1
- package/src/components/docs/demo-controls.tsx +1 -1
- package/src/components/motion/index.ts +0 -0
- package/src/components/motion/overflow-actions.tsx +376 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
- package/src/components/ui/glasscn/glass-container.tsx +125 -0
- package/src/components/ui/glasscn/glass-separator.tsx +39 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
- package/src/components/ui/group.tsx +92 -0
- package/src/components/ui/separator.tsx +11 -11
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/index.css +1 -1
- package/src/index.ts +13 -1
- package/src/lib/ease.ts +26 -0
- package/src/lib/hooks/use-hover-capable.ts +23 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
- package/src/components/motion/animated-sidebar.tsx +0 -1171
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client"
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
type CSSProperties,
|
|
5
|
-
type HTMLAttributes,
|
|
6
5
|
forwardRef,
|
|
6
|
+
type HTMLAttributes,
|
|
7
7
|
useCallback,
|
|
8
8
|
useEffect,
|
|
9
9
|
useId,
|
|
10
10
|
useMemo,
|
|
11
11
|
useRef,
|
|
12
12
|
useState,
|
|
13
|
-
} from "react"
|
|
13
|
+
} from "react"
|
|
14
14
|
|
|
15
|
-
import { cn } from "@/lib/utils"
|
|
15
|
+
import { cn } from "@/lib/utils"
|
|
16
16
|
|
|
17
17
|
type MapGeometry = {
|
|
18
|
-
width: number
|
|
19
|
-
height: number
|
|
20
|
-
radius: number
|
|
21
|
-
bezel: number
|
|
22
|
-
}
|
|
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
23
|
|
|
24
|
-
const MAX_TEXTURE_SIZE = 480
|
|
25
|
-
const EDGE_TAPER_PX = 1.25
|
|
24
|
+
const MAX_TEXTURE_SIZE = 480
|
|
25
|
+
const EDGE_TAPER_PX = 1.25
|
|
26
26
|
|
|
27
|
-
const displacementMapCache = new Map<string, string>()
|
|
27
|
+
const displacementMapCache = new Map<string, string>()
|
|
28
28
|
|
|
29
29
|
// CSS.supports("backdrop-filter", "url(#f)") returns true in Safari and
|
|
30
30
|
// Firefox (they parse the value) but neither browser actually renders SVG
|
|
31
31
|
// filters through backdrop-filter. UA sniffing is the only reliable approach.
|
|
32
32
|
// When WebKit ships support (track WebKit bug 245510), loosen this function.
|
|
33
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)
|
|
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
38
|
}
|
|
39
39
|
|
|
40
40
|
// NOTE: Per-corner radii and percentage radii are NOT supported. The map
|
|
@@ -45,232 +45,272 @@ function supportsSvgBackdropFilter() {
|
|
|
45
45
|
// each unique (width, height, radius, bezel). Continuously animating the
|
|
46
46
|
// element's SIZE will thrash the cache; animate transform: scale instead.
|
|
47
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))
|
|
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
68
|
|
|
69
69
|
for (let ty = 0; ty < texHeight; ty += 1) {
|
|
70
70
|
for (let tx = 0; tx < texWidth; tx += 1) {
|
|
71
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
|
|
72
|
+
const px = ((tx + 0.5) / texWidth) * width - halfWidth
|
|
73
|
+
const py = ((ty + 0.5) / texHeight) * height - halfHeight
|
|
74
74
|
|
|
75
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
|
|
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
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
|
|
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
86
|
} else if (qx > qy) {
|
|
87
|
-
signedDistance = qx - r
|
|
88
|
-
dirX = Math.sign(px)
|
|
87
|
+
signedDistance = qx - r
|
|
88
|
+
dirX = Math.sign(px)
|
|
89
89
|
} else {
|
|
90
|
-
signedDistance = qy - r
|
|
91
|
-
dirY = Math.sign(py)
|
|
90
|
+
signedDistance = qy - r
|
|
91
|
+
dirY = Math.sign(py)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const index = (ty * texWidth + tx) * 4
|
|
95
|
-
const inside = -signedDistance
|
|
94
|
+
const index = (ty * texWidth + tx) * 4
|
|
95
|
+
const inside = -signedDistance // px from the contour, positive inside
|
|
96
96
|
|
|
97
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
|
|
98
|
+
data[index] = 128
|
|
99
|
+
data[index + 1] = 128
|
|
100
|
+
data[index + 2] = 128
|
|
101
|
+
data[index + 3] = 255
|
|
102
|
+
continue
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const t = Math.min(1, inside / bezelPx)
|
|
106
|
-
let magnitude = 1 - convexSquircle(t)
|
|
107
|
-
magnitude *= Math.min(1, inside / EDGE_TAPER_PX)
|
|
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
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
|
|
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
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
ctx.putImageData(image, 0, 0)
|
|
117
|
-
const mapUrl = canvas.toDataURL("image/png")
|
|
118
|
-
displacementMapCache.set(cacheKey, mapUrl)
|
|
119
|
-
return mapUrl
|
|
116
|
+
ctx.putImageData(image, 0, 0)
|
|
117
|
+
const mapUrl = canvas.toDataURL("image/png")
|
|
118
|
+
displacementMapCache.set(cacheKey, mapUrl)
|
|
119
|
+
return mapUrl
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
function convexSquircle(x: number) {
|
|
123
|
-
return Math.pow(1 - Math.pow(1 - x, 4), 0.25)
|
|
123
|
+
return Math.pow(1 - Math.pow(1 - x, 4), 0.25)
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export type LiquidGlassProps = HTMLAttributes<HTMLDivElement> & {
|
|
127
127
|
/** Extra blur mixed into the backdrop-filter after the SVG refraction. */
|
|
128
|
-
blur?: number
|
|
128
|
+
blur?: number
|
|
129
129
|
/** SVG displacement strength. Higher values bend the sampled backdrop more. */
|
|
130
|
-
refraction?: number
|
|
130
|
+
refraction?: number
|
|
131
131
|
/**
|
|
132
132
|
* @deprecated Texture size now derives from element size. Accepted for
|
|
133
133
|
* backwards-compatibility but has no effect.
|
|
134
134
|
*/
|
|
135
|
-
mapSize?: number
|
|
135
|
+
mapSize?: number
|
|
136
136
|
/** Fraction of the radius used as the curved edge where most refraction happens. */
|
|
137
|
-
bezel?: number
|
|
137
|
+
bezel?: number
|
|
138
138
|
/** Backdrop saturation. */
|
|
139
|
-
saturation?: number
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export const LiquidGlass = forwardRef<HTMLDivElement, LiquidGlassProps>(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
139
|
+
saturation?: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const LiquidGlass = forwardRef<HTMLDivElement, LiquidGlassProps>(
|
|
143
|
+
function LiquidGlass(
|
|
144
|
+
{
|
|
145
|
+
blur = 2,
|
|
146
|
+
refraction = 15,
|
|
147
|
+
mapSize: _mapSize,
|
|
148
|
+
bezel = 0.34,
|
|
149
|
+
saturation = 1.28,
|
|
150
|
+
className,
|
|
151
|
+
style,
|
|
152
|
+
children,
|
|
153
|
+
...props
|
|
170
154
|
},
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
155
|
+
ref
|
|
156
|
+
) {
|
|
157
|
+
const rawId = useId()
|
|
158
|
+
const filterId = useMemo(
|
|
159
|
+
() => `liquid-glass-${rawId.replace(/:/g, "")}`,
|
|
160
|
+
[rawId]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
// Ref merge — we need the DOM node to measure geometry while still
|
|
165
|
+
// forwarding the ref to the consumer.
|
|
166
|
+
// -------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
const localRef = useRef<HTMLDivElement | null>(null)
|
|
169
|
+
const setRefs = useCallback(
|
|
170
|
+
(node: HTMLDivElement | null) => {
|
|
171
|
+
localRef.current = node
|
|
172
|
+
if (typeof ref === "function") ref(node)
|
|
173
|
+
else if (ref) ref.current = node
|
|
174
|
+
},
|
|
175
|
+
[ref]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// -------------------------------------------------------------------------
|
|
179
|
+
// Geometry measurement via ResizeObserver
|
|
180
|
+
// -------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
const [geometry, setGeometry] = useState<MapGeometry | null>(null)
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
const el = localRef.current
|
|
186
|
+
if (!el) return
|
|
187
|
+
const measure = () => {
|
|
188
|
+
const rect = el.getBoundingClientRect()
|
|
189
|
+
const width = Math.round(rect.width)
|
|
190
|
+
const height = Math.round(rect.height)
|
|
191
|
+
if (!width || !height) return
|
|
192
|
+
const parsed = Number.parseFloat(
|
|
193
|
+
getComputedStyle(el).borderTopLeftRadius
|
|
194
|
+
)
|
|
195
|
+
const radius = Math.min(
|
|
196
|
+
Number.isFinite(parsed) ? parsed : Math.min(width, height) / 2,
|
|
197
|
+
width / 2,
|
|
198
|
+
height / 2
|
|
199
|
+
)
|
|
200
|
+
setGeometry((prev) =>
|
|
201
|
+
prev &&
|
|
202
|
+
prev.width === width &&
|
|
203
|
+
prev.height === height &&
|
|
204
|
+
prev.radius === radius &&
|
|
205
|
+
prev.bezel === bezel
|
|
206
|
+
? prev
|
|
207
|
+
: { width, height, radius, bezel }
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
measure()
|
|
211
|
+
const observer = new ResizeObserver(measure)
|
|
212
|
+
observer.observe(el)
|
|
213
|
+
return () => observer.disconnect()
|
|
214
|
+
}, [bezel])
|
|
215
|
+
|
|
216
|
+
const mapUrl = useMemo(
|
|
217
|
+
() => (geometry ? createDisplacementMap(geometry) : ""),
|
|
218
|
+
[geometry]
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// -------------------------------------------------------------------------
|
|
222
|
+
// Browser capability gate
|
|
223
|
+
//
|
|
224
|
+
// Both SSR and the first client render take the fallback branch so hydration
|
|
225
|
+
// never mismatches. The state is set in an effect (client-only).
|
|
226
|
+
// -------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
const [supported, setSupported] = useState(false)
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
setSupported(supportsSvgBackdropFilter())
|
|
232
|
+
}, [])
|
|
233
|
+
|
|
234
|
+
const refractionActive = supported && mapUrl !== ""
|
|
235
|
+
const backdropFilter = refractionActive
|
|
236
|
+
? `url(#${filterId}) blur(${blur}px) saturate(${saturation})`
|
|
237
|
+
: `blur(${blur + 2}px) saturate(${saturation})`
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
<div
|
|
242
|
+
ref={setRefs}
|
|
243
|
+
className={cn(
|
|
244
|
+
"relative overflow-hidden rounded-full bg-white/[0.08]",
|
|
245
|
+
className
|
|
246
|
+
)}
|
|
247
|
+
style={
|
|
248
|
+
{
|
|
249
|
+
...style,
|
|
250
|
+
backdropFilter,
|
|
251
|
+
WebkitBackdropFilter: backdropFilter,
|
|
252
|
+
} as CSSProperties
|
|
253
|
+
}
|
|
254
|
+
{...props}
|
|
255
|
+
>
|
|
256
|
+
{children}
|
|
257
|
+
<span
|
|
258
|
+
aria-hidden
|
|
259
|
+
className="pointer-events-none absolute inset-0 rounded-[inherit]"
|
|
260
|
+
style={{
|
|
261
|
+
// Rim thickness, overridable per-consumer via --liquid-glass-rim-width.
|
|
262
|
+
padding: "var(--liquid-glass-rim-width, 0.5px)",
|
|
263
|
+
background:
|
|
264
|
+
// iOS 27 liquid-glass rim. Each gradient runs ALONG its edges and
|
|
265
|
+
// fades to nothing at the corners, uniform in between:
|
|
266
|
+
// - to right → white streak on the top + bottom runs
|
|
267
|
+
// - to bottom → dark streak on the left + right runs
|
|
268
|
+
// Streak colors are overridable per-consumer via the
|
|
269
|
+
// --liquid-glass-rim-light / --liquid-glass-rim-dark variables;
|
|
270
|
+
// --liquid-glass-rim-fade sets how far from each corner the
|
|
271
|
+
// streak takes to reach full strength (smaller = longer streak).
|
|
272
|
+
"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)), " +
|
|
273
|
+
"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))",
|
|
274
|
+
WebkitMask:
|
|
275
|
+
"linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
|
|
276
|
+
WebkitMaskComposite: "xor",
|
|
277
|
+
mask: "linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)",
|
|
278
|
+
}}
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{refractionActive && (
|
|
283
|
+
<svg className="absolute size-0 overflow-hidden" aria-hidden>
|
|
284
|
+
<filter
|
|
285
|
+
id={filterId}
|
|
286
|
+
x="0"
|
|
287
|
+
y="0"
|
|
288
|
+
width="100%"
|
|
289
|
+
height="100%"
|
|
290
|
+
colorInterpolationFilters="sRGB"
|
|
291
|
+
>
|
|
292
|
+
<feImage
|
|
293
|
+
href={mapUrl}
|
|
294
|
+
x="0"
|
|
295
|
+
y="0"
|
|
296
|
+
width="100%"
|
|
297
|
+
height="100%"
|
|
298
|
+
preserveAspectRatio="none"
|
|
299
|
+
result="map"
|
|
300
|
+
/>
|
|
301
|
+
<feDisplacementMap
|
|
302
|
+
in="SourceGraphic"
|
|
303
|
+
in2="map"
|
|
304
|
+
scale={refraction}
|
|
305
|
+
xChannelSelector="R"
|
|
306
|
+
yChannelSelector="G"
|
|
307
|
+
result="displaced"
|
|
308
|
+
/>
|
|
309
|
+
<feGaussianBlur in="displaced" stdDeviation="0.15" />
|
|
310
|
+
</filter>
|
|
311
|
+
</svg>
|
|
229
312
|
)}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
});
|
|
313
|
+
</>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
import type * as React from "react"
|
|
7
|
+
import { Separator } from "@/components/ui/separator"
|
|
8
|
+
import { cn } from "@/lib/utils"
|
|
9
|
+
|
|
10
|
+
export const groupVariants = cva(
|
|
11
|
+
"flex w-fit *:focus-visible:z-1 *:has-focus-visible:z-1 has-[>[data-slot=group]]:gap-2 dark:*:[[data-slot=separator]:has(~button:hover):not(:has(~[data-slot=separator]~[data-slot]:hover)),[data-slot=separator]:has(~[data-slot][data-pressed]):not(:has(~[data-slot=separator]~[data-slot][data-pressed]))]:before:bg-input/64 dark:*:[button:hover~[data-slot=separator]:not([data-slot]:hover~[data-slot=separator]~[data-slot=separator]),[data-slot][data-pressed]~[data-slot=separator]:not([data-slot][data-pressed]~[data-slot=separator]~[data-slot=separator])]:before:bg-input/64",
|
|
12
|
+
{
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
orientation: "horizontal",
|
|
15
|
+
},
|
|
16
|
+
variants: {
|
|
17
|
+
orientation: {
|
|
18
|
+
horizontal:
|
|
19
|
+
"*:data-slot:has-[~[data-slot]]:rounded-e-none *:data-slot:has-[~[data-slot]]:border-e-0 *:data-slot:has-[~[data-slot]]:before:rounded-e-none *:data-slot:not-data-[slot=separator]:has-[~[data-slot]]:before:-end-[0.5px] *:pointer-coarse:after:min-w-auto *:[[data-slot]~[data-slot]]:rounded-s-none *:[[data-slot]~[data-slot]]:border-s-0 *:[[data-slot]~[data-slot]]:before:rounded-s-none *:[[data-slot]~[data-slot]:not([data-slot=separator])]:before:-start-[0.5px]",
|
|
20
|
+
vertical:
|
|
21
|
+
"flex-col *:data-slot:has-[~[data-slot]]:rounded-b-none *:data-slot:has-[~[data-slot]]:border-b-0 *:data-slot:has-[~[data-slot]]:before:rounded-b-none *:data-slot:not-data-[slot=separator]:has-[~[data-slot]]:before:-bottom-[0.5px] *:data-slot:not-data-[slot=separator]:has-[~[data-slot]]:before:hidden dark:*:first:before:block dark:*:last:before:hidden *:pointer-coarse:after:min-h-auto *:[[data-slot]~[data-slot]]:rounded-t-none *:[[data-slot]~[data-slot]]:border-t-0 *:[[data-slot]~[data-slot]]:before:rounded-t-none *:[[data-slot]~[data-slot]:not([data-slot=separator])]:before:-top-[0.5px]",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export function Group({
|
|
28
|
+
className,
|
|
29
|
+
orientation,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: {
|
|
33
|
+
className?: string
|
|
34
|
+
orientation?: VariantProps<typeof groupVariants>["orientation"]
|
|
35
|
+
children: React.ReactNode
|
|
36
|
+
} & React.ComponentProps<"div">): React.ReactElement {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={cn(groupVariants({ orientation }), className)}
|
|
40
|
+
data-orientation={orientation}
|
|
41
|
+
data-slot="group"
|
|
42
|
+
role="group"
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function GroupText({
|
|
51
|
+
className,
|
|
52
|
+
render,
|
|
53
|
+
...props
|
|
54
|
+
}: useRender.ComponentProps<"div">): React.ReactElement {
|
|
55
|
+
const defaultProps = {
|
|
56
|
+
className: cn(
|
|
57
|
+
"relative inline-flex items-center gap-2 rounded-lg border border-input bg-muted px-[calc(--spacing(3)-1px)] text-base whitespace-nowrap text-muted-foreground shadow-xs/5 transition-shadow outline-none not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] sm:text-sm dark:bg-input/64 dark:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
|
|
58
|
+
className
|
|
59
|
+
),
|
|
60
|
+
"data-slot": "group-text",
|
|
61
|
+
}
|
|
62
|
+
return useRender({
|
|
63
|
+
defaultTagName: "div",
|
|
64
|
+
props: mergeProps(defaultProps, props),
|
|
65
|
+
render,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function GroupSeparator({
|
|
70
|
+
className,
|
|
71
|
+
orientation = "vertical",
|
|
72
|
+
...props
|
|
73
|
+
}: {
|
|
74
|
+
className?: string
|
|
75
|
+
} & React.ComponentProps<typeof Separator>): React.ReactElement {
|
|
76
|
+
return (
|
|
77
|
+
<Separator
|
|
78
|
+
className={cn(
|
|
79
|
+
"pointer-events-none relative z-2 bg-input before:absolute before:inset-0 has-[+[data-slot=input-control]:focus-within,+[data-slot=input-group]:focus-within,+[data-slot=select-trigger]:focus-visible+*,+[data-slot=number-field]:focus-within]:translate-x-px has-[+[data-slot=input-control]:focus-within,+[data-slot=input-group]:focus-within,+[data-slot=select-trigger]:focus-visible+*,+[data-slot=number-field]:focus-within]:bg-ring dark:before:bg-input/32 [[data-slot=input-control]:focus-within+&,[data-slot=input-group]:focus-within+&,[data-slot=select-trigger]:focus-visible+*+&,[data-slot=number-field]:focus-within+&,[data-slot=number-field]:focus-within+input+&]:bg-ring [[data-slot=input-control]:focus-within+&,[data-slot=input-group]:focus-within+&,[data-slot=select-trigger]:focus-visible+*+&,[data-slot=number-field]:focus-within+input+&]:-translate-x-px",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
orientation={orientation}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
Group as ButtonGroup,
|
|
90
|
+
GroupText as ButtonGroupText,
|
|
91
|
+
GroupSeparator as ButtonGroupSeparator,
|
|
92
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
|
2
|
-
import type React from "react";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Separator({
|
|
6
6
|
className,
|
|
7
7
|
orientation = "horizontal",
|
|
8
8
|
...props
|
|
9
|
-
}: SeparatorPrimitive.Props)
|
|
9
|
+
}: SeparatorPrimitive.Props) {
|
|
10
10
|
return (
|
|
11
11
|
<SeparatorPrimitive
|
|
12
|
-
className={cn(
|
|
13
|
-
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",
|
|
14
|
-
className,
|
|
15
|
-
)}
|
|
16
12
|
data-slot="separator"
|
|
17
13
|
orientation={orientation}
|
|
14
|
+
className={cn(
|
|
15
|
+
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
18
|
{...props}
|
|
19
19
|
/>
|
|
20
|
-
)
|
|
20
|
+
)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export {
|
|
23
|
+
export { Separator }
|