Files
TUIkit/docs/app/components/LanguageBar.tsx
T
phranck cfa8b31631 Refactor: Standardize UI card styling and remove em-dashes
- Unified card headers: gap-3, Icon size 20, text-accent color
- PlansCard: collapsible sections, animated expand/collapse
- update-plans-data.ts: export all plans (no limit)
- Replaced all em-dashes with colons or full sentences
- Content text standardized to text-lg across all cards
- FeatureCard, ArchHighlight descriptions adjusted
2026-02-07 00:24:19 +01:00

103 lines
3.4 KiB
TypeScript

"use client";
import type { LanguageBreakdown } from "../hooks/useGitHubStats";
import Icon from "./Icon";
interface LanguageBarProps {
/** Language byte counts from the GitHub API. */
languages: LanguageBreakdown;
/** Whether data is still loading. */
loading?: boolean;
}
/**
* Color palette for up to 6 languages, using theme-consistent colors.
*
* Swift gets the accent color, others get progressively muted tones.
*/
const LANG_COLORS = [
"bg-accent",
"bg-accent-secondary",
"bg-muted",
"bg-border",
"bg-foreground/30",
"bg-foreground/15",
];
/**
* A horizontal stacked bar showing the language breakdown of the repository.
*
* Each segment is proportional to the byte count. A legend below the bar lists
* each language with its percentage.
*/
export default function LanguageBar({ languages, loading = false }: LanguageBarProps) {
if (loading) {
return (
<div className="rounded-xl border border-border bg-frosted-glass p-6 backdrop-blur-xl">
<h3 className="mb-4 flex items-center gap-3 text-xl font-semibold text-foreground">
<Icon name="swift" size={20} className="text-accent" />
Languages
</h3>
<div className="h-4 w-full rounded-full bg-accent/10 animate-skeleton" />
<div className="mt-3 flex gap-4">
<div className="h-4 w-16 rounded-md bg-accent/10 animate-skeleton" />
<div className="h-4 w-12 rounded-md bg-accent/10 animate-skeleton" />
</div>
</div>
);
}
const entries = Object.entries(languages).sort((entryA, entryB) => entryB[1] - entryA[1]);
const totalBytes = entries.reduce((sum, [, bytes]) => sum + bytes, 0);
if (totalBytes === 0) {
return (
<div className="rounded-xl border border-border bg-frosted-glass p-6 backdrop-blur-xl">
<h3 className="mb-4 flex items-center gap-3 text-xl font-semibold text-foreground">
<Icon name="swift" size={20} className="text-accent" />
Languages
</h3>
<p className="text-lg text-muted">No language data available.</p>
</div>
);
}
return (
<div className="rounded-xl border border-border bg-frosted-glass p-6 backdrop-blur-xl">
<h3 className="mb-4 flex items-center gap-3 text-xl font-semibold text-foreground">
<Icon name="swift" size={20} className="text-accent" />
Languages
</h3>
{/* Stacked bar */}
<div className="flex h-4 w-full overflow-hidden rounded-full">
{entries.map(([lang, bytes], idx) => {
const pct = (bytes / totalBytes) * 100;
return (
<div
key={lang}
className={`${LANG_COLORS[idx % LANG_COLORS.length]} transition-all duration-300`}
style={{ width: `${Math.max(pct, 0.5)}%` }}
title={`${lang}: ${pct.toFixed(1)}%`}
/>
);
})}
</div>
{/* Legend */}
<div className="mt-3 flex flex-wrap gap-x-5 gap-y-1">
{entries.map(([lang, bytes], idx) => {
const pct = (bytes / totalBytes) * 100;
return (
<div key={lang} className="flex items-center gap-1.5 text-base text-muted">
<span className={`inline-block h-3 w-3 rounded-sm ${LANG_COLORS[idx % LANG_COLORS.length]}`} />
<span>{lang}</span>
<span className="text-foreground/60">{pct < 0.1 ? "<0.1" : pct.toFixed(1)}%</span>
</div>
);
})}
</div>
</div>
);
}