Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Panels component #2001

Draft
wants to merge 93 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
7736226
Starter exploration
r100-stack Apr 15, 2024
7060cca
Working demo
r100-stack Apr 23, 2024
ef1e1e1
Merge branch 'main' into rohan/layered-dropdown-menu
r100-stack Apr 25, 2024
d248d21
Merge branch 'main' into rohan/layered-dropdown-menu
r100-stack May 10, 2024
3fd8c0b
Revert all changes
r100-stack May 10, 2024
ff277dd
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack May 10, 2024
46eea38
WIP from last week
r100-stack May 13, 2024
94230bd
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Jun 14, 2024
33c571c
Added PanelHeader, PanelBackButton.
r100-stack Jun 17, 2024
2190432
Remove fallbackId since it's not a DOM node
r100-stack Jun 17, 2024
a6953e2
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Jun 20, 2024
6a8f5af
Logic improvements/additions.
r100-stack Jun 21, 2024
0d11d42
WIP sandbox with animations
r100-stack Jun 24, 2024
078acdc
WIP
r100-stack Jun 24, 2024
00e98ac
WIP
r100-stack Jun 28, 2024
619e2c0
WIP state reducer
r100-stack Jul 1, 2024
f39e15c
WIP: Fix short flickering when changing pages
r100-stack Jul 2, 2024
45ee0d0
Animation working in Panels and playground
r100-stack Jul 22, 2024
e99c2f6
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Jul 22, 2024
019f0f4
Cleanup
r100-stack Jul 22, 2024
a93d8a6
Focus new panel after animation completes.
r100-stack Jul 22, 2024
8fac8c3
Prevent scroll on focus
r100-stack Jul 29, 2024
571acc8
Controlled state without storing history
r100-stack Jul 30, 2024
68d1691
Don't change activePanel if panel already there
r100-stack Jul 30, 2024
f9984f8
inert, reduced-motion, JSDocs, testing
r100-stack Jul 30, 2024
8f23e20
instance, animationOptions, stories
r100-stack Jul 31, 2024
ca6e854
Fix focus
r100-stack Jul 31, 2024
2f9fa72
Tests
r100-stack Aug 1, 2024
4aa3bd5
More tests, PanelInstanceProvider
r100-stack Aug 5, 2024
782224a
Rename
r100-stack Aug 5, 2024
ceba173
Fix failing build
r100-stack Aug 6, 2024
c28041f
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 6, 2024
4ab3281
Undo playgrounds
r100-stack Aug 6, 2024
ad35667
Fix install, leftover improvements
r100-stack Aug 6, 2024
3debf85
Removed unused class
r100-stack Aug 6, 2024
a90b292
Better inert
r100-stack Aug 7, 2024
bd27069
Remove unused code, small cleanups
r100-stack Aug 7, 2024
5adb857
Remove animationOptions
r100-stack Aug 7, 2024
a62ec7e
Remove controlled mode
r100-stack Aug 7, 2024
650d958
Remove no longer needed tests
r100-stack Aug 7, 2024
ac15a37
Keep focus on trigger during animation
r100-stack Aug 7, 2024
c7b4f3f
Fix e2e test
r100-stack Aug 7, 2024
8617961
No `position: absolute`
r100-stack Aug 8, 2024
4e51ac0
Better names
r100-stack Aug 8, 2024
e93d3c0
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 8, 2024
c60c296
Merge branch 'main' into rohan/layered-dropdown-menu
r100-stack Aug 19, 2024
77f2829
Move animations out
r100-stack Aug 19, 2024
729ba7c
Merge remote-tracking branch 'origin/rohan/layered-dropdown-menu' int…
r100-stack Aug 20, 2024
4eb5efb
goBack in PanelInstanceProvider
r100-stack Aug 21, 2024
c573b9e
Remove options in goBack for simplification
r100-stack Aug 21, 2024
14e28ef
No ref.current in rendering path
r100-stack Aug 21, 2024
9079808
Added examples and corrected stories
r100-stack Aug 22, 2024
9b3a202
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 22, 2024
d16d7b6
nit
r100-stack Aug 23, 2024
898c806
Step towards removing animations.
r100-stack Aug 23, 2024
5f52104
Working front and back.
r100-stack Aug 23, 2024
2c21cb5
Working smooth scroll.
r100-stack Aug 23, 2024
1e7d0a8
TODOs from planning
r100-stack Aug 23, 2024
ec1b2ae
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 23, 2024
268d0fd
WIP improvements based on TODOs and regressions
r100-stack Aug 27, 2024
4431970
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 27, 2024
a0e535c
More WIP improvements. overflow-anchor not working.
r100-stack Aug 28, 2024
5c6be6a
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Aug 28, 2024
c94ee12
Cleanup PR a bit for overflow-anchor testing
r100-stack Sep 3, 2024
4936b63
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Sep 3, 2024
3905400
Leftover
r100-stack Sep 3, 2024
18af31d
Diff from Mayank's PR comment
r100-stack Sep 4, 2024
e0c8590
Prevent changing panels when transitioning
r100-stack Sep 4, 2024
66fe6ad
Call onActivePanelChange
r100-stack Sep 4, 2024
a410dbe
Working focus
r100-stack Sep 4, 2024
1d844a8
- Removed `compareDocumentPosition`
r100-stack Sep 4, 2024
f52e3d8
Merge remote-tracking branch 'origin/main' into rohan/layered-dropdow…
r100-stack Sep 4, 2024
a891dcd
No scroll when reduced motion preferred
r100-stack Sep 5, 2024
f1e5552
e2e tests improvements
r100-stack Sep 5, 2024
9bdde7c
Rename vars
r100-stack Sep 5, 2024
0cc4c41
JSDocs, tests, etc.
r100-stack Sep 5, 2024
d1a711c
Fix test failures
r100-stack Sep 5, 2024
c296e05
Undo a891dcd. Use useMediaQuery instead.
r100-stack Sep 6, 2024
756f8a3
Added missing useInertPolyfill
r100-stack Sep 6, 2024
49c0983
changeset
r100-stack Sep 6, 2024
5da8fdc
nit
r100-stack Sep 6, 2024
6c2c2cf
Revert testing leftover
r100-stack Sep 6, 2024
345e494
useDelayed can return undefined
r100-stack Sep 6, 2024
760391f
Leftover
r100-stack Sep 6, 2024
88c4bea
Merge remote-tracking branch 'origin/r/add-missing-inert-polyfills' i…
r100-stack Sep 6, 2024
cd202e9
Remove panelElements
r100-stack Sep 6, 2024
974c6f6
Focus management using refs.
r100-stack Sep 6, 2024
0ab1fde
Code rearrangements
r100-stack Sep 6, 2024
ee06164
Remove unnecessary ref
r100-stack Sep 6, 2024
d3490e4
Show warning when trigger points to dne panel
r100-stack Sep 9, 2024
24516bb
Better var names
r100-stack Sep 9, 2024
4ea8b6a
Leftover
r100-stack Sep 9, 2024
89230b8
Remove unnecessary variables
r100-stack Sep 9, 2024
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
5 changes: 5 additions & 0 deletions .changeset/sour-parrots-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-react': minor
---

Added a new `Panels` component for easy setup of nested screens/panels. Example uses: multi-layered menus, wizards, settings screens, etc.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
373 changes: 373 additions & 0 deletions apps/react-workshop/src/Panels.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
/*---------------------------------------------------------------------------------------------
* 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,
Panels,
Surface,
Text,
ToggleSwitch,
} from '@itwin/itwinui-react';
import { SvgArrowLeft } from '@itwin/itwinui-icons-react';

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

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

return (
<Panels.Wrapper
initialActiveId={panelIdRoot}
as={Surface}
style={{
inlineSize: 'min(300px, 30vw)',
blockSize: 'min(500px, 50vh)',
}}
>
<Panels.Panel id={panelIdRoot} as={Surface} border={false} elevation={0}>
<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}
as={Surface}
border={false}
elevation={0}
>
<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
initialActiveId={initialActiveId}
as={Surface}
style={{
inlineSize: 'min(300px, 30vw)',
blockSize: 'min(500px, 50vh)',
}}
>
<Panels.Panel
id={initialActiveId}
as={Surface}
border={false}
elevation={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
key={panel.id}
id={panel.id}
as={Surface}
border={false}
elevation={0}
>
<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'),
[],
);

return (
<>
<Panels.Wrapper
instance={panels}
as={Surface}
initialActiveId={initialActiveId}
style={{
inlineSize: 'min(200px, 30vw)',
blockSize: 'min(250px, 50vh)',
}}
>
<Panels.Panel as={List} id={initialActiveId}>
<ListItem as='label'>
<ListItem.Content>Repeat</ListItem.Content>
<ToggleSwitch
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>
</Panels.Panel>

<Panels.Panel
id={qualityPanelId}
as={Surface}
border={false}
elevation={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={Surface}
border={false}
elevation={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
as={Surface}
id={accessibilityPanelId}
border={false}
elevation={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 CustomBackButton = () => {
const panelIdRoot = React.useId();
const panelIdMoreInfo = React.useId();

return (
<Panels.Wrapper
initialActiveId={panelIdRoot}
as={Surface}
style={{
inlineSize: 'min(300px, 30vw)',
blockSize: 'min(500px, 50vh)',
}}
>
<Panels.Panel id={panelIdRoot} as={Surface} border={false} elevation={0}>
<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} as={Surface}>
<Surface.Header as={Flex}>
<Panels.BackButton label='Go back to home'>
<SvgArrowLeft />
</Panels.BackButton>
More details
</Surface.Header>
<Surface.Body isPadded>
<Text>Content</Text>
</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}
initialActiveId={initialActiveId}
style={{
width: 'min(300px, 30vw)',
height: 'min(500px, 50vh)',
}}
as={Surface}
>
{panelIds.map((id, index) => (
<Panels.Panel
key={id}
id={id}
as={Surface}
border={false}
elevation={0}
>
<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>
);
};
Loading
Loading