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

Introduce decidePalette to DCR #2378

Merged
merged 11 commits into from Jan 26, 2021
Merged

Introduce decidePalette to DCR #2378

merged 11 commits into from Jan 26, 2021

Conversation

nitro-marky
Copy link
Contributor

@nitro-marky nitro-marky commented Jan 18, 2021

What does this change?

Work with @oliverlloyd to begin the implementation of a decidePalette function that, given format: Format, will return the correct colours to be used throughout DCR simply by calling the function decidePallet(format) where format equals:

type Format = { display: Display; design: Design; theme: Theme };

See: https://github.com/guardian/types/blob/main/src/format.ts

We also add the definition of format to decideLayout. This is where we build the format object for an article, based on the CAPI response.

const format: Format = { display, design, theme: pillar };

Any thoughts would be greatly appreciated.

@github-actions
Copy link

github-actions bot commented Jan 18, 2021

Size Change: +510 B (0%)

Total Size: 824 kB

Filename Size Change
dist/frontend.server.js 241 kB +510 B (0%)
ℹ️ View Unchanged
Filename Size Change
dist/atomIframe.js 1.22 kB 0 B
dist/atomIframe.legacy.js 1.22 kB 0 B
dist/dynamicImport.js 1.99 kB 0 B
dist/dynamicImport.legacy.js 2.04 kB 0 B
dist/EditionDropdown.js 767 B 0 B
dist/EditionDropdown.legacy.js 776 B 0 B
dist/elements-RichLinkComponent.js 3.16 kB 0 B
dist/elements-RichLinkComponent.legacy.js 3.25 kB 0 B
dist/elements-YoutubeBlockComponent.js 2.18 kB 0 B
dist/elements-YoutubeBlockComponent.legacy.js 2.3 kB 0 B
dist/embedIframe.js 1.22 kB 0 B
dist/embedIframe.legacy.js 1.23 kB 0 B
dist/ga.js 3.06 kB 0 B
dist/ga.legacy.js 3.54 kB 0 B
dist/GetMatchStats.js 3.24 kB 0 B
dist/GetMatchStats.legacy.js 3.34 kB 0 B
dist/lotame.js 1.15 kB 0 B
dist/lotame.legacy.js 1.16 kB 0 B
dist/MostViewedFooterData.js 6.64 kB 0 B
dist/MostViewedFooterData.legacy.js 6.83 kB 0 B
dist/MostViewedRightWrapper.js 4.87 kB 0 B
dist/MostViewedRightWrapper.legacy.js 5.08 kB 0 B
dist/newsletterEmbedIframe.js 1.15 kB 0 B
dist/newsletterEmbedIframe.legacy.js 1.16 kB 0 B
dist/OnwardsLower.js 9.43 kB 0 B
dist/OnwardsLower.legacy.js 9.69 kB 0 B
dist/OnwardsUpper.js 13.3 kB 0 B
dist/OnwardsUpper.legacy.js 13.6 kB 0 B
dist/ophan.js 7.83 kB 0 B
dist/ophan.legacy.js 8.29 kB 0 B
dist/react.js 128 kB 0 B
dist/react.legacy.js 132 kB 0 B
dist/sentry.js 732 B 0 B
dist/sentry.legacy.js 737 B 0 B
dist/sentryLoader.js 2.02 kB 0 B
dist/sentryLoader.legacy.js 4.54 kB 0 B
dist/shimport.js 3.21 kB 0 B
dist/shimport.legacy.js 3.22 kB 0 B
dist/SignInGateDesignOptVar1.js 1.89 kB 0 B
dist/SignInGateDesignOptVar1.legacy.js 1.9 kB 0 B
dist/SignInGateDesignOptVar2.js 2.09 kB 0 B
dist/SignInGateDesignOptVar2.legacy.js 2.11 kB 0 B
dist/SignInGateDesignOptVar3.js 2.05 kB 0 B
dist/SignInGateDesignOptVar3.legacy.js 2.07 kB 0 B
dist/SignInGateDesignOptVar4.js 2.04 kB 0 B
dist/SignInGateDesignOptVar4.legacy.js 2.06 kB 0 B
dist/SignInGateDesignOptVar5.js 2.44 kB 0 B
dist/SignInGateDesignOptVar5.legacy.js 2.45 kB 0 B
dist/SignInGateDesignOptVar6.js 2.05 kB 0 B
dist/SignInGateDesignOptVar6.legacy.js 2.07 kB 0 B
dist/SignInGateMain.js 2 kB 0 B
dist/SignInGateMain.legacy.js 2.02 kB 0 B
dist/vendors~braze-web-sdk-core.js 34.9 kB 0 B
dist/vendors~braze-web-sdk-core.legacy.js 34.9 kB 0 B
dist/vendors~guardian-braze-components.js 19.2 kB 0 B
dist/vendors~guardian-braze-components.legacy.js 19.2 kB 0 B
dist/vendors~sentry.js 23.5 kB 0 B
dist/vendors~sentry.legacy.js 23.5 kB 0 B

compressed-size-action

@nitro-marky nitro-marky marked this pull request as draft January 18, 2021 16:14
index.d.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@liywjl liywjl left a comment

Choose a reason for hiding this comment

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

Great work 👍 just some minor things to clear up

@oliverlloyd
Copy link
Contributor

This guardian/types#61, along with this #2365 will unblock this PR

@oliverlloyd
Copy link
Contributor

@oliverlloyd
Copy link
Contributor

We need to replace white and black with values from Source. Perhaps, we can assign them to named variables so it's easier to read?

@oliverlloyd oliverlloyd marked this pull request as ready for review January 25, 2021 11:36
@oliverlloyd oliverlloyd removed the 🥒WiP Work in Progress label Jan 25, 2021
Copy link
Contributor

@JamieB-gu JamieB-gu left a comment

Choose a reason for hiding this comment

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

This is exciting!

It got me curious about a couple of design decisions that you've made: prop-drilling and the API. I've rambled a bit below, sketching out my understanding of how you've gone about these and asking a couple questions. Let me know if I've understood correctly and what you think!

Prop-drilling

By this I mean passing the palette down from the "top-level" (i.e. DecideLayout). As far as I can tell this is one of three options, the other two being:

  1. Pass Format down and derive subsets of the palette at the component level, which is what AR has tentatively started doing.
  2. Use a Theme to dependency inject the palette without prop-drilling, which I believe was the direction @oliverlloyd was originally leaning in.

What made you settle on prop-drilling in the end?

The API

The palette here is a single large object, derived from Format, with the colours pulled from nested fields. We discussed the fact that DCR would probably go this route while AR used individual functions, but I can't remember why 🤔.

I'm also interested in the naming system and how that corresponds to what Source does. There's a discussion about this here (featuring @SiAdcock), with the hierarchy defined by Source as:

[theme][property].[component][variant][state][mode]

For a headline text colour I believe this maps to:

  • property: text
  • component: headline
  • variant: primary
  • mode: inverse (only used for dark mode)

and this is why the AR version of this looks something like:

{
  text: {
    headlinePrimary: text.headlinePrimary(format),
    headlinePrimaryInverse: text.headlinePrimaryInverse(format),
  },
  background: {
    headlinePrimary: background.headlinePrimary(format),
    headlinePrimaryInverse: background.headlinePrimaryInverse(format),
  },
}

(I've included background as a parallel example).

I notice that the API defined here looks like this: palette.headline.colour. I think colour corresponds to property and therefore this flips the order of property and component from what I outlined above? Is that right?

src/web/layouts/DecideLayout.tsx Outdated Show resolved Hide resolved
src/web/lib/decidePalette.ts Outdated Show resolved Hide resolved
src/web/lib/decidePalette.ts Show resolved Hide resolved
@oliverlloyd
Copy link
Contributor

What made you settle on prop-drilling in the end?

We've looked at doing less prop drilling before but we always end up keeping it. I don't think there's anything wrong with using a theme and we may well go down that route, but the predictability and simplicity of prop drilling is really nice.

I think this is definately are area where there's a case to be made for it but I think we want to be cautious and approach it in a new PR at a later stage when we better understand our use case and have a known api. I'm also wondering if using a theme via Emotion might have benefits in terms of efficiencies when flipping between dark and light mode?

It's perhaps worth pointing out that I'm - so far - grateful that my previous attempts to add Context failed and I'm actually enjoying good old prop drilling (not all the file changes, that bit is less fun, but the simplicity of it)

@oliverlloyd
Copy link
Contributor

The palette here is a single large object

This was, I think, related to how we prefer prop drilling in DCR. It certainly feels more natural to me to take this appraoch and I'm also a fan of the discoverability of being able to autocomplete on palette.. But I'm not wedded to it and I'm happy to take direction from the team.

@oliverlloyd
Copy link
Contributor

the naming system

This is up for grabs. I definately think it makes sense to align on a pattern and it sounds like you've got a well established one already. I not sure though about having function names the same as property names. I worry this would make refactoring difficult (hard to search or do find and replaces). Maybe instead of

headlinePrimary: text.headlinePrimary(format),

you could

headlinePrimary: text.getHeadlinePrimary(format),

The get prefix highlights that the function and the name of the property are two different things. What do you think?

@oliverlloyd
Copy link
Contributor

headlinePrimary

Is there a need for primary? We don't have a concept in DCR of a secondary. We decide the colour based on format and that's that

@oliverlloyd
Copy link
Contributor

{
  text: {
    headlinePrimary: text.headlinePrimary(format),
    headlinePrimaryInverse: text.headlinePrimaryInverse(format),
  },
  background: {
    headlinePrimary: background.headlinePrimary(format),
    headlinePrimaryInverse: background.headlinePrimaryInverse(format),
  },
}

Would this pattern mean the calling component has to decide between dark and light mode? What about having this logic inside decidePalette so you have a conditional, a bit like:

text: {
   headlinePrimary: dark ? text.headlinePrimary(format) : text.headlinePrimaryInverse(format),
},

(or similar, that api could be better!)

@gtrufitt
Copy link
Contributor

Would this pattern mean the calling component has to decide between dark and light mode?

The palette object would be one big object and the getter functions would handle the light and dark I think?

Just my input reading through:

  • I originally likes the getter in components approach in isolation but within DCR, the prop drilling approach is architecturally consistent. I think stick with it!
  • I think working out and sticking closely to what @SiAdcock is planning in the design system makes sense as the pillar object will likely sit here at some point. I think that's why in the doc we defined as @JamieB-gu mentions

I think we should try and create a type for the palette in @guardian/types and cascade that into AR and DCR - it may be that there are a number of properties on the type that DCR doesn't use in relation particularly to dark mode (or as Oliver mentions the 'secondary' headline type), but having a split in naming or even where the palette is used between the platforms will make it difficult to maintain.

@oliverlloyd
Copy link
Contributor

I think we should try and create a type for the palette in @guardian/types and cascade that into AR and DCR

I think we definately want this 👍 But maybe not straight away. The chore of having to make changes to two repos could be a drag on development when initially creating this object. Or we use a monorepo 😄

@JamieB-gu
Copy link
Contributor

the predictability and simplicity of prop drilling is really nice

This was, I think, related to how we prefer prop drilling in DCR.

I originally likes the getter in components approach in isolation but within DCR, the prop drilling approach is architecturally consistent. I think stick with it!

Fair, I think that's a reasonable preference. What are your thoughts about passing down the whole palette vs passing down Format and deriving subsets of the palette at the component level?

I not sure though about having function names the same as property names

By this do you mean that in the palette object the field is a property but in the standalone text, background etc objects the field is a function? I personally don't have an issue with it because the types tell you what it does (alternatively you can think of a constant as a function with arity 0), but happy to discuss.

Is there a need for primary?

Good question, I don't know. Perhaps one for @SiAdcock?

Would this pattern mean the calling component has to decide between dark and light mode? What about having this logic inside decidePalette so you have a conditional

It does, but typically it already does that because there's often more than one property to change for dark mode. Regarding having it inside decidePalette, I think you'd have to pass whether the user is in dark mode or not when you call it? I can see two potential issues with that:

  1. It won't work server-side because there's no concept of a user with dark mode set at that point.
  2. On the client you'll have to look up whether the user has dark mode set in JS and re-render everything if they do, and also listen for changes to re-render again if they change it. I think it's easier to use the CSS media query?

I think we should try and create a type for the palette in @guardian/types and cascade that into AR and DCR

I think we definately want this 👍 But maybe not straight away

Agree with both of these statements. We started incubating it in AR as well but haven't progressed too far because of other priorities.

@oliverlloyd
Copy link
Contributor

What are your thoughts about passing down the whole palette vs passing down Format and deriving subsets of the palette at the component level?

I think for DCR, it makes sense to pass down the whole palette, at least for now. Mainly because we're used to this pattern but also because I like the simplicity of it, the way decidePalette has one export and does one thing. I'm also afraid that we might reduce discoverability if you have to import a function rather than use an object property to get the value.

I'm very much of the opinion that we should keep an open mind here though and this could well be an area we come back to later after we've spent some time doing things one way and learnt more about how that felt

By this do you mean that in the palette object the field is a property but in the standalone text, background etc objects the field is a function?

I think I just mean that the headlineBackgroundColour property and headlineBackgroundColour() function are named the same thing. To be honest I always thought of this as big no, no. But that's just because I've been conditioned to think this way by compilers and linters shouting at me when I make the mistake of using the same name in two places. Because of this I see it as a mistake but it seems like - and I'm surprised by this - the linting rules allow it in this case so maybe my mental model is wrong? 🤷

One problem with this could be if I am using find and replace or CMD-D to multi select I'll be highlighting both names, but then maybe that's desirable?

Another is, like this then we're constrained to always have one function per property, you can't have one generic function used in multiple places. Is that maybe intentional though?

I think it's easier to use the CSS media query

Yeah, I think you're right, CSS would be a good solution here. But lets take this one out of this PR and discuss it seperately

@oliverlloyd
Copy link
Contributor

@JamieB-gu after discussing the api (same name business) with @nitro-marky we agreed that the approach used in AR is actually fine and so I've modified the code to align on this

I think for drilling palette this works well for DCR at the moment but is something we can revisit.

And let's talk dark mode, but not here.

index.d.ts Show resolved Hide resolved
@JamieB-gu
Copy link
Contributor

JamieB-gu commented Jan 26, 2021

I'll be highlighting both names, but then maybe that's desirable?

I think in this case it probably is because you'll want to keep them in sync?

have one function per property, you can't have one generic function used in multiple places. Is that maybe intentional though?

Maybe? In this case we're defining two APIs that do the same thing:

  1. The palette object for DCR to use: palette.text.headlinePrimary
  2. The functions for AR to use: text.headlinePrimary(format)

We probably want every property to have an equivalent function, and to have the property and function provide the same values and be kept in sync? I think the important thing to remember is that the function versions are also part of the public API - they're just being dog-fooded under the hood by the palette object because that's an easy way to keep them in sync.

Again, if you accidentally use the wrong one (e.g. try to use a function where you're supposed to be using a property) the compiler should tell you.

the approach used in AR is actually fine and so I've modified the code to align on this

I think for drilling palette this works well for DCR at the moment but is something we can revisit.

And let's talk dark mode, but not here.

👍

@nitro-marky nitro-marky merged commit 5f8e3e3 into main Jan 26, 2021
@nitro-marky nitro-marky deleted the ma/decidePalette branch January 26, 2021 15:17
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.

None yet

5 participants