@coinbase/cds-mobile 8.61.0 → 8.62.0

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 (236) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dts/accordion/Accordion.d.ts +1 -9
  3. package/dts/accordion/Accordion.d.ts.map +1 -1
  4. package/dts/alpha/combobox/Combobox.d.ts.map +1 -1
  5. package/dts/alpha/select/Select.d.ts.map +1 -1
  6. package/dts/alpha/select-chip/SelectChip.d.ts.map +1 -1
  7. package/dts/alpha/tabbed-chips/TabbedChips.d.ts.map +1 -1
  8. package/dts/banner/Banner.d.ts.map +1 -1
  9. package/dts/buttons/AvatarButton.d.ts +6 -3
  10. package/dts/buttons/AvatarButton.d.ts.map +1 -1
  11. package/dts/buttons/Button.d.ts.map +1 -1
  12. package/dts/buttons/ButtonGroup.d.ts +3 -1
  13. package/dts/buttons/ButtonGroup.d.ts.map +1 -1
  14. package/dts/buttons/IconButton.d.ts.map +1 -1
  15. package/dts/buttons/IconCounterButton.d.ts.map +1 -1
  16. package/dts/buttons/SlideButton.d.ts.map +1 -1
  17. package/dts/cards/LikeButton.d.ts.map +1 -1
  18. package/dts/carousel/Carousel.d.ts.map +1 -1
  19. package/dts/cells/Cell.d.ts.map +1 -1
  20. package/dts/cells/ListCell.d.ts.map +1 -1
  21. package/dts/cells/ListCellFallback.d.ts.map +1 -1
  22. package/dts/chips/Chip.d.ts.map +1 -1
  23. package/dts/chips/ChipProps.d.ts +10 -8
  24. package/dts/chips/ChipProps.d.ts.map +1 -1
  25. package/dts/chips/InputChip.d.ts.map +1 -1
  26. package/dts/chips/MediaChip.d.ts +3 -2
  27. package/dts/chips/MediaChip.d.ts.map +1 -1
  28. package/dts/chips/TabbedChips.d.ts.map +1 -1
  29. package/dts/coachmark/Coachmark.d.ts.map +1 -1
  30. package/dts/collapsible/Collapsible.d.ts.map +1 -1
  31. package/dts/controls/Checkbox.d.ts.map +1 -1
  32. package/dts/controls/CheckboxCell.d.ts.map +1 -1
  33. package/dts/controls/Control.d.ts.map +1 -1
  34. package/dts/controls/ControlGroup.d.ts +13 -4
  35. package/dts/controls/ControlGroup.d.ts.map +1 -1
  36. package/dts/controls/InputStack.d.ts.map +1 -1
  37. package/dts/controls/Radio.d.ts.map +1 -1
  38. package/dts/controls/RadioCell.d.ts.map +1 -1
  39. package/dts/controls/SearchInput.d.ts.map +1 -1
  40. package/dts/controls/SelectOption.d.ts +3 -1
  41. package/dts/controls/SelectOption.d.ts.map +1 -1
  42. package/dts/controls/Switch.d.ts.map +1 -1
  43. package/dts/controls/TextInput.d.ts.map +1 -1
  44. package/dts/core/componentConfig.d.ts +166 -0
  45. package/dts/core/componentConfig.d.ts.map +1 -0
  46. package/dts/dates/Calendar.d.ts.map +1 -1
  47. package/dts/dates/DateInput.d.ts.map +1 -1
  48. package/dts/dates/DatePicker.d.ts.map +1 -1
  49. package/dts/dots/DotCount.d.ts +1 -11
  50. package/dts/dots/DotCount.d.ts.map +1 -1
  51. package/dts/dots/DotStatusColor.d.ts +1 -8
  52. package/dts/dots/DotStatusColor.d.ts.map +1 -1
  53. package/dts/dots/DotSymbol.d.ts +1 -17
  54. package/dts/dots/DotSymbol.d.ts.map +1 -1
  55. package/dts/hooks/useComponentConfig.d.ts +22 -0
  56. package/dts/hooks/useComponentConfig.d.ts.map +1 -0
  57. package/dts/icons/Icon.d.ts +11 -1
  58. package/dts/icons/Icon.d.ts.map +1 -1
  59. package/dts/index.d.ts +2 -0
  60. package/dts/index.d.ts.map +1 -1
  61. package/dts/layout/Divider.d.ts +3 -1
  62. package/dts/layout/Divider.d.ts.map +1 -1
  63. package/dts/layout/Fallback.d.ts +3 -1
  64. package/dts/layout/Fallback.d.ts.map +1 -1
  65. package/dts/media/Avatar.d.ts +1 -14
  66. package/dts/media/Avatar.d.ts.map +1 -1
  67. package/dts/media/RemoteImage.d.ts.map +1 -1
  68. package/dts/media/RemoteImageGroup.d.ts +3 -10
  69. package/dts/media/RemoteImageGroup.d.ts.map +1 -1
  70. package/dts/navigation/BrowserBar.d.ts +5 -14
  71. package/dts/navigation/BrowserBar.d.ts.map +1 -1
  72. package/dts/navigation/NavigationTitle.d.ts +4 -7
  73. package/dts/navigation/NavigationTitle.d.ts.map +1 -1
  74. package/dts/navigation/NavigationTitleSelect.d.ts +5 -11
  75. package/dts/navigation/NavigationTitleSelect.d.ts.map +1 -1
  76. package/dts/navigation/TopNavBar.d.ts +3 -13
  77. package/dts/navigation/TopNavBar.d.ts.map +1 -1
  78. package/dts/numbers/RollingNumber/RollingNumber.d.ts.map +1 -1
  79. package/dts/numpad/Numpad.d.ts +41 -38
  80. package/dts/numpad/Numpad.d.ts.map +1 -1
  81. package/dts/overlays/Alert.d.ts.map +1 -1
  82. package/dts/overlays/Toast.d.ts.map +1 -1
  83. package/dts/overlays/drawer/Drawer.d.ts.map +1 -1
  84. package/dts/overlays/modal/Modal.d.ts.map +1 -1
  85. package/dts/overlays/modal/ModalBody.d.ts.map +1 -1
  86. package/dts/overlays/modal/ModalFooter.d.ts +3 -8
  87. package/dts/overlays/modal/ModalFooter.d.ts.map +1 -1
  88. package/dts/overlays/modal/ModalHeader.d.ts.map +1 -1
  89. package/dts/overlays/overlay/Overlay.d.ts +7 -3
  90. package/dts/overlays/overlay/Overlay.d.ts.map +1 -1
  91. package/dts/overlays/tooltip/Tooltip.d.ts +2 -20
  92. package/dts/overlays/tooltip/Tooltip.d.ts.map +1 -1
  93. package/dts/overlays/tray/Tray.d.ts.map +1 -1
  94. package/dts/page/PageFooter.d.ts.map +1 -1
  95. package/dts/page/PageHeader.d.ts.map +1 -1
  96. package/dts/perf/component-config/Button.component-config.perf-test.d.ts +2 -0
  97. package/dts/perf/component-config/Button.component-config.perf-test.d.ts.map +1 -0
  98. package/dts/perf/component-config/ComponentConfigProvider.perf-test.d.ts +2 -0
  99. package/dts/perf/component-config/ComponentConfigProvider.perf-test.d.ts.map +1 -0
  100. package/dts/perf/component-config/ComponentConfigStickerSheet.perf-test.d.ts +2 -0
  101. package/dts/perf/component-config/ComponentConfigStickerSheet.perf-test.d.ts.map +1 -0
  102. package/dts/stepper/Stepper.d.ts.map +1 -1
  103. package/dts/system/ComponentConfigProvider.d.ts +26 -0
  104. package/dts/system/ComponentConfigProvider.d.ts.map +1 -0
  105. package/dts/system/index.d.ts +1 -0
  106. package/dts/system/index.d.ts.map +1 -1
  107. package/dts/tabs/SegmentedTab.d.ts +6 -3
  108. package/dts/tabs/SegmentedTab.d.ts.map +1 -1
  109. package/dts/tabs/SegmentedTabs.d.ts +6 -3
  110. package/dts/tabs/SegmentedTabs.d.ts.map +1 -1
  111. package/dts/tabs/Tabs.d.ts +25 -24
  112. package/dts/tabs/Tabs.d.ts.map +1 -1
  113. package/dts/tag/Tag.d.ts.map +1 -1
  114. package/dts/tour/Tour.d.ts +42 -41
  115. package/dts/tour/Tour.d.ts.map +1 -1
  116. package/dts/typography/Link.d.ts +1 -14
  117. package/dts/typography/Link.d.ts.map +1 -1
  118. package/dts/utils/mergeComponentProps.d.ts +34 -0
  119. package/dts/utils/mergeComponentProps.d.ts.map +1 -0
  120. package/dts/visualizations/ProgressBar.d.ts.map +1 -1
  121. package/dts/visualizations/ProgressBarWithFixedLabels.d.ts +5 -3
  122. package/dts/visualizations/ProgressBarWithFixedLabels.d.ts.map +1 -1
  123. package/dts/visualizations/ProgressBarWithFloatLabel.d.ts +3 -1
  124. package/dts/visualizations/ProgressBarWithFloatLabel.d.ts.map +1 -1
  125. package/dts/visualizations/ProgressCircle.d.ts.map +1 -1
  126. package/esm/accordion/Accordion.js +5 -3
  127. package/esm/alpha/combobox/Combobox.js +8 -6
  128. package/esm/alpha/select/Select.js +6 -4
  129. package/esm/alpha/select-chip/SelectChip.js +6 -4
  130. package/esm/alpha/tabbed-chips/TabbedChips.js +6 -4
  131. package/esm/banner/Banner.js +6 -4
  132. package/esm/buttons/AvatarButton.js +6 -4
  133. package/esm/buttons/Button.js +6 -4
  134. package/esm/buttons/ButtonGroup.js +5 -3
  135. package/esm/buttons/IconButton.js +6 -4
  136. package/esm/buttons/IconCounterButton.js +6 -4
  137. package/esm/buttons/SlideButton.js +10 -8
  138. package/esm/cards/LikeButton.js +6 -4
  139. package/esm/carousel/Carousel.js +10 -8
  140. package/esm/cells/Cell.js +6 -4
  141. package/esm/cells/ListCell.js +6 -4
  142. package/esm/cells/ListCellFallback.js +6 -4
  143. package/esm/chips/Chip.js +6 -4
  144. package/esm/chips/InputChip.js +6 -4
  145. package/esm/chips/MediaChip.js +6 -4
  146. package/esm/chips/TabbedChips.js +6 -4
  147. package/esm/coachmark/Coachmark.js +6 -4
  148. package/esm/collapsible/Collapsible.js +10 -8
  149. package/esm/controls/Checkbox.js +6 -4
  150. package/esm/controls/CheckboxCell.js +6 -4
  151. package/esm/controls/Control.js +8 -6
  152. package/esm/controls/ControlGroup.js +6 -4
  153. package/esm/controls/InputStack.js +6 -4
  154. package/esm/controls/Radio.js +6 -4
  155. package/esm/controls/RadioCell.js +6 -4
  156. package/esm/controls/SearchInput.js +6 -4
  157. package/esm/controls/SelectOption.js +6 -4
  158. package/esm/controls/Switch.js +6 -4
  159. package/esm/controls/TextInput.js +6 -4
  160. package/esm/core/componentConfig.js +1 -0
  161. package/esm/dates/Calendar.js +8 -6
  162. package/esm/dates/DateInput.js +6 -4
  163. package/esm/dates/DatePicker.js +9 -6
  164. package/esm/dots/DotCount.js +6 -4
  165. package/esm/dots/DotStatusColor.js +6 -4
  166. package/esm/dots/DotSymbol.js +6 -4
  167. package/esm/hooks/useComponentConfig.js +27 -0
  168. package/esm/icons/Icon.js +10 -8
  169. package/esm/index.js +2 -0
  170. package/esm/layout/Divider.js +6 -4
  171. package/esm/layout/Fallback.js +6 -4
  172. package/esm/media/Avatar.js +6 -4
  173. package/esm/media/RemoteImage.js +6 -4
  174. package/esm/media/RemoteImageGroup.js +6 -4
  175. package/esm/navigation/BrowserBar.js +6 -4
  176. package/esm/navigation/NavigationTitle.js +6 -4
  177. package/esm/navigation/NavigationTitleSelect.js +8 -6
  178. package/esm/navigation/TopNavBar.js +5 -3
  179. package/esm/numbers/RollingNumber/RollingNumber.js +6 -4
  180. package/esm/numpad/Numpad.js +8 -6
  181. package/esm/overlays/Alert.js +7 -5
  182. package/esm/overlays/Toast.js +10 -8
  183. package/esm/overlays/drawer/Drawer.js +12 -10
  184. package/esm/overlays/modal/Modal.js +4 -1
  185. package/esm/overlays/modal/ModalBody.js +8 -6
  186. package/esm/overlays/modal/ModalFooter.js +8 -6
  187. package/esm/overlays/modal/ModalHeader.js +6 -4
  188. package/esm/overlays/overlay/Overlay.js +6 -4
  189. package/esm/overlays/tooltip/Tooltip.js +5 -3
  190. package/esm/overlays/tray/Tray.js +13 -11
  191. package/esm/page/PageFooter.js +6 -4
  192. package/esm/page/PageHeader.js +6 -4
  193. package/esm/perf/component-config/Button.component-config.perf-test.js +35 -0
  194. package/esm/perf/component-config/ComponentConfigProvider.perf-test.js +147 -0
  195. package/esm/perf/component-config/ComponentConfigStickerSheet.perf-test.js +326 -0
  196. package/esm/perf/component-config/README.md +8 -0
  197. package/esm/stepper/Stepper.js +11 -9
  198. package/esm/system/ComponentConfigProvider.js +39 -0
  199. package/esm/system/__stories__/ComponentConfigProvider.stories.js +12 -0
  200. package/esm/system/__stories__/ComponentConfigProviderCustom.stories.js +22 -0
  201. package/esm/system/__stories__/componentConfigStickerSheet/Container.js +27 -0
  202. package/esm/system/__stories__/componentConfigStickerSheet/StickerSheet.js +94 -0
  203. package/esm/system/__stories__/componentConfigStickerSheet/customComponentConfig.js +116 -0
  204. package/esm/system/__stories__/componentConfigStickerSheet/customTheme.js +520 -0
  205. package/esm/system/__stories__/componentConfigStickerSheet/examples/Accordion.js +67 -0
  206. package/esm/system/__stories__/componentConfigStickerSheet/examples/Avatar.js +48 -0
  207. package/esm/system/__stories__/componentConfigStickerSheet/examples/Banner.js +43 -0
  208. package/esm/system/__stories__/componentConfigStickerSheet/examples/Button.js +77 -0
  209. package/esm/system/__stories__/componentConfigStickerSheet/examples/Coachmark.js +15 -0
  210. package/esm/system/__stories__/componentConfigStickerSheet/examples/Controls.js +29 -0
  211. package/esm/system/__stories__/componentConfigStickerSheet/examples/DatePicker.js +15 -0
  212. package/esm/system/__stories__/componentConfigStickerSheet/examples/DotCount.js +28 -0
  213. package/esm/system/__stories__/componentConfigStickerSheet/examples/Icon.js +57 -0
  214. package/esm/system/__stories__/componentConfigStickerSheet/examples/InputChip.js +17 -0
  215. package/esm/system/__stories__/componentConfigStickerSheet/examples/ListCell.js +48 -0
  216. package/esm/system/__stories__/componentConfigStickerSheet/examples/Search.js +20 -0
  217. package/esm/system/__stories__/componentConfigStickerSheet/examples/SegmentedTabs.js +12 -0
  218. package/esm/system/__stories__/componentConfigStickerSheet/examples/Select.js +24 -0
  219. package/esm/system/__stories__/componentConfigStickerSheet/examples/SelectChip.js +22 -0
  220. package/esm/system/__stories__/componentConfigStickerSheet/examples/Tag.js +35 -0
  221. package/esm/system/__stories__/componentConfigStickerSheet/examples/TextInput.js +46 -0
  222. package/esm/system/__stories__/componentConfigStickerSheet/examples/constants.js +33 -0
  223. package/esm/system/__stories__/componentConfigStickerSheet/themeVars.js +2 -0
  224. package/esm/system/index.js +1 -0
  225. package/esm/tabs/SegmentedTab.js +7 -5
  226. package/esm/tabs/SegmentedTabs.js +9 -4
  227. package/esm/tabs/Tabs.js +12 -10
  228. package/esm/tag/Tag.js +6 -4
  229. package/esm/tour/Tour.js +5 -3
  230. package/esm/typography/Link.js +6 -4
  231. package/esm/utils/mergeComponentProps.js +35 -0
  232. package/esm/visualizations/ProgressBar.js +7 -5
  233. package/esm/visualizations/ProgressBarWithFixedLabels.js +5 -3
  234. package/esm/visualizations/ProgressBarWithFloatLabel.js +5 -3
  235. package/esm/visualizations/ProgressCircle.js +7 -5
  236. package/package.json +4 -3
@@ -3,13 +3,15 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
4
  import React, { forwardRef, memo } from 'react';
5
5
  import { pageFooterHeight } from '@coinbase/cds-common/tokens/page';
6
+ import { useComponentConfig } from '../hooks/useComponentConfig';
6
7
  import { Box } from '../layout/Box';
7
8
  import { jsx as _jsx } from "react/jsx-runtime";
8
- export const PageFooter = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(function PageFooter(_ref, ref) {
9
- let {
9
+ export const PageFooter = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
10
+ const mergedProps = useComponentConfig('PageFooter', _props);
11
+ const {
10
12
  action
11
- } = _ref,
12
- props = _objectWithoutPropertiesLoose(_ref, _excluded);
13
+ } = mergedProps,
14
+ props = _objectWithoutPropertiesLoose(mergedProps, _excluded);
13
15
  return /*#__PURE__*/_jsx(Box, _extends({
14
16
  ref: ref,
15
17
  accessibilityRole: "toolbar",
@@ -3,20 +3,22 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
4
  import React, { forwardRef, memo, useMemo } from 'react';
5
5
  import { pageHeaderHeight } from '@coinbase/cds-common/tokens/page';
6
+ import { useComponentConfig } from '../hooks/useComponentConfig';
6
7
  import { Box } from '../layout/Box';
7
8
  import { HStack } from '../layout/HStack';
8
9
  import { VStack } from '../layout/VStack';
9
10
  import { Text } from '../typography/Text';
10
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- export const PageHeader = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(function PageHeader(_ref, ref) {
12
- let {
12
+ export const PageHeader = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
13
+ const mergedProps = useComponentConfig('PageHeader', _props);
14
+ const {
13
15
  start,
14
16
  title,
15
17
  end,
16
18
  styles,
17
19
  style
18
- } = _ref,
19
- props = _objectWithoutPropertiesLoose(_ref, _excluded);
20
+ } = mergedProps,
21
+ props = _objectWithoutPropertiesLoose(mergedProps, _excluded);
20
22
  const isMultiRow = useMemo(() => Boolean(start && title && end), [start, end, title]);
21
23
  return /*#__PURE__*/_jsxs(VStack, _extends({
22
24
  ref: ref,
@@ -0,0 +1,35 @@
1
+ import { measurePerformance } from 'reassure';
2
+ import { Button } from '../../buttons/Button';
3
+ import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
4
+ import { DefaultThemeProvider } from '../../utils/testHelpers';
5
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
6
+ const buttonCount = 1000;
7
+ const ButtonList = () => {
8
+ return /*#__PURE__*/_jsx(_Fragment, {
9
+ children: Array.from({
10
+ length: buttonCount
11
+ }, (_, index) => /*#__PURE__*/_jsx(Button, {
12
+ children: "Child"
13
+ }, index))
14
+ });
15
+ };
16
+ describe('Button component-config performance (mobile)', () => {
17
+ jest.setTimeout(20000);
18
+ it('no provider', async () => {
19
+ await measurePerformance(/*#__PURE__*/_jsx(DefaultThemeProvider, {
20
+ children: /*#__PURE__*/_jsx(ButtonList, {})
21
+ }));
22
+ });
23
+ it('provider customization', async () => {
24
+ await measurePerformance(/*#__PURE__*/_jsx(DefaultThemeProvider, {
25
+ children: /*#__PURE__*/_jsx(ComponentConfigProvider, {
26
+ value: {
27
+ Button: {
28
+ compact: true
29
+ }
30
+ },
31
+ children: /*#__PURE__*/_jsx(ButtonList, {})
32
+ })
33
+ }));
34
+ });
35
+ });
@@ -0,0 +1,147 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { Pressable, Text } from 'react-native';
3
+ import { fireEvent, screen } from '@testing-library/react-native';
4
+ import { measurePerformance } from 'reassure';
5
+ import { useComponentConfig } from '../../hooks/useComponentConfig';
6
+ import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
7
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const consumerCount = 1000;
9
+ const updateIterations = 50;
10
+ const testTimeoutMs = 20000;
11
+ const stableButtonConfig = () => ({
12
+ compact: true
13
+ });
14
+ const stableAvatarConfig = () => ({});
15
+ const ButtonConfigConsumer = _ref => {
16
+ let {
17
+ index
18
+ } = _ref;
19
+ const mergedProps = useComponentConfig('Button', {
20
+ compact: false,
21
+ variant: 'primary'
22
+ });
23
+ return /*#__PURE__*/_jsx(Text, {
24
+ testID: "consumer-" + index,
25
+ children: mergedProps.compact ? 'compact' : 'default'
26
+ });
27
+ };
28
+ const ButtonConfigConsumerList = _ref2 => {
29
+ let {
30
+ count
31
+ } = _ref2;
32
+ return /*#__PURE__*/_jsx(_Fragment, {
33
+ children: Array.from({
34
+ length: count
35
+ }, (_, index) => /*#__PURE__*/_jsx(ButtonConfigConsumer, {
36
+ index: index
37
+ }, index))
38
+ });
39
+ };
40
+ const UnrelatedKeyUpdateHarness = _ref3 => {
41
+ let {
42
+ count
43
+ } = _ref3;
44
+ const [unrelatedUpdates, setUnrelatedUpdates] = useState(0);
45
+ const value = useMemo(() => ({
46
+ Avatar: () => unrelatedUpdates % 2 === 0 ? {} : {},
47
+ Button: stableButtonConfig
48
+ }), [unrelatedUpdates]);
49
+ return /*#__PURE__*/_jsxs(_Fragment, {
50
+ children: [/*#__PURE__*/_jsx(Pressable, {
51
+ onPress: () => setUnrelatedUpdates(v => v + 1),
52
+ testID: "update-unrelated-key",
53
+ children: /*#__PURE__*/_jsx(Text, {
54
+ children: "Update unrelated key"
55
+ })
56
+ }), /*#__PURE__*/_jsx(ComponentConfigProvider, {
57
+ value: value,
58
+ children: /*#__PURE__*/_jsx(ButtonConfigConsumerList, {
59
+ count: count
60
+ })
61
+ })]
62
+ });
63
+ };
64
+ const TargetKeyUpdateHarness = _ref4 => {
65
+ let {
66
+ count
67
+ } = _ref4;
68
+ const [targetUpdates, setTargetUpdates] = useState(0);
69
+ const value = useMemo(() => ({
70
+ Avatar: stableAvatarConfig,
71
+ Button: () => ({
72
+ compact: targetUpdates % 2 === 0
73
+ })
74
+ }), [targetUpdates]);
75
+ return /*#__PURE__*/_jsxs(_Fragment, {
76
+ children: [/*#__PURE__*/_jsx(Pressable, {
77
+ onPress: () => setTargetUpdates(v => v + 1),
78
+ testID: "update-target-key",
79
+ children: /*#__PURE__*/_jsx(Text, {
80
+ children: "Update target key"
81
+ })
82
+ }), /*#__PURE__*/_jsx(ComponentConfigProvider, {
83
+ value: value,
84
+ children: /*#__PURE__*/_jsx(ButtonConfigConsumerList, {
85
+ count: count
86
+ })
87
+ })]
88
+ });
89
+ };
90
+ describe('ComponentConfigProvider performance tests (mobile)', () => {
91
+ jest.setTimeout(testTimeoutMs);
92
+ beforeAll(() => {
93
+ jest.spyOn(console, 'error').mockImplementation(() => {});
94
+ });
95
+ afterAll(() => {
96
+ jest.restoreAllMocks();
97
+ });
98
+ it('Scenario A: renders 1000 consumers under one provider', async () => {
99
+ await measurePerformance(/*#__PURE__*/_jsx(ComponentConfigProvider, {
100
+ value: {
101
+ Button: stableButtonConfig
102
+ },
103
+ children: /*#__PURE__*/_jsx(ButtonConfigConsumerList, {
104
+ count: consumerCount
105
+ })
106
+ }));
107
+ });
108
+ it('Scenario B: updates unrelated component key 50 times', async () => {
109
+ const scenario = async () => {
110
+ for (let i = 0; i < updateIterations; i += 1) {
111
+ fireEvent.press(screen.getByTestId('update-unrelated-key'));
112
+ }
113
+ };
114
+ await measurePerformance(/*#__PURE__*/_jsx(UnrelatedKeyUpdateHarness, {
115
+ count: consumerCount
116
+ }), {
117
+ scenario
118
+ });
119
+ });
120
+ it('Scenario C: updates target component key 50 times', async () => {
121
+ const scenario = async () => {
122
+ for (let i = 0; i < updateIterations; i += 1) {
123
+ fireEvent.press(screen.getByTestId('update-target-key'));
124
+ }
125
+ };
126
+ await measurePerformance(/*#__PURE__*/_jsx(TargetKeyUpdateHarness, {
127
+ count: consumerCount
128
+ }), {
129
+ scenario
130
+ });
131
+ });
132
+ it('Scenario D (baseline): no provider with 1000 consumers', async () => {
133
+ await measurePerformance(/*#__PURE__*/_jsx(ButtonConfigConsumerList, {
134
+ count: consumerCount
135
+ }));
136
+ });
137
+ it('Scenario D (provider): provider enabled with 1000 consumers', async () => {
138
+ await measurePerformance(/*#__PURE__*/_jsx(ComponentConfigProvider, {
139
+ value: {
140
+ Button: stableButtonConfig
141
+ },
142
+ children: /*#__PURE__*/_jsx(ButtonConfigConsumerList, {
143
+ count: consumerCount
144
+ })
145
+ }));
146
+ });
147
+ });
@@ -0,0 +1,326 @@
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
+ import React from 'react';
3
+ import { Pressable } from 'react-native';
4
+ import { fireEvent, screen } from '@testing-library/react-native';
5
+ import { measurePerformance } from 'reassure';
6
+ import { Button } from '../../buttons/Button';
7
+ import { IconButton } from '../../buttons/IconButton';
8
+ import { ListCell } from '../../cells/ListCell';
9
+ import { Chip } from '../../chips/Chip';
10
+ import { SearchInput } from '../../controls/SearchInput';
11
+ import { TextInput } from '../../controls/TextInput';
12
+ import { DotCount } from '../../dots/DotCount';
13
+ import { Icon } from '../../icons/Icon';
14
+ import { HStack } from '../../layout/HStack';
15
+ import { VStack } from '../../layout/VStack';
16
+ import { Avatar } from '../../media/Avatar';
17
+ import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
18
+ import { ThemeProvider } from '../../system/ThemeProvider';
19
+ import { Tag } from '../../tag/Tag';
20
+ import { defaultTheme } from '../../themes/defaultTheme';
21
+ import { Text } from '../../typography/Text';
22
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
23
+ const updateIterations = 50;
24
+ const customPerfTheme = _extends({}, defaultTheme, {
25
+ id: 'component-config-mobile-perf-theme',
26
+ lightColor: _extends({}, defaultTheme.lightColor, {
27
+ bgAlternate: defaultTheme.lightColor.bgSecondary
28
+ }),
29
+ darkColor: _extends({}, defaultTheme.darkColor, {
30
+ bgAlternate: defaultTheme.darkColor.bgSecondary
31
+ })
32
+ });
33
+ const customComponentConfig = {
34
+ Button: props => ({
35
+ borderRadius: 200,
36
+ height: props.compact ? 24 : 32,
37
+ font: props.compact ? 'label1' : 'headline'
38
+ }),
39
+ IconButton: props => ({
40
+ borderRadius: 200,
41
+ height: props.compact ? 24 : 32,
42
+ width: props.compact ? 24 : 32
43
+ }),
44
+ TextInput: props => ({
45
+ bordered: false,
46
+ inputBackground: 'bgAlternate',
47
+ font: props.compact ? 'label2' : 'body',
48
+ variant: 'foregroundMuted',
49
+ focusedBorderWidth: 100
50
+ }),
51
+ SearchInput: props => ({
52
+ borderRadius: 200,
53
+ height: props.compact ? 24 : 32
54
+ }),
55
+ Chip: {
56
+ borderRadius: 200
57
+ },
58
+ ListCell: {
59
+ spacingVariant: 'condensed'
60
+ }
61
+ };
62
+ const ComplexStickerSheetLike = _ref => {
63
+ let {
64
+ tick = 0
65
+ } = _ref;
66
+ return /*#__PURE__*/_jsx(VStack, {
67
+ alignItems: "center",
68
+ background: "bgAlternate",
69
+ gap: 2,
70
+ padding: 2,
71
+ children: /*#__PURE__*/_jsxs(HStack, {
72
+ gap: 2,
73
+ children: [/*#__PURE__*/_jsxs(VStack, {
74
+ gap: 2,
75
+ width: 420,
76
+ children: [/*#__PURE__*/_jsx(HStack, {
77
+ gap: 1,
78
+ children: Array.from({
79
+ length: 12
80
+ }, (_, i) => /*#__PURE__*/_jsx(Button, {
81
+ variant: tick % 2 === 0 ? 'primary' : 'secondary',
82
+ children: "Primary"
83
+ }, "primary-" + i))
84
+ }), /*#__PURE__*/_jsx(HStack, {
85
+ gap: 1,
86
+ children: Array.from({
87
+ length: 12
88
+ }, (_, i) => /*#__PURE__*/_jsx(Button, {
89
+ compact: true,
90
+ variant: "secondary",
91
+ children: "Secondary"
92
+ }, "secondary-" + i))
93
+ }), /*#__PURE__*/_jsx(VStack, {
94
+ gap: 1,
95
+ children: Array.from({
96
+ length: 8
97
+ }, (_, i) => /*#__PURE__*/_jsx(TextInput, {
98
+ label: "Label " + i,
99
+ onChangeText: () => {},
100
+ value: ""
101
+ }, "input-" + i))
102
+ }), /*#__PURE__*/_jsx(VStack, {
103
+ gap: 1,
104
+ children: Array.from({
105
+ length: 8
106
+ }, (_, i) => /*#__PURE__*/_jsx(SearchInput, {
107
+ label: "Search " + i,
108
+ onChangeText: () => {},
109
+ value: ""
110
+ }, "search-" + i))
111
+ }), /*#__PURE__*/_jsx(VStack, {
112
+ gap: 1,
113
+ children: Array.from({
114
+ length: 8
115
+ }, (_, i) => /*#__PURE__*/_jsx(ListCell, {
116
+ accessibilityLabel: "List row " + i,
117
+ description: "$100",
118
+ media: /*#__PURE__*/_jsx(Avatar, {
119
+ name: "A",
120
+ size: "m"
121
+ }),
122
+ onPress: () => {},
123
+ subtitle: "Subtitle",
124
+ title: "Row " + i
125
+ }, "cell-" + i))
126
+ })]
127
+ }), /*#__PURE__*/_jsxs(VStack, {
128
+ gap: 2,
129
+ width: 600,
130
+ children: [/*#__PURE__*/_jsx(HStack, {
131
+ gap: 1,
132
+ children: Array.from({
133
+ length: 16
134
+ }, (_, i) => /*#__PURE__*/_jsx(IconButton, {
135
+ accessibilityLabel: "Icon button " + i,
136
+ name: "add",
137
+ variant: "tertiary"
138
+ }, "icon-" + i))
139
+ }), /*#__PURE__*/_jsx(HStack, {
140
+ flexWrap: "wrap",
141
+ gap: 1,
142
+ children: Array.from({
143
+ length: 24
144
+ }, (_, i) => /*#__PURE__*/_jsxs(Chip, {
145
+ accessibilityLabel: "Chip " + i,
146
+ onPress: () => {},
147
+ children: ["Chip ", i]
148
+ }, "chip-" + i))
149
+ }), /*#__PURE__*/_jsx(HStack, {
150
+ gap: 1,
151
+ children: Array.from({
152
+ length: 20
153
+ }, (_, i) => /*#__PURE__*/_jsxs(Tag, {
154
+ intent: i % 2 === 0 ? 'informational' : 'promotional',
155
+ children: ["Tag ", i]
156
+ }, "tag-" + i))
157
+ }), /*#__PURE__*/_jsx(HStack, {
158
+ gap: 1,
159
+ children: Array.from({
160
+ length: 10
161
+ }, (_, i) => /*#__PURE__*/_jsx(DotCount, {
162
+ count: i + 1,
163
+ children: /*#__PURE__*/_jsx(Icon, {
164
+ name: "bell",
165
+ size: "l"
166
+ })
167
+ }, "dot-" + i))
168
+ }), /*#__PURE__*/_jsxs(Text, {
169
+ font: "title3",
170
+ children: ["Complex story-like surface tick=", tick]
171
+ })]
172
+ })]
173
+ })
174
+ });
175
+ };
176
+ const BaselineHarness = () => /*#__PURE__*/_jsx(ThemeProvider, {
177
+ activeColorScheme: "light",
178
+ theme: defaultTheme,
179
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {})
180
+ });
181
+ const CustomHarness = () => /*#__PURE__*/_jsx(ThemeProvider, {
182
+ activeColorScheme: "dark",
183
+ theme: customPerfTheme,
184
+ children: /*#__PURE__*/_jsx(ComponentConfigProvider, {
185
+ value: customComponentConfig,
186
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {})
187
+ })
188
+ });
189
+ const UnrelatedConfigUpdateHarness = () => {
190
+ const [tick, setTick] = React.useState(0);
191
+ const value = React.useMemo(() => _extends({}, customComponentConfig, {
192
+ Tour: tick % 2 === 0 ? {} : {}
193
+ }), [tick]);
194
+ return /*#__PURE__*/_jsxs(_Fragment, {
195
+ children: [/*#__PURE__*/_jsx(Pressable, {
196
+ onPress: () => setTick(v => v + 1),
197
+ testID: "update-unrelated-config"
198
+ }), /*#__PURE__*/_jsx(ThemeProvider, {
199
+ activeColorScheme: "dark",
200
+ theme: customPerfTheme,
201
+ children: /*#__PURE__*/_jsx(ComponentConfigProvider, {
202
+ value: value,
203
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {})
204
+ })
205
+ })]
206
+ });
207
+ };
208
+ const TargetedConfigUpdateHarness = () => {
209
+ const [tick, setTick] = React.useState(0);
210
+ const value = React.useMemo(() => _extends({}, customComponentConfig, {
211
+ Button: props => ({
212
+ borderRadius: tick % 2 === 0 ? 200 : 300,
213
+ height: props.compact ? 24 : 32,
214
+ font: props.compact ? 'label1' : 'headline'
215
+ })
216
+ }), [tick]);
217
+ return /*#__PURE__*/_jsxs(_Fragment, {
218
+ children: [/*#__PURE__*/_jsx(Pressable, {
219
+ onPress: () => setTick(v => v + 1),
220
+ testID: "update-targeted-config"
221
+ }), /*#__PURE__*/_jsx(ThemeProvider, {
222
+ activeColorScheme: "dark",
223
+ theme: customPerfTheme,
224
+ children: /*#__PURE__*/_jsx(ComponentConfigProvider, {
225
+ value: value,
226
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {})
227
+ })
228
+ })]
229
+ });
230
+ };
231
+ const RandomStateUpdateHarness = () => {
232
+ const [tick, setTick] = React.useState(0);
233
+ return /*#__PURE__*/_jsxs(_Fragment, {
234
+ children: [/*#__PURE__*/_jsx(Pressable, {
235
+ onPress: () => setTick(v => v + 1),
236
+ testID: "update-random-state"
237
+ }), /*#__PURE__*/_jsx(ThemeProvider, {
238
+ activeColorScheme: "dark",
239
+ theme: customPerfTheme,
240
+ children: /*#__PURE__*/_jsx(ComponentConfigProvider, {
241
+ value: customComponentConfig,
242
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {
243
+ tick: tick
244
+ })
245
+ })
246
+ })]
247
+ });
248
+ };
249
+ const CustomThemeNoProviderHarness = () => /*#__PURE__*/_jsx(ThemeProvider, {
250
+ activeColorScheme: "dark",
251
+ theme: customPerfTheme,
252
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {})
253
+ });
254
+ const CustomThemeNoProviderStateUpdateHarness = () => {
255
+ const [tick, setTick] = React.useState(0);
256
+ return /*#__PURE__*/_jsxs(_Fragment, {
257
+ children: [/*#__PURE__*/_jsx(Pressable, {
258
+ onPress: () => setTick(v => v + 1),
259
+ testID: "update-page-state-no-provider"
260
+ }), /*#__PURE__*/_jsx(ThemeProvider, {
261
+ activeColorScheme: "dark",
262
+ theme: customPerfTheme,
263
+ children: /*#__PURE__*/_jsx(ComplexStickerSheetLike, {
264
+ tick: tick
265
+ })
266
+ })]
267
+ });
268
+ };
269
+ describe('ComponentConfig StickerSheet performance tests (mobile)', () => {
270
+ jest.setTimeout(90000);
271
+ beforeAll(() => {
272
+ jest.spyOn(console, 'error').mockImplementation(() => {});
273
+ });
274
+ afterAll(() => {
275
+ jest.restoreAllMocks();
276
+ });
277
+ it('renders StickerSheet baseline (no provider)', async () => {
278
+ await measurePerformance(/*#__PURE__*/_jsx(BaselineHarness, {}));
279
+ });
280
+ it('renders StickerSheet custom story (theme + component config)', async () => {
281
+ await measurePerformance(/*#__PURE__*/_jsx(CustomHarness, {}));
282
+ });
283
+ it('updates unrelated config key 50 times', async () => {
284
+ const scenario = async () => {
285
+ for (let i = 0; i < updateIterations; i += 1) {
286
+ fireEvent.press(screen.getByTestId('update-unrelated-config'));
287
+ }
288
+ };
289
+ await measurePerformance(/*#__PURE__*/_jsx(UnrelatedConfigUpdateHarness, {}), {
290
+ scenario
291
+ });
292
+ });
293
+ it('updates targeted config key 50 times', async () => {
294
+ const scenario = async () => {
295
+ for (let i = 0; i < updateIterations; i += 1) {
296
+ fireEvent.press(screen.getByTestId('update-targeted-config'));
297
+ }
298
+ };
299
+ await measurePerformance(/*#__PURE__*/_jsx(TargetedConfigUpdateHarness, {}), {
300
+ scenario
301
+ });
302
+ });
303
+ it('updates random local state 50 times (provider enabled)', async () => {
304
+ const scenario = async () => {
305
+ for (let i = 0; i < updateIterations; i += 1) {
306
+ fireEvent.press(screen.getByTestId('update-random-state'));
307
+ }
308
+ };
309
+ await measurePerformance(/*#__PURE__*/_jsx(RandomStateUpdateHarness, {}), {
310
+ scenario
311
+ });
312
+ });
313
+ it('renders custom theme with no provider', async () => {
314
+ await measurePerformance(/*#__PURE__*/_jsx(CustomThemeNoProviderHarness, {}));
315
+ });
316
+ it('updates page state 50 times with custom theme and no provider', async () => {
317
+ const scenario = async () => {
318
+ for (let i = 0; i < updateIterations; i += 1) {
319
+ fireEvent.press(screen.getByTestId('update-page-state-no-provider'));
320
+ }
321
+ };
322
+ await measurePerformance(/*#__PURE__*/_jsx(CustomThemeNoProviderStateUpdateHarness, {}), {
323
+ scenario
324
+ });
325
+ });
326
+ });
@@ -0,0 +1,8 @@
1
+ # Component Config Perf Tests
2
+
3
+ This folder contains manual performance benchmarks for component config behavior.
4
+
5
+ ## Run
6
+
7
+ - Web + mobile together:
8
+ - `yarn perf:component-config`
@@ -7,6 +7,7 @@ import { useHasMounted } from '@coinbase/cds-common/hooks/useHasMounted';
7
7
  import { usePreviousValue } from '@coinbase/cds-common/hooks/usePreviousValue';
8
8
  import { containsStep, flattenSteps, isStepVisited } from '@coinbase/cds-common/stepper/utils';
9
9
  import { useSprings } from '@react-spring/native';
10
+ import { useComponentConfig } from '../hooks/useComponentConfig';
10
11
  import { Box } from '../layout/Box';
11
12
  import { VStack } from '../layout/VStack';
12
13
  import { DefaultStepperHeaderHorizontal } from './DefaultStepperHeaderHorizontal';
@@ -34,9 +35,10 @@ export const defaultProgressSpringConfig = {
34
35
  tension: 100,
35
36
  clamp: true
36
37
  };
37
- const StepperBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
38
+ const StepperBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_props, ref) => {
38
39
  var _usePreviousValue, _usePreviousValue2;
39
- let {
40
+ const mergedProps = useComponentConfig('Stepper', _props);
41
+ const {
40
42
  direction,
41
43
  activeStepId,
42
44
  steps,
@@ -56,8 +58,8 @@ const StepperBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
56
58
  progressSpringConfig = defaultProgressSpringConfig,
57
59
  animate = true,
58
60
  disableAnimateOnMount
59
- } = _ref,
60
- props = _objectWithoutPropertiesLoose(_ref, _excluded);
61
+ } = mergedProps,
62
+ props = _objectWithoutPropertiesLoose(mergedProps, _excluded);
61
63
  const hasMounted = useHasMounted();
62
64
  const flatStepIds = useMemo(() => flattenSteps(steps).map(step => step.id), [steps]);
63
65
 
@@ -72,11 +74,11 @@ const StepperBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
72
74
  rootStyle,
73
75
  stepStyles
74
76
  } = useMemo(() => {
75
- const _ref2 = styles != null ? styles : {},
77
+ const _ref = styles != null ? styles : {},
76
78
  {
77
79
  root
78
- } = _ref2,
79
- stepStyles = _objectWithoutPropertiesLoose(_ref2, _excluded2);
80
+ } = _ref,
81
+ stepStyles = _objectWithoutPropertiesLoose(_ref, _excluded2);
80
82
  const rootStyle = [style, root];
81
83
  return {
82
84
  rootStyle,
@@ -84,12 +86,12 @@ const StepperBase = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
84
86
  };
85
87
  }, [styles, style]);
86
88
  const accessibilityLabel = useMemo(() => {
87
- var _ref3, _activeStep$accessibi;
89
+ var _ref2, _activeStep$accessibi;
88
90
  if (accessibilityLabelProp) return accessibilityLabelProp;
89
91
  if (!activeStep) return 'No active step';
90
92
  const pagination = activeFlatStepIndex + 1 + " of " + flatStepIds.length;
91
93
  const stepLabel = typeof activeStep.label === 'string' ? activeStep.label : null;
92
- const baseLabel = (_ref3 = (_activeStep$accessibi = activeStep.accessibilityLabel) != null ? _activeStep$accessibi : stepLabel) != null ? _ref3 : "Step " + (activeFlatStepIndex + 1);
94
+ const baseLabel = (_ref2 = (_activeStep$accessibi = activeStep.accessibilityLabel) != null ? _activeStep$accessibi : stepLabel) != null ? _ref2 : "Step " + (activeFlatStepIndex + 1);
93
95
  return baseLabel + " " + pagination;
94
96
  }, [activeStep, activeFlatStepIndex, flatStepIds.length, accessibilityLabelProp]);
95
97
 
@@ -0,0 +1,39 @@
1
+ import React, { createContext, useContext, useRef } from 'react';
2
+ import { createStore } from 'zustand';
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ export const ComponentConfigContext = /*#__PURE__*/createContext(undefined);
5
+ const createComponentConfigStoreState = config => {
6
+ return {
7
+ components: config
8
+ };
9
+ };
10
+ /**
11
+ * Provides component-level default props via a zustand store.
12
+ * Each component subscribes to only its own config slice, preventing cross-component re-renders.
13
+ * Supports nesting with isolated scopes: a child provider only applies its own config map.
14
+ */
15
+ export const ComponentConfigProvider = _ref => {
16
+ let {
17
+ value,
18
+ children
19
+ } = _ref;
20
+ const storeRef = useRef(null);
21
+ if (!storeRef.current) {
22
+ storeRef.current = createStore(() => createComponentConfigStoreState(value));
23
+ }
24
+ const newState = createComponentConfigStoreState(value);
25
+ storeRef.current.setState(newState, true);
26
+ return /*#__PURE__*/_jsx(ComponentConfigContext.Provider, {
27
+ value: storeRef.current,
28
+ children: children
29
+ });
30
+ };
31
+
32
+ /** Singleton empty store used when no ComponentConfigProvider exists in the tree. */
33
+ const emptyComponentConfigStore = createStore(() => ({}));
34
+
35
+ /** Returns the nearest ComponentConfigProvider's zustand store, or an empty fallback. */
36
+ export const useComponentConfigStore = () => {
37
+ const context = useContext(ComponentConfigContext);
38
+ return context != null ? context : emptyComponentConfigStore;
39
+ };
@@ -0,0 +1,12 @@
1
+ import { Example, ExampleScreen } from '../../examples/ExampleScreen';
2
+ import { StickerSheet } from './componentConfigStickerSheet/StickerSheet';
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ const ComponentConfigProviderStory = () => {
5
+ return /*#__PURE__*/_jsx(ExampleScreen, {
6
+ children: /*#__PURE__*/_jsx(Example, {
7
+ title: "ComponentConfigProvider",
8
+ children: /*#__PURE__*/_jsx(StickerSheet, {})
9
+ })
10
+ });
11
+ };
12
+ export default ComponentConfigProviderStory;