All files / packages/fds-uif/generator-react/src classname-builder.ts

100% Statements 31/31
100% Branches 8/8
100% Functions 0/0
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137                          21x     1x     20x       17x       3x             1x             17x 17x             3x 3x     4x 4x       3x   3x               3x     1x     2x     2x   2x       2x         1x   1x 1x   1x       2x   2x                       1x     2x     4x 4x                               2x    
/**
 * ClassName Builder
 *
 * Generates conditional className composition logic from UIF modifiers.
 */
 
import type { ComponentMetadata, ClassNameRule } from '@fds-uif/generator-base/browser';
import type { GenerationContext } from './types.js';
 
/**
 * Build className computation logic
 */
export function buildClassNameLogic(metadata: ComponentMetadata, context: GenerationContext): string {
  const { classNames } = metadata;
 
  if (!classNames || classNames.length === 0) {
    return buildSimpleClassName(context);
  }
 
  const rule = classNames[0]; // Primary class rule
 
  // If no conditional classes, use simple className
  if (!rule.conditional || rule.conditional.length === 0) {
    return buildStaticClassName(rule.base, context);
  }
 
  // Build template literal with conditionals
  return buildConditionalClassName(rule, context);
}
 
/**
 * Build simple className pass-through
 */
function buildSimpleClassName(context: GenerationContext): string {
  return 'const computedClassName = className;';
}
 
/**
 * Build static className (no conditionals)
 */
function buildStaticClassName(baseClasses: string[], context: GenerationContext): string {
  const base = baseClasses.join(' ');
  return `const computedClassName = className ? \`${base} \${className}\` : '${base}';`;
}
 
/**
 * Build conditional className using template literals
 */
function buildConditionalClassName(rule: ClassNameRule, context: GenerationContext): string {
  const baseClasses = rule.base.join(' ');
  const conditionals: string[] = [];
 
  for (const cond of rule.conditional) {
    const classes = cond.classes.join(' ');
    conditionals.push(`\${${cond.condition} ? ' ${classes}' : ''}`);
  }
 
  // Build the template literal
  const template = `\`${baseClasses}${conditionals.join('')}\${className ? ' ' + className : ''}\``;
 
  return `const computedClassName = ${template};`;
}
 
/**
 * Build className with classnames() library
 * Alternative approach using the classnames package
 */
export function buildClassNamesLibrary(metadata: ComponentMetadata, context: GenerationContext): string {
  const { classNames } = metadata;
 
  if (!classNames || classNames.length === 0) {
    return 'const computedClassName = className;';
  }
 
  const rule = classNames[0];
 
  // Add import for classnames
  context.imports.add("import classNames from 'classnames';");
 
  const args: string[] = [];
 
  // Base classes as first argument
  if (rule.base.length > 0) {
    args.push(`'${rule.base.join(' ')}'`);
  }
 
  // Conditional classes as object
  if (rule.conditional && rule.conditional.length > 0) {
    const condObj: string[] = [];
    for (const cond of rule.conditional) {
      const classes = cond.classes.join(' ');
      condObj.push(`'${classes}': ${cond.condition}`);
    }
    args.push(`{ ${condObj.join(', ')} }`);
  }
 
  // User-provided className
  args.push('className');
 
  return `const computedClassName = classNames(${args.join(', ')});`;
}
 
/**
 * Generate className utility for variants that change element types
 */
export function buildVariantClassName(
  variantName: string,
  options: Array<{ value: string; classes: string[] }>,
  context: GenerationContext,
): string {
  if (options.length === 0) {
    return '';
  }
 
  const cases: string[] = [];
 
  for (const opt of options) {
    const classes = opt.classes.join(' ');
    cases.push(`    case '${opt.value}': return '${classes}';`);
  }
 
  return `
  const get${capitalize(variantName)}Classes = () => {
    switch (${variantName}) {
${cases.join('\n')}
      default: return '';
    }
  };`;
}
 
/**
 * Capitalize first letter
 */
function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}