Skip to content

Commit

Permalink
feat: rewrite the library (#102)
Browse files Browse the repository at this point in the history
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
thebuilder authored Jul 29, 2024
1 parent 41fc8eb commit c1731f4
Show file tree
Hide file tree
Showing 194 changed files with 6,156 additions and 20,800 deletions.
55 changes: 41 additions & 14 deletions .github/workflows/nodejs.yml
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
20 changes: 20 additions & 0 deletions .github/workflows/pkg-pr.yml
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
23 changes: 23 additions & 0 deletions .github/workflows/size-limit.yml
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 }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ npm-debug.log*
.tern
.tmp
*.log
report.*
report.*

__screenshots__
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm lint-staged
7 changes: 2 additions & 5 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
dist/
lib/
example/
package.json
coverage
**/*.*
!**/*.md
5 changes: 0 additions & 5 deletions .prettierrc

This file was deleted.

20 changes: 20 additions & 0 deletions .size-limit.ts
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;
27 changes: 0 additions & 27 deletions CONTRIBUTING.md

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2019 Charlie Tango
Copyright (c) 2024 Charlie Tango

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
124 changes: 72 additions & 52 deletions README.md
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
7 changes: 0 additions & 7 deletions babel.config.js

This file was deleted.

Loading

0 comments on commit c1731f4

Please sign in to comment.