Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8fcdc4f
feat(storybook): add Storybook setup and Button component stories
Nov 25, 2025
5523af2
ci(storybook): add GitHub Pages deployment workflow
Nov 25, 2025
5f89c5d
fix(storybook): align Storybook versions and fix TypeScript issues
Nov 25, 2025
1093454
feat(storybook): replace Button stories with Dropdown component stories
Nov 25, 2025
84630ec
feat(storybook): add SettingsPage stories and remove dropdown stories
Nov 25, 2025
6309eb5
feat(storybook): fix SettingsPage stories to show full page and add p…
Nov 25, 2025
599aa30
revert(storybook): restore SettingsPage stories to original working v…
Nov 25, 2025
e54e969
feat(storybook): add stories for ShortcutsPage and GlobalVariablesPag…
Nov 25, 2025
b261777
fix(storybook): add QueryClientProvider to GlobalVariablesPage stories
Nov 25, 2025
07b9b65
fix(storybook): remove WithGeneralSettings story to fix nested router…
Nov 25, 2025
55f0dcb
feat(storybook): enhance SettingsPage stories with multiple states an…
Nov 25, 2025
db7d2f4
fix(storybook): initialize Zustand stores synchronously in SettingsPa…
Nov 25, 2025
bc6bce4
feat(storybook): add story to verify store state accessibility
Nov 25, 2025
e660e94
fix(storybook): remove router from SettingsPage stories to fix errors
Nov 25, 2025
b924ed4
fix(storybook): add router back to SettingsPage stories for useNaviga…
Nov 25, 2025
7324203
fix(storybook): fix router decorator order in SettingsPage stories
Nov 25, 2025
eb61a54
feat(storybook): add PlaygroundPage story as example for complex page…
Nov 26, 2025
f3c5ce4
chore(storybook): remove SettingsPage stories
Nov 26, 2025
255cc35
chore: restore pyproject.toml to match main branch
Nov 26, 2025
847c583
chore: restore pyproject.toml to match main branch
Nov 26, 2025
3c56aa1
Revert "chore: restore pyproject.toml to match main branch"
Nov 26, 2025
6002388
chore: remove src/frontend/pyproject.toml as it doesn't exist in main
Nov 26, 2025
686dce6
fix gitignore and add make commands
Nov 26, 2025
11ac198
update package-json
Nov 26, 2025
8cfe170
chore(storybook): migrate from v8.6.14 to v10.1.0
Nov 26, 2025
d0cd3fe
fix: update package-lock.json to sync with package.json
Nov 26, 2025
67e8974
fix: regenerate package-lock.json with all optional dependencies
Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(storybook): replace Button stories with Dropdown component stories
- Remove Button stories (shadcn already has documentation)
- Add comprehensive Dropdown component stories showcasing:
  - Default, with value, combobox mode
  - With metadata, disabled, loading states
  - Many options, searchable filtering
  - Dark mode support
- Add store initialization decorator for Storybook
- Include interaction testing with play functions
  • Loading branch information
Olfa Maslah Olfa Maslah
Olfa Maslah authored and Olfa Maslah committed Nov 26, 2025
commit 10934545eecb334e42c4d7c0edf95253af72b789
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import type { Meta, StoryObj } from "@storybook/react";
import { expect, userEvent, within } from "@storybook/test";
import { useEffect } from "react";
import useAlertStore from "@/stores/alertStore";
import { useDarkStore } from "@/stores/darkStore";
import useFlowStore from "@/stores/flowStore";
import { useTypesStore } from "@/stores/typesStore";
import type { APIClassType } from "@/types/api";
import type { DropDownComponent } from "@/types/components";
import type { BaseInputProps } from "../parameterRenderComponent/types";
import Dropdown from "./index";

// Initialize stores with minimal mock data for Storybook
const withStoreSetup = (Story: React.ComponentType) => {
const StoreWrapper = () => {
useEffect(() => {
// Initialize flowStore with empty nodes
useFlowStore.setState({
nodes: [],
edges: [],
getNode: () => null,
setFilterEdge: () => {},
setFilterType: () => {},
});

// Initialize typesStore with empty data
useTypesStore.setState({
data: [],
});

// Initialize alertStore (no initial state needed)
useAlertStore.setState({
setErrorData: () => {},
});
}, []);

return <Story />;
};
return <StoreWrapper />;
};

const withDarkMode = (
Story: React.ComponentType,
context: {
args?: { darkMode?: boolean } & DropDownComponent & BaseInputProps;
parameters?: { darkMode?: boolean };
},
) => {
const DarkModeWrapper = () => {
const dark =
context.args?.darkMode || context.parameters?.darkMode === true;
useEffect(() => {
const body = document.getElementById("body") || document.body;
if (dark) {
body.classList.add("dark");
useDarkStore.setState({ dark: true });
} else {
body.classList.remove("dark");
useDarkStore.setState({ dark: false });
}
}, [dark]);
return <Story />;
};
return <DarkModeWrapper />;
};

const mockNodeClass: APIClassType = {
base_classes: [],
description: "Mock component",
display_name: "Mock Component",
documentation: "",
custom_fields: {},
output_types: [],
name: "MockComponent",
beta: false,
error: null,
official: false,
tags: [],
};

const defaultArgs: DropDownComponent & BaseInputProps = {
value: "",
options: ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"],
onSelect: (value: string) => {
console.log("Selected:", value);
},
nodeId: "mock-node-id",
nodeClass: mockNodeClass,
handleNodeClass: () => {},
name: "testField",
disabled: false,
isLoading: false,
editNode: false,
placeholder: "Select an option...",
helperText: "",
hasRefreshButton: false,
};

const meta: Meta<typeof Dropdown> = {
title: "Components/Dropdown",
component: Dropdown,
decorators: [withStoreSetup, withDarkMode],
parameters: { layout: "padded" },
tags: ["autodocs"],
argTypes: {
value: { control: "text" },
options: { control: "object" },
combobox: { control: "boolean" },
disabled: { control: "boolean" },
isLoading: { control: "boolean" },
editNode: { control: "boolean" },
placeholder: { control: "text" },
darkMode: {
control: "boolean",
description: "Toggle dark mode",
table: { category: "Theme" },
},
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
...defaultArgs,
darkMode: false,
},
};

export const WithValue: Story = {
args: {
...defaultArgs,
value: "Option 2",
darkMode: false,
},
};

export const Combobox: Story = {
args: {
...defaultArgs,
combobox: true,
placeholder: "Type or select an option...",
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("combobox");
await userEvent.click(trigger);
const input = canvas.getByPlaceholderText("Type or select an option...");
await userEvent.type(input, "Custom Value");
await expect(input).toHaveValue("Custom Value");
},
};

export const WithMetadata: Story = {
args: {
...defaultArgs,
options: ["OpenAI", "Anthropic", "Cohere"],
optionsMetaData: [
{ provider: "OpenAI", model: "gpt-4", status: "active" },
{ provider: "Anthropic", model: "claude-3", status: "active" },
{ provider: "Cohere", model: "command", status: "beta" },
],
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("combobox");
await userEvent.click(trigger);
// Check that options are visible
const option1 = canvas.getByText("OpenAI");
await expect(option1).toBeVisible();
},
};

export const Disabled: Story = {
args: {
...defaultArgs,
disabled: true,
value: "Option 1",
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("combobox");
await expect(trigger).toBeDisabled();
},
};

export const Loading: Story = {
args: {
...defaultArgs,
isLoading: true,
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Check for loading indicator
const loadingText = canvas.queryByText(/receiving/i);
if (loadingText) {
await expect(loadingText).toBeVisible();
}
},
};

export const ManyOptions: Story = {
args: {
...defaultArgs,
options: Array.from({ length: 50 }, (_, i) => `Option ${i + 1}`),
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("combobox");
await userEvent.click(trigger);
// Verify scrolling works with many options
const option50 = canvas.getByText("Option 50");
await expect(option50).toBeVisible();
},
};

export const Searchable: Story = {
args: {
...defaultArgs,
combobox: true,
options: [
"Python",
"JavaScript",
"TypeScript",
"Java",
"C++",
"Go",
"Rust",
"Swift",
],
placeholder: "Search languages...",
darkMode: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole("combobox");
await userEvent.click(trigger);
const input = canvas.getByPlaceholderText("Search languages...");
await userEvent.type(input, "Script");
// Should filter to JavaScript and TypeScript
await expect(canvas.getByText("JavaScript")).toBeVisible();
await expect(canvas.getByText("TypeScript")).toBeVisible();
await expect(canvas.queryByText("Python")).not.toBeInTheDocument();
},
};

export const DarkMode: Story = {
args: {
...defaultArgs,
darkMode: true,
},
};
Loading