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