Skip to content

Commit

Permalink
STCOM-454: <PaneHeaderIconButton> (#866)
Browse files Browse the repository at this point in the history
* Added PaneHeaderIconButton component

* Replaced IconButton with PaneHeaderIconButton in components/examples

* Added readme for PaneHeaderIconButton component

* Added storybook example for PaneHeaderIconButton

* Updated PaneHeaderIconButton example, fixed linting errors and added export in lib/index.js

* Added an 'innerClassName'-prop for IconButton which enables styling of the inner element. This is needed in the <PaneHeaderIconButton>-component

* Updated PaneHeaderIconButton example and added some classes to the component

* Replaced <IconButton> with <PaneHeaderIconButton> in <PaneHeader>

* Updated styles for PaneHeaderIconButton

* Updated PaneHeader to make sure that there's always a PaneMenu present – even if none is passed. Also made sure that the button areas always has a class name

* Now passing ref using forwardRef and made some css targeted PaneHeaderIconButton's inside of <PaneMenu>'s to ensure correct positioning without changing the horizontal padding of the <PaneHeader>

* Fixed linting errors

* Fixed some css selection specificity by moving adjustment styles into PaneHeader.css instead. Removed min-width and replace it with some horizontal padding instead to save space

* Updated some readme's to reference PaneHeaderIconButton instead of IconButton

* Minor fix for the PaneCloseLink-component that renders two PaneHeaderIconButton's which messes with the negative margin when targeting :first-child.

* Made sure that button areas inside paneHeader's are always on top by setting a z-index. Also fixed some logic within PaneHeader by setting up refs with createRef()

* Minor style update

* Minor change to make sonarcloud succeed
  • Loading branch information
rasmuswoelk authored Mar 5, 2019
1 parent 882c23c commit 033f02b
Show file tree
Hide file tree
Showing 19 changed files with 204 additions and 52 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export { default as List } from './lib/List';

/* layout containers */
export { default as Pane } from './lib/Pane';
export { default as PaneHeaderIconButton } from './lib/PaneHeaderIconButton';
export { default as PaneBackLink } from './lib/PaneBackLink';
export { default as PaneCloseLink } from './lib/PaneCloseLink';
export { default as PaneHeader } from './lib/PaneHeader';
Expand Down
1 change: 1 addition & 0 deletions lib/AppIcon/AppIcon.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.appIcon {
display: inline-flex;
align-items: center;
flex-shrink: 0;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion lib/IconButton/IconButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const IconButton = React.forwardRef(({
className,
badgeCount,
iconClassName,
innerClassName,
tabIndex,
badgeColor,
href,
Expand Down Expand Up @@ -70,7 +71,7 @@ const IconButton = React.forwardRef(({

return (
<Element {...buttonProps}>
<span className={classNames(css.iconButtonInner, css[`${size}Offset`])} {...rest}>
<span className={classNames(css.iconButtonInner, css[`${size}Offset`], innerClassName)} {...rest}>
<Icon icon={icon} size={iconSize} iconRootClass={css.icon} iconClassName={iconClassName} />
{ badgeCount !== undefined && <Badge size="medium" color={badgeColor}>{badgeCount}</Badge> }
</span>
Expand All @@ -95,6 +96,7 @@ IconButton.propTypes = {
'medium',
]),
id: PropTypes.string,
innerClassName: PropTypes.string,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
size: PropTypes.oneOf([
Expand Down
1 change: 1 addition & 0 deletions lib/IconButton/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ href | string | Turns the button into a link (instead of using an onClick handle
to | string or object | accepts `to` prop similar to `<Link>` from `react-router` [Details for `<Link>`](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/Link.md).
badgeCount | string / number | Display a small number badge on the IconButton
id | string | Adds an id attribute to the button
innerClassName | string | Apply a custom class name to the inner element of the component
ariaLabel | string | Adds an aria label to the button
autoFocus | bool | If this prop is `true`, component will automatically focus on mount | |
4 changes: 4 additions & 0 deletions lib/Pane/stories/Pane.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ import PaneBackLinkBasicUsage from '../../PaneBackLink/stories/BasicUsage';
import PaneCloseLinkReadme from '../../PaneCloseLink/readme.md';
import PaneCloseLinkBasicUsage from '../../PaneCloseLink/stories/BasicUsage';

import PaneHeaderIconButtonReadme from '../../PaneHeaderIconButton/readme.md';
import PaneHeaderIconButtonBasicUsage from '../../PaneHeaderIconButton/stories/BasicUsage';

import PaneMenuReadme from '../../PaneMenu/readme.md';

storiesOf('Pane', module)
.add('Basic Usage', withReadme(readme, () => <BasicUsage />))
.add('PaneHeader', withReadme(PaneHeaderReadme, () => <PaneHeaderBasicUsage />))
.add('PaneMenu', withReadme(PaneMenuReadme, () => <PaneHeaderBasicUsage />))
.add('PaneHeaderIconButton', withReadme(PaneHeaderIconButtonReadme, () => <PaneHeaderIconButtonBasicUsage />))
.add('PaneBackLink', withReadme(PaneBackLinkReadme, () => <PaneBackLinkBasicUsage />))
.add('PaneCloseLink', withReadme(PaneCloseLinkReadme, () => <PaneCloseLinkBasicUsage />));
4 changes: 2 additions & 2 deletions lib/PaneBackLink/PaneBackLink.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import IconButton from '../IconButton';
import PaneHeaderIconButton from '../PaneHeaderIconButton';
import css from './PaneBackLink.css';

export default function PaneBackLink(props) {
return (
<IconButton
<PaneHeaderIconButton
className={css.paneBackLink}
icon="arrow-left"
{...props}
Expand Down
4 changes: 2 additions & 2 deletions lib/PaneBackLink/readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PaneBackLink

Renders an `<IconButton>` with icon `arrow-left`. Above the `medium` breakpoint, it hides. This component is appropriate for instances of `<Pane>` that do not need to be closeable on larger screens. Example: the `<Pane>` listing settings for a module.
Renders an `<PaneHeaderIconButton>` with icon `arrow-left`. Above the `medium` breakpoint, it hides. This component is appropriate for instances of `<Pane>` that do not need to be closeable on larger screens. Example: the `<Pane>` listing settings for a module.

## Usage
Use with the `firstMenu` prop of a `<Pane>` or `<PaneHeader>`:
Expand All @@ -11,4 +11,4 @@ Use with the `firstMenu` prop of a `<Pane>` or `<PaneHeader>`:
</Pane>
```

`<PaneBackLink>` passes along all received props to its child `<IconButton>`.
`<PaneBackLink>` passes along all received props to its child `<PaneHeaderIconButton>`.
5 changes: 5 additions & 0 deletions lib/PaneCloseLink/PaneCloseLink.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@import '../variables.css';

.paneCloseLinkArrow,
.paneCloseLinkX {
margin: 0 !important;
}

.paneCloseLink {
display: inline-flex;
}
Expand Down
10 changes: 6 additions & 4 deletions lib/PaneCloseLink/PaneCloseLink.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from 'react';
import IconButton from '../IconButton';
import classnames from 'classnames';
import PaneHeaderIconButton from '../PaneHeaderIconButton';
import css from './PaneCloseLink.css';
import paneHeaderCss from '../PaneHeader/PaneHeader.css';

export default function PaneCloseLink(props) {
return (
<div className={css.paneCloseLink}>
<IconButton
<div className={classnames(css.paneCloseLink, paneHeaderCss.paneHeaderIconButton)}>
<PaneHeaderIconButton
className={css.paneCloseLinkArrow}
icon="arrow-left"
{...props}
/>
<IconButton
<PaneHeaderIconButton
className={css.paneCloseLinkX}
icon="times"
{...props}
Expand Down
4 changes: 2 additions & 2 deletions lib/PaneCloseLink/readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PaneCloseLink

Renders an `<IconButton>` with icon `arrow-left`. Above the `medium` breakpoint, it instead displays the `times` icon. This component is appropriate for instances of `<Pane>` that should appear closeable on larger screens. Example: the detail `<Pane>` in a search layout.
Renders an `<PaneHeaderIconButton>` with icon `arrow-left`. Above the `medium` breakpoint, it instead displays the `times` icon. This component is appropriate for instances of `<Pane>` that should appear closeable on larger screens. Example: the detail `<Pane>` in a search layout.

## Usage
Use with the `firstMenu` prop of a `<Pane>` or `<PaneHeader>`:
Expand All @@ -11,4 +11,4 @@ Use with the `firstMenu` prop of a `<Pane>` or `<PaneHeader>`:
</Pane>
```

`<PaneCloseLink>` passes along all received props to its child `<IconButton>`.
`<PaneCloseLink>` passes along all received props to its child `<PaneHeaderIconButton>`.
37 changes: 29 additions & 8 deletions lib/PaneHeader/PaneHeader.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,17 @@
line-height: 1.2;
}

/* Last content area */
.paneContentLastArea {
margin: 0 0 0 auto;
/* Pane button areas */
.paneHeaderButtonsArea {
position: relative;
z-index: 1;
}

/* Control close icon position with order. Useful for when changing dir to rtl */
& .paneHeaderCloseIcon {
order: 99;
}
.paneHeaderButtonsArea.last {
margin: 0 0 0 auto;
}

[dir="rtl"] .paneContentLastArea {
[dir="rtl"] .paneHeaderButtonsArea.last {
margin: 0 auto 0 0;
}

Expand All @@ -110,3 +110,24 @@
padding: 2px 5px;
border-radius: var(--radius);
}

/* stylelint-disable no-descending-specificity */

/* Reset IconButton margin */
.paneHeader .paneHeaderIconButton {
margin: 0;
}

/* Pull first pane icon button to the left/start */
.paneHeaderButtonsArea.first .paneHeaderIconButton:first-child,
[dir=rtl] .paneHeaderButtonsArea.last .paneHeaderIconButton:last-child {
margin-left: calc(var(--gutter-static-two-thirds) * -1);
margin-right: 0;
}

/* Pull last pane icon button to the right/end */
.paneHeaderButtonsArea.last .paneHeaderIconButton:last-child,
[dir="rtl"] .paneHeaderButtonsArea.first .paneHeaderIconButton:first-child {
margin-right: calc(var(--gutter-static-two-thirds) * -1);
margin-left: 0;
}
52 changes: 25 additions & 27 deletions lib/PaneHeader/PaneHeader.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classnames from 'classnames';
import has from 'lodash/has';

import css from './PaneHeader.css';
import PaneHeaderIconButton from '../PaneHeaderIconButton';
import AppIcon from '../AppIcon';
import IconButton from '../IconButton';
import PaneMenu from '../PaneMenu';
import { Dropdown } from '../Dropdown';
import DropdownMenu from '../DropdownMenu';
import Icon from '../Icon';

import css from './PaneHeader.css';

class PaneHeader extends Component {
static propTypes = {
actionMenu: PropTypes.func,
Expand All @@ -37,6 +37,9 @@ class PaneHeader extends Component {
};

this._id = `paneHeader${this.props.id}`;

this.firstPaneButtonsArea = React.createRef();
this.lastPaneButtonsArea = React.createRef();
}

componentDidMount() {
Expand All @@ -49,10 +52,9 @@ class PaneHeader extends Component {
* is always centered in the header - no matter what content there is to the left and right.
* We add margin so that the centered content won't overflow the menu areas.
*/
setCenteredMargin = () => {
const { firstAreaElement, lastAreaElement } = this;
const firstWidth = firstAreaElement ? firstAreaElement.offsetWidth : 0;
const lastWidth = lastAreaElement ? lastAreaElement.offsetWidth : 0;
setCenteredMargin() {
const firstWidth = this.firstPaneButtonsArea.current ? this.firstPaneButtonsArea.current.offsetWidth + 10 : 0;
const lastWidth = this.lastPaneButtonsArea.current ? this.lastPaneButtonsArea.current.offsetWidth + 10 : 0;

if (firstWidth || lastWidth) {
this.setState({
Expand All @@ -75,7 +77,7 @@ class PaneHeader extends Component {
return (
<FormattedMessage id="stripes-components.closeItem" values={{ item: description }}>
{ariaLabel => (
<IconButton
<PaneHeaderIconButton
key="close-pane"
icon="times"
onClick={onClose}
Expand Down Expand Up @@ -231,35 +233,31 @@ class PaneHeader extends Component {
/**
* Get content area default
*/
getContentArea(placement, menuElement, className, hasDismissibleIcon) {
getContentArea(placement, menuElement, hasDismissibleIcon) {
const { getDismissibleButton } = this;
const classes = classNames(className);

/* Default content to provided menuElement */
let content = menuElement;

/* Don't add the first content area if there is nothing to show */
// Don't add the first content area if there is nothing to show
if (!hasDismissibleIcon && !menuElement) {
return false;
}

/* If we have a dismissible icon but no menuElement */
if (hasDismissibleIcon && !menuElement) {
content = getDismissibleButton();
}
// Default content to provided menuElement or empty <PaneMenu> if none is provided
let content = menuElement || <PaneMenu />;

/* If a dismissible button is required
and we have a menu element we merge that with the menu content */
if (hasDismissibleIcon && menuElement) {
// If a dismissible buton is activated we merge this into the menuElement
if (hasDismissibleIcon) {
content = React.cloneElement(
menuElement,
content,
{},
[getDismissibleButton()].concat(React.Children.toArray(menuElement.props.children))
[getDismissibleButton()].concat(React.Children.toArray(content.props.children))
);
}

return (
<div className={classes} ref={(el) => { this[`${placement}AreaElement`] = el; }}>
<div
className={classnames(css.paneHeaderButtonsArea, css[placement])}
ref={this[`${placement}PaneButtonsArea`]}
>
{ content }
</div>
);
Expand All @@ -271,7 +269,7 @@ class PaneHeader extends Component {
getFirstContentArea = () => {
const { firstMenu, dismissible } = this.props;
const hasDismissibleIcon = dismissible === true || dismissible === 'first';
return this.getContentArea('first', firstMenu, css.paneContentFirstArea, hasDismissibleIcon);
return this.getContentArea('first', firstMenu, hasDismissibleIcon);
}

/**
Expand All @@ -280,7 +278,7 @@ class PaneHeader extends Component {
getLastContentArea = () => {
const { lastMenu, dismissible } = this.props;
const hasDismissibleIcon = dismissible === 'last';
return this.getContentArea('last', lastMenu, css.paneContentLastArea, hasDismissibleIcon);
return this.getContentArea('last', lastMenu, hasDismissibleIcon);
}

render() {
Expand Down
8 changes: 4 additions & 4 deletions lib/PaneHeader/stories/BasicUsage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, { Fragment } from 'react';
import Paneset from '../../Paneset';
import PaneMenu from '../../PaneMenu';
import Pane from '../../Pane/Pane';
import IconButton from '../../IconButton';
import PaneHeaderIconButton from '../../PaneHeaderIconButton';
import Button from '../../Button';
import MenuSection from '../../MenuSection';
import RadioButton from '../../RadioButton';
Expand All @@ -14,14 +14,14 @@ import Icon from '../../Icon';

const firstMenu = (
<PaneMenu>
<IconButton key="icon-search" icon="search" />
<PaneHeaderIconButton key="icon-search" icon="search" />
</PaneMenu>
);

const lastMenu = (
<PaneMenu>
<IconButton key="icon-comment" icon="comment" />
<IconButton key="icon-edit" icon="edit" />
<PaneHeaderIconButton key="icon-comment" icon="comment" />
<PaneHeaderIconButton key="icon-edit" icon="edit" />
</PaneMenu>
);

Expand Down
20 changes: 20 additions & 0 deletions lib/PaneHeaderIconButton/PaneHeaderIconButton.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* PaneHeaderIconButton
*/

@import "../variables";

.PaneHeaderIconButton {
/* min-width: var(--control-min-size-touch); */
padding: 0 var(--gutter-static-one-third);
min-height: var(--control-min-size-touch);
}

.PaneHeaderIconButton__inner {
padding: 0;
min-width: var(--control-min-size-desktop);
min-height: var(--control-min-size-desktop);
justify-content: center;
align-items: center;
composes: boxOffsetSmall from "../sharedStyles/interactionStyles.css";
}
20 changes: 20 additions & 0 deletions lib/PaneHeaderIconButton/PaneHeaderIconButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* PaneHeaderIconButton
*/

import React from 'react';
import classnames from 'classnames';
import IconButton from '../IconButton';
import css from './PaneHeaderIconButton.css';
import paneHeaderCss from '../PaneHeader/PaneHeader.css';

const PaneHeaderIconButton = React.forwardRef(({ className, innerClassName, ...rest }, ref) => (
<IconButton
ref={ref}
className={classnames(className, css.PaneHeaderIconButton, paneHeaderCss.paneHeaderIconButton)}
innerClassName={classnames(css.PaneHeaderIconButton__inner, innerClassName)}
{...rest}
/>
));

export default PaneHeaderIconButton;
1 change: 1 addition & 0 deletions lib/PaneHeaderIconButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PaneHeaderIconButton';
Loading

0 comments on commit 033f02b

Please sign in to comment.