Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions .changeset/healthy-worms-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"paris": minor
---

### pageConfig API
- Per-page configuration for title, bottomPanel, and additionalActions
- Declarative approach eliminates conditional logic in parent components
- Clean API for multi-step drawer flows

### DrawerBottomPanelPortal Component
- Portal system for child components to control bottom panel content
- Eliminates need for prop drilling for components in separate files
- Automatic page-active detection in paginated drawers
- Supports replace, append, prepend modes

### Context Providers
- useDrawer() - Access drawer controls from any child component
- usePaginationContext() - Access pagination state without prop drilling
- useDrawerBottomPanel() - Imperative bottom panel control
- useIsDrawerPageActive() - Internal page active tracking
5 changes: 5 additions & 0 deletions .changeset/mighty-points-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"paris": patch
---

fix(Card): Update Card and Cardbutton styles
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,4 @@
"type-fest": "^3.10.0",
"typescript": "^5.2.2"
}
}
}
2 changes: 1 addition & 1 deletion src/stories/card/Card.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
.flat {
border-radius: var(--pte-new-borders-radius-roundedMedium);
border: 1px solid var(--pte-new-colors-borderStrong);
background: linear-gradient(0deg, var(--pte-new-colors-overlayWhiteSubtle) 0%, var(--pte-new-colors-overlayWhiteSubtle) 100%), var(--pte-new-colors-surfacePrimary);
background: var(--pte-new-colors-overlayWhiteSubtle);

&.pending {
border: 1px dashed var(--pte-new-colors-borderStrong);
Expand Down
2 changes: 1 addition & 1 deletion src/stories/cardbutton/CardButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
.flat {
border-radius: var(--pte-new-borders-radius-roundedMedium);
border: 1px solid var(--pte-new-colors-borderStrong);
background: linear-gradient(0deg, var(--pte-new-colors-overlayWhiteSubtle) 0%, var(--pte-new-colors-overlayWhiteSubtle) 100%), var(--pte-new-colors-surfacePrimary);
background: var(--pte-new-colors-overlayWhiteSubtle);

&:hover {
background-color: var(--pte-new-colors-overlayStrong);
Expand Down
259 changes: 258 additions & 1 deletion src/stories/drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';

import { Drawer } from './Drawer';
import { DrawerBottomPanelPortal } from './DrawerBottomPanelPortal';
import { useDrawer } from '.';
import { Button } from '../button';
import { Callout } from '../callout';
import {
Menu, MenuButton, MenuItems, MenuItem,
} from '../menu';
import { usePagination } from '../pagination';
import { usePagination, usePaginationContext } from '../pagination';
import { ChevronRight, Ellipsis } from '../icon';
import { Input } from '../input';

const meta: Meta<typeof Drawer> = {
title: 'Surfaces/Drawer',
Expand Down Expand Up @@ -278,3 +281,257 @@ export const Full: Story = {
);
},
};

/**
* Example using pageConfig for declarative per-page configuration.
* Eliminates conditional logic in the parent component.
*/
export const WithPageConfig: Story = {
args: {},
render: () => {
const [isOpen, setIsOpen] = useState(false);
const pages = ['details', 'edit', 'confirm'] as const;
const pagination = usePagination<typeof pages>('details');

return (
<>
<Button onClick={() => setIsOpen(true)}>
Open Multi-Step Form
</Button>
<Drawer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Multi-Step Process"
pagination={pagination}
pageConfig={{
details: {
title: 'View Details',
bottomPanel: (
<Button onClick={() => pagination.open('edit')}>
Edit
</Button>
),
},
edit: {
title: 'Edit Information',
bottomPanel: (
<Button onClick={() => pagination.open('confirm')}>
Continue to Confirm
</Button>
),
},
confirm: {
title: 'Confirm Changes',
bottomPanel: (
<div style={{ display: 'flex', gap: '8px', width: '100%' }}>
<Button onClick={() => setIsOpen(false)}>
Confirm
</Button>
<Button kind="secondary" onClick={() => pagination.back()}>
Go Back
</Button>
</div>
),
},
}}
>
<div key="details">
<h3>Account Details</h3>
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
</div>
<div key="edit">
<h3>Edit Account</h3>
<Input label="Name" defaultValue="John Doe" />
<Input label="Email" defaultValue="john@example.com" />
</div>
<div key="confirm">
<h3>Confirm Your Changes</h3>
<Callout>
Please review your changes before confirming.
</Callout>
<p>Updated Name: John Doe</p>
<p>Updated Email: john@example.com</p>
</div>
</Drawer>
</>
);
},
};

/**
* Example using DrawerBottomPanelPortal to inject bottom panel content from a child component.
* Child component can control its own actions without prop drilling.
*/
export const WithBottomPanelPortal: Story = {
args: {},
render: () => {
const [isOpen, setIsOpen] = useState(false);

// Child component that uses the portal
const FormWithActions = () => {
const [value, setValue] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const { close } = useDrawer();

const handleSubmit = async () => {
setIsSubmitting(true);
// Simulate API call
await new Promise((resolve) => { setTimeout(resolve, 1000); });
setIsSubmitting(false);
close();
};

return (
<>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Input
label="Enter your message"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Type something..."
/>
<p>
Your message:
{' '}
{value || '(empty)'}
</p>
</div>

<DrawerBottomPanelPortal mode="replace">
<div style={{ display: 'flex', gap: '8px', width: '100%' }}>
<Button
onClick={handleSubmit}
loading={isSubmitting}
disabled={!value}
>
Submit
</Button>
<Button kind="secondary" onClick={close}>
Cancel
</Button>
</div>
</DrawerBottomPanelPortal>
</>
);
};

return (
<>
<Button onClick={() => setIsOpen(true)}>
Open Form with Portal
</Button>
<Drawer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Form Example"
>
<FormWithActions />
</Drawer>
</>
);
},
};

/**
* Example using usePaginationContext to access pagination from nested components.
* Eliminates prop drilling for navigation callbacks.
*/
export const WithPaginationContext: Story = {
args: {},
render: () => {
const [isOpen, setIsOpen] = useState(false);
const pages = ['step1', 'step2', 'step3'] as const;
const pagination = usePagination<typeof pages>('step1');

// Nested component that accesses pagination directly
const Step1Content = () => {
const paginationCtx = usePaginationContext<typeof pages>();

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<h3>Step 1: Personal Information</h3>
<Input label="Name" placeholder="Enter your name" />

<DrawerBottomPanelPortal>
<Button onClick={() => paginationCtx?.open('step2')}>
Next: Contact Info
</Button>
</DrawerBottomPanelPortal>
</div>
);
};

const Step2Content = () => {
const paginationCtx = usePaginationContext<typeof pages>();

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<h3>Step 2: Contact Information</h3>
<Input label="Email" placeholder="Enter your email" />

<DrawerBottomPanelPortal>
<div style={{ display: 'flex', gap: '8px', width: '100%' }}>
<Button onClick={() => paginationCtx?.open('step3')}>
Next: Review
</Button>
<Button kind="secondary" onClick={() => paginationCtx?.back()}>
Back
</Button>
</div>
</DrawerBottomPanelPortal>
</div>
);
};

const Step3Content = () => {
const { close } = useDrawer();

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<h3>Step 3: Review & Submit</h3>
<Callout>
Please review your information before submitting.
</Callout>

<DrawerBottomPanelPortal>
<div style={{ display: 'flex', gap: '8px', width: '100%' }}>
<Button onClick={() => {
pagination.reset();
close();
}}>
Submit
</Button>
<Button kind="secondary" onClick={() => pagination.back()}>
Back
</Button>
</div>
</DrawerBottomPanelPortal>
</div>
);
};

return (
<>
<Button onClick={() => setIsOpen(true)}>
Start Wizard
</Button>
<Drawer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Registration Wizard"
pagination={pagination}
pageConfig={{
step1: { title: 'Step 1 of 3' },
step2: { title: 'Step 2 of 3' },
step3: { title: 'Step 3 of 3' },
}}
>
<div key="step1"><Step1Content /></div>
<div key="step2"><Step2Content /></div>
<div key="step3"><Step3Content /></div>
</Drawer>
</>
);
},
};
Loading