Implement a small utility, conventionally named classnames, that takes any number of arguments — strings, numbers, objects, arrays — and folds them down to a single space-separated string of CSS class names. It's the library a million React components reach for to write className={cn('btn', { active, disabled })} without a .filter(Boolean).join(' ') salad. See the popular classnames package on npm for the canonical behavior you're recreating.
// Accepts any number of args of mixed types and returns a single string.
function classnames(...args: ClassValue[]): string;
type ClassValue =
| string // included as-is
| number // included as-is (yes, including 0)
| null // skipped
| undefined // skipped
| boolean // skipped (true and false alike)
| { [key: string]: unknown } // key included when value is truthy
| ClassValue[]; // recursively flattened
classnames('btn', 'btn-primary');
// → 'btn btn-primary'
classnames('btn', { active: true, disabled: false }, ['lg', 'primary']);
// → 'btn active lg primary'
// Falsy values are dropped; nested arrays are flattened.
classnames('a', null, undefined, false, ['b', ['c', { d: true, e: 0 }]]);
// → 'a b c d'
// Numbers (including 0) are kept; empty strings are skipped.
classnames(0, 1, '', 'x');
// → '0 1 x'
null, undefined, false, and the empty string '' are all dropped from the output. They never appear, and they never produce stray spaces.0, is converted to its string form and included. This matches the real classnames library.{ active: true, disabled: false, hidden: 0 } contributes only active.['a', ['b', ['c']]] contributes a b c. The order of names in the output must match the left-to-right reading order of the input.classnames('a', 'a') is 'a a', not 'a'. Output order mirrors input order.