From 676d8f8c50115d9e9951059aeeec69b8d5ab439d Mon Sep 17 00:00:00 2001 From: Dale Seo Date: Mon, 6 Jan 2025 20:45:50 -0500 Subject: [PATCH] feat: implement heading component --- panda.config.ts | 24 +++- src/components/Heading/Heading.stories.tsx | 48 ++++++++ src/components/Heading/Heading.test.tsx | 20 ++++ src/components/Heading/Heading.tsx | 61 ++++++++++ src/index.css | 2 +- src/tokens/typography.mdx | 4 +- src/tokens/typography.ts | 130 +++++++++------------ tsconfig.app.json | 3 +- vite.config.ts | 3 + 9 files changed, 213 insertions(+), 82 deletions(-) create mode 100644 src/components/Heading/Heading.stories.tsx create mode 100644 src/components/Heading/Heading.test.tsx create mode 100644 src/components/Heading/Heading.tsx diff --git a/panda.config.ts b/panda.config.ts index e62fdef..fd43190 100644 --- a/panda.config.ts +++ b/panda.config.ts @@ -26,15 +26,33 @@ export default defineConfig({ "--font-spoqa": "Spoqa Han Sans Neo", }, + staticCss: { + css: [ + { + properties: { + textStyle: Object.keys(textStyles), + fontSize: Object.keys(fontSizes), + fontWeight: Object.keys(fontWeights), + }, + }, + ], + }, + // Useful for theme customization theme: { extend: { - textStyles, + textStyles: Object.fromEntries( + Object.entries(textStyles).map(([key, value]) => [key, { value }]) + ), tokens: { colors, fonts, - fontWeights, - fontSizes, + fontWeights: Object.fromEntries( + Object.entries(fontWeights).map(([key, value]) => [key, { value }]) + ), + fontSizes: Object.fromEntries( + Object.entries(fontSizes).map(([key, value]) => [key, { value }]) + ), letterSpacings, lineHeights, }, diff --git a/src/components/Heading/Heading.stories.tsx b/src/components/Heading/Heading.stories.tsx new file mode 100644 index 0000000..d30d5f9 --- /dev/null +++ b/src/components/Heading/Heading.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { vstack } from "../../../styled-system/patterns"; +import { Heading } from "./Heading"; + +const meta = { + component: Heading, + parameters: { + layout: "centered", + }, + args: { + children: "제목", + level: 2, + }, +} satisfies Meta; + +export default meta; + +export const Basic: StoryObj = {}; + +export const Levels: StoryObj = { + render: (args) => { + return ( +
+ + 1 단계 + + + 2 단계 + + + 3 단계 + + + 4 단계 + + + 5 단계 + + + 6 단계 + +
+ ); + }, + parameters: { + controls: { disable: true }, + }, +}; diff --git a/src/components/Heading/Heading.test.tsx b/src/components/Heading/Heading.test.tsx new file mode 100644 index 0000000..95a25cb --- /dev/null +++ b/src/components/Heading/Heading.test.tsx @@ -0,0 +1,20 @@ +import { composeStories } from "@storybook/react"; +import { render, screen } from "@testing-library/react"; +import { expect, test } from "vitest"; +import * as stories from "./Heading.stories"; + +const { Basic } = composeStories(stories); + +test("renders the text content", () => { + render(제목); + + const heading = screen.getByRole("heading"); + + expect(heading).toHaveTextContent("제목"); +}); + +test.each([1, 2, 3, 4, 5, 6] as const)("use the correct level %s", (level) => { + render(); + + expect(screen.getByRole("heading", { level })).toBeInTheDocument(); +}); diff --git a/src/components/Heading/Heading.tsx b/src/components/Heading/Heading.tsx new file mode 100644 index 0000000..fb4df0f --- /dev/null +++ b/src/components/Heading/Heading.tsx @@ -0,0 +1,61 @@ +import { css } from "../../../styled-system/css"; +import type { TextStyle, FontSize, FontWeight } from "../../tokens/typography"; + +type Level = 1 | 2 | 3 | 4 | 5 | 6; + +export interface HeadingProps extends React.HTMLAttributes { + /** 텍스트 */ + children: React.ReactNode | string; + /** 단계 */ + level?: Level; + /** 크기 */ + size?: FontSize; + /** 굵기 */ + weight?: FontWeight; + /** 명암비 */ + // contrast?: "low" | "high"; +} + +const textStyles: Record = { + 1: "4xl", + 2: "3xl", + 3: "2xl", + 4: "xl", + 5: "lg", + 6: "md", +}; + +/** + * - `level` 속성을 통해서 `

`, `

`, `

`, `

`, `

`, `
` 요소 중 하나를 선택할 수 있습니다. + * - `level` 속성은 단계 별 기본 텍스트 스타일을 제공합니다. + * - `size` 속성과 `weight` 속성을 통해서 기본 스타일을 변경할 수 있습니다. + */ +export const Heading = ({ + children, + level, + size, + weight, + // contrast = "low", + ...rest +}: HeadingProps) => { + if (!level) { + throw new Error( + "The level prop is required and you can cause accessibility issues." + ); + } + + const Tag = `h${level}` as const; + + return ( + + {children} + + ); +}; diff --git a/src/index.css b/src/index.css index 0f6cb42..14b5d9c 100644 --- a/src/index.css +++ b/src/index.css @@ -1,2 +1,2 @@ @layer reset, base, tokens, recipes, utilities; -@import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css); +@import url(https://spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css); diff --git a/src/tokens/typography.mdx b/src/tokens/typography.mdx index 8a2556c..34c02a3 100644 --- a/src/tokens/typography.mdx +++ b/src/tokens/typography.mdx @@ -9,7 +9,7 @@ import { fonts, fontWeights, fontSizes } from "./typography.ts";

{Object.entries(fontSizes) - .map(([name, { value }]) => `${name}(${value})`) + .map(([name, value]) => `${name}(${value})`) .join(", ")}

@@ -22,7 +22,7 @@ import { fonts, fontWeights, fontSizes } from "./typography.ts"; ## 글꼴 굵기 -{Object.entries(fontWeights).map(([name, {value}]) => ( +{Object.entries(fontWeights).map(([name, value]) => ( <>

diff --git a/src/tokens/typography.ts b/src/tokens/typography.ts index 91c171d..67d1776 100644 --- a/src/tokens/typography.ts +++ b/src/tokens/typography.ts @@ -1,116 +1,96 @@ -import type { TextStyles, Tokens } from "@pandacss/types"; +import type { Tokens } from "@pandacss/types"; -export const textStyles: TextStyles = { +export const textStyles = { xs: { - value: { - fontSize: "0.75rem", - lineHeight: "1rem", - }, + fontSize: "0.75rem", + lineHeight: "1rem", }, sm: { - value: { - fontSize: "0.875rem", - lineHeight: "1.25rem", - }, + fontSize: "0.875rem", + lineHeight: "1.25rem", }, md: { - value: { - fontSize: "1rem", - lineHeight: "1.5rem", - }, + fontSize: "1rem", + lineHeight: "1.5rem", }, lg: { - value: { - fontSize: "1.125rem", - lineHeight: "1.75rem", - }, + fontSize: "1.125rem", + lineHeight: "1.75rem", }, xl: { - value: { - fontSize: "1.25rem", - lineHeight: "1.75rem", - }, + fontSize: "1.25rem", + lineHeight: "1.75rem", }, "2xl": { - value: { - fontSize: "1.5rem", - lineHeight: "2rem", - }, + fontSize: "1.5rem", + lineHeight: "2rem", }, "3xl": { - value: { - fontSize: "1.875rem", - lineHeight: "2.25rem", - }, + fontSize: "1.875rem", + lineHeight: "2.25rem", }, "4xl": { - value: { - fontSize: "2.25rem", - lineHeight: "2.5rem", - }, + fontSize: "2.25rem", + lineHeight: "2.5rem", }, "5xl": { - value: { - fontSize: "3rem", - lineHeight: "1", - }, + fontSize: "3rem", + lineHeight: "1", }, "6xl": { - value: { - fontSize: "3.75rem", - lineHeight: "1", - }, + fontSize: "3.75rem", + lineHeight: "1", }, "7xl": { - value: { - fontSize: "4.5rem", - lineHeight: "1", - }, + fontSize: "4.5rem", + lineHeight: "1", }, "8xl": { - value: { - fontSize: "6rem", - lineHeight: "1", - }, + fontSize: "6rem", + lineHeight: "1", }, "9xl": { - value: { - fontSize: "8rem", - lineHeight: "1", - }, + fontSize: "8rem", + lineHeight: "1", }, }; +export type TextStyle = keyof typeof textStyles; + export const fonts: Tokens["fonts"] = { sans: { value: '"Spoqa Han Sans Neo", "Noto Sans KR", sans-serif' }, // TODO customize serif and mono font styles when needed }; -export const fontWeights: Tokens["fontWeights"] = { - thin: { value: "100" }, - light: { value: "300" }, - normal: { value: "400" }, - medium: { value: "500" }, - bold: { value: "700" }, +export const fontWeights = { + thin: "100", + light: "300", + normal: "400", + medium: "500", + bold: "700", }; -export const fontSizes: Tokens["fontSizes"] = { - "2xs": { value: "0.5rem" }, - xs: { value: "0.75rem" }, - sm: { value: "0.875rem" }, - md: { value: "1rem" }, - lg: { value: "1.125rem" }, - xl: { value: "1.25rem" }, - "2xl": { value: "1.5rem" }, - "3xl": { value: "1.875rem" }, - "4xl": { value: "2.25rem" }, - "5xl": { value: "3rem" }, - "6xl": { value: "3.75rem" }, - "7xl": { value: "4.5rem" }, - "8xl": { value: "6rem" }, - "9xl": { value: "8rem" }, +export type FontWeight = keyof typeof fontWeights; + +export const fontSizes = { + "2xs": "0.5rem", + xs: "0.75rem", + sm: "0.875rem", + md: "1rem", + lg: "1.125rem", + xl: "1.25rem", + "2xl": "1.5rem", + "3xl": "1.875rem", + "4xl": "2.25rem", + "5xl": "3rem", + "6xl": "3.75rem", + "7xl": "4.5rem", + "8xl": "6rem", + "9xl": "8rem", }; +export type FontSize = keyof typeof fontSizes; + export const letterSpacings: Tokens["letterSpacings"] = { tighter: { value: "-0.05em" }, tight: { value: "-0.025em" }, diff --git a/tsconfig.app.json b/tsconfig.app.json index c7bcde8..07afee5 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -18,7 +18,8 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "verbatimModuleSyntax": true }, "include": ["src", "styled-system"] } diff --git a/vite.config.ts b/vite.config.ts index 3e9a2f0..741c3ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,4 +9,7 @@ export default defineConfig({ environment: "happy-dom", setupFiles: ["./src/setupTests.tsx"], }, + optimizeDeps: { + exclude: ["node_modules/.cache/storybook"], + }, });