プログレスバー
stimeo--progress
progressbar の ARIA 値属性を同期し、割合をバー描画用に公開する。
stimeo--progress コントローラは WAI-ARIA の progressbar ロールを駆動します。値を [min, max] に正規化し、aria-valuenow / aria-valuemin / aria-valuemax を同期しつつ、割合を --stimeo-progress-ratio(0–1)カスタムプロパティとして公開します(利用側 CSS が幅に変換)。進捗不定の状態では aria-valuenow を外し、data-state="indeterminate" に切り替えてループアニメーションを当てられるようにします。setValue は amount アクションパラメータか progress:set イベントの detail で値を受け取り、stimeo--progress:change / :complete を発火します。ライブラリは挙動のみを提供し、バーの見た目はこの Playground が持ちます。
<%# Markup for the progress (progress bar) demo.
The library syncs role="progressbar" and aria-value*, and exposes the ratio as
--stimeo-progress-ratio (0–1). The bar's width and color live in demo.css.
The control buttons sit outside the progressbar (a progressbar must not contain
interactive children), so they can't drive it through a Stimulus data-action —
that only binds inside the controller's own element. Instead they dispatch the
progress:set CustomEvent the controller listens for (see demo.js), the same
contract a consumer would use to update progress from elsewhere. %>
<div class="progress-demo">
<div
class="progress"
data-controller="stimeo--progress"
role="progressbar"
aria-label="<%= t('components.progress.demo.label') %>"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="40"
data-stimeo--progress-value-value="40"
data-stimeo--progress-value-text-value="<%= t('components.progress.demo.value_text') %>"
data-action="progress:set->stimeo--progress#setValue">
<div class="progress__bar" data-stimeo--progress-target="bar"></div>
</div>
<div
class="progress-demo__controls"
role="group"
aria-label="<%= t('components.progress.demo.controls_label') %>">
<% [0, 25, 50, 75, 100].each do |amount| %>
<button
class="demo-trigger"
type="button"
data-progress-amount="<%= amount %>">
<%= amount %>%
</button>
<% end %>
</div>
<%# Indeterminate example: drop aria-valuenow and let the consumer apply a looping
animation via data-state="indeterminate". %>
<div
class="progress progress--indeterminate"
data-controller="stimeo--progress"
role="progressbar"
aria-label="<%= t('components.progress.demo.indeterminate_label') %>"
aria-valuemin="0"
aria-valuemax="100"
data-stimeo--progress-indeterminate-value="true">
<div class="progress__bar" data-stimeo--progress-target="bar"></div>
</div>
</div>
/*
* Presentation-only styles for the progress demo.
* The bar width is derived from --stimeo-progress-ratio (0–1) exposed by the
* library; the indeterminate state is detected via data-state="indeterminate" to
* apply a looping animation.
*/
.progress-demo {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 32rem;
}
.progress {
height: 0.75rem;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface-subtle);
overflow: hidden;
}
.progress__bar {
height: 100%;
border-radius: inherit;
background: var(--accent);
/* Convert the 0–1 ratio into a percentage width. */
width: calc(var(--stimeo-progress-ratio, 0) * 100%);
transition: width 0.25s ease;
}
/* Indeterminate: fix the width and slide it back and forth to signal "working". */
.progress--indeterminate[data-state="indeterminate"] .progress__bar {
width: 40%;
animation: progress-indeterminate 1.2s ease-in-out infinite;
}
@keyframes progress-indeterminate {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(250%);
}
}
.progress-demo__controls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
@media (prefers-reduced-motion: reduce) {
.progress__bar {
transition: none;
}
.progress--indeterminate[data-state="indeterminate"] .progress__bar {
animation: none;
}
}
// progress external-update demo (consumer-side JS).
//
// The core controller (stimeo--progress) syncs the ARIA value attributes and the
// --stimeo-progress-ratio custom property; it does not own any buttons. A
// progressbar must not contain interactive children, so the preset buttons live
// next to it rather than inside it — which means a Stimulus data-action on them
// would never bind (actions only wire up within the controller's own element).
// The contract for updating from outside is the progress:set event, so here each
// button fires progress:set with its target value on the progressbar element.
document.querySelectorAll(".progress-demo").forEach((root) => {
const bar = root.querySelector(
'.progress:not(.progress--indeterminate)[data-controller~="stimeo--progress"]',
);
const controls = root.querySelector(".progress-demo__controls");
if (!bar || !controls) return;
controls.querySelectorAll("[data-progress-amount]").forEach((button) => {
button.addEventListener("click", () => {
const value = Number(button.getAttribute("data-progress-amount"));
bar.dispatchEvent(new CustomEvent("progress:set", { detail: { value } }));
});
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--progress"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
bar
|
比率カスタムプロパティと data-state から駆動する塗り要素。 |
data-stimeo--progress-target="bar" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
value
|
現在の進捗値。[min, max] に丸められる(既定 0)。 | data-stimeo--progress-value-value |
min
|
範囲の下限。aria-valuemin に反映(既定 0)。 |
data-stimeo--progress-min-value |
max
|
範囲の上限。aria-valuemax に反映(既定 100)。 |
data-stimeo--progress-max-value |
indeterminate
|
不確定状態か。aria-valuenow を外し data-state を indeterminate にする(既定 false)。 |
data-stimeo--progress-indeterminate-value |
valueText
|
aria-valuetext 用テンプレート。{value}/{percent} を置換。空なら解除。 |
data-stimeo--progress-value-text-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
setValue
|
アクション引数 amount か detail.value から値を設定し、不確定を解除して ARIA を更新する。 | stimeo--progress#setValue |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
値更新のたびに発火。detail.value / detail.ratio を伴う。 | stimeo--progress:change |
complete
|
値が max に達したときに発火。detail.value を伴う。 | stimeo--progress:complete |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-valuenow |
ルート要素 | 現在値。進捗不定時は外す。 |
--stimeo-progress-ratio |
ルート要素 | 0–1 の割合(利用側 CSS がバー幅に変換)。 |
data-state |
ルート要素 | "determinate" / "indeterminate"。 |