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
Content access gate with age verification, location restrictions, and content sensitivity filters. Renders children when all checks pass. Shows a branded explanation with the specific restriction reason when blocked. Supports multiple gate types that can be composed.
No interactive demo is registered for this component yet — the source is shown directly.
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { useNyuchiHarness } from "@/lib/harness"
/* ═══════════════════════════════════════════════════════════════
NYUCHI CONTENT GATE — Layer 4 Safety & Trust
Age gates, location gates, content sensitivity filters.
✅ HARNESS ✅ TOKENS ✅ ARIA ✅ LOADING ✅ MOTION
═══════════════════════════════════════════════════════════════ */
type GateType = "age" | "location" | "sensitivity" | "custom"
interface GateCheck {
type: GateType
passed: boolean
reason?: string
/** Minimum age required (for age gates) */
minAge?: number
/** Sensitivity level (for sensitivity gates) */
level?: "mild" | "moderate" | "mature" | "restricted"
}
interface NyuchiContentGateProps {
children: React.ReactNode
checks: GateCheck[]
fallback?: React.ReactNode
/** Whether to show which specific check failed */
showReason?: boolean
onAction?: (failedCheck: GateCheck) => void
loading?: boolean
className?: string
}
const GATE_ICONS: Record<GateType, string> = {
age: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
location: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z",
sensitivity: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243",
custom: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z",
}
const GATE_LABELS: Record<GateType, string> = {
age: "Age Restriction", location: "Location Restricted",
sensitivity: "Content Warning", custom: "Access Restricted",
}
export function NyuchiContentGate({
children, checks, fallback, showReason = true, onAction, loading = false, className,
}: NyuchiContentGateProps) {
const { log, motion, LiveRegion } = useNyuchiHarness("content-gate")
const animStyle = React.useMemo(() => motion.prefersReduced ? {} : { animation: `nyuchi-fade-slide-up ${motion.enterDuration}ms ${motion.enterEasing} both` }, [motion])
const failedCheck = checks.find((c) => !c.passed)
React.useEffect(() => {
if (failedCheck) log.info(`blocked: type=${failedCheck.type}, reason=${failedCheck.reason || "unspecified"}`)
}, [failedCheck, log])
if (loading) return <div data-slot="nyuchi-content-gate" data-portal="https://design.nyuchi.com/components/nyuchi-content-gate" data-loading role="status" className="h-32 animate-pulse rounded-[var(--radius-lg,14px)] bg-muted" />
if (!failedCheck) return <>{children}</>
if (fallback) return <>{fallback}</>
const icon = GATE_ICONS[failedCheck.type]
const label = GATE_LABELS[failedCheck.type]
return (
<div data-slot="nyuchi-content-gate" role="alert" aria-live="polite" style={animStyle}
className={cn("flex flex-col items-center gap-4 rounded-[var(--radius-lg,14px)] bg-card p-6 text-center ring-1 ring-foreground/10", className)}>
<div className="flex size-14 items-center justify-center rounded-full bg-[var(--status-warning,#F59E0B)]/15">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--status-warning, #F59E0B)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d={icon} />
</svg>
</div>
<div>
<p className="text-base font-semibold text-foreground" style={{ fontFamily: "var(--font-serif)" }}>{label}</p>
{showReason && failedCheck.reason && (
<p className="mt-1 text-sm text-muted-foreground">{failedCheck.reason}</p>
)}
{failedCheck.type === "age" && failedCheck.minAge && (
<p className="mt-1 text-sm text-muted-foreground">You must be at least {failedCheck.minAge} years old to view this content.</p>
)}
</div>
{onAction && (
<button onClick={() => onAction(failedCheck)}
className="min-h-[48px] rounded-full bg-muted px-6 text-sm font-medium text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)] transition-colors hover:bg-muted/80">
{failedCheck.type === "age" ? "Verify Age" : failedCheck.type === "location" ? "Change Location" : "Learn More"}
</button>
)}
</div>
)
}
export type { GateType, GateCheck, NyuchiContentGateProps }
The variants, sizes, and props surfaced by the registry. Each is sourced from the Supabase component_docs table.
Hit the registry API live and see the JSON the shadcn CLI consumes when it installs this component.
/api/v1/ui/mzizi-content-gatenpx shadcn@latest add https://mzizi.dev/api/v1/ui/mzizi-content-gatecomponents/ui/mzizi-content-gate.tsx