tanstack-ranger
TS/JS、React、Vue、Solid、Svelte、Angularに対応した、レンジスライダーおよびマルチレンジスライダーを構築するためのヘッドレスユーティリティです。UIを自由にカスタマイズしながら、スライダーのロジックを手軽に実装できます。
description の原文を見る
Headless utilities for building range and multi-range sliders in TS/JS, React, Vue, Solid, Svelte & Angular.
SKILL.md 本文
Overview
TanStack Ranger は、完全にアクセシブルなレンジスライダーおよびマルチレンジスライダーコンポーネントを構築するためのヘッドレスユーティリティを提供します。単一値、レンジ、マルチサムスライダーのすべての複雑なロジックを処理しながら、スタイリングとマークアップの完全なコントロールを提供します。
Package: @tanstack/react-ranger
Core: @tanstack/ranger-core (フレームワーク非依存)
Status: Stable
Installation
npm install @tanstack/react-ranger
Core Pattern
import { useRanger } from '@tanstack/react-ranger'
function RangeSlider() {
const [values, setValues] = useState([25, 75])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 1,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div
ref={rangerRef}
style={{
position: 'relative',
height: '8px',
background: '#ddd',
borderRadius: '4px',
width: '100%',
}}
>
{/* Track segments */}
{rangerInstance.getSteps().map(({ left, width }, i) => (
<div
key={i}
style={{
position: 'absolute',
left: `${left}%`,
width: `${width}%`,
height: '100%',
background: i === 1 ? '#3b82f6' : '#ddd',
borderRadius: '4px',
}}
/>
))}
{/* Thumbs */}
{rangerInstance.handles.map((handle, i) => (
<button
key={i}
{...handle.getHandleProps()}
style={{
position: 'absolute',
left: `${handle.getPercentage()}%`,
transform: 'translateX(-50%)',
width: '20px',
height: '20px',
borderRadius: '50%',
background: '#3b82f6',
border: '2px solid white',
cursor: 'grab',
}}
/>
))}
</div>
)
}
Ranger Options
Required
| Option | Type | Description |
|---|---|---|
getRangerElement | () => Element | null | スライダートラック要素を返す |
values | number[] | 現在のサムの値 |
min | number | 最小値 |
max | number | 最大値 |
onChange | (instance) => void | 値が変更されたときに呼び出される |
Optional
| Option | Type | Default | Description |
|---|---|---|---|
stepSize | number | 1 | 値間のステップインクリメント |
steps | number[] | - | カスタムステップ位置 (stepSize を上書き) |
tickSize | number | - | 目盛りのサイズ |
ticks | number[] | - | カスタム目盛り位置 |
interpolator | Interpolator | linear | 値補間関数 |
onDrag | (instance) => void | - | ドラッグ操作中に呼び出される |
Ranger Instance API
// ソート済みの値を取得 (常に昇順)
rangerInstance.sortedValues: number[]
// レンダリング用のハンドルを取得
rangerInstance.handles: Handle[]
// ハンドル間のトラックセグメントを取得
rangerInstance.getSteps(): { left: number; width: number }[]
// 目盛りを取得
rangerInstance.getTicks(): { value: number; percentage: number }[]
// プログラマティックに値を設定
rangerInstance.setValues(newValues: number[])
Handle API
interface Handle {
// トラック上のパーセンテージ位置を取得 (0-100)
getPercentage(): number
// 現在の値を取得
getValue(): number
// ハンドル要素に適用するプロップを取得
getHandleProps(): {
role: 'slider'
tabIndex: number
'aria-valuemin': number
'aria-valuemax': number
'aria-valuenow': number
onKeyDown: (e: KeyboardEvent) => void
onMouseDown: (e: MouseEvent) => void
onTouchStart: (e: TouchEvent) => void
}
}
Single Value Slider
function SingleSlider() {
const [values, setValues] = useState([50])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 1,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div ref={rangerRef} className="slider-track">
{rangerInstance.handles.map((handle, i) => (
<button key={i} {...handle.getHandleProps()} className="slider-thumb">
{handle.getValue()}
</button>
))}
</div>
)
}
Multi-Range Slider
function MultiRangeSlider() {
const [values, setValues] = useState([10, 40, 60, 90])
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 5,
onChange: (instance) => setValues(instance.sortedValues),
})
const rangerRef = useRef<HTMLDivElement>(null)
return (
<div ref={rangerRef} className="slider-track">
{rangerInstance.getSteps().map(({ left, width }, i) => (
<div
key={i}
className={`segment ${i % 2 === 1 ? 'active' : ''}`}
style={{ left: `${left}%`, width: `${width}%` }}
/>
))}
{rangerInstance.handles.map((handle, i) => (
<button key={i} {...handle.getHandleProps()} className="slider-thumb" />
))}
</div>
)
}
Custom Steps
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
steps: [0, 10, 25, 50, 75, 100], // これらの値のみが許可される
onChange: (instance) => setValues(instance.sortedValues),
})
Tick Marks
function SliderWithTicks() {
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 0,
max: 100,
stepSize: 10,
ticks: [0, 25, 50, 75, 100],
onChange: (instance) => setValues(instance.sortedValues),
})
return (
<div>
<div ref={rangerRef} className="slider-track">
{/* Handles */}
</div>
<div className="tick-container">
{rangerInstance.getTicks().map((tick, i) => (
<div
key={i}
style={{ left: `${tick.percentage}%` }}
className="tick"
>
<span className="tick-label">{tick.value}</span>
</div>
))}
</div>
</div>
)
}
Logarithmic Scale
import { logarithmicInterpolator } from '@tanstack/react-ranger'
const rangerInstance = useRanger({
getRangerElement: () => rangerRef.current,
values,
min: 1,
max: 1000,
interpolator: logarithmicInterpolator,
onChange: (instance) => setValues(instance.sortedValues),
})
Accessibility
TanStack Ranger は組み込みのアクセシビリティ機能を提供します:
- ハンドルに
role="slider"を指定 aria-valuemin,aria-valuemax,aria-valuenow属性- キーボードナビゲーション (矢印キー、Home、End、Page Up/Down)
- フォーカス管理
// スクリーンリーダー用の aria-label を追加
<button
{...handle.getHandleProps()}
aria-label={`Value: ${handle.getValue()}`}
/>
Controlled vs Uncontrolled
// Controlled (推奨)
const [values, setValues] = useState([50])
const ranger = useRanger({
values,
onChange: (instance) => setValues(instance.sortedValues),
// ...
})
// バリデーション付き
const handleChange = (instance) => {
const [min, max] = instance.sortedValues
// 最小ギャップが10であることを確認
if (max - min >= 10) {
setValues(instance.sortedValues)
}
}
Styling Tips
/* Track */
.slider-track {
position: relative;
height: 8px;
background: #e5e7eb;
border-radius: 4px;
width: 100%;
}
/* Active segment */
.segment.active {
background: #3b82f6;
}
/* Thumb */
.slider-thumb {
position: absolute;
transform: translateX(-50%);
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
cursor: grab;
}
.slider-thumb:active {
cursor: grabbing;
}
.slider-thumb:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
Framework Adapters
| Framework | Package | Status |
|---|---|---|
| React | @tanstack/react-ranger | Stable |
| Vue | @tanstack/vue-ranger | Stable |
| Solid | @tanstack/solid-ranger | Stable |
| Svelte | @tanstack/svelte-ranger | Stable |
| Angular | @tanstack/angular-ranger | Stable |
| Core | @tanstack/ranger-core | Stable |
Best Practices
- 常に onChange から
sortedValuesを使用する - ドラッグ中にハンドルが交差する場合がある getRangerElementコールバックをメモ化する - 不要な再レンダリングを防ぐ- セマンティック HTML を使用する - アクセシビリティのためにハンドルを
<button>要素として レンダリング aria-labelを追加する - 各ハンドルの目的を説明する- CSS transforms を使用する (
translateX) - パフォーマンス向上のためleftの代わりに使用 - onChange でバリデーションする - 制約 (最小ギャップ、最大レンジなど) を強制する
onDragを使用する - ドラッグ操作中のリアルタイムフィードバック用- タッチターゲットを検討する - モバイルでは少なくとも 44x44px のハンドルにする
Common Pitfalls
- トラックコンテナに
position: relativeを忘れる valuesを使用する (sortedValuesの代わりに) - ハンドルが位置を交換する可能性があるgetRangerElementをコールバックとして提供しないleftでサムの位置を設定する (transform: translateX()の代わりに)- キーボードナビゲーションの処理を忘れる (getHandleProps 経由で組み込み済み)
- 位置を計算するときにサムの幅を考慮しない
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- tanstack-skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/tanstack-skills/tanstack-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。