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