Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Source-Available License 1.0을 따른다. compatibility canary는 비공개·
때만 만든다 (명세 §8):
`@devslab/dds-tokens` (✅ 2026-08-13) → `dds-css` v1 인벤토리 (Core 6종 ✅ 2026-08-13, +13종 ✅ 2026-08-15) →
AskLinq 적용 → `dds-icons` (✅ 2026-08-15) → `dds-solid` + `site-kit` → 안드로이드 네이티브 값·테마·모션
(0.6.0, 2026-11-12, Maven Central — 테마·모션·부품·아이콘). 0.7.0(2026-11-13, Maven Central)에서 화면 틀과 `dds-compose-testing` 꾸러미가 생겼다(D-052). 0.8.0(2026-11-13)에서 `DdsSegmentedControl`이 생겼고, 0.8.1에서 어두운 화면의 고른 칸이 밝아졌고(`bg.raised`, D-054), 0.9.0에서 고른 면이 미끄러져 옮겨 가고(D-055), 0.9.1에서 앱이 그 속도를 정할 수 있다. RN·Ionic 패키지는 그 제품이 생길 때.
(0.6.0, 2026-11-12, Maven Central — 테마·모션·부품·아이콘). 0.7.0(2026-11-13, Maven Central)에서 화면 틀과 `dds-compose-testing` 꾸러미가 생겼다(D-052). 0.8.0(2026-11-13)에서 `DdsSegmentedControl`이 생겼고, 0.8.1에서 어두운 화면의 고른 칸이 밝아졌고(`bg.raised`, D-054), 0.9.0에서 고른 면이 미끄러져 옮겨 가고(D-055), 0.9.1에서 앱이 그 속도를, 0.9.2에서 `DdsTheme`에 자기 글꼴을 넘길 수 있다. RN·Ionic 패키지는 그 제품이 생길 때.

## 입력·선택·공유 달력 확장

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Priorities live in [`docs/backlog.md`](docs/backlog.md). Packages are created
only when a consumer exists (spec §8):
`@devslab/dds-tokens` (✅ 2026-08-13) → `dds-css` v1 inventory (Core 6 ✅ 2026-08-13, +13 ✅ 2026-08-15) →
AskLinq adoption → `dds-icons` (✅ 2026-08-15) → `dds-solid` + `site-kit` → native Android values, theme and motion
(0.6.0, 2026-11-12, on Maven Central — theme, motion, components, icons). 0.7.0 (2026-11-13, on Maven Central) adds the screen frame and the `dds-compose-testing` artifact (D-052). 0.8.0 (2026-11-13) adds `DdsSegmentedControl`, 0.8.1 its lighter chosen face in dark (`bg.raised`, D-054), 0.9.0 makes that face slide between choices (D-055), and 0.9.1 lets an app set the slide speed. RN and Ionic packages only when such a product exists.
(0.6.0, 2026-11-12, on Maven Central — theme, motion, components, icons). 0.7.0 (2026-11-13, on Maven Central) adds the screen frame and the `dds-compose-testing` artifact (D-052). 0.8.0 (2026-11-13) adds `DdsSegmentedControl`, 0.8.1 its lighter chosen face in dark (`bg.raised`, D-054), 0.9.0 makes that face slide between choices (D-055), 0.9.1 lets an app set the slide speed, and 0.9.2 lets an app pass its own typeface to `DdsTheme`. RN and Ionic packages only when such a product exists.

## Input, choice and shared calendar additions

Expand Down
8 changes: 8 additions & 0 deletions android/CHANGELOG.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
`kr.devslab:dds-android-tokens`, `kr.devslab:dds-compose`, 그리고 0.7.0부터 `kr.devslab:dds-compose-testing`은 한 버전을 쓰고, 웹 패키지와는
버전이 따로다(D-048). 릴리스는 `android-v<버전>` 태그로 낸다.

## 0.9.2 — 2026-11-13

**새로 생긴 것**

- **`DdsTheme(fontFamily = …)`** — 앱이 자기 글꼴을 넘기면 DDS 부품과 Material 글자가 모두 그 글꼴로 그려진다.
기본값은 `DdsSans`(Pretendard)로 지금과 같아서, 넘기지 않은 앱은 한 글자도 바뀌지 않는다. 크기·줄높이·굵기는 DDS
글자 표 그대로이고 글꼴만 바뀐다. 가이드 §4.

## 0.9.1 — 2026-11-13

**새로 생긴 것**
Expand Down
8 changes: 8 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
`kr.devslab:dds-android-tokens`, `kr.devslab:dds-compose` and (from 0.7.0) `kr.devslab:dds-compose-testing` share one version,
separate from the web packages (D-048). Releases are cut by an `android-v<version>` tag.

## 0.9.2 — 2026-11-13

**New**

- **`DdsTheme(fontFamily = …)`** — an app that passes its own family gets DDS components and Material text set in it.
The default is `DdsSans` (Pretendard), as today, so an app that passes nothing changes not one glyph. Only the family
changes; sizes, line heights and weights stay the DDS type scale. Guide §4.

## 0.9.1 — 2026-11-13

**New**
Expand Down
4 changes: 2 additions & 2 deletions android/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ dependencies {

```kotlin
dependencies {
implementation("kr.devslab:dds-compose:0.9.1")
androidTestImplementation("kr.devslab:dds-compose-testing:0.9.1")
implementation("kr.devslab:dds-compose:0.9.2")
androidTestImplementation("kr.devslab:dds-compose-testing:0.9.2")
}
```

Expand Down
4 changes: 2 additions & 2 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ dependencies {

```kotlin
dependencies {
implementation("kr.devslab:dds-compose:0.9.1")
androidTestImplementation("kr.devslab:dds-compose-testing:0.9.1")
implementation("kr.devslab:dds-compose:0.9.2")
androidTestImplementation("kr.devslab:dds-compose-testing:0.9.2")
}
```

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package kr.devslab.dds.compose

import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.font.FontFamily
import kr.devslab.dds.compose.component.DdsButton
import kr.devslab.dds.compose.component.DdsSegment
import kr.devslab.dds.compose.component.DdsSegmentedControl
import kr.devslab.dds.compose.theme.DdsSans
import kr.devslab.dds.compose.theme.DdsTheme
import org.junit.Assert.assertSame
import org.junit.Rule
import org.junit.Test

/**
* The font family an app passes to [DdsTheme] reaches the text DDS components lay out;
* without one, the text is Pretendard as before.
*
* Run on an emulator or a phone (CI has neither):
* ANDROID_SERIAL=<device> ./gradlew :dds-compose:connectedDebugAndroidTest
*/
class ThemeFontOnDeviceTest {
@get:Rule
val rule = createComposeRule()

private fun show(family: FontFamily?) {
val content: @Composable () -> Unit = {
Column {
DdsButton("저장", onClick = {})
DdsSegmentedControl(listOf(DdsSegment(1, "개인"), DdsSegment(2, "비즈니스")), 1, {}, contentDescription = "종류")
}
}
rule.setContent { if (family == null) DdsTheme(content = content) else DdsTheme(fontFamily = family, content = content) }
rule.waitForIdle()
}

/** The family the label was laid out in. */
private fun familyOf(text: String): FontFamily? {
val results = mutableListOf<TextLayoutResult>()
rule.onNodeWithText(text, useUnmergedTree = true).fetchSemanticsNode()
.config[SemanticsActions.GetTextLayoutResult].action!!.invoke(results)
return results.single().layoutInput.style.fontFamily
}

@Test
fun withoutAFamilyTheTextIsPretendard() {
show(null)
assertSame(DdsSans, familyOf("저장"))
assertSame(DdsSans, familyOf("개인"))
}

@Test
fun aFamilyTheAppPassesIsTheOneTheTextIsLaidOutIn() {
val own = FontFamily.Serif
show(own)
assertSame(own, familyOf("저장"))
assertSame(own, familyOf("개인"))
assertSame(own, familyOf("비즈니스"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import kr.devslab.dds.tokens.DdsTouch

Expand All @@ -29,16 +30,24 @@ import kr.devslab.dds.tokens.DdsTouch
* DevsLab cyan on the system's backgrounds
* @param largeText the app-offered large text mode: a stepped-up type scale
* and a larger minimum touch target. It stacks with the system font scale.
* @param fontFamily the family every DDS style, and Material's type, is set in.
* [DdsSans] (Pretendard) by default; an app that bundles its own (another cut
* of Pretendard, a variable font) passes it here. Only the family changes:
* sizes, line heights and weights stay the DDS type scale. The app checks its
* family covers the weights the scale uses (400, 500, 600, 700) and the
* scripts it shows; to use another family for another language, pass a
* different family when that language is on
*/
@Composable
public fun DdsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
brand: DdsBrand = rememberDdsBrand(),
largeText: Boolean = false,
fontFamily: FontFamily = DdsSans,
content: @Composable () -> Unit,
) {
val colors = remember(darkTheme, brand) { ddsColors(darkTheme, brand) }
val typography = remember(largeText) { ddsTypography(largeText) }
val typography = remember(largeText, fontFamily) { ddsTypography(largeText, fontFamily) }
val colorScheme = remember(colors, darkTheme, brand) {
ddsColorScheme(colors, darkTheme, inverseTextBrand = (if (darkTheme) brand.light else brand.dark).textBrand)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public class DdsTypography internal constructor(
public val caption: TextStyle,
)

private fun DdsTypeToken.style(): TextStyle = TextStyle(
fontFamily = DdsSans,
private fun DdsTypeToken.style(family: FontFamily): TextStyle = TextStyle(
fontFamily = family,
fontSize = fontSize.sp,
lineHeight = lineHeight.sp,
fontWeight = FontWeight(fontWeight),
Expand All @@ -77,28 +77,32 @@ private fun DdsTypeToken.style(): TextStyle = TextStyle(
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None),
)

internal fun ddsTypography(largeText: Boolean): DdsTypography =
/**
* The eight styles in [family] ([DdsSans] unless the app passed its own to [DdsTheme]).
* Sizes, line heights and weights are the tokens' whatever the family.
*/
internal fun ddsTypography(largeText: Boolean, family: FontFamily = DdsSans): DdsTypography =
if (largeText) {
DdsTypography(
display = DdsLargeType.display.style(),
title1 = DdsLargeType.title1.style(),
title2 = DdsLargeType.title2.style(),
title3 = DdsLargeType.title3.style(),
body1 = DdsLargeType.body1.style(),
body2 = DdsLargeType.body2.style(),
label = DdsLargeType.label.style(),
caption = DdsLargeType.caption.style(),
display = DdsLargeType.display.style(family),
title1 = DdsLargeType.title1.style(family),
title2 = DdsLargeType.title2.style(family),
title3 = DdsLargeType.title3.style(family),
body1 = DdsLargeType.body1.style(family),
body2 = DdsLargeType.body2.style(family),
label = DdsLargeType.label.style(family),
caption = DdsLargeType.caption.style(family),
)
} else {
DdsTypography(
display = DdsType.display.style(),
title1 = DdsType.title1.style(),
title2 = DdsType.title2.style(),
title3 = DdsType.title3.style(),
body1 = DdsType.body1.style(),
body2 = DdsType.body2.style(),
label = DdsType.label.style(),
caption = DdsType.caption.style(),
display = DdsType.display.style(family),
title1 = DdsType.title1.style(family),
title2 = DdsType.title2.style(family),
title3 = DdsType.title3.style(family),
body1 = DdsType.body1.style(family),
body2 = DdsType.body2.style(family),
label = DdsType.label.style(family),
caption = DdsType.caption.style(family),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kr.devslab.dds.compose.theme

import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test

/** The family an app passes to DdsTheme (0.9.2): the default is unchanged, a passed one is used everywhere. */
class TypographyFamilyTest {
private fun DdsTypography.all(): List<TextStyle> = listOf(display, title1, title2, title3, body1, body2, label, caption)

@Test
fun byDefaultEveryStyleIsPretendard() {
for (large in listOf(false, true)) {
for (style in ddsTypography(large).all()) assertSame(DdsSans, style.fontFamily)
}
}

@Test
fun aPassedFamilyIsUsedByEveryStyle() {
val own = FontFamily.Serif
for (large in listOf(false, true)) {
for (style in ddsTypography(large, own).all()) assertSame(own, style.fontFamily)
}
}

@Test
fun onlyTheFamilyChanges() {
for (large in listOf(false, true)) {
val dds = ddsTypography(large).all()
val own = ddsTypography(large, FontFamily.Serif).all()
for ((a, b) in dds.zip(own)) assertEquals(a.copy(fontFamily = null), b.copy(fontFamily = null))
}
}

@Test
fun materialTypeFollowsTheFamily() {
val own = FontFamily.Serif
val material = materialTypography(ddsTypography(false, own))
for (style in listOf(material.bodyLarge, material.labelLarge, material.titleLarge, material.displayLarge)) {
assertSame(own, style.fontFamily)
}
}
}
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ kotlin.code.style=official
# Android releases are versioned apart from the web packages (docs/decisions.md D-048).
# The release workflow overrides VERSION_NAME from the android-v* tag.
GROUP=kr.devslab
VERSION_NAME=0.9.1
VERSION_NAME=0.9.2
5 changes: 5 additions & 0 deletions docs/mobile-native.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,11 @@ Material3의 `primary`는 채움 면과 글자색을 겸하는데 DDS는 둘을
- 한글은 띄어쓰기에서만 줄을 바꾼다(`"…".keepWords()`) — 웹의 `keep-all`과 같은 결과.
- 단위는 sp다(웹의 px·RN의 pt와 같은 숫자). 시스템 글자 배율을 그대로 따른다.
- 모바일에만 있는 것: 글꼴은 Pretendard 하나(§9), Material3 칸 대응, 큰 글자 모드(§10의 7번).
- **제품이 자기 글꼴을 쓸 수 있다(0.9.2).** `DdsTheme(fontFamily = …)`에 넘기면 DDS 부품과 Material 글자가 모두 그
글꼴로 그려진다. 기본값은 지금처럼 `DdsSans`(Pretendard)이고, 넘기지 않은 앱은 바뀌는 것이 없다. 바뀌는 것은
글꼴뿐이고 크기·줄높이·굵기는 위 표 그대로다. 그 글꼴이 표의 굵기(400, 500, 600, 700)와 화면에 나오는 문자를 갖췄는지는
앱이 확인한다. 언어마다 다른 글꼴을 쓰려면 그 언어일 때 다른 글꼴을 넘긴다. Qooroo가 첫 사용처다(Pretendard Std
Variable).

## 5. 간격·둥글기·높이

Expand Down
6 changes: 6 additions & 0 deletions docs/mobile-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,12 @@ Material3's `primary` is both a fill and a text color, where DDS has `bg.brand`
- Korean breaks only at spaces (`"…".keepWords()`) — the result of the web's `keep-all`.
- Units are sp (the same numbers as the web's px and RN's pt), so the system font scale applies.
- Mobile only: one typeface, Pretendard (§9); the Material3 slot mapping; large text mode (§10, item 7).
- **A product can use its own typeface (0.9.2).** Pass it to `DdsTheme(fontFamily = …)` and DDS
components and Material text are all set in it. The default stays `DdsSans` (Pretendard); an app
that passes nothing sees no change. Only the family changes: sizes, line heights and weights stay
the table above. The app checks its family has the weights the table uses (400, 500, 600, 700)
and the scripts it shows. For another family per language, pass a different family when that
language is on. Qooroo is the first user (Pretendard Std Variable).

## 5. Spacing, radius, heights

Expand Down
Loading