retro-floppy
v1.0.0
Published
Retro Floppy - A beautiful, interactive 3.5" floppy disk React component for retro-themed UIs
Downloads
15
Maintainers
Readme
Features
- Highly Customizable - Size variants, color themes, and structured label content
- Interactive - Hover animations plus click and double-click handlers
- Flexible Sizing - From tiny (60px) to hero (600px) or custom pixel values
- Performant - Optimized for rendering multiple instances in lists/grids
- Accessible - ARIA labels and keyboard navigation support
- TypeScript - Full type definitions included
- Zero Dependencies - Only requires React
Installation
npm install retro-floppy
# or
yarn add retro-floppy
# or
pnpm add retro-floppyQuick Start
import { FloppyDisk } from 'retro-floppy';
import 'retro-floppy/dist/retro-floppy.css';
function App() {
return (
<FloppyDisk
size="medium"
label={{
name: 'Second Reality',
author: 'Future Crew',
year: '1993',
description: 'Legendary 1993 demo by Future Crew',
type: 'ZIP',
size: '1.44 MB',
}}
onClick={() => console.log('Disk clicked!')}
/>
);
}Usage Examples
Basic Usage
<FloppyDisk
size="small"
label={{
name: 'My Application',
author: 'Version 1.0',
}}
diskType="HD"
capacity="1.44 MB"
/>Software Library Grid
const applications = [
{ id: 1, name: 'Photoshop 3.0', author: 'Adobe Systems' },
{ id: 2, name: 'Doom', author: 'id Software' },
{ id: 3, name: 'Windows 95', author: 'Microsoft' },
];
function SoftwareLibrary() {
const [selected, setSelected] = useState<number | null>(null);
return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
gap: '20px',
}}
>
{applications.map((app) => (
<FloppyDisk
key={app.id}
size="small"
label={{
name: app.name,
author: app.author,
}}
selected={selected === app.id}
onClick={() => setSelected(app.id)}
onDoubleClick={() => alert(`Launching ${app.name}`)}
/>
))}
</div>
);
}Custom Theme
<FloppyDisk
size="large"
label={{
name: 'Custom Theme',
}}
theme={{
diskColor: '#1a1a1a',
slideColor: '#ffd700',
backgroundColor: '#f0f0f0',
labelColor: '#ffffcc',
labelTextColor: '#333333',
}}
/>List View with Compact Variant
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{files.map((file) => (
<div
key={file.id}
style={{ display: 'flex', alignItems: 'center', gap: '15px' }}
>
<FloppyDisk size="tiny" variant="compact" label={{ name: file.name }} />
<span>{file.name}</span>
<span>{file.size}</span>
</div>
))}
</div>Theming
The disk uses a default theme that works well in both light and dark UIs.
You can customize colors via the theme prop to match your design.
Theme Showcase
The component includes several built-in theme presets for different aesthetics:
Light Theme (Default)
Classic floppy disk appearance optimized for light backgrounds.
import { FloppyDisk, LIGHT_FLOPPY_THEME } from 'retro-floppy';
<FloppyDisk
label={{ name: 'My App', author: 'Developer' }}
theme={LIGHT_FLOPPY_THEME}
/>;Dark Theme
Sleek dark appearance optimized for dark backgrounds.
import { FloppyDisk, DARK_FLOPPY_THEME } from 'retro-floppy';
<FloppyDisk
label={{ name: 'My App', author: 'Developer' }}
theme={DARK_FLOPPY_THEME}
/>;Neon Theme
Vibrant cyberpunk aesthetic with magenta and cyan accents.
import { FloppyDisk, NEON_THEME } from 'retro-floppy';
<FloppyDisk
label={{ name: 'My App', author: 'Developer' }}
theme={NEON_THEME}
/>;Retro Theme
Classic 90s beige computer aesthetic with warm, vintage colors.
import { FloppyDisk, RETRO_THEME } from 'retro-floppy';
<FloppyDisk
label={{ name: 'My App', author: 'Developer' }}
theme={RETRO_THEME}
/>;Pastel Theme
Soft, modern colors with gradient labels.
import { FloppyDisk, PASTEL_THEME } from 'retro-floppy';
<FloppyDisk
label={{ name: 'My App', author: 'Developer' }}
theme={PASTEL_THEME}
/>;Custom Themes
Create your own theme by providing a custom theme object:
<FloppyDisk
label={{ name: 'Custom', author: 'Me' }}
theme={{
diskColor: '#ff6b6b',
slideColor: '#4ecdc4',
backgroundColor: '#ffe66d',
labelColor: '#ffffff',
labelTextColor: '#2c3e50',
enableGradient: false,
}}
/>Gradient Customization
When using gradients, you can customize the generation:
<FloppyDisk
label={{ name: 'Gradient Demo', author: 'Dev' }}
theme={{
enableGradient: true,
gradientType: 'linear', // 'linear', 'radial', 'conic', or 'auto'
gradientOptions: {
seed: 12345, // Custom seed for reproducible gradients
colors: ['#ff6b6b', '#4ecdc4', '#45b7d1'], // Custom color palette
angle: 135, // Angle for linear gradients (0-360)
},
}}
/>API Reference
Props
| Prop | Type | Default | Description |
| --------------- | -------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------- |
| size | 'tiny' \| 'small' \| 'medium' \| 'large' \| 'hero' \| number | 'medium' | Size of the disk. Predefined or custom px value |
| label | FloppyLabel | undefined | Structured label data for the disk (name, author, year, description, type, size) |
| diskType | 'HD' \| 'DD' | 'HD' | High Density or Double Density |
| capacity | string | '1.44 MB' | Storage capacity display (overrides label.size if provided) |
| theme | FloppyTheme | Default theme | Color customization object |
| animation | AnimationConfig | {} | Animation timing and easing configuration |
| variant | 'interactive' \| 'static' \| 'compact' | 'interactive' | Interaction mode |
| selected | boolean | false | Whether the disk is selected |
| disabled | boolean | false | Whether the disk is disabled |
| loading | boolean | false | Shows subtle pulse animation |
| error | boolean | false | Shows red label with white text |
| onClick | () => void | - | Click handler |
| onDoubleClick | () => void | - | Double-click handler |
| onHover | (isHovered: boolean) => void | - | Hover state change handler |
| onFocus | (isFocused: boolean) => void | - | Focus state change handler |
| className | string | '' | Additional CSS class |
| style | React.CSSProperties | - | Inline styles for root element |
| data-testid | string | - | Test ID for testing libraries |
| data-disk-id | string | - | Custom disk ID for tracking |
| badge | React.ReactNode | - | Badge content (top-right corner) |
| children | React.ReactNode | - | Custom overlay content |
| ariaLabel | string | Auto-generated | Accessible label override |
Size Variants
| Size | Pixels | Use Case |
| -------- | ---------- | ----------------------- |
| tiny | 60px | Compact lists, icons |
| small | 120px | Grid views, thumbnails |
| medium | 200px | Featured items, cards |
| large | 400px | Detail views, showcases |
| hero | 600px | Landing pages, heroes |
| Custom | Any number | Specific requirements |
Theme Object
interface FloppyTheme {
diskColor?: string; // Main disk body color
slideColor?: string; // Metal slide color
backgroundColor?: string; // Background/cutout color
labelColor?: string; // Label paper color
labelTextColor?: string; // Label text color
enableGradient?: boolean; // Enable dynamic gradient backgrounds (default: false)
gradientType?: 'linear' | 'radial' | 'conic' | 'auto'; // Gradient type (default: 'auto')
gradientOptions?: GradientOptions; // Custom gradient options
}
interface GradientOptions {
seed?: number; // Custom seed for gradient generation
colors?: string[]; // Custom color palette (HSL or hex colors)
angle?: number; // Gradient angle for linear gradients (0-360 degrees)
}Animation Config
interface AnimationConfig {
hoverDuration?: number; // Hover animation duration in ms (default: 500)
slideDuration?: number; // Slide animation duration in ms (default: 500)
easing?: string; // Animation easing function (default: 'linear')
disableAnimations?: boolean; // Disable all animations (default: false)
}Variants
Interactive (Default)
- Hover to slide out the metal shutter
- Click to trigger the
onClickhandler - Double-click to trigger the
onDoubleClickhandler
Static
- No hover animations
- Click handlers still work
- Best for performance-critical lists
Compact
- Reduced label area
- Smaller slide track
- Optimized for tight spaces
Advanced Features
Loading and Error States
// Loading state with pulse animation
<FloppyDisk loading label={{ name: "Uploading..." }} />
// Error state with red label and white text
<FloppyDisk error label={{ name: "Failed" }} />Badge and Overlays
// Simple badge
<FloppyDisk
label={{ name: "New Release" }}
badge={<span style={{ background: 'red', color: 'white', padding: '2px 6px', borderRadius: '4px' }}>NEW</span>}
/>
// Custom overlay
<FloppyDisk label={{ name: "Locked" }}>
<div style={{ fontSize: '48px' }}>🔒</div>
</FloppyDisk>Note: The lock emoji above is just an example. For production use, consider using an icon library or SVG instead.
Event Callbacks
<FloppyDisk
label={{ name: 'Interactive' }}
onHover={(isHovered) => console.log('Hover:', isHovered)}
onFocus={(isFocused) => console.log('Focus:', isFocused)}
onClick={() => console.log('Clicked')}
onDoubleClick={() => console.log('Double-clicked')}
/>Animation Customization
// Custom animation timing
<FloppyDisk
animation={{
hoverDuration: 1000,
easing: 'ease-in-out'
}}
/>
// Disable animations
<FloppyDisk animation={{ disableAnimations: true }} />CSS Customization
// Using inline styles
<FloppyDisk
style={
{
'--floppy-border-radius': '10%',
'--floppy-hover-scale': '1.1',
'--floppy-shadow-blur': '10px',
} as React.CSSProperties
}
/>;
// Using CSS module classes
import { floppyDiskStyles } from 'retro-floppy';
// Access individual classes for advanced styling
<div className={floppyDiskStyles.silhouette}>...</div>;Available CSS Custom Properties
You can customize the appearance using CSS custom properties:
--floppy-size /* Disk size in px */
--floppy-border /* Border thickness */
--floppy-border-radius /* Corner radius (default: 3%) */
--floppy-color /* Main disk color */
--floppy-highlight /* Highlight color */
--floppy-shadow /* Shadow color */
--floppy-hover-scale /* Hover scale factor (default: 1.02) */
--floppy-hover-brightness /* Hover brightness (default: 1.05) */
--animation-duration /* Animation duration (default: 500ms) */
--animation-easing /* Animation easing (default: linear) */
--slide-color /* Metal slide color */
--bg-color /* Background color */
--label-color /* Label background */
--label-text-color /* Label text color */
--label-text-shadow /* Label text shadow */Best Practices
Performance with Multiple Disks
When rendering many disks (50+), consider:
import { memo } from 'react';
const MemoizedFloppyDisk = memo(FloppyDisk);
// Use in your list
{
disks.map((disk) => <MemoizedFloppyDisk key={disk.id} {...disk} />);
}Responsive Sizing
// Use CSS custom properties for responsive sizing
<div style={{ '--floppy-size': 'clamp(80px, 15vw, 200px)' }}>
<FloppyDisk size={120} />
</div>Accessibility
<FloppyDisk
label={{ name: 'Important Document' }}
ariaLabel="Important Document floppy disk, double-click to open"
onClick={handleSelect}
onDoubleClick={handleOpen}
/>- The entire floppy is a focusable
figurewithrole="button". ariaLabelcontrols the screen reader label; if omitted, it falls back to thelabeltext.- Keyboard users can press Enter or Space to trigger
onClickwhenvariant !== 'static'anddisabledisfalse. - A visible focus outline appears when navigating with the keyboard.
Development
Running the Example
# Install dependencies
npm install
# Build the component
npm run build
# Run the example
cd example
npm install
npm run devBuilding for Production
npm run buildThis creates:
dist/index.cjs- CommonJS bundledist/index.esm.js- ES Module bundledist/index.d.ts- TypeScript definitionsdist/retro-floppy.css- Extracted CSS styles for the component (import this in your app)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
See CONTRIBUTING.md for development instructions and guidelines and CODE_OF_CONDUCT.md for expected behavior.
License
This project is licensed under the MIT License – see the LICENSE file for details.
Credits
Inspired by the iconic 3.5" floppy disk that stored our precious data in the 80s and 90s.
Made with ❤️ by Cameron Rye
