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
|
||||
|
||||
Reference in new issue
Block a user