diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d935b51f..16ac2125 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -288,6 +288,7 @@ For detailed package-specific guidance, see: - `packages/addon-mcp/**` → `.github/instructions/addon-mcp.instructions.md` - `packages/mcp/**` → `.github/instructions/mcp.instructions.md` +- `eval/**` → `.github/instructions/eval.instructions.md` ## Documentation resources diff --git a/.github/instructions/eval.instructions.md b/.github/instructions/eval.instructions.md new file mode 100644 index 00000000..df9dab19 --- /dev/null +++ b/.github/instructions/eval.instructions.md @@ -0,0 +1,640 @@ +````instructions +--- +applyTo: 'eval/**' +--- + +# Copilot Instructions for Storybook MCP Eval Framework + +## Project Overview + +This is an evaluation framework for testing AI coding agents' ability to build UI components using Storybook and MCP tools. The framework automates the process of running experiments, executing agents with prompts, and evaluating the results through multiple quality metrics. + +**Core Purpose**: Measure how effectively AI agents can use Storybook's MCP tools to build production-quality UI components. + +## Architecture + +### Framework Flow + +1. **Prepare**: Create fresh Vite + React + Storybook project from template +2. **Execute**: Run AI agent (Claude Code CLI) with prompt and optional context +3. **Evaluate**: Run automated checks (build, typecheck, lint, tests, a11y) +4. **Report**: Generate metrics, save results, optionally upload to Google Sheets + +### Key Components + +- **CLI Interface**: Interactive and non-interactive modes via `eval.ts` +- **Context System**: Four context modes (none, component manifest, MCP server, extra prompts) +- **Agent Abstraction**: Pluggable agent implementations (currently Claude Code CLI) +- **Evaluation Pipeline**: Parallel execution of multiple quality checks +- **Hooks System**: Lifecycle hooks for custom experiment logic +- **Telemetry**: Optional results upload to Google Sheets for tracking + +### File Structure + +``` +eval/ +├── eval.ts # Main CLI entry point +├── types.ts # Core types and schemas +├── lib/ +│ ├── collect-args.ts # Interactive CLI argument collection +│ ├── show-help.ts # Help text formatting +│ ├── generate-prompt.ts # Combines prompt parts +│ ├── prepare-experiment.ts # Project template setup +│ ├── agents/ +│ │ └── claude-code-cli.ts # Claude Code CLI agent implementation +│ └── evaluations/ +│ ├── evaluate.ts # Main evaluation orchestrator +│ ├── prepare-evaluations.ts # Install test dependencies +│ ├── build.ts # Vite build check +│ ├── typecheck.ts # TypeScript checking +│ ├── lint.ts # ESLint execution +│ ├── test-stories.ts # Vitest + a11y testing +│ ├── environment.ts # Git branch/commit tracking +│ └── save-to-sheets.ts # Google Sheets upload +├── evals/ # Evaluation definitions +│ └── {number}-{name}/ +│ ├── prompt.md # Main prompt +│ ├── hooks.ts # Optional lifecycle hooks +│ ├── components.json # Optional component manifest +│ ├── mcp.config.json # Optional MCP server config +│ ├── *.md # Optional additional context +│ ├── expected/ # Expected output for reference +│ └── experiments/ # Generated experiment runs +└── templates/ + ├── project/ # Base Vite + React template + └── evaluation/ # Test/lint configs +``` + +### Context Modes + +The framework supports four distinct context types: + +1. **No Context** (`--no-context`): + - Agent uses only built-in tools + - Tests baseline agent capabilities + +2. **Component Manifest** (`--context components.json`): + - Provides component documentation via `@storybook/mcp` + - Uses stdio transport with `packages/mcp/bin.ts` + - Best for testing agents with library/component documentation + +3. **MCP Server** (`--context mcp.config.json` or inline JSON): + - Custom MCP server configuration (HTTP or stdio) + - Supports multiple named servers + - Flexible for testing different MCP tool combinations + +4. **Extra Prompts** (`--context extra-prompt-01.md,extra-prompt-02.md`): + - Appends additional markdown files to main prompt + - Useful for providing supplementary instructions + - Keeps main prompt clean while testing variations + +## Development Workflow + +### Prerequisites + +- Node.js 24+ (see root `.nvmrc`) +- pnpm 10.19.0+ (monorepo root enforces this) +- Claude Code CLI: `npm install -g claude-code` + +### Running Evaluations + +**Interactive mode (recommended):** +```bash +cd eval +node eval.ts +``` + +**Non-interactive mode:** +```bash +node eval.ts --agent claude-code --context components.json --upload 100-flight-booking-plain +``` + +**Get help:** +```bash +node eval.ts --help +``` + +### Creating a New Eval + +1. **Create directory:** + ```bash + mkdir evals/200-my-component + ``` + +2. **Write `prompt.md`:** + ```markdown + Build a SearchBar component with autocomplete... + + + 1. Component MUST be default export in src/components/SearchBar.tsx + 2. Component MUST have data-testid="search-bar" + + ``` + +3. **Optional: Add context files:** + - `components.json` - Component manifest for `@storybook/mcp` + - `mcp.config.json` - Custom MCP server configuration + - `extra-prompt-*.md` - Supplementary instructions + +4. **Optional: Create `hooks.ts`:** + ```typescript + import type { Hooks } from '../../types.ts'; + + export default { + async postPrepareExperiment(args, log) { + // Custom setup (e.g., copy fixture data) + await fs.cp( + path.join(args.evalPath, 'fixtures'), + path.join(args.projectPath, 'public/fixtures'), + { recursive: true } + ); + } + } satisfies Hooks; + ``` + +5. **Optional: Create `expected/` directory:** + - Add reference implementation in `expected/src/components/` + - Add expected stories in `expected/stories/` + - Used for comparison during evaluation + +### Viewing Results + +**Conversation viewer (visualize agent activity):** +```bash +open conversation-viewer.html +# Select the full-conversation.js file from results/ +``` + +**Inspect generated project:** +```bash +cd evals/{eval-name}/experiments/{experiment-name}/project +pnpm storybook +``` + +## Code Style and Conventions + +### TypeScript Configuration + +- Uses `@tsconfig/node24` as base +- Module system: ESM with `"type": "module"` +- Module resolution: `bundler` +- Strict mode enabled + +### Code Style + +- **Always include file extensions in imports**: `import { foo } from './bar.ts'` +- Use Valibot for schema validation (see `types.ts` and `collect-args.ts`) +- Prefer async/await over callbacks +- Use `taskLog` (verbose) or `spinner` (normal) for user feedback +- Export types explicitly + +### Naming Conventions + +- Constants: SCREAMING_SNAKE_CASE (rare in this codebase) +- Functions: camelCase (e.g., `collectArgs`, `prepareExperiment`) +- Types/Interfaces: PascalCase (e.g., `ExperimentArgs`, `Context`) + +### Argument Parsing Pattern + +The `collect-args.ts` module demonstrates a robust pattern for handling CLI arguments: + +1. Parse raw args with `node:util.parseArgs` +2. Validate with Valibot schemas (including async transformations) +3. Prompt for missing values using `@clack/prompts` +4. Build rerun command incrementally for user convenience +5. Return fully-resolved arguments + +**Example:** +```typescript +const parsedArgValues = await v.parseAsync(ArgValuesSchema, nodeParsedArgs.values); + +const result = await p.group({ + agent: async () => { + if (parsedArgValues.agent) return parsedArgValues.agent; + return await p.select({ message: '...', options: [...] }); + } +}); +``` + +## Important Files + +### Core Framework + +- `eval.ts` - Main entry point, orchestrates entire flow +- `types.ts` - All TypeScript types and Valibot schemas +- `lib/collect-args.ts` - CLI argument parsing and validation +- `lib/show-help.ts` - Help text formatting +- `lib/generate-prompt.ts` - Combines prompt parts with constraints +- `lib/prepare-experiment.ts` - Project template setup + +### Agent Integration + +- `lib/agents/claude-code-cli.ts` - Claude Code CLI wrapper + - Streams JSON messages from Claude + - Parses tool calls and todo lists + - Calculates token counts using `ai-tokenizer` + - Tracks conversation for debugging + +### Evaluation Pipeline + +- `lib/evaluations/evaluate.ts` - Main orchestrator + - Runs checks in parallel: build, typecheck, lint, test, environment + - Creates unified logging interface (verbose vs. normal) + - Formats results and optionally uploads +- `lib/evaluations/prepare-evaluations.ts` - Installs test dependencies +- `lib/evaluations/build.ts` - Vite build verification +- `lib/evaluations/typecheck.ts` - TypeScript compilation check +- `lib/evaluations/lint.ts` - ESLint execution +- `lib/evaluations/test-stories.ts` - Vitest + a11y testing +- `lib/evaluations/save-to-sheets.ts` - Google Sheets upload + +### Templates + +- `templates/project/` - Base Vite + React + Storybook template + - Minimal setup with TypeScript + - `src/main.tsx` - React root (agents modify this) + - `vite.config.ts` - Vite configuration +- `templates/evaluation/` - Testing infrastructure + - `.storybook/` - Storybook config with Vitest addon + - `eslint.config.js` - ESLint rules + - `vitest.config.ts` - Vitest + a11y setup + +## Agent Implementation Details + +### Claude Code CLI Integration + +The Claude Code CLI agent (`lib/agents/claude-code-cli.ts`) implements a sophisticated integration: + +**Key Features:** + +1. **Auto-approval of MCP servers**: Sends "1\n" to stdin to automatically trust MCP servers +2. **Streaming JSON parsing**: Parses `--output-format=stream-json` line-by-line +3. **Token counting**: Uses `ai-tokenizer` with Claude encoding to calculate tokens per message +4. **Cost tracking**: Calculates USD cost based on Anthropic pricing +5. **Todo list tracking**: Extracts todo progress from `TodoWrite` tool calls for progress display +6. **Conversation logging**: Saves complete conversation with metadata to `full-conversation.js` + +**Message Types:** + +- `SystemInitMessage`: Session start, tools available, MCP servers +- `AssistantMessage`: Agent responses with text and/or tool calls +- `UserMessage`: Tool results from user +- `ResultMessage`: Final summary with usage stats + +**Output Format:** + +The agent generates `full-conversation.js` that's viewable in `conversation-viewer.html`: +```javascript +const prompt = `...`; +const promptTokenCount = 1234; +const promptCost = 0.0123; +const messages = [...]; // All messages with metadata +globalThis.loadConversation?.({ prompt, promptTokenCount, promptCost, messages }); +``` + +### Adding a New Agent + +To add support for a new coding agent: + +1. Create `lib/agents/my-agent.ts` +2. Implement the `Agent` interface from `types.ts`: + ```typescript + export const myAgent: Agent = { + async execute(prompt, experimentArgs, mcpServerConfig) { + // 1. Setup MCP config if provided + // 2. Execute agent with prompt + // 3. Stream/parse output + // 4. Save conversation log + // 5. Return ExecutionSummary + return { cost, duration, durationApi, turns }; + } + }; + ``` +3. Add to `agents` object in `eval.ts` +4. Update `ArgValuesSchema` in `collect-args.ts` to include new agent option + +## Evaluation Metrics + +Each experiment produces comprehensive metrics: + +### Execution Metrics (from agent) + +- **cost**: Total API cost in USD +- **duration**: Total execution time in seconds +- **durationApi**: API request time in seconds +- **turns**: Number of conversation turns + +### Quality Metrics (from evaluation) + +- **buildSuccess**: Boolean - can the project build? +- **typeCheckErrors**: Number of TypeScript errors +- **lintErrors**: Number of ESLint errors +- **test.passed**: Number of passing tests +- **test.failed**: Number of failing tests +- **a11y.violations**: Number of accessibility violations + +### Output Files + +**`summary.json`**: Complete metrics +```json +{ + "cost": 0.1234, + "duration": 45, + "durationApi": 38, + "turns": 8, + "buildSuccess": true, + "typeCheckErrors": 0, + "lintErrors": 0, + "test": { "passed": 3, "failed": 0 }, + "a11y": { "violations": 1 } +} +``` + +**`full-conversation.js`**: Complete conversation log for debugging +**`test-results.json`**: Detailed Vitest results with a11y violations +**`build-output.txt`**: Vite build logs +**`typecheck-output.txt`**: TypeScript compiler output +**`lint-output.txt`**: ESLint output + +## Lifecycle Hooks + +Evals can define lifecycle hooks in `hooks.ts` to customize behavior: + +```typescript +import type { Hooks } from '../../types.ts'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; + +export default { + // Before project template is copied + prePrepareExperiment: async (args, log) => { + log.message('Custom pre-preparation'); + }, + + // After dependencies are installed + postPrepareExperiment: async (args, log) => { + // Copy fixture files + await fs.cp( + path.join(args.evalPath, 'fixtures'), + path.join(args.projectPath, 'public/fixtures'), + { recursive: true } + ); + }, + + // Before agent starts execution + preExecuteAgent: async (args, log) => { + log.message('Starting agent'); + }, + + // After agent completes + postExecuteAgent: async (args, log) => { + log.message('Agent finished'); + }, + + // Before evaluation runs + preEvaluate: async (args, log) => { + log.start('Custom pre-evaluation'); + }, + + // After evaluation completes + postEvaluate: async (args, log) => { + log.success('Custom post-evaluation'); + } +} satisfies Hooks; +``` + +**Logger Interface:** + +Both `taskLog` (verbose) and `spinner` (normal) are wrapped in a unified interface: +- `start(title)`: Start a new task +- `success(message)`: Mark task as successful +- `error(message)`: Mark task as failed +- `message(message)`: Log a message +- `complete(message)`: Complete the entire operation + +## Prompt Engineering + +### Best Practices + +1. **Use ``**: Specify exact file paths, component names, and testable criteria +2. **Use MUST/SHOULD/MAY**: Clear requirement priority +3. **Specify test identifiers**: Use `data-testid` for reliable testing +4. **Define exact content**: Specify button text, labels, placeholders +5. **Keep prompts focused**: Use extra prompts for supplementary info + +### Example Prompt Structure + +```markdown +Build a {component} that includes: + +- Feature 1 +- Feature 2 +- Feature 3 + + + 1. Component MUST be default export in src/components/{Name}.tsx + 2. Component MUST be added to main.tsx + 3. Component MUST take optional onSubmit prop + 4. Element X SHOULD have data-testid="x" + 5. Element Y SHOULD have "Text" as content + +``` + +### Constraints System + +The framework automatically appends constraints to all prompts (see `generate-prompt.ts`): + +```markdown + + IMPORTANT: Do not run npm, pnpm, yarn, or any package manager commands. + Dependencies have already been installed. Do not run build, test, or + dev server commands. Just write the code files. + +``` + +This prevents agents from running unnecessary commands and keeps them focused on code generation. + +## Testing Strategy + +### Test Infrastructure + +Each experiment's project includes: + +- **Vitest**: For running component tests +- **Playwright**: For browser automation +- **@storybook/addon-vitest**: For story-based testing +- **@storybook/addon-a11y**: For accessibility testing +- **ESLint**: For code quality + +### Expected Stories + +Evals should include `expected/stories/*.stories.ts` files that: + +1. Import the component +2. Define basic stories (e.g., Default) +3. Use `play` functions for interaction testing +4. Export as default story objects + +**Example:** +```typescript +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent, within } from '@storybook/test'; +import { FlightBooking } from '../src/components/FlightBooking'; + +const meta = { + component: FlightBooking, + tags: ['test'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByTestId('submit')); + await expect(canvas.getByText('Success')).toBeInTheDocument(); + } +}; +``` + +### Accessibility Testing + +The framework uses `@storybook/addon-a11y` which runs Axe checks on all stories: + +- Violations are counted per story +- Total violations across all passing tests are reported +- Failed tests don't contribute to a11y metrics + +## Dependencies + +### Framework Dependencies + +- `@clack/prompts` - Interactive CLI prompts +- `valibot` - Schema validation +- `tinyexec` - Command execution +- `nypm` - Package manager detection and operations +- `ai-tokenizer` - Token counting for Claude + +### Template Dependencies + +- **Project template**: Vite + React + TypeScript (minimal) +- **Evaluation template**: Vitest + Playwright + Storybook + ESLint + a11y + +### Agent Dependencies + +- `claude-code` - Claude Code CLI (must be installed globally) + +## Google Sheets Integration + +The framework can optionally upload results to Google Sheets for tracking experiments over time. + +**How it works:** + +1. Uses Google Apps Script web app as proxy +2. Appends row with metrics to spreadsheet +3. Includes git branch/commit for context +4. Respects `--upload` / `--no-upload` flag + +**Setup** (for maintainers): + +- Google Apps Script code is in `google-apps-script.js` +- Deployed as web app with spreadsheet access +- URL is hardcoded in `save-to-sheets.ts` + +## Conversation Viewer + +The `conversation-viewer.html` file provides a web-based interface for viewing agent conversations: + +**Features:** + +- Timeline view of all messages +- Token counts and costs per message +- Tool call visualization +- Todo list progress tracking +- Collapsible message details + +**Usage:** + +1. Open `conversation-viewer.html` in browser +2. Select `results/full-conversation.js` file +3. Browse conversation chronologically + +## MCP Server Configuration + +### Component Manifest Pattern + +When using `--context components.json`, the framework: + +1. Reads the manifest file from the eval directory +2. Creates `.mcp.json` in project with stdio server config: + ```json + { + "mcpServers": { + "storybook-mcp": { + "type": "stdio", + "command": "node", + "args": ["../../packages/mcp/bin.ts", "--manifestPath", "/path/to/components.json"] + } + } + } + ``` +3. Agent receives MCP tools from `@storybook/mcp` package + +### Custom MCP Server Pattern + +When using `--context mcp.config.json`, the framework: + +1. Reads the config file (or parses inline JSON) +2. Validates against `McpServerConfigSchema` +3. Writes to project's `.mcp.json` +4. Agent connects to specified servers (HTTP or stdio) + +**Example config:** +```json +{ + "my-server": { + "type": "http", + "url": "http://localhost:6006/mcp", + "headers": { "X-Custom": "value" } + } +} +``` + +## Tips for Development + +### Debugging Failed Experiments + +1. **Check `full-conversation.js`**: See exact agent activity +2. **Review `build-output.txt`**: Build errors +3. **Check `typecheck-output.txt`**: TypeScript issues +4. **Inspect `lint-output.txt`**: Code quality problems +5. **Read `test-results.json`**: Test failures and a11y violations +6. **Compare with `expected/`**: See reference implementation + +### Common Issues + +- **Dependencies not installed**: Framework handles this, but hooks may need to wait +- **MCP server not trusted**: Framework auto-approves via stdin +- **Tests fail to run**: Check that stories are in `stories/` directory and have `tags: ['test']` +- **Build fails**: Agent may have created invalid TypeScript + +### Performance Optimization + +- Evaluations run in parallel (build, typecheck, lint, test) +- Use `--verbose` only for debugging (slower) +- Skip `--upload` for faster local iteration + +## Notes for AI Assistants + +- The framework is designed for reproducibility - same inputs should give comparable outputs +- Always check `collect-args.ts` for the canonical list of CLI options +- Hooks are optional - most evals don't need them +- Extra prompts are append-only - they don't replace the main prompt +- The `CONSTRAINTS_PROMPT` is always appended to prevent package manager usage +- Agent token counting is approximate - uses client-side tokenizer, not actual API response +- Coverage metrics track quality trends across experiments +- The conversation viewer is critical for debugging agent behavior +- All experiment artifacts are saved - nothing is deleted automatically +- Timestamps use local time with timezone offset for consistent naming +```` diff --git a/.gitignore b/.gitignore index 66be58e4..81a46bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ coverage/ test-report.junit.xml # Turborepo .turbo + +*storybook.log +experiments/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 33fbe6a5..bb7fe4cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { + "oxc.enable": true, "editor.defaultFormatter": "esbenp.prettier-vscode", - "oxc.typeAware": true + "oxc.typeAware": true, + "vitest.disableWorkspaceWarning": true } diff --git a/eval/.claude/settings.local.json b/eval/.claude/settings.local.json new file mode 100644 index 00000000..0efb24eb --- /dev/null +++ b/eval/.claude/settings.local.json @@ -0,0 +1,3 @@ +{ + "enabledMcpjsonServers": ["storybook-addon-mcp", "storybook-mcp"] +} diff --git a/eval/.storybook/main.ts b/eval/.storybook/main.ts new file mode 100644 index 00000000..e310484e --- /dev/null +++ b/eval/.storybook/main.ts @@ -0,0 +1,38 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +import { dirname } from 'path'; + +import { fileURLToPath } from 'url'; + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value: string): any { + return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`))); +} + +const config: StorybookConfig = { + stories: [ + '../evals/*/experiments/*/project/stories/*.stories.@(js|jsx|mjs|ts|tsx)', + '../evals/*/expected/stories/*.stories.@(js|jsx|mjs|ts|tsx)', + '../templates/result-docs/*.stories.@(js|jsx|mjs|ts|tsx)', + ], + addons: [ + getAbsolutePath('@storybook/addon-a11y'), + getAbsolutePath('storybook-addon-test-codegen'), + ], + framework: { + name: getAbsolutePath('@storybook/react-vite'), + options: {}, + }, + core: { + builder: { + name: '@storybook/builder-vite', + options: { + viteConfigPath: './templates/project/vite.config.ts', + }, + }, + }, +}; +export default config; diff --git a/eval/.storybook/manager.ts b/eval/.storybook/manager.ts new file mode 100644 index 00000000..131b301d --- /dev/null +++ b/eval/.storybook/manager.ts @@ -0,0 +1,7 @@ +import { addons } from 'storybook/manager-api'; + +addons.setConfig({ + sidebar: { + showRoots: false, + }, +}); diff --git a/eval/.storybook/preview.ts b/eval/.storybook/preview.ts new file mode 100644 index 00000000..5bd16e17 --- /dev/null +++ b/eval/.storybook/preview.ts @@ -0,0 +1,21 @@ +import type { Preview } from '@storybook/react-vite'; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo', + }, + }, +}; + +export default preview; diff --git a/eval/.storybook/vitest.setup.ts b/eval/.storybook/vitest.setup.ts new file mode 100644 index 00000000..ea170b04 --- /dev/null +++ b/eval/.storybook/vitest.setup.ts @@ -0,0 +1,7 @@ +import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview'; +import { setProjectAnnotations } from '@storybook/react-vite'; +import * as projectAnnotations from './preview'; + +// This is an important step to apply the right configuration when testing your stories. +// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations +setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); diff --git a/eval/README.md b/eval/README.md new file mode 100644 index 00000000..20fa6df7 --- /dev/null +++ b/eval/README.md @@ -0,0 +1,222 @@ +# Storybook MCP Evaluation Framework + +A CLI-based evaluation framework for testing AI coding agents' ability to build UI components with Storybook and MCP tools. + +## What is this? + +This framework runs automated experiments where AI coding agents (like Claude Code CLI) are given prompts to build UI components. Each experiment: + +1. **Prepares** a fresh Vite + React + Storybook project +2. **Executes** the agent with a prompt and optional context (MCP servers, component manifests, or extra prompts) +3. **Evaluates** the results using automated checks: build success, type checking, linting, tests, and accessibility + +The goal is to measure how well agents can use Storybook's MCP tools to build production-quality components. + +> [!NOTE] +> All eval results that are uploaded (opt-outable) are publicly available in [this Google Sheet](https://docs.google.com/spreadsheets/d/1TAvPyK6S6J-Flc1-gNrQpwmd6NWVXoTrQhaQ35y13vw/edit?usp=sharing). + +## Quick Start + +```bash +# Interactive mode (recommended) +node eval.ts + +# With all options specified +node eval.ts --agent claude-code --context components.json --upload 100-flight-booking-plain +``` + +## CLI Options + +| Option | Short | Type | Description | +| ------------- | ----- | ------- | ---------------------------------------------------------------------------------------- | +| `--agent` | `-a` | string | Which agent to use (`claude-code`) | +| `--context` | `-c` | string | Context type: `false`, `*.json` (manifest), `mcp.config.json`, or `*.md` (extra prompts) | +| `--verbose` | `-v` | boolean | Show detailed logs during execution | +| `--storybook` | `-s` | boolean | Auto-start Storybook after completion | +| `--upload` | `-u` | boolean | Upload results to Google Sheets (default: true) | +| `--help` | `-h` | - | Display help information | + +**Positional argument:** The eval directory name (e.g., `100-flight-booking-plain`) + +### Context Types + +The framework supports four context modes: + +1. **No context** (`--no-context`): Agent uses only default tools +2. **Component manifest** (`--context components.json`): Provides component documentation via `@storybook/mcp` +3. **MCP server config** (`--context mcp.config.json` or inline JSON): Custom MCP server setup +4. **Extra prompts** (`--context extra-prompt-01.md,extra-prompt-02.md`): Additional markdown files appended to main prompt + +## Project Structure + +``` +eval/ +├── evals/ # Evaluation definitions +│ └── 100-flight-booking-plain/ +│ ├── prompt.md # Main prompt for the agent +│ ├── components.json # Optional: component manifest +│ ├── mcp.config.json # Optional: MCP server config +│ ├── extra-prompt-*.md # Optional: additional context +│ ├── hooks.ts # Optional: lifecycle hooks +│ ├── expected/ # Expected output for comparison +│ └── experiments/ # Generated experiment runs +│ └── {context}-{agent}-{timestamp}/ +│ ├── prompt.md # Full prompt sent to agent +│ ├── project/ # Generated project code +│ └── results/ # Evaluation results +│ ├── summary.json +│ ├── full-conversation.js +│ ├── build-output.txt +│ ├── typecheck-output.txt +│ ├── lint-output.txt +│ └── test-results.json +├── templates/ +│ ├── project/ # Base Vite + React + Storybook template +│ └── evaluation/ # Test/lint configs for evaluations +└── lib/ + ├── agents/ # Agent implementations + ├── evaluations/ # Evaluation runners (build, test, lint, etc.) + └── *.ts # Core framework logic +``` + +## Creating an Eval + +1. **Create eval directory:** + + ```bash + mkdir evals/200-my-component + ``` + +2. **Write `prompt.md`:** + + ```markdown + Build a SearchBar component with autocomplete... + + + + 1. Component MUST be default export in src/components/SearchBar.tsx + 2. Component MUST have data-testid="search-bar" + + ``` + +3. **Optional: Add context files:** + - `components.json` - Component manifest for Storybook MCP + - `mcp.config.json` - Custom MCP server configuration + - `extra-prompt-*.md` - Supplementary instructions + +4. **Optional: Create `hooks.ts`:** + + ```typescript + import type { Hooks } from '../../types.ts'; + + export default { + async postPrepareExperiment(args, log) { + // Custom setup (e.g., copy fixtures) + }, + } satisfies Hooks; + ``` + +## Evaluation Metrics + +Each experiment produces: + +- **Build success**: Can the project build without errors? +- **Type check**: TypeScript compilation errors count +- **Lint**: ESLint errors count +- **Tests**: Storybook test results (passed/failed) +- **Accessibility**: Axe violations count +- **Cost**: API usage cost in USD +- **Duration**: Total time and API time in seconds +- **Turns**: Number of agent conversation turns + +## Output Files + +### `summary.json` + +Complete metrics from execution and evaluation: + +```json +{ + "cost": 0.1234, + "duration": 45, + "turns": 8, + "buildSuccess": true, + "typeCheckErrors": 0, + "lintErrors": 0, + "test": { "passed": 3, "failed": 0 }, + "a11y": { "violations": 1 } +} +``` + +### `full-conversation.js` + +Complete conversation log viewable in `conversation-viewer.html`: + +- All assistant and user messages +- Tool calls with arguments +- Token counts and costs per message +- Todo list progress + +### Test Results + +- `test-results.json` - Detailed test outcomes +- `build-output.txt` - Build logs +- `typecheck-output.txt` - TypeScript errors +- `lint-output.txt` - ESLint output + +## Lifecycle Hooks + +Customize experiment behavior with `hooks.ts`: + +```typescript +export default { + prePrepareExperiment: async (args, log) => { + // Before project template copy + }, + postPrepareExperiment: async (args, log) => { + // After dependencies installed + }, + preExecuteAgent: async (args, log) => { + // Before agent starts + }, + postExecuteAgent: async (args, log) => { + // After agent completes + }, + preEvaluate: async (args, log) => { + // Before evaluation runs + }, + postEvaluate: async (args, log) => { + // After evaluation completes + }, +} satisfies Hooks; +``` + +## Viewing Results + +**Conversation viewer:** + +```bash +# Open the HTML file and select the full-conversation.js file +open conversation-viewer.html +``` + +**Storybook:** + +```bash +cd evals/100-flight-booking-plain/experiments/{experiment-name}/project +pnpm storybook +``` + +## Tips + +- Use `--verbose` to see detailed agent activity and tool calls +- Check `full-conversation.js` to debug agent behavior +- Compare `project/` output with `expected/` directory +- Use extra prompts to guide agent without modifying main prompt +- Component manifests work best when agents need library documentation + +## Requirements + +- Node.js 24+ +- pnpm 10.19.0+ +- Claude Code CLI (`npm install -g claude-code`) diff --git a/eval/eval.ts b/eval/eval.ts new file mode 100644 index 00000000..b34db2f5 --- /dev/null +++ b/eval/eval.ts @@ -0,0 +1,180 @@ +import * as p from '@clack/prompts'; +import { claudeCodeCli } from './lib/agents/claude-code-cli.ts'; +import * as path from 'node:path'; +import * as fs from 'node:fs/promises'; +import type { ExperimentArgs } from './types.ts'; +import { prepareExperiment } from './lib/prepare-experiment.ts'; +import { evaluate } from './lib/evaluations/evaluate.ts'; +import { save } from './lib/save/save.ts'; +import { collectArgs } from './lib/collect-args.ts'; +import { generatePrompt } from './lib/generate-prompt.ts'; +import { x } from 'tinyexec'; +import { styleText } from 'node:util'; +import { showHelp } from './lib/show-help.ts'; + +// Check for --help flag before any processing +if (process.argv.includes('--help') || process.argv.includes('-h')) { + showHelp(); +} + +p.intro('🧪 Storybook MCP Eval'); + +const args = await collectArgs(); + +const evalPath = path.resolve(path.join('evals', args.eval)); +// Validate that eval directory exists +const dirExists = await fs + .access(evalPath) + .then(() => true) + .catch(() => false); +if (!dirExists) { + p.log.error(`Eval directory does not exist: ${evalPath}`); + process.exit(1); +} + +const localDateTimestamp = new Date( + Date.now() - new Date().getTimezoneOffset() * 60000, +) + .toISOString() + .slice(0, 19) + .replace(/[:.]/g, '-'); + +let contextPrefix = ''; +switch (args.context.type) { + case false: + contextPrefix = 'no-context'; + break; + case 'extra-prompts': + contextPrefix = args.context.prompts + .map((prompt) => + path.parse(prompt).name.toLowerCase().replace(/\s+/g, '-'), + ) + .join('-'); + break; + case 'mcp-server': + contextPrefix = Object.keys(args.context.mcpServerConfig) + .map((mcpServerName) => mcpServerName.toLowerCase().replace(/\s+/g, '-')) + .join('-'); + break; + case 'components-manifest': + contextPrefix = 'components-manifest'; + break; +} + +const experimentDirName = `${contextPrefix}-${args.agent}-${localDateTimestamp}`; +const experimentPath = path.join(evalPath, 'experiments', experimentDirName); +const projectPath = path.join(experimentPath, 'project'); +const resultsPath = path.join(experimentPath, 'results'); +const experimentArgs: ExperimentArgs = { + evalPath, + experimentPath, + projectPath, + resultsPath, + verbose: args.verbose, + upload: args.upload, + evalName: args.eval, + context: args.context, + agent: args.agent, + hooks: await import(path.join(evalPath, 'hooks.ts')) + .then((mod) => mod.default) + .catch(() => ({})), +}; + +p.log.info(`Running experiment '${args.eval}' with agent '${args.agent}'`); + +await prepareExperiment(experimentArgs); + +const prompt = await generatePrompt(evalPath, args.context); +await fs.writeFile(path.join(experimentPath, 'prompt.md'), prompt); + +const agents = { + 'claude-code': claudeCodeCli, +}; +const agent = agents[args.agent as keyof typeof agents]; +const promptSummary = await agent.execute( + prompt, + experimentArgs, + args.context.type === 'mcp-server' || + args.context.type === 'components-manifest' + ? args.context.mcpServerConfig + : undefined, +); + +const evaluationSummary = await evaluate(experimentArgs); + +await fs.writeFile( + path.join(resultsPath, 'summary.json'), + JSON.stringify({ ...promptSummary, ...evaluationSummary }, null, 2), +); + +p.log.info('Summary:'); +p.log.message(`🏗️ Build: ${evaluationSummary.buildSuccess ? '✅' : '❌'}`); +p.log.message( + `🔍 Type Check: ${evaluationSummary.typeCheckErrors === 0 ? '✅' : styleText('red', `❌ ${evaluationSummary.typeCheckErrors} errors`)}`, +); +p.log.message( + `✨ Lint: ${evaluationSummary.lintErrors === 0 ? '✅' : styleText('red', `❌ ${evaluationSummary.lintErrors} errors`)}`, +); + +if ( + evaluationSummary.test.failed === 0 && + evaluationSummary.test.passed === 0 +) { + p.log.message(`🧪 Tests: ❌ ${styleText('red', 'Failed to run')}`); + p.log.message(`🦾 Accessibility: ⚠️ ${styleText('yellow', 'Inconclusive')}`); +} else if (evaluationSummary.test.failed > 0) { + p.log.message( + `🧪 Tests: ${styleText('red', `❌ ${evaluationSummary.test.failed} failed`) + ' | ' + styleText('green', `${evaluationSummary.test.passed} passed`)}`, + ); + if (evaluationSummary.test.passed > 0) { + p.log.message( + `🦾 Accessibility: ⚠️ ${styleText('yellow', `${evaluationSummary.a11y.violations} violations from ${evaluationSummary.test.passed}/${evaluationSummary.test.passed + evaluationSummary.test.failed} tests`)}`, + ); + } else { + p.log.message( + `🦾 Accessibility: ⚠️ ${styleText('yellow', 'Inconclusive')}`, + ); + } +} else { + p.log.message('🧪 Tests: ✅'); + p.log.message( + `🦾 Accessibility: ${evaluationSummary.a11y.violations === 0 ? '✅' : styleText('yellow', `⚠️ ${evaluationSummary.a11y.violations} violations`)}`, + ); +} + +p.log.message( + `⏱️ Duration: ${promptSummary.duration}s (API: ${promptSummary.durationApi}s)`, +); +p.log.message(`💰 Cost: $${promptSummary.cost}`); +p.log.message(`🔄 Turns: ${promptSummary.turns}`); + +const chromaticUrl = await save( + experimentArgs, + evaluationSummary, + promptSummary, +); + +if (chromaticUrl) { + p.log.message( + `🔍 View results at:\n\u001b]8;;${chromaticUrl}\u0007${chromaticUrl}\u001b]8;;\u0007`, + ); +} + +const startStorybook = + args.storybook !== undefined + ? args.storybook + : await p.confirm({ + message: "Would you like to start the experiment's Storybook?", + }); + +p.outro('✨ Evaluation complete!'); + +if (startStorybook) { + console.log(''); + await x('pnpm', ['run', 'storybook'], { + nodeOptions: { + cwd: projectPath, + stdio: 'inherit', + }, + }); +} diff --git a/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.css b/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.css new file mode 100644 index 00000000..3b26b28b --- /dev/null +++ b/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.css @@ -0,0 +1,223 @@ +.flight-booking { + max-width: 800px; + margin: 0 auto; + padding: 24px; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; +} + +.trip-type-toggle { + display: flex; + gap: 8px; + margin-bottom: 24px; +} + +.trip-type-toggle button { + flex: 1; + padding: 12px 24px; + border: 1px solid #ddd; + background: white; + cursor: pointer; + font-size: 14px; + font-weight: 500; + border-radius: 4px; + transition: all 0.2s; +} + +.trip-type-toggle button:hover { + background: #f5f5f5; +} + +.trip-type-toggle button.active { + background: #007bff; + color: white; + border-color: #007bff; +} + +.flight-inputs { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.autocomplete-wrapper { + position: relative; +} + +.autocomplete-input { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + box-sizing: border-box; +} + +.autocomplete-input:focus { + outline: none; + border-color: #007bff; +} + +.autocomplete-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + max-height: 300px; + overflow-y: auto; + background: white; + border: 1px solid #ddd; + border-top: none; + border-radius: 0 0 4px 4px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + z-index: 1000; + margin-top: -1px; +} + +.autocomplete-item { + padding: 12px; + cursor: pointer; + font-size: 14px; + border-bottom: 1px solid #f0f0f0; +} + +.autocomplete-item:last-child { + border-bottom: none; +} + +.autocomplete-item:hover { + background: #f5f5f5; +} + +.date-picker-wrapper { + position: relative; +} + +.date-trigger { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 4px; + background: white; + cursor: pointer; + font-size: 14px; + text-align: left; + box-sizing: border-box; +} + +.date-trigger:hover { + background: #f5f5f5; +} + +.date-trigger:focus { + outline: none; + border-color: #007bff; +} + +.calendar-popover { + position: absolute; + top: 100%; + left: 0; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 16px; + z-index: 1000; + margin-top: 4px; + min-width: 280px; +} + +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.calendar-header button { + background: none; + border: none; + cursor: pointer; + padding: 4px 8px; + font-size: 18px; + color: #333; +} + +.calendar-header button:hover { + background: #f5f5f5; + border-radius: 4px; +} + +.calendar-header span { + font-weight: 600; + font-size: 14px; +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 4px; +} + +.calendar-day-header { + text-align: center; + font-size: 12px; + font-weight: 600; + color: #666; + padding: 8px 4px; +} + +.calendar-day { + aspect-ratio: 1; + border: none; + background: white; + cursor: pointer; + font-size: 14px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.calendar-day:not(.empty):not(.disabled):hover { + background: #e7f3ff; +} + +.calendar-day.selected { + background: #007bff; + color: white; +} + +.calendar-day.disabled { + color: #ccc; + cursor: not-allowed; +} + +.calendar-day.empty { + cursor: default; +} + +.search-button { + width: 100%; + padding: 14px 24px; + background: #28a745; + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} + +.search-button:hover { + background: #218838; +} + +.search-button:active { + background: #1e7e34; +} diff --git a/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.tsx b/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.tsx new file mode 100644 index 00000000..75bbebb0 --- /dev/null +++ b/eval/evals/100-flight-booking-plain/expected/src/components/FlightBooking.tsx @@ -0,0 +1,378 @@ +import { useState, useRef, useEffect } from 'react'; +import './FlightBooking.css'; + +interface Airport { + code: string; + name: string; +} + +const AIRPORTS: Airport[] = [ + { code: 'SYD', name: 'Sydney Airport, Australia' }, + { code: 'MEL', name: 'Melbourne Airport (Tullamarine), Australia' }, + { code: 'LAX', name: 'Los Angeles International Airport, USA' }, + { code: 'JFK', name: 'John F. Kennedy International Airport, New York, USA' }, + { code: 'LHR', name: 'Heathrow Airport, London, UK' }, + { code: 'CDG', name: 'Charles de Gaulle Airport, Paris, France' }, + { + code: 'ATL', + name: 'Hartsfield–Jackson Atlanta International Airport, USA', + }, + { code: 'DXB', name: 'Dubai International Airport, UAE' }, + { code: 'HKG', name: 'Hong Kong International Airport, Hong Kong' }, + { code: 'BNE', name: 'Brisbane Airport, Australia' }, + { code: 'PER', name: 'Perth Airport, Australia' }, + { code: 'DFW', name: 'Dallas Fort Worth International Airport, USA' }, +]; + +interface FlightBookingProps { + onSubmit?: () => void; +} + +export default function FlightBooking({ onSubmit }: FlightBookingProps) { + const [tripType, setTripType] = useState<'oneway' | 'return'>('return'); + const [fromAirport, setFromAirport] = useState(null); + const [toAirport, setToAirport] = useState(null); + const [departureDate, setDepartureDate] = useState(null); + const [returnDate, setReturnDate] = useState(null); + + const [fromSearch, setFromSearch] = useState(''); + const [toSearch, setToSearch] = useState(''); + const [showFromDropdown, setShowFromDropdown] = useState(false); + const [showToDropdown, setShowToDropdown] = useState(false); + const [showDeparturePicker, setShowDeparturePicker] = useState(false); + const [showReturnPicker, setShowReturnPicker] = useState(false); + + const fromRef = useRef(null); + const toRef = useRef(null); + const departureRef = useRef(null); + const returnRef = useRef(null); + + // Close dropdowns when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (fromRef.current && !fromRef.current.contains(event.target as Node)) { + setShowFromDropdown(false); + } + if (toRef.current && !toRef.current.contains(event.target as Node)) { + setShowToDropdown(false); + } + if ( + departureRef.current && + !departureRef.current.contains(event.target as Node) + ) { + setShowDeparturePicker(false); + } + if ( + returnRef.current && + !returnRef.current.contains(event.target as Node) + ) { + setShowReturnPicker(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const filteredFromAirports = AIRPORTS.filter( + (airport) => + airport.code.toLowerCase().includes(fromSearch.toLowerCase()) || + airport.name.toLowerCase().includes(fromSearch.toLowerCase()), + ); + + const filteredToAirports = AIRPORTS.filter( + (airport) => + airport.code.toLowerCase().includes(toSearch.toLowerCase()) || + airport.name.toLowerCase().includes(toSearch.toLowerCase()), + ); + + const handleFromSelect = (airport: Airport) => { + setFromAirport(airport); + setFromSearch(''); + setShowFromDropdown(false); + }; + + const handleToSelect = (airport: Airport) => { + setToAirport(airport); + setToSearch(''); + setShowToDropdown(false); + }; + + const handleSubmit = () => { + if (onSubmit) { + onSubmit(); + } + }; + + const formatDate = (date: Date) => { + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + }; + + return ( +
+
+ + +
+ +
+
+ { + setFromSearch(e.target.value); + setFromAirport(null); + setShowFromDropdown(true); + }} + onFocus={() => setShowFromDropdown(true)} + data-testid="flight-trigger-from" + className="autocomplete-input" + /> + {showFromDropdown && ( +
+ {filteredFromAirports.map((airport) => ( +
handleFromSelect(airport)} + data-testid={`airport-${airport.code}`} + > + {airport.code} - {airport.name} +
+ ))} +
+ )} +
+ +
+ { + setToSearch(e.target.value); + setToAirport(null); + setShowToDropdown(true); + }} + onFocus={() => setShowToDropdown(true)} + data-testid="flight-trigger-to" + className="autocomplete-input" + /> + {showToDropdown && ( +
+ {filteredToAirports.map((airport) => ( +
handleToSelect(airport)} + data-testid={`airport-${airport.code}`} + > + {airport.code} - {airport.name} +
+ ))} +
+ )} +
+ +
+ + {showDeparturePicker && ( + { + setDepartureDate(date); + setShowDeparturePicker(false); + // Reset return date if it's before the new departure date + if (returnDate && date > returnDate) { + setReturnDate(null); + } + }} + minDate={new Date()} + /> + )} +
+ + {tripType === 'return' && ( +
+ + {showReturnPicker && ( + { + setReturnDate(date); + setShowReturnPicker(false); + }} + minDate={departureDate || new Date()} + /> + )} +
+ )} +
+ + +
+ ); +} + +interface CalendarProps { + selectedDate: Date | null; + onSelectDate: (date: Date) => void; + minDate?: Date; +} + +function Calendar({ selectedDate, onSelectDate, minDate }: CalendarProps) { + const today = new Date(); + const [currentMonth, setCurrentMonth] = useState( + selectedDate || minDate || today, + ); + + const startOfMonth = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth(), + 1, + ); + const endOfMonth = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() + 1, + 0, + ); + + const startDay = startOfMonth.getDay(); + const daysInMonth = endOfMonth.getDate(); + + const days: (Date | null)[] = []; + + // Add empty slots for days before the month starts + for (let i = 0; i < startDay; i++) { + days.push(null); + } + + // Add all days in the month + for (let i = 1; i <= daysInMonth; i++) { + days.push(new Date(currentMonth.getFullYear(), currentMonth.getMonth(), i)); + } + + const isDateDisabled = (date: Date) => { + if (!minDate) return false; + const dateWithoutTime = new Date( + date.getFullYear(), + date.getMonth(), + date.getDate(), + ); + const minDateWithoutTime = new Date( + minDate.getFullYear(), + minDate.getMonth(), + minDate.getDate(), + ); + return dateWithoutTime < minDateWithoutTime; + }; + + const isDateSelected = (date: Date) => { + if (!selectedDate) return false; + return ( + date.getDate() === selectedDate.getDate() && + date.getMonth() === selectedDate.getMonth() && + date.getFullYear() === selectedDate.getFullYear() + ); + }; + + const goToPreviousMonth = () => { + setCurrentMonth( + new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1), + ); + }; + + const goToNextMonth = () => { + setCurrentMonth( + new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1), + ); + }; + + const monthYear = currentMonth.toLocaleDateString('en-US', { + month: 'long', + year: 'numeric', + }); + + return ( +
+
+ + {monthYear} + +
+
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+ {days.map((day, index) => { + if (!day) { + return ( +
+ ); + } + + const disabled = isDateDisabled(day); + const selected = isDateSelected(day); + + return ( + + ); + })} +
+
+ ); +} diff --git a/eval/evals/100-flight-booking-plain/expected/stories/FlightBooking.stories.ts b/eval/evals/100-flight-booking-plain/expected/stories/FlightBooking.stories.ts new file mode 100644 index 00000000..bdac7944 --- /dev/null +++ b/eval/evals/100-flight-booking-plain/expected/stories/FlightBooking.stories.ts @@ -0,0 +1,274 @@ +import FlightBookingComponent from '../src/components/FlightBooking.tsx'; +import { userEvent, fn, expect, screen, waitFor } from 'storybook/test'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import type { StepFunction } from 'storybook/internal/types'; + +const meta = { + component: FlightBookingComponent, + args: { + onSubmit: fn(), + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +async function looseGetInteractiveElements( + testId: string, + label: string, + step: StepFunction, +) { + let elements: HTMLElement[] = []; + await step( + `Get element by test ID '${testId}' or label '${label}'`, + async () => { + elements = await waitFor(function getElement() { + const byTestId = screen.queryAllByTestId(testId); + if (byTestId.length > 0) { + return byTestId; + } + const candidates = [ + ...screen.queryAllByTestId(testId), + ...screen.queryAllByLabelText(label, { exact: false }), + ...screen.queryAllByPlaceholderText(label, { exact: false }), + ...screen.queryAllByText(label, { exact: false }), + ]; + + // Return all interactive elements + const interactive = candidates.filter((el) => { + if (!el) { + return false; + } + if ( + el.getAttribute('disabled') === '' || + el.getAttribute('aria-disabled') === 'true' + ) { + return false; + } + const tagName = el.tagName.toLowerCase(); + // Check for naturally interactive HTML elements + if ( + [ + 'button', + 'a', + 'input', + 'select', + 'textarea', + 'details', + 'summary', + 'audio', + 'video', + ].includes(tagName) + ) { + return true; + } + // Check for elements with interactive ARIA roles + const role = el.getAttribute('role'); + if ( + !!role && + [ + 'button', + 'link', + 'textbox', + 'checkbox', + 'radio', + 'combobox', + 'listbox', + 'option', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'tab', + 'switch', + 'slider', + 'spinbutton', + 'searchbox', + 'progressbar', + 'scrollbar', + ].includes(role) + ) { + return true; + } + // Check for cursor: pointer style + const computedStyle = window.getComputedStyle(el); + if (computedStyle.cursor === 'pointer') { + return true; + } + return false; + }); + interactive.push(null as any); + + return interactive; + }); + }, + ); + return elements!; +} + +export const Initial: Story = {}; + +export const FlightPicker: Story = { + play: async ({ step }) => { + const fromFlightTrigger = ( + await looseGetInteractiveElements('flight-trigger-from', 'From', step) + )[0]; + await expect(fromFlightTrigger).toBeInTheDocument(); + + if ( + fromFlightTrigger.tagName.toLowerCase() === 'input' && + (fromFlightTrigger as HTMLInputElement).type === 'text' + ) { + await userEvent.type(fromFlightTrigger, 'M'); + } else { + await userEvent.click(fromFlightTrigger); + } + + await expect( + (await looseGetInteractiveElements('airport-MEL', 'MEL', step))[0], + ).toBeInTheDocument(); + }, +}; + +export const DatePicker: Story = { + play: async ({ step }) => { + await userEvent.click( + ( + await looseGetInteractiveElements( + 'date-trigger-departure', + 'Departure Date', + step, + ) + )[0], + ); + await expect( + (await looseGetInteractiveElements('date-27', '27', step))[0], + ).toBeInTheDocument(); + }, +}; + +export const ReturnDatePickerIsUnavailableWhenOneWaySelected: Story = { + play: async ({ step }) => { + await userEvent.click( + (await looseGetInteractiveElements('one-way', 'One Way', step))[0], + ); + + const returnDatePicker = ( + await looseGetInteractiveElements( + 'date-trigger-return', + 'Return Date', + step, + ) + )[0]; + + // If the return datepicker exists, ensure it's disabled by trying to open it + if (returnDatePicker) { + await userEvent.click(returnDatePicker); + const date15 = ( + await looseGetInteractiveElements('date-15', '15', step) + )[0]; + await expect(date15).toBeNull(); + } else { + await expect(returnDatePicker).toBeNull(); + } + }, +}; + +export const Submitted: Story = { + play: async ({ canvasElement, args, step }) => { + await step('Enable return flight', async () => { + const returnToggle = ( + await looseGetInteractiveElements('return', 'Return', step) + )[0]; + await userEvent.click(returnToggle); + }); + + await step('Select fromt flight', async () => { + const fromFlightTrigger = ( + await looseGetInteractiveElements('flight-trigger-from', 'From', step) + )[0]; + await expect(fromFlightTrigger).toBeInTheDocument(); + + if ( + fromFlightTrigger.tagName.toLowerCase() === 'input' && + (fromFlightTrigger as HTMLInputElement).type === 'text' + ) { + await userEvent.type(fromFlightTrigger, 'M'); + } else { + await userEvent.click(fromFlightTrigger); + } + + const melbourneAirport = ( + await looseGetInteractiveElements('airport-MEL', 'MEL', step) + )[0]; + await userEvent.click(melbourneAirport); + }); + + await step('Select to flight', async () => { + const toFlightTrigger = ( + await looseGetInteractiveElements('flight-trigger-to', 'To', step) + )[0]; + await expect(toFlightTrigger).toBeInTheDocument(); + + if ( + toFlightTrigger.tagName.toLowerCase() === 'input' && + (toFlightTrigger as HTMLInputElement).type === 'text' + ) { + await userEvent.type(toFlightTrigger, 'L'); + } else { + await userEvent.click(toFlightTrigger); + } + const laxAirport = ( + await looseGetInteractiveElements('airport-LAX', 'LAX', step) + )[0]; + await userEvent.click(laxAirport); + }); + + await step('Select departure date', async () => { + await userEvent.click( + ( + await looseGetInteractiveElements( + 'date-trigger-departure', + 'Departure Date', + step, + ) + )[0], + ); + const date = ( + await looseGetInteractiveElements('date-27', '27', step) + ).at(-1)!; + await expect(date).toBeInTheDocument(); + await userEvent.click(date); + await userEvent.click(canvasElement); // dismiss datepicker popover + }); + + await step('Select return date', async () => { + await userEvent.click( + ( + await looseGetInteractiveElements( + 'date-trigger-return', + 'Return Date', + step, + ) + )[0], + ); + const date = ( + await looseGetInteractiveElements('date-28', '28', step) + ).at(-1)!; + await expect(date).toBeInTheDocument(); + await userEvent.click(date); + await userEvent.click(canvasElement); // dismiss datepicker popover + }); + + await userEvent.click( + ( + await looseGetInteractiveElements( + 'search-flights', + 'Search Flights', + step, + ) + )[0], + ); + await expect(args.onSubmit).toHaveBeenCalledOnce(); + }, +}; diff --git a/eval/evals/100-flight-booking-plain/extra-prompt-01.md b/eval/evals/100-flight-booking-plain/extra-prompt-01.md new file mode 100644 index 00000000..78f32778 --- /dev/null +++ b/eval/evals/100-flight-booking-plain/extra-prompt-01.md @@ -0,0 +1 @@ +Your final result message should include the word "banana" diff --git a/eval/evals/100-flight-booking-plain/extra-prompt-02.md b/eval/evals/100-flight-booking-plain/extra-prompt-02.md new file mode 100644 index 00000000..79ff3b4f --- /dev/null +++ b/eval/evals/100-flight-booking-plain/extra-prompt-02.md @@ -0,0 +1 @@ +It's extremely important that the submit button has three emojis in it diff --git a/eval/evals/100-flight-booking-plain/full-manifest.fixture.json b/eval/evals/100-flight-booking-plain/full-manifest.fixture.json new file mode 100644 index 00000000..b2e02ba6 --- /dev/null +++ b/eval/evals/100-flight-booking-plain/full-manifest.fixture.json @@ -0,0 +1,524 @@ +{ + "v": 1, + "components": { + "button": { + "id": "button", + "path": "src/components/Button.tsx", + "name": "Button", + "description": "A versatile button component that supports multiple variants, sizes, and states.\n\nThe Button component is a fundamental building block for user interactions. It can be styled as primary, secondary, or tertiary actions, and supports disabled and loading states.\n\n## Usage\n\nButtons should be used for actions that affect the current page or trigger operations. For navigation, consider using a Link component instead.", + "summary": "A versatile button component for user interactions", + "import": "import { Button } from '@storybook/design-system';", + "reactDocgen": { + "props": { + "variant": { + "description": "The visual style variant of the button", + "required": false, + "tsType": { + "name": "union", + "raw": "\"primary\" | \"secondary\" | \"tertiary\" | \"danger\"", + "elements": [ + { "name": "literal", "value": "\"primary\"" }, + { "name": "literal", "value": "\"secondary\"" }, + { "name": "literal", "value": "\"tertiary\"" }, + { "name": "literal", "value": "\"danger\"" } + ] + }, + "defaultValue": { "value": "\"primary\"", "computed": false } + }, + "size": { + "description": "The size of the button", + "required": false, + "tsType": { + "name": "union", + "raw": "\"small\" | \"medium\" | \"large\"", + "elements": [ + { "name": "literal", "value": "\"small\"" }, + { "name": "literal", "value": "\"medium\"" }, + { "name": "literal", "value": "\"large\"" } + ] + }, + "defaultValue": { "value": "\"medium\"", "computed": false } + }, + "disabled": { + "description": "Whether the button is disabled", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "loading": { + "description": "Whether the button is in a loading state", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "fullWidth": { + "description": "Whether the button should take up the full width of its container", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "onClick": { + "description": "Callback function when the button is clicked", + "required": false, + "tsType": { + "name": "signature", + "type": "function", + "signature": { + "arguments": [ + { "name": "event", "type": { "name": "MouseEvent" } } + ], + "return": { "name": "void" } + } + } + }, + "children": { + "description": "The content of the button", + "required": true, + "tsType": { "name": "ReactNode" } + } + } + }, + "stories": [ + { + "id": "button--primary", + "name": "Primary", + "description": "The primary button variant is used for the main call-to-action on a page. It has the highest visual prominence and should be used sparingly to guide users toward the most important action.\n\n## Best Practices\n\n- Use only one primary button per section\n- Keep button text concise and action-oriented\n- Ensure sufficient contrast for accessibility", + "summary": "Primary button with high visual prominence", + "import": "import { Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Primary Button" + } + ], + "snippet": "const Primary = () => " + }, + { + "id": "button--secondary", + "name": "Secondary", + "description": "The secondary button variant is used for secondary actions that are still important but not the primary focus of the page.\n\nSecondary buttons have less visual weight than primary buttons and can be used multiple times on a page.", + "summary": "Secondary button for supporting actions", + "import": "import { Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Secondary Button" + } + ], + "snippet": "const Secondary = () => " + }, + { + "id": "button--with-sizes", + "name": "WithSizes", + "description": "Buttons are available in three sizes: small, medium (default), and large.\n\nChoose the appropriate size based on the context and hierarchy of actions. Larger buttons are more prominent and easier to tap on mobile devices.", + "summary": "Button size variations", + "import": "import { Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Button Sizes" + } + ], + "snippet": "const WithSizes = () => (\n <>\n \n \n \n \n)" + }, + { + "id": "button--loading", + "name": "Loading", + "description": "The loading state provides visual feedback when an async operation is in progress.\n\nWhen loading is true, the button displays a spinner and is automatically disabled to prevent multiple submissions. The button text remains visible to maintain layout stability.", + "summary": "Button in loading state during async operations", + "import": "import { Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Loading Button" + } + ], + "snippet": "const Loading = () => " + }, + { + "id": "button--danger", + "name": "Danger", + "description": "The danger variant is used for destructive actions that cannot be easily undone, such as deleting data or canceling subscriptions.\n\nUse this variant to draw attention to the serious nature of the action. Consider adding a confirmation dialog for critical operations.", + "summary": "Danger button for destructive actions", + "import": "import { Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Danger Button" + }, + { + "key": "warning", + "value": "Use with caution for destructive actions" + } + ], + "snippet": "const Danger = () => " + } + ], + "jsDocTag": [ + { + "key": "summary", + "value": "A versatile button component for user interactions" + }, + { + "key": "since", + "value": "1.0.0" + }, + { + "key": "component", + "value": "Button" + } + ] + }, + "card": { + "id": "card", + "path": "src/components/Card.tsx", + "name": "Card", + "description": "A flexible container component for grouping related content with optional header, footer, and action areas.\n\nThe Card component provides a consistent way to present information in a contained, elevated surface. It's commonly used for displaying articles, products, user profiles, or any grouped content that benefits from visual separation.\n\n## Design Principles\n\n- Cards should contain a single subject or action\n- Maintain consistent padding and spacing\n- Use elevation to indicate interactive vs static cards\n- Keep content hierarchy clear with proper use of typography", + "summary": "A flexible container component for grouping related content", + "import": "import { Card } from '@storybook/design-system';", + "reactDocgen": { + "props": { + "variant": { + "description": "The visual style variant of the card", + "required": false, + "tsType": { + "name": "union", + "raw": "\"elevated\" | \"outlined\" | \"flat\"", + "elements": [ + { "name": "literal", "value": "\"elevated\"" }, + { "name": "literal", "value": "\"outlined\"" }, + { "name": "literal", "value": "\"flat\"" } + ] + }, + "defaultValue": { "value": "\"elevated\"", "computed": false } + }, + "padding": { + "description": "The amount of internal padding", + "required": false, + "tsType": { + "name": "union", + "raw": "\"none\" | \"small\" | \"medium\" | \"large\"", + "elements": [ + { "name": "literal", "value": "\"none\"" }, + { "name": "literal", "value": "\"small\"" }, + { "name": "literal", "value": "\"medium\"" }, + { "name": "literal", "value": "\"large\"" } + ] + }, + "defaultValue": { "value": "\"medium\"", "computed": false } + }, + "clickable": { + "description": "Whether the entire card is clickable/interactive", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "header": { + "description": "Content to display in the card header", + "required": false, + "tsType": { "name": "ReactNode" } + }, + "footer": { + "description": "Content to display in the card footer", + "required": false, + "tsType": { "name": "ReactNode" } + }, + "children": { + "description": "The main content of the card", + "required": true, + "tsType": { "name": "ReactNode" } + }, + "onClick": { + "description": "Callback function when the card is clicked (requires clickable=true)", + "required": false, + "tsType": { + "name": "signature", + "type": "function", + "signature": { + "arguments": [ + { "name": "event", "type": { "name": "MouseEvent" } } + ], + "return": { "name": "void" } + } + } + } + } + }, + "stories": [ + { + "id": "card--basic", + "name": "Basic", + "description": "A basic card with just content.\n\nThe default elevated variant provides subtle depth through shadow, making the card appear to float above the page. This is ideal for creating visual hierarchy and grouping related information.", + "summary": "Basic card with content only", + "import": "import { Card } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Basic Card" + } + ], + "snippet": "const Basic = () => (\n \n

Card Title

\n

This is some card content that provides information to the user.

\n
\n)" + }, + { + "id": "card--with-header-and-footer", + "name": "WithHeaderAndFooter", + "description": "A card with distinct header and footer sections.\n\nHeaders typically contain titles, subtitles, or avatars. Footers often contain actions like buttons or metadata like timestamps. The header and footer are visually separated from the main content area.", + "summary": "Card with header and footer sections", + "import": "import { Card } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Card with Header and Footer" + } + ], + "snippet": "const WithHeaderAndFooter = () => (\n Article Title}\n footer={}\n >\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

\n \n)" + }, + { + "id": "card--clickable", + "name": "Clickable", + "description": "An interactive card that responds to clicks.\n\nClickable cards add hover effects and cursor changes to indicate interactivity. This pattern is useful for navigation cards, product cards, or any scenario where the entire card acts as a single interactive element.\n\n## Accessibility\n\nClickable cards are rendered as buttons with proper keyboard support and ARIA attributes.", + "summary": "Interactive clickable card", + "import": "import { Card } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Clickable Card" + } + ], + "snippet": "const Clickable = () => (\n alert('Card clicked!')}>\n

Product Name

\n

Click anywhere on this card to view details.

\n
\n)" + }, + { + "id": "card--variants", + "name": "Variants", + "description": "Different visual variants of the card component.\n\n- **Elevated**: Default variant with shadow for depth\n- **Outlined**: Border-only variant without shadow\n- **Flat**: No border or shadow, minimal visual separation\n\nChoose variants based on your design system and the level of emphasis needed.", + "summary": "Card visual variants (elevated, outlined, flat)", + "import": "import { Card } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Card Variants" + } + ], + "snippet": "const Variants = () => (\n <>\n \n

Elevated card with shadow

\n
\n \n

Outlined card with border

\n
\n \n

Flat card without border or shadow

\n
\n \n)" + }, + { + "id": "card--user-profile", + "name": "UserProfile", + "description": "A real-world example of a user profile card.\n\nThis example demonstrates how to compose the Card component with other design system components to create a complete, functional UI element. It includes an avatar, user information, stats, and action buttons.", + "summary": "Complete user profile card example", + "import": "import { Card, Avatar, Button } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "User Profile Card" + }, + { + "key": "composition", + "value": "Uses Avatar and Button components" + } + ], + "snippet": "const UserProfile = () => (\n \n \n
\n

Jane Doe

\n

Senior Developer

\n
\n
\n }\n footer={\n
\n \n \n
\n }\n >\n
\n
1.2K
Followers
\n
342
Following
\n
89
Posts
\n
\n \n)" + } + ], + "jsDocTag": [ + { + "key": "summary", + "value": "A flexible container component for grouping related content" + }, + { + "key": "since", + "value": "1.0.0" + }, + { + "key": "component", + "value": "Card" + }, + { + "key": "pattern", + "value": "Container" + } + ] + }, + "input": { + "id": "input", + "path": "src/components/Input.tsx", + "name": "Input", + "description": "A flexible text input component that supports various input types, validation states, and accessibility features.\n\nThe Input component is a foundational form element that wraps the native HTML input with consistent styling and behavior. It includes support for labels, error messages, helper text, and different visual states.\n\n## Accessibility\n\nThe Input component automatically manages ARIA attributes for labels, descriptions, and error messages to ensure screen reader compatibility.", + "summary": "A flexible text input component with validation support", + "import": "import { Input } from '@storybook/design-system';", + "reactDocgen": { + "props": { + "type": { + "description": "The type of input field", + "required": false, + "tsType": { + "name": "union", + "raw": "\"text\" | \"email\" | \"password\" | \"number\" | \"tel\" | \"url\"", + "elements": [ + { "name": "literal", "value": "\"text\"" }, + { "name": "literal", "value": "\"email\"" }, + { "name": "literal", "value": "\"password\"" }, + { "name": "literal", "value": "\"number\"" }, + { "name": "literal", "value": "\"tel\"" }, + { "name": "literal", "value": "\"url\"" } + ] + }, + "defaultValue": { "value": "\"text\"", "computed": false } + }, + "label": { + "description": "The label text for the input", + "required": false, + "tsType": { "name": "string" } + }, + "placeholder": { + "description": "Placeholder text shown when the input is empty", + "required": false, + "tsType": { "name": "string" } + }, + "value": { + "description": "The controlled value of the input", + "required": false, + "tsType": { "name": "string" } + }, + "defaultValue": { + "description": "The initial value for an uncontrolled input", + "required": false, + "tsType": { "name": "string" } + }, + "disabled": { + "description": "Whether the input is disabled", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "required": { + "description": "Whether the input is required", + "required": false, + "tsType": { "name": "boolean" }, + "defaultValue": { "value": "false", "computed": false } + }, + "error": { + "description": "Error message to display below the input", + "required": false, + "tsType": { "name": "string" } + }, + "helperText": { + "description": "Helper text to display below the input", + "required": false, + "tsType": { "name": "string" } + }, + "onChange": { + "description": "Callback function when the input value changes", + "required": false, + "tsType": { + "name": "signature", + "type": "function", + "signature": { + "arguments": [ + { + "name": "event", + "type": { + "name": "ChangeEvent", + "elements": [{ "name": "HTMLInputElement" }] + } + } + ], + "return": { "name": "void" } + } + } + } + } + }, + "stories": [ + { + "id": "input--basic", + "name": "Basic", + "description": "A basic text input with a label.\n\nThis is the most common use case for the Input component. Always include a label for accessibility, even if it's visually hidden in your design.", + "summary": "Basic text input with label", + "import": "import { Input } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Basic Input" + } + ], + "snippet": "const Basic = () => " + }, + { + "id": "input--with-error", + "name": "WithError", + "description": "An input displaying an error state with an error message.\n\nError messages should be clear, concise, and provide actionable guidance to help users fix the issue. The input border and message text are styled in red to indicate the error state.", + "summary": "Input with validation error", + "import": "import { Input } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Input with Error" + } + ], + "snippet": "const WithError = () => " + }, + { + "id": "input--with-helper-text", + "name": "WithHelperText", + "description": "An input with helper text providing additional context or instructions.\n\nHelper text appears below the input and provides guidance without being an error. Use it to clarify format expectations, character limits, or provide helpful hints.", + "summary": "Input with helper text for guidance", + "import": "import { Input } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Input with Helper Text" + } + ], + "snippet": "const WithHelperText = () => " + }, + { + "id": "input--types", + "name": "Types", + "description": "Different input types for various data formats.\n\nUsing the correct input type improves the user experience by showing appropriate mobile keyboards and enabling browser validation features.", + "summary": "Various input types (email, tel, url, number)", + "import": "import { Input } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Input Types" + } + ], + "snippet": "const Types = () => (\n <>\n \n \n \n \n \n)" + }, + { + "id": "input--disabled", + "name": "Disabled", + "description": "A disabled input that cannot be interacted with.\n\nDisabled inputs are useful for displaying non-editable data in forms or for inputs that become available only after certain conditions are met.", + "summary": "Disabled input state", + "import": "import { Input } from '@storybook/design-system';", + "jsDocTag": [ + { + "key": "example", + "value": "Disabled Input" + } + ], + "snippet": "const Disabled = () => " + } + ], + "jsDocTag": [ + { + "key": "summary", + "value": "A flexible text input component with validation support" + }, + { + "key": "since", + "value": "1.0.0" + }, + { + "key": "component", + "value": "Input" + }, + { + "key": "accessibility", + "value": "WCAG 2.1 Level AA compliant" + } + ] + } + } +} diff --git a/eval/evals/100-flight-booking-plain/hooks.ts b/eval/evals/100-flight-booking-plain/hooks.ts new file mode 100644 index 00000000..eae0a49d --- /dev/null +++ b/eval/evals/100-flight-booking-plain/hooks.ts @@ -0,0 +1,13 @@ +import type { Hooks } from '../../types.ts'; + +const hooks: Hooks = { + postPrepareExperiment: async (experimentArgs, log) => { + // Custom logic to run after preparing the experiment + log.success( + `Post-prepare hook executed for experiment at ${experimentArgs.experimentPath}`, + ); + await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate async work + }, +}; + +export default hooks; diff --git a/eval/evals/100-flight-booking-plain/prompt.md b/eval/evals/100-flight-booking-plain/prompt.md new file mode 100644 index 00000000..3be2bc5f --- /dev/null +++ b/eval/evals/100-flight-booking-plain/prompt.md @@ -0,0 +1,36 @@ +Create a flight booking component that includes: + +- An autocomplete component for choosing source and destination from the following list of airports: + SYD: – Sydney Airport, Australia + MEL: – Melbourne Airport (Tullamarine), Australia + LAX: – Los Angeles International Airport, USA + JFK: – John F. Kennedy International Airport, New York, USA + LHR: – Heathrow Airport, London, UK + CDG: – Charles de Gaulle Airport, Paris, France + ATL: – Hartsfield–Jackson Atlanta International Airport, USA + DXB: – Dubai International Airport, UAE + HKG: – Hong Kong International Airport, Hong Kong + BNE: – Brisbane Airport, Australia + PER: – Perth Airport, Australia + DFW: – Dallas Fort Worth International Airport, USA + +- A toggle button for return vs one way +- One or two date selects that when clicked on triggers a popover with a calendar widget. + +The calendar widget shouldn't allow selecting dates in the past and the return flight must be after the departure flight. + + + +1. The component MUST be a default export in src/components/FlightBooking.tsx +2. The component MUST be added to the main.tsx file as the ONLY component being rendered +3. The component MUST take an optional onSubmit() prop that is called when the submit button is clicked +4. The element for the "One Way"-toggle SHOULD have "One Way" as its only content and SHOULD have data-testid="one-way" +5. The element for the "Return"-toggle SHOULD have a "Return" as its only content and SHOULD have data-testid="return" +6. The autocomplete to open the From airport picker SHOULD have "From" as its placeholder and SHOULD have data-testid="flight-trigger-from" +7. The autocomplete to open the To airport picker SHOULD have "To" as its placeholder and SHOULD have data-testid="flight-trigger-to" +8. Each element to select an airport in the pickers SHOULD have include both the shortcode and full airport name in its content and SHOULD have data-testid="airport-{SHORTCODE}" (e.g., "airport-MEL", "airport-LAX") +9. The element to open the Departure Date date select SHOULD have "Departure Date" as its initial content and SHOULD have data-testid="date-trigger-departure" +10. The (optional) element to open the Return Date date select SHOULD have "Return Date" as its initial content and SHOULD have data-testid="date-trigger-return" +11. Each date in the date selects SHOULD the day of month as its only content and SHOULD have data-testid="date-{DAY}" (e.g., "date-27", "date-15") +12. The submit button SHOULD have "Search Flights" as its only content and SHOULD have data-testid="search-flights" + diff --git a/eval/evals/110-flight-booking-reshaped/components.json b/eval/evals/110-flight-booking-reshaped/components.json new file mode 100644 index 00000000..c3b408c5 --- /dev/null +++ b/eval/evals/110-flight-booking-reshaped/components.json @@ -0,0 +1,3548 @@ +{ + "v": 0, + "components": { + "components-actionbar": { + "id": "components-actionbar", + "name": "ActionBar", + "path": "./src/components/ActionBar/tests/ActionBar.stories.tsx", + "stories": [ + { + "name": "positionRelative", + "snippet": "const positionRelative = () => (\n \n \n \n \n \n \n\n \n \n \n \n \n \n);" + }, + { + "name": "positionAbsolute", + "snippet": "const positionAbsolute = () => (\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "positionFixed", + "snippet": "const positionFixed = () => (\n <>\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n
\n \n);" + }, + { + "name": "elevated", + "snippet": "const elevated = () => (\n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "offset", + "snippet": "const offset = () => (\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "active", + "snippet": "const active = () => {\n const barToggle = useToggle();\n\n return (\n <>\n \n \n \n \n \n );\n};" + }, + { + "name": "padding", + "snippet": "const padding = () => (\n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n
\n);" + } + ], + "import": "import ActionBar from \"components/ActionBar\";\nimport Button from \"components/Button\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "ActionBar", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/ActionBar/ActionBar.tsx", + "actualName": "ActionBar", + "exportName": "default" + } + }, + "components-alert": { + "id": "components-alert", + "name": "Alert", + "path": "./src/components/Alert/tests/Alert.stories.tsx", + "stories": [ + { + "name": "color", + "snippet": "const color = () => (\n \n {([\"neutral\", \"primary\", \"critical\", \"positive\", \"warning\"] as const).map((color) => (\n \n \n {}}\n >\n View now\n \n {}}\n >\n Dismiss\n \n \n }\n >\n Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum\n has been the industry's standard\n \n \n ))}\n \n);" + }, + { + "name": "inline", + "snippet": "const inline = () => (\n \n \n \n {}}>\n View now\n \n {}}>\n Dismiss\n \n \n }\n >\n Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has\n been the industry's standard\n \n \n \n);" + }, + { + "name": "bleed", + "snippet": "const bleed = () => (\n \n \n \n Content\n \n \n \n \n Content\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n
\n);" + } + ], + "import": "import Alert from \"components/Alert\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport Link from \"components/Link\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Alert", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Alert/Alert.tsx", + "actualName": "Alert", + "exportName": "default" + } + }, + "components-autocomplete": { + "id": "components-autocomplete", + "name": "Autocomplete", + "path": "./src/components/Autocomplete/tests/Autocomplete.stories.tsx", + "stories": [ + { + "name": "active", + "snippet": "const active = (args) => {\n const toggle = useToggle(true);\n\n return (\n \n \n \n Label\n {\n args.handleOpen();\n toggle.activate();\n }}\n onClose={() => {\n args.handleClose();\n toggle.deactivate();\n }}\n onChange={(args) => console.log(args)}\n >\n {[\"Pizza\", \"Pie\", \"Ice-cream\"].map((v, i) => {\n return (\n \n {v}\n \n );\n })}\n \n \n \n \n );\n};" + }, + { + "name": "base", + "snippet": "const base = () => {\n const [value, setValue] = React.useState(\"\");\n\n return (\n \n \n \n Food\n setValue(args.value)}\n onBackspace={fn()}\n onItemSelect={fn()}>\n {[\"Pizza\", \"Pie\", \"Ice-cream\"].map((v, i) => {\n return (\n \n {v}\n \n );\n })}\n \n \n \n \n );\n};" + }, + { + "name": "itemData", + "snippet": "const itemData = () => {\n return (\n \n \n \n Label\n \n {[\"Pizza\", \"Pie\", \"Ice-cream\"].map((v, i) => {\n return (\n \n {v}\n \n );\n })}\n \n \n \n \n );\n};" + }, + { + "name": "itemDisabled", + "snippet": "const itemDisabled = () => {\n return (\n \n \n \n Label\n \n {[\"Pizza\", \"Pie\", \"Ice-cream\"].map((v, i) => {\n return (\n \n {v}\n \n );\n })}\n \n \n \n \n );\n};" + }, + { + "name": "multiselect", + "snippet": "const multiselect = () => {\n const options = [\n \"Pizza\",\n \"Pie\",\n \"Ice-cream\",\n \"Fries\",\n \"Salad\",\n \"Option 4\",\n \"Option 5\",\n \"Option 6\",\n ];\n\n const inputRef = React.useRef(null);\n const [values, setValues] = React.useState([\n \"Option 4\",\n \"Option 5\",\n \"Option 6\",\n \"Pizza\",\n \"Ice-cream\",\n ]);\n const [query, setQuery] = React.useState(\"\");\n\n const handleDismiss = (dismissedValue: string) => {\n const nextValues = values.filter((value) => value !== dismissedValue);\n setValues(nextValues);\n inputRef.current?.focus();\n };\n\n const valuesNode = values.map((value) => (\n handleDismiss(value)} key={value}>\n {value}\n \n ));\n\n return (\n \n Food\n setQuery(args.value)}\n onBackspace={() => {\n if (query.length === 0) {\n setValues((prev) => prev.slice(0, -1));\n }\n }}\n onItemSelect={(args) => {\n setQuery(\"\");\n setValues((prev) => [...prev, args.value]);\n }}\n >\n {options.map((v) => {\n if (!v.toLowerCase().includes(query.toLowerCase())) return;\n if (values.includes(v)) return;\n\n return (\n \n {v}\n \n );\n })}\n \n \n );\n};" + } + ], + "import": "import Autocomplete from \"components/Autocomplete\";\nimport Badge from \"components/Badge\";\nimport { Example } from \"utilities/storybook\";\nimport FormControl from \"components/FormControl\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Autocomplete", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Autocomplete/Autocomplete.tsx", + "actualName": "Autocomplete", + "exportName": "default" + } + }, + "components-avatar": { + "id": "components-avatar", + "name": "Avatar", + "path": "./src/components/Avatar/tests/Avatar.stories.tsx", + "stories": [ + { + "name": "src", + "snippet": "const src = () => (\n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "initials", + "snippet": "const initials = () => (\n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n);" + }, + { + "name": "squared", + "snippet": "const squared = () => (\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "colors", + "snippet": "const colors = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "fallback", + "snippet": "const fallback = (args) => {\n const [error, setError] = useState(false);\n\n return (\n {\n setError(true);\n args.handleError();\n },\n }}\n />\n );\n};" + }, + { + "name": "renderImage", + "snippet": "const renderImage = () => (\n \n \n }\n />\n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Avatar from \"components/Avatar\";\nimport { Example } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Avatar", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Avatar/Avatar.tsx", + "actualName": "Avatar", + "exportName": "default" + } + }, + "components-badge": { + "id": "components-badge", + "name": "Badge", + "path": "./src/components/Badge/tests/Badge.stories.tsx", + "stories": [ + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n Badge\n \n \n Badge\n \n \n Badge\n \n \n);" + }, + { + "name": "color", + "snippet": "const color = () => (\n \n \n \n Badge\n \n Badge\n \n \n Badge\n \n \n \n\n \n \n Badge\n \n Badge\n \n \n Badge\n \n \n \n\n \n \n Badge\n \n Badge\n \n \n Badge\n \n \n \n\n \n \n Badge\n \n Badge\n \n \n Badge\n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n Badge\n \n Badge\n \n \n \n \n \n Badge\n Badge\n \n \n \n \n Badge\n \n Badge\n \n \n \n \n);" + }, + { + "name": "icon", + "snippet": "const icon = () => (\n \n \n \n \n Badge\n \n \n \n \n \n \n \n Badge\n \n \n \n \n \n \n \n Badge\n \n\n \n \n \n \n);" + }, + { + "name": "onDismiss", + "snippet": "const onDismiss = () => \n \n Badge\n \n \n;" + }, + { + "name": "rounded", + "snippet": "const rounded = () => (\n \n \n \n Badge\n \n Badge\n \n \n Badge\n \n \n \n \n \n \n 2\n \n \n 2\n \n \n 2\n \n \n \n \n);" + }, + { + "name": "empty", + "snippet": "const empty = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "highlighted", + "snippet": "const highlighted = () => (\n \n \n \n Badge\n \n \n \n);" + }, + { + "name": "container", + "snippet": "const container = () => {\n const [hidden, setHidden] = React.useState(false);\n\n return (\n setHidden(!hidden)}>Toggle badges}>\n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n );\n};" + }, + { + "name": "href", + "snippet": "const href = () => (\n \n Badge\n \n);" + }, + { + "name": "onClick", + "snippet": "const onClick = () => Badge;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Avatar from \"components/Avatar\";\nimport Badge from \"components/Badge\";\nimport Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport Icon from \"components/Icon\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Badge", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Badge/Badge.tsx", + "actualName": "Badge", + "exportName": "default" + } + }, + "components-breadcrumbs": { + "id": "components-breadcrumbs", + "name": "Breadcrumbs", + "path": "./src/components/Breadcrumbs/tests/Breadcrumbs.stories.tsx", + "stories": [ + { + "name": "color", + "snippet": "const color = () => (\n \n \n \n {}}>Item 1\n {}}>Item 2\n Item 3\n \n \n\n \n \n {}}>Item 1\n {}}>Item 2\n Item 3\n \n \n \n);" + }, + { + "name": "item", + "snippet": "const item = () => (\n \n \n \n {}}>Item 1\n {}} disabled>\n Disabled item 2\n \n Item 3\n \n \n \n);" + }, + { + "name": "icon", + "snippet": "const icon = () => (\n \n \n \n {}}>\n Item 1\n \n {}}>Item 2\n Item 3\n \n \n \n);" + }, + { + "name": "slots", + "snippet": "const slots = () => (\n \n \n \n {}}>Item 1\n {}}>Item 2\n Item 3\n \n \n\n \n \n {}}>\n Item 1\n \n {}}>\n Item 2\n \n Item 3\n \n \n \n);" + }, + { + "name": "collapsed", + "snippet": "const collapsed = () => (\n \n \n \n {}}>Item 1\n {}}>Item 2\n {}}>Item 3\n {}}>Item 4\n Item 5\n \n \n\n \n \n {}}>Item 1\n {}}>Item 2\n {}}>Item 3\n {}}>Item 4\n Item 5\n \n \n\n \n \n {}}>Item 1\n {}}>Item 2\n {}}>Item 3\n {}}>Item 4\n Item 5\n \n \n \n);" + }, + { + "name": "multiline", + "snippet": "const multiline = () => (\n \n \n \n {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => (\n {}} key={i}>\n Item {i}\n \n ))}\n \n \n \n);" + }, + { + "name": "onClick", + "snippet": "const onClick = () => \n Trigger\n Trigger\n \n;" + }, + { + "name": "href", + "snippet": "const href = () => (\n \n Trigger\n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Trigger\n \n
\n);" + } + ], + "import": "import Badge from \"components/Badge\";\nimport Breadcrumbs from \"components/Breadcrumbs\";\nimport { Example } from \"utilities/storybook\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Breadcrumbs", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx", + "actualName": "Breadcrumbs", + "exportName": "default" + } + }, + "components-button": { + "id": "components-button", + "name": "Button", + "path": "./src/components/Button/tests/Button.stories.tsx", + "stories": [ + { + "name": "variantAndColor", + "snippet": "const variantAndColor = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "icon", + "snippet": "const icon = () => (\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "elevated", + "snippet": "const elevated = () => (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "rounded", + "snippet": "const rounded = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "loading", + "snippet": "const loading = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n
\n \n \n \n \n
\n
\n
\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n
\n \n \n \n \n
\n
\n
\n
\n);" + }, + { + "name": "aligner", + "snippet": "const aligner = () => (\n \n \n \n Content\n \n \n \n \n);" + }, + { + "name": "href", + "snippet": "const href = () => ;" + }, + { + "name": "onClick", + "snippet": "const onClick = () => ;" + }, + { + "name": "hrefOnClick", + "snippet": "const hrefOnClick = (args) => (\n {\n e.preventDefault();\n args.handleClick(e);\n }}\n href=\"https://reshaped.so\"\n >\n Trigger\n \n);" + }, + { + "name": "as", + "snippet": "const as = () => (\n \n \n \n \n \n {}}\n render={(props) =>
}\n attributes={{ \"data-testid\": \"render-el\" }}\n >\n Trigger\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + }, + { + "name": "group", + "snippet": "const group = () => (\n \n \n \n \n \n \n \n \n ))}\n \n \n \n \n {([\"neutral\", \"primary\", \"critical\", \"positive\"] as const).map((color) => (\n \n \n \n \n \n ))}\n \n \n \n \n {([\"neutral\", \"primary\", \"critical\", \"positive\", \"media\"] as const).map((color) => (\n \n \n \n \n \n ))}\n \n \n\n \n \n {([\"neutral\", \"primary\", \"critical\", \"positive\"] as const).map((color) => (\n \n \n \n \n \n ))}\n \n \n \n);" + }, + { + "name": "groupClassName", + "snippet": "const groupClassName = () => (\n
\n \n \n \n
\n);" + } + ], + "import": "import Avatar from \"components/Avatar\";\nimport Button from \"components/Button\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport Hotkey from \"components/Hotkey\";\nimport Image from \"components/Image\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Button", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Button/Button.tsx", + "actualName": "Button", + "exportName": "default" + } + }, + "components-calendar": { + "id": "components-calendar", + "name": "Calendar", + "path": "./src/components/Calendar/tests/Calendar.stories.tsx", + "stories": [ + { + "name": "defaultMonth", + "snippet": "const defaultMonth = () => (\n \n \n \n \n \n);" + }, + { + "name": "uncontrolled", + "snippet": "const uncontrolled = () => \n \n \n \n \n \n \n;" + }, + { + "name": "controlled", + "snippet": "const controlled = () => \n \n \n \n \n \n \n;" + }, + { + "name": "selectedDates", + "snippet": "const selectedDates = () => (\n \n \n \n \n \n);" + }, + { + "name": "minMax", + "snippet": "const minMax = () => (\n \n \n \n \n \n);" + }, + { + "name": "firstWeekDay", + "snippet": "const firstWeekDay = () => (\n \n \n \n \n \n);" + }, + { + "name": "translation", + "snippet": "const translation = () => (\n \n \n date.toLocaleDateString(\"nl\", { month: \"short\" })}\n renderSelectedMonthLabel={({ date }) =>\n date.toLocaleDateString(\"nl\", { month: \"long\", year: \"numeric\" })\n }\n renderWeekDay={({ date }) => date.toLocaleDateString(\"nl\", { weekday: \"short\" })}\n />\n \n \n);" + }, + { + "name": "ariaLabels", + "snippet": "const ariaLabels = () => (\n \n \n \"Test date\"}\n renderMonthAriaLabel={() => \"Test month\"}\n previousYearAriaLabel=\"Test previous year\"\n previousMonthAriaLabel=\"Test previous month\"\n monthSelectionAriaLabel=\"Test month selection\"\n />\n \n \n);" + }, + { + "name": "monthSelection", + "snippet": "const monthSelection = () => (\n \n \n \n \n \n);" + }, + { + "name": "keyboardNavigation", + "snippet": "const keyboardNavigation = () => (\n \n \n \n \n \n);" + } + ], + "import": "import Calendar from \"components/Calendar\";\nimport { Example } from \"utilities/storybook\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Calendar", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Calendar/Calendar.tsx", + "actualName": "Calendar", + "exportName": "default" + } + }, + "components-card": { + "id": "components-card", + "name": "Card", + "path": "./src/components/Card/tests/Card.stories.tsx", + "stories": [ + { + "name": "padding", + "snippet": "const padding = () => (\n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n);" + }, + { + "name": "selected", + "snippet": "const selected = () => (\n \n \n \n \n \n \n \n);" + }, + { + "name": "elevated", + "snippet": "const elevated = () => (\n \n \n \n \n \n \n \n);" + }, + { + "name": "bleed", + "snippet": "const bleed = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "height", + "snippet": "const height = () => (\n \n \n \n \n \n);" + }, + { + "name": "onClick", + "snippet": "const onClick = () => Trigger;" + }, + { + "name": "href", + "snippet": "const href = () => Trigger;" + }, + { + "name": "as", + "snippet": "const as = () => ;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Card from \"components/Card\";\nimport { Example, Placeholder } from \"utilities/storybook\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Card", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Card/Card.tsx", + "actualName": "Card", + "exportName": "default" + } + }, + "components-carousel": { + "id": "components-carousel", + "name": "Carousel", + "path": "./src/components/Carousel/tests/Carousel.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n \n Content\n Content\n Content\n Content\n Content\n Content\n \n);" + }, + { + "name": "visibleItems", + "snippet": "const visibleItems = () => (\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "gap", + "snippet": "const gap = () => (\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "bleed", + "snippet": "const bleed = () => (\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "navigationDisplay", + "snippet": "const navigationDisplay = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "instanceRef", + "snippet": "const instanceRef = (args) => {\n const carouselRef = React.useRef(null);\n const [index, setIndex] = React.useState(0);\n\n return (\n \n \n \n \n \n \n \n Index: {index}\n \n {\n args.handleChange(changeArgs);\n setIndex(changeArgs.index);\n }}\n >\n Item 0\n Item 1\n Item 2\n Item 3\n Item 4\n Item 5\n \n \n \n \n );\n};" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Item 0\n Item 1\n Item 2\n Item 3\n Item 4\n Item 5\n \n
\n);" + } + ], + "import": "import Button from \"components/Button\";\nimport Carousel from \"components/Carousel\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [ + { + "name": "navigateTo", + "docblock": null, + "modifiers": [], + "params": [ + { + "name": "index", + "optional": false, + "type": { "name": "number" } + } + ], + "returns": null + } + ], + "displayName": "Carousel", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Carousel/Carousel.tsx", + "actualName": "Carousel", + "exportName": "default" + } + }, + "components-checkbox": { + "id": "components-checkbox", + "name": "Checkbox", + "path": "./src/components/Checkbox/tests/Checkbox.stories.tsx", + "stories": [ + { + "name": "render", + "snippet": "const render = () => (\n \n Content\n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n Checkbox\n \n \n Checkbox\n \n \n \n \n \n \n Checkbox\n \n \n Checkbox\n \n \n \n \n \n \n Checkbox\n \n \n Checkbox\n \n \n \n \n \n \n Checkbox\n \n \n \n \n);" + }, + { + "name": "error", + "snippet": "const error = () => (\n \n \n \n Checkbox\n \n \n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n \n \n Checkbox\n \n \n \n \n Checkbox\n \n \n \n \n Checkbox\n \n \n \n);" + }, + { + "name": "checked", + "snippet": "const checked = () => Content\n ;" + }, + { + "name": "defaultChecked", + "snippet": "const defaultChecked = () => Content\n ;" + }, + { + "name": "indeterminate", + "snippet": "const indeterminate = () => (\n \n Content\n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Content\n \n
\n);" + } + ], + "import": "import Checkbox from \"components/Checkbox\";\nimport { Example } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Checkbox", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Checkbox/Checkbox.tsx", + "actualName": "Checkbox", + "exportName": "default" + } + }, + "components-checkboxgroup": { + "id": "components-checkboxgroup", + "name": "CheckboxGroup", + "path": "./src/components/CheckboxGroup/tests/CheckboxGroup.stories.tsx", + "stories": [ + { + "name": "value", + "snippet": "const value = () => \n \n {/* checked should be ignored */}\n Content\n \n Content 2\n \n;" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => \n \n {/* checked should be ignored */}\n Content\n \n Content 2\n \n;" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n Item 1\n Item 2\n \n);" + } + ], + "import": "import Checkbox from \"components/Checkbox\";\nimport CheckboxGroup from \"components/CheckboxGroup\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "CheckboxGroup", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/CheckboxGroup/CheckboxGroup.tsx", + "actualName": "CheckboxGroup", + "exportName": "default" + } + }, + "components-contextmenu": { + "id": "components-contextmenu", + "name": "ContextMenu", + "path": "./src/components/ContextMenu/tests/ContextMenu.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n \n \n
\n \n \n\n \n Item 1\n Item 2\n \n \n
\n
\n
\n);" + }, + { + "name": "handlers", + "snippet": "const handlers = () =>
\n \n \n \n Item\n \n \n
;" + } + ], + "import": "import ContextMenu from \"components/ContextMenu\";\nimport { Example } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "ContextMenu", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/ContextMenu/ContextMenu.tsx", + "actualName": "ContextMenu", + "exportName": "default" + } + }, + "components-divider": { + "id": "components-divider", + "name": "Divider", + "path": "./src/components/Divider/tests/Divider.stories.tsx", + "stories": [ + { + "name": "rendering", + "snippet": "const rendering = () => (\n \n \n \n \n\n \n \n \n \n \n);" + }, + { + "name": "vertical", + "snippet": "const vertical = () => (\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "label", + "snippet": "const label = () => {\n return (\n \n \n \n Centered label\n Start label\n End label\n \n \n\n \n \n \n \n or pick second option\n \n \n \n \n \n );\n};" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Divider from \"components/Divider\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Divider", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Divider/Divider.tsx", + "actualName": "Divider", + "exportName": "default" + } + }, + "components-dropdownmenu": { + "id": "components-dropdownmenu", + "name": "DropdownMenu", + "path": "./src/components/DropdownMenu/tests/DropdownMenu.stories.tsx", + "stories": [ + { + "name": "position", + "snippet": "const position = () => (\n \n \n \n \n {(attributes) => }\n \n \n Item 1\n Item 2\n \n \n \n\n \n \n \n {(attributes) => }\n \n \n Item 1\n Item 2\n \n \n \n\n \n \n \n {(attributes) => }\n \n \n Item 1\n Item 2\n \n \n \n
\n \n);" + }, + { + "name": "sections", + "snippet": "const sections = () => (\n \n \n \n \n {(attributes) => }\n \n \n \n Item 1\n Item 2\n \n\n \n Item 3\n Item 4\n \n \n \n \n \n);" + }, + { + "name": "submenu", + "snippet": "const submenu = () => (\n \n \n \n \n {(attributes) => }\n \n \n {}}>Item 1\n \n Item 2\n \n {}}>SubItem 1\n {}}>SubItem 2\n \n \n \n Item 3\n \n {}}>SubItem 2-1\n {}}>SubItem 2-2\n \n \n\n \n Item 4, disabled\n \n {}}>SubItem 3-1\n {}}>SubItem 3-2\n \n \n \n \n \n \n);" + }, + { + "name": "defaultActive", + "snippet": "const defaultActive = () => \n \n {(attributes) => }\n \n \n Item\n \n;" + }, + { + "name": "active", + "snippet": "const active = () => \n \n {(attributes) => }\n \n \n Item\n \n;" + }, + { + "name": "activeFalse", + "snippet": "const activeFalse = () => \n \n {(attributes) => }\n \n \n Item\n \n;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n {(attributes) => }\n \n \n Item\n \n \n
\n);" + }, + { + "name": "testScroll", + "snippet": "const testScroll = () => (\n \n \n
\n \n \n {(attributes) => }\n \n \n Item 1\n Item 2\n \n \n \n \n);" + }, + { + "name": "testTheme", + "snippet": "const testTheme = () => (\n \n \n \n \n \n \n \n \n);" + } + ], + "import": "import Button from \"components/Button\";\nimport DropdownMenu from \"components/DropdownMenu\";\nimport { Example } from \"utilities/storybook\";\nimport Theme from \"components/Theme\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "DropdownMenu", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/DropdownMenu/DropdownMenu.tsx", + "actualName": "DropdownMenu", + "exportName": "default" + } + }, + "components-fileupload": { + "id": "components-fileupload", + "name": "FileUpload", + "path": "./src/components/FileUpload/tests/FileUpload.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n \n \n \n \n \n \n
\n Drop files to attach, or{\" \"}\n \n browse\n \n
\n
\n
\n
\n);" + }, + { + "name": "inline", + "snippet": "const inline = () => {\n return (\n \n \n \n \n Upload\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {(props) => }\n \n \n \n );\n};" + }, + { + "name": "height", + "snippet": "const height = () => (\n \n \n \n \n \n Drop files to attach\n \n \n \n \n);" + }, + { + "name": "onChange", + "snippet": "const onChange = () =>
\n Content\n \n
;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Content\n \n
\n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport FileUpload from \"components/FileUpload\";\nimport Icon from \"components/Icon\";\nimport Image from \"components/Image\";\nimport Link from \"components/Link\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "FileUpload", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/FileUpload/FileUpload.tsx", + "actualName": "FileUpload", + "exportName": "default" + } + }, + "components-hotkey": { + "id": "components-hotkey", + "name": "Hotkey", + "path": "./src/components/Hotkey/tests/Hotkey.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t⌘K\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t}\n\t\t\t\t\tinputAttributes={{ \"aria-label\": \"hotkey test\" }}\n\t\t\t\t/>\n\t\t\t\n\t\t\n\t\n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n ⌘K\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport { Hotkey } from \"reshaped\";\nimport TextField from \"components/TextField\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Hotkey", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Hotkey/Hotkey.tsx", + "actualName": "Hotkey", + "exportName": "default" + } + }, + "components-link": { + "id": "components-link", + "name": "Link", + "path": "./src/components/Link/tests/Link.stories.tsx", + "stories": [ + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n Reshaped\n \n \n \n {}} variant=\"plain\">\n Link\n \n \n \n);" + }, + { + "name": "color", + "snippet": "const color = () => (\n \n \n Link\n \n \n Link\n \n \n Link\n \n \n Link\n \n \n Link\n \n \n);" + }, + { + "name": "icon", + "snippet": "const icon = () => (\n \n \n Link\n \n \n \n Link\n \n \n \n \n \n Instant delivery\n \n \n \n \n);" + }, + { + "name": "href", + "snippet": "const href = () => Trigger;" + }, + { + "name": "onClick", + "snippet": "const onClick = () => Trigger;" + }, + { + "name": "hrefOnClick", + "snippet": "const hrefOnClick = (args) => (\n {\n e.preventDefault();\n args.handleClick(e);\n }}\n href=\"https://reshaped.so\"\n >\n Trigger\n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n {}}>\n Trigger\n \n);" + }, + { + "name": "render", + "snippet": "const render = () =>
}>Trigger;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Trigger\n \n
\n);" + }, + { + "name": "testMultilineInText", + "snippet": "const testMultilineInText = () => (\n \n \n
\n Someone asked me to write this text that is boring to ready for everyone and to add \n this very very long link to it.\n
\n
\n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Link from \"components/Link\";\nimport Text from \"components/Text\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Link", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Link/Link.tsx", + "actualName": "Link", + "exportName": "default" + } + }, + "components-loader": { + "id": "components-loader", + "name": "Loader", + "path": "./src/components/Loader/tests/Loader.stories.tsx", + "stories": [ + { + "name": "size", + "snippet": "const size = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "color", + "snippet": "const color = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "ariaLabel", + "snippet": "const ariaLabel = () => ;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Loader from \"components/Loader\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Loader", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Loader/Loader.tsx", + "actualName": "Loader", + "exportName": "default" + } + }, + "components-menuitem": { + "id": "components-menuitem", + "name": "MenuItem", + "path": "./src/components/MenuItem/tests/MenuItem.stories.tsx", + "stories": [ + { + "name": "size", + "snippet": "const size = () => (\n \n \n {}} endSlot={⌘K}>\n Menu item\n \n \n \n {}}>\n Menu item\n \n \n \n {}}>\n Menu item\n \n \n \n {}}>\n Menu item\n \n \n \n);" + }, + { + "name": "color", + "snippet": "const color = () => (\n \n \n \n Menu item\n \n \n \n \n Menu item\n \n \n \n \n Menu item\n \n \n \n);" + }, + { + "name": "selected", + "snippet": "const selected = () => (\n \n \n \n Menu item\n \n \n \n \n Menu item\n \n \n \n \n Menu item\n \n \n \n);" + }, + { + "name": "roundedCorners", + "snippet": "const roundedCorners = () => (\n \n \n \n Menu item\n \n \n\n \n \n Menu item\n \n \n \n);" + }, + { + "name": "slots", + "snippet": "const slots = () => (\n \n \n } endSlot={} selected>\n Menu item\n \n \n \n);" + }, + { + "name": "aligner", + "snippet": "const aligner = () => (\n \n \n \n Heading\n \n {}}>\n Menu item\n \n \n \n \n\n \n \n Heading\n \n Menu item\n \n \n \n\n \n \n Heading\n \n \n Menu item\n \n \n \n \n \n);" + }, + { + "name": "href", + "snippet": "const href = () => Trigger;" + }, + { + "name": "onClick", + "snippet": "const onClick = () => Trigger;" + }, + { + "name": "hrefOnClick", + "snippet": "const hrefOnClick = (args) => (\n {\n e.preventDefault();\n args.handleClick(e);\n }}\n href=\"https://reshaped.so\"\n >\n Trigger\n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n {}}>\n Trigger\n \n);" + }, + { + "name": "as", + "snippet": "const as = () => (\n \n \n {}}\n render={(props) =>
}\n attributes={{ \"data-testid\": \"render-el\" }}\n >\n Trigger\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Trigger\n \n
\n);" + }, + { + "name": "alignerClassName", + "snippet": "const alignerClassName = () => (\n
\n \n Trigger\n \n
\n);" + } + ], + "import": "import { Example, Placeholder } from \"utilities/storybook\";\nimport Hotkey from \"components/Hotkey\";\nimport MenuItem from \"components/MenuItem\";\nimport Text from \"components/Text\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "MenuItem", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/MenuItem/MenuItem.tsx", + "actualName": "MenuItem", + "exportName": "default" + } + }, + "components-modal": { + "id": "components-modal", + "name": "Modal", + "path": "./src/components/Modal/tests/Modal.stories.tsx", + "stories": [ + { + "name": "position", + "snippet": "const position = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "padding", + "snippet": "const padding = () => (\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "overflow", + "snippet": "const overflow = () => (\n \n \n \n \n \n \n\n \n \n \n \n \n \n);" + }, + { + "name": "composition", + "snippet": "const composition = () => (\n \n \n \n \n \n);" + }, + { + "name": "overlay", + "snippet": "const overlay = () => (\n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "flags", + "snippet": "const flags = () => {\n return (\n \n \n \n \n \n );\n};" + }, + { + "name": "containerRef", + "snippet": "const containerRef = () => {\n const containerRef = React.useRef(null);\n const containerRef2 = React.useRef(null);\n const toggle = useToggle();\n const toggle2 = useToggle();\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "renderProps", + "snippet": "const renderProps = () => (\n \n Title\n Content\n \n);" + }, + { + "name": "handlers", + "snippet": "const handlers = () => {\n const overlayToggle = useToggle();\n\n return (\n <>\n \n {\n overlayToggle.deactivate();\n args.handleClose(closeArgs);\n }}\n onOpen={fn()}\n onAfterOpen={fn()}\n onAfterClose={fn()}>\n TitleContent\n \n \n );\n};" + }, + { + "name": "className", + "snippet": "const className = () => (\n \n Title\n Content\n \n);" + }, + { + "name": "edgeCases", + "snippet": "const edgeCases = () => {\n const menuModalToggle = useToggle();\n const menuModalToggleInner = useToggle();\n const scrollModalToggle = useToggle();\n const inputRef = React.useRef(null);\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {\n inputRef.current?.focus();\n }}\n >\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Content\n \n \n \n \n \n \n \n {(attributes) => }\n \n \n Open dialog\n Item 2\n \n \n \n \n \n \n {(attributes) => }\n \n \n Item 1\n Item 2\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n );\n};" + }, + { + "name": "trapFocusEdgeCases", + "snippet": "const trapFocusEdgeCases = () => {\n const toggle = useToggle();\n\n return (\n \n \n \n \n \n \n \n Option 1\n \n \n Option 2\n \n \n \n \n \n \n );\n};" + } + ], + "import": "import Button from \"components/Button\";\nimport Dismissible from \"components/Dismissible\";\nimport DropdownMenu from \"components/DropdownMenu\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport Modal from \"components/Modal\";\nimport Radio from \"components/Radio\";\nimport Switch from \"components/Switch\";\nimport TextField from \"components/TextField\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Modal", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Modal/Modal.tsx", + "actualName": "Modal", + "exportName": "default" + } + }, + "components-numberfield": { + "id": "components-numberfield", + "name": "NumberField", + "path": "./src/components/NumberField/tests/NumberField.stories.tsx", + "stories": [ + { + "name": "variant", + "snippet": "const variant = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "size", + "snippet": "const size = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "disabled", + "snippet": "const disabled = () => ;" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => ;" + }, + { + "name": "value", + "snippet": "const value = () => ;" + }, + { + "name": "minMax", + "snippet": "const minMax = () => (\n \n);" + }, + { + "name": "step", + "snippet": "const step = () => (\n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + }, + { + "name": "formControl", + "snippet": "const formControl = () => (\n \n \n \n Label\n \n Helper\n \n \n\n \n \n Label\n \n Helper\n \n \n\n \n \n Label\n \n Error\n \n \n \n);" + }, + { + "name": "valueChanges", + "snippet": "const valueChanges = () => (\n \n \n \n \n \n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport FormControl from \"components/FormControl\";\nimport NumberField from \"components/NumberField\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "NumberField", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/NumberField/NumberField.tsx", + "actualName": "NumberField", + "exportName": "default" + } + }, + "components-pagination": { + "id": "components-pagination", + "name": "Pagination", + "path": "./src/components/Pagination/tests/Pagination.stories.tsx", + "stories": [ + { + "name": "truncate", + "snippet": "const truncate = () => {\n return (\n \n \n `Page ${args.page}`}\n />\n \n \n `Page ${args.page}`}\n />\n \n \n `Page ${args.page}`}\n />\n \n \n `Page ${args.page}`}\n />\n \n \n );\n};" + }, + { + "name": "render", + "snippet": "const render = () => (\n
\n `Page ${args.page}`}\n />\n
\n);" + }, + { + "name": "defaultPage", + "snippet": "const defaultPage = () =>
\n \n
;" + }, + { + "name": "page", + "snippet": "const page = () =>
\n \n
;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Pagination from \"components/Pagination\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Pagination", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Pagination/Pagination.tsx", + "actualName": "Pagination", + "exportName": "default" + } + }, + "components-pinfield": { + "id": "components-pinfield", + "name": "PinField", + "path": "./src/components/PinField/tests/PinField.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => ;" + }, + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n);" + }, + { + "name": "valueLength", + "snippet": "const valueLength = () => (\n \n);" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => ;" + }, + { + "name": "value", + "snippet": "const value = () => ;" + }, + { + "name": "pattern", + "snippet": "const pattern = () => ;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + }, + { + "name": "formControl", + "snippet": "const formControl = () => (\n \n Label\n \n \n);" + }, + { + "name": "keyboard", + "snippet": "const keyboard = () => ;" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport FormControl from \"components/FormControl\";\nimport PinField from \"components/PinField\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "PinField", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/PinField/PinField.tsx", + "actualName": "PinField", + "exportName": "default" + } + }, + "components-popover": { + "id": "components-popover", + "name": "Popover", + "path": "./src/components/Popover/tests/Popover.stories.tsx", + "stories": [ + { + "name": "position", + "snippet": "const position = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "widthNumber", + "snippet": "const widthNumber = () => ;" + }, + { + "name": "widthFull", + "snippet": "const widthFull = () => ;" + }, + { + "name": "padding", + "snippet": "const padding = () => (\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "elevation", + "snippet": "const elevation = () => (\n \n \n \n \n \n);" + }, + { + "name": "defaultActive", + "snippet": "const defaultActive = () => \n \n {(attributes) => }\n \n Content\n;" + }, + { + "name": "active", + "snippet": "const active = () => \n \n {(attributes) => }\n \n Content\n;" + }, + { + "name": "activeFalse", + "snippet": "const activeFalse = () => \n \n {(attributes) => }\n \n Content\n;" + }, + { + "name": "dismissible", + "snippet": "const dismissible = () => \n \n {(attributes) => }\n \n \n Content\n \n \n;" + }, + { + "name": "autoFocus", + "snippet": "const autoFocus = () => (\n \n \n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n {(attributes) => }\n \n \n Content\n \n \n
\n);" + }, + { + "name": "testNested", + "snippet": "const testNested = () => (\n \n \n {(attributes) => }\n \n \n \n Popover content\n \n \n {(attributes) => }\n \n Hello\n \n \n \n \n);" + }, + { + "name": "testWithTooltip", + "snippet": "const testWithTooltip = () => (\n \n \n {(tooltipAttributes) => (\n \n \n {(attributes) => (\n \n Open\n \n )}\n \n \n \n Popover content\n \n \n \n \n )}\n \n \n);" + }, + { + "name": "testContentEditable", + "snippet": "const testContentEditable = () => {\n const [active, setActive] = useState(false);\n\n return (\n \n \n {(attributes) => }\n \n \n \n \n \n \n\n \n {\n setActive(e.currentTarget.innerText.startsWith(\"@\"));\n }}\n onKeyDown={(e) => {\n console.log(e.key);\n if (e.key === \"Enter\" && active) {\n e.preventDefault();\n e.currentTarget.innerText = \"@hello\";\n setActive(false);\n }\n }}\n />\n \n\n setActive(false)}\n originCoordinates={{ x: 300, y: 300 }}\n trapFocusMode=\"selection-menu\"\n >\n \n \n {}}>Action\n {}}>Close\n \n \n \n \n \n \n );\n};" + }, + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n {(attributes) => }\n \n \n \n \n \n \n \n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport MenuItem from \"components/MenuItem\";\nimport Popover from \"components/Popover\";\nimport ScrollArea from \"components/ScrollArea\";\nimport Tooltip from \"components/Tooltip\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Popover", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Popover/Popover.tsx", + "actualName": "Popover", + "exportName": "default" + } + }, + "components-progress": { + "id": "components-progress", + "name": "Progress", + "path": "./src/components/Progress/tests/Progress.stories.tsx", + "stories": [ + { + "name": "value", + "snippet": "const value = () => (\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "color", + "snippet": "const color = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "duration", + "snippet": "const duration = () => {\n const [active, setActive] = React.useState(false);\n\n const handleChange = () => {\n setActive((state) => !state);\n };\n\n return (\n \n \n \n \n \n \n\n \n \n \n \n );\n};" + }, + { + "name": "render", + "snippet": "const render = () => ;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport Progress from \"components/Progress\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Progress", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Progress/Progress.tsx", + "actualName": "Progress", + "exportName": "default" + } + }, + "components-progressindicator": { + "id": "components-progressindicator", + "name": "ProgressIndicator", + "path": "./src/components/ProgressIndicator/tests/ProgressIndicator.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => {\n const [activeIndex, setActiveIndex] = React.useState(0);\n const total = 10;\n\n return (\n \n \n \n \n {\n setActiveIndex((prev) => Math.max(0, prev - 1));\n }}\n >\n Previous\n \n {\n setActiveIndex((prev) => Math.min(total - 1, prev + 1));\n }}\n >\n Next\n \n Index: {activeIndex}\n \n\n \n }\n >\n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "color", + "snippet": "const color = () => {\n return (\n \n \n \n \n \n \n }\n >\n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "ariaLabel", + "snippet": "const ariaLabel = () => ;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport ProgressIndicator from \"components/ProgressIndicator\";\nimport Scrim from \"components/Scrim\";\nimport Text from \"components/Text\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "ProgressIndicator", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx", + "actualName": "ProgressIndicator", + "exportName": "default" + } + }, + "components-radio": { + "id": "components-radio", + "name": "Radio", + "path": "./src/components/Radio/tests/Radio.stories.tsx", + "stories": [ + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n Radio\n \n \n \n \n \n \n Radio\n \n \n \n \n \n \n Radio\n \n \n \n \n \n \n Radio\n \n \n \n \n);" + }, + { + "name": "error", + "snippet": "const error = () => (\n \n \n \n Radio\n \n \n \n);" + }, + { + "name": "render", + "snippet": "const render = () => (\n \n Content\n \n);" + }, + { + "name": "checked", + "snippet": "const checked = () => Content\n ;" + }, + { + "name": "checkedFalse", + "snippet": "const checkedFalse = () => Content\n ;" + }, + { + "name": "defaultChecked", + "snippet": "const defaultChecked = () => Content\n ;" + }, + { + "name": "defaultCheckedFalse", + "snippet": "const defaultCheckedFalse = () => Content\n ;" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n Content\n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Content\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Radio from \"components/Radio\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Radio", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Radio/Radio.tsx", + "actualName": "Radio", + "exportName": "default" + } + }, + "components-radiogroup": { + "id": "components-radiogroup", + "name": "RadioGroup", + "path": "./src/components/RadioGroup/tests/RadioGroup.stories.tsx", + "stories": [ + { + "name": "value", + "snippet": "const value = () => \n {/* checked should be ignored */}\n Content\n \n Content 2\n;" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => \n {/* checked should be ignored */}\n Content\n \n Content 2\n;" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n Content\n \n);" + } + ], + "import": "import Radio from \"components/Radio\";\nimport RadioGroup from \"components/RadioGroup\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "RadioGroup", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/RadioGroup/RadioGroup.tsx", + "actualName": "RadioGroup", + "exportName": "default" + } + }, + "components-resizable": { + "id": "components-resizable", + "name": "Resizable", + "path": "./src/components/Resizable/tests/Resizable.stories.tsx", + "stories": [ + { + "name": "direction", + "snippet": "const direction = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n {/* Test that page doesn't scroll on dragging */}\n
\n \n );\n};" + }, + { + "name": "children", + "snippet": "const children = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};" + }, + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "layout", + "snippet": "const layout = () => (\n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n \n \n \n \n \n \n
\n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport Resizable from \"components/Resizable\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Resizable", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Resizable/Resizable.tsx", + "actualName": "Resizable", + "exportName": "default" + } + }, + "components-scrim": { + "id": "components-scrim", + "name": "Scrim", + "path": "./src/components/Scrim/tests/Scrim.stories.tsx", + "stories": [ + { + "name": "position", + "snippet": "const position = () => (\n \n \n }>Scrim\n \n\n \n }>\n Scrim\n \n \n\n \n }>\n Scrim\n \n \n\n \n }>\n Scrim\n \n \n\n \n }>\n Scrim\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Content\n \n
\n);" + }, + { + "name": "composition", + "snippet": "const composition = () => (\n \n \n
\n Text\n
\n
\n
\n);" + } + ], + "import": "import { Example, Placeholder } from \"utilities/storybook\";\nimport Scrim from \"components/Scrim\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Scrim", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Scrim/Scrim.tsx", + "actualName": "Scrim", + "exportName": "default" + } + }, + "components-select": { + "id": "components-select", + "name": "Select", + "path": "./src/components/Select/tests/Select.stories.tsx", + "stories": [ + { + "name": "nativeRender", + "snippet": "const nativeRender = () => (\n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "customRender", + "snippet": "const customRender = () => (\n \n \n \n Dog\n Turtle\n \n \n \n \n \n Pigeon\n Parrot\n \n \n Whale\n Dolphin\n \n \n \n \n);" + }, + { + "name": "nativeHandlers", + "snippet": "const nativeHandlers = () => \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n;" + }, + { + "name": "customHandlers", + "snippet": "const customHandlers = () => \n \n \n Dog\n Turtle\n \n \n \n \n Dog\n Turtle\n \n \n \n \n Dog\n Turtle\n \n \n;" + }, + { + "name": "triggerOnly", + "snippet": "const triggerOnly = (args) => {\n const toggle = useToggle();\n const [value, setValue] = React.useState(\"Dog\");\n\n const handleClick: SelectProps[\"onClick\"] = (e) => {\n args.handleClick(e);\n toggle.toggle();\n };\n\n return (\n \n \n \n {value}\n \n \n
\n {\n setValue(\"Dog\");\n toggle.deactivate();\n }}\n attributes={{\n role: \"option\",\n }}\n >\n Dog\n \n {\n setValue(\"Turtle\");\n toggle.deactivate();\n }}\n >\n Turtle\n \n
\n \n
\n
\n );\n};" + }, + { + "name": "multiple", + "snippet": "const multiple = () => \n \n \n Dog\n Turtle\n \n \n;" + }, + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n \n \n \n\n \n \n Dog\n Turtle\n \n \n\n \n \n \n \n \n \n\n \n \n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n Dog\n Turtle\n \n \n \n \n Dog\n Turtle\n \n \n \n \n Dog\n Turtle\n \n \n \n \n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "startSlot", + "snippet": "const startSlot = () => (\n \n \n \n Dog\n Turtle\n \n \n\n \n }\n >\n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "renderValue", + "snippet": "const renderValue = () => {\n const options = [\n {\n value: \"1\",\n label: \"Title 1\",\n subtitle: \"Subtitle 1\",\n },\n {\n value: \"2\",\n label: \"Title 2\",\n subtitle: \"Subtitle 2\",\n },\n ];\n\n return (\n \n \n \n {options.map((option) => (\n \n {option.label}\n \n {option.subtitle}\n \n \n ))}\n \n \n\n \n \n {options.map((option) => (\n \n {option.label}\n \n {option.subtitle}\n \n \n ))}\n \n \n\n \n Title {args.value}}\n >\n {options.map((option) => (\n \n {option.label}\n \n {option.subtitle}\n \n \n ))}\n \n \n\n \n Titles {args.value.join(\", \")}}\n >\n {options.map((option) => (\n \n {option.label}\n \n {option.subtitle}\n \n \n ))}\n \n \n \n );\n};" + }, + { + "name": "error", + "snippet": "const error = () => (\n \n \n \n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n \n \n \n \n \n \n \n \n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n \n \n \n \n \n \n \n \n \n Dog\n Turtle\n \n \n \n);" + }, + { + "name": "fallback", + "snippet": "const fallback = () => (\n \n \n \n {[...Array(100)].map((_, index) => (\n \n Item {index + 1}\n \n ))}\n \n
\n \n {[...Array(100)].map((_, index) => (\n \n Item {index + 1}\n \n ))}\n \n
\n
\n);" + }, + { + "name": "formControl", + "snippet": "const formControl = () => (\n \n \n \n Animal\n \n Dog\n Turtle\n \n This field is required\n \n \n \n);" + }, + { + "name": "testComposition", + "snippet": "const testComposition = () => (\n \n \n \n \n Dog\n Turtle\n \n Hello\n \n \n \n);" + } + ], + "import": "import Badge from \"components/Badge\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport FormControl from \"components/FormControl\";\nimport MenuItem from \"components/MenuItem\";\nimport Modal from \"components/Modal\";\nimport Select from \"components/Select\";\nimport Text from \"components/Text\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Select", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Select/Select.tsx", + "actualName": "Select", + "exportName": "default" + } + }, + "components-skeleton": { + "id": "components-skeleton", + "name": "Skeleton", + "path": "./src/components/Skeleton/tests/Skeleton.stories.tsx", + "stories": [ + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n);" + }, + { + "name": "radius", + "snippet": "const radius = () => (\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Skeleton from \"components/Skeleton\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Skeleton", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Skeleton/Skeleton.tsx", + "actualName": "Skeleton", + "exportName": "default" + } + }, + "components-slider": { + "id": "components-slider", + "name": "Slider", + "path": "./src/components/Slider/tests/Slider.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n \n \n \n \n \n \n \n \n \n \n
\n \n);" + }, + { + "name": "orientation", + "snippet": "const orientation = () => (\n \n \n \n \n \n \n \n \n);" + }, + { + "name": "minMax", + "snippet": "const minMax = () => (\n \n \n \n \n \n \n \n max\">\n \n \n \n);" + }, + { + "name": "step", + "snippet": "const step = () => (\n \n \n \n \n\n \n \n \n\n \n \n \n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n \n \n \n\n \n \n \n \n);" + }, + { + "name": "renderValue", + "snippet": "const renderValue = () => (\n \n \n `$${args.value}`} />\n \n \n \n \n \n);" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => \n \n;" + }, + { + "name": "value", + "snippet": "const value = () => \n \n;" + }, + { + "name": "rangeDefaultValue", + "snippet": "const rangeDefaultValue = () => \n \n;" + }, + { + "name": "rangeValue", + "snippet": "const rangeValue = () => \n \n;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n
\n);" + }, + { + "name": "testForm", + "snippet": "const testForm = (args) => {\n const formRef = React.useRef(null);\n const [data, setData] = React.useState<[string, FormDataEntryValue][]>([]);\n\n const handleChange = (e: React.FormEvent) => {\n const formData = new FormData(e.currentTarget);\n const nextState = [...formData.entries()];\n\n args.handleFormChange({ formData: nextState });\n setData(nextState);\n };\n\n return (\n \n
\n \n \n\n {data.map((v) => v.join(\": \")).join(\", \")}\n
\n );\n};" + }, + { + "name": "testSwipe", + "snippet": "const testSwipe = () => {\n const toggle = useToggle(true);\n\n return (\n \n \n Modal\n \n \n \n );\n};" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Modal from \"components/Modal\";\nimport Slider from \"components/Slider\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Slider", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Slider/Slider.tsx", + "actualName": "Slider", + "exportName": "default" + } + }, + "components-stepper": { + "id": "components-stepper", + "name": "Stepper", + "path": "./src/components/Stepper/tests/Stepper.stories.tsx", + "stories": [ + { + "name": "direction", + "snippet": "const direction = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "labelDisplay", + "snippet": "const labelDisplay = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "gap", + "snippet": "const gap = () => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n
\n);" + }, + { + "name": "edgeCases", + "snippet": "const edgeCases = () => (\n \n \n \n \n \n);" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example, Placeholder } from \"utilities/storybook\";\nimport Stepper from \"components/Stepper\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Stepper", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Stepper/Stepper.tsx", + "actualName": "Stepper", + "exportName": "default" + } + }, + "components-switch": { + "id": "components-switch", + "name": "Switch", + "path": "./src/components/Switch/tests/Switch.stories.tsx", + "stories": [ + { + "name": "size", + "snippet": "const size = () => (\n\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\n);" + }, + { + "name": "label", + "snippet": "const label = () => (\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\n\t\t\n\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tWi-fi\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n);" + }, + { + "name": "defaultChecked", + "snippet": "const defaultChecked = () => Label\n ;" + }, + { + "name": "checked", + "snippet": "const checked = () => Label\n ;" + }, + { + "name": "disabled", + "snippet": "const disabled = () => (\n \n \n \n \n \n \n \n \n \n Switch\n \n \n \n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n Label\n \n
\n);" + } + ], + "import": "import { Example } from \"utilities/storybook\";\nimport Switch from \"components/Switch\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Switch", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Switch/Switch.tsx", + "actualName": "Switch", + "exportName": "default" + } + }, + "components-table": { + "id": "components-table", + "name": "Table", + "path": "./src/components/Table/tests/Table.stories.tsx", + "stories": [ + { + "name": "layout", + "snippet": "const layout = () => (\n \n \n \n \n \n Column 1\n Column 2\n \n \n \n \n Cell 1\n Cell 2\n \n \n
\n
\n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n Cell 3\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n Column 1\n Column 2\n Column 2\n \n \n \n \n \n Cell 2\n Cell 3\n \n
\n
\n \n \n \n \n Column 1\n \n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n
\n);" + }, + { + "name": "border", + "snippet": "const border = () => (\n \n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n
\n);" + }, + { + "name": "rows", + "snippet": "const rows = () => (\n \n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n Column 1\n Column 2\n \n {}}>\n Cell 1\n Cell 2\n \n
\n
\n
\n);" + }, + { + "name": "render", + "snippet": "const render = () => (\n \n \n \n Heading\n Heading\n \n \n \n \n Content\n Content\n \n \n
\n);" + }, + { + "name": "tbody", + "snippet": "const tbody = () => (\n \n \n Heading\n Heading\n \n
\n);" + }, + { + "name": "tabIndex", + "snippet": "const tabIndex = () => (\n \n \n \n \n {}}>\n \n \n {} }}>\n \n \n
\n);" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n \n
\n
\n);" + }, + { + "name": "edgeCases", + "snippet": "const edgeCases = () => (\n \n \n \n \n Column 1\n Column 2\n \n \n Cell 1\n Cell 2\n \n
\n
\n \n \n \n \n \n Column 1\n \n \n Column 2\n \n \n \n Cell 1\n Cell 2\n Cell 3\n \n \n Cell 1\n Cell 2\n Cell 3\n \n
\n
\n
\n \n \n \n Column 1\n Column 2\n Column 3\n Long heading title\n \n \n Cell 1\n Cell 2\n Cell 3\n Cell 4\n \n
\n
\n
\n);" + }, + { + "name": "examples", + "snippet": "const examples = () => (\n \n \n \n \n \n);" + } + ], + "import": "import Card from \"components/Card\";\nimport Checkbox from \"components/Checkbox\";\nimport { Example } from \"utilities/storybook\";\nimport Table from \"components/Table\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Table", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Table/Table.tsx", + "actualName": "Table", + "exportName": "default" + } + }, + "components-tabs": { + "id": "components-tabs", + "name": "Tabs", + "path": "./src/components/Tabs/tests/Tabs.stories.tsx", + "stories": [ + { + "name": "base", + "snippet": "const base = () => (\n \n \n Item 1\n Item 2\n \n\n \n Content 1\n \n \n Content 2\n \n \n);" + }, + { + "name": "variant", + "snippet": "const variant = () => (\n \n \n \n \n Long item 2\n Item 1\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n);" + }, + { + "name": "size", + "snippet": "const size = () => (\n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n\n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n);" + }, + { + "name": "direction", + "snippet": "const direction = () => (\n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n \n \n \n);" + }, + { + "name": "icon", + "snippet": "const icon = () => (\n \n \n \n \n \n Item 1\n \n \n Long item 2\n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "equalWidth", + "snippet": "const equalWidth = () => (\n \n \n \n \n \n Item 1\n \n \n Long item 2\n \n \n Very long item 3\n \n \n \n \n \n);" + }, + { + "name": "href", + "snippet": "const href = () => (\n \n \n \n \n \n Item 1\n \n \n Long item 2\n \n \n Very long item 3\n \n \n \n \n \n);" + }, + { + "name": "disabled", + "snippet": "const disabled = () => {\n return (\n \n \n \n \n Item 1\n \n Item 2\n \n Item 3\n \n\n \n Content 1\n \n \n Content 2\n \n \n Content 3\n \n \n \n\n \n \n \n \n Item 1\n \n \n Item 2\n \n \n Item 3\n \n \n\n \n Content 1\n \n \n Content 2\n \n \n Content 3\n \n \n \n \n );\n};" + }, + { + "name": "defaultValue", + "snippet": "const defaultValue = () => \n \n Item 1\n Item 2\n \n Content 1\n Content 2\n;" + }, + { + "name": "value", + "snippet": "const value = () => \n \n Item 1\n Item 2\n \n Content 1\n Content 2\n;" + }, + { + "name": "className", + "snippet": "const className = () => (\n
\n \n \n \n Item\n \n \n\n \n \n
\n);" + }, + { + "name": "testFocusableContent", + "snippet": "const testFocusableContent = () => (\n \n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n\n Tab 1\n Tab 2\n Tab 3\n \n \n \n\n \n \n \n \n Item 1\n Long item 2\n Very long item 3\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n);" + }, + { + "name": "testComposition", + "snippet": "const testComposition = () => (\n \n \n \n \n {[...Array(8)].map((_, i) => (\n \n Very long item {i}\n \n ))}\n \n\n {[...Array(8)].map((_, i) => (\n \n Tab {i}\n \n ))}\n \n \n \n \n \n Item 1\n Long item 2\n \n \n Item 3\n \n \n \n \n \n \n \n \n Item 1\n \n Long item 2\n Very long item 3\n \n \n \n \n);" + }, + { + "name": "testEdgeCaseDom", + "snippet": "const testEdgeCaseDom = () => {\n const [activeItem, setActiveItem] = React.useState(\"1\");\n const sectionsRef = React.useRef(null);\n\n return (\n \n \n \n \n setActiveItem(args.value)}>\n \n Item 1\n Item 2\n Item 3\n Item 4\n \n \n\n {\n setActiveItem(Math.min(4, Math.floor(args.y * 10) + 1).toString());\n }}\n >\n \n \n Section 1\n\n \n {[...Array(4)].map((_, i) => (\n \n ))}\n \n \n\n \n Section 2\n\n \n {[...Array(4)].map((_, i) => (\n \n ))}\n \n \n\n \n Section 3\n\n \n {[...Array(4)].map((_, i) => (\n \n ))}\n \n \n\n \n Section 4\n\n \n {[...Array(4)].map((_, i) => (\n \n ))}\n \n \n \n \n \n \n \n \n );\n};" + } + ], + "import": "import Button from \"components/Button\";\nimport { Example } from \"utilities/storybook\";\nimport ScrollArea from \"components/ScrollArea\";\nimport Tabs from \"components/Tabs\";\nimport Text from \"components/Text\";\nimport View from \"components/View\";", + "jsDocTags": {}, + "reactDocgen": { + "description": "", + "methods": [], + "displayName": "Tabs", + "definedInFile": "/Users/jeppe/dev/temp/reshaped/src/components/Tabs/Tabs.tsx", + "actualName": "Tabs", + "exportName": "default" + } + }, + "components-textarea": { + "id": "components-textarea", + "name": "TextArea", + "path": "./src/components/TextArea/tests/TextArea.stories.tsx", + "stories": [ + { + "name": "variants", + "snippet": "const variants = () => (\n \n \n