Resources
14Install
npx skillscat add gilbatini/abientot-erp Install via the SkillsCat registry.
SKILL.md
VoyageDoc Design System — SKILL.md
Reference for consistent UI generation across all VoyageDoc modules.
Brand Tokens
--color-primary: #2BBFB3;
--color-primary-dark: #1a9990;
--color-primary-light: #e6f9f8;
--color-bg: #f1f3f4;
--color-surface: #ffffff;
--color-surface-var: #f8f9fa;
--color-border: #dadce0;
--color-text: #202124;
--color-text-muted: #5f6368;
--color-error: #d93025;
--color-success: #188038;
--color-warning: #f29900;Typography
| Role | Font | Weights | Usage |
|---|---|---|---|
| Display | Space Grotesk | 300–700 | Page titles, card headers |
| Body/UI | DM Sans | 300–600 | Labels, inputs, body text |
Google Fonts URL:
https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swapTailwind config:
fontFamily: {
display: ["Space Grotesk", "sans-serif"],
body: ["DM Sans", "sans-serif"],
}Tailwind Color Tokens
// tailwind.config.ts
colors: {
primary: {
DEFAULT: "#2BBFB3",
dark: "#1a9990",
light: "#e6f9f8",
}
}Layout
App Shell
- Sidebar: 240px fixed left, white bg, border-r border-gray-200
- Content area: ml-60, p-6, bg-gray-50, min-h-screen
- Card: bg-white rounded-2xl shadow-sm border border-gray-100 p-6
- Page header: flex justify-between, mb-6, title in font-display text-2xl font-semibold
Sidebar Nav Items
// Active
<a className="flex items-center gap-3 px-3 py-2 rounded-xl bg-primary-light text-primary font-medium text-sm">
// Inactive
<a className="flex items-center gap-3 px-3 py-2 rounded-xl text-gray-600 hover:bg-gray-100 text-sm transition-colors">Nav Structure
- Dashboard /dashboard 🏠
- Travellers /travellers 👥
- Invoices /invoices 🧾
- Receipts /receipts 💳
- Proformas /proformas 📋
- Quotations /quotations 💬
- Settings /settings ⚙️ (admin only)
Status Badge Component
const STATUS_STYLES: Record<string, string> = {
draft: "bg-gray-100 text-gray-600",
sent: "bg-teal-50 text-teal-700",
paid: "bg-green-50 text-green-700",
cancelled: "bg-red-50 text-red-600",
approved: "bg-green-50 text-green-700",
rejected: "bg-red-50 text-red-600",
expired: "bg-amber-50 text-amber-700",
};
export function Badge({ status }: { status: string }) {
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${STATUS_STYLES[status] ?? "bg-gray-100 text-gray-600"}`}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</span>
);
}Avatar Component
const AVATAR_COLORS = ["#1a73e8","#e37400","#188038","#a142f4","#d93025","#007b83","#c5221f","#0d652d"];
function avatarColor(name: string): string {
let h = 0;
for (const c of name) h = (h * 31 + c.charCodeAt(0)) % AVATAR_COLORS.length;
return AVATAR_COLORS[h];
}
export function Avatar({ name, size = 36 }: { name: string; size?: number }) {
const initials = name.split(" ").map(n => n[0]).join("").slice(0, 2).toUpperCase();
return (
<div
className="rounded-full flex items-center justify-center text-white font-semibold text-sm flex-shrink-0"
style={{ width: size, height: size, backgroundColor: avatarColor(name) }}
>
{initials}
</div>
);
}Button Variants
// Primary
<button className="inline-flex items-center gap-2 px-4 py-2 bg-primary text-white text-sm font-medium rounded-lg hover:bg-primary-dark transition-colors">
// Secondary
<button className="inline-flex items-center gap-2 px-4 py-2 bg-white text-gray-700 text-sm font-medium rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors">
// Danger (admin delete)
<button className="inline-flex items-center gap-2 px-4 py-2 bg-red-50 text-red-600 text-sm font-medium rounded-lg hover:bg-red-100 transition-colors">
// Ghost (table row actions)
<button className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">Table Pattern
<div className="bg-white rounded-2xl border border-gray-100 overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-gray-50 border-b border-gray-100">
<tr>
<th className="text-left px-4 py-3 font-medium text-gray-500 text-xs uppercase tracking-wide">
Column
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
<tr className="hover:bg-gray-50 transition-colors">
<td className="px-4 py-3 text-gray-900">Value</td>
</tr>
</tbody>
</table>
</div>Form Input
<div className="space-y-1.5">
<label className="block text-sm font-medium text-gray-700">{label}</label>
<input
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg bg-white text-gray-900
placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/30
focus:border-primary transition-colors"
/>
</div>Dashboard Stats Card
<div className="bg-white rounded-2xl border border-gray-100 p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-500">{label}</span>
<div className="w-8 h-8 rounded-lg bg-primary-light flex items-center justify-center">
{icon}
</div>
</div>
<p className="font-display text-2xl font-semibold text-gray-900">{value}</p>
<p className="text-xs text-gray-400 mt-1">{subtext}</p>
</div>PDF Document Styles (@react-pdf — inline JS objects only, no Tailwind)
import { StyleSheet } from "@react-pdf/renderer";
export const pdfStyles = StyleSheet.create({
page: { padding: 48, fontFamily: "Helvetica", backgroundColor: "#ffffff" },
header: { flexDirection: "row", justifyContent: "space-between", marginBottom: 32 },
rule: { height: 2, backgroundColor: "#2BBFB3", marginBottom: 24 },
h1: { fontSize: 22, fontWeight: "bold", color: "#202124" },
h2: { fontSize: 14, fontWeight: "bold", color: "#202124", marginBottom: 8 },
body: { fontSize: 10, color: "#5f6368", lineHeight: 1.5 },
label: { fontSize: 8, color: "#5f6368", textTransform: "uppercase", letterSpacing: 0.5 },
tableHead: { backgroundColor: "#f1f3f4", flexDirection: "row", padding: "6 8" },
tableRow: { flexDirection: "row", padding: "6 8", borderBottomWidth: 1, borderBottomColor: "#e8eaed" },
totalRow: { flexDirection: "row", padding: "8 8", backgroundColor: "#e6f9f8" },
amountBold: { fontSize: 12, fontWeight: "bold", color: "#202124" },
});Document Header Layout
┌──────────────────────────────────────────────────┐
│ [LOGO] À Bientôt Tour & Travels Ltd │ Space Grotesk bold
│ Pearl of Africa · Kampala, Uganda │ 9pt muted
│ INVOICE │ right, 28pt, #2BBFB3
│ ABT-2026-0001 │ right, 11pt
├──────────────────────────────────────────────────┤ 2px #2BBFB3 rule
│ Bill To: Issue Date: │
│ [Traveller name] [date] │
│ [Country] Due Date: │
│ [Email] [date] │
└──────────────────────────────────────────────────┘M3 Shadow Scale
export const SHADOWS = {
sm: "0 1px 2px rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15)",
md: "0 1px 2px rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15)",
lg: "0 4px 8px 3px rgba(60,64,67,0.15), 0 1px 3px rgba(60,64,67,0.3)",
};Border Radius Scale
export const RADIUS = { xs:4, sm:8, md:12, lg:16, xl:24, pill:50 };
// Tailwind: rounded-sm(4) rounded-lg(12) rounded-2xl(16) rounded-3xl(24) rounded-full(pill)