nyuchimzizi
Mzizi — an open-architecture project of the Bundu Foundation, operated and developed by Nyuchi. Built on the Five African Minerals palette.
Built by Nyuchi Africav4.0.39
Brand calendar component wrapping react-day-picker with Mukoko event dot indicators, mineral-colored day highlights, and integrated agenda view for the selected day. Used by nhimbe (events), planner, and any Mukoko app that shows date-anchored content. The event dots are the key differentiator — small mineral-colored circles beneath dates that have associated content.
View the full component source code below.
"use client"
// ── INFRASTRUCTURE HARNESS (auto-wired) ──
// Every brand component participates in observability, motion, a11y,
// and health monitoring via the harness. Zero manual config.
import { useNyuchiHarness } from "@/lib/harness"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "@/lib/icons"
import { cn } from "@/lib/utils"
/* ═══════════════════════════════════════════════════════════════
NYUCHI CALENDAR — Brand Data Display Component
Wraps the base calendar primitive with Mukoko-specific
features that create brand identity:
1. Event dot indicators — mineral-colored dots under dates
that have associated content (events, deadlines, etc.)
2. Malachite highlight on today/selected date
3. Month navigation with brand typography
4. Integrated agenda slot for selected-day content
This is used by nhimbe (events), planner (tasks), and any
app that needs a date-anchored content view.
Design identity markers:
- 14px card radius for the calendar container
- 7px inner radius for day cells
- Malachite (var(--color-malachite,#64FFDA)) for today/selected
- 4px mineral-colored dots for event indicators
- Day headers in 11px uppercase Noto Sans
═══════════════════════════════════════════════════════════════ */
type Mineral = "cobalt" | "tanzanite" | "malachite" | "gold" | "terracotta"
const mineralColorMap: Record<Mineral, string> = {
cobalt: "var(--color-cobalt, var(--color-cobalt,#00B0FF))",
tanzanite: "var(--color-tanzanite, var(--color-tanzanite,#B388FF))",
malachite: "var(--color-malachite, var(--color-malachite,#64FFDA))",
gold: "var(--color-gold, var(--color-gold,#FFD740))",
terracotta: "var(--color-terracotta, var(--color-terracotta,#D4A574))",
}
interface CalendarEvent {
/** Date string (YYYY-MM-DD) or Date object */
date: string | Date
/** Optional mineral color for the dot (defaults to malachite) */
mineral?: Mineral
/** Event data passed through to the agenda slot */
[key: string]: unknown
}
interface NyuchiCalendarProps {
/** Events to display as dots on the calendar */
events?: CalendarEvent[]
/** Currently selected date */
selectedDate?: Date
/** Callback when a date is selected */
onDateSelect?: (date: Date) => void
/** Callback when month changes */
onMonthChange?: (month: Date) => void
/** Render prop for the agenda area below the calendar */
renderAgenda?: (date: Date, events: CalendarEvent[]) => React.ReactNode
/** Initial month to display */
defaultMonth?: Date
className?: string
}
const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
function NyuchiCalendar({ loading = false,
events = [],
selectedDate,
onDateSelect,
onMonthChange,
renderAgenda,
defaultMonth,
className,
}: NyuchiCalendarProps) {
const { log, motion, LiveRegion } = useNyuchiHarness("calendar")
const animStyle = React.useMemo(() => motion.prefersReduced ? {} : { animation: `nyuchi-fade-slide-up ${motion.enterDuration}ms ${motion.enterEasing} both` }, [motion])
if (loading) return (<div data-slot="nyuchi-calendar" data-portal="https://design.nyuchi.com/components/nyuchi-calendar" data-loading role="application" aria-label="Calendar" className="animate-pulse rounded-[var(--radius-lg,14px)] bg-card p-4 ring-1 ring-foreground/10 space-y-3"><div className="flex justify-between"><div className="h-4 w-24 rounded bg-muted" /><div className="flex gap-1"><div className="size-8 rounded bg-muted" /><div className="size-8 rounded bg-muted" /></div></div><div className="grid grid-cols-7 gap-1">{Array.from({length:35}).map((_,i)=>(<div key={i} className="h-8 rounded bg-muted" />))}</div></div>)
const [currentMonth, setCurrentMonth] = React.useState(defaultMonth || new Date())
const [selected, setSelected] = React.useState<Date | undefined>(selectedDate)
/* Build a lookup: "YYYY-MM-DD" -> CalendarEvent[] */
const eventsByDate = React.useMemo(() => {
const map = new Map<string, CalendarEvent[]>()
for (const ev of events) {
const d = typeof ev.date === "string" ? ev.date : ev.date.toISOString().split("T")[0]
const existing = map.get(d) || []
existing.push(ev)
map.set(d, existing)
}
return map
}, [events])
/* Calendar grid computation */
const year = currentMonth.getFullYear()
const month = currentMonth.getMonth()
const firstDay = new Date(year, month, 1).getDay() // 0=Sun
const daysInMonth = new Date(year, month + 1, 0).getDate()
const today = new Date()
const todayKey = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`
function handlePrev() {
const prev = new Date(year, month - 1, 1)
setCurrentMonth(prev)
onMonthChange?.(prev)
}
function handleNext() {
const next = new Date(year, month + 1, 1)
setCurrentMonth(next)
onMonthChange?.(next)
}
function handleDayClick(day: number) {
const date = new Date(year, month, day)
setSelected(date)
onDateSelect?.(date)
}
function dayKey(day: number): string {
return `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`
}
const monthLabel = currentMonth.toLocaleDateString("en-US", { month: "long", year: "numeric" })
/* Events for the selected date */
const selectedKey = selected
? `${selected.getFullYear()}-${String(selected.getMonth() + 1).padStart(2, "0")}-${String(selected.getDate()).padStart(2, "0")}`
: null
const selectedEvents = selectedKey ? (eventsByDate.get(selectedKey) || []) : []
return (
<div data-slot="nyuchi-calendar" role="application" aria-label="Calendar" className={cn("flex flex-col gap-4", className)}>
{/* Month navigation */}
<div className="flex items-center justify-between">
<button onClick={handlePrev} className="p-1 text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)] transition-colors hover:text-foreground">
<ChevronLeft className="size-5" />
</button>
<span className="text-lg font-semibold text-foreground">{monthLabel}</span>
<button onClick={handleNext} className="p-1 text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)] transition-colors hover:text-foreground">
<ChevronRight className="size-5" />
</button>
</div>
{/* Day headers */}
<div className="grid grid-cols-7 gap-1">
{DAY_LABELS.map((d) => (
<div key={d} className="text-center text-[11px] font-medium uppercase text-muted-foreground">
{d}
</div>
))}
</div>
{/* Calendar grid */}
<div className="rounded-[var(--radius-card,14px)] bg-card p-2 ring-1 ring-foreground/10">
<div className="grid grid-cols-7 gap-[2px]">
{/* Empty cells for offset */}
{Array.from({ length: firstDay }).map((_, i) => (
<div key={`empty-${i}`} className="h-11" />
))}
{/* Day cells */}
{Array.from({ length: daysInMonth }).map((_, i) => {
const day = i + 1
const key = dayKey(day)
const isToday = key === todayKey
const isSelected = selected &&
selected.getDate() === day &&
selected.getMonth() === month &&
selected.getFullYear() === year
const dayEvents = eventsByDate.get(key) || []
const hasEvents = dayEvents.length > 0
/* Get the dominant mineral for the dot (first event mineral) */
const dotMineral = dayEvents[0]?.mineral || "malachite"
const dotColor = isToday || isSelected
? "var(--color-background, var(--muted,#050504))"
: mineralColorMap[dotMineral]
return (
<button
key={day}
onClick={() => handleDayClick(day)}
className={cn(
"flex h-11 flex-col items-center justify-center rounded-[var(--radius-inner,7px)] transition-colors",
isSelected && "bg-[var(--color-malachite)]",
isToday && !isSelected && "bg-[var(--color-malachite)]/20",
)}
>
<span
className={cn(
"text-sm",
isSelected && "font-semibold text-background",
isToday && !isSelected && "font-semibold text-[var(--color-malachite)]",
!isToday && !isSelected && "text-foreground",
)}
>
{day}
</span>
{/* Event dot indicator — the brand identity marker */}
{hasEvents && (
<div
className="mt-0.5 size-1 rounded-full"
style={{ backgroundColor: dotColor }}
/>
)}
</button>
)
})}
</div>
</div>
{/* Agenda slot for selected day */}
{selected && renderAgenda && (
<div data-slot="nyuchi-calendar-agenda">
{renderAgenda(selected, selectedEvents)}
</div>
)}
</div>
)
}
export { NyuchiCalendar, mineralColorMap }
export type { NyuchiCalendarProps, CalendarEvent, Mineral }
npx shadcn@latest add https://mzizi.dev/api/v1/ui/nyuchi-calendarFetch this component's metadata and source code from the registry API.
/api/v1/ui/nyuchi-calendarcomponents/mukoko/mukoko-calendar.tsx