feat(ui): add breadcrumb icons, fix alignment, and remove duplicate form headers
- add section icons to all breadcrumbs across every page - fix breadcrumb nav display:flex so icons align vertically with buttons - wrap all page breadcrumbs in pageToolbar div for consistent layout - add Breadcrumbs to AllRunsPage which previously used a plain h1 - fix envCardHeader flex:1 so settings button hugs the right edge - remove card header from environment create/edit forms (duplicated breadcrumb)
This commit is contained in:
@@ -45,6 +45,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Clickable / hover ────────────────────────────────────── */
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
border-radius: 99px;
|
||||
padding: 2px 6px;
|
||||
margin: -2px -6px;
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.clickable:hover {
|
||||
background: color-mix(in srgb, var(--color-text) 8%, transparent);
|
||||
}
|
||||
|
||||
.clickable:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Active state ─────────────────────────────────────────── */
|
||||
|
||||
.active .dot::before {
|
||||
@@ -67,3 +86,18 @@
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* ── Error state ──────────────────────────────────────────── */
|
||||
|
||||
.error .dot::before {
|
||||
background: var(--color-error-fg);
|
||||
}
|
||||
|
||||
.error .ring {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.error .label {
|
||||
color: var(--color-error-fg);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -4,24 +4,39 @@ export interface AutoRefreshIndicatorProps {
|
||||
active: boolean;
|
||||
pulseKey?: number;
|
||||
label?: string;
|
||||
error?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function AutoRefreshIndicator({
|
||||
active,
|
||||
pulseKey,
|
||||
label = 'Live',
|
||||
error = false,
|
||||
onClick,
|
||||
}: AutoRefreshIndicatorProps) {
|
||||
const stateClass = error ? styles.error : active ? styles.active : styles.inactive;
|
||||
const titleText = error
|
||||
? 'Refresh failed — click to retry'
|
||||
: active
|
||||
? 'Auto-refreshing'
|
||||
: 'Auto-refresh stopped';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[styles.root, active ? styles.active : styles.inactive].join(' ')}
|
||||
title={active ? 'Auto-refreshing' : 'Auto-refresh stopped'}
|
||||
aria-label={active ? 'Auto-refreshing' : 'Auto-refresh stopped'}
|
||||
className={[styles.root, stateClass, onClick ? styles.clickable : ''].join(' ')}
|
||||
title={titleText}
|
||||
aria-label={titleText}
|
||||
aria-live="polite"
|
||||
role={onClick ? 'button' : undefined}
|
||||
tabIndex={onClick ? 0 : undefined}
|
||||
onClick={onClick}
|
||||
onKeyDown={onClick ? (e) => (e.key === 'Enter' || e.key === ' ') && onClick() : undefined}
|
||||
>
|
||||
<span className={styles.dot}>
|
||||
{pulseKey !== undefined && pulseKey > 0 && <span key={pulseKey} className={styles.ring} />}
|
||||
</span>
|
||||
<span className={styles.label}>{label}</span>
|
||||
<span className={styles.label}>{error ? 'Error' : label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
border-radius: var(--radius-full);
|
||||
white-space: nowrap;
|
||||
line-height: 1.6;
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
.dot {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
@@ -19,6 +21,9 @@
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
@@ -42,6 +47,9 @@
|
||||
}
|
||||
|
||||
.current {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
|
||||
@@ -3,6 +3,7 @@ import styles from './Breadcrumbs.module.css';
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
href?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
@@ -35,15 +36,15 @@ export function Breadcrumbs({ items, separator, className }: BreadcrumbsProps) {
|
||||
<li key={i} className={styles.item}>
|
||||
{isLast ? (
|
||||
<span className={styles.current} aria-current="page">
|
||||
{item.label}
|
||||
{item.icon}{item.label}
|
||||
</span>
|
||||
) : item.href ? (
|
||||
<a href={item.href} className={styles.link} onClick={item.onClick}>
|
||||
{item.label}
|
||||
{item.icon}{item.label}
|
||||
</a>
|
||||
) : (
|
||||
<button type="button" className={styles.link} onClick={item.onClick}>
|
||||
{item.label}
|
||||
{item.icon}{item.label}
|
||||
</button>
|
||||
)}
|
||||
{!isLast && <span className={styles.separator}>{sep}</span>}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
box-shadow: var(--shadow-xs);
|
||||
transition:
|
||||
background var(--transition),
|
||||
border-color var(--transition),
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-6);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
@@ -45,7 +45,26 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* ── Layout modifiers ───────────────────────────────────────── */
|
||||
/* ── Grid layout ───────────────────────────────────────────── */
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: var(--space-4) var(--space-6);
|
||||
}
|
||||
|
||||
.grid .row {
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.grid .term {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.grid .detail {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.inline .row {
|
||||
flex-direction: row;
|
||||
|
||||
@@ -16,8 +16,8 @@ export interface DescriptionListItem {
|
||||
|
||||
export interface DescriptionListProps {
|
||||
items: DescriptionListItem[];
|
||||
/** 'compact' stacks term above detail; 'inline' places them side by side; 'comfortable' adds generous spacing with dividers (default) */
|
||||
layout?: 'inline' | 'compact' | 'comfortable';
|
||||
/** 'compact' stacks term above detail; 'inline' places them side by side; 'comfortable' adds generous spacing with dividers (default); 'grid' flows items into a responsive multi-column grid */
|
||||
layout?: 'inline' | 'compact' | 'comfortable' | 'grid';
|
||||
/** Truncate detail values with ellipsis; full value shown in a tooltip on hover */
|
||||
truncate?: boolean;
|
||||
className?: string;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
box-shadow: var(--shadow-xs);
|
||||
transition: border-color var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
--border-width: 1.5px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0px 1px rgba(0, 0, 0, 0.04);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.10), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* Transitions */
|
||||
|
||||
Reference in New Issue
Block a user