feat(sw): implement property field interaction variants

- Property panel rows follow Figma 26:12388 ~ 26:12649: 28px fields with
  Default / Hover border / numeric editing On (#0F0D0A) / dropdown On (#211D13)
- Layer, line style and color fields open the shared header dropdown contents
  (SWHeaderLayerRows, SWHeaderLineStyleColumns, SWHeaderColorPanelBody)
- Additive API: property.picker, property.decimals, panel menus
- Property Fields story and browser test (77 checks)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shkim4andClaude Opus 5.5 committed 2026-10-19 09:38:31 +09:00
1 parent 724c51732b
commit 0029cef60c
23 files changed
+1907 -189

No files matched your search

+111 -2
View File
@@ -75,6 +75,12 @@
--hm-sw-property-label: #000;
--hm-sw-property-table: #d2c8b4;
--hm-sw-property-inner-border: #948871;
/* Property fields (Figma 26:12170 ~ 26:12649): Hover border, editing On, dropdown On, unit colors. */
--hm-sw-property-field-hover-border-color: rgba(33, 29, 19, 0.4);
--hm-sw-property-field-editing-background: #0f0d0a;
--hm-sw-property-field-open-background: #211d13;
--hm-sw-property-field-editing-unit-color: #fff;
--hm-sw-property-field-open-unit-color: #d4cbbd;
/* Gauge (user-supplied Figma SVGs, 2026-10-19). Fill = Rectangle 6926: 6px pill, 1px black stroke,
vertical #8E0000 / #FF4242 37% / #E40000 76% / #1E0000 inside the stroke; its right end sits under
the thumb. Unfilled track = beige measured row by row from the user's reference image.
@@ -1520,8 +1526,10 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-property__value > select:disabled { opacity: 1; appearance: none; color: var(--hm-sw-header-input-color); }
.hm-sw-property__value > select:focus { background: var(--hm-sw-property-content); border-color: var(--hm-sw-property-inner-border); }
.hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; }
.hm-sw-property__swatch { display: block; flex: 0 0 70px; height: 16px; padding: 1px; border: 1px solid #000; border-radius: 2px; }
.hm-sw-property__swatch > i { display: block; height: 12px; border: 1px solid #fff; }
/* Color preview (26:12175): 70 x 16 black tile (1px padding, radius 2), 68 x 14 swatch with a white 1px border. */
.hm-sw-property__swatch-area { display: flex; align-items: center; flex: 0 0 auto; }
.hm-sw-property__swatch { display: block; flex: 0 0 70px; width: 70px; height: 16px; padding: 1px; border-radius: 2px; background: #000; }
.hm-sw-property__swatch > i { display: block; height: 14px; border: 1px solid #fff; border-radius: 1px; }
.hm-sw-property__value > input[type="range"] { appearance: none; flex: 0 0 86px; width: 86px; height: 16px; margin: 0; background: transparent; cursor: pointer; }
/* --hm-sw-range-fill (0-1) is set from the value; the red ends under the thumb centre (14px thumb).
The 3px radius rounds the fill's left cap; the first layer is its left stroke. Track and thumb are
@@ -1582,6 +1590,107 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-property__row[data-disabled="true"] { opacity: .4; }
.hm-sw-property button:focus-visible, .hm-sw-property input:focus-visible, .hm-sw-property select:focus-visible { outline: 2px solid var(--hm-sw-property-border); outline-offset: -2px; }
/* Property fields (2026-10-19, Figma 26:12170 ~ 26:12649). Common rows only; the circle / position groups keep
their layout. Box: 28px, radius 2, a transparent 1px border so Hover / On never move the value (8px inset as
in Default). Default: value only. Hover: rgba(33,29,19,.4) border, values / units / previews unchanged.
On (editing, numbers 26:12323 / 26:12599 / 26:12656): #0F0D0A, black text outline, unit #FFF, red caret.
On (dropdown 26:12265 / 26:12291 / 26:12404 / 26:12184): #211D13, outlined value, unit #D4CBBD.
Text and computed values (26:12583 / 26:12574) have no Figma On: only Default / Hover. */
.hm-sw-property__group--rows .hm-sw-property__value[data-field] {
position: relative;
height: 28px;
border: 1px solid transparent;
border-radius: 2px;
justify-content: space-between;
}
.hm-sw-property__group--rows .hm-sw-property__value[data-field="box"] { padding: 0 7px; }
.hm-sw-property__group--rows .hm-sw-property__value[data-field="picker"] { padding: 0; }
.hm-sw-property__group--rows .hm-sw-property__row:not([data-disabled]) > .hm-sw-property__value[data-field]:is(:hover, [data-open="true"], :not([data-readonly]):focus-within) {
border-color: var(--hm-sw-property-field-hover-border-color);
}
.hm-sw-property__group--rows .hm-sw-property__value[data-field] .hm-sw-header__input,
.hm-sw-property__group--rows .hm-sw-property__value[data-field] .hm-sw-header__input:is(:hover, :focus) {
flex: 1 1 auto;
align-self: stretch;
width: auto;
min-width: 0;
height: auto;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
font-weight: 500;
letter-spacing: 0.02em;
text-shadow: none;
}
/* The box shows the editing state; read-only values keep the keyboard focus ring. */
.hm-sw-property__group--rows .hm-sw-property__value[data-field] .hm-sw-header__input:not(:read-only):focus-visible { outline: none; }
.hm-sw-property__group--rows .hm-sw-property__value[data-on="edit"]:focus-within { background: var(--hm-sw-property-field-editing-background); }
.hm-sw-property__group--rows .hm-sw-property__value[data-on="edit"]:focus-within > .hm-sw-header__input { text-shadow: var(--hm-sw-header-text-outline); }
.hm-sw-property__group--rows .hm-sw-property__value[data-on="edit"]:focus-within > .hm-sw-property__unit { color: var(--hm-sw-property-field-editing-unit-color); }
/* 41px hug field (26:12649: 선가중치 / 투명도 value): pl 6, pr 4, the input itself is the box. */
.hm-sw-property__group--rows .hm-sw-property__value > .hm-sw-header__input.hm-sw-property__compact,
.hm-sw-property__group--rows .hm-sw-property__value > .hm-sw-header__input.hm-sw-property__compact:is(:hover, :focus) {
flex: 0 0 auto;
width: calc(var(--hm-sw-input-chars, 1) * (1ch + 0.02em) + 10px);
height: 28px;
padding: 0 3px 0 5px;
border: 1px solid transparent;
border-radius: 2px;
background: transparent;
}
@supports (field-sizing: content) {
.hm-sw-property__group--rows .hm-sw-property__value > .hm-sw-header__input.hm-sw-property__compact { width: auto; min-width: calc(1ch + 10px); }
}
.hm-sw-property__group--rows .hm-sw-property__row:not([data-disabled]) .hm-sw-header__input.hm-sw-property__compact:is(:hover, :focus:not(:read-only)) { border-color: var(--hm-sw-property-field-hover-border-color); }
.hm-sw-property__group--rows .hm-sw-property__value > .hm-sw-header__input.hm-sw-property__compact:focus:not(:read-only) {
outline: none;
background: var(--hm-sw-property-field-editing-background);
text-shadow: var(--hm-sw-header-text-outline);
}
/* Dropdown fields: layer (pl/pr 8), line style (pl 8, pr 6: value + (ByL) + pattern), color (RGB | swatch). */
.hm-sw-property__trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
flex: 1 1 auto;
align-self: stretch;
min-width: 0;
padding: 0 7px;
color: var(--hm-sw-header-input-color);
text-align: left;
letter-spacing: 0.02em;
}
.hm-sw-property__value[data-picker="lineStyle"] > .hm-sw-property__trigger { padding-right: 5px; }
.hm-sw-property__value[data-readonly] > .hm-sw-property__trigger,
.hm-sw-property__trigger:disabled { cursor: default; opacity: 1; }
.hm-sw-property__trigger-text { display: flex; align-items: center; gap: 4px; min-width: 0; }
.hm-sw-property__text { min-width: 0; overflow: clip; overflow-clip-margin: var(--hm-sw-header-text-outline-room); white-space: nowrap; text-overflow: ellipsis; font-weight: 500; }
.hm-sw-property__trigger .hm-sw-property__unit { color: var(--hm-sw-property-label); }
/* Figma 68px (Dotum). Noto Sans KR needs more room for "Continuous (ByL)": the preview gives way first (Figma On draws it at 50px), then the name ellipsizes. */
.hm-sw-property__trigger > .hm-sw-header__line-preview { flex: 0 1 68px; width: 68px; min-width: 40px; }
.hm-sw-property__value[data-picker="lineStyle"] .hm-sw-property__trigger-text { flex-shrink: 0; max-width: calc(100% - 44px); }
.hm-sw-property__value[data-field="picker"][data-open="true"]:not([data-picker="color"]),
.hm-sw-property__value[data-picker="color"][data-open="true"] > .hm-sw-property__color-value { background: var(--hm-sw-property-field-open-background); }
.hm-sw-property__group--rows .hm-sw-property__value[data-field][data-open="true"] :is(.hm-sw-property__text, .hm-sw-header__input) { text-shadow: var(--hm-sw-header-text-outline); }
.hm-sw-property__value[data-open="true"] .hm-sw-property__trigger .hm-sw-property__unit { color: var(--hm-sw-property-field-open-unit-color); }
.hm-sw-property__color-value { display: flex; align-items: center; flex: 1 1 auto; align-self: stretch; min-width: 0; padding: 0 7px; border-right: 1px solid transparent; }
.hm-sw-property__value[data-field] > .hm-sw-property__swatch-area { align-self: stretch; padding: 0 5px; }
.hm-sw-property__value[data-field="box"] > .hm-sw-property__swatch-area { padding-right: 0; }
.hm-sw-property__value[data-picker="color"]:not([data-readonly]) > .hm-sw-property__swatch-area { cursor: pointer; }
.hm-sw-property__group--rows .hm-sw-property__row:not([data-disabled]) > .hm-sw-property__value[data-picker="color"]:is(:hover, [data-open="true"]) > .hm-sw-property__color-value {
border-right-color: var(--hm-sw-property-field-hover-border-color);
}
/* The shared header dropdown contents in a fixed overlay (escapes the scrolling body). No yellow ring:
the property Figma dropdowns have the black stroke and the brown surface only. */
.hm-sw-property__drop { position: fixed; z-index: 50; top: 0; left: 0; color: var(--hm-sw-header-text-color); text-align: left; }
.hm-sw-property__drop[data-picker="layer"] { width: var(--hm-sw-header-layer-menu-width); }
.hm-sw-property__drop.hm-sw-header__menu-panel { position: fixed; max-width: none; }
.hm-sw-property__drop.hm-sw-header__menu-panel::before { display: none; }
/* K guide 26:11698 / 26:11669: original image, fixed mask viewport and continuous pan/zoom. */
.hm-sw-k-guide { position: relative; flex: 0 0 40px; height: 38px; }
.hm-sw-k-guide__panel { z-index: 45; height: 149px; min-height: 149px; overflow: hidden; border: 1px solid var(--hm-sw-property-border); border-radius: 2px; background: #0e3c2e; animation: hm-sw-k-guide-enter 511ms ease-out; }