From 3b9222514288559a4a6f2e58f75aad604351f2e4 Mon Sep 17 00:00:00 2001 From: Robin Drexler Date: Sun, 8 Dec 2024 22:47:56 -0500 Subject: [PATCH] clean up remote-ui example app and tests --- .../app/remote/examples/remote-ui.ts | 83 ++++++++++++++++++ examples/remote-ui/app/host/components.tsx | 17 +++- examples/remote-ui/app/host/host.tsx | 10 ++- examples/remote-ui/app/remote/app.tsx | 85 +++++++++---------- examples/remote-ui/app/remote/remote.tsx | 4 +- examples/remote-ui/app/style.css | 8 ++ examples/remote-ui/app/types.ts | 11 ++- 7 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 examples/kitchen-sink/app/remote/examples/remote-ui.ts diff --git a/examples/kitchen-sink/app/remote/examples/remote-ui.ts b/examples/kitchen-sink/app/remote/examples/remote-ui.ts new file mode 100644 index 00000000..7747e35b --- /dev/null +++ b/examples/kitchen-sink/app/remote/examples/remote-ui.ts @@ -0,0 +1,83 @@ +import type {RenderAPI} from '../../types.ts'; +import {Modal} from '../elements.ts'; + +export function renderUsingRemoteUI(root: Element, api: RenderAPI) { + let count = 0; + + function handlePress() { + updateCount(count + 1); + } + + function handleClose() { + if (count > 0) { + api.alert(`You clicked ${count} times!`); + } + + updateCount(0); + } + + function updateCount(newCount: number) { + count = newCount; + countText.textContent = String(count); + } + + function handlePrimaryAction() { + modal.close(); + } + + const countText = document.createElement('ui-text'); + countText.textContent = String(count); + countText.setAttribute('emphasis', ''); + + const template = document.createElement('div'); + + template.innerHTML = ` + + Click count: + Click me! + + Close + + + `.trim(); + + const modal = template.querySelector('ui-modal')! as InstanceType< + typeof Modal + >; + + modal.addEventListener('close', handleClose); + + modal.querySelector('ui-text')!.append(countText); + + const [countButton, primaryActionButton] = [ + ...modal.querySelectorAll('ui-button'), + ]; + + countButton!.addEventListener('press', handlePress); + primaryActionButton!.addEventListener('press', handlePrimaryAction); + + template.innerHTML = ` + + + Rendering example: + + + Rendering in sandbox: + + + Open modal + + `.trim(); + + const stack = template.firstElementChild!; + + const [exampleText, sandboxText] = [ + ...stack.querySelectorAll('ui-text[emphasis]')!, + ]; + exampleText!.textContent = api.example; + sandboxText!.textContent = api.sandbox; + + stack.querySelector('ui-button')!.append(modal); + + root.append(stack); +} diff --git a/examples/remote-ui/app/host/components.tsx b/examples/remote-ui/app/host/components.tsx index 62ffd3fc..78289b43 100644 --- a/examples/remote-ui/app/host/components.tsx +++ b/examples/remote-ui/app/host/components.tsx @@ -45,7 +45,11 @@ export function Button({ class="Button" type="button" onClick={() => { - onPress?.(); + if (onPress) { + onPress(); + } else { + document.querySelector('dialog')?.showModal(); + } }} > {children} @@ -74,11 +78,16 @@ export const Modal = forwardRef< children?: ComponentChildren; primaryAction?: ComponentChildren; } & ModalProperties ->(function Modal({children, primaryAction}) { +>(function Modal({children, primaryAction, onClose}) { return ( - + ); }); diff --git a/examples/remote-ui/app/host/host.tsx b/examples/remote-ui/app/host/host.tsx index 7975a4b9..72ef4f70 100644 --- a/examples/remote-ui/app/host/host.tsx +++ b/examples/remote-ui/app/host/host.tsx @@ -9,7 +9,8 @@ import { } from '@remote-dom/preact/host'; import {adaptToLegacyRemoteChannel} from '@remote-dom/core/legacy'; import {createEndpoint, fromIframe, retain, release} from '@remote-ui/rpc'; -import {Button, Modal, Stack, Text} from './components.tsx'; +import {Button, Modal, Stack, Text, ModalProvider} from './components.tsx'; +import {EndpointApi} from '../types.ts'; const components = new Map([ ['Text', createRemoteComponentRenderer(Text)], @@ -21,7 +22,7 @@ const components = new Map([ const root = document.querySelector('#root')!; const iframe = document.querySelector('#remote-iframe') as HTMLIFrameElement; -const endpoint = createEndpoint(fromIframe(iframe)); +const endpoint = createEndpoint(fromIframe(iframe)); const receiver = new SignalRemoteReceiver({retain, release}); const channel = adaptToLegacyRemoteChannel(receiver.connection); @@ -32,7 +33,10 @@ render( ); endpoint.call.render(channel, { - showAlert: (message: string) => { + showAlert: async (message: string) => { alert(message); }, + closeModal: async () => { + document.querySelector('dialog')?.close(); + }, }); diff --git a/examples/remote-ui/app/remote/app.tsx b/examples/remote-ui/app/remote/app.tsx index 136f27a9..88f9b5b5 100644 --- a/examples/remote-ui/app/remote/app.tsx +++ b/examples/remote-ui/app/remote/app.tsx @@ -3,58 +3,57 @@ import {useState} from 'react'; import {Button, Modal, Stack, Text} from './components'; +import {RenderAPI} from '../types'; -export function App({api}: {api: any}) { - const [showModal, setShowModal] = useState(false); - const [count, setCount] = useState(0); - - function removeModal() { - setShowModal(false); - } - +export function App({api}: {api: RenderAPI}) { return ( <> Rendering example: remote-ui legacy + + + + ); +} + +function CountModal({showAlert, closeModal}: RenderAPI) { + const [count, setCount] = useState(0); + + const primaryAction = ( + + ); + + return ( + { + if (count > 0) { + showAlert(`You clicked ${count} times!`); + } + + setCount(0); + }} + > + + + Click count: {count} + - } - > - - - Click count: {count} - - - - - ) : undefined - } + onPress={() => { + setCount((count) => count + 1); + }} > - Open modal + Click me! - - + + ); } diff --git a/examples/remote-ui/app/remote/remote.tsx b/examples/remote-ui/app/remote/remote.tsx index 35f6539e..e0cc12d1 100644 --- a/examples/remote-ui/app/remote/remote.tsx +++ b/examples/remote-ui/app/remote/remote.tsx @@ -5,8 +5,10 @@ import {createRoot, createRemoteRoot} from '@remote-ui/react'; import * as components from './components'; import {App} from './app'; +import {EndpointApi} from '../types'; + +const endpoint = createEndpoint(fromInsideIframe()); -const endpoint = createEndpoint(fromInsideIframe()); endpoint.expose({ render(channel, api) { retain(channel); diff --git a/examples/remote-ui/app/style.css b/examples/remote-ui/app/style.css index a04ae88e..1890e081 100644 --- a/examples/remote-ui/app/style.css +++ b/examples/remote-ui/app/style.css @@ -164,6 +164,14 @@ li { background: rgba(9, 9, 11, 0.65); } +.Modal[open] { + animation: OpenModal 0.2s ease normal; +} + +.Modal:not([open]) { + display: none; +} + @keyframes OpenModal { from { opacity: 0; diff --git a/examples/remote-ui/app/types.ts b/examples/remote-ui/app/types.ts index 1eb5780a..143c247b 100644 --- a/examples/remote-ui/app/types.ts +++ b/examples/remote-ui/app/types.ts @@ -2,7 +2,7 @@ // They will be used to describe the properties that are available on the elements that can // be synchronized between the two environments. -import {RemoteFragment} from '@remote-ui/core'; +import {RemoteChannel, RemoteFragment} from '@remote-ui/core'; /** * A `Text` element renders a styled string of text. @@ -62,3 +62,12 @@ export interface StackProperties { */ spacing?: boolean; } + +export interface EndpointApi { + render(channel: RemoteChannel, api: RenderAPI): Promise; +} + +export interface RenderAPI { + closeModal(): void; + showAlert(message: string): void; +}