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