Skip to content

Commit

Permalink
Update docs for Slots & Layout Kit
Browse files Browse the repository at this point in the history
  • Loading branch information
attitude committed Jul 18, 2023
1 parent 0469fc1 commit e219a71
Show file tree
Hide file tree
Showing 19 changed files with 1,571 additions and 1,064 deletions.
25 changes: 13 additions & 12 deletions docs/reference/admin/layouts/_Example/components/Directives.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { createDirectiveContext } from '@contember/layout';
import * as React from 'react';
import { Directives } from '@contember/layout';

type DirectivesType = {
'title': string | null | undefined;
export type DirectivesType = {
'full-width': boolean
}

const directivesDefaultValues: DirectivesType = Object.freeze({
'title': undefined,
export const initialDirectives: DirectivesType = Object.freeze({
'full-width': false,
})

export const directivesList = Object.keys(directivesDefaultValues) as (keyof DirectivesType)[]
export const [DirectivesProvider, Directive, DirectivesConsumer, useDirectives] = createDirectiveContext<DirectivesType>('Directives', directivesDefaultValues)
// Local export with current DirectivesType specific to the project
export const Directive = Directives.Directive as unknown as Directives.DirectiveComponentType<DirectivesType>

// Short-hand for <Directive name="title" content={children} />
export const Title = React.memo<{ children: string | null | undefined }>(({ children }) => (
<Directive name="title" content={children} />
))
// Local export with current DirectivesType specific to the project
export const useDirectives = Directives.useDirectives<DirectivesType>

export function FullWidth() {
return <Directive name={'full-width'} content={true} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Dummy component to make the example work
export function EditOrCreateForm() {
return null
}
5 changes: 3 additions & 2 deletions docs/reference/admin/layouts/_Example/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export const Layout = ({
children: pages,
}: React.PropsWithChildren) => (
<>
<MyCustomLayout />
{pages}
<MyCustomLayout>
{pages}
</MyCustomLayout>
</>
)
23 changes: 0 additions & 23 deletions docs/reference/admin/layouts/_Example/components/Slots.ts

This file was deleted.

43 changes: 43 additions & 0 deletions docs/reference/admin/layouts/_Example/components/Slots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CommonSlotSources, CommonSlotTargets, Slots, commonSlots } from '@contember/layout'
import { useDocumentTitle } from '@contember/react-utils'
import { memo } from 'react'

// 1. Defining your own slots:
// highlight-start
const [
mySlots, // 👈 same array as the argument (for convenience)
MySlotSources, // 👈 components that "portal" their children to other DOM nodes
MySlotTargets, // 👈 components that "receive" children from slot sources
] = Slots.createSlotComponents([
'Subtitle',
])
// highlight-end

// 2. Exporting your slots together with the common ones:
export const slots = [
...commonSlots,
// highlight-next-line
...mySlots,
]

export const SlotSources = {
...CommonSlotSources,
// highlight-next-line
...MySlotSources,
}

export const SlotTargets = {
...CommonSlotTargets,
// highlight-next-line
...MySlotTargets,
}

export const Title = memo<{ children: string | null | undefined }>(({ children }) => {
// Hook that will update the document title in browser tab:
useDocumentTitle(children)

return (
// Title is already defined in CommonSlotSources, so we can use it:
<SlotSources.Title>{children}</SlotSources.Title>
)
})
29 changes: 12 additions & 17 deletions docs/reference/admin/layouts/_Example/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { ApplicationEntrypoint, DataBindingProvider, FeedbackRenderer, PageModule, Pages, runReactApp } from '@contember/admin'
import { LayoutSlotsProvider } from '@contember/layout'
import { ApplicationEntrypoint, PageModule, Pages, runReactApp } from '@contember/admin'
import { Directives, Slots } from '@contember/layout'
import { createRoot } from 'react-dom/client'
import { DirectivesProvider } from './components/Directives'
import { initialDirectives } from './components/Directives'
import { Layout } from './components/Layout'
import './index.css'

runReactApp(
<DirectivesProvider>
<LayoutSlotsProvider>
<Directives.Provider value={initialDirectives}>
<Slots.Provider>
<ApplicationEntrypoint
apiBaseUrl={import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL as string}
sessionToken={import.meta.env.VITE_CONTEMBER_ADMIN_SESSION_TOKEN as string}
project={'admin-sandbox'}
stage={'live'}
basePath={import.meta.env.BASE_URL}
apiBaseUrl={import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL}
sessionToken={import.meta.env.VITE_CONTEMBER_ADMIN_SESSION_TOKEN}
project={import.meta.env.VITE_CONTEMBER_ADMIN_PROJECT_NAME}
stage="live"
envVariables={{ WEB_URL: import.meta.env.VITE_CONTEMBER_ADMIN_WEB_URL }}
children={
<DataBindingProvider stateComponent={FeedbackRenderer}>
<Pages children={import.meta.glob<PageModule>('./pages/**/*.tsx')} layout={Layout} />
</DataBindingProvider>
}
children={<Pages layout={Layout} children={import.meta.glob<PageModule>('./pages/**/*.tsx', { eager: true })} />}
/>
</LayoutSlotsProvider>
</DirectivesProvider>,
</Slots.Provider>
</Directives.Provider>,
null,
(dom, react, onRecoverableError) => createRoot(dom, { onRecoverableError }).render(react),
)
79 changes: 50 additions & 29 deletions docs/reference/admin/layouts/_Example/my-custom-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,60 @@
import { DropdownContentContainerProvider } from "@contember/admin"
import {
commonSlotTargets,
CommonSlotTargets,
useDocumentTitle,
useLayoutSlotRegistryContext,
} from "@contember/layout"
import { Slots } from "@contember/layout"
import { PropsWithChildren } from "react"
import { useDirectives } from "./components/Directives"
import { SlotTargets, slotTargets } from "./components/Slots"
import { SlotTargets } from "./components/Slots"

export function MyCustomLayout() {
const { activeSlots } = useLayoutSlotRegistryContext()
const directives = useDirectives()
const { Actions, Back, Logo, Navigation, Sidebar, Title, Profile, Subtitle, Switchers, ...rest } = SlotTargets

// Make sure we implement all the slots:
if (import.meta.env.DEV) {
const __EMPTY_REST_GUARD: { [key: string]: never } = rest
}

export function MyCustomLayout({ children }: PropsWithChildren) {
const slotTargetsIfActive = Slots.useTargetsIfActiveFactory(SlotTargets)

useDocumentTitle(directives.title)
const directives = useDirectives()

return (
<div className="layout">
<header className="layout__header">
{activeSlots.has(commonSlotTargets.Back) ? <CommonSlotTargets.Back /> : null}
{activeSlots.has(commonSlotTargets.Logo) ? <CommonSlotTargets.Logo /> : null}
{activeSlots.has(commonSlotTargets.Actions) ? <CommonSlotTargets.Actions /> : null}
</header>
<div className="layout" data-full-width={directives ? true : undefined}>
{slotTargetsIfActive(['Logo', 'Actions'], (
<header className="layout__header">
<Logo />
<Actions />
<Profile />
</header>
))}
{slotTargetsIfActive(['Navigation'], (
<aside className="layout__aside">
<Navigation />
</aside>
))}
<main className="layout__main">
<h1>{directives.title}</h1>
{activeSlots.has(slotTargets.Subtitle) ? <SlotTargets.Subtitle /> : null}
{activeSlots.has(commonSlotTargets.Content) ? <CommonSlotTargets.Content /> : null}
<section className="layout__main__content">
{slotTargetsIfActive(['Back', 'Title'], (
<>
<Back />
<Title as="h1" />
</>
))}
{slotTargetsIfActive(['Subtitle'])}
{children}
</section>

{slotTargetsIfActive(['Sidebar'], (
<section className="layout__main__sidebar">
<Sidebar />
</section>
))}
</main>
<aside className="layout__aside">
{activeSlots.has(commonSlotTargets.Navigation) ? <CommonSlotTargets.Navigation /> : null}
{activeSlots.has(commonSlotTargets.Sidebar) ? <CommonSlotTargets.Sidebar /> : null}
</aside>
<footer className="layout__footer">
</footer>
<DropdownContentContainerProvider />
<div id="portal-root" />


{slotTargetsIfActive(['Profile', 'Switchers'], (
<footer className="layout__footer">
<Switchers />
<Profile />
</footer>
))}
</div>
)
}
23 changes: 15 additions & 8 deletions docs/reference/admin/layouts/_Example/pages/example.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { CommonSlots } from '@contember/layout'
import { Title } from '../components/Directives'
import { Slots } from '../components/Slots'
import { EditScope, FieldView, PersistButton } from '@contember/admin'
import { Directive } from '../components/Directives'
import { EditOrCreateForm } from '../components/EditOrCreateForm'
import { SlotSources, Title } from '../components/Slots'

export default () => (
<>
<Title>My page title</Title>
<Directive name="full-width" content={true} />

<Slots.Subtitle>This is a subtitle</Slots.Subtitle>
<EditScope entity="Article(id = $id)">
<FieldView field="title" render={title => (
<Title>{`Edit ${title.getAccessor().value ? title.getAccessor().value : 'Article'}`}</Title>
)} />

<CommonSlots.Content>
<p>My page content</p>
</CommonSlots.Content>
<EditOrCreateForm />

<SlotSources.Actions>
<PersistButton />
</SlotSources.Actions>
</EditScope>
</>
)
6 changes: 2 additions & 4 deletions docs/reference/admin/layouts/custom-layouts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import _LayoutUISource from '!!raw-loader!./_Example/my-custom-layout.tsx'
import _components_LayoutSource from '!!raw-loader!./_Example/components/Layout.tsx'
import _pages_example_Source from '!!raw-loader!./_Example/pages/example.tsx'

# <span className="version">Interface 1.2+</span> Custom layouts
# <span className="version">Interface 1.2+</span> Any HTML can be your layout

With slots and directives you have all the tools you need to:

Expand All @@ -18,8 +18,6 @@ Slots and directives lets you decouple content from the layout,
so you can use any layout you want, even have multiple layouts
for different parts of your app.

## 1. Any HTML can be your layout

Take any HTML, set where content should be rendered using the slot targets,
connect directives to control any aspect of your app (layout, theme, etc.)
and you have a custom layout.
Expand All @@ -28,7 +26,7 @@ and you have a custom layout.
{_LayoutUISource}
</CodeBlock>

## 2. Using your custom layout
## Using your custom layout

Now that we have a layout, we need to connect it with Contember,
swap it anytime you need.
Expand Down
38 changes: 23 additions & 15 deletions docs/reference/admin/layouts/directives.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ import _indexSource from '!!raw-loader!./_Example/index.tsx'

# <span className="version">Interface 1.2+</span> Directives

Directives are React components that can be used to pass values (even rendered) to upper context
and control your app from within the page—**to direct it what to do**, hence the name.
**Use directives to to pass values to upper contexts using JSX components.**

Directives are React components that can be used to pass values to upper contexts
just by rendering `<Directive>` components and specifying the name and content.

Similar to slots that give you a way to escape from the tied page layout, directives give you a way to escape
from the tied page behavior.

In most cases you can use it to control your app from within the page to **direct it what to do** (hence the name),
e.g. when you need some pages to stretch to the full width of the screen.

:::tip You can render as many directives as you want, **the last one rendered wins**.
This principle ensures the ability to **override the value of a directive from within
Expand All @@ -27,16 +35,16 @@ To use a directive that will push value to the parent context just render the `D
```tsx title="admin/pages/example.tsx" {3,7,12}
import * as React from 'react'
import { CommonSlots } from '@contember/layout'
import { Directive, Title } from '../components/Directives'
import { Directive } from '../components/Directives'

export default () => (
<>
<Directive name="title" content="Page Title" />
<Directive name="full-width" content={true} />
<p>Some content</p>

<div>
<p>Nested content</p>
<Directive name="title" content="Override Page Title" />
<Directive name="full-width" content={false} />
</div>
</>
)
Expand All @@ -46,14 +54,15 @@ export default () => (

To create a directive, you need to use the `createDirectiveContext` factory function.

:::tip
To make the usage of directives easier, you can create a shorthand component. For example, you can create a `Title` component that will render the `Directive` component with the `title` name.
:::

<CodeBlock language="typescript" title="admin/components/Directives.tsx" metastring="{1,13,16-18}">
{_components_DirectivesSource}
</CodeBlock>

:::tip
To make the usage of directives easier, you can create a shorthand component.
For example, you can create a `FullWidth` component that will render the `Directive` that will set the full width directive to `true`.
:::

## 3. Applying the directives in the layout

To use the directives in the layout, you need to use the `useDirectives` hook.
Expand All @@ -65,13 +74,12 @@ export const Layout = ({ children: pages }: React.PropsWithChildren) => {
const directives = useDirectives()

return (
<>
<main>
<h1>{directives.title}</h1>
</main>

<div className={directives['full-width']
? "full-width"
: undefined
}>
{pages}
</>
</div>
)
}
```
Loading

0 comments on commit e219a71

Please sign in to comment.