From 8a507f245e856dd07d817609753ea36741414645 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Fri, 14 Mar 2025 10:08:17 -0400 Subject: [PATCH] floating action button for sessions panel --- frontend/common/dist/components/ui/index.d.ts | 1 + frontend/common/dist/components/ui/index.js | 1 + frontend/common/dist/index.d.ts | 1 + frontend/common/dist/index.js | 2 + frontend/common/dist/styles/global.css | 102 +++++++++ .../src/components/DefaultAgentScreen.tsx | 215 ++++++++++++++++++ .../components/ui/floating-action-button.tsx | 36 +++ frontend/common/src/components/ui/index.ts | 1 + frontend/common/src/components/ui/layout.tsx | 19 +- frontend/common/src/index.ts | 3 + frontend/web/src/index.tsx | 215 +----------------- 11 files changed, 387 insertions(+), 209 deletions(-) create mode 100644 frontend/common/src/components/DefaultAgentScreen.tsx create mode 100644 frontend/common/src/components/ui/floating-action-button.tsx diff --git a/frontend/common/dist/components/ui/index.d.ts b/frontend/common/dist/components/ui/index.d.ts index 89b9725..838adde 100644 --- a/frontend/common/dist/components/ui/index.d.ts +++ b/frontend/common/dist/components/ui/index.d.ts @@ -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'; diff --git a/frontend/common/dist/components/ui/index.js b/frontend/common/dist/components/ui/index.js index 89b9725..838adde 100644 --- a/frontend/common/dist/components/ui/index.js +++ b/frontend/common/dist/components/ui/index.js @@ -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'; diff --git a/frontend/common/dist/index.d.ts b/frontend/common/dist/index.d.ts index 06d95ab..3f13bba 100644 --- a/frontend/common/dist/index.d.ts +++ b/frontend/common/dist/index.d.ts @@ -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'; diff --git a/frontend/common/dist/index.js b/frontend/common/dist/index.js index 6ad90fa..863663b 100644 --- a/frontend/common/dist/index.js +++ b/frontend/common/dist/index.js @@ -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"); diff --git a/frontend/common/dist/styles/global.css b/frontend/common/dist/styles/global.css index 2813e8d..d68f322 100644 --- a/frontend/common/dist/styles/global.css +++ b/frontend/common/dist/styles/global.css @@ -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; } diff --git a/frontend/common/src/components/DefaultAgentScreen.tsx b/frontend/common/src/components/DefaultAgentScreen.tsx new file mode 100644 index 0000000..916fe5b --- /dev/null +++ b/frontend/common/src/components/DefaultAgentScreen.tsx @@ -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(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 = ( +
+

+ RA-Aid +

+ +
+ {/* Theme toggle button */} + +
+
+ ); + + // Render sidebar content + const sidebarContent = ( + + ); + + // Render drawer + const drawerContent = ( + setIsDrawerOpen(false)} + /> + ); + + // Render main content + const mainContent = ( + selectedSessionId ? ( + <> +

+ Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} +

+ + + ) : ( +
+

Select a session to view details

+
+ ) + ); + + // Create floating action button for mobile sidebar toggle + const floatingAction = ( + + ); + + return ( + <> + + {mainContent} + +
+ {floatingAction} +
+ + ); +}; + +// 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; +}; diff --git a/frontend/common/src/components/ui/floating-action-button.tsx b/frontend/common/src/components/ui/floating-action-button.tsx new file mode 100644 index 0000000..90ed420 --- /dev/null +++ b/frontend/common/src/components/ui/floating-action-button.tsx @@ -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 = ({ + icon, + onClick, + ariaLabel = 'Action button', + className = '', + variant = 'default' +}) => { + return ( + + ); +}; diff --git a/frontend/common/src/components/ui/index.ts b/frontend/common/src/components/ui/index.ts index 1b2cc61..b7dc02e 100644 --- a/frontend/common/src/components/ui/index.ts +++ b/frontend/common/src/components/ui/index.ts @@ -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'; diff --git a/frontend/common/src/components/ui/layout.tsx b/frontend/common/src/components/ui/layout.tsx index b2da5be..97fee7b 100644 --- a/frontend/common/src/components/ui/layout.tsx +++ b/frontend/common/src/components/ui/layout.tsx @@ -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 = ({ header, sidebar, drawer, children }) => { +export const Layout: React.FC = ({ + header, + sidebar, + drawer, + children, + floatingAction +}) => { return ( -
+
{/* Header - always visible, spans full width */}
{header} @@ -36,6 +44,13 @@ export const Layout: React.FC = ({ header, sidebar, drawer, childre {/* Mobile drawer - rendered outside grid */} {drawer} + + {/* Floating action button for mobile */} + {floatingAction && ( +
+ {floatingAction} +
+ )}
); }; \ No newline at end of file diff --git a/frontend/common/src/index.ts b/frontend/common/src/index.ts index 6a63b0c..e47a3fc 100644 --- a/frontend/common/src/index.ts +++ b/frontend/common/src/index.ts @@ -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"); diff --git a/frontend/web/src/index.tsx b/frontend/web/src/index.tsx index f46832b..bb67e26 100644 --- a/frontend/web/src/index.tsx +++ b/frontend/web/src/index.tsx @@ -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(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 = ( -
-

- RA-Aid -

- -
- {/* Theme toggle button */} - - - {/* Mobile drawer toggle - show only on small screens */} -
- -
-
-
- ); - - // Render sidebar content - const sidebarContent = ( - - ); - - // Render drawer - const drawerContent = ( - setIsDrawerOpen(false)} - /> - ); - - // Render main content - const mainContent = ( - selectedSessionId ? ( - <> -

- Session: {sessions.find(s => s.id === selectedSessionId)?.name || 'Unknown'} -

- - - ) : ( -
-

Select a session to view details

-
- ) - ); - - return ( - - {mainContent} - - ); + return ; }; -// Initialize theme before rendering the app -setupTheme(); - +// Mount the app to the root element const root = ReactDOM.createRoot(document.getElementById('root')!); root.render(