Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Buttons): correctly support overriding default data-garden-id attribute #1804

Merged
merged 14 commits into from May 17, 2024

Conversation

ze-flo
Copy link
Contributor

@ze-flo ze-flo commented May 9, 2024

Description

### ❗❗ BREAKING CHANGES ❗❗

Users targeting Button and IconButton using the data-garden-id attribute (commonly for testing purposes) should acknowledge these id changes:

See previous breaking changes
Module Component Previous ID New ID
@zendeskgarden/react-notifications GlobalAlert.Close buttons.button notifications.global-alert.button
@zendeskgarden/react-colorpickers ColorSwatchDialog buttons.button colorpickers.colordialog_button
@zendeskgarden/react-dropdowns (formerly react-dropdowns.next) Menu buttons.button dropdowns.menu.button
@zendeskgarden/react-grid Pane.SplitterButton buttons.icon_button pane.splitter_button
@zendeskgarden/react-notifications GlobalAlert buttons.icon_button notifications.global-alert.close

We can avoid the breaking change by renaming the new ids to match the previous ones.

Detail

  • Fix support for overriding default data-garden-id for each button component exported from @zendeskgarden/react-buttons

Example

const COMPONENT_ID = 'notifications.global-alert.button';

export const StyledGlobalAlertButton = styled(Button).attrs({
'data-garden-id': COMPONENT_ID,
'data-garden-version': PACKAGE_VERSION,
focusInset: false,
isDanger: false,
isLink: false,
isNeutral: false,
isPill: false,

Screen Shot 2024-05-08 at 5 20 19 PM

Checklist

  • 👌 design updates will be Garden Designer approved (add the designer as a reviewer)
  • 🌐 demo is up-to-date (npm start)
  • ⬅️ renders as expected with reversed (RTL) direction
  • 🤘 renders as expected with Bedrock CSS (?bedrock)
  • 💂‍♂️ includes new unit tests. Maintain existing coverage (always >= 96%)
  • ♿ tested for WCAG 2.1 AA accessibility compliance
  • 📝 tested in Chrome, Firefox, Safari, and Edge

@ze-flo ze-flo requested a review from a team as a code owner May 9, 2024 04:34
@ze-flo ze-flo changed the base branch from main to next May 9, 2024 04:44
@@ -24,7 +25,7 @@ export const StyledAnchor = styled(StyledButton).attrs(props => ({
}))`
direction: ${props => props.theme.rtl && 'rtl'};

${props => retrieveComponentStyles(COMPONENT_ID, props)};
${props => retrieveComponentStyles(props['data-garden-id'], props)};
Copy link
Contributor Author

@ze-flo ze-flo May 9, 2024

Choose a reason for hiding this comment

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

props always include the ones set in .attrs. If we complete this type of migration for every component and drop the componentId param from `retrieveComponentStyles, we could rewrite this to:

Suggested change
${props => retrieveComponentStyles(props['data-garden-id'], props)};
${retrieveComponentStyles};

Copy link
Member

Choose a reason for hiding this comment

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

I would discourage the rewrite at this point since a v10 goal is to consolidate packages. At that point, we should visit styled component re-wrapping in earnest and make sure we have the right solution for CSS targeting.

Copy link
Member

@jzempel jzempel left a comment

Choose a reason for hiding this comment

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

I don't think this is necessary. Please compare menu with combobox in the dropdowns.next package. While it doesn't always get sorted in dev, the attrs work out as expected in production.

Comment on lines 72 to 73
export const StyledIconButton = styled(StyledButton).attrs<IButtonProps>(props => ({
'data-garden-id': props['data-garden-id'] || COMPONENT_ID,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in this comment, this pattern causes a regression with IconButton. StyledIconButton now inherits the COMPONENT_ID buttons.button from StyledButton. It should be:

const COMPONENT_ID = 'buttons.icon_button';

Screen Shot 2024-05-15 at 7 37 03 AM

For styled-components extending another styled-component, we must first check the data-garden-id doesn't match the one set on the styled-component it extends.

Suggested change
export const StyledIconButton = styled(StyledButton).attrs<IButtonProps>(props => ({
'data-garden-id': props['data-garden-id'] || COMPONENT_ID,
export const StyledIconButton = styled(StyledButton).attrs<IButtonProps>(props => ({
'data-garden-id':
props['data-garden-id'] && props['data-garden-id'] !== 'buttons.button'
? props['data-garden-id']
: COMPONENT_ID,

This sandbox highlights all the possible combinations, what works and what doesn't. There's a lot there to unfold. You've been warned. 😅

@coveralls
Copy link

coveralls commented May 15, 2024

Coverage Status

coverage: 96.201% (-0.02%) from 96.216%
when pulling 608afe0 on ze-flo/button-override-attrs
into 717a8d5 on next.

@ze-flo
Copy link
Contributor Author

ze-flo commented May 15, 2024

Summary

Here are 2 rules-of-thumb when creating Styled components with data-garden-id:

  1. styled-components that apply styles to a HTML element should uses the attrs Prop Factory fn to support overriding the default data-garden-id attribute if it receives one.
  2. styled-components that extend another styled-component should:
    a. Use the attrs 'Prop Factory' fn to support overriding the default data-garden-id attribute if it receives one.
    b. AND first checks that the received data-garden-id doesn't match the one set on the styled-component it extends.
const BASIC_BTN_ID = 'basic-btn';
/**
 * styled-component that apply styles to a HTML element should
 * uses the `attrs` 'Prop Factory' fn to support overriding
 * the default `data-garden-id` attribute if it receives one.
 * @see https://styled-components.com/docs/api#.attrs
 * 
 */
const StyledBasicBtn = styled.button.attrs((props) => ({
  'data-garden-id': props['data-garden-id'] || BASIC_BTN_ID
}))`
  ...
`;

CONST PINK_BTN_ID = 'pink-btn';

/**
 * styled-components that extend another styled-component should:
 * 1. use the `attrs` 'Prop Factory' fn to support overriding
 *    the default `data-garden-id` attribute if it receives one.
 * 2. First checks that the received `data-garden-id`
 *    doesn't match the one set on the styled-component it extends.
 */
const StyledPinkBtn = styled(StyledBasicBtn).attrs((props) => ({
  'data-garden-id':
    props['data-garden-id'] && props['data-garden-id'] !== BASIC_BTN_ID // Very important!
      ? props['data-garden-id']
      : PINK_BTN_ID
}))`
  background: hotpink;
`;

This sandbox highlights all the possible combinations, what works , and what doesn't.

overriding_attrs

Copy link
Member

@jzempel jzempel left a comment

Choose a reason for hiding this comment

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

  • needs migration.md update similar to 3464347
  • consider using simple data-garden-id comparison strings and avoiding the added export/import clunkiness (i.e. toHaveAttribute('data-garden-id', 'pane.splitter_button'))

In general, let's really try to limit the spread here. I don't think we want this pattern to be normative. The backing and overarching goal is simply to retain the modal.close component ID while being able to reuse Button 😅

Comment on lines 18 to 22
export const StyledAnchor = styled(StyledButton).attrs<IAnchorProps>(props => ({
'data-garden-id':
props['data-garden-id'] && props['data-garden-id'] !== BTN_COMPONENT_ID
? props['data-garden-id']
: COMPONENT_ID,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export const StyledAnchor = styled(StyledButton).attrs<IAnchorProps>(props => ({
'data-garden-id':
props['data-garden-id'] && props['data-garden-id'] !== BTN_COMPONENT_ID
? props['data-garden-id']
: COMPONENT_ID,
export const StyledAnchor = styled(StyledButton).attrs<IAnchorProps>(props => {
const externalId = props['data-garden-id'];
const componentId = externalId && externalId !== BTN_COMPONENT_ID ? externalId : COMPONENT_ID;
return {
'data-garden-id': componentId,

would this improve readability?

Comment on lines 72 to 76
export const StyledIconButton = styled(StyledButton).attrs<IButtonProps>(props => ({
'data-garden-id':
props['data-garden-id'] && props['data-garden-id'] !== BTN_COMPONENT_ID
? props['data-garden-id']
: COMPONENT_ID,
Copy link
Member

Choose a reason for hiding this comment

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

ditto re: readability

export const StyledSplitButton = styled.div.attrs({
'data-garden-id': COMPONENT_ID,
export const StyledSplitButton = styled.div.attrs<ISplitButtonProps>(props => ({
'data-garden-id': props['data-garden-id'] || COMPONENT_ID,
Copy link
Member

Choose a reason for hiding this comment

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

Is this necessary? Where is SplitButton wrapped? We should limit the spread of this change as much as possible.

export const StyledIcon = styled(StyledBaseIcon).attrs({
'data-garden-id': COMPONENT_ID,
export const StyledIcon = styled(StyledBaseIcon).attrs<IStyledIconProps>(props => ({
'data-garden-id': props['data-garden-id'] || COMPONENT_ID,
Copy link
Member

Choose a reason for hiding this comment

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

ditto along with SplitButton – is this necessary?

Comment on lines 13 to 14
/** @ignore Set identifier to retrieve component styles */
'data-garden-id'?: string;
Copy link
Member

Choose a reason for hiding this comment

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

I think we can prefer limited (props as any)['data-garden-id'] vs. any updates to types.

Comment on lines 9 to 13
export { COMPONENT_ID as BTN_COMPONENT_ID, StyledButton } from './StyledButton';
export * from './StyledSplitButton';
export * from './StyledExternalIcon';
export * from './StyledIcon';
export * from './StyledIconButton';
export { COMPONENT_ID as ICON_COMPONENT_ID, StyledIcon } from './StyledIcon';
export { COMPONENT_ID as ICON_BTN_COMPONENT_ID, StyledIconButton } from './StyledIconButton';
Copy link
Member

Choose a reason for hiding this comment

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

Are these COMPONENT_ID exports necessary?

Comment on lines 90 to 96
<StyledButton {...triggerProps}>
<Button {...triggerProps} data-garden-id={BTN_COMPONENT_ID}>
{button}
<StyledButton.EndIcon isRotated={isExpanded}>
<Button.EndIcon isRotated={isExpanded}>
<ChevronIcon />
</StyledButton.EndIcon>
</StyledButton>
</Button.EndIcon>
</Button>
Copy link
Member

Choose a reason for hiding this comment

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

Let's keep StyledButton restored rather than making architecture assertions in this PR.

@@ -9,12 +9,12 @@ import { ThemeProps, DefaultTheme } from 'styled-components';

/** @component */
export default function retrieveComponentStyles(
componentId: string,
componentId: string | undefined,
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't allow undefined here.

@ze-flo ze-flo changed the title fix!(Buttons): correctly support overriding default data-garden-id attribute fix(Buttons): correctly support overriding default data-garden-id attribute May 16, 2024
Comment on lines -14 to -19
export const StyledButton = styled(Button).attrs({
'data-garden-id': COMPONENT_ID,
'data-garden-version': PACKAGE_VERSION
})`
${props => retrieveComponentStyles(COMPONENT_ID, props)};
`;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

RE: #1804 (comment)

After removing all code causing a breaking-change, we would be left with:

export const StyledButton = styled(Button)``

So I think it's better to delete.

@ze-flo
Copy link
Contributor Author

ze-flo commented May 16, 2024

Verified that breaking-changes have been removed and that the data-garden-id matches with V8 for the following components:

  • GlobalAlert.Button
  • GlobalAlert.Close
  • ColorSwatchDialog
  • Menu
  • Pane.SplitterButton

This also aligns with updated specs.

Screenshot 2024-05-16 at 10 37 19 AM

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I'm missing something, but I'm not seeing the need for changes to StyledAnchor in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was applying the new dev pattern consistently to all Styled buttons that could be extended. At this time, however, neither StyledAnchor nor Anchor are extended anywhere else.

Should I rollback?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, please. Again, let's keep this somewhat hack as minimal as possible so that we have less to unwind when package consolidation is a thing.

Copy link
Member

Choose a reason for hiding this comment

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

Can this rollback now that there are no more competing styled(Button) extensions? The remaining styled(StyledButton) extension still works as expected without this, 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.

If I'm understanding your questions correctly, rolling back would create a breaking-change with the Modal.Close button in #1808. As you can see below, the data-garden-id will be buttons.icon_button rather than modals.close.

demo.mp4

You can see this in action by updating line 51 in the Sandbox.

Screen Recording 2024-05-17 at 5 41 52 AM

Copy link
Member

Choose a reason for hiding this comment

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

Ah, got it. Thanks for the clarification/reminder. Sorry, my brain is slow on accepting this (anti)pattern 😜

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The crazy magic built into styled-components took me a while to grasp and is forcing us down a less-than-ideal path. I definitely wanna revisit this in the near future to find a better solution.

@ze-flo ze-flo merged commit 12c7917 into next May 17, 2024
5 checks passed
@ze-flo ze-flo deleted the ze-flo/button-override-attrs branch May 17, 2024 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

4 participants