Skip to content

Commit

Permalink
Add original react-collapsed (#140)
Browse files Browse the repository at this point in the history
Co-authored-by: Rogin Farrer <rfarrer@wayfair.com>
  • Loading branch information
roginfarrer and Rogin Farrer committed Mar 29, 2023
1 parent 15af3d5 commit 5e427ec
Show file tree
Hide file tree
Showing 45 changed files with 2,282 additions and 51 deletions.
38 changes: 38 additions & 0 deletions .changeset/slimy-walls-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'react-collapsed': major
---

This is a big refactor of `react-collapsed`, enough I wanted to denote it with a new major version.

## BREAKING CHANGES

- `expandStyles` and `collapseStyles` options have been removed.
- `onExpandStart`, `onExpandEnd`, `onCollapseStart`, `onCollapseEnd` options have been removed and replaced with `onTransitionStateChange`:

```typescript
const useCollapse({
onTransitionStateChange(stage) {
switch (stage) {
case 'expandStart':
case 'expandEnd':
case 'expanding':
case 'collapseStart':
case 'collapseEnd':
case 'collapsing':
// do thing
default:
break;
}
}
})
```

## Other changes

- Unique IDs for accessibility are now generated with `React.useId` if it's available.
- Styles assigned to the collapse element are now assigned to the DOM element directly via a ref, and no longer require `ReactDOM.flushSync` to update styles in transition.
- Added `role="region"` to collapse.
- Added logic to handle disabling the animation if the user has the prefers reduced motion setting enabled.
- Replaced animation resolution handling to a programmatic timer, instead of the `'transitionend'` event. Should fix #103.
- Improved the types for `getCollapseProps` and `getToggleProps`, so their arguments and return type is more accurately typed. This should help catch cases when props returned by the getters are duplicated on the component (such as `ref` or `style`).
- Changes the props returned by `getToggleProps` depending on the HTML tag of the component.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
registry = https://registry.npmjs.org
public-hoist-pattern[]=*storybook*
19 changes: 0 additions & 19 deletions README.md

This file was deleted.

1 change: 1 addition & 0 deletions README.md
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
"@changesets/cli": "^2.25.2",
"@collapsed-internal/tsconfig": "workspace:*",
"@types/node": "^16.7.13",
"buffer": "^5.5.0",
"eslint": "^7.32.0",
"eslint-config-collapsed": "workspace:*",
"np": "^6.4.0",
"prettier": "^2.3.2",
"process": "^0.11.10",
"turbo": "^1.6.3",
"typescript": "^4.9",
"buffer": "^5.5.0",
"process": "^0.11.10"
"typescript": "^4.9"
},
"prettier": "eslint-config-rogin/prettier",
"alias": {
Expand Down
6 changes: 6 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# NOTE

You're probably looking for [react-collapsed](../react-collapsed). This package (alongside [@collapsed/react](../react)) is a WIP rewrite to create a Vanilla JS core.

---

## @collapsed/core

[![CI][ci-badge]][ci]
Expand Down
2 changes: 2 additions & 0 deletions packages/react-collapsed/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
3 changes: 3 additions & 0 deletions packages/react-collapsed/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['collapsed', 'collapsed/react'],
}
11 changes: 11 additions & 0 deletions packages/react-collapsed/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-a11y',
],
core: {
builder: '@storybook/builder-vite',
},
}
9 changes: 9 additions & 0 deletions packages/react-collapsed/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
47 changes: 47 additions & 0 deletions packages/react-collapsed/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Changelog has been moved to [the releases tab](https://github.com/roginfarrer/react-collapsed/releases).

# 2.0.0

Complete rewrite using React hooks!

- Ends support for React versions < 16.8.x
- Library now exports a custom hook in lieu of a render prop component
- Adds support for unmounting the contents of the Collapse element when closed

```js
import React from 'react'
import useCollapse from 'react-collapsed'

function Demo() {
const { getCollapseProps, getToggleProps, isOpen } = useCollapse()

return (
<>
<button {...getToggleProps()}>{isOpen ? 'Collapse' : 'Expand'}</button>
<section {...getCollapseProps()}>Collapsed content 🙈</section>
</>
)
}
```

# 1.0.0

Bumped to full release! :)

- `duration`, `easing`, and `delay` now support taking an object with `in` and `out` keys to configure differing in-and-out transitions

# 0.2.0

### Breaking Changes

- `getCollapsibleProps` => `getCollapseProps`. Renamed since it's easier to spell 😅

### Other

- Slew of Flow bug fixes
- Improved documentation

# 0.1.3

- ESLINT wasn't working properly - fixed this
- Added `files` key to package.json to improve NPM load
150 changes: 150 additions & 0 deletions packages/react-collapsed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# 🙈 react-collapsed (useCollapse)

[![CI][ci-badge]][ci]
![npm bundle size (version)][minzipped-badge]
[![npm version][npm-badge]][npm-version]
[![Netlify Status](https://api.netlify.com/api/v1/badges/5a5b0e80-d15e-4983-976d-37fe6bdada7a/deploy-status)](https://app.netlify.com/sites/react-collapsed/deploys)

A React hook for creating accessible expand/collapse components. Animates the height using CSS transitions from `0` to `auto`.

## Features

- Handles the height of animations of your elements, `auto` included!
- You control the UI - `useCollapse` provides the necessary props, you control the styles and the elements.
- Accessible out of the box - no need to worry if your collapse/expand component is accessible, since this takes care of it for you!
- No animation framework required! Simply powered by CSS animations
- Written in TypeScript

## Demo

[See the demo site!](https://react-collapsed.netlify.app/)

[CodeSandbox demo](https://codesandbox.io/s/magical-browser-vibv2?file=/src/App.tsx)

## Installation

```bash
$ npm i react-collapsed
```

## Usage

### Simple Usage

```js
import React from 'react'
import { useCollapse } from 'react-collapsed'

function Demo() {
const { getCollapseProps, getToggleProps, isExpanded } = useCollapse()

return (
<div>
<button {...getToggleProps()}>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
<section {...getCollapseProps()}>Collapsed content 🙈</section>
</div>
)
}
```

### Control it yourself

```js
import React, { useState } from 'react'
import { useCollapse } from 'react-collapsed'

function Demo() {
const [isExpanded, setExpanded] = useState(false)
const { getCollapseProps, getToggleProps } = useCollapse({ isExpanded })

return (
<div>
<button
{...getToggleProps({
onClick: () => setExpanded((prevExpanded) => !prevExpanded),
})}
>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
<section {...getCollapseProps()}>Collapsed content 🙈</section>
</div>
)
}
```

## API

```js
const { getCollapseProps, getToggleProps, isExpanded, setExpanded } =
useCollapse({
isExpanded: boolean,
defaultExpanded: boolean,
collapsedHeight: 0,
easing: string,
duration: number,
onTransitionStateChange: func,
})
```

### `useCollapse` Config

The following are optional properties passed into `useCollapse({ })`:

| Prop | Type | Default | Description |
| ----------------------- | -------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| isExpanded | boolean | `undefined` | If true, the Collapse is expanded |
| defaultExpanded | boolean | `false` | If true, the Collapse will be expanded when mounted |
| collapsedHeight | number | `0` | The height of the content when collapsed |
| easing | string | `cubic-bezier(0.4, 0, 0.2, 1)` | The transition timing function for the animation |
| duration | number | `undefined` | The duration of the animation in milliseconds. By default, the duration is programmatically calculated based on the height of the collapsed element |
| onTransitionStateChange | function | no-op | Handler called with at each stage of the transition animation |
| hasDisabledAnimation | boolean | false | If true, will disable the animation |

### What you get

| Name | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------- |
| getCollapseProps | Function that returns a prop object, which should be spread onto the collapse element |
| getToggleProps | Function that returns a prop object, which should be spread onto an element that toggles the collapse panel |
| isExpanded | Whether or not the collapse is expanded (if not controlled) |
| setExpanded | Sets the hook's internal isExpanded state |

## Alternative Solutions

- [react-spring](https://www.react-spring.io/) - JavaScript animation based library that can potentially have smoother animations. Requires a bit more work to create an accessible collapse component.
- [react-animate-height](https://github.com/Stanko/react-animate-height/) - Another library that uses CSS transitions to animate to any height. It provides components, not a hook.

## FAQ

<details>
<summary>When I apply vertical <code>padding</code> to the component that gets <code>getCollapseProps</code>, the animation is janky and it doesn't collapse all the way. What gives?</summary>

The collapse works by manipulating the `height` property. If an element has vertical padding, that padding expandes the size of the element, even if it has `height: 0; overflow: hidden`.

To avoid this, simply move that padding from the element to an element directly nested within in.

```javascript
// from
<div {...getCollapseProps({style: {padding: 20}})}
This will do weird things
</div>

// to
<div {...getCollapseProps()}
<div style={{padding: 20}}>
Much better!
</div>
</div>
```

</details>

[minzipped-badge]: https://img.shields.io/bundlephobia/minzip/react-collapsed/latest
[npm-badge]: http://img.shields.io/npm/v/react-collapsed.svg?style=flat
[npm-version]: https://npmjs.org/package/react-collapsed 'View this project on npm'
[ci-badge]: https://github.com/roginfarrer/collapsed/workflows/CI/badge.svg
[ci]: https://github.com/roginfarrer/collapsed/actions?query=workflow%3ACI+branch%3Amain
[netlify]: https://app.netlify.com/sites/react-collapsed/deploys
[netlify-badge]: https://api.netlify.com/api/v1/badges/4d285ffc-aa4f-4d32-8549-eb58e00dd2d1/deploy-status
12 changes: 12 additions & 0 deletions packages/react-collapsed/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'cypress'

export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
video: false,
screenshotOnRunFailure: false,
})
50 changes: 50 additions & 0 deletions packages/react-collapsed/cypress/component/Controlled.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react'
import { useCollapse } from '../../src'

const Collapse = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<'div'>
>(function Collapse(props, ref) {
return (
<div {...props} ref={ref} data-testid="collapse">
<div
style={{
height: 300,
border: '2px solid red',
backgroundColor: 'lightblue',
}}
>
helloooo
</div>
</div>
)
})

const Controlled = () => {
const [isExpanded, setOpen] = React.useState<boolean>(true)
const { getToggleProps, getCollapseProps } = useCollapse({
isExpanded,
})

return (
<div>
<button {...getToggleProps({ onClick: () => setOpen((x) => !x) })}>
{isExpanded ? 'Close' : 'Open'}
</button>
<Collapse {...getCollapseProps()} />
</div>
)
}

describe('Controlled', () => {
it('playground', () => {
cy.mount(<Controlled />)

// getToggleProps
cy.get('button').should('have.text', 'Close')
cy.get('[data-testid="collapse"]').should('be.visible')
cy.get('button').click()
cy.get('button').should('have.text', 'Open')
cy.get('[data-testid="collapse"]').should('not.be.visible')
})
})
Loading

0 comments on commit 5e427ec

Please sign in to comment.