From 48db3e7c0dbca1a686b556ea13b66c23a9f40a1e Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 13 May 2026 12:38:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Component=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20flat=20ShapeStyle/CGFloat=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=85=B8=EC=B6=9C=20#DS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tools/TokenGenerator.swift 의 Component 처리 로직을 nested enum → flat path 로 교체 · walkComponent → walkComponentFlat (pathPrefix 누적해서 camelCase 단일 이름 생성) · Component.button.primary.background.default → buttonPrimaryBackgroundDefault · Component.button.radius → buttonRadius - ShapeStyle+.swift 끝 섹션에 Component 색상 25종 추가 (Color.buttonPrimaryBackgroundDefault 등) - CGFloat+Component+.swift 신규 — Component 숫자 토큰 (buttonRadius) - 옛 ComponentToken nested enum 폐기 (ComponentToken.swift 삭제) · develop 전체에서 ComponentToken 직접 사용처가 ComponentToken.swift 외 0건 확인 후 폐기 사용 예 - .background(.buttonPrimaryBackgroundDefault) - .foregroundStyle(.buttonPrimaryTextDefault) - .clipShape(RoundedRectangle(cornerRadius: .buttonRadius)) --- .../Sources/Color/ShapeStyle+.swift | 27 +++++++ .../CGFloat/CGFloat+Component+.swift | 10 +++ .../Sources/UI/Token/ComponentToken.swift | 78 ------------------- Tools/TokenGenerator.swift | 70 ++++++++++++----- 4 files changed, 89 insertions(+), 96 deletions(-) create mode 100644 Projects/Shared/DesignSystem/Sources/Extension/CGFloat/CGFloat+Component+.swift delete mode 100644 Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift diff --git a/Projects/Shared/DesignSystem/Sources/Color/ShapeStyle+.swift b/Projects/Shared/DesignSystem/Sources/Color/ShapeStyle+.swift index 492bc3a..cb0d4b7 100644 --- a/Projects/Shared/DesignSystem/Sources/Color/ShapeStyle+.swift +++ b/Projects/Shared/DesignSystem/Sources/Color/ShapeStyle+.swift @@ -91,4 +91,31 @@ public extension ShapeStyle where Self == Color { static var statusError: Color { .init(hex: "C92D33") } static var statusWarningAlpha: Color { .init(hex: "FFB400", alpha: 0.4) } static var statusWarning: Color { .init(hex: "FFB400") } + // MARK: - Component + static var bedgeFilledBackgroundDefault: Color { .beige600 } + static var bedgeFilledBackgroundInverse: Color { .primary500 } + static var bedgeFilledTextDefault: Color { .primary500 } + static var bedgeFilledTextInverse: Color { .beige50 } + static var bedgeOutlineBackround: Color { .beige50 } + static var bedgeOutlineBorder: Color { .primary100 } + static var bedgeOutlineText: Color { .primary500 } + static var buttonPrimaryBackgroundDefault: Color { .primary500 } + static var buttonPrimaryBackgroundDisabled: Color { .primary200 } + static var buttonPrimaryBackgroundPressed: Color { .primary800 } + static var buttonPrimaryTextDefault: Color { .beige50 } + static var buttonSecondaryBackgroundDefault: Color { .beige300 } + static var buttonSecondaryBackgroundPressed: Color { .beige400 } + static var buttonSecondaryBorderDefault: Color { .beige600 } + static var buttonSecondaryBorderPressed: Color { .secondary500 } + static var buttonSecondaryTextDefault: Color { .neutral600 } + static var buttonSecondaryTextDisabled: Color { .neutral300 } + static var inputBorderActive: Color { .beige700 } + static var inputBorderDefault: Color { .beige600 } + static var inputBorderError: Color { .borderError } + static var inputSurfaceDefault: Color { .beige50 } + static var inputSurfaceDisabled: Color { .beige300 } + static var inputTextActive: Color { .neutral500 } + static var inputTextDefault: Color { .neutral300 } + static var inputTextError: Color { .statusError } + } diff --git a/Projects/Shared/DesignSystem/Sources/Extension/CGFloat/CGFloat+Component+.swift b/Projects/Shared/DesignSystem/Sources/Extension/CGFloat/CGFloat+Component+.swift new file mode 100644 index 0000000..663eae5 --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/Extension/CGFloat/CGFloat+Component+.swift @@ -0,0 +1,10 @@ +// AUTO-GENERATED by Tools/TokenGenerator.swift — DO NOT EDIT +// Source: Projects/Shared/DesignSystem/Resources/Mode 1.tokens.json + +import CoreGraphics + +public extension CGFloat { + + // MARK: - Component + static let buttonRadius: CGFloat = .`default` +} diff --git a/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift b/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift deleted file mode 100644 index 6a3e717..0000000 --- a/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift +++ /dev/null @@ -1,78 +0,0 @@ -// AUTO-GENERATED by Tools/TokenGenerator.swift — DO NOT EDIT -// Source: Projects/Shared/DesignSystem/Resources/Mode 1.tokens.json - -import SwiftUI - -public enum ComponentToken { - public enum Bedge { - public enum Filled { - public enum Background { - public static var `default`: Color { .beige600 } - public static var inverse: Color { .primary500 } - } - - public enum Text { - public static var `default`: Color { .primary500 } - public static var inverse: Color { .beige50 } - } - } - - public enum Outline { - public static var backround: Color { .beige50 } - public static var border: Color { .primary100 } - public static var text: Color { .primary500 } - } - } - - public enum Button { - public static var radius: CGFloat { .`default` } - - public enum Primary { - public enum Background { - public static var `default`: Color { .primary500 } - public static var disabled: Color { .primary200 } - public static var pressed: Color { .primary800 } - } - - public enum Text { - public static var `default`: Color { .beige50 } - } - } - - public enum Secondary { - public enum Background { - public static var `default`: Color { .beige300 } - public static var pressed: Color { .beige400 } - } - - public enum Border { - public static var `default`: Color { .beige600 } - public static var pressed: Color { .secondary500 } - } - - public enum Text { - public static var `default`: Color { .neutral600 } - public static var disabled: Color { .neutral300 } - } - } - } - - public enum Input { - public enum Border { - public static var active: Color { .beige700 } - public static var `default`: Color { .beige600 } - public static var error: Color { .borderError } - } - - public enum Surface { - public static var `default`: Color { .beige50 } - public static var disabled: Color { .beige300 } - } - - public enum Text { - public static var active: Color { .neutral500 } - public static var `default`: Color { .neutral300 } - public static var error: Color { .statusError } - } - } -} diff --git a/Tools/TokenGenerator.swift b/Tools/TokenGenerator.swift index 5b5488f..1dc7ac3 100644 --- a/Tools/TokenGenerator.swift +++ b/Tools/TokenGenerator.swift @@ -16,7 +16,8 @@ let colorOut = "\(sourcesDir)/Color/ShapeStyle+.swift" let cgfloatDir = "\(sourcesDir)/Extension/CGFloat" let radiusOut = "\(cgfloatDir)/CGFloat+Radius+.swift" let spacingOut = "\(cgfloatDir)/CGFloat+Spacing+.swift" -let componentOut = "\(sourcesDir)/UI/Token/ComponentToken.swift" +let componentOut = "\(sourcesDir)/UI/Token/ComponentToken.swift" // legacy nested file (deleted at end) +let componentNumberOut = "\(cgfloatDir)/CGFloat+Component+.swift" try? FileManager.default.createDirectory(atPath: "\(sourcesDir)/UI/Token", withIntermediateDirectories: true) try? FileManager.default.createDirectory(atPath: cgfloatDir, withIntermediateDirectories: true) @@ -124,35 +125,47 @@ func resolveComponentNumber(_ node: [String: Any]) -> String? { return nil } -func walkComponent(_ node: [String: Any], indent: String, out: inout [String]) { +// Component subtree 를 flat path 로 풀어 ShapeStyle / CGFloat 확장에 직접 추가한다. +// Component.button.primary.background.default → buttonPrimaryBackgroundDefault +// Component.button.radius → buttonRadius +func walkComponentFlat( + _ node: [String: Any], + pathPrefix: [String], + colorLines: inout [String], + numberLines: inout [String] +) { let keys = node.keys.sorted() - // 리프(컬러/숫자) 먼저, 그 다음 그룹(중첩 enum) let leafKeys = keys.filter { (node[$0] as? [String: Any])?["$type"] != nil } let groupKeys = keys.filter { (node[$0] as? [String: Any])?["$type"] == nil } for key in leafKeys { guard let child = node[key] as? [String: Any], let type = child["$type"] as? String else { continue } + let propName = flatPropertyName(pathPrefix + [key]) switch type { case "color": if let expr = resolveComponentColor(child) { - out.append("\(indent)public static var \(swiftKey(key)): Color { \(expr) }") + colorLines.append(" static var \(propName): Color { \(expr) }") } case "number": if let expr = resolveComponentNumber(child) { - out.append("\(indent)public static var \(swiftKey(key)): CGFloat { \(expr) }") + numberLines.append(" static let \(propName): CGFloat = \(expr)") } default: continue } } - for (i, key) in groupKeys.enumerated() { + for key in groupKeys { guard let child = node[key] as? [String: Any] else { continue } - if i == 0, !leafKeys.isEmpty { out.append("") } - if i > 0 { out.append("") } - out.append("\(indent)public enum \(capitalizeFirst(key)) {") - walkComponent(child, indent: indent + " ", out: &out) - out.append("\(indent)}") + walkComponentFlat(child, pathPrefix: pathPrefix + [key], colorLines: &colorLines, numberLines: &numberLines) } } +// ["button", "primary", "background", "default"] → "buttonPrimaryBackgroundDefault" +func flatPropertyName(_ segs: [String]) -> String { + guard let first = segs.first else { return "" } + let head = first.prefix(1).lowercased() + first.dropFirst() + let tail = segs.dropFirst().map(capitalizeFirst).joined() + return swiftKey(head + tail) +} + func capitalizeFirst(_ s: String) -> String { s.prefix(1).uppercased() + s.dropFirst() } @@ -251,6 +264,17 @@ if let status = semantic["status"] as? [String: Any] { } } +// Component colors — flat ShapeStyle 확장에 직접 합쳐 ComponentToken 중첩 enum 을 폐기. +let component = json["Component"] as! [String: Any] +var componentColorLines: [String] = [] +var componentNumberLines: [String] = [] +walkComponentFlat(component, pathPrefix: [], colorLines: &componentColorLines, numberLines: &componentNumberLines) +if !componentColorLines.isEmpty { + lines.append(" // MARK: - Component") + lines.append(contentsOf: componentColorLines) + lines.append("") +} + lines.append("}") lines.append("") try writeFile(colorOut, lines.joined(separator: "\n")) @@ -286,13 +310,23 @@ sLines.append("}") sLines.append("") try writeFile(spacingOut, sLines.joined(separator: "\n")) -// MARK: - Component +// MARK: - Component (numbers) -let component = json["Component"] as! [String: Any] -var compLines: [String] = [header, "", "import SwiftUI", "", "public enum ComponentToken {"] -walkComponent(component, indent: " ", out: &compLines) -compLines.append("}") -compLines.append("") -try writeFile(componentOut, compLines.joined(separator: "\n")) +// 색상은 위에서 ShapeStyle+.swift 에 이미 추가됨. 숫자만 CGFloat 확장으로 별도 출력. + +if !componentNumberLines.isEmpty { + var cLines: [String] = [header, "", "import CoreGraphics", "", "public extension CGFloat {", ""] + cLines.append(" // MARK: - Component") + cLines.append(contentsOf: componentNumberLines) + cLines.append("}") + cLines.append("") + try writeFile(componentNumberOut, cLines.joined(separator: "\n")) +} + +// 옛 nested ComponentToken.swift 폐기: 더 이상 생성하지 않고 잔재 파일이 있으면 제거. +if FileManager.default.fileExists(atPath: componentOut) { + try FileManager.default.removeItem(atPath: componentOut) + print("[token-gen] removed legacy \(componentOut)") +} print("[token-gen] done.") From d0f986a497cfda27fe0cdbdd3752b84cbc62e29d Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 13 May 2026 12:49:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20ComponentToken=20nested=20enum=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=20=E2=80=94=20flat=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=EB=A5=BC=20forwarding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #28 의 nested 폐기 결정을 번복. flat + nested 둘 다 유지하되, nested 가 flat 정의를 forwarding 하는 형태로 source-of-truth 를 단일화. - TokenGenerator.swift 에 walkComponentNested 추가 · 색상: public static var `default`: Color { .buttonPrimaryBackgroundDefault } · 숫자: public static var radius: CGFloat { .buttonRadius } - ComponentToken.swift 재생성 (옛 inline hex/brand alias 대신 flat 확장 참조) - 토큰 변경 시 flat 정의만 바뀌면 nested 도 자동 동기화 두 가지 사용 패턴 모두 가능 - .background(.buttonPrimaryBackgroundDefault) - .background(ComponentToken.Button.Primary.Background.default) --- .../Sources/UI/Token/ComponentToken.swift | 78 +++++++++++++++++++ Tools/TokenGenerator.swift | 47 +++++++++-- 2 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift diff --git a/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift b/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift new file mode 100644 index 0000000..6b2e567 --- /dev/null +++ b/Projects/Shared/DesignSystem/Sources/UI/Token/ComponentToken.swift @@ -0,0 +1,78 @@ +// AUTO-GENERATED by Tools/TokenGenerator.swift — DO NOT EDIT +// Source: Projects/Shared/DesignSystem/Resources/Mode 1.tokens.json + +import SwiftUI + +public enum ComponentToken { + public enum Bedge { + public enum Filled { + public enum Background { + public static var `default`: Color { .bedgeFilledBackgroundDefault } + public static var inverse: Color { .bedgeFilledBackgroundInverse } + } + + public enum Text { + public static var `default`: Color { .bedgeFilledTextDefault } + public static var inverse: Color { .bedgeFilledTextInverse } + } + } + + public enum Outline { + public static var backround: Color { .bedgeOutlineBackround } + public static var border: Color { .bedgeOutlineBorder } + public static var text: Color { .bedgeOutlineText } + } + } + + public enum Button { + public static var radius: CGFloat { .buttonRadius } + + public enum Primary { + public enum Background { + public static var `default`: Color { .buttonPrimaryBackgroundDefault } + public static var disabled: Color { .buttonPrimaryBackgroundDisabled } + public static var pressed: Color { .buttonPrimaryBackgroundPressed } + } + + public enum Text { + public static var `default`: Color { .buttonPrimaryTextDefault } + } + } + + public enum Secondary { + public enum Background { + public static var `default`: Color { .buttonSecondaryBackgroundDefault } + public static var pressed: Color { .buttonSecondaryBackgroundPressed } + } + + public enum Border { + public static var `default`: Color { .buttonSecondaryBorderDefault } + public static var pressed: Color { .buttonSecondaryBorderPressed } + } + + public enum Text { + public static var `default`: Color { .buttonSecondaryTextDefault } + public static var disabled: Color { .buttonSecondaryTextDisabled } + } + } + } + + public enum Input { + public enum Border { + public static var active: Color { .inputBorderActive } + public static var `default`: Color { .inputBorderDefault } + public static var error: Color { .inputBorderError } + } + + public enum Surface { + public static var `default`: Color { .inputSurfaceDefault } + public static var disabled: Color { .inputSurfaceDisabled } + } + + public enum Text { + public static var active: Color { .inputTextActive } + public static var `default`: Color { .inputTextDefault } + public static var error: Color { .inputTextError } + } + } +} diff --git a/Tools/TokenGenerator.swift b/Tools/TokenGenerator.swift index 1dc7ac3..a73f300 100644 --- a/Tools/TokenGenerator.swift +++ b/Tools/TokenGenerator.swift @@ -158,6 +158,39 @@ func walkComponentFlat( } } +// Component subtree 의 nested ComponentToken enum. 값은 flat 정의를 forwarding 하므로 +// source of truth 는 항상 ShapeStyle / CGFloat 확장 한 곳. +// public static var `default`: Color { .buttonPrimaryBackgroundDefault } +func walkComponentNested( + _ node: [String: Any], + pathPrefix: [String], + indent: String, + out: inout [String] +) { + let keys = node.keys.sorted() + let leafKeys = keys.filter { (node[$0] as? [String: Any])?["$type"] != nil } + let groupKeys = keys.filter { (node[$0] as? [String: Any])?["$type"] == nil } + for key in leafKeys { + guard let child = node[key] as? [String: Any], let type = child["$type"] as? String else { continue } + let flatName = flatPropertyName(pathPrefix + [key]) + switch type { + case "color": + out.append("\(indent)public static var \(swiftKey(key)): Color { .\(flatName) }") + case "number": + out.append("\(indent)public static var \(swiftKey(key)): CGFloat { .\(flatName) }") + default: continue + } + } + for (i, key) in groupKeys.enumerated() { + guard let child = node[key] as? [String: Any] else { continue } + if i == 0, !leafKeys.isEmpty { out.append("") } + if i > 0 { out.append("") } + out.append("\(indent)public enum \(capitalizeFirst(key)) {") + walkComponentNested(child, pathPrefix: pathPrefix + [key], indent: indent + " ", out: &out) + out.append("\(indent)}") + } +} + // ["button", "primary", "background", "default"] → "buttonPrimaryBackgroundDefault" func flatPropertyName(_ segs: [String]) -> String { guard let first = segs.first else { return "" } @@ -323,10 +356,14 @@ if !componentNumberLines.isEmpty { try writeFile(componentNumberOut, cLines.joined(separator: "\n")) } -// 옛 nested ComponentToken.swift 폐기: 더 이상 생성하지 않고 잔재 파일이 있으면 제거. -if FileManager.default.fileExists(atPath: componentOut) { - try FileManager.default.removeItem(atPath: componentOut) - print("[token-gen] removed legacy \(componentOut)") -} +// MARK: - Component (nested ComponentToken) + +// flat ShapeStyle / CGFloat 확장을 forwarding 하는 구조적 접근용 enum. +// 그룹 단위 캡처/자동완성 탐색에 사용. 값의 source of truth 는 flat 정의 한 곳. +var ctLines: [String] = [header, "", "import SwiftUI", "", "public enum ComponentToken {"] +walkComponentNested(component, pathPrefix: [], indent: " ", out: &ctLines) +ctLines.append("}") +ctLines.append("") +try writeFile(componentOut, ctLines.joined(separator: "\n")) print("[token-gen] done.")