(null);
+ const [currentTheme, setCurrentTheme] = useState(getTheme);
+
+ const handleThemeSwitchItemClick: ShellBarItemPropTypes['onClick'] = (e) => {
+ popoverRef.current?.showAt(e.detail.targetRef);
+ };
+ const handleThemeSwitch: ListPropTypes['onSelectionChange'] = (e) => {
+ const { targetItem } = e.detail;
+ setTheme(targetItem.dataset.key!);
+ setCurrentTheme(targetItem.dataset.key!);
+ };
+
+ return (
+ <>
+ {
+ navigate(-1);
+ }}
+ />
+ )
+ }
+ >
+
+
+
+
+ {THEMES.map((theme) => (
+
+ {theme.value}
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/examples/remix-ts/app/components/TodoList.tsx b/examples/remix-ts/app/components/TodoList.tsx
new file mode 100644
index 00000000000..34fa740da44
--- /dev/null
+++ b/examples/remix-ts/app/components/TodoList.tsx
@@ -0,0 +1,32 @@
+import { useNavigate } from '@remix-run/react';
+import { List, ListItemType, ListPropTypes, StandardListItem, ValueState } from '@ui5/webcomponents-react';
+import { Todo } from '~/mockData/todos';
+
+interface TodoListProps {
+ items: Todo[];
+}
+
+export function TodoList({ items }: TodoListProps) {
+ const navigate = useNavigate();
+ const handleTodoClick: ListPropTypes['onItemClick'] = (event) => {
+ navigate(`/todos/${event.detail.item.dataset.id}`);
+ };
+
+ return (
+
+ {items.map((todo) => {
+ return (
+
+ {todo.title}
+
+ );
+ })}
+
+ );
+}
diff --git a/examples/remix-ts/app/entry.client.tsx b/examples/remix-ts/app/entry.client.tsx
new file mode 100644
index 00000000000..966fbc13309
--- /dev/null
+++ b/examples/remix-ts/app/entry.client.tsx
@@ -0,0 +1,22 @@
+/**
+ * By default, Remix will handle hydrating your app on the client for you.
+ * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
+ * For more information, see https://remix.run/file-conventions/entry.client
+ */
+
+import { RemixBrowser } from '@remix-run/react';
+import { startTransition, StrictMode } from 'react';
+import { hydrateRoot } from 'react-dom/client';
+
+import './globals.css';
+import '@ui5/webcomponents-react/styles.css';
+import '@ui5/webcomponents-icons/dist/Assets.js';
+
+startTransition(() => {
+ hydrateRoot(
+ document.getElementById('root')!,
+
+
+
+ );
+});
diff --git a/examples/remix-ts/app/entry.server.tsx b/examples/remix-ts/app/entry.server.tsx
new file mode 100644
index 00000000000..206cc4ebe4b
--- /dev/null
+++ b/examples/remix-ts/app/entry.server.tsx
@@ -0,0 +1,130 @@
+/**
+ * By default, Remix will handle generating the HTTP Response for you.
+ * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
+ * For more information, see https://remix.run/file-conventions/entry.server
+ */
+
+import { PassThrough } from 'node:stream';
+
+import type { AppLoadContext, EntryContext } from '@remix-run/node';
+import { createReadableStreamFromReadable } from '@remix-run/node';
+import { RemixServer } from '@remix-run/react';
+import { isbot } from 'isbot';
+import { renderToPipeableStream } from 'react-dom/server';
+import { renderHeadToString } from 'remix-island';
+import { Head } from './root';
+
+const ABORT_DELAY = 5_000;
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext,
+ // This is ignored so we can keep it in the template for visibility. Feel
+ // free to delete this parameter in your app if you're not using it!
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ loadContext: AppLoadContext
+) {
+ return isbot(request.headers.get('user-agent') || '')
+ ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
+ : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
+}
+
+function handleBotRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext
+) {
+ return new Promise((resolve, reject) => {
+ let shellRendered = false;
+ const { pipe, abort } = renderToPipeableStream(
+ ,
+ {
+ onAllReady() {
+ const head = renderHeadToString({ request, remixContext, Head });
+ shellRendered = true;
+ const body = new PassThrough();
+ const stream = createReadableStreamFromReadable(body);
+
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: responseStatusCode
+ })
+ );
+
+ responseHeaders.set('Content-Type', 'text/html');
+ body.write(`${head}`);
+ pipe(body);
+ body.write('
');
+ },
+ onShellError(error: unknown) {
+ reject(error);
+ },
+ onError(error: unknown) {
+ responseStatusCode = 500;
+ // Log streaming rendering errors from inside the shell. Don't log
+ // errors encountered during initial shell rendering since they'll
+ // reject and get logged in handleDocumentRequest.
+ if (shellRendered) {
+ console.error(error);
+ }
+ }
+ }
+ );
+
+ setTimeout(abort, ABORT_DELAY);
+ });
+}
+
+function handleBrowserRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext
+) {
+ return new Promise((resolve, reject) => {
+ let shellRendered = false;
+ const { pipe, abort } = renderToPipeableStream(
+ ,
+ {
+ onShellReady() {
+ const head = renderHeadToString({ request, remixContext, Head });
+ shellRendered = true;
+ const body = new PassThrough();
+ const stream = createReadableStreamFromReadable(body);
+
+ responseHeaders.set('Content-Type', 'text/html');
+
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: responseStatusCode
+ })
+ );
+
+ body.write(`${head}`);
+
+ pipe(body);
+ body.write('
');
+ },
+ onShellError(error: unknown) {
+ reject(error);
+ },
+ onError(error: unknown) {
+ responseStatusCode = 500;
+ // Log streaming rendering errors from inside the shell. Don't log
+ // errors encountered during initial shell rendering since they'll
+ // reject and get logged in handleDocumentRequest.
+ if (shellRendered) {
+ console.error(error);
+ }
+ }
+ }
+ );
+
+ setTimeout(abort, ABORT_DELAY);
+ });
+}
diff --git a/examples/remix-ts/app/globals.css b/examples/remix-ts/app/globals.css
new file mode 100644
index 00000000000..68f152dfda3
--- /dev/null
+++ b/examples/remix-ts/app/globals.css
@@ -0,0 +1,25 @@
+/* to prevent flickering, only show the web-component when its custom-element is defined */
+:not(:defined) {
+ display: none;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+ padding: 0;
+ margin: 0;
+}
+
+.appShell {
+ height: 100vh;
+ width: 100vw;
+ overflow: hidden;
+}
+
+.appScrollContainer {
+ height: calc(100vh - 3.25rem);
+ width: 100vw;
+ overflow-y: auto;
+ position: relative;
+}
\ No newline at end of file
diff --git a/examples/remix-ts/app/mockData/todos.ts b/examples/remix-ts/app/mockData/todos.ts
new file mode 100644
index 00000000000..9674227bbad
--- /dev/null
+++ b/examples/remix-ts/app/mockData/todos.ts
@@ -0,0 +1,149 @@
+export type Todo = {
+ id: number;
+ title: string;
+ details?: string;
+ completed: boolean;
+};
+
+export const todos: Todo[] = [
+ {
+ id: 1,
+ title: 'Do something nice for someone I care about',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ },
+ {
+ id: 2,
+ title: 'Memorize the fifty states and their capitals',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 3,
+ title: 'Watch a classic movie',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 4,
+ title: 'Contribute code or a monetary donation to an open-source software project',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 5,
+ title: "Solve a Rubik's cube",
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 6,
+ title: 'Bake pastries for me and neighbor',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 7,
+ title: 'Go see a Broadway production',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 8,
+ title: 'Write a thank you letter to an influential person in my life',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ },
+ {
+ id: 9,
+ title: 'Invite some friends over for a game night',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 10,
+ title: 'Have a football scrimmage with some friends',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 11,
+ title: "Text a friend I haven't talked to in a long time",
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 12,
+ title: 'Organize pantry',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ },
+ {
+ id: 13,
+ title: 'Buy a new house decoration',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 14,
+ title: "Plan a vacation I've always wanted to take",
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 15,
+ title: 'Clean out car',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 16,
+ title: 'Draw and color a Mandala',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ },
+ {
+ id: 17,
+ title: 'Create a cookbook with favorite recipes',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 18,
+ title: 'Bake a pie with some friends',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: false
+ },
+ {
+ id: 19,
+ title: 'Create a compost pile',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ },
+ {
+ id: 20,
+ title: 'Take a hike at a local park',
+ details:
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et',
+ completed: true
+ }
+];
diff --git a/examples/remix-ts/app/root.tsx b/examples/remix-ts/app/root.tsx
new file mode 100644
index 00000000000..fc5d7b3b373
--- /dev/null
+++ b/examples/remix-ts/app/root.tsx
@@ -0,0 +1,28 @@
+import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
+import { createHead } from 'remix-island';
+import { AppShell } from './components/AppShell';
+
+export const Head = createHead(() => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+});
+
+export default function App() {
+ // this will be rendered inside a node
+ return (
+ <>
+
+