Skip to content

Commit

Permalink
feat(theme): add Theme support (#89)
Browse files Browse the repository at this point in the history
* feat(theme): add Theme support

Add Theme with context API
Changes to makeStyles function to accept themeClasses inside useStyles hook
Default Theme and types

Resolves #88

Signed-off-by: Niloy Sikdar <niloysikdar30@gmail.com>

* docs(tsdoc): add TSDoc comments

Add TSDoc comments for the Theme interface and theme props inside Email

Signed-off-by: Niloy Sikdar <niloysikdar30@gmail.com>

Signed-off-by: Niloy Sikdar <niloysikdar30@gmail.com>
  • Loading branch information
niloysikdar committed Aug 22, 2022
1 parent d629549 commit 05544f5
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 37 deletions.
8 changes: 5 additions & 3 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ReactNode, HTMLAttributeAnchorTarget } from 'react';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { useTheme } from '../ThemeProvider';

type ButtonStyles = 'root' | 'primary' | 'secondary';
export type ButtonStyles = 'root' | 'primary' | 'secondary';

/**
* Interface for PropTypes for the `Button` component.
Expand Down Expand Up @@ -44,7 +45,8 @@ export const Button = ({
classes,
className,
}: ButtonProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('button');
const styles = useStyles({ classes }, themeClasses);
const buttonStyle = sx(styles.root, styles[variant]);

return (
Expand Down
8 changes: 5 additions & 3 deletions src/components/Column/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CSSProperties, ReactNode } from 'react';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type ColumnStyles = 'root';
export type ColumnStyles = 'root';

/**
* Interface for PropTypes for the `Column` component.
Expand Down Expand Up @@ -36,7 +37,8 @@ export const Column = ({
align = 'center',
verticalAlign = 'top',
}: ColumnProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('column');
const styles = useStyles({ classes }, themeClasses);

return (
<td className={className} style={sx(styles.root, { verticalAlign })} align={align}>
Expand Down
87 changes: 87 additions & 0 deletions src/components/DefaultTheme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CSSProperties } from 'react';

import type { EmailStyles } from '../Email/Email';
import type { SectionStyles } from '../Section/Section';
import type { ColumnStyles } from '../Column/Column';
import type { ButtonStyles } from '../Button/Button';
import type { LinkStyles } from '../Link/Link';
import type { DividerStyles } from '../Divider/Divider';
import type { TypographyStyles } from '../Typography/Typography';
import type { ImageStyles } from '../Image/Image';
import type { PreheaderStyles } from '../Preheader/Preheader';
import type { QuoteStyles } from '../Quote/Quote';

/**
* Interface for the Deafult and Custom Theme.
*/
export interface ThemeOptions {
/**
* Theme for the `Email` component.
*/
email?: { [key in EmailStyles]?: CSSProperties };
/**
* Theme for the `Section` component.
*/
section?: { [key in SectionStyles]?: CSSProperties };
/**
* Theme for the `Column` component.
*/
column?: { [key in ColumnStyles]?: CSSProperties };
/**
* Theme for the `Button` component.
*/
button?: { [key in ButtonStyles]?: CSSProperties };
/**
* Theme for the `Link` component.
*/
link?: { [key in LinkStyles]?: CSSProperties };
/**
* Theme for the `Divider` component.
*/
divider?: { [key in DividerStyles]?: CSSProperties };
/**
* Theme for the `Typography` component.
*/
typography?: { [key in TypographyStyles]?: CSSProperties };
/**
* Theme for the `Image` component.
*/
image?: { [key in ImageStyles]?: CSSProperties };
/**
* Theme for the `Preheader` component.
*/
preheader?: { [key in PreheaderStyles]?: CSSProperties };
/**
* Theme for the `Quote` component.
*/
quote?: { [key in QuoteStyles]?: CSSProperties };
}

/**
* Default theme for the whole layout.
*/
export const defaultTheme: ThemeOptions = {
email: { root: {} },
section: { root: {}, body: {}, row: {} },
column: { root: {} },
button: {
root: {},
primary: {},
secondary: {},
},
link: { root: {} },
divider: { root: {} },
typography: { root: {} },
image: {
root: {},
body: {},
table: {},
image: {},
caption: {},
imageSection: {},
captionSection: {},
imageColumn: {},
},
preheader: { root: {} },
quote: { root: {} },
};
8 changes: 5 additions & 3 deletions src/components/Divider/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CSSProperties } from 'react';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type DividerStyles = 'root';
export type DividerStyles = 'root';

/**
* Interface for PropTypes for the `Divider` component.
Expand Down Expand Up @@ -54,7 +55,8 @@ export const Divider = ({
size = '1px',
width = '100%',
}: DividerProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('divider');
const styles = useStyles({ classes }, themeClasses);
const hrStyles = sx(styles.root, alignStyles[align], {
borderTop: `${size} ${type} ${color}`,
width: width,
Expand Down
8 changes: 7 additions & 1 deletion src/components/Email/Email.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ Default.args = {
),
classes: {
root: {
backgroundColor: 'gray',
color: 'white',
},
},
theme: {
email: {
root: {
backgroundColor: 'lightgray',
},
},
},
};
26 changes: 23 additions & 3 deletions src/components/Email/Email.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ReactNode } from 'react';
import { BaseStyleProp } from '../types';
import { makeStyles } from '../../utils/makeStyles';
import { ThemeProvider, useTheme } from '../ThemeProvider';
import type { ThemeOptions } from '../DefaultTheme';
import { defaultTheme } from '../DefaultTheme';

type EmailStyles = 'root';
export type EmailStyles = 'root';

/**
* Interface for PropTypes for the `Email` component.
Expand All @@ -12,6 +15,10 @@ export interface EmailProps extends BaseStyleProp<EmailStyles> {
* The content of the component.
*/
children?: ReactNode;
/**
* Applies the custom theme to the whole layout.
*/
theme?: ThemeOptions;
}

const useStyles = makeStyles({
Expand All @@ -21,8 +28,21 @@ const useStyles = makeStyles({
},
});

export const Email = ({ children, className, classes }: EmailProps): JSX.Element => {
const styles = useStyles({ classes });
export const Email = ({ theme = defaultTheme, ...rest }: EmailProps) => {
return (
<ThemeProvider theme={theme}>
<EmailComponent {...rest} />
</ThemeProvider>
);
};

const EmailComponent = ({
children,
className,
classes,
}: Omit<EmailProps, 'theme'>): JSX.Element => {
const themeClasses = useTheme('email');
const styles = useStyles({ classes }, themeClasses);

return (
<div style={styles.root} className={className}>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Image/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CSSProperties } from 'react';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type ImageStyles =
export type ImageStyles =
| 'root'
| 'table'
| 'body'
Expand Down Expand Up @@ -66,7 +67,8 @@ export const Image = ({
className,
captionAlign = 'center',
}: ImageProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('image');
const styles = useStyles({ classes }, themeClasses);

return caption ? (
<table cellPadding="0" cellSpacing="0" role="presentation" style={styles.table}>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ReactNode, HTMLAttributeAnchorTarget } from 'react';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { makeStyles } from '../../utils/makeStyles';
import { useTheme } from '../ThemeProvider';

type LinkStyles = 'root';
export type LinkStyles = 'root';

/**
* Interface for PropTypes for the `Link` component.
Expand Down Expand Up @@ -34,7 +35,8 @@ export const Link = ({
classes,
className,
}: LinkProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('link');
const styles = useStyles({ classes }, themeClasses);

return (
<a href={href} target={target} style={styles.root} className={className}>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Preheader/Preheader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { makeStyles } from '../../utils/makeStyles';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type PreheaderStyles = 'root';
export type PreheaderStyles = 'root';

/**
* Interface for PropTypes for the `Preheader` component.
Expand All @@ -27,7 +28,8 @@ const useStyles = makeStyles({
});

export const Preheader = ({ text, classes, className }: PreheaderProps) => {
const styles = useStyles({ classes });
const themeClasses = useTheme('preheader');
const styles = useStyles({ classes }, themeClasses);

return (
<div style={styles.root} className={className} id="preheader">
Expand Down
8 changes: 5 additions & 3 deletions src/components/Quote/Quote.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ReactNode } from 'react';
import { makeStyles } from '../../utils/makeStyles';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type QuoteStyles = 'root';
export type QuoteStyles = 'root';

/**
* Interface for PropTypes for the `Quote` component.
Expand All @@ -19,7 +20,8 @@ const useStyles = makeStyles({
});

export const Quote = ({ children, classes, className }: QuoteProps) => {
const styles = useStyles({ classes });
const themeClasses = useTheme('quote');
const styles = useStyles({ classes }, themeClasses);

return (
<div style={styles.root} className={className} id="quote">
Expand Down
8 changes: 5 additions & 3 deletions src/components/Section/Section.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ReactNode } from 'react';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { useTheme } from '../ThemeProvider';

type SectionStyles = 'root' | 'body' | 'row';
export type SectionStyles = 'root' | 'body' | 'row';

/**
* Interface for PropTypes for the `Section` component.
Expand Down Expand Up @@ -32,7 +33,8 @@ export const Section = ({
classes,
fullWidth = true,
}: SectionProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('section');
const styles = useStyles({ classes }, themeClasses);

return (
<table
Expand Down
30 changes: 30 additions & 0 deletions src/components/ThemeProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createContext, ReactNode, useContext } from 'react';
import type { ThemeOptions } from '../DefaultTheme';

const ThemeContext = createContext<ThemeOptions>({});

type componentTypes =
| 'email'
| 'section'
| 'column'
| 'button'
| 'link'
| 'divider'
| 'typography'
| 'image'
| 'preheader'
| 'quote';

export const useTheme = (componentName: componentTypes) => {
const theme = useContext(ThemeContext);
return theme[componentName];
};

interface ThemeProviderProps {
theme: ThemeOptions;
children?: ReactNode;
}

export const ThemeProvider = ({ theme, children }: ThemeProviderProps) => {
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
};
10 changes: 6 additions & 4 deletions src/components/Typography/Typography.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { CSSProperties, ReactNode } from 'react';
import { BaseStyleProp } from '../types';
import type { BaseStyleProp } from '../types';
import { makeStyles } from '../../utils/makeStyles';
import { sx } from '../../utils/sx';
import { useTheme } from '../ThemeProvider';

type ButtonStyles = 'root';
export type TypographyStyles = 'root';

/**
* Interface for PropTypes for the `Typography` component.
*/
export interface TypographyProps extends BaseStyleProp<ButtonStyles> {
export interface TypographyProps extends BaseStyleProp<TypographyStyles> {
/**
* The content of the component.
*/
Expand Down Expand Up @@ -122,7 +123,8 @@ export const Typography = ({
className,
align = 'left',
}: TypographyProps): JSX.Element => {
const styles = useStyles({ classes });
const themeClasses = useTheme('typography');
const styles = useStyles({ classes }, themeClasses);

return ComponentMapping[variant]({
children,
Expand Down
Loading

0 comments on commit 05544f5

Please sign in to comment.