diff --git a/panda.config.ts b/panda.config.ts index e62fdef..1856648 100644 --- a/panda.config.ts +++ b/panda.config.ts @@ -33,8 +33,12 @@ export default defineConfig({ 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..a8edded --- /dev/null +++ b/src/components/Heading/Heading.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { vstack } from "../../../styled-system/patterns"; +import { fontSizes } from "../../tokens/typography"; +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 }, + }, +}; + +export const Sizes: StoryObj = { + render: (args) => { + return ( +
+ {Object.keys(fontSizes).map((size) => ( + + {size} 제목 + + ))} +
+ ); + }, + 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..cc0afbd --- /dev/null +++ b/src/components/Heading/Heading.tsx @@ -0,0 +1,45 @@ +import { css } from "../../../styled-system/css"; +import { token } from "../../../styled-system/tokens"; +import { fontSizes, fontWeights } from "../../tokens/typography"; + +export interface HeadingProps extends React.HTMLAttributes { + /** 텍스트 */ + children: React.ReactNode | string; + /** 단계 */ + level?: 1 | 2 | 3 | 4 | 5 | 6; + /** 크기 */ + size?: keyof typeof fontSizes; + /** 굵기 */ + weight?: keyof typeof fontWeights; + /** 명암비 */ + // contrast?: "low" | "high"; +} + +export const Heading = ({ + children, + level, + size = "3xl", + weight = "bold", + // 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..e13face 100644 --- a/src/tokens/typography.ts +++ b/src/tokens/typography.ts @@ -86,30 +86,34 @@ export const fonts: Tokens["fonts"] = { // 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", +} as const; -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", +} as const; + +export type FontSize = keyof typeof fontSizes; export const letterSpacings: Tokens["letterSpacings"] = { tighter: { value: "-0.05em" }, 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"], + }, });