fix: align org chart hierarchy and typography weights

This commit is contained in:
shkim4 committed 2026-10-14 13:33:59 +09:00
1 parent 72d427fcd2
commit d42466f5eb
33 files changed
+358 -85

No files matched your search

+22 -3
View File
@@ -1,6 +1,8 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fixture from '../../../../examples/org-chart/data-expanded-large.json';
import small from '../../../../examples/org-chart/data-expanded-small.json';
import medium from '../../../../examples/org-chart/data-expanded-medium.json';
import { companyColorPresets, type CompanyId } from '../company-color-presets.js';
import {
calculateSummary, createOrgChartExampleDataset, JsonOrgChartDataSource,
@@ -20,17 +22,34 @@ function assertContract(dataset: OrgChartDataset) {
assert.equal(new Set(dataset.affiliations.map(affiliation => affiliation.id)).size, dataset.affiliations.length);
const summary = calculateSummary(dataset).raw;
assert.equal(summary.total, dataset.employees.length, 'leaders count once as employees');
const pending = normalizeOrgChartDataset(dataset).roots.map(node => ({ node, depth: 1 }));
const pending = normalizeOrgChartDataset(dataset).roots.map(node => ({ node, depth: 1, underCell: false }));
let maximumDepth = 0;
while (pending.length) {
const { node, depth } = pending.pop()!;
const { node, depth, underCell } = pending.pop()!;
assert.ok(!(underCell && node.organization.type === 'team'), `team ${node.organization.id} must not descend from a cell`);
maximumDepth = Math.max(maximumDepth, depth);
pending.push(...node.children.map(child => ({ node: child, depth: depth + 1 })));
pending.push(...node.children.map(child => ({ node: child, depth: depth + 1, underCell: underCell || node.organization.type === 'cell' })));
}
assert.ok(maximumDepth >= 3);
assert.ok(new Set(Object.values(summary.byOrganization).map(value => value.direct)).size > 1, 'unequal organization membership');
}
test('generated small/medium/large fixtures follow the hierarchy, retaining team -> cell examples', () => {
for (const dataset of [small, medium, fixture] as OrgChartDataset[]) assertContract(dataset);
const byId = new Map(base.organizations.map(org => [org.id, org]));
const cells = base.organizations.filter(org => org.type === 'cell');
assert.equal(cells.length, companies.length * 2);
for (const cell of cells) assert.equal(byId.get(cell.parentId!)!.type, 'team');
});
test('hierarchy regression check rejects a team below an indirect cell ancestor', () => {
const invalid = structuredClone(base);
const office = invalid.organizations.find(org => org.id === 'hanmac-office-sibling')!;
office.parentId = 'hanmac-cell-0';
invalid.organizations.find(org => org.id === 'hanmac-team-sibling')!.parentId = office.id;
assert.throws(() => assertContract(invalid), /must not descend from a cell/);
});
for (const employeeCount of sizes) {
test(`total ${employeeCount} contains exactly that many unique employees across all seven preset companies`, () => {
const dataset = createOrgChartExampleDataset(base, { scope: 'total', employeeCount });
@@ -248,7 +248,7 @@
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"]) .hm-orgchart-person-name {
color: var(--hm-orgchart-card-color, #071D3A);
font-size: var(--hm-orgchart-person-name-size);
font-weight: 600;
font-weight: 700;
line-height: var(--hm-orgchart-person-name-line-height);
letter-spacing: var(--hm-orgchart-person-name-letter-spacing);
overflow: hidden;
@@ -264,7 +264,7 @@
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"])[data-card-asset-size="representative"] .hm-orgchart-person-attributes { left: 4px; width: 36px; }
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"]) .hm-orgchart-job-title { max-width: 100%; padding: 0 2px; font-size: 8px; line-height: 12px; letter-spacing: -0.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"])[data-card-asset-size="leader"] .hm-orgchart-job-title { font-size: 9px; font-weight: 700; }
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"]):is([data-card-asset-size="employee"], [data-card-layout="compact"]) .hm-orgchart-job-title { font-size: 8.5px; font-weight: 600; }
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"]):is([data-card-asset-size="employee"], [data-card-layout="compact"]) .hm-orgchart-job-title { font-size: 8.5px; font-weight: 500; }
/* Workdesk source: index.css .rank-position/.role-rank and .bg-*.
Preserve the approved SVG geometry and position/name typography. The photo
slot uses the existing reserved width because the approved cards are smaller
@@ -326,7 +326,7 @@
.hm-orgchart-employee:is([data-card-asset], [data-card-layout="compact"]):is([data-card-asset-size="employee"], [data-card-layout="compact"]) .hm-orgchart-job-title {
padding: 0 2px;
font-size: 8.5px;
font-weight: 600;
font-weight: 500;
}
/* Status is retained for assistive reading; the specified two-line card
composition has no third status row. Supplementary status cards are intact. */
@@ -132,7 +132,7 @@
}
/* Workdesk .hw/.hr labels share compact typography; .sw/.ux retain 20px/700. */
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-engine.orgchart .node[data-header-role="group"][data-organization-id^="business-planning-"] .hm-orgchart-organization-name,
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-engine.orgchart .node[data-header-role="group"][data-organization-id^="talent-growth-"] .hm-orgchart-organization-name { font-size: 18px; font-weight: 600; line-height: 16.9px; }
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-engine.orgchart .node[data-header-role="group"][data-organization-id^="talent-growth-"] .hm-orgchart-organization-name { font-size: 18px; font-weight: 700; line-height: 16.9px; }
/* Stacked cells: the vertical spine follows the 16px node gap. */
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-engine.orgchart .hierarchy:has(> .node[data-depth]) > .nodes > .hierarchy::before { top: -16px; height: 16px; }
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-leader { margin: 8px 0 0; }
@@ -182,7 +182,7 @@
gap: 8px;
}
.hm-orgchart-hub-icon { width: 20px; height: 20px; flex: 0 0 20px; stroke: #f8fcff; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.38)); }
.hm-orgchart-hub-label { position: relative; font-size: 21px; font-weight: 750; line-height: 24px; letter-spacing: normal; background: linear-gradient(180deg, #dbeeff, #f8fcff 15.38%, #fff 48.08%, #f4faff 79.33%, #dbeeff); background-clip: text; -webkit-text-fill-color: transparent; filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.5)); }
.hm-orgchart-hub-label { position: relative; font-size: 21px; font-weight: 700; line-height: 24px; letter-spacing: normal; background: linear-gradient(180deg, #dbeeff, #f8fcff 15.38%, #fff 48.08%, #f4faff 79.33%, #dbeeff); background-clip: text; -webkit-text-fill-color: transparent; filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.5)); }
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-stage[data-workdesk-artwork] .hm-orgchart-axis::before,
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-stage[data-workdesk-artwork] .hm-orgchart-hub::after,
.hm-orgchart-composition[data-display-mode="readOnly"] .hm-orgchart-stage[data-workdesk-artwork] .hm-orgchart-forest::before,