xref: /expo/docs/ui/components/BoxLink/index.tsx (revision c615fa2e)
1import { css } from '@emotion/react';
2import {
3  borderRadius,
4  spacing,
5  theme,
6  ArrowRightIcon,
7  iconSize,
8  shadows,
9  ArrowUpRightIcon,
10  breakpoints,
11} from '@expo/styleguide';
12import type { IconProps } from '@expo/styleguide/dist/types';
13import React, { ComponentType, PropsWithChildren, ReactNode } from 'react';
14
15import { A, DEMI, P } from '~/ui/components/Text';
16
17type BoxLinkProps = PropsWithChildren<{
18  title: string;
19  description: string | ReactNode;
20  href?: string;
21  testID?: string;
22  Icon?: ComponentType<IconProps>;
23}>;
24
25export function BoxLink({ title, description, href, testID, Icon }: BoxLinkProps) {
26  const isExternal = Boolean(href && href.startsWith('http'));
27  const ArrowIcon = isExternal ? ArrowUpRightIcon : ArrowRightIcon;
28  return (
29    <A href={href} css={tileContainerStyle} data-testid={testID} openInNewTab={isExternal} isStyled>
30      <div css={tileContentWrapperStyle}>
31        {Icon && (
32          <div css={tileIconBackgroundStyle}>
33            <Icon width={iconSize.md} />
34          </div>
35        )}
36        <div>
37          <DEMI>{title}</DEMI>
38          <P>{description}</P>
39        </div>
40      </div>
41      <ArrowIcon css={arrowIconStyle} color={theme.icon.secondary} />
42    </A>
43  );
44}
45
46const tileContainerStyle = css({
47  display: 'flex',
48  flexDirection: 'row',
49  justifyContent: 'space-between',
50  border: `1px solid ${theme.border.default}`,
51  borderRadius: borderRadius.md,
52  padding: `${spacing[3]}px ${spacing[4]}px`,
53  marginBottom: spacing[3],
54
55  ':hover': {
56    boxShadow: shadows.xs,
57  },
58});
59
60const tileContentWrapperStyle = css({
61  display: 'flex',
62  flexDirection: 'row',
63  gap: spacing[4],
64});
65
66const tileIconBackgroundStyle = css({
67  display: 'flex',
68  backgroundColor: theme.background.element,
69  borderRadius: borderRadius.md,
70  alignSelf: 'center',
71  alignItems: 'center',
72  justifyContent: 'center',
73  minWidth: 36,
74  height: 36,
75
76  [`@media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px)`]: {
77    alignSelf: 'flex-start',
78  },
79});
80
81const arrowIconStyle = css({
82  alignSelf: 'center',
83  alignContent: 'flex-end',
84  minWidth: iconSize.md,
85  marginLeft: spacing[3],
86});
87