fix(sw): 2026-10-21 SW design review and responsive header without More from 760px
User-approved design review (2026-10-21): - SW Header: from 760px up nothing moves into More; the five menus stay with their titles (titles and values ellipsize), file name shrinks to 96px. Open next to the logo no longer blocks field shrinking above 760px (760/768 overflow fixed, also for configured `controls` headers). Dropdown yellow ring inside the button, black seam. - Property Bar values as read-only red text; property popup "=" labels, read-only black values, per-entry hover with pointer. - Home Menu recent files (3 rows, path + name caption, empty preview), Footer + slot guide off by default, Viewer file list without icons, Storybook widths 1920-760. - Tests: old More expectations (integrated-improvement, keyboard-dismiss, footer-popup-viewer-refinement, storybook-cleanup-stage1) moved to the new rule; functional checks kept.
This commit is contained in:
1 parent
a25d2b31c5
commit
2fc1b3e108
37 files changed
+447
-211
No files matched your search
@@ -39,7 +39,10 @@ export interface SWFooterProps extends Omit<HTMLAttributes<HTMLElement>, "childr
|
||||
slotCount?: number
|
||||
/** An empty + slot was clicked (register a command). `slotIndex` is 0-based in the row. Omitted: the empty slots are inert. */
|
||||
onSlotAdd?: (slotIndex: number, categoryId: string) => void
|
||||
/** Guide over a hovered / focused empty slot. Default: the Figma guide (`명령어 등록` / `빠른사용`). `false`: none. */
|
||||
/**
|
||||
* Guide over a hovered / focused empty slot (opt-in). Default `false` (2026-10-21: no preview over the + slot). `{}`:
|
||||
* the Figma guide (`명령어 등록` / `빠른사용`), or pass its title / caption / media.
|
||||
*/
|
||||
slotGuide?: SWFooterGuideContent | false
|
||||
/** Shows the + after the categories. */
|
||||
onCategoryAdd?: () => void
|
||||
@@ -76,7 +79,7 @@ const SLOT = 68
|
||||
*/
|
||||
export const SWFooter = forwardRef<HTMLElement, SWFooterProps>(function SWFooter({
|
||||
categories, categoryId, defaultCategoryId, onCategoryChange, activeToolId = null, onToolSelect,
|
||||
slotCount, onSlotAdd, slotGuide = {}, onCategoryAdd,
|
||||
slotCount, onSlotAdd, slotGuide = false, onCategoryAdd,
|
||||
auxItems, onAuxPressedChange, onAuxItemClick, auxAll = {}, commandAll = {},
|
||||
command, status, layoutTabs, trailing, label = "명령 도구", className, ...rest
|
||||
}, ref) {
|
||||
|
||||
@@ -153,8 +153,8 @@ export const SWFooterGuide = forwardRef<HTMLDivElement, SWFooterGuideProps>(func
|
||||
|
||||
export interface SWFooterAddSlotProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
||||
/**
|
||||
* Guide shown above the slot while it is hovered or focused. Default: the Figma guide (`명령어 등록` / `빠른사용`,
|
||||
* empty preview). `false`: no guide.
|
||||
* Guide shown above the slot while it is hovered or focused (opt-in). Default `false` (2026-10-21: no preview over
|
||||
* the + slot). `{}`: the Figma guide (`명령어 등록` / `빠른사용`, empty preview), or pass its title / caption / media.
|
||||
*/
|
||||
guide?: SWFooterGuideContent | false
|
||||
/** Shows the guide (controlled). Default: while hovered or keyboard focused. */
|
||||
@@ -165,7 +165,7 @@ export interface SWFooterAddSlotProps extends Omit<ButtonHTMLAttributes<HTMLButt
|
||||
|
||||
/** 68 × 46 empty command slot (Figma btn_add_menu 26:9971): the + well; hover draws the orange dashed ring. */
|
||||
export const SWFooterAddSlot = forwardRef<HTMLButtonElement, SWFooterAddSlotProps>(function SWFooterAddSlot(
|
||||
{ guide = {}, guideOpen, onGuideOpenChange, className, type = "button", onMouseEnter, onMouseLeave, onFocus, onBlur, ...rest }, ref,
|
||||
{ guide = false, guideOpen, onGuideOpenChange, className, type = "button", onMouseEnter, onMouseLeave, onFocus, onBlur, ...rest }, ref,
|
||||
) {
|
||||
const [hovered, setOpen] = useState(false)
|
||||
const open = guideOpen ?? hovered
|
||||
|
||||
@@ -41,6 +41,8 @@ const defaultControls: SWHeaderControlConfig[] = (["document", "drawingMode", ..
|
||||
const frameControls: readonly SWHeaderFrameControlId[] = ["home", "windowMove", "minimize", "close"]
|
||||
const subControls: readonly SWHeaderSubControlId[] = ["documentNew", "documentOpen", "layerFreeze", "layerLock"]
|
||||
const isMenu = (id: string): id is SWHeaderMenuId => id in menuSlot
|
||||
/** Header width from which nothing moves into More (2026-10-21): the header compresses instead. Below it, as before. */
|
||||
const MORE_BELOW = 760
|
||||
|
||||
/**
|
||||
* Order in which visible parts move into More when the measured object area has no room left (on top of the
|
||||
@@ -318,7 +320,10 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
|
||||
const menuIds = configuration.map(item => item.id).filter((id): id is SWHeaderMenuId => id in menuSlot)
|
||||
const baseMenu = menuIds.includes("layer") ? "layer" : menuIds[0]
|
||||
const validPromoted = promoted && enabled(promoted) ? promoted : null
|
||||
const breakpointOverflowAt = (id: SWHeaderControlId, width: number) => id === "color" ? width < 900 : ["lineStyle", "textStyle", "dimensionStyle"].includes(id) ? width < 1800 : ["propertyBar", "propertyPanel", "details"].includes(id) ? width < 1080 : ["print", "popupGuide"].includes(id) ? width < 640 : id === "drawingMode" ? width < 1180 : false
|
||||
// 2026-10-21 (user decision): no responsive More from 760px up. Nothing moves out of the header there; narrower headers
|
||||
// hide the field titles, then shrink the file name, then the field values (ellipsis, sw.css "Responsive"). Below 760px
|
||||
// (phones, e.g. 390) the former breakpoints and More are unchanged.
|
||||
const breakpointOverflowAt = (id: SWHeaderControlId, width: number) => width < MORE_BELOW && (id === "color" ? width < 900 : ["lineStyle", "textStyle", "dimensionStyle"].includes(id) ? width < 1800 : ["propertyBar", "propertyPanel", "details"].includes(id) ? width < 1080 : ["print", "popupGuide"].includes(id) ? width < 640 : id === "drawingMode" ? width < 1180 : false)
|
||||
const breakpointOverflow = (id: SWHeaderControlId) => breakpointOverflowAt(id, headerWidth)
|
||||
// Measured More: the parts that can move (the default layout moves the property block as one part; print / K sit in
|
||||
// the window area there and keep the breakpoint). Keys of the moved parts are kept in move order.
|
||||
@@ -373,6 +378,13 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
|
||||
collapseRef.current = null
|
||||
if (openCollapsed) return setOpenCollapsed(false)
|
||||
}
|
||||
// No responsive More from 760px up (2026-10-21): the header compresses instead. Below it the measured More runs as
|
||||
// before; parts it moved leave silently once the header is that wide again.
|
||||
if (headerWidth >= MORE_BELOW) {
|
||||
if (squeezed.length) { squeezeRef.current.clear(); return setSqueezed([]) }
|
||||
if (openCollapsed) { collapseRef.current = null; return setOpenCollapsed(false) }
|
||||
return
|
||||
}
|
||||
if (widthRef.current.current !== headerWidth) widthRef.current = { previous: widthRef.current.current, current: headerWidth }
|
||||
const { previous } = widthRef.current
|
||||
if (releasedRef.current.width !== headerWidth) releasedRef.current = { width: headerWidth, keys: new Set() }
|
||||
|
||||
@@ -24,13 +24,15 @@ function BarField({ property, onPropertyChange, readOnly, disabled }: Pick<SWHea
|
||||
return (
|
||||
<div className="hm-sw-property-bar__field" data-property-id={property.id}>
|
||||
<label htmlFor={id}>{property.name}</label>
|
||||
<SWHeaderInput id={id} value={property.mixed ? "다양함" : property.value}
|
||||
{/* 2026-10-21: a value that cannot be edited here (read-only, computed, disabled) is plain text: no input, no
|
||||
focus stop. It still follows the host value. */}
|
||||
{!editable ? <output id={id} className="hm-sw-property-bar__value" data-disabled={disabled || property.disabled ? "true" : undefined}>{property.mixed ? "다양함" : property.value}</output> : <SWHeaderInput id={id} value={property.mixed ? "다양함" : property.value}
|
||||
valueType={property.type === "number" || property.type === "slider" ? "number" : "text"}
|
||||
disabled={disabled || property.disabled} readOnly={!editable} parse={parse} commitOnUnmount={false}
|
||||
onCommit={editable && (onPropertyChange || property.onCommit) ? next => {
|
||||
property.onCommit?.(next, property)
|
||||
onPropertyChange?.(property, next)
|
||||
} : undefined} />
|
||||
} : undefined} />}
|
||||
{property.unit && <span>{property.unit}</span>}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -116,8 +116,6 @@ export function SWHeaderPropertyPickerField({ picker, property, id, editable, me
|
||||
if (picker === "lineStyle") {
|
||||
const lineTypes = propertyLineTypes(property, menus)
|
||||
const current = mixed ? undefined : byValue(lineTypes, value)
|
||||
const weights = menus?.lineWeights
|
||||
const currentWeight = weights?.find(item => item.id === menus?.activeLineWeightId)
|
||||
const text = mixed ? "다양함" : current?.name ?? property.options?.find(option => option.id === value)?.name ?? value
|
||||
return (
|
||||
<div {...shared}>
|
||||
@@ -128,17 +126,13 @@ export function SWHeaderPropertyPickerField({ picker, property, id, editable, me
|
||||
</span>
|
||||
{property.linePreview !== false && <SWHeaderLinePreview pattern={current?.pattern ?? property.linePattern} />}
|
||||
</button>
|
||||
{open && drop("", "선스타일", (
|
||||
{/* 2026-10-21 design review: the property window opens the line type list only (선꼴: preview + name); weight
|
||||
and scale are their own rows. The SWHeader line style field keeps its 3 columns. */}
|
||||
{open && drop("", property.name, (
|
||||
<SWHeaderLineStyleColumns
|
||||
lineTypes={lineTypes}
|
||||
lineWeights={weights}
|
||||
currentType={current}
|
||||
currentWeight={currentWeight}
|
||||
scale={menus?.lineScale}
|
||||
onLineTypeSelect={item => { commit(item.id); close(true) }}
|
||||
onLineWeightSelect={menus?.onLineWeightSelect}
|
||||
onLineWeightValueChange={menus?.onLineWeightValueChange}
|
||||
onScaleChange={menus?.onLineScaleChange}
|
||||
onDetail={menus?.onLineStyleDetail && (() => { menus.onLineStyleDetail?.(); close(true) })}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -217,40 +217,47 @@ export function PropertyEditor({ property, onChange, menus }: { property: SWHead
|
||||
if (event.key === "Enter") { event.preventDefault(); commit(draft) }
|
||||
if (event.key === "Escape" && draft !== value) { event.stopPropagation(); cancelDraft() }
|
||||
}
|
||||
// Figma 26:12649: a number without unit / annotation (선가중치) and the slider value are the 41px hug field;
|
||||
// every other value is the 200px box (26:12314 / 26:12590 / 26:12583 / 26:12574). Figma has an editing On
|
||||
// (#0F0D0A) only for numbers; text and computed values keep Default / Hover (no invented On).
|
||||
const compact = type === "slider" || (type === "number" && !unit && !property.annotation)
|
||||
const field = compact ? undefined : "box"
|
||||
// Every value is the 200px box (26:12314 / 26:12590 / 26:12583 / 26:12574). 2026-10-21 design review: a bare
|
||||
// number (선 가중치) is no longer the 41px hug field; its editing box fills the value area like 선 축척. The slider
|
||||
// value is its own box that fills the room before the gauge (투명도). Figma has an editing On (#0F0D0A) only for
|
||||
// numbers; text and computed values keep Default / Hover (no invented On).
|
||||
const field = type === "slider" ? undefined : "box"
|
||||
const focusInput = (event: React.MouseEvent<HTMLElement>) => {
|
||||
// A click on the unit / empty part of the box edits the value (the box is the Figma field).
|
||||
if (!editable || event.target === inputRef.current || type === "select") return
|
||||
event.preventDefault()
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
const input = (
|
||||
<SWHeaderInput
|
||||
ref={inputRef}
|
||||
id={inputId} aria-label={property.name} value={mixed ? "다양함" : value}
|
||||
valueType={type === "number" || type === "slider" ? "number" : "text"}
|
||||
disabled={disabled} readOnly={!editable} parse={parse} commitOnUnmount={false}
|
||||
onCommit={editable ? commit : undefined}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div className="hm-sw-property__row" data-property-id={property.id} data-type={type} data-disabled={disabled || undefined}>
|
||||
<label htmlFor={inputId} title={property.name}>{property.name}</label>
|
||||
{picker ? <SWHeaderPropertyPickerField picker={picker} property={property} id={inputId} editable={editable} menus={menus} commit={commit} parse={parse} /> : (
|
||||
<div className="hm-sw-property__value" data-field={field} data-on={field && editable && type === "number" ? "edit" : undefined}
|
||||
data-readonly={editable ? undefined : "true"}
|
||||
onMouseDown={event => {
|
||||
// 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()
|
||||
}}>
|
||||
onMouseDown={field ? focusInput : undefined}>
|
||||
{type === "select" ? (
|
||||
<select id={inputId} aria-label={property.name} value={mixed && draft === value ? "__mixed" : draft} disabled={!editable} onChange={event => setDraft(event.target.value)} onBlur={() => commit(draft)} onKeyDown={nativeKeys}>
|
||||
{mixed && <option value="__mixed" disabled>다양함</option>}
|
||||
{!mixed && !property.options?.some(option => option.id === value) && <option value={value}>{value}</option>}
|
||||
{property.options?.map(option => <option key={option.id} value={option.id} disabled={option.disabled}>{option.name}</option>)}
|
||||
</select>
|
||||
) : (
|
||||
<SWHeaderInput
|
||||
ref={inputRef}
|
||||
id={inputId} aria-label={property.name} value={mixed ? "다양함" : value}
|
||||
className={compact ? "hm-sw-property__compact" : undefined}
|
||||
valueType={type === "number" || type === "slider" ? "number" : "text"}
|
||||
disabled={disabled} readOnly={!editable} parse={parse} commitOnUnmount={false}
|
||||
onCommit={editable ? commit : undefined}
|
||||
/>
|
||||
)}
|
||||
{property.annotation && <span className="hm-sw-property__unit">{property.annotation}</span>}
|
||||
) : type === "slider" ? (
|
||||
// Slider value box: the value and its annotation ((ByL)) are one editing field; the gauge and unit follow.
|
||||
<span className="hm-sw-property__slider-value" data-on={editable ? "edit" : undefined} data-readonly={editable ? undefined : "true"} onMouseDown={focusInput}>
|
||||
{input}
|
||||
{property.annotation && <span className="hm-sw-property__unit">{property.annotation}</span>}
|
||||
</span>
|
||||
) : input}
|
||||
{type !== "slider" && property.annotation && <span className="hm-sw-property__unit">{property.annotation}</span>}
|
||||
{unit && type !== "slider" && <span className="hm-sw-property__unit">{unit}</span>}
|
||||
{type === "color" && <span className="hm-sw-property__swatch-area" aria-hidden="true"><span className="hm-sw-property__swatch"><i style={{ backgroundColor: property.swatch ?? parseSWHeaderColor(value)?.hex ?? "transparent" }} /></span></span>}
|
||||
{property.linePreview && <SWHeaderLinePreview pattern={property.linePattern} />}
|
||||
|
||||
@@ -4,6 +4,17 @@ import { useSWHomeMenuClose } from "./SWHomeMenu"
|
||||
import type { SWFileEntry } from "./types"
|
||||
|
||||
type PreviewSide = "right" | "left" | "below" | "above"
|
||||
const MAX_RECENT_FILES = 3
|
||||
|
||||
/** Preview caption (2026-10-21): the folder `path` + the file name; a path that already ends with the name is kept as is. */
|
||||
function previewCaption(file: SWFileEntry) {
|
||||
if (file.previewTitle) return file.previewTitle
|
||||
const path = file.path?.replace(/[\\/]+$/, "")
|
||||
if (!path) return file.name
|
||||
const rest = path.slice(0, path.length - file.name.length)
|
||||
if (path.endsWith(file.name) && (rest === "" || /[\\/]$/.test(rest))) return path
|
||||
return `${path}${path.includes("/") && !path.includes("\\") ? "/" : "\\"}${file.name}`
|
||||
}
|
||||
|
||||
export interface SWRecentFilesProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
||||
/** Host-owned list (most recent first). Nothing is read from disk here. */
|
||||
@@ -24,7 +35,8 @@ export interface SWRecentFilesProps extends Omit<HTMLAttributes<HTMLDivElement>,
|
||||
/**
|
||||
* Figma 최근파일 662:67331: 354px recent-file list (rows 50px: optional 29 x 28 badge, name 14px, path 12px)
|
||||
* with the 2-depth preview 662:67471 beside the hovered / focused row. The list height follows the rows and is
|
||||
* capped (`--hm-sw-recent-files-max-height`, Figma 750 = 15 rows); longer lists scroll inside.
|
||||
* capped (`--hm-sw-recent-files-max-height`, Figma 750 = 15 rows); longer lists scroll inside. Only the first 3
|
||||
* files are shown (2026-10-21).
|
||||
*/
|
||||
export function SWRecentFiles({
|
||||
files, onFileOpen, onFileHover, renderPreview, closeOnOpen = true, emptyText = "최근 파일이 없습니다",
|
||||
@@ -35,8 +47,12 @@ export function SWRecentFiles({
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
const previewRef = useRef<HTMLDivElement>(null)
|
||||
const [hovered, setHovered] = useState<{ id: string; top: number; rowHeight: number; side: PreviewSide } | null>(null)
|
||||
const current = hovered ? files.find(file => file.id === hovered.id) : undefined
|
||||
const preview = current ? current.preview ?? renderPreview?.(current) : undefined
|
||||
// 2026-10-21 (user decision): only the 3 most recent files are shown; `files` itself is not trimmed.
|
||||
const shown = files.slice(0, MAX_RECENT_FILES)
|
||||
const current = hovered ? shown.find(file => file.id === hovered.id) : undefined
|
||||
// 2026-10-21 (user decision): the 2-depth preview keeps its frame, size and position on hover, but its body stays
|
||||
// empty — no image / thumbnail is rendered. `preview` / `renderPreview` stay accepted (public types unchanged).
|
||||
void renderPreview
|
||||
|
||||
const show = (file: SWFileEntry | null, row?: HTMLElement) => {
|
||||
const list = listRef.current
|
||||
@@ -84,7 +100,7 @@ export function SWRecentFiles({
|
||||
<div ref={listRef} className="hm-sw-recent-files__list" id={listId} role="list" aria-label={rest["aria-label"] ?? "최근 파일"}
|
||||
onScroll={() => hovered && setHovered(null)}>
|
||||
{files.length === 0 && <div className="hm-sw-recent-files__empty" role="listitem">{emptyText}</div>}
|
||||
{files.map(file => (
|
||||
{shown.map(file => (
|
||||
<div key={file.id} role="listitem" className="hm-sw-recent-files__item">
|
||||
<button type="button" className="hm-sw-recent-files__row" data-file-id={file.id}
|
||||
data-hovered={hovered?.id === file.id ? "true" : undefined} disabled={file.disabled}
|
||||
@@ -100,12 +116,18 @@ export function SWRecentFiles({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{current && preview != null && (
|
||||
{current && (
|
||||
<div ref={previewRef} className="hm-sw-recent-files__preview" data-side={hovered?.side} aria-hidden="true"
|
||||
style={{ top: hovered?.side === "below" ? hovered.top - 1 + hovered.rowHeight
|
||||
: hovered?.side === "above" ? hovered.top - 1 : hovered?.top }}>
|
||||
<span className="hm-sw-recent-files__preview-title">{current.previewTitle ?? current.path ?? current.name}</span>
|
||||
<div className="hm-sw-recent-files__preview-body">{preview}</div>
|
||||
<span className="hm-sw-recent-files__preview-title"><span dir="ltr">{previewCaption(current)}</span></span>
|
||||
<div className="hm-sw-recent-files__preview-body">
|
||||
{/* No preview (2026-10-21 design): the red prohibition sign centered on the preview panel. */}
|
||||
<svg className="hm-sw-recent-files__preview-none" viewBox="0 0 100 100" aria-hidden="true">
|
||||
<circle cx="50" cy="50" r="44.5" />
|
||||
<path d="M18.5 18.5 81.5 81.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="132" height="27" viewBox="0 0 132 27" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- 2026-10-21: Figma 26:10591 tit_* draws the badge narrow on top and flaring out onto the tile group below
|
||||
(the earlier export was upside down). Shape and gradients are mirrored vertically (y -> 31 - y); the
|
||||
-2 r4 shadow stays upward like the Figma effect. -->
|
||||
<g id="Union" filter="url(#filter0_d_0_457)">
|
||||
<use xlink:href="#stroke0_0_457" fill="url(#paint0_linear_0_457)"/>
|
||||
<mask id="stroke1_0_457" fill="white">
|
||||
<use xlink:href="#stroke0_0_457"/>
|
||||
</mask>
|
||||
<path d="M27 6H4C9.02879 6.86861 12.6686 11.6685 16.1248 16.226C19.5413 20.7314 22.7783 25 27 25H28H104H105C109.222 25 112.459 20.7314 115.875 16.226C119.331 11.6685 122.971 6.86861 128 6H105H104H28H27Z" stroke-width="2" stroke="url(#paint1_linear_0_457)" mask="url(#stroke1_0_457)"/>
|
||||
<path d="M27 25H4C9.02879 24.13139 12.6686 19.3315 16.1248 14.774C19.5413 10.2686 22.7783 6 27 6H28H104H105C109.222 6 112.459 10.2686 115.875 14.774C119.331 19.3315 122.971 24.13139 128 25H105H104H28H27Z" stroke-width="2" stroke="url(#paint1_linear_0_457)" mask="url(#stroke1_0_457)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_0_457" x="0" y="0" width="132" height="27" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
@@ -17,15 +20,15 @@
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_457"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_457" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_0_457" x1="66.3862" y1="6" x2="66.3862" y2="25" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_0_457" x1="66.3862" y1="25" x2="66.3862" y2="6" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#887757"/>
|
||||
<stop offset="0.755507" stop-color="#A8997C"/>
|
||||
<stop offset="1" stop-color="#C3B79E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_0_457" x1="66.3862" y1="6" x2="66.3862" y2="28" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint1_linear_0_457" x1="66.3862" y1="25" x2="66.3862" y2="3" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#7B6332"/>
|
||||
<stop offset="1" stop-color="#9F8D68"/>
|
||||
</linearGradient>
|
||||
<path id="stroke0_0_457" d="M4 6H27H28H104H105H128C122.971 6.86861 119.331 11.6685 115.875 16.226C112.459 20.7314 109.222 25 105 25H104H28H27C22.7783 25 19.5413 20.7314 16.1248 16.226C12.6686 11.6685 9.02879 6.86861 4 6Z" fill-rule="evenodd" clip-rule="evenodd"/>
|
||||
<path id="stroke0_0_457" d="M4 25H27H28H104H105H128C122.971 24.13139 119.331 19.3315 115.875 14.774C112.459 10.2686 109.222 6 105 6H104H28H27C22.7783 6 19.5413 10.2686 16.1248 14.774C12.6686 19.3315 9.02879 24.13139 4 25Z" fill-rule="evenodd" clip-rule="evenodd"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -109,7 +109,7 @@ export interface SWFileEntry {
|
||||
badge?: ReactNode
|
||||
/** 2-depth preview shown beside the hovered / focused row (Figma 파일출력 2뎁스 662:67471). */
|
||||
preview?: ReactNode
|
||||
/** Preview caption; defaults to `path`. */
|
||||
/** Preview caption; defaults to `path` + `name` (2026-10-21; a `path` already ending with the name is not doubled). */
|
||||
previewTitle?: string
|
||||
disabled?: boolean
|
||||
title?: string
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
--hm-sw-recent-files-row-hover-color: var(--hm-sw-header-layer-row-hover-color);
|
||||
--hm-sw-recent-files-preview-width: 310px;
|
||||
--hm-sw-recent-files-preview-background: #62533d;
|
||||
/* No preview (2026-10-21 design): the panel brown behind a red prohibition sign. */
|
||||
--hm-sw-recent-files-preview-body-background: var(--hm-sw-recent-files-preview-background);
|
||||
--hm-sw-recent-files-preview-none-color: #ff0000;
|
||||
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
@@ -241,7 +244,7 @@
|
||||
padding: 1px 23px 0;
|
||||
color: var(--hm-sw-home-menu-title-color);
|
||||
font-size: var(--hm-sw-header-label-size);
|
||||
font-weight: 400;
|
||||
font-weight: 700; /* 2026-10-21 user request: group title Bold (was Figma 400). */
|
||||
line-height: 14px;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
@@ -319,10 +322,11 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 18 x 18 icon slot at (31, 6); larger art is centered on it and may overflow (Figma assets carry shadows). */
|
||||
/* 18 x 18 icon slot at (31, 4); larger art is centered on it and may overflow (Figma assets carry shadows).
|
||||
2026-10-21 user decision: top 4px (was Figma 6px) so the icon no longer touches the bold label below it. */
|
||||
.hm-sw-home-menu__icon {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
top: 4px;
|
||||
left: 31px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -406,15 +410,17 @@
|
||||
.hm-sw-recent-files__name,
|
||||
.hm-sw-recent-files__path {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
/* clip (not hidden): overflow-clip-margin only applies to clip, so the outline of the first glyph is not cut at the
|
||||
left edge (2026-10-21 hover clipping report); the ellipsis and the text box are unchanged. */
|
||||
overflow: clip;
|
||||
overflow-clip-margin: var(--hm-sw-header-text-outline-room);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.hm-sw-recent-files__name { color: var(--hm-sw-header-text-color); font-size: var(--hm-sw-header-font-size); font-weight: 700; line-height: 16px; text-shadow: var(--hm-sw-header-text-outline); }
|
||||
.hm-sw-recent-files__name { color: var(--hm-sw-header-text-color); font-size: var(--hm-sw-header-font-size); font-weight: 700; line-height: 20px; text-shadow: var(--hm-sw-header-text-outline); }
|
||||
/* Path Bold with its 12px black outline (2026-10-20 user request), like the file name above (700 + 14px outline). */
|
||||
.hm-sw-recent-files__path { color: var(--hm-sw-recent-files-path-color); font-size: var(--hm-sw-header-label-size); font-weight: 700; line-height: 14px; opacity: 0.6; text-shadow: var(--hm-sw-header-label-outline); }
|
||||
.hm-sw-recent-files__path { color: var(--hm-sw-recent-files-path-color); font-size: var(--hm-sw-header-label-size); font-weight: 700; line-height: 16px; opacity: 0.6; text-shadow: var(--hm-sw-header-label-outline); }
|
||||
.hm-sw-recent-files__row:is(:hover, [data-hovered="true"]):not(:disabled) { background: var(--hm-sw-recent-files-row-hover-background); }
|
||||
.hm-sw-recent-files__row:is(:hover, [data-hovered="true"]):not(:disabled) .hm-sw-recent-files__name { color: var(--hm-sw-recent-files-row-hover-color); }
|
||||
.hm-sw-recent-files__empty { padding: 16px 45px; color: var(--hm-sw-recent-files-path-color); font-size: var(--hm-sw-header-label-size); opacity: 0.6; }
|
||||
@@ -471,6 +477,14 @@
|
||||
font-size: var(--hm-sw-header-font-size);
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.02em;
|
||||
/* 2026-10-21: path + file name on one line; a long path is cut at its start (…) so the file name stays visible.
|
||||
Right-to-left only for the ellipsis side; the text is an isolated left-to-right span (dir="ltr"), centered. */
|
||||
direction: rtl;
|
||||
text-align: center;
|
||||
}
|
||||
.hm-sw-recent-files__preview-body { position: relative; width: 100%; height: 243px; overflow: hidden; border-radius: 0 0 4px 0; }
|
||||
.hm-sw-recent-files__preview-body { position: relative; width: 100%; height: 243px; overflow: hidden; border-radius: 0 0 4px 0; background: var(--hm-sw-recent-files-preview-body-background); }
|
||||
/* 2026-10-21 design: no image in the preview; the body shows the "no preview" prohibition sign, centered and sized to
|
||||
the 243px body (no image file: inline SVG drawn with CSS strokes). */
|
||||
.hm-sw-recent-files__preview-body { display: grid; place-items: center; }
|
||||
.hm-sw-recent-files__preview-body > .hm-sw-recent-files__preview-none { display: block; width: 200px; height: 200px; fill: none; stroke: var(--hm-sw-recent-files-preview-none-color); stroke-width: 11; }
|
||||
.hm-sw-recent-files__preview-body > :is(img, svg, canvas) { display: block; width: 100%; height: 100%; object-fit: cover; }
|
||||
@@ -210,6 +210,11 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
calc(100% + 2 * var(--hm-sw-header-document-menu-border-width))
|
||||
);
|
||||
}
|
||||
/* 2026-10-21 user request: viewer file rows show the name only (no icon, no icon gap); rows, hover and the current
|
||||
file color are unchanged. The SWHeader document list keeps its icons. */
|
||||
.hm-sw-viewer-header__document[data-list] .hm-sw-header__document-icon { display: none; }
|
||||
/* SWHeader minimize / close (SWHeaderWindowControls) at the end of `trailing`: right edge as in SWHeader, no inset. */
|
||||
.hm-sw-viewer-header__object:has(> .hm-sw-viewer-header__actions > .hm-sw-header__window-buttons:last-child) { padding-right: 0; }
|
||||
/* Viewer header dropdown colors (2026-10-21): the menus of the header — menu buttons, More and their submenus — use the
|
||||
colors of the SWHeader `...` menu (sw.css .hm-sw-header__more-menu): its face and shadow (fixed values there), the
|
||||
object border token, the header text, secondary text at 80% (as its values) and the white 8% row hover with the text
|
||||
@@ -569,6 +574,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
}
|
||||
/* SWSelect inside viewer panels reads the viewer panel tokens (same values; viewer theme overrides follow). */
|
||||
.hm-sw-viewer-panel { --hm-sw-select-border-color: var(--hm-sw-viewer-panel-inner-border); --hm-sw-select-background: var(--hm-sw-viewer-panel-field-background); --hm-sw-select-text: var(--hm-sw-viewer-panel-text); }
|
||||
.hm-sw-property.hm-sw-viewer-panel .hm-sw-select:not(:disabled) { color: var(--hm-sw-select-text); }
|
||||
/* Viewer rows dim a disabled field as a whole (SWViewerField 50%); the slider keeps its own opacity as before. */
|
||||
.hm-sw-theme .hm-sw-viewer-slider:disabled { opacity: 1; }
|
||||
|
||||
|
||||
+134
-32
@@ -240,6 +240,10 @@
|
||||
.hm-sw-property-bar__field { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
|
||||
.hm-sw-property-bar__field label { color: var(--hm-sw-header-text-color); letter-spacing: .04em; }
|
||||
.hm-sw-property-bar__field .hm-sw-header__input { flex-shrink: 0; }
|
||||
/* 2026-10-21 (user decision): the bar shows its values (반지름 · 지름 · 원주 · 면적 …) as plain text (an <output>, never
|
||||
focused) in the input's place, font and outline. They stay red: the property window edits them. */
|
||||
.hm-sw-property-bar__value { flex-shrink: 0; padding: 2px 4px 2px 6px; color: var(--hm-sw-header-input-color); font-size: var(--hm-sw-header-font-size); font-weight: 700; line-height: 16px; letter-spacing: 0.02em; white-space: nowrap; text-shadow: var(--hm-sw-header-text-outline); }
|
||||
.hm-sw-property-bar__value[data-disabled="true"] { opacity: 0.4; }
|
||||
.hm-sw-property-bar:focus-visible { outline: 2px solid var(--hm-sw-header-focus-color); outline-offset: -2px; }
|
||||
|
||||
/* Configured controls use the same components in an ordered list; no empty removed slots.
|
||||
@@ -1602,6 +1606,10 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
/* SWSelect: native select, panel field look; focus = the SW popup focus ring. */
|
||||
.hm-sw-select { flex: 0 0 auto; height: 28px; padding: 0 8px; border: 1px solid var(--hm-sw-select-border-color); border-radius: 4px; background: var(--hm-sw-select-background); color: var(--hm-sw-select-text); font: inherit; font-size: 12px; }
|
||||
.hm-sw-select:disabled { opacity: 0.4; cursor: default; }
|
||||
/* 2026-10-21: in an SW popup the chosen value of an enabled select is editable, so it is red like every editable value;
|
||||
a disabled select and the option list keep --hm-sw-select-text. The viewer panels keep their own text color (sw-viewer.css). */
|
||||
.hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-header-input-color); }
|
||||
.hm-sw-property .hm-sw-select option { color: var(--hm-sw-select-text); }
|
||||
.hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; }
|
||||
/* 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; }
|
||||
@@ -1654,6 +1662,12 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
.hm-sw-property__expand[aria-expanded="true"] > img { transform: rotate(180deg); }
|
||||
.hm-sw-property__counts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.hm-sw-property__counts > span { min-height: 28px; padding: 5px 0; border-bottom: 1px solid rgba(0,0,0,.5); font-weight: 700; }
|
||||
/* 2026-10-21 design review: each object type entry (원 1, 폴리선 10 …) changes its own text color on Hover, with the
|
||||
SW row accent #FF5C00 (the header list row hover color). Display only: no click, selection or filter. */
|
||||
.hm-sw-property__counts > span:hover { color: var(--hm-sw-header-layer-row-hover-color); }
|
||||
/* 2026-10-21 user request: Popup items whose color changes on Hover show the pointer (display-only entries too: no
|
||||
click is added). Inputs and read-only values keep their own cursor. */
|
||||
:is(.hm-sw-property__tool, .hm-sw-property__close, .hm-sw-property__expand, .hm-sw-property__counts > span) { cursor: pointer; }
|
||||
.hm-sw-property__group--circle .hm-sw-property__group-content { position: relative; min-height: 260px; }
|
||||
.hm-sw-property__group--circle .hm-sw-property__editors { position: relative; min-height: 260px; padding-top: 260px; }
|
||||
/* Shape slot (Figma 26:11932 / 26:12035): 208 x 208 at (39, 26) of the 260px shape group. Default content is
|
||||
@@ -1664,6 +1678,8 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
.hm-sw-property__group--circle .hm-sw-property__editor { position: absolute; }
|
||||
.hm-sw-property__group--circle .hm-sw-property__row { padding: 0; min-height: 24px; gap: 4px; }
|
||||
.hm-sw-property__group--circle .hm-sw-property__row > label { flex-basis: auto; }
|
||||
/* 2026-10-21 user request: 원주 / 지름 / 반지름 / 면적 = [Input] — a text "=" after the name, outside the input. */
|
||||
.hm-sw-property__editor[data-slot] .hm-sw-property__row > label::after { content: " =" / ""; }
|
||||
.hm-sw-property__group--circle .hm-sw-property__value { flex: 0 1 auto; }
|
||||
.hm-sw-property__group--circle .hm-sw-header__input { width: calc(var(--hm-sw-input-chars) * 1ch + 8px); flex: 0 1 auto; }
|
||||
.hm-sw-property__editor[data-slot="circumference"] { left: 113px; top: 0; }
|
||||
@@ -1749,6 +1765,46 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
text-shadow: var(--hm-sw-header-text-outline);
|
||||
}
|
||||
|
||||
/* Slider value box (2026-10-21, 투명도): value + annotation in one field that fills the room before the gauge; the
|
||||
same Default / Hover / On (#0F0D0A, white annotation) as the number box. */
|
||||
.hm-sw-property__slider-value {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: 28px;
|
||||
padding: 0 7px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.hm-sw-property__row:not([data-disabled]) .hm-sw-property__slider-value:is(:hover, :not([data-readonly]):focus-within) { border-color: var(--hm-sw-property-field-hover-border-color); }
|
||||
.hm-sw-property__slider-value > .hm-sw-header__input,
|
||||
.hm-sw-property__slider-value > .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;
|
||||
}
|
||||
.hm-sw-property__slider-value > .hm-sw-header__input:not(:read-only):focus-visible { outline: none; }
|
||||
.hm-sw-property__slider-value[data-on="edit"]:focus-within { background: var(--hm-sw-property-field-editing-background); }
|
||||
.hm-sw-property__slider-value[data-on="edit"]:focus-within > .hm-sw-header__input { text-shadow: var(--hm-sw-header-text-outline); }
|
||||
.hm-sw-property__slider-value[data-on="edit"]:focus-within > .hm-sw-property__unit { color: var(--hm-sw-property-field-editing-unit-color); }
|
||||
|
||||
/* Value color rule (2026-10-21): red (#E60403) marks an editable value only. A read-only, computed or disabled value
|
||||
is the black label color of the light popup; a disabled row also keeps its 40%. */
|
||||
:is(.hm-sw-property__value, .hm-sw-property__slider-value)[data-readonly] :is(.hm-sw-header__input, .hm-sw-property__trigger, select:not(.hm-sw-select)) { color: var(--hm-sw-property-label); }
|
||||
|
||||
/* Dropdown fields: layer (pl/pr 8), line style (pl 8, pr 6: value + (ByL) + pattern), color (RGB | swatch). */
|
||||
.hm-sw-property__trigger {
|
||||
display: flex;
|
||||
@@ -1808,40 +1864,72 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
}
|
||||
|
||||
/* Responsive — header width (container query); the header never scrolls horizontally.
|
||||
Fields keep their Figma widths at every tier (measured Noto Sans KR content widths are
|
||||
231 / 168 / 278 / 142 / 163px, so shrinking would only truncate values). Tools move into
|
||||
More instead, so every Prop and Callback stays reachable. Breakpoints come from the
|
||||
measured widths: fixed parts (home 63 + gaps 6 + window 240) + object area + file name.
|
||||
From 760px up (2026-10-21 user decision; the SW Footer story widths) nothing moves into More: the five menus 레이어 · 색상 · 선스타일 ·
|
||||
문자스타일 · 치수스타일 always stay in the header with their titles, and the More button is not shown. As it narrows:
|
||||
>= 1800 Figma layout. 1920 -> 1800 only the file name shrinks (310 -> 190).
|
||||
< 1800 선/문자/치수 스타일 -> More (object area 1301 -> 719)
|
||||
< 1180 그리기모드 label hides (719 -> 633)
|
||||
< 1080 속성Bar/속성창 -> More, window move 88 -> 24 (633 -> 480, window 240 -> 176)
|
||||
< 900 색상 -> More (480 -> 284)
|
||||
< 640 인쇄/팝업 가이드 -> More, window move hides (window 176 -> 64)
|
||||
Only below ~520px (phones) does the layer field shrink: its state icons hide and the
|
||||
layer name ellipsizes.
|
||||
The file name always takes the remaining width (ellipsis). cqw = header width.
|
||||
Hiding targets the flex item of each field ([data-menu] / [data-tier] on the field button, or on
|
||||
the wrapper of a field with a dropdown), so a dropdown field hides like a plain one. */
|
||||
< 1800 the 그리기모드 text hides, the line-type / dimension pictures hide and the color chip is 24px (fields take
|
||||
their content width); the file name gives way first (down to 96px, ellipsis), then the field values
|
||||
shrink with an ellipsis, each by its own width.
|
||||
< 1080 window move area 88 -> 24 (window 240 -> 176); layer-state icons, the color chip and the 문자스타일
|
||||
font name hide, field insets 6px.
|
||||
<= 900 the 선스타일 field shows 선스타일 only (가중치 / 스케일 hide).
|
||||
Below 760px (phones, e.g. 390) the former tiers are unchanged: parts move into More (선/문자/치수 스타일, 그리기모드,
|
||||
속성Bar/속성창, 색상, then 인쇄/팝업 가이드 below 640), and below 520 the layer-state icons hide.
|
||||
Each field keeps its button, title, value, dropdown and hover / focus box. cqw = header width.
|
||||
Hiding targets the flex item of each field ([data-menu] / [data-tier] on the field button, or on the wrapper of a
|
||||
field with a dropdown), so a dropdown field hides like a plain one. */
|
||||
.hm-sw-header__document { flex: 0 0 clamp(190px, calc(100cqw - 1610px - var(--hm-sw-header-leading-width, 0px)), 310px); min-width: 0; }
|
||||
.hm-sw-header__field--layer { flex-shrink: 1; }
|
||||
.hm-sw-header__field:not(.hm-sw-header__field--layer) { flex-shrink: 0; }
|
||||
|
||||
@container hm-sw-header (max-width: 1799.98px) {
|
||||
.hm-sw-header__fields > [data-tier="aux"] { display: none; }
|
||||
.hm-sw-header__more { display: block; }
|
||||
.hm-sw-header__more-item[data-tier="aux"] { display: flex; }
|
||||
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 1030px - var(--hm-sw-header-leading-width, 0px)), 310px); }
|
||||
}
|
||||
@container hm-sw-header (max-width: 1179.98px) {
|
||||
@container hm-sw-header (min-width: 760px) and (max-width: 1799.98px) {
|
||||
.hm-sw-header__mode { display: none; }
|
||||
.hm-sw-header__fields { padding-left: 4px; }
|
||||
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 944px - var(--hm-sw-header-leading-width, 0px)), 310px); }
|
||||
/* Content width (no Figma box) that may shrink: the free room is taken from each value by its own width. */
|
||||
.hm-sw-header__fields > :is(.hm-sw-header__layer, .hm-sw-header__menu),
|
||||
.hm-sw-header__fields > .hm-sw-header__control > :is(.hm-sw-header__layer, .hm-sw-header__menu) { flex: 0 1 auto; width: auto; min-width: 0; }
|
||||
.hm-sw-header__fields .hm-sw-header__field { flex: 0 1 auto; width: auto; min-width: 0; }
|
||||
/* Fixed-size buttons in a configured bar (인쇄 · K) keep their box: only the fields give way. */
|
||||
.hm-sw-header__fields > .hm-sw-header__control:is([data-control="print"], [data-control="popupGuide"]) { flex-shrink: 0; }
|
||||
.hm-sw-header__layer > .hm-sw-header__field--layer { width: 100%; }
|
||||
.hm-sw-header__field :is(.hm-sw-header__line-values, .hm-sw-header__font-name) { min-width: 0; }
|
||||
/* Tighter fields: smaller insets, a 24px color chip, no line-type / dimension pictures (the value names stay). */
|
||||
.hm-sw-header__fields .hm-sw-header__field { padding: 0 8px; gap: 8px; }
|
||||
.hm-sw-header__fields .hm-sw-header__chip { flex-basis: 24px; width: 24px; }
|
||||
.hm-sw-header__fields :is(.hm-sw-header__linetype, .hm-sw-header__dimension-icon) { display: none; }
|
||||
.hm-sw-header__fields .hm-sw-header__line-values { gap: 8px; }
|
||||
/* Open dropdown (2026-10-21 user request): the fields are narrower than their lists here, so the yellow ring stays
|
||||
inside each button and the list gets its own ring, as a shifted list (치수스타일) already does. */
|
||||
.hm-sw-header__fields :is(.hm-sw-header__layer-menu, .hm-sw-header__menu-panel:not(.hm-sw-header__line-menu))::before { top: -1px; }
|
||||
.hm-sw-header__fields .hm-sw-header__field[data-expanded="true"]::after { opacity: 1; }
|
||||
/* Where the list meets the button the seam is black across the button width (as the 선스타일 seam). */
|
||||
.hm-sw-header__fields :is(.hm-sw-header__layer, .hm-sw-header__menu:not(.hm-sw-header__menu--line-style)):has(> .hm-sw-header__field[data-expanded="true"])::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 31;
|
||||
top: calc(100% - 1px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--hm-sw-header-layer-menu-border-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 1610 - 1301 + the tighter object area with titles, without 그리기모드 (891, measured) = 1200. */
|
||||
.hm-sw-header__document { flex-basis: clamp(96px, calc(100cqw - 1200px - var(--hm-sw-header-leading-width, 0px)), 310px); }
|
||||
}
|
||||
@container hm-sw-header (min-width: 760px) and (max-width: 1079.98px) {
|
||||
/* Values need the room (1024px, 2026-10-21 user request): the layer-state icons, the color chip and the font name
|
||||
beside 문자스타일 hide, insets 6px, every value part may shrink (ellipsis). */
|
||||
.hm-sw-header__fields .hm-sw-header__field { padding: 0 6px; gap: 6px; }
|
||||
.hm-sw-header__fields :is(.hm-sw-header__field--layer .hm-sw-header__layer-icons, .hm-sw-header__chip, .hm-sw-header__font-name) { display: none; }
|
||||
.hm-sw-header__fields .hm-sw-header__line-values { grid-template-columns: minmax(0, auto) minmax(0, 38px) minmax(0, 38px); gap: 6px; }
|
||||
}
|
||||
@container hm-sw-header (min-width: 760px) and (max-width: 900px) {
|
||||
/* 900px and narrower (2026-10-21 user request): the 선스타일 field keeps only 선스타일 (가중치 / 스케일 hide). */
|
||||
.hm-sw-header__fields .hm-sw-header__line-values { grid-template-columns: minmax(0, auto); }
|
||||
.hm-sw-header__fields .hm-sw-header__line-values > :not(:first-child) { display: none; }
|
||||
}
|
||||
@container hm-sw-header (max-width: 1079.98px) {
|
||||
.hm-sw-header__properties { display: none; }
|
||||
.hm-sw-header__more { margin-left: auto; margin-right: 4px; }
|
||||
.hm-sw-header__more-item[data-tier="panel"] { display: flex; }
|
||||
.hm-sw-header__motion { flex-basis: 24px; background-size: 18px 37px; }
|
||||
/* 24px move area: no room for the icon motion, so hover shows only the tooltip, right of the
|
||||
area like Figma hover2 (x = area width + 1, y 11). */
|
||||
@@ -1851,9 +1939,17 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
.hm-sw-header__motion-tooltip { left: calc(100% + 1px); top: 11px; }
|
||||
.hm-sw-header__motion:hover > .hm-sw-header__motion-tooltip { animation: none; opacity: 1; }
|
||||
.hm-sw-header__window { flex-basis: 176px; width: 176px; }
|
||||
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 725px - var(--hm-sw-header-leading-width, 0px)), 310px); }
|
||||
}
|
||||
@container hm-sw-header (max-width: 899.98px) {
|
||||
/* Below 760px: the former tiers (선/문자/치수 스타일 < 1800, 그리기모드 < 1180, 속성Bar/속성창 < 1080, 색상 < 900). */
|
||||
@container hm-sw-header (max-width: 759.98px) {
|
||||
.hm-sw-header__fields > [data-tier="aux"] { display: none; }
|
||||
.hm-sw-header__more { display: block; }
|
||||
.hm-sw-header__more-item[data-tier="aux"] { display: flex; }
|
||||
.hm-sw-header__mode { display: none; }
|
||||
.hm-sw-header__fields { padding-left: 4px; }
|
||||
.hm-sw-header__properties { display: none; }
|
||||
.hm-sw-header__more { margin-left: auto; margin-right: 4px; }
|
||||
.hm-sw-header__more-item[data-tier="panel"] { display: flex; }
|
||||
.hm-sw-header__fields > [data-menu="color"] { display: none; }
|
||||
.hm-sw-header__more-item[data-tier="narrow"] { display: flex; }
|
||||
.hm-sw-header__document { flex-basis: clamp(96px, calc(100cqw - 529px - var(--hm-sw-header-leading-width, 0px)), 310px); }
|
||||
@@ -1960,17 +2056,23 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
:is(.hm-sw-header__leading, .hm-sw-header__document[data-tool]) + .hm-sw-header__document > .hm-sw-header__document-button { padding-left: 12px; }
|
||||
/* While the header Open is shown, the default fields keep their width: a lack of room becomes a measured
|
||||
overflow that moves Open into More instead of narrowing the layer field. Custom leading content alone keeps
|
||||
the existing behavior (file name first, then the layer field below 520px). */
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields:not([data-configured]) > :is(.hm-sw-header__layer, .hm-sw-header__field--layer, .hm-sw-header__control[data-control="layer"]) { flex-shrink: 0; }
|
||||
the existing behavior (file name first, then the layer field below 520px). Below 760px only (2026-10-21): from
|
||||
760px up nothing moves into More, so the fields shrink beside Open like everywhere else. */
|
||||
@container hm-sw-header (max-width: 759.98px) {
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields:not([data-configured]) > :is(.hm-sw-header__layer, .hm-sw-header__field--layer, .hm-sw-header__control[data-control="layer"]) { flex-shrink: 0; }
|
||||
}
|
||||
.hm-sw-header__more[data-forced] { display: block; }
|
||||
/* Tool groups reuse the 상세보기 / print-K surfaces but are host tools: the Header breakpoints that move those
|
||||
built-in parts into More never hide them (they have no More entry). */
|
||||
.hm-sw-header__properties.hm-sw-tool__group { display: flex; }
|
||||
.hm-sw-header__actions.hm-sw-tool__pill { display: flex; }
|
||||
/* Configured layouts too: while the header Open is shown, controls other than the file name keep their width,
|
||||
so a lack of room is a measured overflow that moves Open into More (the file name still gives way first). */
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]),
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]) > * { flex-shrink: 0; }
|
||||
so a lack of room is a measured overflow that moves Open into More (the file name still gives way first).
|
||||
Below 760px only (2026-10-21), as above. */
|
||||
@container hm-sw-header (max-width: 759.98px) {
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]),
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]) > * { flex-shrink: 0; }
|
||||
}
|
||||
/* ...and the file name gives way only down to the default design's smallest width (96px, see the clamps above). */
|
||||
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control[data-control="document"] { min-width: 96px; }
|
||||
|
||||
|
||||
Reference in new issue
Block a user