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

Add STYLING.md to clarify styling best practices #2185

Merged
merged 7 commits into from
Apr 26, 2021
Merged

Conversation

marcaaron
Copy link
Contributor

@marcaaron marcaaron commented Mar 31, 2021

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

@marcaaron marcaaron self-assigned this Mar 31, 2021
STYLE.md Show resolved Hide resolved
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.
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 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.

Copy link
Contributor

@roryabraham roryabraham Apr 1, 2021

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?

Copy link
Contributor Author

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.

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 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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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: {},
}

Copy link
Contributor

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}>

Copy link
Contributor Author

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 ?

Copy link
Contributor

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
Copy link
Contributor Author

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`.
Copy link
Contributor Author

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.

Copy link
Contributor

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).

Copy link
Contributor Author

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.

STYLING.md Outdated Show resolved Hide resolved

## 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.
Copy link
Contributor Author

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

Copy link
Contributor

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:

  1. Built-in React Native components such as View and Pressable can take either and object or array of styles, so having our components follow the same pattern is a good thing for consistency.
  2. 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 🤷🏼‍♂️
  3. 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 and styles == Array might slip through the cracks and end up not being thoroughly enforced.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.
Copy link
Contributor

@roryabraham roryabraham Apr 1, 2021

Choose a reason for hiding this comment

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

I've also used a pattern a few times of creating functions in a style file to create dynamic styles like here and here. What are our thoughts on that? I sort of think it's fine, but we could also move those same functions into styles.js.

Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

  1. All styles must be defined in the /styles directory and style.js contains the final export after gathering all appropriate styles.
  2. Remove "of sorts"

@marcaaron marcaaron changed the title [WIP] Add STYLING.md to clarify styles Add STYLING.md to clarify styles Apr 20, 2021
@marcaaron marcaaron marked this pull request as ready for review April 20, 2021 17:28
@marcaaron marcaaron requested a review from a team as a code owner April 20, 2021 17:28
@marcaaron marcaaron changed the title Add STYLING.md to clarify styles Add STYLING.md to clarify styling best practices Apr 20, 2021
@MelvinBot MelvinBot requested review from nkuoch and removed request for a team April 20, 2021 17:28
@marcaaron marcaaron requested review from tgolen and a team April 20, 2021 17:28
@MelvinBot MelvinBot removed the request for review from a team April 20, 2021 17:29
@marcaaron
Copy link
Contributor Author

@tgolen curious to get your thoughts on these proposed changes if you have some time

Copy link
Contributor

@tgolen tgolen left a 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.
Copy link
Contributor

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.

  1. All styles must be defined in the /styles directory and style.js contains the final export after gathering all appropriate styles.
  2. 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`.
Copy link
Contributor

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.
Copy link
Contributor

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.

@stitesExpensify stitesExpensify removed the request for review from nkuoch April 20, 2021 19:24
@marcaaron
Copy link
Contributor Author

Thanks @tgolen. Made some updates based on your feedback.

tgolen
tgolen previously approved these changes Apr 20, 2021
roryabraham
roryabraham previously approved these changes Apr 21, 2021
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.
Copy link
Contributor

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.

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 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.

Copy link
Contributor

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.

Copy link
Contributor

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.
Copy link
Contributor

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.

@marcaaron marcaaron dismissed stale reviews from roryabraham and tgolen via 0467521 April 22, 2021 17:22
@roryabraham roryabraham mentioned this pull request Apr 23, 2021
5 tasks
@marcaaron
Copy link
Contributor Author

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.

@marcaaron marcaaron merged commit ed3c40c into main Apr 26, 2021
@marcaaron marcaaron deleted the marcaaron-stylingDoc branch April 26, 2021 20:28
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging in version: 1.0.31-1🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

OSBotify commented May 8, 2021

🚀 Deployed to production in version: 1.0.39-5🚀

platform result
🤖 android 🤖 failure ❌
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants