Skip to content

Commit

Permalink
feat: add render features + render strategy (#106)
Browse files Browse the repository at this point in the history
* add unmount strategy to README (React)

* add unmount strategy to README (Vue)

* add different render features (React)

* use render features in Menu and Listbox (React)

* add different render features (Vue)

* use render features in Menu and Listbox (Vue)

* bump dependencies

* add ability to change the ref property using `refName`

Example use case:

```tsx
// Some components have this API with an `innerRef`. The suggested approach is to use
// `React.forwardRef` so that you get the actual `ref` value. However if you already have this
// `innerRef` API than we can use the `refName="innerRef"` to give the `ref` prop a good name. It
// defaults to `ref` so that it still works everywhere else.

function MyButton({ innerRef, ...props }) {
  return <button ref={innerRef} {...props} />
}

<Menu.Button as={MyButton} refName="innerRef" />
```

* small cleanup, move refs to props we control

* add tests for the render abstraction (Render)

+ use the unique __ symbol as a default value in the Props type for the
  omitable props.

* use render features in Transition (React)

* add/update Transition examples to also showcase the `unmount={false}` render strategy

* bump dependencies

* add example with nested unmount/hide transitions

* add unmount to Transition documentation
  • Loading branch information
RobinMalfait committed Oct 18, 2020
1 parent 7d4af1a commit aab23c9
Show file tree
Hide file tree
Showing 26 changed files with 2,467 additions and 981 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
"devDependencies": {
"@tailwindcss/ui": "^0.6.2",
"@testing-library/jest-dom": "^5.11.4",
"@types/node": "^14.11.8",
"@types/node": "^14.11.10",
"husky": "^4.3.0",
"lint-staged": "^10.4.0",
"lint-staged": "^10.4.2",
"prismjs": "^1.22.0",
"tailwindcss": "^1.9.1",
"tailwindcss": "^1.9.4",
"tsdx": "^0.14.1",
"tslib": "^2.0.3",
"typescript": "^3.9.7"
Expand Down
24 changes: 16 additions & 8 deletions packages/@headlessui-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ function MyComponent({ isShowing }) {
| `show` | Boolean | Whether the children should be shown or hidden. |
| `as` | String Component _(Default: `'div'`)_ | The element or component to render in place of the `Transition` itself. |
| `appear` | Boolean _(Default: `false`)_ | Whether the transition should run on initial mount. |
| `unmount` | Boolean _(Default: `true`)_ | Whether the element should be unmounted or hidden based on the show state. |
| `enter` | String _(Default: '')_ | Classes to add to the transitioning element during the entire enter phase. |
| `enterFrom` | String _(Default: '')_ | Classes to add to the transitioning element before the enter phase starts. |
| `enterTo` | String _(Default: '')_ | Classes to add to the transitioning element immediately after the enter phase starts. |
Expand Down Expand Up @@ -358,6 +359,7 @@ function MyComponent({ isShowing }) {
| ----------- | ------------------------------------- | ------------------------------------------------------------------------------------- |
| `as` | String Component _(Default: `'div'`)_ | The element or component to render in place of the `Transition.Child` itself. |
| `appear` | Boolean _(Default: `false`)_ | Whether the transition should run on initial mount. |
| `unmount` | Boolean _(Default: `true`)_ | Whether the element should be unmounted or hidden based on the show state. |
| `enter` | String _(Default: '')_ | Classes to add to the transitioning element during the entire enter phase. |
| `enterFrom` | String _(Default: '')_ | Classes to add to the transitioning element before the enter phase starts. |
| `enterTo` | String _(Default: '')_ | Classes to add to the transitioning element immediately after the enter phase starts. |
Expand Down Expand Up @@ -726,10 +728,13 @@ function MyDropdown() {

##### Props

| Prop | Type | Default | Description |
| -------- | ------------------- | ------- | --------------------------------------------------------------------------- |
| `as` | String \| Component | `div` | The element or component the `Menu.Items` should render as. |
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
| Prop | Type | Default | Description |
| --------- | ------------------- | ------- | --------------------------------------------------------------------------------- |
| `as` | String \| Component | `div` | The element or component the `Menu.Items` should render as. |
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
| `unmount` | Boolean | `true` | Whether the element should be unmounted or hidden based on the open/closed state. |

> **note**: `static` and `unmount` can not be used at the same time. You will get a TypeScript error if you try to do it.
##### Render prop object

Expand Down Expand Up @@ -1231,10 +1236,13 @@ function MyListbox() {

##### Props

| Prop | Type | Default | Description |
| -------- | ------------------- | ------- | --------------------------------------------------------------------------- |
| `as` | String \| Component | `ul` | The element or component the `Listbox.Options` should render as. |
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
| Prop | Type | Default | Description |
| --------- | ------------------- | ------- | --------------------------------------------------------------------------------- |
| `as` | String \| Component | `ul` | The element or component the `Listbox.Options` should render as. |
| `static` | Boolean | `false` | Whether the element should ignore the internally managed open/closed state. |
| `unmount` | Boolean | `true` | Whether the element should be unmounted or hidden based on the open/closed state. |

> **note**: `static` and `unmount` can not be used at the same time. You will get a TypeScript error if you try to do it.
##### Render prop object

Expand Down
8 changes: 4 additions & 4 deletions packages/@headlessui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
"react": ">=16"
},
"devDependencies": {
"@types/react": "^16.9.52",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@popperjs/core": "^2.5.3",
"@testing-library/react": "^11.0.4",
"@testing-library/react": "^11.1.0",
"framer-motion": "^2.9.1",
"next": "9.5.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"snapshot-diff": "^0.8.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from 'react'
import { Transition } from '@headlessui/react'

export default function Home() {
const [isOpen, setIsOpen] = useState(true)

return (
<>
<div className="flex justify-center w-screen h-full p-12 bg-gray-50">
<div className="space-y-2 w-96">
<span className="inline-flex rounded-md shadow-sm">
<button
type="button"
onClick={() => setIsOpen(v => !v)}
className="inline-flex items-center px-3 py-2 text-sm font-medium leading-4 text-gray-700 transition bg-white border border-gray-300 rounded-md duration-150-out hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50"
>
{isOpen ? 'Hide' : 'Show'}
</button>
</span>

<Transition show={isOpen} unmount={false}>
<Box>
<Box>
<Box>
<Box />
</Box>
<Box>
<Box>
<Box>
<Box />
</Box>
</Box>
</Box>
</Box>
</Box>
</Transition>
</div>
</div>
</>
)
}

function Box({ children }: { children?: React.ReactNode }) {
return (
<Transition.Child
unmount={false}
enter="transition translate duration-300"
enterFrom="transform -translate-x-full"
enterTo="transform translate-x-0"
leave="transition translate duration-300"
leaveFrom="transform translate-x-0"
leaveTo="transform translate-x-full"
>
<div className="p-4 space-y-2 text-sm font-semibold tracking-wide text-gray-700 uppercase bg-white rounded-md shadow">
<span>This is a box</span>
{children}
</div>
</Transition.Child>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from 'react'
import { Transition } from '@headlessui/react'

export default function Home() {
const [isOpen, setIsOpen] = useState(true)

return (
<>
<div className="flex justify-center w-screen h-full p-12 bg-gray-50">
<div className="space-y-2 w-96">
<span className="inline-flex rounded-md shadow-sm">
<button
type="button"
onClick={() => setIsOpen(v => !v)}
className="inline-flex items-center px-3 py-2 text-sm font-medium leading-4 text-gray-700 transition bg-white border border-gray-300 rounded-md duration-150-out hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50"
>
{isOpen ? 'Hide' : 'Show'}
</button>
</span>

<Transition show={isOpen} unmount={true}>
<Box>
<Box>
<Box>
<Box />
</Box>
<Box>
<Box>
<Box>
<Box />
</Box>
</Box>
</Box>
</Box>
</Box>
</Transition>
</div>
</div>
</>
)
}

function Box({ children }: { children?: React.ReactNode }) {
return (
<Transition.Child
unmount={true}
enter="transition translate duration-300"
enterFrom="transform -translate-x-full"
enterTo="transform translate-x-0"
leave="transition translate duration-300"
leaveFrom="transform translate-x-0"
leaveTo="transform translate-x-full"
>
<div className="p-4 space-y-2 text-sm font-semibold tracking-wide text-gray-700 uppercase bg-white rounded-md shadow">
<span>This is a box</span>
{children}
</div>
</Transition.Child>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react'
import { Transition } from '@headlessui/react'

export default function Home() {
const [isOpen, setIsOpen] = useState(true)

return (
<>
<div className="flex justify-center w-screen h-full p-12 bg-gray-50">
<div className="space-y-2 w-96">
<span className="inline-flex rounded-md shadow-sm">
<button
type="button"
onClick={() => setIsOpen(v => !v)}
className="inline-flex items-center px-3 py-2 text-sm font-medium leading-4 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50"
>
{isOpen ? 'Hide' : 'Show'}
</button>
</span>

<Transition
show={isOpen}
unmount={false}
enter="transition ease-out duration-300"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition ease-in duration-300"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
className="p-4 bg-white rounded-md shadow"
>
Contents to show and hide
</Transition>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ export default function App() {

<div className="flex h-screen overflow-hidden bg-cool-gray-100">
{/* Off-canvas menu for mobile */}
<Transition show={mobileOpen} className="fixed inset-0 z-40 flex">
<Transition show={mobileOpen} unmount={false} className="fixed inset-0 z-40 flex">
{/* Off-canvas menu overlay, show/hide based on off-canvas menu state. */}
<Transition.Child
unmount={false}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
{ref => (
<div ref={ref} className="fixed inset-0">
{() => (
<div className="fixed inset-0">
<div
onClick={() => setMobileOpen(false)}
className="absolute inset-0 opacity-75 bg-cool-gray-600"
Expand All @@ -48,6 +49,7 @@ export default function App() {

{/* Off-canvas menu, show/hide based on off-canvas menu state. */}
<Transition.Child
unmount={false}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
Expand All @@ -58,6 +60,7 @@ export default function App() {
>
<div className="absolute top-0 right-0 p-1 -mr-14">
<Transition.Child
unmount={false}
className="flex items-center justify-center w-12 h-12 rounded-full focus:outline-none focus:bg-cool-gray-600"
aria-label="Close sidebar"
as="button"
Expand Down
Loading

0 comments on commit aab23c9

Please sign in to comment.