From 1a71656508529dc2c80a63fb6b384027928b1a87 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 25 Feb 2026 20:33:02 -0800 Subject: [PATCH] app tiles --- src/client/components/DashboardLanding.tsx | 2 +- src/client/components/Urls.tsx | 50 +++++----- src/client/styles/dashboard.ts | 101 ++++++++++++--------- src/client/styles/index.ts | 11 ++- 4 files changed, 93 insertions(+), 71 deletions(-) diff --git a/src/client/components/DashboardLanding.tsx b/src/client/components/DashboardLanding.tsx index 3a1b848..1c5e5cc 100644 --- a/src/client/components/DashboardLanding.tsx +++ b/src/client/components/DashboardLanding.tsx @@ -63,7 +63,7 @@ export function DashboardLanding({ render }: { render: () => void }) { - + diff --git a/src/client/components/Urls.tsx b/src/client/components/Urls.tsx index ffe9aca..da171d8 100644 --- a/src/client/components/Urls.tsx +++ b/src/client/components/Urls.tsx @@ -1,16 +1,16 @@ import { buildAppUrl } from '../../shared/urls' -import { apps } from '../state' +import { apps, setSelectedApp } from '../state' import { EmptyState, - StatusDot, - UrlLeft, - UrlLink, - UrlList, - UrlPort, - UrlRow, + Tile, + TileGrid, + TileIcon, + TileName, + TilePort, + TileStatus, } from '../styles' -export function Urls() { +export function Urls({ render }: { render: () => void }) { const nonTools = apps.filter(a => !a.tool) if (nonTools.length === 0) { @@ -18,26 +18,32 @@ export function Urls() { } return ( - + {nonTools.map(app => { const url = buildAppUrl(app.name, location.origin) const running = app.state === 'running' + const openAppPage = (e: MouseEvent) => { + e.preventDefault() + e.stopPropagation() + setSelectedApp(app.name) + render() + } + return ( - - - - {app.icon} - {running ? ( - {url} - ) : ( - {app.name} - )} - - {app.port ? :{app.port} : null} - + + + {app.icon} + {app.name} + {app.port ? `:${app.port}` : '\u2014'} + ) })} - + ) } diff --git a/src/client/styles/dashboard.ts b/src/client/styles/dashboard.ts index 9e3d9b7..d6e7e26 100644 --- a/src/client/styles/dashboard.ts +++ b/src/client/styles/dashboard.ts @@ -203,58 +203,73 @@ export const LogStatus = define('LogStatus', { }, }) -// URL List -export const UrlLeft = define('UrlLeft', { - display: 'flex', - alignItems: 'center', - gap: 8, - minWidth: 0, -}) - -export const UrlLink = define('UrlLink', { - base: 'a', - color: theme('colors-link'), - textDecoration: 'none', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - selectors: { - '&:hover': { textDecoration: 'underline' }, - }, -}) - -export const UrlList = define('UrlList', { +// App Tiles Grid +export const TileGrid = define('TileGrid', { width: '100%', - minWidth: 400, - maxWidth: 800, + maxWidth: 900, + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', + gap: 20, +}) + +export const Tile = define('Tile', { + base: 'a', + position: 'relative', display: 'flex', flexDirection: 'column', + alignItems: 'center', + gap: 8, + padding: '28px 20px 24px', background: theme('colors-bgElement'), border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), - overflow: 'hidden', -}) - -export const UrlPort = define('UrlPort', { - fontFamily: theme('fonts-mono'), - fontSize: 12, - color: theme('colors-textFaint'), - flexShrink: 0, -}) - -export const UrlRow = define('UrlRow', { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '10px 16px', - fontFamily: theme('fonts-mono'), - fontSize: 13, + textDecoration: 'none', + cursor: 'pointer', selectors: { '&:hover': { background: theme('colors-bgHover'), - }, - '&:not(:last-child)': { - borderBottom: `1px solid ${theme('colors-border')}`, + borderColor: theme('colors-textFaint'), + }, + }, +}) + +export const TileIcon = define('TileIcon', { + fontSize: 48, + lineHeight: 1, + userSelect: 'none', +}) + +export const TileName = define('TileName', { + fontSize: 15, + fontWeight: 600, + color: theme('colors-text'), + textAlign: 'center', +}) + +export const TilePort = define('TilePort', { + fontFamily: theme('fonts-mono'), + fontSize: 13, + color: theme('colors-textFaint'), +}) + +export const TileStatus = define('TileStatus', { + position: 'absolute', + top: 8, + right: 8, + width: 2, + height: 2, + borderRadius: '50%', + cursor: 'pointer', + padding: 4, + backgroundClip: 'content-box', + variants: { + state: { + error: { background: theme('colors-statusInvalid') }, + invalid: { background: theme('colors-statusInvalid') }, + stopped: { background: theme('colors-statusStopped') }, + starting: { background: theme('colors-statusStarting') }, + running: { background: theme('colors-statusRunning') }, + stopping: { background: theme('colors-statusStarting') }, }, }, }) diff --git a/src/client/styles/index.ts b/src/client/styles/index.ts index 3e33da7..f9223fa 100644 --- a/src/client/styles/index.ts +++ b/src/client/styles/index.ts @@ -15,11 +15,12 @@ export { LogStatus, LogText, LogTimestamp, - UrlLeft, - UrlLink, - UrlList, - UrlPort, - UrlRow, + Tile, + TileGrid, + TileIcon, + TileName, + TilePort, + TileStatus, VitalCard, VitalLabel, VitalsSection,