feat(react-native): let surveys cap how far their text scales, per text role - #4605
feat(react-native): let surveys cap how far their text scales, per text role#4605safaiyeh wants to merge 1 commit into
Conversation
…xt role
React Native surveys render every `Text` and `TextInput` with no
`maxFontSizeMultiplier`, so survey copy scales without a ceiling under the OS
text-size setting. At the largest accessibility sizes an 18pt question headline
renders roughly one word per line on a small phone, and a host app cannot fix it
from the outside: React Native only inherits `maxFontSizeMultiplier` through
nested `Text`, and these are siblings inside `View`s.
`appearance.maxFontSizeMultiplier` now carries a ceiling into the survey. It
takes either one number for the whole survey, or an object keyed by text role:
maxFontSizeMultiplier: 1.6
maxFontSizeMultiplier: { question: 1.5, description: 1.8, ratingNumber: 1.2 }
Per-role rather than one number, because one ceiling cannot serve every kind of
text a survey draws. `question` is a headline that can wrap freely; `ratingNumber`
sits inside a fixed-width button and has nowhere to grow; an app that already
caps its own text by role wants to match those ceilings here rather than flatten
them. The nine roles cover all sixteen text nodes the survey renders.
Nothing changes unless it is set: an unset appearance passes `undefined`, which
is React Native's "no ceiling", so every existing survey renders exactly as it
did. A role omitted from the object is likewise uncapped. `0` is passed through
rather than swallowed — in React Native that is a documented value meaning "no
maximum", not an absent one.
Two supporting changes the option needs to reach where it is read:
- `PostHogSurveyProvider`'s `defaultSurveyAppearance` was typed as the shared
`SurveyAppearance`, so React Native-only appearance fields could not be passed
even though the provider already merges them into a `SurveyAppearanceTheme`.
- `getQuestionComponent`'s intermediate props type narrowed `appearance` back to
the shared type, dropping every React Native-only field; the `as any` on the
call below hid it. The question components themselves already declare
`SurveyAppearanceTheme`.
The auto-scroll spec's react-native shim renders `Text` as a `div` and spreads
unknown props, so it now strips this native-only prop alongside the others it
already strips.
|
thanks @safaiyeh The shared SurveyAppearance type and PostHog survey API do not include this field, so it cannot currently be configured from the PostHog survey editor/API Moving to draft until we hear from @PostHog/team-surveys |
marandaneto
left a comment
There was a problem hiding this comment.
Inline review findings from the validated review.
| * survey components read, and PostHog never sends them down. | ||
| */ | ||
| defaultSurveyAppearance?: SurveyAppearance | ||
| defaultSurveyAppearance?: Partial<SurveyAppearanceTheme> |
There was a problem hiding this comment.
blocking: Preserve previously accepted appearance keys
Changing this prop from SurveyAppearance to Partial<SurveyAppearanceTheme> drops widgetSelector, widgetType, widgetColor, widgetLabel, and shuffleQuestions from accepted object-literal keys, so existing TypeScript callers using them fail excess-property checks. Please extend the existing public appearance contract with the React Native-only field rather than narrowing it.
| * | ||
| * @default undefined (no ceiling) | ||
| */ | ||
| maxFontSizeMultiplier?: number | Partial<Record<SurveyTextRole, number>> |
There was a problem hiding this comment.
blocking: Expose the new option through a public appearance type
The package root still exports only SurveyAppearance from @posthog/core, which lacks this field, while neither SurveyAppearanceTheme nor the referenced SurveyTextRole is publicly exported. Consumers therefore cannot type reusable appearance configuration containing maxFontSizeMultiplier, and API Extractor reports forgotten exports. Please export a public React Native appearance type and its referenced role type, and use it for public props.
| /** | ||
| * Caps how far survey text may grow under the OS text-size setting, as a | ||
| * multiple of its base size - React Native's `maxFontSizeMultiplier`, applied | ||
| * to every `Text` and `TextInput` the survey renders. |
There was a problem hiding this comment.
blocking: Apply the survey-wide cap to fallback icon text
When react-native-svg or its native view managers are unavailable, icons.tsx renders glyph fallbacks with React Native Text, but those nodes never receive this cap. A numeric whole-survey limit therefore does not cover every Text, and the glyphs can scale and clip inside their fixed dimensions. Pass the relevant cap to those fallbacks or disable font scaling for icon-only glyphs.
Problem
React Native surveys render every
TextandTextInputwith nomaxFontSizeMultiplier, so survey copy scales without a ceiling under the OS text-size setting.At the largest accessibility sizes an 18pt question headline renders roughly one word per line on a small phone, with the
?alone on its own line. Nothing clips and the card still scrolls — but in an app that caps its own text, the survey becomes the one uncapped thing on the screen.A host app cannot fix this from the outside. React Native only inherits
maxFontSizeMultiplierthrough nestedText, and these are siblings insideViews, so the value has to come in through the survey's own API.What this adds
appearance.maxFontSizeMultiplier— one number for the whole survey, or an object keyed by text role:Why per role, and not one number
One ceiling cannot serve every kind of text a survey draws.
questionis a headline that can wrap freely;ratingNumbersits inside a fixed-width button and has nowhere to grow;buttonsits in a row that has to stay one line. An app that already caps its own text by role wants to match those ceilings here rather than flatten them to a single value.The nine roles cover all 16 text nodes the survey renders:
questiondescriptionheaderchoiceinputbuttonratingLabelratingNumbervalidationHintBecause
appearanceis already threaded to every survey component, this needed no new prop plumbing.Nothing changes unless it is set
undefined, which is React Native's "no ceiling" — every existing survey renders exactly as it does today.0is passed through rather than swallowed. In React Native that is a documented value meaning no maximum, not an absent one, so the resolver teststypeof … === 'number'instead of truthiness.Two supporting type fixes the option needs
Both are places where a React Native-only appearance field could not reach the component that reads it:
PostHogSurveyProvider'sdefaultSurveyAppearancewas typed as the sharedSurveyAppearance, so RN-only fields could not be passed — even though the provider already merges that object into aSurveyAppearanceTheme. NowPartial<SurveyAppearanceTheme>.getQuestionComponent's intermediate props type narrowedappearanceback to the sharedSurveyAppearance, dropping every RN-only field; theas anyon the call below hid it. The question components themselves already declareSurveyAppearanceTheme.Tests
New
test/surveyMaxFontSizeMultiplier.spec.tsx— 9 tests covering the resolver (unset, flat number, per-role, omitted role,0) and the wiring (no ceiling when unconfigured; headline and description getting different ceilings; the button capped independently).One line in
test/surveys-autoScroll.spec.tsx: its react-native shim rendersTextas adivand spreads unknown props, so it now strips this native-only prop alongside the others it already strips.Checks
Ran from the repo root, per
packages/react-native/CONTRIBUTING.md:pnpm --filter=posthog-react-native lint— cleanpnpm --filter=posthog-react-native test— 612 passed, 42 suitespnpm --filter=posthog-react-native build— cleanChangeset included (
minor).Deliberately left out
allowFontScaling. It would be one more field, but it turns OS text scaling off entirely rather than bounding it, which is the opposite of what this PR is for — happy to add it if you'd rather the surface be complete.