Skip to content

Commit

Permalink
[CCUBE-1545][MAHI]Set up V3 Typography component with its test case a…
Browse files Browse the repository at this point in the history
…nd storybook visualisation
  • Loading branch information
mahidhar-reddy09 committed Sep 11, 2024
1 parent 3e4177e commit e10348b
Show file tree
Hide file tree
Showing 11 changed files with 678 additions and 0 deletions.
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,
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};
${getMarginBottomStyle()}
`;
};
};

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"]};
${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 =
| "HeaderXXL"
| "HeaderXL"
| "HeaderLG"
| "HeaderMD"
| "HeaderSM"
| "HeaderXS"
| "BodyBL"
| "BodyLG"
| "BodyMD"
| "BodySM"
| "LinkBL"
| "LinkMD"
| "LinkLG"
| "LinkSM";

export interface TypographyStyleSpec {
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 = {
[key in TypographySizeType]: TypographyStyleSpec;
};

export interface TypographyProps extends React.HTMLAttributes<HTMLElement> {
// Can be any weight such as regular or 400
weight?: TypographyWeight;
// 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 {
// If the link is external
external?: boolean;
textStyle?: TypographySizeType;
}
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");

// FOR LINK :
export const StyledExternalIcon = styled(ExternalIcon)`
height: 1em;
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}>
<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

0 comments on commit e10348b

Please sign in to comment.