feat: global styles with Tabler dark theme, 3-column layout
This commit is contained in:
@@ -0,0 +1,300 @@
|
|||||||
|
/* Tabler CSS imports */
|
||||||
|
@import "@tabler/core/dist/css/tabler.min.css";
|
||||||
|
@import "@tabler/core/dist/css/tabler-vendors.min.css";
|
||||||
|
|
||||||
|
/* Force dark theme */
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
body {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||||
|
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
3-COLUMN LAYOUT
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.app-layout {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left sidebar */
|
||||||
|
.sidebar {
|
||||||
|
width: 240px;
|
||||||
|
min-width: 240px;
|
||||||
|
background: #16213e;
|
||||||
|
border-right: 1px solid #2a2a4a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Centre chat panel */
|
||||||
|
.chat-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right panel */
|
||||||
|
.right-panel {
|
||||||
|
width: 45%;
|
||||||
|
min-width: 300px;
|
||||||
|
background: #16213e;
|
||||||
|
border-left: 1px solid #2a2a4a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
SIDEBAR COMPONENTS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #2a2a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #8888aa;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item {
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #c0c0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item:hover {
|
||||||
|
background: #1e2a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item.active {
|
||||||
|
background: #2a3a5a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-meta {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6a6a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
CHAT COMPONENTS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message {
|
||||||
|
max-width: 85%;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.user {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #2a3a6a;
|
||||||
|
border-radius: 12px 12px 4px 12px;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.assistant {
|
||||||
|
background: #1e2a4a;
|
||||||
|
border-radius: 12px 12px 12px 4px;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.assistant p {
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area {
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid #2a2a4a;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area input,
|
||||||
|
.chat-input-area textarea {
|
||||||
|
background: #1e2a4a;
|
||||||
|
border: 1px solid #3a3a5a;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area input:focus,
|
||||||
|
.chat-input-area textarea:focus {
|
||||||
|
border-color: #5a7aba;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area button {
|
||||||
|
background: #3a5aba;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
TOOL CALL CARDS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.tool-call-card {
|
||||||
|
background: #12192e;
|
||||||
|
border: 1px solid #2a2a4a;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-name {
|
||||||
|
color: #7aa2f7;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-args {
|
||||||
|
color: #8888aa;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-status.running {
|
||||||
|
background: #2a4a3a;
|
||||||
|
color: #7af7a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-status.complete {
|
||||||
|
background: #2a3a4a;
|
||||||
|
color: #7aa2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
RIGHT PANEL TABS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.right-panel-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #2a2a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tabs button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.6rem;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
color: #8888aa;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tabs button.active {
|
||||||
|
color: #fff;
|
||||||
|
border-bottom-color: #5a7aba;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
LOGIN PAGE
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: #16213e;
|
||||||
|
border: 1px solid #2a2a4a;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.6rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
background: #1e2a4a;
|
||||||
|
border: 1px solid #3a3a5a;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.7rem;
|
||||||
|
background: #3a5aba;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #f7607a;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
"""
|
||||||
|
Tests for frontend/src/styles/globals.css
|
||||||
|
|
||||||
|
Verifies the CSS file exists with all required selectors, imports, and styles
|
||||||
|
as specified in task-3.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
CSS_FILE = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
"..",
|
||||||
|
"frontend",
|
||||||
|
"src",
|
||||||
|
"styles",
|
||||||
|
"globals.css",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def css_content():
|
||||||
|
assert os.path.exists(CSS_FILE), f"globals.css not found at {CSS_FILE}"
|
||||||
|
with open(CSS_FILE) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
class TestTablerImports:
|
||||||
|
def test_imports_tabler_core_css(self, css_content):
|
||||||
|
assert "@tabler/core/dist/css/tabler.min.css" in css_content
|
||||||
|
|
||||||
|
def test_imports_tabler_vendors_css(self, css_content):
|
||||||
|
assert "tabler-vendors.min.css" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestColorScheme:
|
||||||
|
def test_forces_dark_color_scheme(self, css_content):
|
||||||
|
assert "color-scheme: dark" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestBodyStyles:
|
||||||
|
def test_body_background_color(self, css_content):
|
||||||
|
assert "#1a1a2e" in css_content
|
||||||
|
|
||||||
|
def test_body_text_color(self, css_content):
|
||||||
|
assert "#e0e0e0" in css_content
|
||||||
|
|
||||||
|
def test_body_margin_zero(self, css_content):
|
||||||
|
assert "margin: 0" in css_content
|
||||||
|
|
||||||
|
def test_body_overflow_hidden(self, css_content):
|
||||||
|
assert "overflow: hidden" in css_content
|
||||||
|
|
||||||
|
def test_body_height_100vh(self, css_content):
|
||||||
|
# body should have height: 100vh
|
||||||
|
assert "100vh" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestRootStyles:
|
||||||
|
def test_root_height_100vh(self, css_content):
|
||||||
|
assert "#root" in css_content
|
||||||
|
assert "100vh" in css_content
|
||||||
|
|
||||||
|
def test_root_display_flex(self, css_content):
|
||||||
|
assert "#root" in css_content
|
||||||
|
assert "flex" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestLayoutClasses:
|
||||||
|
def test_app_layout_class(self, css_content):
|
||||||
|
assert ".app-layout" in css_content
|
||||||
|
|
||||||
|
def test_sidebar_class(self, css_content):
|
||||||
|
assert ".sidebar" in css_content
|
||||||
|
|
||||||
|
def test_sidebar_width(self, css_content):
|
||||||
|
assert "240px" in css_content
|
||||||
|
|
||||||
|
def test_sidebar_background(self, css_content):
|
||||||
|
assert "#16213e" in css_content
|
||||||
|
|
||||||
|
def test_chat_panel_class(self, css_content):
|
||||||
|
assert ".chat-panel" in css_content
|
||||||
|
|
||||||
|
def test_right_panel_class(self, css_content):
|
||||||
|
assert ".right-panel" in css_content
|
||||||
|
|
||||||
|
def test_right_panel_width_45_percent(self, css_content):
|
||||||
|
assert "45%" in css_content
|
||||||
|
|
||||||
|
def test_right_panel_min_width(self, css_content):
|
||||||
|
assert "300px" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestSidebarComponents:
|
||||||
|
def test_sidebar_header(self, css_content):
|
||||||
|
assert ".sidebar-header" in css_content
|
||||||
|
|
||||||
|
def test_session_list(self, css_content):
|
||||||
|
assert ".session-list" in css_content
|
||||||
|
|
||||||
|
def test_session_item(self, css_content):
|
||||||
|
assert ".session-item" in css_content
|
||||||
|
|
||||||
|
def test_session_item_hover(self, css_content):
|
||||||
|
assert ".session-item:hover" in css_content
|
||||||
|
|
||||||
|
def test_session_item_active(self, css_content):
|
||||||
|
assert ".session-item.active" in css_content
|
||||||
|
|
||||||
|
def test_session_meta(self, css_content):
|
||||||
|
assert ".session-meta" in css_content
|
||||||
|
|
||||||
|
def test_sidebar_h3_color(self, css_content):
|
||||||
|
assert "#8888aa" in css_content
|
||||||
|
|
||||||
|
def test_session_item_color(self, css_content):
|
||||||
|
assert "#c0c0d0" in css_content
|
||||||
|
|
||||||
|
def test_session_meta_color(self, css_content):
|
||||||
|
assert "#6a6a8a" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestChatComponents:
|
||||||
|
def test_chat_messages_class(self, css_content):
|
||||||
|
assert ".chat-messages" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_class(self, css_content):
|
||||||
|
assert ".chat-message" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_max_width(self, css_content):
|
||||||
|
assert "85%" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_user_class(self, css_content):
|
||||||
|
assert ".chat-message.user" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_user_background(self, css_content):
|
||||||
|
assert "#2a3a6a" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_assistant_class(self, css_content):
|
||||||
|
assert ".chat-message.assistant" in css_content
|
||||||
|
|
||||||
|
def test_chat_message_assistant_background(self, css_content):
|
||||||
|
assert "#1e2a4a" in css_content
|
||||||
|
|
||||||
|
def test_chat_input_area_class(self, css_content):
|
||||||
|
assert ".chat-input-area" in css_content
|
||||||
|
|
||||||
|
def test_chat_input_border_color(self, css_content):
|
||||||
|
assert "#3a3a5a" in css_content
|
||||||
|
|
||||||
|
def test_chat_input_focus_border(self, css_content):
|
||||||
|
assert "#5a7aba" in css_content
|
||||||
|
|
||||||
|
def test_chat_button_background(self, css_content):
|
||||||
|
assert "#3a5aba" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestToolCallCards:
|
||||||
|
def test_tool_call_card_class(self, css_content):
|
||||||
|
assert ".tool-call-card" in css_content
|
||||||
|
|
||||||
|
def test_tool_call_card_background(self, css_content):
|
||||||
|
assert "#12192e" in css_content
|
||||||
|
|
||||||
|
def test_tool_name_class(self, css_content):
|
||||||
|
assert ".tool-name" in css_content
|
||||||
|
|
||||||
|
def test_tool_name_color(self, css_content):
|
||||||
|
assert "#7aa2f7" in css_content
|
||||||
|
|
||||||
|
def test_tool_args_class(self, css_content):
|
||||||
|
assert ".tool-args" in css_content
|
||||||
|
|
||||||
|
def test_tool_args_color(self, css_content):
|
||||||
|
assert "#8888aa" in css_content
|
||||||
|
|
||||||
|
def test_tool_status_class(self, css_content):
|
||||||
|
assert ".tool-status" in css_content
|
||||||
|
|
||||||
|
def test_tool_status_running(self, css_content):
|
||||||
|
assert ".running" in css_content
|
||||||
|
|
||||||
|
def test_tool_status_running_color(self, css_content):
|
||||||
|
assert "#7af7a2" in css_content
|
||||||
|
|
||||||
|
def test_tool_status_complete(self, css_content):
|
||||||
|
assert ".complete" in css_content
|
||||||
|
|
||||||
|
def test_tool_status_complete_color(self, css_content):
|
||||||
|
assert "#7aa2f7" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestRightPanelTabs:
|
||||||
|
def test_right_panel_tabs_class(self, css_content):
|
||||||
|
assert ".right-panel-tabs" in css_content
|
||||||
|
|
||||||
|
def test_right_panel_content_class(self, css_content):
|
||||||
|
assert ".right-panel-content" in css_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoginPage:
|
||||||
|
def test_login_page_class(self, css_content):
|
||||||
|
assert ".login-page" in css_content
|
||||||
|
|
||||||
|
def test_login_card_class(self, css_content):
|
||||||
|
assert ".login-card" in css_content
|
||||||
|
|
||||||
|
def test_login_card_width(self, css_content):
|
||||||
|
assert "360px" in css_content
|
||||||
|
|
||||||
|
def test_login_card_background(self, css_content):
|
||||||
|
# Already tested via sidebar bg, but login card also uses #16213e
|
||||||
|
assert "#16213e" in css_content
|
||||||
|
|
||||||
|
def test_login_card_border_radius(self, css_content):
|
||||||
|
assert "12px" in css_content
|
||||||
|
|
||||||
|
def test_login_error_class(self, css_content):
|
||||||
|
assert ".error" in css_content
|
||||||
|
|
||||||
|
def test_login_error_color(self, css_content):
|
||||||
|
assert "#f7607a" in css_content
|
||||||
Reference in New Issue
Block a user