Files
cinny/src/playground/mocks/PlaygroundProviders.tsx
litruv 0da4f0d6cb Add emoji confetti bursts and component playground.
Support app.relay.emoji_confetti on jumbo emoji clicks with canvas physics,
per-emoji particle profiles, and a fixed overlay that does not affect scroll.
Also add a Vite playground for live component previews and theme hover preview.
2026-07-29 20:40:43 +10:00

107 lines
3.9 KiB
TypeScript

import { ReactNode, useEffect, useMemo, useState } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import {
Box,
OverlayContainerProvider,
PopOutContainerProvider,
TooltipContainerProvider,
configClass,
varsClass,
} from 'folds';
import { MatrixClientProvider } from '@cinny/app/hooks/useMatrixClient';
import { RoomProvider, IsDirectRoomProvider } from '@cinny/app/hooks/useRoom';
import { SpecVersionsProvider } from '@cinny/app/hooks/useSpecVersions';
import { DarkTheme, ThemeContextProvider } from '@cinny/app/hooks/useTheme';
import {
PowerLevelsContextProvider,
type IPowerLevels,
} from '@cinny/app/hooks/usePowerLevels';
import { ContainerColor } from '@cinny/app/styles/ContainerColor.css';
import type { MatrixClient, Room } from 'matrix-js-sdk';
import { MOCK_POWER_LEVELS, createMockRoom, mocks } from './matrix';
type Props = {
children: ReactNode;
client?: MatrixClient;
room?: Room;
isDirect?: boolean;
powerLevels?: IPowerLevels;
};
const SPEC_VERSIONS = {
versions: ['v1.11', 'v1.12'],
unstable_features: {
'org.matrix.msc3916.stable': true,
},
};
/**
* Wraps a preview in the providers Cinny UI pieces usually expect.
* Overlay/popout/tooltip portals stay inside the stage (not the full window).
*/
export function PlaygroundProviders({
children,
client,
room,
isDirect = false,
powerLevels = MOCK_POWER_LEVELS as IPowerLevels,
}: Props) {
const [portal, setPortal] = useState<HTMLDivElement | null>(null);
const queryClient = useMemo(() => new QueryClient(), []);
const resolvedClient = client ?? mocks.client;
// Always provide a room with `.client` set — RoomInput/useStateEvent require it.
const resolvedRoom = useMemo(() => {
if (room) return room;
return createMockRoom({ client: resolvedClient });
}, [room, resolvedClient]);
useEffect(() => {
document.body.classList.add(configClass, varsClass, ...DarkTheme.classNames);
}, []);
return (
<div className="preview-frame">
<ThemeContextProvider value={DarkTheme}>
<TooltipContainerProvider value={portal ?? undefined}>
<PopOutContainerProvider value={portal ?? undefined}>
<OverlayContainerProvider value={portal ?? undefined}>
<QueryClientProvider client={queryClient}>
<JotaiProvider>
<MemoryRouter>
<SpecVersionsProvider value={SPEC_VERSIONS}>
<MatrixClientProvider value={resolvedClient}>
<RoomProvider value={resolvedRoom}>
<IsDirectRoomProvider value={isDirect}>
<PowerLevelsContextProvider value={powerLevels}>
<Box
className={ContainerColor({ variant: 'Surface' })}
style={{
width: '100%',
minHeight: '100%',
height: '100%',
padding: 16,
borderRadius: 8,
}}
>
{children}
</Box>
</PowerLevelsContextProvider>
</IsDirectRoomProvider>
</RoomProvider>
</MatrixClientProvider>
</SpecVersionsProvider>
</MemoryRouter>
</JotaiProvider>
</QueryClientProvider>
</OverlayContainerProvider>
</PopOutContainerProvider>
</TooltipContainerProvider>
</ThemeContextProvider>
{/* Host for folds Overlay/Dialog portals — contained by .preview-frame */}
<div className="preview-portal" ref={setPortal} />
</div>
);
}