Files
myeasycms-v2/packages/ui/src/makerkit/page.tsx
2024-11-26 15:21:07 +08:00

214 lines
4.7 KiB
TypeScript

import * as React from 'react';
import { cn } from '../lib/utils';
import { If } from './if';
export type PageLayoutStyle = 'sidebar' | 'header' | 'custom';
type PageProps = React.PropsWithChildren<{
style?: PageLayoutStyle;
contentContainerClassName?: string;
className?: string;
sticky?: boolean;
}>;
export function Page(props: PageProps) {
switch (props.style) {
case 'header':
return <PageWithHeader {...props} />;
case 'custom':
return props.children;
default:
return <PageWithSidebar {...props} />;
}
}
function PageWithSidebar(props: PageProps) {
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
return (
<div className={cn('flex', props.className)}>
{Navigation}
<div
className={
props.contentContainerClassName ??
'mx-auto flex h-screen w-full flex-col overflow-y-auto bg-inherit'
}
>
{MobileNavigation}
<div
className={
'flex flex-1 flex-col overflow-y-auto bg-background px-4 lg:px-0'
}
>
{Children}
</div>
</div>
</div>
);
}
export function PageMobileNavigation(
props: React.PropsWithChildren<{
className?: string;
}>,
) {
return (
<div
className={cn(
'flex w-full items-center border-b px-4 py-2 lg:hidden lg:px-0',
props.className,
)}
>
{props.children}
</div>
);
}
function PageWithHeader(props: PageProps) {
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
return (
<div className={cn('flex h-screen flex-1 flex-col', props.className)}>
<div
className={
props.contentContainerClassName ?? 'flex flex-1 flex-col space-y-4'
}
>
<div
className={cn(
'flex h-14 items-center justify-between bg-muted/40 px-4 dark:border-border dark:shadow-primary/10 lg:justify-start lg:shadow-sm',
{
'sticky top-0 z-10 backdrop-blur-md': props.sticky ?? true,
},
)}
>
<div
className={'hidden w-full flex-1 items-center space-x-8 lg:flex'}
>
{Navigation}
</div>
{MobileNavigation}
</div>
<div className={'container flex flex-1 flex-col'}>{Children}</div>
</div>
</div>
);
}
export function PageBody(
props: React.PropsWithChildren<{
className?: string;
}>,
) {
const className = cn('flex w-full flex-1 flex-col lg:px-4', props.className);
return <div className={className}>{props.children}</div>;
}
export function PageNavigation(props: React.PropsWithChildren) {
return <div className={'flex-1 bg-inherit'}>{props.children}</div>;
}
export function PageDescription(props: React.PropsWithChildren) {
return (
<div className={'h-6'}>
<div className={'text-xs font-normal leading-none text-muted-foreground'}>
{props.children}
</div>
</div>
);
}
export function PageTitle(props: React.PropsWithChildren) {
return (
<h1
className={
'h-6 font-heading font-bold leading-none tracking-tight dark:text-white'
}
>
{props.children}
</h1>
);
}
export function PageHeaderActions(props: React.PropsWithChildren) {
return <div className={'flex items-center space-x-2'}>{props.children}</div>;
}
export function PageHeader({
children,
title,
description,
className,
}: React.PropsWithChildren<{
className?: string;
title?: string | React.ReactNode;
description?: string | React.ReactNode;
}>) {
return (
<div
className={cn(
'flex items-center justify-between py-4 lg:px-4',
className,
)}
>
<div className={'flex flex-col'}>
<If condition={description}>
<PageDescription>{description}</PageDescription>
</If>
<If condition={title}>
<PageTitle>{title}</PageTitle>
</If>
</div>
{children}
</div>
);
}
function getSlotsFromPage(props: React.PropsWithChildren) {
return React.Children.toArray(props.children).reduce<{
Children: React.ReactElement | null;
Navigation: React.ReactElement | null;
MobileNavigation: React.ReactElement | null;
}>(
(acc, child) => {
if (!React.isValidElement(child)) {
return acc;
}
if (child.type === PageNavigation) {
return {
...acc,
Navigation: child,
};
}
if (child.type === PageMobileNavigation) {
return {
...acc,
MobileNavigation: child,
};
}
return {
...acc,
Children: child,
};
},
{
Children: null,
Navigation: null,
MobileNavigation: null,
},
);
}