Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions Projects/DVDesign/SampleApp/Sources/ContentView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions Projects/DVDesign/SampleApp/Sources/TypographyPreviewView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
74 changes: 74 additions & 0 deletions Projects/DVDesign/Sources/Foundations/Fonts/DVFont.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
9 changes: 9 additions & 0 deletions Projects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright © 2026 Devault. All rights reserved

import SwiftUI

public extension Font {
static func dv(_ token: DVFont) -> Font {
token.font
}
}
14 changes: 14 additions & 0 deletions Projects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 0 additions & 1 deletion Projects/DVDesign/Sources/Foundations/PlaceholderC.swift

This file was deleted.