Files
element-web/packages/shared-components/src/ViewWrapper.tsx
T
David Baker 2698ad422e Move shared components to a packages/ directory (#30962)
* Move shared components to a packages/ directory

so they can be publish more sensibly

* Iterate towards split out shared-components module

 * Move shared component source into src/ subdir
 * Fix up imports
 * Include shared components in babel-ing (again)

* Remove now unused dependencies

* Update import in storybook preview

* ...except of course they aren't unused

if we import the shared components by source

* Ignore shared components deps

* Add shared-components to i18n paths

and upgrade web-i18n to version that supports doing so

* Move storybook stuff to shared-components

* Seems we don't need this anymore...

* Remove unused deps

and remove storybook plugin from eslint

* Presumably working-directory is only valid on run steps

* Ignore dep & run prettier

* Prettier on knips.ts

* Hopefully run in right dir

* Remember how to software write

* Okay... how about THIS way?

* Oh right, they were git ignored. Sigh.

* Add concurrently

* Ignore in knip

* Better?

* Paaaaaaaackageeeeeeees

* More packages

* Move playwright snapshots

* Still need a custom snapshots dir

* Add eslint back

* Oh, now knip sees them

* Fix another import

* Don't lint shared-components with everything else

Okay, eslint & tsconfig are tied too closely for this to work and
running tsc on the shared components will need its deps installing

* Maybe lint shared components

please?

* Not quite

* Remove storybook again

Re-check if it does work without it

* Remove storybook eslint plugin

as we're not linting storybook here anymore

* Remove this too

* We do need it here though
2025-10-13 10:54:50 +00:00

53 lines
1.8 KiB
TypeScript

/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, useMemo, type ComponentType } from "react";
import { omitBy, pickBy } from "lodash";
import { MockViewModel } from "./MockViewModel";
import { type ViewModel } from "./ViewModel";
interface ViewWrapperProps<V> {
/**
* The component to render, which should accept a `vm` prop of type `V`.
*/
Component: ComponentType<{ vm: V }>;
/**
* The props to pass to the component, which can include both snapshot data and actions.
*/
props: Record<string, any>;
}
/**
* A wrapper component that creates a view model instance and passes it to the specified component.
* This is useful for testing components in isolation with a mocked view model and allows to use primitive types in stories.
*
* Props is parsed and split into snapshot and actions. Where values that are functions (`typeof Function`) are considered actions and the rest is considered the snapshot.
*
* @example
* ```tsx
* <ViewWrapper<SnapshotType, ViewModelType> props={Snapshot&Actions} Component={MyComponent} />
* ```
*/
export function ViewWrapper<T, V extends ViewModel<T>>({
props,
Component,
}: Readonly<ViewWrapperProps<V>>): JSX.Element {
const vm = useMemo(() => {
const isFunction = (value: any): value is typeof Function => typeof value === typeof Function;
const snapshot = omitBy(props, isFunction) as T;
const actions = pickBy(props, isFunction);
const vm = new MockViewModel<T>(snapshot);
Object.assign(vm, actions);
return vm as unknown as V;
}, [props]);
return <Component vm={vm} />;
}