Layouts
Every pages may have a two levels of layouts:
- Root Layout (global)
- Nested Layout (page level)
Root Layout
This layout is global for every application page. By default, only current page component will be rendered, but you can easily add Header, Footer or any global components.
Use Root Layout features, when you need to add some HTML tags globally to all application Pages.
Structure
Layout has the following structure:
<LayoutWrapper>
  {globalComponents}
  <ContentWrapper>
    <HeaderWrapper>
      <Header />
    </HeaderWrapper>
    <PageWrapper>{page}</PageWrapper>
    <FooterWrapper>
      <Footer />
    </FooterWrapper>
  </ContentWrapper>
</LayoutWrapper>
Any of the wrappers could be customized. By default, every wrapper just renders passed children prop, but HeaderWrapper and FooterWrapper render only if components Header and Footer were passed as props to result layout.
Components
- header,- footer,- layout,- content,- pageare base components for wrappers. They should render passed prop- children. By default,- layout,- content,- pageare "render children" while- headerand- footerare "render prop"
- any of the other components are, so called, globalComponents. They are just rendered as components insideLayoutWrapper
Wrappers
HOC for components
- header,- footer,- layout,- content,- page- HOC for the base components
- all of the other components are HOCs for все остальные wrappers - HOC for corresponding globalComponents
It is possible to pass a list of HOCs. This way order of render wrapping for passed component will be from end to start of the list.
Such wrappers and used for:
- hide/show elements by condition
- set additional css style for components
- inject additional code/handler
- pass additional props
Example of such wrapper:
function layoutWrapper(WrappedComponent) {
  return (props) => (
    <div className="ui-layout">
      <WrappedComponent {...props} />
    </div>
  );
}
Header and Footer
You can register header and footer components through providers:
import { DEFAULT_HEADER_COMPONENT, DEFAULT_FOOTER_COMPONENT } from '@tramvai/tokens-render';
import { provide, Scope } from '@tramvai/core';
createApp({
  providers: [
    provide({
      provide: DEFAULT_HEADER_COMPONENT,
      scope: Scope.SINGLETON,
      useValue: DefaultHeader,
    }),
    provide({
      provide: DEFAULT_FOOTER_COMPONENT,
      scope: Scope.SINGLETON,
      useValue: DefaultFooter,
    }),
  ],
});
Components and Wrappers
You can add custom components and wrappers for layout via the token LAYOUT_OPTIONS, this wrappers also will be applied on every application page:
import { LAYOUT_OPTIONS } from '@tramvai/tokens-render';
import { provide } from '@tramvai/core';
createApp({
  providers: [
    provide({
      provide: LAYOUT_OPTIONS,
      useValue: {
        // React components
        components: {
          // global layout component
          layout: Layout,
          // custom global component
          feedback: Feedback,
        },
        // HOC's for components
        wrappers: {
          // wrapper for global layout component
          layout: layoutWrapper,
          // wrappers for global Feedback component
          feedback: [feedbackWrapper1, feedbackWrapper2],
        },
      },
    }),
  ],
});
Replace Root Layout
We are strongly recommend to always use a default Root Layout, because some of tramvai core functionality depends on it.
If the basic layout doesn't work for you, you can replace it with any other React component. In doing so, you need to implement all the wrappers yourself and plug in global components if you need them.
You can add a layoutComponent property to route config property when adding routes manually (this feature is not supported with File-System Routing). This layout will be rendered when you go to the corresponding route.
First, create a new component:
import type { LayoutComponent } from '@tramvai/react';
const CustomLayout: LayoutComponent = ({ children }) => {
  return (
    <>
      <Header />
      {children}
    </>
  );
};
export default CustomLayout;
LayoutComponent interface is used here for better typings.
And register this component as layoutComponent property for some application route, e.g.:
import { SpaRouterModule } from '@tramvai/modules-router';
createApp({
  modules: [
    SpaRouterModule.forRoot([{
      name: 'main',
      path: '/',
      config: {
        pageComponent: '@/pages/index',
        layoutComponent: '@/pages/custom-layout',
      },
    }]),
  ],
});
Nested Layout
For every page, unique nested layout can be applied. It is useful when you need to wrap group of pages in the same block, or add the same actions.
Use Nested Layout, when you need to add the same HTML tags to a few or more Pages.
For now, only one level of layout nesting supported, and simplified component structure will look like this:
<RootLayout>
  <NestedLayout>
    <Page />
  </NestedLayout>
</RootLayout>
Nested layout it is a simple React component with children property:
import type { NestedLayoutComponent } from '@tramvai/react';
const Layout: NestedLayoutComponent = ({ children }) => {
  return <div>{children}</div>;
};
NestedLayoutComponent interface is used here for better typings - Nested Layout components may have additional static properties:
Actions
Nested Layout components support global actions in actions static property, these actions will executed only for pages with this layout:
Layout.actions = [fetchSomeDataAction];
This actions will be code-splitted with layout component code.
Reducers
Nested Layout components support reducers in reducers static property, these reducers will be registered in application store:
Layout.reducers = [SomeDataStore];
This reducers will be code-splitted with layout component code.
Adding a nested layout
_layout.tsx
With File-System Routing, adding a nested layout is trivial:
⌛ Create file _layout.tsx with layout component near page component:
import type { NestedLayoutComponent } from '@tramvai/react';
export const Layout: NestedLayoutComponent = ({ children }) => {
  return (
    <>
      <h3>Nested Layout</h3>
      <div>{children}</div>
    </>
  );
};
export default Layout;
And we will get this file structure:
src
└── routes
    ├── index.tsx
    └── _layout.tsx
After this, layout will be rendered at application main page.
For manually created routes
With File-System Components, you can add a nested layout in two steps:
⌛ Create file with layout component in pages directory:
import type { NestedLayoutComponent } from '@tramvai/react';
export const Layout: NestedLayoutComponent = ({ children }) => {
  return (
    <>
      <h3>Nested Layout</h3>
      <div>{children}</div>
    </>
  );
};
export default Layout;
And we will get this file structure:
src
└── pages
    ├── index.tsx
    └── custom-nested-layout.tsx
This component will be available with key @/pages/custom-nested-layout.
⌛ Add nestedLayoutComponent to route configuration:
import { SpaRouterModule } from '@tramvai/modules-router';
createApp({
  modules: [
    SpaRouterModule.forRoot([{
      name: 'main',
      path: '/',
      config: {
        pageComponent: '@/pages/index',
        nestedLayoutComponent: '@/pages/custom-nested-layout',
      },
    }]),
  ],
});
After this, layout will be rendered at application main page.