Skip to content
This repository was archived by the owner on Feb 28, 2026. It is now read-only.

Commit 0f4c992

Browse files
committed
Plugin item component
1 parent 697acbe commit 0f4c992

File tree

9 files changed

+394
-2
lines changed

9 files changed

+394
-2
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import {
3+
Download,
4+
Headphones,
5+
Heart,
6+
Music,
7+
Palette,
8+
Radio,
9+
Star,
10+
Zap,
11+
} from 'lucide-react';
12+
13+
import { PluginItem } from '@nuclearplayer/ui';
14+
15+
const meta = {
16+
title: 'Components/PluginItem',
17+
component: PluginItem,
18+
parameters: {
19+
layout: 'padded',
20+
},
21+
tags: ['autodocs'],
22+
argTypes: {
23+
onViewDetails: { action: 'view details clicked' },
24+
},
25+
} satisfies Meta<typeof PluginItem>;
26+
27+
export default meta;
28+
type Story = StoryObj<typeof meta>;
29+
30+
export const Default: Story = {
31+
args: {
32+
name: 'YouTube Music',
33+
author: 'Nuclear Team',
34+
description:
35+
'Stream music directly from YouTube Music with full search and playlist support.',
36+
icon: <Music size={24} />,
37+
},
38+
};
39+
40+
export const WithoutIcon: Story = {
41+
args: {
42+
name: 'Last.fm Scrobbler',
43+
author: 'Community',
44+
description:
45+
'Automatically scrobble your listening history to Last.fm and discover new music based on your taste.',
46+
},
47+
};
48+
49+
export const ThemeExample: Story = {
50+
args: {
51+
name: 'Dark Theme Pro',
52+
author: 'ThemeStudio',
53+
description:
54+
'A sleek dark theme with customizable accent colors and improved readability.',
55+
icon: <Palette size={24} />,
56+
},
57+
};
58+
59+
export const LongDescription: Story = {
60+
args: {
61+
name: 'Advanced Equalizer',
62+
author: 'AudioTech Solutions',
63+
description:
64+
'Professional-grade 10-band equalizer with presets for different music genres, custom curve editing, and real-time spectrum analysis. Includes bass boost, treble enhancement, and spatial audio effects.',
65+
icon: <Zap size={24} />,
66+
},
67+
};
68+
69+
export const PluginShowcase = () => (
70+
<div className="space-y-4 max-w-2xl">
71+
<h2 className="text-xl font-bold text-foreground mb-6">
72+
Available Plugins
73+
</h2>
74+
75+
<PluginItem
76+
name="YouTube Music"
77+
author="Nuclear Team"
78+
description="Stream music directly from YouTube Music with full search and playlist support."
79+
icon={<Music size={24} />}
80+
onViewDetails={() => console.log('YouTube Music details')}
81+
/>
82+
83+
<PluginItem
84+
name="Spotify Connect"
85+
author="Community"
86+
description="Connect to your Spotify account and access your playlists, liked songs, and recommendations."
87+
icon={<Headphones size={24} />}
88+
onViewDetails={() => console.log('Spotify details')}
89+
/>
90+
91+
<PluginItem
92+
name="Radio Browser"
93+
author="Nuclear Team"
94+
description="Browse and play thousands of internet radio stations from around the world."
95+
icon={<Radio size={24} />}
96+
onViewDetails={() => console.log('Radio details')}
97+
/>
98+
99+
<PluginItem
100+
name="Download Manager"
101+
author="DevTools Inc"
102+
description="Download your favorite tracks for offline listening with metadata and artwork."
103+
icon={<Download size={24} />}
104+
onViewDetails={() => console.log('Download Manager details')}
105+
/>
106+
</div>
107+
);
108+
109+
export const ThemeShowcase = () => (
110+
<div className="space-y-4 max-w-2xl">
111+
<h2 className="text-xl font-bold text-foreground mb-6">Available Themes</h2>
112+
113+
<PluginItem
114+
name="Neon Nights"
115+
author="DesignStudio"
116+
description="Vibrant neon colors with a cyberpunk aesthetic. Perfect for late-night listening sessions."
117+
icon={<Palette size={24} />}
118+
onViewDetails={() => console.log('Neon Nights details')}
119+
/>
120+
121+
<PluginItem
122+
name="Minimal White"
123+
author="CleanDesign Co"
124+
description="Clean, minimalist white theme with subtle shadows and excellent readability."
125+
icon={<Heart size={24} />}
126+
onViewDetails={() => console.log('Minimal White details')}
127+
/>
128+
129+
<PluginItem
130+
name="Retro Vinyl"
131+
author="VintageThemes"
132+
description="Nostalgic theme inspired by classic vinyl records and vintage audio equipment."
133+
icon={<Star size={24} />}
134+
onViewDetails={() => console.log('Retro Vinyl details')}
135+
/>
136+
</div>
137+
);
138+
139+
export const WithoutButton: Story = {
140+
args: {
141+
name: 'System Integration',
142+
author: 'Nuclear Core',
143+
description:
144+
'Built-in system integration features for media keys and notifications.',
145+
icon: <Zap size={24} />,
146+
onViewDetails: undefined,
147+
},
148+
};

packages/tailwind-config/global.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
/* Text Colors */
6161
--foreground: oklch(0% 0 0);
62-
--foreground-secondary: oklch(0% 0 0);
62+
--foreground-secondary: oklch(0.3 0 0);
6363
--foreground-input: oklch(0% 0 0);
6464

6565
/* Interactive Elements */

packages/ui/src/components/Box/Box.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ComponentProps, FC, ReactNode } from 'react';
44
import { cn } from '../../utils';
55

66
const boxVariants = cva(
7-
'flex h-full w-full rounded-base border-2 border-border text-foreground p-4',
7+
'flex h-full w-full rounded border-2 border-border text-foreground p-4',
88
{
99
variants: {
1010
variant: {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { render } from '@testing-library/react';
2+
import { Music } from 'lucide-react';
3+
4+
import { PluginItem } from './PluginItem';
5+
6+
describe('PluginItem', () => {
7+
it('(Snapshot) renders with all props', () => {
8+
const { asFragment } = render(
9+
<PluginItem
10+
name="YouTube Music"
11+
author="Nuclear Team"
12+
description="Stream music directly from YouTube Music with full search and playlist support."
13+
icon={<Music size={24} />}
14+
onViewDetails={() => {}}
15+
/>,
16+
);
17+
18+
expect(asFragment()).toMatchSnapshot();
19+
});
20+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { ExternalLink } from 'lucide-react';
2+
import { FC, ReactNode } from 'react';
3+
4+
import { Box } from '../Box';
5+
import { Button } from '../Button';
6+
7+
type PluginItemProps = {
8+
name: string;
9+
author: string;
10+
description: string;
11+
icon?: ReactNode;
12+
onViewDetails?: () => void;
13+
className?: string;
14+
};
15+
16+
export const PluginItem: FC<PluginItemProps> = ({
17+
name,
18+
author,
19+
description,
20+
icon,
21+
onViewDetails,
22+
className,
23+
}) => {
24+
return (
25+
<Box variant="secondary" className={className}>
26+
<div className="flex items-start gap-4 w-full">
27+
{icon && (
28+
<Box
29+
variant="tertiary"
30+
shadow="none"
31+
className="flex-shrink-0 w-12 h-12 p-0 items-center justify-center"
32+
>
33+
{icon}
34+
</Box>
35+
)}
36+
37+
<div className="flex-1 min-w-0">
38+
<div className="flex items-start justify-between gap-4">
39+
<div className="min-w-0 flex-1">
40+
<h3 className="font-bold text-foreground text-lg leading-tight">
41+
{name}
42+
</h3>
43+
<p className="text-sm text-foreground-secondary mt-1">
44+
by {author}
45+
</p>
46+
<p className="text-sm text-foreground-secondary mt-2 leading-relaxed">
47+
{description}
48+
</p>
49+
</div>
50+
51+
{onViewDetails && (
52+
<Button
53+
size="sm"
54+
variant="noShadow"
55+
onClick={onViewDetails}
56+
className="flex-shrink-0"
57+
>
58+
<ExternalLink size={14} className="mr-2" />
59+
View Details
60+
</Button>
61+
)}
62+
</div>
63+
</div>
64+
</div>
65+
</Box>
66+
);
67+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`PluginItem > (Snapshot) renders with all props 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="flex h-full w-full rounded border-2 border-border text-foreground p-4 bg-background shadow-shadow"
7+
>
8+
<div
9+
class="flex items-start gap-4 w-full"
10+
>
11+
<div
12+
class="flex rounded border-2 border-border text-foreground bg-background-secondary shadow-none flex-shrink-0 w-12 h-12 p-0 items-center justify-center"
13+
>
14+
<svg
15+
class="lucide lucide-music"
16+
fill="none"
17+
height="24"
18+
stroke="currentColor"
19+
stroke-linecap="round"
20+
stroke-linejoin="round"
21+
stroke-width="2"
22+
viewBox="0 0 24 24"
23+
width="24"
24+
xmlns="http://www.w3.org/2000/svg"
25+
>
26+
<path
27+
d="M9 18V5l12-2v13"
28+
/>
29+
<circle
30+
cx="6"
31+
cy="18"
32+
r="3"
33+
/>
34+
<circle
35+
cx="18"
36+
cy="16"
37+
r="3"
38+
/>
39+
</svg>
40+
</div>
41+
<div
42+
class="flex-1 min-w-0"
43+
>
44+
<div
45+
class="flex items-start justify-between gap-4"
46+
>
47+
<div
48+
class="min-w-0 flex-1"
49+
>
50+
<h3
51+
class="font-bold text-foreground text-lg leading-tight"
52+
>
53+
YouTube Music
54+
</h3>
55+
<p
56+
class="text-sm text-foreground-secondary mt-1"
57+
>
58+
by Nuclear Team
59+
</p>
60+
<p
61+
class="text-sm text-foreground-secondary mt-2 leading-relaxed"
62+
>
63+
Stream music directly from YouTube Music with full search and playlist support.
64+
</p>
65+
</div>
66+
<button
67+
class="inline-flex items-center transition-all rounded cursor-pointer text-foreground bg-primary border-2 border-border h-9 px-3 flex-shrink-0"
68+
>
69+
<svg
70+
class="lucide lucide-external-link mr-2"
71+
fill="none"
72+
height="14"
73+
stroke="currentColor"
74+
stroke-linecap="round"
75+
stroke-linejoin="round"
76+
stroke-width="2"
77+
viewBox="0 0 24 24"
78+
width="14"
79+
xmlns="http://www.w3.org/2000/svg"
80+
>
81+
<path
82+
d="M15 3h6v6"
83+
/>
84+
<path
85+
d="M10 14 21 3"
86+
/>
87+
<path
88+
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
89+
/>
90+
</svg>
91+
View Details
92+
</button>
93+
</div>
94+
</div>
95+
</div>
96+
</div>
97+
</DocumentFragment>
98+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { PluginItem } from './PluginItem';

0 commit comments

Comments
 (0)