-
+ {picker ?
: (
+
{
+ // A click on the unit / empty part of the box edits the value (the box is the Figma field).
+ if (!field || !editable || event.target === inputRef.current || type === "select") return
+ event.preventDefault()
+ inputRef.current?.focus()
+ }}>
{type === "select" ? (
) : (
{property.annotation}}
{unit && type !== "slider" && {unit}}
- {type === "color" && }
+ {type === "color" && }
{property.linePreview && }
{type === "slider" && setDraft(event.target.value)} onBlur={() => commit(draft)} onKeyDown={nativeKeys} />}
{unit && type === "slider" && {unit}}
+ )}
)
}
/** A single data-driven, non-modal property tool window (Figma 26:11822). */
export function SWHeaderPropertyPanel({
- selection, properties, groups, onPropertyChange, expandedGroupIds, defaultExpandedGroupIds = [],
+ selection, properties, groups, onPropertyChange, menus, expandedGroupIds, defaultExpandedGroupIds = [],
onExpandedGroupsChange, toolActive: controlledTool, onToolActiveChange, toolLabel = "객체 선택 도구",
onOverviewClick, shapeContent, onClose, id, anchorRef, style: customStyle,
}: SWHeaderPropertyPanelProps) {
@@ -244,7 +312,7 @@ export function SWHeaderPropertyPanel({
{group.layout === "position" &&
구분{visible.map(item => {item.name})}
}
{group.layout === "position" &&
{group.positionLabel}}
- {visible.map(property =>
)}
+ {visible.map(property =>
)}
{expandable &&
}
diff --git a/packages/sw/src/components/sw-header/index.ts b/packages/sw/src/components/sw-header/index.ts
index 90cf302..28d48db 100644
--- a/packages/sw/src/components/sw-header/index.ts
+++ b/packages/sw/src/components/sw-header/index.ts
@@ -19,7 +19,7 @@ export {
type SWHeaderInputProps, type SWHeaderInputValueType,
} from "./components/SWHeaderInput"
export { SWHeaderProperties, type SWHeaderPropertiesProps } from "./components/SWHeaderProperties"
-export { SWHeaderPropertyPanel, type SWHeaderPropertyPanelProps, type SWHeaderProperty, type SWHeaderPropertyType, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyShapeContent, type SWHeaderPropertyShapeContext } from "./components/SWHeaderPropertyPanel"
+export { SWHeaderPropertyPanel, type SWHeaderPropertyPanelProps, type SWHeaderProperty, type SWHeaderPropertyType, type SWHeaderPropertyPicker, type SWHeaderPropertyMenus, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyShapeContent, type SWHeaderPropertyShapeContext } from "./components/SWHeaderPropertyPanel"
export { SWHeaderActions, type SWHeaderActionsProps } from "./components/SWHeaderActions"
export { SWHeaderWindowControls, type SWHeaderWindowControlsProps } from "./components/SWHeaderWindowControls"
export type {
diff --git a/packages/sw/src/theme/sw.css b/packages/sw/src/theme/sw.css
index 3b577b4..c7f117c 100644
--- a/packages/sw/src/theme/sw.css
+++ b/packages/sw/src/theme/sw.css
@@ -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; }
diff --git a/packages/sw/tests/property-fields.browser.mjs b/packages/sw/tests/property-fields.browser.mjs
new file mode 100644
index 0000000..87d544d
--- /dev/null
+++ b/packages/sw/tests/property-fields.browser.mjs
@@ -0,0 +1,227 @@
+// Property fields (Figma 26:12388 ~ 26:12649): Default / real pointer Hover / Mouse Leave / On (editing, dropdown),
+// selection callbacks, keyboard, Escape, external data, Disabled / ReadOnly, and the four panel states.
+// Usage: node packages/sw/tests/property-fields.browser.mjs