this repo has no description
1import {UITextView} from 'react-native-uitextview'
2
3import {logger} from '#/logger'
4import {atoms as a, type TextStyleProp, useAlf, useTheme, web} from '#/alf'
5import {
6 childHasEmoji,
7 normalizeTextStyles,
8 renderChildrenWithEmoji,
9 type TextProps,
10} from '#/alf/typography'
11
12export type {TextProps}
13export {Text as Span} from 'react-native'
14
15/**
16 * Our main text component. Use this most of the time.
17 */
18export function Text({
19 children,
20 emoji,
21 style,
22 selectable,
23 title,
24 dataSet,
25 numberOfLines,
26 ...rest
27}: TextProps) {
28 const {fonts, flags} = useAlf()
29 const t = useTheme()
30 const s = normalizeTextStyles(
31 [
32 a.text_sm,
33 t.atoms.text,
34 web(numberOfLines === 1 && numberOfLinesClippingFix),
35 style,
36 ],
37 {
38 fontScale: fonts.scaleMultiplier,
39 fontFamily: fonts.family,
40 flags,
41 },
42 )
43
44 if (__DEV__) {
45 if (!emoji && childHasEmoji(children)) {
46 logger.warn(
47 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
48 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
49 )
50 }
51 }
52
53 const shared = {
54 uiTextView: true,
55 selectable,
56 numberOfLines,
57 style: s,
58 dataSet: Object.assign({tooltip: title}, dataSet || {}),
59 ...rest,
60 }
61
62 return (
63 <UITextView {...shared}>
64 {renderChildrenWithEmoji(children, shared, emoji ?? false)}
65 </UITextView>
66 )
67}
68
69function createHeadingElement({level}: {level: number}) {
70 return function HeadingElement({style, ...rest}: TextProps) {
71 const attr =
72 web({
73 role: 'heading',
74 'aria-level': level,
75 }) || {}
76 return <Text {...attr} {...rest} style={style} />
77 }
78}
79
80/*
81 * Use semantic components when it's beneficial to the user or to a web scraper
82 */
83export const H1 = createHeadingElement({level: 1})
84export const H2 = createHeadingElement({level: 2})
85export const H3 = createHeadingElement({level: 3})
86export const H4 = createHeadingElement({level: 4})
87export const H5 = createHeadingElement({level: 5})
88export const H6 = createHeadingElement({level: 6})
89export function P({style, ...rest}: TextProps) {
90 const attr =
91 web({
92 role: 'paragraph',
93 }) || {}
94 return (
95 <Text {...attr} {...rest} style={[a.text_md, a.leading_relaxed, style]} />
96 )
97}
98
99/**
100 * HACKFIX: React Native Web applies `overflow: hidden` to
101 * text when using the `numberOfLines` prop, which causes it to clip
102 * ascenders/descenders. It only needs to be doing this for the X axis,
103 * so override the style with `overflowX: 'hidden'`.
104 * Note this only works for `numberOfLines={1}` -sfn
105 *
106 * @see https://github.com/necolas/react-native-web/pull/2836
107 */
108const numberOfLinesClippingFix = {
109 overflowY: 'visible',
110 overflowX: 'clip',
111 // mimic browser default behavior of `min-width: 0` on `overflow: hidden`
112 // elements to allow text to shrink smaller than its intrinsic width when
113 // necessary
114 minWidth: 0,
115 // this is neater and supports vertical writing modes, but it's only baseline newly available
116 // overflowInline: 'clip',
117} satisfies React.CSSProperties as TextStyleProp