From b2160960f71f4011c0bdbf6acd4465b7d5ad00f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?= Date: Wed, 26 Apr 2023 17:45:41 +0300 Subject: [PATCH] Improve React Native experience (#1462) * Improve React Native guide * Update reactNative.md * Update reactNative.md * Update reactNative.md * Add remote renderer overlay * Improve remote renderer connected overlay * Fix typo and improve brevity --- docs/reactNative.md | 45 +++++++++++++++---- .../react-cosmos-ui/src/components/Icon.tsx | 11 +++-- .../src/components/icons/index.tsx | 13 ++---- .../RemoteRendererConnected.tsx | 30 +++++++++++++ .../RemoteRendererOverlay.test.tsx | 27 +++++++++++ .../RendererOverlay/RemoteRendererOverlay.tsx | 14 ++++++ .../RendererOverlay/RendererOverlay.tsx | 1 - .../RendererOverlay/WaitingForRenderer.tsx | 42 +++++++---------- .../RendererOverlay/index.fixture.tsx | 3 ++ .../RendererOverlay/rendererOverlayShared.ts | 30 +++++++++++++ .../RendererPreview/RendererPreview.tsx | 11 ++--- .../src/plugins/RendererPreview/index.tsx | 5 +++ 12 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererConnected.tsx create mode 100644 packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.test.tsx create mode 100644 packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.tsx create mode 100644 packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/rendererOverlayShared.ts diff --git a/docs/reactNative.md b/docs/reactNative.md index 36f2a5bfb4..da95c7b69a 100644 --- a/docs/reactNative.md +++ b/docs/reactNative.md @@ -94,15 +94,25 @@ At this point Cosmos should successfully read your fixtures. One more step befor This is very similar to a [custom bundler setup](customBundlerSetup.md). Cosmos cannot plug itself automatically into React Native's build pipeline (Metro), but you can do it with minimal effort. -Replace your `App.js` entrypoint with the following code: +Here's a basic file structure to get going. You can tweak this after everything's working. + +1. Your production app entry point: `App.main.js`. +2. Your Cosmos renderer entry point: `App.cosmos.js`. +3. The root entry point that decides which to load: `App.js`. + +> If you're using TypeScript replace `.js` file extensions with `.tsx`. + +First, rename your existing `App.js` to `App.main.js`. + +Then add the Cosmos renderer under `App.cosmos.js`: ```jsx -// App.js +// App.cosmos.js import React, { Component } from 'react'; import { NativeFixtureLoader } from 'react-cosmos-native'; import { rendererConfig, moduleWrappers } from './cosmos.userdeps.js'; -export default class App extends Component { +export default class CosmosApp extends Component { render() { return ( When using TypeScript you'll notice an error related to `cosmos.userdeps.js`, which is a plain JS module. We're working on providing an option to generate `cosmos.userdeps.ts` soon. Meanwhile you can ignore this error by slapping a naughty `@ts-ignore` comment: +> +> ```diff +> rendererConfig={rendererConfig} +> + // @ts-ignore +> moduleWrappers={moduleWrappers} +> /> +> ``` + +Finally, create a new `App.js` that routes between your main and Cosmos entry points based on enviromnent: ```js // App.js @@ -123,16 +143,25 @@ module.exports = global.__DEV__ : require('./App.main'); ``` -Where `App.cosmos.js` contains the code above that renders `NativeFixtureLoader` and `App.main.js` contains your original App.js. - -6\. **Render fixture in simulator** +That's it! -That's it. Open your app in the simulator and the Cosmos renderer should say "No fixture selected". Go back to your React Cosmos UI, click on the `Hello` fixture and it will render in the simulator. +Open your app in the simulator and the Cosmos renderer should say "No fixture selected". Go back to your React Cosmos UI, click on the `Hello` fixture and it will render in the simulator. **Congratulations 😎** You've taken the first step towards designing reusable components. You're ready to prototype, test and interate on components in isolation. +## App fixture + +You'll often want to load the entire app in development. The simplest way to do this without disconnecting the Cosmos entry point is to create an App fixture: + +```jsx +// App.fixture.js +import App from './App.main'; + +export default () => ; +``` + ## Initial fixture You can configure the Cosmos Native renderer to auto load a fixture on init. diff --git a/packages/react-cosmos-ui/src/components/Icon.tsx b/packages/react-cosmos-ui/src/components/Icon.tsx index 3dfb5bd037..f7a0fea1b7 100644 --- a/packages/react-cosmos-ui/src/components/Icon.tsx +++ b/packages/react-cosmos-ui/src/components/Icon.tsx @@ -1,12 +1,15 @@ import React from 'react'; import { BaseSvg, SvgChildren } from './BaseSvg.js'; -type IconProps = { - children: SvgChildren; +export type IconProps = { size?: number | string; + strokeWidth?: number; }; -export function Icon({ children, size = '100%' }: IconProps) { +type Props = IconProps & { + children: SvgChildren; +}; +export function Icon({ children, size = '100%', strokeWidth = 1.5 }: Props) { return ( diff --git a/packages/react-cosmos-ui/src/components/icons/index.tsx b/packages/react-cosmos-ui/src/components/icons/index.tsx index 2a301c5955..4c54cacd70 100644 --- a/packages/react-cosmos-ui/src/components/icons/index.tsx +++ b/packages/react-cosmos-ui/src/components/icons/index.tsx @@ -1,10 +1,5 @@ import React from 'react'; -import { Icon } from '../Icon.js'; - -// Add common interface to each icon when needed -type Props = { - size?: number; -}; +import { Icon, IconProps } from '../Icon.js'; export const ChevronLeftIcon = () => ( @@ -75,7 +70,7 @@ export const RefreshCwIcon = () => ( ); -export const RefreshCcwIcon = (props: Props) => ( +export const RefreshCcwIcon = (props: IconProps) => ( @@ -104,8 +99,8 @@ export const EditIcon = () => ( ); -export const CheckCircleIcon = () => ( - +export const CheckCircleIcon = (props: IconProps) => ( + diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererConnected.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererConnected.tsx new file mode 100644 index 0000000000..9a553f7464 --- /dev/null +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererConnected.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import styled from 'styled-components'; +import { CheckCircleIcon } from '../../../components/icons/index.js'; +import { grey144 } from '../../../style/colors.js'; +import { + RendererOverlayContainer, + RendererOverlayIconWrapper, + RendererOverlayMessage, +} from './rendererOverlayShared.js'; + +export function RemoteRendererConnected() { + return ( + + + {} + + Remote renderer connected + + ); +} + +const svgRingRatio = 26.667 / 32; +const ringSize = 34; +const iconSize = ringSize / svgRingRatio; + +const IconContainer = styled.span` + width: ${iconSize}px; + height: ${iconSize}px; + color: ${grey144}; +`; diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.test.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.test.tsx new file mode 100644 index 0000000000..ab3d77d755 --- /dev/null +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.test.tsx @@ -0,0 +1,27 @@ +import retry from '@skidding/async-retry'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { wrapActSetTimeout } from '../../../testHelpers/wrapActSetTimeout.js'; +import { RemoteRendererOverlay } from './RemoteRendererOverlay.js'; + +it('does not immediately render anything before renderer is connected', () => { + const { container } = render( + + ); + expect(container).toMatchInlineSnapshot(`
`); +}); + +it('renders "waiting for renderer" state after waiting for some time', async () => { + wrapActSetTimeout(); + const { getByText } = render( + + ); + await retry(() => getByText(/waiting for renderer/i)); +}); + +it('renders "remote renderer connected" after renderer is connected', () => { + const { getByText } = render( + + ); + getByText(/remote renderer connected/i); +}); diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.tsx new file mode 100644 index 0000000000..7285fae3d3 --- /dev/null +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RemoteRendererOverlay.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { RemoteRendererConnected } from './RemoteRendererConnected.js'; +import { WaitingForRenderer } from './WaitingForRenderer.js'; + +type Props = { + rendererConnected: boolean; +}; +export function RemoteRendererOverlay({ rendererConnected }: Props) { + return rendererConnected ? ( + + ) : ( + + ); +} diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RendererOverlay.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RendererOverlay.tsx index 0b687deaa6..dd4a36ebab 100644 --- a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RendererOverlay.tsx +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/RendererOverlay.tsx @@ -5,7 +5,6 @@ import { WaitingForRenderer } from './WaitingForRenderer.js'; type Props = { runtimeStatus: RuntimeStatus; }; - export function RendererOverlay({ runtimeStatus }: Props) { if (runtimeStatus === 'pending') { return ; diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/WaitingForRenderer.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/WaitingForRenderer.tsx index 827205b443..5463a772fe 100644 --- a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/WaitingForRenderer.tsx +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/WaitingForRenderer.tsx @@ -1,43 +1,37 @@ import React from 'react'; import styled from 'styled-components'; import { DelayedLoading } from '../../../components/DelayedLoading.js'; -import { createGreyColor, grey144, grey192 } from '../../../style/colors.js'; +import { grey144 } from '../../../style/colors.js'; +import { + RendererOverlayContainer, + RendererOverlayIconWrapper, + RendererOverlayMessage, +} from './rendererOverlayShared.js'; export function WaitingForRenderer() { return ( - - - Waiting for renderer... - + + + + + Waiting for renderer... + ); } -const Container = styled.div` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: ${createGreyColor(8, 0.85)}; - border-radius: 3px; - padding: 16px 24px; - display: flex; - flex-direction: column; - align-items: center; -`; - // Copied from https://codepen.io/bernethe/pen/dorozd const Loader = styled.div` - margin: 12px 0 24px 0; - width: 32px; - height: 32px; + width: 34px; + height: 34px; border-radius: 50%; position: relative; :before, :after { content: ''; + box-sizing: border-box; border: 1px ${grey144} solid; border-radius: 50%; width: 100%; @@ -80,9 +74,3 @@ const Loader = styled.div` } } `; - -const Message = styled.p` - color: ${grey192}; - text-transform: uppercase; - white-space: nowrap; -`; diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/index.fixture.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/index.fixture.tsx index 405b3f9a85..ee6da91404 100644 --- a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/index.fixture.tsx +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/index.fixture.tsx @@ -1,6 +1,9 @@ import React from 'react'; +import { RemoteRendererConnected } from './RemoteRendererConnected.js'; import { WaitingForRenderer } from './WaitingForRenderer.js'; export default { 'waiting for renderer': , + + 'remote renderer connected': , }; diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/rendererOverlayShared.ts b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/rendererOverlayShared.ts new file mode 100644 index 0000000000..596bfac737 --- /dev/null +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererOverlay/rendererOverlayShared.ts @@ -0,0 +1,30 @@ +import styled from 'styled-components'; +import { createGreyColor, grey192 } from '../../../style/colors.js'; + +export const RendererOverlayContainer = styled.div` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: ${createGreyColor(8, 0.9)}; + border-radius: 3px; + height: 116px; + padding: 0 24px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; +`; + +export const RendererOverlayIconWrapper = styled.div` + height: 76px; + display: flex; + align-items: center; +`; + +export const RendererOverlayMessage = styled.p` + margin-bottom: 16px; + color: ${grey192}; + text-transform: uppercase; + white-space: nowrap; +`; diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererPreview.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererPreview.tsx index 520e451c0b..3e23c5da17 100644 --- a/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererPreview.tsx +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/RendererPreview.tsx @@ -1,30 +1,27 @@ import React from 'react'; import { Slot } from 'react-plugin'; import styled from 'styled-components'; +import { RemoteRendererOverlay } from './RendererOverlay/RemoteRendererOverlay.js'; import { RendererOverlay } from './RendererOverlay/RendererOverlay.js'; -import { WaitingForRenderer } from './RendererOverlay/WaitingForRenderer.js'; import { RuntimeStatus } from './spec.js'; export type OnIframeRef = (elRef: null | HTMLIFrameElement) => void; type Props = { rendererUrl: null | string; + rendererConnected: boolean; runtimeStatus: RuntimeStatus; onIframeRef: OnIframeRef; }; - export const RendererPreview = React.memo(function RendererPreview({ rendererUrl, + rendererConnected, runtimeStatus, onIframeRef, }: Props) { if (!rendererUrl) { // This code path is used when Cosmos is in React Native mode - return ( - - {runtimeStatus === 'pending' && } - - ); + return ; } return ( diff --git a/packages/react-cosmos-ui/src/plugins/RendererPreview/index.tsx b/packages/react-cosmos-ui/src/plugins/RendererPreview/index.tsx index 747bc89d1b..5aa4ac636e 100644 --- a/packages/react-cosmos-ui/src/plugins/RendererPreview/index.tsx +++ b/packages/react-cosmos-ui/src/plugins/RendererPreview/index.tsx @@ -42,6 +42,7 @@ plug('rendererPreview', ({ pluginContext }) => { return ( @@ -59,3 +60,7 @@ function getRuntimeStatus({ getState }: RendererPreviewContext) { function getRendererUrl({ getMethodsOf }: RendererPreviewContext) { return getMethodsOf('core').getWebRendererUrl(); } + +function getRendererConnected({ getMethodsOf }: RendererPreviewContext) { + return getMethodsOf('rendererCore').isRendererConnected(); +}