@nsxbet/admin-ui
v0.6.0
Published
UI component library for NSX Admin modules
Downloads
280
Maintainers
Readme
@nsxbet/admin-ui
UI component library for NSX Admin admin modules. Based on shadcn/ui with the Brasa Design System theme.
Installation
bun add @nsxbet/admin-uiSetup
1. Import CSS
In your src/index.css:
@import "@nsxbet/admin-ui/styles.css";
@tailwind base;
@tailwind components;
@tailwind utilities;2. Configure Tailwind
Recommended - Use withAdminSdk from the SDK (automatically includes the preset and content paths):
import { withAdminSdk } from "@nsxbet/admin-sdk/tailwind";
/** @type {import('tailwindcss').Config} */
export default withAdminSdk({
content: ["./index.html", "./src/**/*.{ts,tsx}"],
});Alternative - Use the preset directly (requires manual content paths):
import { preset } from "@nsxbet/admin-ui/tailwind-preset";
/** @type {import('tailwindcss').Config} */
export default {
presets: [preset],
content: [
"./index.html",
"./src/**/*.{ts,tsx}",
],
};The preset includes all theme colors, animations, and the tailwindcss-animate plugin.
To verify your Tailwind/PostCSS setup is correct, run npx @nsxbet/admin-cli checklist and check the Configuration section.
Component Catalog
Buttons
import { Button } from "@nsxbet/admin-ui";
<Button>Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Plus /></Button>Cards
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@nsxbet/admin-ui";
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description text</CardDescription>
</CardHeader>
<CardContent>
<p>Card content goes here.</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>Badges
import { Badge } from "@nsxbet/admin-ui";
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="info">Info</Badge>Form Inputs
import { Input, Label, Textarea, Checkbox, Switch } from "@nsxbet/admin-ui";
// Text input
<div>
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Enter name" />
</div>
// Textarea
<div>
<Label htmlFor="description">Description</Label>
<Textarea id="description" placeholder="Enter description" />
</div>
// Checkbox
<div className="flex items-center gap-2">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms</Label>
</div>
// Switch
<div className="flex items-center gap-2">
<Switch id="notifications" />
<Label htmlFor="notifications">Enable notifications</Label>
</div>Select
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@nsxbet/admin-ui";
<Select>
<SelectTrigger>
<SelectValue placeholder="Select option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>Tables
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@nsxbet/admin-ui";
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>[email protected]</TableCell>
<TableCell><Badge variant="success">Active</Badge></TableCell>
</TableRow>
</TableBody>
</Table>Dialog
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@nsxbet/admin-ui";
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
This is a dialog description.
</DialogDescription>
</DialogHeader>
<div className="py-4">
Dialog content goes here.
</div>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>Sheet (Slide-out Panel)
import {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@nsxbet/admin-ui";
<Sheet>
<SheetTrigger asChild>
<Button>Open Panel</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Panel Title</SheetTitle>
<SheetDescription>Panel description</SheetDescription>
</SheetHeader>
<div className="py-4">Panel content</div>
</SheetContent>
</Sheet>Tabs
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@nsxbet/admin-ui";
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Tab 1 content</TabsContent>
<TabsContent value="tab2">Tab 2 content</TabsContent>
</Tabs>Dropdown Menu
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
} from "@nsxbet/admin-ui";
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Options</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Tooltip
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from "@nsxbet/admin-ui";
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">Hover me</Button>
</TooltipTrigger>
<TooltipContent>
<p>Tooltip content</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>Alert
import { Alert, AlertTitle, AlertDescription } from "@nsxbet/admin-ui";
<Alert>
<AlertTitle>Information</AlertTitle>
<AlertDescription>This is an informational alert.</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>Something went wrong.</AlertDescription>
</Alert>Avatar
import { Avatar, AvatarImage, AvatarFallback } from "@nsxbet/admin-ui";
<Avatar>
<AvatarImage src="https://example.com/avatar.jpg" alt="User" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<Avatar className="h-12 w-12">
<AvatarImage src="/profile.png" alt="Jane Doe" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>Breadcrumb
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
} from "@nsxbet/admin-ui";
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/settings">Settings</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Profile</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>Collapsible
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "@nsxbet/admin-ui";
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="ghost">Toggle Section</Button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="p-4">
Collapsible content goes here.
</div>
</CollapsibleContent>
</Collapsible>Command
Command palette for searchable menus and keyboard-driven navigation:
import {
Command,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandSeparator,
CommandShortcut,
} from "@nsxbet/admin-ui";
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Actions">
<CommandItem>
<span>New Task</span>
<CommandShortcut>⌘N</CommandShortcut>
</CommandItem>
<CommandItem>
<span>Search</span>
<CommandShortcut>⌘K</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>Profile</CommandItem>
<CommandItem>Preferences</CommandItem>
</CommandGroup>
</CommandList>
</Command>Use CommandDialog to render the command palette inside a dialog:
import { CommandDialog } from "@nsxbet/admin-ui";
const [open, setOpen] = useState(false);
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem>Dashboard</CommandItem>
<CommandItem>Settings</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>ScrollArea
import { ScrollArea } from "@nsxbet/admin-ui";
<ScrollArea className="h-[200px]">
<div className="p-4">
{/* Long content that scrolls */}
</div>
</ScrollArea>Separator
import { Separator } from "@nsxbet/admin-ui";
<div>
<p>Above separator</p>
<Separator className="my-4" />
<p>Below separator</p>
</div>Skeleton (Loading Placeholder)
import { Skeleton } from "@nsxbet/admin-ui";
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
<Skeleton className="h-4 w-[150px]" />
</div>Sidebar
Composable sidebar navigation with collapsible groups, menus, and responsive behavior:
import {
SidebarProvider,
Sidebar,
SidebarHeader,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarGroupContent,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarTrigger,
} from "@nsxbet/admin-ui";
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<span className="text-lg font-semibold">App Name</span>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Main</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/dashboard">Dashboard</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/tasks">Tasks</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<p className="text-xs text-muted-foreground">v1.0.0</p>
</SidebarFooter>
</Sidebar>
<main className="flex-1">
<SidebarTrigger />
<div className="p-4">Page content</div>
</main>
</SidebarProvider>Use useSidebar to programmatically control sidebar state:
import { useSidebar } from "@nsxbet/admin-ui";
function MyComponent() {
const { open, toggleSidebar } = useSidebar();
return <Button onClick={toggleSidebar}>{open ? "Close" : "Open"}</Button>;
}Data Components
LoadingState
import { LoadingState } from "@nsxbet/admin-ui";
<LoadingState />
<LoadingState text="Loading items..." />
<LoadingState size="sm" />
<LoadingState size="lg" />
<LoadingState fullPage />EmptyState
import { EmptyState } from "@nsxbet/admin-ui";
<EmptyState
title="No items found"
description="Get started by creating your first item."
action={{
label: "Create Item",
onClick: () => console.log("create"),
}}
/>StatusBadge
import { StatusBadge } from "@nsxbet/admin-ui";
<StatusBadge status="active" />
<StatusBadge status="inactive" />
<StatusBadge status="pending" />
<StatusBadge status="error" />
<StatusBadge status="success" />
<StatusBadge status="warning" />DataTable
import { DataTable } from "@nsxbet/admin-ui";
const columns = [
{ accessor: "name" as const, header: "Name" },
{ accessor: "email" as const, header: "Email" },
{
accessor: "status" as const,
header: "Status",
cell: (value: string) => <StatusBadge status={value as any} />,
},
];
<DataTable
data={users}
columns={columns}
loading={isLoading}
emptyState={{
title: "No users",
description: "Add your first user",
}}
/>Icon Component
Render Lucide icons dynamically by name (kebab-case):
import { Icon } from "@nsxbet/admin-ui";
<Icon name="clipboard-list" className="h-4 w-4" />
<Icon name="file-text" className="h-4 w-4" />
<Icon name="users" size={24} />
<Icon name="plus" className="h-4 w-4 text-primary" />Direct Icon Imports
For static icons, import directly:
import {
Plus,
Edit,
Trash,
Check,
X,
Search,
ChevronRight,
User,
Users,
Settings,
Home,
FileText,
ClipboardList,
} from "@nsxbet/admin-ui";
<Plus className="h-4 w-4" />
<Edit className="h-4 w-4" />
<Trash className="h-4 w-4 text-destructive" />Available Icons
| Category | Icons |
|----------|-------|
| Navigation | ChevronRight, ChevronLeft, ChevronDown, ChevronUp, Search, Menu, X, Home, Settings, LogOut, PanelLeft |
| Actions | Plus, Minus, Edit, Trash, Trash2, Copy, Pin, Clock, Star, Sparkles, Crown, Wrench |
| Status | Check, AlertCircle, Info, AlertTriangle, Ban, Lock, Unlock, Eye |
| Content | File, FileText, Folder, ClipboardList, Calendar, Mail, Phone |
| Users | User, Users |
| Theme | Sun, Moon |
Theming
The UI uses CSS variables for theming. Default is dark mode (Brasa Design System).
Color Tokens
| Token | Usage |
|-------|-------|
| --primary | Primary actions, links |
| --secondary | Secondary elements |
| --background | Page background |
| --foreground | Text color |
| --card | Card surfaces |
| --muted | Muted text/backgrounds |
| --accent | Accent elements |
| --destructive | Error/destructive actions |
| --success | Success states |
| --warning | Warning states |
| --info | Informational states |
Using Colors
// Text colors
<p className="text-primary">Primary text</p>
<p className="text-muted-foreground">Muted text</p>
<p className="text-destructive">Error text</p>
<p className="text-success">Success text</p>
// Background colors
<div className="bg-background">Page background</div>
<div className="bg-card">Card background</div>
<div className="bg-muted">Muted background</div>
<div className="bg-primary text-primary-foreground">Primary button</div>Utilities
cn() - Class Name Merger
import { cn } from "@nsxbet/admin-ui";
<div className={cn("base-class", isActive && "active-class", className)}>
Content
</div>useIsMobile
Hook that returns true when the viewport width is below the mobile breakpoint (768px):
import { useIsMobile } from "@nsxbet/admin-ui";
function MyComponent() {
const isMobile = useIsMobile();
return isMobile ? <MobileLayout /> : <DesktopLayout />;
}DO NOT (Common Mistakes)
❌ DO NOT forget to import styles
/* WRONG - missing import */
@tailwind base;
@tailwind components;
@tailwind utilities;/* CORRECT - import before tailwind directives */
@import "@nsxbet/admin-ui/styles.css";
@tailwind base;
@tailwind components;
@tailwind utilities;❌ DO NOT forget to use the preset
// WRONG - manual theme configuration
export default {
content: ["./src/**/*.{ts,tsx}"],
theme: { /* manually copying colors... */ }
}// CORRECT - use withAdminSdk from SDK (recommended)
import { withAdminSdk } from "@nsxbet/admin-sdk/tailwind";
export default withAdminSdk({
content: ["./index.html", "./src/**/*.{ts,tsx}"],
});❌ DO NOT import SDK hooks from UI
// WRONG - hooks are in SDK, not UI
import { useAuth } from "@nsxbet/admin-ui"; // ❌// CORRECT - import from SDK
import { useAuth } from "@nsxbet/admin-sdk"; // ✅
import { Button, Card } from "@nsxbet/admin-ui"; // ✅License
UNLICENSED - Internal use only
