From 80583eb4ca39fb871f24a19c06cdaef1ea4cf5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 12 Oct 2026 17:59:20 +0900 Subject: [PATCH] feat(org-chart): add P5-0 company color presets --- .../src/company-color-presets.test.ts | 44 ++++++++++++++ .../org-chart/src/company-color-presets.ts | 57 +++++++++++++++++++ packages/org-chart/src/index.ts | 2 + 3 files changed, 103 insertions(+) create mode 100644 packages/org-chart/src/company-color-presets.test.ts create mode 100644 packages/org-chart/src/company-color-presets.ts diff --git a/packages/org-chart/src/company-color-presets.test.ts b/packages/org-chart/src/company-color-presets.test.ts new file mode 100644 index 0000000..3274c6b --- /dev/null +++ b/packages/org-chart/src/company-color-presets.test.ts @@ -0,0 +1,44 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { companyColorPresets, getCompanyColorPreset } from './index.js'; +import type { CompanyColorPreset, CompanyId } from './index.js'; + +// Independent acceptance values from the P5-0 request, including the secondary order. +const expected: ReadonlyArray = [ + { companyId: 'hanmac', companyName: '한맥기술', primary: '#CB2E28', secondary: ['#1D4289'] }, + { companyId: 'saman', companyName: '삼안', primary: '#F08300', secondary: ['#6D6C6C'] }, + { companyId: 'jangheon-industry', companyName: '장헌산업', primary: '#4580A6', secondary: ['#92BED4'] }, + { companyId: 'jangheon', companyName: '장헌', primary: '#1D418D', secondary: ['#AE232A'] }, + { companyId: 'ptc', companyName: '피티씨', primary: '#84338E', secondary: ['#F7AF00', '#0066B5'] }, + { companyId: 'halla', companyName: '한라산업개발', primary: '#0068B7', secondary: ['#231815'] }, + { companyId: 'baron', companyName: '바론컨설턴트', primary: '#0C367F', secondary: [] }, +]; + +test('the public registry contains exactly the seven existing company IDs', () => { + assert.deepEqual(Object.keys(companyColorPresets).sort(), expected.map(preset => preset.companyId).sort()); +}); + +for (const preset of expected) { + test(`${preset.companyName}: public lookup preserves the approved ID, name and exact colors`, () => { + assert.deepEqual(getCompanyColorPreset(preset.companyId), preset); + assert.equal(getCompanyColorPreset(preset.companyId), companyColorPresets[preset.companyId]); + }); +} + +test('unknown IDs and inherited property names never select a company or fallback color', () => { + for (const id of ['', 'unknown', 'hanmaek', 'HANMAC', '__proto__', 'constructor', 'toString']) { + assert.equal(getCompanyColorPreset(id), undefined); + } +}); + +test('shared preset values cannot be mutated by HTML or React consumers', () => { + assert.ok(Object.isFrozen(companyColorPresets)); + for (const { companyId } of expected) { + const preset = getCompanyColorPreset(companyId); + assert.ok(Object.isFrozen(preset)); + assert.ok(Object.isFrozen(preset.secondary)); + assert.equal(Reflect.set(preset, 'primary', '#000000'), false); + assert.equal(Reflect.set(preset.secondary, '0', '#000000'), false); + assert.deepEqual(getCompanyColorPreset(companyId), expected.find(item => item.companyId === companyId)); + } +}); diff --git a/packages/org-chart/src/company-color-presets.ts b/packages/org-chart/src/company-color-presets.ts new file mode 100644 index 0000000..7e27fb3 --- /dev/null +++ b/packages/org-chart/src/company-color-presets.ts @@ -0,0 +1,57 @@ +/** Company colors reserved for the future Auto Layout view. + * The fixed Workdesk composition continues to use core/style-presets.ts. + * These values do not apply CSS, change rendering or depend on HM Theme. */ +export interface CompanyColorPreset { + readonly companyId: string; + readonly companyName: string; + readonly primary: string; + readonly secondary: readonly string[]; +} + +function freezePreset(preset: CompanyColorPreset): CompanyColorPreset { + return Object.freeze({ ...preset, secondary: Object.freeze([...preset.secondary]) }); +} + +/** IDs match the existing affiliation IDs. Only user-approved colors belong here. */ +export const companyColorPresets = Object.freeze({ + hanmac: freezePreset({ + companyId: 'hanmac', companyName: '한맥기술', + primary: '#CB2E28', secondary: ['#1D4289'], + }), + saman: freezePreset({ + companyId: 'saman', companyName: '삼안', + primary: '#F08300', secondary: ['#6D6C6C'], + }), + 'jangheon-industry': freezePreset({ + companyId: 'jangheon-industry', companyName: '장헌산업', + primary: '#4580A6', secondary: ['#92BED4'], + }), + jangheon: freezePreset({ + companyId: 'jangheon', companyName: '장헌', + primary: '#1D418D', secondary: ['#AE232A'], + }), + ptc: freezePreset({ + companyId: 'ptc', companyName: '피티씨', + primary: '#84338E', secondary: ['#F7AF00', '#0066B5'], + }), + halla: freezePreset({ + companyId: 'halla', companyName: '한라산업개발', + primary: '#0068B7', secondary: ['#231815'], + }), + baron: freezePreset({ + companyId: 'baron', companyName: '바론컨설턴트', + primary: '#0C367F', secondary: [], + }), +}); + +export type CompanyId = keyof typeof companyColorPresets; + +/** HTML/React share this lookup. Unknown IDs return undefined without a fallback. + * @example const { primary, secondary } = getCompanyColorPreset('hanmac'); */ +export function getCompanyColorPreset(companyId: CompanyId): CompanyColorPreset; +export function getCompanyColorPreset(companyId: string): CompanyColorPreset | undefined; +export function getCompanyColorPreset(companyId: string): CompanyColorPreset | undefined { + return Object.hasOwn(companyColorPresets, companyId) + ? companyColorPresets[companyId as CompanyId] + : undefined; +} diff --git a/packages/org-chart/src/index.ts b/packages/org-chart/src/index.ts index b56c539..cebfae6 100644 --- a/packages/org-chart/src/index.ts +++ b/packages/org-chart/src/index.ts @@ -1,4 +1,6 @@ export * from './data/index.js'; +export { companyColorPresets, getCompanyColorPreset } from './company-color-presets.js'; +export type { CompanyColorPreset, CompanyId } from './company-color-presets.js'; export { createOrgChart } from './core/index.js'; export type { OrgChartDisplayMode, OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './core/types.js'; export { createOrgChartTree, createPositionSummary, createAffiliationSummary, createStatusSummary } from './components.js';