feat: UI design system - sidebar layout, workspace system, and shell redesign (#77)
* feat: add workspace system and sidebar layout (UI design system Phase A+B) Backend: Workspace model, migration (036), schemas, CRUD API endpoints. Adds workspace_id to trees and categories, seeds 4 default workspaces per account, auto-assigns existing trees by tree_type. Frontend: Complete AppLayout rewrite from top-nav to CSS Grid shell with persistent sidebar + topbar. New components: WorkspaceSwitcher, NavItem, CategoryList, TagCloud, TopBar, Sidebar. Dashboard components: QuickStats, FiltersBar, SectionGroup, TreeListItem, SessionsPanel. WorkspaceStore with localStorage persistence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add command palette search, dashboard rewrite, and shell height fixes (Phase C) - Add ⌘K command palette with debounced search across flows and sessions - Rewrite QuickStartPage as dashboard with stats, filters, sessions panel - Fix h-[calc(100vh-4rem)] → h-full across all pages for CSS Grid shell - Add active session count badge to sidebar Sessions nav item Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add sidebar collapse, category/tag filtering, and workspace CRUD (Phase D) - Sidebar collapse/expand toggle with icon-only rail mode (persisted) - Sidebar category/tag clicks navigate to /trees with URL params - TreeLibraryPage syncs filters from URL search params bidirectionally - Workspace create modal with icon picker and auto-slug generation - TopBar logo adapts to collapsed sidebar state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Quick Launch modal with actions and recent flows - Zap button opens Quick Launch with create/navigate shortcuts - Shows recent flows for quick session start - Keyboard navigation support (arrows + enter) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add activity notifications panel with session feed - Bell icon shows dot indicator for recent activity - Dropdown panel shows recent sessions with status icons - Links to session detail and sessions list page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: remove workspace system, add pinned flows and label renames Replace workspace system with pinned flows API (pin/unpin/list/reorder). Rename user-facing labels: Tree→Flow, Procedure→Project. Add sidebar nav sub-items for flow type filtering. Remove 11 workspace files, add migrations 037-038, clean all workspace references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapsed sidebar layout scaling and toggle button size Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate auth pages to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate TreeLibraryPage to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate session pages to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate TreeEditorPage to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate TreeNavigationPage to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate session sharing components to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove workspace dropdown animation (dead code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate common components to new design system Migrate 15 components from monochrome glass-card design to purple gradient accent design system tokens (bg-card, border-border, text-foreground, bg-gradient-brand, etc.) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate procedural and step library components to new design system Migrate 10 components from monochrome glass-card design to purple gradient accent design system tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate admin pages and components to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate remaining pages to new design system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: migrate remaining components to new design system Migrates 38 files: tree-editor forms, session modals, step library, common components, library views, tree preview, and misc UI to use design tokens (bg-card, border-border, text-foreground, bg-accent, bg-gradient-brand) replacing old monochrome patterns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: keep brand text visible on sidebar collapse, hide sub-items until hover - TopBar: always show "ResolutionFlow" text regardless of sidebar state - NavItem: sub-items (Troubleshooting, Projects) hidden by default, revealed on hover or when a child route is active Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #77.
This commit is contained in:
@@ -53,8 +53,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
||||
ref={buttonRef}
|
||||
onClick={() => setOpen(!open)}
|
||||
className={cn(
|
||||
'rounded-md p-1.5 text-white/50 transition-colors',
|
||||
'hover:bg-white/10 hover:text-white'
|
||||
'rounded-md p-1.5 text-muted-foreground transition-colors',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
@@ -63,8 +63,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
||||
<div
|
||||
ref={menuRef}
|
||||
className={cn(
|
||||
'fixed z-50 min-w-[160px] rounded-md border border-white/10',
|
||||
'bg-black py-1 shadow-lg animate-scale-in'
|
||||
'fixed z-50 min-w-[160px] rounded-md border border-border',
|
||||
'bg-card py-1 shadow-lg animate-scale-in'
|
||||
)}
|
||||
style={{
|
||||
top: `${menuPosition.top}px`,
|
||||
@@ -81,7 +81,7 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
||||
'disabled:opacity-50 disabled:pointer-events-none',
|
||||
item.destructive
|
||||
? 'text-red-400 hover:bg-red-400/10'
|
||||
: 'text-white/70 hover:bg-white/[0.06]'
|
||||
: 'text-muted-foreground hover:bg-accent'
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
|
||||
@@ -34,9 +34,9 @@ export function AdminLayout() {
|
||||
}, [mobileOpen, handleKeyDown])
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-4rem)]">
|
||||
<div className="flex h-full">
|
||||
{/* Desktop sidebar */}
|
||||
<div className="hidden w-60 flex-shrink-0 border-r border-white/[0.06] bg-black md:block">
|
||||
<div className="hidden w-60 flex-shrink-0 border-r border-border bg-card md:block">
|
||||
<AdminSidebar />
|
||||
</div>
|
||||
|
||||
@@ -44,14 +44,14 @@ export function AdminLayout() {
|
||||
{mobileOpen && (
|
||||
<div className="fixed inset-0 z-40 md:hidden">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
className="absolute inset-0 bg-card/80 backdrop-blur-sm"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
/>
|
||||
<div className="absolute inset-y-0 left-0 w-60 border-r border-white/[0.06] bg-black shadow-xl">
|
||||
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
|
||||
<div className="flex h-12 items-center justify-end px-3">
|
||||
<button
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="rounded-md p-1.5 text-white/50 hover:bg-white/[0.06]"
|
||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -67,7 +67,7 @@ export function AdminLayout() {
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
onClick={() => setMobileOpen(true)}
|
||||
className="mb-4 rounded-md p-2 text-white/50 hover:bg-white/[0.06] md:hidden"
|
||||
className="mb-4 rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
||||
return (
|
||||
<aside className={cn('flex h-full flex-col', className)}>
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-bold text-white">Admin Panel</h2>
|
||||
<h2 className="text-lg font-bold text-foreground">Admin Panel</h2>
|
||||
</div>
|
||||
<nav className="flex-1 space-y-1 px-3">
|
||||
{navItems.map((item) => (
|
||||
@@ -50,8 +50,8 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive(item.path, item.end)
|
||||
? 'bg-white/10 text-white'
|
||||
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
|
||||
? 'bg-accent text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
@@ -59,13 +59,13 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<div className="border-t border-white/[0.06] p-3">
|
||||
<div className="border-t border-border p-3">
|
||||
<Link
|
||||
to="/trees"
|
||||
onClick={onNavigate}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
|
||||
'text-white/50 hover:bg-white/[0.06] hover:text-white'
|
||||
'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
|
||||
@@ -38,7 +38,7 @@ export function CategoryRow({
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
className={cn(
|
||||
'flex items-center gap-3 glass-card rounded-2xl p-4',
|
||||
'flex items-center gap-3 bg-card border border-border rounded-xl p-4',
|
||||
isDragging && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
@@ -47,7 +47,7 @@ export function CategoryRow({
|
||||
type="button"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="cursor-grab touch-none text-white/50 hover:text-white active:cursor-grabbing"
|
||||
className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
|
||||
aria-label="Drag to reorder"
|
||||
>
|
||||
<GripVertical className="h-5 w-5" />
|
||||
@@ -56,17 +56,17 @@ export function CategoryRow({
|
||||
{/* Category Info */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-white">{category.name}</h3>
|
||||
<h3 className="font-medium text-foreground">{category.name}</h3>
|
||||
{!category.is_active && (
|
||||
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs font-medium text-white/70">
|
||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
||||
Archived
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{category.description && (
|
||||
<p className="mt-1 text-sm text-white/40">{category.description}</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
|
||||
)}
|
||||
<p className="mt-1 text-xs text-white/40">
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{stepCount} step{stepCount !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
@@ -77,8 +77,8 @@ export function CategoryRow({
|
||||
type="button"
|
||||
onClick={() => onEdit(category)}
|
||||
className={cn(
|
||||
'rounded-md border border-white/10 bg-black/50 p-2 text-white/50',
|
||||
'hover:bg-white/10 hover:text-white'
|
||||
'rounded-md border border-border bg-card p-2 text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
)}
|
||||
title="Edit category"
|
||||
aria-label="Edit category"
|
||||
|
||||
@@ -59,14 +59,14 @@ export function CreateCategoryModal({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
|
||||
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-white">Create Category</h2>
|
||||
<h2 className="text-lg font-semibold text-foreground">Create Category</h2>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
disabled={isSaving}
|
||||
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
|
||||
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -83,7 +83,7 @@ export function CreateCategoryModal({
|
||||
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label htmlFor="name" className="mb-1 block text-sm font-medium text-white">
|
||||
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
|
||||
Category Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -96,21 +96,21 @@ export function CreateCategoryModal({
|
||||
placeholder="e.g., Network Troubleshooting"
|
||||
required
|
||||
className={cn(
|
||||
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
|
||||
'placeholder:text-white/40',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'disabled:opacity-50'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-white/40">
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{name.length}/100 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Description Field */}
|
||||
<div>
|
||||
<label htmlFor="description" className="mb-1 block text-sm font-medium text-white">
|
||||
Description <span className="text-white/40">(optional)</span>
|
||||
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
|
||||
Description <span className="text-muted-foreground">(optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
@@ -120,9 +120,9 @@ export function CreateCategoryModal({
|
||||
rows={3}
|
||||
placeholder="Brief description of this category..."
|
||||
className={cn(
|
||||
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
|
||||
'placeholder:text-white/40',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'disabled:opacity-50'
|
||||
)}
|
||||
/>
|
||||
@@ -135,8 +135,8 @@ export function CreateCategoryModal({
|
||||
onClick={handleClose}
|
||||
disabled={isSaving}
|
||||
className={cn(
|
||||
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
|
||||
'hover:bg-white/10 hover:text-white disabled:opacity-50'
|
||||
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
Cancel
|
||||
@@ -145,8 +145,8 @@ export function CreateCategoryModal({
|
||||
type="submit"
|
||||
disabled={isSaving || !name.trim()}
|
||||
className={cn(
|
||||
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
|
||||
'hover:bg-white/90 disabled:opacity-50'
|
||||
'rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
|
||||
'hover:opacity-90 disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
{isSaving ? 'Creating...' : 'Create Category'}
|
||||
|
||||
@@ -50,16 +50,16 @@ export function DataTable<T>({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-lg border border-white/[0.06]">
|
||||
<div className="overflow-x-auto rounded-lg border border-border">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-white/[0.06] bg-white/[0.02]">
|
||||
<tr className="border-b border-border bg-accent">
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
className={cn(
|
||||
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-white/50',
|
||||
col.sortable && 'cursor-pointer select-none hover:text-white',
|
||||
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-muted-foreground',
|
||||
col.sortable && 'cursor-pointer select-none hover:text-foreground',
|
||||
col.className
|
||||
)}
|
||||
onClick={col.sortable ? () => handleSort(col.key) : undefined}
|
||||
@@ -87,10 +87,10 @@ export function DataTable<T>({
|
||||
<tbody>
|
||||
{isLoading ? (
|
||||
Array.from({ length: skeletonRows }).map((_, i) => (
|
||||
<tr key={i} className="border-b border-white/[0.06] last:border-0">
|
||||
<tr key={i} className="border-b border-border last:border-0">
|
||||
{columns.map((col) => (
|
||||
<td key={col.key} className="px-4 py-3">
|
||||
<div className="h-4 w-3/4 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-4 w-3/4 animate-pulse rounded bg-accent" />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
@@ -99,7 +99,7 @@ export function DataTable<T>({
|
||||
<tr>
|
||||
<td colSpan={columns.length} className="px-4 py-12 text-center">
|
||||
{emptyState || (
|
||||
<span className="text-white/40">No data found</span>
|
||||
<span className="text-muted-foreground">No data found</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -107,7 +107,7 @@ export function DataTable<T>({
|
||||
data.map((item) => (
|
||||
<tr
|
||||
key={keyExtractor(item)}
|
||||
className="border-b border-white/[0.06] last:border-0 hover:bg-white/[0.04] transition-colors"
|
||||
className="border-b border-border last:border-0 hover:bg-accent transition-colors"
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<td key={col.key} className={cn('px-4 py-3', col.className)}>
|
||||
|
||||
@@ -68,14 +68,14 @@ export function EditCategoryModal({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
|
||||
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-white">Edit Category</h2>
|
||||
<h2 className="text-lg font-semibold text-foreground">Edit Category</h2>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
disabled={isSaving}
|
||||
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
|
||||
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -92,7 +92,7 @@ export function EditCategoryModal({
|
||||
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-white">
|
||||
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
|
||||
Category Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -105,21 +105,21 @@ export function EditCategoryModal({
|
||||
placeholder="e.g., Network Troubleshooting"
|
||||
required
|
||||
className={cn(
|
||||
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
|
||||
'placeholder:text-white/40',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'disabled:opacity-50'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-white/40">
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{name.length}/100 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Description Field */}
|
||||
<div>
|
||||
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-white">
|
||||
Description <span className="text-white/40">(optional)</span>
|
||||
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
|
||||
Description <span className="text-muted-foreground">(optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="edit-description"
|
||||
@@ -129,9 +129,9 @@ export function EditCategoryModal({
|
||||
rows={3}
|
||||
placeholder="Brief description of this category..."
|
||||
className={cn(
|
||||
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
|
||||
'placeholder:text-white/40',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
|
||||
'disabled:opacity-50'
|
||||
)}
|
||||
/>
|
||||
@@ -144,8 +144,8 @@ export function EditCategoryModal({
|
||||
onClick={handleClose}
|
||||
disabled={isSaving}
|
||||
className={cn(
|
||||
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
|
||||
'hover:bg-white/10 hover:text-white disabled:opacity-50'
|
||||
'rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
Cancel
|
||||
@@ -154,8 +154,8 @@ export function EditCategoryModal({
|
||||
type="submit"
|
||||
disabled={isSaving || !name.trim()}
|
||||
className={cn(
|
||||
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
|
||||
'hover:bg-white/90 disabled:opacity-50'
|
||||
'rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
|
||||
'hover:opacity-90 disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
|
||||
@@ -12,10 +12,10 @@ interface EmptyStateProps {
|
||||
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
|
||||
return (
|
||||
<div className={cn('flex flex-col items-center justify-center py-12 text-center', className)}>
|
||||
{icon && <div className="mb-4 text-white/50">{icon}</div>}
|
||||
<h3 className="text-lg font-semibold text-white">{title}</h3>
|
||||
{icon && <div className="mb-4 text-muted-foreground">{icon}</div>}
|
||||
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
||||
{description && (
|
||||
<p className="mt-1 max-w-sm text-sm text-white/40">{description}</p>
|
||||
<p className="mt-1 max-w-sm text-sm text-muted-foreground">{description}</p>
|
||||
)}
|
||||
{action && <div className="mt-4">{action}</div>}
|
||||
</div>
|
||||
|
||||
@@ -36,20 +36,20 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 pt-4">
|
||||
<span className="text-sm text-white/40">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Showing {start}-{end} of {total}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={page <= 1}
|
||||
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
|
||||
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</button>
|
||||
{getPageNumbers().map((p, i) =>
|
||||
p === 'ellipsis' ? (
|
||||
<span key={`e${i}`} className="px-1 text-white/40">...</span>
|
||||
<span key={`e${i}`} className="px-1 text-muted-foreground">...</span>
|
||||
) : (
|
||||
<button
|
||||
key={p}
|
||||
@@ -58,8 +58,8 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
||||
btnBase,
|
||||
'px-2',
|
||||
p === page
|
||||
? 'bg-white text-black'
|
||||
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
|
||||
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
{p}
|
||||
@@ -69,7 +69,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
||||
<button
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
|
||||
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -40,21 +40,21 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/50" />
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
'h-9 w-full rounded-md border border-white/10 bg-black/50 pl-9 pr-8 text-sm text-white',
|
||||
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
|
||||
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
{localValue && (
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-white/50 hover:text-white"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,7 @@ const variantClasses: Record<BadgeVariant, string> = {
|
||||
success: 'bg-emerald-400/10 text-emerald-400',
|
||||
destructive: 'bg-red-400/10 text-red-400',
|
||||
warning: 'bg-yellow-400/10 text-yellow-400',
|
||||
default: 'bg-white/10 text-white/70',
|
||||
default: 'bg-accent text-muted-foreground',
|
||||
}
|
||||
|
||||
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {
|
||||
|
||||
Reference in New Issue
Block a user