-
Notifications
You must be signed in to change notification settings - Fork 1
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
Refactor to make the reminder button configurable #430
Conversation
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 refactoring! Good shout to make this a separate PR. I've left some comments/suggestions on the types, see what you think.
@@ -36,7 +36,7 @@ const loadingAnimCss = css` | |||
} | |||
`; | |||
|
|||
export const LoadingDots = (): React.ReactElement => { | |||
export const buildLoadingDots = (fillStyle = '#707070'): React.ReactElement => { |
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.
Would it be more conventional maybe to have this continue to be named LoadingDots
(it is a component after all, not a function which returns a component)? The fillStyle
could be passed as an (optional) prop, which would allow it to be called in the normal way: <LoadingDots fillStyle="#121212" />
. What do you think?
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.
Current code matches the factory approach we have to generating CSS, but I'm happy to turn this into a component. Will action.
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.
Done!
src/components/ReminderCtaButton.tsx
Outdated
margin-top: ${space[2]}px; | ||
${body.small()}; | ||
`, | ||
const getButtonStyles = (userVals: Extras, defaults: ColorStylesData) => { |
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 wonder if we could be more descriptive/specific with the types of these args. Extras
is very generic and doesn't provide much information about what might be available. My understanding is getColors
takes some colours which might be provided and some defaults which will be provided and comes up with a new instance of ColorStylesData
which is guaranteed to have specific fields set (if we can't get the colour from the user provided values then fall back on the defaults).
What if the type of the user provided styles was called something like UserDefinedColorStylesData
and it looked something like this (clipped for brevity):
export interface UserDefinedColorStylesData {
styleBackground?: string;
styleHeader?: string;
styleBody?: string;
}
So any of those fields may be there, but could also be missing. I think this tells us more about the possible user provided styles than Extras
does. Then we could define ColorStylesData
as the type which is guaranteed to have all these fields:
type ColorStylesData = Required<UserDefinedColourStylesData>;
Then the type of getColors
becomes: (userVals: UserDefinedColorStylesData, defaults: ColorStylesData): ColorStylesData
.
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.
Will take this suggestion forward. We can pair if I run into TS issues?
If we can get rid of Extras
... I wouldn't cry. I think there's another type with the same definition somewhere.
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 can pair if I run into TS issues?
Definitely! 👍
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've given it a go. Discuss tomorrow?
src/components/ReminderCtaButton.tsx
Outdated
${body.small()}; | ||
`, | ||
const getButtonStyles = (userVals: Extras, defaults: ColorStylesData) => { | ||
const styles = getColors(userVals as Extras, defaults); |
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.
Currently the return value of getColours
is ColorStylesData
and all of the fields in that type are optional - but we interpolate them into the CSS styles below as though they are guaranteed to be present. I think returning a type where the fields are guaranteed to be there would be a win in terms of type safety (as in the comment above). What do you reckon? Happy to chat this over/pair if it's useful!
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 above, will give it a go
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.
See above
interface ReminderCtaButtonProps { | ||
reminderComponent: ReminderComponent; |
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!
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.
Thx
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 looking good. I've left a bunch of feedback, see what you think. If we wanted to action the points it could be done on this PR or in a follow up. Happy to chat if anything doesn't make sense (or if I've missed the point with any of it).
styleReminderButton?: string; | ||
styleReminderButtonBackground?: string; | ||
styleReminderButtonHover?: string; | ||
styleReminderAnimation?: 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.
Great idea to group these together, I think it makes it easier to understand what's configurable colour-wise here 👍
styleCloseHover: ColorValueHex; | ||
} | ||
|
||
export interface StyleableBannerColorStyles extends ReminderButtonColorStyles, BannerColorStyles {} |
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 interface implemented like this because only the styleable banner supports the reminder buttons? And other banners don't?
@@ -10,14 +10,15 @@ import type { TrackClick } from '../utils/tracking'; | |||
|
|||
import { AppStore } from '../assets/app-store'; | |||
import { PlayStore } from '../assets/play-store'; | |||
import { StyleData, selfServeStyles } from '../styles/bannerCommon'; | |||
import { BannerColorStyles } from '../styles/colorData'; | |||
import { selfServeStyles } from '../styles/bannerCommon'; |
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 a thing that's been confusing me about the configurable colours is that AppBanner
, BannerNewsletter
& friends, which don't support configuring the colours, still call selfServeStyles
. I think that's because selfServeStyles
serves two purposes:
- Takes user provided styles, and integrates these with defaults (if, for example, the user provides invalid values, or if certain colours are missing)
- Generates CSS from a the colour styles object.
In the case of the banners which don't have configurable colours, we only need the second of those two purposes. This could be done in a follow up PR, but I think it would improve the understandability of the code to de-couple those two things. So instead of selfServeStyles
taking user provided colours and defaults it just takes colours which are guaranteed to be present and returns the CSS. It wouldn't have to care about where the colours came from (for AppBanner
etc they'd be hardcoded, for StyleableBannnerWithLink
they come from combining user provided colours with defaults). At the top level of StyleableBannerWithLink
it'd have to do the combining of user provided colours and defaults, using getColors
, and selfServeStyles
wouldn't call getColors
.
Thoughts?
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.
My long-term utopia is to have a single epic component, and a single banner component (both styleable, though in the case of the epic limited to eg CTA button colours) which Retention can setup/preview in Storybook. Though the competing vision of being able to use RRCP banner/epic designs from Braze is an even better utopia destination.
src/styles/colorData.ts
Outdated
|
||
type ColorStylesType = keyof AllAvailableColorStyles; | ||
|
||
export function getColors(userVals: Extras, defaults: Partial<AllAvailableColorStyles>) { |
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 wonder if using generics here would be a better way to describe the interface of getColours
? I don't think getColors
would ever be called with an object which had properties of both BannerColorStyles
and EpicColorStyles
, hence the need for Partial
. If it was generic it called be called concretely with the specific relevant type acknowledging the fact that it would be called with a specific concrete type:
function getColors(userVals: Extras, defaults: T): T {
// ...
}
// then called like:
getColors<BannerColorStyles>(userVals, defaultBannerColours)
getColors<EpicColorStyles>(userVals, defaultEpicColors)
/// 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.
We've agreed to look at this in a future PR
reminderStage={reminderStage} | ||
reminderOption={reminderOption} | ||
ophanComponentId={ophanComponentId as string} | ||
trackClick={trackClick} | ||
fetchEmail={fetchEmail} | ||
userStyles={defaultColors} |
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.
What about calling this prop colors
or similar? I think userStyles
isn't always accurate (in this case they're hardcoded for example).
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'll take this forward in the next PR
src/components/ReminderCtaButton.tsx
Outdated
userVals: Partial<ReminderButtonColorStyles>, | ||
defaults: ReminderButtonColorStyles, | ||
) => { | ||
const styles = getColors(userVals as Partial<ReminderButtonColorStyles>, defaults); |
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 in the comment above on selfServeStyles
I wonder if it'd be better to decouple the two concerns here (merging user provided vals and defaults) and returning CSS. In some cases there won't be user provided styles as they're hardcoded/not configurable. So perhaps the places that need to can do the combining and then call this function with a complete set of colours.
Refactor getColors to be generic
Add some tests for getColors
What does this change?
This PR refactors some code which will make the work of adding the reminders button to the styleable banner component easier.
The changes are:
styles/colorData.ts
file to colocate colour styling related types and functions in one place.styles/bannerCommon.ts
into the new file. TheAppsBanner
,BannerNewsletter
,BannerWithLink
,Epic
andStyleableBannerWithLink
components have been updated to reflect these changes, as has theContributionCtaButton
component.components/CtaLoadingDotsAnimation
component has been updated to return a function to output the SVG, which can accept a colour string argument. ThenewsletterCommon/index.tsx
andcomponents/ReminderCtaButton.tsx
components have been updated to use the functioncomponents/ReminderCtaButton.tsx
component. These changes mean that the component can now be styled by user-defined colour strings, with default values for those strings based on the existing colours of the reminder button. The colour types and objects now in thestyles/colorData.ts
file have been updated to include these new optional attributes.How to test
All chromatic tests should pass straight away as this PR is an internal refactor which shouldn't affect graphical output.