diff --git a/Projects/DVDesign/SampleApp/Sources/ContentView.swift b/Projects/DVDesign/SampleApp/Sources/ContentView.swift index 1949a8f..dc61b73 100644 --- a/Projects/DVDesign/SampleApp/Sources/ContentView.swift +++ b/Projects/DVDesign/SampleApp/Sources/ContentView.swift @@ -1,13 +1,24 @@ +// Copyright © 2026 Devault. All rights reserved + import SwiftUI +import DVDesign struct ContentView: View { var body: some View { NavigationSplitView { - List(ComponentSection.allCases, id: \.self) { section in - Section(section.title) { - ForEach(section.components, id: \.self) { component in - NavigationLink(component.name) { - ComponentPlaceholderView(name: component.name, owner: component.owner) + List { + Section("Foundation") { + NavigationLink("Typography") { + TypographyPreviewView() + } + } + + ForEach(ComponentSection.allCases, id: \.self) { section in + Section(section.title) { + ForEach(section.components, id: \.self) { component in + NavigationLink(component.name) { + ComponentPlaceholderView(name: component.name, owner: component.owner) + } } } } diff --git a/Projects/DVDesign/SampleApp/Sources/TypographyPreviewView.swift b/Projects/DVDesign/SampleApp/Sources/TypographyPreviewView.swift new file mode 100644 index 0000000..05ba7fb --- /dev/null +++ b/Projects/DVDesign/SampleApp/Sources/TypographyPreviewView.swift @@ -0,0 +1,58 @@ +// Copyright © 2026 Devault. All rights reserved + +import SwiftUI +import DVDesign + +struct TypographyPreviewView: View { + private let sample = "다람쥐 헌 쳇바퀴에 타고파." + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 32) { + group(title: "Display", tokens: [(.displayBrand, "displayBrand")]) + group(title: "Heading", tokens: [ + (.headingXL, "headingXL"), + (.headingLG, "headingLG"), + ]) + group(title: "Body", tokens: [ + (.bodyXL, "bodyXL"), + (.bodyLG, "bodyLG"), + (.bodyMD, "bodyMD"), + ]) + group(title: "Caption", tokens: [ + (.captionLG, "captionLG"), + (.captionMDSemibold, "captionMDSemibold"), + (.captionMDRegular, "captionMDRegular"), + ]) + } + .padding(24) + .frame(maxWidth: .infinity, alignment: .leading) + } + .navigationTitle("Typography") + } + + @ViewBuilder + private func group(title: String, tokens: [(DVFont, String)]) -> some View { + VStack(alignment: .leading, spacing: 16) { + Text(title) + .font(.title2) + .fontWeight(.bold) + ForEach(tokens, id: \.1) { token, name in + row(token: token, name: name) + } + } + } + + private func row(token: DVFont, name: String) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(name) + .font(.caption) + .foregroundStyle(.secondary) + Text(sample) + .dvFont(token) + Text("\(Int(token.size))px / \(Int(token.lineHeightRatio * 100))%") + .font(.caption2) + .foregroundStyle(.tertiary) + } + } +} diff --git a/Projects/DVDesign/Sources/Foundations/Fonts/DVFont.swift b/Projects/DVDesign/Sources/Foundations/Fonts/DVFont.swift new file mode 100644 index 0000000..2b8b5cd --- /dev/null +++ b/Projects/DVDesign/Sources/Foundations/Fonts/DVFont.swift @@ -0,0 +1,74 @@ +// Copyright © 2026 Devault. All rights reserved + +import SwiftUI + +public enum DVFont: CaseIterable { + case displayBrand + + case headingXL + case headingLG + + case bodyXL + case bodyLG + case bodyMD + + case captionLG + case captionMDSemibold + case captionMDRegular + + public var size: CGFloat { + switch self { + case .displayBrand: return 28 + case .headingXL: return 22 + case .headingLG: return 18 + case .bodyXL: return 16 + case .bodyLG: return 15 + case .bodyMD: return 13 + case .captionLG: return 12 + case .captionMDSemibold: return 11 + case .captionMDRegular: return 11 + } + } + + public var weight: Font.Weight { + switch self { + case .displayBrand: + return .bold + case .headingXL, .headingLG, .captionLG, .captionMDSemibold: + return .semibold + case .bodyXL, .bodyLG, .bodyMD: + return .medium + case .captionMDRegular: + return .regular + } + } + + /// 디자인 스펙에 정의된 line-height 비율 (예: 1.14 == 114%) + public var lineHeightRatio: CGFloat { + switch self { + case .displayBrand: return 1.14 + case .headingXL: return 1.36 + case .headingLG: return 1.11 + case .bodyXL: return 1.20 + case .bodyLG: return 1.06 + case .bodyMD: return 1.23 + case .captionLG: return 1.19 + case .captionMDSemibold: return 1.18 + case .captionMDRegular: return 1.18 + } + } + + /// 실제 line-height 값 (size × ratio, 단위: pt) + public var lineHeight: CGFloat { + size * lineHeightRatio + } + + /// 멀티라인 텍스트가 `lineHeight`에 맞도록 SwiftUI `.lineSpacing`에 넘길 추가 간격 + public var lineSpacing: CGFloat { + max(lineHeight - size, 0) + } + + public var font: Font { + .system(size: size, weight: weight) + } +} diff --git a/Projects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swift b/Projects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swift new file mode 100644 index 0000000..d6ab05a --- /dev/null +++ b/Projects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swift @@ -0,0 +1,9 @@ +// Copyright © 2026 Devault. All rights reserved + +import SwiftUI + +public extension Font { + static func dv(_ token: DVFont) -> Font { + token.font + } +} diff --git a/Projects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swift b/Projects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swift new file mode 100644 index 0000000..70d3536 --- /dev/null +++ b/Projects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swift @@ -0,0 +1,14 @@ +// Copyright © 2026 Devault. All rights reserved + +import SwiftUI + +public extension View { + /// `DVFont` 토큰을 line-height까지 함께 적용한다. + /// 디자인 시스템과 정렬된 타이포그래피를 위해 `.font(...)` 대신 이 modifier를 사용한다. + func dvFont(_ token: DVFont) -> some View { + self + .font(token.font) + .lineSpacing(token.lineSpacing) + .padding(.vertical, token.lineSpacing / 2) + } +} diff --git a/Projects/DVDesign/Sources/Foundations/PlaceholderC.swift b/Projects/DVDesign/Sources/Foundations/PlaceholderC.swift deleted file mode 100644 index f2ef082..0000000 --- a/Projects/DVDesign/Sources/Foundations/PlaceholderC.swift +++ /dev/null @@ -1 +0,0 @@ -// This file exists to ensure the directory is tracked by Xcode.