Skip to content

Commit b7b77c0

Browse files
committed
Merge branch 'main' into 25-button-component
2 parents 5aca818 + 09afbbd commit b7b77c0

File tree

8 files changed

+224
-8
lines changed

8 files changed

+224
-8
lines changed

src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import logo from "/logo.svg";
22
import { css } from "../styled-system/css";
33
import { Button } from "./components/Button";
44
import { Heading } from "./components/Heading";
5+
import { Text } from "./components/Text";
56

67
function App() {
78
return (
@@ -56,7 +57,9 @@ function App() {
5657
<Button>클릭</Button>
5758
</section>
5859
</main>
59-
<footer>© 2024 Dale UI. All rights reserved.</footer>
60+
<footer>
61+
<Text muted>© 2024 Dale UI. All rights reserved.</Text>
62+
</footer>
6063
</div>
6164
);
6265
}

src/components/Heading/Heading.stories.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
22
import { vstack } from "../../../styled-system/patterns";
33
import { Heading } from "./Heading";
44

5-
const meta = {
5+
export default {
66
component: Heading,
77
parameters: {
88
layout: "centered",
@@ -13,11 +13,9 @@ const meta = {
1313
},
1414
} satisfies Meta<typeof Heading>;
1515

16-
export default meta;
16+
export const Basic: StoryObj<typeof Heading> = {};
1717

18-
export const Basic: StoryObj<typeof meta> = {};
19-
20-
export const Levels: StoryObj<typeof meta> = {
18+
export const Levels: StoryObj<typeof Heading> = {
2119
render: (args) => {
2220
return (
2321
<div className={vstack({ gap: "6" })}>
@@ -52,7 +50,7 @@ export const Levels: StoryObj<typeof meta> = {
5250
},
5351
};
5452

55-
export const Contrasts: StoryObj<typeof meta> = {
53+
export const Contrasts: StoryObj<typeof Heading> = {
5654
render: (args) => {
5755
return (
5856
<div className={vstack({ gap: "6" })}>

src/components/Heading/Heading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
1313
size?: FontSize;
1414
/** 굵기 */
1515
weight?: FontWeight;
16-
/** 명암비 */
16+
/** 명암비 낮출지 */
1717
muted?: boolean;
1818
}
1919

src/components/Text/Text.stories.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { vstack } from "../../../styled-system/patterns";
3+
import { Text } from "./Text";
4+
5+
export default {
6+
component: Text,
7+
parameters: {
8+
layout: "centered",
9+
},
10+
args: {
11+
children: "본문",
12+
},
13+
} satisfies Meta<typeof Text>;
14+
15+
export const Basic: StoryObj<typeof Text> = {};
16+
17+
export const Tones: StoryObj<typeof Text> = {
18+
render: (args) => {
19+
return (
20+
<div className={vstack({ gap: "6" })}>
21+
<Text {...args} tone="neutral">
22+
중립 색조
23+
</Text>
24+
<Text {...args} tone="accent">
25+
강조 색조
26+
</Text>
27+
<Text {...args} tone="danger">
28+
위험 색조
29+
</Text>
30+
<Text {...args} tone="warning">
31+
경고 색조
32+
</Text>
33+
</div>
34+
);
35+
},
36+
argTypes: {
37+
children: {
38+
control: false,
39+
},
40+
tone: {
41+
control: false,
42+
},
43+
},
44+
};
45+
46+
export const Contrasts: StoryObj<typeof Text> = {
47+
render: (args) => {
48+
return (
49+
<div className={vstack({ gap: "6" })}>
50+
<Text {...args} muted>
51+
낮은 명암비
52+
</Text>
53+
<Text {...args}>높은 명암비</Text>
54+
</div>
55+
);
56+
},
57+
argTypes: {
58+
children: {
59+
control: false,
60+
},
61+
muted: {
62+
control: false,
63+
},
64+
},
65+
};

src/components/Text/Text.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 "./Text.stories";
7+
8+
const { Basic, Tones, Contrasts } = composeStories(stories);
9+
10+
test("renders the heading with the correct text content", () => {
11+
render(<Basic>테스트</Basic>);
12+
13+
expect(screen.getByText("테스트"));
14+
});
15+
16+
test("applies the correct font weight class based on the weight prop", () => {
17+
const weight = faker.helpers.arrayElement(
18+
Object.keys(fontWeights)
19+
) as keyof typeof fontWeights;
20+
21+
render(<Basic weight={weight} />);
22+
23+
expect(screen.getByText("본문")).toHaveClass(`fw_${weight}`);
24+
});
25+
26+
test("applies the correct font size class based on the size prop", () => {
27+
const size = faker.helpers.arrayElement(
28+
Object.keys(fontSizes)
29+
) as keyof typeof fontSizes;
30+
31+
render(<Basic size={size} />);
32+
33+
expect(screen.getByText("본문")).toHaveClass(`fs_${size}`);
34+
});
35+
36+
test("applies the correct color based on the tone", () => {
37+
render(<Tones />);
38+
39+
expect(screen.getByText("중립 색조")).toHaveClass("c_text");
40+
41+
expect(screen.getByText("강조 색조")).toHaveClass("c_text.accent");
42+
43+
expect(screen.getByText("위험 색조")).toHaveClass("c_text.danger");
44+
45+
expect(screen.getByText("경고 색조")).toHaveClass("c_text.warning");
46+
});
47+
48+
test("applies the correct color for low and high contrast", () => {
49+
render(<Contrasts />);
50+
51+
expect(screen.getByText("낮은 명암비")).toHaveClass("c_text.muted");
52+
53+
expect(screen.getByText("높은 명암비")).toHaveClass("c_text");
54+
});

src/components/Text/Text.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { ReactNode, HTMLAttributes } from "react";
2+
import { css, cva } from "../../../styled-system/css";
3+
import type { Tone } from "../../tokens/colors";
4+
import type { FontSize, FontWeight } from "../../tokens/typography";
5+
6+
export interface TextProps extends HTMLAttributes<HTMLElement> {
7+
/** 텍스트 */
8+
children: ReactNode;
9+
/** HTML 태그 */
10+
as?: "span" | "div" | "p" | "strong" | "em" | "small";
11+
/** 색조 */
12+
tone?: Tone;
13+
/** 크기 */
14+
size?: FontSize;
15+
/** 굵기 */
16+
weight?: FontWeight;
17+
/** 명암비 낮출지 */
18+
muted?: boolean;
19+
}
20+
21+
/**
22+
* - `as` 속성으로 어떤 HTML 태그를 사용할지 지정할 수 있습니다.
23+
* - `muted` 속성을 주시면 글자색이 옅어집니다. 명암비가 낮아지므로 접근성 측면에서 주의해서 사용하세요.
24+
*/
25+
export const Text = ({
26+
children,
27+
as: Tag = "span",
28+
tone = "neutral",
29+
size,
30+
weight,
31+
muted = false,
32+
...rest
33+
}: TextProps) => {
34+
return (
35+
<Tag
36+
className={css(
37+
styles.raw({ tone, muted }),
38+
css.raw({
39+
fontSize: size,
40+
fontWeight: weight,
41+
})
42+
)}
43+
{...rest}
44+
>
45+
{children}
46+
</Tag>
47+
);
48+
};
49+
50+
const styles = cva({
51+
compoundVariants: [
52+
{
53+
muted: false,
54+
tone: "neutral",
55+
css: { color: "text" },
56+
},
57+
{
58+
muted: false,
59+
tone: "accent",
60+
css: { color: "text.accent" },
61+
},
62+
{
63+
muted: false,
64+
tone: "danger",
65+
css: { color: "text.danger" },
66+
},
67+
{
68+
muted: false,
69+
tone: "warning",
70+
css: { color: "text.warning" },
71+
},
72+
{
73+
muted: true,
74+
tone: "neutral",
75+
css: { color: "text.muted" },
76+
},
77+
{
78+
muted: true,
79+
tone: "accent",
80+
css: { color: "text.muted.accent" },
81+
},
82+
{
83+
muted: true,
84+
tone: "danger",
85+
css: { color: "text.muted.danger" },
86+
},
87+
{
88+
muted: true,
89+
tone: "warning",
90+
css: { color: "text.muted.warning" },
91+
},
92+
],
93+
});

src/components/Text/index.tsx

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

src/tokens/colors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Tokens, SemanticTokens } from "@pandacss/types";
22

3+
export type Tone = "neutral" | "accent" | "danger" | "warning";
4+
35
export const semanticColors: SemanticTokens["colors"] = {
46
current: { value: "currentColor" },
57
transparent: { value: "rgb(0 0 0 / 0)" },

0 commit comments

Comments
 (0)