TryGhost/Ghost

View on GitHub
apps/admin-x-design-system/src/global/layout/Page.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from 'react';
import {TabList} from '../TabView';
import clsx from 'clsx';
import AppMenu from './AppMenu';
import GlobalActions from './GlobalActions';
import Button from '../Button';
import {BreadcrumbsProps} from '../Breadcrumbs';
import PageHeader from './PageHeader';

export interface PageTab {
    id: string;
    title: string;
}

export interface CustomGlobalAction {
    iconName: string;
    onClick?: () => void;
}

interface PageProps {
    mainContainerClassName?: string;
    mainClassName?: string;
    fullBleedPage?: boolean;

    /**
     * The pageToolbar is a WIP part of this component, it's unused ATM in Ghost Admin.
     */
    pageToolbarClassName?: string;
    fullBleedToolbar?: boolean;

    /**
     * TK. Part of the Page Toolbar
     */
    showAppMenu?: boolean;

    /**
     * Show
     */
    showGlobalActions?: boolean;

    /**
     * TK. Part of the Page Toolbar
     */
    customGlobalActions?: CustomGlobalAction[];
    breadCrumbs?: React.ReactElement<BreadcrumbsProps>;

    /**
     * TK. Part of the Page Toolbar
     */
    pageTabs?: PageTab[],

    /**
     * TK. Part of the Page Toolbar
     */
    selectedTab?: string;

    /**
     * TK. Part of the Page Toolbar
     */
    onTabChange?: (id: string) => void;

    children?: React.ReactNode;
}

/**
 * The page component is the main container in Ghost Admin. It consists of a
 * page level toolbar (`pageToolbar` — unused ATM, it's for page level views and
 * navigation in the future), and the main content area.
 *
 * ### Examples
 * You can find several examples in the sidebar. If you're building a page for the
 * current Admin you can use the ["List in Current Admin"](/story/global-layout-page--example-current-admin-list)
 * example as a starting point. The rest of the examples are showing a potential direction for a
 * future structure.
 */
const Page: React.FC<PageProps> = ({
    fullBleedPage = true,
    mainContainerClassName,
    mainClassName,
    pageToolbarClassName,
    fullBleedToolbar = true,
    showAppMenu = false,
    showGlobalActions = false,
    customGlobalActions,
    breadCrumbs,
    pageTabs,
    selectedTab,
    onTabChange,
    children
}) => {
    const handleTabChange = (e: React.MouseEvent<HTMLButtonElement>) => {
        const newTab = e.currentTarget.id as string;
        onTabChange!(newTab);
    };

    if (pageTabs?.length && !selectedTab) {
        selectedTab = pageTabs[0].id;
    }

    const left: React.ReactNode = (
        (showAppMenu || breadCrumbs || pageTabs?.length) && <div className='flex items-center gap-10'>
            {showAppMenu && (
                <AppMenu />
            )}
            {breadCrumbs}
            {pageTabs?.length && (
                <TabList
                    border={false}
                    buttonBorder={false}
                    handleTabChange={handleTabChange}
                    selectedTab={selectedTab}
                    tabs={pageTabs!}
                    width='normal'
                />
            )}
        </div>);

    mainClassName = clsx(
        'flex w-full flex-auto flex-col',
        mainClassName
    );

    const globalActions = (
        (customGlobalActions?.length || showGlobalActions) &&
        <div className='sticky flex items-center gap-7'>
            {(customGlobalActions?.map((action) => {
                return (
                    <Button icon={action.iconName} iconColorClass='text-black dark:text-white' size='sm' link onClick={action.onClick} />
                );
            }))}
            {showGlobalActions && <GlobalActions />}
        </div>);

    mainContainerClassName = clsx(
        'flex h-[100vh] w-full flex-col overflow-y-auto overflow-x-hidden',
        !fullBleedPage && 'mx-auto max-w-7xl',
        mainContainerClassName
    );

    pageToolbarClassName = clsx(
        'sticky top-0 z-50 flex h-22 min-h-[92px] w-full items-center justify-between gap-5 bg-white p-8 dark:bg-black',
        !fullBleedToolbar && 'mx-auto max-w-7xl',
        pageToolbarClassName
    );

    return (
        <div className={mainContainerClassName}>
            {(left || globalActions) &&
                <PageHeader
                    containerClassName={pageToolbarClassName}
                    left={left}
                    right={globalActions}
                />
            }
            <main className={mainClassName}>
                {children}
            </main>
        </div>
    );
};

export default Page;