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:
@@ -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>;
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+40
-30
@@ -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>,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+36
-30
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+40
-31
@@ -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"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+37
-29
@@ -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>,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+38
-32
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+16
-13
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-12
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-1
@@ -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",
|
||||||
|
|||||||
+23
-17
@@ -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",
|
||||||
|
|||||||
+6
-4
@@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user