floating action button for sessions panel
This commit is contained in:
parent
af16879dd6
commit
8a507f245e
|
|
@ -1,6 +1,7 @@
|
|||
export * from './button';
|
||||
export * from './card';
|
||||
export * from './collapsible';
|
||||
export * from './floating-action-button';
|
||||
export * from './input';
|
||||
export * from './layout';
|
||||
export * from './sheet';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export * from './button';
|
||||
export * from './card';
|
||||
export * from './collapsible';
|
||||
export * from './floating-action-button';
|
||||
export * from './input';
|
||||
export * from './layout';
|
||||
export * from './sheet';
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ export * from './components/TimelineStep';
|
|||
export * from './components/TimelineFeed';
|
||||
export * from './components/SessionDrawer';
|
||||
export * from './components/SessionSidebar';
|
||||
export * from './components/DefaultAgentScreen';
|
||||
export declare const hello: () => void;
|
||||
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export * from './components/TimelineFeed';
|
|||
// Export session navigation components
|
||||
export * from './components/SessionDrawer';
|
||||
export * from './components/SessionSidebar';
|
||||
// Export the main screen component
|
||||
export * from './components/DefaultAgentScreen';
|
||||
// Export the hello function (temporary example)
|
||||
export const hello = () => {
|
||||
console.log("Hello from @ra-aid/common");
|
||||
|
|
|
|||
|
|
@ -614,6 +614,9 @@ video {
|
|||
.bottom-0 {
|
||||
bottom: 0px;
|
||||
}
|
||||
.bottom-6 {
|
||||
bottom: 1.5rem;
|
||||
}
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
|
@ -623,6 +626,9 @@ video {
|
|||
.right-4 {
|
||||
right: 1rem;
|
||||
}
|
||||
.right-6 {
|
||||
right: 1.5rem;
|
||||
}
|
||||
.top-0 {
|
||||
top: 0px;
|
||||
}
|
||||
|
|
@ -638,6 +644,9 @@ video {
|
|||
.z-30 {
|
||||
z-index: 30;
|
||||
}
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
.col-span-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
|
@ -654,12 +663,18 @@ video {
|
|||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.mr-1 {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
.mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.mr-3 {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
|
@ -678,6 +693,9 @@ video {
|
|||
.block {
|
||||
display: block;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
|
@ -693,6 +711,9 @@ video {
|
|||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
.h-14 {
|
||||
height: 3.5rem;
|
||||
}
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
|
@ -711,6 +732,9 @@ video {
|
|||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
.h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
.h-8 {
|
||||
height: 2rem;
|
||||
}
|
||||
|
|
@ -729,6 +753,9 @@ video {
|
|||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.w-14 {
|
||||
width: 3.5rem;
|
||||
}
|
||||
.w-2\.5 {
|
||||
width: 0.625rem;
|
||||
}
|
||||
|
|
@ -744,6 +771,9 @@ video {
|
|||
.w-4 {
|
||||
width: 1rem;
|
||||
}
|
||||
.w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
.w-8 {
|
||||
width: 2rem;
|
||||
}
|
||||
|
|
@ -809,6 +839,9 @@ video {
|
|||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
|
@ -905,6 +938,10 @@ video {
|
|||
.border-transparent {
|
||||
border-color: transparent;
|
||||
}
|
||||
.border-white {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
.border-l-transparent {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
|
@ -924,6 +961,10 @@ video {
|
|||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-blue-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-border {
|
||||
background-color: hsl(var(--border));
|
||||
}
|
||||
|
|
@ -951,6 +992,10 @@ video {
|
|||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-red-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-secondary {
|
||||
background-color: hsl(var(--secondary));
|
||||
}
|
||||
|
|
@ -961,6 +1006,21 @@ video {
|
|||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.bg-gradient-to-r {
|
||||
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||
}
|
||||
.from-blue-400 {
|
||||
--tw-gradient-from: #60a5fa var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(96 165 250 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.to-purple-500 {
|
||||
--tw-gradient-to: #a855f7 var(--tw-gradient-to-position);
|
||||
}
|
||||
.bg-clip-text {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
|
@ -1023,6 +1083,10 @@ video {
|
|||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
|
|
@ -1031,10 +1095,17 @@ video {
|
|||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
@ -1077,6 +1148,13 @@ video {
|
|||
.text-secondary-foreground {
|
||||
color: hsl(var(--secondary-foreground));
|
||||
}
|
||||
.text-transparent {
|
||||
color: transparent;
|
||||
}
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
.underline-offset-4 {
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
|
@ -1098,6 +1176,11 @@ video {
|
|||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
.shadow-xl {
|
||||
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
.outline {
|
||||
outline-style: solid;
|
||||
}
|
||||
|
|
@ -1117,6 +1200,9 @@ video {
|
|||
.ring-offset-background {
|
||||
--tw-ring-offset-color: hsl(var(--background));
|
||||
}
|
||||
.filter {
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
.backdrop-blur-sm {
|
||||
--tw-backdrop-blur: blur(4px);
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
|
|
@ -1203,12 +1289,20 @@ video {
|
|||
.hover\:bg-accent\/50:hover {
|
||||
background-color: hsl(var(--accent) / 0.5);
|
||||
}
|
||||
.hover\:bg-blue-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.hover\:bg-destructive\/90:hover {
|
||||
background-color: hsl(var(--destructive) / 0.9);
|
||||
}
|
||||
.hover\:bg-primary\/90:hover {
|
||||
background-color: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
.hover\:bg-red-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
.hover\:bg-secondary\/80:hover {
|
||||
background-color: hsl(var(--secondary) / 0.8);
|
||||
}
|
||||
|
|
@ -1381,6 +1475,10 @@ video {
|
|||
.data-\[state\=open\]\:duration-500[data-state="open"] {
|
||||
animation-duration: 500ms;
|
||||
}
|
||||
.dark\:border-gray-800:is(.dark *) {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(31 41 55 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
|
||||
.sm\:max-w-md {
|
||||
|
|
@ -1419,6 +1517,10 @@ video {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.md\:hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md\:grid-cols-\[250px_1fr\] {
|
||||
grid-template-columns: 250px 1fr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { PanelLeft } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
Layout
|
||||
} from './ui';
|
||||
import { SessionDrawer } from './SessionDrawer';
|
||||
import { SessionSidebar } from './SessionSidebar';
|
||||
import { TimelineFeed } from './TimelineFeed';
|
||||
import { getSampleAgentSessions, getSampleAgentSteps } from '../utils/sample-data';
|
||||
|
||||
/**
|
||||
* DefaultAgentScreen component
|
||||
*
|
||||
* Main application screen for displaying agent sessions and their steps.
|
||||
* Handles state management, responsive design, and UI interactions.
|
||||
*/
|
||||
export const DefaultAgentScreen: React.FC = () => {
|
||||
// State for drawer open/close
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
|
||||
// State for selected session
|
||||
const [selectedSessionId, setSelectedSessionId] = useState<string | null>(null);
|
||||
|
||||
// State for theme (dark is default)
|
||||
const [isDarkTheme, setIsDarkTheme] = useState(true);
|
||||
|
||||
// Get sample data
|
||||
const sessions = getSampleAgentSessions();
|
||||
const allSteps = getSampleAgentSteps();
|
||||
|
||||
// Set up theme on component mount
|
||||
useEffect(() => {
|
||||
const isDark = setupTheme();
|
||||
setIsDarkTheme(isDark);
|
||||
}, []);
|
||||
|
||||
// Set initial selected session if none selected
|
||||
useEffect(() => {
|
||||
if (!selectedSessionId && sessions.length > 0) {
|
||||
setSelectedSessionId(sessions[0].id);
|
||||
}
|
||||
}, [sessions, selectedSessionId]);
|
||||
|
||||
// Filter steps for selected session
|
||||
const selectedSessionSteps = selectedSessionId
|
||||
? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id))
|
||||
: [];
|
||||
|
||||
// Handle session selection
|
||||
const handleSessionSelect = (sessionId: string) => {
|
||||
setSelectedSessionId(sessionId);
|
||||
setIsDrawerOpen(false); // Close drawer on selection (mobile)
|
||||
};
|
||||
|
||||
// Toggle theme function
|
||||
const toggleTheme = () => {
|
||||
const newIsDark = !isDarkTheme;
|
||||
setIsDarkTheme(newIsDark);
|
||||
|
||||
// Update document element class
|
||||
if (newIsDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('theme', newIsDark ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
// Render header content
|
||||
const headerContent = (
|
||||
<div className="flex justify-between items-center h-full px-4">
|
||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 inline-block text-transparent bg-clip-text">
|
||||
RA-Aid
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Theme toggle button */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
aria-label={isDarkTheme ? "Switch to light mode" : "Switch to dark mode"}
|
||||
className="mr-2"
|
||||
>
|
||||
{isDarkTheme ? (
|
||||
// Sun icon for light mode toggle
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
) : (
|
||||
// Moon icon for dark mode toggle
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Render sidebar content
|
||||
const sidebarContent = (
|
||||
<SessionSidebar
|
||||
sessions={sessions}
|
||||
currentSessionId={selectedSessionId || undefined}
|
||||
onSelectSession={handleSessionSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
// Render drawer
|
||||
const drawerContent = (
|
||||
<SessionDrawer
|
||||
sessions={sessions}
|
||||
currentSessionId={selectedSessionId || undefined}
|
||||
onSelectSession={handleSessionSelect}
|
||||
isOpen={isDrawerOpen}
|
||||
onClose={() => setIsDrawerOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
// Render main content
|
||||
const mainContent = (
|
||||
selectedSessionId ? (
|
||||
<>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'}
|
||||
</h2>
|
||||
<TimelineFeed
|
||||
steps={selectedSessionSteps}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<p className="text-muted-foreground">Select a session to view details</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
// Create floating action button for mobile sidebar toggle
|
||||
const floatingAction = (
|
||||
<Button
|
||||
variant="default"
|
||||
size="icon"
|
||||
onClick={() => setIsDrawerOpen(true)}
|
||||
aria-label="Toggle sessions panel"
|
||||
className="h-14 w-14 rounded-full shadow-xl bg-red-600 hover:bg-red-700 text-white flex items-center justify-center border-2 border-white dark:border-gray-800"
|
||||
>
|
||||
<PanelLeft className="h-6 w-6" />
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout
|
||||
header={headerContent}
|
||||
sidebar={sidebarContent}
|
||||
drawer={drawerContent}
|
||||
>
|
||||
{mainContent}
|
||||
</Layout>
|
||||
<div className="fixed bottom-6 right-6 z-50 md:hidden" style={{zIndex: 9999}}>
|
||||
{floatingAction}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper function for theme setup
|
||||
const setupTheme = () => {
|
||||
// Check if theme preference is stored in localStorage
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
|
||||
// Default to dark mode unless explicitly set to light
|
||||
const isDark = storedTheme ? storedTheme === 'dark' : true;
|
||||
|
||||
// Apply theme to document
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
return isDark;
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { Button } from './button';
|
||||
|
||||
export interface FloatingActionButtonProps {
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||
}
|
||||
|
||||
/**
|
||||
* FloatingActionButton component
|
||||
*
|
||||
* A button typically used for primary actions on mobile layouts
|
||||
* Designed to be used with the Layout component's floatingAction prop
|
||||
*/
|
||||
export const FloatingActionButton: React.FC<FloatingActionButtonProps> = ({
|
||||
icon,
|
||||
onClick,
|
||||
ariaLabel = 'Action button',
|
||||
className = '',
|
||||
variant = 'default'
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
size="icon"
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
className={`h-14 w-14 rounded-full shadow-xl bg-blue-600 hover:bg-blue-700 text-white flex items-center justify-center border-2 border-white dark:border-gray-800 ${className}`}
|
||||
>
|
||||
{icon}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export * from './button';
|
||||
export * from './card';
|
||||
export * from './collapsible';
|
||||
export * from './floating-action-button';
|
||||
export * from './input';
|
||||
export * from './layout';
|
||||
export * from './sheet';
|
||||
|
|
|
|||
|
|
@ -6,17 +6,25 @@ import React from 'react';
|
|||
* - Sticky header at the top (z-index 30)
|
||||
* - Sidebar on desktop (hidden on mobile)
|
||||
* - Main content area with proper positioning
|
||||
* - Optional floating action button for mobile navigation
|
||||
*/
|
||||
export interface LayoutProps {
|
||||
header: React.ReactNode;
|
||||
sidebar?: React.ReactNode;
|
||||
drawer?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
floatingAction?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Layout: React.FC<LayoutProps> = ({ header, sidebar, drawer, children }) => {
|
||||
export const Layout: React.FC<LayoutProps> = ({
|
||||
header,
|
||||
sidebar,
|
||||
drawer,
|
||||
children,
|
||||
floatingAction
|
||||
}) => {
|
||||
return (
|
||||
<div className="grid min-h-screen grid-cols-1 grid-rows-[64px_1fr] md:grid-cols-[250px_1fr] lg:grid-cols-[300px_1fr] bg-background text-foreground">
|
||||
<div className="grid min-h-screen grid-cols-1 grid-rows-[64px_1fr] md:grid-cols-[250px_1fr] lg:grid-cols-[300px_1fr] bg-background text-foreground relative">
|
||||
{/* Header - always visible, spans full width */}
|
||||
<header className="sticky top-0 z-30 h-16 flex items-center bg-background border-b border-border col-span-full">
|
||||
{header}
|
||||
|
|
@ -36,6 +44,13 @@ export const Layout: React.FC<LayoutProps> = ({ header, sidebar, drawer, childre
|
|||
|
||||
{/* Mobile drawer - rendered outside grid */}
|
||||
{drawer}
|
||||
|
||||
{/* Floating action button for mobile */}
|
||||
{floatingAction && (
|
||||
<div className="fixed bottom-6 right-6 z-50 md:hidden">
|
||||
{floatingAction}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -18,6 +18,9 @@ export * from './components/TimelineFeed';
|
|||
export * from './components/SessionDrawer';
|
||||
export * from './components/SessionSidebar';
|
||||
|
||||
// Export the main screen component
|
||||
export * from './components/DefaultAgentScreen';
|
||||
|
||||
// Export the hello function (temporary example)
|
||||
export const hello = (): void => {
|
||||
console.log("Hello from @ra-aid/common");
|
||||
|
|
|
|||
|
|
@ -1,215 +1,16 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import {
|
||||
Button,
|
||||
Layout,
|
||||
SessionDrawer,
|
||||
SessionSidebar,
|
||||
TimelineFeed,
|
||||
getSampleAgentSessions,
|
||||
getSampleAgentSteps
|
||||
} from '@ra-aid/common';
|
||||
// The CSS import happens through the common package's index.ts
|
||||
|
||||
// Theme management helper function
|
||||
const setupTheme = () => {
|
||||
// Check if theme preference is stored in localStorage
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
|
||||
// Default to dark mode unless explicitly set to light
|
||||
const isDark = storedTheme ? storedTheme === 'dark' : true;
|
||||
|
||||
// Apply theme class to document element (html) for better CSS specificity
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// Store the current theme preference
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
|
||||
return isDark;
|
||||
};
|
||||
import { DefaultAgentScreen } from '@ra-aid/common';
|
||||
|
||||
/**
|
||||
* Main application entry point
|
||||
* Simply renders the DefaultAgentScreen component from the common package
|
||||
*/
|
||||
const App = () => {
|
||||
// State for drawer open/close
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
|
||||
// State for selected session
|
||||
const [selectedSessionId, setSelectedSessionId] = useState<string | null>(null);
|
||||
|
||||
// State for theme (dark is default)
|
||||
const [isDarkTheme, setIsDarkTheme] = useState(true);
|
||||
|
||||
// Get sample data
|
||||
const sessions = getSampleAgentSessions();
|
||||
const allSteps = getSampleAgentSteps();
|
||||
|
||||
// Set up theme on component mount
|
||||
useEffect(() => {
|
||||
const isDark = setupTheme();
|
||||
setIsDarkTheme(isDark);
|
||||
}, []);
|
||||
|
||||
// Set initial selected session if none selected
|
||||
useEffect(() => {
|
||||
if (!selectedSessionId && sessions.length > 0) {
|
||||
setSelectedSessionId(sessions[0].id);
|
||||
}
|
||||
}, [sessions, selectedSessionId]);
|
||||
|
||||
// Filter steps for selected session
|
||||
const selectedSessionSteps = selectedSessionId
|
||||
? allSteps.filter(step => sessions.find(s => s.id === selectedSessionId)?.steps.some(s => s.id === step.id))
|
||||
: [];
|
||||
|
||||
// Handle session selection
|
||||
const handleSessionSelect = (sessionId: string) => {
|
||||
setSelectedSessionId(sessionId);
|
||||
setIsDrawerOpen(false); // Close drawer on selection (mobile)
|
||||
};
|
||||
|
||||
// Toggle theme function
|
||||
const toggleTheme = () => {
|
||||
const newIsDark = !isDarkTheme;
|
||||
setIsDarkTheme(newIsDark);
|
||||
|
||||
// Update document element class
|
||||
if (newIsDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('theme', newIsDark ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
// Render header content
|
||||
const headerContent = (
|
||||
<div className="flex justify-between items-center h-full px-4">
|
||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 inline-block text-transparent bg-clip-text">
|
||||
RA-Aid
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Theme toggle button */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
aria-label={isDarkTheme ? "Switch to light mode" : "Switch to dark mode"}
|
||||
className="mr-2"
|
||||
>
|
||||
{isDarkTheme ? (
|
||||
// Sun icon for light mode toggle
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
) : (
|
||||
// Moon icon for dark mode toggle
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Mobile drawer toggle - show only on small screens */}
|
||||
<div className="md:hidden">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsDrawerOpen(true)}
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Render sidebar content
|
||||
const sidebarContent = (
|
||||
<SessionSidebar
|
||||
sessions={sessions}
|
||||
currentSessionId={selectedSessionId || undefined}
|
||||
onSelectSession={handleSessionSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
// Render drawer
|
||||
const drawerContent = (
|
||||
<SessionDrawer
|
||||
sessions={sessions}
|
||||
currentSessionId={selectedSessionId || undefined}
|
||||
onSelectSession={handleSessionSelect}
|
||||
isOpen={isDrawerOpen}
|
||||
onClose={() => setIsDrawerOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
// Render main content
|
||||
const mainContent = (
|
||||
selectedSessionId ? (
|
||||
<>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'}
|
||||
</h2>
|
||||
<TimelineFeed
|
||||
steps={selectedSessionSteps}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<p className="text-muted-foreground">Select a session to view details</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={headerContent}
|
||||
sidebar={sidebarContent}
|
||||
drawer={drawerContent}
|
||||
>
|
||||
{mainContent}
|
||||
</Layout>
|
||||
);
|
||||
return <DefaultAgentScreen />;
|
||||
};
|
||||
|
||||
// Initialize theme before rendering the app
|
||||
setupTheme();
|
||||
|
||||
// Mount the app to the root element
|
||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
|
|
|
|||
Loading…
Reference in New Issue