Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .nx/version-plans/cds-2620-dismiss-android-keyboard.md

This file was deleted.

5 changes: 0 additions & 5 deletions .nx/version-plans/illustrations-sync-tooling.md

This file was deleted.

11 changes: 11 additions & 0 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 9.26.2 (2026-09-11)

### 🩹 Fixes

- Fix: vertically align the helper-text error icon with the text in web and mobile inputs.
- Fix: dismiss the Android soft keyboard when the mobile `TextInput` unmounts while focused.

### 🧱 Updated Dependencies

- Updated illustrations to 4.48.1

## 9.26.1 (2026-09-03)

### 🩹 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-common",
"version": "9.26.1",
"version": "9.26.2",
"description": "Coinbase Design System - Common",
"repository": {
"type": "git",
Expand Down
6 changes: 6 additions & 0 deletions packages/illustrations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 4.48.1 (2026-09-11)

### 🩹 Fixes

- Add the `illustrations:sync-illustrations` target, which syncs illustrations from Figma and writes a version plan describing what changed. Adds sync-only devDependencies; no runtime change for consumers.

## 4.48.0 (8/12/2026 PST)

#### 🚀 Updates
Expand Down
2 changes: 1 addition & 1 deletion packages/illustrations/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-illustrations",
"version": "4.48.0",
"version": "4.48.1",
"description": "CDS illustrations",
"repository": {
"type": "git",
Expand Down
7 changes: 7 additions & 0 deletions packages/mcp-server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 9.26.2 (2026-09-11)

### 🩹 Fixes

- Fix: vertically align the helper-text error icon with the text in web and mobile inputs.
- Fix: dismiss the Android soft keyboard when the mobile `TextInput` unmounts while focused.

## 9.26.1 (2026-09-03)

### 🩹 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-mcp-server",
"version": "9.26.1",
"version": "9.26.2",
"description": "Coinbase Design System - MCP Server",
"repository": {
"type": "git",
Expand Down
11 changes: 11 additions & 0 deletions packages/mobile/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 9.26.2 (2026-09-11)

### 🩹 Fixes

- Fix: vertically align the helper-text error icon with the text in web and mobile inputs.
- Fix: dismiss the Android soft keyboard when the mobile `TextInput` unmounts while focused.

### 🧱 Updated Dependencies

- Updated illustrations to 4.48.1

## 9.26.1 (2026-09-03)

### 🩹 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-mobile",
"version": "9.26.1",
"version": "9.26.2",
"description": "Coinbase Design System - Mobile",
"repository": {
"type": "git",
Expand Down
79 changes: 32 additions & 47 deletions packages/mobile/src/controls/HelperText.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { memo, useMemo } from 'react';
import type { ThemeVars } from '@coinbase/cds-common/core/theme';
import { glyphMap } from '@coinbase/cds-icons/glyphMap';

import { useTheme } from '../hooks/useTheme';
import { getIconSourceSize } from '../icons/Icon';
import { Icon } from '../icons/Icon';
import { HStack } from '../layout/HStack';
import type { TextProps } from '../typography/Text';
import { Text } from '../typography/Text';

Expand All @@ -30,64 +30,49 @@ export const HelperText = memo(function HelperText({
color,
errorIconAccessibilityLabel,
errorIconTestID,
children,
align,
dangerouslySetColor,
style,
styles,
...props
}: HelperTextProps) {
const theme = useTheme();
// Get info icon for negative variant
const iconSize = theme.iconSize.xs;
const sourceSize = getIconSourceSize(iconSize);
const glyphKey = `info-${sourceSize}-active` as const;
const glyph = glyphMap[glyphKey];

const rootStyle = [props.style, styles?.root];
// TODO: when we actually remove dangerouslySetColor:
// when migrating from dangerouslySetColor to style.color,
// root style/className color will not automatically style the error icon like dangerouslySetColor.
// Consumers must set both styles.root and styles.icon (or classNames equivalents).
// We need to have a migrator handle this or document in future migration guide.
const iconStyle = useMemo(
() => [
{
fontFamily: 'CoinbaseIcons',
fontSize: iconSize,
height: iconSize,
width: iconSize,
letterSpacing: 4,
// Sit on the first line of label2, matching nested-text alignment.
marginTop: (theme.lineHeight.label2 - theme.iconSize.xs) / 2,
},
// TODO: when we actually remove dangerouslySetColor:
// when migrating from dangerouslySetColor to style.color,
// root style/className color will not automatically style the error icon like dangerouslySetColor.
// Consumers must set both styles.root and styles.icon (or classNames equivalents).
// We need to have a migrator handle this or document in future migration guide.
styles?.icon,
],
[iconSize, styles?.icon],
[styles?.icon, theme.iconSize.xs, theme.lineHeight.label2],
);
const justifyContent =
props.align === 'end' ? 'flex-end' : props.align === 'center' ? 'center' : 'flex-start';

return (
<Text
align={align}
color={color}
dangerouslySetColor={dangerouslySetColor}
font="label2"
style={[style, styles?.root]}
{...props}
>
{color === 'fgNegative' && (
<Text
accessible
if (color === 'fgNegative') {
return (
<HStack alignItems="flex-start" gap={0.5} justifyContent={justifyContent}>
<Icon
active
accessibilityLabel={errorIconAccessibilityLabel}
accessibilityRole="image"
align={align}
color={color}
dangerouslySetColor={dangerouslySetColor}
font="label2"
allowFontScaling={false}
color="fgNegative"
dangerouslySetColor={
typeof props.dangerouslySetColor === 'string' ? props.dangerouslySetColor : undefined
}
name="info"
size="xs"
style={iconStyle}
testID={errorIconTestID}
>
{glyph}
</Text>
)}
{children}
</Text>
);
/>
<Text color={color} font="label2" {...props} flexShrink={1} style={rootStyle} />
</HStack>
);
}

return <Text color={color} font="label2" {...props} style={rootStyle} />;
});
38 changes: 38 additions & 0 deletions packages/mobile/src/controls/__tests__/HelperText.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,44 @@ describe('HelperText.test', () => {
expect(screen.getByTestId('error-icon')).toHaveStyle({ color: 'yellow' });
});

it('sizes the error icon to the xs icon token', () => {
render(
<DefaultThemeProvider>
<HelperText
color="fgNegative"
errorIconAccessibilityLabel="Error"
errorIconTestID="error-icon"
>
Test text
</HelperText>
</DefaultThemeProvider>,
);

expect(screen.getByTestId('error-icon')).toBeTruthy();
expect(screen.getByLabelText('Error')).toHaveStyle({
fontSize: 12,
});
});

it('passes dangerouslySetColor to the helper text and error icon', () => {
render(
<DefaultThemeProvider>
<HelperText
color="fgNegative"
dangerouslySetColor="red"
errorIconAccessibilityLabel="Error"
errorIconTestID="error-icon"
testID="helper-text"
>
Test text
</HelperText>
</DefaultThemeProvider>,
);

expect(screen.getByTestId('helper-text')).toHaveStyle({ color: 'red' });
expect(screen.getByLabelText('Error')).toHaveStyle({ color: 'red' });
});

it('renders custom spacing', () => {
render(
<DefaultThemeProvider>
Expand Down
11 changes: 11 additions & 0 deletions packages/web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 9.26.2 (2026-09-11)

### 🩹 Fixes

- Fix: vertically align the helper-text error icon with the text in web and mobile inputs.
- Fix: dismiss the Android soft keyboard when the mobile `TextInput` unmounts while focused.

### 🧱 Updated Dependencies

- Updated illustrations to 4.48.1

## 9.26.1 (2026-09-03)

### 🩹 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-web",
"version": "9.26.1",
"version": "9.26.2",
"description": "Coinbase Design System - Web",
"repository": {
"type": "git",
Expand Down
82 changes: 43 additions & 39 deletions packages/web/src/controls/HelperText.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import type { ThemeVars } from '@coinbase/cds-common/core/theme';
import { css } from '@linaria/core';

import { cx } from '../cx';
import { Icon } from '../icons/Icon';
import { Box } from '../layout/Box';
import { HStack } from '../layout/HStack';
import { Text, type TextDefaultElement, type TextProps } from '../typography/Text';

export type HelperTextProps = {
Expand Down Expand Up @@ -32,61 +31,66 @@ export type HelperTextProps = {
};
} & TextProps<TextDefaultElement>;

const iconCss = css`
display: inline-block;
padding-inline-end: var(--space-0_5);
`;

export const HelperText = memo(function HelperTex({
color,
id,
errorIconAccessibilityLabel,
errorIconTestID,
children,
dangerouslySetColor,
textAlign = 'start',
style,
styles,
className,
classNames,
...props
}: HelperTextProps) {
const rootStyle = { ...style, ...styles?.root };
const rootStyle = { ...props.style, ...styles?.root };
// TODO: when we actually remove dangerouslySetColor:
// when migrating from dangerouslySetColor to style.color,
// root style/className color will not automatically style the error icon like dangerouslySetColor.
// Consumers must set both styles.root and styles.icon (or classNames equivalents).
// We need to have a migrator handle this or document in future migration guide.
const iconStyle = styles?.icon;
const iconStyle = useMemo(
() => ({
// Sit on the first line of label2, matching nested-text alignment.
marginTop: 'calc((var(--lineHeight-label2) - var(--iconSize-xs)) / 2)',
...styles?.icon,
}),
[styles?.icon],
);
const justifyContent =
props.textAlign === 'end' ? 'flex-end' : props.textAlign === 'center' ? 'center' : 'flex-start';

if (color === 'fgNegative') {
return (
<HStack alignItems="flex-start" gap={0.5} justifyContent={justifyContent}>
<Icon
active
accessibilityLabel={errorIconAccessibilityLabel}
className={classNames?.icon}
color="fgNegative"
dangerouslySetColor={props.dangerouslySetColor}
name="info"
size="xs"
style={iconStyle}
testID={errorIconTestID}
/>
<Text
color={color}
display="block"
font="label2"
{...props}
className={cx(props.className, classNames?.root)}
flexShrink={1}
style={rootStyle}
/>
</HStack>
);
}

return (
<Text
className={cx(className, classNames?.root)}
color={color}
dangerouslySetColor={dangerouslySetColor}
display="block"
font="label2"
id={id}
style={rootStyle}
textAlign={textAlign}
{...props}
>
{color === 'fgNegative' && (
<Box as="span" className={iconCss}>
<Icon
active
accessibilityLabel={errorIconAccessibilityLabel}
className={classNames?.icon}
color="fgNegative"
dangerouslySetColor={dangerouslySetColor}
name="info"
size="xs"
style={iconStyle}
testID={errorIconTestID}
/>
</Box>
)}
{children}
</Text>
className={cx(props.className, classNames?.root)}
style={rootStyle}
/>
);
});
Loading
Loading