Skip to content

Commit

Permalink
New Panels component (#2001)
Browse files Browse the repository at this point in the history
Co-authored-by: Mayank <[email protected]>
  • Loading branch information
r100-stack and mayank99 authored Nov 19, 2024
1 parent c9872e1 commit b829983
Show file tree
Hide file tree
Showing 27 changed files with 2,051 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/sour-parrots-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@itwin/itwinui-react': minor
---

Added a new generic `unstable_Panels` component for easy setup of nested screens/panels. Example uses: multi-layered menus, wizards, settings screens, etc.
- This API is temporarily marked as **unstable** to collect [feedback](https://github.com/iTwin/iTwinUI/discussions/2348) for some time to tailor the generic component specifically to users' needs.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
329 changes: 329 additions & 0 deletions apps/react-workshop/src/Panels.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import {
Button,
Divider,
Flex,
List,
ListItem,
unstable_Panels as Panels,
Surface,
Text,
ToggleSwitch,
} from '@itwin/itwinui-react';

export default {
component: Panels,
title: 'Panels',
};

export const Basic = () => {
const panelIdRoot = 'root';
const panelIdMoreInfo = 'more-info';

return (
<Panels.Wrapper
as={Surface}
style={{
inlineSize: 'min(300px, 30vw)',
blockSize: 'min(500px, 50vh)',
}}
>
<Panels.Panel id={panelIdRoot}>
<Surface.Header as={Panels.Header}>Root</Surface.Header>
<Surface.Body as={List}>
<ListItem>
<Panels.Trigger for={panelIdMoreInfo}>
<ListItem.Action>More details</ListItem.Action>
</Panels.Trigger>
</ListItem>
</Surface.Body>
</Panels.Panel>

<Panels.Panel id={panelIdMoreInfo}>
<Surface.Header as={Panels.Header}>More details</Surface.Header>
<Surface.Body isPadded>
<Text>Content</Text>
</Surface.Body>
</Panels.Panel>
</Panels.Wrapper>
);
};

export const MultiPanelInformationPanel = () => {
const initialActiveId = 'root';

const panels = Array.from(Array(20).keys()).map((i) => ({
id: `panel-${i}`,
label: `Panel ${i}`,
}));

return (
<Panels.Wrapper
as={Surface}
style={{
inlineSize: 'min(300px, 30vw)',
blockSize: 'min(500px, 50vh)',
}}
>
<Panels.Panel
id={initialActiveId}
as={Flex}
flexDirection='column'
alignItems='stretch'
gap='0'
>
<Surface.Header as={Panels.Header}>Root</Surface.Header>
<Surface.Body as={List}>
{panels.map((panel) => (
<ListItem key={panel.id}>
<ListItem.Content>
<Panels.Trigger for={`${panel.id}`}>
<ListItem.Action>{panel.label}</ListItem.Action>
</Panels.Trigger>
</ListItem.Content>
</ListItem>
))}
</Surface.Body>
</Panels.Panel>

{panels.map((panel) => (
<Panels.Panel
as={Flex}
key={panel.id}
id={panel.id}
flexDirection='column'
alignItems='stretch'
>
<Surface.Header as={Panels.Header}>{panel.label}</Surface.Header>
<Surface.Body as={Flex} flexDirection='column'>
<Text>{`Content for ${panel.id}`}</Text>
<Flex.Spacer />
<Divider />
<Text>{`Footer for ${panel.id}`}</Text>
</Surface.Body>
</Panels.Panel>
))}
</Panels.Wrapper>
);
};

export const MultiLevelList = () => {
const initialActiveId = React.useId();
const qualityPanelId = React.useId();
const speedPanelId = React.useId();
const accessibilityPanelId = React.useId();

const [repeat, setRepeat] = React.useState(false);
const [quality, setQuality] = React.useState('240p');
const [speed, setSpeed] = React.useState('1.0x');
const [accessibilityOptions, setAccessibilityOptions] = React.useState<
string[]
>([]);

const panels = Panels.useInstance();

const _Item = React.useCallback(
({
content,
state,
setState,
}: {
content: string;
state: string;
setState: React.Dispatch<React.SetStateAction<string>>;
}) => {
const selected = state === content;

return (
<ListItem
active={selected}
aria-selected={selected}
onClick={() => {
panels.goBack();
}}
>
<ListItem.Action onClick={() => setState(content)}>
{content}
</ListItem.Action>
<ListItem.Icon />
</ListItem>
);
},
[panels],
);

const _ItemQuality = React.useCallback(
({ content }: { content: string }) => (
<_Item content={content} state={quality} setState={setQuality} />
),
[_Item, quality],
);

const _ItemSpeed = React.useCallback(
({ content }: { content: string }) => (
<_Item content={content} state={speed} setState={setSpeed} />
),
[_Item, speed],
);

const _ItemAccessibility = React.useCallback(
({ content }: { content: string }) => (
<_Item
content={content}
state={accessibilityOptions.includes(content) ? content : ''}
setState={() => {
setAccessibilityOptions((prev) =>
prev.includes(content)
? prev.filter((item) => item !== content)
: [...prev, content],
);
}}
/>
),
[_Item, accessibilityOptions],
);

const qualities = React.useMemo(
() => ['240p', '360p', '480p', '720p', '1080p'],
[],
);

const speeds = React.useMemo(
() => Array.from({ length: 21 }, (_, i) => (i * 0.1).toFixed(1) + 'x'),
[],
);

const toggleSwitchId = React.useId();

return (
<>
<Panels.Wrapper
instance={panels}
as={Surface}
style={{
inlineSize: 'min(200px, 30vw)',
blockSize: 'min(250px, 50vh)',
}}
>
<Panels.Panel id={initialActiveId}>
<List>
<ListItem>
<ListItem.Content as='label' htmlFor={toggleSwitchId}>
Repeat
</ListItem.Content>
<ToggleSwitch
id={toggleSwitchId}
onChange={(e) => setRepeat(e.target.checked)}
checked={repeat}
/>
</ListItem>
<ListItem>
<Panels.Trigger for={qualityPanelId}>
<ListItem.Action>Quality</ListItem.Action>
</Panels.Trigger>
</ListItem>
<ListItem>
<Panels.Trigger for={speedPanelId}>
<ListItem.Action>Speed</ListItem.Action>
</Panels.Trigger>
</ListItem>
<ListItem>
<Panels.Trigger for={accessibilityPanelId}>
<ListItem.Action>Accessibility</ListItem.Action>
</Panels.Trigger>
</ListItem>
</List>
</Panels.Panel>

<Panels.Panel
id={qualityPanelId}
as={Flex}
flexDirection='column'
alignItems='stretch'
gap='0'
>
<Surface.Header as={Panels.Header}>Quality</Surface.Header>
<Surface.Body as={List}>
{qualities.map((quality) => (
<_ItemQuality key={quality} content={quality} />
))}
</Surface.Body>
</Panels.Panel>

<Panels.Panel
id={speedPanelId}
as={Flex}
flexDirection='column'
alignItems='stretch'
gap='0'
>
<Surface.Header as={Panels.Header}>Speed</Surface.Header>
<Surface.Body as={List}>
{speeds.map((speed) => (
<_ItemSpeed key={speed} content={speed} />
))}
</Surface.Body>
</Panels.Panel>

<Panels.Panel
id={accessibilityPanelId}
as={Flex}
flexDirection='column'
alignItems='stretch'
gap='0'
>
<Surface.Header as={Panels.Header}>Accessibility</Surface.Header>
<Surface.Body as={List}>
<_ItemAccessibility content='High contrast' />
<_ItemAccessibility content='Large text' />
<_ItemAccessibility content='Screen reader' />
</Surface.Body>
</Panels.Panel>
</Panels.Wrapper>
</>
);
};

export const NestedPanels = () => {
const panels = Panels.useInstance();

const initialActiveId = 'root';
const panel1Id = 'panel-1';
const panel1_1Id = 'panel-1-1';
const panel1_1_1Id = 'panel-1-1-1';

const panelIds = [initialActiveId, panel1Id, panel1_1Id, panel1_1_1Id];

return (
<Flex flexDirection='column' alignItems='flex-start'>
<Button id='instance-go-back' onClick={() => panels.goBack()}>
Go Back
</Button>
<Panels.Wrapper
instance={panels}
as={Surface}
style={{
width: 'min(300px, 30vw)',
height: 'min(500px, 50vh)',
}}
>
{panelIds.map((id, index) => (
<Panels.Panel key={id} id={id}>
<Surface.Header as={Panels.Header}>{id}</Surface.Header>
<Surface.Body isPadded>
<Panels.Trigger for={panelIds[index + 1]}>
<Button>
Go to {panelIds[index + 1] ?? "panel that doesn't exist"}
</Button>
</Panels.Trigger>
</Surface.Body>
</Panels.Panel>
))}
</Panels.Wrapper>
</Flex>
);
};
21 changes: 21 additions & 0 deletions apps/react-workshop/src/Panels.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
describe('Panels', () => {
const storyPath = 'Panels';
const tests = [
'Basic',
'Multi Panel Information Panel',
'Multi Level List',
'Nested Panels',
];

tests.forEach((testName) => {
it(testName, function () {
const id = Cypress.storyId(storyPath, testName);
cy.visit('/', { qs: { mode: 'preview', story: id } });
cy.compareSnapshot(testName);
});
});
});
Loading

0 comments on commit b829983

Please sign in to comment.