fix(sw): stabilize footer command layout and overflow alignment

This commit is contained in:
shkim4 committed 2026-10-20 10:38:42 +09:00
1 parent 41e963a12a
commit 73e30242f2
7 files changed
+36 -21

No files matched your search

@@ -32,8 +32,9 @@ export interface SWFooterProps extends Omit<HTMLAttributes<HTMLElement>, "childr
/** A command was clicked. The host runs it and updates `activeToolId`. */
onToolSelect?: (toolId: string, categoryId: string, tool: SWFooterTool) => void
/**
* Slots of the command row (commands + empty + slots). Default 17 (Figma 1920px frame), shrinking to the bar width.
* With `command` the row keeps its own width: default commands + 2 empty slots.
* Most slots of the command row (commands + empty + slots). The row width follows the bar width only (never the
* command count of the category). Default 17 (Figma 1920px frame); with `command` no limit (the share of the
* free width decides).
*/
slotCount?: number
/** An empty + slot was clicked (register a command). `slotIndex` is 0-based in the row. Omitted: the empty slots are inert. */
@@ -87,13 +88,17 @@ export const SWFooter = forwardRef<HTMLElement, SWFooterProps>(function SWFooter
const panelId = `${uid}panel`
const tabPrefix = `${uid}tab`
// Room for slots, measured (Figma 26:10097 has no narrow layout; the reference screens of 2026-10-20 do):
// empty slots go first, then the commands that do not fit move to the ▲ panel above the last slot.
// Room for slots comes from the bar width only, never from the current category's command count: switching
// categories keeps the slot area, 명령어 전체보기 and the command line in place (2026-10-20). Without `command`
// the group fills the bar (Figma 26:10097: up to 17 slots); with `command` the free width is shared by
// --hm-sw-footer-slot-share (command line ≥ --hm-sw-footer-command-min-width). Commands beyond the room move
// to the ▲ panel above the last slot; fewer commands leave empty slots.
const rootRef = useRef<HTMLElement | null>(null)
const groupRef = useRef<HTMLDivElement>(null)
const auxRef = useRef<HTMLDivElement>(null)
const embedded = command !== undefined && command !== null && command !== false
const [fit, setFit] = useState(Number.POSITIVE_INFINITY)
// Before the first measure: the Figma 1920 row (17 slots). Never infinite (the row renders this many slots).
const [fit, setFit] = useState(slotCount ?? 17)
useLayoutEffect(() => {
const root = rootRef.current
const group = groupRef.current
@@ -104,8 +109,11 @@ export const SWFooter = forwardRef<HTMLElement, SWFooterProps>(function SWFooter
if (embedded) {
const aux = auxRef.current
const auxWidth = aux ? aux.offsetWidth + parseFloat(getComputedStyle(aux).marginRight || "0") : 0
const commandMin = parseFloat(getComputedStyle(root).getPropertyValue("--hm-sw-footer-command-min-width")) || 0
room = root.clientWidth - auxWidth - commandMin - 4 - end - 2
const style = getComputedStyle(root)
const commandMin = parseFloat(style.getPropertyValue("--hm-sw-footer-command-min-width")) || 0
const share = parseFloat(style.getPropertyValue("--hm-sw-footer-slot-share")) || 0.5
const free = root.clientWidth - auxWidth - 4 - end - 2
room = Math.min(free * share, free - commandMin)
}
setFit(Math.max(1, Math.floor((room - 1) / SLOT)))
}
@@ -116,11 +124,10 @@ export const SWFooter = forwardRef<HTMLElement, SWFooterProps>(function SWFooter
if (auxRef.current) observer.observe(auxRef.current)
return () => observer.disconnect()
}, [embedded])
const overflow = tools.length > fit
const visible = overflow ? tools.slice(0, Math.max(0, fit - 1)) : tools
const slots = Math.max(1, Math.min(slotCount ?? (embedded ? Number.POSITIVE_INFINITY : 17), fit))
const overflow = tools.length > slots
const visible = overflow ? tools.slice(0, Math.max(0, slots - 1)) : tools
const hidden = overflow ? tools.slice(visible.length) : []
const wanted = embedded ? slotCount ?? tools.length + 2 : slotCount ?? 17
const slots = overflow ? fit : Math.max(tools.length, Math.min(wanted, fit))
// ▲ panel of the commands that do not fit (ico_arrow 26:10023: ▲ closed, ▼ open).
const [moreOpen, setMoreOpen] = useState(false)
+6 -1
View File
@@ -89,6 +89,10 @@
--hm-sw-footer-command-line-height: 14px;
/* Not in Figma (reference screens 2026-10-20): the command line inside the bar keeps at least this width. */
--hm-sw-footer-command-min-width: 280px;
/* Not in Figma: with the command line inside the bar, the slot area takes this share of the free width (bar minus
the auxiliary area and 명령어 전체보기), the command line the rest. 0.57 reproduces both reference screens
(1920 → 12 slots / command line 629px, 952 → 4 commands + ▲). Bar width only, never the command count. */
--hm-sw-footer-slot-share: 0.57;
/* Not in Figma: below this bar width the auxiliary toggles show their icon only (reference screen 2026-10-20). */
--hm-sw-footer-aux-compact-width: 45px;
}
@@ -282,7 +286,8 @@
bottom: 100%;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
/* Rows fill from the left in data order; a short last row stays on the left (the panel itself is right aligned). */
justify-content: flex-start;
border: 1px solid var(--hm-sw-footer-group-border-color);
border-bottom: 0;
border-radius: var(--hm-sw-footer-group-radius) var(--hm-sw-footer-group-radius) 0 0;