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

Set up V3 Typography component #570

Merged
merged 10 commits into from
Oct 1, 2024
98 changes: 98 additions & 0 deletions src/typography/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { css } from "styled-components";
import { TypographyProps, TypographySizeType, TypographyWeight } from "./types";
import { TypographyStyle } from "./typography-style";
import { Colour, Font } from "../theme";
import { StyledComponentProps } from "../theme/helpers";

const getResolvedTypographyWeight = (
weight: TypographyWeight,
qroll marked this conversation as resolved.
Show resolved Hide resolved
props: StyledComponentProps
): string => {
const weightMap: Record<number, string> = {
300: "light",
400: "regular",
600: "semibold",
700: "bold",
};

// If resolvedWeight is a string that is a number for eg "400", convert it to a number
const numericWeight =
typeof weight === "string" && !isNaN(Number(weight))
? Number(weight)
: weight;

// Map it to its string equivalent
const mappedWeight = weightMap[numericWeight as number] || numericWeight;
const finalWeight = Font[`weight-${mappedWeight}`];

// If final weight is a function, resolve it with props
return typeof finalWeight === "function" ? finalWeight(props) : finalWeight;
};

export const getTypographyStyle = (
type: TypographySizeType,
weight: TypographyWeight,
paragraph = false
) => {
return (props: any) => {
const attrs = TypographyStyle[type];

const resolvedWeight = getResolvedTypographyWeight(weight, props);

// Check if function if so resolve with props
const fontSize =
typeof attrs.fontSize === "function"
? attrs.fontSize(props)
: attrs.fontSize;

// Make it a int for calc
const fontSizeValue = parseFloat(fontSize);
const fontSizeUnit = fontSize.replace(fontSizeValue.toString(), "");

// Add extra margin for paragraphs
const getMarginBottomStyle = () => {
const marginBottomScale = paragraph ? 1.05 : 0;
return css`
margin-bottom: ${fontSizeValue * marginBottomScale}${fontSizeUnit};
`;
};

return css`
font-size: ${fontSize};
line-height: ${attrs.lineHeight};
letter-spacing: ${attrs.letterSpacing || 0};
font-weight: ${resolvedWeight};
qroll marked this conversation as resolved.
Show resolved Hide resolved
${getMarginBottomStyle()}
qroll marked this conversation as resolved.
Show resolved Hide resolved
`;
};
};

export const getDisplayStyle = (inline = false, paragraph = false) => {
if (paragraph) {
return css`
display: block;
`;
} else if (inline) {
return css`
display: inline;
`;
} else {
return css`
display: block;
`;
}
};

// Helper func to refactor code
export const createTypographyStyles = (
textStyle: TypographySizeType,
props: TypographyProps
) => css`
${getTypographyStyle(
textStyle,
props.weight || "regular",
props.paragraph
)(props)}
color: ${Colour.Primitive["neutral-20"]};
qroll marked this conversation as resolved.
Show resolved Hide resolved
${getDisplayStyle(props.inline, props.paragraph)}
`;
51 changes: 51 additions & 0 deletions src/typography/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export type TypographySizeType =
qroll marked this conversation as resolved.
Show resolved Hide resolved
| "HeaderXXL"
| "HeaderXL"
| "HeaderLG"
| "HeaderMD"
| "HeaderSM"
| "HeaderXS"
| "BodyBL"
| "BodyLG"
| "BodyMD"
| "BodySM"
| "LinkBL"
| "LinkMD"
| "LinkLG"
| "LinkSM";

export interface TypographyStyleSpec {
qroll marked this conversation as resolved.
Show resolved Hide resolved
fontSize?: number | undefined;
fontWeight?: number | undefined;
lineHeight?: number | undefined;
letterSpacing?: number | undefined;
}

export type TypographyWeight =
| "regular"
| "semibold"
| "bold"
| "light"
| 400
| 600
| 700
| 300;

export type TextStyleSetType = {
qroll marked this conversation as resolved.
Show resolved Hide resolved
[key in TypographySizeType]: TypographyStyleSpec;
};

export interface TypographyProps extends React.HTMLAttributes<HTMLElement> {
// Can be any weight such as regular or 400
weight?: TypographyWeight;
qroll marked this conversation as resolved.
Show resolved Hide resolved
// For consumer to choose if they want the text to be inline for example
inline?: boolean;
// For consumer to choose for block level style
paragraph?: boolean;
}

export interface LinkProps extends TypographyProps {
qroll marked this conversation as resolved.
Show resolved Hide resolved
// If the link is external
external?: boolean;
textStyle?: TypographySizeType;
qroll marked this conversation as resolved.
Show resolved Hide resolved
}
75 changes: 75 additions & 0 deletions src/typography/typography-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Font } from "../theme";

export const TypographyStyle = {
HeaderXXL: {
fontSize: Font["header-size-xxl"],
lineHeight: Font["header-lh-xxl"],
letterSpacing: Font["header-ls-xxl"],
},
HeaderXL: {
fontSize: Font["header-size-xl"],
lineHeight: Font["header-lh-xl"],
letterSpacing: Font["header-ls-xl"],
},
HeaderLG: {
fontSize: Font["header-size-lg"],
lineHeight: Font["header-lh-lg"],
letterSpacing: Font["header-ls-lg"],
},
HeaderMD: {
fontSize: Font["header-size-md"],
lineHeight: Font["header-lh-md"],
letterSpacing: Font["header-ls-md"],
},
HeaderSM: {
fontSize: Font["header-size-sm"],
lineHeight: Font["header-lh-sm"],
letterSpacing: Font["header-ls-sm"],
},
HeaderXS: {
fontSize: Font["header-size-xs"],
lineHeight: Font["header-lh-xs"],
letterSpacing: Font["header-ls-xs"],
},

BodyBL: {
fontSize: Font["body-size-baseline"],
lineHeight: Font["body-lh-baseline"],
letterSpacing: Font["body-ls-baseline"],
},
BodyLG: {
fontSize: Font["body-size-lg"],
lineHeight: Font["body-lh-lg"],
letterSpacing: Font["body-ls-lg"],
},
BodyMD: {
fontSize: Font["body-size-md"],
lineHeight: Font["body-lh-md"],
letterSpacing: Font["body-ls-md"],
},
BodySM: {
fontSize: Font["body-size-sm"],
lineHeight: Font["body-lh-sm"],
letterSpacing: Font["body-ls-sm"],
},
LinkBL: {
fontSize: Font["body-size-baseline"],
lineHeight: Font["body-lh-baseline"],
letterSpacing: Font["body-ls-baseline"],
},
LinkLG: {
fontSize: Font["body-size-lg"],
lineHeight: Font["body-lh-lg"],
letterSpacing: Font["body-ls-lg"],
},
LinkMD: {
fontSize: Font["body-size-md"],
lineHeight: Font["body-lh-md"],
letterSpacing: Font["body-ls-md"],
},
LinkSM: {
fontSize: Font["body-size-sm"],
lineHeight: Font["body-lh-sm"],
letterSpacing: Font["body-ls-sm"],
},
};
106 changes: 106 additions & 0 deletions src/typography/typography.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import styled, { css } from "styled-components";
import {
createTypographyStyles,
getDisplayStyle,
getTypographyStyle,
} from "./helper";
import { Colour } from "../theme";
import { LinkProps, TypographyProps, TypographySizeType } from "./types";
import { ExternalIcon } from "@lifesg/react-icons/external";

export namespace Typography {
const createHeader = (
tag: keyof JSX.IntrinsicElements,
textStyle: TypographySizeType
) => {
const Header = styled(tag)`
${(props: TypographyProps) =>
createTypographyStyles(textStyle, props)}
`;
Header.displayName = `Header-${textStyle}`;
return Header;
};

export const HeaderXXL = createHeader("h1", "HeaderXXL");
export const HeaderXL = createHeader("h2", "HeaderXL");
export const HeaderLG = createHeader("h3", "HeaderLG");
export const HeaderMD = createHeader("h4", "HeaderMD");
export const HeaderSM = createHeader("h5", "HeaderSM");
export const HeaderXS = createHeader("h6", "HeaderXS");

const createBody = (textStyle: TypographySizeType) => {
const Body = styled.p`
${(props: TypographyProps) =>
createTypographyStyles(textStyle, props)}
`;
Body.displayName = `Body-${textStyle}`;
return Body;
};

export const BodyBL = createBody("BodyBL");
export const BodyLG = createBody("BodyLG");
export const BodyMD = createBody("BodyMD");
export const BodySM = createBody("BodySM");

const createLinkComponent = (textStyle: TypographySizeType) => {
const Component = (props: LinkProps) => (
<HyperlinkComponent {...props} textStyle={textStyle} />
);
Component.displayName = `Link-${textStyle}`;
return Component;
};

export const LinkBL = createLinkComponent("LinkBL");
export const LinkMD = createLinkComponent("LinkMD");
export const LinkLG = createLinkComponent("LinkLG");
export const LinkSM = createLinkComponent("LinkSM");
}

console.log("Hello");
qroll marked this conversation as resolved.
Show resolved Hide resolved

// FOR LINK :
export const StyledExternalIcon = styled(ExternalIcon)`
height: 1em;
qroll marked this conversation as resolved.
Show resolved Hide resolved
width: 1em;
margin-left: 0.4em;
vertical-align: middle;
`;

const HyperlinkBase = styled.a<LinkProps>`
${(props) => {
return css`
${getTypographyStyle(
props.textStyle,
props.weight || "regular"
)(props)}
color: ${Colour.hyperlink};
text-decoration: none;

:hover,
:active,
:focus {
color: ${Colour["text-hover"]};

svg {
color: ${Colour["text-hover"]};
}
}

${getDisplayStyle(props.inline, props.paragraph)}
`;
}}
`;

const HyperlinkComponent = ({
external = false,
children,
...rest
}: LinkProps) => {
return (
<HyperlinkBase external={external} {...rest}>
{children}
{external && <StyledExternalIcon />}
</HyperlinkBase>
);
};
HyperlinkComponent.displayName = "HyperlinkComponent";
38 changes: 38 additions & 0 deletions stories/typography-test/Typography-Body.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ThemeProvider } from "styled-components";
import { Typography } from "../../src/typography/typography";
import { mockOverrideTheme, mockTheme } from "./mock-theme";

export default {
title: "Typography-Test/Body",
component: Typography,
};

export const Body_Inline = () => (
<ThemeProvider theme={mockTheme}>
qroll marked this conversation as resolved.
Show resolved Hide resolved
<Typography.BodyBL weight="bold" inline>
Testing for BodyBL and Bold with inline
</Typography.BodyBL>
<br />
<Typography.BodyLG weight="regular" inline>
Testing for BodyLG and SemiBold with inline
</Typography.BodyLG>
<br />
<Typography.BodyMD weight="light" inline>
Testing for BodyMD and light with inline
</Typography.BodyMD>
</ThemeProvider>
);

export const Body_Paragraph = () => (
<ThemeProvider theme={mockOverrideTheme}>
<Typography.BodyBL weight="bold" paragraph>
Testing for BodyBL and Bold with paragraph
</Typography.BodyBL>
<Typography.BodyLG weight="regular" paragraph>
Testing for BodyLG and regular with paragraph
</Typography.BodyLG>
<Typography.BodyMD weight="light" paragraph>
Testing for BodyMD and Light with paragraph
</Typography.BodyMD>
</ThemeProvider>
);
Loading
Loading