Fix documentation of view component in storybook and migrate to CSF3 format (#32604)

* chore: add a way to keep story doc in wrapper

* chore: use `withViewDocs`

* doc: update SC readme

* doc: update copyright
This commit is contained in:
Florian Duros
2026-02-23 16:03:49 +00:00
committed by GitHub
parent 77670eb369
commit b08cf5fdaa
16 changed files with 422 additions and 258 deletions
@@ -49,6 +49,12 @@ const config: StorybookConfig = {
}, },
typescript: { typescript: {
reactDocgen: "react-docgen-typescript", reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
// The default exclude is ["**/**.stories.tsx"] which prevents
// docgen from extracting snapshot field descriptions from wrapper
// components defined in story files.
exclude: [],
},
}, },
async viteFinal(config) { async viteFinal(config) {
return mergeConfig(config, { return mergeConfig(config, {
@@ -0,0 +1,62 @@
/*
* Copyright 2026 Element Creations 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.
*/
/**
* Copies the component description and props documentation from a View's
* `__docgenInfo` (injected at build time by Storybook's react-docgen-typescript
* Vite plugin) onto the story wrapper component.
*
* This lets Storybook's default `extractComponentDescription` pick up the
* View's JSDoc and display per-field descriptions in the ArgTypes table.
*
* **Important:** the wrapper must be defined as a named variable *before*
* being passed here so that react-docgen-typescript can extract its props.
*
* @example
* ```ts
* const MyViewWrapperImpl = (props: MyViewProps) => {
* const vm = useMockedViewModel(props, {});
* return <MyView vm={vm} />;
* };
* const MyViewWrapper = withViewDocs(MyViewWrapperImpl, MyView);
* ```
*/
export function withViewDocs<T extends (...args: never[]) => unknown>(wrapper: T, view: object): T {
const viewInfo = (view as { __docgenInfo?: DocgenInfo }).__docgenInfo;
const viewDescription = viewInfo?.description;
if (!viewDescription) return wrapper;
// The wrapper must be defined as a named variable (not inline) so that
// react-docgen-typescript can extract its props. The docgen Vite plugin
// appends a `Wrapper.__docgenInfo = { … }` assignment at the *end* of the
// module, which runs **after** this function. We install a setter trap so
// that the View's description is merged into the generated info.
let stored: DocgenInfo | undefined = (wrapper as { __docgenInfo?: DocgenInfo }).__docgenInfo;
Object.defineProperty(wrapper, "__docgenInfo", {
get() {
return stored;
},
set(incoming: DocgenInfo) {
stored = {
...incoming,
description: incoming.description || viewDescription,
};
},
configurable: true,
enumerable: true,
});
// Also apply immediately for the current state.
stored = { ...stored, description: viewDescription };
return wrapper;
}
interface DocgenInfo {
description?: string;
props?: Record<string, unknown>;
}
+31 -15
View File
@@ -68,7 +68,7 @@ instance should be provided as a prop.
Here's a basic example: Here's a basic example:
```jsx ```tsx
import { ViewExample } from "@element-hq/web-shared-components"; import { ViewExample } from "@element-hq/web-shared-components";
function MyApp() { function MyApp() {
@@ -180,27 +180,32 @@ export const Disabled: Story = {
#### MVVM Component Stories #### MVVM Component Stories
For MVVM components, create a wrapper component that uses `useMockedViewModel`: For MVVM components, create a wrapper component that uses `useMockedViewModel` and `withViewDocs`:
```tsx ```tsx
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { MyComponentView, type MyComponentViewSnapshot, type MyComponentViewActions } from "./MyComponentView"; import { MyComponentView, type MyComponentViewSnapshot, type MyComponentViewActions } from "./MyComponentView";
import { useMockedViewModel } from "../../useMockedViewModel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
// Combine snapshot and actions for easier typing // Combine snapshot and actions for easier typing
type MyComponentProps = MyComponentViewSnapshot & MyComponentViewActions; type MyComponentProps = MyComponentViewSnapshot & MyComponentViewActions;
// Wrapper component that creates a mocked ViewModel // Wrapper component that creates a mocked ViewModel.
const MyComponentViewWrapper = ({ onAction, ...rest }: MyComponentProps): JSX.Element => { // Must be a named variable (not inline) for docgen to extract its props.
const MyComponentViewWrapperImpl = ({ onAction, ...rest }: MyComponentProps): JSX.Element => {
const vm = useMockedViewModel(rest, { const vm = useMockedViewModel(rest, {
onAction, onAction,
}); });
return <MyComponentView vm={vm} />; return <MyComponentView vm={vm} />;
}; };
// withViewDocs copies the View's JSDoc description onto the wrapper for Storybook autodocs
const MyComponentViewWrapper = withViewDocs(MyComponentViewWrapperImpl, MyComponentView);
export default { // Must use `satisfies` (not `as` or `: Meta`) to preserve type info for docgen
const meta = {
title: "Category/MyComponentView", title: "Category/MyComponentView",
component: MyComponentViewWrapper, component: MyComponentViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -211,20 +216,29 @@ export default {
// Action properties (callbacks) // Action properties (callbacks)
onAction: fn(), onAction: fn(),
}, },
} as Meta<typeof MyComponentViewWrapper>; } satisfies Meta<typeof MyComponentViewWrapper>;
const Template: StoryFn<typeof MyComponentViewWrapper> = (args) => <MyComponentViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof MyComponentViewWrapper>;
export const Default = Template.bind({}); export const Default: Story = {};
export const Loading = Template.bind({}); export const Loading: Story = {
Loading.args = { args: {
isLoading: true, isLoading: true,
},
}; };
``` ```
Thanks to this approach, we can directly use primitives in the story arguments instead of a view model object. Thanks to this approach, we can directly use primitives in the story arguments instead of a view model object.
> [!IMPORTANT]
> Three requirements must be met for snapshot field documentation to appear in Storybook's ArgTypes table:
>
> 1. **Named wrapper variable** — the wrapper must be assigned to a named `const` (e.g. `MyComponentViewWrapperImpl`) before being passed to `withViewDocs`, so that `react-docgen-typescript` can extract its props.
> 2. **`withViewDocs` call** — wraps the wrapper component with the original View to copy the View's JSDoc description.
> 3. **`satisfies Meta`** — the meta object must use `satisfies Meta<...>` (not `as Meta<...>` or `: Meta<...> =`). Type assertions and annotations erase the inferred component type that docgen relies on.
#### Linking Figma Designs #### Linking Figma Designs
This package uses [@storybook/addon-designs](https://github.com/storybookjs/addon-designs) to embed Figma designs directly in Storybook. This helps developers compare their implementation with the design specs. This package uses [@storybook/addon-designs](https://github.com/storybookjs/addon-designs) to embed Figma designs directly in Storybook. This helps developers compare their implementation with the design specs.
@@ -239,7 +253,7 @@ This package uses [@storybook/addon-designs](https://github.com/storybookjs/addo
Example with Figma integration: Example with Figma integration:
```tsx ```tsx
export default { const meta = {
title: "Room List/RoomListSearchView", title: "Room List/RoomListSearchView",
component: RoomListSearchViewWrapper, component: RoomListSearchViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -252,7 +266,9 @@ export default {
url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=98-1979", url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=98-1979",
}, },
}, },
} as Meta<typeof RoomListSearchViewWrapper>; } satisfies Meta<typeof RoomListSearchViewWrapper>;
export default meta;
``` ```
The Figma design will appear in the "Design" tab in Storybook. The Figma design will appear in the "Design" tab in Storybook.
@@ -8,12 +8,18 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView"; import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions; type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions;
const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...rest }: AudioPlayerProps): JSX.Element => { const AudioPlayerViewWrapperImpl = ({
togglePlay,
onKeyDown,
onSeekbarChange,
...rest
}: AudioPlayerProps): JSX.Element => {
const vm = useMockedViewModel(rest, { const vm = useMockedViewModel(rest, {
togglePlay, togglePlay,
onKeyDown, onKeyDown,
@@ -21,8 +27,9 @@ const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...res
}); });
return <AudioPlayerView vm={vm} />; return <AudioPlayerView vm={vm} />;
}; };
const AudioPlayerViewWrapper = withViewDocs(AudioPlayerViewWrapperImpl, AudioPlayerView);
export default { const meta = {
title: "Audio/AudioPlayerView", title: "Audio/AudioPlayerView",
component: AudioPlayerViewWrapper, component: AudioPlayerViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -44,23 +51,27 @@ export default {
onKeyDown: fn(), onKeyDown: fn(),
onSeekbarChange: fn(), onSeekbarChange: fn(),
}, },
} as Meta<typeof AudioPlayerViewWrapper>; } satisfies Meta<typeof AudioPlayerViewWrapper>;
const Template: StoryFn<typeof AudioPlayerViewWrapper> = (args) => <AudioPlayerViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof meta>;
export const Default = Template.bind({}); export const Default: Story = {};
export const NoMediaName = Template.bind({}); export const NoMediaName: Story = {
NoMediaName.args = { args: {
mediaName: undefined, mediaName: undefined,
},
}; };
export const NoSize = Template.bind({}); export const NoSize: Story = {
NoSize.args = { args: {
sizeBytes: undefined, sizeBytes: undefined,
},
}; };
export const HasError = Template.bind({}); export const HasError: Story = {
HasError.args = { args: {
error: true, error: true,
},
}; };
@@ -7,19 +7,21 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { EncryptionEventView, EncryptionEventState, type EncryptionEventViewSnapshot } from "./EncryptionEventView"; import { EncryptionEventView, EncryptionEventState, type EncryptionEventViewSnapshot } from "./EncryptionEventView";
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel"; import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type EncryptionEventProps = EncryptionEventViewSnapshot; type EncryptionEventProps = EncryptionEventViewSnapshot;
const EncryptionEventViewWrapper = ({ ...rest }: EncryptionEventProps): JSX.Element => { const EncryptionEventViewWrapperImpl = ({ ...rest }: EncryptionEventProps): JSX.Element => {
const vm = useMockedViewModel(rest, {}); const vm = useMockedViewModel(rest, {});
return <EncryptionEventView vm={vm} />; return <EncryptionEventView vm={vm} />;
}; };
const EncryptionEventViewWrapper = withViewDocs(EncryptionEventViewWrapperImpl, EncryptionEventView);
export default { const meta = {
title: "Event/EncryptionEvent", title: "Event/EncryptionEvent",
component: EncryptionEventViewWrapper, component: EncryptionEventViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -37,46 +39,54 @@ export default {
userName: "Alice", userName: "Alice",
className: "", className: "",
}, },
} as Meta<typeof EncryptionEventViewWrapper>; } satisfies Meta<typeof EncryptionEventViewWrapper>;
const Template: StoryFn<typeof EncryptionEventViewWrapper> = (args) => <EncryptionEventViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof meta>;
export const Default = Template.bind({}); export const Default: Story = {};
export const StateEncryptionEnabled = Template.bind({}); export const StateEncryptionEnabled: Story = {
StateEncryptionEnabled.args = { args: {
state: EncryptionEventState.ENABLED, state: EncryptionEventState.ENABLED,
encryptedStateEvents: true, encryptedStateEvents: true,
},
}; };
export const ParametersChanged = Template.bind({}); export const ParametersChanged: Story = {
ParametersChanged.args = { args: {
state: EncryptionEventState.CHANGED, state: EncryptionEventState.CHANGED,
},
}; };
export const DisableAttempt = Template.bind({}); export const DisableAttempt: Story = {
DisableAttempt.args = { args: {
state: EncryptionEventState.DISABLE_ATTEMPT, state: EncryptionEventState.DISABLE_ATTEMPT,
},
}; };
export const EnabledDirectMessage = Template.bind({}); export const EnabledDirectMessage: Story = {
EnabledDirectMessage.args = { args: {
state: EncryptionEventState.ENABLED_DM, state: EncryptionEventState.ENABLED_DM,
userName: "Alice", userName: "Alice",
},
}; };
export const EnabledLocalRoom = Template.bind({}); export const EnabledLocalRoom: Story = {
EnabledLocalRoom.args = { args: {
state: EncryptionEventState.ENABLED_LOCAL, state: EncryptionEventState.ENABLED_LOCAL,
},
}; };
export const Unsupported = Template.bind({}); export const Unsupported: Story = {
Unsupported.args = { args: {
state: EncryptionEventState.UNSUPPORTED, state: EncryptionEventState.UNSUPPORTED,
},
}; };
export const WithTimestamp = Template.bind({}); export const WithTimestamp: Story = {
WithTimestamp.args = { args: {
state: EncryptionEventState.ENABLED, state: EncryptionEventState.ENABLED,
timestamp: <span>14:56</span>, timestamp: <span>14:56</span>,
},
}; };
@@ -7,23 +7,25 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
DecryptionFailureBodyView, DecryptionFailureBodyView,
DecryptionFailureReason, DecryptionFailureReason,
type DecryptionFailureBodyViewSnapshot, type DecryptionFailureBodyViewSnapshot,
} from "./DecryptionFailureBodyView"; } from "./DecryptionFailureBodyView";
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel"; import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type DecryptionFailureBodyProps = DecryptionFailureBodyViewSnapshot; type DecryptionFailureBodyProps = DecryptionFailureBodyViewSnapshot;
const DecryptionFailureBodyViewWrapper = ({ ...rest }: DecryptionFailureBodyProps): JSX.Element => { const DecryptionFailureBodyViewWrapperImpl = ({ ...rest }: DecryptionFailureBodyProps): JSX.Element => {
const vm = useMockedViewModel(rest, {}); const vm = useMockedViewModel(rest, {});
return <DecryptionFailureBodyView vm={vm} />; return <DecryptionFailureBodyView vm={vm} />;
}; };
const DecryptionFailureBodyViewWrapper = withViewDocs(DecryptionFailureBodyViewWrapperImpl, DecryptionFailureBodyView);
export default { const meta = {
title: "MessageBody/DecryptionFailureBodyView", title: "MessageBody/DecryptionFailureBodyView",
component: DecryptionFailureBodyViewWrapper, component: DecryptionFailureBodyViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -40,42 +42,46 @@ export default {
isLocalDeviceVerified: true, isLocalDeviceVerified: true,
extraClassNames: ["extra_class"], extraClassNames: ["extra_class"],
}, },
} as Meta<typeof DecryptionFailureBodyViewWrapper>; } satisfies Meta<typeof DecryptionFailureBodyViewWrapper>;
const Template: StoryFn<typeof DecryptionFailureBodyViewWrapper> = (args) => ( export default meta;
<DecryptionFailureBodyViewWrapper {...args} /> type Story = StoryObj<typeof meta>;
);
export const Default = Template.bind({}); export const Default: Story = {};
export const HasExtraClassNames = Template.bind({}); export const HasExtraClassNames: Story = {
HasExtraClassNames.args = { args: {
decryptionFailureReason: DecryptionFailureReason.UNABLE_TO_DECRYPT, decryptionFailureReason: DecryptionFailureReason.UNABLE_TO_DECRYPT,
extraClassNames: ["extra_class_1", "extra_class_2"], extraClassNames: ["extra_class_1", "extra_class_2"],
},
}; };
export const HasErrorClassName = Template.bind({}); export const HasErrorClassName: Story = {
HasErrorClassName.args = { args: {
decryptionFailureReason: DecryptionFailureReason.UNSIGNED_SENDER_DEVICE, decryptionFailureReason: DecryptionFailureReason.UNSIGNED_SENDER_DEVICE,
extraClassNames: undefined, extraClassNames: undefined,
},
}; };
export const HasErrorBlockIcon = Template.bind({}); export const HasErrorBlockIcon: Story = {
HasErrorBlockIcon.args = { args: {
decryptionFailureReason: DecryptionFailureReason.SENDER_IDENTITY_PREVIOUSLY_VERIFIED, decryptionFailureReason: DecryptionFailureReason.SENDER_IDENTITY_PREVIOUSLY_VERIFIED,
extraClassNames: undefined, extraClassNames: undefined,
},
}; };
export const HasBackupConfiguredVerifiedFalse = Template.bind({}); export const HasBackupConfiguredVerifiedFalse: Story = {
HasBackupConfiguredVerifiedFalse.args = { args: {
decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED, decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED,
isLocalDeviceVerified: false, isLocalDeviceVerified: false,
extraClassNames: undefined, extraClassNames: undefined,
},
}; };
export const HasBackupConfiguredVerifiedTrue = Template.bind({}); export const HasBackupConfiguredVerifiedTrue: Story = {
HasBackupConfiguredVerifiedTrue.args = { args: {
decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED, decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED,
isLocalDeviceVerified: true, isLocalDeviceVerified: true,
extraClassNames: undefined, extraClassNames: undefined,
},
}; };
@@ -8,24 +8,26 @@
import React, { type ReactNode } from "react"; import React, { type ReactNode } from "react";
import { expect, userEvent, within } from "storybook/test"; import { expect, userEvent, within } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
MessageTimestampView, MessageTimestampView,
type MessageTimestampViewActions, type MessageTimestampViewActions,
type MessageTimestampViewSnapshot, type MessageTimestampViewSnapshot,
} from "./MessageTimestampView"; } from "./MessageTimestampView";
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel"; import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type MessageTimestampProps = MessageTimestampViewSnapshot & MessageTimestampViewActions; type MessageTimestampProps = MessageTimestampViewSnapshot & MessageTimestampViewActions;
const MessageTimestampWrapper = ({ onClick, onContextMenu, ...rest }: MessageTimestampProps): ReactNode => { const MessageTimestampWrapperImpl = ({ onClick, onContextMenu, ...rest }: MessageTimestampProps): ReactNode => {
const vm = useMockedViewModel(rest, { const vm = useMockedViewModel(rest, {
onClick, onClick,
onContextMenu, onContextMenu,
}); });
return <MessageTimestampView vm={vm} />; return <MessageTimestampView vm={vm} />;
}; };
const MessageTimestampWrapper = withViewDocs(MessageTimestampWrapperImpl, MessageTimestampView);
export default { const meta = {
title: "MessageBody/MessageTimestamp", title: "MessageBody/MessageTimestamp",
component: MessageTimestampWrapper, component: MessageTimestampWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -37,44 +39,51 @@ export default {
className: "", className: "",
href: "", href: "",
}, },
} as Meta<typeof MessageTimestampWrapper>; } satisfies Meta<typeof MessageTimestampWrapper>;
const Template: StoryFn<typeof MessageTimestampWrapper> = (args) => <MessageTimestampWrapper {...args} />; export default meta;
type Story = StoryObj<typeof meta>;
export const Default = Template.bind({}); export const Default: Story = {
Default.play = async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await userEvent.hover(canvas.getByText("04:58")); await userEvent.hover(canvas.getByText("04:58"));
await expect(within(canvasElement.ownerDocument.body).findByRole("tooltip")).resolves.toBeInTheDocument(); await expect(within(canvasElement.ownerDocument.body).findByRole("tooltip")).resolves.toBeInTheDocument();
},
}; };
export const HasTsReceivedAt = Template.bind({}); export const HasTsReceivedAt: Story = {
HasTsReceivedAt.args = { args: {
tsReceivedAt: "Thu, 17 Nov 2022, 4:58:33 pm", tsReceivedAt: "Thu, 17 Nov 2022, 4:58:33 pm",
}; },
HasTsReceivedAt.play = async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await userEvent.hover(canvas.getByText("04:58")); await userEvent.hover(canvas.getByText("04:58"));
await expect(within(canvasElement.ownerDocument.body).findByRole("tooltip")).resolves.toBeInTheDocument(); await expect(within(canvasElement.ownerDocument.body).findByRole("tooltip")).resolves.toBeInTheDocument();
},
}; };
export const HasInhibitTooltip = Template.bind({}); export const HasInhibitTooltip: Story = {
HasInhibitTooltip.args = { args: {
inhibitTooltip: true, inhibitTooltip: true,
},
}; };
export const HasExtraClassNames = Template.bind({}); export const HasExtraClassNames: Story = {
HasExtraClassNames.args = { args: {
className: "extra_class_1 extra_class_2", className: "extra_class_1 extra_class_2",
},
}; };
export const HasHref = Template.bind({}); export const HasHref: Story = {
HasHref.args = { args: {
href: "~", href: "~",
},
}; };
export const HasActions = Template.bind({}); export const HasActions: Story = {
HasActions.args = { args: {
onClick: () => console.log("Clicked message timestamp"), onClick: () => console.log("Clicked message timestamp"),
onContextMenu: () => console.log("Context menu on message timestamp"), onContextMenu: () => console.log("Context menu on message timestamp"),
},
}; };
@@ -7,8 +7,9 @@
import React, { type JSX, type PropsWithChildren } from "react"; import React, { type JSX, type PropsWithChildren } from "react";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { import {
ReactionsRowButtonTooltipView, ReactionsRowButtonTooltipView,
type ReactionsRowButtonTooltipViewSnapshot, type ReactionsRowButtonTooltipViewSnapshot,
@@ -16,12 +17,16 @@ import {
type WrapperProps = ReactionsRowButtonTooltipViewSnapshot & PropsWithChildren; type WrapperProps = ReactionsRowButtonTooltipViewSnapshot & PropsWithChildren;
const ReactionsRowButtonTooltipViewWrapper = ({ children, ...snapshotProps }: WrapperProps): JSX.Element => { const ReactionsRowButtonTooltipViewWrapperImpl = ({ children, ...snapshotProps }: WrapperProps): JSX.Element => {
const vm = useMockedViewModel(snapshotProps, {}); const vm = useMockedViewModel(snapshotProps, {});
return <ReactionsRowButtonTooltipView vm={vm}>{children}</ReactionsRowButtonTooltipView>; return <ReactionsRowButtonTooltipView vm={vm}>{children}</ReactionsRowButtonTooltipView>;
}; };
const ReactionsRowButtonTooltipViewWrapper = withViewDocs(
ReactionsRowButtonTooltipViewWrapperImpl,
ReactionsRowButtonTooltipView,
);
export default { const meta = {
title: "MessageBody/ReactionsRowButtonTooltip", title: "MessageBody/ReactionsRowButtonTooltip",
component: ReactionsRowButtonTooltipViewWrapper, component: ReactionsRowButtonTooltipViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -32,38 +37,41 @@ export default {
args: { args: {
children: <button>👍 3</button>, children: <button>👍 3</button>,
}, },
} as Meta<typeof ReactionsRowButtonTooltipViewWrapper>; } satisfies Meta<typeof ReactionsRowButtonTooltipViewWrapper>;
const Template: StoryFn<typeof ReactionsRowButtonTooltipViewWrapper> = (args) => ( export default meta;
<ReactionsRowButtonTooltipViewWrapper {...args} /> type Story = StoryObj<typeof meta>;
);
export const Default = Template.bind({}); export const Default: Story = {
Default.args = { args: {
formattedSenders: "Alice, Bob and Charlie", formattedSenders: "Alice, Bob and Charlie",
caption: ":thumbsup:", caption: ":thumbsup:",
tooltipOpen: true, tooltipOpen: true,
},
}; };
export const ManySenders = Template.bind({}); export const ManySenders: Story = {
ManySenders.args = { args: {
formattedSenders: "Alice, Bob, Charlie, David, Eve, Frank and 2 others", formattedSenders: "Alice, Bob, Charlie, David, Eve, Frank and 2 others",
caption: ":heart:", caption: ":heart:",
children: <button> 8</button>, children: <button> 8</button>,
tooltipOpen: true, tooltipOpen: true,
},
}; };
export const WithoutCaption = Template.bind({}); export const WithoutCaption: Story = {
WithoutCaption.args = { args: {
formattedSenders: "Alice and Bob", formattedSenders: "Alice and Bob",
caption: undefined, caption: undefined,
children: <button>🎉 2</button>, children: <button>🎉 2</button>,
tooltipOpen: true, tooltipOpen: true,
},
}; };
export const NoTooltip = Template.bind({}); export const NoTooltip: Story = {
NoTooltip.args = { args: {
formattedSenders: undefined, formattedSenders: undefined,
caption: undefined, caption: undefined,
children: <button>👍 1</button>, children: <button>👍 1</button>,
},
}; };
@@ -8,22 +8,24 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
DisambiguatedProfileView, DisambiguatedProfileView,
type DisambiguatedProfileViewSnapshot, type DisambiguatedProfileViewSnapshot,
type DisambiguatedProfileViewActions, type DisambiguatedProfileViewActions,
} from "./DisambiguatedProfileView"; } from "./DisambiguatedProfileView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type DisambiguatedProfileProps = DisambiguatedProfileViewSnapshot & DisambiguatedProfileViewActions; type DisambiguatedProfileProps = DisambiguatedProfileViewSnapshot & DisambiguatedProfileViewActions;
const DisambiguatedProfileViewWrapper = ({ onClick, ...rest }: DisambiguatedProfileProps): JSX.Element => { const DisambiguatedProfileViewWrapperImpl = ({ onClick, ...rest }: DisambiguatedProfileProps): JSX.Element => {
const vm = useMockedViewModel(rest, { onClick }); const vm = useMockedViewModel(rest, { onClick });
return <DisambiguatedProfileView vm={vm} />; return <DisambiguatedProfileView vm={vm} />;
}; };
const DisambiguatedProfileViewWrapper = withViewDocs(DisambiguatedProfileViewWrapperImpl, DisambiguatedProfileView);
export default { const meta = {
title: "Profile/DisambiguatedProfile", title: "Profile/DisambiguatedProfile",
component: DisambiguatedProfileViewWrapper, component: DisambiguatedProfileViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -40,44 +42,48 @@ export default {
emphasizeDisplayName: true, emphasizeDisplayName: true,
onClick: fn(), onClick: fn(),
}, },
} as Meta<typeof DisambiguatedProfileViewWrapper>; } satisfies Meta<typeof DisambiguatedProfileViewWrapper>;
const Template: StoryFn<typeof DisambiguatedProfileViewWrapper> = (args) => ( export default meta;
<DisambiguatedProfileViewWrapper {...args} /> type Story = StoryObj<typeof meta>;
);
export const Default = Template.bind({}); export const Default: Story = {};
export const WithMxid = Template.bind({}); export const WithMxid: Story = {
WithMxid.args = { args: {
displayName: "Alice", displayName: "Alice",
displayIdentifier: "@alice:example.org", displayIdentifier: "@alice:example.org",
colorClass: "mx_Username_color1", colorClass: "mx_Username_color1",
},
}; };
export const WithColorClass = Template.bind({}); export const WithColorClass: Story = {
WithColorClass.args = { args: {
displayName: "Bob", displayName: "Bob",
colorClass: "mx_Username_color3", colorClass: "mx_Username_color3",
},
}; };
export const Emphasized = Template.bind({}); export const Emphasized: Story = {
Emphasized.args = { args: {
displayName: "Charlie", displayName: "Charlie",
emphasizeDisplayName: true, emphasizeDisplayName: true,
},
}; };
export const WithTooltip = Template.bind({}); export const WithTooltip: Story = {
WithTooltip.args = { args: {
displayName: "Diana", displayName: "Diana",
title: "Diana (@diana:example.org)", title: "Diana (@diana:example.org)",
},
}; };
export const FullExample = Template.bind({}); export const FullExample: Story = {
FullExample.args = { args: {
displayName: "Eve", displayName: "Eve",
displayIdentifier: "@eve:matrix.org", displayIdentifier: "@eve:matrix.org",
colorClass: "mx_Username_color5", colorClass: "mx_Username_color5",
title: "Eve (@eve:matrix.org)", title: "Eve (@eve:matrix.org)",
emphasizeDisplayName: true, emphasizeDisplayName: true,
},
}; };
@@ -10,17 +10,18 @@ import { fn } from "storybook/test";
import { IconButton } from "@vector-im/compound-web"; import { IconButton } from "@vector-im/compound-web";
import TriggerIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal"; import TriggerIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
type WidgetContextMenuAction, type WidgetContextMenuAction,
type WidgetContextMenuSnapshot, type WidgetContextMenuSnapshot,
WidgetContextMenuView, WidgetContextMenuView,
} from "./WidgetContextMenuView"; } from "./WidgetContextMenuView";
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel"; import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type WidgetContextMenuViewModelProps = WidgetContextMenuSnapshot & WidgetContextMenuAction; type WidgetContextMenuViewModelProps = WidgetContextMenuSnapshot & WidgetContextMenuAction;
const WidgetContextMenuViewWrapper = ({ const WidgetContextMenuViewWrapperImpl = ({
onStreamAudioClick, onStreamAudioClick,
onEditClick, onEditClick,
onSnapshotClick, onSnapshotClick,
@@ -41,8 +42,9 @@ const WidgetContextMenuViewWrapper = ({
}); });
return <WidgetContextMenuView vm={vm} />; return <WidgetContextMenuView vm={vm} />;
}; };
const WidgetContextMenuViewWrapper = withViewDocs(WidgetContextMenuViewWrapperImpl, WidgetContextMenuView);
export default { const meta = {
title: "RightPanel/WidgetContextMenuView", title: "RightPanel/WidgetContextMenuView",
component: WidgetContextMenuViewWrapper, component: WidgetContextMenuViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -54,7 +56,6 @@ export default {
showSnapshotButton: true, showSnapshotButton: true,
showMoveButtons: [true, true], showMoveButtons: [true, true],
canModify: true, canModify: true,
widgetMessaging: undefined,
isMenuOpened: true, isMenuOpened: true,
trigger: ( trigger: (
<IconButton size="24px" aria-label="context menu trigger button" inert={true} tabIndex={-1}> <IconButton size="24px" aria-label="context menu trigger button" inert={true} tabIndex={-1}>
@@ -69,16 +70,18 @@ export default {
onFinished: fn(), onFinished: fn(),
onMoveButton: fn(), onMoveButton: fn(),
}, },
} as Meta<typeof WidgetContextMenuViewWrapper>; } satisfies Meta<typeof WidgetContextMenuViewWrapper>;
const Template: StoryFn<typeof WidgetContextMenuViewWrapper> = (args) => <WidgetContextMenuViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof WidgetContextMenuViewWrapper>;
export const Default = Template.bind({}); export const Default: Story = {};
export const OnlyBasicModification = Template.bind({}); export const OnlyBasicModification: Story = {
OnlyBasicModification.args = { args: {
showSnapshotButton: false, showSnapshotButton: false,
showMoveButtons: [false, false], showMoveButtons: [false, false],
showStreamAudioStreamButton: false, showStreamAudioStreamButton: false,
showEditButton: false, showEditButton: false,
},
}; };
@@ -8,18 +8,19 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
RoomListHeaderView, RoomListHeaderView,
type RoomListHeaderViewActions, type RoomListHeaderViewActions,
type RoomListHeaderViewSnapshot, type RoomListHeaderViewSnapshot,
} from "./RoomListHeaderView"; } from "./RoomListHeaderView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { defaultSnapshot } from "./default-snapshot"; import { defaultSnapshot } from "./default-snapshot";
type RoomListHeaderProps = RoomListHeaderViewSnapshot & RoomListHeaderViewActions; type RoomListHeaderProps = RoomListHeaderViewSnapshot & RoomListHeaderViewActions;
const RoomListHeaderViewWrapper = ({ const RoomListHeaderViewWrapperImpl = ({
createChatRoom, createChatRoom,
createRoom, createRoom,
createVideoRoom, createVideoRoom,
@@ -44,8 +45,9 @@ const RoomListHeaderViewWrapper = ({
}); });
return <RoomListHeaderView vm={vm} />; return <RoomListHeaderView vm={vm} />;
}; };
const RoomListHeaderViewWrapper = withViewDocs(RoomListHeaderViewWrapperImpl, RoomListHeaderView);
export default { const meta = {
title: "Room List/RoomListHeaderView", title: "Room List/RoomListHeaderView",
component: RoomListHeaderViewWrapper, component: RoomListHeaderViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -67,18 +69,21 @@ export default {
url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=2925-19173", url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=2925-19173",
}, },
}, },
} as Meta<typeof RoomListHeaderViewWrapper>; } satisfies Meta<typeof RoomListHeaderViewWrapper>;
const Template: StoryFn<typeof RoomListHeaderViewWrapper> = (args) => <RoomListHeaderViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof meta>;
export const Default = Template.bind({}); export const Default: Story = {};
export const NoSpaceMenu = Template.bind({}); export const NoSpaceMenu: Story = {
NoSpaceMenu.args = { args: {
displaySpaceMenu: false, displaySpaceMenu: false,
},
}; };
export const NoComposeMenu = Template.bind({}); export const NoComposeMenu: Story = {
NoComposeMenu.args = { args: {
displayComposeMenu: false, displayComposeMenu: false,
},
}; };
@@ -12,6 +12,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
import type { Room } from "./RoomListItemView"; import type { Room } from "./RoomListItemView";
import { RoomListItemView, type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItemView"; import { RoomListItemView, type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItemView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { defaultSnapshot } from "./default-snapshot"; import { defaultSnapshot } from "./default-snapshot";
import { renderAvatar } from "../story-mocks"; import { renderAvatar } from "../story-mocks";
@@ -26,7 +27,7 @@ type RoomListItemProps = RoomListItemSnapshot &
}; };
// Wrapper component that creates a mocked ViewModel // Wrapper component that creates a mocked ViewModel
const RoomListItemWrapper = ({ const RoomListItemWrapperImpl = ({
onOpenRoom, onOpenRoom,
onMarkAsRead, onMarkAsRead,
onMarkAsUnread, onMarkAsUnread,
@@ -67,6 +68,7 @@ const RoomListItemWrapper = ({
/> />
); );
}; };
const RoomListItemWrapper = withViewDocs(RoomListItemWrapperImpl, RoomListItemView);
const meta = { const meta = {
title: "Room List/RoomListItemView", title: "Room List/RoomListItemView",
@@ -8,17 +8,18 @@
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import { import {
RoomListSearchView, RoomListSearchView,
type RoomListSearchViewActions, type RoomListSearchViewActions,
type RoomListSearchViewSnapshot, type RoomListSearchViewSnapshot,
} from "./RoomListSearchView"; } from "./RoomListSearchView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
type RoomListSearchProps = RoomListSearchViewSnapshot & RoomListSearchViewActions; type RoomListSearchProps = RoomListSearchViewSnapshot & RoomListSearchViewActions;
const RoomListSearchViewWrapper = ({ const RoomListSearchViewWrapperImpl = ({
onSearchClick, onSearchClick,
onDialPadClick, onDialPadClick,
onExploreClick, onExploreClick,
@@ -31,8 +32,9 @@ const RoomListSearchViewWrapper = ({
}); });
return <RoomListSearchView vm={vm} />; return <RoomListSearchView vm={vm} />;
}; };
const RoomListSearchViewWrapper = withViewDocs(RoomListSearchViewWrapperImpl, RoomListSearchView);
export default { const meta = {
title: "Room List/RoomListSearchView", title: "Room List/RoomListSearchView",
component: RoomListSearchViewWrapper, component: RoomListSearchViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -50,25 +52,29 @@ export default {
url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel-2025?node-id=98-1979&t=vafb4zoYMNLRuAbh-4", url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel-2025?node-id=98-1979&t=vafb4zoYMNLRuAbh-4",
}, },
}, },
} as Meta<typeof RoomListSearchViewWrapper>; } satisfies Meta<typeof RoomListSearchViewWrapper>;
const Template: StoryFn<typeof RoomListSearchViewWrapper> = (args) => <RoomListSearchViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof meta>;
export const Default = Template.bind({}); export const Default: Story = {};
export const WithDialPad = Template.bind({}); export const WithDialPad: Story = {
WithDialPad.args = { args: {
displayDialButton: true, displayDialButton: true,
},
}; };
export const WithoutExplore = Template.bind({}); export const WithoutExplore: Story = {
WithoutExplore.args = { args: {
displayExploreButton: false, displayExploreButton: false,
},
}; };
export const AllButtons = Template.bind({}); export const AllButtons: Story = {
AllButtons.args = { args: {
displayExploreButton: true, displayExploreButton: true,
displayDialButton: true, displayDialButton: true,
searchShortcut: "⌘ K", searchShortcut: "⌘ K",
},
}; };
@@ -13,6 +13,7 @@ import type { Room } from "../RoomListItemView";
import type { FilterId } from "../RoomListPrimaryFilters"; import type { FilterId } from "../RoomListPrimaryFilters";
import { RoomListView, type RoomListSnapshot, type RoomListViewActions } from "./RoomListView"; import { RoomListView, type RoomListSnapshot, type RoomListViewActions } from "./RoomListView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { import {
renderAvatar, renderAvatar,
createGetRoomItemViewModel, createGetRoomItemViewModel,
@@ -26,7 +27,7 @@ type RoomListViewProps = RoomListSnapshot & RoomListViewActions & { renderAvatar
const mockFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite"]; const mockFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite"];
// Wrapper component that creates a mocked ViewModel // Wrapper component that creates a mocked ViewModel
const RoomListViewWrapper = ({ const RoomListViewWrapperImpl = ({
onToggleFilter, onToggleFilter,
createChatRoom, createChatRoom,
createRoom, createRoom,
@@ -44,6 +45,7 @@ const RoomListViewWrapper = ({
}); });
return <RoomListView vm={vm} renderAvatar={renderAvatarProp} />; return <RoomListView vm={vm} renderAvatar={renderAvatarProp} />;
}; };
const RoomListViewWrapper = withViewDocs(RoomListViewWrapperImpl, RoomListView);
const meta = { const meta = {
title: "Room List/RoomListView", title: "Room List/RoomListView",
@@ -13,6 +13,7 @@ import type { Room } from "../RoomListItemView";
import { VirtualizedRoomListView, type RoomListViewState } from "./VirtualizedRoomListView"; import { VirtualizedRoomListView, type RoomListViewState } from "./VirtualizedRoomListView";
import type { RoomListSnapshot, RoomListViewActions } from "../RoomListView"; import type { RoomListSnapshot, RoomListViewActions } from "../RoomListView";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import type { FilterId } from "../RoomListPrimaryFilters"; import type { FilterId } from "../RoomListPrimaryFilters";
import { renderAvatar, createGetRoomItemViewModel, mockRoomIds } from "../story-mocks"; import { renderAvatar, createGetRoomItemViewModel, mockRoomIds } from "../story-mocks";
@@ -22,7 +23,7 @@ type RoomListStoryProps = RoomListSnapshot & RoomListViewActions & { renderAvata
const storyRoomIds = mockRoomIds.slice(0, 10); const storyRoomIds = mockRoomIds.slice(0, 10);
// Wrapper component that creates a mocked ViewModel // Wrapper component that creates a mocked ViewModel
const RoomListWrapper = ({ const RoomListWrapperImpl = ({
onToggleFilter, onToggleFilter,
createChatRoom, createChatRoom,
createRoom, createRoom,
@@ -45,6 +46,7 @@ const RoomListWrapper = ({
</div> </div>
); );
}; };
const RoomListWrapper = withViewDocs(RoomListWrapperImpl, VirtualizedRoomListView);
const mockFilterIds: FilterId[] = ["unread", "people"]; const mockFilterIds: FilterId[] = ["unread", "people"];
@@ -54,7 +56,7 @@ const defaultRoomListState: RoomListViewState = {
filterKeys: undefined, filterKeys: undefined,
}; };
const meta: Meta<RoomListStoryProps> = { const meta = {
title: "Room List/VirtualizedRoomListView", title: "Room List/VirtualizedRoomListView",
component: RoomListWrapper, component: RoomListWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -86,9 +88,9 @@ const meta: Meta<RoomListStoryProps> = {
</div> </div>
), ),
], ],
}; } satisfies Meta<typeof RoomListWrapper>;
export default meta; export default meta;
type Story = StoryObj<RoomListStoryProps>; type Story = StoryObj<typeof meta>;
export const Default: Story = {}; export const Default: Story = {};
@@ -4,11 +4,12 @@
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial * 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. * Please see LICENSE files in the repository root for full details.
*/ */
import { type Meta, type StoryFn } from "@storybook/react-vite"; import { type Meta, type StoryObj } from "@storybook/react-vite";
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import { useMockedViewModel } from "../../viewmodel"; import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { import {
RoomStatusBarState, RoomStatusBarState,
RoomStatusBarView, RoomStatusBarView,
@@ -18,7 +19,7 @@ import {
type RoomStatusBarProps = RoomStatusBarViewSnapshot & RoomStatusBarViewActions; type RoomStatusBarProps = RoomStatusBarViewSnapshot & RoomStatusBarViewActions;
const RoomStatusBarViewWrapper = ({ const RoomStatusBarViewWrapperImpl = ({
onResendAllClick, onResendAllClick,
onDeleteAllClick, onDeleteAllClick,
onRetryRoomCreationClick, onRetryRoomCreationClick,
@@ -33,8 +34,9 @@ const RoomStatusBarViewWrapper = ({
}); });
return <RoomStatusBarView vm={vm} />; return <RoomStatusBarView vm={vm} />;
}; };
const RoomStatusBarViewWrapper = withViewDocs(RoomStatusBarViewWrapperImpl, RoomStatusBarView);
export default { const meta = {
title: "room/RoomStatusBarView", title: "room/RoomStatusBarView",
component: RoomStatusBarViewWrapper, component: RoomStatusBarViewWrapper,
tags: ["autodocs"], tags: ["autodocs"],
@@ -45,61 +47,69 @@ export default {
onRetryRoomCreationClick: fn(), onRetryRoomCreationClick: fn(),
onTermsAndConditionsClicked: fn(), onTermsAndConditionsClicked: fn(),
}, },
} as Meta<typeof RoomStatusBarViewWrapper>; } satisfies Meta<typeof RoomStatusBarViewWrapper>;
const Template: StoryFn<typeof RoomStatusBarViewWrapper> = (args) => <RoomStatusBarViewWrapper {...args} />; export default meta;
type Story = StoryObj<typeof RoomStatusBarViewWrapper>;
/** /**
* Rendered when the client has lost connection with the server. * Rendered when the client has lost connection with the server.
*/ */
export const WithConnectionLost = Template.bind({}); export const WithConnectionLost: Story = {
WithConnectionLost.args = { args: {
state: RoomStatusBarState.ConnectionLost, state: RoomStatusBarState.ConnectionLost,
},
}; };
/** /**
* Rendered when the client needs the user to consent to some terms and conditions before * Rendered when the client needs the user to consent to some terms and conditions before
* they can perform any room actions. * they can perform any room actions.
*/ */
export const WithConsentLink = Template.bind({}); export const WithConsentLink: Story = {
WithConsentLink.args = { args: {
state: RoomStatusBarState.NeedsConsent, state: RoomStatusBarState.NeedsConsent,
consentUri: "#example", consentUri: "#example",
},
}; };
/** /**
* Rendered when the server has hit a usage limit and is forbidding the user from performing * Rendered when the server has hit a usage limit and is forbidding the user from performing
* any actions in the room. There is an optional parameter to link to an admin to contact. * any actions in the room. There is an optional parameter to link to an admin to contact.
*/ */
export const WithResourceLimit = Template.bind({}); export const WithResourceLimit: Story = {
WithResourceLimit.args = { args: {
state: RoomStatusBarState.ResourceLimited, state: RoomStatusBarState.ResourceLimited,
resourceLimit: "hs_disabled", resourceLimit: "hs_disabled",
adminContactHref: "#example", adminContactHref: "#example",
},
}; };
/** /**
* Rendered when the client has some unsent messages in the room, stored locally. * Rendered when the client has some unsent messages in the room, stored locally.
*/ */
export const WithUnsentMessages = Template.bind({}); export const WithUnsentMessages: Story = {
WithUnsentMessages.args = { args: {
state: RoomStatusBarState.UnsentMessages, state: RoomStatusBarState.UnsentMessages,
isResending: false, isResending: false,
},
}; };
/** /**
* Rendered when the client has some unsent messages in the room, stored locally and is * Rendered when the client has some unsent messages in the room, stored locally and is
* trying to send them. * trying to send them.
*/ */
export const WithUnsentMessagesSending = Template.bind({}); export const WithUnsentMessagesSending: Story = {
WithUnsentMessagesSending.args = { args: {
state: RoomStatusBarState.UnsentMessages, state: RoomStatusBarState.UnsentMessages,
isResending: true, isResending: true,
},
}; };
/** /**
* Rendered when a local room has failed to be created. * Rendered when a local room has failed to be created.
*/ */
export const WithLocalRoomRetry = Template.bind({}); export const WithLocalRoomRetry: Story = {
WithLocalRoomRetry.args = { args: {
state: RoomStatusBarState.LocalRoomFailed, state: RoomStatusBarState.LocalRoomFailed,
},
}; };