Skip to content

Commit

Permalink
Merge pull request #33 from DaleStudy/29-text
Browse files Browse the repository at this point in the history
Text 컴포넌트
  • Loading branch information
DaleSeo authored Jan 22, 2025
2 parents 3a00846 + 9bd715c commit 09afbbd
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 8 deletions.
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import logo from "/logo.svg";
import { css } from "../styled-system/css";
import { Button } from "./components/Button";
import { Heading } from "./components/Heading";
import { Text } from "./components/Text";

function App() {
return (
Expand Down Expand Up @@ -56,7 +57,9 @@ function App() {
<Button>클릭</Button>
</section>
</main>
<footer>© 2024 Dale UI. All rights reserved.</footer>
<footer>
<Text muted>© 2024 Dale UI. All rights reserved.</Text>
</footer>
</div>
);
}
Expand Down
10 changes: 4 additions & 6 deletions src/components/Heading/Heading.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { vstack } from "../../../styled-system/patterns";
import { Heading } from "./Heading";

const meta = {
export default {
component: Heading,
parameters: {
layout: "centered",
Expand All @@ -13,11 +13,9 @@ const meta = {
},
} satisfies Meta<typeof Heading>;

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

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

export const Levels: StoryObj<typeof meta> = {
export const Levels: StoryObj<typeof Heading> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
Expand Down Expand Up @@ -52,7 +50,7 @@ export const Levels: StoryObj<typeof meta> = {
},
};

export const Contrasts: StoryObj<typeof meta> = {
export const Contrasts: StoryObj<typeof Heading> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Heading/Heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
size?: FontSize;
/** 굵기 */
weight?: FontWeight;
/** 명암비 */
/** 명암비 낮출지 */
muted?: boolean;
}

Expand Down
65 changes: 65 additions & 0 deletions src/components/Text/Text.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Meta, StoryObj } from "@storybook/react";
import { vstack } from "../../../styled-system/patterns";
import { Text } from "./Text";

export default {
component: Text,
parameters: {
layout: "centered",
},
args: {
children: "본문",
},
} satisfies Meta<typeof Text>;

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

export const Tones: StoryObj<typeof Text> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
<Text {...args} tone="neutral">
중립 색조
</Text>
<Text {...args} tone="accent">
강조 색조
</Text>
<Text {...args} tone="danger">
위험 색조
</Text>
<Text {...args} tone="warning">
경고 색조
</Text>
</div>
);
},
argTypes: {
children: {
control: false,
},
tone: {
control: false,
},
},
};

export const Contrasts: StoryObj<typeof Text> = {
render: (args) => {
return (
<div className={vstack({ gap: "6" })}>
<Text {...args} muted>
낮은 명암비
</Text>
<Text {...args}>높은 명암비</Text>
</div>
);
},
argTypes: {
children: {
control: false,
},
muted: {
control: false,
},
},
};
54 changes: 54 additions & 0 deletions src/components/Text/Text.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 "./Text.stories";

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

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

expect(screen.getByText("테스트"));
});

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.getByText("본문")).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.getByText("본문")).toHaveClass(`fs_${size}`);
});

test("applies the correct color based on the tone", () => {
render(<Tones />);

expect(screen.getByText("중립 색조")).toHaveClass("c_text");

expect(screen.getByText("강조 색조")).toHaveClass("c_text.accent");

expect(screen.getByText("위험 색조")).toHaveClass("c_text.danger");

expect(screen.getByText("경고 색조")).toHaveClass("c_text.warning");
});

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

expect(screen.getByText("낮은 명암비")).toHaveClass("c_text.muted");

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

export interface TextProps extends HTMLAttributes<HTMLElement> {
/** 텍스트 */
children: ReactNode;
/** HTML 태그 */
as?: "span" | "div" | "p" | "strong" | "em" | "small";
/** 색조 */
tone?: Tone;
/** 크기 */
size?: FontSize;
/** 굵기 */
weight?: FontWeight;
/** 명암비 낮출지 */
muted?: boolean;
}

/**
* - `as` 속성으로 어떤 HTML 태그를 사용할지 지정할 수 있습니다.
* - `muted` 속성을 주시면 글자색이 옅어집니다. 명암비가 낮아지므로 접근성 측면에서 주의해서 사용하세요.
*/
export const Text = ({
children,
as: Tag = "span",
tone = "neutral",
size,
weight,
muted = false,
...rest
}: TextProps) => {
return (
<Tag
className={css(
styles.raw({ tone, muted }),
css.raw({
fontSize: size,
fontWeight: weight,
})
)}
{...rest}
>
{children}
</Tag>
);
};

const styles = cva({
compoundVariants: [
{
muted: false,
tone: "neutral",
css: { color: "text" },
},
{
muted: false,
tone: "accent",
css: { color: "text.accent" },
},
{
muted: false,
tone: "danger",
css: { color: "text.danger" },
},
{
muted: false,
tone: "warning",
css: { color: "text.warning" },
},
{
muted: true,
tone: "neutral",
css: { color: "text.muted" },
},
{
muted: true,
tone: "accent",
css: { color: "text.muted.accent" },
},
{
muted: true,
tone: "danger",
css: { color: "text.muted.danger" },
},
{
muted: true,
tone: "warning",
css: { color: "text.muted.warning" },
},
],
});
1 change: 1 addition & 0 deletions src/components/Text/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Text } from "./Text";
2 changes: 2 additions & 0 deletions src/tokens/colors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Tokens, SemanticTokens } from "@pandacss/types";

export type Tone = "neutral" | "accent" | "danger" | "warning";

export const semanticColors: SemanticTokens["colors"] = {
current: { value: "currentColor" },
transparent: { value: "rgb(0 0 0 / 0)" },
Expand Down

0 comments on commit 09afbbd

Please sign in to comment.