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