@coinbase/cds-mcp-server 8.17.2 → 8.17.4

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 (284) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/mcp-docs/mobile/components/Accordion.txt +188 -0
  3. package/mcp-docs/mobile/components/AccordionItem.txt +29 -0
  4. package/mcp-docs/mobile/components/Alert.txt +155 -0
  5. package/mcp-docs/mobile/components/AreaChart.txt +265 -0
  6. package/mcp-docs/mobile/components/Avatar.txt +195 -0
  7. package/mcp-docs/mobile/components/AvatarButton.txt +225 -0
  8. package/mcp-docs/mobile/components/Banner.txt +221 -0
  9. package/mcp-docs/mobile/components/BarChart.txt +815 -0
  10. package/mcp-docs/mobile/components/Box.txt +173 -0
  11. package/mcp-docs/mobile/components/BrowserBar.txt +146 -0
  12. package/mcp-docs/mobile/components/Button.txt +198 -0
  13. package/mcp-docs/mobile/components/ButtonGroup.txt +79 -0
  14. package/mcp-docs/mobile/components/Carousel.txt +1083 -0
  15. package/mcp-docs/mobile/components/CartesianChart.txt +825 -0
  16. package/mcp-docs/mobile/components/CellMedia.txt +70 -0
  17. package/mcp-docs/mobile/components/Checkbox.txt +245 -0
  18. package/mcp-docs/mobile/components/CheckboxCell.txt +201 -0
  19. package/mcp-docs/mobile/components/CheckboxGroup.txt +284 -0
  20. package/mcp-docs/mobile/components/Chip.txt +194 -0
  21. package/mcp-docs/mobile/components/Coachmark.txt +157 -0
  22. package/mcp-docs/mobile/components/Collapsible.txt +104 -0
  23. package/mcp-docs/mobile/components/ContainedAssetCard.txt +134 -0
  24. package/mcp-docs/mobile/components/ContentCard.txt +365 -0
  25. package/mcp-docs/mobile/components/ContentCardBody.txt +135 -0
  26. package/mcp-docs/mobile/components/ContentCardFooter.txt +127 -0
  27. package/mcp-docs/mobile/components/ContentCardHeader.txt +145 -0
  28. package/mcp-docs/mobile/components/ContentCell.txt +226 -0
  29. package/mcp-docs/mobile/components/ControlGroup.txt +443 -0
  30. package/mcp-docs/mobile/components/DatePicker.txt +496 -0
  31. package/mcp-docs/mobile/components/Divider.txt +138 -0
  32. package/mcp-docs/mobile/components/DotCount.txt +145 -0
  33. package/mcp-docs/mobile/components/DotStatusColor.txt +58 -0
  34. package/mcp-docs/mobile/components/DotSymbol.txt +134 -0
  35. package/mcp-docs/mobile/components/Fallback.txt +157 -0
  36. package/mcp-docs/mobile/components/FloatingAssetCard.txt +155 -0
  37. package/mcp-docs/mobile/components/HStack.txt +234 -0
  38. package/mcp-docs/mobile/components/HeroSquare.txt +47 -0
  39. package/mcp-docs/mobile/components/Icon.txt +51 -0
  40. package/mcp-docs/mobile/components/IconButton.txt +268 -0
  41. package/mcp-docs/mobile/components/InputChip.txt +187 -0
  42. package/mcp-docs/mobile/components/Interactable.txt +186 -0
  43. package/mcp-docs/mobile/components/LineChart.txt +1324 -0
  44. package/mcp-docs/mobile/components/Link.txt +291 -0
  45. package/mcp-docs/mobile/components/ListCell.txt +412 -0
  46. package/mcp-docs/mobile/components/LogoMark.txt +84 -0
  47. package/mcp-docs/mobile/components/LogoWordMark.txt +93 -0
  48. package/mcp-docs/mobile/components/Lottie.txt +138 -0
  49. package/mcp-docs/mobile/components/LottieStatusAnimation.txt +46 -0
  50. package/mcp-docs/mobile/components/Modal.txt +83 -0
  51. package/mcp-docs/mobile/components/ModalBody.txt +33 -0
  52. package/mcp-docs/mobile/components/ModalFooter.txt +24 -0
  53. package/mcp-docs/mobile/components/ModalHeader.txt +27 -0
  54. package/mcp-docs/mobile/components/MultiContentModule.txt +379 -0
  55. package/mcp-docs/mobile/components/NavigationTitle.txt +131 -0
  56. package/mcp-docs/mobile/components/NavigationTitleSelect.txt +141 -0
  57. package/mcp-docs/mobile/components/NudgeCard.txt +89 -0
  58. package/mcp-docs/mobile/components/Numpad.txt +340 -0
  59. package/mcp-docs/mobile/components/Overlay.txt +151 -0
  60. package/mcp-docs/mobile/components/PageFooter.txt +160 -0
  61. package/mcp-docs/mobile/components/PageHeader.txt +185 -0
  62. package/mcp-docs/mobile/components/PeriodSelector.txt +407 -0
  63. package/mcp-docs/mobile/components/Pictogram.txt +47 -0
  64. package/mcp-docs/mobile/components/Point.txt +204 -0
  65. package/mcp-docs/mobile/components/PortalProvider.txt +78 -0
  66. package/mcp-docs/mobile/components/Pressable.txt +210 -0
  67. package/mcp-docs/mobile/components/ProgressBar.txt +129 -0
  68. package/mcp-docs/mobile/components/ProgressBarWithFixedLabels.txt +160 -0
  69. package/mcp-docs/mobile/components/ProgressBarWithFloatLabel.txt +137 -0
  70. package/mcp-docs/mobile/components/ProgressCircle.txt +236 -0
  71. package/mcp-docs/mobile/components/Radio.txt +241 -0
  72. package/mcp-docs/mobile/components/RadioCell.txt +201 -0
  73. package/mcp-docs/mobile/components/RadioGroup.txt +281 -0
  74. package/mcp-docs/mobile/components/ReferenceLine.txt +152 -0
  75. package/mcp-docs/mobile/components/RemoteImage.txt +105 -0
  76. package/mcp-docs/mobile/components/RemoteImageGroup.txt +60 -0
  77. package/mcp-docs/mobile/components/RollingNumber.txt +788 -0
  78. package/mcp-docs/mobile/components/Scrubber.txt +203 -0
  79. package/mcp-docs/mobile/components/SearchInput.txt +191 -0
  80. package/mcp-docs/mobile/components/SectionHeader.txt +204 -0
  81. package/mcp-docs/mobile/components/SegmentedTabs.txt +315 -0
  82. package/mcp-docs/mobile/components/Select.txt +211 -0
  83. package/mcp-docs/mobile/components/SelectChip.txt +323 -0
  84. package/mcp-docs/mobile/components/SelectOption.txt +84 -0
  85. package/mcp-docs/mobile/components/SlideButton.txt +330 -0
  86. package/mcp-docs/mobile/components/Spacer.txt +83 -0
  87. package/mcp-docs/mobile/components/Sparkline.txt +122 -0
  88. package/mcp-docs/mobile/components/SparklineGradient.txt +106 -0
  89. package/mcp-docs/mobile/components/SparklineInteractive.txt +156 -0
  90. package/mcp-docs/mobile/components/SparklineInteractiveHeader.txt +72 -0
  91. package/mcp-docs/mobile/components/Spinner.txt +48 -0
  92. package/mcp-docs/mobile/components/SpotIcon.txt +47 -0
  93. package/mcp-docs/mobile/components/SpotRectangle.txt +47 -0
  94. package/mcp-docs/mobile/components/SpotSquare.txt +47 -0
  95. package/mcp-docs/mobile/components/Stepper.txt +527 -0
  96. package/mcp-docs/mobile/components/SubBrandLogoMark.txt +125 -0
  97. package/mcp-docs/mobile/components/SubBrandLogoWordMark.txt +125 -0
  98. package/mcp-docs/mobile/components/Switch.txt +97 -0
  99. package/mcp-docs/mobile/components/TabIndicator.txt +48 -0
  100. package/mcp-docs/mobile/components/TabLabel.txt +153 -0
  101. package/mcp-docs/mobile/components/TabNavigation.txt +146 -0
  102. package/mcp-docs/mobile/components/TabbedChips.txt +142 -0
  103. package/mcp-docs/mobile/components/Tabs.txt +190 -0
  104. package/mcp-docs/mobile/components/Tag.txt +300 -0
  105. package/mcp-docs/mobile/components/Text.txt +211 -0
  106. package/mcp-docs/mobile/components/TextInput.txt +717 -0
  107. package/mcp-docs/mobile/components/ThemeProvider.txt +132 -0
  108. package/mcp-docs/mobile/components/Toast.txt +196 -0
  109. package/mcp-docs/mobile/components/Tooltip.txt +59 -0
  110. package/mcp-docs/mobile/components/TopNavBar.txt +161 -0
  111. package/mcp-docs/mobile/components/Tour.txt +158 -0
  112. package/mcp-docs/mobile/components/Tray.txt +252 -0
  113. package/mcp-docs/mobile/components/UpsellCard.txt +321 -0
  114. package/mcp-docs/mobile/components/VStack.txt +222 -0
  115. package/mcp-docs/mobile/components/XAxis.txt +621 -0
  116. package/mcp-docs/mobile/components/YAxis.txt +567 -0
  117. package/mcp-docs/mobile/getting-started/ai-overview.txt +108 -0
  118. package/mcp-docs/mobile/getting-started/installation.txt +57 -0
  119. package/mcp-docs/mobile/getting-started/introduction.txt +102 -0
  120. package/mcp-docs/mobile/getting-started/playground.txt +28 -0
  121. package/mcp-docs/mobile/getting-started/styling.txt +84 -0
  122. package/mcp-docs/mobile/getting-started/theming.txt +286 -0
  123. package/mcp-docs/mobile/hooks/useDimensions.txt +72 -0
  124. package/mcp-docs/mobile/hooks/useEventHandler.txt +120 -0
  125. package/mcp-docs/mobile/hooks/useMergeRefs.txt +116 -0
  126. package/mcp-docs/mobile/hooks/useOverlayContentContext.txt +280 -0
  127. package/mcp-docs/mobile/hooks/usePreviousValue.txt +74 -0
  128. package/mcp-docs/mobile/hooks/useRefMap.txt +178 -0
  129. package/mcp-docs/mobile/hooks/useTheme.txt +321 -0
  130. package/mcp-docs/mobile/routes.txt +139 -0
  131. package/mcp-docs/web/components/Accordion.txt +189 -0
  132. package/mcp-docs/web/components/AccordionItem.txt +31 -0
  133. package/mcp-docs/web/components/Alert.txt +164 -0
  134. package/mcp-docs/web/components/AreaChart.txt +510 -0
  135. package/mcp-docs/web/components/Avatar.txt +211 -0
  136. package/mcp-docs/web/components/AvatarButton.txt +240 -0
  137. package/mcp-docs/web/components/Banner.txt +226 -0
  138. package/mcp-docs/web/components/BarChart.txt +1267 -0
  139. package/mcp-docs/web/components/Box.txt +175 -0
  140. package/mcp-docs/web/components/Button.txt +212 -0
  141. package/mcp-docs/web/components/ButtonGroup.txt +79 -0
  142. package/mcp-docs/web/components/Calendar.txt +181 -0
  143. package/mcp-docs/web/components/Carousel.txt +1575 -0
  144. package/mcp-docs/web/components/CartesianChart.txt +1044 -0
  145. package/mcp-docs/web/components/CellMedia.txt +56 -0
  146. package/mcp-docs/web/components/Checkbox.txt +188 -0
  147. package/mcp-docs/web/components/CheckboxCell.txt +202 -0
  148. package/mcp-docs/web/components/CheckboxGroup.txt +219 -0
  149. package/mcp-docs/web/components/Chip.txt +196 -0
  150. package/mcp-docs/web/components/Coachmark.txt +188 -0
  151. package/mcp-docs/web/components/Collapsible.txt +119 -0
  152. package/mcp-docs/web/components/ContainedAssetCard.txt +232 -0
  153. package/mcp-docs/web/components/ContentCard.txt +367 -0
  154. package/mcp-docs/web/components/ContentCardBody.txt +137 -0
  155. package/mcp-docs/web/components/ContentCardFooter.txt +129 -0
  156. package/mcp-docs/web/components/ContentCardHeader.txt +147 -0
  157. package/mcp-docs/web/components/ContentCell.txt +219 -0
  158. package/mcp-docs/web/components/ControlGroup.txt +436 -0
  159. package/mcp-docs/web/components/DatePicker.txt +505 -0
  160. package/mcp-docs/web/components/Divider.txt +143 -0
  161. package/mcp-docs/web/components/DotCount.txt +149 -0
  162. package/mcp-docs/web/components/DotStatusColor.txt +58 -0
  163. package/mcp-docs/web/components/DotSymbol.txt +137 -0
  164. package/mcp-docs/web/components/Dropdown.txt +119 -0
  165. package/mcp-docs/web/components/Fallback.txt +163 -0
  166. package/mcp-docs/web/components/FloatingAssetCard.txt +250 -0
  167. package/mcp-docs/web/components/FullscreenAlert.txt +69 -0
  168. package/mcp-docs/web/components/FullscreenModal.txt +145 -0
  169. package/mcp-docs/web/components/FullscreenModalLayout.txt +187 -0
  170. package/mcp-docs/web/components/Grid.txt +236 -0
  171. package/mcp-docs/web/components/GridColumn.txt +209 -0
  172. package/mcp-docs/web/components/HStack.txt +236 -0
  173. package/mcp-docs/web/components/HeroSquare.txt +48 -0
  174. package/mcp-docs/web/components/Icon.txt +145 -0
  175. package/mcp-docs/web/components/IconButton.txt +390 -0
  176. package/mcp-docs/web/components/InputChip.txt +187 -0
  177. package/mcp-docs/web/components/Interactable.txt +193 -0
  178. package/mcp-docs/web/components/LineChart.txt +1576 -0
  179. package/mcp-docs/web/components/Link.txt +243 -0
  180. package/mcp-docs/web/components/ListCell.txt +418 -0
  181. package/mcp-docs/web/components/LogoMark.txt +84 -0
  182. package/mcp-docs/web/components/LogoWordMark.txt +93 -0
  183. package/mcp-docs/web/components/Lottie.txt +157 -0
  184. package/mcp-docs/web/components/LottieStatusAnimation.txt +57 -0
  185. package/mcp-docs/web/components/MediaQueryProvider.txt +108 -0
  186. package/mcp-docs/web/components/Modal.txt +196 -0
  187. package/mcp-docs/web/components/ModalBody.txt +117 -0
  188. package/mcp-docs/web/components/ModalFooter.txt +119 -0
  189. package/mcp-docs/web/components/ModalHeader.txt +123 -0
  190. package/mcp-docs/web/components/MultiContentModule.txt +381 -0
  191. package/mcp-docs/web/components/NavigationBar.txt +102 -0
  192. package/mcp-docs/web/components/NavigationTitle.txt +25 -0
  193. package/mcp-docs/web/components/NavigationTitleSelect.txt +45 -0
  194. package/mcp-docs/web/components/NudgeCard.txt +181 -0
  195. package/mcp-docs/web/components/Overlay.txt +171 -0
  196. package/mcp-docs/web/components/PageFooter.txt +184 -0
  197. package/mcp-docs/web/components/PageHeader.txt +243 -0
  198. package/mcp-docs/web/components/Pagination.txt +499 -0
  199. package/mcp-docs/web/components/PeriodSelector.txt +703 -0
  200. package/mcp-docs/web/components/Pictogram.txt +48 -0
  201. package/mcp-docs/web/components/Point.txt +460 -0
  202. package/mcp-docs/web/components/PortalProvider.txt +76 -0
  203. package/mcp-docs/web/components/Pressable.txt +193 -0
  204. package/mcp-docs/web/components/ProgressBar.txt +163 -0
  205. package/mcp-docs/web/components/ProgressBarWithFixedLabels.txt +212 -0
  206. package/mcp-docs/web/components/ProgressBarWithFloatLabel.txt +181 -0
  207. package/mcp-docs/web/components/ProgressCircle.txt +443 -0
  208. package/mcp-docs/web/components/Radio.txt +219 -0
  209. package/mcp-docs/web/components/RadioCell.txt +215 -0
  210. package/mcp-docs/web/components/RadioGroup.txt +288 -0
  211. package/mcp-docs/web/components/ReferenceLine.txt +451 -0
  212. package/mcp-docs/web/components/RemoteImage.txt +165 -0
  213. package/mcp-docs/web/components/RemoteImageGroup.txt +86 -0
  214. package/mcp-docs/web/components/RollingNumber.txt +1021 -0
  215. package/mcp-docs/web/components/Scrubber.txt +231 -0
  216. package/mcp-docs/web/components/SearchInput.txt +117 -0
  217. package/mcp-docs/web/components/SectionHeader.txt +217 -0
  218. package/mcp-docs/web/components/SegmentedTabs.txt +324 -0
  219. package/mcp-docs/web/components/Select.txt +224 -0
  220. package/mcp-docs/web/components/SelectChip.txt +314 -0
  221. package/mcp-docs/web/components/SelectOption.txt +165 -0
  222. package/mcp-docs/web/components/Sidebar.txt +349 -0
  223. package/mcp-docs/web/components/SidebarItem.txt +131 -0
  224. package/mcp-docs/web/components/SidebarMoreMenu.txt +30 -0
  225. package/mcp-docs/web/components/Spacer.txt +173 -0
  226. package/mcp-docs/web/components/Sparkline.txt +122 -0
  227. package/mcp-docs/web/components/SparklineGradient.txt +106 -0
  228. package/mcp-docs/web/components/SparklineInteractive.txt +153 -0
  229. package/mcp-docs/web/components/SparklineInteractiveHeader.txt +76 -0
  230. package/mcp-docs/web/components/Spinner.txt +128 -0
  231. package/mcp-docs/web/components/SpotIcon.txt +48 -0
  232. package/mcp-docs/web/components/SpotRectangle.txt +48 -0
  233. package/mcp-docs/web/components/SpotSquare.txt +48 -0
  234. package/mcp-docs/web/components/Stepper.txt +682 -0
  235. package/mcp-docs/web/components/SubBrandLogoMark.txt +125 -0
  236. package/mcp-docs/web/components/SubBrandLogoWordMark.txt +125 -0
  237. package/mcp-docs/web/components/Switch.txt +85 -0
  238. package/mcp-docs/web/components/TabIndicator.txt +48 -0
  239. package/mcp-docs/web/components/TabLabel.txt +158 -0
  240. package/mcp-docs/web/components/TabNavigation.txt +159 -0
  241. package/mcp-docs/web/components/TabbedChips.txt +155 -0
  242. package/mcp-docs/web/components/Table.txt +367 -0
  243. package/mcp-docs/web/components/TableBody.txt +83 -0
  244. package/mcp-docs/web/components/TableCaption.txt +102 -0
  245. package/mcp-docs/web/components/TableCell.txt +165 -0
  246. package/mcp-docs/web/components/TableCellFallback.txt +97 -0
  247. package/mcp-docs/web/components/TableFooter.txt +83 -0
  248. package/mcp-docs/web/components/TableHeader.txt +100 -0
  249. package/mcp-docs/web/components/TableRow.txt +140 -0
  250. package/mcp-docs/web/components/Tabs.txt +212 -0
  251. package/mcp-docs/web/components/Tag.txt +304 -0
  252. package/mcp-docs/web/components/Text.txt +232 -0
  253. package/mcp-docs/web/components/TextInput.txt +652 -0
  254. package/mcp-docs/web/components/ThemeProvider.txt +199 -0
  255. package/mcp-docs/web/components/TileButton.txt +158 -0
  256. package/mcp-docs/web/components/Toast.txt +203 -0
  257. package/mcp-docs/web/components/Tooltip.txt +89 -0
  258. package/mcp-docs/web/components/Tour.txt +179 -0
  259. package/mcp-docs/web/components/Tray.txt +288 -0
  260. package/mcp-docs/web/components/UpsellCard.txt +319 -0
  261. package/mcp-docs/web/components/VStack.txt +224 -0
  262. package/mcp-docs/web/components/XAxis.txt +619 -0
  263. package/mcp-docs/web/components/YAxis.txt +548 -0
  264. package/mcp-docs/web/getting-started/ai-overview.txt +108 -0
  265. package/mcp-docs/web/getting-started/installation.txt +103 -0
  266. package/mcp-docs/web/getting-started/introduction.txt +102 -0
  267. package/mcp-docs/web/getting-started/playground.txt +28 -0
  268. package/mcp-docs/web/getting-started/styling.txt +161 -0
  269. package/mcp-docs/web/getting-started/templates.txt +121 -0
  270. package/mcp-docs/web/getting-started/theming.txt +426 -0
  271. package/mcp-docs/web/hooks/useBreakpoints.txt +61 -0
  272. package/mcp-docs/web/hooks/useDimensions.txt +114 -0
  273. package/mcp-docs/web/hooks/useEventHandler.txt +120 -0
  274. package/mcp-docs/web/hooks/useHasMounted.txt +75 -0
  275. package/mcp-docs/web/hooks/useIsoEffect.txt +58 -0
  276. package/mcp-docs/web/hooks/useMediaQuery.txt +114 -0
  277. package/mcp-docs/web/hooks/useMergeRefs.txt +116 -0
  278. package/mcp-docs/web/hooks/useOverlayContentContext.txt +279 -0
  279. package/mcp-docs/web/hooks/usePreviousValue.txt +74 -0
  280. package/mcp-docs/web/hooks/useRefMap.txt +178 -0
  281. package/mcp-docs/web/hooks/useScrollBlocker.txt +82 -0
  282. package/mcp-docs/web/hooks/useTheme.txt +364 -0
  283. package/mcp-docs/web/routes.txt +163 -0
  284. package/package.json +1 -1
@@ -0,0 +1,1575 @@
1
+ # Carousel
2
+
3
+ A flexible carousel component for displaying sequences of content with navigation and pagination options.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Carousel } from '@coinbase/cds-web/carousel/Carousel'
9
+ ```
10
+
11
+ ## Examples
12
+
13
+ ### Basic Example
14
+
15
+ Carousels are a great way to showcase a list of items in a compact and engaging way.
16
+ By default, Carousels have navigation and pagination enabled.
17
+ You can also add a title to the Carousel by setting `title` prop.
18
+
19
+ You simply wrap each child in a `CarouselItem` component, and can optionally set the `width` prop to control the width of the item.
20
+
21
+ You can also set the `styles` prop to control the styles of the carousel, such as the gap between items.
22
+
23
+ :::tip Images
24
+
25
+ Images inside of the carousel have `pointer-events` disabled by default.
26
+
27
+ :::
28
+
29
+ ```jsx live
30
+ function MyCarousel() {
31
+ function SquareAssetCard({ imageUrl, name }) {
32
+ return (
33
+ <ContainedAssetCard
34
+ description={
35
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
36
+ ↗6.37%
37
+ </TextLabel2>
38
+ }
39
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
40
+ subtitle={name}
41
+ title="$0.87"
42
+ />
43
+ );
44
+ }
45
+ return (
46
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
47
+ <Carousel
48
+ hidePagination
49
+ title="Explore Assets"
50
+ styles={{
51
+ root: { paddingInline: 'var(--space-3)' },
52
+ carousel: { gap: 'var(--space-1)' },
53
+ }}
54
+ >
55
+ {Object.values(assets).map((asset, index) => (
56
+ <CarouselItem key={asset.symbol} id={asset.symbol} accessibilityLabel={asset.name}>
57
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
58
+ </CarouselItem>
59
+ ))}
60
+ </Carousel>
61
+ </Box>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ### Item Sizing
67
+
68
+ Items by default take their natural width while in the carousel, such as from our example above.
69
+ However, you can set the `width` prop of `CarouselItem` to control the width of the item.
70
+
71
+ #### Dynamic Sizing
72
+
73
+ Items can be given a width proportional to the carousel width.
74
+
75
+ :::tip Tip
76
+
77
+ If you have a gap between items, you should account for that in the width.
78
+ For example, if you have a gap of 8px, and you want to show 2 items per page,
79
+ you should give each item a width of `calc(50% - 4px)`.
80
+
81
+ :::
82
+
83
+ ```jsx live
84
+ function DynamicSizingCarousel() {
85
+ const itemsPerPage = [
86
+ { id: 'one', label: 'One' },
87
+ { id: 'two', label: 'Two' },
88
+ { id: 'three', label: 'Three' },
89
+ ];
90
+ const [selectedItemsPerPage, setSelectedItemsPerPage] = useState(itemsPerPage[0]);
91
+ const itemWidths = {
92
+ one: '100%',
93
+ two: 'calc((100% - var(--space-1)) / 2)',
94
+ three: 'calc((100% - (2 * var(--space-1))) / 3)',
95
+ };
96
+ function NoopFn() {
97
+ console.log('pressed');
98
+ }
99
+ function ActionButton({ isVisible, children }) {
100
+ return (
101
+ <Button
102
+ compact
103
+ flush="start"
104
+ numberOfLines={1}
105
+ onClick={NoopFn}
106
+ tabIndex={isVisible ? undefined : -1}
107
+ variant="secondary"
108
+ >
109
+ {children}
110
+ </Button>
111
+ );
112
+ }
113
+ return (
114
+ <VStack gap={2}>
115
+ <HStack justifyContent="flex-end" gap={2} alignItems="center">
116
+ <TextHeadline as="h3">Items per page</TextHeadline>
117
+ <SegmentedTabs
118
+ activeTab={selectedItemsPerPage}
119
+ onChange={setSelectedItemsPerPage}
120
+ tabs={itemsPerPage}
121
+ />
122
+ </HStack>
123
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
124
+ <Carousel
125
+ hidePagination
126
+ title="Learn more"
127
+ styles={{
128
+ root: { paddingInline: 'var(--space-3)' },
129
+ carousel: { gap: 'var(--space-1)' },
130
+ }}
131
+ key={selectedItemsPerPage.id}
132
+ >
133
+ <CarouselItem
134
+ id="recurring-buy"
135
+ width={itemWidths[selectedItemsPerPage.id]}
136
+ accessibilityLabelledBy="recurring-buy-label"
137
+ >
138
+ {({ isVisible }) => (
139
+ <UpsellCard
140
+ action={<ActionButton isVisible={isVisible}>Get started</ActionButton>}
141
+ description="Want to add funds to your card every week or month?"
142
+ media={
143
+ <Box bottom={6} position="relative" right={24}>
144
+ <Pictogram dimension="64x64" name="recurringPurchases" />
145
+ </Box>
146
+ }
147
+ minWidth="0"
148
+ title={
149
+ <TextHeadline as="h3" id="recurring-buy-label">
150
+ Recurring Buy
151
+ </TextHeadline>
152
+ }
153
+ width="100%"
154
+ />
155
+ )}
156
+ </CarouselItem>
157
+ <CarouselItem
158
+ id="eths-apr"
159
+ width={itemWidths[selectedItemsPerPage.id]}
160
+ accessibilityLabelledBy="eths-apr-label"
161
+ >
162
+ {({ isVisible }) => (
163
+ <UpsellCard
164
+ action={<ActionButton isVisible={isVisible}>Start earning</ActionButton>}
165
+ dangerouslySetBackground="rgb(var(--purple70))"
166
+ description={
167
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
168
+ Earn staking rewards on ETH by holding it on Coinbase
169
+ </TextLabel2>
170
+ }
171
+ media={
172
+ <Box left={16} position="relative" top={12}>
173
+ <RemoteImage height={174} source="/img/feature.png" />
174
+ </Box>
175
+ }
176
+ minWidth="0"
177
+ title={
178
+ <TextHeadline id="eths-apr-label" color="fgInverse" as="h3">
179
+ Up to 3.29% APR on ETHs
180
+ </TextHeadline>
181
+ }
182
+ width="100%"
183
+ />
184
+ )}
185
+ </CarouselItem>
186
+ <CarouselItem
187
+ id="join-the-community"
188
+ width={itemWidths[selectedItemsPerPage.id]}
189
+ accessibilityLabelledBy="join-the-community-label"
190
+ >
191
+ {({ isVisible }) => (
192
+ <UpsellCard
193
+ action={<ActionButton isVisible={isVisible}>Start chatting</ActionButton>}
194
+ dangerouslySetBackground="rgb(var(--teal70))"
195
+ description={
196
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
197
+ Chat with other devs in our Discord community
198
+ </TextLabel2>
199
+ }
200
+ media={
201
+ <Box left={16} position="relative" top={4}>
202
+ <RemoteImage height={174} source="/img/community.png" />
203
+ </Box>
204
+ }
205
+ minWidth="0"
206
+ title={
207
+ <TextHeadline id="join-the-community-label" color="fgInverse" as="h3">
208
+ Join the community
209
+ </TextHeadline>
210
+ }
211
+ width="100%"
212
+ />
213
+ )}
214
+ </CarouselItem>
215
+ <CarouselItem
216
+ id="coinbase-one-offer"
217
+ width={itemWidths[selectedItemsPerPage.id]}
218
+ accessibilityLabelledBy="coinbase-one-offer-label"
219
+ >
220
+ {({ isVisible }) => (
221
+ <UpsellCard
222
+ action={<ActionButton isVisible={isVisible}>Get 60 days free</ActionButton>}
223
+ dangerouslySetBackground="rgb(var(--blue80))"
224
+ description={
225
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
226
+ Use code NOV60 when you sign up for Coinbase One
227
+ </TextLabel2>
228
+ }
229
+ media={
230
+ <Box left={16} position="relative" top={0}>
231
+ <RemoteImage height={174} source="/img/marketing.png" />
232
+ </Box>
233
+ }
234
+ minWidth="0"
235
+ title={
236
+ <TextHeadline id="coinbase-one-offer-label" color="fgInverse" as="h3">
237
+ Coinbase One offer
238
+ </TextHeadline>
239
+ }
240
+ width="100%"
241
+ />
242
+ )}
243
+ </CarouselItem>
244
+ <CarouselItem
245
+ id="coinbase-card"
246
+ width={itemWidths[selectedItemsPerPage.id]}
247
+ accessibilityLabelledBy="coinbase-card-label"
248
+ >
249
+ {({ isVisible }) => (
250
+ <UpsellCard
251
+ action={<ActionButton isVisible={isVisible}>Get started</ActionButton>}
252
+ dangerouslySetBackground="rgb(var(--gray100))"
253
+ description={
254
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
255
+ Spend USDC to get rewards with our Visa® debit card
256
+ </TextLabel2>
257
+ }
258
+ media={
259
+ <Box left={16} position="relative" top={0}>
260
+ <RemoteImage height={174} source="/img/object.png" />
261
+ </Box>
262
+ }
263
+ minWidth="0"
264
+ title={
265
+ <TextHeadline id="coinbase-card-label" color="fgInverse" as="h3">
266
+ Coinbase Card
267
+ </TextHeadline>
268
+ }
269
+ width="100%"
270
+ />
271
+ )}
272
+ </CarouselItem>
273
+ </Carousel>
274
+ </Box>
275
+ </VStack>
276
+ );
277
+ }
278
+ ```
279
+
280
+ #### Responsive Sizing
281
+
282
+ You can also use responsive props to change the number of items visible based on the carousel width.
283
+ The carousel below will show per page 1 item on mobile, 2 items on tablet, and 3 items on desktop.
284
+
285
+ ```jsx live
286
+ function ResponsiveSizingCarousel() {
287
+ const itemWidth = {
288
+ phone: '100%',
289
+ tablet: 'calc((100% - var(--space-1)) / 2)',
290
+ desktop: 'calc((100% - (2 * var(--space-1))) / 3)',
291
+ };
292
+ function NoopFn() {
293
+ console.log('pressed');
294
+ }
295
+ function ActionButton({ isVisible, children }) {
296
+ return (
297
+ <Pressable
298
+ background="transparent"
299
+ onClick={NoopFn}
300
+ paddingY={1}
301
+ tabIndex={isVisible ? undefined : -1}
302
+ borderRadius={500}
303
+ >
304
+ <Text color="fgPrimary" font="headline" numberOfLines={1}>
305
+ {children}
306
+ </Text>
307
+ </Pressable>
308
+ );
309
+ }
310
+ return (
311
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
312
+ <Carousel
313
+ hidePagination
314
+ title="Learn more"
315
+ styles={{ root: { paddingInline: 'var(--space-3)' }, carousel: { gap: 'var(--space-1)' } }}
316
+ drag="free"
317
+ >
318
+ <CarouselItem id="earn-more-crypto" width={itemWidth}>
319
+ {({ isVisible }) => (
320
+ <NudgeCard
321
+ title="Earn more crypto"
322
+ description="You've got unstaked crypto."
323
+ pictogram="key"
324
+ action={<ActionButton isVisible={isVisible}>Start earning</ActionButton>}
325
+ width="100%"
326
+ minWidth="0"
327
+ />
328
+ )}
329
+ </CarouselItem>
330
+ <CarouselItem id="secure-your-account" width={itemWidth}>
331
+ {({ isVisible }) => (
332
+ <NudgeCard
333
+ title="Secure your account"
334
+ description="Add two-factor authentication."
335
+ pictogram="shield"
336
+ action={<ActionButton isVisible={isVisible}>Enable 2FA</ActionButton>}
337
+ width="100%"
338
+ minWidth="0"
339
+ />
340
+ )}
341
+ </CarouselItem>
342
+ <CarouselItem id="complete-your-profile" width={itemWidth}>
343
+ {({ isVisible }) => (
344
+ <NudgeCard
345
+ title="Complete your profile"
346
+ description="Add more details."
347
+ pictogram="accountsNavigation"
348
+ action={<ActionButton isVisible={isVisible}>Update</ActionButton>}
349
+ width="100%"
350
+ minWidth="0"
351
+ />
352
+ )}
353
+ </CarouselItem>
354
+ </Carousel>
355
+ </Box>
356
+ );
357
+ }
358
+ ```
359
+
360
+ #### Varied Sizing
361
+
362
+ Not all carousel items need to be the same size. You can provide CarouselItems of varying widths as well.
363
+
364
+ ```jsx live
365
+ function VariedSizingCarousel() {
366
+ function SquareAssetCard({ imageUrl, name }) {
367
+ return (
368
+ <ContainedAssetCard
369
+ description={
370
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
371
+ ↗6.37%
372
+ </TextLabel2>
373
+ }
374
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
375
+ subtitle={name}
376
+ title="$0.87"
377
+ />
378
+ );
379
+ }
380
+ function NoopFn() {
381
+ console.log('pressed');
382
+ }
383
+ function ActionButton({ isVisible, children }) {
384
+ return (
385
+ <Pressable
386
+ background="transparent"
387
+ onClick={NoopFn}
388
+ paddingY={1}
389
+ tabIndex={isVisible ? undefined : -1}
390
+ borderRadius={500}
391
+ >
392
+ <Text color="fgPrimary" font="headline" numberOfLines={1}>
393
+ {children}
394
+ </Text>
395
+ </Pressable>
396
+ );
397
+ }
398
+ const itemWidth = {
399
+ phone: '100%',
400
+ tablet: 'calc((100% - var(--space-1)) / 2)',
401
+ desktop: 'calc((100% - var(--space-1)) / 2)',
402
+ };
403
+ return (
404
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
405
+ <Carousel
406
+ hidePagination
407
+ title="Varied Sizing"
408
+ styles={{ root: { paddingInline: 'var(--space-3)' }, carousel: { gap: 'var(--space-1)' } }}
409
+ >
410
+ <CarouselItem id="earn-more-crypto" width={itemWidth}>
411
+ {({ isVisible }) => (
412
+ <NudgeCard
413
+ title="Earn more crypto"
414
+ description="You've got unstaked crypto. Stake it now to earn more."
415
+ pictogram="key"
416
+ action={<ActionButton isVisible={isVisible}>Start earning</ActionButton>}
417
+ width="100%"
418
+ minWidth="0"
419
+ />
420
+ )}
421
+ </CarouselItem>
422
+ <CarouselItem id="btc">
423
+ <SquareAssetCard imageUrl={assets.btc.imageUrl} name="BTC" />
424
+ </CarouselItem>
425
+ <CarouselItem id="secure-your-account" width={itemWidth}>
426
+ {({ isVisible }) => (
427
+ <NudgeCard
428
+ title="Secure your account"
429
+ description="Add two-factor authentication for enhanced security."
430
+ pictogram="shield"
431
+ action={<ActionButton isVisible={isVisible}>Enable 2FA</ActionButton>}
432
+ width="100%"
433
+ minWidth="0"
434
+ />
435
+ )}
436
+ </CarouselItem>
437
+ <CarouselItem id="eth">
438
+ <SquareAssetCard imageUrl={assets.eth.imageUrl} name="ETH" />
439
+ </CarouselItem>
440
+ <CarouselItem id="complete-your-profile" width={itemWidth}>
441
+ {({ isVisible }) => (
442
+ <NudgeCard
443
+ title="Complete your profile"
444
+ description="Add more details to personalize your experience."
445
+ pictogram="accountsNavigation"
446
+ action={<ActionButton isVisible={isVisible}>Update profile</ActionButton>}
447
+ width="100%"
448
+ minWidth="0"
449
+ />
450
+ )}
451
+ </CarouselItem>
452
+ <CarouselItem id="ltc">
453
+ <SquareAssetCard imageUrl={assets.ltc.imageUrl} name="LTC" />
454
+ </CarouselItem>
455
+ </Carousel>
456
+ </Box>
457
+ );
458
+ }
459
+ ```
460
+
461
+ ### Drag
462
+
463
+ You can set the `drag` prop to `snap` (default), `free`, or `none`.
464
+ When set to `snap`, upon release the carousel will snap to either the nearest item or page (depending on `snapMode`).
465
+
466
+ ```jsx live
467
+ function DragCarousel() {
468
+ const dragOptions = [
469
+ { id: 'snap', label: 'Snap' },
470
+ { id: 'free', label: 'Free' },
471
+ { id: 'none', label: 'None' },
472
+ ];
473
+ const [drag, setDrag] = useState(dragOptions[0]);
474
+ function SquareAssetCard({ imageUrl, name }) {
475
+ return (
476
+ <ContainedAssetCard
477
+ description={
478
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
479
+ ↗6.37%
480
+ </TextLabel2>
481
+ }
482
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
483
+ subtitle={name}
484
+ title="$0.87"
485
+ />
486
+ );
487
+ }
488
+ return (
489
+ <VStack gap={2}>
490
+ <HStack justifyContent="flex-end" gap={2} alignItems="center">
491
+ <TextHeadline as="h3">Drag</TextHeadline>
492
+ <SegmentedTabs activeTab={drag} onChange={setDrag} tabs={dragOptions} />
493
+ </HStack>
494
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
495
+ <Carousel
496
+ title="Explore Assets"
497
+ hidePagination
498
+ drag={drag.id}
499
+ styles={{
500
+ root: { paddingInline: 'var(--space-3)' },
501
+ carousel: { gap: 'var(--space-1)' },
502
+ }}
503
+ snapMode="item"
504
+ key={drag.id}
505
+ >
506
+ {Object.values(assets).map((asset, index) => (
507
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
508
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
509
+ </CarouselItem>
510
+ ))}
511
+ </Carousel>
512
+ </Box>
513
+ </VStack>
514
+ );
515
+ }
516
+ ```
517
+
518
+ ### Snap Mode
519
+
520
+ You can set the `snapMode` to `page` (default) or `item`.
521
+ When set to `page`, the carousel will automatically group items into pages.
522
+ When set to `item`, the carousel will snap to the nearest item.
523
+
524
+ ```jsx live
525
+ function SnapModeCarousel() {
526
+ const snapModeOptions = [
527
+ { id: 'page', label: 'Page' },
528
+ { id: 'item', label: 'Item' },
529
+ ];
530
+ const [snapMode, setSnapMode] = useState(snapModeOptions[0]);
531
+ function SquareAssetCard({ imageUrl, name }) {
532
+ return (
533
+ <ContainedAssetCard
534
+ description={
535
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
536
+ ↗6.37%
537
+ </TextLabel2>
538
+ }
539
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
540
+ subtitle={name}
541
+ title="$0.87"
542
+ />
543
+ );
544
+ }
545
+ return (
546
+ <VStack gap={2}>
547
+ <HStack justifyContent="flex-end" gap={2} alignItems="center">
548
+ <TextHeadline as="h3">Snap mode</TextHeadline>
549
+ <SegmentedTabs activeTab={snapMode} onChange={setSnapMode} tabs={snapModeOptions} />
550
+ </HStack>
551
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
552
+ <Carousel
553
+ title="Explore Assets"
554
+ styles={{
555
+ root: { paddingInline: 'var(--space-3)' },
556
+ carousel: { gap: 'var(--space-1)' },
557
+ }}
558
+ snapMode={snapMode.id}
559
+ key={snapMode.id}
560
+ >
561
+ {Object.values(assets).map((asset, index) => (
562
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
563
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
564
+ </CarouselItem>
565
+ ))}
566
+ </Carousel>
567
+ </Box>
568
+ </VStack>
569
+ );
570
+ }
571
+ ```
572
+
573
+ ### Overflow
574
+
575
+ By default, the carousel's inner overflow is visible.
576
+ This means that you can apply padding to the inner carousel element
577
+ (such as `styles={{ carousel: { paddingInline: 'var(--space-3)' } }}`) and it will not be clipped.
578
+ You can pair this with modifying the spacing of the inner carousel to match
579
+ the padding of your page (along with a wrapping div to negate any default spacing).
580
+ This creates a seamless experience.
581
+
582
+ :::tip Tip
583
+
584
+ If you want to have the next item be shown at the edge of the screen, make sure your carousel padding is larger than your gap.
585
+
586
+ :::
587
+
588
+ ```jsx live
589
+ function OverflowCarousel() {
590
+ function SquareAssetCard({ isVisible, imageUrl, name }) {
591
+ return (
592
+ <ContainedAssetCard
593
+ description={
594
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
595
+ ↗6.37%
596
+ </TextLabel2>
597
+ }
598
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
599
+ onClick={isVisible ? () => console.log('clicked') : undefined}
600
+ subtitle={name}
601
+ title="$0.87"
602
+ />
603
+ );
604
+ }
605
+ return (
606
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
607
+ <Carousel
608
+ title="Explore Assets"
609
+ snapMode="item"
610
+ styles={{
611
+ root: { paddingInline: 'var(--space-3)' },
612
+ carousel: { gap: 'var(--space-1)' },
613
+ }}
614
+ >
615
+ {Object.values(assets).map((asset, index) => (
616
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
617
+ {({ isVisible }) => (
618
+ <SquareAssetCard
619
+ isVisible={isVisible}
620
+ imageUrl={asset.imageUrl}
621
+ name={asset.symbol}
622
+ />
623
+ )}
624
+ </CarouselItem>
625
+ ))}
626
+ </Carousel>
627
+ </Box>
628
+ );
629
+ }
630
+ ```
631
+
632
+ ### Accessibility
633
+
634
+ The carousel is accessible by default.
635
+
636
+ You need to use `accessibilityLabel` or `accessibilityLabelledBy` props to provide a label for the carousel items.
637
+
638
+ If you have elements that are focusable, you can use `isVisible` render prop to disable focus when the item is not visible.
639
+
640
+ ```jsx
641
+ <Carousel>
642
+ <CarouselItem id="btc" accessibilityLabel="Bitcoin">
643
+ <SquareAssetCard imageUrl={assets.btc.imageUrl} name={assets.btc.symbol} />
644
+ </CarouselItem>
645
+ <CarouselItem id="recurring-buy" width="100%" accessibilityLabelledBy="recurring-buy-label">
646
+ {({ isVisible }) => (
647
+ <UpsellCard
648
+ action={
649
+ <Button
650
+ compact
651
+ flush="start"
652
+ numberOfLines={1}
653
+ onClick={NoopFn}
654
+ tabIndex={isVisible ? undefined : -1}
655
+ variant="secondary"
656
+ >
657
+ Get started
658
+ </Button>
659
+ }
660
+ description="Want to add funds to your card every week or month?"
661
+ media={
662
+ <Box bottom={6} position="relative" right={24}>
663
+ <Pictogram dimension="64x64" name="recurringPurchases" />
664
+ </Box>
665
+ }
666
+ minWidth="0"
667
+ title={
668
+ <TextHeadline as="h3" id="recurring-buy-label">
669
+ Recurring Buy
670
+ </TextHeadline>
671
+ }
672
+ width="100%"
673
+ />
674
+ )}
675
+ </CarouselItem>
676
+ </Carousel>
677
+ ```
678
+
679
+ ### Customization
680
+
681
+ #### Custom Components
682
+
683
+ You can customize the navigation and pagination components of the carousel using the `NavigationComponent` and `PaginationComponent` props. You can also modify the title by providing a ReactNode for the `title` prop.
684
+
685
+ ```jsx live
686
+ function CustomComponentsCarousel() {
687
+ function SeeAllComponent({ style }) {
688
+ return (
689
+ <TextHeadline style={style}>
690
+ <Link openInNewWindow href="https://coinbase.com/">
691
+ See all
692
+ </Link>
693
+ </TextHeadline>
694
+ );
695
+ }
696
+ function PaginationComponent({ totalPages, activePageIndex, onClickPage, style }) {
697
+ const canGoPrevious = activePageIndex > 0;
698
+ const canGoNext = activePageIndex < totalPages - 1;
699
+ const dotStyles = {
700
+ width: 'var(--space-2)',
701
+ height: 'var(--space-2)',
702
+ borderRadius: 'var(--borderRadius-1000)',
703
+ } as const;
704
+ function onPrevious() {
705
+ onClickPage(activePageIndex - 1);
706
+ }
707
+ function onNext() {
708
+ onClickPage(activePageIndex + 1);
709
+ }
710
+ return (
711
+ <HStack justifyContent="space-between" paddingY={0.5} style={style}>
712
+ <HStack gap={1}>
713
+ <IconButton
714
+ accessibilityLabel="Previous"
715
+ disabled={!canGoPrevious}
716
+ name="caretLeft"
717
+ onClick={onPrevious}
718
+ variant="foregroundMuted"
719
+ />
720
+ <IconButton
721
+ accessibilityLabel="Next"
722
+ disabled={!canGoNext}
723
+ name="caretRight"
724
+ onClick={onNext}
725
+ variant="foregroundMuted"
726
+ />
727
+ </HStack>
728
+ <HStack alignItems="center" gap={1}>
729
+ {Array.from({ length: totalPages }, (_, index) => (
730
+ <Pressable
731
+ key={index}
732
+ accessibilityLabel={`Go to page ${index + 1}`}
733
+ background={index === activePageIndex ? 'bgPrimary' : 'bgSecondary'}
734
+ borderColor={index === activePageIndex ? 'fgPrimary' : 'bgLine'}
735
+ data-testid={`carousel-page-${index}`}
736
+ onClick={() => onClickPage(index)}
737
+ style={dotStyles}
738
+ />
739
+ ))}
740
+ </HStack>
741
+ </HStack>
742
+ );
743
+ }
744
+ function NoopFn() {
745
+ console.log('pressed');
746
+ }
747
+ function ActionButton({ isVisible, children }) {
748
+ return (
749
+ <Button
750
+ compact
751
+ flush="start"
752
+ numberOfLines={1}
753
+ onClick={NoopFn}
754
+ tabIndex={isVisible ? undefined : -1}
755
+ variant="secondary"
756
+ >
757
+ {children}
758
+ </Button>
759
+ );
760
+ }
761
+ const itemWidth = {
762
+ phone: '100%',
763
+ tablet: 'round(down, calc((100% - var(--space-1)) / 2), 1px)',
764
+ desktop: 'round(down, calc((100% - var(--space-1)) / 2), 1px)',
765
+ };
766
+ return (
767
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
768
+ <Carousel
769
+ NavigationComponent={SeeAllComponent}
770
+ PaginationComponent={PaginationComponent}
771
+ styles={{
772
+ root: { paddingInline: 'var(--space-3)' },
773
+ carousel: { gap: 'var(--space-1)' },
774
+ }}
775
+ title={
776
+ <TextHeadline as="h3">
777
+ Learn more
778
+ </TextHeadline>
779
+ }
780
+ >
781
+ <CarouselItem id="recurring-buy" width={itemWidth}>
782
+ {({ isVisible }) => (
783
+ <UpsellCard
784
+ action={<ActionButton isVisible={isVisible}>Get started</ActionButton>}
785
+ description="Want to add funds to your card every week or month?"
786
+ media={
787
+ <Box bottom={6} position="relative" right={24}>
788
+ <Pictogram dimension="64x64" name="recurringPurchases" />
789
+ </Box>
790
+ }
791
+ minWidth="0"
792
+ title="Recurring Buy"
793
+ width="100%"
794
+ />
795
+ )}
796
+ </CarouselItem>
797
+ <CarouselItem id="eths-apr" width={itemWidth}>
798
+ {({ isVisible }) => (
799
+ <UpsellCard
800
+ action={<ActionButton isVisible={isVisible}>Start earning</ActionButton>}
801
+ dangerouslySetBackground="rgb(var(--purple70))"
802
+ description={
803
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
804
+ Earn staking rewards on ETH by holding it on Coinbase
805
+ </TextLabel2>
806
+ }
807
+ media={
808
+ <Box left={16} position="relative" top={12}>
809
+ <RemoteImage height={174} source="/img/feature.png" />
810
+ </Box>
811
+ }
812
+ minWidth="0"
813
+ title={
814
+ <TextHeadline color="fgInverse" as="h3">
815
+ Up to 3.29% APR on ETHs
816
+ </TextHeadline>
817
+ }
818
+ width="100%"
819
+ />
820
+ )}
821
+ </CarouselItem>
822
+ <CarouselItem id="join-the-community" width={itemWidth}>
823
+ {({ isVisible }) => (
824
+ <UpsellCard
825
+ action={<ActionButton isVisible={isVisible}>Start chatting</ActionButton>}
826
+ dangerouslySetBackground="rgb(var(--teal70))"
827
+ description={
828
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
829
+ Chat with other devs in our Discord community
830
+ </TextLabel2>
831
+ }
832
+ media={
833
+ <Box left={16} position="relative" top={4}>
834
+ <RemoteImage height={174} source="/img/community.png" />
835
+ </Box>
836
+ }
837
+ minWidth="0"
838
+ title={
839
+ <TextHeadline color="fgInverse" as="h3">
840
+ Join the community
841
+ </TextHeadline>
842
+ }
843
+ width="100%"
844
+ />
845
+ )}
846
+ </CarouselItem>
847
+ <CarouselItem id="coinbase-one-offer" width={itemWidth}>
848
+ {({ isVisible }) => (
849
+ <UpsellCard
850
+ action={<ActionButton isVisible={isVisible}>Get 60 days free</ActionButton>}
851
+ dangerouslySetBackground="rgb(var(--blue80))"
852
+ description={
853
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
854
+ Use code NOV60 when you sign up for Coinbase One
855
+ </TextLabel2>
856
+ }
857
+ media={
858
+ <Box left={16} position="relative" top={0}>
859
+ <RemoteImage height={174} source="/img/marketing.png" />
860
+ </Box>
861
+ }
862
+ minWidth="0"
863
+ title={
864
+ <TextHeadline color="fgInverse" as="h3">
865
+ Coinbase One offer
866
+ </TextHeadline>
867
+ }
868
+ width="100%"
869
+ />
870
+ )}
871
+ </CarouselItem>
872
+ <CarouselItem id="coinbase-card" width={itemWidth}>
873
+ {({ isVisible }) => (
874
+ <UpsellCard
875
+ action={<ActionButton isVisible={isVisible}>Get started</ActionButton>}
876
+ dangerouslySetBackground="rgb(var(--gray100))"
877
+ description={
878
+ <TextLabel2 as="p" numberOfLines={3} color="fgInverse">
879
+ Spend USDC to get rewards with our Visa® debit card
880
+ </TextLabel2>
881
+ }
882
+ media={
883
+ <Box left={16} position="relative" top={0}>
884
+ <RemoteImage height={174} source="/img/object.png" />
885
+ </Box>
886
+ }
887
+ minWidth="0"
888
+ title={
889
+ <TextHeadline color="fgInverse" as="h3">
890
+ Coinbase Card
891
+ </TextHeadline>
892
+ }
893
+ width="100%"
894
+ />
895
+ )}
896
+ </CarouselItem>
897
+ </Carousel>
898
+ </Box>
899
+ );
900
+ }
901
+ ```
902
+
903
+ #### Custom Styles
904
+
905
+ You can use the `classNames` and `styles` props to customize different parts of the carousel.
906
+
907
+ ```jsx live
908
+ function CustomStylesCarousel() {
909
+ return (
910
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
911
+ <Carousel
912
+ styles={{
913
+ root: { position: 'relative', paddingInline: 'var(--space-6)' },
914
+ carousel: { gap: 'var(--space-6)' },
915
+ }}
916
+ NavigationComponent={({
917
+ className,
918
+ style,
919
+ disableGoNext,
920
+ disableGoPrevious,
921
+ nextPageAccessibilityLabel,
922
+ onGoNext,
923
+ onGoPrevious,
924
+ previousPageAccessibilityLabel,
925
+ }) => {
926
+ return (
927
+ <DefaultCarouselNavigation
928
+ className={className}
929
+ disableGoNext={disableGoNext}
930
+ disableGoPrevious={disableGoPrevious}
931
+ nextPageAccessibilityLabel={nextPageAccessibilityLabel}
932
+ onGoNext={onGoNext}
933
+ onGoPrevious={onGoPrevious}
934
+ previousPageAccessibilityLabel={previousPageAccessibilityLabel}
935
+ style={style}
936
+ styles={{
937
+ previousButton: {
938
+ position: 'absolute',
939
+ top: 'var(--space-8)',
940
+ zIndex: 1,
941
+ left: 'var(--space-0_5)',
942
+ },
943
+ nextButton: {
944
+ position: 'absolute',
945
+ top: 'var(--space-8)',
946
+ zIndex: 1,
947
+ right: 'var(--space-0_5)',
948
+ },
949
+ }}
950
+ />
951
+ );
952
+ }}
953
+ >
954
+ <CarouselItem id="earn-more-crypto" width="100%">
955
+ <NudgeCard
956
+ title="Earn more crypto"
957
+ description="You've got unstaked crypto. Stake it now to earn more."
958
+ pictogram="key"
959
+ action="Start earning"
960
+ onActionPress={() => console.log('Action pressed')}
961
+ width="100%"
962
+ minWidth="0"
963
+ />
964
+ </CarouselItem>
965
+ <CarouselItem id="secure-your-account" width="100%">
966
+ <NudgeCard
967
+ title="Secure your account"
968
+ description="Add two-factor authentication for enhanced security."
969
+ pictogram="shield"
970
+ action="Enable 2FA"
971
+ onActionPress={() => console.log('Enable 2FA pressed')}
972
+ width="100%"
973
+ minWidth="0"
974
+ />
975
+ </CarouselItem>
976
+ <CarouselItem id="complete-your-profile" width="100%">
977
+ <NudgeCard
978
+ title="Complete your profile"
979
+ description="Add more details to personalize your experience."
980
+ pictogram="accountsNavigation"
981
+ action="Update profile"
982
+ onActionPress={() => console.log('Update profile pressed')}
983
+ width="100%"
984
+ minWidth="0"
985
+ />
986
+ </CarouselItem>
987
+ </Carousel>
988
+ </Box>
989
+ );
990
+ }
991
+ ```
992
+
993
+ #### Dynamic Content
994
+
995
+ You can dynamically add and remove items from the carousel.
996
+
997
+ ```jsx live
998
+ function DynamicContentCarousel() {
999
+ const [items, setItems] = useState(Object.values(assets).slice(0, 3));
1000
+
1001
+ function SquareAssetCard({ imageUrl, name }) {
1002
+ return (
1003
+ <ContainedAssetCard
1004
+ description={
1005
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1006
+ ↗6.37%
1007
+ </TextLabel2>
1008
+ }
1009
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1010
+ subtitle={name}
1011
+ title="$0.87"
1012
+ />
1013
+ );
1014
+ }
1015
+ function addAsset() {
1016
+ const randomAsset =
1017
+ Object.values(assets)[Math.floor(Math.random() * Object.values(assets).length)];
1018
+ setItems([...items, { ...randomAsset, symbol: `${randomAsset.symbol}-${items.length}` }]);
1019
+ }
1020
+ return (
1021
+ <VStack gap={2}>
1022
+ <HStack justifyContent="flex-end" gap={2} alignItems="center">
1023
+ <Button compact onClick={addAsset}>
1024
+ Add Asset
1025
+ </Button>
1026
+ <Button compact onClick={() => setItems(items.slice(0, -1))} disabled={items.length === 0}>
1027
+ Remove Last
1028
+ </Button>
1029
+ </HStack>
1030
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1031
+ <Carousel
1032
+ title="Explore Assets"
1033
+ styles={{
1034
+ root: { paddingInline: 'var(--space-3)' },
1035
+ carousel: { gap: 'var(--space-1)', height: '156px' },
1036
+ }}
1037
+ >
1038
+ {items.map((asset, index) => (
1039
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
1040
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
1041
+ </CarouselItem>
1042
+ ))}
1043
+ </Carousel>
1044
+ </Box>
1045
+ </VStack>
1046
+ );
1047
+ }
1048
+ ```
1049
+
1050
+ You can also animate items as they enter or leave the viewport.
1051
+
1052
+ ```jsx live
1053
+ function AnimatedCarousel() {
1054
+ function SquareAssetCard({ imageUrl, name }) {
1055
+ const ref = useRef(null);
1056
+ // useInView is a framer motion hook that detects when an element is in the viewport
1057
+ const isInView = useInView(ref, {
1058
+ amount: 0.5,
1059
+ once: false,
1060
+ });
1061
+
1062
+ return (
1063
+ <motion.div
1064
+ ref={ref}
1065
+ initial={{ scale: 1 }}
1066
+ animate={{
1067
+ scale: isInView ? 1 : 0.8,
1068
+ }}
1069
+ >
1070
+ <ContainedAssetCard
1071
+ description={
1072
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1073
+ ↗6.37%
1074
+ </TextLabel2>
1075
+ }
1076
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1077
+ subtitle={name}
1078
+ title="$0.87"
1079
+ />
1080
+ </motion.div>
1081
+ );
1082
+ }
1083
+ return (
1084
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1085
+ <Carousel
1086
+ title="Explore Assets"
1087
+ styles={{
1088
+ root: { paddingInline: 'var(--space-3)' },
1089
+ carousel: { gap: 'var(--space-1)' },
1090
+ }}
1091
+ snapMode="item"
1092
+ hidePagination
1093
+ >
1094
+ {Object.values(assets).map((asset, index) => (
1095
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
1096
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
1097
+ </CarouselItem>
1098
+ ))}
1099
+ </Carousel>
1100
+ </Box>
1101
+ );
1102
+ }
1103
+ ```
1104
+
1105
+ You can even change the size or content of items. In the example below, click an asset to highlight it.
1106
+
1107
+ ```jsx live
1108
+ function AnimatedSelectionCarousel() {
1109
+ const dimensions = { width: 140, height: 60 };
1110
+ const path = useSparklinePath({ ...dimensions, data: prices });
1111
+
1112
+ const SquareAssetCard = memo(({ isVisible, imageUrl, name, color }) => {
1113
+ const squareSize = 156;
1114
+ const largeSize = 327;
1115
+ const [isHighlighted, setIsHighlighted] = useState(false);
1116
+ const [size, setSize] = useState('s');
1117
+
1118
+ const handleClick = useCallback(() => {
1119
+ setIsHighlighted((highlighted) => !highlighted);
1120
+ }, [setIsHighlighted]);
1121
+
1122
+ const onAnimationStart = useCallback(() => {
1123
+ if (isHighlighted) {
1124
+ setSize('l');
1125
+ }
1126
+ }, [isHighlighted, setSize]);
1127
+ const onAnimationComplete = useCallback(() => {
1128
+ if (!isHighlighted) {
1129
+ setSize('s');
1130
+ }
1131
+ }, [isHighlighted, setSize]);
1132
+
1133
+ return (
1134
+ <Pressable onClick={handleClick} tabIndex={isVisible ? undefined : -1} borderRadius={500}>
1135
+ <motion.div
1136
+ initial={{ width: squareSize }}
1137
+ animate={{
1138
+ width: isHighlighted ? largeSize : squareSize,
1139
+ }}
1140
+ onAnimationStart={onAnimationStart}
1141
+ onAnimationComplete={onAnimationComplete}
1142
+ style={{
1143
+ overflow: 'hidden',
1144
+ borderRadius: 'var(--borderRadius-500)',
1145
+ }}
1146
+ >
1147
+ <ContainedAssetCard
1148
+ description={
1149
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1150
+ ↗6.37%
1151
+ </TextLabel2>
1152
+ }
1153
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1154
+ subtitle={name}
1155
+ title="$0.87"
1156
+ style={{ maxWidth: largeSize, width: '100%' }}
1157
+ size={size}
1158
+ >
1159
+ <VStack padding={1} justifyContent="center" height={squareSize}>
1160
+ <Sparkline {...dimensions} path={path} color={color} />
1161
+ </VStack>
1162
+ </ContainedAssetCard>
1163
+ </motion.div>
1164
+ </Pressable>
1165
+ );
1166
+ });
1167
+ return (
1168
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1169
+ <Carousel
1170
+ title="Explore Assets"
1171
+ styles={{
1172
+ root: { paddingInline: 'var(--space-3)' },
1173
+ carousel: { gap: 'var(--space-1)' },
1174
+ }}
1175
+ >
1176
+ {Object.values(assets).map((asset, index) => (
1177
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
1178
+ {({ isVisible }) => (
1179
+ <SquareAssetCard
1180
+ isVisible={isVisible}
1181
+ imageUrl={asset.imageUrl}
1182
+ name={asset.symbol}
1183
+ color={asset.color}
1184
+ />
1185
+ )}
1186
+ </CarouselItem>
1187
+ ))}
1188
+ </Carousel>
1189
+ </Box>
1190
+ );
1191
+ }
1192
+ ```
1193
+
1194
+ #### Hide Navigation and Pagination
1195
+
1196
+ You can hide the navigation and pagination components of the carousel if desired (using `hideNavigation` and `hidePagination` props).
1197
+
1198
+ Note that this can prevent proper accessibility for the carousel. If hiding pagination, it's recommended instead to
1199
+ pass in `DefaultCarouselNavigation` with `hideUnlessFocused` prop. Alternatively, you can ensure that the carousel is
1200
+ navigable by keyboard through other means.
1201
+
1202
+ ```jsx live
1203
+ function HideNavigationAndPaginationCarousel() {
1204
+ function SquareAssetCard({ imageUrl, name }) {
1205
+ return (
1206
+ <ContainedAssetCard
1207
+ description={
1208
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1209
+ ↗6.37%
1210
+ </TextLabel2>
1211
+ }
1212
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1213
+ subtitle={name}
1214
+ title="$0.87"
1215
+ />
1216
+ );
1217
+ }
1218
+ return (
1219
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1220
+ <Carousel
1221
+ title="Explore Assets"
1222
+ hidePagination
1223
+ NavigationComponent={(props) => <DefaultCarouselNavigation {...props} hideUnlessFocused />}
1224
+ drag="free"
1225
+ snapMode="item"
1226
+ styles={{
1227
+ root: { paddingInline: 'var(--space-3)' },
1228
+ carousel: { gap: 'var(--space-1)' },
1229
+ }}
1230
+ >
1231
+ {Object.values(assets).map((asset, index) => (
1232
+ <CarouselItem key={asset.symbol} id={asset.symbol}>
1233
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
1234
+ </CarouselItem>
1235
+ ))}
1236
+ </Carousel>
1237
+ </Box>
1238
+ );
1239
+ }
1240
+ ```
1241
+
1242
+ #### Animated Pagination
1243
+
1244
+ You can create smooth pagination animations by customizing the pagination dots. This example shows how to create expanding dots that smoothly transition between active and inactive states.
1245
+
1246
+ ```jsx live
1247
+ function AnimatedPaginationCarousel() {
1248
+ const AnimatedPagination = memo((props) => {
1249
+ const { totalPages, activePageIndex, onClickPage, style } = props;
1250
+
1251
+ const dotStyles = {
1252
+ height: 'var(--space-1)',
1253
+ borderRadius: 'var(--borderRadius-1000)',
1254
+ transition: 'all 0.3s ease',
1255
+ cursor: 'pointer',
1256
+ };
1257
+
1258
+ return (
1259
+ <HStack alignItems="center" gap={0.5} justifyContent="center" style={style}>
1260
+ {Array.from({ length: totalPages }, (_, index) => {
1261
+ const isActive = index === activePageIndex;
1262
+ return (
1263
+ <Pressable
1264
+ key={index}
1265
+ accessibilityLabel={`Go to page ${index + 1}`}
1266
+ background={isActive ? 'bgPrimary' : 'bgLine'}
1267
+ borderWidth={0}
1268
+ data-testid={`carousel-page-${index}`}
1269
+ onClick={() => onClickPage?.(index)}
1270
+ style={{
1271
+ ...dotStyles,
1272
+ width: isActive ? 'var(--space-3)' : 'var(--space-1)',
1273
+ }}
1274
+ />
1275
+ );
1276
+ })}
1277
+ </HStack>
1278
+ );
1279
+ });
1280
+
1281
+ function SquareAssetCard({ imageUrl, name }) {
1282
+ return (
1283
+ <ContainedAssetCard
1284
+ description={
1285
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1286
+ ↗6.37%
1287
+ </TextLabel2>
1288
+ }
1289
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1290
+ subtitle={name}
1291
+ title="$0.87"
1292
+ />
1293
+ );
1294
+ }
1295
+
1296
+ return (
1297
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1298
+ <Carousel
1299
+ PaginationComponent={AnimatedPagination}
1300
+ drag="snap"
1301
+ snapMode="page"
1302
+ styles={{
1303
+ root: { paddingInline: 'var(--space-3)' },
1304
+ carousel: { gap: 'var(--space-1)' },
1305
+ }}
1306
+ title="Explore Assets"
1307
+ >
1308
+ {Object.values(assets).map((asset, index) => (
1309
+ <CarouselItem key={asset.symbol} id={asset.symbol} accessibilityLabel={asset.name}>
1310
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
1311
+ </CarouselItem>
1312
+ ))}
1313
+ </Carousel>
1314
+ </Box>
1315
+ );
1316
+ }
1317
+ ```
1318
+
1319
+ ### Imperative API
1320
+
1321
+ You can control the carousel programmatically using a ref. The carousel exposes methods to navigate to specific pages and access the current page index.
1322
+
1323
+ ```jsx live
1324
+ function ImperativeApiCarousel() {
1325
+ const carouselRef = useRef(null);
1326
+ const [currentPageInfo, setCurrentPageInfo] = useState('Page 1');
1327
+
1328
+ function handleGoToPage(pageIndex: number) {
1329
+ if (carouselRef.current) {
1330
+ const clampedPageIndex = Math.max(0, Math.min(carouselRef.current.totalPages - 1, pageIndex));
1331
+ carouselRef.current.goToPage(clampedPageIndex);
1332
+ setCurrentPageInfo(`Page ${clampedPageIndex + 1}`);
1333
+ }
1334
+ }
1335
+
1336
+ function handleGoToFirstPage() {
1337
+ handleGoToPage(0);
1338
+ }
1339
+
1340
+ function handleGoToLastPage() {
1341
+ if (carouselRef.current) {
1342
+ handleGoToPage(carouselRef.current.totalPages - 1);
1343
+ }
1344
+ }
1345
+
1346
+ function handleGoToPrevPage() {
1347
+ if (carouselRef.current) {
1348
+ handleGoToPage(carouselRef.current.activePageIndex - 1);
1349
+ }
1350
+ }
1351
+
1352
+ function handleGoToNextPage() {
1353
+ if (carouselRef.current) {
1354
+ handleGoToPage(carouselRef.current.activePageIndex + 1);
1355
+ }
1356
+ }
1357
+
1358
+ function SquareAssetCard({ imageUrl, name }) {
1359
+ return (
1360
+ <ContainedAssetCard
1361
+ description={
1362
+ <TextLabel2 as="p" color="fgPositive" numberOfLines={2}>
1363
+ ↗6.37%
1364
+ </TextLabel2>
1365
+ }
1366
+ header={<RemoteImage height="32px" source={imageUrl} width="32px" />}
1367
+ subtitle={name}
1368
+ title="$0.87"
1369
+ />
1370
+ );
1371
+ }
1372
+
1373
+ return (
1374
+ <VStack gap={2}>
1375
+ <HStack gap={2} style={{ flexWrap: 'wrap' }} justifyContent="space-between">
1376
+ <HStack gap={1}>
1377
+ <IconButton
1378
+ onClick={handleGoToFirstPage}
1379
+ variant="secondary"
1380
+ name="doubleChevronRight"
1381
+ style={{ transform: 'rotate(180deg)' }}
1382
+ accessibilityLabel="Go to first page"
1383
+ />
1384
+ <IconButton
1385
+ onClick={handleGoToPrevPage}
1386
+ variant="secondary"
1387
+ name="arrowLeft"
1388
+ active
1389
+ accessibilityLabel="Go to previous page"
1390
+ />
1391
+ </HStack>
1392
+ <Box
1393
+ alignItems="center"
1394
+ background="bgSecondary"
1395
+ borderRadius={500}
1396
+ flexGrow={1}
1397
+ justifyContent="center"
1398
+ paddingX={2}
1399
+ paddingY={1}
1400
+ >
1401
+ <Text color="fgMuted" font="label1">
1402
+ {currentPageInfo}
1403
+ </Text>
1404
+ </Box>
1405
+ <HStack gap={1}>
1406
+ <IconButton
1407
+ onClick={handleGoToNextPage}
1408
+ variant="secondary"
1409
+ name="arrowRight"
1410
+ active
1411
+ accessibilityLabel="Go to next page"
1412
+ />
1413
+ <IconButton
1414
+ onClick={handleGoToLastPage}
1415
+ variant="secondary"
1416
+ name="doubleChevronRight"
1417
+ accessibilityLabel="Go to last page"
1418
+ />
1419
+ </HStack>
1420
+ </HStack>
1421
+ <Box style={{ marginInline: 'calc(-1 * var(--space-3))' }}>
1422
+ <Carousel
1423
+ ref={carouselRef}
1424
+ hidePagination
1425
+ hideNavigation
1426
+ drag="none"
1427
+ snapMode="item"
1428
+ styles={{
1429
+ root: { paddingInline: 'var(--space-3)' },
1430
+ carousel: { gap: 'var(--space-1)' },
1431
+ }}
1432
+ title="Explore Assets"
1433
+ >
1434
+ {Object.values(assets).map((asset, index) => (
1435
+ <CarouselItem key={asset.symbol} id={asset.symbol} accessibilityLabel={asset.name}>
1436
+ <SquareAssetCard imageUrl={asset.imageUrl} name={asset.symbol} />
1437
+ </CarouselItem>
1438
+ ))}
1439
+ </Carousel>
1440
+ </Box>
1441
+ </VStack>
1442
+ );
1443
+ }
1444
+ ```
1445
+
1446
+ ### Callbacks
1447
+
1448
+ You can use the `onChangePage`, `onDragStart`, and `onDragEnd` callbacks to listen for user interaction in the carousel.
1449
+
1450
+ ```tsx
1451
+ <Carousel
1452
+ onChangePage={(pageIndex: number) => console.log('Page changed', activePageIndex)}
1453
+ onDragStart={() => console.log('Drag started')}
1454
+ onDragEnd={() => console.log('Drag ended')}
1455
+ >
1456
+ ...
1457
+ </Carousel>
1458
+ ```
1459
+
1460
+ ## Props
1461
+
1462
+ | Prop | Type | Required | Default | Description |
1463
+ | --- | --- | --- | --- | --- |
1464
+ | `NavigationComponent` | `CarouselNavigationComponent` | No | `DefaultCarouselNavigation` | Custom component to render navigation arrows. |
1465
+ | `PaginationComponent` | `CarouselPaginationComponent` | No | `DefaultCarouselPagination` | Custom component to render pagination indicators. |
1466
+ | `alignContent` | `ResponsiveProp<center \| normal \| start \| end \| flex-start \| flex-end \| stretch \| baseline \| first baseline \| last baseline \| space-between \| space-around \| space-evenly>` | No | `-` | - |
1467
+ | `alignItems` | `ResponsiveProp<center \| normal \| start \| end \| flex-start \| flex-end \| self-start \| self-end \| stretch \| baseline \| first baseline \| last baseline>` | No | `-` | - |
1468
+ | `alignSelf` | `ResponsiveProp<center \| normal \| auto \| start \| end \| flex-start \| flex-end \| self-start \| self-end \| stretch \| baseline \| first baseline \| last baseline>` | No | `-` | - |
1469
+ | `as` | `div` | No | `-` | - |
1470
+ | `aspectRatio` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1471
+ | `background` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
1472
+ | `borderBottomLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
1473
+ | `borderBottomRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
1474
+ | `borderBottomWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
1475
+ | `borderColor` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
1476
+ | `borderEndWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
1477
+ | `borderRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
1478
+ | `borderStartWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
1479
+ | `borderTopLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
1480
+ | `borderTopRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
1481
+ | `borderTopWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
1482
+ | `borderWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
1483
+ | `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. |
1484
+ | `borderedBottom` | `boolean` | No | `-` | Add a border to the bottom side of the box. |
1485
+ | `borderedEnd` | `boolean` | No | `-` | Add a border to the trailing side of the box. |
1486
+ | `borderedHorizontal` | `boolean` | No | `-` | Add a border to the leading and trailing sides of the box. |
1487
+ | `borderedStart` | `boolean` | No | `-` | Add a border to the leading side of the box. |
1488
+ | `borderedTop` | `boolean` | No | `-` | Add a border to the top side of the box. |
1489
+ | `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. |
1490
+ | `bottom` | `ResponsiveProp<Bottom<string \| number>>` | No | `-` | - |
1491
+ | `classNames` | `{ root?: string; title?: string \| undefined; navigation?: string \| undefined; pagination?: string \| undefined; carousel?: string \| undefined; carouselContainer?: string \| undefined; } \| undefined` | No | `-` | Custom class names for the component. |
1492
+ | `color` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
1493
+ | `columnGap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1494
+ | `dangerouslySetBackground` | `string` | No | `-` | - |
1495
+ | `display` | `ResponsiveProp<grid \| revert \| none \| block \| inline \| inline-block \| flex \| inline-flex \| inline-grid \| contents \| flow-root \| list-item>` | No | `-` | - |
1496
+ | `drag` | `none \| free \| snap` | No | `'snap'` | Defines the drag interaction behavior for the carousel. none disables dragging completely. free enables free-form dragging with natural deceleration when released. snap enables dragging with automatic snapping to targets when released, defined by snapMode. |
1497
+ | `elevation` | `0 \| 1 \| 2` | No | `-` | - |
1498
+ | `flexBasis` | `ResponsiveProp<FlexBasis<string \| number>>` | No | `-` | - |
1499
+ | `flexDirection` | `ResponsiveProp<column \| row \| row-reverse \| column-reverse>` | No | `-` | - |
1500
+ | `flexGrow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
1501
+ | `flexShrink` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
1502
+ | `flexWrap` | `ResponsiveProp<nowrap \| wrap \| wrap-reverse>` | No | `-` | - |
1503
+ | `font` | `ResponsiveProp<FontFamily \| inherit>` | No | `-` | - |
1504
+ | `fontFamily` | `ResponsiveProp<FontFamily \| inherit>` | No | `-` | - |
1505
+ | `fontSize` | `ResponsiveProp<FontSize \| inherit>` | No | `-` | - |
1506
+ | `fontWeight` | `ResponsiveProp<FontWeight \| inherit>` | No | `-` | - |
1507
+ | `gap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1508
+ | `grid` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
1509
+ | `gridArea` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1510
+ | `gridAutoColumns` | `ResponsiveProp<GridAutoColumns<string \| number>>` | No | `-` | - |
1511
+ | `gridAutoFlow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| row \| column \| dense` | No | `-` | - |
1512
+ | `gridAutoRows` | `ResponsiveProp<GridAutoRows<string \| number>>` | No | `-` | - |
1513
+ | `gridColumn` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1514
+ | `gridColumnEnd` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1515
+ | `gridColumnStart` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1516
+ | `gridRow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1517
+ | `gridRowEnd` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1518
+ | `gridRowStart` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1519
+ | `gridTemplate` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
1520
+ | `gridTemplateAreas` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
1521
+ | `gridTemplateColumns` | `ResponsiveProp<GridTemplateColumns<string \| number>>` | No | `-` | - |
1522
+ | `gridTemplateRows` | `ResponsiveProp<GridTemplateRows<string \| number>>` | No | `-` | - |
1523
+ | `height` | `ResponsiveProp<Height<string \| number>>` | No | `-` | - |
1524
+ | `hideNavigation` | `boolean` | No | `-` | Hides the navigation arrows (previous/next buttons). |
1525
+ | `hidePagination` | `boolean` | No | `-` | Hides the pagination indicators (dots/bars showing current page). |
1526
+ | `justifyContent` | `ResponsiveProp<left \| right \| center \| normal \| start \| end \| flex-start \| flex-end \| stretch \| space-between \| space-around \| space-evenly>` | No | `-` | - |
1527
+ | `key` | `Key \| null` | No | `-` | - |
1528
+ | `left` | `ResponsiveProp<Left<string \| number>>` | No | `-` | - |
1529
+ | `lineHeight` | `ResponsiveProp<LineHeight \| inherit>` | No | `-` | - |
1530
+ | `margin` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1531
+ | `marginBottom` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1532
+ | `marginEnd` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1533
+ | `marginStart` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1534
+ | `marginTop` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1535
+ | `marginX` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1536
+ | `marginY` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
1537
+ | `maxHeight` | `ResponsiveProp<MaxHeight<string \| number>>` | No | `-` | - |
1538
+ | `maxWidth` | `ResponsiveProp<MaxWidth<string \| number>>` | No | `-` | - |
1539
+ | `minHeight` | `ResponsiveProp<MinHeight<string \| number>>` | No | `-` | - |
1540
+ | `minWidth` | `ResponsiveProp<MinWidth<string \| number>>` | No | `-` | - |
1541
+ | `nextPageAccessibilityLabel` | `string` | No | `-` | Accessibility label for the next page button. |
1542
+ | `onChange` | `FormEventHandler<HTMLDivElement>` | No | `-` | - |
1543
+ | `onChangePage` | `((activePageIndex: number) => void)` | No | `-` | Callback fired when the carousel page changes. |
1544
+ | `opacity` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
1545
+ | `overflow` | `ResponsiveProp<hidden \| auto \| visible \| clip \| scroll>` | No | `-` | - |
1546
+ | `padding` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1547
+ | `paddingBottom` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1548
+ | `paddingEnd` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1549
+ | `paddingStart` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1550
+ | `paddingTop` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1551
+ | `paddingX` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1552
+ | `paddingY` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1553
+ | `paginationAccessibilityLabel` | `string \| ((pageIndex: number) => string)` | No | `-` | Accessibility label for the go to page button. |
1554
+ | `pin` | `top \| bottom \| left \| right \| all` | No | `-` | Direction in which to absolutely pin the box. |
1555
+ | `position` | `ResponsiveProp<fixed \| static \| relative \| absolute \| sticky>` | No | `-` | - |
1556
+ | `previousPageAccessibilityLabel` | `string` | No | `-` | Accessibility label for the previous page button. |
1557
+ | `ref` | `((instance: CarouselImperativeHandle \| null) => void) \| RefObject<CarouselImperativeHandle> \| null` | No | `-` | - |
1558
+ | `right` | `ResponsiveProp<Right<string \| number>>` | No | `-` | - |
1559
+ | `rowGap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
1560
+ | `snapMode` | `page \| item` | No | `'page'` | Specifies the pagination and navigation strategy for the carousel. item treats each item as a separate page for navigation, pagination, and snapping. page groups items into pages based on visible area for navigation, pagination, and snapping. This affects page calculation, navigation button behavior, and snap targets when dragging. |
1561
+ | `style` | `CSSProperties` | No | `-` | Custom styles for the root element. |
1562
+ | `styles` | `{ root?: CSSProperties; title?: CSSProperties \| undefined; navigation?: CSSProperties \| undefined; pagination?: CSSProperties \| undefined; carousel?: CSSProperties \| undefined; carouselContainer?: CSSProperties \| undefined; } \| undefined` | No | `-` | Custom styles for the component. |
1563
+ | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID |
1564
+ | `textAlign` | `ResponsiveProp<center \| start \| end \| justify>` | No | `-` | - |
1565
+ | `textDecoration` | `ResponsiveProp<none \| underline \| overline \| line-through \| underline overline \| underline double>` | No | `-` | - |
1566
+ | `textTransform` | `ResponsiveProp<capitalize \| lowercase \| none \| uppercase>` | No | `-` | - |
1567
+ | `title` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Title to display above the carousel. When a string is provided, it will be rendered with default title styling. When a React element is provided, it completely replaces the default title component and styling. |
1568
+ | `top` | `ResponsiveProp<Top<string \| number>>` | No | `-` | - |
1569
+ | `transform` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
1570
+ | `userSelect` | `ResponsiveProp<text \| none \| auto \| all>` | No | `-` | - |
1571
+ | `visibility` | `ResponsiveProp<hidden \| visible>` | No | `-` | - |
1572
+ | `width` | `ResponsiveProp<Width<string \| number>>` | No | `-` | - |
1573
+ | `zIndex` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
1574
+
1575
+