-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Core] Add panel stack component (#2642)
* Added panel stack * First test integrated * Tests added * Transitions adjusted * Updated readme * Made lower case * Dark theme added * Linting fixed * Review items fixed, transitions reverted * Removed extra panelClassName * _panel-stack.scss * minor docs * updated tests * Tests adjusted and fixed * [PanelStack] refactors & fix transitions (#2717) * fix the transitions! * example refactor: move stack to options bar * lint example * refactor header styles to use fewer classes * Classes.PANEL_STACK * fix tests * style fixes, simpler transition * no stack limit * back button nowrap * Adjusted transition to include opacity and -50% left movement * Removed useless type checking * Fixed opacity * Opacity * Opacity flipped * Removed 0 * Reversed opacities * One more attempt * [PanelStack] merge some files (#2719) * merge PanelHeader into PanelView * panelProps file with more docs * panelProps.ts * export panelProps, more docs * example key * docs * safeInvoke * changed easing settings * fix copyrights * onClose/Open param names * [PanelStack] use IPanel in openPanel() public API (#2738) * openPanel accepts IPanel instead of three args * PanelView onClose/Open props skips getPanelProps() step * fix tests * remove IPanelPptions * [PanelStack] documentation (#2737) * PanelStack docs * more docs refactors * adjust docs-modifiers margins * remove "pop the stack"
- Loading branch information
1 parent
5922413
commit 6581f83
Showing
16 changed files
with
658 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
packages/core/src/components/panel-stack/_panel-stack.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2018 Palantir Technologies, Inc. All rights reserved. | ||
// Licensed under the terms of the LICENSE file distributed with this project. | ||
|
||
@import "../../common/variables"; | ||
@import "~@blueprintjs/core/src/common/react-transition"; | ||
|
||
.#{$ns}-panel-stack { | ||
position: relative; | ||
overflow: hidden; | ||
} | ||
|
||
.#{$ns}-panel-stack-header { | ||
display: flex; | ||
flex-shrink: 0; | ||
align-items: center; | ||
box-shadow: 0 1px $pt-divider-black; | ||
height: $pt-grid-size * 3; | ||
|
||
.#{$ns}-dark & { | ||
box-shadow: 0 1px $pt-dark-divider-white; | ||
} | ||
|
||
// two span children act as spacers to keep the title centered. | ||
> span { | ||
display: flex; | ||
flex: 1; | ||
align-items: stretch; | ||
} | ||
|
||
.#{$ns}-heading { | ||
margin: 0 ($pt-grid-size / 2); | ||
} | ||
} | ||
|
||
.#{$ns}-button.#{$ns}-panel-stack-header-back { | ||
margin-left: $pt-grid-size / 2; | ||
padding-left: 0; | ||
white-space: nowrap; | ||
|
||
.#{$ns}-icon { | ||
// reduce margins around icon so it fits better in tight header | ||
margin: 0 2px; | ||
} | ||
} | ||
|
||
.#{$ns}-panel-stack-view { | ||
@include position-all(absolute, 0); | ||
display: flex; | ||
flex-direction: column; | ||
|
||
// border between panels, visible during transition | ||
margin-right: -1px; | ||
border-right: 1px solid $pt-divider-black; | ||
|
||
background-color: $white; | ||
overflow-y: auto; | ||
|
||
.#{$ns}-dark & { | ||
background-color: $dark-gray4; | ||
} | ||
} | ||
|
||
// PUSH transition: enter from right (100%), existing panel moves off left. | ||
.#{$ns}-panel-stack-push { | ||
@include react-transition-phase( | ||
"#{$ns}-panel-stack", | ||
"enter", | ||
(transform: translateX(100%) translate(0%), opacity: 0 1), | ||
$easing: ease, | ||
$duration: $pt-transition-duration * 4 | ||
); | ||
@include react-transition-phase( | ||
"#{$ns}-panel-stack", | ||
"exit", | ||
(transform: translateX(-50%) translate(0%), opacity: 0 1), | ||
$easing: ease, | ||
$duration: $pt-transition-duration * 4 | ||
); | ||
} | ||
|
||
// POP transition: enter from left (-50%), existing panel moves off right. | ||
.#{$ns}-panel-stack-pop { | ||
@include react-transition-phase( | ||
"#{$ns}-panel-stack", | ||
"enter", | ||
(transform: translateX(-50%) translate(0%), opacity: 0 1), | ||
$easing: ease, | ||
$duration: $pt-transition-duration * 4 | ||
); | ||
@include react-transition-phase( | ||
"#{$ns}-panel-stack", | ||
"exit", | ||
(transform: translateX(100%) translate(0%), opacity: 0 1), | ||
$easing: ease, | ||
$duration: $pt-transition-duration * 4 | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
@# Panel stack | ||
|
||
`PanelStack` manages a stack of panels and displays only the topmost panel. | ||
|
||
Each panel appears with a header containing a "back" button to return to the | ||
previous panel. The bottom-most `initialPanel` cannot be closed or removed from | ||
the stack. Panels use | ||
[`CSSTransition`](http://reactcommunity.org/react-transition-group/css-transition) | ||
for seamless transitions. | ||
|
||
|
||
@reactExample PanelStackExample | ||
|
||
@## Panels | ||
|
||
Panels are supplied as `IPanel` objects like `{ component, props, title }`, | ||
where `component` and `props` are used to render the panel element and `title` | ||
will appear in the header and back button. This breakdown allows the component | ||
to avoid cloning elements. Note that each panel is only mounted when it is atop | ||
the stack and is unmounted when it is closed or when a panel opens above it. | ||
|
||
`PanelStack` injects its own `IPanelProps` into each panel (in addition to the | ||
`props` defined alongside the `component`), providing methods to imperatively | ||
close the current panel or open a new one on top of it. | ||
|
||
```tsx | ||
import { Button, IPanelProps, PanelStack } from "@blueprintjs/core"; | ||
|
||
class MyPanel extends React.Component<IPanelProps> { | ||
public render() { | ||
return <Button onClick={this.openSettingsPanel} text="Settings" /> | ||
} | ||
|
||
private openSettingsPanel() { | ||
// openPanel (and closePanel) are injected by PanelStack | ||
this.props.openPanel({ | ||
component: SettingsPanel, // <- class or stateless function type | ||
props: { enabled: true }, // <- SettingsPanel props without IPanelProps | ||
title: "Settings", // <- appears in header and back button | ||
}); | ||
} | ||
} | ||
|
||
class SettingsPanel extends React.Component<IPanelProps & { enabled: boolean }> { | ||
// ... | ||
} | ||
|
||
<PanelStack initialPanel={{ component: MyPanel, title: "Home" }} /> | ||
``` | ||
|
||
@interface IPanel | ||
|
||
@interface IPanelProps | ||
|
||
@## Props | ||
|
||
The panel stack cannot be controlled but `onClose` and `onOpen` callbacks are | ||
available to listen for changes. | ||
|
||
@interface IPanelStackProps | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2018 Palantir Technologies, Inc. All rights reserved. | ||
* | ||
* Licensed under the terms of the LICENSE file distributed with this project. | ||
*/ | ||
|
||
import * as React from "react"; | ||
|
||
/** | ||
* An object describing a panel in a `PanelStack`. | ||
*/ | ||
export interface IPanel<P = {}> { | ||
/** | ||
* The component type to render for this panel. This must be a reference to | ||
* the component class or SFC, _not_ a JSX element, so it can be re-created | ||
* dynamically when needed. | ||
*/ | ||
component: React.ComponentType<P & IPanelProps>; | ||
|
||
/** | ||
* The props passed to the component type when it is rendered. The methods | ||
* in `IPanelProps` will be injected by `PanelStack`. | ||
*/ | ||
props?: P; | ||
|
||
/** | ||
* The title to be displayed above this panel. It is also used as the text | ||
* of the back button for any panel opened by this panel. | ||
*/ | ||
title?: React.ReactNode; | ||
} | ||
|
||
/** | ||
* Include this interface in your panel component's props type to access these | ||
* two functions which are injected by `PanelStack`. | ||
* | ||
* ```tsx | ||
* import { IPanelProps } from "@blueprintjs/core"; | ||
* export class SettingsPanel extends React.Component<IPanelProps & ISettingsPanelProps> {...} | ||
* ``` | ||
*/ | ||
export interface IPanelProps { | ||
/** | ||
* Call this method to programatically close this panel. If this is the only | ||
* panel on the stack then this method will do nothing. | ||
* | ||
* Remember that the panel header always contains a "back" button that | ||
* closes this panel on click (unless there is only one panel on the stack). | ||
*/ | ||
closePanel(): void; | ||
|
||
/** | ||
* Call this method to open a new panel on the top of the stack. | ||
*/ | ||
openPanel<P>(panel: IPanel<P>): void; | ||
} |
102 changes: 102 additions & 0 deletions
102
packages/core/src/components/panel-stack/panelStack.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
* Copyright 2018 Palantir Technologies, Inc. All rights reserved. | ||
* | ||
* Licensed under the terms of the LICENSE file distributed with this project. | ||
*/ | ||
|
||
import classNames from "classnames"; | ||
import * as React from "react"; | ||
import { CSSTransition, TransitionGroup } from "react-transition-group"; | ||
|
||
import * as Classes from "../../common/classes"; | ||
import { IProps } from "../../common/props"; | ||
import { safeInvoke } from "../../common/utils"; | ||
import { IPanel } from "./panelProps"; | ||
import { PanelView } from "./panelView"; | ||
|
||
export interface IPanelStackProps extends IProps { | ||
/** | ||
* The initial panel to show on mount. This panel cannot be removed from the | ||
* stack and will appear when the stack is empty. | ||
*/ | ||
initialPanel: IPanel; | ||
|
||
/** | ||
* Callback invoked when the user presses the back button or a panel invokes | ||
* the `closePanel()` injected prop method. | ||
*/ | ||
onClose?: (removedPanel: IPanel) => void; | ||
|
||
/** | ||
* Callback invoked when a panel invokes the `openPanel(panel)` injected | ||
* prop method. | ||
*/ | ||
onOpen?: (addedPanel: IPanel) => void; | ||
} | ||
|
||
export interface IPanelStackState { | ||
/** Whether the stack is currently animating the push or pop of a panel. */ | ||
direction: "push" | "pop"; | ||
|
||
/** The current stack of panels. The first panel in the stack will be displayed. */ | ||
stack: IPanel[]; | ||
} | ||
|
||
export class PanelStack extends React.PureComponent<IPanelStackProps, IPanelStackState> { | ||
public state: IPanelStackState = { | ||
direction: "push", | ||
stack: [this.props.initialPanel], | ||
}; | ||
|
||
public render() { | ||
const classes = classNames( | ||
Classes.PANEL_STACK, | ||
`${Classes.PANEL_STACK}-${this.state.direction}`, | ||
this.props.className, | ||
); | ||
return ( | ||
<TransitionGroup className={classes} component="div"> | ||
{this.renderCurrentPanel()} | ||
</TransitionGroup> | ||
); | ||
} | ||
|
||
private renderCurrentPanel() { | ||
const { stack } = this.state; | ||
if (stack.length === 0) { | ||
return null; | ||
} | ||
const [activePanel, previousPanel] = stack; | ||
return ( | ||
<CSSTransition classNames={Classes.PANEL_STACK} key={stack.length} timeout={400}> | ||
<PanelView | ||
onClose={this.handlePanelClose} | ||
onOpen={this.handlePanelOpen} | ||
panel={activePanel} | ||
previousPanel={previousPanel} | ||
/> | ||
</CSSTransition> | ||
); | ||
} | ||
|
||
private handlePanelClose = (panel: IPanel) => { | ||
const { stack } = this.state; | ||
// only remove this panel if it is at the top and not the only one. | ||
if (stack[0] !== panel || stack.length <= 1) { | ||
return; | ||
} | ||
safeInvoke(this.props.onClose, panel); | ||
this.setState(state => ({ | ||
direction: "pop", | ||
stack: state.stack.filter(p => p !== panel), | ||
})); | ||
}; | ||
|
||
private handlePanelOpen = (panel: IPanel) => { | ||
safeInvoke(this.props.onOpen, panel); | ||
this.setState(state => ({ | ||
direction: "push", | ||
stack: [panel, ...state.stack], | ||
})); | ||
}; | ||
} |
Oops, something went wrong.
6581f83
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Core] Add panel stack component (#2642)
Preview: documentation | landing | table