Skip to main content

Configurable Layout

ConfigurableLayout

The ConfigurableLayout component contains the components necessary to configure and render a configurable layout. It is itself a component and also has the following components attached:

  • ConfigurableLayout.Grid
  • ConfigurableLayout.Slot
  • ConfigurableLayout.Panel

These four components work together to provide a declarative way to define a layout. A typical code example is as follows:

const layoutConfig = [
{ name: 'four', breakpoint: 1390, columns: 4 },
{ name: 'three', breakpoint: 1050, columns: 3 },
{ name: 'two', breakpoint: 670, columns: 2 },
{ name: 'one', breakpoint: 0, columns: 1 },
]

function DashboardLayout() {
const pageLayout // Page layout data fetched from the server
const savePageLayout // Function to call when saving a page layout
const revertPageLayout // Function to call when reverting a page layout
const canViewUsers // Function to that returns a boolean indicating if the current user can view all users

return (
<ConfigurableLayout
pageLayout={pageLayout}
onSave={savePageLayout}
onRevert={revertPageLayout}
fallback={<FallbackLoader />}
config={layoutConfig}
rowHeight={112}
isResizable
>
{/* Use `ConfigurableLayout.Grid` to delineate where the UI for the layout should display. */}
<ConfigurableLayout.Grid />
{/* Use `ConfigurableLayout.Slot` to define configuration for which panels are available for the layout. */}
<ConfigurableLayout.Slot
name="users"
title="Users"
enableWhen={canViewUsers}
>
{/* Use `ConfigurableLayout.Panel` when bulding out panel UI. */}
<ConfigurableLayout.Panel title="Users">
{/* Content goes here. */}
</ConfigurableLayout.Panel>
</ConfigurableLayout.Slot>
<ConfigurableLayout.Slot name="teams" title="Teams">
<ConfigurableLayout.Panel title="Teams">
{/* Content goes here. */}
</ConfigurableLayout.Panel>
</ConfigurableLayout.Slot>
</ConfigurableLayout>
)
}

Network Requests

Fetching and persisting configurable layouts requires network requests to an API. This package provides three functions for reading and writing configurable layouts to the server. Their type signatures are included below:

// NOTE: the `PageLayoutDataInput` type is a data structure used internally by the configurable layout engine. You should not need to interact with this data directly.

/** Fetch a page layout for the given `page` from the server. */
function fetchPageLayout(page: string): Promise<PageLayoutDataInput | undefined>

/** Insert or update a given page layout, based on its ID. */
function upsertPageLayout(
pageLayout?: PageLayoutDataInput | undefined
): Promise<PageLayoutDataInput | undefined>

/** Delete a given page layout from the server. */
function deletePageLayout(
pageLayout?: PageLayoutDataInput | undefined
): Promise<void>

Customizing Layouts

This package provides a suite of components that can be used in tandem to customize layouts:

  • useConfigurableLayoutEditor: A hook to get access to state and callbacks from the configurable layout engine
  • CustomizeLayoutMenu: A component that renders a menu for editing or reverting (if applicable) the current layout
  • PanelConfiguration: A component that renders a list of the currently available panels for the user to enable/disable

These three components are used in concert to provide a layout customization to the user. A typical code example is as follows:

import React from 'react'
import { ButtonGroup, Button } from '@starlight/buttons'
import {
useConfigurableLayoutEditor,
CustomizeLayoutMenu,
PanelConfiguration,
} from '@shared/configurable-layout'

export function LayoutEditButton() {
const {
isEditing,
save,
edit,
cancel,
revert,
pageLayout,
} = useConfigurableLayoutEditor()

return isEditing ? (
<>
<PanelConfiguration />
<ButtonGroup>
<Button variant="neutral" text="Cancel" onClick={cancel} />
<Button text="Save" onClick={save} />
</ButtonGroup>
</>
) : (
<CustomizeLayoutMenu
onEdit={edit}
onRevert={revert}
canRevert={pageLayout.canRevert}
/>
)
}

Testing

When unit testing configurable layouts, it can be helpful to replace the panels that will be shown with placeholders. Putting DebugProvider in the component tree during testing swaps out panels with debug equivalents with the data-testid prop set to configurable-layout-panel-NAME where NAME is the name of the panel.

A typical code example is as follows:

// Assume `DashboardLayout` is a component containing a full implementation of the `ConfigurableLayout` with two panels: 'user' and 'team'
it('renders the correct panels', async () => {
renderWithThemeProvider(
<DebugProvider>
<DashboardLayout />
</DebugProvider>
)

expect(
await screen.findByTestId('configurable-layout-panel-user')
).toBeInTheDocument()
expect(
await screen.findByTestId('configurable-layout-panel-team')
).toBeInTheDocument()
})