@coinbase/cds-mobile 9.0.1 → 9.1.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ All notable changes to this project will be documented in this file.
8
8
 
9
9
  <!-- template-start -->
10
10
 
11
+ ## 9.1.0 (5/26/2026 PST)
12
+
13
+ #### 🚀 Updates
14
+
15
+ - Feat: support DotCount theming. [[#723](https://github.com/coinbase/cds/pull/723)]
16
+
17
+ #### 🐞 Fixes
18
+
19
+ - Fix: DotCount border mixing with background color. [[#723](https://github.com/coinbase/cds/pull/723)]
20
+
21
+ ## 9.0.2 ((5/22/2026, 09:54 AM PST))
22
+
23
+ This is an artificial version bump with no new change.
24
+
11
25
  ## 9.0.1 (5/22/2026 PST)
12
26
 
13
27
  #### 🐞 Fixes
@@ -7,13 +7,18 @@ import type {
7
7
  SharedAccessibilityProps,
8
8
  SharedProps,
9
9
  } from '@coinbase/cds-common/types';
10
- export declare const MAX_OVERFLOW_COUNT = 99;
11
- export declare const parseDotCountMaxOverflow: (count: number, max?: number) => string | number;
10
+ import {
11
+ MAX_OVERFLOW_COUNT,
12
+ parseDotCountMaxOverflow,
13
+ } from '@coinbase/cds-common/utils/parseDotCountMaxOverflow';
14
+ import { type BoxBaseProps } from '../layout/Box';
15
+ export { MAX_OVERFLOW_COUNT, parseDotCountMaxOverflow };
12
16
  export type DotCountBaseProps = SharedProps &
13
17
  Pick<
14
18
  SharedAccessibilityProps,
15
19
  'accessibilityLabel' | 'accessibilityLabelledBy' | 'accessibilityHint'
16
- > & {
20
+ > &
21
+ Omit<BoxBaseProps, 'children' | 'background' | 'pin' | 'style' | 'height'> & {
17
22
  /**
18
23
  * The number value to be shown in the dot. If count is <= 0, dot will not show up.
19
24
  * */
@@ -35,17 +40,10 @@ export type DotCountBaseProps = SharedProps &
35
40
  /** Indicates what shape Dot is overlapping */
36
41
  overlap?: DotOverlap;
37
42
  /**
38
- * An optional fixed height of the DotCount component.
39
- * Width grows based on content length.
43
+ * Fixed height of the DotCount badge container. Width grows based on content length.
40
44
  * @default 24
41
- * */
42
- height?: number;
43
- /**
44
- * An optional fixed width of the DotCount component.
45
- * By default, width grows based on content length.
46
- * @default auto
47
- * */
48
- width?: number;
45
+ */
46
+ height?: BoxBaseProps['height'];
49
47
  };
50
48
  export type DotCountProps = DotCountBaseProps & {
51
49
  style?: StyleProp<ViewStyle>;
@@ -1 +1 @@
1
- {"version":3,"file":"DotCount.d.ts","sourceRoot":"","sources":["../../src/dots/DotCount.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,SAAS,EAEd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAgBtB,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,wBAAwB,EACxB,WAAW,EACZ,MAAM,4BAA4B,CAAC;AAepC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,eAAO,MAAM,wBAAwB,GAAI,OAAO,MAAM,EAAE,MAAK,MAA2B,oBAEvF,CAAC;AAaF,MAAM,MAAM,iBAAiB,GAAG,WAAW,GACzC,IAAI,CACF,wBAAwB,EACxB,oBAAoB,GAAG,yBAAyB,GAAG,mBAAmB,CACvE,GAAG;IACF;;UAEM;IACN,KAAK,EAAE,MAAM,CAAC;IACd;;;UAGM;IACN,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;SAGK;IACL,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,6CAA6C;IAC7C,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB;;;;SAIK;IACL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;SAIK;IACL,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEJ,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sEAAsE;IACtE,MAAM,CAAC,EAAE;QACP,mBAAmB;QACnB,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAC5B,wBAAwB;QACxB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QACjC,mBAAmB;QACnB,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;KAC7B,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,QAAQ,qCAAiB,aAAa,6CAsHjD,CAAC"}
1
+ {"version":3,"file":"DotCount.d.ts","sourceRoot":"","sources":["../../src/dots/DotCount.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,SAAS,EAEd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAgBtB,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,wBAAwB,EACxB,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,qDAAqD,CAAC;AAK7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AASvD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,CAAC;AAexD,MAAM,MAAM,iBAAiB,GAAG,WAAW,GACzC,IAAI,CACF,wBAAwB,EACxB,oBAAoB,GAAG,yBAAyB,GAAG,mBAAmB,CACvE,GACD,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG;IAC3E;;UAEM;IACN,KAAK,EAAE,MAAM,CAAC;IACd;;;UAGM;IACN,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;SAGK;IACL,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,6CAA6C;IAC7C,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;CACjC,CAAC;AAEJ,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sEAAsE;IACtE,MAAM,CAAC,EAAE;QACP,mBAAmB;QACnB,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAC5B,wBAAwB;QACxB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QACjC,mBAAmB;QACnB,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;KAC7B,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,QAAQ,qCAAiB,aAAa,6CAyJjD,CAAC"}
@@ -1,31 +1,26 @@
1
- const _excluded = ["children", "pin", "variant", "count", "max", "height", "width", "overlap", "style", "styles"];
1
+ const _excluded = ["children", "pin", "variant", "count", "max", "height", "width", "testID", "accessibilityLabel", "accessibilityLabelledBy", "accessibilityHint", "overlap", "style", "styles", "alignItems", "justifyContent", "paddingX", "borderWidth", "borderRadius", "borderColor", "font", "color", "fontFamily", "fontSize", "fontWeight", "lineHeight", "overflow"];
2
2
  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); }
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, { memo, useEffect, useMemo, useState } from 'react';
5
- import { StyleSheet, View } from 'react-native';
5
+ import { View } from 'react-native';
6
6
  import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
7
7
  import { usePreviousValue } from '@coinbase/cds-common/hooks/usePreviousValue';
8
8
  import { dotOpacityEnterConfig, dotOpacityExitConfig, dotScaleEnterConfig, dotScaleExitConfig } from '@coinbase/cds-common/motion/dot';
9
9
  import { dotCountSize } from '@coinbase/cds-common/tokens/dot';
10
+ import { MAX_OVERFLOW_COUNT, parseDotCountMaxOverflow } from '@coinbase/cds-common/utils/parseDotCountMaxOverflow';
10
11
  import { useComponentConfig } from '../hooks/useComponentConfig';
11
12
  import { useDotPinStyles } from '../hooks/useDotPinStyles';
12
- import { useTheme } from '../hooks/useTheme';
13
+ import { Box } from '../layout/Box';
13
14
  import { convertMotionConfigs } from '../motion/convertMotionConfig';
14
15
  import { withMotionTiming } from '../motion/withMotionTiming';
15
16
  import { Text } from '../typography/Text';
16
17
  import { getTransform } from './dotStyles';
17
18
  import { useDotsLayout } from './useDotsLayout';
18
19
 
19
- // If a badge count is greater than max (optional, defaults at 99), it should
20
- // truncate the numbers so its x+.
20
+ // Re-exporting for backwards compatibility
21
21
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
- export const MAX_OVERFLOW_COUNT = 99;
23
- export const parseDotCountMaxOverflow = function (count, max) {
24
- if (max === void 0) {
25
- max = MAX_OVERFLOW_COUNT;
26
- }
27
- return count <= max ? count : max + "+";
28
- };
22
+ export { MAX_OVERFLOW_COUNT, parseDotCountMaxOverflow };
23
+ const AnimatedBox = Animated.createAnimatedComponent(Box);
29
24
  const [opacityEnter, opacityExit, scaleEnter, scaleExit] = convertMotionConfigs([dotOpacityEnterConfig, dotOpacityExitConfig, dotScaleEnterConfig, dotScaleExitConfig]);
30
25
  const variantColorMap = {
31
26
  negative: 'bgNegative'
@@ -40,12 +35,28 @@ export const DotCount = /*#__PURE__*/memo(_props => {
40
35
  max,
41
36
  height = dotCountSize,
42
37
  width,
38
+ testID = 'dot-count',
39
+ accessibilityLabel,
40
+ accessibilityLabelledBy,
41
+ accessibilityHint,
43
42
  overlap,
44
43
  style,
45
- styles
44
+ styles,
45
+ alignItems = 'center',
46
+ justifyContent = 'center',
47
+ paddingX = 0.75,
48
+ borderWidth = 100,
49
+ borderRadius = 400,
50
+ borderColor = 'bgSecondary',
51
+ font = 'caption',
52
+ color = 'fgInverse',
53
+ fontFamily,
54
+ fontSize,
55
+ fontWeight,
56
+ lineHeight,
57
+ overflow = 'hidden'
46
58
  } = mergedProps,
47
59
  props = _objectWithoutPropertiesLoose(mergedProps, _excluded);
48
- const theme = useTheme();
49
60
  const [childrenSize, onChildrenLayout] = useDotsLayout();
50
61
  const transforms = useDotPinStyles(childrenSize, {
51
62
  width: width != null ? width : height,
@@ -63,18 +74,6 @@ export const DotCount = /*#__PURE__*/memo(_props => {
63
74
  }
64
75
  return {};
65
76
  }, [pin, transforms]);
66
- const containerStyles = useMemo(() => {
67
- return [styleSheet.container, {
68
- height,
69
- minWidth: height,
70
- width,
71
- paddingHorizontal: theme.space[0.75],
72
- borderWidth: theme.borderWidth[100],
73
- borderRadius: theme.borderRadius[400],
74
- borderColor: theme.color.bgSecondary,
75
- backgroundColor: theme.color[variantColorMap[variant]]
76
- }];
77
- }, [height, width, theme.space, theme.borderWidth, theme.borderRadius, theme.color, variant]);
78
77
 
79
78
  // avoid displaying 0 during animations and preserve exit animation
80
79
  useEffect(() => {
@@ -106,37 +105,51 @@ export const DotCount = /*#__PURE__*/memo(_props => {
106
105
  }]
107
106
  };
108
107
  });
109
- const dotCountContainerStyle = useMemo(() => [containerStyles, animatedStyles, styles == null ? void 0 : styles.container], [containerStyles, animatedStyles, styles == null ? void 0 : styles.container]);
108
+ const dotCountContainerStyle = useMemo(() => [animatedStyles, styles == null ? void 0 : styles.container], [animatedStyles, styles == null ? void 0 : styles.container]);
110
109
  const rootStyles = useMemo(() => [style, styles == null ? void 0 : styles.root], [styles == null ? void 0 : styles.root, style]);
110
+ const displayCount = useMemo(() => parseDotCountMaxOverflow(countInternal, max), [countInternal, max]);
111
111
 
112
112
  // only check childrenSize when children is defined
113
113
  const shouldShow = children !== undefined ? childrenSize !== null : true;
114
- return /*#__PURE__*/_jsxs(View, _extends({
115
- style: rootStyles
116
- }, props, {
114
+ return /*#__PURE__*/_jsxs(View, {
115
+ accessibilityHint: accessibilityHint,
116
+ accessibilityLabel: accessibilityLabel,
117
+ accessibilityLabelledBy: accessibilityLabelledBy,
118
+ style: rootStyles,
119
+ testID: testID,
117
120
  children: [/*#__PURE__*/_jsx(View, {
118
121
  onLayout: onChildrenLayout,
119
- testID: props.testID + "-children",
122
+ testID: testID + "-children",
120
123
  children: children
121
124
  }), !shouldUnmount && shouldShow && /*#__PURE__*/_jsx(View, {
122
125
  style: pinStyles,
123
- children: /*#__PURE__*/_jsx(Animated.View, {
126
+ children: /*#__PURE__*/_jsx(AnimatedBox, _extends({
127
+ animated: true,
128
+ alignItems: alignItems,
129
+ background: variantColorMap[variant],
130
+ borderColor: borderColor,
131
+ borderRadius: borderRadius,
132
+ borderWidth: borderWidth,
133
+ height: height,
134
+ justifyContent: justifyContent,
135
+ minWidth: height,
136
+ overflow: overflow,
137
+ paddingX: paddingX,
124
138
  style: dotCountContainerStyle,
125
139
  testID: "dotcount-container",
140
+ width: width
141
+ }, props, {
126
142
  children: /*#__PURE__*/_jsx(Text, {
127
- color: "fgInverse",
128
- font: "caption",
143
+ color: color,
144
+ font: font,
145
+ fontFamily: fontFamily,
146
+ fontSize: fontSize,
147
+ fontWeight: fontWeight,
148
+ lineHeight: lineHeight,
129
149
  style: styles == null ? void 0 : styles.text,
130
- children: parseDotCountMaxOverflow(countInternal, max)
150
+ children: displayCount
131
151
  })
132
- })
152
+ }))
133
153
  })]
134
- }));
135
- });
136
- const styleSheet = StyleSheet.create({
137
- container: {
138
- alignItems: 'center',
139
- justifyContent: 'center',
140
- display: 'flex'
141
- }
154
+ });
142
155
  });
@@ -5,20 +5,22 @@ export const customComponentConfig = {
5
5
  Banner: {
6
6
  borderRadius: 0
7
7
  },
8
- Button: props => ({
8
+ Button: props => _extends({
9
9
  borderRadius: 200,
10
- height: props.compact ? 24 : 32,
11
- font: props.compact ? 'label1' : 'headline',
12
- progressCircleSize: props.compact ? 12 : 16,
13
- paddingY: 0
14
- }),
10
+ paddingX: props.compact ? 2 : 4,
11
+ paddingY: props.compact ? 1 : 2,
12
+ font: props.compact ? 'label1' : 'headline'
13
+ }, props.variant === 'tertiary' ? {
14
+ background: 'bgAlternate',
15
+ color: 'fg',
16
+ borderColor: 'bgAlternate'
17
+ } : {}),
15
18
  IconButton: props => {
16
19
  var _props$compact;
17
20
  const isCompact = (_props$compact = props.compact) != null ? _props$compact : true;
18
21
  return _extends({
19
22
  borderRadius: 200,
20
- height: isCompact ? 24 : 32,
21
- width: isCompact ? 24 : 32
23
+ padding: isCompact ? 1.5 : 2
22
24
  }, props.variant === 'tertiary' ? {
23
25
  background: 'bgAlternate',
24
26
  color: 'fg',
@@ -49,7 +51,7 @@ export const customComponentConfig = {
49
51
  },
50
52
  Radio: props => ({
51
53
  background: 'bg',
52
- borderWidth: props.checked ? 200 : 100,
54
+ borderWidth: 200,
53
55
  borderColor: props.checked ? 'bgPrimary' : 'bgLinePrimarySubtle',
54
56
  controlColor: 'bgPrimary',
55
57
  dotSize: 20 / 3
@@ -124,5 +126,11 @@ export const customComponentConfig = {
124
126
  paddingX: 1,
125
127
  font: 'caption',
126
128
  emphasis: 'low'
129
+ },
130
+ DotCount: {
131
+ height: 16,
132
+ // Design is 1.5 but this causes the badge to be too wide for some single-digit counts
133
+ paddingX: 1,
134
+ paddingY: 0
127
135
  }
128
136
  };
@@ -512,7 +512,7 @@ export const customTheme = _extends({}, defaultTheme, {
512
512
  title4: 20,
513
513
  headline: 16,
514
514
  body: 16,
515
- label1: 12,
515
+ label1: 16,
516
516
  label2: 16,
517
517
  caption: 12,
518
518
  legal: 12
@@ -1,28 +1,21 @@
1
1
  import React, { memo } from 'react';
2
+ import { IconButton } from '../../../../buttons/IconButton';
2
3
  import { DotCount } from '../../../../dots/DotCount';
3
- import { Icon } from '../../../../icons/Icon';
4
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { HStack } from '../../../../layout/HStack';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ const dotCounts = [3, 12, 100];
5
7
  export const DotCountExample = /*#__PURE__*/memo(() => {
6
- return /*#__PURE__*/_jsxs(_Fragment, {
7
- children: [/*#__PURE__*/_jsx(DotCount, {
8
- count: 3,
9
- children: /*#__PURE__*/_jsx(Icon, {
10
- name: "bell",
11
- size: "m"
8
+ return /*#__PURE__*/_jsx(HStack, {
9
+ gap: 2,
10
+ children: dotCounts.map(count => /*#__PURE__*/_jsx(DotCount, {
11
+ count: count,
12
+ pin: "top-end",
13
+ children: /*#__PURE__*/_jsx(IconButton, {
14
+ transparent: true,
15
+ accessibilityLabel: "Notifications",
16
+ iconSize: "m",
17
+ name: "bell"
12
18
  })
13
- }), /*#__PURE__*/_jsx(DotCount, {
14
- count: 12,
15
- children: /*#__PURE__*/_jsx(Icon, {
16
- name: "bell",
17
- size: "m"
18
- })
19
- }), /*#__PURE__*/_jsx(DotCount, {
20
- count: 100,
21
- max: 99,
22
- children: /*#__PURE__*/_jsx(Icon, {
23
- name: "bell",
24
- size: "m"
25
- })
26
- })]
19
+ }, count))
27
20
  });
28
21
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-mobile",
3
- "version": "9.0.1",
3
+ "version": "9.1.0",
4
4
  "description": "Coinbase Design System - Mobile",
5
5
  "repository": {
6
6
  "type": "git",
@@ -202,7 +202,7 @@
202
202
  "react-native-worklets": "0.5.2"
203
203
  },
204
204
  "dependencies": {
205
- "@coinbase/cds-common": "^9.0.1",
205
+ "@coinbase/cds-common": "^9.1.0",
206
206
  "@coinbase/cds-icons": "^5.17.0",
207
207
  "@coinbase/cds-illustrations": "^4.40.1",
208
208
  "@coinbase/cds-lottie-files": "^3.3.4",