Angle Dial <mv-angle-dial>

CSS と同じ感覚で読める度数の角度入力(0° が上、時計回り):スナップのグリッドを兼ねる目盛りのリングの中にノブがあり、ノブの後ろの目盛りは点灯し、距離に応じてフェードします。

仕組み

CSS と同じ感覚で読める度数の角度入力(0° が上、時計回り):スナップのグリッドを兼ねる目盛りのリングの中にノブがあり、ノブの後ろの目盛りは点灯し、距離に応じてフェードします。45° ごとのゆるやかなデテント付きのドラッグ、または Shift でスナップでき、スライダーと同じキーボード操作をすべて備え、フォームに関連付けられます。

カテゴリーフォーム
タイプWeb Component(<mv-angle-dial>)
ステータス安定版
Keywordsangle, dial, knob, rotation, gradient, inspector, slider, form-associated, editor, ticks

When to use

  • A design tool lets users set a rotation or gradient direction in degrees by dragging around a dial
  • An inspector panel needs a compact angle control that snaps to 15 or 45 degree steps
  • A settings form must submit a direction or heading as a plain number of degrees

Avoid when

  • The value is a generic linear number rather than an angle → use Slider instead
  • Users need to type the exact angle more often than drag it → use Scrub Field instead
  • The audience is non-technical and a circular control would be unfamiliar in a regular form

インストール

node scripts/add.mjs angle-dial --out ./src/marvelous

Marvelous UI の MCP サーバーを使う AI エージェント:install_components({ slugs: ["angle-dial"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

コピーされるファイル(依存関係を含む):tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/form.js, components/angle-dial/angle-dial.js, components/angle-dial/angle-dial.css。

使い方

基本となるマークアップです。ここから属性、data-*、CSS 変数でカスタマイズしてください:

<div id="angle-demo">
  <style>
    #angle-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
    #angle-demo .ad-stage { display: grid; place-items: center; width: 15rem; height: 15rem; border-radius: var(--mv-radius-xl); background: radial-gradient(circle at 1px 1px, color-mix(in oklch, var(--mv-fg) 12%, transparent) 1px, transparent 0) 0 0 / 12px 12px, var(--mv-bg); border: 1px solid var(--mv-border); }
    #angle-demo .ad-shape { width: 8rem; height: 8rem; border-radius: 1.5rem; box-shadow: 0 18px 40px -16px oklch(0.4 0.2 260 / .55); }
    #angle-demo .ad-panel { display: grid; gap: .6rem; width: 16.5rem; padding: .85rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
    #angle-demo .ad-head { margin: 0; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
    #angle-demo .ad-row { display: grid; grid-template-columns: 4.75rem auto 1fr; align-items: center; gap: .6rem; font-size: .75rem; color: var(--mv-fg-muted); }
    #angle-demo .ad-row mv-scrub-field { width: 100%; }
    #angle-demo .ad-big { --mv-angle-dial-size: 3.5rem; }
    #angle-demo .ad-hint { margin: .15rem 0 0; font-size: .7rem; line-height: 1.5; color: var(--mv-fg-muted); }
    #angle-demo kbd { font: 500 .65rem var(--mv-font-mono); padding: 0 .25rem; border-radius: 3px; background: var(--mv-bg-muted); border: 1px solid var(--mv-border); }
  </style>
  <div class="ad-stage"><div class="ad-shape" id="angle-demo-shape"></div></div>
  <div class="ad-panel" role="group" aria-label="Inspector">
    <p class="ad-head">Transform</p>
    <div class="ad-row">
      <span id="angle-demo-l1">Rotation</span>
      <mv-angle-dial id="angle-demo-rot" name="rotation" value="-15" aria-labelledby="angle-demo-l1"></mv-angle-dial>
      <mv-scrub-field id="angle-demo-rot-field" aria-label="Rotation in degrees" unit="°" value="345" min="0" max="360" wrap precision="0"></mv-scrub-field>
    </div>
    <p class="ad-head">Fill</p>
    <div class="ad-row">
      <span id="angle-demo-l2">Gradient</span>
      <mv-angle-dial id="angle-demo-grad" class="ad-big" name="angle" value="135" snap="45" readout aria-labelledby="angle-demo-l2"></mv-angle-dial>
      <span></span>
    </div>
    <p class="ad-hint">Drag the dial or use the arrow keys. <kbd>Shift</kbd> snaps to the ticks, <kbd>Alt</kbd> drags freely, double-click resets.</p>
  </div>
</div>
<script type="module">
  const $ = (id) => document.getElementById(`angle-demo-${id}`);
  const apply = () => {
    $("shape").style.rotate = `${$("rot").value}deg`;
    $("shape").style.background = `linear-gradient(${$("grad").value}deg, oklch(0.8 0.13 190), oklch(0.6 0.2 265) 55%, oklch(0.68 0.2 340))`;
  };
  $("rot").addEventListener("mv-input", (e) => { $("rot-field").value = e.detail.value; apply(); });
  $("rot-field").addEventListener("mv-input", (e) => { $("rot").value = e.detail.value; apply(); });
  $("grad").addEventListener("mv-input", apply);
  Promise.all(["mv-angle-dial", "mv-scrub-field"].map((t) => customElements.whenDefined(t))).then(() => requestAnimationFrame(apply));
</script>

API

Attributes

NameタイプDefaultDescription
valuenumber0Angle in degrees, normalized to [0, 360). Restored on form reset and on double-click.
namestringForm field name; the form receives the number of degrees.
labelstringAngleAccessible name. aria-labelledby / aria-label on the element or an external <label> work too.
stepnumber1Arrow-key step (Alt: a tenth of it, when precision allows).
snapnumber15Snap grid: Shift while dragging or with the arrows, PageUp/PageDown step, and the spacing of the ticks.
precisionnumber0Decimal places kept.
readoutbooleanShows the value in the center of the knob (for sizes of about 3rem and up).
localestringNumber formatting locale (defaults to the page lang, then en-US).
disabled / readonly / requiredbooleanStandard form states.

Properties

NameタイプDescription
valuenumberCurrent angle.
valueAsNumbernumberSame as value.
defaultValuenumberValue restored on form reset and double-click (the initial value).
stringsPartial<Record<string, string>>Overrides for every text: label ("Angle"), valueText ("{value} degrees"). English defaults.

Methods

NameDescription
focus()Focuses the slider.

Events

NameDescription
mv-inputWhile dragging and on every key press. detail: { value }.
mv-changeOn release (if the value changed) and on every key press. detail: { value }.

CSS classes

NameDescription
mv-angle-dial-face / -ticks / -tick / -arc / -plate / -needle / -knob / -readout / -badgeGenerated parts. -tick[data-major] marks every 90°.

CSS variables

NameDefaultDescription
--mv-angle-dial-sizevar(--mv-control-sm)Diameter; ticks, knob and readout scale with it.
--mv-angle-dial-bgBackground of the tick band.

Accessibility

The face has role="slider" with aria-valuemin 0, aria-valuemax 359 (the last value before it wraps, finer with precision), aria-valuenow and aria-valuetext “135 degrees” (localizable). Keyboard: →/↑ and ←/↓ by step (wrapping past 0°), Alt+arrow for a tenth of a step, Shift+arrow to the next snap tick, PageUp/PageDown ±snap, Home 0°, End the maximum. Esc while dragging restores the starting angle. Named by label, aria-labelledby/aria-label on the element, or a <label>. Visible focus ring; forced colors use system colors (Highlight for the value); the value bubble briefly shows after a key press; motion is limited to short transitions.

このページは AI で翻訳されています。翻訳の問題を報告