@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.
Files changed (296) hide show
  1. package/AGENTS.md +24 -0
  2. package/README.md +2 -0
  3. package/coverage.svg +1 -1
  4. package/docs/components/accordion/content.md +538 -0
  5. package/docs/components/alert/content.md +203 -0
  6. package/docs/components/alert-dialog/content.md +319 -0
  7. package/docs/components/avatar/content.md +368 -0
  8. package/docs/components/avatar/design.md +202 -0
  9. package/docs/components/badge/content.md +225 -0
  10. package/docs/components/bottom-sheet/content.md +353 -0
  11. package/docs/components/bottom-sheet/design.md +249 -0
  12. package/docs/components/box/content.md +141 -0
  13. package/docs/components/breadcrumbs/content.md +169 -0
  14. package/docs/components/button/content.md +398 -0
  15. package/docs/components/button/design.md +258 -0
  16. package/docs/components/calendar/content.md +137 -0
  17. package/docs/components/card/content.md +283 -0
  18. package/docs/components/carousel/content.md +417 -0
  19. package/docs/components/checkbox/content.md +236 -0
  20. package/docs/components/checkbox/design.md +214 -0
  21. package/docs/components/chip/content.md +439 -0
  22. package/docs/components/chip/design.md +418 -0
  23. package/docs/components/color-area/content.md +84 -0
  24. package/docs/components/color-field/content.md +132 -0
  25. package/docs/components/color-picker/content.md +209 -0
  26. package/docs/components/color-slider/content.md +118 -0
  27. package/docs/components/color-swatch/content.md +62 -0
  28. package/docs/components/color-swatch-picker/content.md +124 -0
  29. package/docs/components/content.md +98 -0
  30. package/docs/components/currency-input/content.md +205 -0
  31. package/docs/components/data-grid/content.md +521 -0
  32. package/docs/components/date-field/content.md +147 -0
  33. package/docs/components/date-picker/content.md +360 -0
  34. package/docs/components/date-range-picker/content.md +301 -0
  35. package/docs/components/date-time-field/content.md +157 -0
  36. package/docs/components/date-time-list/content.md +105 -0
  37. package/docs/components/date-time-picker/content.md +272 -0
  38. package/docs/components/dialog/content.md +436 -0
  39. package/docs/components/dialog/design.md +192 -0
  40. package/docs/components/divider/content.md +112 -0
  41. package/docs/components/drawer/content.md +428 -0
  42. package/docs/components/field/content.md +277 -0
  43. package/docs/components/form/content.md +62 -0
  44. package/docs/components/full-screen-dialog/content.md +881 -0
  45. package/docs/components/heading/content.md +55 -0
  46. package/docs/components/icon/content.md +190 -0
  47. package/docs/components/icon-button/content.md +200 -0
  48. package/docs/components/icon-button/design.md +165 -0
  49. package/docs/components/image/content.md +104 -0
  50. package/docs/components/input/content.md +346 -0
  51. package/docs/components/input/design.md +389 -0
  52. package/docs/components/insurance-disclosure/content.md +103 -0
  53. package/docs/components/link/content.md +180 -0
  54. package/docs/components/link/design.md +160 -0
  55. package/docs/components/list-item/content.md +695 -0
  56. package/docs/components/menu/content.md +1254 -0
  57. package/docs/components/month-calendar/content.md +84 -0
  58. package/docs/components/month-picker/content.md +273 -0
  59. package/docs/components/otp-input/content.md +327 -0
  60. package/docs/components/progress-bar/content.md +87 -0
  61. package/docs/components/progress-circle/content.md +176 -0
  62. package/docs/components/progress-segmented/content.md +119 -0
  63. package/docs/components/quick-action-button/content.md +154 -0
  64. package/docs/components/quick-action-button/design.md +170 -0
  65. package/docs/components/radio/content.md +224 -0
  66. package/docs/components/range-calendar/content.md +123 -0
  67. package/docs/components/reorderable-list/content.md +311 -0
  68. package/docs/components/scoped-theme/content.md +47 -0
  69. package/docs/components/scroll-shadow/content.md +693 -0
  70. package/docs/components/select/content.md +473 -0
  71. package/docs/components/select/design.md +301 -0
  72. package/docs/components/skeleton/content.md +104 -0
  73. package/docs/components/slider/content.md +423 -0
  74. package/docs/components/stack/content.md +161 -0
  75. package/docs/components/switch/content.md +150 -0
  76. package/docs/components/switch/design.md +208 -0
  77. package/docs/components/table/content.md +548 -0
  78. package/docs/components/table-pagination/content.md +177 -0
  79. package/docs/components/tabs/content.md +297 -0
  80. package/docs/components/text/content.md +142 -0
  81. package/docs/components/textarea/content.md +222 -0
  82. package/docs/components/tile/content.md +702 -0
  83. package/docs/components/toast/content.md +456 -0
  84. package/docs/components/toggle-button/content.md +505 -0
  85. package/docs/components/tooltip/content.md +350 -0
  86. package/docs/components/virtualized-list/content.md +250 -0
  87. package/docs/foundations/content.md +9 -0
  88. package/docs/foundations/expressions/icons/content.md +19 -0
  89. package/docs/foundations/styles/tokens/all-tokens/content.md +192 -0
  90. package/docs/foundations/styles/tokens/what-are-tokens/content.md +33 -0
  91. package/docs/foundations/styles/tokens/what-are-tokens/design.md +33 -0
  92. package/docs/getting-started/content.md +4 -0
  93. package/docs/getting-started/designers/content.md +13 -0
  94. package/docs/getting-started/designers/design.md +13 -0
  95. package/docs/getting-started/developers/accessibility/content.md +31 -0
  96. package/docs/getting-started/developers/content.md +612 -0
  97. package/docs/getting-started/developers/styling/content.md +195 -0
  98. package/docs/llms.txt +195 -0
  99. package/docs/patterns/account-management/content.md +75 -0
  100. package/docs/patterns/authentication/content.md +50 -0
  101. package/docs/patterns/content.md +10 -0
  102. package/docs/patterns/dashboard/content.md +34 -0
  103. package/docs/patterns/empty-states/content.md +32 -0
  104. package/docs/patterns/forms/content.md +108 -0
  105. package/docs/patterns/onboarding/content.md +69 -0
  106. package/docs/patterns/payments/content.md +35 -0
  107. package/docs/patterns/settings/content.md +72 -0
  108. package/docs/resources/content.md +9 -0
  109. package/docs/resources/figma-libraries/content.md +42 -0
  110. package/docs/resources/release-notes/0.0.1-beta.118/content.md +27 -0
  111. package/docs/resources/release-notes/0.0.1-beta.122/content.md +21 -0
  112. package/docs/resources/release-notes/0.0.1-beta.124/content.md +16 -0
  113. package/docs/resources/release-notes/0.0.1-beta.126/content.md +10 -0
  114. package/docs/resources/release-notes/0.0.1-beta.127/content.md +14 -0
  115. package/docs/resources/release-notes/0.0.1-beta.128/content.md +9 -0
  116. package/docs/resources/release-notes/0.0.1-beta.129/content.md +18 -0
  117. package/docs/resources/release-notes/0.0.1-beta.130/content.md +18 -0
  118. package/docs/resources/release-notes/0.0.1-beta.133/content.md +7 -0
  119. package/docs/resources/release-notes/0.0.1-beta.134/content.md +17 -0
  120. package/docs/resources/release-notes/0.0.1-beta.136/content.md +7 -0
  121. package/docs/resources/release-notes/0.0.1-beta.137/content.md +10 -0
  122. package/docs/resources/release-notes/0.0.1-beta.138/content.md +7 -0
  123. package/docs/resources/release-notes/0.0.1-beta.139/content.md +13 -0
  124. package/docs/resources/release-notes/0.0.1-beta.140/content.md +26 -0
  125. package/docs/resources/release-notes/0.0.1-beta.141/content.md +63 -0
  126. package/docs/resources/release-notes/0.0.1-beta.142/content.md +7 -0
  127. package/docs/resources/release-notes/0.0.1-beta.143/content.md +12 -0
  128. package/docs/resources/release-notes/0.0.1-beta.144/content.md +7 -0
  129. package/docs/resources/release-notes/0.0.1-beta.145/content.md +7 -0
  130. package/docs/resources/release-notes/0.0.1-beta.147/content.md +10 -0
  131. package/docs/resources/release-notes/0.0.1-beta.149/content.md +10 -0
  132. package/docs/resources/release-notes/0.0.1-beta.150/content.md +13 -0
  133. package/docs/resources/release-notes/0.0.1-beta.152/content.md +7 -0
  134. package/docs/resources/release-notes/0.0.1-beta.154/content.md +15 -0
  135. package/docs/resources/release-notes/0.0.1-beta.156/content.md +7 -0
  136. package/docs/resources/release-notes/0.0.1-beta.158/content.md +9 -0
  137. package/docs/resources/release-notes/0.0.1-beta.159/content.md +11 -0
  138. package/docs/resources/release-notes/0.0.1-beta.160/content.md +7 -0
  139. package/docs/resources/release-notes/0.0.1-beta.161/content.md +7 -0
  140. package/docs/resources/release-notes/0.0.1-beta.163/content.md +7 -0
  141. package/docs/resources/release-notes/0.0.1-beta.164/content.md +9 -0
  142. package/docs/resources/release-notes/0.0.1-beta.166/content.md +17 -0
  143. package/docs/resources/release-notes/0.0.1-beta.167/content.md +7 -0
  144. package/docs/resources/release-notes/0.0.1-beta.168/content.md +7 -0
  145. package/docs/resources/release-notes/0.0.1-beta.169/content.md +7 -0
  146. package/docs/resources/release-notes/0.0.1-beta.171/content.md +16 -0
  147. package/docs/resources/release-notes/0.0.1-beta.172/content.md +24 -0
  148. package/docs/resources/release-notes/0.0.1-beta.173/content.md +15 -0
  149. package/docs/resources/release-notes/0.0.1-beta.174/content.md +7 -0
  150. package/docs/resources/release-notes/0.0.1-beta.176/content.md +32 -0
  151. package/docs/resources/release-notes/0.0.1-beta.179/content.md +11 -0
  152. package/docs/resources/release-notes/0.0.1-beta.182/content.md +13 -0
  153. package/docs/resources/release-notes/0.0.1-beta.185/content.md +7 -0
  154. package/docs/resources/release-notes/0.0.1-beta.187/content.md +27 -0
  155. package/docs/resources/release-notes/0.1.1/content.md +23 -0
  156. package/docs/resources/release-notes/0.1.2/content.md +7 -0
  157. package/docs/resources/release-notes/0.1.4/content.md +19 -0
  158. package/docs/resources/release-notes/0.1.5/content.md +7 -0
  159. package/docs/resources/release-notes/0.1.6/content.md +7 -0
  160. package/docs/resources/release-notes/0.2.0/content.md +31 -0
  161. package/docs/resources/release-notes/0.2.1/content.md +17 -0
  162. package/docs/resources/release-notes/0.2.2/content.md +11 -0
  163. package/docs/resources/release-notes/0.2.3/content.md +15 -0
  164. package/docs/resources/release-notes/0.2.4/content.md +17 -0
  165. package/docs/resources/release-notes/content.md +65 -0
  166. package/lib/commonjs/components/Box/Box.js +9 -1
  167. package/lib/commonjs/components/Box/Box.js.map +1 -1
  168. package/lib/commonjs/components/Breadcrumbs/index.js +28 -3
  169. package/lib/commonjs/components/Breadcrumbs/index.js.map +1 -1
  170. package/lib/commonjs/components/Breadcrumbs/styles.js +27 -0
  171. package/lib/commonjs/components/Breadcrumbs/styles.js.map +1 -1
  172. package/lib/commonjs/components/DateTimeList/index.js +11 -26
  173. package/lib/commonjs/components/DateTimeList/index.js.map +1 -1
  174. package/lib/commonjs/components/DateTimeList/styles.js +2 -12
  175. package/lib/commonjs/components/DateTimeList/styles.js.map +1 -1
  176. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  177. package/lib/commonjs/components/ListItem/styles.js +2 -1
  178. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  179. package/lib/commonjs/components/Menu/index.js +11 -5
  180. package/lib/commonjs/components/Menu/index.js.map +1 -1
  181. package/lib/commonjs/components/ScrollShadow/gradient.js +29 -0
  182. package/lib/commonjs/components/ScrollShadow/gradient.js.map +1 -0
  183. package/lib/commonjs/components/ScrollShadow/index.js +171 -44
  184. package/lib/commonjs/components/ScrollShadow/index.js.map +1 -1
  185. package/lib/commonjs/components/ScrollShadow/styles.js +58 -14
  186. package/lib/commonjs/components/ScrollShadow/styles.js.map +1 -1
  187. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js +63 -0
  188. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  189. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +48 -8
  190. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  191. package/lib/commonjs/components/Stack/HStack.js +6 -2
  192. package/lib/commonjs/components/Stack/HStack.js.map +1 -1
  193. package/lib/commonjs/components/Stack/VStack.js +6 -2
  194. package/lib/commonjs/components/Stack/VStack.js.map +1 -1
  195. package/lib/commonjs/components/Tabs/index.js +16 -23
  196. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  197. package/lib/commonjs/components/Tabs/styles.js +1 -10
  198. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  199. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js +20 -5
  200. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js.map +1 -1
  201. package/lib/commonjs/index.js +10 -10
  202. package/lib/commonjs/index.js.map +1 -1
  203. package/lib/module/components/Box/Box.js +9 -1
  204. package/lib/module/components/Box/Box.js.map +1 -1
  205. package/lib/module/components/Breadcrumbs/index.js +29 -4
  206. package/lib/module/components/Breadcrumbs/index.js.map +1 -1
  207. package/lib/module/components/Breadcrumbs/styles.js +26 -0
  208. package/lib/module/components/Breadcrumbs/styles.js.map +1 -1
  209. package/lib/module/components/DateTimeList/index.js +12 -27
  210. package/lib/module/components/DateTimeList/index.js.map +1 -1
  211. package/lib/module/components/DateTimeList/styles.js +1 -11
  212. package/lib/module/components/DateTimeList/styles.js.map +1 -1
  213. package/lib/module/components/ListItem/index.js.map +1 -1
  214. package/lib/module/components/ListItem/styles.js +2 -1
  215. package/lib/module/components/ListItem/styles.js.map +1 -1
  216. package/lib/module/components/Menu/index.js +11 -5
  217. package/lib/module/components/Menu/index.js.map +1 -1
  218. package/lib/module/components/ScrollShadow/gradient.js +24 -0
  219. package/lib/module/components/ScrollShadow/gradient.js.map +1 -0
  220. package/lib/module/components/ScrollShadow/index.js +170 -48
  221. package/lib/module/components/ScrollShadow/index.js.map +1 -1
  222. package/lib/module/components/ScrollShadow/styles.js +57 -13
  223. package/lib/module/components/ScrollShadow/styles.js.map +1 -1
  224. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js +57 -0
  225. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  226. package/lib/module/components/ScrollShadow/useScrollShadow.js +46 -8
  227. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  228. package/lib/module/components/Stack/HStack.js +6 -2
  229. package/lib/module/components/Stack/HStack.js.map +1 -1
  230. package/lib/module/components/Stack/VStack.js +6 -2
  231. package/lib/module/components/Stack/VStack.js.map +1 -1
  232. package/lib/module/components/Tabs/index.js +17 -24
  233. package/lib/module/components/Tabs/index.js.map +1 -1
  234. package/lib/module/components/Tabs/styles.js +0 -9
  235. package/lib/module/components/Tabs/styles.js.map +1 -1
  236. package/lib/module/figma/ListItem.SectionHeader.figma.js +20 -5
  237. package/lib/module/figma/ListItem.SectionHeader.figma.js.map +1 -1
  238. package/lib/module/index.js +6 -1
  239. package/lib/module/index.js.map +1 -1
  240. package/lib/typescript/animation/hooks/usePopupDialogContentAnimation.d.ts +2 -2
  241. package/lib/typescript/animation/hooks/usePopupOverlayAnimation.d.ts +2 -2
  242. package/lib/typescript/components/Box/Box.d.ts +4 -0
  243. package/lib/typescript/components/Box/Box.d.ts.map +1 -1
  244. package/lib/typescript/components/Breadcrumbs/index.d.ts +3 -1
  245. package/lib/typescript/components/Breadcrumbs/index.d.ts.map +1 -1
  246. package/lib/typescript/components/Breadcrumbs/styles.d.ts +2 -0
  247. package/lib/typescript/components/Breadcrumbs/styles.d.ts.map +1 -1
  248. package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -1
  249. package/lib/typescript/components/DateTimeList/styles.d.ts +1 -4
  250. package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -1
  251. package/lib/typescript/components/ListItem/index.d.ts +5 -4
  252. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  253. package/lib/typescript/components/ListItem/styles.d.ts +1 -1
  254. package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
  255. package/lib/typescript/components/Menu/index.d.ts.map +1 -1
  256. package/lib/typescript/components/ScrollShadow/gradient.d.ts +7 -0
  257. package/lib/typescript/components/ScrollShadow/gradient.d.ts.map +1 -0
  258. package/lib/typescript/components/ScrollShadow/index.d.ts +57 -15
  259. package/lib/typescript/components/ScrollShadow/index.d.ts.map +1 -1
  260. package/lib/typescript/components/ScrollShadow/styles.d.ts +8 -2
  261. package/lib/typescript/components/ScrollShadow/styles.d.ts.map +1 -1
  262. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts +13 -0
  263. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts.map +1 -0
  264. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts +15 -2
  265. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  266. package/lib/typescript/components/Stack/HStack.d.ts +3 -2
  267. package/lib/typescript/components/Stack/HStack.d.ts.map +1 -1
  268. package/lib/typescript/components/Stack/VStack.d.ts +3 -2
  269. package/lib/typescript/components/Stack/VStack.d.ts.map +1 -1
  270. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  271. package/lib/typescript/components/Tabs/styles.d.ts +0 -7
  272. package/lib/typescript/components/Tabs/styles.d.ts.map +1 -1
  273. package/lib/typescript/components/Toast/animation.d.ts +3 -3
  274. package/lib/typescript/components/Toast/animation.d.ts.map +1 -1
  275. package/lib/typescript/figma/ListItem.SectionHeader.figma.d.ts.map +1 -1
  276. package/lib/typescript/index.d.ts +6 -1
  277. package/lib/typescript/index.d.ts.map +1 -1
  278. package/package.json +11 -9
  279. package/src/components/Box/Box.tsx +18 -4
  280. package/src/components/Breadcrumbs/index.tsx +28 -4
  281. package/src/components/Breadcrumbs/styles.ts +30 -0
  282. package/src/components/DateTimeList/index.tsx +13 -26
  283. package/src/components/DateTimeList/styles.ts +1 -10
  284. package/src/components/ListItem/index.tsx +5 -4
  285. package/src/components/ListItem/styles.ts +1 -0
  286. package/src/components/Menu/index.tsx +11 -8
  287. package/src/components/ScrollShadow/gradient.ts +48 -0
  288. package/src/components/ScrollShadow/index.tsx +300 -69
  289. package/src/components/ScrollShadow/styles.ts +45 -28
  290. package/src/components/ScrollShadow/useComposedScrollHandler.ts +58 -0
  291. package/src/components/ScrollShadow/useScrollShadow.ts +72 -10
  292. package/src/components/Stack/HStack.tsx +8 -4
  293. package/src/components/Stack/VStack.tsx +8 -4
  294. package/src/components/Tabs/index.tsx +25 -35
  295. package/src/components/Tabs/styles.ts +0 -9
  296. package/src/index.ts +8 -1
@@ -35,6 +35,7 @@ import {
35
35
  type IMenuTriggerProps,
36
36
  } from '@cdx-ui/primitives';
37
37
  import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
38
+ import { ScrollShadow } from '../ScrollShadow';
38
39
  import { Icon, type IconProps } from '../Icon';
39
40
  import { checkboxIconVariants, checkboxIndicatorVariants } from '../Checkbox/styles';
40
41
  import { radioIndicatorVariants, radioInnerDotVariants } from '../Radio/styles';
@@ -222,14 +223,16 @@ const MenuContent = forwardRef<View, MenuContentProps>(
222
223
  : mergeDataAttributes(props, dataAttributes({ slot: 'menu-content' }));
223
224
 
224
225
  const content = shouldScroll ? (
225
- <ScrollView
226
- className="shrink"
227
- style={[MENU_SCROLL_STYLE, constrainedStyle]}
228
- scrollEventThrottle={16}
229
- nestedScrollEnabled
230
- >
231
- {children}
232
- </ScrollView>
226
+ <ScrollShadow className="shrink" style={constrainedStyle} surface="primary">
227
+ <ScrollView
228
+ className="shrink"
229
+ style={[MENU_SCROLL_STYLE, constrainedStyle]}
230
+ scrollEventThrottle={16}
231
+ nestedScrollEnabled
232
+ >
233
+ {children}
234
+ </ScrollView>
235
+ </ScrollShadow>
233
236
  ) : (
234
237
  children
235
238
  );
@@ -0,0 +1,48 @@
1
+ import { I18nManager } from 'react-native';
2
+ import { SCROLL_SHADOW_SURFACE_FROM_CLASS, type ScrollShadowSurface } from './styles';
3
+
4
+ type ScrollShadowEdge = 'start' | 'end';
5
+ type ScrollShadowOrientation = 'horizontal' | 'vertical';
6
+
7
+ export function getWebSurfaceGradientClasses(
8
+ surface: ScrollShadowSurface,
9
+ orientation: ScrollShadowOrientation,
10
+ edge: ScrollShadowEdge,
11
+ ): string {
12
+ const fromClass = SCROLL_SHADOW_SURFACE_FROM_CLASS[surface];
13
+
14
+ if (orientation === 'vertical') {
15
+ return edge === 'start'
16
+ ? `bg-gradient-to-b ${fromClass} to-transparent`
17
+ : `bg-gradient-to-t ${fromClass} to-transparent`;
18
+ }
19
+
20
+ // ltr:/rtl: so the fade flips with layout direction on the first paint,
21
+ // not after a later scroll re-render.
22
+ return edge === 'start'
23
+ ? `ltr:bg-gradient-to-r rtl:bg-gradient-to-l ${fromClass} to-transparent`
24
+ : `ltr:bg-gradient-to-l rtl:bg-gradient-to-r ${fromClass} to-transparent`;
25
+ }
26
+
27
+ export function getInlineBackgroundImage(
28
+ color: string,
29
+ orientation: ScrollShadowOrientation,
30
+ edge: ScrollShadowEdge,
31
+ isRTL = I18nManager.isRTL,
32
+ ): string {
33
+ if (orientation === 'vertical') {
34
+ return edge === 'start'
35
+ ? `linear-gradient(to bottom, ${color}, transparent)`
36
+ : `linear-gradient(to top, ${color}, transparent)`;
37
+ }
38
+
39
+ if (isRTL) {
40
+ return edge === 'start'
41
+ ? `linear-gradient(to left, ${color}, transparent)`
42
+ : `linear-gradient(to right, ${color}, transparent)`;
43
+ }
44
+
45
+ return edge === 'start'
46
+ ? `linear-gradient(to right, ${color}, transparent)`
47
+ : `linear-gradient(to left, ${color}, transparent)`;
48
+ }
@@ -1,119 +1,350 @@
1
- import { Children, cloneElement, isValidElement, type ReactNode } from 'react';
2
- import { StyleSheet, View, type ViewStyle } from 'react-native';
1
+ import {
2
+ Children,
3
+ cloneElement,
4
+ isValidElement,
5
+ useCallback,
6
+ useRef,
7
+ type ReactElement,
8
+ } from 'react';
9
+ import { Animated, Platform, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
10
+ import { useCSSVariable, useResolveClassNames } from 'uniwind';
11
+ import { dataAttributes, mergeDataAttributes } from '@cdx-ui/primitives';
3
12
  import { cn } from '@cdx-ui/utils';
13
+ import { getInlineBackgroundImage, getWebSurfaceGradientClasses } from './gradient';
4
14
  import {
15
+ SCROLL_SHADOW_SIZE_PX,
16
+ SCROLL_SHADOW_SURFACE_CSS_VARS,
5
17
  scrollShadowContainerVariants,
6
- scrollShadowOverlayVariants,
18
+ scrollShadowEdgeVariants,
7
19
  scrollShadowVariants,
20
+ type ScrollShadowSize,
21
+ type ScrollShadowSurface,
8
22
  type ScrollShadowVariantProps,
9
23
  } from './styles';
10
- import { useScrollShadow, type ScrollShadowCapability } from './useScrollShadow';
24
+ import {
25
+ composeScrollHandler,
26
+ isReanimatedScrollHandler,
27
+ useReanimatedComposedScrollHandler,
28
+ } from './useComposedScrollHandler';
29
+ import {
30
+ useScrollShadowTracker,
31
+ type ScrollShadowCapability,
32
+ type ScrollShadowDistance,
33
+ type UseScrollShadowReturn,
34
+ } from './useScrollShadow';
35
+
36
+ export type {
37
+ ScrollShadowCapability,
38
+ UseScrollShadowOptions,
39
+ UseScrollShadowReturn,
40
+ } from './useScrollShadow';
41
+ export { useScrollShadow } from './useScrollShadow';
11
42
 
12
- export type { ScrollShadowCapability };
43
+ export type ScrollShadowVisibility = 'auto' | 'both' | 'start' | 'end' | 'none';
13
44
 
14
45
  export interface ScrollShadowProps extends ScrollShadowVariantProps {
15
- children: ReactNode;
46
+ /**
47
+ * The scroll view to decorate: a `ScrollView`, `FlatList`, or `VirtualizedList`, passed as
48
+ * the direct child. With a forced `visibility`, any single view works.
49
+ */
50
+ children: ReactElement;
51
+ /** Classes for the wrapping view, for example `h-full` or `flex-1` to bound the scroller. */
16
52
  className?: string;
53
+ /** Style for the wrapping view. */
54
+ style?: StyleProp<ViewStyle>;
17
55
  /**
18
- * Tailwind `from-*` utility for edge overlays — match the surface behind the scroll area.
19
- * Pair with a low opacity (e.g. `from-surface-secondary/40`) for a light fade.
20
- * @default 'from-background/35'
56
+ * Scroll axis that gets fades.
57
+ * @default inferred from the child's `horizontal` prop, otherwise `'vertical'`
21
58
  */
22
- fadeFromClassName?: string;
59
+ orientation?: 'horizontal' | 'vertical';
23
60
  /**
24
- * Shadow fade size in pixels.
25
- * @default 64
61
+ * Theme surface the fade blends into. Match it to the background behind the scroller.
62
+ * @default 'background'
26
63
  */
27
- size?: number;
64
+ surface?: ScrollShadowSurface;
28
65
  /**
29
- * Scroll offset before showing edge shadows.
66
+ * Fade depth along the scroll axis: `small` is 32px, `default` is 40px, `large` is 72px.
67
+ * @default 'default'
68
+ */
69
+ size?: ScrollShadowSize;
70
+ /**
71
+ * Which edges show a fade. `auto` follows the scroll position; `both`, `start`, and `end`
72
+ * force those edges on; `none` hides them.
73
+ * @default 'auto'
74
+ */
75
+ visibility?: ScrollShadowVisibility;
76
+ /**
77
+ * Pixels of scroll slack before an edge counts as scrollable. Only used with `auto`.
30
78
  * @default 0
31
79
  */
32
80
  offset?: number;
81
+ /**
82
+ * Hides the child's scrollbar on web. On iOS and Android, set
83
+ * `showsVerticalScrollIndicator` or `showsHorizontalScrollIndicator` on the child instead.
84
+ * @default false
85
+ */
86
+ hideScrollBar?: boolean;
87
+ /**
88
+ * Width, opacity, or position overrides for the fade overlays, applied after `size`, for
89
+ * example `w-24` or `opacity-85`. Must not carry color classes; use `surface` or
90
+ * `colorClassName` instead.
91
+ */
92
+ edgeClassName?: string;
93
+ /**
94
+ * One-off fade color outside the `surface` options, as an `accent-*` class. Overrides
95
+ * `surface` on every platform and follows dark mode and `ScopedTheme`.
96
+ */
97
+ colorClassName?: string;
98
+ /**
99
+ * Called when either edge starts or stops being scrollable, with
100
+ * `{ canScrollBefore, canScrollAfter }`. Only fires with `visibility="auto"`.
101
+ */
33
102
  onCapabilityChange?: (capability: ScrollShadowCapability) => void;
34
- style?: ViewStyle;
35
103
  }
36
104
 
37
105
  interface ScrollShadowChildProps {
38
106
  className?: string;
39
- style?: ViewStyle;
40
- onLayout?: (event: Parameters<ReturnType<typeof useScrollShadow>['handleLayout']>[0]) => void;
107
+ horizontal?: boolean;
108
+ style?: StyleProp<ViewStyle>;
109
+ scrollEventThrottle?: number;
110
+ onLayout?: (event: Parameters<UseScrollShadowReturn['handleLayout']>[0]) => void;
41
111
  onContentSizeChange?: (width: number, height: number) => void;
42
- onScroll?: (event: Parameters<ReturnType<typeof useScrollShadow>['handleScroll']>[0]) => void;
112
+ onScroll?: unknown;
43
113
  ref?: React.Ref<unknown>;
44
114
  }
45
115
 
116
+ interface ScrollShadowEdgeProps {
117
+ edge: 'start' | 'end';
118
+ orientation: 'horizontal' | 'vertical';
119
+ size: ScrollShadowSize;
120
+ surface: ScrollShadowSurface;
121
+ colorClassName?: string;
122
+ edgeClassName?: string;
123
+ opacity?: Animated.Value;
124
+ }
125
+
126
+ function ScrollShadowEdge({
127
+ edge,
128
+ orientation,
129
+ size,
130
+ surface,
131
+ colorClassName,
132
+ edgeClassName,
133
+ opacity,
134
+ }: ScrollShadowEdgeProps) {
135
+ const surfaceColor = useCSSVariable(SCROLL_SHADOW_SURFACE_CSS_VARS[surface]) || 'transparent';
136
+ const resolvedColorClassName = useResolveClassNames(colorClassName ?? '');
137
+ const customColor =
138
+ colorClassName != null
139
+ ? (resolvedColorClassName.accentColor ?? resolvedColorClassName.backgroundColor)
140
+ : undefined;
141
+
142
+ if (__DEV__ && colorClassName != null && customColor == null) {
143
+ console.warn(
144
+ `[ScrollShadow] colorClassName "${colorClassName}" did not resolve to a color. Falling back to surface="${surface}".`,
145
+ );
146
+ }
147
+
148
+ const isWeb = Platform.OS === 'web';
149
+ // Web paints built-in surfaces with static classes; a custom color has no static class,
150
+ // so it takes a CSS background-image (experimental_backgroundImage paints nothing on RNW).
151
+ const useInlinePath = !isWeb || customColor != null;
152
+ const inlineGradient = useInlinePath
153
+ ? getInlineBackgroundImage(String(customColor ?? surfaceColor), orientation, edge, false)
154
+ : undefined;
155
+ const webGradientClasses = useInlinePath
156
+ ? undefined
157
+ : getWebSurfaceGradientClasses(surface, orientation, edge);
158
+ const inlineStyle = inlineGradient
159
+ ? isWeb
160
+ ? ({ backgroundImage: inlineGradient } as unknown as ViewStyle)
161
+ : { experimental_backgroundImage: inlineGradient }
162
+ : undefined;
163
+
164
+ const edgeView = (
165
+ <View
166
+ pointerEvents="none"
167
+ accessible={false}
168
+ accessibilityElementsHidden
169
+ importantForAccessibility="no-hide-descendants"
170
+ aria-hidden
171
+ className={cn(
172
+ scrollShadowEdgeVariants({ orientation, edge, size }),
173
+ // The inline gradient is authored LTR; mirroring follows Uniwind's layout
174
+ // direction (I18nManager or LayoutDirection) without a re-render.
175
+ useInlinePath && orientation === 'horizontal' ? 'rtl:-scale-x-100' : undefined,
176
+ webGradientClasses,
177
+ edgeClassName,
178
+ )}
179
+ style={inlineStyle}
180
+ {...mergeDataAttributes(dataAttributes({ slot: 'scroll-shadow-edge', edge }))}
181
+ />
182
+ );
183
+
184
+ if (!opacity) {
185
+ return edgeView;
186
+ }
187
+
188
+ return (
189
+ <Animated.View
190
+ pointerEvents="none"
191
+ accessible={false}
192
+ accessibilityElementsHidden
193
+ importantForAccessibility="no-hide-descendants"
194
+ aria-hidden
195
+ style={[StyleSheet.absoluteFill, { opacity }]}
196
+ >
197
+ {edgeView}
198
+ </Animated.View>
199
+ );
200
+ }
201
+
202
+ function shouldShowEdge(
203
+ visibility: ScrollShadowVisibility,
204
+ edge: 'start' | 'end',
205
+ capability: ScrollShadowCapability,
206
+ ): boolean {
207
+ switch (visibility) {
208
+ case 'both':
209
+ return true;
210
+ case 'start':
211
+ return edge === 'start';
212
+ case 'end':
213
+ return edge === 'end';
214
+ case 'none':
215
+ return false;
216
+ case 'auto':
217
+ default:
218
+ return edge === 'start' ? capability.canScrollBefore : capability.canScrollAfter;
219
+ }
220
+ }
221
+
222
+ function ReanimatedTrackedChild({
223
+ child,
224
+ handleScroll,
225
+ trackedChildProps,
226
+ }: {
227
+ child: ReactElement<ScrollShadowChildProps>;
228
+ handleScroll: UseScrollShadowReturn['handleScroll'];
229
+ trackedChildProps: ScrollShadowChildProps;
230
+ }) {
231
+ const onScroll = useReanimatedComposedScrollHandler(handleScroll, child.props.onScroll);
232
+
233
+ return cloneElement(child, {
234
+ ...trackedChildProps,
235
+ onScroll,
236
+ });
237
+ }
238
+
46
239
  export function ScrollShadow({
47
240
  children,
48
241
  className,
49
- fadeFromClassName = 'from-background/35',
242
+ style,
243
+ surface = 'background',
244
+ size = 'default',
245
+ visibility = 'auto',
50
246
  hideScrollBar = false,
51
- orientation = 'horizontal',
247
+ orientation,
52
248
  offset = 0,
249
+ edgeClassName,
250
+ colorClassName,
53
251
  onCapabilityChange,
54
- size = 64,
55
- style,
56
252
  }: ScrollShadowProps) {
57
- const resolvedOrientation = orientation ?? 'horizontal';
58
253
  const child = Children.only(children);
59
254
 
60
255
  if (!isValidElement<ScrollShadowChildProps>(child)) {
61
256
  throw new Error('ScrollShadow expects a single React element child.');
62
257
  }
63
258
 
64
- const { capability, handleContentSizeChange, handleLayout, handleScroll } = useScrollShadow({
65
- offset,
66
- onCapabilityChange,
67
- orientation: resolvedOrientation,
68
- });
259
+ const resolvedOrientation = orientation ?? (child.props.horizontal ? 'horizontal' : 'vertical');
260
+ const trackingEnabled = visibility === 'auto';
261
+
262
+ const startOpacity = useRef(new Animated.Value(0)).current;
263
+ const endOpacity = useRef(new Animated.Value(0)).current;
264
+ const fadeDistance = SCROLL_SHADOW_SIZE_PX[size];
69
265
 
70
- const overlayFade = cn(fadeFromClassName, 'to-transparent');
71
- const overlayAxisSize = resolvedOrientation === 'horizontal' ? { width: size } : { height: size };
266
+ // Opacity tracks the distance to each edge, so the fade eases out over the last
267
+ // `fadeDistance` px of scroll instead of cutting when the edge is reached.
268
+ const handleDistanceChange = useCallback(
269
+ ({ before, after }: ScrollShadowDistance) => {
270
+ startOpacity.setValue(Math.min(1, before / fadeDistance));
271
+ endOpacity.setValue(Math.min(1, after / fadeDistance));
272
+ },
273
+ [startOpacity, endOpacity, fadeDistance],
274
+ );
275
+
276
+ const { capability, handleContentSizeChange, handleLayout, handleScroll } =
277
+ useScrollShadowTracker({
278
+ offset,
279
+ onCapabilityChange,
280
+ onDistanceChange: handleDistanceChange,
281
+ orientation: resolvedOrientation,
282
+ enabled: trackingEnabled,
283
+ });
284
+
285
+ const showStart = shouldShowEdge(visibility, 'start', capability);
286
+ const showEnd = shouldShowEdge(visibility, 'end', capability);
287
+ const useReanimatedScroll = isReanimatedScrollHandler(child.props.onScroll);
288
+
289
+ const trackedChildProps: ScrollShadowChildProps = {
290
+ className: cn(scrollShadowVariants({ hideScrollBar }), child.props.className),
291
+ // Keep measuring while visibility is forced so a switch back to auto fades
292
+ // from the scroll position the user already reached.
293
+ onContentSizeChange: (width: number, height: number) => {
294
+ handleContentSizeChange(width, height);
295
+ child.props.onContentSizeChange?.(width, height);
296
+ },
297
+ onLayout: (event) => {
298
+ handleLayout(event);
299
+ child.props.onLayout?.(event);
300
+ },
301
+ ...(useReanimatedScroll
302
+ ? {}
303
+ : {
304
+ onScroll: composeScrollHandler(handleScroll, child.props.onScroll),
305
+ }),
306
+ ...(child.props.scrollEventThrottle === undefined ? { scrollEventThrottle: 16 } : {}),
307
+ };
72
308
 
73
309
  return (
74
- <View className={cn(scrollShadowContainerVariants(), className)} style={style}>
75
- {cloneElement(child, {
76
- className: cn(scrollShadowVariants({ hideScrollBar }), child.props.className),
77
- onContentSizeChange: (width: number, height: number) => {
78
- handleContentSizeChange(width, height);
79
- child.props.onContentSizeChange?.(width, height);
80
- },
81
- onLayout: (event) => {
82
- handleLayout(event);
83
- child.props.onLayout?.(event);
84
- },
85
- onScroll: (event) => {
86
- handleScroll(event);
87
- child.props.onScroll?.(event);
88
- },
89
- style: StyleSheet.flatten([child.props.style]),
90
- })}
91
- {capability.canScrollBefore ? (
92
- <View
93
- pointerEvents="none"
94
- className={cn(
95
- scrollShadowOverlayVariants({
96
- orientation: resolvedOrientation,
97
- edge: 'start',
98
- }),
99
- overlayFade,
100
- )}
101
- style={overlayAxisSize}
310
+ <View
311
+ className={cn(scrollShadowContainerVariants(), className)}
312
+ style={style}
313
+ {...mergeDataAttributes(dataAttributes({ slot: 'scroll-shadow' }))}
314
+ >
315
+ {useReanimatedScroll ? (
316
+ <ReanimatedTrackedChild
317
+ child={child}
318
+ handleScroll={handleScroll}
319
+ trackedChildProps={trackedChildProps}
320
+ />
321
+ ) : (
322
+ cloneElement(child, trackedChildProps)
323
+ )}
324
+ {showStart ? (
325
+ <ScrollShadowEdge
326
+ colorClassName={colorClassName}
327
+ edge="start"
328
+ edgeClassName={edgeClassName}
329
+ opacity={trackingEnabled ? startOpacity : undefined}
330
+ orientation={resolvedOrientation}
331
+ size={size}
332
+ surface={surface}
102
333
  />
103
334
  ) : null}
104
- {capability.canScrollAfter ? (
105
- <View
106
- pointerEvents="none"
107
- className={cn(
108
- scrollShadowOverlayVariants({
109
- orientation: resolvedOrientation,
110
- edge: 'end',
111
- }),
112
- overlayFade,
113
- )}
114
- style={overlayAxisSize}
335
+ {showEnd ? (
336
+ <ScrollShadowEdge
337
+ colorClassName={colorClassName}
338
+ edge="end"
339
+ edgeClassName={edgeClassName}
340
+ opacity={trackingEnabled ? endOpacity : undefined}
341
+ orientation={resolvedOrientation}
342
+ size={size}
343
+ surface={surface}
115
344
  />
116
345
  ) : null}
117
346
  </View>
118
347
  );
119
348
  }
349
+
350
+ ScrollShadow.displayName = 'ScrollShadow';
@@ -1,57 +1,74 @@
1
1
  import { cva, type VariantProps } from '@cdx-ui/utils';
2
2
 
3
+ export type ScrollShadowSurface = 'background' | 'primary' | 'secondary';
4
+ export type ScrollShadowSize = 'small' | 'default' | 'large';
5
+
6
+ export const SCROLL_SHADOW_SURFACE_CSS_VARS: Record<ScrollShadowSurface, string> = {
7
+ background: '--color-surface-background',
8
+ primary: '--color-surface-primary',
9
+ secondary: '--color-surface-secondary',
10
+ };
11
+
12
+ export const SCROLL_SHADOW_SIZE_PX: Record<ScrollShadowSize, number> = {
13
+ small: 32,
14
+ default: 40,
15
+ large: 72,
16
+ };
17
+
18
+ export const SCROLL_SHADOW_SURFACE_FROM_CLASS: Record<ScrollShadowSurface, string> = {
19
+ background: 'from-surface-background',
20
+ primary: 'from-surface-primary',
21
+ secondary: 'from-surface-secondary',
22
+ };
23
+
3
24
  export const scrollShadowVariants = cva([], {
4
25
  variants: {
5
- orientation: {
6
- horizontal: '',
7
- vertical: '',
8
- },
9
26
  hideScrollBar: {
10
27
  true: 'web:[scrollbar-width:none] web:[&::-webkit-scrollbar]:hidden',
11
28
  false: '',
12
29
  },
13
30
  },
14
31
  defaultVariants: {
15
- orientation: 'horizontal',
16
32
  hideScrollBar: false,
17
33
  },
18
34
  });
19
35
 
20
36
  export const scrollShadowContainerVariants = cva('relative min-w-0 w-full');
21
37
 
22
- export const scrollShadowOverlayVariants = cva('pointer-events-none absolute z-10', {
38
+ export const scrollShadowEdgeVariants = cva('pointer-events-none absolute z-10', {
23
39
  variants: {
24
40
  orientation: {
25
- horizontal: '',
26
- vertical: '',
41
+ horizontal: 'top-0 bottom-0',
42
+ vertical: 'start-0 end-0',
27
43
  },
28
44
  edge: {
29
45
  start: '',
30
46
  end: '',
31
47
  },
48
+ size: {
49
+ small: '',
50
+ default: '',
51
+ large: '',
52
+ },
32
53
  },
33
54
  compoundVariants: [
34
- {
35
- orientation: 'horizontal',
36
- edge: 'start',
37
- className: 'left-0 top-0 bottom-0 bg-gradient-to-r',
38
- },
39
- {
40
- orientation: 'horizontal',
41
- edge: 'end',
42
- className: 'right-0 top-0 bottom-0 bg-gradient-to-l',
43
- },
44
- {
45
- orientation: 'vertical',
46
- edge: 'start',
47
- className: 'left-0 right-0 top-0 bg-gradient-to-b',
48
- },
49
- {
50
- orientation: 'vertical',
51
- edge: 'end',
52
- className: 'left-0 right-0 bottom-0 bg-gradient-to-t',
53
- },
55
+ { orientation: 'horizontal', edge: 'start', className: 'start-0' },
56
+ { orientation: 'horizontal', edge: 'end', className: 'end-0' },
57
+ { orientation: 'vertical', edge: 'start', className: 'top-0' },
58
+ { orientation: 'vertical', edge: 'end', className: 'bottom-0' },
59
+ { orientation: 'horizontal', size: 'small', className: 'w-8' },
60
+ { orientation: 'horizontal', size: 'default', className: 'w-10' },
61
+ { orientation: 'horizontal', size: 'large', className: 'w-18' },
62
+ { orientation: 'vertical', size: 'small', className: 'h-8' },
63
+ { orientation: 'vertical', size: 'default', className: 'h-10' },
64
+ { orientation: 'vertical', size: 'large', className: 'h-18' },
54
65
  ],
66
+ defaultVariants: {
67
+ orientation: 'vertical',
68
+ edge: 'start',
69
+ size: 'default',
70
+ },
55
71
  });
56
72
 
57
73
  export type ScrollShadowVariantProps = VariantProps<typeof scrollShadowVariants>;
74
+ export type ScrollShadowEdgeVariantProps = VariantProps<typeof scrollShadowEdgeVariants>;
@@ -0,0 +1,58 @@
1
+ import { useRef } from 'react';
2
+ import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
3
+ import { useAnimatedScrollHandler, useComposedEventHandler } from 'react-native-reanimated';
4
+ import { scheduleOnRN } from 'react-native-worklets';
5
+ import { composeEventHandlers } from '@cdx-ui/utils';
6
+
7
+ type ScrollHandler = (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
8
+
9
+ export function isReanimatedScrollHandler(handler: unknown): boolean {
10
+ return handler != null && typeof handler !== 'function';
11
+ }
12
+
13
+ export function composeScrollHandler(
14
+ internalHandler: ScrollHandler,
15
+ externalHandler: unknown,
16
+ ): ScrollHandler {
17
+ if (typeof externalHandler === 'function') {
18
+ return composeEventHandlers(internalHandler, externalHandler as ScrollHandler);
19
+ }
20
+
21
+ return internalHandler;
22
+ }
23
+
24
+ /**
25
+ * Only call this when the child already passed a Reanimated scroll handler.
26
+ * `useAnimatedScrollHandler` requires a Babel worklet, and the published
27
+ * package is not transformed by that plugin, so the plain ScrollView path
28
+ * must not mount this hook.
29
+ */
30
+ export function useReanimatedComposedScrollHandler(
31
+ internalHandler: ScrollHandler,
32
+ externalHandler: unknown,
33
+ ): unknown {
34
+ const internalHandlerRef = useRef(internalHandler);
35
+ internalHandlerRef.current = internalHandler;
36
+
37
+ const internalAnimatedHandler = useAnimatedScrollHandler({
38
+ onScroll: (event) => {
39
+ 'worklet';
40
+ // The UI-thread event is a host object. Copy the numbers scheduleOnRN can
41
+ // serialize; passing the event itself drops contentOffset and contentSize.
42
+ const { contentOffset, contentSize, layoutMeasurement } = event;
43
+
44
+ scheduleOnRN(internalHandlerRef.current, {
45
+ nativeEvent: {
46
+ contentOffset: { x: contentOffset.x, y: contentOffset.y },
47
+ contentSize: { width: contentSize.width, height: contentSize.height },
48
+ layoutMeasurement: {
49
+ width: layoutMeasurement.width,
50
+ height: layoutMeasurement.height,
51
+ },
52
+ },
53
+ } as NativeSyntheticEvent<NativeScrollEvent>);
54
+ },
55
+ });
56
+
57
+ return useComposedEventHandler([externalHandler as never, internalAnimatedHandler]);
58
+ }