Skip to content

Commit 3a00846

Browse files
authored
Merge pull request #30 from DaleStudy/29-heading
2 parents 010e541 + 3ceec5f commit 3a00846

File tree

13 files changed

+238
-15
lines changed

13 files changed

+238
-15
lines changed

bun.lockb

371 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"devDependencies": {
2525
"@chromatic-com/storybook": "^3.2.2",
2626
"@eslint/js": "^9.16.0",
27+
"@faker-js/faker": "^9.3.0",
2728
"@pandacss/dev": "^0.48.1",
2829
"@storybook/addon-a11y": "^8.4.7",
2930
"@storybook/addon-essentials": "^8.4.7",

panda.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export default defineConfig({
2626
"--font-spoqa": "Spoqa Han Sans Neo",
2727
},
2828

29+
staticCss: {
30+
css: [
31+
{
32+
properties: {
33+
textStyle: Object.keys(textStyles),
34+
fontSize: Object.keys(fontSizes),
35+
fontWeight: Object.keys(fontWeights),
36+
},
37+
},
38+
],
39+
},
40+
2941
// Useful for theme customization
3042
theme: {
3143
extend: {

src/App.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logo from "/logo.svg";
22
import { css } from "../styled-system/css";
33
import { Button } from "./components/Button";
4+
import { Heading } from "./components/Heading";
45

56
function App() {
67
return (
@@ -23,9 +24,9 @@ function App() {
2324
</a>
2425
</header>
2526
<main>
26-
<h1>Welcome Dale UI!</h1>
27+
<Heading level={1}>Welcome Dale UI!</Heading>
2728
<section>
28-
<h2>유용한 링크</h2>
29+
<Heading level={2}>유용한 링크</Heading>
2930
<ul>
3031
<li>
3132
<a href="https://main--675790d317ba346348aa3490.chromatic.com/">
@@ -51,7 +52,7 @@ function App() {
5152
</ul>
5253
</section>
5354
<section>
54-
<h2>섹션 2</h2>
55+
<Heading level={2}>섹션 2</Heading>
5556
<Button>클릭</Button>
5657
</section>
5758
</main>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { vstack } from "../../../styled-system/patterns";
3+
import { Heading } from "./Heading";
4+
5+
const meta = {
6+
component: Heading,
7+
parameters: {
8+
layout: "centered",
9+
},
10+
args: {
11+
children: "제목",
12+
level: 1,
13+
},
14+
} satisfies Meta<typeof Heading>;
15+
16+
export default meta;
17+
18+
export const Basic: StoryObj<typeof meta> = {};
19+
20+
export const Levels: StoryObj<typeof meta> = {
21+
render: (args) => {
22+
return (
23+
<div className={vstack({ gap: "6" })}>
24+
<Heading {...args} level={1}>
25+
1 단계
26+
</Heading>
27+
<Heading {...args} level={2}>
28+
2 단계
29+
</Heading>
30+
<Heading {...args} level={3}>
31+
3 단계
32+
</Heading>
33+
<Heading {...args} level={4}>
34+
4 단계
35+
</Heading>
36+
<Heading {...args} level={5}>
37+
5 단계
38+
</Heading>
39+
<Heading {...args} level={6}>
40+
6 단계
41+
</Heading>
42+
</div>
43+
);
44+
},
45+
argTypes: {
46+
children: {
47+
control: false,
48+
},
49+
level: {
50+
control: false,
51+
},
52+
},
53+
};
54+
55+
export const Contrasts: StoryObj<typeof meta> = {
56+
render: (args) => {
57+
return (
58+
<div className={vstack({ gap: "6" })}>
59+
<Heading {...args} muted>
60+
낮은 명암비
61+
</Heading>
62+
<Heading {...args}>높은 명암비</Heading>
63+
</div>
64+
);
65+
},
66+
argTypes: {
67+
children: {
68+
control: false,
69+
},
70+
muted: {
71+
control: false,
72+
},
73+
},
74+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { faker } from "@faker-js/faker";
2+
import { composeStories } from "@storybook/react";
3+
import { render, screen } from "@testing-library/react";
4+
import { expect, test } from "vitest";
5+
import { fontSizes, fontWeights } from "../../tokens/typography";
6+
import * as stories from "./Heading.stories";
7+
8+
const { Basic, Contrasts } = composeStories(stories);
9+
10+
test("renders the heading with the correct text content", () => {
11+
render(<Basic>제목</Basic>);
12+
13+
expect(screen.getByRole("heading")).toHaveTextContent("제목");
14+
});
15+
16+
test.each([1, 2, 3, 4, 5, 6] as const)(
17+
"renders the correct HTML heading element for level %i",
18+
(level) => {
19+
render(<Basic level={level} />);
20+
21+
expect(screen.getByRole("heading", { level })).toBeInTheDocument();
22+
}
23+
);
24+
25+
test("applies the correct font weight class based on the weight prop", () => {
26+
const weight = faker.helpers.arrayElement(
27+
Object.keys(fontWeights)
28+
) as keyof typeof fontWeights;
29+
30+
render(<Basic weight={weight} />);
31+
32+
expect(screen.getByRole("heading")).toHaveClass(`fw_${weight}`);
33+
});
34+
35+
test("applies the correct font size class based on the size prop", () => {
36+
const size = faker.helpers.arrayElement(
37+
Object.keys(fontSizes)
38+
) as keyof typeof fontSizes;
39+
40+
render(<Basic size={size} />);
41+
42+
expect(screen.getByRole("heading")).toHaveClass(`fs_${size}`);
43+
});
44+
45+
test("applies the correct color for low and high contrast", () => {
46+
render(<Contrasts />);
47+
48+
expect(screen.getByRole("heading", { name: "낮은 명암비" })).toHaveClass(
49+
"c_text.muted"
50+
);
51+
52+
expect(screen.getByRole("heading", { name: "높은 명암비" })).toHaveClass(
53+
"c_text"
54+
);
55+
});

src/components/Heading/Heading.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { ReactNode, HTMLAttributes } from "react";
2+
import { css, cva } from "../../../styled-system/css";
3+
import type { FontSize, FontWeight } from "../../tokens/typography";
4+
5+
type Level = 1 | 2 | 3 | 4 | 5 | 6;
6+
7+
export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
8+
/** 텍스트 */
9+
children: ReactNode;
10+
/** 단계 */
11+
level: Level;
12+
/** 크기 */
13+
size?: FontSize;
14+
/** 굵기 */
15+
weight?: FontWeight;
16+
/** 명암비 */
17+
muted?: boolean;
18+
}
19+
20+
/**
21+
* - `level` 속성을 통해서 `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>` 요소 중 하나를 선택할 수 있습니다.
22+
* - `level` 속성은 단계 별 기본 텍스트 스타일을 제공합니다.
23+
* - `size` 속성과 `weight` 속성을 통해서 기본 스타일을 변경할 수 있습니다.
24+
* - `muted` 속성을 주시면 글자색이 옅어집니다. 명암비가 낮아지므로 접근성 측면에서 주의해서 사용하세요.
25+
*/
26+
export const Heading = ({
27+
children,
28+
level,
29+
size,
30+
weight,
31+
muted = false,
32+
...rest
33+
}: HeadingProps) => {
34+
if (!level) {
35+
throw new Error(
36+
"The level prop is required and you can cause accessibility issues."
37+
);
38+
}
39+
40+
const Tag = `h${level}` as const;
41+
42+
return (
43+
<Tag
44+
className={css(
45+
styles.raw({ level, muted }),
46+
css.raw({
47+
fontSize: size,
48+
fontWeight: weight,
49+
})
50+
)}
51+
{...rest}
52+
>
53+
{children}
54+
</Tag>
55+
);
56+
};
57+
58+
const styles = cva({
59+
variants: {
60+
level: {
61+
1: { textStyle: "4xl" },
62+
2: { textStyle: "3xl" },
63+
3: { textStyle: "2xl" },
64+
4: { textStyle: "xl" },
65+
5: { textStyle: "lg" },
66+
6: { textStyle: "md" },
67+
},
68+
muted: {
69+
true: { color: "text.muted" },
70+
false: { color: "text" },
71+
},
72+
},
73+
});

src/components/Heading/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Heading } from "./Heading";

src/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
@layer reset, base, tokens, recipes, utilities;
2-
@import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css);
2+
@import url(https://spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css);

src/tokens/colors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const semanticColors: SemanticTokens["colors"] = {
111111
},
112112
},
113113
text: {
114-
DEFAULT: {
114+
muted: {
115115
DEFAULT: {
116116
value: { base: "{colors.gray.11}", _dark: "{colors.grayDark.11}" },
117117
},
@@ -125,7 +125,7 @@ export const semanticColors: SemanticTokens["colors"] = {
125125
value: { base: "{colors.yellow.11}", _dark: "{colors.yellowDark.11}" },
126126
},
127127
},
128-
contrast: {
128+
DEFAULT: {
129129
DEFAULT: {
130130
value: { base: "{colors.gray.12}", _dark: "{colors.grayDark.12}" },
131131
},

0 commit comments

Comments
 (0)