Skip to content

Commit 2e4d09a

Browse files
authored
UILivePreview component snippet wrapper (#3351)
* UILivePreview component snippet wrapper * hide iframe scrollbar * livePreviewSource * sortableGridList snippet fix * fixed review notes
1 parent 5e4c08a commit 2e4d09a

File tree

11 files changed

+162
-41
lines changed

11 files changed

+162
-41
lines changed

docs/getting-started/usage.md

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,30 @@ title: 'Usage'
55
# path: "/getting-started/usage"
66
---
77

8+
import UILivePreview from '@site/src/components/UILivePreview';
9+
810
This is a quick example of how to use our basic components, modifiers and presets to generate a good looking screen.
911
For detailed information please go over the other sections: [Style](../foundation/style.md), [Modifiers](../foundation/modifiers.md), Components...
1012

1113
<!-- ![basic showcase](basic-showcase.png). -->
1214

13-
```jsx live
14-
function Example(props) {
15-
return (
16-
<div>
17-
<View flex center>
18-
<Text blue50 text20 marginB-s5>
19-
Welcome
20-
</Text>
21-
<SegmentedControl segments={[{label: 'Register'}, {label: 'Login'}]} />
22-
23-
<View marginT-s5>
24-
<TextField preset="outline" placeholder="username" />
25-
<TextField preset="outline" placeholder="password" secureTextEntry grey10 />
26-
</View>
27-
28-
<View row marginT-s5 centerV>
29-
<Button link text70 orange30 label="Sign Up" marginR-s5 />
30-
<Button text70 white background-orange30 label="Login" />
15+
<UILivePreview code={`function Example(props) {
16+
return (
17+
<div>
18+
<View flex center>
19+
<Text blue50 text20 marginB-s5>
20+
Welcome
21+
</Text>
22+
<SegmentedControl segments={[{label: 'Register'}, {label: 'Login'}]}/>
23+
<View marginT-s5>
24+
<TextField preset="outline" placeholder="username" />
25+
<TextField preset="outline" placeholder="password" secureTextEntry grey10 />
26+
</View>
27+
<View row marginT-s5 centerV>
28+
<Button link text70 orange30 label="Sign Up" marginR-s5 />
29+
<Button text70 white background-orange30 label="Login" />
30+
</View>
3131
</View>
32-
</View>
33-
</div>
34-
);
35-
}
36-
```
32+
</div>
33+
);
34+
}`}/>

docuilib/docusaurus.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ const darkCodeTheme = themes.dracula;
1717
projectName: 'react-native-ui-lib', // Usually your repo name.
1818
trailingSlash: false,
1919
customFields: {
20+
livePreviewSource:
21+
process.env.NODE_ENV === 'development'
22+
? 'http://localhost:3000/react-native-ui-lib/livePreview'
23+
: 'https://wix.github.io/react-native-ui-lib/livePreview',
2024
docsMainEntry: 'getting-started/setup',
2125
expoSnackLink: 'https://snack.expo.io/@ethanshar/rnuilib_snack',
2226
stars: '4.7'
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, {useEffect, useRef, useState, useMemo} from 'react';
2+
import {StyleSheet} from 'react-native';
3+
import {LiveProvider, LiveEditor} from 'react-live';
4+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
5+
import {View, Colors} from 'react-native-ui-lib/core';
6+
import ReactLiveScope from '../theme/ReactLiveScope';
7+
8+
export const IFRAME_MESSAGE_TYPE = 'LIVE_PREVIEW_CODE_UPDATE_MESSAGE';
9+
10+
export default function UILivePreview({code: codeProp}) {
11+
const [code, setCode] = useState(codeProp);
12+
const [iframeLoaded, setIframeLoaded] = useState(false);
13+
const {siteConfig} = useDocusaurusContext();
14+
const iframeRef = useRef(null);
15+
const iframeSource = siteConfig?.customFields?.livePreviewSource as string;
16+
17+
useEffect(() => {
18+
if (iframeLoaded) {
19+
sendMessageToIframe(code);
20+
}
21+
}, [iframeLoaded, code]);
22+
23+
const sendMessageToIframe = code => {
24+
const message = {type: IFRAME_MESSAGE_TYPE, code};
25+
iframeRef.current?.contentWindow.postMessage(message, '*');
26+
};
27+
28+
const liveEditorStyle = useMemo(() => {
29+
return {overflowY: 'scroll', scrollbarWidth: 'none'};
30+
}, []);
31+
32+
return (
33+
<View row gap-s2 style={styles.liveCodeWrapper}>
34+
<LiveProvider code={code} scope={ReactLiveScope}>
35+
<View flex style={styles.editorWrapper}>
36+
<LiveEditor
37+
className="font-mono"
38+
onChange={setCode}
39+
//@ts-ignore
40+
style={liveEditorStyle}
41+
/>
42+
</View>
43+
<View bg-$backgroundDefault margin-s2 style={styles.iframeWrapper}>
44+
<iframe
45+
ref={iframeRef}
46+
style={styles.iframe}
47+
src={iframeSource}
48+
title="Simulator"
49+
onLoad={() => setIframeLoaded(true)}
50+
/>
51+
</View>
52+
</LiveProvider>
53+
</View>
54+
);
55+
}
56+
57+
const styles = StyleSheet.create({
58+
liveCodeWrapper: {
59+
borderRadius: 20,
60+
borderWidth: 1,
61+
backgroundColor: '#011627',
62+
height: 725,
63+
width: 900
64+
},
65+
editorWrapper: {maxHeight: 700, padding: 10, borderRadius: 20, overflow: 'hidden'},
66+
iframeWrapper: {
67+
alignSelf: 'center',
68+
overflow: 'hidden',
69+
borderRadius: 40,
70+
borderWidth: 4,
71+
borderColor: Colors.$outlineDisabledHeavy,
72+
width: 320,
73+
height: 700
74+
},
75+
iframe: {
76+
width: 335, // Slightly wider to hide scrollbar
77+
height: '100%',
78+
position: 'absolute',
79+
top: 0,
80+
left: 0,
81+
border: 0,
82+
padding: 10,
83+
background: 'transparent'
84+
}
85+
});

docuilib/src/pages/livePreview.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, {useEffect, useState} from 'react';
2+
import {StyleSheet} from 'react-native';
3+
import {LiveProvider, LivePreview} from 'react-live';
4+
import ReactLiveScope from '../theme/ReactLiveScope';
5+
import {IFRAME_MESSAGE_TYPE} from '@site/src/components/UILivePreview';
6+
7+
export default function UILivePreview() {
8+
const [code, setCode] = useState(``);
9+
10+
useEffect(() => {
11+
window.addEventListener('message', (e: MessageEvent) => {
12+
if (e.data.type === IFRAME_MESSAGE_TYPE) {
13+
setCode(e.data.code);
14+
}
15+
});
16+
}, []);
17+
18+
return (
19+
<LiveProvider code={code} scope={ReactLiveScope}>
20+
<LivePreview style={styles.livePreview}/>
21+
</LiveProvider>
22+
);
23+
}
24+
25+
const styles = StyleSheet.create({
26+
livePreview: {
27+
overflow: 'hidden'
28+
}
29+
});

docuilib/src/theme/ReactLiveScope/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SortableGridList from 'react-native-ui-lib/sortableGridList';
2121
import SortableList from 'react-native-ui-lib/sortableList';
2222
import Switch from 'react-native-ui-lib/switch';
2323
import TextField from 'react-native-ui-lib/textField';
24+
import UILivePreview from '@site/src/components/UILivePreview';
2425
import * as Playground from './Playground';
2526

2627
Assets.loadAssetsGroup('icons.demo', {
@@ -56,6 +57,7 @@ const ReactLiveScope = {
5657
/* Docs' utils and components */
5758
Data,
5859
Playground,
60+
UILivePreview,
5961
/* UI Lib's components */
6062
ActionBar,
6163
Assets,

scripts/buildDocsCommon.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ function resetDocsDir() {
5252
function processComponents(components) {
5353
/** Break into compound components (TabController.TabPage) and parent components (TabController) */
5454
const compoundComponents = components.filter(c => c.name.includes('.'));
55-
const parentComponents = _.flow(
56-
components => _.map(components, c => c.name.split('.')[0]),
57-
_.uniq
58-
)(compoundComponents);
55+
const parentComponents = _.flow(components => _.map(components, c => c.name.split('.')[0]),
56+
_.uniq)(compoundComponents);
5957

6058
components.forEach(component => {
6159
const [componentName, componentParentName] = getComponentNameParts(component.name);
@@ -114,6 +112,7 @@ function generateExtendsLink(extendsLink) {
114112

115113
function buildOldDocs(component) {
116114
let content = '';
115+
content += `import UILivePreview from '@site/src/components/UILivePreview';\n\n`;
117116

118117
/* General Info */
119118
content += `${component.description} \n`;
@@ -170,9 +169,10 @@ function buildOldDocs(component) {
170169
/* Snippet */
171170
if (component.snippet) {
172171
content += `### Usage\n`;
173-
content += '``` jsx live\n';
174-
content += component.snippet?.map(item => _.replace(item, new RegExp(/\$[1-9]/, 'g'), '')).join('\n');
175-
content += '\n```\n';
172+
content += `<UILivePreview code={\`${component.snippet
173+
?.map(item => _.replace(item, new RegExp(/\$[1-9]/, 'g'), ''))
174+
.join('\n')
175+
.toString()}\`}/>\n\n`;
176176
} else {
177177
console.warn(`${component.name} does not have a snippet`);
178178
}

src/components/marquee/marquee.api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
],
1919
"snippet": [
2020
"<Marquee",
21-
" key={`${index}`$1}",
21+
" key={\\`\\${index}\\`$1}",
2222
" label={'Hey there'$2}",
2323
" direction={directionHorizontal ? MarqueeDirections.LEFT : MarqueeDirections.RIGHT$3}",
2424
" duration={duration$4}",

src/components/sortableGridList/sortableGridList.api.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@
5858
" };",
5959
"",
6060
" return (",
61-
" <div>",
62-
" <View style={{width: 800}}>",
61+
" <View flex>",
6362
" <Playground.ToggleControl title={'Order by index'} state={shouldOrderByIndex} setState={setShouldOrderByIndex}/>",
6463
" <SortableGridList",
6564
" data={data$1}",
@@ -69,11 +68,9 @@
6968
" renderItem={renderItem$4}",
7069
" itemSpacing={Spacings.s3$5}",
7170
" listPadding={Spacings.s5$6}",
72-
" containerWidth={800$7}",
7371
" orderByIndex={shouldOrderByIndex$8}",
7472
" />",
7573
" </View>",
76-
" </div>",
7774
" );",
7875
"}"
7976
]

src/components/sortableList/SortableList.api.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@
4545
"snippet": [
4646
"function Example(props) {",
4747
" const data = Array.from({length: 10}, (_, index) => {",
48-
" let text = `${index}`;",
48+
" let text = \\`\\${index}\\`;",
4949
" if (index === 3) {",
5050
" text = 'Locked item';",
5151
" }",
5252
" ",
5353
" return {",
5454
" text,",
55-
" id: `${index}`,",
55+
" id: \\`\\${index}\\`,",
5656
" locked: index === 3",
5757
" };",
5858
" });",
@@ -78,7 +78,7 @@
7878
" }, []);",
7979
"",
8080
" const keyExtractor = useCallback((item: Item) => {",
81-
" return `${item.id}`;",
81+
" return \\`\\${item.id}\\`;",
8282
" }, []);",
8383
"",
8484
" return (",

src/components/touchableOpacity/touchableOpacity.api.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,15 @@
2828
},
2929
{"name": "style", "type": "ViewStyle", "description": "Custom style"},
3030
{"name": "recorderTag", "type": "'mask' | 'unmask'", "description": "Recorder Tag"},
31-
{"name": "onPress", "type": "(props?: TouchableOpacityProps & {event: GestureResponderEvent} | any) => void", "description": "On press callback"}
31+
{
32+
"name": "onPress",
33+
"type": "(props?: TouchableOpacityProps & {event: GestureResponderEvent} | any) => void",
34+
"description": "On press callback"
35+
}
3236
],
3337
"snippet": [
34-
"<TouchableOpacity onPress={() => console.log('pressed')$1}><Text text40>Click Me!</Text></TouchableOpacity>"
38+
"<TouchableOpacity onPress={() => console.log('pressed')$1}>",
39+
" <Text text40>Click Me!</Text>",
40+
"</TouchableOpacity>"
3541
]
3642
}

src/incubator/Slider/slider.api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
" value={0$1}",
104104
" minimumValue={0$2}",
105105
" maximumValue={10$3}",
106-
" onValueChange={value => console.log(`value changed: ${value}`)$4}",
106+
" onValueChange={value => console.log(\\`value changed: \\${value}\\`)$4}",
107107
"/>"
108108
]
109109
}

0 commit comments

Comments
 (0)