@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.
Files changed (112) hide show
  1. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
  2. package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
  3. package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
  4. package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
  5. package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
  6. package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
  7. package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
  8. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
  9. package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
  10. package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
  11. package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
  12. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
  13. package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
  14. package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
  15. package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
  16. package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
  17. package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
  18. package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
  19. package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
  20. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
  21. package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
  22. package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
  23. package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
  24. package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
  25. package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
  26. package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
  27. package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
  28. package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
  29. package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
  30. package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
  31. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
  32. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
  33. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
  34. package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
  35. package/dist-lib/components/blocks/shell/index.d.ts +16 -0
  36. package/dist-lib/components/blocks/shell/type.d.ts +90 -4
  37. package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
  38. package/dist-lib/components/motion/index.d.ts +1 -0
  39. package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
  40. package/dist-lib/components/ui/button-group.d.ts +11 -0
  41. package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
  42. package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
  43. package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
  44. package/dist-lib/components/ui/group.d.ts +17 -0
  45. package/dist-lib/components/ui/separator.d.ts +2 -3
  46. package/dist-lib/components/ui/sheet.d.ts +14 -0
  47. package/dist-lib/components/ui/tooltip.d.ts +6 -0
  48. package/dist-lib/index.d.ts +2 -1
  49. package/dist-lib/index.js +1592 -1084
  50. package/dist-lib/index.js.map +1 -1
  51. package/dist-lib/lib/ease.d.ts +3 -0
  52. package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
  53. package/package.json +2 -1
  54. package/src/components/beste/piece/browser-frame.tsx +2 -2
  55. package/src/components/blocks/nav/sidebar-block.tsx +5 -5
  56. package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
  57. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
  58. package/src/components/blocks/scaffold/constants.ts +28 -0
  59. package/src/components/blocks/scaffold/index.ts +38 -0
  60. package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
  61. package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
  62. package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
  63. package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
  64. package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
  65. package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
  66. package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
  67. package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
  68. package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
  69. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
  70. package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
  71. package/src/components/blocks/scaffold/type.ts +171 -0
  72. package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
  73. package/src/components/blocks/scaffold/utils.ts +30 -0
  74. package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
  75. package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
  76. package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
  77. package/src/components/blocks/shell/animated-panel.tsx +135 -0
  78. package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
  79. package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
  80. package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
  81. package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
  82. package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
  83. package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
  84. package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
  85. package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
  86. package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
  87. package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
  88. package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
  89. package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
  90. package/src/components/blocks/shell/index.ts +18 -0
  91. package/src/components/blocks/shell/shell-layout.tsx +28 -36
  92. package/src/components/blocks/shell/type.ts +112 -6
  93. package/src/components/blocks/shell/use-shell-panels.ts +1 -1
  94. package/src/components/blocks/shell/utils.ts +142 -1
  95. package/src/components/docs/demo-controls.tsx +1 -1
  96. package/src/components/motion/index.ts +0 -0
  97. package/src/components/motion/overflow-actions.tsx +376 -0
  98. package/src/components/ui/button-group.tsx +88 -0
  99. package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
  100. package/src/components/ui/glasscn/glass-container.tsx +125 -0
  101. package/src/components/ui/glasscn/glass-separator.tsx +39 -0
  102. package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
  103. package/src/components/ui/group.tsx +92 -0
  104. package/src/components/ui/separator.tsx +11 -11
  105. package/src/components/ui/sheet.tsx +135 -0
  106. package/src/components/ui/tooltip.tsx +66 -0
  107. package/src/index.css +1 -1
  108. package/src/index.ts +13 -1
  109. package/src/lib/ease.ts +26 -0
  110. package/src/lib/hooks/use-hover-capable.ts +23 -0
  111. package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
  112. 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; // 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
- };
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; // px from the contour, positive inside
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); // 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
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>(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;
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
- [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,
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
- 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
- });
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
- export function Separator({
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Separator({
6
6
  className,
7
7
  orientation = "horizontal",
8
8
  ...props
9
- }: SeparatorPrimitive.Props): React.ReactElement {
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 { SeparatorPrimitive };
23
+ export { Separator }