Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5a52823
feat(ProgressBar): add new ProgressBar component
MaxLee-dev Aug 27, 2026
a60ac13
test(ProgressBar): add visual regression snapshots
MaxLee-dev Aug 27, 2026
4ab0fb1
feat(ProgressBar): replace Status with Description
MaxLee-dev Sep 1, 2026
ad4de7e
fix(ProgressBar): align with Figma spec and cover error visuals
MaxLee-dev Sep 1, 2026
6d6bab6
Merge branch 'main' of https://github.com/goorm-dev/vapor-ui into pro…
MaxLee-dev Sep 3, 2026
af8e362
feat(ProgressBar): render IndicatorPrimitive inside Track by default
MaxLee-dev Sep 3, 2026
ff872d2
test(ProgressBar): regenerate visual regression snapshots
MaxLee-dev Sep 3, 2026
35d21d2
docs(ProgressBar): translate props descriptions and tighten prose
MaxLee-dev Sep 3, 2026
4911973
docs(ProgressBar): trim ProgressBar docs to essentials
MaxLee-dev Sep 3, 2026
490a7d3
refactor(ProgressBar): simplify description and clean up story examples
MaxLee-dev Sep 3, 2026
afa1f77
test(ProgressBar): add tests for callback ref forwarding and indeterm…
MaxLee-dev Sep 3, 2026
cc98ca4
chore: update vrt image
MaxLee-dev Sep 3, 2026
7b25247
docs(ProgressBar): remove trailing whitespace in progress-bar docs
MaxLee-dev Sep 3, 2026
4b85901
docs(changeset): reword ProgressBar changeset summary
MaxLee-dev Sep 3, 2026
41ce085
refactor(ProgressBar): drop duplicated value formatting and align dev…
MaxLee-dev Sep 3, 2026
41d8267
docs(rules): document how compound roots type their variants
MaxLee-dev Sep 3, 2026
7e821c6
chore: untrack personal report and plan documents
MaxLee-dev Sep 3, 2026
f0663dd
fix: apply CodeRabbit auto-fixes
MaxLee-dev Sep 4, 2026
0ea2478
ci(core): install http-server as a devDependency for the VRT web server
MaxLee-dev Sep 4, 2026
c6db808
Merge branch 'main' of https://github.com/goorm-dev/vapor-ui into pro…
MaxLee-dev Sep 4, 2026
6140783
fix: remove unnecessary styles from label and value components
MaxLee-dev Sep 4, 2026
2434107
fix: remove unnecessary comments from progress bar CSS
MaxLee-dev Sep 4, 2026
c482dfe
Merge remote-tracking branch 'origin/main' into progressbar-component
MaxLee-dev Sep 7, 2026
a408c27
feat(ProgressBar): fill the indicator with semantic colors and match …
MaxLee-dev Sep 10, 2026
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
5 changes: 5 additions & 0 deletions .changeset/progress-bar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@vapor-ui/core': minor
---

add new `ProgressBar` component
22 changes: 22 additions & 0 deletions .claude/rules/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ const { variant, size, orientation } = useTabsContext();

Context is used when the same variant value must style multiple sub-parts simultaneously. The sub-part's Props type uses `Omit` to remove Context-managed props, preventing users from passing them directly.

#### Typing Root-owned variants

Type the Root's variants from the recipe of the sub-part that actually consumes them. Do not declare a Root recipe just to host the type, and do not hand-write the union — the recipe is the single source and the type must follow it.

Alias the derived type once in `*.tsx` and use that alias for both `Root.Props` and the Context:

```ts
// dialog.tsx — Root has no recipe; Popup consumes the variants
type DialogVariants = DialogPopupVariants;
type DialogContext = DialogVariants;

// tabs.tsx — variants span two sub-parts
type TabsVariants = ListVariants & ButtonVariants;

// progress-bar.tsx — each sub-part contributes one key
type ProgressBarVariants = Pick<TrackVariants, 'size'> & Pick<DescriptionVariants, 'type'>;
```

Only when the Root element itself is styled by the recipe (`Toolbar`, `RadioGroup`, `SegmentedControl`) does the alias come from `RootVariants`.

Recipe variant keys are optional. If Root fills the defaults before writing to Context, type the Context as `Required<XxxVariants>` so sub-parts read them without re-applying defaults.

## CSS Variables — Component-scoped Tokens

Use `createVar` to decouple color palette from visual variant within a single recipe. The palette variant sets the variable values; the visual variant consumes them — this avoids N×M `compoundVariants` for every palette × variant combination.
Expand Down
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.
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.
133 changes: 133 additions & 0 deletions apps/website/content/docs/components/(components)/progress-bar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: 'ProgressBar'
site_name: 'ProgressBar - Vapor Core'
description: 'ProgressBar는 시작과 끝이 있는 작업의 진행 정도를 보여줍니다. 디스크 사용량이나 점수처럼 고정된 척도의 측정값에는 Meter를 사용합니다.'
---

<Demo name="progress-bar/default-progress-bar">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/default-progress-bar.tsx",
"codeblock": true
}
```
</Demo>

## Property

---

### Size

Track의 높이를 설정합니다.

<Demo name="progress-bar/progress-bar-size">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/progress-bar-size.tsx",
"codeblock": true
}
```
</Demo>

### Type

작업의 상태를 나타냅니다.

<Demo name="progress-bar/progress-bar-type">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/progress-bar-type.tsx",
"codeblock": true
}
```
</Demo>

### Value Text

`ProgressBar.Value`는 선언한 범위를 기준으로 값을 백분율로 환산해 보여줍니다.

백분율 대신 다른 표현이 필요하면 두 곳을 함께 바꿉니다. `getAriaValueText`는 보조기기가 듣는 `aria-valuetext`를, `ProgressBar.Value`의 `children` 함수는 화면 텍스트를 정합니다. 한쪽만 바꾸면 보는 사람과 듣는 사람이 다른 값을 받습니다.

<Demo name="progress-bar/progress-bar-value-text">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/progress-bar-value-text.tsx",
"codeblock": true
}
```
</Demo>

### Indeterminate

`value`가 `null`이면 진행률을 알 수 없는 상태입니다. 40% 폭의 세그먼트가 Track을 한 방향으로 훑고, `aria-valuenow`는 쓰이지 않습니다.

<Demo name="progress-bar/progress-bar-indeterminate">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/progress-bar-indeterminate.tsx",
"codeblock": true
}
```
</Demo>

## Examples

---

### Description

`ProgressBar.Description`은 진행 상황을 말로 풀어 씁니다. 남은 용량, 실패 사유, 다음에 할 일 같은 내용입니다.

`ProgressBar.Root`의 `type`이 `error`이면 이 텍스트가 danger 색이 되고 막대는 danger 색으로 끝까지 찹니다.

단계마다 다른 문구가 필요하면 `render`나 `className` 함수의 두 번째 인수 `state.status`를 보세요. 값이 없으면 `indeterminate`, 최댓값에 닿으면 `complete`, 그 사이는 `progressing`입니다.

<Demo name="progress-bar/progress-bar-description">
```json doc-gen:file
{
"file": "./src/components/demo/examples/progress-bar/progress-bar-description.tsx",
"codeblock": true
}
```
</Demo>

## Accessibility

---

- `ProgressBar.Label`을 넣거나 `ProgressBar.Root`에 `aria-label` 또는 `aria-labelledby`를 주세요. 이름이 없으면 보조기기가 숫자만 읽습니다. 개발 모드에서는 콘솔 경고가 나옵니다.
- 실패 사유나 다음 행동은 `ProgressBar.Description`에 적으세요. **`type="error"`는 겉모습만 바꿉니다.** 실패했다는 사실은 문구가 직접 말해야 합니다. 색과 꽉 찬 막대만으로는 스크린 리더 사용자도, 빨강과 회색을 구별하지 못하는 사용자도 실패를 알 수 없습니다.
- 진행 상황을 막대 길이만으로 전달하지 마세요. `ProgressBar.Value`나 `ProgressBar.Description`으로 텍스트를 함께 두면 확대 화면이나 저시력 환경에서도 값을 읽을 수 있습니다.

## Props Table

---

### ProgressBar.Root

<ComponentPropsTable componentName="progress-bar-root" />

### ProgressBar.Label

<ComponentPropsTable componentName="progress-bar-label" />

### ProgressBar.Value

<ComponentPropsTable componentName="progress-bar-value" />

### ProgressBar.Track

<ComponentPropsTable componentName="progress-bar-track" />

### ProgressBar.TrackPrimitive

<ComponentPropsTable componentName="progress-bar-track-primitive" />

### ProgressBar.IndicatorPrimitive

<ComponentPropsTable componentName="progress-bar-indicator-primitive" />

### ProgressBar.Description

<ComponentPropsTable componentName="progress-bar-description" />
1 change: 1 addition & 0 deletions apps/website/content/docs/components/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"(components)/navigation-menu.mdx",
"(components)/pagination.mdx",
"(components)/popover.mdx",
"(components)/progress-bar.mdx",
"(components)/radio-card.mdx",
"(components)/radio.mdx",
"(components)/select.mdx",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Description",
"displayName": "ProgressBar.Description",
"description": "진행 상황을 말로 풀어 씁니다. 실패 사유나 다음에 할 일 같은 내용입니다. `<span>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": [
"string | ((state: ProgressBarDescription.State) => (string | undefined))"
],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: ProgressBarDescription.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "IndicatorPrimitive",
"displayName": "ProgressBar.IndicatorPrimitive",
"description": "Track에서 채워진 부분입니다. `type`이 `error`이면 Track을 끝까지 채웁니다. `<div>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": [
"string | ((state: ProgressBar.IndicatorPrimitive.State) => (string | undefined))"
],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: ProgressBar.IndicatorPrimitive.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: ProgressBar.IndicatorPrimitive.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "Label",
"displayName": "ProgressBar.Label",
"description": "진행 중인 작업의 이름입니다. 접근 가능한 이름이 됩니다. `<span>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": [
"string | ((state: ProgressBar.Label.State) => (string | undefined))"
],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: ProgressBar.Label.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: ProgressBar.Label.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
103 changes: 103 additions & 0 deletions apps/website/public/components/generated/progress-bar-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"name": "Root",
"displayName": "ProgressBar.Root",
"description": "시작과 끝이 있는 작업의 진행 정도를 보여줍니다. `<div>` 요소로 렌더링됩니다. 디스크 사용량이나 점수처럼 고정된 척도의 측정값에는 `Meter`를 사용합니다.",
"props": [
{
"name": "value",
"type": [
"number",
"null"
],
"required": true,
"description": "현재 값입니다. `null`이면 진행률을 알 수 없는(indeterminate) 상태입니다."
},
{
"name": "size",
"type": [
"sm",
"md",
"lg"
],
"required": false,
"description": "Track의 높이입니다.",
"defaultValue": "md"
},
{
"name": "type",
"type": [
"default",
"error"
],
"required": false,
"description": "작업의 상태입니다. `error`는 Description을 danger 색으로 바꾸고 Indicator로 Track을 끝까지 채웁니다.",
"defaultValue": "default"
},
{
"name": "className",
"type": [
"string | ((state: ProgressBar.Root.State) => (string | undefined))"
],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "getAriaValueText",
"type": [
"(formattedValue: string, value: number | null) => string"
],
"required": false,
"description": "보조기기가 읽을 값 텍스트를 반환합니다. 지정하지 않으면 `aria-valuetext`를 쓰지 않고 `aria-valuenow`만으로 값을 전달합니다.\n\n화면에 보이는 텍스트는 바뀌지 않으니 `ProgressBar.Value`의 `children` 함수로 같은 텍스트를 맞춰 주세요."
},
{
"name": "format",
"type": [
"Intl.NumberFormatOptions"
],
"required": false,
"description": "값을 형식화하는 `Intl.NumberFormat` 옵션입니다."
},
{
"name": "locale",
"type": [
"Intl.LocalesArgument"
],
"required": false,
"description": "값을 형식화할 때 `Intl.NumberFormat`이 사용하는 로케일입니다. 기본값은 사용자 런타임의 로케일입니다."
},
{
"name": "max",
"type": [
"number"
],
"required": false,
"description": "최대값입니다.",
"defaultValue": "100"
},
{
"name": "min",
"type": [
"number"
],
"required": false,
"description": "최소값입니다.",
"defaultValue": "0"
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: ProgressBar.Root.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: ProgressBar.Root.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
Loading
Loading