-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite of our `@charlietango/hooks package`, to better reflect the current state of hooks. The package was fairly complex to support generating individual packages for each tiny hook, making it difficult to maintain, simplify it to one package, that exports each hook as an import (no barrel file with all hooks) - Only include useful hooks, that we use across packages - One package for all hooks (`@charlietango/hooks`) - Deprecate the standalone hooks - Only support React 18+ - Only export ESM - Write tests for all hooks, and run the tests in browser with Vitest - Remove Storybook. We might set up an examples dir if needed BREAKING CHANGE
- Loading branch information
1 parent
41fc8eb
commit c1731f4
Showing
194 changed files
with
6,156 additions
and
20,800 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,52 @@ | ||
name: Node CI | ||
name: Test | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
build: | ||
test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- run: corepack enable | ||
- uses: actions/checkout@v4 | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: "pnpm" | ||
- name: Install dependencies | ||
run: pnpm install | ||
- name: Lint | ||
run: pnpm biome ci . | ||
- name: Build | ||
run: pnpm build | ||
|
||
test_matrix: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
node-version: [14.x, 16.x] | ||
|
||
react: | ||
- 18 | ||
- latest | ||
- rc | ||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v1 | ||
- run: corepack enable | ||
- uses: actions/checkout@v4 | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
- name: npm install, build, and test | ||
node-version: 20 | ||
cache: "pnpm" | ||
- name: Install dependencies | ||
run: pnpm install | ||
- name: Install legacy React types | ||
if: ${{ startsWith(matrix.react, '18') }} | ||
run: pnpm add -D @types/react@${{ matrix.react }} @types/react-dom@${{ matrix.react }} | ||
- name: Install ${{ matrix.react }} | ||
run: pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }} | ||
- name: Validate types | ||
run: | | ||
yarn | ||
yarn build | ||
yarn test | ||
env: | ||
CI: true | ||
pnpm tsc | ||
pnpx playwright install chromium | ||
pnpm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: Publish Pull Requests | ||
on: [push, pull_request] | ||
|
||
jobs: | ||
pr-package: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- run: corepack enable | ||
- uses: actions/checkout@v4 | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: "pnpm" | ||
- name: Install dependencies | ||
run: pnpm install | ||
- name: Build | ||
run: pnpm build | ||
- name: Publish preview package | ||
run: pnpx pkg-pr-new publish --no-template |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: "size" | ||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
permissions: | ||
pull-requests: write | ||
jobs: | ||
size: | ||
runs-on: ubuntu-latest | ||
env: | ||
CI_JOB_NUMBER: 1 | ||
steps: | ||
- run: corepack enable | ||
- uses: actions/checkout@v4 | ||
- name: Setup Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: "pnpm" | ||
- uses: andresz1/size-limit-action@v1 | ||
with: | ||
github_token: ${{ secrets.GH_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,6 @@ npm-debug.log* | |
.tern | ||
.tmp | ||
*.log | ||
report.* | ||
report.* | ||
|
||
__screenshots__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pnpm lint-staged |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,2 @@ | ||
dist/ | ||
lib/ | ||
example/ | ||
package.json | ||
coverage | ||
**/*.* | ||
!**/*.md |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import * as path from "node:path"; | ||
import { globbySync } from "globby"; | ||
import type { SizeLimitConfig } from "size-limit"; | ||
|
||
const toCamelCase = (str: string) => | ||
str.replace(/-([a-z])/g, (_, m) => m.toUpperCase()); | ||
|
||
// Get all hooks from the `src/hooks` directory, and validate their size | ||
const limits = globbySync("dist/hooks/use*.js").map((file) => { | ||
const name = path.parse(file).name; | ||
|
||
return { | ||
name: name, | ||
path: file, | ||
import: `{ ${toCamelCase(name)} }`, | ||
limit: "1 KB", | ||
}; | ||
}) satisfies SizeLimitConfig; | ||
|
||
export default limits; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,105 @@ | ||
# Charlie Tango Hooks | ||
|
||
[![Version Badge][npm-version-svg]][package-url] | ||
[![dependency status][deps-svg]][deps-url] | ||
[![dev dependency status][dev-deps-svg]][dev-deps-url] | ||
[![License][license-image]][license-url] | ||
[![styled with prettier][prettier-svg]][prettier-url] | ||
[![npm version][npm-version-src]][npm-version-href] | ||
[![License][license-src]][license-href] | ||
|
||
Collection of React Hooks used by Charlie Tango. | ||
Collection of React Hooks used by [Charlie Tango](https://www.charlietango.dk/). | ||
|
||
**Storybook Demo:** https://ct-hooks.now.sh | ||
- Written in TypeScript, with full types support. | ||
- Small and focused, each hook does one thing well. | ||
- No barrel file, only import the hooks you need. | ||
- Optimized for modern React, uses newer APIs like `useSyncExternalStore`. | ||
- All hooks work in a server-side rendering environment. | ||
- All hooks are tested with [Vitest](https://vitest.dev/) in a real browser environment. | ||
|
||
## Installation | ||
|
||
Install using [Yarn](https://yarnpkg.com): | ||
Install using npm: | ||
|
||
```sh | ||
yarn add @charlietango/hooks | ||
npm install @charlietango/hooks --save | ||
``` | ||
|
||
or NPM: | ||
## The Hooks | ||
|
||
```sh | ||
npm install @charlietango/hooks --save | ||
All the hooks are exported on their own, so we don't have a barrel file with all the hooks. | ||
This guarantees that you only import the hooks you need, and don't bloat your bundle with unused code. | ||
|
||
### `useDebouncedCallback` | ||
|
||
Debounce a callback function. The callback will only be called after the delay has passed without the function being called again. | ||
|
||
```ts | ||
import { useDebouncedCallback } from "@charlietango/hooks/use-debounced-callback"; | ||
|
||
const debouncedCallback = useDebouncedCallback((value: string) => { | ||
console.log(value); | ||
}, 500); | ||
|
||
debouncedCallback("Hello"); | ||
debouncedCallback("World"); // Will only log "World" after 500ms | ||
``` | ||
|
||
## The Hooks | ||
### `useElementSize` | ||
|
||
### Individual hooks | ||
Monitor the size of an element, and return the size object. | ||
Uses the ResizeObserver API, so it will keep track of the size changes. | ||
|
||
All of our Hooks are published into their own NPM module, so you can pick and choose exactly the ones you need. | ||
```ts | ||
import { useElementSize } from "@charlietango/hooks/use-element-size"; | ||
|
||
<!-- HOOKS_START --> | ||
const { ref, size } = useElementSize(options); | ||
``` | ||
|
||
- **[@charlietango/use-client-hydrated](https://www.npmjs.com/package/@charlietango/use-client-hydrated)** _([useClientHydrated](packages/useClientHydrated/src))_ - Check if the client has been hydrated | ||
- **[@charlietango/use-element-size](https://www.npmjs.com/package/@charlietango/use-element-size)** _([useElementSize](packages/useElementSize/src))_ - Measure the size of a DOM element using ResizeObserver | ||
- **[@charlietango/use-focus-trap](https://www.npmjs.com/package/@charlietango/use-focus-trap)** _([useFocusTrap](packages/useFocusTrap/src))_ - Trap keyboard focus inside a DOM element, to prevent the user navigating outside a modal | ||
- **[@charlietango/use-id](https://www.npmjs.com/package/@charlietango/use-id)** _([useId](packages/useId/src))_ - Generate a deterministic id using a Context Provider | ||
- **[@charlietango/use-interaction](https://www.npmjs.com/package/@charlietango/use-interaction)** _([useInteraction](packages/useInteraction/src))_ - Monitor the user interactions on an element | ||
- **[@charlietango/use-lazy-ref](https://www.npmjs.com/package/@charlietango/use-lazy-ref)** _([useLazyRef](packages/useLazyRef/src))_ - Create a new ref with lazy instantiated value | ||
- **[@charlietango/use-media](https://www.npmjs.com/package/@charlietango/use-media)** _([useMedia](packages/useMedia/src))_ - Detect if the browser matches a media query | ||
- **[@charlietango/use-native-lazy-loading](https://www.npmjs.com/package/@charlietango/use-native-lazy-loading)** _([useNativeLazyLoading](packages/useNativeLazyLoading/src))_ - Detect if the browser supports the new 'loading' attribute on Image elements. | ||
- **[@charlietango/use-script](https://www.npmjs.com/package/@charlietango/use-script)** _([useScript](packages/useScript/src))_ - Load an external third party script | ||
- **[@charlietango/use-toggle](https://www.npmjs.com/package/@charlietango/use-toggle)** _([useToggle](packages/useToggle/src))_ - Simple boolean state toggler | ||
- **[@charlietango/use-window-size](https://www.npmjs.com/package/@charlietango/use-window-size)** _([useWindowSize](packages/useWindowSize/src))_ - Get the width and height of the viewport | ||
### `useMedia` | ||
|
||
<!-- HOOKS_END --> | ||
Monitor a media query, and return a boolean indicating if the media query matches. Until the media query is matched, the hook will return `undefined`. | ||
|
||
To use the Hook, import it from the package you installed, like: | ||
```ts | ||
import { useMedia } from "@charlietango/hooks/use-media"; | ||
|
||
```js | ||
import useMedia from "@charlietango/use-media"; | ||
const isDesktop = useMedia({ minWidth: 1024 }); | ||
const prefersReducedMotion = useMedia( | ||
"(prefers-reduced-motion: no-preference)", | ||
); | ||
``` | ||
|
||
### `@charlietango/hooks` | ||
### `usePrevious` | ||
|
||
The [@charlietango/hooks](https://www.npmjs.com/package/@charlietango/hooks) | ||
module collects all of the individual modules into a single dependency. The module | ||
is optimized for tree shaking, so you application should only include the dependencies | ||
you actually use. | ||
Keep track of the previous value of a variable. | ||
|
||
```js | ||
import { useMedia } from "@charlietango/hooks"; | ||
```ts | ||
const prevValue = usePrevious(value); | ||
``` | ||
|
||
## Contributing | ||
### `useScript` | ||
|
||
This hooks library is built at as a monorepo using Lerna and Yarn Workspaces. | ||
When loading external scripts, you might want to know when the script has loaded, and if there was an error. | ||
Because it's external, it won't be able to trigger a callback when it's done - Therefor you need to monitor the `<script>` tag itself. | ||
The `useScript` hook will handle this for you. | ||
|
||
To start working on a new hook, you should run the `new-hook` script to generate the new package. | ||
You can load the same script multiple times, and the hook will share the script and status between all instances. | ||
|
||
```ts | ||
const status = useScript("https://example.com/script.js"); // "idle" | "loading" | "ready" | "error" | ||
if (status === "ready") { | ||
// Script is loaded | ||
} | ||
``` | ||
yarn new-hook | ||
|
||
### `useWindowSize` | ||
|
||
Get the current window size. If the window resizes, the hook will update the size. | ||
|
||
```ts | ||
import { useWindowSize } from "@charlietango/hooks/use-window-size"; | ||
|
||
const { width, height } = useWindowSize(); | ||
``` | ||
|
||
[package-url]: https://npmjs.org/package/@charlietango/hooks | ||
[npm-version-svg]: https://img.shields.io/npm/v/@charlietango/hooks.svg | ||
[deps-svg]: https://david-dm.org/charlie-tango/hooks.svg | ||
[deps-url]: https://david-dm.org/charlie-tango/hooks | ||
[dev-deps-svg]: https://david-dm.org/charlie-tango/hooks/dev-status.svg | ||
[dev-deps-url]: https://david-dm.org/charlie-tango/hooks#info=devDependencies | ||
[license-image]: http://img.shields.io/npm/l/@charlietango/hooks.svg | ||
[license-url]: LICENSE | ||
[prettier-svg]: https://img.shields.io/badge/styled_with-prettier-ff69b4.svg | ||
[prettier-url]: https://github.com/prettier/prettier | ||
<!-- Badges --> | ||
|
||
[npm-version-src]: https://img.shields.io/npm/v/@charlietango/hooks?style=flat&colorA=080f12&colorB=1fa669 | ||
[npm-version-href]: https://npmjs.com/package/@charlietango/hooks | ||
[license-src]: https://img.shields.io/github/license/charlie-tango/hooks.svg?style=flat&colorA=080f12&colorB=1fa669 | ||
[license-href]: https://github.com/charlie-tango/hooks/blob/main/LICENSE |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.