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

[core] feat: Section component #6245

Merged
merged 19 commits into from
Jul 11, 2023
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
3 changes: 3 additions & 0 deletions packages/core/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ module.exports = async function (config) {
// HACKHACK: need to add hotkeys tests
"src/components/hotkeys/*",
"src/context/hotkeys/hotkeysProvider.tsx",

// HACKHACK: need to add section tests
"src/components/section/*",
],
coverageOverrides: {
"src/components/editable-text/editableText.tsx": {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const LARGE = `${NS}-large`;
export const LOADING = `${NS}-loading`;
export const MINIMAL = `${NS}-minimal`;
export const OUTLINED = `${NS}-outlined`;
export const PADDED = `${NS}-padded`;
export const MULTILINE = `${NS}-multiline`;
export const READ_ONLY = `${NS}-read-only`;
export const ROUND = `${NS}-round`;
Expand Down Expand Up @@ -213,6 +214,17 @@ export const MULTISTEP_DIALOG_RIGHT_PANEL = `${MULTISTEP_DIALOG}-right-panel`;
export const MULTISTEP_DIALOG_NAV_TOP = `${MULTISTEP_DIALOG}-nav-top`;
export const MULTISTEP_DIALOG_NAV_RIGHT = `${MULTISTEP_DIALOG}-nav-right`;

export const SECTION = `${NS}-section`;
export const SECTION_COLLAPSED = `${SECTION}-collapsed`;
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer to add a "section"-namespaced "collapsed" class name for now rather than a .bp5-collapsed name.

export const SECTION_HEADER = `${SECTION}-header`;
export const SECTION_HEADER_LEFT = `${SECTION_HEADER}-left`;
export const SECTION_HEADER_TITLE = `${SECTION_HEADER}-title`;
export const SECTION_HEADER_SUB_TITLE = `${SECTION_HEADER}-sub-title`;
export const SECTION_HEADER_DIVIDER = `${SECTION_HEADER}-divider`;
export const SECTION_HEADER_TABS = `${SECTION_HEADER}-tabs`;
export const SECTION_HEADER_RIGHT = `${SECTION_HEADER}-right`;
export const SECTION_PANEL = `${SECTION}-panel`;

export const NAVBAR = `${NS}-navbar`;
export const NAVBAR_GROUP = `${NAVBAR}-group`;
export const NAVBAR_HEADING = `${NAVBAR}-heading`;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
@import "popover/popover";
@import "portal/portal";
@import "progress-bar/progress-bar";
@import "section/section";
@import "skeleton/skeleton";
@import "slider/slider";
@import "spinner/spinner";
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/components/collapse/collapse.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
@# Collapse

The `Collapse` element shows and hides content with a built-in slide in/out animation.
The __Collapse__ element shows and hides content with a built-in slide in/out animation.
You might use this to create a panel of settings for your application, with sub-sections
that can be expanded and collapsed.

@reactExample CollapseExample

@## Props
@## Usage

Any content should be a child of the `Collapse` element. Content must be in the document
flow (e.g. `position: absolute;` wouldn't work, as the parent element would inherit a height of 0).
Any content should be a child of `<Collapse>`. Content must be in the document flow
(e.g. `position: absolute;` wouldn't work, as the parent element would inherit a height of 0).

Toggling the `isOpen` prop triggers the open and close animations.
Once the component is in the closed state, the children are no longer rendered, unless the
Expand Down Expand Up @@ -46,4 +46,6 @@ export class CollapseExample extends React.Component<{}, CollapseExampleState> {
}
```

@## Props interface

@interface CollapseProps
1 change: 1 addition & 0 deletions packages/core/src/components/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@page panel-stack2
@page progress-bar
@page resize-sensor
@page section
@page skeleton
@page spinner
@page tabs
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ export { ResizeEntry, ResizeSensor, ResizeSensorProps } from "./resize-sensor/re
export { HandleHtmlProps, HandleInteractionKind, HandleProps, HandleType } from "./slider/handleProps";
export { MultiSlider, MultiSliderProps, SliderBaseProps } from "./slider/multiSlider";
export { NumberRange, RangeSlider, RangeSliderProps } from "./slider/rangeSlider";
export { Section, SectionProps } from "./section/section";
export { SectionPanel, SectionPanelProps } from "./section/sectionPanel";
export { Slider, SliderProps } from "./slider/slider";
export { Spinner, SpinnerProps, SpinnerSize } from "./spinner/spinner";
export { Tab, TabId, TabProps } from "./tabs/tab";
Expand Down
112 changes: 112 additions & 0 deletions packages/core/src/components/section/_section.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@use "sass:math";
@import "../../common/variables";

$section-min-height: $pt-grid-size * 5 !default;
$section-vertical-padding: $pt-grid-size !default;
$section-horizontal-padding: $pt-grid-size * 2 !default;
$section-panel-padding: $pt-grid-size * 2 !default;

$section-compact-min-height: $pt-grid-size * 4 !default;
$section-compact-vertical-padding: 7px !default;
$section-compact-horizontal-padding: 15px !default;
$section-compact-panel-padding: $pt-grid-size * 1.5 !default;

.#{$ns}-section {
overflow: hidden;
padding: 0;
width: 100%;

&-header {
align-items: center;
border-bottom: 1px solid $pt-divider-black;
display: flex;
gap: $pt-grid-size * 2;
justify-content: space-between;
min-height: $section-min-height;
padding: 0 $section-horizontal-padding;
position: relative;
width: 100%;

&.#{$ns}-dark,
.#{$ns}-dark & {
border-color: $pt-dark-divider-white;
}

&-left {
align-items: center;
display: flex;
gap: $pt-grid-size;
padding: $section-vertical-padding 0;
}

&-title {
margin-bottom: 0;
}

&-sub-title {
margin-top: 2px;
}

&-right {
align-items: center;
display: flex;
gap: $pt-grid-size;
margin-left: auto;
}

&-divider {
align-self: stretch;
margin: $pt-grid-size * 1.5 0;
}

&.#{$ns}-interactive {
cursor: pointer;

&:hover,
&:active {
background: $light-gray5;

&.#{$ns}-dark,
.#{$ns}-dark & {
background: $dark-gray4;
}
}
}
}

&-panel {
&.#{$ns}-padded {
padding: $section-panel-padding;
}

&:not(:last-child) {
border-bottom: 1px solid $pt-divider-black;

&.#{$ns}-dark,
.#{$ns}-dark & {
border-color: $pt-dark-divider-white;
}
}
}

&.#{$ns}-section-collapsed {
.#{$ns}-section-header {
border: none;
}
}

&.#{$ns}-compact {
.#{$ns}-section-header {
min-height: $section-compact-min-height;
padding: 0 $section-compact-horizontal-padding;

&-left {
padding: $section-compact-vertical-padding 0;
}
}

.#{$ns}-section-panel.#{$ns}-padded {
padding: $section-compact-panel-padding;
}
}
}
25 changes: 25 additions & 0 deletions packages/core/src/components/section/section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@# Section

The __Section__ component can be used to contain, structure, and create hierarchy for information in your UI. It makes use of some concepts from other more atomic Blueprint components:

- The overall appearance looks like a [__Card__](#core/components/card)
- Contents may be collapsible like the [__Collapse__](#core/components/collapse) component

@reactExample SectionExample

@## Props interface

@interface SectionProps

@## Section panel

Multiple __SectionPanel__ child components can be added under one __Section__, they will be stacked vertically. This layout can be used to further group information.

```tsx
<Section>
<SectionPanel>{/* ... */}</SectionPanel>
<SectionPanel>{/* ... */}</SectionPanel>
</Section>
```

@interface SectionPanelProps
165 changes: 165 additions & 0 deletions packages/core/src/components/section/section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright 2023 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 classNames from "classnames";
import * as React from "react";

import { ChevronDown, ChevronUp, IconName } from "@blueprintjs/icons";

import { Classes, Elevation } from "../../common";
import { DISPLAYNAME_PREFIX, HTMLDivProps, MaybeElement, Props } from "../../common/props";
import { Card } from "../card/card";
import { Collapse, CollapseProps } from "../collapse/collapse";
import { H6 } from "../html/html";
import { Icon } from "../icon/icon";

export interface SectionProps extends Props, Omit<HTMLDivProps, "title">, React.RefAttributes<HTMLDivElement> {
/**
* Whether this section's contents should be collapsible.
*
* @default false
*/
collapsible?: boolean;

/**
* Subset of props to forward to the underlying {@link Collapse} component, with the addition of a
* `defaultIsOpen` option which sets the default open state of the component.
*
* This prop has no effect if `collapsible={false}`.
*/
collapseProps?: Pick<CollapseProps, "className" | "keepChildrenMounted" | "transitionDuration"> & {
defaultIsOpen?: boolean;
};

/**
* Whether this section should use compact styles.
*
* @default false
*/
compact?: boolean;

/**
* Name of a Blueprint UI icon (or an icon element) to render in the section's header.
* Note that the header will only be rendered if `title` is provided.
*/
icon?: IconName | MaybeElement;

/**
* Element to render on the right side of the section header.
*/
rightElement?: JSX.Element;

/**
* Sub-title of the section.
* Note that the header will only be rendered if `title` is provided.
*/
subtitle?: JSX.Element | string;

/**
* Title of the section.
* Note that the header will only be rendered if `title` is provided.
*/
title?: JSX.Element | string;
}

/**
* Section component.
*
* @see https://blueprintjs.com/docs/#core/components/section
*/
export const Section: React.FC<SectionProps> = React.forwardRef((props, ref) => {
const {
children,
className,
collapseProps,
collapsible,
compact,
icon,
rightElement,
subtitle,
title,
...cardProps
} = props;
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(collapseProps?.defaultIsOpen ?? false);
const toggleIsCollapsed = React.useCallback(() => setIsCollapsed(!isCollapsed), [isCollapsed]);

const isHeaderLeftContainerVisible = title != null || icon != null || subtitle != null;
const isHeaderRightContainerVisible = rightElement != null || collapsible;

return (
<Card
elevation={Elevation.ZERO}
className={classNames(className, Classes.SECTION, {
[Classes.COMPACT]: compact,
[Classes.SECTION_COLLAPSED]: collapsible && isCollapsed,
})}
ref={ref}
{...cardProps}
>
<div
role={collapsible ? "button" : undefined}
aria-pressed={collapsible ? isCollapsed : undefined}
className={classNames(Classes.SECTION_HEADER, {
[Classes.INTERACTIVE]: collapsible,
})}
onClick={collapsible != null ? toggleIsCollapsed : undefined}
>
{isHeaderLeftContainerVisible && (
<>
<div className={Classes.SECTION_HEADER_LEFT}>
{title && icon && (
<Icon icon={icon} aria-hidden={true} tabIndex={-1} className={Classes.TEXT_MUTED} />
)}

<div>
{title && <H6 className={Classes.SECTION_HEADER_TITLE}>{title}</H6>}
{title && subtitle && (
<div className={classNames(Classes.TEXT_MUTED, Classes.SECTION_HEADER_SUB_TITLE)}>
{subtitle}
</div>
)}
</div>
</div>
</>
)}

{isHeaderRightContainerVisible && (
<div className={Classes.SECTION_HEADER_RIGHT}>
{rightElement}
{collapsible &&
(isCollapsed ? (
<ChevronDown className={Classes.TEXT_MUTED} />
) : (
<ChevronUp className={Classes.TEXT_MUTED} />
))}
</div>
)}
</div>

{collapsible ? (
<Collapse {...collapseProps} isOpen={!isCollapsed}>
{children}
</Collapse>
) : (
children
)}
</Card>
);
});
Section.defaultProps = {
compact: false,
};
Section.displayName = `${DISPLAYNAME_PREFIX}.Section`;
Loading