diff --git a/docs/features.md b/docs/features.md index 68692f078..c922ba07c 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1261,6 +1261,18 @@ Tests: `internal/theme/resolve_test.go`, `packages/ui/tests/themes.test.ts`, and `apps/web/scripts/claude-code-theme.mjs` cover catalog parity, validation, source colors, rendered surfaces, selection, reload and switching away. +## Native web scrollbars + +Chat/reading lists, the session sidebar, workspace content, shared ScrollArea, +and dialog viewports keep native scrolling with shared theme-aware styling: +9px tracks, rounded inset thumbs, and stronger hover feedback. Firefox uses the +standard thin/color fallback; touch and forced-colors mode retain platform +defaults. No new scroller, dependency, or scroll state is introduced. + +Code: `packages/ui/src/scrollbar.stylex.ts` and its viewport consumers. +Tests: `packages/ui/tests/scrollbars.mjs` (production CSS, browser geometry, +themes, keyboard/wheel, fallbacks and CSP); `WHIP/Scrollbars` in Storybook. + ## Web directory navigation The saved-session sidebar follows the compact Claude/Paper hierarchy while using diff --git a/docs/frontend.md b/docs/frontend.md index ec9b05cfa..290f59744 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -2422,6 +2422,20 @@ backdrop. Short dialogs remain centered; tall dialogs retain the former top spacing. Sheet and explicitly bounded picker compositions keep their own internal scrolling. Consumers should not add wrappers to implement default dialog scrolling. +Native scrollbar visuals are shared by `@whip/ui/scrollbar.stylex`: a 9px +track with a rounded thumb, a transparent 2px inset, and theme-foreground color at +25% opacity (40% on thumb hover). Transparent tracks blend into the owning +surface. Apply `scrollbarStyles.native` to the existing scrolling element, not a +new wrapper; shared ScrollArea, dialog viewports, workspace content, the session +sidebar and ReadingList already do this. Scroll ownership, virtualization, and +chat follow/reading-position behavior stay unchanged. Fine-pointer browsers use +WebKit pseudo-elements where supported; only the fallback uses standard +`scrollbar-width/color`, since non-auto values override the pseudo-elements. +Coarse pointers and forced-colors mode keep platform defaults. Tab strips retain +intentionally hidden scrollbars. The Scrollbars story and +`npm run test:scrollbars -w @whip/ui` cover both axes, themes, hover, keyboard/wheel +scrolling, platform fallbacks, and strict CSP in production-extracted CSS. + Use `stylex.create` and `stylex.props` for authored visuals and import shared variables from `@whip/ui/tokens.stylex`. The `.stylex` suffix is required for cross-package static resolution. Do not add Tailwind, CSS-in-JS runtime injection, diff --git a/packages/app/src/reading-list.tsx b/packages/app/src/reading-list.tsx index 0a3d1c8b7..d0f8c18ba 100644 --- a/packages/app/src/reading-list.tsx +++ b/packages/app/src/reading-list.tsx @@ -4,6 +4,7 @@ import { Button } from '@whip/ui'; import { ArrowDown } from 'lucide-react'; import * as stylex from '@stylexjs/stylex'; import { scale } from '@whip/ui/tokens.stylex'; +import { scrollbarStyles } from '@whip/ui/scrollbar.stylex'; import { useRuntime } from './context'; import { readingTarget } from './reading-positions'; import { layout } from './styles'; @@ -511,7 +512,7 @@ export function ReadingList({ )}
-
setScrolled(event.currentTarget.scrollTop > 0)} {...stylex.props(layout.sessionList, styles.list)}> +
setScrolled(event.currentTarget.scrollTop > 0)} {...stylex.props(layout.sessionList, styles.list, scrollbarStyles.native)}>