Sofondo
SofondoFrameworkDocs

Sofondo Framework - API Design Standards

This document defines the API design standards for all Sofondo Framework components, hooks, and utilities.

Core Principles

  1. Consistency: Similar patterns across all components
  2. Predictability: Intuitive naming and behavior
  3. Type Safety: Full TypeScript support with proper types
  4. Documentation: Comprehensive JSDoc comments
  5. Flexibility: Support common use cases without overengineering
  6. Accessibility: ARIA attributes and keyboard support by default

Component Prop Patterns

Standard Props

All components should accept these standard React props where applicable:

interface StandardProps {
  /** Custom CSS class name */
  className?: string;

  /** Inline styles */
  style?: React.CSSProperties;

  /** Test ID for testing */
  'data-testid'?: string;

  /** ARIA label */
  'aria-label'?: string;

  /** ARIA described by */
  'aria-describedby'?: string;
}

Naming Conventions

Boolean Props

  • Use is, has, should, or enable prefixes
  • Examples: isOpen, hasError, shouldAutoFocus, enableTooltips

Handler Props

  • Use on prefix followed by event name
  • Examples: onClick, onChange, onClose, onSubmit
  • Type: (event: EventType) => void or () => void

Size Props

  • Use standardized size values where possible
  • Examples: size?: 'sm' | 'md' | 'lg'
  • Or specific: width?: number, height?: number

Variant Props

  • Use variant for visual variations
  • Examples: variant?: 'primary' | 'secondary' | 'outline'

Children

  • Always type children props properly
  • Use ReactNode for flexible content
  • Use specific types when children must be certain components

Prop Ordering

Order props in interfaces consistently:

  1. Required props (no optional marker)
  2. Content props (children, title, label)
  3. State props (value, checked, selected)
  4. Handler props (onClick, onChange)
  5. Visual props (variant, size, color)
  6. Behavior props (disabled, readOnly, autoFocus)
  7. Standard props (className, style, data-testid)

Default Values

  • Use default parameters over default props
  • Document defaults in JSDoc
  • Keep defaults consistent across similar components

Example:

/**
 * Button component
 * @param variant - Button style variant (default: 'primary')
 * @param size - Button size (default: 'md')
 */
export function Button({
  variant = 'primary',
  size = 'md',
  // ...rest
}: ButtonProps) {
  // ...
}

JSDoc Standards

Component Documentation

Every component should have comprehensive JSDoc:

/**
 * Brief one-line description of the component
 *
 * Longer description explaining:
 * - What the component does
 * - When to use it
 * - Key features
 * - Important notes or warnings
 *
 * @example
 * ```tsx
 * <ComponentName
 *   requiredProp="value"
 *   optionalProp={123}
 * >
 *   Content
 * </ComponentName>
 * ```
 *
 * @see {@link RelatedComponent}
 */
export function ComponentName(props: ComponentNameProps) {
  // ...
}

Prop Documentation

Every prop should be documented:

export interface ButtonProps {
  /**
   * Button text or content
   */
  children: ReactNode;

  /**
   * Click handler
   * @param event - Click event
   */
  onClick?: (event: React.MouseEvent) => void;

  /**
   * Visual style variant
   * @default 'primary'
   */
  variant?: 'primary' | 'secondary' | 'outline';

  /**
   * Disable the button
   * @default false
   */
  disabled?: boolean;
}

Hook Documentation

Hooks should document:

  • Purpose and use case
  • Parameters
  • Return value(s)
  • Side effects
  • Example usage
/**
 * Hook to manage modal state
 *
 * Provides open/close state and handlers for modal components.
 *
 * @param defaultOpen - Initial open state (default: false)
 * @returns Object with `isOpen`, `open`, `close`, `toggle` functions
 *
 * @example
 * ```tsx
 * function MyComponent() {
 *   const modal = useModal();
 *
 *   return (
 *     <>
 *       <button onClick={modal.open}>Open</button>
 *       <Modal isOpen={modal.isOpen} onClose={modal.close}>
 *         Content
 *       </Modal>
 *     </>
 *   );
 * }
 * ```
 */
export function useModal(defaultOpen = false) {
  // ...
}

Utility Documentation

Utilities should document:

  • Purpose
  • Parameters with types
  • Return value with type
  • Examples
  • Edge cases
/**
 * Format a file size in bytes to human-readable format
 *
 * @param bytes - File size in bytes
 * @param decimals - Number of decimal places (default: 2)
 * @returns Formatted string (e.g., "1.5 MB")
 *
 * @example
 * ```ts
 * formatFileSize(1024) // "1.00 KB"
 * formatFileSize(1536, 0) // "2 KB"
 * formatFileSize(0) // "0 Bytes"
 * ```
 */
export function formatFileSize(bytes: number, decimals = 2): string {
  // ...
}

Type Patterns

Component Props Interface

Always export the props interface:

export interface ComponentNameProps {
  // props...
}

export function ComponentName(props: ComponentNameProps) {
  // ...
}

Generic Components

Use generics for flexible, reusable components:

export interface DataGridProps<T> {
  data: T[];
  columns: Column<T>[];
  keyField: keyof T;
}

export function DataGrid<T>({
  data,
  columns,
  keyField,
}: DataGridProps<T>) {
  // ...
}

Discriminated Unions

Use for components with different modes:

type ButtonProps =
  | { variant: 'link'; href: string }
  | { variant: 'button'; onClick: () => void };

Accessibility Standards

Required ARIA

Components should include:

  • aria-label or aria-labelledby for non-text controls
  • role when semantic HTML isn’t sufficient
  • aria-expanded, aria-selected, etc. for state
  • aria-describedby for additional context

Keyboard Support

Interactive components must support:

  • Tab / Shift+Tab for focus navigation
  • Enter / Space for activation
  • Escape for closing/canceling
  • Arrow keys for list/menu navigation

Focus Management

  • Use autoFocus prop sparingly
  • Trap focus in modals
  • Return focus on close
  • Visible focus indicators

Error Handling

Prop Validation

Use TypeScript for compile-time validation. For runtime:

if (typeof onClick !== 'function') {
  console.error('onClick must be a function');
  return;
}

Helpful Errors

Throw helpful errors with context:

if (!data || data.length === 0) {
  throw new Error(
    'DataGrid: data prop is required and must be a non-empty array. ' +
    'Did you forget to pass data or is it loading?'
  );
}

Context Errors

For hooks that require context:

const context = useContext(MyContext);

if (!context) {
  throw new Error(
    'useMyHook must be used within a MyProvider. ' +
    'Wrap your component tree with <MyProvider> to use this hook.'
  );
}

Performance Guidelines

Memoization

Use React optimization hooks appropriately:

  • useMemo for expensive calculations
  • useCallback for stable function references
  • React.memo for components with expensive renders

Avoid Inline Objects

Don’t create objects in render:

// ❌ Bad
<Component style={{ margin: 10 }} />

// ✅ Good
const style = { margin: 10 };
<Component style={style} />

// ✅ Better (if dynamic)
const style = useMemo(() => ({ margin: spacing }), [spacing]);
<Component style={style} />

Testing Considerations

Test IDs

Support data-testid prop:

<div data-testid={props['data-testid'] || 'component-name'}>

Public API

Expose interfaces and types for testing:

// Export for testing
export interface ComponentMethods {
  focus: () => void;
  reset: () => void;
}

// Use with forwardRef
export const Component = forwardRef<ComponentMethods, ComponentProps>(
  // ...
);

Version Compatibility

Breaking Changes

Avoid breaking changes when possible. When necessary:

  1. Deprecate old API first
  2. Provide codemod or migration guide
  3. Use major version bump
  4. Document in CHANGELOG

Deprecation

Mark deprecated APIs with JSDoc:

/**
 * @deprecated Use `newFunction` instead. Will be removed in v2.0.
 * @see {@link newFunction}
 */
export function oldFunction() {
  console.warn('oldFunction is deprecated, use newFunction instead');
  // ...
}

Examples

Complete Component Example

import { ReactNode, MouseEvent } from 'react';
import clsx from 'clsx';
import styles from './Button.module.css';

/**
 * Button component for user interactions
 *
 * Provides a styled, accessible button with multiple variants and sizes.
 * Supports both button and link modes.
 *
 * @example
 * ```tsx
 * <Button variant="primary" size="lg" onClick={handleClick}>
 *   Click me
 * </Button>
 * ```
 *
 * @example
 * ```tsx
 * <Button variant="outline" disabled>
 *   Disabled Button
 * </Button>
 * ```
 */
export interface ButtonProps {
  /**
   * Button content
   */
  children: ReactNode;

  /**
   * Visual style variant
   * @default 'primary'
   */
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';

  /**
   * Button size
   * @default 'md'
   */
  size?: 'sm' | 'md' | 'lg';

  /**
   * Click handler
   * @param event - Click event
   */
  onClick?: (event: MouseEvent<HTMLButtonElement>) => void;

  /**
   * Disable the button
   * @default false
   */
  disabled?: boolean;

  /**
   * Button type attribute
   * @default 'button'
   */
  type?: 'button' | 'submit' | 'reset';

  /**
   * Auto focus on mount
   * @default false
   */
  autoFocus?: boolean;

  /**
   * Custom CSS class
   */
  className?: string;

  /**
   * Inline styles
   */
  style?: React.CSSProperties;

  /**
   * Test identifier
   */
  'data-testid'?: string;

  /**
   * ARIA label
   */
  'aria-label'?: string;
}

export function Button({
  children,
  variant = 'primary',
  size = 'md',
  onClick,
  disabled = false,
  type = 'button',
  autoFocus = false,
  className,
  style,
  'data-testid': testId,
  'aria-label': ariaLabel,
}: ButtonProps) {
  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled}
      autoFocus={autoFocus}
      className={clsx(
        styles.button,
        styles[variant],
        styles[size],
        disabled && styles.disabled,
        className
      )}
      style={style}
      data-testid={testId}
      aria-label={ariaLabel}
    >
      {children}
    </button>
  );
}

Checklist

Before marking a component as complete:

  • Props interface exported
  • Component documented with JSDoc
  • All props documented
  • Example usage in JSDoc
  • TypeScript types are accurate
  • Default values documented
  • Accessibility attributes included
  • Keyboard support implemented
  • Error handling for edge cases
  • Performance optimized (if needed)
  • Test IDs supported
  • Follows naming conventions
  • Consistent with other components

References