jfs-components 0.0.73 → 0.0.77
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/CHANGELOG.md +115 -6
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +147 -82
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/Avatar/Avatar.js +20 -0
- package/lib/commonjs/components/Badge/Badge.js +23 -0
- package/lib/commonjs/components/Button/Button.js +37 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
- package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
- package/lib/commonjs/components/FormField/FormField.js +328 -178
- package/lib/commonjs/components/IconButton/IconButton.js +20 -0
- package/lib/commonjs/components/Image/Image.js +26 -1
- package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +116 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +82 -0
- package/lib/commonjs/components/LottiePlayer/loadNativeLottieView.js +74 -0
- package/lib/commonjs/components/LottiePlayer/loadWebLottieView.js +50 -0
- package/lib/commonjs/components/PageHero/PageHero.js +189 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
- package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +32 -17
- package/lib/commonjs/components/Text/Text.js +40 -3
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +67 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/Icon.js +16 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/skeleton/Skeleton.js +234 -0
- package/lib/commonjs/skeleton/SkeletonGroup.js +140 -0
- package/lib/commonjs/skeleton/index.js +58 -0
- package/lib/commonjs/skeleton/shimmer-tokens.js +189 -0
- package/lib/commonjs/skeleton/useReducedMotion.js +64 -0
- package/lib/module/components/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/ActionFooter/ActionFooter.js +146 -82
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/Avatar/Avatar.js +19 -0
- package/lib/module/components/Badge/Badge.js +23 -0
- package/lib/module/components/Button/Button.js +37 -0
- package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
- package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
- package/lib/module/components/Dropdown/Dropdown.js +206 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
- package/lib/module/components/FormField/FormField.js +330 -180
- package/lib/module/components/IconButton/IconButton.js +20 -0
- package/lib/module/components/Image/Image.js +25 -1
- package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.js +111 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.web.js +77 -0
- package/lib/module/components/LottiePlayer/loadNativeLottieView.js +69 -0
- package/lib/module/components/LottiePlayer/loadWebLottieView.js +45 -0
- package/lib/module/components/PageHero/PageHero.js +183 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
- package/lib/module/components/PoweredByLabel/finvu.png +0 -0
- package/lib/module/components/RechargeCard/RechargeCard.js +33 -17
- package/lib/module/components/Text/Text.js +40 -3
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +8 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/Icon.js +16 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/skeleton/Skeleton.js +229 -0
- package/lib/module/skeleton/SkeletonGroup.js +133 -0
- package/lib/module/skeleton/index.js +6 -0
- package/lib/module/skeleton/shimmer-tokens.js +181 -0
- package/lib/module/skeleton/useReducedMotion.js +61 -0
- package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +26 -21
- package/lib/typescript/src/components/Avatar/Avatar.d.ts +7 -1
- package/lib/typescript/src/components/Badge/Badge.d.ts +7 -1
- package/lib/typescript/src/components/Button/Button.d.ts +8 -1
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
- package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
- package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
- package/lib/typescript/src/components/IconButton/IconButton.d.ts +7 -1
- package/lib/typescript/src/components/Image/Image.d.ts +8 -1
- package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +85 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.web.d.ts +28 -0
- package/lib/typescript/src/components/LottiePlayer/loadNativeLottieView.d.ts +11 -0
- package/lib/typescript/src/components/LottiePlayer/loadWebLottieView.d.ts +11 -0
- package/lib/typescript/src/components/PageHero/PageHero.d.ts +79 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
- package/lib/typescript/src/components/Text/Text.d.ts +31 -2
- package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
- package/lib/typescript/src/components/index.d.ts +8 -1
- package/lib/typescript/src/icons/Icon.d.ts +7 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/skeleton/Skeleton.d.ts +60 -0
- package/lib/typescript/src/skeleton/SkeletonGroup.d.ts +78 -0
- package/lib/typescript/src/skeleton/index.d.ts +5 -0
- package/lib/typescript/src/skeleton/shimmer-tokens.d.ts +160 -0
- package/lib/typescript/src/skeleton/useReducedMotion.d.ts +15 -0
- package/package.json +11 -3
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/ActionFooter/ActionFooter.tsx +152 -86
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/Avatar/Avatar.tsx +26 -0
- package/src/components/Badge/Badge.tsx +27 -0
- package/src/components/Button/Button.tsx +40 -0
- package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
- package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
- package/src/components/Dropdown/Dropdown.tsx +331 -0
- package/src/components/DropdownInput/DropdownInput.tsx +819 -0
- package/src/components/FormField/FormField.tsx +542 -215
- package/src/components/IconButton/IconButton.tsx +27 -0
- package/src/components/Image/Image.tsx +25 -0
- package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
- package/src/components/LottiePlayer/LottiePlayer.tsx +145 -0
- package/src/components/LottiePlayer/LottiePlayer.web.tsx +94 -0
- package/src/components/LottiePlayer/loadNativeLottieView.tsx +87 -0
- package/src/components/LottiePlayer/loadWebLottieView.tsx +64 -0
- package/src/components/PageHero/PageHero.tsx +257 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
- package/src/components/PoweredByLabel/finvu.png +0 -0
- package/src/components/RechargeCard/RechargeCard.tsx +32 -24
- package/src/components/Text/Text.tsx +78 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +16 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/Icon.tsx +17 -0
- package/src/icons/registry.ts +1 -1
- package/src/index.ts +1 -0
- package/src/skeleton/Skeleton.tsx +298 -0
- package/src/skeleton/SkeletonGroup.tsx +193 -0
- package/src/skeleton/index.ts +10 -0
- package/src/skeleton/shimmer-tokens.ts +221 -0
- package/src/skeleton/useReducedMotion.ts +72 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the skeleton shimmer behaviour spec.
|
|
3
|
+
*
|
|
4
|
+
* The two modes mirror the documented design behaviour:
|
|
5
|
+
*
|
|
6
|
+
* Normal:
|
|
7
|
+
* - 1.5s linear sawtooth cycle (band sweeps end-to-end then resets)
|
|
8
|
+
* - 135° gradient (top-left -> bottom-right), fixed angle on any aspect
|
|
9
|
+
* ratio via SVG userSpaceOnUse coordinates
|
|
10
|
+
* - Band alpha animates 33% -> 100% -> 33% across the band core;
|
|
11
|
+
* transparent fades at the gradient edges hide the sawtooth reset
|
|
12
|
+
* - 50–100ms per-item stagger
|
|
13
|
+
*
|
|
14
|
+
* Reduced motion (system-level OS toggle):
|
|
15
|
+
* - 3.0s ease-in-out cycle
|
|
16
|
+
* - No translation — solid white overlay opacity pulses 33% -> 100% in
|
|
17
|
+
* place (`opacityRange` below is consumed in this path)
|
|
18
|
+
* - No gradient
|
|
19
|
+
* - No stagger
|
|
20
|
+
*
|
|
21
|
+
* Centralising the spec here keeps `Skeleton.tsx` purely presentational and
|
|
22
|
+
* lets us tune the behaviour in one place if design ever updates it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type SkeletonKind = 'text' | 'image' | 'badge' | 'other';
|
|
26
|
+
|
|
27
|
+
export interface ShimmerModeSpec {
|
|
28
|
+
durationMs: number;
|
|
29
|
+
/** Per-item delay range. Min/max in milliseconds. */
|
|
30
|
+
staggerMsRange: readonly [number, number];
|
|
31
|
+
gradient: boolean;
|
|
32
|
+
/** Opacity range applied to the moving "blob" overlay. */
|
|
33
|
+
opacityRange: readonly [number, number];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const SHIMMER = {
|
|
37
|
+
normal: {
|
|
38
|
+
durationMs: 1500,
|
|
39
|
+
staggerMsRange: [50, 100] as const,
|
|
40
|
+
gradient: true,
|
|
41
|
+
opacityRange: [0.33, 1.0] as const,
|
|
42
|
+
} satisfies ShimmerModeSpec,
|
|
43
|
+
reduced: {
|
|
44
|
+
durationMs: 3000,
|
|
45
|
+
staggerMsRange: [0, 0] as const,
|
|
46
|
+
gradient: false,
|
|
47
|
+
opacityRange: [0.33, 1.0] as const,
|
|
48
|
+
} satisfies ShimmerModeSpec,
|
|
49
|
+
/**
|
|
50
|
+
* Gradient angle in degrees, measured the CSS way: 0deg points "up", 90deg
|
|
51
|
+
* "right", 135deg therefore points down-right (top-left corner to
|
|
52
|
+
* bottom-right corner).
|
|
53
|
+
*/
|
|
54
|
+
gradientAngleDeg: 135,
|
|
55
|
+
/**
|
|
56
|
+
* Hard cap on the cumulative stagger delay so very long lists don't drift
|
|
57
|
+
* out forever; the wave wraps after this many ms.
|
|
58
|
+
*/
|
|
59
|
+
maxStaggerMs: 600,
|
|
60
|
+
} as const
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Token names — referenced via `getVariableByName(...)` so the existing
|
|
64
|
+
* design-token resolver does its job (caching, mode resolution, etc.).
|
|
65
|
+
*
|
|
66
|
+
* The four tokens already live in the Figma export at
|
|
67
|
+
* `src/design-tokens/Coin Variables-variables-full.json`:
|
|
68
|
+
*
|
|
69
|
+
* - `bg/defaultSkeleton` -> base color for every skeleton block
|
|
70
|
+
* - `cornerRadius/defaultSkeleton` -> text & "other" (pill: 9999)
|
|
71
|
+
* - `cornerRadius/imageSkeleton` -> images (10)
|
|
72
|
+
* - `cornerRadius/badgeSkeleton` -> badges (4)
|
|
73
|
+
*/
|
|
74
|
+
export const SKELETON_TOKEN = {
|
|
75
|
+
background: 'bg/defaultSkeleton',
|
|
76
|
+
radius: {
|
|
77
|
+
text: 'cornerRadius/defaultSkeleton',
|
|
78
|
+
image: 'cornerRadius/imageSkeleton',
|
|
79
|
+
badge: 'cornerRadius/badgeSkeleton',
|
|
80
|
+
other: 'cornerRadius/defaultSkeleton',
|
|
81
|
+
} satisfies Record<SkeletonKind, string>,
|
|
82
|
+
} as const
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fallback constants used when the token resolver is unavailable (tests,
|
|
86
|
+
* SSR, etc.). Match the current Figma values.
|
|
87
|
+
*/
|
|
88
|
+
export const SKELETON_FALLBACK = {
|
|
89
|
+
backgroundColor: 'rgb(245, 245, 246)',
|
|
90
|
+
radius: {
|
|
91
|
+
text: 9999,
|
|
92
|
+
image: 10,
|
|
93
|
+
badge: 4,
|
|
94
|
+
other: 9999,
|
|
95
|
+
} satisfies Record<SkeletonKind, number>,
|
|
96
|
+
} as const
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compute a stable per-item delay from a 0-based registration index.
|
|
100
|
+
*
|
|
101
|
+
* We pick the midpoint of the documented range (75ms) so the cascade is
|
|
102
|
+
* deterministic for snapshot tests and visually identical between renders.
|
|
103
|
+
* The total delay is then wrapped at `maxStaggerMs` so deep lists don't
|
|
104
|
+
* drift past the cap.
|
|
105
|
+
*/
|
|
106
|
+
export function staggerDelayMs(index: number, mode: ShimmerModeSpec): number {
|
|
107
|
+
const [min, max] = mode.staggerMsRange
|
|
108
|
+
if (max <= 0) return 0
|
|
109
|
+
const step = (min + max) / 2
|
|
110
|
+
return (index * step) % SHIMMER.maxStaggerMs
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** One stop on the moving shimmer gradient (offset 0–1 along the 135° axis). */
|
|
114
|
+
export interface ShimmerGradientStop {
|
|
115
|
+
offset: number
|
|
116
|
+
opacity: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Gradient stops for the normal-mode moving band. The peak sits at 0.5; fully
|
|
121
|
+
* transparent tails at 0 and 1. The 0.30 / 0.70 stops mark where the band
|
|
122
|
+
* reaches the documented 33 % alpha.
|
|
123
|
+
*/
|
|
124
|
+
export const SHIMMER_GRADIENT_STOPS: readonly ShimmerGradientStop[] = [
|
|
125
|
+
{ offset: 0, opacity: 0 },
|
|
126
|
+
{ offset: 0.30, opacity: 0.33 },
|
|
127
|
+
{ offset: 0.50, opacity: 1 },
|
|
128
|
+
{ offset: 0.70, opacity: 0.33 },
|
|
129
|
+
{ offset: 1.0, opacity: 0 },
|
|
130
|
+
] as const
|
|
131
|
+
|
|
132
|
+
/** Offset (0–1) of the brightest point on the gradient line. */
|
|
133
|
+
export function gradientPeakOffset(
|
|
134
|
+
stops: readonly ShimmerGradientStop[] = SHIMMER_GRADIENT_STOPS,
|
|
135
|
+
): number {
|
|
136
|
+
const peak = stops.find((s) => s.opacity === 1)
|
|
137
|
+
return peak?.offset ?? 0.5
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* How far the gradient extends from the peak to a fully transparent stop,
|
|
142
|
+
* expressed as a fraction of the gradient line (0–1). With the default stops
|
|
143
|
+
* this is 0.5 (peak at 0.5, transparent at 0 and 1).
|
|
144
|
+
*
|
|
145
|
+
* This value drives the overshoot: the band must travel this fraction of a
|
|
146
|
+
* full corner-to-corner sweep *beyond* each corner so the soft transparent
|
|
147
|
+
* tails fully clear the box before the sawtooth reset.
|
|
148
|
+
*/
|
|
149
|
+
export function gradientTransparentExtent(
|
|
150
|
+
stops: readonly ShimmerGradientStop[] = SHIMMER_GRADIENT_STOPS,
|
|
151
|
+
): number {
|
|
152
|
+
const peak = gradientPeakOffset(stops)
|
|
153
|
+
const transparent = stops.filter((s) => s.opacity === 0)
|
|
154
|
+
if (transparent.length === 0) return 0.5
|
|
155
|
+
return Math.max(...transparent.map((s) => Math.abs(s.offset - peak)))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ShimmerMotionGeometry {
|
|
159
|
+
/** Square overlay side length (gradient is painted on this). */
|
|
160
|
+
overlaySize: number
|
|
161
|
+
/** Padding to centre the overlay on the box. */
|
|
162
|
+
padX: number
|
|
163
|
+
padY: number
|
|
164
|
+
/** Translation (k, k) when the band is fully off-screen before entry. */
|
|
165
|
+
kStart: number
|
|
166
|
+
/** Translation (k, k) when the band is fully off-screen after exit. */
|
|
167
|
+
kEnd: number
|
|
168
|
+
/**
|
|
169
|
+
* Normalised overshoot on each side of the corner-to-corner sweep. A value
|
|
170
|
+
* of 0.5 means the travel extends 50 % past each corner; at reset the
|
|
171
|
+
* clock jumps from `1 + overshoot` to `-overshoot` while the box still
|
|
172
|
+
* shows only the base skeleton colour.
|
|
173
|
+
*/
|
|
174
|
+
overshootFraction: number
|
|
175
|
+
/** k distance from peak-at-TL to peak-at-BR. */
|
|
176
|
+
cornerTravelK: number
|
|
177
|
+
/** k distance from peak to fully transparent tail on the gradient. */
|
|
178
|
+
fadeBeyondPeakK: number
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Derive the moving-shimmer geometry from box size, overlay size, and the
|
|
183
|
+
* gradient stop layout.
|
|
184
|
+
*
|
|
185
|
+
* Coordinate model (135° / down-right):
|
|
186
|
+
* - Peak stripe world diagonal sum: (W + H) / 2 + 2k
|
|
187
|
+
* - Gradient offset 0 → 1 spans 2 × overlaySize in diagonal-sum units
|
|
188
|
+
* - Transparent tail beyond peak: fadeExtent × 2 × overlaySize / 2
|
|
189
|
+
* = fadeExtent × overlaySize in k units
|
|
190
|
+
*
|
|
191
|
+
* The sawtooth clock maps linearly kStart → kEnd so t = 0 and t = 1 both
|
|
192
|
+
* land with the entire gradient outside the box — no jitter on reset.
|
|
193
|
+
*/
|
|
194
|
+
export function computeShimmerMotion(
|
|
195
|
+
width: number,
|
|
196
|
+
height: number,
|
|
197
|
+
overlaySize: number,
|
|
198
|
+
stops: readonly ShimmerGradientStop[] = SHIMMER_GRADIENT_STOPS,
|
|
199
|
+
): ShimmerMotionGeometry | null {
|
|
200
|
+
if (width <= 0 || height <= 0 || overlaySize <= 0) return null
|
|
201
|
+
|
|
202
|
+
const fadeExtent = gradientTransparentExtent(stops)
|
|
203
|
+
const cornerTravelK = (width + height) / 2
|
|
204
|
+
const baseSweepHalfK = cornerTravelK / 2
|
|
205
|
+
const fadeBeyondPeakK = fadeExtent * overlaySize
|
|
206
|
+
const kStart = -baseSweepHalfK - fadeBeyondPeakK
|
|
207
|
+
const kEnd = baseSweepHalfK + fadeBeyondPeakK
|
|
208
|
+
const overshootFraction =
|
|
209
|
+
cornerTravelK > 0 ? fadeBeyondPeakK / cornerTravelK : 0
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
overlaySize,
|
|
213
|
+
padX: (overlaySize - width) / 2,
|
|
214
|
+
padY: (overlaySize - height) / 2,
|
|
215
|
+
kStart,
|
|
216
|
+
kEnd,
|
|
217
|
+
overshootFraction,
|
|
218
|
+
cornerTravelK,
|
|
219
|
+
fadeBeyondPeakK,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { AccessibilityInfo, Platform } from 'react-native'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cross-platform "prefers reduced motion" hook.
|
|
6
|
+
*
|
|
7
|
+
* - Native: reads `AccessibilityInfo.isReduceMotionEnabled()` and subscribes
|
|
8
|
+
* to `reduceMotionChanged` events so the value stays live as the user
|
|
9
|
+
* toggles the OS setting.
|
|
10
|
+
* - Web: uses `window.matchMedia('(prefers-reduced-motion: reduce)')`,
|
|
11
|
+
* subscribing to its `change` event.
|
|
12
|
+
* - Anywhere either API is missing: returns `false` (no reduction).
|
|
13
|
+
*
|
|
14
|
+
* The hook never throws — every native API access is defensively guarded so
|
|
15
|
+
* the skeleton system stays safe in tests, SSR, and constrained sandboxes.
|
|
16
|
+
*/
|
|
17
|
+
export function useReducedMotion(): boolean {
|
|
18
|
+
const [reduced, setReduced] = useState<boolean>(false)
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
let cancelled = false
|
|
22
|
+
|
|
23
|
+
if (Platform.OS === 'web') {
|
|
24
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const mql = window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
28
|
+
const update = (matches: boolean) => {
|
|
29
|
+
if (!cancelled) setReduced(matches)
|
|
30
|
+
}
|
|
31
|
+
update(mql.matches)
|
|
32
|
+
const listener = (e: MediaQueryListEvent) => update(e.matches)
|
|
33
|
+
if (typeof mql.addEventListener === 'function') {
|
|
34
|
+
mql.addEventListener('change', listener)
|
|
35
|
+
return () => {
|
|
36
|
+
cancelled = true
|
|
37
|
+
mql.removeEventListener('change', listener)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const legacyMql = mql as MediaQueryList & {
|
|
41
|
+
addListener?: (l: (e: MediaQueryListEvent) => void) => void;
|
|
42
|
+
removeListener?: (l: (e: MediaQueryListEvent) => void) => void;
|
|
43
|
+
}
|
|
44
|
+
legacyMql.addListener?.(listener)
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true
|
|
47
|
+
legacyMql.removeListener?.(listener)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof AccessibilityInfo?.isReduceMotionEnabled === 'function') {
|
|
52
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
53
|
+
.then((value: boolean) => {
|
|
54
|
+
if (!cancelled) setReduced(!!value)
|
|
55
|
+
})
|
|
56
|
+
.catch(() => {})
|
|
57
|
+
}
|
|
58
|
+
const sub =
|
|
59
|
+
typeof AccessibilityInfo?.addEventListener === 'function'
|
|
60
|
+
? AccessibilityInfo.addEventListener('reduceMotionChanged', (value: boolean) => {
|
|
61
|
+
if (!cancelled) setReduced(!!value)
|
|
62
|
+
})
|
|
63
|
+
: null
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true
|
|
67
|
+
sub?.remove?.()
|
|
68
|
+
}
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
return reduced
|
|
72
|
+
}
|