Skip to content

Commit 2603a8d

Browse files
authored
fix(surveys): react native text colors (#2774)
## Problem survey text color is hard-coded to black on react native. this means users can't create dark-mode surveys <!-- Who are we building for, what are their needs, why is this important? --> ## Changes - adds `inputBackground` to core sdk types - updates all text colors for surveys in RN to use the contrasting color function, matching browser behavior - update contrast function to support shorthand hex e.g. `#111` ![survey-rn-demo2.png](https://app.graphite.com/user-attachments/assets/deb27ef6-5340-4f48-8ae1-aec9037139a4.png) <!-- What is changed and what information would be useful to a reviewer? --> ## Release info Sub-libraries affected ### Libraries affected <!-- Please mark which libraries will require a version bump. --> - [ ] All of them - [ ] posthog-js (web) - [ ] posthog-js-lite (web lite) - [ ] posthog-node - [x] posthog-react-native - [ ] @posthog/react - [ ] @posthog/ai - [ ] @posthog/nextjs-config - [ ] @posthog/nuxt - [ ] @posthog/rollup-plugin - [ ] @posthog/webpack-plugin ## Checklist - [ ] Tests for new code - [x] Accounted for the impact of any changes across different platforms - [x] Accounted for backwards compatibility of any changes (no breaking changes!) - [x] Took care not to unnecessarily increase the bundle size ### If releasing new changes - [x] Ran `pnpm changeset` to generate a changeset file - [x] Added the "release" label to the PR to indicate we're publishing new versions for the affected packages <!-- For more details check RELEASING.md -->
1 parent 5f75a54 commit 2603a8d

File tree

6 files changed

+78
-14
lines changed

6 files changed

+78
-14
lines changed

.changeset/wide-dolls-chew.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'posthog-react-native': minor
3+
'@posthog/core': minor
4+
---
5+
6+
fix survey text color on react native

packages/core/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ export type SurveyAppearance = {
300300
submitButtonTextColor?: string
301301
ratingButtonColor?: string
302302
ratingButtonActiveColor?: string
303+
inputBackground?: string
303304
autoDisappear?: boolean
304305
displayThankYouMessage?: boolean
305306
thankYouMessageHeader?: string

packages/react-native/src/surveys/components/ConfirmationMessage.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import React from 'react'
22
import { StyleSheet, Text, View, ViewStyle } from 'react-native'
33

4-
import { getContrastingTextColor, shouldRenderDescription, SurveyAppearanceTheme } from '../surveys-utils'
4+
import {
5+
defaultDescriptionOpacity,
6+
getContrastingTextColor,
7+
shouldRenderDescription,
8+
SurveyAppearanceTheme,
9+
} from '../surveys-utils'
510
import { SurveyQuestionDescriptionContentType } from '@posthog/core'
611
import { BottomSection } from './BottomSection'
712

@@ -28,7 +33,9 @@ export function ConfirmationMessage({
2833
<View style={styleOverrides}>
2934
<View style={styles.thankYouMessageContainer}>
3035
<Text style={[styles.thankYouMessageHeader, { color: textColor }]}>{header}</Text>
31-
{shouldRenderDescription(description, contentType) && <Text>{description}</Text>}
36+
{shouldRenderDescription(description, contentType) && (
37+
<Text style={{ color: textColor, opacity: defaultDescriptionOpacity }}>{description}</Text>
38+
)}
3239
</View>
3340
{isModal && (
3441
<BottomSection

packages/react-native/src/surveys/components/QuestionHeader.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,33 @@ import React from 'react'
22
import { StyleSheet, Text, View } from 'react-native'
33

44
import { SurveyQuestionDescriptionContentType } from '@posthog/core'
5-
import { shouldRenderDescription } from '../surveys-utils'
5+
import {
6+
defaultDescriptionOpacity,
7+
getContrastingTextColor,
8+
shouldRenderDescription,
9+
SurveyAppearanceTheme,
10+
} from '../surveys-utils'
611

712
export function QuestionHeader({
813
question,
914
description,
1015
descriptionContentType,
16+
appearance,
1117
}: {
1218
question: string
1319
description?: string | null
1420
descriptionContentType?: SurveyQuestionDescriptionContentType
21+
appearance: SurveyAppearanceTheme
1522
}): JSX.Element {
23+
const textColor = getContrastingTextColor(appearance.backgroundColor)
24+
1625
return (
1726
<View style={styles.container}>
18-
<Text style={styles.question}>{question}</Text>
27+
<Text style={[styles.question, { color: textColor }]}>{question}</Text>
1928
{shouldRenderDescription(description, descriptionContentType) && (
20-
<Text style={styles.description}>{description}</Text>
29+
<Text style={[styles.description, { color: textColor, opacity: defaultDescriptionOpacity }]}>
30+
{description}
31+
</Text>
2132
)}
2233
</View>
2334
)

packages/react-native/src/surveys/components/QuestionTypes.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import {
99
VeryDissatisfiedEmoji,
1010
VerySatisfiedEmoji,
1111
} from '../icons'
12-
import { getContrastingTextColor, getDisplayOrderChoices, SurveyAppearanceTheme } from '../surveys-utils'
12+
import {
13+
defaultRatingLabelOpacity,
14+
getContrastingTextColor,
15+
getDisplayOrderChoices,
16+
SurveyAppearanceTheme,
17+
} from '../surveys-utils'
1318
import {
1419
SurveyQuestion,
1520
SurveyRatingDisplay,
@@ -41,13 +46,25 @@ export function OpenTextQuestion({
4146
question={question.question}
4247
description={question.description}
4348
descriptionContentType={question.descriptionContentType}
49+
appearance={appearance}
4450
/>
4551
<View style={styles.textInputContainer}>
4652
<TextInput
47-
style={styles.textInput}
53+
style={[
54+
styles.textInput,
55+
{
56+
backgroundColor: appearance.inputBackground,
57+
color: getContrastingTextColor(appearance.inputBackground),
58+
},
59+
]}
4860
multiline
4961
numberOfLines={4}
5062
placeholder={appearance.placeholder}
63+
placeholderTextColor={
64+
getContrastingTextColor(appearance.inputBackground) === 'black'
65+
? 'rgba(0, 0, 0, 0.5)'
66+
: 'rgba(255, 255, 255, 0.5)'
67+
}
5168
onChangeText={setText}
5269
value={text}
5370
/>
@@ -77,6 +94,7 @@ export function LinkQuestion({
7794
question={question.question}
7895
description={question.description}
7996
descriptionContentType={question.descriptionContentType}
97+
appearance={appearance}
8098
/>
8199
<BottomSection
82100
text={question.buttonText ?? appearance.submitButtonText ?? 'Submit'}
@@ -106,6 +124,7 @@ export function RatingQuestion({
106124
question={question.question}
107125
description={question.description}
108126
descriptionContentType={question.descriptionContentType}
127+
appearance={appearance}
109128
/>
110129
<View style={styles.ratingSection}>
111130
<View style={styles.ratingOptions}>
@@ -140,8 +159,16 @@ export function RatingQuestion({
140159
)}
141160
</View>
142161
<View style={styles.ratingText}>
143-
<Text>{question.lowerBoundLabel}</Text>
144-
<Text>{question.upperBoundLabel}</Text>
162+
<Text
163+
style={{ color: getContrastingTextColor(appearance.backgroundColor), opacity: defaultRatingLabelOpacity }}
164+
>
165+
{question.lowerBoundLabel}
166+
</Text>
167+
<Text
168+
style={{ color: getContrastingTextColor(appearance.backgroundColor), opacity: defaultRatingLabelOpacity }}
169+
>
170+
{question.upperBoundLabel}
171+
</Text>
145172
</View>
146173
</View>
147174
<BottomSection
@@ -204,16 +231,23 @@ export function MultipleChoiceQuestion({
204231
question={question.question}
205232
description={question.description}
206233
descriptionContentType={question.descriptionContentType}
234+
appearance={appearance}
207235
/>
208236
<View style={styles.multipleChoiceOptions}>
209237
{choices.map((choice: string, idx: number) => {
210238
const isOpenChoice = choice === openChoice
211239
const isSelected = selectedChoices.includes(choice)
212240

241+
const inputTextColor = getContrastingTextColor(appearance.inputBackground)
242+
213243
return (
214244
<Pressable
215245
key={idx}
216-
style={[styles.choiceOption, isSelected ? { borderColor: 'black' } : {}]}
246+
style={[
247+
styles.choiceOption,
248+
{ backgroundColor: appearance.inputBackground },
249+
isSelected ? { borderColor: getContrastingTextColor(appearance.backgroundColor) } : {},
250+
]}
217251
onPress={() => {
218252
if (allowMultiple) {
219253
setSelectedChoices(
@@ -225,7 +259,7 @@ export function MultipleChoiceQuestion({
225259
}}
226260
>
227261
<View style={styles.choiceText}>
228-
<Text style={{ flexGrow: 1 }}>
262+
<Text style={{ flexGrow: 1, color: inputTextColor }}>
229263
{choice}
230264
{isOpenChoice ? ':' : ''}
231265
</Text>
@@ -294,7 +328,6 @@ const styles = StyleSheet.create({
294328
borderWidth: 1,
295329
padding: 10,
296330
marginVertical: 10,
297-
backgroundColor: 'white',
298331
fontSize: 16,
299332
},
300333
ratingSection: {
@@ -343,7 +376,6 @@ const styles = StyleSheet.create({
343376
borderColor: 'grey',
344377
borderRadius: 5,
345378
padding: 10,
346-
backgroundColor: 'white',
347379
},
348380
choiceText: {
349381
flexDirection: 'row',

packages/react-native/src/surveys/surveys-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export function shouldRenderDescription(
3737
}
3838

3939
export const defaultBackgroundColor = '#eeeded' as const
40+
export const defaultDescriptionOpacity = 0.8
41+
export const defaultRatingLabelOpacity = 0.7
4042

4143
export type SurveyAppearanceTheme = Omit<
4244
Required<SurveyAppearance>,
@@ -48,6 +50,7 @@ export const defaultSurveyAppearance: SurveyAppearanceTheme = {
4850
submitButtonTextColor: 'white',
4951
ratingButtonColor: 'white',
5052
ratingButtonActiveColor: 'black',
53+
inputBackground: 'white',
5154
borderColor: '#c9c6c6',
5255
placeholder: 'Start typing...',
5356
displayThankYouMessage: true,
@@ -290,7 +293,11 @@ export function getContrastingTextColor(color: string): 'black' | 'white' {
290293

291294
function hex2rgb(c: string): string {
292295
if (c.startsWith('#')) {
293-
const hexColor = c.replace(/^#/, '')
296+
let hexColor = c.replace(/^#/, '')
297+
// Handle 3-character shorthand (e.g., #111 -> #111111, #abc -> #aabbcc)
298+
if (/^[0-9A-Fa-f]{3}$/.test(hexColor)) {
299+
hexColor = hexColor[0] + hexColor[0] + hexColor[1] + hexColor[1] + hexColor[2] + hexColor[2]
300+
}
294301
if (!/^[0-9A-Fa-f]{6}$/.test(hexColor)) {
295302
return 'rgb(255, 255, 255)'
296303
}

0 commit comments

Comments
 (0)