Skip to content

Commit

Permalink
[PanelStack] refactors & fix transitions (#2717)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
giladgray authored Jul 24, 2018
1 parent 8fbe610 commit 0d006ad
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 203 deletions.
11 changes: 4 additions & 7 deletions packages/core/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,10 @@ export const OVERLAY_INLINE = `${OVERLAY}-inline`;
export const OVERLAY_OPEN = `${OVERLAY}-open`;
export const OVERLAY_SCROLL_CONTAINER = `${OVERLAY}-scroll-container`;

export const PANELSTACK = `${NS}-panel-stack`;
export const PANELSTACK_HEADER = `${PANELSTACK}-header`;
export const PANELSTACK_HEADER_BACK = `${PANELSTACK}-header-back`;
export const PANELSTACK_HEADER_SPACER = `${PANELSTACK}-header-spacer`;
export const PANELSTACK_HEADER_TITLE = `${PANELSTACK}-header-title`;
export const PANELSTACK_TRANSITION = `${PANELSTACK}-transition`;
export const PANELSTACK_VIEW = `${PANELSTACK}-view`;
export const PANEL_STACK = `${NS}-panel-stack`;
export const PANEL_STACK_HEADER = `${PANEL_STACK}-header`;
export const PANEL_STACK_HEADER_BACK = `${PANEL_STACK}-header-back`;
export const PANEL_STACK_VIEW = `${PANEL_STACK}-view`;

export const POPOVER = `${NS}-popover`;
export const POPOVER_ARROW = `${POPOVER}-arrow`;
Expand Down
153 changes: 55 additions & 98 deletions packages/core/src/components/panel-stack/_panel-stack.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,134 +6,91 @@
.#{$ns}-panel-stack {
position: relative;
overflow: hidden;

// .#{$ns}-panel-stack-transition-push {
// @include react-transition-phase(
// "#{$ns}-panel-stack-transition-push",
// "enter",
// (transform: translateX(100%) translate(0%)),
// $easing: $pt-transition-ease-bounce,
// $before: "&"
// );
// @include react-transition-phase(
// "#{$ns}-panel-stack-transition-push",
// "exit",
// (transform: translateX(0%) translate(-100%)),
// $easing: $pt-transition-ease-bounce,
// $before: "&"
// );
// }

// .#{$ns}-panel-stack-transition-pop {
// @include react-transition-phase(
// "#{$ns}-panel-stack-transition-pop",
// "enter",
// (transform: translateX(-100%) translate(0%)),
// $easing: $pt-transition-ease-bounce,
// $before: "&"
// );
// @include react-transition-phase(
// "#{$ns}-panel-stack-transition-pop",
// "exit",
// (transform: translateX(0%) translate(100%)),
// $easing: $pt-transition-ease-bounce,
// $before: "&"
// );
// }

.#{$ns}-panel-stack-transition-push-enter {
transform: translateX(100%);
}

.#{$ns}-panel-stack-transition-push-enter-active {
transform: translateX(0%);
transition: transform $pt-transition-duration $pt-transition-ease-bounce;
}

.#{$ns}-panel-stack-transition-push-exit {
transform: translateX(0%);
}

.#{$ns}-panel-stack-transition-push-exit-active {
transform: translateX(-100%);
transition: transform $pt-transition-duration $pt-transition-ease-bounce;
}

.#{$ns}-panel-stack-transition-pop-enter {
transform: translateX(-100%);
}

.#{$ns}-panel-stack-transition-pop-enter-active {
transform: translateX(0%);
transition: transform $pt-transition-duration $pt-transition-ease-bounce;
}

.#{$ns}-panel-stack-transition-pop-exit {
transform: translateX(0%);
}

.#{$ns}-panel-stack-transition-pop-exit-active {
transform: translateX(100%);
transition: transform $pt-transition-duration $pt-transition-ease-bounce;
}
}

.#{$ns}-panel-stack-header {
display: flex;
flex-shrink: 0;
align-items: center;
z-index: $pt-z-index-content + 2;
box-shadow: 0 1px $pt-divider-black;
height: $pt-grid-size * 3;

.#{$ns}-dark & {
box-shadow: 0 1px $pt-dark-divider-white;
}

.#{$ns}-panel-stack-header-back {
display: flex;
align-items: center;
cursor: pointer;
color: $pt-text-color-muted;

.#{$ns}-dark & {
color: $pt-dark-text-color-muted;
}

&:hover {
color: $pt-text-color;

.#{$ns}-dark & {
color: $pt-dark-text-color;
}
}
}

.#{$ns}-panel-stack-header-spacer {
// two span children act as spacers to keep the title centered.
> span {
display: flex;
flex: 1;
align-items: stretch;
}

.#{$ns}-panel-stack-header-title {
margin: 0 $pt-grid-size;
color: $pt-text-color;
font-weight: 600;
.#{$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}-dark & {
color: $pt-dark-text-color;
}
.#{$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%)),
$easing: linear,
$duration: $pt-transition-duration * 4
);
@include react-transition-phase(
"#{$ns}-panel-stack",
"exit",
(transform: translateX(-100%) translate(0%)),
$easing: linear,
$duration: $pt-transition-duration * 4
);
}

// POP transition: enter from left (-100%), existing panel moves off right.
.#{$ns}-panel-stack-pop {
@include react-transition-phase(
"#{$ns}-panel-stack",
"enter",
(transform: translateX(-100%) translate(0%)),
$easing: linear,
$duration: $pt-transition-duration * 4
);
@include react-transition-phase(
"#{$ns}-panel-stack",
"exit",
(transform: translateX(100%) translate(0%)),
$easing: linear,
$duration: $pt-transition-duration * 4
);
}
34 changes: 16 additions & 18 deletions packages/core/src/components/panel-stack/panelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

import * as React from "react";

import { Icon } from "../icon/icon";
import { Text } from "../text/text";

import { Button } from "..";
import * as Classes from "../../common/classes";

export interface IPanelHeaderProps {
/**
* The name of the previous panel header in the stack. Will not render unless an onBackClick
* handler is defined.
* The name of the previous panel header in the stack, used as text for the
* back button. This prop is ignored if `onBackClick` is omitted.
*/
backTitle?: string;

/**
* The handler when the back button is clicked.
*/
Expand All @@ -22,12 +23,12 @@ export interface IPanelHeaderProps {
export class PanelHeader extends React.PureComponent<IPanelHeaderProps> {
public render() {
return (
<div className={Classes.PANELSTACK_HEADER}>
<div className={Classes.PANELSTACK_HEADER_SPACER}>{this.maybeRenderBack()}</div>
<Text className={Classes.PANELSTACK_HEADER_TITLE} ellipsize={true}>
<div className={Classes.PANEL_STACK_HEADER}>
<span>{this.maybeRenderBack()}</span>
<Text className={Classes.HEADING} ellipsize={true}>
{this.props.children}
</Text>
<div className={Classes.PANELSTACK_HEADER_SPACER} />
<span />
</div>
);
}
Expand All @@ -37,17 +38,14 @@ export class PanelHeader extends React.PureComponent<IPanelHeaderProps> {
return null;
}
return (
<div className={Classes.PANELSTACK_HEADER_BACK} onClick={this.props.onBackClick}>
<Icon icon="chevron-left" />
{this.maybeRenderBackTitle()}
</div>
<Button
className={Classes.PANEL_STACK_HEADER_BACK}
icon="chevron-left"
minimal={true}
small={true}
text={this.props.backTitle}
onClick={this.props.onBackClick}
/>
);
}

private maybeRenderBackTitle() {
if (this.props.backTitle === undefined) {
return null;
}
return this.props.backTitle;
}
}
14 changes: 7 additions & 7 deletions packages/core/src/components/panel-stack/panelStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ export class PanelStack extends React.PureComponent<IPanelStackProps, IPanelStac
};

public render() {
const className = classNames(Classes.PANELSTACK, this.props.className);
const classes = classNames(
Classes.PANEL_STACK,
`${Classes.PANEL_STACK}-${this.state.direction}`,
this.props.className,
);
return (
<TransitionGroup className={className} component="div">
<TransitionGroup className={classes} component="div">
{this.renderCurrentPanel()}
</TransitionGroup>
);
Expand All @@ -78,11 +82,7 @@ export class PanelStack extends React.PureComponent<IPanelStackProps, IPanelStac
const { stack } = this.state;
const [activePanel, previousPanel] = stack;
return (
<CSSTransition
classNames={`${Classes.PANELSTACK_TRANSITION}-${this.state.direction}`}
key={activePanel.title + "_" + stack.length}
timeout={100}
>
<CSSTransition classNames={Classes.PANEL_STACK} key={stack.length} timeout={400}>
<PanelView
key={stack.length}
previousPanel={previousPanel}
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/components/panel-stack/panelView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright 2018 Palantir Technologies, Inc. All rights reserved.

import classNames from "classnames";
import * as React from "react";

import { PanelHeader } from "./panelHeader";
Expand All @@ -26,9 +25,8 @@ export interface IPanelViewProps {
export class PanelView extends React.PureComponent<IPanelViewProps> {
public render() {
const { panel, panelProps } = this.props;
const className = classNames(Classes.PANELSTACK_VIEW);
return (
<div className={className}>
<div className={Classes.PANEL_STACK_VIEW}>
{this.renderPanelHeader()}
<panel.component {...panelProps} {...panel.props} />
</div>
Expand Down
17 changes: 8 additions & 9 deletions packages/core/test/panel-stack/panelStackTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ describe("<PanelStack>", () => {
assert.exists(newPanelButton);
newPanelButton.simulate("click");

const newPanelHeader = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_TITLE);
const newPanelHeader = panelStackWrapper.findClass(Classes.HEADING);
assert.exists(newPanelHeader);
assert.equal(newPanelHeader.at(0).text(), "New Panel 1");

const backButton = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_BACK);
const backButton = panelStackWrapper.findClass(Classes.PANEL_STACK_HEADER_BACK);
assert.exists(backButton);
backButton.simulate("click");

const oldPanelHeader = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_TITLE);
const oldPanelHeader = panelStackWrapper.findClass(Classes.HEADING);
assert.exists(oldPanelHeader);
assert.equal(oldPanelHeader.at(1).text(), "Test Title");
});
Expand All @@ -91,7 +91,7 @@ describe("<PanelStack>", () => {
assert.isTrue(onOpen.calledOnce);
assert.isFalse(onClose.calledOnce);

const backButton = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_BACK);
const backButton = panelStackWrapper.findClass(Classes.PANEL_STACK_HEADER_BACK);
assert.exists(backButton);
backButton.simulate("click");
assert.isTrue(onClose.calledOnce);
Expand All @@ -100,15 +100,14 @@ describe("<PanelStack>", () => {

it("does not have the back button when only a single panel is on the stack", () => {
panelStackWrapper = renderPanelStack({ initialPanel });
const backButton = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_BACK);
const backButton = panelStackWrapper.findClass(Classes.PANEL_STACK_HEADER_BACK);
assert.equal(backButton.length, 0);
});

it("assigns the class to both the TransitionGroup and the PanelView", () => {
const TEST_CLASS_NAME = "TEST_CLASS_NAME";
panelStackWrapper = renderPanelStack({ initialPanel, className: TEST_CLASS_NAME });
const foundClasses = panelStackWrapper.findClass(TEST_CLASS_NAME);
assert.equal(foundClasses.length, 2);
assert.isTrue(panelStackWrapper.hasClass(TEST_CLASS_NAME));
});

it("can render a panel without a title", () => {
Expand All @@ -124,7 +123,7 @@ describe("<PanelStack>", () => {
assert.exists(newPanelButton);
newPanelButton.simulate("click");

const backButtonWithoutTitle = panelStackWrapper.findClass(Classes.PANELSTACK_HEADER_BACK);
const backButtonWithoutTitle = panelStackWrapper.findClass(Classes.PANEL_STACK_HEADER_BACK);
assert.equal(backButtonWithoutTitle.text(), "chevron-left");

const newPanelButtonOnNotEmpty = panelStackWrapper
Expand All @@ -135,7 +134,7 @@ describe("<PanelStack>", () => {
newPanelButtonOnNotEmpty.simulate("click");

const backButtonWithTitle = panelStackWrapper
.findClass(Classes.PANELSTACK_HEADER_BACK)
.findClass(Classes.PANEL_STACK_HEADER_BACK)
.hostNodes()
.at(1);
assert.equal(backButtonWithTitle.text(), "chevron-left");
Expand Down
Loading

0 comments on commit 0d006ad

Please sign in to comment.