Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .changeset/divider-dashed-gap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
"@jects/jds": minor
---

**Divider**

`variant="dashed"`의 대시가 6px 선, 6px 간격으로 그려집니다. 이전에는 브라우저가 대시 길이를 정했습니다. `DividerProps`가 `variant` 값에 따라 갈리는 유니온 타입이 되었고, 선이 `border`가 아닌 `background`로 그려집니다.

**소비처 영향 (코드 수정 필요)**

| AS-IS | TO-BE |
| ------------------------------------------ | ------------------------------------------ |
| `interface X extends DividerProps { ... }` | `type X = DividerProps & { ... }` |
| `border`로 선 두께나 색을 덮어쓰기 | `thickness` prop, `dividerColorVar`로 지정 |

`DividerProps`를 확장하던 타입은 교차 타입으로 바꿉니다.

```diff
-interface MyDividerProps extends DividerProps {
- spacing?: number;
-}
+type MyDividerProps = DividerProps & {
+ spacing?: number;
+};
```

`border`로 선을 덮어쓰던 스타일은 제거합니다. 기본 선을 대체하지 않고 겹쳐 그려지며, 요소 높이가 `thickness`만큼 늘어납니다.

```diff
-<Divider className='border-t-2 border-red-500' />
+<Divider thickness='bold' />
```

`dashLength`와 `dashGap`은 `variant="dashed"`를 함께 지정해야 쓸 수 있습니다.

**추가**

- `dashLength` — 점선 한 칸의 길이(px), 기본값 `6`, 최솟값 `1`
- `dashGap` — 점선 사이 간격(px), 지정하지 않으면 하한이 적용된 `dashLength`를 따라감, 최솟값 `1`

```tsx
<Divider variant='dashed' /> // 6 / 6
<Divider variant='dashed' dashLength={2} /> // 2 / 2
<Divider variant='dashed' dashLength={2} dashGap={6} /> // 2 / 6
```

**동작 변경 (코드 수정 불필요)**

- 강제 색상 모드(`forced-colors: active`)에서 `dashLength`와 `dashGap`이 반영되지 않음, 브라우저가 정한 대시가 적용됨
- `dividerColorVar`로 색을 덮어쓰는 방식은 그대로 동작
- `orientation`, `thickness`, `variant`와 렌더 엘리먼트, `role`은 변경 없음
158 changes: 35 additions & 123 deletions packages/jds/src/components/Divider/Divider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ const meta = {
defaultValue: { summary: "false" },
},
},
dashLength: {
control: "number",
description: "점선 한 칸의 길이(px), variant가 dashed일 때만 반영",
table: {
defaultValue: { summary: "6" },
},
},
dashGap: {
control: "number",
description: "점선 사이 간격(px), 지정하지 않으면 dashLength를 따라감",
table: {
defaultValue: { summary: "dashLength" },
},
},
},
} satisfies Meta<typeof Divider>;

Expand Down Expand Up @@ -121,22 +135,32 @@ export const AllVariants: Story = {
},
};

export const HorizontalDivider: Story = {
export const DashSpacing: Story = {
render: () => (
<FlexColumn gap='16px' style={{ width: "400px" }}>
<div>섹션 1의 콘텐츠</div>
<Divider />
<div>섹션 2의 콘텐츠</div>
<Divider thickness='bold' />
<div>섹션 3의 콘텐츠</div>
<FlexColumn gap='24px' style={{ width: "300px" }}>
<FlexColumn gap='8px'>
<Label>기본값 (6 / 6):</Label>
<Divider variant='dashed' />
</FlexColumn>
<FlexColumn gap='8px'>
<Label>dashLength=2 (2 / 2):</Label>
<Divider variant='dashed' dashLength={2} />
</FlexColumn>
<FlexColumn gap='8px'>
<Label>dashLength=2, dashGap=6:</Label>
<Divider variant='dashed' dashLength={2} dashGap={6} />
</FlexColumn>
<FlexColumn gap='8px'>
<Label>dashLength=12, dashGap=4, thickness=bold:</Label>
<Divider variant='dashed' dashLength={12} dashGap={4} thickness='bold' />
</FlexColumn>
</FlexColumn>
),
parameters: {
docs: {
description: {
story:
"Horizontal divider는 `<hr>` 태그를 사용하여 섹션을 수평으로 구분합니다. " +
'Semantic HTML을 준수하며, 스크린 리더는 이를 "Horizontal Splitter"로 읽습니다.',
"점선 간격은 기본값이 6px / 6px이고 dashLength와 dashGap으로 조정합니다. dashGap을 생략하면 dashLength를 따라갑니다.",
},
},
},
Expand All @@ -150,6 +174,8 @@ export const VerticalDivider: Story = {
<div>중앙 콘텐츠</div>
<Divider orientation='vertical' thickness='bold' />
<div>우측 콘텐츠</div>
<Divider orientation='vertical' variant='dashed' />
<div>점선 구분</div>
</FlexRow>
),
parameters: {
Expand All @@ -163,84 +189,6 @@ export const VerticalDivider: Story = {
},
};

export const DashedDivider: Story = {
render: () => (
<FlexColumn gap='24px' style={{ width: "400px" }}>
<FlexColumn gap='16px'>
<h4 style={{ margin: 0 }}>수평 Dashed</h4>
<div>섹션 1</div>
<Divider variant='dashed' />
<div>섹션 2</div>
<Divider variant='dashed' thickness='bold' />
<div>섹션 3</div>
</FlexColumn>
<FlexRow gap='16px' style={{ height: "100px", alignItems: "stretch" }}>
<div>좌측</div>
<Divider orientation='vertical' variant='dashed' />
<div>중앙</div>
<Divider orientation='vertical' variant='dashed' thickness='bold' />
<div>우측</div>
</FlexRow>
</FlexColumn>
),
parameters: {
docs: {
description: {
story: 'variant="dashed"를 사용하여 점선 스타일의 구분선을 만들 수 있습니다.',
},
},
},
};

export const InCard: Story = {
render: () => (
<div
style={{
width: "400px",
padding: "24px",
border: "1px solid #e0e0e0",
borderRadius: "8px",
}}
>
<h3 style={{ margin: "0 0 16px 0" }}>카드 제목</h3>
<p style={{ margin: "0 0 16px 0" }}>첫 번째 정보 그룹입니다.</p>
<Divider />
<p style={{ margin: "16px 0 0 0" }}>두 번째 정보 그룹입니다.</p>
</div>
),
parameters: {
docs: {
description: {
story:
"카드 내부에서 정보 그룹을 구분할 때 사용할 수 있습니다.(실제 Card 컴포넌트를 사용하지 않음)",
},
},
},
};

export const InFlexLayout: Story = {
render: () => (
<FlexRow gap='16px' style={{ height: "200px", width: "500px" }}>
<FlexColumn style={{ flex: 1, padding: "16px", backgroundColor: "#f5f5f5" }}>
<h4 style={{ margin: "0 0 8px 0" }}>사이드바</h4>
<p style={{ margin: 0, fontSize: "14px" }}>사이드바 콘텐츠</p>
</FlexColumn>
<Divider orientation='vertical' thickness='normal' />
<FlexColumn style={{ flex: 2, padding: "16px" }}>
<h4 style={{ margin: "0 0 8px 0" }}>메인 콘텐츠</h4>
<p style={{ margin: 0, fontSize: "14px" }}>메인 콘텐츠 영역</p>
</FlexColumn>
</FlexRow>
),
parameters: {
docs: {
description: {
story: "Flex 레이아웃에서 수직 divider를 사용하여 영역을 구분할 수 있습니다.",
},
},
},
};

export const DecorativeDivider: Story = {
render: () => (
<FlexColumn gap='16px' style={{ width: "400px" }}>
Expand All @@ -259,39 +207,3 @@ export const DecorativeDivider: Story = {
},
},
};

export const StyleGuide: Story = {
render: () => (
<FlexColumn gap='32px' style={{ width: "700px", fontSize: "14px" }}>
<div>
<h3 style={{ margin: "0 0 16px 0" }}>스타일 가이드</h3>
</div>

<Divider />

<div>
<h4 style={{ margin: "0 0 12px 0" }}>기본 사용</h4>
<pre
style={{
backgroundColor: "#f5f5f5",
padding: "12px",
borderRadius: "4px",
margin: 0,
}}
>
{`<Divider />
<Divider orientation="vertical" />
<Divider thickness="bold" />
<Divider variant="dashed" />`}
</pre>
</div>
</FlexColumn>
),
parameters: {
docs: {
description: {
story: "컴포넌트의 스타일 구현 원칙과 사용 가이드입니다.",
},
},
},
};
13 changes: 12 additions & 1 deletion packages/jds/src/components/Divider/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { assignInlineVars } from "@vanilla-extract/dynamic";
import { clsx } from "clsx";
import { forwardRef, type Ref } from "react";

import { divider } from "./divider.css";
import { divider, dividerDashGapVar, dividerDashLengthVar } from "./divider.css";
import type { DividerProps } from "./divider.types";

export const Divider = forwardRef<HTMLHRElement | HTMLDivElement, DividerProps>(
Expand All @@ -11,12 +12,20 @@ export const Divider = forwardRef<HTMLHRElement | HTMLDivElement, DividerProps>(
orientation = "horizontal",
decorative = false,
variant = "solid",
dashLength,
dashGap,
className,
style,
...restProps
},
ref,
) => {
const classes = clsx(divider({ orientation, thickness, variant }), className);
const dashStyle = assignInlineVars({
...(dashLength === undefined ? {} : { [dividerDashLengthVar]: `${dashLength}px` }),
...(dashGap === undefined ? {} : { [dividerDashGapVar]: `${dashGap}px` }),
});
const mergedStyle = { ...dashStyle, ...style };

if (orientation === "vertical") {
return (
Expand All @@ -26,6 +35,7 @@ export const Divider = forwardRef<HTMLHRElement | HTMLDivElement, DividerProps>(
aria-orientation='vertical'
aria-hidden={decorative}
className={classes}
style={mergedStyle}
{...restProps}
/>
);
Expand All @@ -36,6 +46,7 @@ export const Divider = forwardRef<HTMLHRElement | HTMLDivElement, DividerProps>(
ref={ref as Ref<HTMLHRElement>}
aria-hidden={decorative}
className={classes}
style={mergedStyle}
{...restProps}
/>
);
Expand Down
42 changes: 37 additions & 5 deletions packages/jds/src/components/Divider/divider.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ const lineStyle = createVar();
* 값을 주입하지 않으면 Divider의 기본 stroke 색상으로 fallback된다.
*/
export const dividerColorVar = createVar();
export const dividerDashLengthVar = createVar();
export const dividerDashGapVar = createVar();

const DEFAULT_DASH_LENGTH = 6;

const lineColor = fallbackVar(dividerColorVar, vars.color.semantic.stroke.alpha.assistive);
const dashLengthInput = fallbackVar(dividerDashLengthVar, `${DEFAULT_DASH_LENGTH}px`);
const dashLength = `max(1px, ${dashLengthInput})`;
const dashGap = `max(1px, ${fallbackVar(dividerDashGapVar, dashLength)})`;

const dashedLine = (direction: "to right" | "to bottom") =>
`repeating-linear-gradient(${direction}, ${lineColor} 0 ${dashLength}, transparent ${dashLength} calc(${dashLength} + ${dashGap}))`;

export const divider = recipe({
base: {
Expand All @@ -23,14 +35,24 @@ export const divider = recipe({
orientation: {
horizontal: {
width: "100%",
height: 0,
borderTop: `${thickness} ${lineStyle} ${fallbackVar(dividerColorVar, vars.color.semantic.stroke.alpha.assistive)}`,
height: thickness,
"@media": {
"(forced-colors: active)": {
height: 0,
borderTop: `${thickness} ${lineStyle} currentColor`,
},
},
},
vertical: {
width: 0,
width: thickness,
height: "100%",
alignSelf: "stretch",
borderLeft: `${thickness} ${lineStyle} ${fallbackVar(dividerColorVar, vars.color.semantic.stroke.alpha.assistive)}`,
"@media": {
"(forced-colors: active)": {
width: 0,
borderLeft: `${thickness} ${lineStyle} currentColor`,
},
},
},
},
thickness: {
Expand All @@ -40,10 +62,20 @@ export const divider = recipe({
boldest: { vars: { [thickness]: "8px" } },
},
variant: {
solid: { vars: { [lineStyle]: "solid" } },
solid: { backgroundColor: lineColor, vars: { [lineStyle]: "solid" } },
dashed: { vars: { [lineStyle]: "dashed" } },
},
},
compoundVariants: [
{
variants: { orientation: "horizontal", variant: "dashed" },
style: { backgroundImage: dashedLine("to right") },
},
{
variants: { orientation: "vertical", variant: "dashed" },
style: { backgroundImage: dashedLine("to bottom") },
},
],
defaultVariants: {
orientation: "horizontal",
thickness: "normal",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
17 changes: 15 additions & 2 deletions packages/jds/src/components/Divider/divider.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ export type DividerThickness = "normal" | "bold" | "bolder" | "boldest";
export type DividerOrientation = "horizontal" | "vertical";
export type DividerVariant = "solid" | "dashed";

export interface DividerProps extends Omit<ComponentPropsWithoutRef<"hr">, "children" | "color"> {
interface BaseDividerProps extends Omit<ComponentPropsWithoutRef<"hr">, "children" | "color"> {
thickness?: DividerThickness;
decorative?: boolean;
orientation?: DividerOrientation;
variant?: DividerVariant;
}

interface SolidDividerProps {
variant?: Exclude<DividerVariant, "dashed">;
dashLength?: never;
dashGap?: never;
}

interface DashedDividerProps {
variant: "dashed";
dashLength?: number;
dashGap?: number;
}

export type DividerProps = BaseDividerProps & (SolidDividerProps | DashedDividerProps);
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading