feat(org-chart): implement expandable layout and multi-company navigation

Integrate P5-1B through P5-4 shared Core layout, collapse, zoom/pan/fit, company roots and keyboard accessibility. Include React/HTML examples, virtual 20/100/300 employee fixtures, focused checks and the additive data contract.
This commit is contained in:
shkim4 committed 2026-10-12 21:59:09 +09:00
1 parent a92913d6bc
commit 064517ec46
30 files changed
+7532 -183

No files matched your search

@@ -1,92 +1,195 @@
/* P5-1A flow preview only. Every rule is under the new presenter namespace;
the fixed Workdesk composition and approved card internals stay unchanged.
Ordinary flex wrapping is not the P5-1B automatic hierarchy layout. */
/* Auto-only layout. P5-4 retains the current card geometry by user decision;
no external SVG reconstruction is required for this presentation. */
.hm-orgchart-expanded {
--hm-orgchart-expanded-sibling-gap: var(--hm-orgchart-resolved-spacing-xxl);
--hm-orgchart-expanded-level-gap: calc(2 * var(--hm-orgchart-resolved-spacing-xxl));
--hm-orgchart-expanded-title-gap: var(--hm-orgchart-resolved-spacing-md);
--hm-orgchart-expanded-employee-gap: var(--hm-orgchart-resolved-spacing-md);
--hm-orgchart-expanded-line-width: var(--hm-orgchart-resolved-border-width);
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--hm-orgchart-resolved-spacing-sm);
gap: var(--hm-orgchart-resolved-spacing-lg);
width: 100%;
min-width: 0;
padding: var(--hm-orgchart-resolved-spacing-lg);
background: var(--hm-orgchart-resolved-bg-content);
}
.hm-orgchart-expanded .hm-orgchart-heading { margin: 0; }
.hm-orgchart-expanded .hm-orgchart-expanded-stage-note {
margin: 0;
color: var(--hm-orgchart-resolved-fg-primary);
font-weight: var(--hm-orgchart-resolved-font-weight-bold);
}
.hm-orgchart-expanded .hm-orgchart-expanded-description {
margin: 0;
color: var(--hm-orgchart-resolved-fg-secondary);
overflow-wrap: anywhere;
}
.hm-orgchart-expanded .hm-orgchart-expanded-top-leaders {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--hm-orgchart-resolved-spacing-lg);
padding: var(--hm-orgchart-resolved-spacing-lg) 0;
}
.hm-orgchart-expanded .hm-orgchart-viewport {
padding: var(--hm-orgchart-resolved-spacing-lg);
overflow: auto;
position: relative;
height: clamp(320px, 65vh, 800px);
padding: 0;
overflow: hidden;
touch-action: none;
cursor: grab;
background: var(--hm-orgchart-resolved-bg-surface);
}
.hm-orgchart-expanded .hm-orgchart-forest {
display: flex;
flex-wrap: wrap;
position: absolute;
top: 0;
left: 0;
width: max-content;
min-width: 0;
transform-origin: 0 0;
justify-content: flex-start;
align-items: flex-start;
justify-content: center;
gap: var(--hm-orgchart-resolved-spacing-xxl);
width: 100%;
min-width: 0;
gap: var(--hm-orgchart-expanded-sibling-gap);
}
.hm-orgchart-expanded .hm-orgchart-tree { flex: 0 0 auto; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart {
min-width: 0;
padding: 0;
.hm-orgchart-expanded .hm-orgchart-engine.orgchart { min-width: 0; padding: 0; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes {
flex-direction: row;
align-items: flex-start;
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] {
vertical-align: top;
width: max-content;
min-width: 146px;
margin: 0;
min-width: var(--hm-orgchart-resolved-card-width-leader);
margin: 0 calc(var(--hm-orgchart-expanded-sibling-gap) / 2) var(--hm-orgchart-expanded-level-gap);
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-organization {
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] > .hm-orgchart-organization {
display: flex;
flex-direction: column;
align-items: center;
width: auto;
gap: var(--hm-orgchart-expanded-title-gap);
width: max-content;
min-width: var(--hm-orgchart-resolved-card-width-leader);
padding: 0;
border: 0;
border-radius: 0;
background: none;
box-shadow: none;
position: relative;
}
/* Same approved 1Depth title geometry; color resolves from the auto-only
company variables supplied by the shared node decorator. */
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-organization-name {
align-self: stretch;
min-width: 146px;
height: 36px;
min-height: 36px;
margin: 0;
padding: 0 12px;
background: var(--hm-orgchart-current-gradient);
color: var(--hm-orgchart-resolved-on-action-primary);
font-size: 20px;
font-weight: 700;
line-height: 36px;
height: auto;
min-height: 0;
padding: 0;
background: var(--hm-orgchart-expanded-primary);
color: var(--hm-orgchart-expanded-title-ink);
font-size: var(--hm-orgchart-resolved-font-size-base);
font-weight: var(--hm-orgchart-resolved-font-weight-bold);
line-height: 1.5;
white-space: nowrap;
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee { margin: 0; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee.hm-orgchart-leader { margin: 8px 0 0; }
.hm-orgchart-expanded .hm-orgchart-organization-link { color: inherit; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
gap: 8px;
margin-top: 8px;
gap: var(--hm-orgchart-expanded-employee-gap);
margin: 0;
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees:empty { display: none; }
/* Shared neutral cards, isolated from Fixed asset backgrounds and geometry. */
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee[data-card-presentation="expanded"] {
margin: 0;
box-sizing: border-box;
border: var(--hm-orgchart-expanded-card-border-width, var(--hm-orgchart-resolved-border-width)) solid var(--hm-orgchart-resolved-stroke-default);
background: var(--hm-orgchart-resolved-bg-surface);
color: var(--hm-orgchart-resolved-fg-primary);
}
.hm-orgchart-expanded .hm-orgchart-employee[data-card-presentation="expanded"]::before { content: none; }
.hm-orgchart-expanded .hm-orgchart-employee[data-card-presentation="expanded"] .hm-orgchart-avatar { border-radius: 0; }
.hm-orgchart-expanded .hm-orgchart-person-text { display: flex; flex-direction: column; gap: 0; }
.hm-orgchart-expanded .hm-orgchart-employee::after { content: none; }
/* Keep dabeng's first/last/only-child horizontal branch geometry. Only change
connector length and offsets to agree with the Auto vertical gap. */
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes > .hierarchy::before {
top: calc(-1 * var(--hm-orgchart-expanded-level-gap) / 2);
left: 0;
width: 100%;
height: 0;
border-width: var(--hm-orgchart-expanded-line-width) 0 0;
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes > .hierarchy:first-child::before { left: calc(50% - 1px); width: calc(50% + 1px); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes > .hierarchy:last-child::before { width: calc(50% + 1px); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes > .hierarchy:only-child::before { left: calc(50% - 1px); width: var(--hm-orgchart-expanded-line-width); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart > ul > li > ul li > .node::before {
top: calc(-1 * var(--hm-orgchart-expanded-level-gap) / 2);
height: calc(var(--hm-orgchart-expanded-level-gap) / 2);
width: var(--hm-orgchart-expanded-line-width);
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .hierarchy:not(.couple) > .node:has(+ .nodes)::after {
bottom: calc(-1 * var(--hm-orgchart-expanded-level-gap) / 2);
height: calc(100% - var(--hm-orgchart-expanded-content-height) + var(--hm-orgchart-expanded-level-gap) / 2);
width: var(--hm-orgchart-expanded-line-width);
}
/* Standalone content variables, independent of Fixed and HM React controls. */
.hm-orgchart-expanded {
--hm-orgchart-expanded-card-border-width: 1px;
--hm-orgchart-expanded-card-accent-width: 3px;
--hm-orgchart-expanded-card-padding: 8px;
--hm-orgchart-expanded-card-radius: var(--hm-orgchart-resolved-radius-container);
--hm-orgchart-expanded-photo-size: 36px;
--hm-orgchart-expanded-name-size: 13px;
--hm-orgchart-expanded-position-size: 10px;
--hm-orgchart-expanded-name-weight: var(--hm-orgchart-resolved-font-weight-bold);
--hm-orgchart-expanded-person-gap: 0px;
--hm-orgchart-expanded-title-padding: var(--hm-orgchart-resolved-spacing-sm) var(--hm-orgchart-resolved-spacing-md);
--hm-orgchart-expanded-title-radius: var(--hm-orgchart-resolved-radius-control-md);
}
.hm-orgchart-expanded .hm-orgchart-organization-name { display: flex; border-radius: var(--hm-orgchart-expanded-title-radius); }
.hm-orgchart-expanded-toggle {
flex: 1; border: 0; background: none; color: inherit; font: inherit;
cursor: pointer; padding: var(--hm-orgchart-expanded-title-padding); white-space: nowrap;
border-radius: inherit;
}
.hm-orgchart-expanded-toggle:not(:disabled):hover,
.hm-orgchart-expanded-link:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
.hm-orgchart-expanded-toggle:disabled { cursor: default; opacity: 0.75; }
.hm-orgchart-expanded-link { display: inline-flex; align-items: center; padding-inline: var(--hm-orgchart-resolved-spacing-sm); color: inherit; }
.hm-orgchart-expanded-toggle:focus-visible, .hm-orgchart-expanded-link:focus-visible,
.hm-orgchart-expanded-toolbar button:focus-visible {
outline: 3px solid var(--hm-orgchart-resolved-action-primary); outline-offset: 3px;
}
.hm-orgchart-expanded-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--hm-orgchart-resolved-spacing-sm); }
.hm-orgchart-expanded-toolbar button {
font: inherit; padding: var(--hm-orgchart-resolved-spacing-sm) var(--hm-orgchart-resolved-spacing-md);
border: var(--hm-orgchart-resolved-border-width) solid var(--hm-orgchart-resolved-stroke-default);
border-radius: var(--hm-orgchart-resolved-radius-control-md); background: var(--hm-orgchart-resolved-bg-surface);
color: var(--hm-orgchart-resolved-fg-primary); cursor: pointer;
}
.hm-orgchart-expanded-toolbar button:disabled { opacity: 0.5; cursor: default; }
.hm-orgchart-expanded-toolbar span { color: var(--hm-orgchart-resolved-fg-secondary); font-size: var(--hm-orgchart-resolved-font-size-meta); }
.hm-orgchart-expanded-selection, .hm-orgchart-top-node-title { margin: 0; }
.hm-orgchart-expanded-empty { padding: var(--hm-orgchart-resolved-spacing-lg); }
.hm-orgchart-expanded .hm-orgchart-viewport[data-dragging] { cursor: grabbing; user-select: none; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] { cursor: default; }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees[data-connection-direction="left"],
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees[data-connection-direction="right"] {
padding-inline: var(--hm-orgchart-resolved-spacing-xxl);
}
.hm-orgchart-expanded .hm-orgchart-employee-connectors { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
.hm-orgchart-expanded .hm-orgchart-employee-connectors path { fill: none; stroke: var(--hm-orgchart-resolved-stroke-default); stroke-width: var(--hm-orgchart-expanded-line-width); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee[data-card-presentation="expanded"] {
padding: var(--hm-orgchart-expanded-card-padding);
border-radius: var(--hm-orgchart-expanded-card-radius);
width: var(--hm-orgchart-expanded-card-width, var(--hm-orgchart-resolved-card-width-employee));
min-width: 0;
min-height: var(--hm-orgchart-expanded-card-height, var(--hm-orgchart-resolved-card-height-employee));
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-leader[data-card-presentation="expanded"] {
width: var(--hm-orgchart-expanded-leader-width, var(--hm-orgchart-resolved-card-width-leader));
min-height: var(--hm-orgchart-expanded-leader-height, var(--hm-orgchart-resolved-card-height-leader));
}
/* Inset accents preserve all outer dimensions even when accent width changes. */
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee[data-card-presentation="expanded"][data-connection-direction="top"] {
box-shadow: inset 0 var(--hm-orgchart-expanded-card-accent-width) 0 var(--hm-orgchart-expanded-card-primary);
border-top-color: var(--hm-orgchart-expanded-card-primary);
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee[data-card-presentation="expanded"][data-connection-direction="left"] {
box-shadow: inset var(--hm-orgchart-expanded-card-accent-width) 0 0 var(--hm-orgchart-expanded-card-primary);
border-left-color: var(--hm-orgchart-expanded-card-primary);
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee[data-card-presentation="expanded"][data-connection-direction="right"] {
box-shadow: inset calc(-1 * var(--hm-orgchart-expanded-card-accent-width)) 0 0 var(--hm-orgchart-expanded-card-primary);
border-right-color: var(--hm-orgchart-expanded-card-primary);
}
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee .hm-orgchart-avatar {
width: var(--hm-orgchart-expanded-photo-size); height: var(--hm-orgchart-expanded-photo-size); flex-basis: var(--hm-orgchart-expanded-photo-size);
}
.hm-orgchart-expanded .hm-orgchart-person-text { gap: var(--hm-orgchart-expanded-person-gap); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-person-name { font-size: var(--hm-orgchart-expanded-name-size); font-weight: var(--hm-orgchart-expanded-name-weight); }
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-person-role { font-size: var(--hm-orgchart-expanded-position-size); }