Skip to content

Conversation

@haoruikun-cb
Copy link
Contributor

What changed? Why?

Updated Cell border handling so all border-related props are applied to the same inner container as borderRadius (web + mobile), and added focused border customization stories for both platforms. This resolves a customer-reported issue where borders were hard to configure because bordered applied to the outer Box while borderRadius applied to the inner Pressable, causing inconsistent results (see conversation: https://coinbase.slack.com/archives/C05H922EYP7/p1768246943588929).

Refactored Interactable to accept responsive borderColor and added coverage for breakpoint-based resolution. This improves Interactable/Pressable parity with other responsive props and makes border styling consistent across breakpoints.

Root cause (required for bugfixes)

UI changes

iOS Old iOS New
old screenshot image
Android Old Android New
old screenshot new screenshot
Web Old Web New
old screenshot image

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

@linear
Copy link

linear bot commented Jan 30, 2026

@haoruikun-cb haoruikun-cb force-pushed the harryhao/cds-1513-consolidate-listcell-border-props branch from 55f3eb7 to f84b95d Compare February 10, 2026 22:22
Copy link
Contributor

@cb-ekuersch cb-ekuersch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please yarn install and verify your unit tests, lint and typecheck commands pass

>
Spacing variant: {spacingVariant}
</Switch>
<ListCell
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitL are this many examples necessary? Try to limit stories to a few key examples. Keep in mind the primary use case is visual regression testing and visual documentation/interaction.

Copy link
Contributor Author

@haoruikun-cb haoruikun-cb Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that make sense, I'll cut down the story here and only leave interesting/important examples


const { marginX: innerSpacingMarginX, ...innerSpacingWithoutMarginX } = innerSpacing;

const borderProps = useMemo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please leave a comment:

  • explaining why we need to collect the border props
  • a link to the linear issue we created to remove the outer Cell element (i believe we have one, if not lets create a breaking change cds-v10 issue for it)

interactablePressedBorderColor,
interactablePressedOpacity,
} from './interactableCSSProperties';
import { MediaQueryContext } from './MediaQueryProvider';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is technically a breaking change. Before Interactible didn't have to be used inside the MediaQueryProvider (even tho it probably is in most cases already)

Copy link
Contributor Author

@haoruikun-cb haoruikun-cb Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be an OK breaking change to make since Pressable are being used inside MediaQueryProvider as well so customer technically don't need to change anything in most cases. But we need to make this thing clear in our v9 documentation so customer is aware of this.

}
`;

const isResponsiveValue = <T,>(value: ResponsiveProp<T>): value is ResponsiveValue<T> =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lets put this in a web util. Maybe if we already have a responsive module or something similar it can live there

/** Set element to block and expand to 100% width. */
block?: boolean;
/** Border color of the element. */
borderColor?: ThemeVars.Color;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this comes from Box right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct.

const Component = (as ?? interactableDefaultElement) satisfies React.ElementType;
const theme = useTheme();
const mediaQueryContext = useContext(MediaQueryContext);
const resolvedBorderColor = useMemo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to do this? I thought responsive props resolve for us automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the borderColor is being applied to Box directly, we shouldn't need to write this logic. However it's being applied to a style prop. It has a dedicated logic to compute what style is being applied to the Box based on the colors passed in:
image

@haoruikun-cb
Copy link
Contributor Author

please yarn install and verify your unit tests, lint and typecheck commands pass

There are some typecheck and lint issues coming from cds-v9 branch. I can draft a separate PR to fix that

* @returns The resolved value for the current breakpoint, or the fallback when
* getSnapshot is not provided
*/
export const resolveResponsiveProp = <T>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haoruikun-cb it might be more useful to make this a react hook, otherwise we have to always get the context before using this function

function useResolveResponsiveProp(value?: ResponsiveProp<T>) {
  const { getSnapshot } = useContext(MediaQueryContext);

  if (!value || !isResponsiveValue(value)) return value;
  const fallback = value.base ?? value.phone ?? value.tablet ?? value.desktop;
  if (!getSnapshot) return fallback;
  if (typeof value.phone !== 'undefined' && getSnapshot(media.phone)) return value.phone;
  if (typeof value.tablet !== 'undefined' && getSnapshot(media.tablet)) return value.tablet;
  if (typeof value.desktop !== 'undefined' && getSnapshot(media.desktop)) return value.desktop;
  return fallback;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants