and a reduced‑motion guard. Haptics now honor the toggle and still fall back gracefully on iOS (switch disabled when
navigator.vibrate isn’t available).
What changed
- Haptics preference storage + gating: resources/js/guest/lib/haptics.ts
- Preference hook: resources/js/guest/hooks/useHapticsPreference.ts
- Settings UI toggle in sheet + page: resources/js/guest/components/settings-sheet.tsx, resources/js/guest/pages/
SettingsPage.tsx
- i18n labels: resources/js/guest/i18n/messages.ts
- Tests: resources/js/guest/lib/__tests__/haptics.test.ts
63 lines
1.5 KiB
TypeScript
63 lines
1.5 KiB
TypeScript
import { prefersReducedMotion } from './motion';
|
|
|
|
export type HapticPattern = 'selection' | 'light' | 'medium' | 'success' | 'error';
|
|
|
|
const PATTERNS: Record<HapticPattern, number | number[]> = {
|
|
selection: 10,
|
|
light: 15,
|
|
medium: 30,
|
|
success: [10, 30, 10],
|
|
error: [20, 30, 20],
|
|
};
|
|
|
|
export const HAPTICS_STORAGE_KEY = 'guestHapticsEnabled';
|
|
|
|
export function supportsHaptics(): boolean {
|
|
return typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function';
|
|
}
|
|
|
|
export function getHapticsPreference(): boolean {
|
|
if (typeof window === 'undefined') {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
const raw = window.localStorage.getItem(HAPTICS_STORAGE_KEY);
|
|
if (raw === null) {
|
|
return true;
|
|
}
|
|
return raw !== '0';
|
|
} catch (error) {
|
|
console.warn('Failed to read haptics preference', error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export function setHapticsPreference(enabled: boolean): void {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
window.localStorage.setItem(HAPTICS_STORAGE_KEY, enabled ? '1' : '0');
|
|
} catch (error) {
|
|
console.warn('Failed to store haptics preference', error);
|
|
}
|
|
}
|
|
|
|
export function isHapticsEnabled(): boolean {
|
|
return getHapticsPreference() && supportsHaptics() && !prefersReducedMotion();
|
|
}
|
|
|
|
export function triggerHaptic(pattern: HapticPattern): void {
|
|
if (!isHapticsEnabled()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
navigator.vibrate(PATTERNS[pattern]);
|
|
} catch (error) {
|
|
console.warn('Haptic feedback failed', error);
|
|
}
|
|
}
|