- 
                Notifications
    
You must be signed in to change notification settings  - Fork 97
 
          fix(Buttons): correctly support overriding default data-garden-id attribute
          #1804
        
          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
Conversation
| direction: ${props => props.theme.rtl && 'rtl'}; | ||
| ${props => retrieveComponentStyles(COMPONENT_ID, props)}; | ||
| ${props => retrieveComponentStyles(props['data-garden-id'], props)}; | 
There was a problem hiding this comment.
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:
| ${props => retrieveComponentStyles(props['data-garden-id'], props)}; | |
| ${retrieveComponentStyles}; | 
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
| export const StyledIconButton = styled(StyledButton).attrs<IButtonProps>(props => ({ | ||
| 'data-garden-id': props['data-garden-id'] || COMPONENT_ID, | 
There was a problem hiding this comment.
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'; | 
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.
| 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. 😅
          SummaryHere are 2 rules-of-thumb when creating Styled components with  
 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.  | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- needs 
migration.mdupdate similar to 3464347 - consider using simple 
data-garden-idcomparison 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 😅
| 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, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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?
| 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, | 
There was a problem hiding this comment.
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, | 
There was a problem hiding this comment.
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, | 
There was a problem hiding this comment.
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?
        
          
                packages/buttons/src/types/index.ts
              
                Outdated
          
        
      | /** @ignore Set identifier to retrieve component styles */ | ||
| 'data-garden-id'?: string; | 
There was a problem hiding this comment.
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.
        
          
                packages/buttons/src/styled/index.ts
              
                Outdated
          
        
      | 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'; | 
There was a problem hiding this comment.
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?
| <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> | 
There was a problem hiding this comment.
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.
| /** @component */ | ||
| export default function retrieveComponentStyles( | ||
| componentId: string, | ||
| componentId: string | undefined, | 
There was a problem hiding this comment.
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/button-override-attrs
data-garden-id attributedata-garden-id attribute
      | export const StyledButton = styled(Button).attrs({ | ||
| 'data-garden-id': COMPONENT_ID, | ||
| 'data-garden-version': PACKAGE_VERSION | ||
| })` | ||
| ${props => retrieveComponentStyles(COMPONENT_ID, props)}; | ||
| `; | 
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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 😜
There was a problem hiding this comment.
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.




Description
### ❗❗ BREAKING CHANGES ❗❗Users targetingButtonandIconButtonusing thedata-garden-idattribute (commonly for testing purposes) should acknowledge these id changes:See previous breaking changes
@zendeskgarden/react-notificationsGlobalAlert.Closebuttons.buttonnotifications.global-alert.button@zendeskgarden/react-colorpickersColorSwatchDialogbuttons.buttoncolorpickers.colordialog_button@zendeskgarden/react-dropdowns(formerlyreact-dropdowns.next)Menubuttons.buttondropdowns.menu.button@zendeskgarden/react-gridPane.SplitterButtonbuttons.icon_buttonpane.splitter_button@zendeskgarden/react-notificationsGlobalAlertbuttons.icon_buttonnotifications.global-alert.closeWe can avoid the breaking change by renaming the new ids to match the previous ones.Detail
data-garden-idfor each button component exported from@zendeskgarden/react-buttonsExample
react-components/packages/notifications/src/styled/global-alert/StyledGlobalAlertButton.ts
Line 20 in 717a8d5
react-components/packages/notifications/src/styled/global-alert/StyledGlobalAlertButton.ts
Lines 92 to 100 in 717a8d5
Checklist
👌 design updates will be Garden Designer approved (add the designer as a reviewer)npm start)⬅️ renders as expected with reversed (RTL) direction🤘 renders as expected with Bedrock CSS (?bedrock)♿ tested for WCAG 2.1 AA accessibility compliance