Skip to content

Commit

Permalink
[core] feat: Section component (#6245)
Browse files Browse the repository at this point in the history
Co-authored-by: Adi Dahiya <adahiya@palantir.com>
  • Loading branch information
CPerinet and adidahiya authored Jul 11, 2023
1 parent 5214dac commit 253ad84
Show file tree
Hide file tree
Showing 14 changed files with 542 additions and 4 deletions.
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`;
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

1 comment on commit 253ad84

@adidahiya
Copy link
Contributor

Choose a reason for hiding this comment

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

[core] feat: Section component (#6245)

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

Please sign in to comment.