Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
5 changes: 5 additions & 0 deletions .changeset/meter-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@vapor-ui/core': minor
---

add new `Meter` component
152 changes: 152 additions & 0 deletions apps/website/content/docs/components/(components)/meter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
title: 'Meter'
site_name: 'Meter - Vapor Core'
description: 'Meter는 디스크 사용량이나 점수처럼 알려진 범위 안의 측정값을 표시합니다.'
---

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

`Meter.Label`은 보조기술에 전달되는 이름이기도 합니다. `저장 공간`, `CPU 사용률`처럼 **무엇을 재는 값인지**를 문구에 담으세요. 화면에 이름을 두지 않을 때는 `Meter.Label` 대신 `Meter.Root`에 `aria-label`을 주세요 — 둘을 함께 주면 `aria-label`이 무시됩니다.

## Property

---

### Size

`size`로 트랙의 높이를 조절합니다.

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

### Type

`type`으로 채워진 영역의 색상을 지정합니다. 색만으로는 위험 구간을 알 수 없으므로 레이블에도 함께 적어줍니다.

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

### Range

`min`, `max`로 범위를, `format`으로 표시 형식을 지정합니다. 값 텍스트가 단위만 담고 있다면 `getAriaValueText`로 무엇을 재는 값인지 함께 읽히도록 합니다.

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

## Examples

---

### Auto Update

값이 스스로 바뀌는 미터에는 갱신을 멈출 수단을 함께 둡니다. 갱신 간격은 초당 3회를 넘기지 않습니다.

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

## Accessibility

---

`role="meter"`와 `aria-valuenow` / `aria-valuemin` / `aria-valuemax`는 `Meter`가 알아서 붙입니다. 나머지는 컴포넌트가 대신 지킬 수 없으니 **쓰는 쪽에서 챙겨야 합니다.**

### 진행률에는 쓰지 마세요

`Meter`가 다루는 값은 **범위가 정해진 지금의 측정값**입니다. 디스크 사용량, 점수, 잔량이 여기 해당합니다. 작업이 얼마나 끝났는지 보여주는 진행률이나 상한이 없는 값은 대상이 아닙니다.

진행률에 `role="meter"`가 붙으면 보조기술은 "완료를 향해 가는 값"이 아니라 "지금 재고 있는 값"으로 읽습니다. `render`로 역할만 갈아끼우지 말고 진행률에 맞는 컴포넌트를 쓰세요.

### 이름은 하나만 주세요

`Meter.Label`을 렌더하면 `Meter.Root`에 준 `aria-label`은 이름 계산에서 밀려납니다. 오류도 경고도 없이 조용히 사라지니 둘 중 하나만 쓰세요.

- 이름을 화면에 보여준다 → `Meter.Label`
- 화면에 이름을 두지 않는다 → `Meter.Root`의 `aria-label`

### 같은 값은 같은 문구·같은 형식으로

한 측정값이 여러 화면에 나온다면 레이블 문구와 `format`을 맞추세요. 여기서는 `저장 공간 4.2GB`, 저기서는 `디스크 52%`라면 읽는 사람은 둘이 같은 값인지 알 길이 없습니다.

### 자동으로 갱신된다면 멈출 수단을 두세요

값이 5초를 넘겨 저절로 바뀐다면 사용자가 일시정지·정지·숨김 중 하나를 고를 수 있어야 합니다. 갱신 간격은 **초당 3회 이하**로 잡으세요. 그보다 빠르면 화면이 깜빡이는 것처럼 보입니다. 위 [Auto Update](#auto-update)가 그 예입니다.

### 색을 바꿀 때는 대비를 확인하세요

`className`이나 `$css`로 색을 갈아끼우면 기본 색이 맞춰 둔 대비가 무너집니다.

- `Meter.Label` / `Meter.Value` 텍스트 대 배경 — **4.5:1** 이상
- `Meter.Indicator` 대 `Meter.Track`, `Meter.Track` 대 페이지 배경 — **3:1** 이상

`type="warning"`처럼 색으로 상태를 알린다면 그 상태를 레이블 문구에도 적으세요. 색을 구분하지 못하는 사용자에게는 문구가 유일한 단서입니다.

### 미터를 가리킬 때 모양·색·위치로만 말하지 마세요

"오른쪽 초록 막대를 확인하세요" 같은 안내는 그 색과 위치를 알아채는 사람에게만 통합니다. 미터를 가리킬 때는 레이블 문구를 부르세요.

### 페이지와 언어가 다르면 표시하세요

레이블이 페이지 언어와 다른 언어로 적혀 있다면 그 요소에 `lang`을 주세요. 값 형식도 그 언어를 따라야 한다면 `locale`까지 맞춥니다.

### 폭을 고정하지 마세요

`Meter.Root`는 기본이 `width: 100%`입니다. `width`를 고정하는 대신 `max-width`를 쓰면 320px 폭에서도, 200% 확대에서도 내용이 잘리지 않습니다.

### 값은 텍스트로 보여주세요

`42%` 같은 숫자를 그림으로 만들어 넣지 마세요. `Meter.Value`가 내는 텍스트여야 사용자가 글자 크기나 자간을 바꿨을 때 값도 함께 바뀝니다.

## Props Table

---

### Meter.Root

<ComponentPropsTable componentName="meter-root" />

### Meter.Label

<ComponentPropsTable componentName="meter-label" />

### Meter.Value

<ComponentPropsTable componentName="meter-value" />

### Meter.Track

<ComponentPropsTable componentName="meter-track" />

### Meter.Indicator

<ComponentPropsTable componentName="meter-indicator" />
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 @@ -30,6 +30,7 @@
"(components)/icon-button.mdx",
"(components)/input-group.mdx",
"(components)/menu.mdx",
"(components)/meter.mdx",
"(components)/multi-select.mdx",
"(components)/navigation-menu.mdx",
"(components)/pagination.mdx",
Expand Down
3 changes: 3 additions & 0 deletions apps/website/docs-extractor.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ export default defineConfig({
'button/button.tsx': {
include: ['nativeButton'],
},
'meter/meter.tsx': {
include: ['aria-valuetext'],
},
},
});
29 changes: 29 additions & 0 deletions apps/website/public/components/generated/meter-indicator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "Indicator",
"displayName": "Meter.Indicator",
"description": "현재 값에 해당하는 만큼 트랙을 채웁니다. `<div>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": ["string | ((state: Meter.Indicator.State) => (string | undefined))"],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: Meter.Indicator.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: Meter.Indicator.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
29 changes: 29 additions & 0 deletions apps/website/public/components/generated/meter-label.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "Label",
"displayName": "Meter.Label",
"description": "미터의 이름을 화면에 보여주고 보조기술에 전달합니다. `<span>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": ["string | ((state: Meter.Label.State) => (string | undefined))"],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: Meter.Label.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: Meter.Label.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
87 changes: 87 additions & 0 deletions apps/website/public/components/generated/meter-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"name": "Root",
"displayName": "Meter.Root",
"description": "알려진 범위 안에서 측정값을 표시하는 컴포넌트입니다. 디스크 사용량이나 점수처럼 값 자체의 크기를 보여줄 때 사용합니다. `<div>` 요소로 렌더링됩니다.",
"props": [
{
"name": "value",
"type": ["number"],
"required": true,
"description": "미터의 현재 값"
},
{
"name": "size",
"type": ["sm", "md", "lg"],
"required": false,
"description": "미터의 크기. 트랙의 높이를 결정합니다.",
"defaultValue": "md"
},
{
"name": "type",
"type": ["default", "warning"],
"required": false,
"description": "채워진 영역의 색상. 색만으로 상태를 알리지 않도록 레이블 문구에도 함께 적어 주세요.",
"defaultValue": "default"
},
{
"name": "className",
"type": ["string | ((state: Meter.Root.State) => (string | undefined))"],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "aria-valuetext",
"type": ["string"],
"required": false,
"description": "현재 값을 대신해 보조기술에 전달할 텍스트. 값에 따라 달라지는 텍스트가 필요하면 `getAriaValueText`를 사용하세요."
},
{
"name": "format",
"type": ["Intl.NumberFormatOptions"],
"required": false,
"description": "값의 표시 형식을 지정하는 옵션"
},
{
"name": "getAriaValueText",
"type": ["(formattedValue: string, value: number) => string"],
"required": false,
"description": "`aria-valuenow`(미터의 현재 값)를 대신할, 사람이 읽을 수 있는 텍스트를 반환하는 함수"
},
{
"name": "locale",
"type": ["ko-KR", "ja-JP", "en-US"],
"required": false,
"description": "값을 형식화할 때 `Intl.NumberFormat`이 사용할 로케일\n\n기본값은 사용자 런타임의 로케일입니다."
},
{
"name": "max",
"type": ["number"],
"required": false,
"description": "미터의 최대값",
"defaultValue": "100"
},
{
"name": "min",
"type": ["number"],
"required": false,
"description": "미터의 최소값",
"defaultValue": "0"
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: Meter.Root.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: Meter.Root.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
29 changes: 29 additions & 0 deletions apps/website/public/components/generated/meter-track.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "Track",
"displayName": "Meter.Track",
"description": "미터의 전체 범위를 나타내며 Indicator를 담습니다. `<div>` 요소로 렌더링됩니다.",
"props": [
{
"name": "className",
"type": ["string | ((state: Meter.Track.State) => (string | undefined))"],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: Meter.Track.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: Meter.Track.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
35 changes: 35 additions & 0 deletions apps/website/public/components/generated/meter-value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Value",
"displayName": "Meter.Value",
"description": "형식화된 값을 텍스트로 표시합니다. 중복 안내를 막기 위해 보조기술에서는 숨겨집니다. `<span>` 요소로 렌더링됩니다.",
"props": [
{
"name": "children",
"type": ["(formattedValue: string, value: number) => ReactNode"],
"required": false,
"description": "형식화된 값과 원본 값을 받아 표시할 내용을 반환하는 함수"
},
{
"name": "className",
"type": ["string | ((state: Meter.Value.State) => (string | undefined))"],
"required": false,
"description": "요소에 적용된 CSS 클래스 또는 컴포넌트의 상태에 따라 클래스를 반환하는 함수."
},
{
"name": "style",
"type": [
"React.CSSProperties | ((state: Meter.Value.State) => (React.CSSProperties | undefined))"
],
"required": false,
"description": "요소에 적용된 인라인 스타일 또는 컴포넌트의 상태에 따라 스타일 객체를 반환하는 함수."
},
{
"name": "render",
"type": [
"ReactElement | ((props: HTMLProps, state: Meter.Value.State) => ReactElement)"
],
"required": false,
"description": "컴포넌트의 HTML 요소를 다른 태그로 대체하거나 다른 컴포넌트와 조합할 수 있습니다. 렌더링할 요소를 반환하는 `ReactElement` 또는 함수를 인수로 받습니다."
}
]
}
Loading
Loading