import * as React from 'react'
import { Conditional, cn, merge } from '@maker-ui/utils'
import {
Skiplinks,
Header,
Footer,
Topbar,
MobileMenu,
Main,
Sidebar,
Panel,
defaultSettings,
type MakerUIOptions,
type Options,
type MenuItemProps,
type LayoutButtonProps,
type PanelProps,
} from '@maker-ui/layout-server'
import { Menu, MenuButton, LayoutSettings } from '@maker-ui/layout-client'
interface LayoutProps {
/** You can use Layout dot children to build layouts with JSX */
children?: React.ReactNode
/** A valid Maker UI Options configuration object */
options?: MakerUIOptions
/** Allows you to nest multiple different layouts without re-rerendering container components */
fragment?: boolean
}
function isMenu(i: any): i is MenuItemProps[] {
return !!(i && typeof i[0] === 'object' && i[0].label)
}
function isIcon(i: any): i is LayoutButtonProps {
return !!(i && typeof i === 'object' && ('icon' in i || 'defaultIcon' in i))
}
const LeftPanel = (props: PanelProps) =>
LeftPanel.defaultProps = { _type: 'leftPanel' }
const RightPanel = (props: PanelProps) =>
RightPanel.defaultProps = { _type: 'rightPanel' }
/**
* This function sorts all Layout dot children into an object with corresponding keys.
* We use this to merge JSX with the slots prop and MakerUIOptions.
*/
function assign(children: React.ReactNode) {
let c: { [k: string]: any } = {}
let layout = 'content'
React.Children.toArray(children).forEach((child: any) => {
const type = child.props._type
if (type) {
c[type] = child
} else {
c['children'] = child
}
})
if (c.leftPanel && !c?.rightPanel) layout = 'left-content'
if (c.rightPanel && !c?.leftPanel) layout = 'content-right'
if (c.leftPanel && c.rightPanel) layout = 'left-content-right'
return { slots: c, layoutType: layout }
}
/**
* Wrap your application in the `Layout` component to use Maker UI.
*
* @link https://maker-ui.com/docs/layout/layout
*/
export const Layout = ({
options = {},
fragment = false,
children,
}: LayoutProps) => {
const opts = merge(defaultSettings, options) as Options
const { slots, layoutType } = assign(children)
// Helpers
const isPanel = !!(slots.leftPanel || slots.rightPanel)
const isSidebar = !!slots.sidebar
const dir = opts?.content?.sidebar || 'left'
/**
* Merged and formatted props for the Header component.
*/
const headerProps = merge(opts.header, {
...slots?.header?.props,
menu: isMenu(slots?.header?.props?.menu) ? (
) : (
slots?.header?.props?.menu
),
menuSplit: isMenu(slots?.header?.props?.menuSplit) ? (
) : (
slots?.header?.props?.menuSplit
),
menuButton: isIcon(slots?.header?.props?.menuButton) ? (
) : (
slots?.header?.props?.menuButton
),
})
/**
* Merged and formatted props for the MobileMenu component.
*/
const mobileMenuProps = merge(opts.mobileMenu, {
...slots?.mobileMenu?.props,
closeButton: isIcon(slots?.mobileMenu?.props?.closeButton) ? (
) : (
slots?.mobileMenu?.props?.closeButton
),
children: isMenu(slots?.mobileMenu?.props?.menu) ? (
) : (
slots?.mobileMenu?.props?.children
),
})
/**
* Merged and formatted props for the Left Panel.
*/
const leftProps = merge(opts.leftPanel || {}, {
...slots?.leftPanel?.props,
menuButton: isIcon(slots?.leftPanel?.props?.menuButton) ? (
) : (
slots?.leftPanel?.menuButton
),
})
/**
* Merged and formatted props for the Right Panel.
*/
const rightProps = merge(opts.rightPanel || {}, {
...slots?.rightPanel?.props,
menuButton: isIcon(slots?.rightPanel?.props?.menuButton) ? (
) : (
slots?.rightPanel?.menuButton
),
})
return (
<>
{(fragment && options) || slots?.settings ? (
) : null}
{!fragment && }
{slots?.topbar && }
{slots?.header ? (
: null
}
/>
) : null}
{slots?.children || (
{isPanel &&
}
{slots?.leftPanel &&
}
(
{c}
)}>
{slots?.rightPanel &&
}
)}
{slots?.footer && }
>
)
}
Layout.Header = Header
Layout.MobileMenu = MobileMenu
Layout.Topbar = Topbar
Layout.Main = Main
Layout.Footer = Footer
Layout.Sidebar = Sidebar
Layout.LeftPanel = LeftPanel
Layout.RightPanel = RightPanel
Layout.Settings = LayoutSettings
Layout.displayName = 'Layout'