Skip to content

Commit f8dfd8b

Browse files
authored
Create DaxText Composable (#6943)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202857801505092/task/1211621862635506 ### Description Added a new `DaxText` Compose component that follows the DuckDuckGo design system guidelines. This component provides a consistent way to display text in our Compose UI with proper styling and theming support. Key changes: - Created `DaxText` component with support for all typography styles - Added proper text color handling through the theme system - Updated typography definitions with proper letter spacing and font weights - Added a lint rule to enforce using theme colors with DaxText - Enhanced the design system preview app to show both View and Compose implementations ### Steps to test this PR _DaxText Component_ - [x] Open the AppComponents activity and navigate to the Typography tab - [ ] Verify that all text styles render correctly in both light and dark themes - [ ] Check that the Compose implementations match their View counterparts _Lint Rule_ - [ ] Try using `DaxText` with a direct Color value (e.g., `Color.Red`) and verify the lint warning appears - [ ] Verify that using `DuckDuckGoTheme.textColors.primary` doesn't trigger the warning ### UI changes https://github.com/user-attachments/assets/1ba1cbe5-e312-4e13-8ed7-a3d5cc400cd1
1 parent 9bd3cf2 commit f8dfd8b

File tree

20 files changed

+2125
-56
lines changed

20 files changed

+2125
-56
lines changed

design-system/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dependencies {
5858
implementation Google.android.material
5959
implementation AndroidX.constraintLayout
6060
implementation AndroidX.core.splashscreen
61+
implementation AndroidX.fragment.ktx
6162
implementation AndroidX.recyclerView
6263
implementation AndroidX.lifecycle.viewModelKtx
6364
// just to get the dagger annotations
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.common.ui.compose.text
18+
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.graphics.Color
23+
import androidx.compose.ui.text.style.TextAlign
24+
import androidx.compose.ui.text.style.TextOverflow
25+
import androidx.compose.ui.tooling.preview.PreviewLightDark
26+
import com.duckduckgo.common.ui.compose.theme.DuckDuckGoTextStyle
27+
import com.duckduckgo.common.ui.compose.theme.DuckDuckGoTheme
28+
import com.duckduckgo.common.ui.compose.theme.asTextStyle
29+
import com.duckduckgo.common.ui.compose.tools.PreviewBox
30+
31+
/**
32+
* Base text component for the DuckDuckGo design system.
33+
*
34+
* @param color The text color. Should use colors from [DuckDuckGoTheme.textColors] for consistency
35+
* with the design system (e.g., [DuckDuckGoTheme.textColors.primary], [DuckDuckGoTheme.textColors.secondary]).
36+
* A lint rule will warn if arbitrary colors are used.
37+
*
38+
* Asana Task: https://app.asana.com/1/137249556945/project/1202857801505092/task/1211634956773768
39+
* Figma reference: https://www.figma.com/design/jHLwh4erLbNc2YeobQpGFt/Design-System-Guidelines?node-id=1313-19967
40+
*/
41+
@Composable
42+
fun DaxText(
43+
text: String,
44+
modifier: Modifier = Modifier,
45+
style: DuckDuckGoTextStyle = DuckDuckGoTheme.typography.body1,
46+
color: Color = DuckDuckGoTheme.textColors.primary,
47+
textAlign: TextAlign? = null,
48+
overflow: TextOverflow = TextOverflow.Ellipsis,
49+
maxLines: Int = Int.MAX_VALUE,
50+
) {
51+
Text(
52+
text = text,
53+
color = color,
54+
style = style.asTextStyle,
55+
textAlign = textAlign,
56+
overflow = overflow,
57+
maxLines = maxLines,
58+
modifier = modifier,
59+
)
60+
}
61+
62+
@PreviewLightDark
63+
@Composable
64+
private fun DaxTextTitlePreview() {
65+
DaxTextPreviewBox {
66+
DaxText(text = "Title Text", style = DuckDuckGoTheme.typography.title)
67+
}
68+
}
69+
70+
@PreviewLightDark
71+
@Composable
72+
private fun DaxTextH1Preview() {
73+
DaxTextPreviewBox {
74+
DaxText(text = "H1 Text", style = DuckDuckGoTheme.typography.h1)
75+
}
76+
}
77+
78+
@PreviewLightDark
79+
@Composable
80+
private fun DaxTextH2Preview() {
81+
DaxTextPreviewBox {
82+
DaxText(text = "H2 Text", style = DuckDuckGoTheme.typography.h2)
83+
}
84+
}
85+
86+
@PreviewLightDark
87+
@Composable
88+
private fun DaxTextH3Preview() {
89+
DaxTextPreviewBox {
90+
DaxText(text = "H3 Text", style = DuckDuckGoTheme.typography.h3)
91+
}
92+
}
93+
94+
@PreviewLightDark
95+
@Composable
96+
private fun DaxTextH4Preview() {
97+
DaxTextPreviewBox {
98+
DaxText(text = "H4 Text", style = DuckDuckGoTheme.typography.h4)
99+
}
100+
}
101+
102+
@PreviewLightDark
103+
@Composable
104+
private fun DaxTextH5Preview() {
105+
DaxTextPreviewBox {
106+
DaxText(text = "H5 Text", style = DuckDuckGoTheme.typography.h5)
107+
}
108+
}
109+
110+
@PreviewLightDark
111+
@Composable
112+
private fun DaxTextBody1Preview() {
113+
DaxTextPreviewBox {
114+
DaxText(text = "Body1 Text", style = DuckDuckGoTheme.typography.body1)
115+
}
116+
}
117+
118+
@PreviewLightDark
119+
@Composable
120+
private fun DaxTextBody1BoldPreview() {
121+
DaxTextPreviewBox {
122+
DaxText(text = "Body1Bold Text", style = DuckDuckGoTheme.typography.body1Bold)
123+
}
124+
}
125+
126+
@PreviewLightDark
127+
@Composable
128+
private fun DaxTextBody1MonoPreview() {
129+
DaxTextPreviewBox {
130+
DaxText(text = "Body1Mono Text", style = DuckDuckGoTheme.typography.body1Mono)
131+
}
132+
}
133+
134+
@PreviewLightDark
135+
@Composable
136+
private fun DaxTextBody2Preview() {
137+
DaxTextPreviewBox {
138+
DaxText(text = "Body2 Text", style = DuckDuckGoTheme.typography.body2)
139+
}
140+
}
141+
142+
@PreviewLightDark
143+
@Composable
144+
private fun DaxTextBody2BoldPreview() {
145+
DaxTextPreviewBox {
146+
DaxText(text = "Body2Bold Text", style = DuckDuckGoTheme.typography.body2Bold)
147+
}
148+
}
149+
150+
@PreviewLightDark
151+
@Composable
152+
private fun DaxTextButtonPreview() {
153+
DaxTextPreviewBox {
154+
DaxText(text = "Button Text", style = DuckDuckGoTheme.typography.button)
155+
}
156+
}
157+
158+
@PreviewLightDark
159+
@Composable
160+
private fun DaxTextCaptionPreview() {
161+
DaxTextPreviewBox {
162+
DaxText(text = "Caption Text", style = DuckDuckGoTheme.typography.caption)
163+
}
164+
}
165+
166+
@PreviewLightDark
167+
@Composable
168+
private fun DaxTextColorPrimaryPreview() {
169+
DaxTextPreviewBox {
170+
DaxText(
171+
text = "Primary Color",
172+
color = DuckDuckGoTheme.textColors.primary,
173+
)
174+
}
175+
}
176+
177+
@PreviewLightDark
178+
@Composable
179+
private fun DaxTextColorPrimaryInvertedPreview() {
180+
DaxTextInvertedPreviewBox {
181+
DaxText(
182+
text = "Primary Inverted",
183+
color = DuckDuckGoTheme.textColors.primaryInverted,
184+
)
185+
}
186+
}
187+
188+
@PreviewLightDark
189+
@Composable
190+
private fun DaxTextColorSecondaryPreview() {
191+
DaxTextPreviewBox {
192+
DaxText(
193+
text = "Secondary Color",
194+
color = DuckDuckGoTheme.textColors.secondary,
195+
)
196+
}
197+
}
198+
199+
@PreviewLightDark
200+
@Composable
201+
private fun DaxTextColorSecondaryInvertedPreview() {
202+
DaxTextInvertedPreviewBox {
203+
DaxText(
204+
text = "Secondary Inverted",
205+
color = DuckDuckGoTheme.textColors.secondaryInverted,
206+
)
207+
}
208+
}
209+
210+
@PreviewLightDark
211+
@Composable
212+
private fun DaxTextColorTertiaryPreview() {
213+
DaxTextPreviewBox {
214+
DaxText(
215+
text = "Tertiary Color",
216+
color = DuckDuckGoTheme.textColors.tertiary,
217+
)
218+
}
219+
}
220+
221+
@PreviewLightDark
222+
@Composable
223+
private fun DaxTextColorDisabledPreview() {
224+
DaxTextPreviewBox {
225+
DaxText(
226+
text = "Disabled Color",
227+
color = DuckDuckGoTheme.textColors.disabled,
228+
)
229+
}
230+
}
231+
232+
@Composable
233+
private fun DaxTextPreviewBox(
234+
content: @Composable () -> Unit,
235+
) {
236+
DuckDuckGoTheme {
237+
PreviewBox(backgroundColor = DuckDuckGoTheme.colors.background) {
238+
content()
239+
}
240+
}
241+
}
242+
243+
@Composable
244+
private fun DaxTextInvertedPreviewBox(
245+
content: @Composable () -> Unit,
246+
) {
247+
DuckDuckGoTheme {
248+
PreviewBox(backgroundColor = DuckDuckGoTheme.colors.backgroundInverted) {
249+
content()
250+
}
251+
}
252+
}

design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Theme.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ fun DuckDuckGoTheme(
8686
accentYellow = colorResource(R.color.yellow50),
8787
ripple = colorResource(R.color.black6),
8888
text = DuckDuckGoTextColors(
89-
primary = colorResource(R.color.white84),
90-
primaryInverted = colorResource(R.color.black84),
91-
secondary = colorResource(R.color.white60),
92-
secondaryInverted = colorResource(R.color.black60),
93-
tertiary = colorResource(R.color.white48),
94-
disabled = colorResource(R.color.black36),
95-
logoTitle = colorResource(R.color.gray85),
89+
primary = colorResource(R.color.text_primary_light),
90+
primaryInverted = White84,
91+
secondary = colorResource(R.color.text_secondary_light),
92+
secondaryInverted = White60,
93+
tertiary = Black36,
94+
disabled = Black40,
95+
logoTitle = Gray85,
9696
omnibarHighlight = colorResource(R.color.blue50_20),
9797
),
9898
isDark = false,
@@ -112,13 +112,13 @@ fun DuckDuckGoTheme(
112112
accentYellow = colorResource(R.color.yellow50),
113113
ripple = colorResource(R.color.white12),
114114
text = DuckDuckGoTextColors(
115-
primary = colorResource(R.color.white84),
115+
primary = colorResource(R.color.text_primary_dark),
116116
primaryInverted = colorResource(R.color.black84),
117-
secondary = colorResource(R.color.white60),
117+
secondary = colorResource(R.color.text_secondary_dark),
118118
secondaryInverted = colorResource(R.color.black60),
119-
tertiary = colorResource(R.color.white48),
120-
disabled = colorResource(R.color.white36),
121-
logoTitle = colorResource(R.color.white),
119+
tertiary = White36,
120+
disabled = White40,
121+
logoTitle = White,
122122
omnibarHighlight = colorResource(R.color.blue30_20),
123123
),
124124
isDark = true,

design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Typography.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,22 @@ import android.annotation.SuppressLint
2020
import androidx.compose.runtime.Immutable
2121
import androidx.compose.runtime.staticCompositionLocalOf
2222
import androidx.compose.ui.text.TextStyle
23+
import androidx.compose.ui.text.font.Font
24+
import androidx.compose.ui.text.font.FontFamily
2325
import androidx.compose.ui.text.font.FontWeight
2426
import androidx.compose.ui.unit.sp
27+
import com.duckduckgo.mobile.android.R
2528

2629
/**
2730
* Default typography for DuckDuckGo theme.
2831
*
2932
* Figma: https://www.figma.com/design/jHLwh4erLbNc2YeobQpGFt/Design-System-Guidelines?node-id=1313-19967
3033
*/
34+
35+
private val RobotoMono = FontFamily(
36+
Font(R.font.roboto_mono, FontWeight.Normal),
37+
)
38+
3139
@Immutable
3240
data class DuckDuckGoTypography(
3341

@@ -54,6 +62,7 @@ data class DuckDuckGoTypography(
5462
TextStyle(
5563
fontSize = 20.sp,
5664
lineHeight = 24.sp,
65+
letterSpacing = 0.3.sp,
5766
fontWeight = FontWeight.Medium,
5867
),
5968
),
@@ -72,6 +81,7 @@ data class DuckDuckGoTypography(
7281
TextStyle(
7382
fontSize = 14.sp,
7483
lineHeight = 20.sp,
84+
letterSpacing = 0.3.sp,
7585
fontWeight = FontWeight.Medium,
7686
),
7787
),
@@ -92,10 +102,30 @@ data class DuckDuckGoTypography(
92102
),
93103
),
94104

105+
val body1Bold: DuckDuckGoTextStyle = DuckDuckGoTextStyle(
106+
body1.textStyle.copy(
107+
fontWeight = FontWeight.Bold,
108+
),
109+
),
110+
111+
val body1Mono: DuckDuckGoTextStyle = DuckDuckGoTextStyle(
112+
body1.textStyle.copy(
113+
fontFamily = RobotoMono,
114+
),
115+
),
116+
95117
val body2: DuckDuckGoTextStyle = DuckDuckGoTextStyle(
96118
TextStyle(
97119
fontSize = 14.sp,
98120
lineHeight = 18.sp,
121+
letterSpacing = 0.2.sp,
122+
),
123+
),
124+
125+
val body2Bold: DuckDuckGoTextStyle = DuckDuckGoTextStyle(
126+
body2.textStyle.copy(
127+
fontWeight = FontWeight.Bold,
128+
letterSpacing = 0.3.sp,
99129
),
100130
),
101131

@@ -111,6 +141,7 @@ data class DuckDuckGoTypography(
111141
TextStyle(
112142
fontSize = 12.sp,
113143
lineHeight = 16.sp,
144+
letterSpacing = 0.2.sp,
114145
),
115146
),
116147
)
@@ -122,8 +153,9 @@ val LocalDuckDuckGoTypography = staticCompositionLocalOf<DuckDuckGoTypography> {
122153
error("No DuckDuckGoTypography provided")
123154
}
124155

156+
@JvmInline
125157
@Immutable
126-
data class DuckDuckGoTextStyle internal constructor(
158+
value class DuckDuckGoTextStyle internal constructor(
127159
internal val textStyle: TextStyle,
128160
)
129161

0 commit comments

Comments
 (0)