@coinbase/cds-mobile 8.52.2 → 8.53.1

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,18 @@ All notable changes to this project will be documented in this file.
8
8
 
9
9
  <!-- template-start -->
10
10
 
11
+ ## 8.53.1 (3/17/2026 PST)
12
+
13
+ #### 🐞 Fixes
14
+
15
+ - Fix: update RemoteImageGroup excess bg color. [[#512](https://github.com/coinbase/cds/pull/512)]
16
+
17
+ ## 8.53.0 (3/16/2026 PST)
18
+
19
+ #### 🚀 Updates
20
+
21
+ - Feat: update Checkbox borderRadius to match design. [[#509](https://github.com/coinbase/cds/pull/509)]
22
+
11
23
  ## 8.52.2 (3/11/2026 PST)
12
24
 
13
25
  #### 🐞 Fixes
@@ -1,5 +1,7 @@
1
+ const _excluded = ["freeSolo", "options", "value", "onChange", "placeholder"];
1
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); }
2
- import { useRef, useState } from 'react';
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
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
5
  import { useMultiSelect } from '@coinbase/cds-common/select/useMultiSelect';
4
6
  import { Button } from '../../../buttons';
5
7
  import { Example, ExampleScreen } from '../../../examples/ExampleScreen';
@@ -118,37 +120,40 @@ const fruitOptions = [{
118
120
  value: 'lemon',
119
121
  label: 'Lemon'
120
122
  }];
123
+ function getFlagEmoji(cc) {
124
+ return cc.toUpperCase().split('').map(c => String.fromCodePoint(0x1f1e6 - 65 + c.charCodeAt(0))).join('');
125
+ }
121
126
  const countryOptions = [{
122
127
  value: 'us',
123
- label: 'United States',
128
+ label: getFlagEmoji('us') + " United States",
124
129
  description: 'North America'
125
130
  }, {
126
131
  value: 'ca',
127
- label: 'Canada',
132
+ label: getFlagEmoji('ca') + " Canada",
128
133
  description: 'North America'
129
134
  }, {
130
135
  value: 'mx',
131
- label: 'Mexico',
136
+ label: getFlagEmoji('mx') + " Mexico",
132
137
  description: 'North America'
133
138
  }, {
134
139
  value: 'uk',
135
- label: 'United Kingdom',
140
+ label: getFlagEmoji('gb') + " United Kingdom",
136
141
  description: 'Europe'
137
142
  }, {
138
143
  value: 'fr',
139
- label: 'France',
144
+ label: getFlagEmoji('fr') + " France",
140
145
  description: 'Europe'
141
146
  }, {
142
147
  value: 'de',
143
- label: 'Germany',
148
+ label: getFlagEmoji('de') + " Germany",
144
149
  description: 'Europe'
145
150
  }, {
146
151
  value: 'jp',
147
- label: 'Japan',
152
+ label: getFlagEmoji('jp') + " Japan",
148
153
  description: 'Asia'
149
154
  }, {
150
155
  value: 'cn',
151
- label: 'China',
156
+ label: getFlagEmoji('cn') + " China",
152
157
  description: 'Asia'
153
158
  }];
154
159
  const cryptoOptions = [{
@@ -197,6 +202,75 @@ const teamOptions = [{
197
202
  label: 'Charlie Brown',
198
203
  description: 'Marketing'
199
204
  }];
205
+ const CREATE_OPTION_PREFIX = '__create__';
206
+ function FreeSoloCombobox(_ref) {
207
+ let {
208
+ freeSolo = false,
209
+ options: initialOptions,
210
+ value,
211
+ onChange,
212
+ placeholder = 'Search or type to add...'
213
+ } = _ref,
214
+ comboboxProps = _objectWithoutPropertiesLoose(_ref, _excluded);
215
+ const [searchText, setSearchText] = useState('');
216
+ const [options, setOptions] = useState(initialOptions);
217
+ useEffect(() => {
218
+ if (!freeSolo) return;
219
+ const initialSet = new Set(initialOptions.map(o => o.value));
220
+ const valueSet = new Set(Array.isArray(value) ? value : value != null ? [value] : []);
221
+ setOptions(prev => {
222
+ const addedStillSelected = prev.filter(o => !initialSet.has(o.value) && valueSet.has(o.value));
223
+ return [...initialOptions, ...addedStillSelected];
224
+ });
225
+ }, [value, freeSolo, initialOptions]);
226
+ const optionsWithCreate = useMemo(() => {
227
+ if (!freeSolo) return options;
228
+ const trimmed = searchText.trim();
229
+ if (!trimmed) return options;
230
+ const alreadyExists = options.some(o => typeof o.label === 'string' && o.label.toLowerCase() === trimmed.toLowerCase());
231
+ if (alreadyExists) return options;
232
+ return [...options, {
233
+ value: "" + CREATE_OPTION_PREFIX + trimmed,
234
+ label: "Add \"" + trimmed + "\""
235
+ }];
236
+ }, [options, searchText, freeSolo]);
237
+ const handleChange = useCallback(newValue => {
238
+ if (!freeSolo) {
239
+ onChange(newValue);
240
+ return;
241
+ }
242
+ const values = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
243
+ const createValue = values.find(v => String(v).startsWith(CREATE_OPTION_PREFIX));
244
+ if (createValue) {
245
+ const newLabel = String(createValue).slice(CREATE_OPTION_PREFIX.length);
246
+ const newOption = {
247
+ value: newLabel.toLowerCase(),
248
+ label: newLabel
249
+ };
250
+ setOptions(prev => [...prev, newOption]);
251
+ const updatedValues = values.filter(v => !String(v).startsWith(CREATE_OPTION_PREFIX)).concat(newOption.value);
252
+ if (comboboxProps.type === 'multi') {
253
+ onChange(updatedValues);
254
+ } else {
255
+ onChange(newOption.value);
256
+ }
257
+ setSearchText('');
258
+ } else {
259
+ onChange(newValue);
260
+ }
261
+ }, [onChange, freeSolo, comboboxProps.type]);
262
+ const effectiveOptions = freeSolo ? optionsWithCreate : initialOptions;
263
+ const effectiveSearchProps = freeSolo ? {
264
+ onSearch: setSearchText,
265
+ searchText
266
+ } : {};
267
+ return /*#__PURE__*/_jsx(Combobox, _extends({}, comboboxProps, effectiveSearchProps, {
268
+ onChange: handleChange,
269
+ options: effectiveOptions,
270
+ placeholder: placeholder,
271
+ value: value
272
+ }));
273
+ }
200
274
 
201
275
  // Example Components
202
276
  const DefaultExample = () => {
@@ -410,6 +484,7 @@ const AccessibilityLabelExample = () => {
410
484
  initialValue: []
411
485
  });
412
486
  return /*#__PURE__*/_jsx(Combobox, {
487
+ accessibilityHint: "Select one or more fruits",
413
488
  accessibilityLabel: "Custom accessibility label",
414
489
  label: "Accessible combobox",
415
490
  onChange: onChange,
@@ -999,6 +1074,53 @@ const DynamicOptionsExample = () => {
999
1074
  })]
1000
1075
  });
1001
1076
  };
1077
+ const FreeSoloComboboxExample = () => {
1078
+ const [standardSingleValue, setStandardSingle] = useState(null);
1079
+ const [freeSoloSingleValue, setFreeSoloSingle] = useState(null);
1080
+ const standardMulti = useMultiSelect({
1081
+ initialValue: []
1082
+ });
1083
+ const freeSoloMulti = useMultiSelect({
1084
+ initialValue: []
1085
+ });
1086
+ const baseOptions = fruitOptions.slice(0, 6);
1087
+ return /*#__PURE__*/_jsxs(VStack, {
1088
+ gap: 4,
1089
+ children: [/*#__PURE__*/_jsx(FreeSoloCombobox, {
1090
+ freeSolo: false,
1091
+ label: "Standard single",
1092
+ onChange: setStandardSingle,
1093
+ options: baseOptions,
1094
+ placeholder: "Search fruits...",
1095
+ type: "single",
1096
+ value: standardSingleValue
1097
+ }), /*#__PURE__*/_jsx(FreeSoloCombobox, {
1098
+ freeSolo: true,
1099
+ label: "FreeSolo single",
1100
+ onChange: setFreeSoloSingle,
1101
+ options: baseOptions,
1102
+ placeholder: "Search or type to add...",
1103
+ type: "single",
1104
+ value: freeSoloSingleValue
1105
+ }), /*#__PURE__*/_jsx(FreeSoloCombobox, {
1106
+ freeSolo: false,
1107
+ label: "Standard multi",
1108
+ onChange: standardMulti.onChange,
1109
+ options: baseOptions,
1110
+ placeholder: "Search fruits...",
1111
+ type: "multi",
1112
+ value: standardMulti.value
1113
+ }), /*#__PURE__*/_jsx(FreeSoloCombobox, {
1114
+ freeSolo: true,
1115
+ label: "FreeSolo multi",
1116
+ onChange: freeSoloMulti.onChange,
1117
+ options: baseOptions,
1118
+ placeholder: "Search or type to add...",
1119
+ type: "multi",
1120
+ value: freeSoloMulti.value
1121
+ })]
1122
+ });
1123
+ };
1002
1124
  const CustomComponent = props => {
1003
1125
  var _props$value$length, _props$value;
1004
1126
  return /*#__PURE__*/_jsx(DefaultComboboxControl, _extends({}, props, {
@@ -1093,7 +1215,7 @@ const Default = () => {
1093
1215
  title: "Controlled search",
1094
1216
  children: /*#__PURE__*/_jsx(ControlledSearchExample, {})
1095
1217
  }), /*#__PURE__*/_jsx(Example, {
1096
- title: "Custom accessibility label",
1218
+ title: "Custom accessibility label and hint",
1097
1219
  children: /*#__PURE__*/_jsx(AccessibilityLabelExample, {})
1098
1220
  }), /*#__PURE__*/_jsx(Example, {
1099
1221
  title: "Options with descriptions",
@@ -1188,6 +1310,9 @@ const Default = () => {
1188
1310
  }), /*#__PURE__*/_jsx(Example, {
1189
1311
  title: "Borderless",
1190
1312
  children: /*#__PURE__*/_jsx(BorderlessExample, {})
1313
+ }), /*#__PURE__*/_jsx(Example, {
1314
+ title: "FreeSolo (select or add custom)",
1315
+ children: /*#__PURE__*/_jsx(FreeSoloComboboxExample, {})
1191
1316
  })]
1192
1317
  });
1193
1318
  };
@@ -17,7 +17,7 @@ const CheckboxIcon = /*#__PURE__*/memo(_ref => {
17
17
  controlColor = 'fgInverse',
18
18
  background = checked || indeterminate ? 'bgPrimary' : 'bg',
19
19
  borderColor = checked || indeterminate ? 'bgPrimary' : 'bgLineHeavy',
20
- borderRadius,
20
+ borderRadius = 100,
21
21
  borderWidth = 100,
22
22
  elevation,
23
23
  animatedScaleValue,
@@ -77,7 +77,7 @@ export const RemoteImageGroup = _ref => {
77
77
  children: clonedChild
78
78
  }, index);
79
79
  }), excess > 0 && /*#__PURE__*/_jsx(Box, {
80
- background: "bgOverlay",
80
+ background: "bgSecondary",
81
81
  borderColor: borderColor,
82
82
  borderWidth: borderWidth,
83
83
  marginStart: overlapSpacing,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-mobile",
3
- "version": "8.52.2",
3
+ "version": "8.53.1",
4
4
  "description": "Coinbase Design System - Mobile",
5
5
  "repository": {
6
6
  "type": "git",
@@ -198,9 +198,9 @@
198
198
  "react-native-svg": "^14.1.0"
199
199
  },
200
200
  "dependencies": {
201
- "@coinbase/cds-common": "^8.52.2",
202
- "@coinbase/cds-icons": "^5.12.0",
203
- "@coinbase/cds-illustrations": "^4.32.0",
201
+ "@coinbase/cds-common": "^8.53.1",
202
+ "@coinbase/cds-icons": "^5.13.0",
203
+ "@coinbase/cds-illustrations": "^4.34.0",
204
204
  "@coinbase/cds-lottie-files": "^3.3.4",
205
205
  "@coinbase/cds-utils": "^2.3.5",
206
206
  "@floating-ui/react-native": "^0.10.5",