0.3.1 • Published 4 years ago

react-expansion v0.3.1

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

react-expansion

Fully customizable and reusable expand/collapse component

NPM JavaScript Style Guide

What is this

react-expansion sees the expand/collapse system as the composition of multiple bricks. the picture bellow resumes it all.

react-expansion

Picture generated with excalidraw.

Install

npm install --save react-expansion

or

yarn add react-expansion

Demo

This ongoing codeSandbox will contain reusable examples

Props and Usage hints

To use react-expansion, you need to define a few components on your own because we prefer to stay agnostic to the UI.

These components will receive the following props:

Propvalue
expandeda boolean that indicates whether the component is expanded/collapsed
toggleExpansionif controlled, will toggle the expansion state
positionwill be spread to IndicatorComponent, LabelComponent and ActionsComponent, and indicates respectively the position of the nested component

The ExpandCollapse component props are:

PropPropTypeDefault valueUsage
expandedboolundefinedIf present, the expand collapse becomes controlled by this prop
initialValueboolfalseIf not controlled, this is the initial expansion state when the component is mounted
labelPositiononeOf(['start', 'end'])startIndicates the position of the label component and/or label children
actionsPositiononeOf(['start', 'end'])endIndicates the position of the action component and/or the actions children
indicatorPositiononeOf(['start', 'end'])endIndicates the position of the indicator component
ComponentString or funcReact.FragmentIs the component in which the Expansion header and content will be wrapped into
ComponentPropsobject{}The props of the top level wrapper component
ExpansionComponentString or funcundefinedThe component to be mounted to wrap the Expansion Header (that contains the indicator, label, actions, divider)
ExpansionPropsobject{}The props to be passed to the ExpansionComponent plus the automatic props described the in the previous table
IndicatorComponentString or funcundefinedThe component that is supposed to indicate the expansion state and toggles it
IndicatorPropsobject{}The props to be passed to the IndicatorComponent plus the automatic props described the in the previous table
LabelComponentString or funcundefinedThe component in which the label prop is wrapped
LabelPropsobject{}The props to be passed to the LabelComponent plus the automatic props described the in the previous table
ActionsComponentString or funcundefinedThe component in which the actions prop is wrapped
ActionsPropsobject{}The props to be passed to the ActionComponent plus the automatic props described the in the previous table
DividerComponentString or funcundefinedThe component that will be mounted as a divider (may be an hr or any custom object (even a form))
DividerPropsobject{}The props to be passed to the DividerComponent plus the automatic props described the in the previous table
contentContainerPropsobject{}The props to be passed to the div containing the content, these props are only passed if the component is expaned
actionsanyundefinedThe actions children to be mounted in the actions area
childrenanyundefinedWhat will be hidden/visible depending on the expansion state
labelanyundefinedThe label to display in its position
keepMountedboolundefinedIf true, the content will stay into the dom, but the div containing it will have a display: none style
onExpandChangefuncundefinedwill be called with the next expansion state whenever the expand collapse changed
contentDisplaystringundefinedThe display style property's value that will be given the content container when visible

You can also imperatively handle the expansion state if not controlled. The useImperativeHandle hook exposes the following functions:

  • expand
  • collapse
  • toggleExpansion

and can be used like this:

const myRef = React.useRef()
function expand() {
  myRef.current.expand()
}
function collapse() {
  myRef.current.collapse()
}
function toggle() {
  myRef.current.toggleExpansion()
}
// later
<SomeButton onClick={toggle}>Toggle</SomeButton>
<SomeButton onClick={expand}>Expand</SomeButton>
<SomeButton onClick={collapse}>Collapse</SomeButton>
<ExpandCollapse ref={myRef} {...}>Some tags and children</ExpandCollapse>

Limitations and roadmap

  • The ExpandCollapse component wraps the children in a div so its display style is managed and thus we can offer the keepMounted ability
  • There is no support for animations, but we hope it will be released in the v2 (instead, you can pass the needed css using className or style via the contentContainerProps)

Usage examples

There is a lot of examples that can be made with this ExpandCollapse.

Let's create a navigation file system that will display a files tree (folders & files).

I will be using Material-ui to illustrate my examples for faster dev.

First: the IndicatorComponent

const useIndicatorStyles = makeStyles({
  iconButton: {
    border: `1px solid`,
    padding: '0px',
  },
  icon: {
    width: '12px',
    height: '12px'
  }
});
function FileSystemIndicator({ expanded, toggleExpansion, variant = 'file'}) {
  const classes = useIndicatorStyles()
  if (variant === 'file') {
    return null; // no indicator when it's a file
  }
  return (
    <IconButton color="primary"  className={classes.iconButton} onClick={toggleExpansion} size="small">
      <SvgIcon className={classes.icon}>
        {expanded ? <ArrowDown /> : <ArrowRight />}
      </SvgIcon>
    </IconButton>
  )
}

Then: the LabelComponent

import File from '@material-ui/icons/InsertDriveFile'
import FolderOpen from '@material-ui/icons/FolderOpen'
import FolderClosed from '@material-ui/icons/Folder'

const useStyles = makeStyles({
  root: {
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    marginLeft: 4
  },
  icon: {
    width: '12px',
    height: '12px',
    marginRight: 4
  }
})
function FileSystemLabel({filename, fileUrl, variant = 'file', expanded, toggleExpansion}) {
  const classes = useStyles()
  function onClick() {
    if (fileUrl) {
      alert(`We can load a file content here from it's url ${fileUrl}`)
    } else {
      toggleExpansion()
    }
  }
  return (
    <Typography onClick={onClick} color="primary" component="div" className={classes.root}>
      <SvgIcon className={classes.icon}>
        {variant === 'folder' && expanded && <FolderOpen/>}
        {variant === 'folder' && !expanded && <FolderClosed/>}
        {variant === 'file' && <File/>}
      </SvgIcon>
      <Typography color="primary" component="span">
        {filename}
      </Typography>
    </Typography>
  )
}

Then: the DividerComponent which will be nothing:

function Nothing() {
  return null;
}

And then, the FileUnit that returns our ExpandCollapse:

function FileUnit({children, filename, variant, fileUrl, ...rest}) {
  return (
    <ExpandCollapse
      keepMounted
      Component="div"
      indicatorPosition='start'
      ExpansionComponent='div'
      ExpansionProps={{style: {display: 'flex', alignItems: 'center'}}}
      LabelComponent={FileSystemLabel}
      LabelProps={{filename, variant, fileUrl}}
      label={filename}
      IndicatorComponent={FileSystemIndicator}
      IndicatorProps={{variant}}
      contentContainerProps={{
        style: {
          marginLeft: 24
        }
      }}
      {...rest}
    >
      {children}
    </ExpandCollapse>
  )
}

The resulting ExpandCollapse with a minimal example will give this output

file system example

We can also create a large variant of expand collapse and combine them. Here is a picture, I am still looking to create multiple other examples in a demo package.

file system example

License

MIT © incepter