xref: /expo/docs/ui/components/Text/index.tsx (revision dfd15ebd)
1import { css, SerializedStyles } from '@emotion/react';
2import { theme, typography, spacing, borderRadius } from '@expo/styleguide';
3import React from 'react';
4
5import { LinkBase, LinkProps } from './Link';
6import { TextComponentProps, TextElement } from './types';
7
8import { durations } from '~/ui/foundations/durations';
9
10export { LinkBase } from './Link';
11export { AnchorContext } from './withAnchor';
12
13export function createTextComponent(Element: TextElement, textStyle?: SerializedStyles) {
14  function TextComponent(props: TextComponentProps) {
15    const { testID, tag, weight: textWeight, theme: textTheme, ...rest } = props;
16    const TextElementTag = tag ?? Element;
17
18    return (
19      <TextElementTag
20        css={[
21          baseTextStyle,
22          textStyle,
23          textWeight && typography.utility.weight[textWeight],
24          textTheme && { color: theme.text[textTheme] },
25        ]}
26        data-testid={testID}
27        {...rest}
28      />
29    );
30  }
31  TextComponent.displayName = `Text(${Element})`;
32  return TextComponent;
33}
34
35const baseTextStyle = css({
36  ...typography.body.paragraph,
37  color: theme.text.default,
38});
39
40const link = css({
41  textDecoration: 'none',
42  cursor: 'pointer',
43
44  // transform prevents a 1px shift on hover on Safari
45  transform: 'translate3d(0,0,0)',
46
47  ':hover': {
48    transition: durations.hover,
49    opacity: 0.8,
50  },
51});
52
53const listStyle = css({
54  marginLeft: '1.5rem',
55});
56
57export const H1 = createTextComponent(TextElement.H1, css(typography.headers.default.h1));
58export const H2 = createTextComponent(TextElement.H2, css(typography.headers.default.h2));
59export const H3 = createTextComponent(TextElement.H4, css(typography.headers.default.h3));
60export const H4 = createTextComponent(TextElement.H4, css(typography.headers.default.h4));
61export const H5 = createTextComponent(TextElement.H5, css(typography.headers.default.h5));
62export const H6 = createTextComponent(TextElement.H6, css(typography.headers.default.h6));
63export const P = createTextComponent(TextElement.P, css(typography.body.paragraph));
64export const CODE = createTextComponent(TextElement.CODE, css(typography.utility.inlineCode));
65export const LI = createTextComponent(TextElement.LI, css(typography.body.li));
66export const LABEL = createTextComponent(TextElement.SPAN, css(typography.body.label));
67export const HEADLINE = createTextComponent(TextElement.P, css(typography.body.headline));
68export const FOOTNOTE = createTextComponent(TextElement.P, css(typography.body.footnote));
69export const CALLOUT = createTextComponent(TextElement.P, css(typography.body.callout));
70export const BOLD = createTextComponent(TextElement.SPAN, css(typography.utility.weight.semiBold));
71export const DEMI = createTextComponent(TextElement.SPAN, css(typography.utility.weight.medium));
72export const UL = createTextComponent(TextElement.UL, css([typography.body.ul, listStyle]));
73export const OL = createTextComponent(TextElement.OL, css([typography.body.ol, listStyle]));
74export const PRE = createTextComponent(TextElement.PRE, css(typography.utility.pre));
75
76export const kbdStyle = css({
77  fontFamily: typography.fontFaces.medium,
78  color: theme.text.secondary,
79  padding: `0 ${spacing[1]}px`,
80  boxShadow: `0 0.1rem 0 1px ${theme.border.default}`,
81  borderRadius: borderRadius.small,
82  position: 'relative',
83  display: 'inline-flex',
84  margin: 0,
85  minWidth: 22,
86  justifyContent: 'center',
87  top: -1,
88});
89
90const isExternalLink = (href?: string) => href?.includes('://');
91
92export const KBD = createTextComponent(TextElement.KBD, css([typography.utility.pre, kbdStyle]));
93
94export const A = (props: LinkProps & { isStyled?: boolean }) => {
95  const { isStyled, ...rest } = props;
96  return (
97    <LinkBase
98      css={[link, isStyled && css(typography.utility.anchor)]}
99      openInNewTab={isExternalLink(props.href)}
100      {...rest}
101    />
102  );
103};
104A.displayName = 'Text(a)';
105