ui phase 1

This commit is contained in:
hamid
2026-07-17 17:10:39 +03:30
parent f1cba6cf74
commit 370c1beefa
151 changed files with 4254 additions and 1840 deletions
@@ -0,0 +1,24 @@
import { render } from '@testing-library/react';
import { ThemeProvider } from '../../../theme';
import AppLoading from './AppLoading';
describe('<AppLoading/>', () => {
it('renders a status region with the brand mark', () => {
const { container } = render(
<ThemeProvider>
<AppLoading />
</ThemeProvider>,
);
expect(container.querySelector('[role="status"]')).toBeInTheDocument();
expect(container.querySelector('[data-icon="logo"]')).toBeInTheDocument();
});
it('forwards sx overrides to the root', () => {
const { container } = render(
<ThemeProvider>
<AppLoading data-testid="loading-root" />
</ThemeProvider>,
);
expect(container.querySelector('[data-testid="loading-root"]')).toBeInTheDocument();
});
});
@@ -1,36 +1,36 @@
'use client'
import { FunctionComponent } from 'react';
import { CircularProgress, CircularProgressProps, LinearProgress, Stack, StackProps } from '@mui/material';
import { APP_LOADING_COLOR, APP_LOADING_SIZE, APP_LOADING_TYPE } from '@/components/config';
import { keyframes } from '@emotion/react';
import Stack, { StackProps } from '@mui/material/Stack';
import AppIcon from '../AppIcon';
interface Props extends StackProps {
color?: CircularProgressProps['color'];
size?: number | string;
type?: 'circular' | 'linear';
value?: number;
}
export type AppLoadingProps = StackProps;
const breathe = keyframes`
0%, 100% { opacity: 0.55; transform: scale(0.92); }
50% { opacity: 1; transform: scale(1); }
`;
/**
* Renders MI circular progress centered inside Stack
* 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
* @prop {string} [size] - size of the progress component. Numbers means pixels, string can be '2.5rem'
*/
const AppLoading: FunctionComponent<Props> = ({
color = APP_LOADING_COLOR,
size = APP_LOADING_SIZE,
type = APP_LOADING_TYPE,
value,
...restOfProps
}) => {
const alignItems = type === 'linear' ? undefined : 'center';
return (
<Stack sx={{ my: 2, alignItems }} {...restOfProps}>
{type === 'linear' ? (
<LinearProgress color={color} value={value} />
) : (
<CircularProgress color={color} size={size} value={value} />
)}
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;