mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: implement PageLoader component and register maritime loader as the default theme loader
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState, type ReactNode } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { PageLoader } from '@ema-platform/ui';
|
||||
import { authStorage } from '../utils/auth-storage';
|
||||
import { hydrateAuth, logout, setUser } from '../store/auth.slice';
|
||||
import type { AuthUser } from '../types/auth.types';
|
||||
@@ -70,7 +71,7 @@ export function AuthBootstrap({ children }: { children: ReactNode }) {
|
||||
|
||||
// Rendering the router before the session resolves would let the guards
|
||||
// redirect based on a state that is about to change.
|
||||
if (!ready) return null;
|
||||
if (!ready) return <PageLoader />;
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ export * from "./lib/feedback/notify";
|
||||
export * from "./lib/feedback/FeatureUnavailable";
|
||||
export * from "./lib/feedback/EmptyState";
|
||||
export * from "./lib/feedback/ErrorState";
|
||||
export * from "./lib/feedback/PageLoader";
|
||||
export * from "./lib/components/MaritimeLoader";
|
||||
export * from "./lib/theme/maritime-loader-theme";
|
||||
export * from "./lib/layout/AppHeader";
|
||||
export * from "./lib/layout/AppSidebar";
|
||||
export * from "./lib/layout/AppTopNav";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CSSProperties, ComponentPropsWithoutRef } from "react";
|
||||
import type { CSSProperties, ComponentPropsWithoutRef, Ref } from "react";
|
||||
|
||||
export interface MaritimeLoaderProps extends Omit<
|
||||
ComponentPropsWithoutRef<"span">,
|
||||
@@ -8,8 +8,9 @@ export interface MaritimeLoaderProps extends Omit<
|
||||
size?: number | string;
|
||||
/** Standalone CSS color. Mantine's Loader color is used automatically when omitted. */
|
||||
color?: string;
|
||||
/** Accessible status text. */
|
||||
/** Accessible status text (aria-label only — renders no visible text). */
|
||||
label?: string;
|
||||
ref?: Ref<HTMLSpanElement>;
|
||||
}
|
||||
|
||||
const styles = `
|
||||
@@ -231,15 +232,6 @@ const styles = `
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.ema-loader__label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
margin-top: 4px;
|
||||
color: currentColor;
|
||||
}
|
||||
`;
|
||||
|
||||
function toCssSize(value: number | string | undefined) {
|
||||
@@ -252,6 +244,7 @@ export function MaritimeLoader({
|
||||
label = "Loading maritime services",
|
||||
className,
|
||||
style,
|
||||
ref,
|
||||
...props
|
||||
}: MaritimeLoaderProps) {
|
||||
const cssVariables = {
|
||||
@@ -263,12 +256,15 @@ export function MaritimeLoader({
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={["ema-loader", className].filter(Boolean).join(" ")}
|
||||
style={cssVariables}
|
||||
role="status"
|
||||
aria-label={label}
|
||||
>
|
||||
<style>{styles}</style>
|
||||
<style href="ema-maritime-loader" precedence="low">
|
||||
{styles}
|
||||
</style>
|
||||
|
||||
<svg
|
||||
className="ema-loader__svg"
|
||||
@@ -419,18 +415,8 @@ export function MaritimeLoader({
|
||||
<path d="M200 108c14 1 24-1 35-5" />
|
||||
</g>
|
||||
</svg>
|
||||
{label && <span className="ema-loader__label">{label}</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default MaritimeLoader;
|
||||
|
||||
// how to use this component.
|
||||
// <Center style={{ width: "90vw", height: "90vh" }}>
|
||||
// <MaritimeLoader
|
||||
// size={120}
|
||||
// color="#075985"
|
||||
// label="Loading Maritime Services..."
|
||||
// />
|
||||
// </Center>
|
||||
|
||||
26
libs/ui/src/lib/feedback/PageLoader.tsx
Normal file
26
libs/ui/src/lib/feedback/PageLoader.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Center, Loader, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface PageLoaderProps {
|
||||
/** Visible + accessible label. Defaults to the shared i18n "loading" string. */
|
||||
label?: string;
|
||||
/** Container height. Defaults to a full-page fill. */
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
/** The full-page/section loading state every screen should use instead of a bare `<Loader />`. */
|
||||
export function PageLoader({ label, height = '60vh' }: PageLoaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const resolvedLabel = label ?? t('loading', 'Loading…');
|
||||
|
||||
return (
|
||||
<Center h={height}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Loader size={64} aria-label={resolvedLabel} />
|
||||
<Text c="dimmed" size="sm">
|
||||
{resolvedLabel}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
31
libs/ui/src/lib/theme/maritime-loader-theme.ts
Normal file
31
libs/ui/src/lib/theme/maritime-loader-theme.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Button, createTheme, Loader, type MantineLoaderComponent } from '@mantine/core';
|
||||
import { MaritimeLoader } from '../components/MaritimeLoader';
|
||||
|
||||
/**
|
||||
* Registers the branded ship loader as the default Mantine `Loader` type, and
|
||||
* opts `Button`'s inline loading state back out to the stock oval — a 1.88:1
|
||||
* ship does not fit a button's loader slot.
|
||||
*
|
||||
* Merge into an app theme with `mergeThemeOverrides(appTheme, maritimeLoaderTheme)`
|
||||
* rather than importing this in `libs/shared`: `libs/ui` already depends on
|
||||
* `libs/shared`, so the reverse import would be circular.
|
||||
*/
|
||||
export const maritimeLoaderTheme = createTheme({
|
||||
components: {
|
||||
Loader: Loader.extend({
|
||||
defaultProps: {
|
||||
// Mantine types custom loaders as ForwardRefExoticComponent; a plain
|
||||
// function component satisfies the runtime contract (it forwards a
|
||||
// ref as a normal prop under React 19) but not the structural type.
|
||||
loaders: {
|
||||
...Loader.defaultLoaders,
|
||||
maritime: MaritimeLoader as unknown as MantineLoaderComponent,
|
||||
},
|
||||
type: 'maritime',
|
||||
},
|
||||
}),
|
||||
Button: Button.extend({
|
||||
defaultProps: { loaderProps: { type: 'oval' } },
|
||||
}),
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user