Skip to content

Commit 3c5d201

Browse files
authored
Merge pull request #203 from Mosas2000/feature/badge-timeline-components
feat: add badge/chip component system and timeline component
2 parents bf29cf0 + 23ecf90 commit 3c5d201

6 files changed

Lines changed: 476 additions & 42 deletions

File tree

frontend/src/components/Badge.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React from 'react';
2+
3+
export type BadgeVariant = 'default' | 'status' | 'outline' | 'pill';
4+
export type BadgeColor = 'default' | 'cyan' | 'purple' | 'success' | 'warning' | 'error' | 'info';
5+
export type BadgeSize = 'compact' | 'default';
6+
7+
export interface BadgeProps {
8+
children: React.ReactNode;
9+
variant?: BadgeVariant;
10+
color?: BadgeColor;
11+
size?: BadgeSize;
12+
icon?: React.ReactNode;
13+
className?: string;
14+
style?: React.CSSProperties;
15+
}
16+
17+
const colorStyles: Record<BadgeColor, { bg: string; text: string; border: string }> = {
18+
default: {
19+
bg: 'rgba(148, 163, 184, 0.1)',
20+
text: 'var(--text-secondary)',
21+
border: 'rgba(148, 163, 184, 0.3)',
22+
},
23+
cyan: {
24+
bg: 'var(--accent-cyan-dim)',
25+
text: 'var(--accent-cyan)',
26+
border: 'rgba(0, 240, 255, 0.3)',
27+
},
28+
purple: {
29+
bg: 'rgba(112, 0, 255, 0.1)',
30+
text: '#a855f7',
31+
border: 'rgba(112, 0, 255, 0.3)',
32+
},
33+
success: {
34+
bg: 'rgba(34, 197, 94, 0.1)',
35+
text: '#22c55e',
36+
border: 'rgba(34, 197, 94, 0.3)',
37+
},
38+
warning: {
39+
bg: 'rgba(245, 158, 11, 0.1)',
40+
text: '#f59e0b',
41+
border: 'rgba(245, 158, 11, 0.3)',
42+
},
43+
error: {
44+
bg: 'var(--bg-error)',
45+
text: 'var(--text-error)',
46+
border: 'var(--border-error)',
47+
},
48+
info: {
49+
bg: 'rgba(59, 130, 246, 0.1)',
50+
text: '#3b82f6',
51+
border: 'rgba(59, 130, 246, 0.3)',
52+
},
53+
};
54+
55+
const Badge: React.FC<BadgeProps> = ({
56+
children,
57+
variant = 'default',
58+
color = 'default',
59+
size = 'default',
60+
icon,
61+
className = '',
62+
style,
63+
}) => {
64+
const colors = colorStyles[color];
65+
const isCompact = size === 'compact';
66+
67+
const baseStyles: React.CSSProperties = {
68+
display: 'inline-flex',
69+
alignItems: 'center',
70+
gap: isCompact ? '4px' : '6px',
71+
padding: isCompact ? '2px 6px' : '4px 10px',
72+
fontSize: isCompact ? 'var(--text-xs)' : 'var(--text-sm)',
73+
fontWeight: 600,
74+
letterSpacing: '0.02em',
75+
lineHeight: 1.4,
76+
};
77+
78+
const variantStyles: Record<BadgeVariant, React.CSSProperties> = {
79+
default: {
80+
background: colors.bg,
81+
color: colors.text,
82+
border: `1px solid ${colors.border}`,
83+
borderRadius: '6px',
84+
},
85+
status: {
86+
background: colors.bg,
87+
color: colors.text,
88+
border: `1px solid ${colors.border}`,
89+
borderRadius: '6px',
90+
textTransform: 'uppercase',
91+
letterSpacing: '0.05em',
92+
fontSize: 'var(--text-xs)',
93+
},
94+
outline: {
95+
background: 'transparent',
96+
color: colors.text,
97+
border: `1px solid ${colors.border}`,
98+
borderRadius: '6px',
99+
},
100+
pill: {
101+
background: colors.bg,
102+
color: colors.text,
103+
border: `1px solid ${colors.border}`,
104+
borderRadius: '9999px',
105+
},
106+
};
107+
108+
return (
109+
<span
110+
className={className}
111+
style={{
112+
...baseStyles,
113+
...variantStyles[variant],
114+
...style,
115+
}}
116+
>
117+
{icon && <span style={{ display: 'flex', alignItems: 'center' }}>{icon}</span>}
118+
{children}
119+
</span>
120+
);
121+
};
122+
123+
export default Badge;

frontend/src/components/PageHeader.tsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { Link } from "react-router-dom";
33
import { ChevronRight } from "./icons";
4+
import Badge, { BadgeColor } from "./Badge";
45

56
export interface Breadcrumb {
67
label: string;
@@ -29,6 +30,15 @@ export interface PageHeaderProps {
2930
centered?: boolean;
3031
}
3132

33+
const variantToColor: Record<string, BadgeColor> = {
34+
default: "default",
35+
cyan: "cyan",
36+
purple: "purple",
37+
success: "success",
38+
warning: "warning",
39+
error: "error",
40+
};
41+
3242
const PageHeader: React.FC<PageHeaderProps> = ({
3343
title,
3444
description,
@@ -150,34 +160,13 @@ const PageHeader: React.FC<PageHeaderProps> = ({
150160
aria-live="polite"
151161
>
152162
{statusChips.map((chip, index) => (
153-
<span
163+
<Badge
154164
key={index}
155-
className={`tag ${chip.variant || "default"}`}
156-
style={{
157-
...(chip.variant === "success" && {
158-
background: "rgba(34, 197, 94, 0.1)",
159-
color: "#22c55e",
160-
border: "1px solid rgba(34, 197, 94, 0.3)",
161-
}),
162-
...(chip.variant === "warning" && {
163-
background: "rgba(245, 158, 11, 0.1)",
164-
color: "#f59e0b",
165-
border: "1px solid rgba(245, 158, 11, 0.3)",
166-
}),
167-
...(chip.variant === "error" && {
168-
background: "var(--bg-error)",
169-
color: "var(--text-error)",
170-
border: "1px solid var(--border-error)",
171-
}),
172-
...(chip.variant === "purple" && {
173-
background: "rgba(112, 0, 255, 0.1)",
174-
color: "#a855f7",
175-
border: "1px solid rgba(112, 0, 255, 0.3)",
176-
}),
177-
}}
165+
variant="pill"
166+
color={variantToColor[chip.variant || "default"]}
178167
>
179168
{chip.label}
180-
</span>
169+
</Badge>
181170
))}
182171
</div>
183172
)}

0 commit comments

Comments
 (0)