feat(dashboard): reusable Meet Kevin components (ActionChip, ConvictionBar, YouTubeEmbed)

This commit is contained in:
Viktor Barzin 2026-05-21 19:58:36 +00:00
parent 83b18b43cf
commit a4d75e37c4
4 changed files with 69 additions and 0 deletions

View file

@ -0,0 +1,17 @@
import type { TickerAction } from '../../types/meetKevin';
const COLORS: Record<TickerAction, string> = {
buy: 'bg-green-600/30 text-green-300 border-green-500/40',
sell: 'bg-red-600/30 text-red-300 border-red-500/40',
hold: 'bg-slate-600/30 text-slate-300 border-slate-500/40',
watch: 'bg-yellow-600/30 text-yellow-200 border-yellow-500/40',
avoid: 'bg-rose-600/40 text-rose-200 border-rose-500/40',
};
export function ActionChip({ action }: { action: TickerAction }) {
return (
<span className={`px-2 py-0.5 text-xs font-semibold uppercase rounded-md border ${COLORS[action]}`}>
{action}
</span>
);
}

View file

@ -0,0 +1,8 @@
export function ConvictionBar({ value }: { value: number }) {
const pct = Math.max(0, Math.min(1, value)) * 100;
return (
<div className="w-full h-1.5 bg-slate-700 rounded-full overflow-hidden">
<div className="h-full bg-blue-400" style={{ width: `${pct}%` }} />
</div>
);
}

View file

@ -0,0 +1,40 @@
import { useRef, useImperativeHandle, forwardRef } from 'react';
export interface YouTubeEmbedHandle {
seekTo: (seconds: number) => void;
}
interface Props {
videoId: string;
startSeconds?: number;
}
export const YouTubeEmbed = forwardRef<YouTubeEmbedHandle, Props>(
({ videoId, startSeconds = 0 }, ref) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
useImperativeHandle(ref, () => ({
seekTo: (seconds: number) => {
const win = iframeRef.current?.contentWindow;
win?.postMessage(
JSON.stringify({ event: 'command', func: 'seekTo', args: [seconds, true] }),
'*',
);
},
}));
return (
<div className="relative pb-[56.25%] h-0 bg-black rounded-xl overflow-hidden">
<iframe
ref={iframeRef}
className="absolute inset-0 w-full h-full"
src={`https://www.youtube-nocookie.com/embed/${videoId}?start=${startSeconds}&enablejsapi=1`}
title={`YouTube video ${videoId}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
);
},
);
YouTubeEmbed.displayName = 'YouTubeEmbed';

View file

@ -0,0 +1,4 @@
export { ActionChip } from './ActionChip';
export { ConvictionBar } from './ConvictionBar';
export { YouTubeEmbed } from './YouTubeEmbed';
export type { YouTubeEmbedHandle } from './YouTubeEmbed';