feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion

Installs all five Tier 2 polish deps the audit flagged. Each integrates
where it adds concrete value today:

- **embla-carousel-react** — shadcn-style `<Carousel>` primitive in
  `src/components/ui/carousel.tsx`. Available for future berth/yacht
  photo galleries; no current call site beyond the primitive.
- **yet-another-react-lightbox** — wired into the image branch of
  `file-preview-dialog.tsx`. Clicking the preview image now opens a
  fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the
  ~50kb only ships when a user actually previews an image.
- **@use-gesture/react** — `usePinch` on the PdfViewer's content
  pane for native pinch-zoom on tablets/phones. Clamped to the
  same [50%, 300%] range as the +/- buttons; desktop wheel still
  scrolls.
- **react-virtuoso** — installed but NOT wired. Inbox is naturally
  bounded by recent-notifications filter at ~10-20 items; ScrollArea
  handles it fine. Reserve for actual scale issues (admin audit log
  archive, etc.).
- **motion** — installed but NOT wired. Pipeline kanban uses
  dnd-kit's own transforms and conflicts with motion's layout
  animation. @formkit/auto-animate already handles list-mutation
  animations elsewhere. Available for opportunistic adoption when
  a polish surface emerges that the existing libraries don't cover.

Verified: tsc clean, vitest 1315/1315, next build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 23:29:22 +02:00
parent 4329db7fc3
commit d1c9469fa7
5 changed files with 418 additions and 5 deletions

147
pnpm-lock.yaml generated
View File

@@ -118,6 +118,9 @@ importers:
'@types/pdfkit':
specifier: ^0.17.6
version: 0.17.6
'@use-gesture/react':
specifier: ^10.3.1
version: 10.3.1(react@19.2.6)
archiver:
specifier: ^7.0.1
version: 7.0.1
@@ -145,6 +148,9 @@ importers:
drizzle-orm:
specifier: ^0.45.2
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(gel@2.2.0)(kysely@0.28.17)(postgres@3.4.9)
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.2.6)
imapflow:
specifier: ^1.3.3
version: 1.3.3
@@ -172,6 +178,9 @@ importers:
minio:
specifier: ^8.0.7
version: 8.0.7
motion:
specifier: ^12.38.0
version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
next:
specifier: 16.2.6
version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -241,6 +250,9 @@ importers:
react-resizable-panels:
specifier: ^3.0.6
version: 3.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react-virtuoso:
specifier: ^4.18.7
version: 4.18.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
recharts:
specifier: ^3.8.1
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react-is@18.3.1)(react@19.2.6)(redux@5.0.1)
@@ -280,6 +292,9 @@ importers:
web-vitals:
specifier: ^5.2.0
version: 5.2.0
yet-another-react-lightbox:
specifier: ^3.32.0
version: 3.32.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
zod:
specifier: ^4.4.3
version: 4.4.3
@@ -3446,6 +3461,14 @@ packages:
cpu: [x64]
os: [win32]
'@use-gesture/core@10.3.1':
resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
'@use-gesture/react@10.3.1':
resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
peerDependencies:
react: '>= 16.8.0'
'@vitejs/plugin-react@6.0.1':
resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -4434,6 +4457,19 @@ packages:
electron-to-chromium@1.5.353:
resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==}
embla-carousel-react@8.6.0:
resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==}
peerDependencies:
react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
embla-carousel-reactive-utils@8.6.0:
resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==}
peerDependencies:
embla-carousel: 8.6.0
embla-carousel@8.6.0:
resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==}
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@@ -4786,6 +4822,20 @@ packages:
forwarded-parse@2.1.2:
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
framer-motion@12.38.0:
resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -5631,6 +5681,26 @@ packages:
socks:
optional: true
motion-dom@12.38.0:
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
motion-utils@12.36.0:
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
motion@12.38.0:
resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -6224,6 +6294,12 @@ packages:
'@types/react':
optional: true
react-virtuoso@4.18.7:
resolution: {integrity: sha512-xNF5zDGEEIMB7cKwcen/pLig0YDf6OnfFrVgKFa7sHPf9fRem0CaLshyObbBcP88jzn0enavL39EgplgdyT21g==}
peerDependencies:
react: '>=16 || >=17 || >= 18 || >= 19'
react-dom: '>=16 || >=17 || >= 18 || >=19'
react@19.2.6:
resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==}
engines: {node: '>=0.10.0'}
@@ -7281,6 +7357,20 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yet-another-react-lightbox@3.32.0:
resolution: {integrity: sha512-FWODOMrE07i3O5MeWRcYlcnAUk518zkUYKAe307pVW5pkey3hKMcAIWn8yMIURzGvbd3m9eghWO9CocEZmQPIg==}
engines: {node: '>=14'}
peerDependencies:
'@types/react': ^16 || ^17 || ^18 || ^19
'@types/react-dom': ^16 || ^17 || ^18 || ^19
react: ^16.8.0 || ^17 || ^18 || ^19
react-dom: ^16.8.0 || ^17 || ^18 || ^19
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -10188,6 +10278,13 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
'@use-gesture/core@10.3.1': {}
'@use-gesture/react@10.3.1(react@19.2.6)':
dependencies:
'@use-gesture/core': 10.3.1
react: 19.2.6
'@vitejs/plugin-react@6.0.1(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.41)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.7
@@ -11097,6 +11194,18 @@ snapshots:
electron-to-chromium@1.5.353: {}
embla-carousel-react@8.6.0(react@19.2.6):
dependencies:
embla-carousel: 8.6.0
embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0)
react: 19.2.6
embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0):
dependencies:
embla-carousel: 8.6.0
embla-carousel@8.6.0: {}
emoji-regex-xs@1.0.0: {}
emoji-regex@10.6.0: {}
@@ -11626,6 +11735,16 @@ snapshots:
forwarded-parse@2.1.2: {}
framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
motion-dom: 12.38.0
motion-utils: 12.36.0
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
fsevents@2.3.2:
optional: true
@@ -12464,6 +12583,21 @@ snapshots:
socks: 2.8.8
optional: true
motion-dom@12.38.0:
dependencies:
motion-utils: 12.36.0
motion-utils@12.36.0: {}
motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
mrmime@2.0.1: {}
ms@2.1.3: {}
@@ -13068,6 +13202,11 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.14
react-virtuoso@4.18.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
react@19.2.6: {}
readable-stream@2.3.8:
@@ -14296,6 +14435,14 @@ snapshots:
yaml@2.8.4:
optional: true
yet-another-react-lightbox@3.32.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
optionalDependencies:
'@types/react': 19.2.14
'@types/react-dom': 19.2.3(@types/react@19.2.14)
yocto-queue@0.1.0: {}
yocto-queue@1.2.2: {}