@coinbase/cds-mobile 9.7.1 → 9.8.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,18 @@ All notable changes to this project will be documented in this file.
8
8
 
9
9
  <!-- template-start -->
10
10
 
11
+ ## 9.8.0 (7/23/2026 PST)
12
+
13
+ #### 🚀 Updates
14
+
15
+ - Feat: add createIcon factory to decouple Icon from a single icon set. [[#786](https://github.com/coinbase/cds/pull/786)]
16
+
17
+ ## 9.7.2 (7/23/2026 PST)
18
+
19
+ #### 🐞 Fixes
20
+
21
+ - Fix: adjust select placeholder fallback. [[#805](https://github.com/coinbase/cds/pull/805)]
22
+
11
23
  ## 9.7.1 (7/22/2026 PST)
12
24
 
13
25
  #### 🐞 Fixes
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultSelectControl.d.ts","sourceRoot":"","sources":["../../../src/alpha/select/DefaultSelectControl.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,gBAAgB,EAAkB,MAAM,cAAc,CAAC;AAe3F,OAAO,KAAK,EAAE,kBAAkB,EAAgB,UAAU,EAAE,MAAM,UAAU,CAAC;AAY7E,KAAK,6BAA6B,GAAG,CACnC,IAAI,SAAS,UAAU,EACvB,iBAAiB,SAAS,MAAM,GAAG,MAAM,EAEzC,KAAK,EAAE,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;CAC9D,KACE,KAAK,CAAC,YAAY,CAAC;AAExB,eAAO,MAAM,6BAA6B,wCACvC,IAAI,SAAS,UAAU,EAAE,iBAAiB,SAAS,MAAM,sfAsCvD,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IAC/C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;CAC9D,8CA2YF,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAoC,6BAA6B,CAAC"}
1
+ {"version":3,"file":"DefaultSelectControl.d.ts","sourceRoot":"","sources":["../../../src/alpha/select/DefaultSelectControl.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,gBAAgB,EAAkB,MAAM,cAAc,CAAC;AAe3F,OAAO,KAAK,EAAE,kBAAkB,EAAgB,UAAU,EAAE,MAAM,UAAU,CAAC;AAY7E,KAAK,6BAA6B,GAAG,CACnC,IAAI,SAAS,UAAU,EACvB,iBAAiB,SAAS,MAAM,GAAG,MAAM,EAEzC,KAAK,EAAE,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;CAC9D,KACE,KAAK,CAAC,YAAY,CAAC;AAExB,eAAO,MAAM,6BAA6B,wCACvC,IAAI,SAAS,UAAU,EAAE,iBAAiB,SAAS,MAAM,sfAsCvD,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,CAAC,GAAG;IAC/C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;CAC9D,8CAuZF,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAoC,6BAA6B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  export declare const useAppState: () =>
2
2
  | 'background'
3
- | 'unknown'
4
3
  | 'active'
5
4
  | 'inactive'
5
+ | 'unknown'
6
6
  | 'extension';
7
7
  //# sourceMappingURL=useAppState.d.ts.map
@@ -1,70 +1,52 @@
1
- import React from 'react';
2
- import { Animated, type StyleProp, type TextStyle, type ViewStyle } from 'react-native';
3
- import type { ThemeVars } from '@coinbase/cds-common/core/theme';
4
1
  import type { IconName } from '@coinbase/cds-common/types/IconName';
5
- import type { IconSize, IconSourcePixelSize } from '@coinbase/cds-common/types/IconSize';
6
- import type { SharedAccessibilityProps } from '@coinbase/cds-common/types/SharedAccessibilityProps';
7
- import type { SharedProps } from '@coinbase/cds-common/types/SharedProps';
8
- import type { PaddingProps } from '@coinbase/cds-common/types/SpacingProps';
9
- export type IconBaseProps = SharedProps &
10
- PaddingProps &
11
- Pick<SharedAccessibilityProps, 'accessibilityLabel' | 'accessibilityHint'> & {
12
- /**
13
- * Size for a given icon.
14
- * @default m
15
- */
16
- size?: IconSize;
17
- /** Name of the icon, as defined in Figma. */
18
- name: IconName;
19
- /**
20
- * Fallback element to render if unable to find an icon with matching name
21
- * @default null
22
- * */
23
- fallback?: null | React.ReactNode;
24
- /**
25
- * Toggles the active and inactive state of the navigation icon
26
- * @default false
27
- */
28
- active?: boolean;
29
- /** Color of the icon when used as a foreground.
30
- * @default primary
31
- */
32
- color?: ThemeVars.Color;
33
- /**
34
- * @deprecated Use `style`, `styles.icon`, or the `color` prop to customize icon color. This will be removed in a future major release.
35
- * @deprecationExpectedRemoval v10
36
- */
37
- dangerouslySetColor?: string | Animated.AnimatedInterpolation<string>;
38
- animated?: boolean;
39
- style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
40
- };
41
- export type IconProps = IconBaseProps & {
42
- /** Custom styles for individual elements of the Icon component */
43
- styles?: {
44
- /** Outer Box wrapper element */
45
- root?: StyleProp<ViewStyle>;
46
- /** Inner icon glyph Text element */
47
- icon?: StyleProp<TextStyle>;
48
- };
49
- };
50
- declare const getIconSourceSize: (iconSize: number) => IconSourcePixelSize;
51
- export declare const Icon: React.MemoExoticComponent<
52
- (
53
- _props: IconProps,
54
- ) =>
2
+ import {
3
+ getIconSourceSize,
4
+ type IconBaseProps as IconBasePropsGeneric,
5
+ type IconProps as IconPropsGeneric,
6
+ } from './createIcon';
7
+ export type IconBaseProps = IconBasePropsGeneric<IconName>;
8
+ export type IconProps = IconPropsGeneric<IconName>;
9
+ export declare const Icon: import('react').MemoExoticComponent<
10
+ ({
11
+ ref,
12
+ ..._props
13
+ }: import('@coinbase/cds-common').SharedProps &
14
+ import('@coinbase/cds-common').PaddingProps &
15
+ Pick<
16
+ import('@coinbase/cds-common').SharedAccessibilityProps,
17
+ 'accessibilityLabel' | 'accessibilityHint'
18
+ > & {
19
+ size?: import('@coinbase/cds-common').IconSize;
20
+ name: IconName;
21
+ fallback?: null | React.ReactNode;
22
+ active?: boolean;
23
+ color?: import('@coinbase/cds-common').ThemeVars.Color;
24
+ dangerouslySetColor?: string | import('react-native').Animated.AnimatedInterpolation<string>;
25
+ animated?: boolean;
26
+ } & {
27
+ style?: import('react-native').Animated.WithAnimatedValue<
28
+ import('react-native').StyleProp<import('react-native').TextStyle>
29
+ >;
30
+ styles?: {
31
+ root?: import('react-native').StyleProp<import('react-native').ViewStyle>;
32
+ icon?: import('react-native').StyleProp<import('react-native').TextStyle>;
33
+ };
34
+ } & {
35
+ ref?: React.Ref<import('react-native').Text>;
36
+ }) =>
55
37
  | string
56
38
  | number
57
39
  | bigint
58
40
  | boolean
59
- | Iterable<React.ReactNode>
41
+ | Iterable<import('react').ReactNode>
60
42
  | Promise<
61
43
  | string
62
44
  | number
63
45
  | bigint
64
46
  | boolean
65
- | React.ReactPortal
66
- | React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
67
- | Iterable<React.ReactNode>
47
+ | import('react').ReactPortal
48
+ | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>>
49
+ | Iterable<import('react').ReactNode>
68
50
  | null
69
51
  | undefined
70
52
  >
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../src/icons/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,EACL,QAAQ,EACR,KAAK,SAAS,EAEd,KAAK,SAAS,EAEd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AACzF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qDAAqD,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAQ5E,MAAM,MAAM,aAAa,GAAG,WAAW,GACrC,YAAY,GACZ,IAAI,CAAC,wBAAwB,EAAE,oBAAoB,GAAG,mBAAmB,CAAC,GAAG;IAC3E;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,6CAA6C;IAC7C,IAAI,EAAE,QAAQ,CAAC;IACf;;;SAGK;IACL,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC;IAClC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEJ,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,kEAAkE;IAClE,MAAM,CAAC,EAAE;QACP,gCAAgC;QAChC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAC5B,oCAAoC;QACpC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;KAC7B,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,mBAI7C,CAAC;AAEF,eAAO,MAAM,IAAI,qCAAiB,SAAS,+SAmGzC,CAAC;AAEH,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../src/icons/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAGpE,OAAO,EAGL,iBAAiB,EACjB,KAAK,aAAa,IAAI,oBAAoB,EAC1C,KAAK,SAAS,IAAI,gBAAgB,EACnC,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAC3D,MAAM,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;AAEnD,eAAO,MAAM,IAAI;;;;;;;;;;;YAM8nG,CAAC;YAAyE,CAAC;;;;kWAHxtG,CAAC;AAEH,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import { Animated, type StyleProp, Text, type TextStyle, type ViewStyle } from 'react-native';
3
+ import type { ThemeVars } from '@coinbase/cds-common/core/theme';
4
+ import type { IconSize, IconSourcePixelSize } from '@coinbase/cds-common/types/IconSize';
5
+ import type { SharedAccessibilityProps } from '@coinbase/cds-common/types/SharedAccessibilityProps';
6
+ import type { SharedProps } from '@coinbase/cds-common/types/SharedProps';
7
+ import type { PaddingProps } from '@coinbase/cds-common/types/SpacingProps';
8
+ /** Default font family for the CDS icon glyph font. */
9
+ export declare const DEFAULT_ICON_FONT_FAMILY = 'CoinbaseIcons';
10
+ /**
11
+ * Shape of the glyph map an icon set must provide. Keys are
12
+ * `${name}-${sourcePixelSize}-${'active' | 'inactive'}` and values are the
13
+ * single Unicode character rendered in the icon font.
14
+ */
15
+ export type GlyphMap<Name extends string> = Record<
16
+ `${Name}-${IconSourcePixelSize}-${'active' | 'inactive'}`,
17
+ string
18
+ >;
19
+ /** Arguments passed to a glyph resolver to look up an icon's glyph. */
20
+ export type IconGlyphResolverArgs<Name extends string> = {
21
+ /** Glyph map bound to this icon set. */
22
+ glyphMap: GlyphMap<Name>;
23
+ /** Icon name requested via the `name` prop. */
24
+ name: Name;
25
+ /** Size token requested via the `size` prop. */
26
+ size: IconSize;
27
+ /** Resolved pixel size from the theme (includes device font scaling). */
28
+ pixelSize: number;
29
+ /** Whether the active variant was requested. */
30
+ active: boolean;
31
+ };
32
+ /** Configuration used to bind an icon set to a typed `Icon` component. */
33
+ export type CreateIconConfig<Name extends string> = {
34
+ /** Generated glyph map for this icon set. */
35
+ glyphMap: GlyphMap<Name>;
36
+ /**
37
+ * Font family registered by the icon set's font (loaded via `expo-font`).
38
+ * @default 'CoinbaseIcons'
39
+ */
40
+ fontFamily?: string;
41
+ /**
42
+ * Resolves the glyph to render for an icon from the glyph map. Override to
43
+ * support a custom key format or size model. Defaults to the CDS scheme:
44
+ * `${name}-${sourceSize}-${'active' | 'inactive'}`, where `sourceSize` is
45
+ * `12`, `16`, or `24`.
46
+ */
47
+ getGlyph?: (args: IconGlyphResolverArgs<Name>) => string | undefined;
48
+ };
49
+ export type IconBaseProps<Name extends string = string> = SharedProps &
50
+ PaddingProps &
51
+ Pick<SharedAccessibilityProps, 'accessibilityLabel' | 'accessibilityHint'> & {
52
+ /**
53
+ * Size for a given icon.
54
+ * @default m
55
+ */
56
+ size?: IconSize;
57
+ /** Name of the icon, as defined in Figma. */
58
+ name: Name;
59
+ /**
60
+ * Fallback element to render if unable to find an icon with matching name
61
+ * @default null
62
+ * */
63
+ fallback?: null | React.ReactNode;
64
+ /**
65
+ * Toggles the active and inactive state of the navigation icon
66
+ * @default false
67
+ */
68
+ active?: boolean;
69
+ /** Color of the icon when used as a foreground.
70
+ * @default primary
71
+ */
72
+ color?: ThemeVars.Color;
73
+ /**
74
+ * @deprecated Use `style`, `styles.icon`, or the `color` prop to customize icon color. This will be removed in a future major release.
75
+ * @deprecationExpectedRemoval v10
76
+ */
77
+ dangerouslySetColor?: string | Animated.AnimatedInterpolation<string>;
78
+ animated?: boolean;
79
+ };
80
+ export type IconProps<Name extends string = string> = IconBaseProps<Name> & {
81
+ /** Custom style applied to the outer container. */
82
+ style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
83
+ /** Custom styles for individual elements of the Icon component */
84
+ styles?: {
85
+ /** Outer Box wrapper element */
86
+ root?: StyleProp<ViewStyle>;
87
+ /** Inner icon glyph Text element */
88
+ icon?: StyleProp<TextStyle>;
89
+ };
90
+ };
91
+ declare const getIconSourceSize: (iconSize: number) => IconSourcePixelSize;
92
+ /**
93
+ * Creates a typed `Icon` component bound to a specific icon set (glyph map,
94
+ * font family, and name union). The default CDS `Icon` is created from this
95
+ * factory; consumers with their own icon package can create their own typed
96
+ * icon component that reuses all of the CDS rendering, accessibility, and
97
+ * theming behavior.
98
+ */
99
+ export declare function createIcon<Name extends string>({
100
+ glyphMap,
101
+ fontFamily,
102
+ getGlyph,
103
+ }: CreateIconConfig<Name>): React.MemoExoticComponent<
104
+ ({
105
+ ref,
106
+ ..._props
107
+ }: IconProps<Name> & {
108
+ ref?: React.Ref<Text>;
109
+ }) =>
110
+ | string
111
+ | number
112
+ | bigint
113
+ | boolean
114
+ | Iterable<React.ReactNode>
115
+ | Promise<
116
+ | string
117
+ | number
118
+ | bigint
119
+ | boolean
120
+ | React.ReactPortal
121
+ | React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
122
+ | Iterable<React.ReactNode>
123
+ | null
124
+ | undefined
125
+ >
126
+ | import('react/jsx-runtime').JSX.Element
127
+ | null
128
+ >;
129
+ export { getIconSourceSize };
130
+ //# sourceMappingURL=createIcon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createIcon.d.ts","sourceRoot":"","sources":["../../src/icons/createIcon.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  export * from './Icon';
2
+ export { createIcon, DEFAULT_ICON_FONT_FAMILY } from './createIcon';
3
+ export type { CreateIconConfig, GlyphMap, IconGlyphResolverArgs } from './createIcon';
2
4
  export * from './LogoMark';
3
5
  export * from './LogoWordmark';
4
6
  export * from './SubBrandLogoMark';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/icons/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/icons/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACtF,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,YAAY,CAAC"}
@@ -112,19 +112,23 @@ export const DefaultSelectControlComponent = /*#__PURE__*/memo(_ref => {
112
112
  });
113
113
  return map;
114
114
  }, [options]);
115
+ const matchedOption = useMemo(() => {
116
+ if (isMultiSelect || value === null || Array.isArray(value)) return undefined;
117
+ return optionsMap.get(value);
118
+ }, [isMultiSelect, optionsMap, value]);
119
+ const isShowingPlaceholder = matchedOption === undefined;
115
120
  const singleValueContent = useMemo(() => {
116
- var _ref3, _ref4, _option$label;
117
- const option = !isMultiSelect ? optionsMap.get(value) : undefined;
118
- const label = (_ref3 = (_ref4 = (_option$label = option == null ? void 0 : option.label) != null ? _option$label : option == null ? void 0 : option.description) != null ? _ref4 : option == null ? void 0 : option.value) != null ? _ref3 : placeholder;
119
- return hasValue ? label : placeholder;
120
- }, [hasValue, isMultiSelect, optionsMap, placeholder, value]);
121
+ var _ref3, _matchedOption$label;
122
+ if (!matchedOption) return placeholder;
123
+ return (_ref3 = (_matchedOption$label = matchedOption.label) != null ? _matchedOption$label : matchedOption.description) != null ? _ref3 : matchedOption.value;
124
+ }, [matchedOption, placeholder]);
121
125
  const computedControlAccessibilityLabel = useMemo(() => {
122
126
  // For multi-select, set the label to the content of each selected value and the hidden selected options label
123
127
  if (isMultiSelect) {
124
128
  const selectedValues = value.map(v => {
125
- var _ref5, _ref6, _option$label2;
129
+ var _ref4, _ref5, _option$label;
126
130
  const option = optionsMap.get(v);
127
- return (_ref5 = (_ref6 = (_option$label2 = option == null ? void 0 : option.label) != null ? _option$label2 : option == null ? void 0 : option.description) != null ? _ref6 : option == null ? void 0 : option.value) != null ? _ref5 : v;
131
+ return (_ref4 = (_ref5 = (_option$label = option == null ? void 0 : option.label) != null ? _option$label : option == null ? void 0 : option.description) != null ? _ref5 : option == null ? void 0 : option.value) != null ? _ref4 : v;
128
132
  }).slice(0, maxSelectedOptionsToShow).join(', ');
129
133
  return accessibilityLabel + ", " + (value.length > 0 ? selectedValues : placeholder != null ? placeholder : '') + (value.length > maxSelectedOptionsToShow ? ', ' + hiddenSelectedOptionsLabel : '');
130
134
  }
@@ -183,7 +187,7 @@ export const DefaultSelectControlComponent = /*#__PURE__*/memo(_ref => {
183
187
  maxWidth: "100%",
184
188
  minWidth: 0,
185
189
  children: [optionsToShow.map(option => {
186
- var _option$value, _ref7, _ref8, _option$label3;
190
+ var _option$value, _ref6, _ref7, _option$label2;
187
191
  const accessibilityLabel = typeof option.label === 'string' ? option.label : typeof option.description === 'string' ? option.description : (_option$value = option.value) != null ? _option$value : '';
188
192
  return /*#__PURE__*/_jsx(InputChip, {
189
193
  compact: true,
@@ -196,7 +200,7 @@ export const DefaultSelectControlComponent = /*#__PURE__*/memo(_ref => {
196
200
  event == null || event.stopPropagation();
197
201
  onChange == null || onChange(option.value);
198
202
  },
199
- children: (_ref7 = (_ref8 = (_option$label3 = option.label) != null ? _option$label3 : option.description) != null ? _ref8 : option.value) != null ? _ref7 : ''
203
+ children: (_ref6 = (_ref7 = (_option$label2 = option.label) != null ? _option$label2 : option.description) != null ? _ref7 : option.value) != null ? _ref6 : ''
200
204
  }, option.value);
201
205
  }), value.length - maxSelectedOptionsToShow > 0 && /*#__PURE__*/_jsx(InputChip, {
202
206
  compact: true,
@@ -209,12 +213,12 @@ export const DefaultSelectControlComponent = /*#__PURE__*/memo(_ref => {
209
213
  }
210
214
  return typeof singleValueContent === 'string' ? /*#__PURE__*/_jsx(Text, {
211
215
  align: align,
212
- color: hasValue ? 'fg' : 'fgMuted',
216
+ color: isShowingPlaceholder ? 'fgMuted' : 'fg',
213
217
  ellipsize: "tail",
214
218
  font: font,
215
219
  children: singleValueContent
216
220
  }) : singleValueContent;
217
- }, [hasValue, isMultiSelect, singleValueContent, font, align, value, maxSelectedOptionsToShow, valueAlignment, hiddenSelectedOptionsLabel, optionsMap, removeSelectedOptionAccessibilityLabel, disabled, onChange, isInteractionBlocked]);
221
+ }, [hasValue, isMultiSelect, isShowingPlaceholder, singleValueContent, font, align, value, maxSelectedOptionsToShow, valueAlignment, hiddenSelectedOptionsLabel, optionsMap, removeSelectedOptionAccessibilityLabel, disabled, onChange, isInteractionBlocked]);
218
222
 
219
223
  // onBlur/onFocus on ViewProps allow null returns but TouchableOpacity's onBlur/onFocus props do not.
220
224
  // This appears like a type inconsistency in react-native's type definitions.
@@ -303,6 +303,16 @@ const DefaultExample = () => {
303
303
  value: value
304
304
  });
305
305
  };
306
+ const EmptyStringValueExample = () => {
307
+ const [value, setValue] = useState('');
308
+ return /*#__PURE__*/_jsx(Select, {
309
+ label: "Empty string value",
310
+ onChange: setValue,
311
+ options: exampleOptionsWithoutNull,
312
+ placeholder: "Select an option",
313
+ value: value
314
+ });
315
+ };
306
316
  const TypedSelectExample = () => {
307
317
  const typedOptions = [{
308
318
  value: null,
@@ -1372,6 +1382,9 @@ const SelectV3Screen = () => {
1372
1382
  children: [/*#__PURE__*/_jsx(Example, {
1373
1383
  title: "Default",
1374
1384
  children: /*#__PURE__*/_jsx(DefaultExample, {})
1385
+ }), /*#__PURE__*/_jsx(Example, {
1386
+ title: "Empty string value",
1387
+ children: /*#__PURE__*/_jsx(EmptyStringValueExample, {})
1375
1388
  }), /*#__PURE__*/_jsx(Example, {
1376
1389
  title: "Typed",
1377
1390
  children: /*#__PURE__*/_jsx(TypedSelectExample, {})
package/esm/icons/Icon.js CHANGED
@@ -1,90 +1,7 @@
1
- import React, { memo, useMemo } from 'react';
2
- import { Animated, Text, useWindowDimensions } from 'react-native';
3
1
  import { glyphMap } from '@coinbase/cds-icons/glyphMap';
4
- import { isDevelopment } from '@coinbase/cds-utils';
5
- import { useComponentConfig } from '../hooks/useComponentConfig';
6
- import { useTheme } from '../hooks/useTheme';
7
- import { Box } from '../layout/Box';
8
- import { jsx as _jsx } from "react/jsx-runtime";
9
- const getIconSourceSize = iconSize => {
10
- if (iconSize <= 12) return 12;
11
- if (iconSize <= 16) return 16;
12
- return 24;
13
- };
14
- export const Icon = /*#__PURE__*/memo(_props => {
15
- const mergedProps = useComponentConfig('Icon', _props);
16
- const {
17
- accessibilityLabel,
18
- accessibilityHint,
19
- animated = false,
20
- color = 'fgPrimary',
21
- dangerouslySetColor,
22
- style,
23
- styles,
24
- fallback = null,
25
- name,
26
- size = 'm',
27
- testID,
28
- padding,
29
- paddingX,
30
- paddingY,
31
- paddingTop,
32
- paddingEnd,
33
- paddingBottom,
34
- paddingStart,
35
- active
36
- } = mergedProps;
37
- const TextComponent = animated ? Animated.Text : Text;
38
- const theme = useTheme();
39
- const {
40
- fontScale
41
- } = useWindowDimensions();
42
-
43
- // Scale according to device a11y font size settings
44
- const iconSize = theme.iconSize[size] * fontScale;
45
- const sourceSize = getIconSourceSize(iconSize);
46
- const iconColor = theme.color[color];
47
- const finalColor = dangerouslySetColor != null ? dangerouslySetColor : iconColor;
48
- const rootStyle = useMemo(() => {
49
- var _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8;
50
- return [{
51
- paddingTop: theme.space[(_ref = (_ref2 = paddingTop != null ? paddingTop : paddingY) != null ? _ref2 : padding) != null ? _ref : 0],
52
- paddingEnd: theme.space[(_ref3 = (_ref4 = paddingEnd != null ? paddingEnd : paddingX) != null ? _ref4 : padding) != null ? _ref3 : 0],
53
- paddingBottom: theme.space[(_ref5 = (_ref6 = paddingBottom != null ? paddingBottom : paddingY) != null ? _ref6 : padding) != null ? _ref5 : 0],
54
- paddingStart: theme.space[(_ref7 = (_ref8 = paddingStart != null ? paddingStart : paddingX) != null ? _ref8 : padding) != null ? _ref7 : 0]
55
- }, style, styles == null ? void 0 : styles.root];
56
- }, [style, theme.space, padding, paddingX, paddingY, paddingTop, paddingEnd, paddingBottom, paddingStart, styles == null ? void 0 : styles.root]);
57
- const iconStyle = useMemo(() => [{
58
- fontFamily: 'CoinbaseIcons',
59
- fontSize: iconSize,
60
- height: iconSize,
61
- width: iconSize,
62
- lineHeight: iconSize,
63
- color: finalColor
64
- }, styles == null ? void 0 : styles.icon], [finalColor, iconSize, styles == null ? void 0 : styles.icon]);
65
- const iconName = name + "-" + sourceSize + "-" + (active ? 'active' : 'inactive');
66
- const glyph = glyphMap[iconName];
67
- if (glyph === undefined) {
68
- if (isDevelopment()) {
69
- console.error("Unable to find glyph for icon name \"" + name + "\" with glyph key \"" + iconName + "\"");
70
- }
71
- return fallback;
72
- }
73
- return /*#__PURE__*/_jsx(Box, {
74
- animated: animated,
75
- style: rootStyle,
76
- testID: testID,
77
- children: /*#__PURE__*/_jsx(TextComponent, {
78
- accessibilityHint: accessibilityHint,
79
- accessibilityLabel: accessibilityLabel,
80
- accessibilityRole: "image",
81
- accessible: !!accessibilityLabel,
82
- allowFontScaling: false
83
- // TODO https://linear.app/coinbase/issue/CDS-1518/audit-potentially-harmful-reactnative-animated-pattern
84
- ,
85
- style: iconStyle,
86
- children: glyph
87
- })
88
- });
2
+ import { createIcon, DEFAULT_ICON_FONT_FAMILY, getIconSourceSize } from './createIcon';
3
+ export const Icon = createIcon({
4
+ glyphMap,
5
+ fontFamily: DEFAULT_ICON_FONT_FAMILY
89
6
  });
90
7
  export { getIconSourceSize };
@@ -0,0 +1,144 @@
1
+ const _excluded = ["ref"];
2
+ 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; }
3
+ import React, { memo, useMemo } from 'react';
4
+ import { Animated, Text, useWindowDimensions } from 'react-native';
5
+ import { isDevelopment } from '@coinbase/cds-utils';
6
+ import { useComponentConfig } from '../hooks/useComponentConfig';
7
+ import { useTheme } from '../hooks/useTheme';
8
+ import { Box } from '../layout/Box';
9
+
10
+ /** Default font family for the CDS icon glyph font. */
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ export const DEFAULT_ICON_FONT_FAMILY = 'CoinbaseIcons';
13
+
14
+ /**
15
+ * Shape of the glyph map an icon set must provide. Keys are
16
+ * `${name}-${sourcePixelSize}-${'active' | 'inactive'}` and values are the
17
+ * single Unicode character rendered in the icon font.
18
+ */
19
+
20
+ /** Arguments passed to a glyph resolver to look up an icon's glyph. */
21
+
22
+ /** Configuration used to bind an icon set to a typed `Icon` component. */
23
+
24
+ const getIconSourceSize = iconSize => {
25
+ if (iconSize <= 12) return 12;
26
+ if (iconSize <= 16) return 16;
27
+ return 24;
28
+ };
29
+
30
+ /** Default glyph resolver using the CDS `${name}-${sourceSize}-${state}` key scheme. */
31
+ const defaultGetGlyph = _ref => {
32
+ let {
33
+ glyphMap,
34
+ name,
35
+ pixelSize,
36
+ active
37
+ } = _ref;
38
+ const sourceSize = getIconSourceSize(pixelSize);
39
+ const key = name + "-" + sourceSize + "-" + (active ? 'active' : 'inactive');
40
+ return glyphMap[key];
41
+ };
42
+
43
+ /**
44
+ * Creates a typed `Icon` component bound to a specific icon set (glyph map,
45
+ * font family, and name union). The default CDS `Icon` is created from this
46
+ * factory; consumers with their own icon package can create their own typed
47
+ * icon component that reuses all of the CDS rendering, accessibility, and
48
+ * theming behavior.
49
+ */
50
+ export function createIcon(_ref2) {
51
+ let {
52
+ glyphMap,
53
+ fontFamily = DEFAULT_ICON_FONT_FAMILY,
54
+ getGlyph = defaultGetGlyph
55
+ } = _ref2;
56
+ const Icon = /*#__PURE__*/memo(_ref3 => {
57
+ let {
58
+ ref
59
+ } = _ref3,
60
+ _props = _objectWithoutPropertiesLoose(_ref3, _excluded);
61
+ const mergedProps = useComponentConfig('Icon', _props);
62
+ const {
63
+ accessibilityLabel,
64
+ accessibilityHint,
65
+ animated = false,
66
+ color = 'fgPrimary',
67
+ dangerouslySetColor,
68
+ style,
69
+ styles,
70
+ fallback = null,
71
+ name,
72
+ size = 'm',
73
+ testID,
74
+ padding,
75
+ paddingX,
76
+ paddingY,
77
+ paddingTop,
78
+ paddingEnd,
79
+ paddingBottom,
80
+ paddingStart,
81
+ active
82
+ } = mergedProps;
83
+ const TextComponent = animated ? Animated.Text : Text;
84
+ const theme = useTheme();
85
+ const {
86
+ fontScale
87
+ } = useWindowDimensions();
88
+
89
+ // Scale according to device a11y font size settings
90
+ const iconSize = theme.iconSize[size] * fontScale;
91
+ const iconColor = theme.color[color];
92
+ const finalColor = dangerouslySetColor != null ? dangerouslySetColor : iconColor;
93
+ const rootStyle = useMemo(() => {
94
+ var _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref0, _ref1;
95
+ return [{
96
+ paddingTop: theme.space[(_ref4 = (_ref5 = paddingTop != null ? paddingTop : paddingY) != null ? _ref5 : padding) != null ? _ref4 : 0],
97
+ paddingEnd: theme.space[(_ref6 = (_ref7 = paddingEnd != null ? paddingEnd : paddingX) != null ? _ref7 : padding) != null ? _ref6 : 0],
98
+ paddingBottom: theme.space[(_ref8 = (_ref9 = paddingBottom != null ? paddingBottom : paddingY) != null ? _ref9 : padding) != null ? _ref8 : 0],
99
+ paddingStart: theme.space[(_ref0 = (_ref1 = paddingStart != null ? paddingStart : paddingX) != null ? _ref1 : padding) != null ? _ref0 : 0]
100
+ }, style, styles == null ? void 0 : styles.root];
101
+ }, [style, theme.space, padding, paddingX, paddingY, paddingTop, paddingEnd, paddingBottom, paddingStart, styles == null ? void 0 : styles.root]);
102
+ const iconStyle = useMemo(() => [{
103
+ fontFamily,
104
+ fontSize: iconSize,
105
+ height: iconSize,
106
+ width: iconSize,
107
+ lineHeight: iconSize,
108
+ color: finalColor
109
+ }, styles == null ? void 0 : styles.icon], [finalColor, iconSize, styles == null ? void 0 : styles.icon]);
110
+ const glyph = getGlyph({
111
+ glyphMap,
112
+ name,
113
+ size,
114
+ pixelSize: iconSize,
115
+ active: Boolean(active)
116
+ });
117
+ if (glyph === undefined) {
118
+ if (isDevelopment()) {
119
+ console.error("Unable to find glyph for icon \"" + name + "\" at size \"" + size + "\"");
120
+ }
121
+ return fallback;
122
+ }
123
+ return /*#__PURE__*/_jsx(Box, {
124
+ animated: animated,
125
+ style: rootStyle,
126
+ testID: testID,
127
+ children: /*#__PURE__*/_jsx(TextComponent, {
128
+ ref: ref,
129
+ accessibilityHint: accessibilityHint,
130
+ accessibilityLabel: accessibilityLabel,
131
+ accessibilityRole: "image",
132
+ accessible: !!accessibilityLabel,
133
+ allowFontScaling: false
134
+ // TODO https://linear.app/coinbase/issue/CDS-1518/audit-potentially-harmful-reactnative-animated-pattern
135
+ ,
136
+ style: iconStyle,
137
+ children: glyph
138
+ })
139
+ });
140
+ });
141
+ Icon.displayName = 'Icon';
142
+ return Icon;
143
+ }
144
+ export { getIconSourceSize };
@@ -1,4 +1,5 @@
1
1
  export * from './Icon';
2
+ export { createIcon, DEFAULT_ICON_FONT_FAMILY } from './createIcon';
2
3
  export * from './LogoMark';
3
4
  export * from './LogoWordmark';
4
5
  export * from './SubBrandLogoMark';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-mobile",
3
- "version": "9.7.1",
3
+ "version": "9.8.0",
4
4
  "description": "Coinbase Design System - Mobile",
5
5
  "repository": {
6
6
  "type": "git",
@@ -218,7 +218,7 @@
218
218
  "react-native-worklets": "0.5.2"
219
219
  },
220
220
  "dependencies": {
221
- "@coinbase/cds-common": "^9.7.1",
221
+ "@coinbase/cds-common": "^9.8.0",
222
222
  "@coinbase/cds-icons": "^5.21.0",
223
223
  "@coinbase/cds-illustrations": "^4.44.0",
224
224
  "@coinbase/cds-lottie-files": "^3.3.4",