feat(sw): viewer header keeps every menu down to 520px
Deploy Storybook / deploy (push) Successful in 1m16s
Deploy Storybook / deploy (push) Successful in 1m16s
- File name: clamp(80px, min(45cqw - 256px, 100cqw - 780px), 360px), 56px below 560px; 1280px and wider unchanged - Model / Layout tabs narrow (name, then count ellipsized) down to the new --hm-sw-viewer-tabs-min-width (48px) before anything moves into More; the measurement counts them at that minimum once every icon step applies - Switch items get an optional shortLabel shown below 560px (어두운 배경 -> 배경); counts hidden there - More appears only below about 520px; viewer test and AI guides updated
This commit is contained in:
1 parent
7976801034
commit
cabd2f67de
8 files changed
+83
-34
No files matched your search
@@ -121,9 +121,11 @@ function responsiveSteps(groups: SWViewerHeaderGroup[], extraIcons: string[] = [
|
||||
* width up to its usual width (so a short name leaves room to the menus), the object surface without stretching and
|
||||
* the trailing area without its auto margin (`data-measuring`, sw-viewer.css). Read and reset in one layout effect,
|
||||
* never painted; positioned layers (menus, logo menu) never count. Same method as SWHeader's measured More.
|
||||
* `tabsAtMin` (2026-10-26): once every icon step is applied, the Model / Layout tabs count at their minimum width
|
||||
* (`data-measuring="min"`), so they narrow with an ellipsis before anything moves into More.
|
||||
*/
|
||||
function measureRoom(root: HTMLElement) {
|
||||
root.setAttribute("data-measuring", "")
|
||||
function measureRoom(root: HTMLElement, tabsAtMin = false) {
|
||||
root.setAttribute("data-measuring", tabsAtMin ? "min" : "")
|
||||
const style = getComputedStyle(root), box = root.getBoundingClientRect()
|
||||
const end = box.right - parseFloat(style.borderRightWidth) - parseFloat(style.paddingRight)
|
||||
let used = box.left + parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft)
|
||||
@@ -226,7 +228,11 @@ function SwitchField({ item, onCommand }: { item: Extract<SWViewerHeaderItem, {
|
||||
const id = useId()
|
||||
return (
|
||||
<div className="hm-sw-viewer-header__switch-field" title={item.title}>
|
||||
<label htmlFor={id} className="hm-sw-viewer-header__switch-label">{item.label}</label>
|
||||
{/* shortLabel (2026-10-26): the narrow-header caption (sw-viewer.css); the full label stays the accessible name. */}
|
||||
<label htmlFor={id} className="hm-sw-viewer-header__switch-label" data-short={item.shortLabel ? "" : undefined}>
|
||||
<span className="hm-sw-viewer-header__switch-label-full">{item.label}</span>
|
||||
{item.shortLabel && <span className="hm-sw-viewer-header__switch-label-short" aria-hidden="true">{item.shortLabel}</span>}
|
||||
</label>
|
||||
<SWViewerSwitch id={id} checked={item.checked} disabled={item.disabled}
|
||||
onCheckedChange={next => { item.onCheckedChange?.(next); onCommand({ id: item.id, itemId: item.id, value: next }) }} />
|
||||
</div>
|
||||
@@ -342,7 +348,7 @@ export const SWViewerHeader = forwardRef<HTMLElement, SWViewerHeaderProps>(funct
|
||||
if (level !== 0) return setLevel(0)
|
||||
}
|
||||
if (level > steps.length) return setLevel(steps.length)
|
||||
const room = measureRoom(root)
|
||||
const room = measureRoom(root, level >= steps.filter(step => step.kind === "icons").length)
|
||||
const freed = freedRef.current
|
||||
const last = level > 0 ? freed[level - 1] : undefined
|
||||
if (last && last.room === undefined) last.room = room - last.before
|
||||
|
||||
@@ -108,6 +108,11 @@ export type SWViewerHeaderItem =
|
||||
| (SWViewerHeaderItemBase & {
|
||||
/** Caption above a switch, e.g. 2.5D. */
|
||||
type: "switch"
|
||||
/**
|
||||
* Shorter caption shown instead of `label` in a narrow header (below 560px header width), e.g. 배경 for 어두운 배경.
|
||||
* `label` stays the accessible name. Default: `label`.
|
||||
*/
|
||||
shortLabel?: string
|
||||
checked: boolean
|
||||
onCheckedChange?: (checked: boolean) => void
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user