From fdc0b50cc82f3ac0bd27c9e686eeb199bed01ef3 Mon Sep 17 00:00:00 2001 From: Sudha Shrestha Date: Sun, 31 May 2026 23:08:23 +0545 Subject: [PATCH 1/8] sync prod to main (#472) Signed-off-by: dependabot[bot] Co-authored-by: Hari Lamichhane Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sanjana Podduturi <59354376+P-Sanjana@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 Co-authored-by: Moamal Alaa Co-authored-by: Copilot --- app/(main)/_landing/stats-bento.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/(main)/_landing/stats-bento.tsx b/app/(main)/_landing/stats-bento.tsx index 43efdf23..a7885f60 100644 --- a/app/(main)/_landing/stats-bento.tsx +++ b/app/(main)/_landing/stats-bento.tsx @@ -231,20 +231,22 @@ export default function StatsBento() { {/* Category marquees */} -
+ From 1888dabfef77a531302e2e073a3d281999e8068c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 23:37:07 +0545 Subject: [PATCH 2/8] chore(deps): bump next from 16.2.4 to 16.2.6 in the npm_and_yarn group across 1 directory (#473) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 151 +++++++++++++++++++++++++++++-------------------- 2 files changed, 91 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index a6ae9b3a..e114cb99 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "lucide-react": "^0.475.0", "mdast-util-toc": "^7.1.0", "motion": "^12.38.0", - "next": "^16.1.5", + "next": "^16.2.6", "next-mdx-remote": "^6.0.0", "next-themes": "^0.4.6", "nuqs": "^2.8.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31a1ea7e..5da95fdd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,8 +111,8 @@ importers: specifier: ^12.38.0 version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next: - specifier: ^16.1.5 - version: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + specifier: ^16.2.6 + version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next-mdx-remote: specifier: ^6.0.0 version: 6.0.0(@types/react@19.2.14)(react@19.2.5) @@ -121,7 +121,7 @@ importers: version: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) nuqs: specifier: ^2.8.9 - version: 2.8.9(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 2.8.9(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) ora: specifier: ^8.0.1 version: 8.2.0 @@ -209,7 +209,7 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/nextjs': specifier: ^10.2.15 - version: 10.3.5(esbuild@0.17.19)(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19)) + version: 10.3.5(esbuild@0.17.19)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19)) '@storybook/react': specifier: ^10.2.15 version: 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) @@ -281,6 +281,10 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} @@ -368,6 +372,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -830,6 +838,10 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -1804,57 +1816,57 @@ packages: '@types/react': '>=16' react: '>=16' - '@next/env@16.2.4': - resolution: {integrity: sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==} + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/swc-darwin-arm64@16.2.4': - resolution: {integrity: sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==} + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.4': - resolution: {integrity: sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==} + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.4': - resolution: {integrity: sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==} + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.4': - resolution: {integrity: sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==} + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.4': - resolution: {integrity: sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==} + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.4': - resolution: {integrity: sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==} + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.4': - resolution: {integrity: sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==} + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.4': - resolution: {integrity: sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==} + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2693,7 +2705,7 @@ packages: '@tsparticles/basic@3.9.1': resolution: {integrity: sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==} - deprecated: tsParticles v4 is out, please upgrade for better performances and more features + deprecated: Please update to version 4.1.1 '@tsparticles/engine@3.9.1': resolution: {integrity: sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==} @@ -3232,8 +3244,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.21: - resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} engines: {node: '>=6.0.0'} hasBin: true @@ -3322,8 +3334,8 @@ packages: camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - caniuse-lite@1.0.30001790: - resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} @@ -4862,8 +4874,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.2.4: - resolution: {integrity: sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==} + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -5516,6 +5528,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -6189,6 +6206,12 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} '@babel/core@7.29.0': @@ -6322,6 +6345,8 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.6': @@ -6909,6 +6934,8 @@ snapshots: '@babel/runtime@7.29.2': {} + '@babel/runtime@7.29.7': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -7608,30 +7635,30 @@ snapshots: '@types/react': 19.2.14 react: 19.2.5 - '@next/env@16.2.4': {} + '@next/env@16.2.6': {} - '@next/swc-darwin-arm64@16.2.4': + '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-x64@16.2.4': + '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@16.2.4': + '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-musl@16.2.4': + '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-x64-gnu@16.2.4': + '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-musl@16.2.4': + '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@16.2.4': + '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@16.2.4': + '@next/swc-win32-x64-msvc@16.2.6': optional: true '@opentelemetry/api-logs@0.208.0': @@ -8311,7 +8338,7 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))': + '@storybook/nextjs@10.3.5(esbuild@0.17.19)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(type-fest@2.19.0)(typescript@5.9.3)(webpack-hot-middleware@2.26.1)(webpack@5.106.2(esbuild@0.17.19))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) @@ -8335,7 +8362,7 @@ snapshots: css-loader: 6.11.0(webpack@5.106.2(esbuild@0.17.19)) image-size: 2.0.2 loader-utils: 3.3.1 - next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) node-polyfill-webpack-plugin: 2.0.1(webpack@5.106.2(esbuild@0.17.19)) postcss: 8.5.15 postcss-loader: 8.2.1(postcss@8.5.15)(typescript@5.9.3)(webpack@5.106.2(esbuild@0.17.19)) @@ -8508,8 +8535,8 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.29.2 + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -9196,7 +9223,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.21: {} + baseline-browser-mapping@2.10.33: {} big.js@5.2.2: {} @@ -9265,8 +9292,8 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.21 - caniuse-lite: 1.0.30001790 + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 electron-to-chromium: 1.5.344 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -9310,7 +9337,7 @@ snapshots: pascal-case: 3.1.2 tslib: 2.8.1 - caniuse-lite@1.0.30001790: {} + caniuse-lite@1.0.30001793: {} case-sensitive-paths-webpack-plugin@2.4.0: {} @@ -11201,25 +11228,25 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@next/env': 16.2.4 + '@next/env': 16.2.6 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.21 - caniuse-lite: 1.0.30001790 + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 postcss: 8.4.31 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.4 - '@next/swc-darwin-x64': 16.2.4 - '@next/swc-linux-arm64-gnu': 16.2.4 - '@next/swc-linux-arm64-musl': 16.2.4 - '@next/swc-linux-x64-gnu': 16.2.4 - '@next/swc-linux-x64-musl': 16.2.4 - '@next/swc-win32-arm64-msvc': 16.2.4 - '@next/swc-win32-x64-msvc': 16.2.4 + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: @@ -11272,12 +11299,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): + nuqs@2.8.9(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.5 optionalDependencies: - next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) object-inspect@1.13.4: {} @@ -11985,6 +12012,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -12033,7 +12062,7 @@ snapshots: dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.8.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 From 55d364f4a43c95d48d2916312f386309c370e360 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Sun, 31 May 2026 23:40:37 +0545 Subject: [PATCH 3/8] fix(demo): polish picker UI and humanize demo notes (#474) --- app/demo/demo-experience.tsx | 138 +++---- app/demo/library/browse/cinema-row-notes.tsx | 23 +- .../library/footer/footer-wordmark-notes.tsx | 8 +- app/demo/library/hero/launch-shift-notes.tsx | 8 +- .../hero/photographer-portfolio-notes.tsx | 38 +- components/ui/command.tsx | 21 +- package.json | 2 + scripts/validate-registry-install.js | 355 ++++++++++++++++++ 8 files changed, 462 insertions(+), 131 deletions(-) create mode 100644 scripts/validate-registry-install.js diff --git a/app/demo/demo-experience.tsx b/app/demo/demo-experience.tsx index 2cf71024..2955b0cf 100644 --- a/app/demo/demo-experience.tsx +++ b/app/demo/demo-experience.tsx @@ -1,7 +1,9 @@ "use client"; import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; import { + CheckIcon, ChevronLeftIcon, ChevronRightIcon, LayoutGridIcon, @@ -21,7 +23,6 @@ import { Icons } from "@/components/icons"; import { CommandEmpty, CommandGroup, - CommandInput, CommandItem, CommandList, Command as CommandRoot, @@ -554,69 +555,76 @@ function DemoPicker({ return ( - +
-
@@ -980,24 +964,8 @@ function DemoShellStyles() { isolation: isolate; } - .demo-picker-shell::before { - content: ""; - pointer-events: none; - position: absolute; - inset: 0; - border-radius: inherit; - background: - linear-gradient(180deg, rgb(255 255 255 / 0.08), transparent 22%), - radial-gradient(circle at 50% 0%, rgb(255 255 255 / 0.06), transparent 42%); - } - - .demo-picker-search [cmdk-input-wrapper] { - border: 0; - padding: 0; - } - - .demo-picker-search [cmdk-input-wrapper] svg { - display: none; + .demo-picker-group + .demo-picker-group { + border-top: 1px solid rgb(255 255 255 / 0.05); } .demo-picker-list { @@ -1016,14 +984,10 @@ function DemoShellStyles() { .demo-picker-item { display: flex; - align-items: flex-start; + align-items: center; gap: 0.625rem; } - .demo-picker-item-live { - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); - } - .demo-chrome-pill { min-height: 2.75rem; } diff --git a/app/demo/library/browse/cinema-row-notes.tsx b/app/demo/library/browse/cinema-row-notes.tsx index 179e38fa..06344dc4 100644 --- a/app/demo/library/browse/cinema-row-notes.tsx +++ b/app/demo/library/browse/cinema-row-notes.tsx @@ -72,16 +72,15 @@ export function CinemaRowNotes() { id="demo-notes-title" eyebrow="Recipe" title="Stream browse" - description="Apple TV-style layout: gradient hero with staggered premiere copy, a scrolling poster rail, then a craft panel with twin vertical still columns. TMDB art — demo only." + description="Apple TV-style page: gradient hero, scrolling poster rail, craft panel with two vertical still columns. TMDB posters, demo only." />

- Three bands on one page. The hero is type on a violet radial scrim — eyebrow, title, - tagline, runtime, and CTAs stagger in after load. Below that, premieres scroll - horizontally. The craft panel pairs short copy with two still columns drifting in - opposite directions. + Three bands stacked on one page. Hero copy sits on a violet radial scrim. Eyebrow, + title, tagline, runtime, and CTAs stagger in after load. Premieres scroll sideways + underneath. The craft panel is short copy plus two still columns moving opposite ways.

@@ -91,9 +90,9 @@ export function CinemaRowNotes() {

WaveReveal (mode="word") on the hero title.{" "} Marquee on the premieres rail and both still columns ( - pauseOnHover, applyMask={"{false}"} — local masks instead). - Hero rise/fall and delay math live in the demo file as cinema-hero-rise{" "} - plus heroSequenceDelays(). + pauseOnHover, applyMask={"{false}"}; we use local masks + instead). Hero rise/fall and delay math live in the demo file as{" "} + cinema-hero-rise plus heroSequenceDelays().

@@ -102,9 +101,9 @@ export function CinemaRowNotes() {

- heroSequenceDelays() derives every entrance offset from the title word - count and WaveReveal timing — eyebrow through the premieres rail share one - sequence. cinema-hero-rise is a local keyframe (blur +{" "} + heroSequenceDelays() works out every entrance offset from the title word + count and WaveReveal timing. Eyebrow through the premieres rail all share + one sequence. cinema-hero-rise is a local keyframe (blur +{" "} translateY) on everything except the title words.

@@ -128,7 +127,7 @@ export function CinemaRowNotes() { {HERO_SEQUENCE_SNIPPET} - + {PREMIERES_SNIPPET} {STILLS_SNIPPET} diff --git a/app/demo/library/footer/footer-wordmark-notes.tsx b/app/demo/library/footer/footer-wordmark-notes.tsx index dae320c0..425d28fa 100644 --- a/app/demo/library/footer/footer-wordmark-notes.tsx +++ b/app/demo/library/footer/footer-wordmark-notes.tsx @@ -43,7 +43,7 @@ export function FooterWordmarkNotes() { The type sits in the field. It doesn't float on top like a heading.

- Not a clone — same hierarchy. One blue family in oklch, Syne 800 on the + Not a clone, but same hierarchy. One blue family in oklch, Syne 800 on the wordmark, IBM Plex on the links. Lucien uses a static texture; we put boids behind the gradient at low opacity instead.

@@ -52,7 +52,7 @@ export function FooterWordmarkNotes() { -

Two Animata pieces — boids and SiblingFocusNav. The rest is layout and CSS.

+

Two Animata pieces: boids and SiblingFocusNav. The rest is layout and CSS.

@@ -66,7 +66,7 @@ export function FooterWordmarkNotes() {

The wordmark is SVG <text>, not a styled heading. - That's the trick for edge-to-edge scaling — no clipping, no fighting{" "} + That's what makes edge-to-edge scaling work. No clipping, no fighting{" "} clamp() on a single HTML line.

@@ -90,7 +90,7 @@ export function FooterWordmarkNotes() {

- Pulled from the demo file at build time. Copy what you need below — component docs are + Pulled from the demo file at build time. Copy what you need below. Component docs are linked above.

diff --git a/app/demo/library/hero/launch-shift-notes.tsx b/app/demo/library/hero/launch-shift-notes.tsx index 3394c0f2..55633749 100644 --- a/app/demo/library/hero/launch-shift-notes.tsx +++ b/app/demo/library/hero/launch-shift-notes.tsx @@ -30,8 +30,8 @@ export function LaunchShiftNotes() { @@ -47,8 +47,8 @@ export function LaunchShiftNotes() {

Black field, warm off-white body text, violet on the badge. Headline fades white to - gray, two lines so the gradient doesn't muddy the second row. Boids behind a - vignette for edge motion. + gray, split across two lines so the gradient doesn't muddy the second row. Boids + behind a vignette for edge motion.

diff --git a/app/demo/library/hero/photographer-portfolio-notes.tsx b/app/demo/library/hero/photographer-portfolio-notes.tsx index 65d3ab05..8e9ff783 100644 --- a/app/demo/library/hero/photographer-portfolio-notes.tsx +++ b/app/demo/library/hero/photographer-portfolio-notes.tsx @@ -14,7 +14,7 @@ const TRAIL_EXCLUDE_SNIPPET = ``; -const LAYOUT_SNIPPET = `{/* mobile: column stack · desktop: 2:3 row, both pinned to bottom */} +const LAYOUT_SNIPPET = `{/* mobile: column stack; desktop: 2:3 row, both pinned to bottom */}
{/* hero */}
@@ -59,15 +59,15 @@ export function PhotographerPortfolioNotes() {

- Fake site for Maya, a wedding photographer. White page, black type, not much else — name - and contact in the bottom-left, a tall stack of prints on the right. On phone the stack - goes full width; on desktop it sits on the right edge. Wedding stills from Lummi follow - the cursor behind everything. + Fake site for Maya, a wedding photographer. White page, black type, not much else. Name + and contact sit in the bottom-left; a tall stack of prints fills the right. On phone the + stack goes full width; on desktop it hugs the right edge. Wedding stills from Lummi + follow the cursor behind everything.

The first thing you see is not the page. SplitReveal covers the screen, loads every image, and ticks progress on the center seam. When the batch finishes, the - halves slide apart. The layout was already there — just hidden. + halves slide apart. The layout was already there, just hidden.

Whichever print is on top gets corner brackets, like a viewfinder. The rest are plain @@ -79,14 +79,14 @@ export function PhotographerPortfolioNotes() {

- SplitReveal sits next to the page as a fixed overlay — pass{" "} + SplitReveal sits next to the page as a fixed overlay. Pass{" "} images, wire onComplete, done. We skipped the{" "} .Content wrapper on purpose; the real layout mounts normally underneath.

CardStack advances the portfolio. Autoplay only starts after the preloader clears. PrintCaption mirrors the top frame with fake shutter metadata. - Camera and map pin icons are Lucide Animated — they only move on hover so the hero stays + Camera and map pin icons are Lucide Animated. They only move on hover so the hero stays quiet.

@@ -100,8 +100,8 @@ export function PhotographerPortfolioNotes() {

- All image URLs live in LUMMI_ASSETS — six stack frames, twelve for trails, - one avatar — cropped through a shared lummi() helper.{" "} + All image URLs live in LUMMI_ASSETS: six stack frames, twelve for trails, + one avatar. All cropped through a shared lummi() helper.{" "} PRELOAD_IMAGES dedupes before SplitReveal sees them.

@@ -112,26 +112,26 @@ export function PhotographerPortfolioNotes() {

Print width was the annoying part. We tried JS measurement first; on desktop the caption and stack kept drifting out of sync. Switched to a size container on the print column - and capped width with min(100cqw, calc((100cqh - 4.5rem) * 8 / 11)) — 4:5 - frame plus a 10% peek strip (aspect-[8/1]) that fits the viewport height. - No ResizeObserver. + and capped width with min(100cqw, calc((100cqh - 4.5rem) * 8 / 11)) for a + 4:5 frame plus a 10% peek strip (aspect-[8/1]) that fits the viewport + height. No ResizeObserver.

Caption had a white fill at one point. It fought the page background and made the - metadata feel like a sticker — dropped it; caption and page share the same canvas now. + metadata feel like a sticker. Dropped it; caption and page share the same canvas now.

- For trails we first excluded the whole stack. Looked too clean — nothing ever crossed - the prints. Narrowed it to the hero and caption wrappers only. + For trails we first excluded the whole stack. Looked too clean; nothing ever crossed the + prints. Narrowed it to the hero and caption wrappers only.

- + {LAYOUT_SNIPPET} {PRELOADER_SNIPPET} - + {TRAIL_EXCLUDE_SNIPPET}
@@ -139,7 +139,7 @@ export function PhotographerPortfolioNotes() {

- Photos from Lummi — wedding and event searches. IDs + Photos from Lummi, wedding and event searches. IDs are in LUMMI_ASSETS. Fine for demos; use your own shots for anything real.

diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 2cecd910..0bc19908 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -35,16 +35,27 @@ const CommandDialog = ({ children, ...props }: DialogProps) => { ) } +interface CommandInputProps + extends React.ComponentPropsWithoutRef { + wrapperClassName?: string + hideIcon?: boolean +} + const CommandInput = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -

- + CommandInputProps +>(({ className, wrapperClassName, hideIcon, ...props }, ref) => ( +
+ {hideIcon ? null : ( + + )} { + const local = registryUrlToLocalPath(dep); + if (!local) return dep; + if (!fs.existsSync(local)) { + error( + `missing local registry dependency for ${path.relative(REGISTRY_DIR, itemPath)}: ${dep}`, + ); + return dep; + } + return local; + }); + + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-registry-")); + const outPath = path.join(tmpDir, path.basename(itemPath)); + fs.writeFileSync(outPath, `${JSON.stringify(localized, null, 2)}\n`); + return outPath; +} + +function validateRegistryJsonStatic(jsonPath) { + const rel = path.relative(REGISTRY_DIR, jsonPath); + let item; + + try { + item = JSON.parse(fs.readFileSync(jsonPath, "utf8")); + } catch { + error(`${rel}: invalid JSON`); + return; + } + + if (!item.name || !item.type) { + error(`${rel}: missing name or type`); + } + + if (!Array.isArray(item.files) || item.files.length === 0) { + error(`${rel}: files array is empty`); + return; + } + + const paths = new Set(); + for (const file of item.files) { + if ( + !file.path || + !file.type || + typeof file.content !== "string" || + file.content.trim() === "" + ) { + error(`${rel}: file entry missing path, type, or content`); + continue; + } + if (paths.has(file.path)) { + error(`${rel}: duplicate file path ${file.path}`); + } + paths.add(file.path); + } + + for (const dep of item.registryDependencies ?? []) { + const local = registryUrlToLocalPath(dep); + if (local && !fs.existsSync(local)) { + error(`${rel}: registryDependency not found locally — ${dep}`); + } + } +} + +function createShadcnWorkspace() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "animata-shadcn-")); + + fs.writeFileSync( + path.join(dir, "package.json"), + `${JSON.stringify({ name: "animata-registry-install-test", private: true, type: "module", version: "0.0.0" }, null, 2)}\n`, + ); + + fs.writeFileSync( + path.join(dir, "tsconfig.json"), + `${JSON.stringify( + { + compilerOptions: { + target: "ES2017", + lib: ["dom", "dom.iterable", "esnext"], + allowJs: true, + skipLibCheck: true, + strict: true, + noEmit: true, + esModuleInterop: true, + module: "esnext", + moduleResolution: "bundler", + jsx: "preserve", + paths: { "@/*": ["./*"] }, + }, + include: ["**/*.ts", "**/*.tsx"], + }, + null, + 2, + )}\n`, + ); + + fs.mkdirSync(path.join(dir, "app"), { recursive: true }); + fs.mkdirSync(path.join(dir, "lib"), { recursive: true }); + fs.writeFileSync(path.join(dir, "app", "globals.css"), '@import "tailwindcss";\n'); + fs.writeFileSync( + path.join(dir, "lib", "utils.ts"), + 'export function cn(...inputs: Array) {\n return inputs.filter(Boolean).join(" ");\n}\n', + ); + + fs.writeFileSync( + path.join(dir, "components.json"), + `${JSON.stringify( + { + $schema: "https://ui.shadcn.com/schema.json", + style: "new-york", + rsc: true, + tsx: true, + tailwind: { + css: "app/globals.css", + baseColor: "stone", + cssVariables: true, + prefix: "", + }, + aliases: { + components: "@/components", + utils: "@/lib/utils", + }, + }, + null, + 2, + )}\n`, + ); + + return dir; +} + +function parseDryRunOutput(output) { + const files = []; + const deps = []; + let inDeps = false; + + for (const line of output.split("\n")) { + const fileMatch = line.match(/^│ \+ (.+?) {2}create$/); + if (fileMatch) { + files.push(fileMatch[1].trim()); + continue; + } + + if (line.includes("├ Dependencies")) { + inDeps = true; + continue; + } + + if (inDeps) { + const depMatch = line.match(/^│ \+ (.+)$/); + if (depMatch) { + deps.push(depMatch[1].trim().split("@")[0]); + continue; + } + if (line.includes("├ Files") || line.includes("│ ") || line.startsWith("└")) { + inDeps = false; + } + } + } + + return { files, deps }; +} + +function runShadcnDryRun(workspaceDir, registryItemPath) { + const command = `npx shadcn@latest add -y --dry-run ${JSON.stringify(registryItemPath)}`; + + try { + const output = execSync(command, { + cwd: workspaceDir, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + timeout: 120_000, + env: { ...process.env, CI: "true" }, + }); + return { ok: true, output }; + } catch (err) { + const stdout = err.stdout?.toString?.() ?? ""; + const stderr = err.stderr?.toString?.() ?? ""; + return { ok: false, output: `${stdout}\n${stderr}`.trim() }; + } +} + +function assertFixtureInstall(fixture, workspaceDir) { + const itemPath = path.join(REGISTRY_DIR, fixture.item); + if (!fs.existsSync(itemPath)) { + error(`fixture ${fixture.id}: missing ${fixture.item}`); + return; + } + + const installPath = fixture.localizeDeps ? localizeRegistryItem(itemPath) : itemPath; + const result = runShadcnDryRun(workspaceDir, installPath); + + if (!result.ok) { + error(`fixture ${fixture.id} (${fixture.label}): shadcn dry-run failed\n${result.output}`); + return; + } + + const { files, deps } = parseDryRunOutput(result.output); + + for (const expected of fixture.expectFiles) { + if (!files.includes(expected)) { + error( + `fixture ${fixture.id}: expected file "${expected}" in dry-run output. Got: ${files.join(", ") || "(none)"}`, + ); + } + } + + for (const expectedDep of fixture.expectDeps) { + if (!deps.includes(expectedDep)) { + error( + `fixture ${fixture.id}: expected npm dep "${expectedDep}". Got: ${deps.join(", ") || "(none)"}`, + ); + } + } + + info(`fixture ${fixture.id} OK — ${files.length} file(s), ${deps.length} npm dep(s)`); +} + +function main() { + if (process.argv.includes("--build")) { + execSync("node ./scripts/build-registry.js", { cwd: ROOT, stdio: "inherit" }); + } + + if (!fs.existsSync(REGISTRY_DIR)) { + error(`registry output missing at ${REGISTRY_DIR} — run node scripts/build-registry.js first`); + process.exit(1); + } + + const registryFiles = walkRegistryJson(REGISTRY_DIR); + if (registryFiles.length === 0) { + error("no registry JSON files found"); + process.exit(1); + } + + info(`static validation — ${registryFiles.length} item(s)`); + for (const jsonPath of registryFiles) { + validateRegistryJsonStatic(jsonPath); + } + + if (failed) { + process.exit(1); + } + + info(`shadcn dry-run — ${INSTALL_FIXTURES.length} fixture(s)`); + const workspaceDir = createShadcnWorkspace(); + + try { + for (const fixture of INSTALL_FIXTURES) { + assertFixtureInstall(fixture, workspaceDir); + } + } finally { + fs.rmSync(workspaceDir, { recursive: true, force: true }); + } + + if (failed) { + process.exit(1); + } + + info( + `OK — ${registryFiles.length} registry item(s), ${INSTALL_FIXTURES.length} install fixture(s)`, + ); +} + +main(); From bde69a3211acf472736b701d88d867c3b91e879f Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Mon, 1 Jun 2026 12:03:39 +0545 Subject: [PATCH 4/8] fix(demo): hero layouts on iPhone Safari (#476) --- app/demo/generated/demo-sources.ts | 78 +++++++++---------- app/demo/library/hero/launch-shift.tsx | 12 +-- .../library/hero/photographer-portfolio.tsx | 14 ++-- 3 files changed, 48 insertions(+), 56 deletions(-) diff --git a/app/demo/generated/demo-sources.ts b/app/demo/generated/demo-sources.ts index 7beb294d..981ad449 100644 --- a/app/demo/generated/demo-sources.ts +++ b/app/demo/generated/demo-sources.ts @@ -642,17 +642,11 @@ export default function LaunchShift() { Superhuman -

- +

+ Superpowers, - + everywhere you work.

@@ -753,17 +747,11 @@ export default function LaunchShift() { Superhuman </span> - <h1 className="mt-4 max-w-[16ch] text-balance text-[clamp(2.35rem,6.8vw,3.75rem)] font-semibold tracking-[-0.045em]"> - <span - style={{ textBoxTrim: "trim-both" }} - className="block bg-linear-to-b leading-none from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent" - > + <h1 className="mt-5 max-w-[16ch] text-balance text-[clamp(2.35rem,6.8vw,3.75rem)] font-semibold leading-[1.05] tracking-[-0.045em]"> + <span className="block bg-linear-to-b from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent"> Superpowers, </span> - <span - style={{ textBoxTrim: "trim-both" }} - className="-mt-5 block bg-linear-to-b leading-none from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent" - > + <span className="mt-1 block bg-linear-to-b from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent sm:mt-0 sm:-mt-3 md:-mt-4"> everywhere you work. </span> </h1> @@ -864,17 +852,11 @@ export default function LaunchShift() { Superhuman </span> - <h1 className="mt-4 max-w-[16ch] text-balance text-[clamp(2.35rem,6.8vw,3.75rem)] font-semibold tracking-[-0.045em]"> - <span - style={{ textBoxTrim: "trim-both" }} - className="block bg-linear-to-b leading-none from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent" - > + <h1 className="mt-5 max-w-[16ch] text-balance text-[clamp(2.35rem,6.8vw,3.75rem)] font-semibold leading-[1.05] tracking-[-0.045em]"> + <span className="block bg-linear-to-b from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent"> Superpowers, </span> - <span - style={{ textBoxTrim: "trim-both" }} - className="-mt-5 block bg-linear-to-b leading-none from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent" - > + <span className="mt-1 block bg-linear-to-b from-white via-[#F5F5F3] to-[#A8A8A6] bg-clip-text text-transparent sm:mt-0 sm:-mt-3 md:-mt-4"> everywhere you work. </span> </h1> @@ -2811,7 +2793,7 @@ function PrintStack() { - + {(item, index, layer) => (

-
+
@@ -2904,7 +2890,7 @@ export default function PhotographerPortfolio() { return ( <>
<CardStack.Frame className="absolute inset-0 overflow-visible"> <CardStack.LiveRegion /> <CardStack.Trigger aria-label="Show next photo" className="absolute inset-0 block text-left"> - <CardStack.Viewport className="size-full overflow-visible !min-h-0 !pt-0"> + <CardStack.Viewport className="absolute inset-0 overflow-visible !min-h-0 pt-[14%] sm:pt-0"> <CardStack.List> {(item, index, layer) => ( <CardStack.Card @@ -3255,8 +3241,12 @@ export default function PhotographerPortfolio() { </div> <div aria-hidden="true" className={cn("w-full shrink-0", STACK_PEEK)} /> - <div className="relative aspect-[4/5] w-full shrink-0 overflow-visible"> - <PrintStack /> + <div className="relative w-full shrink-0"> + <div className="relative w-full overflow-visible pb-[125%] sm:aspect-[4/5] sm:pb-0"> + <div className="absolute inset-0 overflow-visible"> + <PrintStack /> + </div> + </div> </div> </div> </figure> @@ -3282,7 +3272,7 @@ export default function PhotographerPortfolio() { </div> </div> - <div className="w-full min-w-0 md:flex md:min-h-0 md:flex-[3] md:basis-0 md:flex-col md:justify-end md:@container/print md:[container-type:size]"> + <div className="w-full min-w-0 shrink-0 md:flex md:min-h-0 md:flex-[3] md:basis-0 md:flex-col md:justify-end md:@container/print md:[container-type:size]"> <PrintProof stackRef={stackRef} captionRef={captionRef} /> </div> </div> @@ -3298,7 +3288,7 @@ export default function PhotographerPortfolio() { return ( <> <section - className="relative isolate min-h-svh overflow-x-hidden overflow-y-auto md:h-svh md:overflow-hidden" + className="relative isolate min-h-svh overflow-x-hidden md:h-svh md:overflow-hidden" style={{ backgroundColor: CANVAS, color: INK, fontFamily: FONT }} > <TrailingImage @@ -3599,7 +3589,7 @@ export default function PhotographerPortfolio() { <CardStack.Frame className="absolute inset-0 overflow-visible"> <CardStack.LiveRegion /> <CardStack.Trigger aria-label="Show next photo" className="absolute inset-0 block text-left"> - <CardStack.Viewport className="size-full overflow-visible !min-h-0 !pt-0"> + <CardStack.Viewport className="absolute inset-0 overflow-visible !min-h-0 pt-[14%] sm:pt-0"> <CardStack.List> {(item, index, layer) => ( <CardStack.Card @@ -3649,8 +3639,12 @@ export default function PhotographerPortfolio() { </div> <div aria-hidden="true" className={cn("w-full shrink-0", STACK_PEEK)} /> - <div className="relative aspect-[4/5] w-full shrink-0 overflow-visible"> - <PrintStack /> + <div className="relative w-full shrink-0"> + <div className="relative w-full overflow-visible pb-[125%] sm:aspect-[4/5] sm:pb-0"> + <div className="absolute inset-0 overflow-visible"> + <PrintStack /> + </div> + </div> </div> </div> </figure> @@ -3676,7 +3670,7 @@ export default function PhotographerPortfolio() { </div> </div> - <div className="w-full min-w-0 md:flex md:min-h-0 md:flex-[3] md:basis-0 md:flex-col md:justify-end md:@container/print md:[container-type:size]"> + <div className="w-full min-w-0 shrink-0 md:flex md:min-h-0 md:flex-[3] md:basis-0 md:flex-col md:justify-end md:@container/print md:[container-type:size]"> <PrintProof stackRef={stackRef} captionRef={captionRef} /> </div> </div> @@ -3692,7 +3686,7 @@ export default function PhotographerPortfolio() { return ( <> <section - className="relative isolate min-h-svh overflow-x-hidden overflow-y-auto md:h-svh md:overflow-hidden" + className="relative isolate min-h-svh overflow-x-hidden md:h-svh md:overflow-hidden" style={{ backgroundColor: CANVAS, color: INK, fontFamily: FONT }} > <TrailingImage diff --git a/app/demo/library/hero/launch-shift.tsx b/app/demo/library/hero/launch-shift.tsx index f1e6d946..b00d1241 100644 --- a/app/demo/library/hero/launch-shift.tsx +++ b/app/demo/library/hero/launch-shift.tsx @@ -66,17 +66,11 @@ export default function LaunchShift() { Superhuman -

- +

+ Superpowers, - + everywhere you work.

diff --git a/app/demo/library/hero/photographer-portfolio.tsx b/app/demo/library/hero/photographer-portfolio.tsx index 6215abec..1ec85309 100644 --- a/app/demo/library/hero/photographer-portfolio.tsx +++ b/app/demo/library/hero/photographer-portfolio.tsx @@ -247,7 +247,7 @@ function PrintStack() { - + {(item, index, layer) => (

-
+
@@ -340,7 +344,7 @@ export default function PhotographerPortfolio() { return ( <>
Date: Mon, 1 Jun 2026 14:18:54 +0545 Subject: [PATCH 5/8] fix(demo): card stack collapsing in WebKit on photographer portfolio (#478) --- app/demo/generated/demo-sources.ts | 6 +++--- app/demo/library/hero/photographer-portfolio.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/demo/generated/demo-sources.ts b/app/demo/generated/demo-sources.ts index 981ad449..d84fc855 100644 --- a/app/demo/generated/demo-sources.ts +++ b/app/demo/generated/demo-sources.ts @@ -2802,7 +2802,7 @@ function PrintStack() { stackIndex={index} className="!inset-x-0 !top-0 !h-fit !w-full gap-0 overflow-visible rounded-none !bg-transparent p-0 shadow-none ring-0" > -
+
{\`\${PHOTOGRAPHER.studio} stackIndex={index} className="!inset-x-0 !top-0 !h-fit !w-full gap-0 overflow-visible rounded-none !bg-transparent p-0 shadow-none ring-0" > - <figure className="relative aspect-[4/5] size-full overflow-hidden bg-black/5"> + <figure className="relative aspect-[4/5] w-full overflow-hidden bg-black/5"> <img src={item.image} alt={\`\${PHOTOGRAPHER.studio} — \${item.title}\`} @@ -3598,7 +3598,7 @@ export default function PhotographerPortfolio() { stackIndex={index} className="!inset-x-0 !top-0 !h-fit !w-full gap-0 overflow-visible rounded-none !bg-transparent p-0 shadow-none ring-0" > - <figure className="relative aspect-[4/5] size-full overflow-hidden bg-black/5"> + <figure className="relative aspect-[4/5] w-full overflow-hidden bg-black/5"> <img src={item.image} alt={\`\${PHOTOGRAPHER.studio} — \${item.title}\`} diff --git a/app/demo/library/hero/photographer-portfolio.tsx b/app/demo/library/hero/photographer-portfolio.tsx index 1ec85309..876a4e2e 100644 --- a/app/demo/library/hero/photographer-portfolio.tsx +++ b/app/demo/library/hero/photographer-portfolio.tsx @@ -256,7 +256,7 @@ function PrintStack() { stackIndex={index} className="!inset-x-0 !top-0 !h-fit !w-full gap-0 overflow-visible rounded-none !bg-transparent p-0 shadow-none ring-0" > -
+
{`${PHOTOGRAPHER.studio} Date: Mon, 1 Jun 2026 23:49:15 +0545 Subject: [PATCH 6/8] feat(widget): improve typography (#479) --- animata/card/card-spread.tsx | 157 ++++++++++---- animata/widget/alarm-clock.stories.tsx | 22 +- animata/widget/alarm-clock.tsx | 116 ++++++---- animata/widget/battery.tsx | 45 ++-- animata/widget/calendar-event.stories.tsx | 36 +++- animata/widget/calendar-event.tsx | 201 +++++++++--------- animata/widget/calorie-counter.stories.tsx | 12 +- animata/widget/calorie-counter.tsx | 91 +++++--- animata/widget/delivery-card.stories.tsx | 28 ++- animata/widget/delivery-card.tsx | 113 ++++++---- animata/widget/flight-widget.tsx | 2 +- animata/widget/fund-widget.tsx | 15 +- animata/widget/music-widget.tsx | 161 +++++++------- animata/widget/notes.stories.tsx | 10 +- animata/widget/notes.tsx | 53 ++++- animata/widget/reminder-widget.stories.tsx | 14 +- animata/widget/reminder-widget.tsx | 129 +++++++---- animata/widget/reminder.stories.tsx | 27 ++- animata/widget/reminder.tsx | 63 ++++-- animata/widget/score-board.stories.tsx | 4 +- animata/widget/score-board.tsx | 135 ++++++------ animata/widget/security-alert.tsx | 66 ++++-- animata/widget/shopping-list.stories.tsx | 10 +- animata/widget/shopping-list.tsx | 156 ++++++++------ animata/widget/study-timer.stories.tsx | 4 +- animata/widget/study-timer.tsx | 113 +++++----- animata/widget/vpn-widget.stories.tsx | 43 ++-- animata/widget/vpn-widget.tsx | 142 +++++++------ animata/widget/water-tracker.stories.tsx | 21 +- animata/widget/water-tracker.tsx | 122 ++++++----- animata/widget/weather-card.stories.tsx | 27 ++- animata/widget/weather-card.tsx | 65 ++++-- animata/widget/weekly-progress.tsx | 66 ++++-- components/component-list-item.tsx | 8 +- config/docs.ts | 1 + content/docs/card/card-spread.mdx | 17 +- content/docs/changelog/2026-06.mdx | 68 ++++++ content/docs/changelog/index.mdx | 9 +- content/docs/contributing/components.mdx | 20 ++ content/docs/widget/alarm-clock.mdx | 8 +- content/docs/widget/calendar-event.mdx | 18 +- content/docs/widget/calendar-widget.mdx | 1 + content/docs/widget/delivery-card.mdx | 9 +- content/docs/widget/index.mdx | 9 + .../docs/widget/music-stack-interaction.mdx | 1 + content/docs/widget/reminder.mdx | 9 +- content/docs/widget/team-clock.mdx | 1 + content/docs/widget/video-chat.mdx | 1 + content/docs/widget/vpn-widget.mdx | 11 +- content/docs/widget/water-tracker.mdx | 9 +- content/docs/widget/weather-card.mdx | 13 +- hooks/use-mobile.tsx | 19 ++ lib/inline-code.ts | 4 +- 53 files changed, 1580 insertions(+), 925 deletions(-) create mode 100644 content/docs/changelog/2026-06.mdx create mode 100644 hooks/use-mobile.tsx diff --git a/animata/card/card-spread.tsx b/animata/card/card-spread.tsx index 354d1dd8..683fe13b 100644 --- a/animata/card/card-spread.tsx +++ b/animata/card/card-spread.tsx @@ -1,17 +1,27 @@ +"use client"; + import { useState } from "react"; import Notes, { NotesCard } from "@/animata/widget/notes"; import ShoppingList from "@/animata/widget/shopping-list"; import { cn } from "@/lib/utils"; +/** w-48 + gap-3 — horizontal step between spread slots */ +const CARD_STEP = "12.75rem"; + +/** 4× w-48 + 3× gap-3 */ +const SPREAD_WIDTH = "calc(4 * 12rem + 3 * 0.75rem)"; + +const DECK_TRANSFORM = "translateX(0) rotate(0deg)"; + function Reminders() { return ( ); @@ -28,66 +38,121 @@ function RemodelNotes() { ); } +/** Outer layer: hover peek only (ease-out, not ease-cc). */ +const hoverPeekClasses = [ + "group-hover/stack:-translate-y-1 group-hover/stack:-rotate-[1deg]", + "group-hover/stack:-translate-y-2 group-hover/stack:rotate-[2deg]", + "group-hover/stack:-translate-y-3 group-hover/stack:rotate-[3deg]", + "group-hover/stack:-translate-y-4 group-hover/stack:rotate-[4deg]", +] as const; + const cards = [ { component: Notes, - rotationClass: "", - revealClass: "-rotate-[2deg]", + spreadTransform: `translateX(0) rotate(-2deg)`, }, { component: ShoppingList, - rotationClass: "group-hover/spread:rotate-[15deg]", - revealClass: "rotate-[3deg] translate-y-2", + spreadTransform: `translateX(${CARD_STEP}) translateY(0.5rem) rotate(3deg)`, }, - { component: RemodelNotes, - rotationClass: "group-hover/spread:rotate-[30deg]", - revealClass: "-rotate-[2deg] translate-x-1", + spreadTransform: `translateX(calc(2 * ${CARD_STEP})) rotate(-2deg) translateX(0.25rem)`, }, - { component: Reminders, - rotationClass: "group-hover/spread:rotate-[45deg]", - revealClass: "rotate-[2deg]", + spreadTransform: `translateX(calc(3 * ${CARD_STEP})) rotate(2deg)`, }, -]; +] as const; + +const zIndex = ["z-10", "z-20", "z-30", "z-40"] as const; export default function CardSpread() { const [isExpanded, setExpanded] = useState(false); return ( -
- {cards.map((item, index) => { - return ( -
{ - setExpanded(!isExpanded); - e.preventDefault(); - }} - className={cn( - "transition-all duration-500 ease-in-out", - { - absolute: !isExpanded, - "origin-bottom": !isExpanded, - }, - !isExpanded && item.rotationClass, - isExpanded && item.revealClass, - )} - > - -
- ); - })} +
+ + +
setExpanded((open) => !open)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + setExpanded((open) => !open); + } + }} + className={cn( + "card-spread-stage group/stack relative h-64 outline-none focus-visible:ring-2 focus-visible:ring-[#ffcc00] focus-visible:ring-offset-2 focus-visible:ring-offset-background", + isExpanded + ? "card-spread-stage--expand cursor-default" + : "card-spread-stage--collapse w-48 cursor-pointer", + )} + style={isExpanded ? { width: SPREAD_WIDTH } : undefined} + > + {cards.map((item, index) => { + const Card = item.component; + return ( +
+
+ +
+
+ ); + })} +
); } diff --git a/animata/widget/alarm-clock.stories.tsx b/animata/widget/alarm-clock.stories.tsx index 04420394..3e1a8974 100644 --- a/animata/widget/alarm-clock.stories.tsx +++ b/animata/widget/alarm-clock.stories.tsx @@ -4,22 +4,28 @@ import AlarmClock from "@/animata/widget/alarm-clock"; const meta = { title: "Widget/Alarm Clock", component: AlarmClock, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const WeekdayRoutine: Story = { + name: "Weekday routine", args: { alarms: [ - { id: 0, time: "7:30 AM", repetition: "Once" }, - { id: 1, time: "8:00 AM", repetition: "Daily" }, - { id: 2, time: "9:00 AM", repetition: "Weekdays" }, + { id: "weekday", time: "6:45 AM", schedule: "Weekdays" }, + { id: "gym", time: "7:15 AM", schedule: "Mon, Wed, Fri" }, ], + defaultEnabledIds: ["weekday"], + }, +}; + +export const WeekendSleepIn: Story = { + name: "Weekend sleep in", + args: { + alarms: [{ id: "weekend", time: "9:30 AM", schedule: "Sat & Sun" }], + defaultEnabledIds: [], }, }; diff --git a/animata/widget/alarm-clock.tsx b/animata/widget/alarm-clock.tsx index 9593bfd8..1c651059 100644 --- a/animata/widget/alarm-clock.tsx +++ b/animata/widget/alarm-clock.tsx @@ -6,61 +6,85 @@ import { useState } from "react"; import ToggleSwitch from "@/animata/button/toggle-switch"; import { cn } from "@/lib/utils"; -interface AlarmProps { - alarms: { id?: number; time: string; repetition: string }[]; -} +export type AlarmItem = { + id: string | number; + time: string; + schedule: string; +}; + +export type AlarmClockProps = { + className?: string; + alarms?: AlarmItem[]; + defaultEnabledIds?: Array; +}; + +const DEFAULT_ALARMS: AlarmItem[] = [ + { id: "weekday", time: "7:30 AM", schedule: "Weekdays" }, + { id: "weekend", time: "9:00 AM", schedule: "Sat & Sun" }, +]; export default function AlarmClock({ - // Placeholder data - alarms = [ - { id: 0, time: "7:30 AM", repetition: "Once" }, - { id: 1, time: "8:00 AM", repetition: "Daily" }, - { id: 2, time: "9:00 AM", repetition: "Weekdays" }, - ], -}: AlarmProps) { - const [toggleStates, setToggleStates] = useState(new Set()); + className, + alarms = DEFAULT_ALARMS, + defaultEnabledIds = ["weekday"], +}: AlarmClockProps) { + const visible = alarms.slice(0, 2); + const [enabledIds, setEnabledIds] = useState(() => new Set(defaultEnabledIds)); - const handleToggleChange = (value: boolean, index: number) => { - setToggleStates((prevStates) => { - const newStates = new Set(prevStates); - if (value) { - newStates.add(index); - } else { - newStates.delete(index); - } - return newStates; + const handleToggle = (id: string | number, on: boolean) => { + setEnabledIds((prev) => { + const next = new Set(prev); + if (on) next.add(id); + else next.delete(id); + return next; }); }; return ( -
-
-

Alarms

- +
+
+

Alarms

+
-
- {alarms.map((alarm, index) => ( -
-
-

- {alarm.time} -

-

- {alarm.repetition} -

-
- handleToggleChange(value, index)} // Pass index to identify which alarm is toggled - /> -
- ))} -
+
    + {visible.map((alarm) => { + const on = enabledIds.has(alarm.id); + return ( +
  • +
    +

    + {alarm.time} +

    +

    + {alarm.schedule} +

    +
    +
    + handleToggle(alarm.id, value)} + /> +
    +
  • + ); + })} +
); } diff --git a/animata/widget/battery.tsx b/animata/widget/battery.tsx index fae1f70e..b70bae1b 100644 --- a/animata/widget/battery.tsx +++ b/animata/widget/battery.tsx @@ -1,14 +1,19 @@ "use client"; + import { BatteryMediumIcon } from "lucide-react"; import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; -const Battery = () => { - const batteryLevel = 50; - const circumference = 2 * Math.PI * 40; - const gap = ((100 - batteryLevel) / 100) * circumference; +export type BatteryProps = { + className?: string; + /** 0–100 */ + level?: number; +}; +export default function Battery({ className, level = 50 }: BatteryProps) { + const circumference = 2 * Math.PI * 40; + const gap = ((100 - level) / 100) * circumference; const circleRef = useRef(null); useEffect(() => { @@ -19,16 +24,21 @@ const Battery = () => { }, [gap]); return ( -
-
- - +
+
+ + { transform="rotate(-90 50 50)" /> -
- +
+
-
- {batteryLevel} - % + +
+ + {level} + + %
); -}; - -export default Battery; +} diff --git a/animata/widget/calendar-event.stories.tsx b/animata/widget/calendar-event.stories.tsx index 7fe7b93a..f31cf3cd 100644 --- a/animata/widget/calendar-event.stories.tsx +++ b/animata/widget/calendar-event.stories.tsx @@ -1,19 +1,39 @@ import type { Meta, StoryObj } from "@storybook/react"; -import CalendarEvent, { testCalendarEventProps } from "@/animata/widget/calendar-event"; +import CalendarEvent from "@/animata/widget/calendar-event"; + +const workdayEvents = [ + { title: "Design critique", time: "10:00 – 10:45", variant: "violet" as const }, + { title: "Lunch with Alex", time: "12:30 – 1:15", variant: "cyan" as const }, + { title: "Ship review", time: "3:00 – 4:00", variant: "emerald" as const }, + { title: "Yoga", time: "6:30 – 7:30", variant: "amber" as const }, +]; const meta = { - title: "Widget/Calendar event", + title: "Widget/Calendar Event", component: CalendarEvent, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, + args: { + date: new Date(2026, 4, 30), + events: workdayEvents, + }, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { - args: testCalendarEventProps, +export const WorkdayGlance: Story = { + name: "Workday glance", }; + +export const LightAfternoon: Story = { + name: "Light afternoon", + args: { + events: [ + { title: "Focus block", time: "2:00 – 4:00", variant: "violet" }, + { title: "1:1", time: "4:30 – 5:00", variant: "rose" }, + ], + }, +}; + +export const Primary = WorkdayGlance; diff --git a/animata/widget/calendar-event.tsx b/animata/widget/calendar-event.tsx index 4bb63582..1e99c4ff 100644 --- a/animata/widget/calendar-event.tsx +++ b/animata/widget/calendar-event.tsx @@ -1,125 +1,134 @@ import { cn } from "@/lib/utils"; -interface CalendarEvent { +export type CalendarEventVariant = "violet" | "cyan" | "emerald" | "amber" | "rose"; + +export type CalendarEventItem = { title: string; time: string; - color: string; - bgcolor: string; - barColor: string; - dateColor: string; -} + variant?: CalendarEventVariant; +}; -interface CalendarEventProps { - dates: CalendarEvent[]; -} +export type CalendarEventProps = { + className?: string; + /** Defaults to today. */ + date?: Date; + events?: CalendarEventItem[]; + maxVisible?: number; +}; -export const testCalendarEventProps: CalendarEventProps = { - dates: [ - { - title: "Backlog Updates", - time: "10:30 - 10:45", - color: "text-purple-900", - bgcolor: "bg-purple-200", - barColor: "bg-purple-700", - dateColor: "text-purple-600", - }, - { - title: "Review Jade A", - time: "12:00 - 12:45", - color: "text-cyan-900", - bgcolor: "bg-cyan-200", - barColor: "bg-cyan-700", - dateColor: "text-cyan-600", - }, - { - title: "Design Meeting", - time: "14:00 - 15:00", - color: "text-green-900", - bgcolor: "bg-green-200", - barColor: "bg-green-700", - dateColor: "text-green-600", - }, - { - title: "Development", - time: "16:00 - 17:00", - color: "text-yellow-900", - bgcolor: "bg-yellow-200", - barColor: "bg-yellow-700", - dateColor: "text-yellow-600", - }, - { - title: "QA Testing", - time: "18:00 - 19:00", - color: "text-red-900", - bgcolor: "bg-red-200", - barColor: "bg-red-700", - dateColor: "text-red-600", - }, - ], +export const DEFAULT_CALENDAR_EVENTS: CalendarEventItem[] = [ + { title: "Design critique", time: "10:00 – 10:45", variant: "violet" }, + { title: "Lunch with Alex", time: "12:30 – 1:15", variant: "cyan" }, + { title: "Ship review", time: "3:00 – 4:00", variant: "emerald" }, +]; + +const VARIANT_STYLES: Record< + CalendarEventVariant, + { chip: string; dot: string; title: string; time: string } +> = { + violet: { + chip: "bg-violet-500/15 dark:bg-violet-500/25", + dot: "bg-violet-600", + title: "text-foreground", + time: "text-muted-foreground", + }, + cyan: { + chip: "bg-cyan-500/15 dark:bg-cyan-500/25", + dot: "bg-cyan-600", + title: "text-foreground", + time: "text-muted-foreground", + }, + emerald: { + chip: "bg-emerald-500/15 dark:bg-emerald-500/25", + dot: "bg-emerald-600", + title: "text-foreground", + time: "text-muted-foreground", + }, + amber: { + chip: "bg-amber-500/15 dark:bg-amber-500/25", + dot: "bg-amber-600", + title: "text-foreground", + time: "text-muted-foreground", + }, + rose: { + chip: "bg-rose-500/15 dark:bg-rose-500/25", + dot: "bg-rose-600", + title: "text-foreground", + time: "text-muted-foreground", + }, }; -const maxEvents = 2; +function eventTimeStart(time: string) { + const parts = time.split(/\s[–-]\s/); + return parts[0]?.trim() ?? time; +} -function EventCard({ date }: { date: CalendarEvent; hides: boolean }) { +function EventRow({ event }: { event: CalendarEventItem }) { + const variant = VARIANT_STYLES[event.variant ?? "violet"]; return ( -
-
-
-

{date.title}

-

{date.time}

+
  • + +
    +

    + {event.title} +

    +

    + {event.time} +

    -
  • + ); } export default function CalendarEvent({ - dates = testCalendarEventProps.dates, + className, + date = new Date(), + events = DEFAULT_CALENDAR_EVENTS, + maxVisible = 2, }: CalendarEventProps) { - const extraCount = dates.length - maxEvents; + const list = events; + const visible = list.slice(0, maxVisible); + const extra = list.length - maxVisible; + const nextEvent = list[maxVisible]; + return (
    -
    -

    - {new Date().toLocaleString("default", { weekday: "short" })} +

    +

    + {date.toLocaleString("default", { weekday: "short" })} +

    +

    + {date.getDate()}

    -

    {new Date().getDate()}

    -
    -
    - {dates.slice(0, maxEvents).map((date, index) => ( - - ))}
    - {extraCount ? ( - <> -
    -

    - +{dates.length - maxEvents} event{extraCount > 1 && "s"} -

    -

    16:15 - 20:00

    -
    - {dates.slice(maxEvents, maxEvents + 3).map((_date, index) => ( -
    -
    -
    + + {visible.length > 0 ? ( +
      + {visible.map((event, index) => ( + ))} - +
    ) : ( -
    No more events
    +

    + No events today +

    )} + + {extra > 0 ? ( +
    +

    + +{extra} more +

    +

    + {nextEvent ? eventTimeStart(nextEvent.time) : ""} +

    +
    + ) : null}
    ); } diff --git a/animata/widget/calorie-counter.stories.tsx b/animata/widget/calorie-counter.stories.tsx index b0aaff0b..789c57da 100644 --- a/animata/widget/calorie-counter.stories.tsx +++ b/animata/widget/calorie-counter.stories.tsx @@ -1,19 +1,19 @@ import type { Meta, StoryObj } from "@storybook/react"; -import CalorieCounter, { testCalorieCounterProps } from "@/animata/widget/calorie-counter"; +import CalorieCounter from "@/animata/widget/calorie-counter"; const meta = { title: "Widget/Calorie Counter", component: CalorieCounter, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { - args: testCalorieCounterProps, + args: { + goal: 4000, + fulfilled: 120, + }, }; diff --git a/animata/widget/calorie-counter.tsx b/animata/widget/calorie-counter.tsx index 92dfc668..b262fb0d 100644 --- a/animata/widget/calorie-counter.tsx +++ b/animata/widget/calorie-counter.tsx @@ -1,49 +1,76 @@ "use client"; + import DonutChart from "@/animata/graphs/donut-chart"; -interface ICalorieCounterProps { - goal: number; - fulfilled: number; - image: string; -} +import { cn } from "@/lib/utils"; + +const DEFAULT_IMAGE = + "https://plus.unsplash.com/premium_vector-1689096672037-98309fdc7f44?bg=FFFFFF&q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3"; -export const testCalorieCounterProps: ICalorieCounterProps = { - goal: 4000, - fulfilled: 120, - image: - "https://plus.unsplash.com/premium_vector-1689096672037-98309fdc7f44?bg=FFFFFF&q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3", +export type CalorieCounterProps = { + className?: string; + goal?: number; + fulfilled?: number; + /** @deprecated Use `avatarUrl` */ + image?: string; + avatarUrl?: string; + weekday?: string; + dateLabel?: string; }; export default function CalorieCounter({ - goal = testCalorieCounterProps.goal, - fulfilled = testCalorieCounterProps.fulfilled, - image = testCalorieCounterProps.image, -}: ICalorieCounterProps) { + className, + goal = 4000, + fulfilled = 120, + image, + avatarUrl = image ?? DEFAULT_IMAGE, + weekday = "Monday", + dateLabel = "25 October", +}: CalorieCounterProps) { + const progress = goal > 0 ? Math.min(100, (fulfilled / goal) * 100) : 0; + return ( -
    -
    +
    +
    -

    Monday

    -

    25 October

    +

    {weekday}

    +

    {dateLabel}

    - image + { + e.currentTarget.onerror = null; + e.currentTarget.src = "https://avatar.vercel.sh/calorie"; + }} + />
    - -
    + +
    + +
    +

    Today

    +

    {fulfilled}Cal

    +
    +
    + +

    Your goal

    {goal}Cal

    -
    -

    Today

    -

    {fulfilled}Cal

    -
    ); } diff --git a/animata/widget/delivery-card.stories.tsx b/animata/widget/delivery-card.stories.tsx index 57066744..9ce98a57 100644 --- a/animata/widget/delivery-card.stories.tsx +++ b/animata/widget/delivery-card.stories.tsx @@ -4,21 +4,31 @@ import DeliveryCard from "@/animata/widget/delivery-card"; const meta = { title: "Widget/Delivery Card", component: DeliveryCard, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const OutForDelivery: Story = { + name: "Out for delivery", + args: { + progress: 62, + arrivalTime: "12:40", + location: "Mission District", + timeAgo: "8 min", + simulateProgress: false, + }, +}; + +export const LiveDemo: Story = { + name: "Live demo", args: { - progress: 30, - arrivalTime: "16:30", - location: "Newroad", - timeAgo: "30min", + progress: 20, + arrivalTime: "09:26", + location: "Pokhara", + timeAgo: "30 min", + simulateProgress: true, }, }; diff --git a/animata/widget/delivery-card.tsx b/animata/widget/delivery-card.tsx index 8505848b..a9c6fcbf 100644 --- a/animata/widget/delivery-card.tsx +++ b/animata/widget/delivery-card.tsx @@ -1,84 +1,105 @@ "use client"; + import { LocateIcon, TruckIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; -interface DeliveryCardProps { +export type DeliveryCardProps = { + className?: string; progress?: number; arrivalTime?: string; location?: string; timeAgo?: string; -} + /** Demo-only: animates progress on an interval. */ + simulateProgress?: boolean; +}; -const DeliveryCard = ({ - progress = 10, +export default function DeliveryCard({ + className, + progress = 42, arrivalTime = "09:26", location = "Pokhara", timeAgo = "30 min", -}: DeliveryCardProps) => { - const [adjustedProgress, setAdjustedProgress] = useState(progress); - const status = progress <= 0 ? "Processing" : progress >= 100 ? "Delivered" : "In Transit"; + simulateProgress = false, +}: DeliveryCardProps) { + const [demoProgress, setDemoProgress] = useState(progress); + const displayProgress = simulateProgress ? demoProgress : progress; + const clamped = Math.min(100, Math.max(0, displayProgress)); + const status = clamped <= 0 ? "Processing" : clamped >= 100 ? "Delivered" : "In transit"; + const headline = clamped >= 100 ? "Arrived" : "Arrives today"; + + useEffect(() => { + setDemoProgress(progress); + }, [progress]); useEffect(() => { - const timeout = setInterval(() => { - setAdjustedProgress((currentProgress) => { - let newProgress = currentProgress; - if (newProgress >= 100) { - return 0; - } - newProgress += 30; - return Math.min(Math.max(0, newProgress), 100); + if (!simulateProgress) return; + const id = setInterval(() => { + setDemoProgress((current) => { + if (current >= 100) return 0; + return Math.min(100, current + 30); }); }, 3000); - return () => clearInterval(timeout); - }, []); + return () => clearInterval(id); + }, [simulateProgress]); return ( -
    +
    -
    +
    -
    -
    +
    +
    -
    +
    -

    {adjustedProgress === 100 ? "Arrived" : "Arrives Today"}

    -

    {arrivalTime}

    +

    {headline}

    +

    + {arrivalTime} +

    -
    -

    {status}

    -
    -
    -
    +
    +

    + {status} +

    + +
    +
    +
    - +
    -
    - -
    - {location} - {timeAgo} ago +
    + +
    +

    + {location} +

    +

    + {timeAgo} ago +

    ); -}; - -export default DeliveryCard; +} diff --git a/animata/widget/flight-widget.tsx b/animata/widget/flight-widget.tsx index 426fb5ec..666b181c 100644 --- a/animata/widget/flight-widget.tsx +++ b/animata/widget/flight-widget.tsx @@ -35,7 +35,7 @@ export default function FlightWidget() {
    AIR CANADA
    Air Canada logo
    diff --git a/animata/widget/fund-widget.tsx b/animata/widget/fund-widget.tsx index e02e9840..166a3e94 100644 --- a/animata/widget/fund-widget.tsx +++ b/animata/widget/fund-widget.tsx @@ -106,20 +106,9 @@ export default function FundWidget({ const rotateYValue = Math.min(dragDistance / 10, 15); return ( -
    -
    +
    -
    +
    { - setPlay((prevValue) => !prevValue); - }; +export type MusicWidgetProps = { + className?: string; + tracks?: MusicTrack[]; + coverUrl?: string; + defaultPlaying?: boolean; +}; - const handleNext = () => { - setCurrentSong((prevValue) => (prevValue + 1) % songs.length); - }; - - const handlePrev = () => { - setCurrentSong((prevValue) => (prevValue - 1 + songs.length) % songs.length); - }; +const DEFAULT_TRACKS: MusicTrack[] = [ + { title: "Never Gonna Give You Up", artist: "Rick Astley" }, + { title: "It Must Have Been Love", artist: "Roxette" }, + { title: "Take On Me", artist: "A-ha" }, +]; - const song = songs[currentSong]; - const { title, artist } = song; +export default function MusicWidget({ + className, + tracks = DEFAULT_TRACKS, + coverUrl = absoluteUrl("/widget/music.jpg"), + defaultPlaying = false, +}: MusicWidgetProps) { + const [index, setIndex] = useState(0); + const [playing, setPlaying] = useState(defaultPlaying); + const safeTracks = tracks.length > 0 ? tracks : DEFAULT_TRACKS; + const track = safeTracks[index % safeTracks.length]; return ( -
    -
    -
    -
    - Album Pic -
    -
    +
    +
    +
    + +
    -

    - {title} -

    -

    - {artist} -

    +
    +

    {track.title}

    +

    {track.artist}

    +
    +
    - -
    - {!play ? ( - + +
    - + +
    ); diff --git a/animata/widget/notes.stories.tsx b/animata/widget/notes.stories.tsx index 0c49744e..7105b51c 100644 --- a/animata/widget/notes.stories.tsx +++ b/animata/widget/notes.stories.tsx @@ -4,16 +4,16 @@ import Notes from "@/animata/widget/notes"; const meta = { title: "Widget/Notes", component: Notes, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const AboutJohn: Story = { + name: "About John", args: {}, }; + +export const Primary = AboutJohn; diff --git a/animata/widget/notes.tsx b/animata/widget/notes.tsx index 165b9fb1..6989c9c8 100644 --- a/animata/widget/notes.tsx +++ b/animata/widget/notes.tsx @@ -1,19 +1,52 @@ -export function NotesCard({ title, children }: { title?: string; children?: React.ReactNode }) { +import type { ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +export type NotesCardProps = { + className?: string; + title?: string; + children?: ReactNode; +}; + +export function NotesCard({ className, title = "Note", children }: NotesCardProps) { return ( -
    -
    {title}
    -
    {children}
    +
    +

    {title}

    +
    + {children} +
    ); } -export default function Notes() { +export type NotesProps = { + className?: string; + title?: string; + lines?: string[]; +}; + +const DEFAULT_LINES = [ + "Has worked with ABC for the last 10 years", + "Built two dozen web apps", + "Latest work is live in stores", + "A cool guy", +]; + +export default function Notes({ + className, + title = "About John", + lines = DEFAULT_LINES, +}: NotesProps) { return ( - -
    Has worked with ABC for last 10 years
    -
    Developed over 2 dozens of web apps
    -
    His latest work is live on stores
    -
    A cool guy
    + + {lines.map((line) => ( +

    {line}

    + ))}
    ); } diff --git a/animata/widget/reminder-widget.stories.tsx b/animata/widget/reminder-widget.stories.tsx index 4f7555a5..f86f3822 100644 --- a/animata/widget/reminder-widget.stories.tsx +++ b/animata/widget/reminder-widget.stories.tsx @@ -4,16 +4,18 @@ import ReminderWidget from "@/animata/widget/reminder-widget"; const meta = { title: "Widget/Reminder Widget", component: ReminderWidget, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { - args: {}, +export const TripPacking: Story = { + name: "Trip packing", + args: { + title: "Packing", + }, }; + +export const Primary = TripPacking; diff --git a/animata/widget/reminder-widget.tsx b/animata/widget/reminder-widget.tsx index e85e9d7a..d0104272 100644 --- a/animata/widget/reminder-widget.tsx +++ b/animata/widget/reminder-widget.tsx @@ -1,57 +1,106 @@ "use client"; -import { BellDot } from "lucide-react"; + +import { Bell } from "lucide-react"; import { useState } from "react"; import { cn } from "@/lib/utils"; -const reminders = [ - "Passport", - "Citizenship", - "birth certificate", - "License", - "key", - "mobile", - "laptop", - "wallet", - "watch", +export type ReminderWidgetItem = { + id: string; + label: string; + done?: boolean; +}; + +export type ReminderWidgetProps = { + className?: string; + title?: string; + items?: ReminderWidgetItem[]; +}; + +const DEFAULT_ITEMS: ReminderWidgetItem[] = [ + { id: "passport", label: "Passport" }, + { id: "citizenship", label: "Citizenship" }, + { id: "birth-cert", label: "Birth certificate" }, + { id: "license", label: "License" }, + { id: "keys", label: "Keys" }, + { id: "phone", label: "Phone" }, + { id: "laptop", label: "Laptop" }, + { id: "wallet", label: "Wallet" }, ]; -export default function ReminderWidget() { - const [reminder, setReminder] = useState(reminders); +export default function ReminderWidget({ + className, + title = "Packing", + items = DEFAULT_ITEMS, +}: ReminderWidgetProps) { + const [rows, setRows] = useState(items); + + const remaining = rows.filter((item) => !item.done).length; - const handleCheckboxChange = (data: string) => { - setReminder((prev) => - prev.includes(data) ? prev.filter((remind) => remind !== data) : [...prev, data], - ); + const toggle = (id: string) => { + setRows((prev) => prev.map((item) => (item.id === id ? { ...item, done: !item.done } : item))); }; return ( -
    -
    -
    - -

    Reminder

    -
    -
    - {reminder.length} +
    +
    +
    + +

    {title}

    + + {remaining} +
    -
    - {reminders.map((data) => ( -
    handleCheckboxChange(data)} - className="flex cursor-pointer items-center gap-3 border-b border-gray-700 py-1" - > - -

    {data}

    -
    + +
      + {rows.map((item) => ( +
    • + +
    • ))} -
    +
    ); } diff --git a/animata/widget/reminder.stories.tsx b/animata/widget/reminder.stories.tsx index 0ed49a24..a1308e30 100644 --- a/animata/widget/reminder.stories.tsx +++ b/animata/widget/reminder.stories.tsx @@ -4,18 +4,33 @@ import Reminder from "@/animata/widget/reminder"; const meta = { title: "Widget/Reminder", component: Reminder, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const StandupIn30: Story = { + name: "Standup in 30", + args: { + title: "Today", + buckets: [ + { label: "Work", count: 2, className: "bg-violet-200 dark:bg-violet-500/35" }, + { label: "Personal", count: 1, className: "bg-emerald-200 dark:bg-emerald-500/35" }, + ], + nextLabel: "Engineering standup in 30 min", + }, +}; + +export const WeekendErrands: Story = { + name: "Weekend errands", args: { - className: "", + title: "Saturday", + buckets: [ + { label: "Home", count: 4, className: "bg-emerald-200 dark:bg-emerald-500/35" }, + { label: "Shop", count: 2, className: "bg-amber-200 dark:bg-amber-500/35" }, + ], + nextLabel: "Grocery pickup at 2:00 PM", }, }; diff --git a/animata/widget/reminder.tsx b/animata/widget/reminder.tsx index f64b64f8..1f30b410 100644 --- a/animata/widget/reminder.tsx +++ b/animata/widget/reminder.tsx @@ -2,28 +2,65 @@ import { ArrowRight } from "lucide-react"; import { cn } from "@/lib/utils"; -export default function Reminder({ className }: { className?: string }) { +export type ReminderBucket = { + label: string; + count: number; + /** Tailwind classes for the bucket chip (e.g. `bg-violet-200 dark:bg-violet-500/35`). */ + className?: string; +}; + +export type ReminderProps = { + className?: string; + title?: string; + buckets?: ReminderBucket[]; + /** Next commitment — the primary glance line. */ + nextLabel?: string; +}; + +const DEFAULT_BUCKETS: ReminderBucket[] = [ + { label: "Work", count: 2, className: "bg-violet-200 dark:bg-violet-500/35" }, + { label: "Home", count: 3, className: "bg-emerald-200 dark:bg-emerald-500/35" }, +]; + +export default function Reminder({ + className, + title = "Today", + buckets = DEFAULT_BUCKETS, + nextLabel = "Meeting in 30 min", +}: ReminderProps) { return (
    -

    Reminder

    +

    + {title} +

    -
    -
    - 2

    Work

    -
    -
    - 3

    Home

    -
    +
    + {buckets.slice(0, 2).map((bucket) => ( +
    + + {bucket.count} + + + {bucket.label} + +
    + ))}
    -
    - -

    Meeting in 30 mins

    +
    + +

    {nextLabel}

    ); diff --git a/animata/widget/score-board.stories.tsx b/animata/widget/score-board.stories.tsx index bd26b6b8..d9d076b4 100644 --- a/animata/widget/score-board.stories.tsx +++ b/animata/widget/score-board.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import ScoreBoard, { testScoreBoardProps } from "@/animata/widget/score-board"; +import ScoreBoard from "@/animata/widget/score-board"; const meta = { title: "Widget/Score Board", @@ -15,5 +15,5 @@ export default meta; type Story = StoryObj; export const Primary: Story = { - args: testScoreBoardProps, + args: {}, }; diff --git a/animata/widget/score-board.tsx b/animata/widget/score-board.tsx index 5143373d..25c27fe5 100644 --- a/animata/widget/score-board.tsx +++ b/animata/widget/score-board.tsx @@ -1,87 +1,74 @@ import BarChart from "@/animata/graphs/bar-chart"; -interface ScoreBoardProps { - items: { - progress: number; - label: string; - className?: string; - containerClassName?: string; - }[]; -} +import { cn } from "@/lib/utils"; + +export type ScoreBoardItem = { + progress: number; + label: string; + className?: string; + containerClassName?: string; +}; -export const testScoreBoardProps: ScoreBoardProps = { - items: [ - { - label: "A", - progress: 34, - className: "rounded-md bg-green-500", - }, - { - label: "B", - progress: 14, - className: "rounded-md bg-red-500", - }, - { - label: "C", - progress: 34, - className: "rounded-md bg-green-500", - }, - { - label: "D", - progress: 70, - className: "rounded-md bg-green-500", - }, - { - label: "E", - progress: 52, - className: "rounded-md bg-green-500", - }, - { - label: "F", - progress: 30, - className: "rounded-md bg-green-500", - }, - { - label: "G", - progress: 37, - className: "rounded-md bg-green-500", - }, - { - label: "H", - progress: 72, - className: "rounded-md bg-green-500", - }, - { - label: "I", - progress: 42, - className: "rounded-md bg-green-500", - }, - ], +export type ScoreBoardProps = { + className?: string; + items?: ScoreBoardItem[]; + statusLabel?: string; + statusTone?: "good" | "warning" | "neutral"; + overallPercent?: number; + overallCaption?: string; }; +const DEFAULT_ITEMS: ScoreBoardItem[] = [ + { label: "A", progress: 34, className: "rounded-md bg-green-500" }, + { label: "B", progress: 14, className: "rounded-md bg-red-500" }, + { label: "C", progress: 34, className: "rounded-md bg-green-500" }, + { label: "D", progress: 70, className: "rounded-md bg-green-500" }, + { label: "E", progress: 52, className: "rounded-md bg-green-500" }, + { label: "F", progress: 30, className: "rounded-md bg-green-500" }, + { label: "G", progress: 37, className: "rounded-md bg-green-500" }, + { label: "H", progress: 72, className: "rounded-md bg-green-500" }, + { label: "I", progress: 42, className: "rounded-md bg-green-500" }, +]; + +const STATUS_TONE_CLASS = { + good: "text-emerald-600 dark:text-emerald-400", + warning: "text-amber-600 dark:text-amber-400", + neutral: "text-foreground", +} as const; + export default function ScoreBoard({ - items = testScoreBoardProps.items, -}: { - items: { - progress: number; - label: string; - className?: string; - containerClassName?: string; - }[]; -}) { + className, + items = DEFAULT_ITEMS, + statusLabel = "Good", + statusTone = "good", + overallPercent = 80, + overallCaption = "score, progress", +}: ScoreBoardProps) { return ( -
    -

    - Status: - Good +

    +

    + Status{" "} + {statusLabel}

    -
    + +
    -
    Overall Progress
    -

    - 80% (score, progress) -

    + +
    +

    Overall progress

    +

    + {overallPercent}% + + ({overallCaption}) + +

    +
    ); } diff --git a/animata/widget/security-alert.tsx b/animata/widget/security-alert.tsx index d078fa16..2dfd6008 100644 --- a/animata/widget/security-alert.tsx +++ b/animata/widget/security-alert.tsx @@ -1,27 +1,57 @@ import { TriangleAlert } from "lucide-react"; -const SecurityAlert = () => { - const riskValue = "139 532", - time = "30m", - numberOfIssue = 2; +import { cn } from "@/lib/utils"; +export type SecurityAlertProps = { + className?: string; + title?: string; + riskValue?: string; + timeAgo?: string; + scanLabel?: string; + issueCount?: number; +}; + +export default function SecurityAlert({ + className, + title = "Security at risk", + riskValue = "139,532", + timeAgo = "30m", + scanLabel = "Quick scan", + issueCount = 2, +}: SecurityAlertProps) { return ( -
    -
    -
    -
    Security is at Risk
    -
    {riskValue}
    -
    -
    {time} ago
    -
    Quick scan
    +
    +
    + +
    +

    {title}

    + +

    + {riskValue} +

    + +
    +

    {timeAgo} ago

    +
    -
    - -
    {numberOfIssue} Items
    + +
    + + + {issueCount} {issueCount === 1 ? "item" : "items"} +
    ); -}; - -export default SecurityAlert; +} diff --git a/animata/widget/shopping-list.stories.tsx b/animata/widget/shopping-list.stories.tsx index 2d3a8a05..1dfd1b88 100644 --- a/animata/widget/shopping-list.stories.tsx +++ b/animata/widget/shopping-list.stories.tsx @@ -4,18 +4,18 @@ import ShoppingList from "@/animata/widget/shopping-list"; const meta = { title: "Widget/Shopping List", component: ShoppingList, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const GroceryRun: Story = { + name: "Grocery run", args: { title: "Shopping list", }, }; + +export const Primary = GroceryRun; diff --git a/animata/widget/shopping-list.tsx b/animata/widget/shopping-list.tsx index 98750270..654f5781 100644 --- a/animata/widget/shopping-list.tsx +++ b/animata/widget/shopping-list.tsx @@ -1,73 +1,107 @@ +"use client"; + +import { useState } from "react"; + import { cn } from "@/lib/utils"; -function Checkbox({ title, checked }: { title: string; checked?: boolean }) { +export type ShoppingListItem = { + id: string; + title: string; + checked?: boolean; +}; + +export type ShoppingListProps = { + className?: string; + title?: string; + items?: ShoppingListItem[]; +}; + +const DEFAULT_ITEMS: ShoppingListItem[] = [ + { id: "milk", title: "Milk" }, + { id: "eggs", title: "Eggs", checked: true }, + { id: "pepper", title: "Ground pepper", checked: true }, + { id: "spaghetti", title: "Spaghetti" }, + { id: "butter", title: "Butter" }, +]; + +function CheckIcon({ className }: { className?: string }) { return ( -
    -
    - - - -
    -
    {title}
    -
    + + + ); } -const list = [ - { - title: "Milk", - }, - { - title: "Eggs", - checked: true, - }, - { - title: "Ground Pepper", - checked: true, - }, - { - title: "Spaghetti", - }, - { - title: "Butter", - }, -]; - export default function ShoppingList({ - data = list, - title, -}: { - title?: string; - data?: { - title: string; - checked?: boolean; - }[]; -}) { + className, + title = "Shopping list", + items = DEFAULT_ITEMS, +}: ShoppingListProps) { + const [rows, setRows] = useState(items); + + const toggle = (id: string) => { + setRows((prev) => + prev.map((item) => (item.id === id ? { ...item, checked: !item.checked } : item)), + ); + }; + return ( -
    -
    - {title || "Shopping list"} -
    -
    - {data.map((item, index) => ( - +
    +

    + {title} +

    + +
      + {rows.map((item) => ( +
    • + +
    • ))} -
    +
    ); } diff --git a/animata/widget/study-timer.stories.tsx b/animata/widget/study-timer.stories.tsx index ec2dc5cc..6e6300c0 100644 --- a/animata/widget/study-timer.stories.tsx +++ b/animata/widget/study-timer.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import StudyTimer, { testStudyTimerProps } from "@/animata/widget/study-timer"; +import StudyTimer from "@/animata/widget/study-timer"; const meta = { title: "Widget/Study Timer", @@ -16,5 +16,5 @@ export default meta; type Story = StoryObj; export const Primary: Story = { - args: testStudyTimerProps, + args: {}, }; diff --git a/animata/widget/study-timer.tsx b/animata/widget/study-timer.tsx index e5bc57f4..a6169fa0 100644 --- a/animata/widget/study-timer.tsx +++ b/animata/widget/study-timer.tsx @@ -1,95 +1,98 @@ import { GraduationCap, XCircle } from "lucide-react"; -import type React from "react"; import { cn } from "@/lib/utils"; -export interface StudyTimerProps { - segments: Segment[]; -} - -interface Segment { +export type StudyTimerSegment = { value: number; color: string; -} +}; -export const testStudyTimerProps: StudyTimerProps = { - segments: [ - { value: 57, color: "orange" }, - { value: 24, color: "pink" }, - { value: 26, color: "yellow" }, - ], +export type StudyTimerProps = { + className?: string; + segments?: StudyTimerSegment[]; + /** Shown in the focus badge (e.g. pomodoro count). */ + sessionCount?: number; }; +const DEFAULT_SEGMENTS: StudyTimerSegment[] = [ + { value: 57, color: "rgb(251 146 60)" }, + { value: 24, color: "rgb(244 114 182)" }, + { value: 26, color: "rgb(250 204 21)" }, +]; + const formatTime = (totalMinutes: number) => { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; - const seconds = (totalMinutes * 60) % 60; - - const formattedHours = hours.toString().padStart(2, "0"); - const formattedMinutes = minutes.toString().padStart(2, "0"); - const formattedSeconds = seconds.toString().padStart(2, "0"); - - return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; + return [hours, minutes].map((n) => String(n).padStart(2, "0")).join(":"); }; -const StudyTimer: React.FC = ({ segments = testStudyTimerProps.segments }) => { +function SegmentBar({ segment, totalSum }: { segment: StudyTimerSegment; totalSum: number }) { + const widthPercent = totalSum > 0 ? (segment.value / totalSum) * 100 : 0; + return ( +
    + ); +} + +export default function StudyTimer({ + className, + segments = DEFAULT_SEGMENTS, + sessionCount = 21, +}: StudyTimerProps) { const totalMinutes = segments.reduce((acc, segment) => acc + segment.value, 0); const time = formatTime(totalMinutes); return ( -
    -
    +
    +
    -
    - - 21 +
    + + + {sessionCount} +
    -
    -
    {time}
    -
    + +
    +

    {time}

    +
    {segments.map((segment, index) => ( - {segment.value}m - {index !== segments.length - 1 && ( -
    - )} + {segment.value}m + {index !== segments.length - 1 ? ( + + ) : null} ))}
    -
    + +
    {segments.map((segment, index) => ( ))}
    ); -}; - -const SegmentBar: React.FC<{ segment: Segment; totalSum: number }> = ({ segment, totalSum }) => { - const widthPercent = (segment.value / totalSum) * 100; - - return ( -
    - ); -}; - -export default StudyTimer; +} diff --git a/animata/widget/vpn-widget.stories.tsx b/animata/widget/vpn-widget.stories.tsx index 538065de..02913a39 100644 --- a/animata/widget/vpn-widget.stories.tsx +++ b/animata/widget/vpn-widget.stories.tsx @@ -1,39 +1,32 @@ import type { Meta, StoryObj } from "@storybook/react"; -import VpnWidget from "@/animata/widget/vpn-widget"; +import VpnConnection from "@/animata/widget/vpn-widget"; -const meta: Meta = { - title: "Widget/Vpn Widget", - component: VpnWidget, - parameters: { - layout: "centered", - }, +const meta = { + title: "Widget/VPN Connection", + component: VpnConnection, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: { - userName: { control: "text" }, - latency: { control: "text" }, - netSpeed: { control: "text" }, - className: { control: "text" }, - }, -}; +} satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const Connected: Story = { + name: "Connected", args: { - userName: "Animata", - latency: "5ms", - netSpeed: "10", - defaultConnected: false, + serverName: "Tokyo", + latency: "48 ms", + downloadMbps: "112.3", + defaultConnected: true, }, }; -export const VpnWidgetStory: Story = { +export const Disconnected: Story = { + name: "Disconnected", args: { - userName: "Animata", - latency: "5ms", - netSpeed: "15.75", - className: "", - defaultConnected: true, + serverName: "Frankfurt", + latency: "—", + downloadMbps: "0", + defaultConnected: false, }, }; diff --git a/animata/widget/vpn-widget.tsx b/animata/widget/vpn-widget.tsx index 0cf7ee7e..6b569e5a 100644 --- a/animata/widget/vpn-widget.tsx +++ b/animata/widget/vpn-widget.tsx @@ -1,106 +1,118 @@ "use client"; + import { FlagIcon, ShieldBan, ShieldCheck } from "lucide-react"; import { useState } from "react"; import { cn } from "@/lib/utils"; -interface SlidingButtonProps { - initialConnected?: boolean; +type SlidingButtonProps = { + connected: boolean; + onConnectedChange: (connected: boolean) => void; className?: string; -} - -interface VpnConnectionProps { - userName?: string; - latency?: string; - netSpeed?: string; - className?: string; - defaultConnected?: boolean; -} - -function SlidingButton({ initialConnected = false, className }: SlidingButtonProps) { - const [connected, setConnected] = useState(initialConnected); +}; +function SlidingButton({ connected, onConnectedChange, className }: SlidingButtonProps) { return ( -
    setConnected(!connected)} + onClick={() => onConnectedChange(!connected)} > -
    -
    + -
    -
    + - + {connected ? "Connected" : "Disconnected"} - + - {connected ? : } - {connected ? "Secured" : "Not Secured"} + {connected ? : } + {connected ? "Secured" : "Not secured"} -
    -
    -
    + + -
    -
    + + ); } +export type VpnConnectionProps = { + className?: string; + serverName?: string; + latency?: string; + downloadMbps?: string; + defaultConnected?: boolean; + connected?: boolean; + onConnectedChange?: (connected: boolean) => void; +}; + export default function VpnConnection({ - userName = "Animata", - latency = "53ms", - netSpeed = "57.4", className, + serverName = "Tokyo", + latency = "53 ms", + downloadMbps = "57.4", defaultConnected = false, + connected: controlledConnected, + onConnectedChange, }: VpnConnectionProps) { + const [internalConnected, setInternalConnected] = useState(defaultConnected); + const connected = controlledConnected ?? internalConnected; + + const setConnected = (next: boolean) => { + if (controlledConnected === undefined) { + setInternalConnected(next); + } + onConnectedChange?.(next); + }; + return (
    -
    -
    - -

    {userName}

    +
    +
    + +

    + {serverName} +

    -
    -
    -
    -
    {latency}
    +
    +
    + + + {latency} +
    -

    - {netSpeed} - mb -

    +

    + {downloadMbps} + Mbps +

    - +
    diff --git a/animata/widget/water-tracker.stories.tsx b/animata/widget/water-tracker.stories.tsx index a9e6789b..5b3c0514 100644 --- a/animata/widget/water-tracker.stories.tsx +++ b/animata/widget/water-tracker.stories.tsx @@ -4,18 +4,27 @@ import WaterTracker from "@/animata/widget/water-tracker"; const meta = { title: "Widget/Water Tracker", component: WaterTracker, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { +export const MorningStart: Story = { + name: "Morning start", + args: { + dailyGoal: 2000, + defaultIntake: 500, + sipMl: 250, + }, +}; + +export const AfternoonCatchUp: Story = { + name: "Afternoon catch-up", args: { - dailyGoal: 3000, + dailyGoal: 2500, + defaultIntake: 1400, + sipMl: 300, }, }; diff --git a/animata/widget/water-tracker.tsx b/animata/widget/water-tracker.tsx index 764b89fe..ced8ee74 100644 --- a/animata/widget/water-tracker.tsx +++ b/animata/widget/water-tracker.tsx @@ -1,72 +1,84 @@ "use client"; + import { Droplet } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { cn } from "@/lib/utils"; -interface WaterTrackerProps { - dailyGoal: number; -} - -export default function WaterTracker({ dailyGoal = 5000 }: WaterTrackerProps) { - const [waterIntake, setWaterIntake] = useState(0); +export type WaterTrackerProps = { + className?: string; + dailyGoal?: number; + sipMl?: number; + defaultIntake?: number; + intake?: number; + onIntakeChange?: (ml: number) => void; +}; - useEffect(() => { - setWaterIntake(1500); - }, []); +export default function WaterTracker({ + className, + dailyGoal = 2000, + sipMl = 250, + defaultIntake = 750, + intake: controlledIntake, + onIntakeChange, +}: WaterTrackerProps) { + const [internalIntake, setInternalIntake] = useState(defaultIntake); + const intake = controlledIntake ?? internalIntake; + const atGoal = intake >= dailyGoal; + const progress = Math.min(100, (intake / dailyGoal) * 100); - const handleAddWater = () => { - setWaterIntake((prevIntake) => { - if (prevIntake + 250 <= dailyGoal) { - return prevIntake + 250; - } - return prevIntake; - }); + const addWater = () => { + if (atGoal) return; + const next = Math.min(dailyGoal, intake + sipMl); + if (controlledIntake === undefined) { + setInternalIntake(next); + } + onIntakeChange?.(next); }; - const progress = (waterIntake / dailyGoal) * 100; - return ( -
    -
    -
    - -

    Water

    +
    +
    +
    + +

    Water

    -
    -

    {waterIntake}ml

    -

    Today

    -
    -
    - + +
    +

    + {intake.toLocaleString()} + ml +

    +

    + of {dailyGoal.toLocaleString()} ml today +

    + +
    -
    -
    -
    -
    +
    +
    ); diff --git a/animata/widget/weather-card.stories.tsx b/animata/widget/weather-card.stories.tsx index 81736c33..fdb6c530 100644 --- a/animata/widget/weather-card.stories.tsx +++ b/animata/widget/weather-card.stories.tsx @@ -4,16 +4,31 @@ import WeatherCard from "@/animata/widget/weather-card"; const meta = { title: "Widget/Weather Card", component: WeatherCard, - parameters: { - layout: "centered", - }, + parameters: { layout: "centered" }, tags: ["autodocs"], - argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = { - args: {}, +export const TokyoMorning: Story = { + name: "Tokyo morning", + args: { + city: "Tokyo", + temperature: 19, + feelsLike: 21, + high: 24, + low: 9, + }, +}; + +export const NycRain: Story = { + name: "NYC rain", + args: { + city: "New York", + temperature: 12, + feelsLike: 10, + high: 14, + low: 8, + }, }; diff --git a/animata/widget/weather-card.tsx b/animata/widget/weather-card.tsx index 0d92e82d..2d991904 100644 --- a/animata/widget/weather-card.tsx +++ b/animata/widget/weather-card.tsx @@ -1,25 +1,58 @@ import { CircleArrowUp, CloudSunRain } from "lucide-react"; -export default function WeatherCard() { +import { cn } from "@/lib/utils"; + +export type WeatherCardProps = { + className?: string; + city?: string; + temperature?: number; + feelsLike?: number; + high?: number; + low?: number; +}; + +export default function WeatherCard({ + className, + city = "Tokyo", + temperature = 19, + feelsLike = 21, + high = 24, + low = 9, +}: WeatherCardProps) { return ( -
    -
    -

    Tokyo

    -
    - -

    19°

    +
    +
    +

    {city}

    +
    + +

    + {temperature}° +

    -

    Feels like 21°

    +

    + Feels like {feelsLike}° +

    -
    -
    - - 24° +
    +
    + + {high}°
    -

    |

    -
    - - 9° + + | + +
    + + {low}°
    diff --git a/animata/widget/weekly-progress.tsx b/animata/widget/weekly-progress.tsx index dd67cb8f..4c1911fe 100644 --- a/animata/widget/weekly-progress.tsx +++ b/animata/widget/weekly-progress.tsx @@ -2,44 +2,71 @@ import { BookOpen } from "lucide-react"; import { cn } from "@/lib/utils"; -export default function WeeklyProgress() { - const days = ["M", "T", "W", "TH", "F", "S", "SU"]; +export type WeeklyProgressProps = { + className?: string; + author?: string; + title?: string; + percent?: number; +}; + +const DAYS = ["M", "T", "W", "TH", "F", "S", "SU"] as const; + +export default function WeeklyProgress({ + className, + author = "Chris Dixon", + title = "Read Write Own", + percent = 30, +}: WeeklyProgressProps) { return ( -
    -
    -

    Chris Dixon

    -

    Read Write Own

    +
    +
    +

    {author}

    +

    {title}

    -
    - -

    30%

    + +
    + +

    + {percent}% +

    -
    - {days.map((day, index) => ( + +
    + {DAYS.map((day, index) => ( + /> +
    setForceUpdate((prev) => prev + 1)} />
    @@ -33,6 +34,18 @@ Open the newly created file and paste the following code: +## Usage + +The stack has three states: + +1. **Deck** — all cards share one origin (transform-only, no absolute fan layout). +2. **Hover** — a light vertical peek on the outer layer; spread motion is unchanged. +3. **Click** — cards throw into their spread positions with a custom `--ease-throw` curve; collapse uses a separate ease-out gather. + +Toggle with click or Enter / Space when the stage is focused. The demo composes Notes, Shopping List, and related widget cards — swap in your own `w-48` tiles as needed. + +Motion is CSS-only: an inner wrapper handles spread/collapse; an outer wrapper handles hover peek so the two never fight for `transform`. + ## Credits Built by [sudha](https://github.com/sudhashrestha) diff --git a/content/docs/changelog/2026-06.mdx b/content/docs/changelog/2026-06.mdx new file mode 100644 index 00000000..f9524e85 --- /dev/null +++ b/content/docs/changelog/2026-06.mdx @@ -0,0 +1,68 @@ +--- +title: June 2026 +description: Widget glance polish, Card Spread motion, and catalog cleanup. +date: 2026-06-01 +--- + +Wave 1 widgets got Apple-style type hierarchy and props-driven demos. Card Spread was rebuilt for deck, peek, and throw-on-click motion. Four oversized widgets left the public catalog, and the internal widget standards doc was removed from contributing. + +## Improved components + + + +

    Reminder

    +

    Glance typography, toggleable rows, and theme tokens — use on home-screen–style boards where quick task completion matters.

    +
    + +

    Weather Card

    +

    Props for city, conditions, and highs/lows with a compact square layout for dashboards and marketing bentos.

    +
    + +

    Water Tracker

    +

    Configurable goal and fill level with clearer hierarchy for health and habit apps.

    +
    + +

    VPN Widget

    +

    Sliding connect control with labeled states — fits security or connectivity glance tiles.

    +
    + +

    Delivery Card

    +

    Transit progress, arrival time, and location in a dark tile suited to order-tracking UIs.

    +
    + +

    Alarm Clock

    +

    Toggleable alarms with schedule labels — for clock or morning-routine widget grids.

    +
    + +

    Calendar Event

    +

    Dot-and-chip event rows with default data — drop into calendar or agenda previews.

    +
    + +

    Music Widget

    +

    Track list, cover art, and transport controls for media player mockups.

    +
    + +

    Shopping List

    +

    Interactive checkboxes and configurable items — grocery or errand list tiles.

    +
    + +

    Notes

    +

    Line-based note content with the yellow sticky treatment — quick memo widgets.

    +
    + +

    Fund Widget

    +

    Chart scaled into the square `size-52` shell for finance glance layouts.

    +
    + +

    Calorie Counter

    +

    Tall donut summary with flex layout instead of absolute positioning — nutrition dashboards.

    +
    + +

    Card Spread

    +

    Stacked deck with a subtle hover peek and click-to-spread throw easing. Use when you want several related cards in one compact stack that expands on demand.

    +
    +
    + +## Documentation + +Unpublished Calendar Widget, Music Stack Interaction, Team Clock, and Video Chat from the docs and shadcn registry so the widget overview lists only glance-sized tiles. Removed the internal widget catalog standards page from contributing. Added `useIsMobile` for responsive docs and demo behavior. Widget overview now tracks published entries only and places Fund Widget in the square section. diff --git a/content/docs/changelog/index.mdx b/content/docs/changelog/index.mdx index cfbc52d1..d8341b61 100644 --- a/content/docs/changelog/index.mdx +++ b/content/docs/changelog/index.mdx @@ -8,13 +8,13 @@ New components, bug fixes, and infrastructure changes, going back to the beta la ## Recent releases -### [May 2026](/docs/changelog/2026-05) +### [June 2026](/docs/changelog/2026-06) -[Boids Ecosystem](/docs/background/boids-ecosystem), [Sibling Focus Nav](/docs/container/sibling-focus-nav), live demos at `/demo`, and Tailwind CSS 4.3 with a shadcn/ui registry refresh. +Wave 1 widget glance polish, [Card Spread](/docs/card/card-spread) throw motion, and catalog cleanup for published widgets only. -### [April 2026](/docs/changelog/2026-04) +### [May 2026](/docs/changelog/2026-05) -19 new text animation presets built on a shared WAAPI runtime. shadcn registry went live — every component became installable via URLs like `https://animata.design/r/{category}/{name}.json`. Announcement ribbon was added, and the site was revamped for speed and accessibility. +[Boids Ecosystem](/docs/background/boids-ecosystem), [Sibling Focus Nav](/docs/container/sibling-focus-nav), live demos at `/demo`, and Tailwind CSS 4.3 with a shadcn/ui registry refresh. --- @@ -22,6 +22,7 @@ New components, bug fixes, and infrastructure changes, going back to the beta la | Month | Highlights | |---|---| +| [June 2026](/docs/changelog/2026-06) | Widget glance polish, Card Spread, unpublished four oversized widgets | | [May 2026](/docs/changelog/2026-05) | Tailwind 4.3, Boids Ecosystem, Sibling Focus Nav, live demos, docs typography | | [April 2026](/docs/changelog/2026-04) | 19 text animation presets, shadcn registry, announcement ribbon, site revamp | | [March 2026](/docs/changelog/2026-03) | Next.js 16, Tailwind v4, Storybook 10, Biome | diff --git a/content/docs/contributing/components.mdx b/content/docs/contributing/components.mdx index 5021e5ae..e5730c7b 100644 --- a/content/docs/contributing/components.mdx +++ b/content/docs/contributing/components.mdx @@ -50,6 +50,26 @@ Simply open the `config/docs.ts` file and paste the new entry in the `docsConfig The script above generates a placeholder documentation file for the new component. You can find the file in the `content/docs/{type}` directory. Update it with the relevant information about the component you are creating and remove the placeholder content. +### Manual install steps (`` / ``) + +`` renders an `

    `. In MDX, put a **blank line** after ``, after each ``, and before fenced code blocks. Otherwise MDX wraps steps in `

    ` and you get invalid HTML (`

    ` cannot contain `

    `) and hydration errors. + +````mdx + + +Install dependencies + +```bash +npm install lucide-react +``` + +Run the following command + +Your prose here… + + +```` + ## Proposing pull requests - **Making changes**: implement your bug fix or feature and write stories to cover different use cases. Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)‘s main branch. diff --git a/content/docs/widget/alarm-clock.mdx b/content/docs/widget/alarm-clock.mdx index 71d96111..ca02f999 100644 --- a/content/docs/widget/alarm-clock.mdx +++ b/content/docs/widget/alarm-clock.mdx @@ -1,11 +1,15 @@ --- title: Alarm Clock -description: It is a alarm clock +description: Toggle one or two alarms on a compact card — built for a morning routine glance. labels: ["requires interaction", "toggle"] author: Sumingurung9 --- - + + +## How it works + +Each entry in `alarms` needs `time`, `schedule`, and a stable `id` for toggle state. `defaultEnabledIds` sets which alarms start on. The list is capped at two rows so the tile stays readable; add more alarms in your app screen, not on the widget. ## Installation diff --git a/content/docs/widget/calendar-event.mdx b/content/docs/widget/calendar-event.mdx index 4f97a524..58349471 100644 --- a/content/docs/widget/calendar-event.mdx +++ b/content/docs/widget/calendar-event.mdx @@ -1,10 +1,23 @@ --- title: Calendar event -description: Calendar event widget +description: Show today’s date plus the next events — built for a calendar home-screen widget. author: Sumin Gurung --- - + + +## How it works + +Pass an `events` array with `title`, `time`, and optional `variant` (`violet`, `cyan`, `emerald`, `amber`, `rose`) for chip colors. `maxVisible` controls how many rows fit before a “+N more” footer. Override `date` when you are not showing “today”. + +```tsx + +``` ## Installation @@ -15,6 +28,7 @@ author: Sumin Gurung ### Manual + Run the following command It will create a new file called `calendar-event.tsx` inside the `components/animata/widget` directory. diff --git a/content/docs/widget/calendar-widget.mdx b/content/docs/widget/calendar-widget.mdx index b1e34200..523580b3 100644 --- a/content/docs/widget/calendar-widget.mdx +++ b/content/docs/widget/calendar-widget.mdx @@ -2,6 +2,7 @@ title: Calendar Widget description: calendar widget is calender like widget with smooth animations and cool effects author: anshu_code +published: false --- diff --git a/content/docs/widget/delivery-card.mdx b/content/docs/widget/delivery-card.mdx index fcf028b0..ba76e5ef 100644 --- a/content/docs/widget/delivery-card.mdx +++ b/content/docs/widget/delivery-card.mdx @@ -1,10 +1,14 @@ --- title: Delivery Card -description: This card helps user to track their orders. +description: Show delivery ETA, status, and route progress on a single home-screen tile. author: sanjaya22780 --- - + + +## How it works + +Pass `progress` (0–100), `arrivalTime`, `location`, and `timeAgo` from your order API. Progress drives the truck marker along a simple bar. Set `simulateProgress` only in demos — it cycles progress on an interval so Storybook feels alive without surprising production embeds. ## Installation @@ -15,6 +19,7 @@ author: sanjaya22780 ### Manual + Install dependencies ```bash diff --git a/content/docs/widget/index.mdx b/content/docs/widget/index.mdx index a438b656..b9e7c5e0 100644 --- a/content/docs/widget/index.mdx +++ b/content/docs/widget/index.mdx @@ -14,6 +14,7 @@ import DeliveryCard from "@/animata/widget/delivery-card"; import DirectionCard from "@/animata/widget/direction-card"; import ExpenseTracker from "@/animata/widget/expense-tracker"; import FlightWidget from "@/animata/widget/flight-widget"; +import FundWidget from "@/animata/widget/fund-widget"; import LiveScore from "@/animata/widget/live-score"; import MobileDetail from "@/animata/widget/mobile-detail"; import MusicWidget from "@/animata/widget/music-widget"; @@ -100,6 +101,14 @@ import WeeklyProgress from "@/animata/widget/weekly-progress"; + + + ```tsx file=/animata/widget/fund-widget.tsx copyId="fund-widget" + + ```` + + + ```tsx file=/animata/widget/mobile-detail.tsx copyId="mobile-detail" diff --git a/content/docs/widget/music-stack-interaction.mdx b/content/docs/widget/music-stack-interaction.mdx index 4fbaf5c0..6f474398 100644 --- a/content/docs/widget/music-stack-interaction.mdx +++ b/content/docs/widget/music-stack-interaction.mdx @@ -2,6 +2,7 @@ title: Music Stack Interaction description: widget for Music stack and unstacking author: Mahlawat2001 +published: false --- diff --git a/content/docs/widget/reminder.mdx b/content/docs/widget/reminder.mdx index 90b9127c..af68d39b 100644 --- a/content/docs/widget/reminder.mdx +++ b/content/docs/widget/reminder.mdx @@ -1,10 +1,14 @@ --- title: Reminder -description: A reminder widget shows notifications for important tasks +description: Glance at open tasks by list and see what is coming up next. author: ll4xmii --- - + + +## How it works + +The card is a fixed `size-52` tile. Pass `buckets` for the two summary counts (work, home, or any labels you define) and `nextLabel` for the single line people read first — usually the next meeting or errand. Styling stays in your story or app; the primitive only handles layout. ## Installation @@ -15,6 +19,7 @@ author: ll4xmii ### Manual + Run the following command It will create a new file called `reminder.tsx` inside the `components/animata/widget` directory. diff --git a/content/docs/widget/team-clock.mdx b/content/docs/widget/team-clock.mdx index 44f0cdb2..0bd78371 100644 --- a/content/docs/widget/team-clock.mdx +++ b/content/docs/widget/team-clock.mdx @@ -2,6 +2,7 @@ title: Team Clock description: A customizable, animated clock displaying multiple time zones for remote teams. author: arjuncodess +published: false --- diff --git a/content/docs/widget/video-chat.mdx b/content/docs/widget/video-chat.mdx index 3409013f..a640c438 100644 --- a/content/docs/widget/video-chat.mdx +++ b/content/docs/widget/video-chat.mdx @@ -3,6 +3,7 @@ title: Video Chat description: Video chat in real-time labels: ["requires interaction", "click"] author: AashishKatila +published: false --- diff --git a/content/docs/widget/vpn-widget.mdx b/content/docs/widget/vpn-widget.mdx index ef0a6f27..b376c409 100644 --- a/content/docs/widget/vpn-widget.mdx +++ b/content/docs/widget/vpn-widget.mdx @@ -1,10 +1,14 @@ --- title: VPN Connection -description: Shows the Vpn Connectivity +description: See server, speed, and connection status at a glance — with a slide-to-connect control. labels: ["requires interaction", "click"] --- - + + +## How it works + +`serverName`, `latency`, and `downloadMbps` are display-only. Connection state is uncontrolled (`defaultConnected`) or controlled (`connected` + `onConnectedChange`). The toggle is a real `role="switch"` button with a 44px hit area, not a hover-only chip. ## Installation @@ -15,10 +19,13 @@ labels: ["requires interaction", "click"] ### Manual + Install dependencies + ```bash npm install lucide-react ``` + Run the following command It will create a new file called `vpn-widget.tsx` inside the `components/animata/widget` directory. diff --git a/content/docs/widget/water-tracker.mdx b/content/docs/widget/water-tracker.mdx index 05f59f17..55d72c6d 100644 --- a/content/docs/widget/water-tracker.mdx +++ b/content/docs/widget/water-tracker.mdx @@ -1,11 +1,15 @@ --- title: Water Tracker -description: Water tracker +description: Track daily water intake with a glanceable fill bar and one-tap top-ups. labels: ["requires interaction", "click"] author: Sumingurung9 --- - + + +## How it works + +`dailyGoal` and `sipMl` set the target and increment size. Use `defaultIntake` for an uncontrolled demo, or `intake` with `onIntakeChange` when your app owns hydration state. The right edge is a CSS height transition — no chart library — so the tile stays lightweight for bento grids. ## Installation @@ -16,6 +20,7 @@ author: Sumingurung9 ### Manual + Install dependencies ```bash diff --git a/content/docs/widget/weather-card.mdx b/content/docs/widget/weather-card.mdx index 1dcf1bff..d1a99837 100644 --- a/content/docs/widget/weather-card.mdx +++ b/content/docs/widget/weather-card.mdx @@ -1,10 +1,14 @@ --- title: Weather Card -description: A cool weather widget +description: Current temperature, feels-like, and today’s high/low for any city. author: AdashBaral --- - + + +## How it works + +All content is props — `city`, `temperature`, `feelsLike`, `high`, `low` — wired to your weather API in the parent. Typography uses three levels: city label, hero degree, and footer hi/lo. The card uses theme tokens so light and dark modes stay legible without custom hex per theme. ## Installation @@ -17,7 +21,10 @@ author: AdashBaral Install dependencies -```bash npm install lucide-react ``` + +```bash +npm install lucide-react +``` Run the following command diff --git a/hooks/use-mobile.tsx b/hooks/use-mobile.tsx new file mode 100644 index 00000000..502fd323 --- /dev/null +++ b/hooks/use-mobile.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; + +const MOBILE_BREAKPOINT = 768; + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState(undefined); + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + }; + mql.addEventListener("change", onChange); + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + return () => mql.removeEventListener("change", onChange); + }, []); + + return !!isMobile; +} diff --git a/lib/inline-code.ts b/lib/inline-code.ts index 1421bc23..01b775bd 100644 --- a/lib/inline-code.ts +++ b/lib/inline-code.ts @@ -17,8 +17,8 @@ const inlineCodeDark = { boxShadow: "inset 0 1px 0 0 hsl(0 0% 100% / 0.05), 0 1px 2px hsl(0 0% 0% / 0.34)", } as const; -/** Keep tokens on one line — asymmetric pad + trailing margin for prose gaps. */ -const inlineCodeWhitespaceClasses = "whitespace-nowrap ms-[0.06em] me-[0.32em]"; +/** Keep inline code on one line. */ +const inlineCodeWhitespaceClasses = "whitespace-nowrap"; /** Rich inline chip — gradient fill, hairline border, inset highlight. */ export const inlineCodeClassName = cn( From 1c19f40322ced67110770aab26c2130a176a8969 Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Tue, 2 Jun 2026 07:27:18 +0545 Subject: [PATCH 7/8] fix(landing): remove card stack row from stats bento (#480) --- app/(main)/_landing/stats-bento.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/(main)/_landing/stats-bento.tsx b/app/(main)/_landing/stats-bento.tsx index a7885f60..b391ade6 100644 --- a/app/(main)/_landing/stats-bento.tsx +++ b/app/(main)/_landing/stats-bento.tsx @@ -16,8 +16,6 @@ import { docsConfig } from "@/config/docs"; import { siteStats } from "@/config/site-stats"; import { cn } from "@/lib/utils"; -import { CardStackBento } from "./card-stack-bento"; - // Get component categories from the sidebar nav (skip Getting Started + Contributing) const categories = docsConfig.sidebarNav .filter((item) => item.title !== "Getting Started" && item.title !== "Contributing") @@ -303,12 +301,6 @@ export default function StatsBento() {

    -
    - - - -
    - {/* Built with care — full width */}
    From 8f61a2d995ff96dce2299893dc64af7329f639bd Mon Sep 17 00:00:00 2001 From: Hari Lamichhane Date: Tue, 2 Jun 2026 10:52:38 +0545 Subject: [PATCH 8/8] feat(card): add collab-card multiplayer bento tile (#481) --- animata/card/collab-card.stories.tsx | 56 ++++ animata/card/collab-card.tsx | 339 +++++++++++++++++++++++ components/mdx-components.tsx | 2 + components/props-table.tsx | 115 ++++++++ content/docs/card/collab-card.mdx | 134 +++++++++ content/docs/changelog/2026-06.mdx | 6 +- content/docs/contributing/components.mdx | 16 ++ scripts/build-docs-markdown.js | 53 +++- 8 files changed, 718 insertions(+), 3 deletions(-) create mode 100644 animata/card/collab-card.stories.tsx create mode 100644 animata/card/collab-card.tsx create mode 100644 components/props-table.tsx create mode 100644 content/docs/card/collab-card.mdx diff --git a/animata/card/collab-card.stories.tsx b/animata/card/collab-card.stories.tsx new file mode 100644 index 00000000..17ad642e --- /dev/null +++ b/animata/card/collab-card.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import CollabCard from "@/animata/card/collab-card"; + +const meta = { + title: "Card/Collab Card", + component: CollabCard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + greeting: { control: "text" }, + eyebrow: { control: "text" }, + intro: { control: "text" }, + conjunction: { control: "text" }, + trailing: { control: "text" }, + liveLabel: { control: "text" }, + backgroundUrl: { control: "text" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** Figma multiplayer — dashed frame, live presence, named cursor pills. */ +export const Primary: Story = { + args: { + greeting: "hello!", + eyebrow: "Now in multiplayer", + intro: "editing", + conjunction: "&", + trailing: "", + liveLabel: "Live · 4 editing", + }, + render: (args) => ( +
    + +
    + ), +}; + +/** Bento slot — scales via container queries, not viewport breakpoints. */ +export const InBentoGrid: Story = { + render: () => ( +
    +
    + +
    +
    + Drop into a wide bento cell — frame, greeting, pills, and cursors track the card width via{" "} + cqi. +
    +
    + ), +}; diff --git a/animata/card/collab-card.tsx b/animata/card/collab-card.tsx new file mode 100644 index 00000000..22e5497e --- /dev/null +++ b/animata/card/collab-card.tsx @@ -0,0 +1,339 @@ +import { cn } from "@/lib/utils"; + +export interface Collaborator { + name: string; + /** Tailwind background class for the pill. */ + pill: string; + /** Tailwind text class for the pill label. */ + pillText?: string; + /** Tailwind text color class for the cursor (sets `color`, used by SVG `fill`). */ + cursor: string; +} + +export interface CollabCardProps { + greeting?: string; + eyebrow?: string; + intro?: string; + conjunction?: string; + trailing?: string; + /** Status line beside the live dot, e.g. `live · 4 editing`. */ + liveLabel?: string; + /** Swatch colors for the visible avatar stack (left → right). */ + presenceColors?: string[]; + /** Additional editors not shown as swatches — renders as `+N` on the stack. */ + extraCount?: number; + collaborators?: [Collaborator, Collaborator]; + /** Optional background image URL. Falls back to a dark canvas gradient. */ + backgroundUrl?: string; + className?: string; +} + +const defaultCollaborators: [Collaborator, Collaborator] = [ + { + name: "Dylan", + pill: "bg-[#A259FF]", + pillText: "text-white", + cursor: "text-[#A259FF]", + }, + { + name: "Evan", + pill: "bg-[#FF7262]", + pillText: "text-white", + cursor: "text-[#FF7262]", + }, +]; + +const defaultPresenceColors = ["#A259FF", "#FF7262", "#1ABCFE", "#0ACF83"]; + +function PresenceStack({ + colors, + extraCount, + className, +}: { + colors: string[]; + extraCount: number; + className?: string; +}) { + const overlap = "-ml-[1.08cqi]"; + + return ( + + ); +} + +function Cursor({ className }: { className?: string }) { + return ( + + ); +} + +function ClickBurst({ className }: { className?: string }) { + return ( +