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

fix(Section): defaultIsOpen should behave as expected #6326

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion packages/core/src/components/section/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export interface SectionProps extends Props, Omit<HTMLDivProps, "title">, React.
* This prop has no effect if `collapsible={false}`.
*/
collapseProps?: Pick<CollapseProps, "className" | "keepChildrenMounted" | "transitionDuration"> & {
/**
* @default true
*/
defaultIsOpen?: boolean;
};

Expand Down Expand Up @@ -109,7 +112,8 @@ export const Section: React.FC<SectionProps> = React.forwardRef((props, ref) =>
title,
...htmlProps
} = props;
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(collapseProps?.defaultIsOpen ?? false);
// The initial useState value is negated in order to conform to the `isCollapsed` expectation.
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(!(collapseProps?.defaultIsOpen ?? true));
const toggleIsCollapsed = React.useCallback(() => setIsCollapsed(!isCollapsed), [isCollapsed]);

const isHeaderLeftContainerVisible = title != null || icon != null || subtitle != null;
Expand Down
9 changes: 5 additions & 4 deletions packages/core/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ import "./common/utilsTests";

// components
import "./alert/alertTests";
import "./breadcrumbs/breadcrumbsTests";
import "./breadcrumbs/breadcrumbTests";
import "./breadcrumbs/breadcrumbsTests";
import "./buttons/buttonTests";
import "./callout/calloutTests";
import "./card/cardTests";
import "./card-list/cardListTests";
import "./card/cardTests";
import "./collapse/collapseTests";
import "./context-menu/contextMenuTests";
import "./context-menu/contextMenuSingletonTests";
import "./context-menu/contextMenuTests";
import "./controls/controlsTests";
import "./controls/inputGroupTests";
import "./controls/numericInputTests";
Expand All @@ -45,8 +45,8 @@ import "./forms/textAreaTests";
import "./hotkeys/hotkeyTests";
import "./hotkeys/hotkeysParserTests";
import "./hotkeys/keyComboTagTests";
import "./html/htmlTests";
import "./html-select/htmlSelectTests";
import "./html/htmlTests";
import "./icon/iconTests";
import "./menu/menuItemTests";
import "./menu/menuTests";
Expand All @@ -61,6 +61,7 @@ import "./popover/popperUtilTests";
import "./portal/portalTests";
import "./progress/progressBarTests";
import "./resize-sensor/resizeSensorTests";
import "./section/sectionTests";
import "./slider/handleTests";
import "./slider/multiSliderTests";
import "./slider/rangeSliderTests";
Expand Down
101 changes: 101 additions & 0 deletions packages/core/test/section/sectionTests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2015 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { assert } from "chai";
import { mount } from "enzyme";
import * as React from "react";

import { IconNames } from "@blueprintjs/icons";

import { Classes, H6, Section, SectionCard } from "../../src";

describe("<Section>", () => {
let containerElement: HTMLElement | undefined;

beforeEach(() => {
containerElement = document.createElement("div");
document.body.appendChild(containerElement);
});
afterEach(() => {
containerElement?.remove();
});

it("supports className", () => {
const wrapper = mount(<Section className="foo" />, {
attachTo: containerElement,
});
assert.isTrue(wrapper.find(`.${Classes.SECTION}`).hostNodes().exists());
assert.isTrue(wrapper.find(`.foo`).hostNodes().exists());
});

it("supports icon", () => {
const wrapper = mount(<Section icon={IconNames.GRAPH} title="title" />, {
attachTo: containerElement,
});
assert.isTrue(wrapper.find(`[data-icon="${IconNames.GRAPH}"]`).exists());
});

it("renders optional title element", () => {
const wrapper = mount(<Section title="title" />, {
attachTo: containerElement,
});
assert.isTrue(wrapper.find(H6).exists());
});

it("renders optional sub-title element", () => {
const wrapper = mount(<Section title="title" subtitle="subtitle" />, {
attachTo: containerElement,
});
assert.isTrue(wrapper.find(`.${Classes.SECTION_HEADER_SUB_TITLE}`).hostNodes().exists());
});

it("collapsible is open when defaultIsOpen={undefined}", () => {
const wrapper = mount(
<Section collapsible={true} collapseProps={{ defaultIsOpen: undefined }}>
<SectionCard>is open</SectionCard>
</Section>,
{
attachTo: containerElement,
},
);
assert.isTrue(wrapper.find(`[data-icon="${IconNames.CHEVRON_UP}"]`).exists());
});

it("collapsible is open when defaultIsOpen={true}", () => {
const wrapper = mount(
<Section collapsible={true} collapseProps={{ defaultIsOpen: true }}>
<SectionCard>is open</SectionCard>
</Section>,
{
attachTo: containerElement,
},
);
assert.isTrue(wrapper.find(`[data-icon="${IconNames.CHEVRON_UP}"]`).exists());
});

it("collapsible is closed when defaultIsOpen={false}", () => {
const wrapper = mount(
<Section collapsible={true} collapseProps={{ defaultIsOpen: false }}>
<SectionCard>is closed</SectionCard>
</Section>,
{
attachTo: containerElement,
},
);

assert.isTrue(wrapper.find(`[data-icon="${IconNames.CHEVRON_DOWN}"]`).exists());
});
});
20 changes: 19 additions & 1 deletion packages/docs-app/src/examples/core-examples/sectionExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
EditableText,
Elevation,
H5,
H6,
Label,
Section,
SectionCard,
Expand All @@ -35,6 +36,7 @@ import { IconNames } from "@blueprintjs/icons";

export interface SectionExampleState {
collapsible: boolean;
defaultIsOpen: boolean;
elevation: SectionElevation;
hasDescription: boolean;
hasIcon: boolean;
Expand All @@ -54,6 +56,7 @@ const BASIL_DESCRIPTION_TEXT = dedent`
export class SectionExample extends React.PureComponent<ExampleProps, SectionExampleState> {
public state: SectionExampleState = {
collapsible: false,
defaultIsOpen: true,
elevation: Elevation.ZERO,
hasDescription: false,
hasIcon: false,
Expand All @@ -68,6 +71,7 @@ export class SectionExample extends React.PureComponent<ExampleProps, SectionExa
public render() {
const {
collapsible,
defaultIsOpen,
elevation,
hasDescription,
hasIcon,
Expand All @@ -82,9 +86,15 @@ export class SectionExample extends React.PureComponent<ExampleProps, SectionExa
<H5>Section Props</H5>
<Switch checked={isCompact} label="Compact" onChange={this.toggleIsCompact} />
<Switch checked={hasIcon} label="Icon" onChange={this.toggleHasIcon} />
<Switch checked={hasDescription} label="Description" onChange={this.toggleHasDescription} />
<Switch checked={hasDescription} label="Sub-title" onChange={this.toggleHasDescription} />
<Switch checked={hasRightElement} label="Right element" onChange={this.toggleHasRightElement} />
<Switch checked={collapsible} label="Collapsible" onChange={this.toggleCollapsible} />
{collapsible && (
<>
<H6>Collapse Props</H6>
<Switch checked={defaultIsOpen} label="Default is open" onChange={this.toggleDefaultIsOpen} />
</>
)}
<Label>
Elevation
<Slider
Expand Down Expand Up @@ -134,8 +144,14 @@ export class SectionExample extends React.PureComponent<ExampleProps, SectionExa
return (
<Example options={options} {...this.props}>
<Section
// A `key` is provided here to force the component to
// re-mount when `defaultIsOpen` is changed, otherwise
// the local state in the `Collapse` component is not
// updated.
key={String(defaultIsOpen)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using key to reset state and animations is one of my favorite tricks in the pocket too. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha yeah it's a neat little trick! :)

collapsible={collapsible}
compact={isCompact}
collapseProps={{ defaultIsOpen }}
elevation={elevation}
icon={hasIcon ? IconNames.BOOK : undefined}
rightElement={
Expand Down Expand Up @@ -170,6 +186,8 @@ export class SectionExample extends React.PureComponent<ExampleProps, SectionExa

private toggleCollapsible = () => this.setState({ collapsible: !this.state.collapsible });

private toggleDefaultIsOpen = () => this.setState({ defaultIsOpen: !this.state.defaultIsOpen });

private togglePanelIsPadded = () => this.setState({ isPanelPadded: !this.state.isPanelPadded });

private handleElevationChange = (elevation: SectionElevation) => this.setState({ elevation });
Expand Down