import PropTypes from 'prop-types';
import { Grid } from '@astryxdesign/core/Grid';
import { VStack } from '@astryxdesign/core/VStack';
import { Card } from '@astryxdesign/core/Card';
import { Divider } from '@astryxdesign/core/Divider';
// ==============================|| PAGE LAYOUT PRIMITIVES ||============================== //
//
// The operator-page design language, in one place.
//
// Every page had been composing its own rhythm out of ad-hoc `marginTop: 16`,
// `gap={2}` and `gap={3}` values, so no two pages agreed on spacing and the
// result read as cramped. These four primitives fix the vertical rhythm, the
// grid behaviour and the card chrome so a page only decides WHAT goes in each
// slot, never how far apart things sit.
//
// The values come from the Astryx `dashboard` page template (which uses
// LayoutContent padding={6} + VStack gap={6} + Grid gap={4}) — i.e. they are the
// design system's own numbers, not invented here.
//
//
//
// …StatCard…
// …} tabs={}>
// …table…
//
//
// ---------------------------------------------------------------------------
// PageShell — owns the vertical rhythm between a page's major regions.
// gap={6} is the template's section spacing. Use this INSTEAD of putting
// `style={{ marginTop: N }}` on individual sections.
//
// `fill` opts a page into FULL-HEIGHT mode: the header (and any KPI row) take
// only the height they need and the last region — a `` — grows
// to the bottom of the viewport, with its table scrolling inside itself.
//
// Why the `:has()` rule rather than a `height: 100%` chain or a `100vh` calc:
//
// AppShell (height="fill") gives its scroll container `.astryx-layout-content`
// a definite `height: 100%` — so a real flex chain IS available. The chain
// breaks at exactly one link: MainLayout's `.main-content-area`, the div that
// holds the page padding, sets `min-height: 100%` and NOT `height`, so its own
// height is auto. A percentage height on any descendant then resolves against
// `auto` and is dropped, which is why full-height pages have had to guess with
// `calc(100vh - 260px)` — a number that is wrong at every viewport the guess
// wasn't written for, and that leaves dead space under the table.
//
// `:has(> .dt-page-fill)` re-definites that one link, and ONLY while a page
// that asked for fill mode is mounted — every other page keeps the auto-height,
// grow-with-content behaviour it has today. Scoping it this way is what makes
// this safe to add to a shared primitive. Astryx's own stylesheet already
// relies on `:has()`, so it is not a new browser requirement; where it is
// unsupported the page simply falls back to today's auto-height layout.
// ---------------------------------------------------------------------------
const FILL_CLASS = 'dt-page-fill';
export function PageShell({ children, gap = 6, fill = false }) {
return (
<>
{fill && (
)}
{children}
>
);
}
PageShell.propTypes = {
children: PropTypes.node,
gap: PropTypes.number,
fill: PropTypes.bool
};
// ---------------------------------------------------------------------------
// KpiGrid — the row of StatCards under the page header.
//
// `repeat: 'fit'` is load-bearing and the reason this exists as a shared
// primitive: Grid's default is `'fill'`, which PRESERVES empty tracks. With a
// small minWidth in a wide container that lays out more tracks than there are
// cards, so the cards bunch to the left and leave a dead gap on the right —
// the single most visible layout bug on these pages. `'fit'` collapses the
// empty tracks so the cards stretch to fill the row.
//
// `max` caps the column count so 3 cards don't each become 400px wide on an
// ultrawide monitor; it defaults to the number of children.
// ---------------------------------------------------------------------------
export function KpiGrid({ children, minWidth = 240, max }) {
const count = Array.isArray(children) ? children.filter(Boolean).length : 1;
return (
{children}
);
}
KpiGrid.propTypes = {
children: PropTypes.node,
minWidth: PropTypes.number,
max: PropTypes.number
};
// ---------------------------------------------------------------------------
// FilterToolbar — the filter/search controls above a table.
//
// NOTE the padding lives on the VStack, not the Grid. `Grid` has no `padding`
// prop (it declares only columns/gap/rowGap/columnGap/align/justify/width/
// height), so passing one spreads it onto the DOM where it silently does
// nothing — controls then sit flush against the card edge with no breathing
// room. That bug is the reason this wrapper exists rather than each page
// hand-rolling the pair.
//
// max 3 keeps a 6-control toolbar as two even rows of three instead of leaving
// an orphan on a second row.
// ---------------------------------------------------------------------------
export function FilterToolbar({ children, minWidth = 220, max = 3, padding = 4 }) {
return (
{children}
);
}
FilterToolbar.propTypes = {
children: PropTypes.node,
minWidth: PropTypes.number,
max: PropTypes.number,
padding: PropTypes.number
};
// ---------------------------------------------------------------------------
// DataCard — one card holding a table and everything that acts on it.
//
// Pages used to stack three separate Cards (filters, then tabs, then table),
// which read as loose boxes with no relationship to each other. Grouping them
// into a single card with dividers makes it obvious that the filters and tabs
// act on the table below them.
//
// `tabs` gets its own padded row so the tab strip never sits flush against the
// card edge or against the toolbar above it.
//
// `fill` (pairs with ``) makes the card a flex column that
// claims the leftover viewport height: the toolbar and tab rows keep their
// intrinsic height, and the content slot takes the rest with `min-height: 0`
// so its scroll area can actually shrink rather than pushing the card past the
// bottom of the screen. A table inside then only needs `maxHeight="100%"` —
// no viewport arithmetic, and it re-measures on every resize for free.
// ---------------------------------------------------------------------------
export function DataCard({ toolbar, tabs, children, fill = false }) {
return (
{toolbar}
{toolbar && }
{tabs && (
<>
{tabs}
>
)}
{children}
);
}
DataCard.propTypes = {
toolbar: PropTypes.node,
tabs: PropTypes.node,
children: PropTypes.node,
fill: PropTypes.bool
};