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
101 changes: 101 additions & 0 deletions src/typography/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { css } from "styled-components";
import { TypographyProps, TypographySizeType, TypographyWeight } from "./types";
import { Colour, Font, Typography } 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 resolvedWeight = getResolvedTypographyWeight(weight, props);

// to make it eg: header-xxl-light
const typographyKey = `${type}-${resolvedWeight.toLowerCase()}`;

// to get the font size from Font token eg header-size-lg
const retrieveSize = (type: string) => {
const [firstPart, secondPart] = type.split("-");
return `${firstPart}-size-${secondPart}`;
};

const fontValue = Font[retrieveSize(type)];

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

// 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`
${Typography[typographyKey]}
${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.text};
${getDisplayStyle(props.inline, props.paragraph)}
`;
42 changes: 42 additions & 0 deletions src/typography/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export type TypographySizeType =
qroll marked this conversation as resolved.
Show resolved Hide resolved
| "header-xxl"
| "header-xl"
| "header-lg"
| "header-md"
| "header-sm"
| "header-xs"
| "body-baseline"
| "body-lg"
| "body-md"
| "body-sm";

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 interface TypographyProps extends React.HTMLAttributes<HTMLElement> {
// Can be any weight such as regular or 400
weight?: TypographyWeight | undefined;
// For consumer to choose if they want the text to be inline for example
inline?: boolean | undefined;
// For consumer to choose for block level style
paragraph?: boolean | undefined;
}

export interface LinkProps extends TypographyProps {
qroll marked this conversation as resolved.
Show resolved Hide resolved
// If the link is external
external?: boolean;
}
99 changes: 99 additions & 0 deletions src/typography/typography.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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", "header-xxl");
export const HeaderXL = createHeader("h2", "header-xl");
export const HeaderLG = createHeader("h3", "header-lg");
export const HeaderMD = createHeader("h4", "header-md");
export const HeaderSM = createHeader("h5", "header-sm");
export const HeaderXS = createHeader("h6", "header-xs");

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

export const BodyBL = createBody("body-baseline");
export const BodyLG = createBody("body-lg");
export const BodyMD = createBody("body-md");
export const BodySM = createBody("body-sm");

const createLinkComponent = (textStyle: TypographySizeType) => {
const HyperlinkBase = styled.a<LinkProps>`
${(props) => css`
${getTypographyStyle(
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)}
`}
`;

// Define the component that uses HyperlinkBase
const Component = ({
external = false,
children,
...rest
}: LinkProps) => (
<HyperlinkBase {...rest}>
{children}
{external && <StyledExternalIcon />}
</HyperlinkBase>
);

Component.displayName = `Link-${textStyle}`;
return Component;
};

export const LinkBL = createLinkComponent("body-baseline");
export const LinkMD = createLinkComponent("body-md");
export const LinkLG = createLinkComponent("body-lg");
export const LinkSM = createLinkComponent("body-sm");
}

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;
`;
36 changes: 36 additions & 0 deletions stories/typography-test/Typography-Body.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Typography } from "../../src/typography/typography";

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

export const Body_Inline = () => (
<>
<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>
</>
);

export const Body_Paragraph = () => (
<>
<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>
</>
);
35 changes: 35 additions & 0 deletions stories/typography-test/Typography-Header.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Typography } from "../../src/typography/typography";

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

// HeaderXXL with bold weight and inline display
export const Header_Inline = () => (
<>
<Typography.HeaderXXL weight="bold" inline>
Testing for HeaderXXL and Bold with inline
</Typography.HeaderXXL>
<Typography.HeaderXL inline>
Testing for HeaderXL and regualar with inline
</Typography.HeaderXL>
<Typography.HeaderLG weight="light" inline>
Testing for HeaderSM and Bold with inline
</Typography.HeaderLG>
</>
);

export const Header_Paragraph = () => (
<>
<Typography.HeaderXXL weight="bold" paragraph>
Testing for HeaderXXL and Bold with paragraph
</Typography.HeaderXXL>
<Typography.HeaderXL paragraph>
Testing for HeaderXL and Bold with paragraph
</Typography.HeaderXL>
<Typography.HeaderLG weight="light" paragraph>
Testing for HeaderSM and Light with paragraph
</Typography.HeaderLG>
</>
);
28 changes: 28 additions & 0 deletions stories/typography-test/Typography-Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Typography } from "../../src/typography/typography";

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

export const LinkBL_Regular = () => (
<>
<Typography.LinkBL weight="regular">
This is a baseline link
</Typography.LinkBL>
</>
);

export const LinkLG_Bold_Inline = () => (
<>
<Typography.LinkLG weight="bold" inline>
This is a bold link with inline display
</Typography.LinkLG>
</>
);

export const LinkSM_External = () => (
<>
<Typography.LinkSM external>External Small Link</Typography.LinkSM>
</>
);
Loading
Loading