Skip to content

Commit

Permalink
Merge pull request #30 from DaleStudy/29-heading
Browse files Browse the repository at this point in the history
  • Loading branch information
DaleSeo authored Jan 14, 2025
2 parents 010e541 + 3ceec5f commit 3a00846
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 15 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"devDependencies": {
"@chromatic-com/storybook": "^3.2.2",
"@eslint/js": "^9.16.0",
"@faker-js/faker": "^9.3.0",
"@pandacss/dev": "^0.48.1",
"@storybook/addon-a11y": "^8.4.7",
"@storybook/addon-essentials": "^8.4.7",
Expand Down
12 changes: 12 additions & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ 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: {
Expand Down
7 changes: 4 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logo from "/logo.svg";
import { css } from "../styled-system/css";
import { Button } from "./components/Button";
import { Heading } from "./components/Heading";

function App() {
return (
Expand All @@ -23,9 +24,9 @@ function App() {
</a>
</header>
<main>
<h1>Welcome Dale UI!</h1>
<Heading level={1}>Welcome Dale UI!</Heading>
<section>
<h2>유용한 링크</h2>
<Heading level={2}>유용한 링크</Heading>
<ul>
<li>
<a href="https://main--675790d317ba346348aa3490.chromatic.com/">
Expand All @@ -51,7 +52,7 @@ function App() {
</ul>
</section>
<section>
<h2>섹션 2</h2>
<Heading level={2}>섹션 2</Heading>
<Button>클릭</Button>
</section>
</main>
Expand Down
74 changes: 74 additions & 0 deletions src/components/Heading/Heading.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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: 1,
},
} satisfies Meta<typeof Heading>;

export default meta;

export const Basic: StoryObj<typeof meta> = {};

export const Levels: StoryObj<typeof meta> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
<Heading {...args} level={1}>
1 단계
</Heading>
<Heading {...args} level={2}>
2 단계
</Heading>
<Heading {...args} level={3}>
3 단계
</Heading>
<Heading {...args} level={4}>
4 단계
</Heading>
<Heading {...args} level={5}>
5 단계
</Heading>
<Heading {...args} level={6}>
6 단계
</Heading>
</div>
);
},
argTypes: {
children: {
control: false,
},
level: {
control: false,
},
},
};

export const Contrasts: StoryObj<typeof meta> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
<Heading {...args} muted>
낮은 명암비
</Heading>
<Heading {...args}>높은 명암비</Heading>
</div>
);
},
argTypes: {
children: {
control: false,
},
muted: {
control: false,
},
},
};
55 changes: 55 additions & 0 deletions src/components/Heading/Heading.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { faker } from "@faker-js/faker";
import { composeStories } from "@storybook/react";
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
import { fontSizes, fontWeights } from "../../tokens/typography";
import * as stories from "./Heading.stories";

const { Basic, Contrasts } = composeStories(stories);

test("renders the heading with the correct text content", () => {
render(<Basic>제목</Basic>);

expect(screen.getByRole("heading")).toHaveTextContent("제목");
});

test.each([1, 2, 3, 4, 5, 6] as const)(
"renders the correct HTML heading element for level %i",
(level) => {
render(<Basic level={level} />);

expect(screen.getByRole("heading", { level })).toBeInTheDocument();
}
);

test("applies the correct font weight class based on the weight prop", () => {
const weight = faker.helpers.arrayElement(
Object.keys(fontWeights)
) as keyof typeof fontWeights;

render(<Basic weight={weight} />);

expect(screen.getByRole("heading")).toHaveClass(`fw_${weight}`);
});

test("applies the correct font size class based on the size prop", () => {
const size = faker.helpers.arrayElement(
Object.keys(fontSizes)
) as keyof typeof fontSizes;

render(<Basic size={size} />);

expect(screen.getByRole("heading")).toHaveClass(`fs_${size}`);
});

test("applies the correct color for low and high contrast", () => {
render(<Contrasts />);

expect(screen.getByRole("heading", { name: "낮은 명암비" })).toHaveClass(
"c_text.muted"
);

expect(screen.getByRole("heading", { name: "높은 명암비" })).toHaveClass(
"c_text"
);
});
73 changes: 73 additions & 0 deletions src/components/Heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { ReactNode, HTMLAttributes } from "react";
import { css, cva } from "../../../styled-system/css";
import type { FontSize, FontWeight } from "../../tokens/typography";

type Level = 1 | 2 | 3 | 4 | 5 | 6;

export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
/** 텍스트 */
children: ReactNode;
/** 단계 */
level: Level;
/** 크기 */
size?: FontSize;
/** 굵기 */
weight?: FontWeight;
/** 명암비 */
muted?: boolean;
}

/**
* - `level` 속성을 통해서 `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>` 요소 중 하나를 선택할 수 있습니다.
* - `level` 속성은 단계 별 기본 텍스트 스타일을 제공합니다.
* - `size` 속성과 `weight` 속성을 통해서 기본 스타일을 변경할 수 있습니다.
* - `muted` 속성을 주시면 글자색이 옅어집니다. 명암비가 낮아지므로 접근성 측면에서 주의해서 사용하세요.
*/
export const Heading = ({
children,
level,
size,
weight,
muted = false,
...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 (
<Tag
className={css(
styles.raw({ level, muted }),
css.raw({
fontSize: size,
fontWeight: weight,
})
)}
{...rest}
>
{children}
</Tag>
);
};

const styles = cva({
variants: {
level: {
1: { textStyle: "4xl" },
2: { textStyle: "3xl" },
3: { textStyle: "2xl" },
4: { textStyle: "xl" },
5: { textStyle: "lg" },
6: { textStyle: "md" },
},
muted: {
true: { color: "text.muted" },
false: { color: "text" },
},
},
});
1 change: 1 addition & 0 deletions src/components/Heading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Heading } from "./Heading";
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 2 additions & 2 deletions src/tokens/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const semanticColors: SemanticTokens["colors"] = {
},
},
text: {
DEFAULT: {
muted: {
DEFAULT: {
value: { base: "{colors.gray.11}", _dark: "{colors.grayDark.11}" },
},
Expand All @@ -125,7 +125,7 @@ export const semanticColors: SemanticTokens["colors"] = {
value: { base: "{colors.yellow.11}", _dark: "{colors.yellowDark.11}" },
},
},
contrast: {
DEFAULT: {
DEFAULT: {
value: { base: "{colors.gray.12}", _dark: "{colors.grayDark.12}" },
},
Expand Down
18 changes: 10 additions & 8 deletions src/tokens/typography.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { TextStyles, Tokens } from "@pandacss/types";

export const textStyles: TextStyles = {
export const textStyles = {
xs: {
value: {
fontSize: "0.75rem",
Expand Down Expand Up @@ -81,20 +79,22 @@ export const textStyles: TextStyles = {
},
};

export const fonts: Tokens["fonts"] = {
export const 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"] = {
export const fontWeights = {
thin: { value: "100" },
light: { value: "300" },
normal: { value: "400" },
medium: { value: "500" },
bold: { value: "700" },
};

export const fontSizes: Tokens["fontSizes"] = {
export type FontWeight = keyof typeof fontWeights;

export const fontSizes = {
"2xs": { value: "0.5rem" },
xs: { value: "0.75rem" },
sm: { value: "0.875rem" },
Expand All @@ -111,7 +111,9 @@ export const fontSizes: Tokens["fontSizes"] = {
"9xl": { value: "8rem" },
};

export const letterSpacings: Tokens["letterSpacings"] = {
export type FontSize = keyof typeof fontSizes;

export const letterSpacings = {
tighter: { value: "-0.05em" },
tight: { value: "-0.025em" },
normal: { value: "0em" },
Expand All @@ -120,7 +122,7 @@ export const letterSpacings: Tokens["letterSpacings"] = {
widest: { value: "0.1em" },
};

export const lineHeights: Tokens["lineHeights"] = {
export const lineHeights = {
none: { value: "1" },
tight: { value: "1.25" },
snug: { value: "1.375" },
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"verbatimModuleSyntax": true
},
"include": ["src", "styled-system"]
}
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export default defineConfig({
environment: "happy-dom",
setupFiles: ["./src/setupTests.tsx"],
},
optimizeDeps: {
exclude: ["node_modules/.cache/storybook"],
},
});

0 comments on commit 3a00846

Please sign in to comment.