1import { mergeClasses, SnackLogo } from '@expo/styleguide';
2import { ArrowUpRightIcon } from '@expo/styleguide-icons';
3import { useEffect, useRef, useState, PropsWithChildren } from 'react';
4
5import { Snippet } from '../Snippet';
6import { SnippetAction } from '../SnippetAction';
7import { SnippetContent } from '../SnippetContent';
8import { SnippetHeader } from '../SnippetHeader';
9
10import { SNACK_URL, getSnackFiles } from '~/common/snack';
11import { cleanCopyValue } from '~/components/base/code';
12import { PageApiVersionContextType, usePageApiVersion } from '~/providers/page-api-version';
13import versions from '~/public/static/constants/versions.json';
14import { CopyAction } from '~/ui/components/Snippet/actions/CopyAction';
15
16const DEFAULT_PLATFORM = 'android';
17const { LATEST_VERSION } = versions;
18
19type Props = PropsWithChildren<{
20  dependencies: string[];
21  label?: string;
22  defaultPlatform?: string;
23  templateId?: string;
24  files?: Record<string, string>;
25  platforms?: string[];
26  buttonTitle?: string;
27  contentHidden?: boolean;
28}>;
29
30export const SnackInline = ({
31  dependencies = [],
32  label,
33  defaultPlatform,
34  templateId,
35  files,
36  platforms,
37  buttonTitle,
38  contentHidden,
39  children,
40}: Props) => {
41  const contentRef = useRef<HTMLDivElement>(null);
42  const [isReady, setReady] = useState(false);
43  const context = usePageApiVersion();
44
45  useEffect(() => setReady(true), []);
46
47  // Filter out `latest` and use the concrete latest version instead. We want to
48  // keep `unversioned` in for the selected docs version though. This is used to
49  // find the examples in the static dir, and we don't have a `latest` version
50  // there, but we do have `unversioned`.
51  const getSelectedDocsVersion = () => {
52    const { version } = context as PageApiVersionContextType;
53    return version === 'latest' ? LATEST_VERSION : version;
54  };
55
56  // Get a SDK version that Snack will understand. `latest` and `unversioned`
57  // are meaningless to Snack so we filter those out and use `LATEST_VERSION` instead
58  const getSnackSdkVersion = () => {
59    let version = getSelectedDocsVersion();
60    if (version === 'unversioned') {
61      version = LATEST_VERSION;
62    }
63
64    return version.replace('v', '');
65  };
66
67  const getExamplesPath = () => {
68    return `${document.location.origin}/static/examples/${getSelectedDocsVersion()}`;
69  };
70
71  const getCode = () => {
72    const code = contentRef.current ? contentRef.current.textContent || '' : '';
73    return code.replace(/%%placeholder-start%%.*%%placeholder-end%%/g, '');
74  };
75
76  return (
77    <Snippet className="flex flex-col mb-3 prose-pre:!m-0 prose-pre:!border-0">
78      <SnippetHeader title={label || 'Example'} Icon={SnackLogo}>
79        <form action={SNACK_URL} method="POST" target="_blank" className="contents">
80          <input type="hidden" name="platform" value={defaultPlatform || DEFAULT_PLATFORM} />
81          <input type="hidden" name="name" value={label || 'Example'} />
82          <input type="hidden" name="dependencies" value={dependencies.join(',')} />
83          <input type="hidden" name="sdkVersion" value={getSnackSdkVersion()} />
84          {platforms && (
85            <input type="hidden" name="supportedPlatforms" value={platforms.join(',')} />
86          )}
87          {isReady && (
88            <input
89              type="hidden"
90              name="files"
91              value={JSON.stringify(
92                getSnackFiles({
93                  templateId,
94                  code: getCode(),
95                  files,
96                  baseURL: getExamplesPath(),
97                })
98              )}
99            />
100          )}
101          <CopyAction text={cleanCopyValue(getCode())} />
102          <SnippetAction
103            disabled={!isReady}
104            rightSlot={<ArrowUpRightIcon className="icon-sm text-icon-secondary" />}
105            type="submit">
106            {buttonTitle || 'Open in Snack'}
107          </SnippetAction>
108        </form>
109      </SnippetHeader>
110      <SnippetContent ref={contentRef} className={mergeClasses('p-0', contentHidden && 'hidden')}>
111        {children}
112      </SnippetContent>
113    </Snippet>
114  );
115};
116