-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Add STYLING.md to clarify styling best practices #2185
Conversation
STYLING.md
Outdated
|
||
## When to Create a New Style | ||
|
||
If we need some minimal set of styling rules applied to a component then it's almost always better to use an array of helper styles rather than create an entirely new style if it will only be used once. Resist the urge to create a new style for any new element added to a screen. There is a very good chance the style we are adding is a "single-use" style. |
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 feel like this is mostly correct based on a lot of what I'm seeing so far. But maybe @shawnborton has some thoughts on this.
Another thought I had was that maybe it's OK to move an array of styles to styles.something
just from a code organization perspective. Since otherwise we'd have these massive lists inline arrays of helpers everywhere (which is kind of where we're headed now).
Still, it's difficult at times to tell whether to create a new style in styles.js
or to use a big array of helpers.
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 personally prefer to see a new style created whenever we're composing more than two or three styles inline (especially if none of those styles are conditional). My reasoning is that we don't want to muddle our JSX with too much style logic inline, because I think it reduces readability of the code. And composing 4 new styles is basically equivalent to creating a new one. Even in style.js
, however, we can take advantage of all the utilities such as our color themes, spacing, fonts, flex, etc... that are nice to be able to adjust globally.
Maybe we could provide some concrete guidelines by imposing a rule-of-three here and agree concretely that composing three styles inline is acceptable, but more than that should be created in a separate file?
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.
Thanks. I think this is probably the most contentious thing I want to introduce here - but mostly providing this as a starting point. I think the suggestion to have no more than inline 3 helpers is interesting, but I'm not totally confident about it. I worry it can lead to lots of "one off" styles that are never re-used again. But maybe that's OK?
Would love to get @shawnborton's take on this if he's got some perspective to share that might help us settle on something. I'm not 100% sure what would be best and would like to lean on his experience to help establish a baseline.
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 if the component will be a truly reusable component, it makes sense to try to put all of the styles together under one "class." Like take buttons for example, we don't want someone to need to know to use the right border radius, background color, font color, and font size. So I actually think I lean a little bit towards the opposite of what you are saying.
Now I do think we should encourage people to use helper styles for things like spacing, layout, flex, etc. But any kind of component that will be reused multiple times, let's give it a class. But maybe one thing to distinguish is that each reusable component is essentially made up of other smaller components. So you should always use (existing) variables for things like font size, colors, radius, etc. Thoughts on that?
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.
Sorry for the late reply here @shawnborton.
we should encourage people to use helper styles for things like spacing, layout, flex, etc. But any kind of component that will be reused multiple times, let's give it a class.
I think you are essentially saying we should do a "class" or style definition over a React component, but want to clarify since "component" could mean different things to different people.
If we could distill what you are saying into some kind of rule what would it be?
I lean a little bit towards the opposite of what you are saying
It sounds like we are generally agreeing that "single use styles" are bad but not sure which thing you are opposed to, so let us know.
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.
Yeah, maybe we are actually agreeing on most of this. I agree that single use styles are bad, we want people to look at the existing design system and reuse what's there if possible rather than creating something new for a one-off scenario.
I think you are essentially saying we should do a "class" or style definition over a React component, but want to clarify since "component" could mean different things to different people.
Totally, from a non-technical perspective, I am saying that that any kind of UI object (button, form, chat row, etc) that will be reused, let's encapsulate it into one object that has additional modifiers. Let me know if that generally makes sense.
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.
Nice. So, by encapsulate into one object that has additional modifiers do we just mean adopt some naming convention where the modifiers are also "classes"? Probably the button*
styles are the clearest example of where we've begun to do something like this...
const styles = {
button: {},
buttonSuccess: {},
buttonDisable: {},
buttonSmallText: {},
}
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.
Yup I think that's a great example. Though in the future, I think the button component would be a great candidate for some kind of prop/value convention where we'd have something roughly like <button size=small style=secondary styles={anything extra could go here like width utilities, etc}>
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.
Cool, yeah I agree with the prop/value convention. Part of my motivation with adding the STYLING.md
stuff is to maybe codify these ideas into a set of rules so we have an easy way to guide people on how to approach styles and refactorings...
What if we did something like:
- If an array of styles is used 3 times it should be given it's own style object e.g.
styles.button
- Variations on that style should follow a naming convention of
styles.componentModifer
e.g.styles.buttonSuccess
- If a major style has more than 3 usages and also multiple modifier styles it should be moved into a re-usable component so styles can be modified via props
I think that might be a good framework to start with ?
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.
That sounds great to me.
STYLING.md
Outdated
|
||
- A new component with that same array of helper styles | ||
|
||
- A new style that is a composite of all helper styles |
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.
Open to other interpretations on this. "Rule of Three" seems pretty simple to me and helps shut down disagreements about what is expected.
Less sure about a guiding principle for when we should create a new component vs. a new style object
STYLING.md
Outdated
**Inline styles are forbidden.** If we run into a case where we feel it's necessary to conditionally render some styles. There are two options: | ||
|
||
1. Create a helper function in `styles.js` and pass any modifying parameters to that function. | ||
1. Conditionally render a style object inline by defaulting to `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 could probably also just settle on one of these options so that the guidance is clearer when this pops up. I think the helper function style makes the most sense.
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'm happy to settle on just using helper methods, but I'm not opposed to leaving both methods for now (I'm not sure how much this happens).
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.
Not too often but often enough that we will have tons eventually. But yeah I think it's fine to promote the helper method for now and will simplify the guidance a bit.
|
||
## When and How to Pass Styles via Props | ||
|
||
In some cases, we may want a more complex component to allow a parent to modify a style of one of it's child elements. In other cases, we may have a very simple component that has one child which has a `style` prop. Let's look at how to handle these two examples. |
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.
This is all stemming from this conversation -> #1878 (comment)
I'm cool with whatever we decide to do as long as it's consistent
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.
Yeah, as stated in that other thread, I think that having the extra handling where a style can be an object or an array will be simpler than actually tying it to the plurality of the prop name. I have three reasons:
- Built-in React Native components such as
View
andPressable
can take either and object or array of styles, so having our components follow the same pattern is a good thing for consistency. - React native provides a pretty easy way to deal with styles that can be an array or an object in
StyleSheet.flatten
, and I think that's a pretty easy pattern to pick up on and remember. "If you have a style prop, flatten it!" The docs make me think there might be some performance implications of overusing that though, so maybe the tertiary pattern is better 🤷🏼♂️ - When dealing with contributors (esp. those for whom English is a second language) and reviewing PRs, I just have a feeling that this
style
==Object
andstyles
==Array
might slip through the cracks and end up not being thoroughly enforced.
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.
Nice thoughts.
I guess my concern with making all components behave like React Native components is that we'll always have to use a bunch of StyleSheet.flatten()
everywhere and it makes adding styles to existing default styles kind of tricky.
Maybe if we are going to standardize on something we can just do "always use an array"?
If extra style props are always arrays then we can spread them or pass them directly and don't have to handle different types.
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 like the idea of always standardizing on arrays. I think that solves the problem pretty nicely and makes it very clear how something should be done.
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.
Always using arrays is fine with me.
STYLING.md
Outdated
|
||
## Where to Define Styles | ||
|
||
All styles must be defined in the `styles.js` file which exists as a globally exported stylesheet of sorts for the application. Unlike some React Native applications we are not using `StyleSheet.create()` and instead store styles as plain JS objects. There are many helper styles available for direct use in components and can be found in the `/styles` directory. |
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.
Some similar functions already live in styles.js
here, so we haven't been consistent there.
I really don't have a preference whether those dynamic style generators live in their own files or styles.js
, but we should be consistent.
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.
Yeah I think those function files can be modular but maybe we enforce that they are imported via styles.js
directly so they are all documented there at least?
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 like the idea of modularizing those methods better. I think that style.js
should just be in charge of collecting and being the final export of our styles. There are a couple minor changes I'd like to request for this paragraph.
- All styles must be defined in the
/styles
directory andstyle.js
contains the final export after gathering all appropriate styles. - Remove "of sorts"
@tgolen curious to get your thoughts on these proposed changes if you have some time |
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 love where this is heading!
STYLING.md
Outdated
|
||
## Where to Define Styles | ||
|
||
All styles must be defined in the `styles.js` file which exists as a globally exported stylesheet of sorts for the application. Unlike some React Native applications we are not using `StyleSheet.create()` and instead store styles as plain JS objects. There are many helper styles available for direct use in components and can be found in the `/styles` directory. |
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 like the idea of modularizing those methods better. I think that style.js
should just be in charge of collecting and being the final export of our styles. There are a couple minor changes I'd like to request for this paragraph.
- All styles must be defined in the
/styles
directory andstyle.js
contains the final export after gathering all appropriate styles. - Remove "of sorts"
STYLING.md
Outdated
**Inline styles are forbidden.** If we run into a case where we feel it's necessary to conditionally render some styles. There are two options: | ||
|
||
1. Create a helper function in `styles.js` and pass any modifying parameters to that function. | ||
1. Conditionally render a style object inline by defaulting to `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.
I'm happy to settle on just using helper methods, but I'm not opposed to leaving both methods for now (I'm not sure how much this happens).
|
||
## When and How to Pass Styles via Props | ||
|
||
In some cases, we may want a more complex component to allow a parent to modify a style of one of it's child elements. In other cases, we may have a very simple component that has one child which has a `style` prop. Let's look at how to handle these two examples. |
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 like the idea of always standardizing on arrays. I think that solves the problem pretty nicely and makes it very clear how something should be done.
Thanks @tgolen. Made some updates based on your feedback. |
STYLING.md
Outdated
|
||
## Inline Styles | ||
|
||
**Inline styles are forbidden.** If we run into a case where we feel it's necessary to conditionally render some styles we should create a helper function and import it into `styles.js` then pass any modifying parameters to that function e.g. |
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.
create a helper function and import it into
styles.js
So we decided to have dynamic style generator functions live in separate files, like getModalStyles.js
, instead of putting those functions directly in styles.js
? It's fine with me, just wanted to clarify that this is the intention.
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 they can live directly in styles.js
unless they are very large and there is some necessity to create a new file? But don't really have strong feelings on this point.
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'd like to avoid having a bunch of one-teeny-method-files if we can. So for smaller helpers, I think having them in style.js
is fine. More complex ones are fine to put into their own modules.
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.
Cool, I agree. Maybe we can add a sentence like:
"Small helper functions can be written directly in styles.js
, but larger, more complex dynamic style generators can be put in their own modules"
|
||
## When and How to Pass Styles via Props | ||
|
||
In some cases, we may want a more complex component to allow a parent to modify a style of one of it's child elements. In other cases, we may have a very simple component that has one child which has a `style` prop. Let's look at how to handle these two examples. |
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.
Always using arrays is fine with me.
Gonna merge this one since I don't think there were any other blockers and it will be good to have some styling notes to pass along to contributors in the future. |
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
🚀 Deployed to staging in version: 1.0.31-1🚀
|
🚀 Deployed to production in version: 1.0.39-5🚀
|
Details
cc @roryabraham this is a first draft of some general styling practices so that contributors (and us) have an agreed upon way of doing things.
also wanted to get some input from @shawnborton on some of this stuff.
I have the feeling that some of the styling stuff is getting away from us and want to establish some baseline best practices before things turn into total chaos.
Fixed Issues
Fixes https://github.com/Expensify/Expensify/issues/161044
Tests
N/A
QA Steps
N/A
Tested On
N/A