feat(mui): PageEnter kit primitive (consistent staggered page entrance) + adopt in WarehouseSuppliers

Each top-level child rises+fades in, staggered (0.4s, ease-out), so every page enters the same way and nothing appears instantly; honors prefers-reduced-motion. Replaces ad-hoc per-page motion.div entrance wrappers. One component → the look is tunable in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-07 09:53:41 +02:00
parent 1a0f57a247
commit b273614128
3 changed files with 64 additions and 18 deletions

View File

@@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import { motion } from "framer-motion";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
@@ -24,6 +23,7 @@ import {
TextField, TextField,
StatusChip, StatusChip,
PageHeader, PageHeader,
PageEnter,
FilterBar, FilterBar,
EmptyState, EmptyState,
LoadingState, LoadingState,
@@ -344,22 +344,16 @@ export default function WarehouseSuppliers() {
]; ];
return ( return (
<Box> <PageEnter>
<motion.div <PageHeader
initial={{ opacity: 0, y: 12 }} title="Dodavatelé"
animate={{ opacity: 1, y: 0 }} subtitle={subtitle}
transition={{ duration: 0.25 }} actions={
> <Button startIcon={PlusIcon} onClick={openCreateModal}>
<PageHeader Přidat dodavatele
title="Dodavatelé" </Button>
subtitle={subtitle} }
actions={ />
<Button startIcon={PlusIcon} onClick={openCreateModal}>
Přidat dodavatele
</Button>
}
/>
</motion.div>
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 320px" }}> <Box sx={{ flex: "1 1 320px" }}>
@@ -525,6 +519,6 @@ export default function WarehouseSuppliers() {
confirmVariant="danger" confirmVariant="danger"
loading={deleteMutation.isPending} loading={deleteMutation.isPending}
/> />
</Box> </PageEnter>
); );
} }

View File

@@ -0,0 +1,51 @@
import { Children, isValidElement, type ReactNode } from "react";
import { motion, useReducedMotion, type Variants } from "framer-motion";
import Box from "@mui/material/Box";
import type { SxProps, Theme } from "@mui/material/styles";
const MotionBox = motion(Box);
const container: Variants = {
hidden: {},
show: { transition: { staggerChildren: 0.05, delayChildren: 0.03 } },
};
const itemVariant: Variants = {
hidden: { opacity: 0, y: 12 },
show: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
},
};
/**
* Consistent page entrance. Use as a page's OUTERMOST wrapper (in place of the
* page's root `<Box>`): each top-level child rises + fades in, staggered, so the
* whole page comes in the same way everywhere — no element appears instantly.
* Wrapping each child in a block `motion.div` is layout-safe for the vertical
* section stacks our pages use (header → filters → card/table); nested grids/
* flex inside a section are untouched. Honors prefers-reduced-motion.
*/
export default function PageEnter({
children,
sx,
}: {
children: ReactNode;
sx?: SxProps<Theme>;
}) {
const reduce = useReducedMotion();
if (reduce) return <Box sx={sx}>{children}</Box>;
return (
<MotionBox variants={container} initial="hidden" animate="show" sx={sx}>
{Children.map(children, (child) =>
isValidElement(child) ? (
<motion.div variants={itemVariant}>{child}</motion.div>
) : (
child
),
)}
</MotionBox>
);
}

View File

@@ -28,3 +28,4 @@ export { default as FileUpload } from "./FileUpload";
export { default as ProgressBar } from "./ProgressBar"; export { default as ProgressBar } from "./ProgressBar";
export type { ProgressColor } from "./ProgressBar"; export type { ProgressColor } from "./ProgressBar";
export { default as ThemeToggle } from "./ThemeToggle"; export { default as ThemeToggle } from "./ThemeToggle";
export { default as PageEnter } from "./PageEnter";