Skip to content

Commit 8b2abc8

Browse files
committed
fix(LayoutPanel): dialog styles
1 parent 98c2a16 commit 8b2abc8

File tree

3 files changed

+189
-2
lines changed

3 files changed

+189
-2
lines changed

src/components/content/Layout/Layout.stories.tsx

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { IconFilter, IconFilterFilled } from '@tabler/icons-react';
12
import { useState } from 'react';
3+
import { FilterIcon } from 'src/icons';
24

35
import { Button, ItemButton } from '../../actions';
46
import { Block } from '../../Block';
@@ -767,3 +769,184 @@ export const VerticalResizablePanes: Story = {
767769
);
768770
},
769771
};
772+
773+
/**
774+
* Layout.Panel can render as a Dialog instead of an inline panel.
775+
* This is useful for responsive designs where you want the panel
776+
* to appear as a modal dialog on mobile devices.
777+
*
778+
* When `isDialog={true}`, the panel content renders inside a DialogContainer
779+
* with standard Dialog styling (backdrop, centered positioning, animations).
780+
*/
781+
export const PanelAsDialog: Story = {
782+
render: function PanelAsDialogStory() {
783+
const [isDialogOpen, setIsDialogOpen] = useState(false);
784+
785+
return (
786+
<Layout height="100dvh">
787+
<Layout.Panel
788+
isDialog
789+
side="left"
790+
size={300}
791+
isDialogOpen={isDialogOpen}
792+
onDialogOpenChange={setIsDialogOpen}
793+
>
794+
<Layout.PanelHeader
795+
isClosable
796+
title="Panel as Dialog"
797+
onClose={() => setIsDialogOpen(false)}
798+
/>
799+
<Layout.Content>
800+
<Space direction="vertical" gap="1x">
801+
<Text>
802+
This panel renders as a Dialog overlay instead of an inline side
803+
panel.
804+
</Text>
805+
<Text preset="t3" color="#dark-02">
806+
Useful for mobile-responsive layouts where side panels should
807+
become modal dialogs on smaller screens.
808+
</Text>
809+
<Space direction="vertical" gap="1bw">
810+
<ItemButton type="neutral">Menu Item 1</ItemButton>
811+
<ItemButton type="neutral">Menu Item 2</ItemButton>
812+
<ItemButton type="neutral">Menu Item 3</ItemButton>
813+
</Space>
814+
</Space>
815+
</Layout.Content>
816+
<Layout.Footer invertOrder>
817+
<Button type="primary" onPress={() => setIsDialogOpen(false)}>
818+
Done
819+
</Button>
820+
</Layout.Footer>
821+
</Layout.Panel>
822+
823+
<Layout.Toolbar>
824+
<Space>
825+
<Button type="primary" onPress={() => setIsDialogOpen(true)}>
826+
Open Panel Dialog
827+
</Button>
828+
<Title level={4}>Dialog Mode Demo</Title>
829+
</Space>
830+
</Layout.Toolbar>
831+
832+
<Layout.Content padding="2x">
833+
<Space direction="vertical" gap="2x">
834+
<Text>
835+
Click the button above to open the panel as a dialog overlay.
836+
</Text>
837+
<Text preset="t3" color="#dark-02">
838+
The panel uses <code>isDialog=true</code> to render inside a
839+
DialogContainer instead of being positioned absolutely within the
840+
Layout.
841+
</Text>
842+
</Space>
843+
</Layout.Content>
844+
</Layout>
845+
);
846+
},
847+
};
848+
849+
/**
850+
* This example demonstrates a responsive pattern where the panel switches
851+
* between inline panel mode (desktop) and dialog mode (mobile).
852+
*
853+
* Use a toggle to simulate the responsive behavior - in a real app,
854+
* you would use a media query hook like `useMediaQuery('(max-width: 768px)')`.
855+
*/
856+
export const ResponsivePanelToDialog: Story = {
857+
render: function ResponsivePanelToDialogStory() {
858+
const [isMobileMode, setIsMobileMode] = useState(false);
859+
const [isPanelOpen, setIsPanelOpen] = useState(true);
860+
861+
return (
862+
<Layout height="100dvh">
863+
{/* Panel switches between inline and dialog mode based on isMobileMode */}
864+
<Layout.Panel
865+
hasTransition
866+
side="left"
867+
size={260}
868+
isOpen={isPanelOpen}
869+
isDialog={isMobileMode}
870+
isDialogOpen={isPanelOpen}
871+
onOpenChange={setIsPanelOpen}
872+
onDialogOpenChange={setIsPanelOpen}
873+
>
874+
<Layout.PanelHeader
875+
isClosable
876+
title="Filters"
877+
onClose={() => setIsPanelOpen(false)}
878+
/>
879+
<Layout.Content padding=".5x">
880+
<Space direction="vertical" gap="1bw">
881+
<Title level={5} preset="c2" color="#dark-04" padding=".5x">
882+
Categories
883+
</Title>
884+
<ItemButton type="neutral" width="100%">
885+
Electronics
886+
</ItemButton>
887+
<ItemButton type="neutral" width="100%">
888+
Clothing
889+
</ItemButton>
890+
<ItemButton type="neutral" width="100%">
891+
Home & Garden
892+
</ItemButton>
893+
<Title level={5} preset="c2" color="#dark-04" padding=".5x">
894+
Price Range
895+
</Title>
896+
<ItemButton type="neutral" width="100%">
897+
Under $50
898+
</ItemButton>
899+
<ItemButton type="neutral" width="100%">
900+
$50 - $100
901+
</ItemButton>
902+
<ItemButton type="neutral" width="100%">
903+
Over $100
904+
</ItemButton>
905+
</Space>
906+
</Layout.Content>
907+
</Layout.Panel>
908+
909+
<Layout.Toolbar>
910+
<Space>
911+
<Button
912+
type={isMobileMode ? 'primary' : 'neutral'}
913+
icon={!isPanelOpen ? <IconFilter /> : <IconFilterFilled />}
914+
onPress={() => setIsPanelOpen(!isPanelOpen)}
915+
/>
916+
<Title level={4}>Product Catalog</Title>
917+
</Space>
918+
<Button
919+
type={isMobileMode ? 'primary' : 'neutral'}
920+
onPress={() => setIsMobileMode(!isMobileMode)}
921+
>
922+
{isMobileMode ? '📱 Mobile' : '🖥️ Desktop'}
923+
</Button>
924+
</Layout.Toolbar>
925+
926+
<Layout.Content padding="1x" gap="2x">
927+
<Card>
928+
<Title level={5}>
929+
Current Mode:{' '}
930+
{isMobileMode ? 'Mobile (Dialog)' : 'Desktop (Inline Panel)'}
931+
</Title>
932+
<Text preset="t3" color="#dark-02">
933+
Toggle the mode button in the toolbar to switch between inline
934+
panel and dialog mode. In a real app, this would be controlled by
935+
a media query.
936+
</Text>
937+
</Card>
938+
<Layout.Grid columns="repeat(auto-fill, minmax(200px, 1fr))" gap="1x">
939+
{Array.from({ length: 6 }, (_, i) => (
940+
<Card key={i}>
941+
<Title level={6}>Product {i + 1}</Title>
942+
<Text preset="t3" color="#dark-02">
943+
$99.99
944+
</Text>
945+
</Card>
946+
))}
947+
</Layout.Grid>
948+
</Layout.Content>
949+
</Layout>
950+
);
951+
},
952+
};

src/components/content/Layout/LayoutPanel.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,6 @@ function LayoutPanel(
620620
return (
621621
<DialogContainer
622622
isOpen={dialogOpen}
623-
type="panel"
624623
onDismiss={() => handleDialogOpenChange(false)}
625624
{...dialogProps}
626625
>

src/components/content/Layout/LayoutPanelHeader.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ import { useLayoutPanelContext } from './LayoutContext';
1616

1717
const PanelHeaderElement = tasty(Item, {
1818
qa: 'PanelHeader',
19+
shape: 'sharp',
1920
styles: {
2021
border: 'bottom',
21-
radius: 0,
22+
preset: {
23+
'': 't3m',
24+
'size=xsmall': 't4',
25+
'size=xlarge': 't2m',
26+
},
2227

2328
'$inline-padding': '($content-padding, 1x)',
2429
},

0 commit comments

Comments
 (0)