Skip to content

Commit

Permalink
Move use-portal to monorepo (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronccasanova committed Aug 4, 2023
1 parent 68fb0c2 commit 8f9263a
Show file tree
Hide file tree
Showing 10 changed files with 426 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-bulldogs-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'use-portal': minor
---

Moved `use-portal` to monorepo and removed default export
11 changes: 11 additions & 0 deletions hooks/use-portal/.babelrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"presets": [
[
"@aacc/babel-preset",
{
"typescript": true,
"react": true
}
]
]
}
12 changes: 12 additions & 0 deletions hooks/use-portal/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @type {import('eslint').Linter.Config}
*/
module.exports = {
root: true,
extends: ['@aacc/eslint-config/typescript'],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.eslint.json',
},
ignorePatterns: ['node_modules', 'dist'],
}
104 changes: 104 additions & 0 deletions hooks/use-portal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# use-portal

React hook wrapping the React Portals API.

## Usage

```bash
npm install use-portal
```

```tsx
import { usePortal } from 'use-portal'
```

## usePortal Options

```tsx
interface UsePortalOptions {
/**
* Element the Portal is attached to.
*
* Defaults to `document.body`.
*/
target?: HTMLElement
/**
* Element that wraps the child content of the Portal component.
*
* Defaults to `div`.
*/
container?: HTMLElement
/**
* Defines where the Portal is inserted into the `target` element.
*
* Defaults to 'append'.
*/
insertionOrder?: 'append' | 'prepend'
}
```

## usePortal ReturnType

```tsx
export interface UsePortalReturnType {
/**
* Portal component that renders child content into
* the `target` DOM element.
*/
Portal: (props: PortalProps) => React.ReactPortal | null
/**
* Element the Portal is attached to.
*
* Defaults to `document.body`.
*/
target: HTMLElement | null
/**
* Element that wraps the child content of the Portal component.
*
* Defaults to `div`.
*/
container: HTMLElement | null
}
```

### Example: Simple Portal

[Demo](https://gt1o1.csb.app/)
[Code Sandbox](https://codesandbox.io/s/simple-portal-gt1o1)

```tsx
import { usePortal } from 'use-portal'

function App() {
const { Portal } = usePortal()

const [clicks, setClicks] = React.useState(0)

function handleClick() {
// This will fire when the button in Child is clicked,
// updating Parent's state, even though button
// is not direct descendant in the DOM.
setClicks((prevClicks) => prevClicks + 1)
}

return (
<div onClick={handleClick}>
<h1>Parent {clicks}</h1>
<Portal>
<Child />
</Portal>
</div>
)
}

function Child() {
// The click event on this button will bubble up to parent,
// because there is no 'onClick' attribute defined
return (
<div>
<h2>Child</h2>
<button>Click</button>
</div>
)
}
```
65 changes: 65 additions & 0 deletions hooks/use-portal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "use-portal",
"version": "0.2.1",
"description": "",
"author": "Aaron Casanova <aaronccasanova@gmail.com>",
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.mjs",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.js"
}
},
"scripts": {
"dev": "npm-run-all --parallel 'build:* -- --watch'",
"build": "npm-run-all --parallel build:*",
"build:js": "rollup -c",
"build:types": "tsc --emitDeclarationOnly",
"type-check": "tsc --noEmit",
"type-check:watch": "npm run type-check -- --watch",
"lint": "TIMING=1 eslint . --ext .js,.ts --cache",
"prepublishOnly": "npm run build"
},
"files": [
"dist"
],
"peerDependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"dependencies": {},
"devDependencies": {
"@aacc/babel-preset": "*",
"@aacc/browserslist-config": "*",
"@aacc/eslint-config": "*",
"@aacc/tsconfigs": "*",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-node-resolve": "^13.2.1",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"react": "^17.0.0",
"rollup": "^2.70.2",
"typescript": "^4.7.3"
},
"browserslist": [
"extends @aacc/browserslist-config"
],
"publishConfig": {
"access": "public",
"@aacc:registry": "https://registry.npmjs.org"
},
"repository": {
"type": "git",
"url": "git+https://github.com/aaronccasanova/aacc.git",
"directory": "hooks/use-portal"
},
"bugs": {
"url": "https://github.com/aaronccasanova/aacc/issues"
},
"homepage": "https://github.com/aaronccasanova/aacc/blob/main/hooks/use-portal/README.md"
}
47 changes: 47 additions & 0 deletions hooks/use-portal/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import path from 'path'

import nodeResolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import babel from '@rollup/plugin-babel'

import pkg from './package.json'

const extensions = ['.js', '.jsx', '.ts', '.tsx']

/**
* @type {import('rollup').RollupOptions}
*/
export default {
input: 'src/index.tsx',
output: [
{
format: /** @type {const} */ ('cjs'),
entryFileNames: '[name][assetExtname].js',
dir: path.dirname(pkg.main),
preserveModules: true,
},
{
format: /** @type {const} */ ('es'),
entryFileNames: '[name][assetExtname].mjs',
dir: path.dirname(pkg.module),
preserveModules: true,
},
],
plugins: [
// Allows node_modules resolution
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
nodeResolve({ extensions }),
// Allow bundling cjs modules. Rollup doesn't understand cjs
commonjs(),
// Compile TypeScript/JavaScript files
babel({
extensions,
babelHelpers: 'bundled',
include: ['src/**/*'],
}),
],
external: [
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.peerDependencies ?? {}),
],
}
97 changes: 97 additions & 0 deletions hooks/use-portal/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react'
import { createPortal } from 'react-dom'

interface UsePortalOptions {
/**
* Element the Portal is attached to.
*
* Defaults to `document.body`.
*/
target?: HTMLElement
/**
* Element that wraps the child content of the Portal component.
*
* Defaults to `div`.
*/
container?: HTMLElement
/**
* Defines where the Portal is inserted into the `target` element.
*
* Defaults to 'append'.
*/
insertionOrder?: 'append' | 'prepend'
}

export interface PortalProps {
children: React.ReactNode
key?: string | null
}

export interface UsePortalReturnType {
/**
* Portal component that renders child content into
* the `target` DOM element.
*/
Portal: (props: PortalProps) => React.ReactPortal | null
/**
* Element the Portal is attached to.
*
* Defaults to `document.body`.
*/
target: HTMLElement | null
/**
* Element that wraps the child content of the Portal component.
*
* Defaults to `div`.
*/
container: HTMLElement | null
}

export function usePortal(options: UsePortalOptions = {}): UsePortalReturnType {
const container = React.useMemo<HTMLElement | null>(
() =>
isServer() ? null : options.container || document.createElement('div'),
[options.container],
)

const target = React.useMemo<null | HTMLElement>(
() => (isServer() ? null : options.target || document.body),
[options.target],
)

React.useEffect(() => {
if (container && target) {
const insert =
options.insertionOrder === 'prepend' ? 'prepend' : 'appendChild'

target[insert](container)
}

return () => {
if (container && target) {
target.removeChild(container)
}
}
}, [container, target, options.insertionOrder])

const Portal = React.useCallback(
(props: PortalProps) => {
if (!container) return null

return createPortal(props.children, container, props.key)
},

[container],
)

return { Portal, container, target }
}

function isServer() {
const isDOM =
typeof window !== 'undefined' &&
window.document &&
window.document.documentElement

return !isDOM
}
14 changes: 14 additions & 0 deletions hooks/use-portal/tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// https://typescript-eslint.io/docs/linting/monorepo#one-root-tsconfigjson
"extends": "./tsconfig.json",
"compilerOptions": {
// Ensures this config is not used for a build
"noEmit": true
},
"include": [
// Paths to lint
"src",
".eslintrc.js",
"rollup.config.js"
]
}
10 changes: 10 additions & 0 deletions hooks/use-portal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "@aacc/tsconfigs/node-library.json",
"compilerOptions": {
"outDir": "dist",
"declarationDir": "dist/types",
"lib": ["DOM"]
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

3 comments on commit 8f9263a

@vercel
Copy link

@vercel vercel bot commented on 8f9263a Aug 4, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

aacc-scales – ./apps/scales

aacc-scales.vercel.app
aacc-scales-aaronccasanova.vercel.app
aacc-scales-git-main-aaronccasanova.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 8f9263a Aug 4, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

aacc-next-ts-styled-comps – ./recipes/next-ts-styled-comps

aacc-next-ts-styled-comps-git-main-aaronccasanova.vercel.app
aacc-next-ts-styled-comps.vercel.app
aacc-next-ts-styled-comps-aaronccasanova.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 8f9263a Aug 4, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

aacc-next-ts – ./recipes/next-ts

aacc-next-ts-git-main-aaronccasanova.vercel.app
aacc-next-ts.vercel.app
aacc-next-ts-aaronccasanova.vercel.app

Please sign in to comment.