@cdx-ui/components 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -0
- package/README.md +2 -0
- package/coverage.svg +1 -1
- package/docs/components/accordion/content.md +538 -0
- package/docs/components/alert/content.md +203 -0
- package/docs/components/alert-dialog/content.md +319 -0
- package/docs/components/avatar/content.md +368 -0
- package/docs/components/avatar/design.md +202 -0
- package/docs/components/badge/content.md +225 -0
- package/docs/components/bottom-sheet/content.md +353 -0
- package/docs/components/bottom-sheet/design.md +249 -0
- package/docs/components/box/content.md +141 -0
- package/docs/components/breadcrumbs/content.md +169 -0
- package/docs/components/button/content.md +398 -0
- package/docs/components/button/design.md +258 -0
- package/docs/components/calendar/content.md +137 -0
- package/docs/components/card/content.md +283 -0
- package/docs/components/carousel/content.md +417 -0
- package/docs/components/checkbox/content.md +236 -0
- package/docs/components/checkbox/design.md +214 -0
- package/docs/components/chip/content.md +439 -0
- package/docs/components/chip/design.md +418 -0
- package/docs/components/color-area/content.md +84 -0
- package/docs/components/color-field/content.md +132 -0
- package/docs/components/color-picker/content.md +209 -0
- package/docs/components/color-slider/content.md +118 -0
- package/docs/components/color-swatch/content.md +62 -0
- package/docs/components/color-swatch-picker/content.md +124 -0
- package/docs/components/content.md +98 -0
- package/docs/components/currency-input/content.md +205 -0
- package/docs/components/data-grid/content.md +521 -0
- package/docs/components/date-field/content.md +147 -0
- package/docs/components/date-picker/content.md +360 -0
- package/docs/components/date-range-picker/content.md +301 -0
- package/docs/components/date-time-field/content.md +157 -0
- package/docs/components/date-time-list/content.md +105 -0
- package/docs/components/date-time-picker/content.md +272 -0
- package/docs/components/dialog/content.md +436 -0
- package/docs/components/dialog/design.md +192 -0
- package/docs/components/divider/content.md +112 -0
- package/docs/components/drawer/content.md +428 -0
- package/docs/components/field/content.md +277 -0
- package/docs/components/form/content.md +62 -0
- package/docs/components/full-screen-dialog/content.md +881 -0
- package/docs/components/heading/content.md +55 -0
- package/docs/components/icon/content.md +190 -0
- package/docs/components/icon-button/content.md +200 -0
- package/docs/components/icon-button/design.md +165 -0
- package/docs/components/image/content.md +104 -0
- package/docs/components/input/content.md +346 -0
- package/docs/components/input/design.md +389 -0
- package/docs/components/insurance-disclosure/content.md +103 -0
- package/docs/components/link/content.md +180 -0
- package/docs/components/link/design.md +160 -0
- package/docs/components/list-item/content.md +695 -0
- package/docs/components/menu/content.md +1254 -0
- package/docs/components/month-calendar/content.md +84 -0
- package/docs/components/month-picker/content.md +273 -0
- package/docs/components/otp-input/content.md +327 -0
- package/docs/components/progress-bar/content.md +87 -0
- package/docs/components/progress-circle/content.md +176 -0
- package/docs/components/progress-segmented/content.md +119 -0
- package/docs/components/quick-action-button/content.md +154 -0
- package/docs/components/quick-action-button/design.md +170 -0
- package/docs/components/radio/content.md +224 -0
- package/docs/components/range-calendar/content.md +123 -0
- package/docs/components/reorderable-list/content.md +311 -0
- package/docs/components/scoped-theme/content.md +47 -0
- package/docs/components/scroll-shadow/content.md +693 -0
- package/docs/components/select/content.md +473 -0
- package/docs/components/select/design.md +301 -0
- package/docs/components/skeleton/content.md +104 -0
- package/docs/components/slider/content.md +423 -0
- package/docs/components/stack/content.md +161 -0
- package/docs/components/switch/content.md +150 -0
- package/docs/components/switch/design.md +208 -0
- package/docs/components/table/content.md +548 -0
- package/docs/components/table-pagination/content.md +177 -0
- package/docs/components/tabs/content.md +297 -0
- package/docs/components/text/content.md +142 -0
- package/docs/components/textarea/content.md +222 -0
- package/docs/components/tile/content.md +702 -0
- package/docs/components/toast/content.md +456 -0
- package/docs/components/toggle-button/content.md +505 -0
- package/docs/components/tooltip/content.md +350 -0
- package/docs/components/virtualized-list/content.md +250 -0
- package/docs/foundations/content.md +9 -0
- package/docs/foundations/expressions/icons/content.md +19 -0
- package/docs/foundations/styles/tokens/all-tokens/content.md +192 -0
- package/docs/foundations/styles/tokens/what-are-tokens/content.md +33 -0
- package/docs/foundations/styles/tokens/what-are-tokens/design.md +33 -0
- package/docs/getting-started/content.md +4 -0
- package/docs/getting-started/designers/content.md +13 -0
- package/docs/getting-started/designers/design.md +13 -0
- package/docs/getting-started/developers/accessibility/content.md +31 -0
- package/docs/getting-started/developers/content.md +612 -0
- package/docs/getting-started/developers/styling/content.md +195 -0
- package/docs/llms.txt +195 -0
- package/docs/patterns/account-management/content.md +75 -0
- package/docs/patterns/authentication/content.md +50 -0
- package/docs/patterns/content.md +10 -0
- package/docs/patterns/dashboard/content.md +34 -0
- package/docs/patterns/empty-states/content.md +32 -0
- package/docs/patterns/forms/content.md +108 -0
- package/docs/patterns/onboarding/content.md +69 -0
- package/docs/patterns/payments/content.md +35 -0
- package/docs/patterns/settings/content.md +72 -0
- package/docs/resources/content.md +9 -0
- package/docs/resources/figma-libraries/content.md +42 -0
- package/docs/resources/release-notes/0.0.1-beta.118/content.md +27 -0
- package/docs/resources/release-notes/0.0.1-beta.122/content.md +21 -0
- package/docs/resources/release-notes/0.0.1-beta.124/content.md +16 -0
- package/docs/resources/release-notes/0.0.1-beta.126/content.md +10 -0
- package/docs/resources/release-notes/0.0.1-beta.127/content.md +14 -0
- package/docs/resources/release-notes/0.0.1-beta.128/content.md +9 -0
- package/docs/resources/release-notes/0.0.1-beta.129/content.md +18 -0
- package/docs/resources/release-notes/0.0.1-beta.130/content.md +18 -0
- package/docs/resources/release-notes/0.0.1-beta.133/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.134/content.md +17 -0
- package/docs/resources/release-notes/0.0.1-beta.136/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.137/content.md +10 -0
- package/docs/resources/release-notes/0.0.1-beta.138/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.139/content.md +13 -0
- package/docs/resources/release-notes/0.0.1-beta.140/content.md +26 -0
- package/docs/resources/release-notes/0.0.1-beta.141/content.md +63 -0
- package/docs/resources/release-notes/0.0.1-beta.142/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.143/content.md +12 -0
- package/docs/resources/release-notes/0.0.1-beta.144/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.145/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.147/content.md +10 -0
- package/docs/resources/release-notes/0.0.1-beta.149/content.md +10 -0
- package/docs/resources/release-notes/0.0.1-beta.150/content.md +13 -0
- package/docs/resources/release-notes/0.0.1-beta.152/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.154/content.md +15 -0
- package/docs/resources/release-notes/0.0.1-beta.156/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.158/content.md +9 -0
- package/docs/resources/release-notes/0.0.1-beta.159/content.md +11 -0
- package/docs/resources/release-notes/0.0.1-beta.160/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.161/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.163/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.164/content.md +9 -0
- package/docs/resources/release-notes/0.0.1-beta.166/content.md +17 -0
- package/docs/resources/release-notes/0.0.1-beta.167/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.168/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.169/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.171/content.md +16 -0
- package/docs/resources/release-notes/0.0.1-beta.172/content.md +24 -0
- package/docs/resources/release-notes/0.0.1-beta.173/content.md +15 -0
- package/docs/resources/release-notes/0.0.1-beta.174/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.176/content.md +32 -0
- package/docs/resources/release-notes/0.0.1-beta.179/content.md +11 -0
- package/docs/resources/release-notes/0.0.1-beta.182/content.md +13 -0
- package/docs/resources/release-notes/0.0.1-beta.185/content.md +7 -0
- package/docs/resources/release-notes/0.0.1-beta.187/content.md +27 -0
- package/docs/resources/release-notes/0.1.1/content.md +23 -0
- package/docs/resources/release-notes/0.1.2/content.md +7 -0
- package/docs/resources/release-notes/0.1.4/content.md +19 -0
- package/docs/resources/release-notes/0.1.5/content.md +7 -0
- package/docs/resources/release-notes/0.1.6/content.md +7 -0
- package/docs/resources/release-notes/0.2.0/content.md +31 -0
- package/docs/resources/release-notes/0.2.1/content.md +17 -0
- package/docs/resources/release-notes/0.2.2/content.md +11 -0
- package/docs/resources/release-notes/0.2.3/content.md +15 -0
- package/docs/resources/release-notes/0.2.4/content.md +17 -0
- package/docs/resources/release-notes/content.md +65 -0
- package/lib/commonjs/components/Box/Box.js +9 -1
- package/lib/commonjs/components/Box/Box.js.map +1 -1
- package/lib/commonjs/components/Breadcrumbs/index.js +28 -3
- package/lib/commonjs/components/Breadcrumbs/index.js.map +1 -1
- package/lib/commonjs/components/Breadcrumbs/styles.js +27 -0
- package/lib/commonjs/components/Breadcrumbs/styles.js.map +1 -1
- package/lib/commonjs/components/DateTimeList/index.js +11 -26
- package/lib/commonjs/components/DateTimeList/index.js.map +1 -1
- package/lib/commonjs/components/DateTimeList/styles.js +2 -12
- package/lib/commonjs/components/DateTimeList/styles.js.map +1 -1
- package/lib/commonjs/components/ListItem/index.js.map +1 -1
- package/lib/commonjs/components/ListItem/styles.js +2 -1
- package/lib/commonjs/components/ListItem/styles.js.map +1 -1
- package/lib/commonjs/components/Menu/index.js +11 -5
- package/lib/commonjs/components/Menu/index.js.map +1 -1
- package/lib/commonjs/components/ScrollShadow/gradient.js +29 -0
- package/lib/commonjs/components/ScrollShadow/gradient.js.map +1 -0
- package/lib/commonjs/components/ScrollShadow/index.js +171 -44
- package/lib/commonjs/components/ScrollShadow/index.js.map +1 -1
- package/lib/commonjs/components/ScrollShadow/styles.js +58 -14
- package/lib/commonjs/components/ScrollShadow/styles.js.map +1 -1
- package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js +63 -0
- package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
- package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +48 -8
- package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
- package/lib/commonjs/components/Stack/HStack.js +6 -2
- package/lib/commonjs/components/Stack/HStack.js.map +1 -1
- package/lib/commonjs/components/Stack/VStack.js +6 -2
- package/lib/commonjs/components/Stack/VStack.js.map +1 -1
- package/lib/commonjs/components/Tabs/index.js +16 -23
- package/lib/commonjs/components/Tabs/index.js.map +1 -1
- package/lib/commonjs/components/Tabs/styles.js +1 -10
- package/lib/commonjs/components/Tabs/styles.js.map +1 -1
- package/lib/commonjs/figma/ListItem.SectionHeader.figma.js +20 -5
- package/lib/commonjs/figma/ListItem.SectionHeader.figma.js.map +1 -1
- package/lib/commonjs/index.js +10 -10
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/Box/Box.js +9 -1
- package/lib/module/components/Box/Box.js.map +1 -1
- package/lib/module/components/Breadcrumbs/index.js +29 -4
- package/lib/module/components/Breadcrumbs/index.js.map +1 -1
- package/lib/module/components/Breadcrumbs/styles.js +26 -0
- package/lib/module/components/Breadcrumbs/styles.js.map +1 -1
- package/lib/module/components/DateTimeList/index.js +12 -27
- package/lib/module/components/DateTimeList/index.js.map +1 -1
- package/lib/module/components/DateTimeList/styles.js +1 -11
- package/lib/module/components/DateTimeList/styles.js.map +1 -1
- package/lib/module/components/ListItem/index.js.map +1 -1
- package/lib/module/components/ListItem/styles.js +2 -1
- package/lib/module/components/ListItem/styles.js.map +1 -1
- package/lib/module/components/Menu/index.js +11 -5
- package/lib/module/components/Menu/index.js.map +1 -1
- package/lib/module/components/ScrollShadow/gradient.js +24 -0
- package/lib/module/components/ScrollShadow/gradient.js.map +1 -0
- package/lib/module/components/ScrollShadow/index.js +170 -48
- package/lib/module/components/ScrollShadow/index.js.map +1 -1
- package/lib/module/components/ScrollShadow/styles.js +57 -13
- package/lib/module/components/ScrollShadow/styles.js.map +1 -1
- package/lib/module/components/ScrollShadow/useComposedScrollHandler.js +57 -0
- package/lib/module/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
- package/lib/module/components/ScrollShadow/useScrollShadow.js +46 -8
- package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
- package/lib/module/components/Stack/HStack.js +6 -2
- package/lib/module/components/Stack/HStack.js.map +1 -1
- package/lib/module/components/Stack/VStack.js +6 -2
- package/lib/module/components/Stack/VStack.js.map +1 -1
- package/lib/module/components/Tabs/index.js +17 -24
- package/lib/module/components/Tabs/index.js.map +1 -1
- package/lib/module/components/Tabs/styles.js +0 -9
- package/lib/module/components/Tabs/styles.js.map +1 -1
- package/lib/module/figma/ListItem.SectionHeader.figma.js +20 -5
- package/lib/module/figma/ListItem.SectionHeader.figma.js.map +1 -1
- package/lib/module/index.js +6 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/animation/hooks/usePopupDialogContentAnimation.d.ts +2 -2
- package/lib/typescript/animation/hooks/usePopupOverlayAnimation.d.ts +2 -2
- package/lib/typescript/components/Box/Box.d.ts +4 -0
- package/lib/typescript/components/Box/Box.d.ts.map +1 -1
- package/lib/typescript/components/Breadcrumbs/index.d.ts +3 -1
- package/lib/typescript/components/Breadcrumbs/index.d.ts.map +1 -1
- package/lib/typescript/components/Breadcrumbs/styles.d.ts +2 -0
- package/lib/typescript/components/Breadcrumbs/styles.d.ts.map +1 -1
- package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -1
- package/lib/typescript/components/DateTimeList/styles.d.ts +1 -4
- package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -1
- package/lib/typescript/components/ListItem/index.d.ts +5 -4
- package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
- package/lib/typescript/components/ListItem/styles.d.ts +1 -1
- package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
- package/lib/typescript/components/Menu/index.d.ts.map +1 -1
- package/lib/typescript/components/ScrollShadow/gradient.d.ts +7 -0
- package/lib/typescript/components/ScrollShadow/gradient.d.ts.map +1 -0
- package/lib/typescript/components/ScrollShadow/index.d.ts +57 -15
- package/lib/typescript/components/ScrollShadow/index.d.ts.map +1 -1
- package/lib/typescript/components/ScrollShadow/styles.d.ts +8 -2
- package/lib/typescript/components/ScrollShadow/styles.d.ts.map +1 -1
- package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts +13 -0
- package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts.map +1 -0
- package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts +15 -2
- package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
- package/lib/typescript/components/Stack/HStack.d.ts +3 -2
- package/lib/typescript/components/Stack/HStack.d.ts.map +1 -1
- package/lib/typescript/components/Stack/VStack.d.ts +3 -2
- package/lib/typescript/components/Stack/VStack.d.ts.map +1 -1
- package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
- package/lib/typescript/components/Tabs/styles.d.ts +0 -7
- package/lib/typescript/components/Tabs/styles.d.ts.map +1 -1
- package/lib/typescript/components/Toast/animation.d.ts +3 -3
- package/lib/typescript/components/Toast/animation.d.ts.map +1 -1
- package/lib/typescript/figma/ListItem.SectionHeader.figma.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +6 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +11 -9
- package/src/components/Box/Box.tsx +18 -4
- package/src/components/Breadcrumbs/index.tsx +28 -4
- package/src/components/Breadcrumbs/styles.ts +30 -0
- package/src/components/DateTimeList/index.tsx +13 -26
- package/src/components/DateTimeList/styles.ts +1 -10
- package/src/components/ListItem/index.tsx +5 -4
- package/src/components/ListItem/styles.ts +1 -0
- package/src/components/Menu/index.tsx +11 -8
- package/src/components/ScrollShadow/gradient.ts +48 -0
- package/src/components/ScrollShadow/index.tsx +300 -69
- package/src/components/ScrollShadow/styles.ts +45 -28
- package/src/components/ScrollShadow/useComposedScrollHandler.ts +58 -0
- package/src/components/ScrollShadow/useScrollShadow.ts +72 -10
- package/src/components/Stack/HStack.tsx +8 -4
- package/src/components/Stack/VStack.tsx +8 -4
- package/src/components/Tabs/index.tsx +25 -35
- package/src/components/Tabs/styles.ts +0 -9
- package/src/index.ts +8 -1
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
# Scroll Shadow (https://design.candescent.com/docs/components/scroll-shadow)
|
|
2
|
+
|
|
3
|
+
Resources: [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/layout-scrollshadow--default)
|
|
4
|
+
|
|
5
|
+
`ScrollShadow` wraps a single scroll view and fades its edges into the surface behind it, showing a fade only on the edge that still has content.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { ScrollShadow } from '@cdx-ui/components';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
>
|
|
13
|
+
> **Wrap the scroll view directly**
|
|
14
|
+
>
|
|
15
|
+
> On iOS, Android, and web, `ScrollShadow` is a wrapper, not a scroll container. It doesn't scroll
|
|
16
|
+
> anything itself, so you always bring your own `ScrollView`, `FlatList`, or `VirtualizedList`, and
|
|
17
|
+
> it must be the direct child. `ScrollShadow` reads that child's scroll events, so a component that
|
|
18
|
+
> renders a scroll view inside it gets no fades, even on web. The one exception is a forced
|
|
19
|
+
> `visibility`, covered under Visibility.
|
|
20
|
+
|
|
21
|
+
## Basic
|
|
22
|
+
|
|
23
|
+
Put the scroll view directly inside `ScrollShadow`. Orientation is inferred from the child's `horizontal` prop and defaults to vertical. Match `surface` to the panel behind the scroller so the fade blends in.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import {
|
|
27
|
+
Avatar,
|
|
28
|
+
Card,
|
|
29
|
+
Heading,
|
|
30
|
+
ListItem,
|
|
31
|
+
ScrollShadow,
|
|
32
|
+
ScrollView,
|
|
33
|
+
Text,
|
|
34
|
+
} from '@cdx-ui/components';
|
|
35
|
+
import { TRANSACTIONS } from './_data';
|
|
36
|
+
|
|
37
|
+
export default function ScrollShadowBasicDemo() {
|
|
38
|
+
return (
|
|
39
|
+
<Card fullBleed className="w-full max-w-sm">
|
|
40
|
+
<Card.Header>
|
|
41
|
+
<Heading size="sm">Recent activity</Heading>
|
|
42
|
+
<Text size="sm" color="secondary">
|
|
43
|
+
Checking · 6643
|
|
44
|
+
</Text>
|
|
45
|
+
</Card.Header>
|
|
46
|
+
<Card.Content>
|
|
47
|
+
<ScrollShadow className="h-80" surface="primary">
|
|
48
|
+
<ScrollView>
|
|
49
|
+
{TRANSACTIONS.map((transaction, index) => (
|
|
50
|
+
<ListItem key={transaction.id} showSeparator={index < TRANSACTIONS.length - 1}>
|
|
51
|
+
<ListItem.LeadingSlot>
|
|
52
|
+
<Avatar color={transaction.isCredit ? 'success' : 'neutral'}>
|
|
53
|
+
<Avatar.Icon as={transaction.icon} />
|
|
54
|
+
</Avatar>
|
|
55
|
+
</ListItem.LeadingSlot>
|
|
56
|
+
<ListItem.Content>
|
|
57
|
+
<ListItem.Title>{transaction.merchant}</ListItem.Title>
|
|
58
|
+
<ListItem.Description>{transaction.date}</ListItem.Description>
|
|
59
|
+
</ListItem.Content>
|
|
60
|
+
<ListItem.TrailingSlot>
|
|
61
|
+
<Text color={transaction.isCredit ? 'success' : 'primary'}>
|
|
62
|
+
{transaction.amount}
|
|
63
|
+
</Text>
|
|
64
|
+
</ListItem.TrailingSlot>
|
|
65
|
+
</ListItem>
|
|
66
|
+
))}
|
|
67
|
+
</ScrollView>
|
|
68
|
+
</ScrollShadow>
|
|
69
|
+
</Card.Content>
|
|
70
|
+
</Card>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Horizontal
|
|
76
|
+
|
|
77
|
+
A horizontal child gets fades on its start and end edges. Those edges are logical, not physical: in a left-to-right layout start is left and end is right; in RTL, start is right and end is left. You never pass `left` or `right`. The same `ScrollShadow` flips with the page direction. For a filter row, put a `ToggleButton.Group` with `isAttached={false}` inside the horizontal `ScrollView`.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useState } from 'react';
|
|
81
|
+
import {
|
|
82
|
+
Avatar,
|
|
83
|
+
Box,
|
|
84
|
+
Card,
|
|
85
|
+
Divider,
|
|
86
|
+
Heading,
|
|
87
|
+
ListItem,
|
|
88
|
+
ScrollShadow,
|
|
89
|
+
ScrollView,
|
|
90
|
+
Text,
|
|
91
|
+
ToggleButton,
|
|
92
|
+
} from '@cdx-ui/components';
|
|
93
|
+
import { ACTIVITY_FILTERS, TRANSACTIONS } from './_data';
|
|
94
|
+
|
|
95
|
+
const VISIBLE_ROWS = 4;
|
|
96
|
+
|
|
97
|
+
export default function ScrollShadowHorizontalDemo() {
|
|
98
|
+
const [filters, setFilters] = useState<string[]>(['purchases', 'dining']);
|
|
99
|
+
|
|
100
|
+
const matches =
|
|
101
|
+
filters.length > 0
|
|
102
|
+
? TRANSACTIONS.filter((transaction) => filters.includes(transaction.category))
|
|
103
|
+
: TRANSACTIONS;
|
|
104
|
+
const rows = matches.slice(0, VISIBLE_ROWS);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Card fullBleed className="w-full max-w-sm">
|
|
108
|
+
<Card.Header>
|
|
109
|
+
<Heading size="sm">Activity</Heading>
|
|
110
|
+
<Text size="sm" color="secondary">
|
|
111
|
+
{matches.length} of {TRANSACTIONS.length}
|
|
112
|
+
</Text>
|
|
113
|
+
</Card.Header>
|
|
114
|
+
<Card.Content>
|
|
115
|
+
<Box className="py-3">
|
|
116
|
+
<ScrollShadow surface="primary" hideScrollBar>
|
|
117
|
+
<ScrollView
|
|
118
|
+
horizontal
|
|
119
|
+
showsHorizontalScrollIndicator={false}
|
|
120
|
+
contentContainerClassName="px-5"
|
|
121
|
+
>
|
|
122
|
+
<ToggleButton.Group
|
|
123
|
+
type="multiple"
|
|
124
|
+
isAttached={false}
|
|
125
|
+
value={filters}
|
|
126
|
+
onValueChange={setFilters}
|
|
127
|
+
aria-label="Filter activity by type"
|
|
128
|
+
>
|
|
129
|
+
{ACTIVITY_FILTERS.map((filter) => (
|
|
130
|
+
<ToggleButton key={filter.value} value={filter.value} size="small">
|
|
131
|
+
<ToggleButton.Icon as={filter.icon} position="leading" />
|
|
132
|
+
<ToggleButton.Label>{filter.label}</ToggleButton.Label>
|
|
133
|
+
</ToggleButton>
|
|
134
|
+
))}
|
|
135
|
+
</ToggleButton.Group>
|
|
136
|
+
</ScrollView>
|
|
137
|
+
</ScrollShadow>
|
|
138
|
+
</Box>
|
|
139
|
+
<Divider />
|
|
140
|
+
{rows.map((transaction, index) => (
|
|
141
|
+
<ListItem key={transaction.id} showSeparator={index < rows.length - 1}>
|
|
142
|
+
<ListItem.LeadingSlot>
|
|
143
|
+
<Avatar color={transaction.isCredit ? 'success' : 'neutral'}>
|
|
144
|
+
<Avatar.Icon as={transaction.icon} />
|
|
145
|
+
</Avatar>
|
|
146
|
+
</ListItem.LeadingSlot>
|
|
147
|
+
<ListItem.Content>
|
|
148
|
+
<ListItem.Title>{transaction.merchant}</ListItem.Title>
|
|
149
|
+
<ListItem.Description>{transaction.date}</ListItem.Description>
|
|
150
|
+
</ListItem.Content>
|
|
151
|
+
<ListItem.TrailingSlot>
|
|
152
|
+
<Text color={transaction.isCredit ? 'success' : 'primary'}>{transaction.amount}</Text>
|
|
153
|
+
</ListItem.TrailingSlot>
|
|
154
|
+
</ListItem>
|
|
155
|
+
))}
|
|
156
|
+
</Card.Content>
|
|
157
|
+
</Card>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Surfaces
|
|
163
|
+
|
|
164
|
+
`surface` picks the fade color from the theme: `background`, `primary`, or `secondary`. The fade follows dark mode and `ScopedTheme`.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import {
|
|
168
|
+
Box,
|
|
169
|
+
Card,
|
|
170
|
+
QuickActionButton,
|
|
171
|
+
ScrollShadow,
|
|
172
|
+
ScrollView,
|
|
173
|
+
Text,
|
|
174
|
+
VStack,
|
|
175
|
+
type ScrollShadowProps,
|
|
176
|
+
} from '@cdx-ui/components';
|
|
177
|
+
import { QUICK_ACTIONS } from './_data';
|
|
178
|
+
|
|
179
|
+
type Surface = NonNullable<ScrollShadowProps['surface']>;
|
|
180
|
+
|
|
181
|
+
const panels: { surface: Surface; className?: string }[] = [
|
|
182
|
+
{ surface: 'background', className: 'bg-surface-background' },
|
|
183
|
+
{ surface: 'primary' },
|
|
184
|
+
{ surface: 'secondary', className: 'bg-surface-secondary' },
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
export default function ScrollShadowSurfacesDemo() {
|
|
188
|
+
return (
|
|
189
|
+
<VStack space="lg" className="w-full max-w-sm">
|
|
190
|
+
{panels.map(({ surface, className }) => (
|
|
191
|
+
<VStack key={surface} space="xs">
|
|
192
|
+
<Text size="sm" color="secondary">
|
|
193
|
+
{surface}
|
|
194
|
+
</Text>
|
|
195
|
+
<Card fullBleed className={className}>
|
|
196
|
+
<Card.Content>
|
|
197
|
+
<Box className="py-5">
|
|
198
|
+
<ScrollShadow surface={surface} size="large">
|
|
199
|
+
<ScrollView
|
|
200
|
+
horizontal
|
|
201
|
+
showsHorizontalScrollIndicator={false}
|
|
202
|
+
contentContainerClassName="gap-4 px-5"
|
|
203
|
+
>
|
|
204
|
+
{QUICK_ACTIONS.map((action) => (
|
|
205
|
+
<QuickActionButton key={action.label}>
|
|
206
|
+
<QuickActionButton.Icon as={action.icon} />
|
|
207
|
+
<QuickActionButton.Label>{action.label}</QuickActionButton.Label>
|
|
208
|
+
</QuickActionButton>
|
|
209
|
+
))}
|
|
210
|
+
</ScrollView>
|
|
211
|
+
</ScrollShadow>
|
|
212
|
+
</Box>
|
|
213
|
+
</Card.Content>
|
|
214
|
+
</Card>
|
|
215
|
+
</VStack>
|
|
216
|
+
))}
|
|
217
|
+
</VStack>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Sizes
|
|
223
|
+
|
|
224
|
+
`size` sets the fade depth along the scroll axis: `small` is 32px, `default` is 40px, and `large` is 72px.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import {
|
|
228
|
+
Avatar,
|
|
229
|
+
Card,
|
|
230
|
+
Divider,
|
|
231
|
+
Heading,
|
|
232
|
+
ScrollShadow,
|
|
233
|
+
ScrollView,
|
|
234
|
+
Text,
|
|
235
|
+
VStack,
|
|
236
|
+
type AvatarColor,
|
|
237
|
+
type ScrollShadowProps,
|
|
238
|
+
} from '@cdx-ui/components';
|
|
239
|
+
import { PAYEES } from './_data';
|
|
240
|
+
|
|
241
|
+
const sizes: NonNullable<ScrollShadowProps['size']>[] = ['small', 'default', 'large'];
|
|
242
|
+
|
|
243
|
+
const avatarColors: AvatarColor[] = ['brand', 'info', 'success', 'warning', 'danger', 'neutral'];
|
|
244
|
+
|
|
245
|
+
function Payees() {
|
|
246
|
+
return PAYEES.map((name, index) => (
|
|
247
|
+
<VStack key={name} space="xs" className="items-center">
|
|
248
|
+
<Avatar size="lg" color={avatarColors[index % avatarColors.length]}>
|
|
249
|
+
<Avatar.Text>{name}</Avatar.Text>
|
|
250
|
+
</Avatar>
|
|
251
|
+
<Text size="xs" numberOfLines={1}>
|
|
252
|
+
{name.split(' ')[0]}
|
|
253
|
+
</Text>
|
|
254
|
+
</VStack>
|
|
255
|
+
));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default function ScrollShadowSizesDemo() {
|
|
259
|
+
return (
|
|
260
|
+
<Card fullBleed className="w-full max-w-sm">
|
|
261
|
+
<Card.Header>
|
|
262
|
+
<Heading size="sm">Send again</Heading>
|
|
263
|
+
</Card.Header>
|
|
264
|
+
<Card.Content>
|
|
265
|
+
{sizes.map((size, index) => (
|
|
266
|
+
<VStack key={size}>
|
|
267
|
+
{index > 0 ? <Divider /> : null}
|
|
268
|
+
<VStack space="sm" className="py-5">
|
|
269
|
+
<Text size="sm" color="secondary" className="px-5">
|
|
270
|
+
{size}
|
|
271
|
+
</Text>
|
|
272
|
+
<ScrollShadow surface="primary" size={size}>
|
|
273
|
+
<ScrollView
|
|
274
|
+
horizontal
|
|
275
|
+
showsHorizontalScrollIndicator={false}
|
|
276
|
+
contentContainerClassName="gap-6 px-5"
|
|
277
|
+
>
|
|
278
|
+
<Payees />
|
|
279
|
+
</ScrollView>
|
|
280
|
+
</ScrollShadow>
|
|
281
|
+
</VStack>
|
|
282
|
+
</VStack>
|
|
283
|
+
))}
|
|
284
|
+
</Card.Content>
|
|
285
|
+
</Card>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Visibility
|
|
291
|
+
|
|
292
|
+
`visibility` defaults to `auto`, which shows each edge only while content overflows past it. Each fade eases out over its own depth as you scroll toward that edge. `both`, `start`, and `end` force fades on, and `none` hides them. The child can be any single view. Switch modes below and scroll the list to compare.
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
import { useState } from 'react';
|
|
296
|
+
import {
|
|
297
|
+
Avatar,
|
|
298
|
+
Card,
|
|
299
|
+
Heading,
|
|
300
|
+
ListItem,
|
|
301
|
+
ScrollShadow,
|
|
302
|
+
ScrollView,
|
|
303
|
+
Text,
|
|
304
|
+
ToggleButton,
|
|
305
|
+
VStack,
|
|
306
|
+
type ScrollShadowProps,
|
|
307
|
+
} from '@cdx-ui/components';
|
|
308
|
+
import { TRANSACTIONS } from './_data';
|
|
309
|
+
|
|
310
|
+
type Visibility = NonNullable<ScrollShadowProps['visibility']>;
|
|
311
|
+
|
|
312
|
+
const MODES: Visibility[] = ['auto', 'both', 'start', 'end', 'none'];
|
|
313
|
+
|
|
314
|
+
export default function ScrollShadowVisibilityDemo() {
|
|
315
|
+
const [visibility, setVisibility] = useState<Visibility>('both');
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<VStack space="md" className="w-full max-w-sm">
|
|
319
|
+
<ToggleButton.Group
|
|
320
|
+
type="single"
|
|
321
|
+
value={visibility}
|
|
322
|
+
onValueChange={(value) => setVisibility(value as Visibility)}
|
|
323
|
+
aria-label="visibility"
|
|
324
|
+
>
|
|
325
|
+
{MODES.map((mode) => (
|
|
326
|
+
<ToggleButton key={mode} value={mode} size="small">
|
|
327
|
+
<ToggleButton.Label>{mode}</ToggleButton.Label>
|
|
328
|
+
</ToggleButton>
|
|
329
|
+
))}
|
|
330
|
+
</ToggleButton.Group>
|
|
331
|
+
<Card fullBleed>
|
|
332
|
+
<Card.Header>
|
|
333
|
+
<Heading size="sm">Statement · September</Heading>
|
|
334
|
+
</Card.Header>
|
|
335
|
+
<Card.Content>
|
|
336
|
+
<ScrollShadow className="h-64" surface="primary" visibility={visibility}>
|
|
337
|
+
<ScrollView>
|
|
338
|
+
{TRANSACTIONS.map((transaction, index) => (
|
|
339
|
+
<ListItem
|
|
340
|
+
key={transaction.id}
|
|
341
|
+
size="compact"
|
|
342
|
+
showSeparator={index < TRANSACTIONS.length - 1}
|
|
343
|
+
>
|
|
344
|
+
<ListItem.LeadingSlot>
|
|
345
|
+
<Avatar color={transaction.isCredit ? 'success' : 'neutral'}>
|
|
346
|
+
<Avatar.Icon as={transaction.icon} />
|
|
347
|
+
</Avatar>
|
|
348
|
+
</ListItem.LeadingSlot>
|
|
349
|
+
<ListItem.Content>
|
|
350
|
+
<ListItem.Title>{transaction.merchant}</ListItem.Title>
|
|
351
|
+
</ListItem.Content>
|
|
352
|
+
<ListItem.TrailingSlot>
|
|
353
|
+
<Text color={transaction.isCredit ? 'success' : 'primary'}>
|
|
354
|
+
{transaction.amount}
|
|
355
|
+
</Text>
|
|
356
|
+
</ListItem.TrailingSlot>
|
|
357
|
+
</ListItem>
|
|
358
|
+
))}
|
|
359
|
+
</ScrollView>
|
|
360
|
+
</ScrollShadow>
|
|
361
|
+
</Card.Content>
|
|
362
|
+
</Card>
|
|
363
|
+
</VStack>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Defaults and styling
|
|
369
|
+
|
|
370
|
+
Start with `surface`, `size`, and `visibility`. If those don't fit, use one of these, from the smallest change to the largest:
|
|
371
|
+
|
|
372
|
+
- **Different depth or opacity:** pass `edgeClassName`. Its classes go on each fade overlay and are applied after `size`, so they win.
|
|
373
|
+
|
|
374
|
+
- Depth: `w-24` makes a horizontal fade 96px deep instead of the `size` value. Use `h-24` for a vertical scroller. Tailwind spacing steps are 4px each, so `w-14` is 56px and `w-24` is 96px.
|
|
375
|
+
- Opacity: `opacity-85` draws the fade at 85% strength, so a little of the content shows through even at the very edge. Lower values give a softer fade.
|
|
376
|
+
|
|
377
|
+
Don't put colors here; a `bg-*` class paints a solid block instead of a fade.
|
|
378
|
+
|
|
379
|
+
- **Different color:** pass `colorClassName` with an `accent-*` class when the panel isn't one of the `surface` colors, such as a tinted card. It replaces `surface` and still follows dark mode and `ScopedTheme`.
|
|
380
|
+
|
|
381
|
+
- **Your own indicator:** skip the fade overlays and use `useScrollShadow`. See Hook below.
|
|
382
|
+
|
|
383
|
+
`hideScrollBar` hides the scrollbar on web only. On iOS and Android, hide it on the child with `showsVerticalScrollIndicator` or `showsHorizontalScrollIndicator`.
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
import { useState } from 'react';
|
|
387
|
+
import {
|
|
388
|
+
Box,
|
|
389
|
+
Card,
|
|
390
|
+
Heading,
|
|
391
|
+
ScrollShadow,
|
|
392
|
+
ScrollView,
|
|
393
|
+
Text,
|
|
394
|
+
ToggleButton,
|
|
395
|
+
VStack,
|
|
396
|
+
} from '@cdx-ui/components';
|
|
397
|
+
import { ACTIVITY_FILTERS } from './_data';
|
|
398
|
+
|
|
399
|
+
const OFFERS = [
|
|
400
|
+
{ value: 'groceries', label: '5% back · Groceries' },
|
|
401
|
+
{ value: 'gas', label: '3% back · Gas' },
|
|
402
|
+
{ value: 'streaming', label: '10% back · StreamFlix' },
|
|
403
|
+
{ value: 'dining', label: '2% back · Dining' },
|
|
404
|
+
{ value: 'travel', label: '4% back · Travel' },
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
function CategoryPicker() {
|
|
408
|
+
const [category, setCategory] = useState('purchases');
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<ToggleButton.Group
|
|
412
|
+
type="single"
|
|
413
|
+
isAttached={false}
|
|
414
|
+
value={category}
|
|
415
|
+
onValueChange={setCategory}
|
|
416
|
+
aria-label="Spending category"
|
|
417
|
+
>
|
|
418
|
+
{ACTIVITY_FILTERS.map((filter) => (
|
|
419
|
+
<ToggleButton key={filter.value} value={filter.value} size="small">
|
|
420
|
+
<ToggleButton.Icon as={filter.icon} position="leading" />
|
|
421
|
+
<ToggleButton.Label>{filter.label}</ToggleButton.Label>
|
|
422
|
+
</ToggleButton>
|
|
423
|
+
))}
|
|
424
|
+
</ToggleButton.Group>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export default function ScrollShadowStylingDemo() {
|
|
429
|
+
const [activated, setActivated] = useState<string[]>([]);
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<VStack space="lg" className="w-full max-w-sm">
|
|
433
|
+
<VStack space="xs">
|
|
434
|
+
<Text size="sm" color="secondary">
|
|
435
|
+
edgeClassName="w-24"
|
|
436
|
+
</Text>
|
|
437
|
+
<Card fullBleed>
|
|
438
|
+
<Card.Content>
|
|
439
|
+
<Box className="py-5">
|
|
440
|
+
<ScrollShadow surface="primary" edgeClassName="w-24">
|
|
441
|
+
<ScrollView
|
|
442
|
+
horizontal
|
|
443
|
+
showsHorizontalScrollIndicator={false}
|
|
444
|
+
contentContainerClassName="px-5"
|
|
445
|
+
>
|
|
446
|
+
<CategoryPicker />
|
|
447
|
+
</ScrollView>
|
|
448
|
+
</ScrollShadow>
|
|
449
|
+
</Box>
|
|
450
|
+
</Card.Content>
|
|
451
|
+
</Card>
|
|
452
|
+
</VStack>
|
|
453
|
+
|
|
454
|
+
<VStack space="xs">
|
|
455
|
+
<Text size="sm" color="secondary">
|
|
456
|
+
edgeClassName="opacity-85"
|
|
457
|
+
</Text>
|
|
458
|
+
<Card fullBleed>
|
|
459
|
+
<Card.Content>
|
|
460
|
+
<Box className="py-5">
|
|
461
|
+
<ScrollShadow surface="primary" size="small" edgeClassName="opacity-85">
|
|
462
|
+
<ScrollView
|
|
463
|
+
horizontal
|
|
464
|
+
showsHorizontalScrollIndicator={false}
|
|
465
|
+
contentContainerClassName="px-5"
|
|
466
|
+
>
|
|
467
|
+
<CategoryPicker />
|
|
468
|
+
</ScrollView>
|
|
469
|
+
</ScrollShadow>
|
|
470
|
+
</Box>
|
|
471
|
+
</Card.Content>
|
|
472
|
+
</Card>
|
|
473
|
+
</VStack>
|
|
474
|
+
|
|
475
|
+
<VStack space="xs">
|
|
476
|
+
<Text size="sm" color="secondary">
|
|
477
|
+
colorClassName="accent-surface-action-subtle" hideScrollBar
|
|
478
|
+
</Text>
|
|
479
|
+
<Card fullBleed className="bg-surface-action-subtle">
|
|
480
|
+
<Card.Content fullBleed={false}>
|
|
481
|
+
<VStack space="xs">
|
|
482
|
+
<Heading size="xs">Cash back offers</Heading>
|
|
483
|
+
<Text size="sm" color="secondary">
|
|
484
|
+
Activate offers, then pay with your Rewards Visa · 1024.
|
|
485
|
+
</Text>
|
|
486
|
+
</VStack>
|
|
487
|
+
</Card.Content>
|
|
488
|
+
<Box className="pb-5">
|
|
489
|
+
<ScrollShadow colorClassName="accent-surface-action-subtle" hideScrollBar>
|
|
490
|
+
<ScrollView
|
|
491
|
+
horizontal
|
|
492
|
+
showsHorizontalScrollIndicator={false}
|
|
493
|
+
contentContainerClassName="px-5"
|
|
494
|
+
>
|
|
495
|
+
<ToggleButton.Group
|
|
496
|
+
type="multiple"
|
|
497
|
+
isAttached={false}
|
|
498
|
+
value={activated}
|
|
499
|
+
onValueChange={setActivated}
|
|
500
|
+
aria-label="Cash back offers"
|
|
501
|
+
>
|
|
502
|
+
{OFFERS.map((offer) => (
|
|
503
|
+
<ToggleButton
|
|
504
|
+
key={offer.value}
|
|
505
|
+
value={offer.value}
|
|
506
|
+
variant="outline"
|
|
507
|
+
size="small"
|
|
508
|
+
>
|
|
509
|
+
<ToggleButton.Label>{offer.label}</ToggleButton.Label>
|
|
510
|
+
</ToggleButton>
|
|
511
|
+
))}
|
|
512
|
+
</ToggleButton.Group>
|
|
513
|
+
</ScrollView>
|
|
514
|
+
</ScrollShadow>
|
|
515
|
+
</Box>
|
|
516
|
+
</Card>
|
|
517
|
+
</VStack>
|
|
518
|
+
</VStack>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Hook
|
|
524
|
+
|
|
525
|
+
### useScrollShadow
|
|
526
|
+
|
|
527
|
+
Use this when you want the overflow state without a fade, for example a "More below" pill or paging chevrons. It does not wrap the scroller and does not paint overlays. Pass the returned handlers onto your `ScrollView` (or `FlatList` / `VirtualizedList`) and read `capability.canScrollBefore` / `canScrollAfter`.
|
|
528
|
+
|
|
529
|
+
The built-in `ScrollShadow` already uses this hook internally. Exporting it is for custom UI only. You can still wrap the same scroll view in `ScrollShadow` to keep the fades: it keeps your handlers and `ref`, as the paging chevrons below do. Options, return values, and types are on the API tab.
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
import { useRef } from 'react';
|
|
533
|
+
import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
534
|
+
import {
|
|
535
|
+
Box,
|
|
536
|
+
Card,
|
|
537
|
+
Heading,
|
|
538
|
+
HStack,
|
|
539
|
+
IconButton,
|
|
540
|
+
ScrollShadow,
|
|
541
|
+
ScrollView,
|
|
542
|
+
Text,
|
|
543
|
+
VStack,
|
|
544
|
+
useScrollShadow,
|
|
545
|
+
} from '@cdx-ui/components';
|
|
546
|
+
import { ChevronLeft, ChevronRight } from '@cdx-ui/icons';
|
|
547
|
+
import { ACCOUNTS } from './_data';
|
|
548
|
+
|
|
549
|
+
export default function ScrollShadowHookDemo() {
|
|
550
|
+
const scrollRef = useRef<ScrollView>(null);
|
|
551
|
+
const offset = useRef(0);
|
|
552
|
+
const viewport = useRef(0);
|
|
553
|
+
const { capability, handleLayout, handleContentSizeChange, handleScroll } = useScrollShadow({
|
|
554
|
+
orientation: 'horizontal',
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
558
|
+
viewport.current = event.nativeEvent.layout.width;
|
|
559
|
+
handleLayout(event);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
563
|
+
offset.current = event.nativeEvent.contentOffset.x;
|
|
564
|
+
handleScroll(event);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const page = (direction: -1 | 1) => {
|
|
568
|
+
const x = Math.max(0, offset.current + direction * viewport.current * 0.8);
|
|
569
|
+
scrollRef.current?.scrollTo({ x, animated: true });
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
return (
|
|
573
|
+
<Card fullBleed className="w-full max-w-sm">
|
|
574
|
+
<Card.Header>
|
|
575
|
+
<Heading size="sm">Your accounts</Heading>
|
|
576
|
+
<HStack space="xs">
|
|
577
|
+
<IconButton
|
|
578
|
+
as={ChevronLeft}
|
|
579
|
+
variant="subtle"
|
|
580
|
+
size="small"
|
|
581
|
+
isDisabled={!capability.canScrollBefore}
|
|
582
|
+
onPress={() => page(-1)}
|
|
583
|
+
accessibilityLabel="Previous accounts"
|
|
584
|
+
/>
|
|
585
|
+
<IconButton
|
|
586
|
+
as={ChevronRight}
|
|
587
|
+
variant="subtle"
|
|
588
|
+
size="small"
|
|
589
|
+
isDisabled={!capability.canScrollAfter}
|
|
590
|
+
onPress={() => page(1)}
|
|
591
|
+
accessibilityLabel="Next accounts"
|
|
592
|
+
/>
|
|
593
|
+
</HStack>
|
|
594
|
+
</Card.Header>
|
|
595
|
+
<Card.Content>
|
|
596
|
+
<Box className="py-5">
|
|
597
|
+
<ScrollShadow surface="primary">
|
|
598
|
+
<ScrollView
|
|
599
|
+
ref={scrollRef}
|
|
600
|
+
horizontal
|
|
601
|
+
contentContainerClassName="gap-3 px-5"
|
|
602
|
+
onContentSizeChange={handleContentSizeChange}
|
|
603
|
+
onLayout={onLayout}
|
|
604
|
+
onScroll={onScroll}
|
|
605
|
+
scrollEventThrottle={16}
|
|
606
|
+
showsHorizontalScrollIndicator={false}
|
|
607
|
+
>
|
|
608
|
+
{ACCOUNTS.map((account) => (
|
|
609
|
+
<Card key={account.mask} className="w-52">
|
|
610
|
+
<Card.Stripe />
|
|
611
|
+
<Card.Content>
|
|
612
|
+
<VStack space="xs">
|
|
613
|
+
<Text size="sm" color="secondary">
|
|
614
|
+
{account.name} · {account.mask}
|
|
615
|
+
</Text>
|
|
616
|
+
<Heading size="md">{account.balance}</Heading>
|
|
617
|
+
<Text size="xs" color="tertiary">
|
|
618
|
+
{account.label}
|
|
619
|
+
</Text>
|
|
620
|
+
</VStack>
|
|
621
|
+
</Card.Content>
|
|
622
|
+
</Card>
|
|
623
|
+
))}
|
|
624
|
+
</ScrollView>
|
|
625
|
+
</ScrollShadow>
|
|
626
|
+
</Box>
|
|
627
|
+
</Card.Content>
|
|
628
|
+
</Card>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## API
|
|
634
|
+
|
|
635
|
+
### ScrollShadow
|
|
636
|
+
|
|
637
|
+
| Prop | Type | Default | Description |
|
|
638
|
+
| --- | --- | --- | --- |
|
|
639
|
+
| `children` | `ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | — | The scroll view to decorate: a `ScrollView`, `FlatList`, or `VirtualizedList`, passed as the direct child. With a forced `visibility`, any single view works. |
|
|
640
|
+
| `className?` | `string` | — | Classes for the wrapping view, for example `h-full` or `flex-1` to bound the scroller. |
|
|
641
|
+
| `style?` | `StyleProp<ViewStyle>` | — | Style for the wrapping view. |
|
|
642
|
+
| `orientation?` | `'horizontal' \| 'vertical'` | `inferred from the child's `horizontal` prop, otherwise `'vertical'`` | Scroll axis that gets fades. |
|
|
643
|
+
| `surface?` | `'background' \| 'primary' \| 'secondary'` | `'background'` | Theme surface the fade blends into. Match it to the background behind the scroller. |
|
|
644
|
+
| `size?` | `'small' \| 'default' \| 'large'` | `'default'` | Fade depth along the scroll axis: `small` is 32px, `default` is 40px, `large` is 72px. |
|
|
645
|
+
| `visibility?` | `'auto' \| 'both' \| 'start' \| 'end' \| 'none'` | `'auto'` | Which edges show a fade. `auto` follows the scroll position; `both`, `start`, and `end` force those edges on; `none` hides them. |
|
|
646
|
+
| `offset?` | `number` | `0` | Pixels of scroll slack before an edge counts as scrollable. Only used with `auto`. |
|
|
647
|
+
| `hideScrollBar?` | `boolean` | `false` | Hides the child's scrollbar on web. On iOS and Android, set `showsVerticalScrollIndicator` or `showsHorizontalScrollIndicator` on the child instead. |
|
|
648
|
+
| `edgeClassName?` | `string` | — | Width, opacity, or position overrides for the fade overlays, applied after `size`, for example `w-24` or `opacity-85`. Must not carry color classes; use `surface` or `colorClassName` instead. |
|
|
649
|
+
| `colorClassName?` | `string` | — | One-off fade color outside the `surface` options, as an `accent-*` class. Overrides `surface` on every platform and follows dark mode and `ScopedTheme`. |
|
|
650
|
+
| `onCapabilityChange?` | `((capability: ScrollShadowCapability) => void)` | — | Called when either edge starts or stops being scrollable, with `{ canScrollBefore, canScrollAfter }`. Only fires with `visibility="auto"`. |
|
|
651
|
+
|
|
652
|
+
Does not extend a React Native base component. Every prop is listed above. `ScrollShadow` keeps the child's own `onScroll`, `onLayout`, and `onContentSizeChange` handlers, including a Reanimated scroll handler, and sets `scrollEventThrottle={16}` when the child doesn't set one.
|
|
653
|
+
|
|
654
|
+
| Attribute | Values | Description |
|
|
655
|
+
| ----------- | ---------------- | ---------------------------- |
|
|
656
|
+
| `data-edge` | `start` \| `end` | Which edge an overlay covers |
|
|
657
|
+
|
|
658
|
+
## Hook
|
|
659
|
+
|
|
660
|
+
### useScrollShadow
|
|
661
|
+
|
|
662
|
+
Tracks whether a scroller can scroll toward its start or end, without rendering any overlay. Spread the handlers onto your scroll view and draw your own indicator from `capability`:
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
const { capability, handleLayout, handleContentSizeChange, handleScroll } = useScrollShadow({
|
|
666
|
+
orientation: 'vertical',
|
|
667
|
+
});
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
| Option | Type | Description |
|
|
671
|
+
| -------------------- | ---------------------------------------------- | --------------------------------------------------- |
|
|
672
|
+
| `orientation` | `'horizontal' \| 'vertical'` | Scroll axis to track. Defaults to `vertical` |
|
|
673
|
+
| `offset` | `number` | Pixels of slack before an edge counts as scrollable |
|
|
674
|
+
| `enabled` | `boolean` | Stops tracking when `false` |
|
|
675
|
+
| `onCapabilityChange` | `(capability: ScrollShadowCapability) => void` | Called when either edge flips |
|
|
676
|
+
|
|
677
|
+
| Return | Type | Description |
|
|
678
|
+
| ------------------------- | ---------------------------------------------------------- | ------------------------------------ |
|
|
679
|
+
| `capability` | `{ canScrollBefore: boolean; canScrollAfter: boolean }` | Whether content overflows each edge |
|
|
680
|
+
| `handleLayout` | `(event: LayoutChangeEvent) => void` | Pass to the scroll view's `onLayout` |
|
|
681
|
+
| `handleContentSizeChange` | `(width: number, height: number) => void` | Pass to `onContentSizeChange` |
|
|
682
|
+
| `handleScroll` | `(event: NativeSyntheticEvent<NativeScrollEvent>) => void` | Pass to `onScroll` |
|
|
683
|
+
|
|
684
|
+
## Anatomy
|
|
685
|
+
|
|
686
|
+
| `data-slot` | Element |
|
|
687
|
+
| -------------------- | -------------------------------- |
|
|
688
|
+
| `scroll-shadow` | Root wrapper around the scroller |
|
|
689
|
+
| `scroll-shadow-edge` | Start or end fade overlay |
|
|
690
|
+
|
|
691
|
+
## Accessibility
|
|
692
|
+
|
|
693
|
+
The fade overlays are decorative. They pass touches through to the scroller and are hidden from screen readers with `aria-hidden`, `accessibilityElementsHidden`, and `importantForAccessibility="no-hide-descendants"`. The scrollable content stays fully reachable, so a fade never hides content from assistive technology.
|