{"_id":"@alphinex/hooks","_rev":"3-ba81e0b01fbfe173e90564186cf7a14f","name":"@alphinex/hooks","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@alphinex/hooks","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/hooks@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"57b62726fcd8ac9f0ce39fe8d0d0602c933f85c7","tarball":"https://registry.npmjs.org/@alphinex/hooks/-/hooks-1.0.0.tgz","fileCount":5,"integrity":"sha512-eS8bfw4FDnzjI/g9dAsNAIbtuR4xM/7HuPsuJ86wyodBNIojvqWM0qU4EXuct3UqbXm7EapsYis5FfSi5XCXuA==","signatures":[{"sig":"MEQCIB/iXLdJaPb7NP4hrY/LoAFQ9SOY+ewPbYIP2b31xRBvAiBwkZTikoVQ06MoHi70r+y2rtEJf19Wm7h9Cf8Q52TVyw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16557},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Cross-cutting React hooks shared across the platform (useDisclosure, useMediaQuery, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/utils":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","react-dom":"^19.0.0","typescript":"^5.7.2","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@vitejs/plugin-react":"^4.3.4","@alphinex/build-config":"0.0.0","@testing-library/react":"^16.1.0","@alphinex/eslint-config":"0.0.0","@testing-library/jest-dom":"^6.6.3","@alphinex/typescript-config":"0.0.0"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/hooks_1.0.0_1785852479807_0.5771631701352895","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@alphinex/hooks","version":"1.0.1","license":"UNLICENSED","_id":"@alphinex/hooks@1.0.1","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"b5a54955fd999cfca1dcc4c99c84d0c46893d4fe","tarball":"https://registry.npmjs.org/@alphinex/hooks/-/hooks-1.0.1.tgz","fileCount":5,"integrity":"sha512-rWVR+AbDAvT3v4ZxbHeYYzpUkrmrhBEO/DtqKVIhzF6jCd8R7XS1TEsrzIweyZz4OIQoQabt26FhGYLPtd09ag==","signatures":[{"sig":"MEUCIQCyGizc44P6qxwY7C8iDZuQFHNLb21upGfS48AA2Q/uMwIgVoBzc41HUSKA3DArh3WdU0F2Xmbre8aoBvEqVnfh7UM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18614},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Cross-cutting React hooks shared across the platform (useDisclosure, useMediaQuery, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/utils":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","react-dom":"^19.0.0","typescript":"^5.7.2","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@vitejs/plugin-react":"^4.3.4","@alphinex/build-config":"0.0.0","@testing-library/react":"^16.1.0","@alphinex/eslint-config":"0.0.0","@testing-library/jest-dom":"^6.6.3","@alphinex/typescript-config":"0.0.0"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/hooks_1.0.1_1785938261582_0.07811543512886998","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@alphinex/hooks","version":"1.0.2","private":false,"description":"Cross-cutting React hooks shared across the platform (useDisclosure, useMediaQuery, ...).","license":"UNLICENSED","type":"module","sideEffects":false,"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"dependencies":{"@alphinex/core":"1.0.0","@alphinex/utils":"1.1.0"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"devDependencies":{"typescript":"^5.7.2","vite":"^6.0.5","vitest":"^2.1.8","react":"^19.0.0","react-dom":"^19.0.0","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@vitejs/plugin-react":"^4.3.4","jsdom":"^25.0.1","eslint":"^9.17.0","@alphinex/build-config":"0.0.0","@alphinex/typescript-config":"0.0.0","@alphinex/eslint-config":"0.0.0"},"scripts":{"build":"vite build","dev":"vite build --watch","lint":"eslint .","typecheck":"tsc --noEmit","test":"vitest run","clean":"rimraf dist .turbo"},"_nodeVersion":"22.14.0","_id":"@alphinex/hooks@1.0.2","dist":{"integrity":"sha512-LuRb/3SEHrRXOVYVN1wmDY0xNTuEwASQYSFv+2qbE/tp/DSwcTVl/zEOz4CNm+A4WwXMGo8Cy5brQids+Qs1wg==","shasum":"5eec797400cd349a3404b67751ce5e5c399ca341","tarball":"https://registry.npmjs.org/@alphinex/hooks/-/hooks-1.0.2.tgz","fileCount":5,"unpackedSize":23126,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBs3gfhFqITvKLeqyiq2h4xldST4/nJnr7RJdImx7XZNAiEAnOqpboEt1U+B+rrAMV4+3EPvvuhn7aCnMJIREPEjPqE="}]},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"directories":{},"maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hooks_1.0.2_1786525491904_0.03915433588462047"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T14:07:59.543Z","modified":"2026-08-12T09:04:52.211Z","1.0.0":"2026-08-04T14:08:00.005Z","1.0.1":"2026-08-05T13:57:41.721Z","1.0.2":"2026-08-12T09:04:52.036Z"},"license":"UNLICENSED","description":"Cross-cutting React hooks shared across the platform (useDisclosure, useMediaQuery, ...).","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/hooks\n\nSmall, cross-cutting React hooks shared across the platform — no UI, no styling, just reusable\nstateful logic that `@alphinex/ui` components and app code both build on.\n\n## useDisclosure\n\nShared open/close boolean state for Dialog, Drawer, Menu, Popover, Tooltip, and anything else with\nan open/closed concept. Takes an optional `defaultOpen`, `onOpen`, and `onClose`:\n\n```tsx\nimport { useDisclosure } from \"@alphinex/hooks\";\n\nfunction DeleteConfirmDialog() {\n  const { isOpen, open, close, toggle } = useDisclosure({\n    onClose: () => console.log(\"dialog closed\"),\n  });\n\n  return (\n    <>\n      <button onClick={open}>Delete</button>\n      {isOpen && <Dialog onDismiss={close}>Are you sure?</Dialog>}\n    </>\n  );\n}\n```\n\n## useControllableState\n\nBacks every stateful `@alphinex/ui` component: uncontrolled by default, but transparently switches to\ncontrolled mode when `value`/`onChange` are supplied — one implementation, no behavioral divergence\nbetween the two modes. Pass `value` as anything other than `undefined` to switch it to controlled:\n\n```tsx\nimport { useControllableState } from \"@alphinex/hooks\";\n\ninterface TabsProps {\n  value?: string;\n  defaultValue?: string;\n  onChange?: (value: string) => void;\n}\n\nfunction Tabs({ value, defaultValue = \"overview\", onChange }: TabsProps) {\n  const [activeTab, setActiveTab] = useControllableState({\n    value,\n    defaultValue,\n    onChange,\n  });\n\n  return (\n    <div role=\"tablist\">\n      <button onClick={() => setActiveTab(\"overview\")} aria-selected={activeTab === \"overview\"}>\n        Overview\n      </button>\n      {/* ... */}\n    </div>\n  );\n}\n\n// Uncontrolled: <Tabs defaultValue=\"settings\" />\n// Controlled:   <Tabs value={tab} onChange={setTab} />\n```\n\nIn uncontrolled mode, `setValue` accepts a plain value or a functional updater, same as `useState`.\nIn controlled mode, calling `setValue` never touches internal state — it just resolves the next value\n(applying your updater against the current controlled `value` if you passed a function) and calls\n`onChange` with it; the parent owns re-rendering with the new `value`.\n\n## useMediaQuery\n\nReactively tracks a CSS media query via `useSyncExternalStore` — SSR-safe, returns `false` until the\nclient can evaluate `window.matchMedia`:\n\n```tsx\nimport { useMediaQuery } from \"@alphinex/hooks\";\n\nfunction ResponsiveNav() {\n  const isDesktop = useMediaQuery(\"(min-width: 48rem)\");\n  return isDesktop ? <DesktopNav /> : <MobileNav />;\n}\n```\n\n## useDebouncedValue\n\nReturns `value`, updated only after it has been stable for `delayMs` — the standard pattern for\ndebouncing a search input before firing a request:\n\n```tsx\nimport { useState } from \"react\";\nimport { useDebouncedValue } from \"@alphinex/hooks\";\n\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debouncedQuery = useDebouncedValue(query, 300);\n\n  // effect/query hook keyed on debouncedQuery, not query\n  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;\n}\n```\n\n## useClickOutside\n\nFires `handler` on any pointer event (mouse or touch) outside the returned ref's element — used for\ndismissing menus, popovers, and dropdowns. Pass `enabled = false` to disable the listener without\nunmounting (e.g. while the target is already closed):\n\n```tsx\nimport { useClickOutside } from \"@alphinex/hooks\";\n\nfunction DropdownMenu({ onClose }: { onClose: () => void }) {\n  const ref = useClickOutside<HTMLDivElement>(onClose);\n  return <div ref={ref}>{/* menu items */}</div>;\n}\n```\n\n## useLocalStorage\n\nPersists a piece of state to `localStorage`, JSON-serialized, with the same tuple shape as\n`useState`. SSR-safe — falls back to `defaultValue` when `window` isn't available, and silently keeps\nworking in-memory if storage is unavailable (private browsing, quota exceeded):\n\n```tsx\nimport { useLocalStorage } from \"@alphinex/hooks\";\n\nfunction SidebarCollapseToggle() {\n  const [collapsed, setCollapsed] = useLocalStorage(\"sidebar-collapsed\", false);\n  return (\n    <button onClick={() => setCollapsed((prev) => !prev)}>\n      {collapsed ? \"Expand\" : \"Collapse\"}\n    </button>\n  );\n}\n```\n\n## usePrevious\n\nReturns the value from the previous render — `undefined` on the first render. Useful for comparing\nagainst a prop/state change (e.g. to animate only on transition):\n\n```tsx\nimport { usePrevious } from \"@alphinex/hooks\";\n\nfunction Counter({ count }: { count: number }) {\n  const previousCount = usePrevious(count);\n  const direction = previousCount === undefined ? null : count > previousCount ? \"up\" : \"down\";\n  return <span data-direction={direction}>{count}</span>;\n}\n```\n\nSee [documentation/ARCHITECTURE.md](../../documentation/ARCHITECTURE.md) for the full package contract, dependency rules, and roadmap placement.\n","readmeFilename":""}