floating action button for sessions panel
This commit is contained in:
parent
af16879dd6
commit
8a507f245e
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ export * from './components/TimelineStep';
|
||||||
export * from './components/TimelineFeed';
|
export * from './components/TimelineFeed';
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
export declare const hello: () => void;
|
export declare const hello: () => void;
|
||||||
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
export { getSampleAgentSteps, getSampleAgentSessions } from './utils/sample-data';
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ export * from './components/TimelineFeed';
|
||||||
// Export session navigation components
|
// Export session navigation components
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
// Export the main screen component
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
// Export the hello function (temporary example)
|
// Export the hello function (temporary example)
|
||||||
export const hello = () => {
|
export const hello = () => {
|
||||||
console.log("Hello from @ra-aid/common");
|
console.log("Hello from @ra-aid/common");
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,9 @@ video {
|
||||||
.bottom-0 {
|
.bottom-0 {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
.bottom-6 {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
}
|
||||||
.left-0 {
|
.left-0 {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -623,6 +626,9 @@ video {
|
||||||
.right-4 {
|
.right-4 {
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
}
|
}
|
||||||
|
.right-6 {
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
.top-0 {
|
.top-0 {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
@ -638,6 +644,9 @@ video {
|
||||||
.z-30 {
|
.z-30 {
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
}
|
}
|
||||||
|
.z-50 {
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
.col-span-full {
|
.col-span-full {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
@ -654,12 +663,18 @@ video {
|
||||||
.mb-2 {
|
.mb-2 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
.mb-5 {
|
.mb-5 {
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
.mr-1 {
|
.mr-1 {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.mr-2 {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
.mr-3 {
|
.mr-3 {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
@ -678,6 +693,9 @@ video {
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
@ -693,6 +711,9 @@ video {
|
||||||
.h-10 {
|
.h-10 {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
.h-14 {
|
||||||
|
height: 3.5rem;
|
||||||
|
}
|
||||||
.h-16 {
|
.h-16 {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
@ -711,6 +732,9 @@ video {
|
||||||
.h-5 {
|
.h-5 {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.h-6 {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
.h-8 {
|
.h-8 {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
@ -729,6 +753,9 @@ video {
|
||||||
.min-h-screen {
|
.min-h-screen {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
.w-14 {
|
||||||
|
width: 3.5rem;
|
||||||
|
}
|
||||||
.w-2\.5 {
|
.w-2\.5 {
|
||||||
width: 0.625rem;
|
width: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
@ -744,6 +771,9 @@ video {
|
||||||
.w-4 {
|
.w-4 {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
.w-6 {
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
.w-8 {
|
.w-8 {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
|
|
@ -809,6 +839,9 @@ video {
|
||||||
.justify-between {
|
.justify-between {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.gap-2 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
.gap-4 {
|
.gap-4 {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
@ -905,6 +938,10 @@ video {
|
||||||
.border-transparent {
|
.border-transparent {
|
||||||
border-color: 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-l-transparent {
|
||||||
border-left-color: transparent;
|
border-left-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
@ -924,6 +961,10 @@ video {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(59 130 246 / var(--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 {
|
.bg-border {
|
||||||
background-color: hsl(var(--border));
|
background-color: hsl(var(--border));
|
||||||
}
|
}
|
||||||
|
|
@ -951,6 +992,10 @@ video {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(239 68 68 / var(--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 {
|
.bg-secondary {
|
||||||
background-color: hsl(var(--secondary));
|
background-color: hsl(var(--secondary));
|
||||||
}
|
}
|
||||||
|
|
@ -961,6 +1006,21 @@ video {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(234 179 8 / var(--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 {
|
.p-2 {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -1023,6 +1083,10 @@ video {
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
.text-lg {
|
.text-lg {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
|
|
@ -1031,10 +1095,17 @@ video {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.text-xl {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
.text-xs {
|
.text-xs {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
.font-medium {
|
.font-medium {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
@ -1077,6 +1148,13 @@ video {
|
||||||
.text-secondary-foreground {
|
.text-secondary-foreground {
|
||||||
color: hsl(var(--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 {
|
.underline-offset-4 {
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
@ -1098,6 +1176,11 @@ video {
|
||||||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
--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);
|
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 {
|
||||||
outline-style: solid;
|
outline-style: solid;
|
||||||
}
|
}
|
||||||
|
|
@ -1117,6 +1200,9 @@ video {
|
||||||
.ring-offset-background {
|
.ring-offset-background {
|
||||||
--tw-ring-offset-color: hsl(var(--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 {
|
.backdrop-blur-sm {
|
||||||
--tw-backdrop-blur: blur(4px);
|
--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);
|
-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 {
|
.hover\:bg-accent\/50:hover {
|
||||||
background-color: hsl(var(--accent) / 0.5);
|
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 {
|
.hover\:bg-destructive\/90:hover {
|
||||||
background-color: hsl(var(--destructive) / 0.9);
|
background-color: hsl(var(--destructive) / 0.9);
|
||||||
}
|
}
|
||||||
.hover\:bg-primary\/90:hover {
|
.hover\:bg-primary\/90:hover {
|
||||||
background-color: hsl(var(--primary) / 0.9);
|
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 {
|
.hover\:bg-secondary\/80:hover {
|
||||||
background-color: hsl(var(--secondary) / 0.8);
|
background-color: hsl(var(--secondary) / 0.8);
|
||||||
}
|
}
|
||||||
|
|
@ -1381,6 +1475,10 @@ video {
|
||||||
.data-\[state\=open\]\:duration-500[data-state="open"] {
|
.data-\[state\=open\]\:duration-500[data-state="open"] {
|
||||||
animation-duration: 500ms;
|
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) {
|
@media (min-width: 640px) {
|
||||||
|
|
||||||
.sm\:max-w-md {
|
.sm\:max-w-md {
|
||||||
|
|
@ -1419,6 +1517,10 @@ video {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md\:hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:grid-cols-\[250px_1fr\] {
|
.md\:grid-cols-\[250px_1fr\] {
|
||||||
grid-template-columns: 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 './button';
|
||||||
export * from './card';
|
export * from './card';
|
||||||
export * from './collapsible';
|
export * from './collapsible';
|
||||||
|
export * from './floating-action-button';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './sheet';
|
export * from './sheet';
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,25 @@ import React from 'react';
|
||||||
* - Sticky header at the top (z-index 30)
|
* - Sticky header at the top (z-index 30)
|
||||||
* - Sidebar on desktop (hidden on mobile)
|
* - Sidebar on desktop (hidden on mobile)
|
||||||
* - Main content area with proper positioning
|
* - Main content area with proper positioning
|
||||||
|
* - Optional floating action button for mobile navigation
|
||||||
*/
|
*/
|
||||||
export interface LayoutProps {
|
export interface LayoutProps {
|
||||||
header: React.ReactNode;
|
header: React.ReactNode;
|
||||||
sidebar?: React.ReactNode;
|
sidebar?: React.ReactNode;
|
||||||
drawer?: React.ReactNode;
|
drawer?: React.ReactNode;
|
||||||
children: 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 (
|
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 - 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 className="sticky top-0 z-30 h-16 flex items-center bg-background border-b border-border col-span-full">
|
||||||
{header}
|
{header}
|
||||||
|
|
@ -36,6 +44,13 @@ export const Layout: React.FC<LayoutProps> = ({ header, sidebar, drawer, childre
|
||||||
|
|
||||||
{/* Mobile drawer - rendered outside grid */}
|
{/* Mobile drawer - rendered outside grid */}
|
||||||
{drawer}
|
{drawer}
|
||||||
|
|
||||||
|
{/* Floating action button for mobile */}
|
||||||
|
{floatingAction && (
|
||||||
|
<div className="fixed bottom-6 right-6 z-50 md:hidden">
|
||||||
|
{floatingAction}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -18,6 +18,9 @@ export * from './components/TimelineFeed';
|
||||||
export * from './components/SessionDrawer';
|
export * from './components/SessionDrawer';
|
||||||
export * from './components/SessionSidebar';
|
export * from './components/SessionSidebar';
|
||||||
|
|
||||||
|
// Export the main screen component
|
||||||
|
export * from './components/DefaultAgentScreen';
|
||||||
|
|
||||||
// Export the hello function (temporary example)
|
// Export the hello function (temporary example)
|
||||||
export const hello = (): void => {
|
export const hello = (): void => {
|
||||||
console.log("Hello from @ra-aid/common");
|
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 ReactDOM from 'react-dom/client';
|
||||||
import {
|
import { DefaultAgentScreen } from '@ra-aid/common';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application entry point
|
||||||
|
* Simply renders the DefaultAgentScreen component from the common package
|
||||||
|
*/
|
||||||
const App = () => {
|
const App = () => {
|
||||||
// State for drawer open/close
|
return <DefaultAgentScreen />;
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize theme before rendering the app
|
// Mount the app to the root element
|
||||||
setupTheme();
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue