Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
51f23af
compound!
scotttjob Jan 6, 2026
4a118c3
adding new tests
scotttjob Jan 6, 2026
a4aed9e
Merge branch 'CLEANUP/page-refactor-visual' into CLEANUP/page-refacto…
scotttjob Jan 6, 2026
2ebd7b3
adding some changes to make debugging easier
scotttjob Jan 7, 2026
bd6b29a
getting visual tests passing
scotttjob Jan 7, 2026
8ecc938
Merge branch 'master' into CLEANUP/page-refactor-visual
scotttjob Jan 7, 2026
e068b62
Merge branch 'CLEANUP/page-refactor-visual' into CLEANUP/page-refacto…
scotttjob Jan 7, 2026
eb40a71
removing debug changes
scotttjob Jan 7, 2026
3b46406
update page to use the proper components
scotttjob Jan 7, 2026
083eb32
fixing the height of the small tests
scotttjob Jan 7, 2026
6dab012
Merge branch 'CLEANUP/page-refactor-visual' into CLEANUP/page-refacto…
scotttjob Jan 7, 2026
cbd194a
adding modified page props
scotttjob Jan 7, 2026
e5c7132
Add the fully composable API to Page component
nad182 Feb 10, 2026
ce524ad
Add docs for composable Page API
nad182 Feb 10, 2026
9f38a1d
Add composable Page stories to showcase various configurations
nad182 Feb 10, 2026
30ada2c
Adds visual tests for composable Page
nad182 Feb 10, 2026
00af4bc
Update Page prop table
nad182 Feb 10, 2026
e97dd83
Use correct icon in visual test
nad182 Feb 10, 2026
113490b
Merge remote-tracking branch 'origin/master' into CLEANUP/page-refact…
nad182 Feb 13, 2026
6a7816e
Update page visual test snapshots
nad182 Feb 13, 2026
cbdd55c
chore(components): move Page stories from v7 to v9 directory
nad182 Feb 13, 2026
d5dba83
chore(components): modernize Page stories to Storybook v9
nad182 Feb 13, 2026
f61fcf0
Migrage Page stories to Storybook 9
nad182 Feb 13, 2026
4968d2b
fix: return previously deleted new line
nad182 Feb 13, 2026
da2a414
refactor(Page): remove unnecessary Content wrapper from stories and d…
nad182 Feb 17, 2026
81322ae
fix: controls panel crash on WithAdditionalTitleFields story
nad182 Feb 18, 2026
dd0c61f
revert(components): move Page stories back to v7 storybook
nad182 Feb 18, 2026
16e3559
refactor(components): move Page compound component types to types.ts
nad182 Feb 18, 2026
6e22467
refactor(components): use discriminated union for Page action props
nad182 Feb 19, 2026
7dfeea7
fix(components): enable Storybook controls for composable Page stories
nad182 Feb 19, 2026
6d72dc2
docs(components): regenerate Page props for docs
nad182 Feb 19, 2026
a5b1478
refactor(components): introduce slots for buttons and actions; change…
nad182 Feb 20, 2026
146f281
docs: update composable Page docs to reflect API/slot changes
nad182 Feb 20, 2026
38f34c3
test(visual): update VisualTestPage with new slot structure for actio…
nad182 Feb 20, 2026
5b18efa
docs: add Markdown examples for Page.Subtitle and Page.Intro; add leg…
nad182 Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/components/Page/Page.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import { Meta } from "@storybook/addon-docs";

# Page

[Page docs](https://atlantis.getjobber.com/components/Page) have moved to the new site.
[Page docs](https://atlantis.getjobber.com/components/Page) have moved to the
new site.
199 changes: 193 additions & 6 deletions docs/components/Page/Web.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import React, { useRef, useState } from "react";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import type { Meta, StoryFn } from "@storybook/react";
import { Heading, StatusLabel, Tooltip } from "@jobber/components";
import { Page } from "@jobber/components/Page";
import {
Page,
type PageComposableProps,
type PageLegacyProps,
} from "@jobber/components/Page";
import { Content } from "@jobber/components/Content";
import { Markdown } from "@jobber/components/Markdown";
import { Text } from "@jobber/components/Text";
import { Menu } from "@jobber/components/Menu";
import { Button } from "@jobber/components/Button";
import { Popover } from "@jobber/components/Popover";

export default {
Expand All @@ -13,17 +20,17 @@ export default {
viewMode: "story",
previewTabs: { code: { hidden: false } },
},
} as ComponentMeta<typeof Page>;
} satisfies Meta<typeof Page>;

const BasicTemplate: ComponentStory<typeof Page> = args => (
const BasicTemplate: StoryFn<PageLegacyProps> = args => (
<Page {...args}>
<Content>
<Text>Page content here</Text>
</Content>
</Page>
);

const CustomTitleTemplate: ComponentStory<typeof Page> = args => {
const CustomTitleTemplate: StoryFn<PageLegacyProps> = args => {
const props = { ...args, titleMetaData: undefined };

return (
Expand All @@ -42,7 +49,7 @@ const CustomTitleTemplate: ComponentStory<typeof Page> = args => {
);
};

const PopoverTemplate: ComponentStory<typeof Page> = args => {
const PopoverTemplate: StoryFn<PageLegacyProps> = args => {
const primaryDivRef = useRef(null);
const [showPrimaryPopover, setShowPrimaryPopover] = useState(false);

Expand Down Expand Up @@ -176,3 +183,183 @@ WithAdditionalTitleFields.parameters = {
},
},
};

export const ComposableBasic: StoryFn<PageComposableProps> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>Notifications</Page.Title>
</Page.Header>
<Page.Body>
<Text>Page content here</Text>
</Page.Body>
</Page>
);

export const ComposableWithActions: StoryFn<PageComposableProps> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>Clients</Page.Title>
<Page.Actions>
<Page.PrimarySlot>
<Page.PrimaryAction
label="New Client"
onClick={() => alert("New Client")}
/>
</Page.PrimarySlot>
<Page.SecondarySlot>
<Page.SecondaryAction
label="Export"
onClick={() => alert("Export")}
/>
</Page.SecondarySlot>
<Page.TertiarySlot>
<Page.Menu>
<Menu.Item textValue="Import" onClick={() => alert("Import")}>
<Menu.ItemIcon name="import" />
<Menu.ItemLabel>Import</Menu.ItemLabel>
</Menu.Item>
<Menu.Item textValue="Archive" onClick={() => alert("Archive")}>
<Menu.ItemIcon name="archive" />
<Menu.ItemLabel>Archive</Menu.ItemLabel>
</Menu.Item>
</Page.Menu>
</Page.TertiarySlot>
</Page.Actions>
</Page.Header>
<Page.Body>
<Text>Page content here</Text>
</Page.Body>
</Page>
);
ComposableWithActions.args = {
width: "fill",
};

export const ComposableWithSubtitleAndIntro: StoryFn<
PageComposableProps
> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>Notifications</Page.Title>
<Page.Subtitle>Notify me of all the work</Page.Subtitle>
</Page.Header>
<Page.Intro>
Improve job completion rates, stop chasing payments, and boost your
customer service by automatically communicating with your clients at key
points before, during, and after a job.
</Page.Intro>
<Page.Body>
<Text>Page content here</Text>
</Page.Body>
</Page>
);

export const ComposableWithAllPieces: StoryFn<PageComposableProps> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>
Kitchen Renovation Project
<Page.TitleMetaData>
<StatusLabel label="In Progress" alignment="start" status="warning" />
</Page.TitleMetaData>
</Page.Title>
<Page.Subtitle>Everything but the Kitchen Sink</Page.Subtitle>
<Page.Actions>
<Page.PrimarySlot>
<Page.PrimaryAction
label="Create Invoice"
icon="add"
onClick={() => alert("Create")}
/>
</Page.PrimarySlot>
<Page.SecondarySlot>
<Page.SecondaryAction
label="Send Quote"
onClick={() => alert("Send")}
/>
</Page.SecondarySlot>
<Page.TertiarySlot>
<Page.Menu>
<Menu.Item textValue="Edit" onClick={() => alert("Edit")}>
<Menu.ItemIcon name="edit" />
<Menu.ItemLabel>Edit</Menu.ItemLabel>
</Menu.Item>
<Menu.Item
textValue="Delete"
variation="destructive"
onClick={() => alert("Delete")}
>
<Menu.ItemIcon name="trash" />
<Menu.ItemLabel>Delete</Menu.ItemLabel>
</Menu.Item>
</Page.Menu>
</Page.TertiarySlot>
</Page.Actions>
</Page.Header>
<Page.Body>
<Text>
Building the greatest kitchen one will ever see. The entire kitchen will
be redone for this renovation.
</Text>
</Page.Body>
</Page>
);
ComposableWithAllPieces.args = {
width: "fill",
};

export const ComposableCustomSlot: StoryFn<PageComposableProps> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>Custom Action Elements</Page.Title>
<Page.Actions>
<Page.PrimarySlot>
<Button
label="Custom Primary"
icon="add"
onClick={() => alert("Custom primary")}
fullWidth
/>
</Page.PrimarySlot>
<Page.SecondarySlot>
<Button
label="Custom Secondary"
type="secondary"
onClick={() => alert("Custom secondary")}
fullWidth
/>
</Page.SecondarySlot>
</Page.Actions>
</Page.Header>
<Page.Body>
<Text>
This example uses custom Button elements via the slots instead of the
default Page.PrimaryAction/Page.SecondaryAction components.
</Text>
</Page.Body>
</Page>
);

export const ComposableWithMarkdown: StoryFn<PageComposableProps> = args => (
<Page {...args}>
<Page.Header>
<Page.Title>Notifications</Page.Title>
<Page.Subtitle>
<Markdown content="Everything but the **_Kitchen Sink_**" basicUsage />
</Page.Subtitle>
</Page.Header>
<Page.Intro>
<Markdown
content="Improve job completion rates, stop chasing payments, and boost your customer service. Read more by visiting our [Help Center](https://help.getjobber.com/hc/en-us)."
basicUsage
externalLink
/>
</Page.Intro>
<Page.Body>
<Text>
This example shows how to use Markdown inside Page.Subtitle and
Page.Intro for parity with the props-based API.
</Text>
</Page.Body>
</Page>
);
35 changes: 35 additions & 0 deletions packages/components/src/Page/Page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,38 @@
.titleBar .subtitle {
margin-bottom: var(--space-base);
}

/* Composable Page API: responsive layout via CSS Container Queries */

/* Base composable behavior (always active inside Container) */
@container page-titlebar (min-width: 0) {
.titleBar > .actionGroup {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}

/* Composable: wider viewports — inline title + actions */
@container page-titlebar (min-width: 500px) {
.titleBar {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}

.titleBar > .actionGroup {
flex-wrap: nowrap;
justify-content: flex-end;
}

.titleBar .primaryAction,
.titleBar .actionButton {
margin: 0;
flex: 0 auto;
}

.titleBar .actionButton {
margin-left: var(--space-small);
}
}
Loading
Loading