feat(client): add Card header/footer, DescriptionList, and portal ContextMenu

- add Card header prop (primary/secondary variants) and footer prop (sticks to bottom)
- add Breadcrumbs to all 5 pages; remove duplicate h1 headings
- add pageToolbar layout for breadcrumbs + danger button aligned right
- add DescriptionList component replacing raw dl/dt/dd markup; supports truncate+tooltip
- rewrite ContextMenu to render dropdown via React portal to avoid clip/overflow issues
- update EnvironmentCard to use Card header (primary), footer (Timestamp), and truncated DescriptionList
- fix cog button contrast, square sizing, and right-edge alignment on colored header
- add border to Timestamp pill for dark-mode visibility
- add id badge pill styling to environment card header
This commit is contained in:
2026-04-09 14:29:16 +03:00
parent 35997f8bcb
commit 89016b01d2
13 changed files with 359 additions and 97 deletions
+25 -20
View File
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Trash2 } from 'lucide-react';
import { environments } from '../api';
import type { Environment } from '../api';
import { Breadcrumbs, Button, Card, Timestamp } from '../ui';
import { Breadcrumbs, Button, Card, DescriptionList, Timestamp } from '../ui';
import styles from './Page.module.css';
export function EnvironmentDetailPage() {
@@ -54,12 +54,19 @@ export function EnvironmentDetailPage() {
{env && (
<>
<div className={styles.detailMeta}>
<span className={styles.metaLabel}>{t('environments.field_id')}</span>
<span>{env.id}</span>
<span className={styles.metaLabel}>{t('environments.field_created')}</span>
<Timestamp value={env.createdAt} />
<span className={styles.metaLabel}>{t('environments.field_updated')}</span>
<Timestamp value={env.updatedAt} />
<DescriptionList
items={[
{ term: t('environments.field_id'), detail: env.id },
{
term: t('environments.field_created'),
detail: <Timestamp value={env.createdAt} />,
},
{
term: t('environments.field_updated'),
detail: <Timestamp value={env.updatedAt} />,
},
]}
/>
</div>
<h2 className={styles.sectionHeading}>{t('environments.section_urls')}</h2>
@@ -67,20 +74,18 @@ export function EnvironmentDetailPage() {
{Object.entries(env.urls).filter(([, v]) => v).length === 0 ? (
<p className={styles.muted}>{t('environments.no_urls')}</p>
) : (
<dl className={styles.envDetailUrls}>
{Object.entries(env.urls)
<DescriptionList
items={Object.entries(env.urls)
.filter(([, v]) => v)
.map(([key, value]) => (
<div key={key} className={styles.envDetailUrlRow}>
<dt className={styles.envCardUrlKey}>{key}</dt>
<dd className={styles.envDetailUrlVal}>
<a href={value} target="_blank" rel="noopener noreferrer">
{value}
</a>
</dd>
</div>
))}
</dl>
.map(([key, value]) => ({
term: key,
detail: (
<a href={value} target="_blank" rel="noopener noreferrer">
{value}
</a>
),
}))}
/>
)}
</Card>
</>