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.
This commit is contained in:
committed by
GitHub
parent
360ea30f4b
commit
ad427365c9
155
apps/dev-tool/app/components/lib/story-utils.ts
Normal file
155
apps/dev-tool/app/components/lib/story-utils.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
'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;
|
||||
}
|
||||
Reference in New Issue
Block a user