Skip to content

[Accepted] ADR: Use Box + sx to author components #2020

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

Merged
merged 12 commits into from
Apr 25, 2022
Merged

Conversation

siddharthkp
Copy link
Member

@siddharthkp siddharthkp commented Apr 12, 2022

There are 2 common ways to create components with primer/react:

  1. with styled-components
  2. with Box + sx

This ADR recommends using the 2nd one exclusively. See rendered markdown →

Next step after ADR is accepted: Update contributor docs.

@changeset-bot
Copy link

changeset-bot bot commented Apr 12, 2022

⚠️ No Changeset found

Latest commit: 845e32a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Apr 12, 2022

size-limit report 📦

Path Size
dist/browser.esm.js 65.55 KB (0%)
dist/browser.umd.js 65.9 KB (0%)

@siddharthkp siddharthkp added the skip changeset This change does not need a changelog label Apr 12, 2022
@siddharthkp siddharthkp marked this pull request as ready for review April 12, 2022 13:09
@siddharthkp siddharthkp requested review from a team and rezrah April 12, 2022 13:09
@siddharthkp siddharthkp requested review from colebemis and removed request for rezrah April 12, 2022 13:10
@siddharthkp siddharthkp changed the title ADR: Use Box + sx to author new components ADR: Use Box + sx for authoring components Apr 12, 2022
@siddharthkp siddharthkp changed the title ADR: Use Box + sx for authoring components ADR: Use Box + sx to author components Apr 12, 2022
Copy link
Contributor

@colebemis colebemis left a comment

Choose a reason for hiding this comment

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

Thanks for writing this, @siddharthkp! 💖


- Better authoring experience with typescript. With Box, we can improve the API and autocomplete for consuming primitives.
- The styling library used used is an implementation detail and we should be able to replace it. (Avoid leaky abstractions)
- We have had issues with exporting types, we can increase confidence by keeping the exported types close to what we author.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you expand on the issues we've had with exporting types?

Copy link
Member Author

Choose a reason for hiding this comment

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

sorry for my lazy phrasing here 😅

What I'm trying to convey here is that

  1. We've had a couple of bugs like #1959 where we were extracting types from a "styled component"
  2. Folks on the memex side have reported that the typew are hard to debug because they have a lot of layers (styled components + styled system)

I feel more confident having more control over what we export + keeping it as flat as possible. Types deserves an ADR of it's own, to be honest.

Copy link
Member

@lesliecdubs lesliecdubs Apr 23, 2022

Choose a reason for hiding this comment

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

Would it be useful to amend the ADR to include some of these additional details for context, either now or in a follow-up PR?

Copy link
Member Author

@siddharthkp siddharthkp Apr 25, 2022

Choose a reason for hiding this comment

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

Going to create a new ADR for types (in new PR), there's more to discuss there because we don't have a standard way right now to propose :)

Co-authored-by: Cole Bemis <colebemis@github.com>
Co-authored-by: Cole Bemis <colebemis@github.com>
thanks Cole!

Co-authored-by: Cole Bemis <colebemis@github.com>
@lesliecdubs
Copy link
Member

As discussed among the PRC maintainers group, let's try assigning a decision-maker here so we can move this ADR toward a conclusion.

ADR decision maker: @rezrah
Decision deadline: Friday, 22 April

I'm going to work on more enduring documentation about this process, but in the meantime:

Your role as decision maker is to gather feedback, probe for discussion and disagreement, and then make an informed decision based on the feedback, identified risks, and trade-offs about whether to adopt the ADR or not. Your role is not to decide based on your own personally preferred approach.

Keep in mind that ADR decisions can be reversed or changed in the future if new information is exposed that make the ADR worth revisiting.

You can fulfill your decision maker role by doing the following:

  • review the ADR, comments, and feedback closely
  • seek additional feedback from any necessary parties, as needed; for example, request a review from any individuals or teams that you know would be unduly affected by this ADR, or who have extensive experience in using either the preferred or non-preferred approach and may have perspective to share about it
  • follow up on any remaining concerns, questions, or unexamined risks around accepting the proposed approach
  • on or before the deadline, make a decision as to whether the ADR is accepted ✅ , which you should signal by approving this PR, or rejected 🚫 , which you should signal by closing this PR with a comment explaining the reason(s)

@lesliecdubs lesliecdubs changed the title ADR: Use Box + sx to author components ADR: Use Box + sx to author components - DEADLINE: Friday 22 April Apr 18, 2022

Prefer using method #2: Creating components with Box for the following reasons:

- Better authoring experience with Typescript. With Box, we can improve the API and autocomplete for consuming primitives.
Copy link
Contributor

Choose a reason for hiding this comment

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

@siddharthkp - can you link to your research, which you mentioned in this comment from the ADR itself for easier discovery? I think it will help to elaborate why working with Primitives might be easier for anyone reading it later.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

Comment on lines 32 to 37
const styles = {
display: 'inline-block',
overflow: 'hidden',
lineHeight: 'condensedUltra',
borderRadius: getBorderRadius({size, square})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Our goals as customers/consumers are:

  1. Ease-of-use in terms of authorship (Be it specifying colors, styles, customization, etc)
  2. Implementing something that is performant with little to no friction
  3. Strongly typed guardrails, so it's easier to implement ("Do I preface this color with colors.? Do I pass a theme here? What does 'space.2' mean?")

I see some pros and cons to this approach:
Pros:

  • This allows consumers to more easily define their styles without as much overhead
  • It's coupled with the component, so there is less mental friction
  • It still provides a solution that is responsive based on props (i.e. dynamic styles)

Cons:

  • The biggest drawback I see is that these styles, because they are implemented inside of the component, would be re-computed every single render cycle. While this might be improved with memoization, I still think there's a good deal of perf impact with this approach
  • I'm not a huge fan of each component needing to run merge() to get styles to work properly, not to mention the added dependency that each app would need to install
  • I'm concerned about the mental friction of implementing everything as a Box and transmuting them into other components by way of as. I always thought of that as an escape hatch more than a common pattern.

I definitely think there are improvements to be made on the implementations of styles, specifically styled-components, but I'm also curious to see what styled-components' plans are now that React 18 is released with all of their improvements and DX hooks for these sorts of libraries.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the intention is to require a dependency on merge @dusave but just that it's the consumers job to merge local and props sx if necessary?

if Box is a Memo component, could we avoid some overhead by doing a deeper comparison on sx props

memo(function Box(props) {...}, function shouldComponentRender(prevProps, nextProps) {
   compare sx deepy, but maintain as object.is equality otherwise
})

which might avoid some/many performance differences from inline definitions of sx?

Copy link
Contributor

@mattcosta7 mattcosta7 Apr 19, 2022

Choose a reason for hiding this comment

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

I'm concerned about the mental friction of implementing everything as a Box and transmuting them into other components by way of as. I always thought of that as an escape hatch more than a common pattern.

this, i think would be just something to extract to module scope and use from there, except when needing the props. how much non-primer usage actually changes based on props though?

Copy link
Member Author

@siddharthkp siddharthkp Apr 20, 2022

Choose a reason for hiding this comment

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

I'm glad we're talking about this. the sx implementation could use a lot of improvements!

The goal of the ADR to standardise using Box + sx for creating new components in primer/react (we are already doing that for a few months), so we can prioritise solving the remaining problems with this approach.

The biggest drawback I see is that these styles, because they are implemented inside of the component, would be re-computed every single render cycle. While this might be improved with memoization, I still think there's a good deal of perf impact with this approach

You're not wrong. We need work performance improvements to make sure the page doesn't slow down over time because of re-renders.

However, injecting styles is the biggest task in the whole process. Because Box still uses styled-components to inject styles in the end, the same styles are not injected twice. The exception to this is prop-based styles which need to be re-evaluated and the additional work required by styled-system on top of styled-components could be a bad fit for use cases like javascript-based animations.

I'm not a huge fan of each component needing to run merge() to get styles to work properly, not to mention the added dependency that each app would need to install

Same. I have a few API ideas on how to simplify the API and abstract the merge away from the component.

For the merge dependency, right now it comes from import {merge} from '@primer/react', but with API changes we should be able to remove it from the public API.

I'm concerned about the mental friction of implementing everything as a Box and transmuting them into other components by way of as. I always thought of that as an escape hatch more than a common pattern.

Can you expand on the mental friction a bit more.

I think you only need to drop down to Box when you're building a new atomic component. <Box as="img"> is equivalent to styled.img.

For most use cases, you should be able to start with a primer/react component, especially as we get more layout components like Stack. However, to give better html semantics, as prop would be the way to go: <Stack as="section"/>

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I was thinking about the Box using as in the wrong light. What I was thinking is that we get into a state where our components are loaded with boxes being converted to other components using as, which would cause mental friction in trying to read through the code. Rather than looking at the tag name, you would have to look for the as attribute to determine what it actually is.

That said, if this is relatively contained to Primer and not something we do in the consuming packages, that's probably fine.

Copy link
Contributor

@mattcosta7 mattcosta7 left a comment

Choose a reason for hiding this comment

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

Is there any consideration for using sx but recommending it be defined in a static scope, with nested classnames, etc being updated instead of the actual sx object?

I think that avoids consumers using styled-components directly, but also avoids the runtime overhead of sx object equality?

Comment on lines 32 to 37
const styles = {
display: 'inline-block',
overflow: 'hidden',
lineHeight: 'condensedUltra',
borderRadius: getBorderRadius({size, square})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the intention is to require a dependency on merge @dusave but just that it's the consumers job to merge local and props sx if necessary?

if Box is a Memo component, could we avoid some overhead by doing a deeper comparison on sx props

memo(function Box(props) {...}, function shouldComponentRender(prevProps, nextProps) {
   compare sx deepy, but maintain as object.is equality otherwise
})

which might avoid some/many performance differences from inline definitions of sx?


```tsx
const Avatar: React.FC<AvatarProps> = ({size = 20, alt = '', square = false, sx = {}, ...props}) => {
const styles = {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we avoid styles here, since it's very close to sounding like inline styles vs localSx? or something like that

should we add types here too?

const localSx: BetterStyledSystemObject (is this still the type to use) = {}

Copy link
Member Author

@siddharthkp siddharthkp Apr 20, 2022

Choose a reason for hiding this comment

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

componentStyles or baseStyles can be a better variable here. I don't want to prescriptive about the variable name in this ADR here because it depends on the context. (for example, Button has baseStyles + variantStyles + sizeStyles)

BetterSystemStyleObject is the correct type to use, however it could use some improvements as well. I'lld add that to the ADR. Example sandbox

Copy link
Contributor

Choose a reason for hiding this comment

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

Totally! Just wanted to make sure we don't cause confusion with inline styles here, more than variable naming!

Comment on lines 32 to 37
const styles = {
display: 'inline-block',
overflow: 'hidden',
lineHeight: 'condensedUltra',
borderRadius: getBorderRadius({size, square})
}
Copy link
Contributor

@mattcosta7 mattcosta7 Apr 19, 2022

Choose a reason for hiding this comment

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

I'm concerned about the mental friction of implementing everything as a Box and transmuting them into other components by way of as. I always thought of that as an escape hatch more than a common pattern.

this, i think would be just something to extract to module scope and use from there, except when needing the props. how much non-primer usage actually changes based on props though?

@siddharthkp
Copy link
Member Author

siddharthkp commented Apr 20, 2022

Is there any consideration for using sx but recommending it be defined in a static scope, with nested classnames, etc being updated instead of the actual sx object?
I think that avoids consumers using styled-components directly, but also avoids the runtime overhead of sx object equality?

Not yet, I only have that in a prototypes outside of primer/react 😅 I can see that happen in the future

@siddharthkp siddharthkp merged commit bbd436e into main Apr 25, 2022
@siddharthkp siddharthkp deleted the adr-box-sx branch April 25, 2022 09:52
@siddharthkp siddharthkp changed the title ADR: Use Box + sx to author components - DEADLINE: Friday 22 April [Accepted] ADR: Use Box + sx to author components Apr 25, 2022
@siddharthkp siddharthkp temporarily deployed to github-pages April 25, 2022 09:54 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
react skip changeset This change does not need a changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants