feat(ui): add ScopeTrail component for scope-based breadcrumbs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
40
ui/src/components/ScopeTrail.module.css
Normal file
40
ui/src/components/ScopeTrail.module.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.trail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-muted);
|
||||
min-height: 1.5rem;
|
||||
}
|
||||
|
||||
.segment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: var(--amber);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 0.375rem;
|
||||
color: var(--text-muted);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.current {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
38
ui/src/components/ScopeTrail.tsx
Normal file
38
ui/src/components/ScopeTrail.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Scope } from '../hooks/useScope';
|
||||
import styles from './ScopeTrail.module.css';
|
||||
|
||||
interface ScopeTrailProps {
|
||||
scope: Scope;
|
||||
onNavigate: (path: string) => void;
|
||||
}
|
||||
|
||||
export function ScopeTrail({ scope, onNavigate }: ScopeTrailProps) {
|
||||
const segments: { label: string; path: string }[] = [
|
||||
{ label: 'All Applications', path: `/${scope.tab}` },
|
||||
];
|
||||
|
||||
if (scope.appId) {
|
||||
segments.push({ label: scope.appId, path: `/${scope.tab}/${scope.appId}` });
|
||||
}
|
||||
|
||||
if (scope.routeId) {
|
||||
segments.push({ label: scope.routeId, path: `/${scope.tab}/${scope.appId}/${scope.routeId}` });
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={styles.trail}>
|
||||
{segments.map((seg, i) => (
|
||||
<span key={seg.path} className={styles.segment}>
|
||||
{i > 0 && <span className={styles.separator}>></span>}
|
||||
{i < segments.length - 1 ? (
|
||||
<button className={styles.link} onClick={() => onNavigate(seg.path)}>
|
||||
{seg.label}
|
||||
</button>
|
||||
) : (
|
||||
<span className={styles.current}>{seg.label}</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user