[Compose] Design System 구축하기 - 3

 

[Compose] Design System 구축하기 - 2

[Compose] Design System 구축하기 - 1Custom Design System Module를 구축하기 전에 Compose 내 테마 분석을 먼저 진행해보려고 합니다. Jetpack Compose의 테마는 여러 개의 하위 수준 구성과 관련 API로 이루어져 있

moondev03.tistory.com

이전 글에서는 Compose에서의 Custom DesignSystem에 대해서 정리했습니다.

이번 글에서는 정리한 내용을 바탕으로 간단하게 DesignSystem Module를 작성해보려 합니다.

 

 

ColorSystem

컬러 네이밍 방식에는 크게 3가지의 기본 범주로 나뉜다고 합니다.

  • Definitive naming:
    색상 자체를 설명하는 네이밍 (ex. yellow, green, red, ...)
  • Semantic naming:
    색상의 의도를 설명하는 네이밍 (ex. primary, info, danger, ...)
  • Contextual naming:
    컴포넌트의 특정 유형 또는 범주에 대한 색상을 설명하는 네이밍 (ex. color-text-brand, color-text-icon-default, ...)

많은 디자인 시스템과 CSS 프레임워크는 Semantic Naming 규칙을 사용합니다.

 

하지만 이번에 진행하는 프로젝트에서는 디자이너가 Definitive Naming 규칙으로 피그마에 정의를 해주셨기 때문에 예제는 해당 규칙으로 진행하려고 합니다.

@Immutable
data class DaysColorSystem(
    val black: Color = hexToColor("#1F1F1F"),
    val white: Color = hexToColor("#FFFFFF"),

    val grey500: Color = hexToColor("#454545"),
    val grey400: Color = hexToColor("#5E5E5E"),
    val grey300: Color = hexToColor("#848484"),
    val grey200: Color = hexToColor("#A0A0A0"),
    val grey100: Color = hexToColor("#CAC7C5"),

    val gradientA: List<Color> = listOf(hexToColor("#FF8BAC"), hexToColor("#98C1FF")),

    val red300: Color = hexToColor("#F2597F"),
    val blue300: Color = hexToColor("#408CFF"),

    val darkGreen: Color = hexToColor("#5B6654"),
    val darkPink: Color = hexToColor("#846470"),
    val darkBlue: Color = hexToColor("#606D8F"),

    val lightYellow: Color = hexToColor("#FEFDED"),
    val lightGreen: Color = hexToColor("#F0F6EB"),
    val lightPink: Color = hexToColor("#F6EBEF"),
    val lightBlue: Color = hexToColor("#EBF1F6"),

    val background: Color = hexToColor("#F5F1EE")
)

internal fun hexToColor(hex: String): Color {
    return Color(android.graphics.Color.parseColor(hex))
}

 

private val lightColorScheme = DaysColorSystem()

val Local3DaysColorSystem = staticCompositionLocalOf { lightColorScheme }
val Local3DaysContentColor = compositionLocalOf { lightColorScheme.black }

 

 

TypographySystem

val Pretendard = FontFamily(
    Font(R.font.pretendard_regular, FontWeight.W400),
    Font(R.font.pretendard_medium, FontWeight.W500),
    Font(R.font.pretendard_semi_bold, FontWeight.W600),
    Font(R.font.pretendard_bold, FontWeight.W700),
)

val RobotoSlab = FontFamily(
    Font(R.font.robotoslab_medium, FontWeight.W500),
)

우선은 Weight를 통해 가변 폰트를 사용할 수 있도록 FontFamily를 작성합니다.

 

// 폰트 사이즈를 Dp로 표현하기 위해 사용합니다.
@Immutable
data class DaysTextStyle(
    val fontFamily: FontFamily = Pretendard,
    val fontWeight: FontWeight = FontWeight.Medium,
    val fontSize: Dp = Dp.Unspecified,
    val lineHeight: Dp = Dp.Unspecified,
    val letterSpacing: TextUnit = 0.em,
    val color: Color = Color.Unspecified,
    val textAlign: TextAlign = TextAlign.Start,
) {
    @Composable
    fun toTextStyle() = TextStyle(
        fontFamily = fontFamily,
        fontWeight = fontWeight,
        fontSize = with(LocalDensity.current) { fontSize.toSp() },
        lineHeight = with(LocalDensity.current) { lineHeight.toSp() },
        letterSpacing = letterSpacing,
        color = color,
        textAlign = textAlign,
    )

    companion object {
        @Stable
        val Default = DaysTextStyle()
    }
}

sp를 사용할 때 크기가 변경되는 것을 방지하기 위해 TextStyle에서 dp 값을 사용할 수 있도록 DaysTextStyle를 작성합니다.

 

@Immutable
data class DaysTypography(
    val enMedium16: DaysTextStyle = DaysTextStyle(
        fontFamily = RobotoSlab,
        fontWeight = FontWeight.W500,
        lineHeight = 16.dp,
        fontSize = 16.dp,
        letterSpacing = (-0.01).em
    ),

    val enMedium20: DaysTextStyle = DaysTextStyle(
        fontFamily = RobotoSlab,
        fontWeight = FontWeight.W500,
        lineHeight = 20.dp,
        fontSize = 20.dp,
        letterSpacing = (-0.01).em
    ),

    val medium14: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W500,
        lineHeight = 21.dp,
        fontSize = 14.dp,
    ),

    val medium16: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W500,
        lineHeight = 24.dp,
        fontSize = 16.dp,
    ),

    val medium18: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W500,
        lineHeight = 27.dp,
        fontSize = 18.dp
    ),

    val regular12: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W400,
        lineHeight = 12.dp,
        fontSize = 12.dp,
    ),

    val regular14: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W400,
        lineHeight = 21.dp,
        fontSize = 14.dp
    ),

    val regular15: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W400,
        lineHeight = (22.5).dp,
        fontSize = 15.dp,
    ),

    val semiBold14: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W600,
        lineHeight = 21.dp,
        fontSize = 14.dp,
    ),

    val semiBold18: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W500,
        lineHeight = 27.dp,
        fontSize = 18.dp
    ),

    val semiBold24: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W600,
        lineHeight = 36.dp,
        fontSize = 24.dp,
    ),

    val semiBold28: DaysTextStyle = DaysTextStyle(
        fontFamily = Pretendard,
        fontWeight = FontWeight.W600,
        lineHeight = 42.dp,
        fontSize = 28.dp,
    )
)

이번에는 디자이너가 피그마에 정의해 준 Typography Naming을 참고하여 작성한 DaysTypography 코드입니다.

 

val Local3DaysTypography = staticCompositionLocalOf { DaysTypography() }

 

Theme

우선은 Theme 객체를 생성합니다.

object DaysTheme {
    val colors: DaysColorSystem
        @Composable
        @ReadOnlyComposable
        get() = Local3DaysColorSystem.current

    val typography: DaysTypography
        @Composable
        @ReadOnlyComposable
        get() = Local3DaysTypography.current

    val contentColor: Color
        @Composable
        @ReadOnlyComposable
        get() = Local3DaysContentColor.current
}

 

@Composable
fun DaysTheme(
    theme: DaysTheme = DaysTheme,
    content: @Composable () -> Unit,
) {
    CompositionLocalProvider(
        Local3DaysColorSystem provides theme.colors,
        Local3DaysTypography provides theme.typography,
        Local3DaysContentColor provides theme.contentColor,
    ) {
        ProvideTextStyle(value = theme.typography.medium14.toTextStyle()) {
            content()
        }
    }
}

 

 

마무리

이렇게 Custom Design System을 구축하는 방법에 대해 간단히 정리해 보았습니다.

여러 Android Design System 예제를 살펴보면서 다양한 접근 방식을 확인할 수 있었고, 각기 다른 시스템들이 어떻게 적용되고 있는지를 배울 수 있었습니다.

 

이번 프로젝트에서는 위에서 정리한 Custom Design System을 기초로 삼아, 다른 서비스의 디자인 시스템을 참고하여 저희 프로젝트에 맞게 효과적인 디자인 시스템이 되도록 지속적으로 개선해 나갈 예정입니다. 

 

 


 

 

디자인 시스템, 컬러 네이밍 정하기

컬러는 모든 디자인 시스템의 기반이 되지만, 숙련된 디자이너에게도 네이밍 지정은 어려울 수 있다. 6년 전, 첫 번째 디자인 시스템을 만들고 QuickThoughts라는 모바일 설문 조사 앱을 지원하기 위

jin-na.tistory.com

 

 

GitHub - yourssu/YDS-Android: 유어슈 디자인 시스템 프로덕트

유어슈 디자인 시스템 프로덕트. Contribute to yourssu/YDS-Android development by creating an account on GitHub.

github.com