diff --git a/README.md b/README.md index 32f892a..4d6f1e4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,304 @@ # useToggles [![useToggles](https://raw.githubusercontent.com/ava/toggles/main/public/useToggles.jpg)](https://alexcory.notion.site/toggles) + +**Make toggle logic so clear, it reads like plain English.** + +> *"What really matters is how well your code communicates to the people who are going to read it later."* +> — Kent C. Dodds + +A React hooks library that standardizes toggle **verbs** and **states** — so your UI logic reads like spoken language. + +```jsx +import useToggles, { open, toggle } from 'toggles' + +const { sidebar, modal } = useToggles() +open(sidebar) // verb(noun) — reads like English +sidebar.isOpen === true // noun.state — clear, predictable +toggle(modal) // universal toggle verb +``` + +## Why? + +
+Before & After + +**useState approach:** + +```jsx +const [isFullscreenOpen, setIsFullscreenOpen] = useState(false) +setIsFullscreenOpen(true) // open +setIsFullscreenOpen(false) // close +setIsFullscreenOpen(prev => !prev) // toggle +isFullscreenOpen && +``` + +**useToggles approach:** + +```jsx +const { fullscreen } = useToggles() +open(fullscreen) +close(fullscreen) +toggle(fullscreen) +fullscreen.isOpen && +``` + +
+ +- **Self-documenting** — `open(sidebar)` is immediately meaningful, no mental parsing required +- **Consistent** — standardized verbs (`open`, `close`, `toggle`) and states (`isOpen`, `isClosed`). You only define nouns. +- **Ship faster** — when someone says "open the sidebar," your code literally says `open(sidebar)` +- **Simple & scalable** — define nouns, invoke verbs, read states. From one toggle to dozens. +- **Tiny footprint** — ~1–2 KB gzipped + +## Install + +```bash +yarn add toggles +``` + +```bash +npm i toggles +``` + +## Quick Start + +```jsx +import useToggles, { useToggle, open, close, toggle } from 'toggles' + +function App() { + const { modal, sidebar } = useToggles(false, true) + const darkMode = useToggle(false) + + return ( + <> + + + + +
+ {modal.isOpen && close(modal)} />} + {sidebar.isVisible && } +
+ + ) +} +``` + +## The Concept: `verb(noun)` + `noun.state` + +Every UI toggle follows the same pattern: + +1. **`verb(noun)`** — the action you'd naturally speak — `open(sidebar)`, `check(checkbox)` +2. **`noun.state`** — a boolean question — `sidebar.isOpen`, `checkbox.isChecked` + +Nouns are dynamically created via destructuring (an [autovivification](https://en.wikipedia.org/wiki/Autovivification) pattern) — define as many as you need on a single line: + +```jsx +const { modal, sidebar, tooltip, dropdown } = useToggles() +``` + +## API + +### `useToggles(...initialValues)` + +Creates multiple toggles. Returns a proxy object — destructure to name your nouns. + +```jsx +// All default to false +const { modal, sidebar } = useToggles() + +// With initial values (mapped by destructuring order) +const { checkbox, drawer, panel } = useToggles(true, false, true) +``` + +#### Namespaced (global) toggles + +Pass a string as the first argument to share state across components: + +```jsx +// In any component — same namespace = shared state +const { spellCheck } = useToggles('editor-settings', true) +``` + +### `useToggle(initialValue)` + +Convenience hook for a single toggle. + +```jsx +const darkMode = useToggle(false) +toggle(darkMode) +darkMode.isOn // true +``` + +### Verbs + +Import individually or use the `verbs` object: + +```jsx +import { open, close, toggle, show, hide } from 'toggles' +// or +import { verbs } from 'toggles' +verbs.open(sidebar) +``` + +**All verb pairs:** + +| + Verb (on) | - Verb (off) | State (on / off) | +|---|---|---| +| `open` | `close` | `isOpen` / `isClosed` | +| `show` | `hide` | `isShown`, `isVisible` / `isHidden` | +| `turnOn` | `turnOff` | `isOn` / `isOff` | +| `check` | `uncheck` | `isChecked` / `isUnchecked` | +| `enable` | `disable` | `isEnabled` / `isDisabled` | +| `expand` | `collapse` | `isExpanded` / `isCollapsed` | +| `activate` | `deactivate` | `isActivated` / `isDeactivated` | +| `start` | `end` | `hasStarted` / `hasEnded` | +| `connect` | `disconnect` | `isConnected` / `isDisconnected` | +| `focus` | `blur` | `isFocused` / `isBlurred` | +| `mount` | `unmount` | `isMounted` | +| `reveal` | `conceal` | `isRevealed` / `isConcealed` | +| `display` | `dismiss` | — | +| `lock` | `unlock` | `isLocked` / `isUnlocked` | +| `subscribe` | `unsubscribe` | `isSubscribed` | +| `toggle` | *(universal)* | flips current value | + +### Noun States + +Every noun exposes multiple boolean state properties — all derived from the same underlying value: + +```jsx +const { modal } = useToggles(true) + +// All positive states → true +modal.isOpen // true +modal.isVisible // true +modal.isOn // true +modal.isChecked // true +modal.isEnabled // true +modal.isExpanded // true +modal.isActive // true + +// All negative states → false (inverse) +modal.isClosed // false +modal.isHidden // false +modal.isOff // false +modal.isDisabled // false +modal.isCollapsed // false +``` + +Use whichever state reads best in context. A modal uses `isOpen`, a checkbox uses `isChecked`, dark mode uses `isOn` — same underlying boolean, different semantic lens. + +## Examples + +### Modal + +```jsx +import useToggles, { open, close } from 'toggles' + +function ConfirmDialog() { + const { confirmModal } = useToggles() + + return ( + <> + + + {confirmModal.isOpen && ( + +

Are you sure?

+ + +
+ )} + + ) +} +``` + +### Settings Panel + +```jsx +import useToggles, { toggle } from 'toggles' + +function Settings() { + const { advanced, notifications, darkMode } = useToggles(false, true, false) + + return ( +
+ toggle(notifications)} + /> + toggle(darkMode)} + /> + {advanced.isVisible && } +
+ ) +} +``` + +### Global State with Namespaces + +```jsx +import useToggles, { toggle } from 'toggles' + +// Header component +function Header() { + const { sidebar } = useToggles('app') + return +} + +// Layout component — shared state, no prop drilling +function Layout() { + const { sidebar } = useToggles('app') + return ( +
+ {sidebar.isVisible && } +
...
+
+ ) +} +``` + +### With useEffect + +```jsx +import useToggles, { open } from 'toggles' + +function DelayedOpen() { + const { settings } = useToggles(false) + + useEffect(() => { + const timer = setTimeout(() => open(settings), 2000) + return () => clearTimeout(timer) + }, [settings]) + + return

Settings: {settings.isOpen ? 'OPEN' : 'CLOSED'}

+} +``` + +## Compatibility + +- React 16.9+ +- Works with SSR (Next.js, Remix) +- TypeScript supported +- ~1–2 KB gzipped + +## Philosophy + +Code should be as human-readable as possible. `useToggles` reduces the cognitive overhead of toggle state by aligning it with natural language patterns — `open(sidebar)` instead of `setIsSidebarOpen(true)`. + +> *"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."* +> — Martin Fowler + +Read the full design philosophy at [alexcory.notion.site/toggles](https://alexcory.notion.site/toggles). + +## Contributing + +Issues and PRs welcome on [GitHub](https://github.com/ava/toggles). + +## License + +MIT