@otfdashkit/ui 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +28 -41
- package/dist/index.js +972 -655
- package/dist/index.mjs +826 -525
- package/package.json +5 -4
- package/src/advanced/BulkActions/BulkActions.tsx +2 -2
- package/src/advanced/CommandBar/CommandBar.tsx +2 -2
- package/src/advanced/Filters/Filters.tsx +6 -6
- package/src/advanced/Kanban/Kanban.tsx +1 -1
- package/src/advanced/ToggleButton/ToggleButton.tsx +1 -1
- package/src/advanced/bulk-actions.tsx +1 -46
- package/src/advanced/command-bar.tsx +1 -115
- package/src/advanced/filters.tsx +1 -103
- package/src/advanced/kanban.tsx +1 -95
- package/src/advanced/toggle-button.tsx +1 -78
- package/src/blocks/ChatDetail/ChatDetail.tsx +4 -4
- package/src/blocks/FeedbackModal/FeedbackModal.tsx +3 -3
- package/src/blocks/FileCards/FileCards.tsx +2 -2
- package/src/blocks/FilesList/FilesList.tsx +3 -3
- package/src/blocks/IntegrationCard/IntegrationCard.tsx +3 -3
- package/src/blocks/InviteModal/InviteModal.tsx +7 -7
- package/src/blocks/ManageTagsModal/ManageTagsModal.tsx +4 -4
- package/src/blocks/MessagesCard/MessagesCard.tsx +3 -3
- package/src/blocks/MetricCard/MetricCard.tsx +3 -3
- package/src/blocks/NotificationSettings/NotificationSettings.tsx +4 -4
- package/src/blocks/OrganizationMenu/OrganizationMenu.tsx +30 -11
- package/src/blocks/RolesMenu/RolesMenu.tsx +4 -4
- package/src/blocks/SelectUsersModal/SelectUsersModal.tsx +5 -5
- package/src/blocks/SidebarLayoutDashboard/SidebarLayoutDashboard.tsx +5 -5
- package/src/blocks/SidebarLayoutGroups/SidebarLayoutGroups.tsx +2 -2
- package/src/blocks/SidebarLayoutMinimal/SidebarLayoutMinimal.tsx +3 -3
- package/src/blocks/SidebarLayoutSearch/SidebarLayoutSearch.tsx +3 -3
- package/src/blocks/SidebarLayoutUser/SidebarLayoutUser.tsx +4 -4
- package/src/blocks/SortableTaskList/SortableTaskList.tsx +3 -3
- package/src/blocks/StackedLayoutBranded/StackedLayoutBranded.tsx +4 -4
- package/src/blocks/StackedLayoutTabs/StackedLayoutTabs.tsx +4 -4
- package/src/blocks/TaskCard/TaskCard.tsx +4 -4
- package/src/blocks/UserMenu/UserMenu.tsx +3 -3
- package/src/blocks/WorkspaceMembers/WorkspaceMembers.tsx +5 -5
- package/src/blocks/chat-detail.tsx +1 -73
- package/src/blocks/feedback-modal.tsx +1 -68
- package/src/blocks/file-cards.tsx +1 -56
- package/src/blocks/files-list.tsx +1 -72
- package/src/blocks/integration-card.tsx +1 -50
- package/src/blocks/invite-modal.tsx +1 -69
- package/src/blocks/manage-tags-modal.tsx +1 -63
- package/src/blocks/messages-card.tsx +1 -59
- package/src/blocks/metric-card.tsx +1 -79
- package/src/blocks/notification-settings.tsx +1 -61
- package/src/blocks/organization-menu.tsx +1 -85
- package/src/blocks/roles-menu.tsx +1 -61
- package/src/blocks/select-users-modal.tsx +1 -64
- package/src/blocks/sidebar-layout-dashboard.tsx +1 -97
- package/src/blocks/sidebar-layout-groups.tsx +1 -79
- package/src/blocks/sidebar-layout-minimal.tsx +1 -73
- package/src/blocks/sidebar-layout-search.tsx +1 -79
- package/src/blocks/sidebar-layout-user.tsx +1 -82
- package/src/blocks/sortable-task-list.tsx +1 -70
- package/src/blocks/stacked-layout-branded.tsx +1 -64
- package/src/blocks/stacked-layout-tabs.tsx +1 -80
- package/src/blocks/task-card.tsx +1 -88
- package/src/blocks/user-menu.tsx +1 -62
- package/src/blocks/workspace-members.tsx +1 -61
- package/src/charts/sparkline.tsx +1 -1
- package/src/components/DataGrid/DataGrid.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +4 -4
- package/src/components/EmptyState/EmptyState.tsx +6 -22
- package/src/components/Persona/Persona.tsx +1 -1
- package/src/components/Stat/Stat.tsx +1 -1
- package/src/components/Stepper/Stepper.tsx +13 -2
- package/src/components/banner.tsx +1 -203
- package/src/components/breadcrumb.tsx +1 -58
- package/src/components/command-item.tsx +1 -36
- package/src/components/data-grid.tsx +1 -125
- package/src/components/data-table.tsx +1 -139
- package/src/components/empty-state.tsx +1 -31
- package/src/components/hotkeys.tsx +1 -31
- package/src/components/icon-badge.tsx +1 -35
- package/src/components/loading-overlay.tsx +1 -38
- package/src/components/persona.tsx +1 -38
- package/src/components/property-list.tsx +1 -29
- package/src/components/stat.tsx +1 -51
- package/src/components/stepper.tsx +1 -69
- package/src/components/structured-list.tsx +1 -51
- package/src/components/timeline.tsx +1 -69
- package/src/components/toaster.tsx +1 -42
- package/src/data/DataList.tsx +1 -1
- package/src/engagement/Tour/Tour.tsx +1 -1
- package/src/engagement/beacon.tsx +1 -50
- package/src/engagement/tour.tsx +1 -106
- package/src/forms/ArrayField/ArrayField.tsx +2 -2
- package/src/forms/AutoForm/AutoForm.tsx +9 -8
- package/src/forms/DatePicker/DatePicker.tsx +3 -3
- package/src/forms/FloatingLabelInput/FloatingLabelInput.tsx +104 -0
- package/src/forms/FloatingLabelInput/index.ts +1 -0
- package/src/forms/PasswordInput/PasswordInput.tsx +2 -2
- package/src/forms/SearchInput/SearchInput.tsx +2 -2
- package/src/forms/StepForm/StepForm.tsx +2 -2
- package/src/forms/array-field.tsx +1 -61
- package/src/forms/auto-form.tsx +1 -133
- package/src/forms/date-picker.tsx +1 -109
- package/src/forms/field.tsx +1 -62
- package/src/forms/file-upload.tsx +1 -101
- package/src/forms/floating-label-input.tsx +1 -0
- package/src/forms/form.tsx +1 -57
- package/src/forms/object-field.tsx +1 -53
- package/src/forms/password-input.tsx +1 -54
- package/src/forms/search-input.tsx +1 -64
- package/src/forms/step-form.tsx +1 -113
- package/src/index.ts +11 -3
- package/src/primitives/Button/Button.tsx +18 -0
- package/src/primitives/cart-line-item.tsx +81 -0
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/comparison.tsx +1 -1
- package/src/primitives/empty-cart.tsx +52 -0
- package/src/primitives/price-tag.tsx +62 -0
- package/src/primitives/qr-code.tsx +5 -1
- package/src/primitives/quantity-stepper.tsx +92 -0
- package/src/primitives/variant-picker.tsx +74 -0
- package/src/themes/base.css +18 -0
package/src/blocks/task-card.tsx
CHANGED
|
@@ -1,88 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Calendar } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
import { Card, CardContent } from '../primitives/card'
|
|
5
|
-
import { Avatar, AvatarFallback } from '../primitives/avatar'
|
|
6
|
-
import { Badge } from '../primitives/badge'
|
|
7
|
-
|
|
8
|
-
const PRIORITY_COLOR: Record<string, string> = {
|
|
9
|
-
High: 'bg-red-500/10 text-red-600 border-red-200',
|
|
10
|
-
Medium: 'bg-yellow-500/10 text-yellow-600 border-yellow-200',
|
|
11
|
-
Low: 'bg-green-500/10 text-green-600 border-green-200',
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TaskCardProps {
|
|
15
|
-
title?: string
|
|
16
|
-
description?: string
|
|
17
|
-
assigneeInitials?: string
|
|
18
|
-
dueDate?: string
|
|
19
|
-
priority?: 'High' | 'Medium' | 'Low'
|
|
20
|
-
className?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function TaskCard({
|
|
24
|
-
title = 'Redesign onboarding flow',
|
|
25
|
-
description = 'Update the user onboarding experience with the new design language.',
|
|
26
|
-
assigneeInitials = 'AC',
|
|
27
|
-
dueDate = 'Mar 20',
|
|
28
|
-
priority = 'High',
|
|
29
|
-
className,
|
|
30
|
-
}: TaskCardProps) {
|
|
31
|
-
return (
|
|
32
|
-
<Card className={cn('cursor-pointer hover:shadow-md transition-shadow', className)}>
|
|
33
|
-
<CardContent className="p-4 space-y-3">
|
|
34
|
-
<div className="flex items-start justify-between gap-2">
|
|
35
|
-
<p className="text-sm font-semibold leading-tight">{title}</p>
|
|
36
|
-
<span className={cn('text-[10px] px-1.5 py-0.5 rounded-full border font-medium shrink-0', PRIORITY_COLOR[priority])}>
|
|
37
|
-
{priority}
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
40
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))] leading-relaxed line-clamp-2">{description}</p>
|
|
41
|
-
<div className="flex items-center justify-between">
|
|
42
|
-
<Avatar className="h-6 w-6"><AvatarFallback className="text-[10px]">{assigneeInitials}</AvatarFallback></Avatar>
|
|
43
|
-
<div className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))]">
|
|
44
|
-
<Calendar className="h-3 w-3" />{dueDate}
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</CardContent>
|
|
48
|
-
</Card>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const LABEL_COLORS = ['bg-blue-100 text-blue-700', 'bg-purple-100 text-purple-700', 'bg-green-100 text-green-700']
|
|
53
|
-
|
|
54
|
-
export interface TaskCardWithLabelsProps extends TaskCardProps {
|
|
55
|
-
labels?: string[]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function TaskCardWithLabels({
|
|
59
|
-
labels = ['Design', 'Frontend'],
|
|
60
|
-
...props
|
|
61
|
-
}: TaskCardWithLabelsProps) {
|
|
62
|
-
return (
|
|
63
|
-
<Card className={cn('cursor-pointer hover:shadow-md transition-shadow', props.className)}>
|
|
64
|
-
<CardContent className="p-4 space-y-3">
|
|
65
|
-
<div className="flex items-start justify-between gap-2">
|
|
66
|
-
<p className="text-sm font-semibold leading-tight">{props.title ?? 'Redesign onboarding flow'}</p>
|
|
67
|
-
<span className={cn('text-[10px] px-1.5 py-0.5 rounded-full border font-medium shrink-0', PRIORITY_COLOR[props.priority ?? 'High'])}>
|
|
68
|
-
{props.priority ?? 'High'}
|
|
69
|
-
</span>
|
|
70
|
-
</div>
|
|
71
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))] leading-relaxed line-clamp-2">
|
|
72
|
-
{props.description ?? 'Update the user onboarding experience with the new design language.'}
|
|
73
|
-
</p>
|
|
74
|
-
<div className="flex flex-wrap gap-1">
|
|
75
|
-
{labels.map((l, i) => (
|
|
76
|
-
<span key={l} className={cn('text-[10px] px-1.5 py-0.5 rounded-full font-medium', LABEL_COLORS[i % LABEL_COLORS.length])}>{l}</span>
|
|
77
|
-
))}
|
|
78
|
-
</div>
|
|
79
|
-
<div className="flex items-center justify-between">
|
|
80
|
-
<Avatar className="h-6 w-6"><AvatarFallback className="text-[10px]">{props.assigneeInitials ?? 'AC'}</AvatarFallback></Avatar>
|
|
81
|
-
<div className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))]">
|
|
82
|
-
<Calendar className="h-3 w-3" />{props.dueDate ?? 'Mar 20'}
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</CardContent>
|
|
86
|
-
</Card>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
1
|
+
export * from './TaskCard'
|
package/src/blocks/user-menu.tsx
CHANGED
|
@@ -1,62 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { ChevronDown, User, Settings, CreditCard, LogOut } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
import { Avatar, AvatarFallback } from '../primitives/avatar'
|
|
5
|
-
import {
|
|
6
|
-
DropdownMenu,
|
|
7
|
-
DropdownMenuContent,
|
|
8
|
-
DropdownMenuItem,
|
|
9
|
-
DropdownMenuLabel,
|
|
10
|
-
DropdownMenuSeparator,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
} from '../primitives/dropdown-menu'
|
|
13
|
-
|
|
14
|
-
export interface UserMenuProps {
|
|
15
|
-
name?: string
|
|
16
|
-
email?: string
|
|
17
|
-
initials?: string
|
|
18
|
-
onProfile?: () => void
|
|
19
|
-
onSettings?: () => void
|
|
20
|
-
onBilling?: () => void
|
|
21
|
-
onSignOut?: () => void
|
|
22
|
-
className?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function UserMenu({
|
|
26
|
-
name = 'Jane Doe',
|
|
27
|
-
email = 'jane@acme.com',
|
|
28
|
-
initials = 'JD',
|
|
29
|
-
onProfile,
|
|
30
|
-
onSettings,
|
|
31
|
-
onBilling,
|
|
32
|
-
onSignOut,
|
|
33
|
-
className,
|
|
34
|
-
}: UserMenuProps) {
|
|
35
|
-
return (
|
|
36
|
-
<DropdownMenu>
|
|
37
|
-
<DropdownMenuTrigger asChild>
|
|
38
|
-
<button className={cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-[hsl(var(--accent))] focus:outline-none', className)}>
|
|
39
|
-
<Avatar className="h-7 w-7">
|
|
40
|
-
<AvatarFallback className="text-xs">{initials}</AvatarFallback>
|
|
41
|
-
</Avatar>
|
|
42
|
-
<span className="font-medium max-w-[120px] truncate">{name}</span>
|
|
43
|
-
<ChevronDown className="h-3.5 w-3.5 text-[hsl(var(--muted-foreground))]" />
|
|
44
|
-
</button>
|
|
45
|
-
</DropdownMenuTrigger>
|
|
46
|
-
<DropdownMenuContent align="end" className="w-52">
|
|
47
|
-
<DropdownMenuLabel className="font-normal">
|
|
48
|
-
<p className="text-sm font-medium">{name}</p>
|
|
49
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))]">{email}</p>
|
|
50
|
-
</DropdownMenuLabel>
|
|
51
|
-
<DropdownMenuSeparator />
|
|
52
|
-
<DropdownMenuItem onClick={onProfile}><User className="h-4 w-4" />Profile</DropdownMenuItem>
|
|
53
|
-
<DropdownMenuItem onClick={onSettings}><Settings className="h-4 w-4" />Account Settings</DropdownMenuItem>
|
|
54
|
-
<DropdownMenuItem onClick={onBilling}><CreditCard className="h-4 w-4" />Billing</DropdownMenuItem>
|
|
55
|
-
<DropdownMenuSeparator />
|
|
56
|
-
<DropdownMenuItem onClick={onSignOut} className="text-[hsl(var(--destructive))] focus:text-[hsl(var(--destructive))]">
|
|
57
|
-
<LogOut className="h-4 w-4" />Sign out
|
|
58
|
-
</DropdownMenuItem>
|
|
59
|
-
</DropdownMenuContent>
|
|
60
|
-
</DropdownMenu>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
1
|
+
export * from './UserMenu'
|
|
@@ -1,61 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Search, X } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
import { Input } from '../primitives/input'
|
|
5
|
-
import { Avatar, AvatarFallback } from '../primitives/avatar'
|
|
6
|
-
import { Badge } from '../primitives/badge'
|
|
7
|
-
import { Button } from '../primitives/button'
|
|
8
|
-
|
|
9
|
-
const MEMBERS = [
|
|
10
|
-
{ id: '1', name: 'Jane Doe', email: 'jane@acme.com', role: 'Owner', initials: 'JD' },
|
|
11
|
-
{ id: '2', name: 'Bob Smith', email: 'bob@acme.com', role: 'Admin', initials: 'BS' },
|
|
12
|
-
{ id: '3', name: 'Alice Chen', email: 'alice@acme.com', role: 'Member', initials: 'AC' },
|
|
13
|
-
{ id: '4', name: 'Tom Ray', email: 'tom@acme.com', role: 'Member', initials: 'TR' },
|
|
14
|
-
{ id: '5', name: 'Sara Kim', email: 'sara@acme.com', role: 'Viewer', initials: 'SK' },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
const roleVariant = (role: string): 'default' | 'secondary' | 'outline' =>
|
|
18
|
-
role === 'Owner' ? 'default' : role === 'Admin' ? 'secondary' : 'outline'
|
|
19
|
-
|
|
20
|
-
export interface WorkspaceMembersProps { className?: string }
|
|
21
|
-
|
|
22
|
-
export function WorkspaceMembers({ className }: WorkspaceMembersProps) {
|
|
23
|
-
const [query, setQuery] = useState('')
|
|
24
|
-
const [members, setMembers] = useState(MEMBERS)
|
|
25
|
-
const filtered = members.filter(m =>
|
|
26
|
-
m.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
27
|
-
m.email.toLowerCase().includes(query.toLowerCase())
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className={cn('rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))]', className)}>
|
|
32
|
-
<div className="flex items-center justify-between p-4 border-b border-[hsl(var(--border))]">
|
|
33
|
-
<div>
|
|
34
|
-
<h2 className="text-sm font-semibold">Members</h2>
|
|
35
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))]">{members.length} members</p>
|
|
36
|
-
</div>
|
|
37
|
-
<div className="relative">
|
|
38
|
-
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[hsl(var(--muted-foreground))]" />
|
|
39
|
-
<Input className="h-8 pl-8 text-xs w-48" placeholder="Search members..." value={query} onChange={e => setQuery(e.target.value)} />
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
<div className="divide-y divide-[hsl(var(--border))]">
|
|
43
|
-
{filtered.map(m => (
|
|
44
|
-
<div key={m.id} className="flex items-center gap-3 px-4 py-3">
|
|
45
|
-
<Avatar className="h-8 w-8"><AvatarFallback className="text-xs">{m.initials}</AvatarFallback></Avatar>
|
|
46
|
-
<div className="flex-1 min-w-0">
|
|
47
|
-
<p className="text-sm font-medium truncate">{m.name}</p>
|
|
48
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))] truncate">{m.email}</p>
|
|
49
|
-
</div>
|
|
50
|
-
<Badge variant={roleVariant(m.role)} className="text-[10px] px-1.5 py-0.5 shrink-0">{m.role}</Badge>
|
|
51
|
-
{m.role !== 'Owner' && (
|
|
52
|
-
<Button size="icon" variant="ghost" className="h-7 w-7 shrink-0" onClick={() => setMembers(ms => ms.filter(x => x.id !== m.id))}>
|
|
53
|
-
<X className="h-3.5 w-3.5" />
|
|
54
|
-
</Button>
|
|
55
|
-
)}
|
|
56
|
-
</div>
|
|
57
|
-
))}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
1
|
+
export * from './WorkspaceMembers'
|
package/src/charts/sparkline.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export function Sparkline({
|
|
|
38
38
|
const max = Math.max(...data)
|
|
39
39
|
const range = max - min || 1
|
|
40
40
|
const pad = 2
|
|
41
|
-
const lastY = pad + (1 - (data
|
|
41
|
+
const lastY = pad + (1 - ((data.at(-1) ?? 0) - min) / range) * (height - pad * 2)
|
|
42
42
|
|
|
43
43
|
const areaPath = type === 'area'
|
|
44
44
|
? `${path} L ${width - pad},${height - pad} L ${pad},${height - pad} Z`
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ColumnResizeMode,
|
|
9
9
|
} from '@tanstack/react-table'
|
|
10
10
|
import { cn } from '../utils/cn'
|
|
11
|
-
import { Checkbox } from '
|
|
11
|
+
import { Checkbox } from '../../primitives/checkbox'
|
|
12
12
|
|
|
13
13
|
interface DataGridProps<T extends object> {
|
|
14
14
|
columns: ColumnDef<T, any>[]
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
} from '@tanstack/react-table'
|
|
12
12
|
import { ArrowUpDown, ArrowUp, ArrowDown, ChevronLeft, ChevronRight } from 'lucide-react'
|
|
13
13
|
import { cn } from '../utils/cn'
|
|
14
|
-
import { Button } from '
|
|
15
|
-
import { Input } from '
|
|
16
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '
|
|
17
|
-
import { Skeleton } from '
|
|
14
|
+
import { Button } from '../../primitives/button'
|
|
15
|
+
import { Input } from '../../primitives/input'
|
|
16
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../primitives/select'
|
|
17
|
+
import { Skeleton } from '../../primitives/skeleton'
|
|
18
18
|
|
|
19
19
|
interface DataTableProps<T> {
|
|
20
20
|
columns: ColumnDef<T>[]
|
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { cn } from '../../utils/cn'
|
|
3
|
-
import { Button } from '../../primitives/
|
|
3
|
+
import { Button } from '../../primitives/button'
|
|
4
4
|
|
|
5
5
|
export const EmptyStateIcon = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
6
|
({ className, ...props }, ref) => (
|
|
7
|
-
<div
|
|
8
|
-
ref={ref}
|
|
9
|
-
data-slot="empty-state-icon"
|
|
10
|
-
className={cn(
|
|
11
|
-
'flex h-12 w-12 items-center justify-center rounded-full',
|
|
12
|
-
'bg-muted text-muted-foreground',
|
|
13
|
-
className
|
|
14
|
-
)}
|
|
15
|
-
{...props}
|
|
16
|
-
/>
|
|
7
|
+
<div ref={ref} className={cn('flex h-12 w-12 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--muted-foreground))]', className)} {...props} />
|
|
17
8
|
)
|
|
18
9
|
)
|
|
19
10
|
EmptyStateIcon.displayName = 'EmptyStateIcon'
|
|
20
11
|
|
|
21
|
-
|
|
12
|
+
interface EmptyStateProps {
|
|
22
13
|
icon?: React.ReactNode
|
|
23
14
|
title: string
|
|
24
15
|
description?: string
|
|
@@ -62,23 +53,16 @@ export function EmptyState({
|
|
|
62
53
|
className,
|
|
63
54
|
}: EmptyStateProps) {
|
|
64
55
|
return (
|
|
65
|
-
<div
|
|
66
|
-
data-slot="empty-state"
|
|
67
|
-
className={cn(
|
|
68
|
-
'flex flex-col items-center justify-center text-center',
|
|
69
|
-
sizeMap[size],
|
|
70
|
-
className
|
|
71
|
-
)}
|
|
72
|
-
>
|
|
56
|
+
<div className={cn('flex flex-col items-center justify-center text-center', sizeMap[size], className)}>
|
|
73
57
|
{icon && (
|
|
74
58
|
<EmptyStateIcon className={iconSizeMap[size]}>
|
|
75
59
|
{icon}
|
|
76
60
|
</EmptyStateIcon>
|
|
77
61
|
)}
|
|
78
62
|
<div className="space-y-1 max-w-sm">
|
|
79
|
-
<h3 className={cn(titleSizeMap[size], 'text-foreground')}>{title}</h3>
|
|
63
|
+
<h3 className={cn(titleSizeMap[size], 'text-[hsl(var(--foreground))]')}>{title}</h3>
|
|
80
64
|
{description && (
|
|
81
|
-
<p className={cn(descSizeMap[size], 'text-muted-foreground')}>
|
|
65
|
+
<p className={cn(descSizeMap[size], 'text-[hsl(var(--muted-foreground))]')}>
|
|
82
66
|
{description}
|
|
83
67
|
</p>
|
|
84
68
|
)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { cn } from '../utils/cn'
|
|
3
|
-
import { Avatar, AvatarFallback, AvatarImage } from '
|
|
3
|
+
import { Avatar, AvatarFallback, AvatarImage } from '../../primitives/avatar'
|
|
4
4
|
|
|
5
5
|
const sizeMap = {
|
|
6
6
|
sm: { avatar: 'h-7 w-7', name: 'text-sm', subtitle: 'text-xs' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
3
3
|
import { cn } from '../utils/cn'
|
|
4
|
-
import { Card, CardContent } from '
|
|
4
|
+
import { Card, CardContent } from '../../primitives/card'
|
|
5
5
|
|
|
6
6
|
interface StatProps {
|
|
7
7
|
label: string
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Check } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
4
|
|
|
5
5
|
interface StepDef { title: string; description?: string }
|
|
6
6
|
|
|
@@ -40,9 +40,20 @@ interface StepperProps {
|
|
|
40
40
|
currentStep: number
|
|
41
41
|
steps: StepDef[]
|
|
42
42
|
className?: string
|
|
43
|
+
/** 'horizontal' (default) | 'vertical' — vertical stacks steps as rows */
|
|
44
|
+
direction?: 'horizontal' | 'vertical'
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
export function Stepper({ currentStep, steps, className }: StepperProps) {
|
|
47
|
+
export function Stepper({ currentStep, steps, className, direction = 'horizontal' }: StepperProps) {
|
|
48
|
+
if (direction === 'vertical' || (className && className.includes('flex-col'))) {
|
|
49
|
+
return (
|
|
50
|
+
<div className={cn('flex flex-col gap-0', className?.replace('flex-col', ''))}>
|
|
51
|
+
{steps.map((step, i) => (
|
|
52
|
+
<StepperStep key={i} {...step} index={i} current={currentStep} total={steps.length} />
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
46
57
|
return (
|
|
47
58
|
<div className={cn('flex gap-0', className)}>
|
|
48
59
|
{steps.map((step, i) => (
|
|
@@ -1,203 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Info, CheckCircle2, AlertTriangle, XCircle, X, ArrowRight } from 'lucide-react'
|
|
3
|
-
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
-
import { cn } from '../utils/cn'
|
|
5
|
-
|
|
6
|
-
// ─── Variant config ──────────────────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
const bannerVariants = cva(
|
|
9
|
-
'relative flex items-start gap-3 rounded-lg border px-4 py-3.5 text-sm transition-all',
|
|
10
|
-
{
|
|
11
|
-
variants: {
|
|
12
|
-
variant: {
|
|
13
|
-
info: 'border-blue-200/80 bg-blue-50/60 dark:border-blue-800/60 dark:bg-blue-950/30',
|
|
14
|
-
success: 'border-green-200/80 bg-green-50/60 dark:border-green-800/60 dark:bg-green-950/30',
|
|
15
|
-
warning: 'border-yellow-200/80 bg-yellow-50/60 dark:border-yellow-800/60 dark:bg-yellow-950/30',
|
|
16
|
-
error: 'border-red-200/80 bg-red-50/60 dark:border-red-800/60 dark:bg-red-950/30',
|
|
17
|
-
neutral: 'border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.5)]',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
defaultVariants: { variant: 'info' },
|
|
21
|
-
}
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
const accentVariants = cva('absolute left-0 top-[12%] bottom-[12%] w-[3px] rounded-full', {
|
|
25
|
-
variants: {
|
|
26
|
-
variant: {
|
|
27
|
-
info: 'bg-blue-500',
|
|
28
|
-
success: 'bg-green-500',
|
|
29
|
-
warning: 'bg-yellow-500',
|
|
30
|
-
error: 'bg-red-500',
|
|
31
|
-
neutral: 'bg-[hsl(var(--foreground)/0.3)]',
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
defaultVariants: { variant: 'info' },
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const iconVariants = cva('mt-0.5 h-4 w-4 shrink-0', {
|
|
38
|
-
variants: {
|
|
39
|
-
variant: {
|
|
40
|
-
info: 'text-blue-500 dark:text-blue-400',
|
|
41
|
-
success: 'text-green-500 dark:text-green-400',
|
|
42
|
-
warning: 'text-yellow-500 dark:text-yellow-400',
|
|
43
|
-
error: 'text-red-500 dark:text-red-400',
|
|
44
|
-
neutral: 'text-[hsl(var(--muted-foreground))]',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
defaultVariants: { variant: 'info' },
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const titleVariants = cva('font-semibold leading-snug', {
|
|
51
|
-
variants: {
|
|
52
|
-
variant: {
|
|
53
|
-
info: 'text-blue-900 dark:text-blue-100',
|
|
54
|
-
success: 'text-green-900 dark:text-green-100',
|
|
55
|
-
warning: 'text-yellow-900 dark:text-yellow-100',
|
|
56
|
-
error: 'text-red-900 dark:text-red-100',
|
|
57
|
-
neutral: 'text-[hsl(var(--foreground))]',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
defaultVariants: { variant: 'info' },
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const descriptionVariants = cva('mt-0.5 leading-snug opacity-80', {
|
|
64
|
-
variants: {
|
|
65
|
-
variant: {
|
|
66
|
-
info: 'text-blue-800 dark:text-blue-200',
|
|
67
|
-
success: 'text-green-800 dark:text-green-200',
|
|
68
|
-
warning: 'text-yellow-800 dark:text-yellow-200',
|
|
69
|
-
error: 'text-red-800 dark:text-red-200',
|
|
70
|
-
neutral: 'text-[hsl(var(--muted-foreground))]',
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
defaultVariants: { variant: 'info' },
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
const actionVariants = cva(
|
|
77
|
-
'inline-flex items-center gap-1 text-xs font-medium underline-offset-2 hover:underline transition-opacity hover:opacity-80',
|
|
78
|
-
{
|
|
79
|
-
variants: {
|
|
80
|
-
variant: {
|
|
81
|
-
info: 'text-blue-700 dark:text-blue-300',
|
|
82
|
-
success: 'text-green-700 dark:text-green-300',
|
|
83
|
-
warning: 'text-yellow-700 dark:text-yellow-300',
|
|
84
|
-
error: 'text-red-700 dark:text-red-300',
|
|
85
|
-
neutral: 'text-[hsl(var(--foreground))]',
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
defaultVariants: { variant: 'info' },
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// ─── Icons ───────────────────────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
const ICONS: Record<NonNullable<BannerVariant>, React.ReactNode> = {
|
|
95
|
-
info: <Info className="h-4 w-4" />,
|
|
96
|
-
success: <CheckCircle2 className="h-4 w-4" />,
|
|
97
|
-
warning: <AlertTriangle className="h-4 w-4" />,
|
|
98
|
-
error: <XCircle className="h-4 w-4" />,
|
|
99
|
-
neutral: <Info className="h-4 w-4" />,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
type BannerVariant = VariantProps<typeof bannerVariants>['variant']
|
|
105
|
-
|
|
106
|
-
export interface BannerAction {
|
|
107
|
-
label: string
|
|
108
|
-
onClick: () => void
|
|
109
|
-
icon?: boolean
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export interface BannerProps extends VariantProps<typeof bannerVariants> {
|
|
113
|
-
title?: string
|
|
114
|
-
description?: string
|
|
115
|
-
dismissible?: boolean
|
|
116
|
-
onDismiss?: () => void
|
|
117
|
-
action?: BannerAction
|
|
118
|
-
icon?: boolean
|
|
119
|
-
className?: string
|
|
120
|
-
children?: React.ReactNode
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ─── Component ───────────────────────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
export function Banner({
|
|
126
|
-
variant = 'info',
|
|
127
|
-
title,
|
|
128
|
-
description,
|
|
129
|
-
dismissible,
|
|
130
|
-
onDismiss,
|
|
131
|
-
action,
|
|
132
|
-
icon = true,
|
|
133
|
-
className,
|
|
134
|
-
children,
|
|
135
|
-
}: BannerProps) {
|
|
136
|
-
const [dismissed, setDismissed] = React.useState(false)
|
|
137
|
-
const [exiting, setExiting] = React.useState(false)
|
|
138
|
-
|
|
139
|
-
const handleDismiss = () => {
|
|
140
|
-
setExiting(true)
|
|
141
|
-
setTimeout(() => {
|
|
142
|
-
setDismissed(true)
|
|
143
|
-
onDismiss?.()
|
|
144
|
-
}, 200)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (dismissed) return null
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<div
|
|
151
|
-
className={cn(
|
|
152
|
-
bannerVariants({ variant }),
|
|
153
|
-
'pl-[18px]',
|
|
154
|
-
exiting && 'opacity-0 translate-x-1 scale-[0.98]',
|
|
155
|
-
'transition-all duration-200',
|
|
156
|
-
className
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
{/* left accent bar */}
|
|
160
|
-
<span className={accentVariants({ variant })} aria-hidden />
|
|
161
|
-
|
|
162
|
-
{/* icon */}
|
|
163
|
-
{icon && (
|
|
164
|
-
<span className={iconVariants({ variant })}>
|
|
165
|
-
{ICONS[variant as NonNullable<BannerVariant>]}
|
|
166
|
-
</span>
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
{/* body */}
|
|
170
|
-
<div className="min-w-0 flex-1 space-y-1">
|
|
171
|
-
{title && (
|
|
172
|
-
<p className={cn(titleVariants({ variant }), 'text-sm')}>{title}</p>
|
|
173
|
-
)}
|
|
174
|
-
{description && (
|
|
175
|
-
<p className={cn(descriptionVariants({ variant }), 'text-sm')}>{description}</p>
|
|
176
|
-
)}
|
|
177
|
-
{children && (
|
|
178
|
-
<div className={cn(descriptionVariants({ variant }), 'text-sm')}>{children}</div>
|
|
179
|
-
)}
|
|
180
|
-
{action && (
|
|
181
|
-
<button
|
|
182
|
-
onClick={action.onClick}
|
|
183
|
-
className={cn(actionVariants({ variant }), 'mt-1.5')}
|
|
184
|
-
>
|
|
185
|
-
{action.label}
|
|
186
|
-
{action.icon !== false && <ArrowRight className="h-3 w-3" />}
|
|
187
|
-
</button>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
{/* dismiss */}
|
|
192
|
-
{dismissible && (
|
|
193
|
-
<button
|
|
194
|
-
onClick={handleDismiss}
|
|
195
|
-
aria-label="Dismiss"
|
|
196
|
-
className="shrink-0 rounded-md p-0.5 text-[hsl(var(--muted-foreground))] opacity-60 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))]"
|
|
197
|
-
>
|
|
198
|
-
<X className="h-4 w-4" />
|
|
199
|
-
</button>
|
|
200
|
-
)}
|
|
201
|
-
</div>
|
|
202
|
-
)
|
|
203
|
-
}
|
|
1
|
+
export * from './Banner/index'
|
|
@@ -1,58 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { ChevronRight } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
|
|
5
|
-
interface BreadcrumbItemProps {
|
|
6
|
-
href?: string
|
|
7
|
-
label: string
|
|
8
|
-
active?: boolean
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function BreadcrumbItem({ href, label, active, className }: BreadcrumbItemProps) {
|
|
13
|
-
return (
|
|
14
|
-
<li className={cn('flex items-center', className)}>
|
|
15
|
-
{href && !active ? (
|
|
16
|
-
<a href={href} className="text-sm text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors">{label}</a>
|
|
17
|
-
) : (
|
|
18
|
-
<span className="text-sm text-[hsl(var(--foreground))] font-medium">{label}</span>
|
|
19
|
-
)}
|
|
20
|
-
</li>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function BreadcrumbPage({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
25
|
-
return (
|
|
26
|
-
<li aria-current="page" className={cn('flex items-center text-sm font-medium text-[hsl(var(--foreground))]', className)}>
|
|
27
|
-
{children}
|
|
28
|
-
</li>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function BreadcrumbSeparator({ className }: { className?: string }) {
|
|
33
|
-
return (
|
|
34
|
-
<li role="presentation" aria-hidden="true" className={cn('flex items-center text-[hsl(var(--muted-foreground))]', className)}>
|
|
35
|
-
<ChevronRight className="h-3.5 w-3.5" />
|
|
36
|
-
</li>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface BreadcrumbProps {
|
|
41
|
-
children: React.ReactNode
|
|
42
|
-
className?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function Breadcrumb({ children, className }: BreadcrumbProps) {
|
|
46
|
-
const items = React.Children.toArray(children)
|
|
47
|
-
const withSeparators: React.ReactNode[] = []
|
|
48
|
-
items.forEach((item, i) => {
|
|
49
|
-
withSeparators.push(item)
|
|
50
|
-
if (i < items.length - 1) withSeparators.push(<BreadcrumbSeparator key={`sep-${i}`} />)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<nav aria-label="breadcrumb">
|
|
55
|
-
<ol className={cn('flex flex-wrap items-center gap-1', className)}>{withSeparators}</ol>
|
|
56
|
-
</nav>
|
|
57
|
-
)
|
|
58
|
-
}
|
|
1
|
+
export * from './Breadcrumb/index'
|
|
@@ -1,36 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
interface CommandItemProps {
|
|
5
|
-
icon?: React.ReactNode
|
|
6
|
-
label: string
|
|
7
|
-
description?: string
|
|
8
|
-
shortcut?: string
|
|
9
|
-
active?: boolean
|
|
10
|
-
onClick?: () => void
|
|
11
|
-
className?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function CommandItem({ icon, label, description, shortcut, active, onClick, className }: CommandItemProps) {
|
|
15
|
-
return (
|
|
16
|
-
<div
|
|
17
|
-
onClick={onClick}
|
|
18
|
-
className={cn(
|
|
19
|
-
'flex items-center gap-3 rounded-md px-3 py-2 cursor-pointer transition-colors',
|
|
20
|
-
active ? 'bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))]' : 'hover:bg-[hsl(var(--accent)/0.6)]',
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
>
|
|
24
|
-
{icon && <span className="shrink-0 text-[hsl(var(--muted-foreground))] h-4 w-4 flex items-center justify-center">{icon}</span>}
|
|
25
|
-
<div className="min-w-0 flex-1">
|
|
26
|
-
<p className="text-sm font-medium truncate">{label}</p>
|
|
27
|
-
{description && <p className="text-xs text-[hsl(var(--muted-foreground))] truncate">{description}</p>}
|
|
28
|
-
</div>
|
|
29
|
-
{shortcut && (
|
|
30
|
-
<kbd className="shrink-0 pointer-events-none inline-flex h-5 items-center gap-1 rounded border border-[hsl(var(--border))] bg-[hsl(var(--muted))] px-1.5 text-[10px] font-medium text-[hsl(var(--muted-foreground))]">
|
|
31
|
-
{shortcut}
|
|
32
|
-
</kbd>
|
|
33
|
-
)}
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
1
|
+
export { CommandItem as CommandListItem } from './CommandItem'
|