{"_id":"@alphinex/ui","_rev":"11-b01146dad8f6ba3a0fc9a16cb9cc7969","name":"@alphinex/ui","dist-tags":{"latest":"2.7.0"},"versions":{"1.0.0":{"name":"@alphinex/ui","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/ui@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"5e5c4386cde60597f68037dc0a16df40368ad848","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-1.0.0.tgz","fileCount":5,"integrity":"sha512-NUCpoiasLQfvZmdp6LidxOJpjxPcdQIcjmFuBjLFKeMWSEC6YYrbKNHyUOq1UvAe6ix5OZcKGoKCuyDO8s7CNw==","signatures":[{"sig":"MEYCIQDugBcviOduwN6zjbnw5ipnlpshPyC46SebzocIJqx8/AIhAMo1JSMxWm5l1CfT0GsZOsQKLby7B6OezWIy/JHMasVi","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":69391},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.0","@alphinex/icons":"1.0.0","@alphinex/theme":"1.0.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_1.0.0_1785852486070_0.6835943574832597","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@alphinex/ui","version":"2.0.0","license":"UNLICENSED","_id":"@alphinex/ui@2.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"c9410016df536e027649f53f4447e3f60f9ea52e","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.0.0.tgz","fileCount":5,"integrity":"sha512-ikUu3GLy4ELjTRXi9dJC5fuwIFl6aYHoHhOl/NwGdGsj/FJJVxY6cBMWg8jSZkV5nk3YR69kTnAThln19o5pRQ==","signatures":[{"sig":"MEUCIQCBSMalsJ60AoRY/wgHyAW0kTjK62DdCi6EvSAeLy7cgAIgYQgtFTBrkEiQ1Gn7AaGq6f12rRiOiFVnuu3iK1dD4ig=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":70587},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.0","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.0.0_1785858426814_0.06933961132376742","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@alphinex/ui","version":"2.1.0","license":"UNLICENSED","_id":"@alphinex/ui@2.1.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"dd8a2d4e64c6bc42df00e33b6376b8a8f7419f98","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.1.0.tgz","fileCount":5,"integrity":"sha512-X9T74BsLgHEmV0amGDk+4e2gS1yEXL4Y/f9XATaNTKmk3YjnagwMEQ6K4oDMF2mWJ0Qlidg+iAhKoz/ofU+5wg==","signatures":[{"sig":"MEUCIAJMYAiIunvmXoIoO73R39fv8LJM/lRjwy8cFPDrK/4uAiEAjyQrXjV6DNjayhmq1IaqhIjXgKIq3D7DdcyrSCpJk9A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95071},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.0","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.1.0_1785896121349_0.8535034940319952","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"name":"@alphinex/ui","version":"2.1.1","license":"UNLICENSED","_id":"@alphinex/ui@2.1.1","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"c7a82bf3e4b16805726c8c076bfcdda9d6de8f87","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.1.1.tgz","fileCount":5,"integrity":"sha512-yB/3pR/cvMxLZP5rKGtc+MCRFP4Yc0oOD6Rbs0yXHV3CMQVY+1s3YfKkHvEJDWyulCHm9/GmmaSEwzAYj3DE+Q==","signatures":[{"sig":"MEUCIQDYw6c5PYXTv04M/G3c2MQsXRf2jKFJBepc94/athZCTQIgCBbf9z0+cLiMhMnkWbf2VtbefhUA3UzHHigqa2mVuP4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95071},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.1.1_1785938263371_0.6473158926360325","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"@alphinex/ui","version":"2.2.0","license":"UNLICENSED","_id":"@alphinex/ui@2.2.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"b18b9bbdcb9b437124b1be45e6c4b95c46a60aea","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.2.0.tgz","fileCount":5,"integrity":"sha512-IKkgR8lOzmbonAlu2ueYGDW5LtZkLm7FxwEUNmSsLH2wAno0A0i3H+dgyfQY2sFg0UyPsiKk5MKtuwbhkevTiQ==","signatures":[{"sig":"MEYCIQDhcuZ7zQeP8HmHYlzJWFj48UZje0y8acNs48tY+Trn2QIhAIKdq7cqk473c/6TnMa1jTjcWx3eUx3WvkAl75Q73qan","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":201438},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.2.0_1786003933522_0.9251157405655892","host":"s3://npm-registry-packages-npm-production"}},"2.3.0":{"name":"@alphinex/ui","version":"2.3.0","license":"UNLICENSED","_id":"@alphinex/ui@2.3.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"1cc4978dda4fb9d26690e691b81947ecf7d6d917","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.3.0.tgz","fileCount":5,"integrity":"sha512-fag3fEOYwFmC9WzBKWsuamF3p83E7e0+2tSkfVsgvS2rEISgeKb3PhN3i9RYjpsGcNl9L6YA7xwQTK63ZvEHQw==","signatures":[{"sig":"MEUCIQDim4xMh+DNxINaxH5i2SW6DpJCxELJlVfcDWrB56emJAIgWzcpFreAZPK3QHEvzlYsykymauBNLR67qUkZN4eeVfA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":218984},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.3.0_1786200452355_0.04000886601207321","host":"s3://npm-registry-packages-npm-production"}},"2.4.0":{"name":"@alphinex/ui","version":"2.4.0","license":"UNLICENSED","_id":"@alphinex/ui@2.4.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"fa5e83ad185ff72182d78d7ab5c6f1122cdd5fac","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.4.0.tgz","fileCount":5,"integrity":"sha512-VS4BSzHTRzfyJdja3AXGRS2DkLDrBnrwtdJxQfYSxWbvQJJJwKmyyi4ZG16hppzwvGt/uWa+4pJCvC2w9kuduw==","signatures":[{"sig":"MEQCIH98g+sc43+w/ZO5kQlMhUX1tycYFWcaL4L19XiNUDfiAiAUvDrG5TYu87TBIgzzmXytJ1/TTv+loG1+CWChL+rptg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":377968},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.4.0_1786218410830_0.9472297124322862","host":"s3://npm-registry-packages-npm-production"}},"2.5.0":{"name":"@alphinex/ui","version":"2.5.0","license":"UNLICENSED","_id":"@alphinex/ui@2.5.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"74010743270b9df0f1387f108f12152f7f5a5899","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.5.0.tgz","fileCount":5,"integrity":"sha512-wBsnVYOGp9XyBiH+JR2mCq+PxYfzQ/sYy1bHMiC33x2aldYnhHqjMAe/a/00NASCsU4oii8prHO+GSuLP7uH3w==","signatures":[{"sig":"MEQCIBRtc0FB8lAxGRAnnoS45pwCIU5RuNE/zsuxO6MMJpTtAiBDrmmr0NJVpo79SawPkRi3DX8ZYzMMVYBLAcppAViUng==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":530388},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.0.1","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.5.0_1786273321515_0.34168746549378004","host":"s3://npm-registry-packages-npm-production"}},"2.5.1":{"name":"@alphinex/ui","version":"2.5.1","license":"UNLICENSED","_id":"@alphinex/ui@2.5.1","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"5f7ae4a23b1564c96904af8b806b343727044e7c","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.5.1.tgz","fileCount":5,"integrity":"sha512-3XfAhDGB5zudfgQTIA2NRLVgvTiTyZPFsT+VTW/VGyqyy8ssP2DHVMBBJNPhlCKrZnNUF6Iq6DvthpNNKemCLQ==","signatures":[{"sig":"MEQCIBtre7FSvExl1HQPOjRoLKoUKlEiycz6A8UUmk+jCuJaAiBCedd1l+MIZCeK3nOGhRnNGTij7C5wOZxANByhHjwvvQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":530388},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.1","@alphinex/icons":"1.1.0","@alphinex/theme":"1.1.0","@alphinex/utils":"1.0.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.5.1_1786298615959_0.6996001181184124","host":"s3://npm-registry-packages-npm-production"}},"2.6.0":{"name":"@alphinex/ui","version":"2.6.0","license":"UNLICENSED","_id":"@alphinex/ui@2.6.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"8ca7d3e51bf65d7a3ec31fd1a427effa4a47f055","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.6.0.tgz","fileCount":5,"integrity":"sha512-76Tsn3XPomlkRQt4Sk5hx36u9mZGyRSJftrph3nj6T6eudt8NF8FzglFPRQBipmpZ0tATrfQ0C0T8vQU+CdQJw==","signatures":[{"sig":"MEYCIQCgnD8m3kiOzHqNedDFQU4XSqNF1zbcHkvVBrqZVPY4YQIhAJKQWQlq7Gtvwsv8UT1vaaVfGepNt4arPDYjsjseNJKI","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":568880},"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":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.2","@alphinex/icons":"1.2.0","@alphinex/theme":"1.1.0","@alphinex/utils":"1.1.0","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","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","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/ui_2.6.0_1786525498012_0.07474808478354289","host":"s3://npm-registry-packages-npm-production"}},"2.7.0":{"name":"@alphinex/ui","version":"2.7.0","private":false,"description":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","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":{"class-variance-authority":"^0.7.1","@alphinex/core":"1.0.0","@alphinex/theme":"1.1.0","@alphinex/icons":"1.2.0","@alphinex/hooks":"1.0.2","@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","@testing-library/user-event":"^14.5.2","@vitejs/plugin-react":"^4.3.4","axe-core":"^4.10.2","jsdom":"^25.0.1","eslint":"^9.17.0","@alphinex/build-config":"0.0.0","@alphinex/eslint-config":"0.0.0","@alphinex/typescript-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/ui@2.7.0","dist":{"integrity":"sha512-bULof+PP/MBMNH1lwb666dIVwnnvRvap+Jy+P7cU+jxxfZfmiYu8FkZXCrakLq6liQ3Uy+zh7ZM4OJlu1OxlrA==","shasum":"d9adc4d60e5ea557d25492a1bfd406c3c0bdc037","tarball":"https://registry.npmjs.org/@alphinex/ui/-/ui-2.7.0.tgz","fileCount":5,"unpackedSize":705818,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBI8rQDdVMaBX85yfNTQX0uPkWnhB/+Qlgbu3QBVGbfzAiEAwaGIgB+xK8ZOip7Z0Mc/wxg9kew9MbH2fElyjTbIe7c="}]},"_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/ui_2.7.0_1786546542782_0.039409211600543115"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T14:08:05.851Z","modified":"2026-08-12T14:55:43.106Z","1.0.0":"2026-08-04T14:08:06.187Z","2.0.0":"2026-08-04T15:47:06.977Z","2.1.0":"2026-08-05T02:15:21.501Z","2.1.1":"2026-08-05T13:57:43.538Z","2.2.0":"2026-08-06T08:12:13.684Z","2.3.0":"2026-08-08T14:47:32.517Z","2.4.0":"2026-08-08T19:46:50.988Z","2.5.0":"2026-08-09T11:02:01.652Z","2.5.1":"2026-08-09T18:03:36.096Z","2.6.0":"2026-08-12T09:04:58.558Z","2.7.0":"2026-08-12T14:55:42.971Z"},"license":"UNLICENSED","description":"Primitive and composite component library (Button, Modal, Menu, Card, ...).","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/ui\n\nThe platform's primitive component library — Button, Input, Select, Card, and friends. Every\ncomponent is styled entirely off the `--aui-*` CSS variables that `@alphinex/theme`'s\n`ThemeProvider` sets on the document root, so nothing here needs its own theming logic: mount a\n`ThemeProvider` above your tree and these components pick up color scheme, density, and RTL\nautomatically. Variant styling is built with [class-variance-authority](https://cva.style/) (cva);\neach component with visual variants exports a matching `*Variants` function for anyone who needs\nthe generated class string directly (e.g. to style a custom element consistently).\n\n## Button\n\nThe workhorse component. Supports `variant` (`solid | outline | ghost | subtle`), `tone`\n(`neutral | danger | success`), `size` (`sm | md | lg`), a built-in loading state, and leading/trailing\nicon slots. Pass `asChild` to render your own element (e.g. a router `<Link>`) styled as a button\ninstead of a `<button>`, via the `Slot` mechanism described below:\n\n```tsx\nimport { Button } from \"@alphinex/ui\";\nimport { Icon } from \"@alphinex/icons\";\n\nfunction DeleteAction({ pending, onDelete }: { pending: boolean; onDelete: () => void }) {\n  return (\n    <Button variant=\"solid\" tone=\"danger\" isLoading={pending} onClick={onDelete}>\n      Delete account\n    </Button>\n  );\n}\n\n// Style a router link as a button, with no extra DOM wrapper:\n<Button asChild variant=\"outline\">\n  <a href=\"/docs\">Read the docs</a>\n</Button>;\n```\n\n`isLoading` disables the button, sets `aria-busy`, and swaps in a spinner in place of `leadingIcon`\n(the `trailingIcon`, if any, is hidden while loading). `buttonVariants` is exported for\nclassName-level customization outside the component.\n\n## Input\n\nA styled `<input>` with optional `leadingIcon`/`trailingIcon` slots (only rendered as a wrapping\n`<div>` when at least one icon is supplied — otherwise it's a bare `<input>`) and a `size` prop\n(`sm | md | lg`, note this shadows the native `size` attribute, which is omitted from `InputProps`).\n`containerClassName` targets the icon wrapper specifically:\n\n```tsx\nimport { Input } from \"@alphinex/ui\";\nimport { Icon } from \"@alphinex/icons\";\n\n<Input\n  size=\"md\"\n  placeholder=\"Search…\"\n  leadingIcon={<Icon name=\"search\" size=\"sm\" />}\n  aria-label=\"Search\"\n/>;\n```\n\n`inputVariants` is exported for the base input class string.\n\n## Select\n\nA native `<select>` styled to match the rest of the platform — fully accessible and\nkeyboard-operable with no custom ARIA. Takes an `options: SelectOption[]` array\n(`{ label, value, disabled? }`) and an optional `placeholder` rendered as a disabled first option.\n(There's no headless combobox/multi-select here by design — see ADR-0001 for when that'd change.)\n\n```tsx\nimport { Select, type SelectOption } from \"@alphinex/ui\";\n\nconst countries: SelectOption[] = [\n  { label: \"United States\", value: \"US\" },\n  { label: \"United Kingdom\", value: \"UK\" },\n  { label: \"Pakistan\", value: \"PK\" },\n];\n\n<Select options={countries} placeholder=\"Choose a country\" size=\"md\" />;\n```\n\n## Tooltip\n\nShows `content` near a single trigger `children` element on hover/focus, after `delayMs` (default\n200ms; hiding is immediate). Positioning is direction-aware — it reads `dir` from\n`@alphinex/theme`'s `useTheme()`, so `placement=\"start\"`/`\"end\"` flip correctly under RTL — and it\nrenders through a portal with `role=\"tooltip\"`, dismissible via Escape:\n\n```tsx\nimport { Tooltip, Button } from \"@alphinex/ui\";\n\n<Tooltip content=\"Copy to clipboard\" placement=\"top\">\n  <Button variant=\"ghost\" size=\"sm\">\n    Copy\n  </Button>\n</Tooltip>;\n```\n\n`children` must be a single element — `Tooltip` merges its trigger props (`onMouseEnter`,\n`onFocus`, `aria-describedby`, ...) onto it via `Slot`, so it needs something to merge onto.\n\n## Slot\n\nThe polymorphic primitive behind every component's `asChild` prop (Radix-style). Instead of\nrendering its own wrapper element, `Slot` clones its single child and merges props, `className`,\n`style`, and `ref` onto it:\n\n```tsx\nimport { Slot } from \"@alphinex/ui\";\n\nfunction Link({\n  asChild,\n  ...props\n}: { asChild?: boolean } & React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n  const Comp = asChild ? Slot : \"a\";\n  return <Comp {...props} />;\n}\n```\n\n`Slot` requires exactly one valid React element as `children`; passing zero or multiple children\nrenders nothing (and throws via `Children.only` for the multiple-children case).\n\n## Simple primitives\n\n| Component  | Purpose                                                                                                                                                                                       |\n| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `Badge`    | Small status/count pill. `variant` (`solid \\| subtle \\| outline`), `tone` (`neutral \\| accent \\| danger \\| success \\| warning`), `size` (`sm \\| md`).                                         |\n| `Card`     | Compound container: `Card`, `Card.Header`, `Card.Body`, `Card.Footer`, each independently optional. `variant`: `outlined \\| elevated`.                                                        |\n| `Avatar`   | Image with initials fallback (auto-derived from `name`) if `src` is missing or fails to load. `size` (`sm \\| md \\| lg`), `shape` (`circle \\| square`).                                        |\n| `Alert`    | Inline banner. `tone` (`info \\| success \\| warning \\| danger`, defaults `info`) picks the icon and `role` (`alert` for danger/warning, `status` otherwise). Optional `title` and `onDismiss`. |\n| `Skeleton` | Loading placeholder block. `variant` for shape, plus `width`/`height`. Renders `aria-hidden`.                                                                                                 |\n| `Label`    | Styled `<label>` with an optional `required` asterisk indicator.                                                                                                                              |\n| `Checkbox` | Styled native `<input type=\"checkbox\">` — controlled/uncontrolled like any checkbox input.                                                                                                    |\n| `Switch`   | Toggle button (`role=\"switch\"`), controlled via `checked`/`onCheckedChange` or uncontrolled via `defaultChecked` (built on `@alphinex/hooks`' `useControllableState`).                        |\n| `Textarea` | Styled native `<textarea>`, defaults to `rows={3}`.                                                                                                                                           |\n\n```tsx\nimport { Badge, Card, Avatar, Alert, Skeleton, Label, Checkbox, Switch, Textarea } from \"@alphinex/ui\";\n\n<Badge variant=\"subtle\" tone=\"success\">Active</Badge>\n\n<Card variant=\"elevated\">\n  <Card.Header>Team members</Card.Header>\n  <Card.Body>...</Card.Body>\n</Card>\n\n<Avatar name=\"Ada Lovelace\" src=\"/avatars/ada.jpg\" size=\"sm\" />\n\n<Alert tone=\"warning\" title=\"Unsaved changes\" onDismiss={() => setShown(false)}>\n  You have changes that haven't been saved yet.\n</Alert>\n\n<Skeleton variant=\"text\" width=\"60%\" height={16} />\n\n<Label htmlFor=\"email\" required>Email</Label>\n\n<Checkbox id=\"agree\" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />\n\n<Switch checked={enabled} onCheckedChange={setEnabled} />\n\n<Textarea rows={5} placeholder=\"Add a comment…\" />\n```\n\n## CommandPalette\n\nA ⌘K-style modal overlay: search input, keyboard-navigable filtered results (arrow keys, Enter to\nselect, Escape to close, hand-rolled focus trap). Filters `items` client-side against `label` and\noptional `keywords`. Supply `query`/`onQueryChange` to observe or drive the query text externally\n(e.g. to power an async fetch that updates `items` reactively) — `CommandPalette` still does its own\nclient-side filtering over whatever `items` you pass in either way:\n\n```tsx\nimport { CommandPalette, type CommandPaletteItem } from \"@alphinex/ui\";\nimport { useState } from \"react\";\n\nfunction AppCommandPalette() {\n  const [isOpen, setIsOpen] = useState(false);\n\n  const items: CommandPaletteItem[] = [\n    { id: \"new-invoice\", label: \"New invoice\", onSelect: () => navigate(\"/invoices/new\") },\n    {\n      id: \"settings\",\n      label: \"Settings\",\n      keywords: [\"preferences\"],\n      onSelect: () => navigate(\"/settings\"),\n    },\n  ];\n\n  return (\n    <CommandPalette\n      isOpen={isOpen}\n      onClose={() => setIsOpen(false)}\n      items={items}\n      placeholder=\"Type a command or search…\"\n    />\n  );\n}\n```\n\n## Stepper\n\nA horizontal step indicator. `activeStep` is a 0-based index; steps before it render as complete\n(with a check icon), the active one as current, the rest as upcoming. Pass `onStepChange` to make\ncompleted/upcoming steps clickable to jump to:\n\n```tsx\nimport { Stepper, type Step } from \"@alphinex/ui\";\n\nconst steps: Step[] = [\n  { id: \"account\", label: \"Account\" },\n  { id: \"billing\", label: \"Billing\" },\n  { id: \"confirm\", label: \"Confirm\" },\n];\n\n<Stepper steps={steps} activeStep={1} onStepChange={setActiveStep} />;\n```\n\n`stepCircleVariants` is exported alongside for className-level customization of the step circle.\n\n## Dialog\n\nA centered modal overlay: focus trap, Escape-to-close, backdrop-click-to-close, body-scroll\nlock, and focus-restore on close — hand-rolled, see [ADR-0017](../../documentation/adr/0017-dialog-drawer-hand-rolled-shared-overlay-hook.md).\nFully controlled — you own `isOpen`:\n\n```tsx\nimport { Button, Dialog } from \"@alphinex/ui\";\nimport { useState } from \"react\";\n\nfunction DeleteInvoiceDialog() {\n  const [isOpen, setIsOpen] = useState(false);\n\n  return (\n    <>\n      <Button variant=\"outline\" tone=\"danger\" onClick={() => setIsOpen(true)}>\n        Delete invoice\n      </Button>\n      <Dialog\n        isOpen={isOpen}\n        onClose={() => setIsOpen(false)}\n        title=\"Delete invoice\"\n        footer={\n          <div className=\"flex justify-end gap-2\">\n            <Button variant=\"ghost\" onClick={() => setIsOpen(false)}>\n              Cancel\n            </Button>\n            <Button tone=\"danger\" onClick={handleDelete}>\n              Delete\n            </Button>\n          </div>\n        }\n      >\n        This can&rsquo;t be undone.\n      </Dialog>\n    </>\n  );\n}\n```\n\n`size` (`\"sm\" | \"md\" | \"lg\"`, default `\"md\"`) controls max-width. Pass `initialFocusRef` to focus\na specific element (e.g. the danger action) instead of the dialog container on open. Set\n`hideCloseButton` when `footer` already supplies a Cancel/close action.\n\n## Drawer\n\nAn off-canvas panel — same focus-trap/Escape/backdrop/focus-restore behavior as `Dialog` (they\nshare `useOverlayBehavior` internally), sliding in from a logical edge instead of centering:\n\n```tsx\nimport { Drawer } from \"@alphinex/ui\";\n\n<Drawer isOpen={isOpen} onClose={() => setIsOpen(false)} title=\"Filters\" side=\"end\" width=\"24rem\">\n  <FilterForm />\n</Drawer>;\n```\n\n`side` is `\"start\"` or `\"end\"` (default `\"start\"`) — these flip automatically under\n`dir=\"rtl\"` (see `@alphinex/theme`'s `ThemeProvider`), the same logical-property convention\n`<Shell>`'s own off-canvas sidebar uses. `width` accepts any CSS width value (default `\"20rem\"`).\n\n## Menu\n\nA click-triggered dropdown of actions with true roving-tabindex keyboard navigation\n(ArrowUp/ArrowDown moves real focus, wrapping; Escape closes and returns focus to the trigger).\nSee [ADR-0018](../../documentation/adr/0018-menu-popover-tabs-pagination-hand-rolled.md):\n\n```tsx\nimport { Menu } from \"@alphinex/ui\";\n\n<Menu\n  trigger={<Button variant=\"outline\">Actions</Button>}\n  items={[\n    { id: \"rename\", label: \"Rename\", onSelect: () => rename(row.id) },\n    { id: \"duplicate\", label: \"Duplicate\", onSelect: () => duplicate(row.id) },\n    { id: \"delete\", label: \"Delete\", disabled: !canDelete, onSelect: () => remove(row.id) },\n  ]}\n/>;\n```\n\n`trigger` is rendered via `Slot` — pass a single element, most commonly a `Button`. `placement`\n(same `TooltipPlacement` type `Tooltip` uses, default `\"bottom\"`) and controlled `isOpen`/\n`onOpenChange` are both available, same shape as `Popover` below.\n\n## Popover\n\nA click-triggered floating panel for arbitrary interactive content — dismisses on outside click\nor Escape, reuses `Tooltip`'s positioning math:\n\n```tsx\nimport { Popover } from \"@alphinex/ui\";\n\n<Popover trigger={<Button variant=\"outline\">Filters</Button>}>\n  <FilterForm />\n</Popover>;\n```\n\nFully controllable — pass `isOpen`/`onOpenChange` to drive it from outside instead of letting it\nmanage its own open state.\n\n## Tabs\n\nHorizontal tabs with roving-tabindex keyboard nav — ArrowLeft/ArrowRight move focus and activate\n(\"automatic activation\"); Home/End jump to the first/last enabled tab:\n\n```tsx\nimport { Tabs } from \"@alphinex/ui\";\n\n<Tabs\n  items={[\n    { id: \"overview\", label: \"Overview\", content: <OverviewPanel /> },\n    { id: \"activity\", label: \"Activity\", content: <ActivityPanel /> },\n    { id: \"settings\", label: \"Settings\", content: <SettingsPanel />, disabled: !canEditSettings },\n  ]}\n  defaultValue=\"overview\"\n  onChange={(id) => trackTabView(id)}\n/>;\n```\n\nFully controllable via `value`/`onChange`, uncontrolled by default via `defaultValue`.\n\n## Pagination\n\nStandalone page-number pagination — generalized out of `@alphinex/table`'s `DataGridPagination`\nfor use outside a data grid (a search-results list, a paginated card grid):\n\n```tsx\nimport { Pagination } from \"@alphinex/ui\";\n\n<Pagination page={page} pageCount={totalPages} onPageChange={setPage} />;\n```\n\n`siblingCount` (default `1`) controls how many page numbers show either side of the current page\nbefore collapsing into an ellipsis. The windowing logic itself is also exported as a pure\nfunction, `getPaginationRange(page, pageCount, siblingCount?)`, if you need the same page-number\nlist for a custom pagination UI.\n\n## Toast\n\nAn app-wide toast queue. Mount `ToastProvider` once near the root, then call `useToast()`\nanywhere beneath it — see [ADR-0019](../../documentation/adr/0019-feedback-navigation-chrome.md):\n\n```tsx\nimport { ToastProvider, useToast } from \"@alphinex/ui\";\n\nfunction Root() {\n  return (\n    <ToastProvider>\n      <SaveButton />\n    </ToastProvider>\n  );\n}\n\nfunction SaveButton() {\n  const { toast } = useToast();\n  return (\n    <Button\n      onClick={async () => {\n        await save();\n        toast({ title: \"Saved\", description: \"Invoice updated.\" });\n      }}\n    >\n      Save\n    </Button>\n  );\n}\n```\n\n`toast({ title, description?, tone?, durationMs? })` returns the toast's id (pass it to\n`dismiss(id)` to close it programmatically). `tone` is `\"neutral\" | \"success\" | \"danger\" |\n\"warning\"` (default `\"neutral\"`) — `\"danger\"` renders with `role=\"alert\"`, the rest with\n`role=\"status\"`. `durationMs` (default `5000`) auto-dismisses the toast; pass `0` to disable\nauto-dismiss. `ToastProvider`'s `limit` prop (default `5`) drops the oldest toast once exceeded.\n\n## ConfirmDialog\n\nA `Dialog` composite for \"are you sure?\" confirmations — `onConfirm` may be async, and both\nbuttons stay disabled with a loading state on Confirm until it resolves:\n\n```tsx\nimport { ConfirmDialog } from \"@alphinex/ui\";\n\n<ConfirmDialog\n  isOpen={isConfirmOpen}\n  onClose={() => setIsConfirmOpen(false)}\n  title=\"Delete invoice\"\n  description=\"This can't be undone.\"\n  tone=\"danger\"\n  confirmLabel=\"Delete\"\n  onConfirm={async () => {\n    await deleteInvoice(id);\n    setIsConfirmOpen(false);\n  }}\n/>;\n```\n\n## Breadcrumb\n\nA trail of ancestor links ending in the current (non-link) page:\n\n```tsx\nimport { Breadcrumb } from \"@alphinex/ui\";\n\n<Breadcrumb\n  items={[\n    { id: \"invoices\", label: \"Invoices\", href: \"/invoices\" },\n    { id: \"current\", label: \"INV-1042\" },\n  ]}\n/>;\n```\n\nThe last item renders as plain text (`aria-current=\"page\"`) regardless of whether it has an\n`href` — omit `href` on it.\n\n## PageHeader\n\nTitle + description + actions + an optional `Breadcrumb`, composed together:\n\n```tsx\nimport { PageHeader } from \"@alphinex/ui\";\n\n<PageHeader\n  title=\"INV-1042\"\n  breadcrumb={[\n    { id: \"invoices\", label: \"Invoices\", href: \"/invoices\" },\n    { id: \"current\", label: \"INV-1042\" },\n  ]}\n  actions={<Button size=\"sm\">Download</Button>}\n/>;\n```\n\n## Progress\n\nA determinate progress indicator, `\"bar\"` (default) or `\"ring\"`:\n\n```tsx\nimport { Progress } from \"@alphinex/ui\";\n\n<Progress value={65} max={100} label=\"Storage used\" showValue />\n<Progress value={40} variant=\"ring\" size=\"lg\" label=\"Sync progress\" />\n```\n\n`label` is required — a progress indicator has no visible text label of its own, so it needs an\naccessible name. `showValue` renders the rounded percentage alongside the indicator.\n\n## EmptyState\n\nA \"nothing here yet\" placeholder for an empty list/grid/search-result state:\n\n```tsx\nimport { EmptyState } from \"@alphinex/ui\";\n\n<EmptyState\n  title=\"No invoices yet\"\n  description=\"Invoices you create will show up here.\"\n  action={<Button size=\"sm\">Create invoice</Button>}\n/>;\n```\n\n## TreeView\n\nA hierarchical, expandable tree list (file-browser/nav-tree style) with true roving-tabindex\nkeyboard navigation — Up/Down move focus between visible rows, Right expands/moves into a node,\nLeft collapses/moves to the parent, Home/End jump to the first/last visible row. See\n[ADR-0021](../../documentation/adr/0021-treeview-and-navigation-package.md):\n\n```tsx\nimport { TreeView, type TreeNode } from \"@alphinex/ui\";\n\nconst items: TreeNode[] = [\n  {\n    id: \"docs\",\n    label: \"Docs\",\n    children: [\n      { id: \"getting-started\", label: \"Getting Started\" },\n      { id: \"guides\", label: \"Guides\", children: [{ id: \"guide-1\", label: \"Guide 1\" }] },\n    ],\n  },\n  { id: \"about\", label: \"About\" },\n];\n\n<TreeView items={items} selectedId={selectedId} onSelect={setSelectedId} />;\n```\n\nExpansion is fully controllable via `expandedIds`/`onExpandedChange`, uncontrolled by default via\n`defaultExpandedIds`. If you want a typed, permission-aware nav sidebar instead of a generic\ntree, see `@alphinex/navigation`'s `NavTree`, built on this component.\n\n## RadioGroup\n\nA single-choice group on the WAI-ARIA `radiogroup` pattern. Arrow keys move focus **and** select\n(the pattern's required \"selection follows focus\"), skipping disabled options and wrapping at both\nends; Home/End jump to the first/last enabled option:\n\n```tsx\nimport { RadioGroup } from \"@alphinex/ui\";\n\n<RadioGroup\n  aria-label=\"Payment method\"\n  value={method}\n  onChange={setMethod}\n  options={[\n    { value: \"card\", label: \"Card\" },\n    { value: \"bank\", label: \"Bank transfer\", description: \"2–3 business days\" },\n    { value: \"crypto\", label: \"Crypto\", disabled: true },\n  ]}\n/>;\n```\n\nPass `name` to have the group emit a hidden input so it participates in native form submission,\nand `orientation=\"horizontal\"` to lay it out in a row.\n\n## NumberInput\n\nA numeric field built on the `spinbutton` pattern rather than `<input type=\"number\">`, whose\nnative behavior varies across engines (scroll-wheel mutation, silent rejection of intermediate\ntext, locale-dependent parsing). ArrowUp/Down step, PageUp/Down step by 10×, Home/End jump to\nmin/max:\n\n```tsx\nimport { NumberInput } from \"@alphinex/ui\";\n\n<NumberInput\n  aria-label=\"Seats\"\n  value={seats}\n  onChange={setSeats}\n  min={1}\n  max={50}\n  step={1}\n  suffix=\"seats\"\n/>;\n```\n\nA cleared field reports `undefined`, not `0`, so \"no value\" stays distinguishable from zero.\n`precision` defaults to the decimals implied by `step`, and stepping is drift-free — `0.1 + 0.1 +\n0.1` gives `0.3`, not `0.30000000000000004`. `hideControls` drops the buttons while keeping\nkeyboard stepping.\n\n## Slider / RangeSlider\n\nSingle- and two-thumb range inputs on the `slider` pattern. Arrow keys step, PageUp/Down step by\n10×, Home/End jump to the bounds — and the arrow axis mirrors automatically under `dir=\"rtl\"`:\n\n```tsx\nimport { RangeSlider, Slider } from \"@alphinex/ui\";\n\n<Slider aria-label=\"Volume\" value={volume} onChange={setVolume} showValue />\n\n<RangeSlider\n  aria-label=\"Budget\"\n  value={budget}\n  onChange={setBudget}\n  marks={[\n    { value: 0, label: \"$0\" },\n    { value: 100, label: \"$100k\" },\n  ]}\n/>\n```\n\nDragging one `RangeSlider` thumb past the other **swaps** them rather than inverting the range.\n`onChangeEnd` fires once when a gesture completes — use it for expensive commits (a network\nwrite) while `onChange` drives the live UI. The windowing math is also exported as pure\nfunctions (`snapToStep`, `valueToPercent`, `percentToValue`, `orderRange`) if you need a custom\nslider UI on the same behavior.\n\n## Accordion\n\nCollapsible sections. Each header is a button inside a heading, controlling a `region` labelled by\nit; Up/Down move between headers (wrapping, skipping disabled), Home/End jump to the ends:\n\n```tsx\nimport { Accordion } from \"@alphinex/ui\";\n\n<Accordion\n  type=\"single\"\n  collapsible\n  defaultValue={[\"shipping\"]}\n  items={[\n    { id: \"shipping\", title: \"Shipping\", content: <p>Ships in 2 business days.</p> },\n    { id: \"returns\", title: \"Returns\", content: <p>Free returns for 30 days.</p> },\n  ]}\n/>;\n```\n\n`type=\"multiple\"` allows several panels open at once. `variant` is `\"separated\"` (default, each\nsection in its own bordered card) or `\"contained\"` (one bordered stack with dividers).\n\n## TagInput\n\nA free-text multi-value field for emails, labels, or keywords. Enter or comma commits the pending\ntext; Backspace on an empty field removes the last tag:\n\n```tsx\nimport { TagInput } from \"@alphinex/ui\";\n\n<TagInput\n  aria-label=\"Recipients\"\n  value={recipients}\n  onChange={setRecipients}\n  placeholder=\"Add an address…\"\n  max={10}\n  validate={(tag) => (tag.includes(\"@\") ? null : \"Enter a valid email address.\")}\n/>;\n```\n\nDuplicates are rejected by default (`allowDuplicates` opts out), and any rejection — duplicate,\n`max` reached, or a `validate` message — is announced via `role=\"alert\"` and marks the field\n`aria-invalid`. `delimiters` customizes which keys commit a tag.\n\n## OtpInput\n\nA segmented one-time-code field. Typing auto-advances, Backspace on an empty slot steps back and\nclears the previous one, and a paste anywhere distributes across the remaining slots — which is\nhow the code actually arrives for most users:\n\n```tsx\nimport { OtpInput } from \"@alphinex/ui\";\n\n<OtpInput length={6} value={code} onChange={setCode} onComplete={(code) => verify(code)} />;\n```\n\n`type` is `\"numeric\"` (default) or `\"alphanumeric\"` and filters both typed and pasted characters.\n`mask` renders filled slots as dots. `onComplete` fires exactly once per completed value.\n\n## Rating\n\nA star rating built on the `slider` pattern rather than a radio group — a slider models a\ncontinuous value natively, which is what `allowHalf` needs, and reads as one value (\"2.5 of 5\")\nto a screen reader instead of ten separate options:\n\n```tsx\nimport { Rating } from \"@alphinex/ui\";\n\n<Rating aria-label=\"Quality\" value={score} onChange={setScore} allowHalf />\n\n<Rating value={4} readOnly />\n```\n\n`readOnly` renders a labelled `img` with no controls, for displaying a score you can't change.\nThe mouse hit-targets are deliberately `aria-hidden` spans, not buttons — nesting interactive\ncontrols inside a slider is an ARIA violation, and the slider already carries the keyboard model.\n\n## ColorPicker\n\nA hex color picker: saturation/brightness area, hue slider, hex entry, and preset swatches. Fully\nhand-rolled — it's HSV↔RGB arithmetic plus pointer math, with no color library:\n\n```tsx\nimport { ColorPicker } from \"@alphinex/ui\";\n\n<ColorPicker value={brand} onChange={setBrand} swatches={[\"#ef4444\", \"#3b82f6\", \"#22c55e\"]} />;\n```\n\nHex entry accepts 3- or 6-digit values with or without the hash, and the round trip is\n**lossless** — the internal HSV state keeps sub-degree hue precision, so typing `#00ff7f` gives\nback `#00ff7f` rather than the `#00ff80` that integer-rounded hue would produce. `swatchesOnly`\nreduces it to swatches plus a hex field. The color math is exported too (`hexToRgb`, `rgbToHsv`,\n`hsvToHex`, `relativeLuminance`, `readableTextColor`) — `readableTextColor` is useful for picking\nforeground text over an arbitrary user-chosen background.\n\n## Combobox / MultiSelect / Autocomplete\n\nThe searchable-selection family, built on one shared state machine (`useComboboxList`). All three\nfollow the WAI-ARIA combobox pattern's **virtual focus** model: DOM focus stays in the text input\nand the active option is tracked via `aria-activedescendant`, rather than the roving-tabindex\napproach `Menu`/`TreeView` use — a combobox has to keep receiving keystrokes while you arrow\nthrough the list.\n\n```tsx\nimport { Combobox, MultiSelect, Autocomplete, type ComboboxOption } from \"@alphinex/ui\";\n\nconst countries: ComboboxOption[] = [\n  { value: \"se\", label: \"Sweden\", group: \"Europe\", keywords: [\"scandinavia\"] },\n  { value: \"jp\", label: \"Japan\", group: \"Asia\" },\n  { value: \"aq\", label: \"Antarctica\", disabled: true },\n];\n\n// Single-select. Requires a selection; unmatched typing is discarded on close.\n<Combobox aria-label=\"Country\" options={countries} value={country} onChange={setCountry} clearable />\n\n// Multi-select. The list stays open so several can be picked in a row.\n<MultiSelect aria-label=\"Visited\" options={countries} value={visited} onChange={setVisited} max={5} />\n\n// Free text with suggestions. The typed text *is* the value; suggestions are a shortcut.\n<Autocomplete aria-label=\"City\" options={countries} value={city} onChange={setCity} />\n```\n\nShared behavior: Arrow keys move the active option (skipping disabled, wrapping), Home/End jump\nto the ends, Enter commits, Escape closes. Opening seeds the first option as active, so typing a\nfilter and pressing Enter picks the top match. Enter is only swallowed when it actually commits\nsomething — a combobox inside a `<form>` won't silently block submission.\n\nOptions support `group` (rendered as headings, ordered by first appearance so re-ranking can't\nscramble them), `keywords` (matched but not displayed), and `description`. Pass `filter` to\nreplace the built-in substring matcher, or `query`/`onQueryChange` + `isLoading` to drive an\nasync fetch. The pure matching logic is exported too — `filterOptions`, `matchesQuery`,\n`groupOptions`, `nextEnabledIndex`.\n\n**Choosing between them:** `Select` for a short static list (it's a native `<select>`, no JS\nkeyboard model at all); `Combobox` when the list is long enough to need filtering; `Autocomplete`\nwhen free text is a valid answer.\n\n## Cascader\n\nA column-per-level drill-down for hierarchical values — region → country → city, or a category\ntree:\n\n```tsx\nimport { Cascader, type CascaderOption } from \"@alphinex/ui\";\n\nconst regions: CascaderOption[] = [\n  {\n    value: \"asia\",\n    label: \"Asia\",\n    children: [{ value: \"jp\", label: \"Japan\", children: [{ value: \"tokyo\", label: \"Tokyo\" }] }],\n  },\n];\n\n<Cascader aria-label=\"Location\" options={regions} value={path} onChange={setPath} />;\n```\n\nThe value is the path array, root-first (`[\"asia\", \"jp\", \"tokyo\"]`). Only a complete path — one\nending on a leaf — commits, unless you pass `allowPartial`. Picking an earlier level drops the\ndeeper segments, and abandoning a half-made selection (Escape, outside click) restores the\nprevious value rather than leaving a partial one. `formatPath`, `columnsForPath`, and\n`isCompletePath` are exported for building your own trail display.\n\n## TreeSelect\n\nA select whose options are a hierarchy, shown all at once in a `TreeView` panel:\n\n```tsx\nimport { TreeSelect, type TreeNode } from \"@alphinex/ui\";\n\n<TreeSelect\n  aria-label=\"Category\"\n  items={categories}\n  value={categoryId}\n  onChange={setCategoryId}\n  defaultExpandedIds={[\"docs\"]}\n  clearable\n/>;\n```\n\nSet `allowBranchSelection={false}` to require a leaf — a branch click then only expands.\n**Versus `Cascader`:** use `TreeSelect` when the shape of the tree is itself informative and the\nuser benefits from seeing it; use `Cascader` for deep-but-narrow data where one level at a time\nreads better.\n\n## Transfer\n\nA dual listbox for moving items between an available set and a chosen set — the pattern for\nassigning permissions, columns, or team members, where a plain multi-select stops scaling:\n\n```tsx\nimport { Transfer } from \"@alphinex/ui\";\n\n<Transfer\n  aria-label=\"Permissions\"\n  options={permissions}\n  value={granted}\n  onChange={setGranted}\n  titles={[\"Available\", \"Granted\"]}\n  searchable\n/>;\n```\n\nThe value is the array of chosen values, ordered by the original `options` order rather than the\norder things were checked. Each pane gets a tri-state select-all (an empty pane reads unchecked,\nnot vacuously checked), and disabled options can never be moved — including via select-all.\n\n## VirtualizedList\n\nRenders only the rows near the viewport, so tens of thousands of items stay responsive:\n\n```tsx\nimport { VirtualizedList } from \"@alphinex/ui\";\n\n<VirtualizedList\n  aria-label=\"Log lines\"\n  items={rows}\n  itemHeight={32}\n  height={400}\n  getKey={(row) => row.id}\n  renderItem={(row) => <LogLine row={row} />}\n/>;\n```\n\n**Fixed row height only.** Variable-height virtualization needs per-row measurement and offset\nbookkeeping that's genuinely easy to get wrong, which is why `@alphinex/table` and\n`@alphinex/calendar` use `@tanstack/react-virtual` for it — keeping that dependency out of\n`@alphinex/ui` matters because every app depends on this package. The windowing math is exported\nas `computeVirtualWindow` and `scrollOffsetForIndex` if you need it directly.\n\nSee [documentation/ARCHITECTURE.md](../../documentation/ARCHITECTURE.md) for the full package contract, dependency rules, and roadmap placement.\n\n## FileUpload\n\nA drop zone plus file picker, with type/size/count validation built in.\n\n```tsx\nimport { FileUpload } from \"@alphinex/ui\";\n\n<FileUpload\n  aria-label=\"Attachments\"\n  multiple\n  accept=\".pdf,.png\"\n  maxSize={5_000_000}\n  maxFiles={5}\n  value={files}\n  onChange={setFiles}\n  onReject={(rejections) => console.warn(rejections)}\n/>;\n```\n\nThe focusable control is the `<input type=\"file\">` itself, hidden inside a `<label>` that wraps\nthe whole zone — so clicking anywhere opens the dialog, and so does focusing it and pressing\nEnter. Drag-and-drop is layered on top, never the only way in.\n\nValidation matters even though the input carries `accept`: the browser applies `accept` to the\n**file dialog** only, and a drag-and-drop bypasses it entirely. Rejections are reported for every\nfailing file, not just the first, and are announced politely.\n\nThe pure validators are exported separately for server-side or pre-flight checks:\n\n```ts\nimport { validateFiles, matchesAccept, fileExtension } from \"@alphinex/ui\";\n\nconst { accepted, rejected } = validateFiles(picked, {\n  accept: \"image/*,.pdf\",\n  maxSize: 5_000_000,\n  maxFiles: 5,\n  existing: alreadyPicked,\n});\n```\n\n`matchesAccept` treats extension and MIME matching as peers rather than one as a fallback,\nbecause operating systems routinely report an empty `type` — for anything unrecognised on Windows,\nand for `.csv` on several platforms.\n\nFor upload queues, a file browser, a gallery or an image cropper, see\n[`@alphinex/files`](../files/README.md).\n\n## Display primitives\n\nSprint 39 added the everyday read-only components. None of them own state beyond what they're\ntold; all of them are theme-token-only.\n\n### Table\n\nA plain semantic `<table>` — **deliberately not a data grid**.\n\n```tsx\nimport { Table } from \"@alphinex/ui\";\n\n<Table\n  caption=\"Recent invoices\"\n  rows={invoices}\n  getRowKey={(row) => row.id}\n  striped\n  columns={[\n    { key: \"invoice\", header: \"Invoice\" },\n    { key: \"amount\", header: \"Amount\", align: \"end\", render: (row) => `$${row.amount}` },\n  ]}\n/>;\n```\n\nNo sorting, filtering, pagination, selection or virtualization — those belong to\n[`@alphinex/table`](../table/README.md), which is built on TanStack Table and costs a much larger\ndependency. Most tables in an app are a static list of rows that needs to look right and be\nreadable; reaching for the DataGrid for that is the mistake this component exists to prevent.\n\nThe `caption` is the table's accessible name and is offered before `aria-label`. Use `hideCaption`\nto keep it for screen readers without painting it.\n\n### List, Descriptions, Timeline\n\n```tsx\n<List items={people} onItemClick={select} header=\"Team\" />\n<Descriptions columns={2} items={[{ label: \"Client\", children: \"Acme\" }]} />\n<Timeline items={events} aria-label=\"Invoice activity\" />\n```\n\n`List` rows become real `<button>`s when `onItemClick` is given, so they focus, activate on Enter\n_and_ Space, and announce themselves without any keyboard code. `trailing` actions stay outside\nthat button, since a button inside a button is invalid.\n\n`Descriptions` renders a real `<dl>`, not a table — these are term/definition pairs, which is what\nassociates a label with its value for a screen reader. Items can `span` multiple columns, and the\nlast item in a row is widened to close a trailing gap.\n\n`Timeline` was generalised out of `@alphinex/business`, where `AuditTimeline` and `PaymentTimeline`\nhad been sharing it privately.\n\n### Statistic\n\n```tsx\n<Statistic label=\"Revenue\" value={128400} prefix=\"$\" delta={12.5} deltaSuffix=\"%\" />\n<Statistic label=\"Churn\" value={2.1} suffix=\"%\" delta={-0.4} invertTrend />\n```\n\n`invertTrend` matters more than it looks: churn, latency, cost and error rate all _improve_ by\ngoing down, and colouring that red is actively misleading. A zero delta reads as flat rather than\nup, so an unchanged figure isn't painted green.\n\n### Result and Spin\n\n```tsx\n<Result status=\"404\" title=\"Page not found\" actions={<Button>Go home</Button>} />\n<Spin spinning={isLoading}>{content}</Spin>\n```\n\n`Result` is for the outcome of something that just happened; `EmptyState` is for a container with\nnothing in it yet. Blocking statuses (`error`, `403`, `500`) announce assertively, the rest\npolitely.\n\n`Spin` is the indeterminate counterpart to `Progress` — reach for `Progress` when you have a real\npercentage. Wrapping content keeps it mounted and dims it rather than swapping it out, so scroll\nposition and focus survive the load. Falls back to a pulse under `prefers-reduced-motion`.\n\n### Segmented\n\n```tsx\n<Segmented\n  aria-label=\"Range\"\n  value={range}\n  onChange={setRange}\n  options={[\n    { value: \"day\", label: \"Day\" },\n    { value: \"week\", label: \"Week\" },\n  ]}\n/>\n```\n\nIt **is** a radiogroup — same role, same \"arrow keys move focus and select\" model, shared with\n`RadioGroup` through `useRovingSelection`. Modelling it as a `tablist` is the tempting mistake:\ntabs switch between visible panels, this sets a value, and a screen-reader user would be sent\nlooking for panel content that doesn't exist.\n\n### Image\n\n```tsx\n<Image src={url} alt=\"A team photo\" fallbackSrc=\"/placeholder.png\" preview />\n```\n\n`alt` is **required**. Pass `alt=\"\"` explicitly for a decorative image — that makes silence a\ndeliberate choice rather than the most common accessibility oversight in an image component.\n\nHandles the loading placeholder, the error fallback (without looping if the fallback fails too),\nand a full-size preview dialog that closes on Escape and restores focus.\n\n### Divider, Space, Flex, Grid\n\n```tsx\n<Divider>OR</Divider>\n<Space split=\"·\"><span>Terms</span><span>Privacy</span></Space>\n<Flex justify=\"between\" gap=\"md\">{children}</Flex>\n<Grid columns={1} sm={2} lg={4} gap=\"md\">{cards}</Grid>\n```\n\n`Grid` takes breakpoints as props because the alternative is every consumer writing the same\n`grid-cols-1 sm:grid-cols-2 lg:grid-cols-4` string and getting the mobile-first order subtly wrong\nsomewhere. It defaults to a single column, so the responsive story starts from the narrow case.\n\n`Space` interleaves `split` separators as siblings rather than wrapping each child, which keeps one\nflex context and so keeps the gaps even. Conditional children can't leave a dangling separator.\n\nThese are intentionally thin. Anything past their handful of props is better written as\n`className` directly — the escape hatch stays first-class (§1.6).\n","readmeFilename":""}