@gigu/ui
v0.4.2
Published
GigU design system components for Granular.js
Readme
@gigu/ui
GigU design system components for Granular.js projects.
v0.2.2 — 40+ components: buttons, forms, data, overlays, charts, and more.
Install
npm install @gigu/uiFor charts, also install Chart.js:
npm install chart.jsPrerequisites
Add to your index.html before your main script:
<!-- Urbane Rounded (display / heading font) -->
<link rel="stylesheet" href="https://use.typekit.net/pzw3ohz.css">
<!-- Nunito Sans (body font) -->
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,400;0,6..12,500;0,6..12,600;0,6..12,700;1,6..12,400&display=swap" rel="stylesheet">
<!-- Font Awesome Pro (icons used by components) -->
<script src="https://kit.fontawesome.com/346525b409.js" crossorigin="anonymous"></script>Quick start
import "@gigu/ui/tokens.css";When the user switches theme, sync the .dark class so tokens apply:
import { setThemeMode } from "@granularjs/ui";
function applyTheme(isDark: boolean) {
setThemeMode(isDark ? "dark" : "light");
document.documentElement.classList.toggle("dark", isDark);
}Tailwind setup
// tailwind.config.js
const giguPreset = require("@gigu/ui/tailwind");
export default {
presets: [giguPreset],
content: ["./src/**/*.{ts,html}"],
};Usage
Buttons
import { Button, ActionIcon } from "@gigu/ui";
const btn = Button({ label: "Save", variant: "filled", color: "primary", size: "md" });
const icon = ActionIcon({ icon: "fa-solid fa-plus", onClick: () => {} });Badges & Alerts
import { Badge, Chip, Alert } from "@gigu/ui";
const badge = Badge({ label: "Active", color: "green" });
const chip = Chip({ label: "TypeScript", onRemove: () => {} });
const alert = Alert({ color: "warning", title: "Heads up", message: "This action is irreversible." });Forms
import {
TextInput, NumberInput, Textarea, SearchInput,
GSelect, Switch, Checkbox, PasswordInput, CopyButton,
MultiSelect, Combobox,
} from "@gigu/ui";
const input = TextInput({ label: "Email", placeholder: "[email protected]" });
const search = SearchInput({ placeholder: "Search jobs…", onSearch: (q) => console.log(q) });
const sel = GSelect({ label: "Status", options: [
{ value: "active", label: "Active" },
{ value: "closed", label: "Closed" },
]});
const combo = Combobox({ items: [
{ value: "js", label: "JavaScript" },
{ value: "ts", label: "TypeScript" },
], multiple: true, placeholder: "Pick languages…" });Display
import { Avatar, Skeleton, Loading, Divider, Spinner } from "@gigu/ui";
const avatar = Avatar({ name: "Alice Smith", size: "md" });
const skel = Skeleton({ width: "100%", height: "40px" });
const spinner = Spinner({ variant: "ring", color: "primary", size: "md" });
// variants: "border" | "ring" | "dots" | "bars"Layout
import { Stack, Group, Flex, Col, SimpleGrid } from "@gigu/ui";
const stack = Stack({ gap: 4 }, item1, item2, item3);
const group = Group({ gap: 2 }, badge1, badge2);
const grid = SimpleGrid({ cols: 3, gap: 6 }, card1, card2, card3);Data
import { StatCard, Table, List, DataGrid, KPICard } from "@gigu/ui";
const stat = StatCard({
label: "Total Applications",
value: "1,284",
delta: "+12%",
deltaUp: true,
});
const kpi = KPICard({
label: "Open Gigs",
value: "47",
icon: "fa-solid fa-briefcase",
iconBg: "bg-orange-100 dark:bg-orange-500/15",
iconColor: "text-primary",
trend: { value: "+8%", up: true, label: "vs last month" },
testId: "kpi-open-gigs",
});
const table = Table({
columns: [
{ key: "name", header: "Name" },
{ key: "status", header: "Status" },
{ key: "date", header: "Date" },
],
rows: data,
});Progress & Slider
import { Progress, Slider, RangeSlider } from "@gigu/ui";
const bar = Progress({ value: 72, label: "Completion", showValue: true, color: "primary" });
const slider = Slider({ min: 0, max: 100, value: 50, onChange: (v) => console.log(v) });
const range = RangeSlider({ min: 0, max: 100, low: 20, high: 80, onChange: (lo, hi) => {} });Calendar
import { Calendar } from "@gigu/ui";
// Single date picker
const cal = Calendar({ mode: "single", onSelect: (date) => console.log(date) });
// Date range picker
const range = Calendar({
mode: "range",
onRangeSelect: (start, end) => console.log(start, end),
});Overlays
import { createModal, createMenu, Accordion, createDrawer, createPopover } from "@gigu/ui";
// Modal
const { trigger, open } = createModal({
title: "Confirm deletion",
content: () => { /* return body element */ },
footer: (close) => { /* return footer element */ },
});
// Drawer
const drawer = createDrawer({
direction: "right",
title: "Applicant details",
size: "md",
content: () => { /* return body element */ },
});
drawer.open();
// Popover
const popover = createPopover({
trigger: myButton,
content: myContentEl,
side: "bottom",
align: "start",
});
popover.toggle();
// Dropdown menu
const menu = createMenu({
trigger: menuBtn,
items: [
{ label: "Edit", icon: "fa-solid fa-pen", onClick: () => {} },
{ label: "Delete", icon: "fa-solid fa-trash", onClick: () => {}, danger: true },
],
});Toast
import { toast } from "@gigu/ui";
toast.success("Profile saved");
toast.error("Upload failed", { description: "File too large" });
toast.promise(fetch("/api/submit"), {
loading: "Submitting…",
success: "Done!",
error: "Failed",
});Tooltip
import { attachTooltip } from "@gigu/ui";
attachTooltip(myButton, "Click to submit", "top");Navigation
import { NavLink, Sidebar, AppShell, Breadcrumbs, NavTabs } from "@gigu/ui";
const crumbs = Breadcrumbs([
{ label: "Dashboard", href: "/" },
{ label: "Jobs", href: "/jobs" },
{ label: "Edit" },
]);
const tabs = NavTabs({
items: [
{ label: "Overview", value: "overview" },
{ label: "Applications", value: "apps" },
],
active: "overview",
onChange: (val) => {},
});ScrollArea
A scrollable container with GigU-styled thin custom scrollbars (6px, hover-reveal, dark-mode aware).
import { ScrollArea } from "@gigu/ui";
// Vertical scroll with preset height
const area = ScrollArea(
{ size: "lg", scrollbars: "vertical" },
item1, item2, item3,
);
// Custom max-height
const custom = ScrollArea(
{ maxHeight: "400px" },
longContent,
);
// Horizontal scroll
const hscroll = ScrollArea(
{ scrollbars: "horizontal", maxHeight: "auto" },
wideTable,
);Size presets: sm (120px) · md (200px) · lg (320px) · auto (no limit) · any CSS value via maxHeight.
Image
An <img> wrapper with an optional loading skeleton and error fallback.
import { GigUImage } from "@gigu/ui";
// Avatar
const avatar = GigUImage({
src: "/uploads/avatar.jpg",
alt: "User avatar",
width: 80,
height: 80,
radius: "full",
fit: "cover",
});
// Card banner with skeleton
const banner = GigUImage({
src: "/uploads/banner.jpg",
alt: "Company banner",
width: "100%",
height: "200px",
radius: "lg",
withPlaceholder: true,
});
// Custom error icon
const img = GigUImage({
src: url,
alt: "Logo",
width: 120,
height: 120,
radius: "md",
fallbackIcon: "fa-solid fa-building",
});Radius options: none · sm · md · lg · xl · 2xl · full · any CSS value.
Fit options: cover · contain · fill · none · scale-down.
Empty States
import { EmptyState, InlineEmpty } from "@gigu/ui";
const empty = EmptyState({
icon: "fa-solid fa-inbox",
iconColor: "text-gray-400",
iconBg: "bg-gray-100 dark:bg-gray-800",
title: "No applications yet",
description: "Applications will appear here once candidates apply.",
action: { label: "Post a gig", icon: "fa-solid fa-plus" },
});
const inline = InlineEmpty("fa-solid fa-magnifying-glass", "No results found");Pagination
import { Pagination, PaginationBar } from "@gigu/ui";
const nav = Pagination({
totalPages: 12,
currentPage: 1,
onPageChange: (page) => loadPage(page),
});
const bar = PaginationBar({
total: 247,
rowsPerPage: 25,
onPageChange: (page) => loadPage(page),
onRowsChange: (rows) => setPageSize(rows),
});Charts
@gigu/ui ships theme-aware Chart.js helpers that automatically apply the GigU visual style in both light and dark mode.
Setup
import Chart from "chart.js/auto";
import "@gigu/ui/tokens.css";Color palette
import { GIGU_COLORS, GIGU_PALETTE } from "@gigu/ui";
// Named colors
GIGU_COLORS.primary // "#FF5900"
GIGU_COLORS.blue // "#3B82F6"
GIGU_COLORS.green // "#22C55E"
GIGU_COLORS.amber // "#F59E0B"
GIGU_COLORS.purple // "#8B5CF6"
GIGU_COLORS.pink // "#EC4899"
// Array for cycling over multiple datasets
GIGU_PALETTE // [primary, blue, green, amber, purple, pink]Theme-aware defaults
giguChartDefaults() reads the current dark-mode state from <html> and returns a full Chart.js options object:
import Chart from "chart.js/auto";
import { ChartCanvas, ChartCard, giguChartDefaults } from "@gigu/ui";
const { defs, xy, r } = giguChartDefaults();
// defs — responsive + legend + tooltip defaults
// xy — x/y scale defaults (gridlines, tick colors)
// r — radar scale defaults
const canvas = ChartCanvas("my-chart");
new Chart(canvas, {
type: "bar",
data: { labels: ["Jan","Feb","Mar"], datasets: [{ label: "Revenue", data: [42,78,65] }] },
options: { ...defs, scales: xy },
});
const card = ChartCard({ title: "Revenue", subtitle: "Monthly breakdown", canvas });
document.body.appendChild(card);Preset chart configs
These return a complete ChartConfiguration object ready to pass to new Chart():
import Chart from "chart.js/auto";
import { ChartCanvas, barChartConfig, lineChartConfig, pieChartConfig, radarChartConfig } from "@gigu/ui";
// Bar — single, grouped, stacked, or horizontal
const barCfg = barChartConfig({
labels: ["Jan","Feb","Mar","Apr","May","Jun"],
datasets: [
{ label: "Applications", data: [42,78,65,90,82,110] },
{ label: "Hires", data: [10,18,15,24,21, 32], color: "#3B82F6" },
],
stacked: false, // true for stacked bars
horizontal: false, // true for horizontal bar (indexAxis: "y")
});
new Chart(ChartCanvas("bar"), barCfg);
// Line — fill optional per dataset
const lineCfg = lineChartConfig({
labels: ["Jan","Feb","Mar","Apr","May","Jun"],
datasets: [
{ label: "Active Gigs", data: [8,12,10,15,14,18], fill: true },
{ label: "Filled", data: [5, 9, 8,12,11,15], fill: false },
],
});
new Chart(ChartCanvas("line"), lineCfg);
// Pie or donut
const pieCfg = pieChartConfig({
labels: ["Part-time","Full-time","Internship","Contract"],
data: [45, 25, 20, 10],
donut: true, // false for pie
cutout: "65%", // donut hole size
});
new Chart(ChartCanvas("pie"), pieCfg);
// Radar
const radarCfg = radarChartConfig({
labels: ["Speed","Quality","Reliability","Communication","Flexibility"],
datasets: [
{ label: "Avg Student", data: [75,80,70,85,78] },
{ label: "Top Performers", data: [90,92,88,95,90] },
],
});
new Chart(ChartCanvas("radar"), radarCfg);Tabbed live chart card
import Chart from "chart.js/auto";
import { LiveChartCard, giguChartDefaults, GIGU_COLORS } from "@gigu/ui";
const { card, canvas, ref } = LiveChartCard(
"Unique Applicants",
"Applications received per period",
[
{ label: "Day", labels: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"], data: [18,32,27,45,38,22,14] },
{ label: "Week", labels: ["Wk 1","Wk 2","Wk 3","Wk 4","Wk 5","Wk 6"], data: [82,110,96,140,128,155] },
{ label: "Month", labels: ["Jan","Feb","Mar","Apr","May","Jun"], data: [42,78,65,90,82,110] },
],
"chart-applicants",
);
document.body.appendChild(card);
const { defs, xy, tooltipDefaults } = giguChartDefaults();
ref.chart = new Chart(canvas, {
type: "bar",
data: {
labels: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
datasets: [{ label: "Applicants", data: [18,32,27,45,38,22,14], backgroundColor: `${GIGU_COLORS.primary}CC`, borderRadius: 6 }],
},
options: {
...defs,
scales: xy,
plugins: { legend: { display: false }, tooltip: tooltipDefaults },
animation: { duration: 300 },
},
});CSS tokens reference
| Variable | Value | Usage |
|----------|-------|-------|
| --gigu-flame | #FF5900 | Primary / CTA |
| --gigu-ember | #E64A19 | Primary hover |
| --gigu-spark | #FF7F50 | Accent / emphasized |
| --gigu-cream | #FFE5D0 | Primary subtle bg |
| --gigu-ash | #6B6B6B | Body text |
| --gigu-fog | #8A9099 | Secondary / descriptions |
| --gigu-snow | #C7CCD6 | Borders / dividers |
| --font-sans | Nunito Sans | Body text |
| --font-display | Urbane Rounded | Headings / titles |
| --font-mono | Geist Mono | Code blocks |
