37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
'use client'
|
|
import { FunctionComponent } from 'react';
|
|
import { keyframes } from '@emotion/react';
|
|
import Stack, { StackProps } from '@mui/material/Stack';
|
|
import AppIcon from '../AppIcon';
|
|
|
|
export type AppLoadingProps = StackProps;
|
|
|
|
const breathe = keyframes`
|
|
0%, 100% { opacity: 0.55; transform: scale(0.92); }
|
|
50% { opacity: 1; transform: scale(1); }
|
|
`;
|
|
|
|
/**
|
|
* The branded full-page loading splash — the Balinyaar mark with a subtle breathing motion, respecting
|
|
* `prefers-reduced-motion` (the animation only applies under the `no-preference` media query; a
|
|
* reduced-motion user sees a static, still-legible mark). Reserved for **true full-page waits** (the auth
|
|
* splash, a payment-gateway return, a route-level `Suspense` fallback) — inline/list loading should reach
|
|
* for a shaped skeleton (a co-located `<X.Skeleton />` twin) instead, never a bare spinner.
|
|
* @component AppLoading
|
|
*/
|
|
const AppLoading: FunctionComponent<AppLoadingProps> = ({ sx, ...rest }) => (
|
|
<Stack role="status" sx={{ my: 4, alignItems: 'center', justifyContent: 'center', ...sx }} {...rest}>
|
|
<Stack
|
|
sx={{
|
|
'@media (prefers-reduced-motion: no-preference)': {
|
|
animation: `${breathe} 1.6s ease-in-out infinite`,
|
|
},
|
|
}}
|
|
>
|
|
<AppIcon icon="logo" size={48} color="var(--bal-primary)" />
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
|
|
export default AppLoading;
|