diff --git a/.changeset/strange-pillows-repair.md b/.changeset/strange-pillows-repair.md new file mode 100644 index 00000000..e993de19 --- /dev/null +++ b/.changeset/strange-pillows-repair.md @@ -0,0 +1,5 @@ +--- +"@cambly/syntax-core": minor +--- + +Typography/Heading: add Cambio styles & options diff --git a/packages/syntax-core/src/Checkbox/Checkbox.tsx b/packages/syntax-core/src/Checkbox/Checkbox.tsx index 0e9fd554..ca376fa0 100644 --- a/packages/syntax-core/src/Checkbox/Checkbox.tsx +++ b/packages/syntax-core/src/Checkbox/Checkbox.tsx @@ -125,7 +125,7 @@ const Checkbox = ({ /> {label} diff --git a/packages/syntax-core/src/Heading/Heading.stories.tsx b/packages/syntax-core/src/Heading/Heading.stories.tsx index ddf49440..56d701e4 100644 --- a/packages/syntax-core/src/Heading/Heading.stories.tsx +++ b/packages/syntax-core/src/Heading/Heading.stories.tsx @@ -26,6 +26,7 @@ export default { color: { options: [ "destructive-primary", + "black", "gray700", "gray900", "primary", @@ -34,10 +35,17 @@ export default { ], control: { type: "radio" }, }, - size: { - options: [500, 600, 700, 800], + lineClamp: { + control: { type: "number", min: 0, max: 10, step: 1 }, + }, + fontStyle: { + options: ["serif", "sans-serif"], control: { type: "radio" }, }, + size: { + options: [400, 500, 600, 700, 800, 900, 1000, 1100], + control: { type: "select" }, + }, }, tags: ["autodocs"], } as Meta; @@ -51,6 +59,9 @@ export const Default: StoryObj = { export const Sizes: StoryObj = { render: (args) => ( <> + + Size 400 (Cambio only) + Size 500 @@ -63,6 +74,15 @@ export const Sizes: StoryObj = { Size 800 + + Size 900 (Cambio only) + + + Size 1000 (Cambio only) + + + Size 1100 (Cambio only) + ), }; diff --git a/packages/syntax-core/src/Heading/Heading.tsx b/packages/syntax-core/src/Heading/Heading.tsx index 2655fd88..101808ca 100644 --- a/packages/syntax-core/src/Heading/Heading.tsx +++ b/packages/syntax-core/src/Heading/Heading.tsx @@ -1,6 +1,6 @@ import { type ReactElement, type ReactNode } from "react"; -import { type Color } from "../constants"; import Typography from "../Typography/Typography"; +import { useTheme } from "../ThemeProvider/ThemeProvider"; /** * [Heading](https://cambly-syntax.vercel.app/?path=/docs/components-heading--docs) enforces a consistent style & accessibility best practices for headings. @@ -11,6 +11,7 @@ const Heading = ({ children, color = "gray900", "data-testid": dataTestId, + fontStyle, lineClamp, size = 500, }: { @@ -37,11 +38,26 @@ const Heading = ({ * * @defaultValue "gray900" */ - color?: (typeof Color)[number]; + color?: + | "gray900" + | "gray700" + | "primary" + | "destructive-primary" + | "success" + | "white" + | "inherit"; /** * Test id for the text. */ "data-testid"?: string; + /** + * Style of the font + * + * Classic only supports `sans-serif` + * + * @defaultValue "sans-serif" + */ + fontStyle?: "serif" | "sans-serif"; /** * The number of lines we should truncate the text at */ @@ -49,25 +65,50 @@ const Heading = ({ /** * Size of the text. * + * Classic: * * `500`: 20px * * `600`: 28px * * `700`: 40px * * `800`: 64px * + * Cambio Mobile: + * * `400`: 20px + * * `500`: 23px + * * `600`: 26px + * * `700`: 29px + * * `800`: 33px + * * `900`: 37px + * * `1000`: 41px + * * `1100`: 46px + * + * Cambio Desktop (viewport width > 480px): + * * `400`: 25px + * * `500`: 31px + * * `600`: 39px + * * `700`: 49px + * * `800`: 61px + * * `900`: 76px + * * `1000`: 95px + * * `1100`: 119px + * * @defaultValue 500 */ - size?: 500 | 600 | 700 | 800; + size?: 400 | 500 | 600 | 700 | 800 | 900 | 1000 | 1100; }): ReactElement => { - const weight = [700, 800].includes(size) ? "heavy" : "bold"; + const { themeName } = useTheme(); + const classicWeight = [700, 800].includes(size) ? "heavy" : "bold"; + const cambioWeight = fontStyle === "serif" ? "medium" : "regular"; + return ( {children} diff --git a/packages/syntax-core/src/RadioButton/RadioButton.tsx b/packages/syntax-core/src/RadioButton/RadioButton.tsx index 8c56d1ca..8df70228 100644 --- a/packages/syntax-core/src/RadioButton/RadioButton.tsx +++ b/packages/syntax-core/src/RadioButton/RadioButton.tsx @@ -138,7 +138,7 @@ const RadioButton = ({ {label && ( {label} diff --git a/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx b/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx index afff5c4e..2c7ecc33 100644 --- a/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx +++ b/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx @@ -44,6 +44,9 @@ describe("themeProvider", () => { expect(screen.getByTestId("themeprovider-style")).toContainHTML( "--color-base-gray-10: rgba(203, 203, 203, 0.5);", ); + expect(screen.getByTestId("themeprovider-style")).toContainHTML( + "--elevation-400: 0px 16px 32px 0px #00000040;", + ); expect(screen.getByTestId("themeprovider-style")).not.toContainHTML( "cambio", ); @@ -68,6 +71,9 @@ describe("themeProvider", () => { expect(screen.getByTestId("themeprovider-style")).toContainHTML( "--color-base-primary-100: #faf4eb;", ); + expect(screen.getByTestId("themeprovider-style")).not.toContainHTML( + "elevation", + ); expect(screen.getByTestId("themeprovider-style")).toContainHTML("cambio"); }); }); diff --git a/packages/syntax-core/src/ThemeProvider/ThemeProvider.tsx b/packages/syntax-core/src/ThemeProvider/ThemeProvider.tsx index 033d2cc9..601dcf98 100644 --- a/packages/syntax-core/src/ThemeProvider/ThemeProvider.tsx +++ b/packages/syntax-core/src/ThemeProvider/ThemeProvider.tsx @@ -90,9 +90,14 @@ function stylesForTheme(themeName: ThemeName) { ], ]; } + // `elevation` is a classic only concept + if (themeName === "cambio" && key.includes("elevation")) { + return [null, null]; + } return [key, value]; }) - .map(([key, value]) => `--${key}: ${value};`) + .map(([key, value]) => (key && value ? `--${key}: ${value};` : null)) + .filter(Boolean) .join("\n")} } `; diff --git a/packages/syntax-core/src/Typography/Typography.module.css b/packages/syntax-core/src/Typography/Typography.module.css index a84f136e..4b5ac8ef 100644 --- a/packages/syntax-core/src/Typography/Typography.module.css +++ b/packages/syntax-core/src/Typography/Typography.module.css @@ -1,9 +1,32 @@ .typography { + margin: 0; +} + +@font-face { + font-family: GT-Super-Text-Medium; + font-style: normal; + font-weight: 500; + src: local("GT-Super-Text-Medium"), + /* TODO fix CORS issues and change to url("https://static.cambly.com/fonts/GT-Super-Text-Medium.woff2") */ + url("https://partners.rebelmouse.com/protocol/GT-Super-Text-Medium.woff2") + format("woff2"); + + /* TODO enable Most performant option - see https://web.dev/articles/font-best-practices */ + + /* font-display: optional; */ +} + +.sansSerif { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; } +.serif { + font-family: GT-Super-Text-Medium, -apple-system, system-ui, + BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, serif; +} + /* Sizes */ .size100 { font-size: 12px; @@ -33,6 +56,89 @@ font-size: 64px; } +/* Sizes - Cambio sizes are responsive */ +.size100Cambio { + font-size: 14px; +} + +.size200Cambio { + font-size: 16px; +} + +.size300Cambio { + font-size: 18px; +} + +.size400Cambio { + font-size: 20px; +} + +.size500Cambio { + font-size: 23px; +} + +.size600Cambio { + font-size: 26px; +} + +.size700Cambio { + font-size: 33px; +} + +.size800Cambio { + font-size: 41px; +} + +.size900Cambio { + font-size: 46px; +} + +@media (min-width: 480px) { + .size100Cambio { + font-size: 13px; + } + + .size200Cambio { + font-size: 16px; + } + + .size300Cambio { + font-size: 20px; + } + + .size400Cambio { + font-size: 25px; + } + + .size500Cambio { + font-size: 31px; + } + + .size600Cambio { + font-size: 39px; + } + + .size700Cambio { + font-size: 49px; + } + + .size800Cambio { + font-size: 61px; + } + + .size900Cambio { + font-size: 76px; + } + + .size1000Cambio { + font-size: 95px; + } + + .size1100Cambio { + font-size: 119px; + } +} + /* Align */ .center { text-align: center; @@ -75,6 +181,18 @@ font-weight: 860; } +.regularCambio { + font-weight: 400; +} + +.mediumCambio { + font-weight: 510; +} + +.semiBoldCambio { + font-weight: 590; +} + .underline { text-decoration: underline; } diff --git a/packages/syntax-core/src/Typography/Typography.stories.tsx b/packages/syntax-core/src/Typography/Typography.stories.tsx index 68d6d4e4..dd99edd8 100644 --- a/packages/syntax-core/src/Typography/Typography.stories.tsx +++ b/packages/syntax-core/src/Typography/Typography.stories.tsx @@ -21,12 +21,23 @@ export default { control: { type: "radio" }, }, color: { + // | "gray900" + // | "gray700" + // | "primary" + // | "destructive-primary" + // | "destructive-darkBackground" + // | "success" + // | "success-darkBackground" + // | "white" + // | "inherit", options: [ "destructive-primary", + "destructive-darkBackground", "gray700", "gray900", "primary", "success", + "success-darkBackground", "white", ], control: { type: "radio" }, @@ -34,6 +45,10 @@ export default { children: { control: "text", }, + fontStyle: { + options: ["serif", "sans-serif"], + control: { type: "radio" }, + }, inline: { control: "boolean", }, @@ -41,8 +56,8 @@ export default { control: { type: "number", min: 0, max: 10, step: 1 }, }, size: { - options: [100, 200, 300, 500, 600, 700, 800], - control: { type: "radio" }, + options: [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100], + control: { type: "select" }, }, tooltip: { control: "text", @@ -55,7 +70,14 @@ export default { control: "boolean", }, weight: { - options: ["regular", "interactive", "semiBold", "bold", "heavy"], + options: [ + "regular", + "interactive", + "medium", + "semiBold", + "bold", + "heavy", + ], control: { type: "radio" }, }, }, @@ -63,64 +85,75 @@ export default { } as Meta; export const Default: StoryObj = { - render: (args) => ( - {args.children ?? "Default text"} - ), + args: { children: "Default text" }, }; -export const Sizes: StoryObj = { - render: (args) => ( - <> - - Size 100 - - - Size 200 (default) - - - Size 300 - - - Size 500 Bold - - - Size 600 Bold - - - Size 700 Heavy - - - Size 800 Heavy - - - ), +export const size100: StoryObj = { + args: { ...Default.args, size: 100 }, }; -export const Colors: StoryObj = { - render: (args) => ( - <> - - Color destructive-primary - - - Color gray700 - - - Color gray900 (default) - - - Color primary - - - Color success - -
- - Color white - -
- - ), +export const size200: StoryObj = { + args: { ...Default.args, size: 200 }, +}; + +export const size300: StoryObj = { + args: { ...Default.args, size: 300 }, +}; + +export const size400CambioOnly: StoryObj = { + args: { ...Default.args, size: 400 }, +}; + +export const size500: StoryObj = { + args: { ...Default.args, size: 500 }, +}; + +export const size600: StoryObj = { + args: { ...Default.args, size: 600 }, +}; + +export const size700: StoryObj = { + args: { ...Default.args, size: 700 }, +}; + +export const size800: StoryObj = { + args: { ...Default.args, size: 800 }, +}; + +export const size900CambioOnly: StoryObj = { + args: { ...Default.args, size: 900 }, +}; + +export const size1000CambioOnly: StoryObj = { + args: { ...Default.args, size: 1000 }, +}; + +export const size1100CambioOnly: StoryObj = { + args: { ...Default.args, size: 1100 }, +}; + +export const colorDestructivePrimary: StoryObj = { + args: { ...Default.args, color: "destructive-primary" }, +}; + +export const colorGray700: StoryObj = { + args: { ...Default.args, color: "gray700" }, +}; + +export const colorGray900Default: StoryObj = { + args: { ...Default.args, color: "gray900" }, +}; + +export const colorPrimary: StoryObj = { + args: { ...Default.args, color: "primary" }, +}; + +export const colorSuccess: StoryObj = { + args: { ...Default.args, color: "success" }, +}; + +export const colorWhite: StoryObj = { + args: { ...Default.args, color: "white" }, }; export const Inline: StoryObj = { @@ -161,16 +194,19 @@ export const Weight: StoryObj = { Weight Regular - Weight interactive + Weight interactive (classic only) + + + Weight medium (cambio only) Weight semiBold - Weight bold + Weight bold (classic only) - Weight heavy + Weight heavy (classic only) ), diff --git a/packages/syntax-core/src/Typography/Typography.tsx b/packages/syntax-core/src/Typography/Typography.tsx index 38142c5d..4cc59bb0 100644 --- a/packages/syntax-core/src/Typography/Typography.tsx +++ b/packages/syntax-core/src/Typography/Typography.tsx @@ -1,10 +1,21 @@ import classNames from "classnames"; import { forwardRef, type ReactElement, type ReactNode } from "react"; -import { type Color } from "../constants"; import styles from "./Typography.module.css"; import colorStyles from "../colors/colors.module.css"; +import { useTheme } from "../ThemeProvider/ThemeProvider"; -function textColor(color: (typeof Color)[number]): string { +function classicTextColor( + color: + | "gray900" + | "gray700" + | "primary" + | "destructive-primary" + | "destructive-darkBackground" + | "success" + | "success-darkBackground" + | "white" + | "inherit", +): string { switch (color) { case "gray700": return colorStyles.gray700Color; @@ -15,6 +26,7 @@ function textColor(color: (typeof Color)[number]): string { case "primary": return colorStyles.primary700Color; case "destructive-primary": + case "destructive-darkBackground": return colorStyles.destructive700Color; case "success": return colorStyles.success700Color; @@ -23,6 +35,64 @@ function textColor(color: (typeof Color)[number]): string { } } +function cambioTextColor( + color: + | "gray900" + | "gray700" + | "primary" + | "destructive-primary" + | "destructive-darkBackground" + | "success" + | "success-darkBackground" + | "white" + | "inherit", +): string { + switch (color) { + case "gray700": + return colorStyles.cambioGray800Color; + case "white": + return colorStyles.cambioWhiteColor; + case "inherit": + return colorStyles.inheritColor; + case "destructive-primary": + return colorStyles.cambioDestructive900Color; + case "destructive-darkBackground": + return colorStyles.cambioDestructive100Color; + case "success": + return colorStyles.cambioSuccess900Color; + case "success-darkBackground": + return colorStyles.cambioSuccess100Color; + // primary / gray900 + default: + return colorStyles.cambioBlackColor; + } +} + +function classicWeight( + weight: "regular" | "interactive" | "medium" | "semiBold" | "bold" | "heavy", +): "regular" | "interactive" | "semiBold" | "bold" | "heavy" { + switch (weight) { + case "medium": + return "regular"; + default: + return weight; + } +} + +function cambioWeight( + weight: "regular" | "interactive" | "medium" | "semiBold" | "bold" | "heavy", +): "regular" | "medium" | "semiBold" { + switch (weight) { + case "interactive": + return "medium"; + case "bold": + case "heavy": + return "regular"; + default: + return weight; + } +} + /** * [Typography](https://cambly-syntax.vercel.app/?path=/docs/components-typography--docs) is a component that renders text. */ @@ -50,13 +120,32 @@ const Typography = forwardRef< /** * The color of the text. * + * Cambio only: `success-darkBackground` / `destructive-darkBackground` + * * @defaultValue "gray900" */ - color?: (typeof Color)[number]; + color?: + | "gray900" + | "gray700" + | "primary" + | "destructive-primary" + | "destructive-darkBackground" + | "success" + | "success-darkBackground" + | "white" + | "inherit"; /** * Test id for the text */ "data-testid"?: string; + /** + * Style of the font + * + * Classic only supports `sans-serif` + * + * @defaultValue "sans-serif" + */ + fontStyle?: "serif" | "sans-serif"; /** * The id for the element */ @@ -74,6 +163,7 @@ const Typography = forwardRef< /** * Size of the text. * + * Classic: * * `100`: 12px * * `200`: 14px * * `300`: 16px @@ -82,9 +172,35 @@ const Typography = forwardRef< * * `700`: 40px * * `800`: 64px * + * Cambio Mobile: + * * `100`: 14px + * * `200`: 16px + * * `300`: 18px + * * `400`: 20px + * * `500`: 23px + * * `600`: 26px + * * `700`: 29px + * * `800`: 33px + * * `900`: 37px + * * `1000`: 41px + * * `1100`: 46px + * + * Cambio Desktop (viewport width > 480px): + * * `100`: 13px + * * `200`: 16px + * * `300`: 20px + * * `400`: 25px + * * `500`: 31px + * * `600`: 39px + * * `700`: 49px + * * `800`: 61px + * * `900`: 76px + * * `1000`: 95px + * * `1100`: 119px + * * @defaultValue 200 */ - size?: 100 | 200 | 300 | 500 | 600 | 700 | 800; + size?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 | 1100; /** * The tooltip to be displayed when the user hovers the text */ @@ -104,9 +220,27 @@ const Typography = forwardRef< /** * Indicates the boldness of the text. * + * Classic: + * * `regular`: 400 + * * `interactive`: 500 (Classic only) + * * `semiBold`: 600 + * * `bold`: 700 (Classic only) + * * `heavy`: 860 (Classic only) + * + * Cambio: + * * `regular`: 400 + * * `medium`: 510 + * * `semiBold`: 590 + * * @defaultValue "regular" */ - weight?: "regular" | "interactive" | "semiBold" | "bold" | "heavy"; + weight?: + | "regular" + | "interactive" + | "medium" + | "semiBold" + | "bold" + | "heavy"; } >(function Typography( { @@ -115,6 +249,7 @@ const Typography = forwardRef< children, color = "gray900", "data-testid": dataTestId, + fontStyle = "sans-serif", id, inline = false, lineClamp = undefined, @@ -128,16 +263,45 @@ const Typography = forwardRef< ): ReactElement { const Tag = as; + const { themeName } = useTheme(); + + const weightStyles = + themeName === "classic" + ? styles[classicWeight(weight)] + : styles[`${cambioWeight(weight)}Cambio`]; + return (