Skip to content

Commit

Permalink
Add max width option to the page (#3484)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Potoms <[email protected]>
Co-authored-by: Bharat Kashyap <[email protected]>
  • Loading branch information
Janpot and bharatkashyap authored May 7, 2024
1 parent 02cc9a9 commit 7ee9540
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 15 deletions.
10 changes: 9 additions & 1 deletion docs/data/toolpad/studio/concepts/page-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ You can override this setting for any page using the `toolpad-display` query par
- `?toolpad-display=shell` - Same as App shell mode.
- `?toolpad-display=standalone` - Same as No shell mode.

{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/display-mode-override.png", "alt": "No shell display mode ", "caption": "Overriding the display mode", "indent": 1}}
{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/display-mode-override.png", "alt": "No shell display mode", "caption": "Overriding the display mode", "indent": 1}}

:::info
See the how-to guide on [how to embed Toolpad Studio pages](/toolpad/studio/how-to-guides/embed-pages/) using the display mode property.
:::

## Max width

Toolpad pages use a Material UI [Container](https://mui.com/material-ui/react-container/) as their top-level element. You can control the maximum width of this container in the page properties.

{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/studio/concepts/page-properties/max-width.png", "alt": "Page container max width", "caption": "Setting the maximum width of the page container", "zoom": false, "width": 306}}

Available values are **xs**, **sm**, **md**, **lg**, **xl**, or **None** to conform to the available width in the window.

## Page parameters

Page parameters allow you to pass external data into the Toolpad Studio page state via the URL query parameters.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/schemas/v1/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@
}
],
"description": "Display mode of the page. This can also be set at runtime with the toolpad-display query parameter"
},
"maxWidth": {
"type": "string",
"enum": ["xs", "sm", "md", "lg", "xl", "none"],
"description": "Top level element of the page."
}
},
"additionalProperties": false,
Expand Down
5 changes: 5 additions & 0 deletions packages/toolpad-studio-runtime/src/appDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export interface ConnectionNode<P = unknown> extends AppDomNodeBase {

export type PageDisplayMode = 'standalone' | 'shell';

export type ContainerWidth = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none';

export const DEFAULT_CONTAINER_WIDTH = 'lg' satisfies ContainerWidth;

export interface PageNode extends AppDomNodeBase {
readonly type: 'page';
readonly attributes: {
Expand All @@ -107,6 +111,7 @@ export interface PageNode extends AppDomNodeBase {
readonly module?: string;
readonly display?: PageDisplayMode;
readonly displayName?: string;
readonly maxWidth?: ContainerWidth;
readonly authorization?: {
readonly allowAll?: boolean;
readonly allowedRoles?: string[];
Expand Down
46 changes: 32 additions & 14 deletions packages/toolpad-studio/src/runtime/ToolpadApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -905,9 +905,15 @@ interface RenderedNodeContentProps {
node: appDom.PageNode | appDom.ElementNode;
childNodeGroups: appDom.NodeChildren<appDom.ElementNode>;
Component: ToolpadComponent<any>;
staticProps?: Record<string, any>;
}

function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeContentProps) {
function RenderedNodeContent({
node,
childNodeGroups,
Component,
staticProps,
}: RenderedNodeContentProps) {
const { setControlledBinding } = React.useContext(SetBindingContext) ?? {};
invariant(setControlledBinding, 'Node must be rendered in a RuntimeScoped context');

Expand Down Expand Up @@ -957,7 +963,10 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC
}

if (typeof hookResult[propName] === 'undefined' && argType) {
hookResult[propName] = getArgTypeDefaultValue(argType);
const defaultValue = getArgTypeDefaultValue(argType);
if (typeof defaultValue !== 'undefined') {
hookResult[propName] = getArgTypeDefaultValue(argType);
}
}
}

Expand Down Expand Up @@ -1082,13 +1091,14 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC

const props: Record<string, any> = React.useMemo(() => {
return {
...staticProps,
...boundProps,
...onChangeHandlers,
...eventHandlers,
...layoutElementProps,
...reactChildren,
};
}, [boundProps, eventHandlers, layoutElementProps, onChangeHandlers, reactChildren]);
}, [staticProps, boundProps, eventHandlers, layoutElementProps, onChangeHandlers, reactChildren]);

const previousProps = React.useRef<Record<string, any>>(props);
const [hasSetInitialBindings, setHasSetInitialBindings] = React.useState(false);
Expand Down Expand Up @@ -1210,24 +1220,19 @@ function RenderedNodeContent({ node, childNodeGroups, Component }: RenderedNodeC
}

interface PageRootProps {
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none';
children?: React.ReactNode;
}

const PageRoot = React.forwardRef<HTMLDivElement, PageRootProps>(function PageRoot(
{ children, ...props }: PageRootProps,
{ children, maxWidth, ...props }: PageRootProps,
ref,
) {
const containerMaxWidth =
maxWidth === 'none' ? false : maxWidth ?? appDom.DEFAULT_CONTAINER_WIDTH;
return (
<Container ref={ref}>
<Stack
data-testid="page-root"
direction="column"
sx={{
my: 2,
gap: 1,
}}
{...props}
>
<Container ref={ref} maxWidth={containerMaxWidth}>
<Stack data-testid="page-root" direction="column" sx={{ my: 2, gap: 1 }} {...props}>
{children}
</Stack>
</Container>
Expand All @@ -1240,6 +1245,11 @@ const PageRootComponent = createComponent(PageRoot, {
type: 'element',
control: { type: 'slots' },
},
maxWidth: {
type: 'string',
enum: ['xs', 'sm', 'md', 'lg', 'xl', 'none'],
control: { type: 'ToggleButtons' },
},
},
});

Expand Down Expand Up @@ -1414,13 +1424,21 @@ function RenderedLowCodePage({ page }: RenderedLowCodePageProps) {

const applicationVm = useApplicationVm(onApplicationVmUpdate);

const pageProps = React.useMemo(
() => ({
maxWidth: page.attributes.maxWidth,
}),
[page.attributes.maxWidth],
);

return (
<ApplicationVmApiContext.Provider value={applicationVm}>
<RuntimeScoped id={'global'} parseBindingsResult={parseBindingsResult} onUpdate={onUpdate}>
<RenderedNodeContent
node={page}
childNodeGroups={{ children }}
Component={PageRootComponent}
staticProps={pageProps}
/>
{queries.map((node) => (
<FetchNode key={node.id} page={page} node={node} />
Expand Down
2 changes: 2 additions & 0 deletions packages/toolpad-studio/src/server/localMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ function expandFromDom<N extends appDom.AppDomNode>(
content: undefinedWhenEmpty(expandChildren(children.children || [], dom)),
queries: undefinedWhenEmpty(expandChildren(children.queries || [], dom)),
display: node.attributes.display,
maxWidth: node.attributes.maxWidth,
authorization: node.attributes.authorization,
},
} satisfies Page;
Expand Down Expand Up @@ -633,6 +634,7 @@ function createPageDomFromPageFile(pageName: string, pageFile: Page): appDom.App
title: pageFileSpec.title,
parameters: pageFileSpec.parameters?.map(({ name, value }) => [name, value]) || [],
display: pageFileSpec.display || undefined,
maxWidth: pageFileSpec.maxWidth || undefined,
authorization: pageFileSpec.authorization || undefined,
},
});
Expand Down
11 changes: 11 additions & 0 deletions packages/toolpad-studio/src/server/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,17 @@ export const pageSchema = toolpadObjectSchema(
.describe(
'Display mode of the page. This can also be set at runtime with the toolpad-display query parameter',
),
maxWidth: z
.union([
z.literal('xs'),
z.literal('sm'),
z.literal('md'),
z.literal('lg'),
z.literal('xl'),
z.literal('none'),
])
.optional()
.describe('Top level element of the page.'),
}),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ const PAGE_DISPLAY_OPTIONS: { value: appDom.PageDisplayMode; label: string }[] =
{ value: 'standalone', label: 'No shell' },
];

const PAGE_CONTAINER_WIDTH_OPTIONS: { value: appDom.ContainerWidth; label: string }[] = [
{ value: 'xs', label: 'xs' },
{ value: 'sm', label: 'sm' },
{ value: 'md', label: 'md' },
{ value: 'lg', label: 'lg' },
{ value: 'xl', label: 'xl' },
{ value: 'none', label: 'None' },
];

export default function PageOptionsPanel() {
const { nodeId: pageNodeId } = usePageEditorState();
const { dom } = useAppState();
Expand All @@ -37,6 +46,7 @@ export default function PageOptionsPanel() {
const appNode = appDom.getApp(dom);

const page = appDom.getNode(dom, pageNodeId, 'page');

const handleDisplayModeChange = React.useCallback(
(event: React.MouseEvent<HTMLElement>, newValue: appDom.PageDisplayMode) => {
domApi.update((draft) =>
Expand All @@ -46,6 +56,15 @@ export default function PageOptionsPanel() {
[domApi, page],
);

const handleContainerModeChange = React.useCallback(
(event: React.MouseEvent<HTMLElement>, newValue: appDom.ContainerWidth) => {
domApi.update((draft) =>
appDom.setNodeNamespacedProp(draft, page, 'attributes', 'maxWidth', newValue),
);
},
[domApi, page],
);

const availableRoles = React.useMemo(() => {
return new Map(appNode.attributes?.authorization?.roles?.map((role) => [role.name, role]));
}, [appNode]);
Expand Down Expand Up @@ -121,6 +140,34 @@ export default function PageOptionsPanel() {
</ToggleButtonGroup>
</Tooltip>
</div>
<div>
<Typography variant="overline">Container width</Typography>
<Tooltip
arrow
placement="left-start"
title={
<Typography variant="inherit">
Set the maximum width of the top-level container.
</Typography>
}
>
<ToggleButtonGroup
exclusive
value={page.attributes.maxWidth ?? appDom.DEFAULT_CONTAINER_WIDTH}
onChange={handleContainerModeChange}
aria-label="Container mode"
fullWidth
>
{PAGE_CONTAINER_WIDTH_OPTIONS.map((option) => {
return (
<ToggleButton key={option.value} value={option.value}>
{option.label}
</ToggleButton>
);
})}
</ToggleButtonGroup>
</Tooltip>
</div>
<div>
<Typography variant="overline">
Authorization
Expand Down

0 comments on commit 7ee9540

Please sign in to comment.