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
Conversation
@@ -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)}; |
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.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
😅
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.
@@ -9,12 +9,12 @@ import { ThemeProps, DefaultTheme } from 'styled-components'; | |||
|
|||
/** @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 targetingButton
andIconButton
using thedata-garden-id
attribute (commonly for testing purposes) should acknowledge these id changes:See previous breaking changes
@zendeskgarden/react-notifications
GlobalAlert.Close
buttons.button
notifications.global-alert.button
@zendeskgarden/react-colorpickers
ColorSwatchDialog
buttons.button
colorpickers.colordialog_button
@zendeskgarden/react-dropdowns
(formerlyreact-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
data-garden-id
for each button component exported from@zendeskgarden/react-buttons
Example
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