Fix light theme support and expand/collapse UX

- Add light/dark theme support to DeploymentsTable and DeploymentLogs
- Add stopPropagation to logs container and buttons to prevent accidental collapse
- Fix absolute positioning by adding relative parent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-02 02:08:40 +00:00
parent 84d5633b36
commit 887ebf0ab8
2 changed files with 50 additions and 45 deletions

View File

@@ -78,13 +78,13 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme
};
return (
<div className="border-t border-stone-800 bg-stone-950">
<div className="relative border-t border-slate-200 dark:border-stone-800 bg-slate-50 dark:bg-stone-950">
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 border-b border-stone-800">
<span className="text-sm font-medium text-stone-300">Build Logs</span>
<div className="flex items-center justify-between px-4 py-2 border-b border-slate-200 dark:border-stone-800">
<span className="text-sm font-medium text-slate-700 dark:text-stone-300">Build Logs</span>
<div className="flex items-center gap-2">
{isPolling && (
<span className="flex items-center gap-1 text-xs text-stone-500">
<span className="flex items-center gap-1 text-xs text-slate-500 dark:text-stone-500">
<span className="animate-spin">
<Icon name="refresh-cw" size={12} />
</span>
@@ -92,8 +92,11 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme
</span>
)}
<button
onClick={copyToClipboard}
className="flex items-center gap-1 px-2 py-1 text-xs text-stone-400 hover:text-stone-200 hover:bg-stone-800 rounded transition-colors"
onClick={(e) => {
e.stopPropagation();
copyToClipboard();
}}
className="flex items-center gap-1 px-2 py-1 text-xs text-slate-500 dark:text-stone-400 hover:text-slate-700 dark:hover:text-stone-200 hover:bg-slate-200 dark:hover:bg-stone-800 rounded transition-colors"
>
<Icon name={copied ? 'check' : 'copy'} size={14} />
{copied ? 'Copied' : 'Copy'}
@@ -105,10 +108,11 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme
<div
ref={containerRef}
onScroll={handleScroll}
onClick={(e) => e.stopPropagation()}
className="max-h-80 overflow-y-auto font-mono text-xs p-4 space-y-0.5"
>
{logs.length === 0 ? (
<div className="text-stone-500 text-center py-8">
<div className="text-slate-500 dark:text-stone-500 text-center py-8">
{isPolling ? 'Waiting for logs...' : 'No logs available'}
</div>
) : (
@@ -116,11 +120,11 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme
<div
key={index}
className={`flex ${
log.type === 'stderr' ? 'text-red-400' : 'text-stone-300'
log.type === 'stderr' ? 'text-red-600 dark:text-red-400' : 'text-slate-700 dark:text-stone-300'
}`}
>
{log.timestamp && (
<span className="text-stone-600 mr-3 select-none shrink-0">
<span className="text-slate-400 dark:text-stone-600 mr-3 select-none shrink-0">
{log.timestamp}
</span>
)}
@@ -134,11 +138,12 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme
{/* Auto-scroll indicator */}
{!autoScroll && logs.length > 0 && (
<button
onClick={() => {
onClick={(e) => {
e.stopPropagation();
setAutoScroll(true);
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}}
className="absolute bottom-4 right-4 px-3 py-1.5 bg-stone-800 text-stone-300 text-xs rounded-full shadow-lg hover:bg-stone-700 transition-colors"
className="absolute bottom-4 right-4 px-3 py-1.5 bg-slate-200 dark:bg-stone-800 text-slate-700 dark:text-stone-300 text-xs rounded-full shadow-lg hover:bg-slate-300 dark:hover:bg-stone-700 transition-colors"
>
Scroll to bottom
</button>

View File

@@ -46,7 +46,7 @@ const StatusDot = ({ status }: { status: DeploymentStatus }) => (
const StatusBadge = ({ status }: { status: DeploymentStatus }) => (
<span className="flex items-center gap-1.5">
<StatusDot status={status} />
<span className="text-stone-300">{STATUS_LABELS[status]}</span>
<span className="text-slate-700 dark:text-stone-300">{STATUS_LABELS[status]}</span>
</span>
);
@@ -81,7 +81,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
cell: ({ row, getValue }) => (
<button
onClick={() => row.toggleExpanded()}
className="flex items-center gap-2 text-stone-300 hover:text-white font-mono text-sm"
className="flex items-center gap-2 text-slate-700 dark:text-stone-300 hover:text-slate-900 dark:hover:text-white font-mono text-sm"
>
<Icon
name="chevron-right"
@@ -96,11 +96,11 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
header: 'Environment',
cell: ({ getValue }) => (
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 text-xs rounded bg-stone-800 text-stone-300">
<span className="px-2 py-0.5 text-xs rounded bg-slate-100 dark:bg-stone-800 text-slate-700 dark:text-stone-300">
Production
</span>
{getValue() && (
<span className="flex items-center gap-1 text-xs text-cyan-400">
<span className="flex items-center gap-1 text-xs text-cyan-600 dark:text-cyan-400">
<Icon name="check" size={12} />
Current
</span>
@@ -113,7 +113,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
cell: ({ getValue }) => {
const duration = getValue();
return (
<span className="text-stone-400 text-sm">
<span className="text-slate-500 dark:text-stone-400 text-sm">
{duration ? formatDuration(duration) : '-'}
</span>
);
@@ -130,11 +130,11 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
href={`http://192.168.1.3:8000/project/a8484ggc88c40w4g4k004ow0/production/application/${row.original.application_uuid}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-stone-300 hover:text-white"
className="flex items-center gap-2 text-slate-700 dark:text-stone-300 hover:text-slate-900 dark:hover:text-white"
onClick={(e) => e.stopPropagation()}
>
<span className="w-5 h-5 flex items-center justify-center rounded bg-stone-800">
<Icon name="box" size={12} className="text-stone-400" />
<span className="w-5 h-5 flex items-center justify-center rounded bg-slate-100 dark:bg-stone-800">
<Icon name="box" size={12} className="text-slate-500 dark:text-stone-400" />
</span>
{getValue()}
</a>
@@ -144,16 +144,16 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
header: 'Source',
cell: ({ getValue, row }) => (
<div className="flex items-center gap-2 text-sm">
<span className="text-stone-500">
<span className="text-slate-400 dark:text-stone-500">
<Icon name="git-branch" size={14} />
</span>
<span className="text-stone-300">{getValue() || 'main'}</span>
<span className="text-slate-700 dark:text-stone-300">{getValue() || 'main'}</span>
{row.original.git_commit_sha && (
<>
<span className="text-stone-600 font-mono">
<span className="text-slate-400 dark:text-stone-600 font-mono">
{row.original.git_commit_sha.substring(0, 7)}
</span>
<span className="text-stone-500 truncate max-w-[200px]">
<span className="text-slate-500 dark:text-stone-500 truncate max-w-[200px]">
{truncateCommitMessage(row.original.commit_message || '')}
</span>
</>
@@ -164,9 +164,9 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
columnHelper.accessor('created_at', {
header: 'Created',
cell: ({ getValue, row }) => (
<div className="flex items-center gap-2 text-sm text-stone-400">
<div className="flex items-center gap-2 text-sm text-slate-500 dark:text-stone-400">
<span>{formatRelativeTime(getValue())}</span>
<span className="text-stone-600">by</span>
<span className="text-slate-400 dark:text-stone-600">by</span>
<span>{row.original.is_webhook ? 'webhook' : 'API'}</span>
</div>
),
@@ -179,7 +179,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
href={`http://192.168.1.3:8000/project/a8484ggc88c40w4g4k004ow0/production/application/${row.original.application_uuid}/deployment/${row.original.deployment_uuid}`}
target="_blank"
rel="noopener noreferrer"
className="p-1 text-stone-500 hover:text-stone-300 hover:bg-stone-800 rounded transition-colors"
className="p-1 text-slate-400 dark:text-stone-500 hover:text-slate-600 dark:hover:text-stone-300 hover:bg-slate-100 dark:hover:bg-stone-800 rounded transition-colors"
onClick={(e) => e.stopPropagation()}
>
<Icon name="external-link" size={16} />
@@ -235,7 +235,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
<select
value={appFilter}
onChange={(e) => setAppFilter(e.target.value)}
className="px-3 py-1.5 text-sm bg-stone-900 border border-stone-700 rounded-lg text-stone-300 focus:outline-none focus:border-stone-500"
className="px-3 py-1.5 text-sm bg-white dark:bg-stone-900 border border-slate-200 dark:border-stone-700 rounded-lg text-slate-700 dark:text-stone-300 focus:outline-none focus:border-slate-400 dark:focus:border-stone-500"
>
<option value="all">All Applications</option>
{applicationNames.map((name) => (
@@ -249,7 +249,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as DeploymentStatus | 'all')}
className="px-3 py-1.5 text-sm bg-stone-900 border border-stone-700 rounded-lg text-stone-300 focus:outline-none focus:border-stone-500"
className="px-3 py-1.5 text-sm bg-white dark:bg-stone-900 border border-slate-200 dark:border-stone-700 rounded-lg text-slate-700 dark:text-stone-300 focus:outline-none focus:border-slate-400 dark:focus:border-stone-500"
>
<option value="all">All Statuses</option>
<option value="finished">Ready</option>
@@ -265,7 +265,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
setStatusFilter('all');
setAppFilter('all');
}}
className="text-xs text-stone-500 hover:text-stone-300"
className="text-xs text-slate-500 dark:text-stone-500 hover:text-slate-700 dark:hover:text-stone-300"
>
Clear filters
</button>
@@ -273,13 +273,13 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-stone-500">
<span className="text-sm text-slate-500 dark:text-stone-500">
{filteredDeployments.length} deployments
</span>
<button
onClick={onRefresh}
disabled={isLoading}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-stone-300 hover:text-white bg-stone-800 hover:bg-stone-700 rounded-lg transition-colors disabled:opacity-50"
className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-slate-700 dark:text-stone-300 hover:text-slate-900 dark:hover:text-white bg-slate-100 dark:bg-stone-800 hover:bg-slate-200 dark:hover:bg-stone-700 rounded-lg transition-colors disabled:opacity-50"
>
<Icon
name="refresh-cw"
@@ -292,21 +292,21 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
</div>
{/* Table */}
<div className="bg-stone-900 rounded-xl border border-stone-700/50 overflow-hidden">
<div className="bg-white dark:bg-stone-900 rounded-xl border border-slate-200 dark:border-stone-700/50 overflow-hidden shadow-sm">
<table className="w-full">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className="border-b border-stone-800">
<tr key={headerGroup.id} className="border-b border-slate-200 dark:border-stone-800">
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="px-4 py-3 text-left text-xs font-medium text-stone-500 uppercase tracking-wider"
className="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-stone-500 uppercase tracking-wider"
>
{header.isPlaceholder ? null : (
<button
className={`flex items-center gap-1 ${
header.column.getCanSort()
? 'cursor-pointer hover:text-stone-300'
? 'cursor-pointer hover:text-slate-700 dark:hover:text-stone-300'
: ''
}`}
onClick={header.column.getToggleSortingHandler()}
@@ -329,7 +329,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
{isLoading && deployments.length === 0 ? (
<tr>
<td colSpan={columns.length} className="px-4 py-12 text-center">
<div className="flex items-center justify-center gap-2 text-stone-500">
<div className="flex items-center justify-center gap-2 text-slate-500 dark:text-stone-500">
<Icon name="refresh-cw" size={16} className="animate-spin" />
Loading deployments...
</div>
@@ -337,7 +337,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
</tr>
) : filteredDeployments.length === 0 ? (
<tr>
<td colSpan={columns.length} className="px-4 py-12 text-center text-stone-500">
<td colSpan={columns.length} className="px-4 py-12 text-center text-slate-500 dark:text-stone-500">
No deployments found
</td>
</tr>
@@ -345,8 +345,8 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
table.getRowModel().rows.map((row) => (
<Fragment key={row.id}>
<tr
className={`border-b border-stone-800/50 hover:bg-stone-800/30 cursor-pointer transition-colors ${
row.getIsExpanded() ? 'bg-stone-800/50' : ''
className={`border-b border-slate-100 dark:border-stone-800/50 hover:bg-slate-50 dark:hover:bg-stone-800/30 cursor-pointer transition-colors ${
row.getIsExpanded() ? 'bg-slate-50 dark:bg-stone-800/50' : ''
}`}
onClick={() => row.toggleExpanded()}
>
@@ -371,13 +371,13 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
{/* Pagination */}
{filteredDeployments.length > 10 && (
<div className="flex items-center justify-between px-4 py-3 border-t border-stone-800">
<div className="flex items-center justify-between px-4 py-3 border-t border-slate-200 dark:border-stone-800">
<div className="flex items-center gap-2">
<span className="text-sm text-stone-500">Rows per page:</span>
<span className="text-sm text-slate-500 dark:text-stone-500">Rows per page:</span>
<select
value={table.getState().pagination.pageSize}
onChange={(e) => table.setPageSize(Number(e.target.value))}
className="px-2 py-1 text-sm bg-stone-800 border border-stone-700 rounded text-stone-300 focus:outline-none"
className="px-2 py-1 text-sm bg-slate-100 dark:bg-stone-800 border border-slate-200 dark:border-stone-700 rounded text-slate-700 dark:text-stone-300 focus:outline-none"
>
{[10, 25, 50].map((size) => (
<option key={size} value={size}>
@@ -388,7 +388,7 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
</div>
<div className="flex items-center gap-4">
<span className="text-sm text-stone-500">
<span className="text-sm text-slate-500 dark:text-stone-500">
Page {table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</span>
@@ -396,14 +396,14 @@ export function DeploymentsTable({ deployments, isLoading, onRefresh }: Deployme
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="p-1 text-stone-400 hover:text-stone-200 disabled:opacity-30 disabled:cursor-not-allowed"
className="p-1 text-slate-400 dark:text-stone-400 hover:text-slate-600 dark:hover:text-stone-200 disabled:opacity-30 disabled:cursor-not-allowed"
>
<Icon name="chevron-left" size={20} />
</button>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="p-1 text-stone-400 hover:text-stone-200 disabled:opacity-30 disabled:cursor-not-allowed"
className="p-1 text-slate-400 dark:text-stone-400 hover:text-slate-600 dark:hover:text-stone-200 disabled:opacity-30 disabled:cursor-not-allowed"
>
<Icon name="chevron-right" size={20} />
</button>