'use client'; import { forwardRef, HTMLAttributes, useMemo } from 'react'; import styles from './sheet-music.module.css'; export const MUSIC_SYMBOLS = { quarterNote: '♩', eighthNote: '♪', beamedNotes: '♫', sixteenthNotes: '♬', trebleClef: '𝄞', bassClef: '𝄢', sharp: '♯', flat: '♭', natural: '♮', fermata: '𝄐', coda: '𝄌', segno: '𝄋', }; export interface NoteConfig { symbol: keyof typeof MUSIC_SYMBOLS | string; position: { top: string; left: string }; size?: 'sm' | 'md' | 'lg' | 'xl'; delay?: number; } export interface SheetMusicProps extends HTMLAttributes { /** Positioning mode */ mode?: 'overlay' | 'inline'; /** Animation style */ animation?: 'drift' | 'falling' | 'swirling' | 'rising' | 'none'; /** Color variant */ variant?: 'ash' | 'silver' | 'gold' | 'blood' | 'ivory'; /** Note density */ density?: 'sparse' | 'normal' | 'dense'; /** Number of random notes */ count?: number; /** Custom note configurations */ notes?: NoteConfig[]; /** Show decorative staff lines */ showStaff?: boolean; /** Symbols to use for random generation */ symbols?: (keyof typeof MUSIC_SYMBOLS)[]; } const generateRandomNotes = ( count: number, symbols: (keyof typeof MUSIC_SYMBOLS)[] ): NoteConfig[] => { const sizes: ('sm' | 'md' | 'lg' | 'xl')[] = ['sm', 'md', 'lg', 'xl']; return Array.from({ length: count }, (_, i) => ({ symbol: symbols[Math.floor(Math.random() * symbols.length)], position: { top: `${Math.random() * 80 + 10}%`, left: `${Math.random() * 90 + 5}%`, }, size: sizes[Math.floor(Math.random() * sizes.length)], delay: i * (Math.random() * 3 + 1), })); }; const sizeClasses = { sm: styles.note4, md: styles.note1, lg: styles.note2, xl: styles.note5, }; export const SheetMusic = forwardRef( ( { mode = 'overlay', animation = 'drift', variant = 'ash', density = 'normal', count = 8, notes: customNotes, showStaff = false, symbols = ['quarterNote', 'eighthNote', 'beamedNotes', 'sixteenthNotes', 'trebleClef', 'bassClef'], className, ...props }, ref ) => { const notes = useMemo(() => customNotes || generateRandomNotes(count, symbols), [customNotes, count, symbols] ); const animationClass = { drift: '', falling: styles.falling, swirling: styles.swirling, rising: styles.rising, none: '', }[animation]; return (
{showStaff && ( <>
{[...Array(5)].map((_, i) =>
)}
{[...Array(5)].map((_, i) =>
)}
)} {notes.map((note, i) => { const symbol = MUSIC_SYMBOLS[note.symbol as keyof typeof MUSIC_SYMBOLS] || note.symbol; return ( {symbol} ); })}
); } ); SheetMusic.displayName = 'SheetMusic'; export default SheetMusic;