From a4d75e37c4e5889ff249841fa060a0def5dfa728 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Thu, 21 May 2026 19:58:36 +0000 Subject: [PATCH] feat(dashboard): reusable Meet Kevin components (ActionChip, ConvictionBar, YouTubeEmbed) --- .../src/components/meetKevin/ActionChip.tsx | 17 ++++++++ .../components/meetKevin/ConvictionBar.tsx | 8 ++++ .../src/components/meetKevin/YouTubeEmbed.tsx | 40 +++++++++++++++++++ dashboard/src/components/meetKevin/index.ts | 4 ++ 4 files changed, 69 insertions(+) create mode 100644 dashboard/src/components/meetKevin/ActionChip.tsx create mode 100644 dashboard/src/components/meetKevin/ConvictionBar.tsx create mode 100644 dashboard/src/components/meetKevin/YouTubeEmbed.tsx create mode 100644 dashboard/src/components/meetKevin/index.ts diff --git a/dashboard/src/components/meetKevin/ActionChip.tsx b/dashboard/src/components/meetKevin/ActionChip.tsx new file mode 100644 index 0000000..5525640 --- /dev/null +++ b/dashboard/src/components/meetKevin/ActionChip.tsx @@ -0,0 +1,17 @@ +import type { TickerAction } from '../../types/meetKevin'; + +const COLORS: Record = { + 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 ( + + {action} + + ); +} diff --git a/dashboard/src/components/meetKevin/ConvictionBar.tsx b/dashboard/src/components/meetKevin/ConvictionBar.tsx new file mode 100644 index 0000000..183a274 --- /dev/null +++ b/dashboard/src/components/meetKevin/ConvictionBar.tsx @@ -0,0 +1,8 @@ +export function ConvictionBar({ value }: { value: number }) { + const pct = Math.max(0, Math.min(1, value)) * 100; + return ( +
+
+
+ ); +} diff --git a/dashboard/src/components/meetKevin/YouTubeEmbed.tsx b/dashboard/src/components/meetKevin/YouTubeEmbed.tsx new file mode 100644 index 0000000..24369f2 --- /dev/null +++ b/dashboard/src/components/meetKevin/YouTubeEmbed.tsx @@ -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( + ({ videoId, startSeconds = 0 }, ref) => { + const iframeRef = useRef(null); + + useImperativeHandle(ref, () => ({ + seekTo: (seconds: number) => { + const win = iframeRef.current?.contentWindow; + win?.postMessage( + JSON.stringify({ event: 'command', func: 'seekTo', args: [seconds, true] }), + '*', + ); + }, + })); + + return ( +
+