Files
myeasycms-v2/apps/dev-tool/app/components/lib/story-utils.ts
Giancarlo Buomprisco ad427365c9 Storybook (#328)
* feat(docs): add interactive examples and API references for Button, Card, and LoadingFallback components
- Updated dependencies
- Set `retries` to a fixed value of 3 for consistent test retries across environments.
- Increased `timeout` from 60 seconds to 120 seconds to allow more time for tests to complete.
- Reduced `expect` timeout from 10 seconds to 5 seconds for quicker feedback on assertions.
2025-08-22 07:35:44 +08:00

156 lines
3.5 KiB
TypeScript

'use client';
import { useState } from 'react';
import { toast } from '@kit/ui/sonner';
/**
* Generic hook for managing component story controls
*/
export function useStoryControls<T extends Record<string, any>>(
initialState: T,
) {
const [controls, setControls] = useState<T>(initialState);
const updateControl = <K extends keyof T>(key: K, value: T[K]) => {
setControls((prev) => ({ ...prev, [key]: value }));
};
const resetControls = () => {
setControls(initialState);
};
return {
controls,
updateControl,
resetControls,
};
}
/**
* Hook for managing copy-to-clipboard functionality
*/
export function useCopyCode() {
const [copiedCode, setCopiedCode] = useState(false);
const copyCode = async (code: string) => {
try {
await navigator.clipboard.writeText(code);
setCopiedCode(true);
toast.success('Code copied to clipboard!');
setTimeout(() => setCopiedCode(false), 2000);
} catch (error) {
toast.error('Failed to copy code');
console.error('Failed to copy code:', error);
}
};
return {
copiedCode,
copyCode,
};
}
/**
* Utility to generate props string from control values
*/
export function generatePropsString(
controls: Record<string, any>,
defaults: Record<string, any> = {},
excludeKeys: string[] = [],
): string {
const props: string[] = [];
Object.entries(controls).forEach(([key, value]) => {
if (excludeKeys.includes(key)) return;
// Skip undefined values - omit the prop entirely
if (value === undefined) return;
const defaultValue = defaults[key];
const hasDefault = defaultValue !== undefined;
// Only include prop if it's different from default or there's no default
if (!hasDefault || value !== defaultValue) {
if (typeof value === 'boolean') {
if (value) {
props.push(key);
}
} else if (typeof value === 'string') {
if (value) {
props.push(`${key}="${value}"`);
}
} else {
props.push(`${key}={${JSON.stringify(value)}}`);
}
}
});
return props.length > 0 ? ` ${props.join(' ')}` : '';
}
/**
* Common tab configuration for component stories
*/
export interface StoryTab {
id: string;
label: string;
content: React.ReactNode;
}
export const defaultStoryTabs = [
{ id: 'playground', label: 'Playground' },
{ id: 'examples', label: 'Examples' },
{ id: 'api', label: 'API Reference' },
{ id: 'usage', label: 'Usage Guidelines' },
];
/**
* Option type for select components with descriptions
*/
export interface SelectOption<T = string> {
value: T;
label: string;
description: string;
icon?: React.ComponentType<{ className?: string }>;
color?: string;
}
/**
* Utility to format component imports for code generation
*/
export function generateImportStatement(
components: string[],
source: string,
): string {
if (components.length === 1) {
return `import { ${components[0]} } from '${source}';`;
}
if (components.length <= 3) {
return `import { ${components.join(', ')} } from '${source}';`;
}
// Multi-line for many imports
return `import {\n ${components.join(',\n ')}\n} from '${source}';`;
}
/**
* Utility to create formatted code blocks
*/
export function formatCodeBlock(
code: string,
imports: string[] = [],
language: 'tsx' | 'jsx' | 'javascript' | 'typescript' = 'tsx',
): string {
let formattedCode = '';
if (imports.length > 0) {
formattedCode += imports.join('\n') + '\n\n';
}
formattedCode += code;
return formattedCode;
}