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

Refactor to make the reminder button configurable #430

Merged
merged 20 commits into from
Nov 13, 2023

Conversation

KaliedaRik
Copy link
Contributor

@KaliedaRik KaliedaRik commented Oct 27, 2023

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:

  • A new styles/colorData.ts file to colocate colour styling related types and functions in one place.
  • The colour-related types and objects, and functionality to validate user-supplied colour strings, has been moved out of styles/bannerCommon.ts into the new file. The AppsBanner, BannerNewsletter, BannerWithLink, Epic and StyleableBannerWithLink components have been updated to reflect these changes, as has the ContributionCtaButton component.
  • The components/CtaLoadingDotsAnimation component has been updated to return a function to output the SVG, which can accept a colour string argument. The newsletterCommon/index.tsx and components/ReminderCtaButton.tsx components have been updated to use the function
  • Most of the refactor in this PR has been to the components/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 the styles/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.

@KaliedaRik KaliedaRik marked this pull request as draft October 27, 2023 16:10
@KaliedaRik KaliedaRik marked this pull request as ready for review November 6, 2023 12:34
Copy link
Member

@tjmw tjmw left a 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 => {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

margin-top: ${space[2]}px;
${body.small()};
`,
const getButtonStyles = (userVals: Extras, defaults: ColorStylesData) => {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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! 👍

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've given it a go. Discuss tomorrow?

${body.small()};
`,
const getButtonStyles = (userVals: Extras, defaults: ColorStylesData) => {
const styles = getColors(userVals as Extras, defaults);
Copy link
Member

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!

Copy link
Contributor Author

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

Copy link
Contributor Author

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;
Copy link
Member

Choose a reason for hiding this comment

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

👏 Nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thx

Copy link
Member

@tjmw tjmw 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 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;
Copy link
Member

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 {}
Copy link
Member

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';
Copy link
Member

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:

  1. Takes user provided styles, and integrates these with defaults (if, for example, the user provides invalid values, or if certain colours are missing)
  2. 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?

Copy link
Contributor Author

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.


type ColorStylesType = keyof AllAvailableColorStyles;

export function getColors(userVals: Extras, defaults: Partial<AllAvailableColorStyles>) {
Copy link
Member

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

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've agreed to look at this in a future PR

reminderStage={reminderStage}
reminderOption={reminderOption}
ophanComponentId={ophanComponentId as string}
trackClick={trackClick}
fetchEmail={fetchEmail}
userStyles={defaultColors}
Copy link
Member

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

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'll take this forward in the next PR

userVals: Partial<ReminderButtonColorStyles>,
defaults: ReminderButtonColorStyles,
) => {
const styles = getColors(userVals as Partial<ReminderButtonColorStyles>, defaults);
Copy link
Member

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.

@KaliedaRik KaliedaRik merged commit ba0b1bb into main Nov 13, 2023
5 checks passed
@KaliedaRik KaliedaRik deleted the rjr-braze-banner-reminder branch November 13, 2023 15:13
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

2 participants