Files
vercel__streamdown/packages/streamdown/lib/utils.ts
shloimy-wiesel 8b1c262982 fix(save): prepend UTF-8 BOM to CSV blobs for Excel compatibility (#441)
* fix(save): prepend UTF-8 BOM to CSV blobs for Excel compatibility

Closes #440

* fix(download): refactor TableDownloadButton to use save() for CSV downloads and ensure UTF-8 BOM compatibility
2026-03-10 16:02:51 -07:00

49 lines
1.8 KiB
TypeScript

import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
export type CnFunction = (...inputs: ClassValue[]) => string;
/**
* Prepends a prefix to each Tailwind utility class in a class string.
* Used to support Tailwind v4's `prefix()` feature.
*
* @example
* prefixClasses("tw", "flex items-center") // "tw:flex tw:items-center"
* prefixClasses("tw", "dark:bg-red-500") // "tw:dark:bg-red-500"
*/
export const prefixClasses = (prefix: string, classString: string): string => {
if (!prefix || !classString) return classString;
const prefixWithColon = `${prefix}:`;
return classString
.split(/\s+/)
.filter(Boolean)
.map((cls) => cls.startsWith(prefixWithColon) ? cls : `${prefix}:${cls}`)
.join(" ");
};
/**
* Creates a prefix-aware `cn` function. When no prefix is provided,
* returns the standard `cn` with zero overhead.
*/
export const createCn = (prefix?: string): CnFunction => {
if (!prefix) return cn;
return (...inputs: ClassValue[]) => prefixClasses(prefix, twMerge(clsx(inputs)));
};
export const save = (filename: string, content: string | Blob, mimeType: string) => {
// Prepend UTF-8 BOM for CSV so Excel on Windows correctly detects the encoding.
// Without it, Excel falls back to the system ANSI codepage and corrupts non-ASCII text.
const bom = typeof content === 'string' && mimeType.startsWith('text/csv') ? '\uFEFF' : '';
const blob = typeof content === 'string' ? new Blob([bom + content], { type: mimeType }) : content;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};