@aberhamm/liquid-glass-react 0.2.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/LICENSE +38 -0
- package/README.md +627 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +1191 -0
- package/dist/index.d.ts +1191 -0
- package/dist/index.mjs +1314 -0
- package/dist/liquid-glass-react.css +1 -0
- package/dist/native/index.native.d.ts +19 -0
- package/dist/native/index.native.js +19 -0
- package/dist/native/liquid-glass.native.d.ts +65 -0
- package/dist/native/liquid-glass.native.js +200 -0
- package/dist/native/tokens.d.ts +245 -0
- package/dist/native/tokens.js +254 -0
- package/dist/native/types.d.ts +464 -0
- package/dist/native/types.js +9 -0
- package/package.json +122 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type contracts for `@aberhamm/liquid-glass-react`.
|
|
3
|
+
*
|
|
4
|
+
* These types are the load-bearing surface that every later plan (refraction,
|
|
5
|
+
* motion, fallback, prebuilt components) builds against. They are defined ONCE
|
|
6
|
+
* here so the API cannot drift between plans. No runtime code lives in this
|
|
7
|
+
* file — it is types only.
|
|
8
|
+
*/
|
|
9
|
+
import type { CSSProperties, MouseEventHandler, ReactNode, RefObject } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* The displacement/refraction algorithm used to generate the SVG
|
|
12
|
+
* `feDisplacementMap` and tune the glass distortion.
|
|
13
|
+
*
|
|
14
|
+
* - `standard` — balanced edge-weighted displacement (the default look).
|
|
15
|
+
* - `polar` — radial/polar displacement, stronger toward the perimeter.
|
|
16
|
+
* - `prominent` — exaggerated displacement for a heavier "thick glass" feel.
|
|
17
|
+
* - `shader` — shader-style displacement profile for sharper highlights.
|
|
18
|
+
* - `turbulence` — adds fractal turbulence for an organic, watery distortion.
|
|
19
|
+
*
|
|
20
|
+
* @defaultValue `'standard'`
|
|
21
|
+
*/
|
|
22
|
+
export type DisplacementMode = 'standard' | 'polar' | 'prominent' | 'shader' | 'turbulence';
|
|
23
|
+
/**
|
|
24
|
+
* The two Liquid Glass material variants Apple distinguishes — selected with one
|
|
25
|
+
* intentional prop instead of hand-tuning opacity.
|
|
26
|
+
*
|
|
27
|
+
* - `regular` — the dependable, fully adaptive control surface. Exactly the
|
|
28
|
+
* current behavior, including content-adaptive auto-tint when
|
|
29
|
+
* {@link LiquidGlassProps.adaptiveTint} is enabled.
|
|
30
|
+
* - `clear` — a permanently MORE transparent material for media-rich contexts
|
|
31
|
+
* (e.g. floating over a photo). It is **non-adaptive by definition**:
|
|
32
|
+
* {@link LiquidGlassProps.adaptiveTint} is force-disabled, and an optional
|
|
33
|
+
* dimming scrim sits behind the content so labels stay legible over busy media.
|
|
34
|
+
*
|
|
35
|
+
* Apple's guidance is that the two **should never be mixed** in the same context.
|
|
36
|
+
*
|
|
37
|
+
* @defaultValue `'regular'`
|
|
38
|
+
*/
|
|
39
|
+
export type GlassVariant = 'regular' | 'clear';
|
|
40
|
+
/**
|
|
41
|
+
* Explicit lens size (CSS pixels) for the cross-browser copy-refraction path
|
|
42
|
+
* (plan 030). When omitted the lens is sized to the glass's measured dimensions.
|
|
43
|
+
*/
|
|
44
|
+
export interface GlassSize {
|
|
45
|
+
/** Lens width in CSS pixels. */
|
|
46
|
+
width: number;
|
|
47
|
+
/** Lens height in CSS pixels. */
|
|
48
|
+
height: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Lens focal point for the cross-browser copy-refraction path (plan 030),
|
|
52
|
+
* expressed as fractions of the lens box in `[0, 1]` (`{ x: 0.5, y: 0.5 }` is
|
|
53
|
+
* the center). It drives the refracted copy's `transform-origin`, so the warp
|
|
54
|
+
* reads as if the light bends around that point.
|
|
55
|
+
*/
|
|
56
|
+
export interface GlassCenter {
|
|
57
|
+
/** Horizontal focal point as a fraction of the lens width (`0`–`1`). */
|
|
58
|
+
x: number;
|
|
59
|
+
/** Vertical focal point as a fraction of the lens height (`0`–`1`). */
|
|
60
|
+
y: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A 2D pointer position in CSS pixels, used for mouse-driven motion/elasticity.
|
|
64
|
+
*
|
|
65
|
+
* Coordinates are interpreted relative to whatever container the consumer wires
|
|
66
|
+
* up via {@link LiquidGlassProps.mouseContainer} (viewport by default).
|
|
67
|
+
*/
|
|
68
|
+
export type MousePos = {
|
|
69
|
+
/** Horizontal position in CSS pixels. */
|
|
70
|
+
x: number;
|
|
71
|
+
/** Vertical position in CSS pixels. */
|
|
72
|
+
y: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Result of runtime capability detection. Every field is a plain boolean so the
|
|
76
|
+
* object is trivially serializable and cheap to compare across renders.
|
|
77
|
+
*
|
|
78
|
+
* Produced by `detectGlassCapabilities()` and surfaced to components via the
|
|
79
|
+
* `useGlassCapabilities()` hook. During SSR (and before mount) all fields are
|
|
80
|
+
* conservatively `false`.
|
|
81
|
+
*/
|
|
82
|
+
export type GlassCapabilities = {
|
|
83
|
+
/**
|
|
84
|
+
* Whether the browser supports `backdrop-filter` (or the `-webkit-` prefixed
|
|
85
|
+
* variant). Required for any frosted-glass blur at all.
|
|
86
|
+
*/
|
|
87
|
+
supportsBackdropFilter: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Positive Blink-family detection (Chrome, Edge, Brave, Opera). Explicitly
|
|
90
|
+
* NOT Firefox and NOT Safari/WebKit.
|
|
91
|
+
*/
|
|
92
|
+
isChromium: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Whether an SVG `feDisplacementMap` filter composites correctly over
|
|
95
|
+
* `backdrop-filter`. This is a Chromium-only rendering-pipeline quirk with no
|
|
96
|
+
* standardized `CSS.supports` probe, so it is derived from {@link isChromium}.
|
|
97
|
+
*/
|
|
98
|
+
supportsSvgBackdropDisplacement: boolean;
|
|
99
|
+
/** Whether the user agent is Firefox (Gecko). */
|
|
100
|
+
isFirefox: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Whether the user has requested reduced motion via
|
|
103
|
+
* `(prefers-reduced-motion: reduce)`.
|
|
104
|
+
*/
|
|
105
|
+
prefersReducedMotion: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the user has requested reduced transparency via
|
|
108
|
+
* `(prefers-reduced-transparency: reduce)`. When `true`, `<LiquidGlass>`
|
|
109
|
+
* suppresses live SVG refraction and renders a frostier, more-opaque surface.
|
|
110
|
+
* Orthogonal to {@link canRefract} — it applies on every tier and changes no
|
|
111
|
+
* box geometry. The authoritative live source is `useReducedTransparency()`;
|
|
112
|
+
* this field is a point-in-time snapshot.
|
|
113
|
+
*/
|
|
114
|
+
prefersReducedTransparency: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Whether the user has requested increased contrast via
|
|
117
|
+
* `(prefers-contrast: more)`. When `true`, `<LiquidGlass>` adds a solid,
|
|
118
|
+
* visible inset border, raises content legibility (a more-opaque surface
|
|
119
|
+
* fill), and tones down decorative saturation/aberration that harms contrast.
|
|
120
|
+
* Orthogonal to {@link canRefract} AND to {@link prefersReducedTransparency} —
|
|
121
|
+
* it applies on every tier, composes with reduced-transparency, and changes no
|
|
122
|
+
* box geometry. The authoritative live source is `usePrefersContrast()`; this
|
|
123
|
+
* field is a point-in-time snapshot.
|
|
124
|
+
*/
|
|
125
|
+
prefersContrastMore: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The single gate later plans consult: `true` only when the full refraction
|
|
128
|
+
* effect can actually render
|
|
129
|
+
* (`supportsBackdropFilter && supportsSvgBackdropDisplacement`). When `false`,
|
|
130
|
+
* components must fall back to a degraded (non-refractive) presentation.
|
|
131
|
+
*/
|
|
132
|
+
canRefract: boolean;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* PLATFORM-NEUTRAL props for the `<LiquidGlass>` PRIMITIVE.
|
|
136
|
+
*
|
|
137
|
+
* The shared base that BOTH the web (`liquid-glass.tsx`) and React Native
|
|
138
|
+
* (`liquid-glass.native.tsx`, plan 025) surfaces extend. It holds every prop
|
|
139
|
+
* that does NOT depend on DOM types, so the native surface can build on it
|
|
140
|
+
* without leaking `CSSProperties` / DOM event handlers. The web
|
|
141
|
+
* {@link LiquidGlassProps} extends this with the DOM-only `style` / `onClick` /
|
|
142
|
+
* pointer props; the native barrel defines its own `style?: ViewStyle` /
|
|
143
|
+
* `onPress?` over this same core.
|
|
144
|
+
*
|
|
145
|
+
* Every prop except {@link children} is optional — the primitive ships sensible
|
|
146
|
+
* defaults. Defaults documented here are the contract; the implementation plan
|
|
147
|
+
* (004+) must honor them.
|
|
148
|
+
*/
|
|
149
|
+
export interface LiquidGlassCoreProps {
|
|
150
|
+
/** Content rendered inside the glass surface. */
|
|
151
|
+
children: ReactNode;
|
|
152
|
+
/**
|
|
153
|
+
* Opt into the live SVG refraction/displacement pass (WEB-ONLY progressive
|
|
154
|
+
* enhancement). When `false` (the default) the glass renders the frosted
|
|
155
|
+
* fallback look — `blur()`/`saturate()` backdrop plus the pure-CSS bevel —
|
|
156
|
+
* with no `feDisplacementMap`.
|
|
157
|
+
*
|
|
158
|
+
* - **Web:** when `true` AND the browser can refract (Chromium), the SVG
|
|
159
|
+
* displacement filter is attached; otherwise it degrades to the frosted look.
|
|
160
|
+
* - **React Native:** a documented NO-OP. RN has no SVG-over-backdrop
|
|
161
|
+
* compositing, so the prop is accepted for a single cross-platform contract
|
|
162
|
+
* but the surface always renders the frosted fallback.
|
|
163
|
+
*
|
|
164
|
+
* @defaultValue `false`
|
|
165
|
+
*/
|
|
166
|
+
displacement?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Strength of the displacement/refraction distortion. Higher values bend the
|
|
169
|
+
* backdrop more aggressively.
|
|
170
|
+
*
|
|
171
|
+
* @defaultValue `70`
|
|
172
|
+
*/
|
|
173
|
+
displacementScale?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Backdrop blur radius in pixels applied behind the glass.
|
|
176
|
+
*
|
|
177
|
+
* @defaultValue `0.0625`
|
|
178
|
+
*/
|
|
179
|
+
blurAmount?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Backdrop saturation multiplier (`1` = unchanged). Boosts color vibrancy of
|
|
182
|
+
* the content seen through the glass.
|
|
183
|
+
*
|
|
184
|
+
* @defaultValue `140`
|
|
185
|
+
*/
|
|
186
|
+
saturation?: number;
|
|
187
|
+
/**
|
|
188
|
+
* Intensity of the chromatic aberration (RGB channel separation) at the
|
|
189
|
+
* refracted edges. `0` disables it.
|
|
190
|
+
*
|
|
191
|
+
* @defaultValue `2`
|
|
192
|
+
*/
|
|
193
|
+
aberrationIntensity?: number;
|
|
194
|
+
/**
|
|
195
|
+
* How elastically the glass reacts to pointer movement. `0` is rigid; higher
|
|
196
|
+
* values produce a softer, more rubbery follow.
|
|
197
|
+
*
|
|
198
|
+
* @defaultValue `0.15`
|
|
199
|
+
*/
|
|
200
|
+
elasticity?: number;
|
|
201
|
+
/**
|
|
202
|
+
* Corner radius of the glass surface. A number is treated as pixels; a string
|
|
203
|
+
* is passed through as a CSS length (e.g. `'1rem'`, `'50%'`).
|
|
204
|
+
*
|
|
205
|
+
* @defaultValue `999`
|
|
206
|
+
*/
|
|
207
|
+
cornerRadius?: number | string;
|
|
208
|
+
/**
|
|
209
|
+
* Inner padding of the glass surface. A number is treated as pixels; a string
|
|
210
|
+
* is passed through as a CSS shorthand (e.g. `'8px 16px'`).
|
|
211
|
+
*
|
|
212
|
+
* @defaultValue `'24px'`
|
|
213
|
+
*/
|
|
214
|
+
padding?: number | string;
|
|
215
|
+
/**
|
|
216
|
+
* Hint that the glass sits over a light background, so it tunes its tint and
|
|
217
|
+
* contrast for legibility.
|
|
218
|
+
*
|
|
219
|
+
* This is the MANUAL override. When set explicitly it ALWAYS wins over
|
|
220
|
+
* {@link adaptiveTint} (see that prop's precedence note) — the two never fight.
|
|
221
|
+
*
|
|
222
|
+
* @defaultValue `false`
|
|
223
|
+
*/
|
|
224
|
+
overLight?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Opt into CONTENT-ADAPTIVE auto-tint (plan 018). When `true`, the glass
|
|
227
|
+
* samples the luminance of the backdrop behind it (via the plan-017 sampler)
|
|
228
|
+
* and automatically shifts toward a light or dark treatment — the SAME tint /
|
|
229
|
+
* displacement / blur plumbing {@link overLight} already drives — so foreground
|
|
230
|
+
* content stays legible without hand-tuning `overLight`. A bright backdrop
|
|
231
|
+
* yields the light treatment (`overLight`-equivalent), a dark backdrop the
|
|
232
|
+
* default treatment.
|
|
233
|
+
*
|
|
234
|
+
* ## Precedence (documented rule)
|
|
235
|
+
*
|
|
236
|
+
* An EXPLICIT {@link overLight} always wins. `adaptiveTint` only drives the
|
|
237
|
+
* light/dark treatment when `overLight` is left unset/undefined:
|
|
238
|
+
*
|
|
239
|
+
* ```ts
|
|
240
|
+
* effectiveOverLight = overLight ?? (adaptiveTint && scheme ? scheme === 'light' : false)
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* ## SSR / hydration
|
|
244
|
+
*
|
|
245
|
+
* The server and the first client paint render the default (unsampled,
|
|
246
|
+
* conservative) treatment, so hydration never mismatches. The sampled treatment
|
|
247
|
+
* is applied in an effect after mount.
|
|
248
|
+
*
|
|
249
|
+
* ## Graceful degradation
|
|
250
|
+
*
|
|
251
|
+
* When the backdrop cannot be sampled (cross-origin-tainted canvas, no canvas,
|
|
252
|
+
* SSR) the reading is `sampled: false` and auto-tint falls back to
|
|
253
|
+
* `overLight ?? false` with no error, no flicker loop, and no `console.error`.
|
|
254
|
+
*
|
|
255
|
+
* ## Accessibility interaction
|
|
256
|
+
*
|
|
257
|
+
* Under `(prefers-contrast: more)` (plan 014) the high-contrast treatment wins:
|
|
258
|
+
* auto-tint never undercuts the increased-contrast surface/legibility treatment.
|
|
259
|
+
*
|
|
260
|
+
* ## Limitation
|
|
261
|
+
*
|
|
262
|
+
* Auto-tint is BEST-EFFORT legibility. Critical text over unknown or
|
|
263
|
+
* cross-origin backdrops (which cannot be sampled) should be verified manually —
|
|
264
|
+
* the auto path silently falls back rather than guessing.
|
|
265
|
+
*
|
|
266
|
+
* @defaultValue `false`
|
|
267
|
+
*/
|
|
268
|
+
adaptiveTint?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Opt into SCROLL-AWARE SHADOW (plan 021). When `true`, the glass's decoupled
|
|
271
|
+
* outer drop-shadow (the `[data-lg-shadow]` sibling rendered behind the clipped
|
|
272
|
+
* surface) modulates its depth/darkness from the backdrop behind the element
|
|
273
|
+
* (via the plan-017 sampler): it DEEPENS and DARKENS over a dark/dense backdrop
|
|
274
|
+
* (reinforcing the "floating above content" read as text scrolls beneath) and
|
|
275
|
+
* EASES/LIGHTENS over a light/solid one. Apple's Liquid Glass does exactly this
|
|
276
|
+
* as content scrolls under a pinned glass bar.
|
|
277
|
+
*
|
|
278
|
+
* ## What it touches
|
|
279
|
+
*
|
|
280
|
+
* ONLY the existing drop-shadow sibling's blur/offset/opacity vary. The shadow
|
|
281
|
+
* stays a SIBLING of the `overflow:hidden` glass surface (never a box-shadow on
|
|
282
|
+
* the clipped node — the plan-005 layering invariant); no new layer is added.
|
|
283
|
+
*
|
|
284
|
+
* ## SSR / hydration
|
|
285
|
+
*
|
|
286
|
+
* The server and the first client paint render the conservative static shadow,
|
|
287
|
+
* so hydration never mismatches. The modulated shadow is applied in an effect
|
|
288
|
+
* after mount; sampling is throttled by the hook.
|
|
289
|
+
*
|
|
290
|
+
* ## Graceful degradation
|
|
291
|
+
*
|
|
292
|
+
* When the backdrop cannot be sampled (cross-origin-tainted canvas, no canvas,
|
|
293
|
+
* SSR) the reading is `sampled: false` and the shadow falls back to the current
|
|
294
|
+
* static shadow with no error and no flicker loop.
|
|
295
|
+
*
|
|
296
|
+
* ## Reduced motion
|
|
297
|
+
*
|
|
298
|
+
* Under `(prefers-reduced-motion: reduce)` the shadow does not animate between
|
|
299
|
+
* depths — it snaps/stays static (no transition).
|
|
300
|
+
*
|
|
301
|
+
* This is ADDITIVE and non-breaking: omitting it (default `false`) keeps today's
|
|
302
|
+
* rendering byte-for-byte and leaves the committed pixel baseline unchanged.
|
|
303
|
+
*
|
|
304
|
+
* @defaultValue `false`
|
|
305
|
+
*/
|
|
306
|
+
scrollAwareShadow?: boolean;
|
|
307
|
+
/**
|
|
308
|
+
* The Liquid Glass material variant. See {@link GlassVariant}.
|
|
309
|
+
*
|
|
310
|
+
* - `'regular'` (default) — EXACTLY the current behavior, including
|
|
311
|
+
* content-adaptive auto-tint when {@link adaptiveTint} is enabled. The
|
|
312
|
+
* dependable, fully adaptive control surface.
|
|
313
|
+
* - `'clear'` — a permanently MORE transparent surface (lower fill opacity /
|
|
314
|
+
* reduced tint) for media-rich contexts, with an optional dimming scrim
|
|
315
|
+
* behind the content so labels stay legible over busy media. Geometry is
|
|
316
|
+
* unchanged from `'regular'`.
|
|
317
|
+
*
|
|
318
|
+
* ## Interaction rules
|
|
319
|
+
*
|
|
320
|
+
* - **Clear is non-adaptive by definition.** In `'clear'`, {@link adaptiveTint}
|
|
321
|
+
* is a **no-op** — the glass never samples the backdrop and never flips its
|
|
322
|
+
* tint/ink. (Use `'regular'` + `adaptiveTint` for the adaptive material.)
|
|
323
|
+
* - **`overLight` still nudges legibility, not adaptivity.** In `'clear'`, an
|
|
324
|
+
* explicit {@link overLight} may still tune the dimming/legibility treatment,
|
|
325
|
+
* but it does NOT re-enable the adaptive sampling path.
|
|
326
|
+
* - **Accessibility wins.** Under `(prefers-contrast: more)` (plan 014) the
|
|
327
|
+
* high-contrast treatment STILL applies in both variants — a11y beats the
|
|
328
|
+
* Clear aesthetic, so Clear surfaces become legible/high-contrast when the
|
|
329
|
+
* user asks for it.
|
|
330
|
+
*
|
|
331
|
+
* Per Apple's guidance, the two variants **should never be mixed** in the same
|
|
332
|
+
* context. This is ADDITIVE and non-breaking: omitting it keeps today's
|
|
333
|
+
* behavior exactly.
|
|
334
|
+
*
|
|
335
|
+
* @defaultValue `'regular'`
|
|
336
|
+
*/
|
|
337
|
+
variant?: GlassVariant;
|
|
338
|
+
/**
|
|
339
|
+
* Which displacement algorithm to use. See {@link DisplacementMode}.
|
|
340
|
+
*
|
|
341
|
+
* @defaultValue `'standard'`
|
|
342
|
+
*/
|
|
343
|
+
mode?: DisplacementMode;
|
|
344
|
+
/**
|
|
345
|
+
* CROSS-BROWSER copy-refraction content (plan 030). Distinct from
|
|
346
|
+
* {@link displacement} (the Chromium-only `backdrop-filter: url(#…)` bend of
|
|
347
|
+
* the LIVE page behind the glass): this path applies the SAME SVG displacement
|
|
348
|
+
* filter as an ELEMENT `filter: url(#…)` to a COPY of content, which DOES run in
|
|
349
|
+
* Chromium, Firefox, AND WebKit. The refracted copy is a decorative sibling
|
|
350
|
+
* BEHIND the crisp {@link children} (never an ancestor of them), so interactive
|
|
351
|
+
* content stays selectable and clickable.
|
|
352
|
+
*
|
|
353
|
+
* - **`refract` set** — the copy layer renders THIS node (a lens floated over
|
|
354
|
+
* content the glass doesn't own), filtered; {@link behind} fills the bleed and
|
|
355
|
+
* crisp {@link children} render on top. The node re-renders with the parent
|
|
356
|
+
* (React keeps it in sync) and the lens region re-measures on resize.
|
|
357
|
+
* - **`refract` unset but {@link size}/{@link center} set** — IN-PLACE mode: the
|
|
358
|
+
* glass refracts a copy of its OWN {@link children} through the element filter,
|
|
359
|
+
* with the crisp children on top.
|
|
360
|
+
*
|
|
361
|
+
* Omitting `refract`, {@link size}, AND {@link center} disables this path
|
|
362
|
+
* entirely — the render is byte-for-byte the {@link displacement} behavior.
|
|
363
|
+
*
|
|
364
|
+
* ## Contract: the refracted copy must be PRESENTATIONAL
|
|
365
|
+
*
|
|
366
|
+
* The copy layer renders a SECOND instance of the copied node (`refract`, or —
|
|
367
|
+
* in IN-PLACE mode — a copy of {@link children}). It is `aria-hidden` + `inert`
|
|
368
|
+
* + `pointer-events: none`, so it is hidden from the a11y tree and cannot be
|
|
369
|
+
* interacted with. But React still MOUNTS it, so the copied subtree should be
|
|
370
|
+
* PURELY PRESENTATIONAL:
|
|
371
|
+
*
|
|
372
|
+
* - **No `id`s / label targets / in-page anchor targets** — they would be
|
|
373
|
+
* duplicated in the DOM (`getElementById` / `label[for]` / `#hash` could bind
|
|
374
|
+
* the hidden copy).
|
|
375
|
+
* - **No effects / focus-on-mount / data fetching / callback refs** — they run
|
|
376
|
+
* for BOTH instances.
|
|
377
|
+
* - **No focusable controls** on engines that don't honor `inert` (older
|
|
378
|
+
* Safari/Firefox), where they could otherwise become tabbable inside an
|
|
379
|
+
* `aria-hidden` subtree.
|
|
380
|
+
*
|
|
381
|
+
* For anything non-trivial, prefer `refract={<decorative node>}` (a background
|
|
382
|
+
* you fully control) over IN-PLACE copying of rich interactive `children`.
|
|
383
|
+
* Passing `refract={null}` opts in but renders an EMPTY lens (only the
|
|
384
|
+
* {@link behind} bleed) — it does NOT fall back to copying `children`.
|
|
385
|
+
*
|
|
386
|
+
* **React Native:** a documented NO-OP (no SVG filters; always frosted).
|
|
387
|
+
*
|
|
388
|
+
* @defaultValue `undefined`
|
|
389
|
+
*/
|
|
390
|
+
refract?: ReactNode;
|
|
391
|
+
/**
|
|
392
|
+
* Explicit lens {@link GlassSize} for the copy-refraction path (plan 030). When
|
|
393
|
+
* omitted the lens is sized to the glass's measured dimensions. Presence of this
|
|
394
|
+
* prop (with or without {@link refract}) opts INTO the cross-browser copy
|
|
395
|
+
* refraction in IN-PLACE mode (refract a copy of the own {@link children}).
|
|
396
|
+
*
|
|
397
|
+
* **React Native:** a documented NO-OP.
|
|
398
|
+
*
|
|
399
|
+
* @defaultValue `undefined` (use measured dimensions)
|
|
400
|
+
*/
|
|
401
|
+
size?: GlassSize;
|
|
402
|
+
/**
|
|
403
|
+
* Lens focal point ({@link GlassCenter}, fractions in `[0, 1]`) for the
|
|
404
|
+
* copy-refraction path (plan 030). Drives the refracted copy's
|
|
405
|
+
* `transform-origin`. Presence opts into the cross-browser copy refraction in
|
|
406
|
+
* IN-PLACE mode, same as {@link size}.
|
|
407
|
+
*
|
|
408
|
+
* **React Native:** a documented NO-OP.
|
|
409
|
+
*
|
|
410
|
+
* @defaultValue `{ x: 0.5, y: 0.5 }` (center)
|
|
411
|
+
*/
|
|
412
|
+
center?: GlassCenter;
|
|
413
|
+
/**
|
|
414
|
+
* Bleed fill (any CSS `background` value) painted behind the refracted copy on
|
|
415
|
+
* the copy-refraction path (plan 030), so the displacement has something to pull
|
|
416
|
+
* from where the copied content doesn't cover the lens box. Most useful with
|
|
417
|
+
* {@link refract} (a lens over foreign content); usually unnecessary for
|
|
418
|
+
* IN-PLACE mode. No effect unless the copy-refraction path is active.
|
|
419
|
+
*
|
|
420
|
+
* **React Native:** a documented NO-OP.
|
|
421
|
+
*
|
|
422
|
+
* @defaultValue `undefined`
|
|
423
|
+
*/
|
|
424
|
+
behind?: string;
|
|
425
|
+
/** Additional class name(s) applied to the outermost glass element. */
|
|
426
|
+
className?: string;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Props for the WEB `<LiquidGlass>` PRIMITIVE.
|
|
430
|
+
*
|
|
431
|
+
* The platform-neutral {@link LiquidGlassCoreProps} plus the DOM-only props the
|
|
432
|
+
* web surface needs: inline `style`, the `onClick` handler, and the externally
|
|
433
|
+
* controlled pointer inputs. This is the same surface the library has always
|
|
434
|
+
* exposed — splitting the core out is type-only and changes nothing for web
|
|
435
|
+
* consumers.
|
|
436
|
+
*/
|
|
437
|
+
export type LiquidGlassProps = LiquidGlassCoreProps & {
|
|
438
|
+
/** Inline styles merged onto the outermost glass element. */
|
|
439
|
+
style?: CSSProperties;
|
|
440
|
+
/** Click handler forwarded to the glass surface. */
|
|
441
|
+
onClick?: MouseEventHandler<HTMLDivElement>;
|
|
442
|
+
/**
|
|
443
|
+
* Externally controlled global pointer position. When provided, the primitive
|
|
444
|
+
* uses this instead of subscribing to its own pointer events — useful for
|
|
445
|
+
* coordinating many glass surfaces from one shared tracker.
|
|
446
|
+
*
|
|
447
|
+
* Leave undefined for the uncontrolled default (the primitive tracks the
|
|
448
|
+
* pointer itself).
|
|
449
|
+
*/
|
|
450
|
+
globalMousePos?: MousePos;
|
|
451
|
+
/**
|
|
452
|
+
* Externally controlled pointer offset relative to the glass element's
|
|
453
|
+
* center, in CSS pixels. Paired with {@link globalMousePos} for fully
|
|
454
|
+
* controlled motion.
|
|
455
|
+
*/
|
|
456
|
+
mouseOffset?: MousePos;
|
|
457
|
+
/**
|
|
458
|
+
* Element (or ref to one) whose bounds define the coordinate space for
|
|
459
|
+
* pointer tracking. Defaults to the viewport when omitted or `null`.
|
|
460
|
+
*
|
|
461
|
+
* @defaultValue `null` (viewport)
|
|
462
|
+
*/
|
|
463
|
+
mouseContainer?: RefObject<HTMLElement | null> | HTMLElement | null;
|
|
464
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type contracts for `@aberhamm/liquid-glass-react`.
|
|
3
|
+
*
|
|
4
|
+
* These types are the load-bearing surface that every later plan (refraction,
|
|
5
|
+
* motion, fallback, prebuilt components) builds against. They are defined ONCE
|
|
6
|
+
* here so the API cannot drift between plans. No runtime code lives in this
|
|
7
|
+
* file — it is types only.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aberhamm/liquid-glass-react",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "An independent React reimplementation of a liquid-glass UI component library.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Matthew Aberham",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"react",
|
|
10
|
+
"liquid-glass",
|
|
11
|
+
"glassmorphism",
|
|
12
|
+
"backdrop-filter",
|
|
13
|
+
"svg-filter",
|
|
14
|
+
"displacement-map",
|
|
15
|
+
"glass",
|
|
16
|
+
"ui",
|
|
17
|
+
"components",
|
|
18
|
+
"refraction"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/aberhamm/liquid-glass-react.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/aberhamm/liquid-glass-react#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/aberhamm/liquid-glass-react/issues"
|
|
27
|
+
},
|
|
28
|
+
"packageManager": "pnpm@10.33.3",
|
|
29
|
+
"sideEffects": ["**/*.css"],
|
|
30
|
+
"files": ["dist"],
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.mjs",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"react-native": "./dist/native/index.native.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"react-native": {
|
|
38
|
+
"types": "./dist/native/index.native.d.ts",
|
|
39
|
+
"default": "./dist/native/index.native.js"
|
|
40
|
+
},
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.mjs"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/index.d.cts",
|
|
47
|
+
"default": "./dist/index.cjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"./styles.css": "./dist/liquid-glass-react.css"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"pnpm": {
|
|
56
|
+
"onlyBuiltDependencies": ["@biomejs/biome", "esbuild"]
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vite build && pnpm build:native",
|
|
60
|
+
"build:native": "tsc -p tsconfig.native.build.json",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"typecheck:native": "tsc -p tsconfig.native.json --noEmit",
|
|
63
|
+
"lint": "biome check .",
|
|
64
|
+
"format": "biome format --write .",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:native": "vitest run --config vitest.native.config.ts",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"storybook": "storybook dev -p 6006 --no-open",
|
|
69
|
+
"build-storybook": "storybook build",
|
|
70
|
+
"e2e": "pnpm build-storybook && playwright test",
|
|
71
|
+
"e2e:ui": "pnpm build-storybook && playwright test --ui",
|
|
72
|
+
"attw": "attw --pack . --exclude-entrypoints ./styles.css",
|
|
73
|
+
"pack:check": "npm pack --dry-run",
|
|
74
|
+
"verify:expo": "node scripts/verify-native-resolution.mjs && vitest run --config vitest.native.config.ts",
|
|
75
|
+
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"react": ">=18",
|
|
79
|
+
"react-dom": ">=18",
|
|
80
|
+
"react-native": ">=0.74",
|
|
81
|
+
"expo-blur": ">=13",
|
|
82
|
+
"expo-linear-gradient": ">=13"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"react-native": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"expo-blur": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"expo-linear-gradient": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"devDependencies": {
|
|
96
|
+
"@arethetypeswrong/cli": "^0.18.3",
|
|
97
|
+
"@biomejs/biome": "^1.9.4",
|
|
98
|
+
"@playwright/test": "^1.61.0",
|
|
99
|
+
"@storybook/addon-essentials": "^8.6.14",
|
|
100
|
+
"@storybook/react": "^8.6.18",
|
|
101
|
+
"@storybook/react-vite": "^8.6.18",
|
|
102
|
+
"@testing-library/dom": "^10.4.0",
|
|
103
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
104
|
+
"@testing-library/react": "^16.1.0",
|
|
105
|
+
"@testing-library/react-native": "^14.0.1",
|
|
106
|
+
"@types/react": "^18.3.18",
|
|
107
|
+
"@types/react-dom": "^18.3.5",
|
|
108
|
+
"expo-blur": "^56.0.3",
|
|
109
|
+
"expo-linear-gradient": "^56.0.4",
|
|
110
|
+
"http-server": "^14.1.1",
|
|
111
|
+
"jsdom": "^25.0.1",
|
|
112
|
+
"react": "^18.3.1",
|
|
113
|
+
"react-dom": "^18.3.1",
|
|
114
|
+
"react-native": "^0.83.10",
|
|
115
|
+
"react-test-renderer": "^18.3.1",
|
|
116
|
+
"storybook": "^8.6.18",
|
|
117
|
+
"typescript": "^5.7.3",
|
|
118
|
+
"vite": "^6.0.7",
|
|
119
|
+
"vite-plugin-dts": "^4.4.0",
|
|
120
|
+
"vitest": "^2.1.8"
|
|
121
|
+
}
|
|
122
|
+
}
|