diff --git a/frontends/nextjs/.storybook/main.ts b/frontends/nextjs/.storybook/main.ts new file mode 100644 index 000000000..debb4b3e2 --- /dev/null +++ b/frontends/nextjs/.storybook/main.ts @@ -0,0 +1,40 @@ +import type { StorybookConfig } from '@storybook/react-vite' +import { mergeConfig } from 'vite' +import path from 'path' + +const config: StorybookConfig = { + stories: [ + '../src/**/*.mdx', + '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', + // Include Lua package stories + '../src/storybook/**/*.stories.@(js|jsx|mjs|ts|tsx)', + ], + addons: [ + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-vite', + options: {}, + }, + docs: {}, + staticDirs: [ + // Serve package static content + { from: '../../packages', to: '/packages' }, + ], + async viteFinal(config) { + return mergeConfig(config, { + resolve: { + alias: { + '@': path.resolve(__dirname, '../src'), + }, + }, + // Mock Node.js modules that aren't available in browser + define: { + 'process.env': {}, + }, + }) + }, +} + +export default config diff --git a/frontends/nextjs/.storybook/preview.tsx b/frontends/nextjs/.storybook/preview.tsx new file mode 100644 index 000000000..faf043d97 --- /dev/null +++ b/frontends/nextjs/.storybook/preview.tsx @@ -0,0 +1,43 @@ +import type { Preview } from '@storybook/react' +import React from 'react' + +// Import global styles +import '../src/app/globals.scss' + +// Create a mock context provider for Storybook +const MockProviders: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return <>{children} +} + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + // Default viewport + viewport: { + defaultViewport: 'desktop', + }, + // Background options + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#1a1a2e' }, + { name: 'canvas', value: '#f5f5f5' }, + ], + }, + }, + decorators: [ + (Story) => ( + + + + ), + ], +} + +export default preview diff --git a/frontends/nextjs/next.config.ts b/frontends/nextjs/next.config.ts index 825344c8d..b2496bf86 100644 --- a/frontends/nextjs/next.config.ts +++ b/frontends/nextjs/next.config.ts @@ -99,7 +99,10 @@ const nextConfig: NextConfig = { path: false, crypto: false, stream: false, + 'stream/promises': false, os: false, + buffer: false, + util: false, } } diff --git a/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts b/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts new file mode 100644 index 000000000..8d46312f9 --- /dev/null +++ b/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts @@ -0,0 +1,148 @@ +/** + * Mock Lua Engine for Storybook + * + * Since fengari requires a real Lua VM, we mock the execution + * by interpreting the JSON-like component trees that Lua packages produce. + * This allows Storybook to render Lua packages without running actual Lua code. + */ + +import type { LuaUIComponent } from '@/lib/lua/ui/types/lua-ui-package' +import type { JsonValue } from '@/types/utility-types' + +export interface MockLuaContext { + user?: { + id: string + username: string + level: number + email?: string + } + nerdMode?: boolean + theme?: 'light' | 'dark' + [key: string]: JsonValue | undefined +} + +export interface MockLuaScript { + id: string + name: string + code: string + /** + * Pre-computed output for this script given specific contexts. + * Keys are JSON-stringified context objects. + */ + mockOutputs?: Record + /** + * Default output to use when no matching mock is found + */ + defaultOutput?: LuaUIComponent +} + +export interface MockPackageManifest { + id: string + name: string + version: string + description: string + category: string + minLevel: number + scripts: MockLuaScript[] + /** + * Pre-rendered component trees for different contexts + */ + renderedPages?: Record +} + +/** + * Create a mock user context for Storybook stories + */ +export function createMockUser(overrides?: Partial): MockLuaContext['user'] { + return { + id: 'mock-user-1', + username: 'storybook_user', + level: 4, + email: 'user@example.com', + ...overrides, + } +} + +/** + * Create a full mock context for Lua package rendering + */ +export function createMockContext(overrides?: Partial): MockLuaContext { + return { + user: createMockUser(overrides?.user), + nerdMode: false, + theme: 'light', + ...overrides, + } +} + +/** + * Mock Lua execution result + */ +export interface MockLuaResult { + success: boolean + result?: LuaUIComponent + error?: string + logs: string[] +} + +/** + * Execute a mock Lua script with the given context + * Returns pre-computed output based on the context + */ +export function executeMockLuaScript( + script: MockLuaScript, + context: MockLuaContext +): MockLuaResult { + const contextKey = JSON.stringify(context) + + // Check for exact context match + if (script.mockOutputs && script.mockOutputs[contextKey]) { + return { + success: true, + result: script.mockOutputs[contextKey], + logs: [], + } + } + + // Fall back to default output + if (script.defaultOutput) { + return { + success: true, + result: script.defaultOutput, + logs: [], + } + } + + return { + success: false, + error: `No mock output defined for script: ${script.id}`, + logs: [`Mock script ${script.id} has no output for context`], + } +} + +/** + * Registry of mock package outputs + * This is populated by individual package mock files + */ +export const mockPackageRegistry: Map = new Map() + +/** + * Register a mock package for Storybook rendering + */ +export function registerMockPackage(manifest: MockPackageManifest): void { + mockPackageRegistry.set(manifest.id, manifest) +} + +/** + * Get a registered mock package + */ +export function getMockPackage(packageId: string): MockPackageManifest | undefined { + return mockPackageRegistry.get(packageId) +} + +/** + * List all registered mock packages + */ +export function listMockPackages(): MockPackageManifest[] { + return Array.from(mockPackageRegistry.values()) +}