@otfdashkit/ui 0.1.1 → 0.1.2
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 +206 -125
- package/dist/index.mjs +194 -124
- 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 +6 -3
- package/src/primitives/Button/Button.tsx +18 -0
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/comparison.tsx +1 -1
- package/src/primitives/qr-code.tsx +5 -1
- package/src/themes/base.css +18 -0
|
@@ -1,139 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
ColumnDef,
|
|
4
|
-
flexRender,
|
|
5
|
-
getCoreRowModel,
|
|
6
|
-
getPaginationRowModel,
|
|
7
|
-
getSortedRowModel,
|
|
8
|
-
getFilteredRowModel,
|
|
9
|
-
SortingState,
|
|
10
|
-
useReactTable,
|
|
11
|
-
} from '@tanstack/react-table'
|
|
12
|
-
import { ArrowUpDown, ArrowUp, ArrowDown, ChevronLeft, ChevronRight } from 'lucide-react'
|
|
13
|
-
import { cn } from '../utils/cn'
|
|
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
|
-
|
|
19
|
-
interface DataTableProps<T> {
|
|
20
|
-
columns: ColumnDef<T>[]
|
|
21
|
-
data: T[]
|
|
22
|
-
pageSize?: number
|
|
23
|
-
searchable?: boolean
|
|
24
|
-
searchColumn?: string
|
|
25
|
-
loading?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function DataTableColumnHeader({ column, title }: { column: any; title: string }) {
|
|
29
|
-
if (!column.getCanSort()) return <span>{title}</span>
|
|
30
|
-
const sorted = column.getIsSorted()
|
|
31
|
-
return (
|
|
32
|
-
<button
|
|
33
|
-
onClick={() => column.toggleSorting(sorted === 'asc')}
|
|
34
|
-
className="flex items-center gap-1 hover:text-[hsl(var(--foreground))]"
|
|
35
|
-
>
|
|
36
|
-
{title}
|
|
37
|
-
{sorted === 'asc' ? <ArrowUp className="h-3 w-3" /> : sorted === 'desc' ? <ArrowDown className="h-3 w-3" /> : <ArrowUpDown className="h-3 w-3 opacity-50" />}
|
|
38
|
-
</button>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function DataTablePagination({ table }: { table: any }) {
|
|
43
|
-
return (
|
|
44
|
-
<div className="flex items-center justify-between px-2 py-3">
|
|
45
|
-
<span className="text-sm text-[hsl(var(--muted-foreground))]">
|
|
46
|
-
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
|
|
47
|
-
</span>
|
|
48
|
-
<div className="flex items-center gap-2">
|
|
49
|
-
<Select
|
|
50
|
-
value={String(table.getState().pagination.pageSize)}
|
|
51
|
-
onValueChange={(v) => table.setPageSize(Number(v))}
|
|
52
|
-
>
|
|
53
|
-
<SelectTrigger className="h-8 w-[70px]"><SelectValue /></SelectTrigger>
|
|
54
|
-
<SelectContent>
|
|
55
|
-
{[10, 20, 50].map((n) => <SelectItem key={n} value={String(n)}>{n}</SelectItem>)}
|
|
56
|
-
</SelectContent>
|
|
57
|
-
</Select>
|
|
58
|
-
<Button variant="outline" size="icon" className="h-8 w-8" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
|
|
59
|
-
<ChevronLeft className="h-4 w-4" />
|
|
60
|
-
</Button>
|
|
61
|
-
<Button variant="outline" size="icon" className="h-8 w-8" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
|
|
62
|
-
<ChevronRight className="h-4 w-4" />
|
|
63
|
-
</Button>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function DataTableToolbar({ table, searchColumn }: { table: any; searchColumn?: string }) {
|
|
70
|
-
if (!searchColumn) return null
|
|
71
|
-
return (
|
|
72
|
-
<div className="py-3">
|
|
73
|
-
<Input
|
|
74
|
-
placeholder="Search..."
|
|
75
|
-
value={(table.getColumn(searchColumn)?.getFilterValue() as string) ?? ''}
|
|
76
|
-
onChange={(e) => table.getColumn(searchColumn)?.setFilterValue(e.target.value)}
|
|
77
|
-
className="max-w-sm h-8"
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function DataTable<T>({ columns, data, pageSize = 10, searchable, searchColumn, loading }: DataTableProps<T>) {
|
|
84
|
-
const [sorting, setSorting] = React.useState<SortingState>([])
|
|
85
|
-
|
|
86
|
-
const table = useReactTable({
|
|
87
|
-
data,
|
|
88
|
-
columns,
|
|
89
|
-
getCoreRowModel: getCoreRowModel(),
|
|
90
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
91
|
-
getSortedRowModel: getSortedRowModel(),
|
|
92
|
-
getFilteredRowModel: getFilteredRowModel(),
|
|
93
|
-
onSortingChange: setSorting,
|
|
94
|
-
initialState: { pagination: { pageSize } },
|
|
95
|
-
state: { sorting },
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
if (loading) return (
|
|
99
|
-
<div className="space-y-2">
|
|
100
|
-
{Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-10 w-full" />)}
|
|
101
|
-
</div>
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<div className="w-full">
|
|
106
|
-
{searchable && <DataTableToolbar table={table} searchColumn={searchColumn} />}
|
|
107
|
-
<div className="rounded-md border border-[hsl(var(--border))] overflow-hidden">
|
|
108
|
-
<table className="w-full text-sm">
|
|
109
|
-
<thead className="bg-[hsl(var(--muted)/0.4)]">
|
|
110
|
-
{table.getHeaderGroups().map((hg) => (
|
|
111
|
-
<tr key={hg.id}>
|
|
112
|
-
{hg.headers.map((h) => (
|
|
113
|
-
<th key={h.id} className="px-4 py-3 text-left font-medium text-[hsl(var(--muted-foreground))]">
|
|
114
|
-
{h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}
|
|
115
|
-
</th>
|
|
116
|
-
))}
|
|
117
|
-
</tr>
|
|
118
|
-
))}
|
|
119
|
-
</thead>
|
|
120
|
-
<tbody>
|
|
121
|
-
{table.getRowModel().rows.map((row, i) => (
|
|
122
|
-
<tr key={row.id} className={cn('border-t border-[hsl(var(--border))] hover:bg-[hsl(var(--muted)/0.3)] transition-colors', i % 2 === 0 ? '' : 'bg-[hsl(var(--muted)/0.1)]')}>
|
|
123
|
-
{row.getVisibleCells().map((cell) => (
|
|
124
|
-
<td key={cell.id} className="px-4 py-3">
|
|
125
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
126
|
-
</td>
|
|
127
|
-
))}
|
|
128
|
-
</tr>
|
|
129
|
-
))}
|
|
130
|
-
{table.getRowModel().rows.length === 0 && (
|
|
131
|
-
<tr><td colSpan={columns.length} className="px-4 py-8 text-center text-[hsl(var(--muted-foreground))]">No results.</td></tr>
|
|
132
|
-
)}
|
|
133
|
-
</tbody>
|
|
134
|
-
</table>
|
|
135
|
-
</div>
|
|
136
|
-
<DataTablePagination table={table} />
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
1
|
+
export * from './DataTable'
|
|
@@ -1,31 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
import { Button } from '../primitives/button'
|
|
4
|
-
|
|
5
|
-
export const EmptyStateIcon = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
-
({ className, ...props }, ref) => (
|
|
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} />
|
|
8
|
-
)
|
|
9
|
-
)
|
|
10
|
-
EmptyStateIcon.displayName = 'EmptyStateIcon'
|
|
11
|
-
|
|
12
|
-
interface EmptyStateProps {
|
|
13
|
-
icon?: React.ReactNode
|
|
14
|
-
title: string
|
|
15
|
-
description?: string
|
|
16
|
-
action?: { label: string; onClick: () => void }
|
|
17
|
-
className?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
|
|
21
|
-
return (
|
|
22
|
-
<div className={cn('flex flex-col items-center justify-center gap-3 py-12 text-center', className)}>
|
|
23
|
-
{icon && <EmptyStateIcon>{icon}</EmptyStateIcon>}
|
|
24
|
-
<div className="space-y-1">
|
|
25
|
-
<h3 className="font-semibold text-[hsl(var(--foreground))]">{title}</h3>
|
|
26
|
-
{description && <p className="text-sm text-[hsl(var(--muted-foreground))]">{description}</p>}
|
|
27
|
-
</div>
|
|
28
|
-
{action && <Button onClick={action.onClick} className="mt-1">{action.label}</Button>}
|
|
29
|
-
</div>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
1
|
+
export * from './EmptyState'
|
|
@@ -1,31 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
interface HotkeyBadgeProps {
|
|
5
|
-
children: React.ReactNode
|
|
6
|
-
className?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function HotkeyBadge({ children, className }: HotkeyBadgeProps) {
|
|
10
|
-
return (
|
|
11
|
-
<kbd className={cn(
|
|
12
|
-
'inline-flex items-center justify-center rounded border border-[hsl(var(--border))] bg-[hsl(var(--muted))] px-1.5 py-0.5 text-[11px] font-medium text-[hsl(var(--muted-foreground))] shadow-[0_1px_0_rgba(0,0,0,0.1)] min-w-[20px] h-5',
|
|
13
|
-
className
|
|
14
|
-
)}>
|
|
15
|
-
{children}
|
|
16
|
-
</kbd>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface HotkeysProps {
|
|
21
|
-
keys: string[]
|
|
22
|
-
className?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function Hotkeys({ keys, className }: HotkeysProps) {
|
|
26
|
-
return (
|
|
27
|
-
<span className={cn('inline-flex items-center gap-0.5', className)}>
|
|
28
|
-
{keys.map((key, i) => <HotkeyBadge key={i}>{key}</HotkeyBadge>)}
|
|
29
|
-
</span>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
1
|
+
export * from './Hotkeys/index'
|
|
@@ -1,35 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
type IconBadgeColor = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'purple'
|
|
5
|
-
type IconBadgeSize = 'sm' | 'md' | 'lg'
|
|
6
|
-
|
|
7
|
-
const colorMap: Record<IconBadgeColor, string> = {
|
|
8
|
-
default: 'bg-[hsl(var(--muted))] text-[hsl(var(--muted-foreground))]',
|
|
9
|
-
primary: 'bg-[hsl(var(--primary)/0.12)] text-[hsl(var(--primary))]',
|
|
10
|
-
success: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
|
|
11
|
-
warning: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',
|
|
12
|
-
error: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
|
|
13
|
-
purple: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const sizeMap: Record<IconBadgeSize, string> = {
|
|
17
|
-
sm: 'h-7 w-7 [&>*]:h-3.5 [&>*]:w-3.5',
|
|
18
|
-
md: 'h-10 w-10 [&>*]:h-5 [&>*]:w-5',
|
|
19
|
-
lg: 'h-14 w-14 [&>*]:h-7 [&>*]:w-7',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface IconBadgeProps {
|
|
23
|
-
icon: React.ReactNode
|
|
24
|
-
color?: IconBadgeColor
|
|
25
|
-
size?: IconBadgeSize
|
|
26
|
-
className?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function IconBadge({ icon, color = 'default', size = 'md', className }: IconBadgeProps) {
|
|
30
|
-
return (
|
|
31
|
-
<div className={cn('inline-flex items-center justify-center rounded-full shrink-0', colorMap[color], sizeMap[size], className)}>
|
|
32
|
-
{icon}
|
|
33
|
-
</div>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
1
|
+
export * from './IconBadge'
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Loader2 } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
|
|
5
|
-
interface LoadingOverlayProps {
|
|
6
|
-
loading: boolean
|
|
7
|
-
text?: string
|
|
8
|
-
fullPage?: boolean
|
|
9
|
-
children?: React.ReactNode
|
|
10
|
-
className?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function LoadingOverlay({ loading, text, fullPage, children, className }: LoadingOverlayProps) {
|
|
14
|
-
if (fullPage) {
|
|
15
|
-
return loading ? (
|
|
16
|
-
<div className={cn('fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm', className)}>
|
|
17
|
-
<div className="flex flex-col items-center gap-2">
|
|
18
|
-
<Loader2 className="h-8 w-8 animate-spin text-[hsl(var(--primary))]" />
|
|
19
|
-
{text && <p className="text-sm text-[hsl(var(--muted-foreground))]">{text}</p>}
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
) : null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className={cn('relative', className)}>
|
|
27
|
-
{children}
|
|
28
|
-
{loading && (
|
|
29
|
-
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-md bg-[hsl(var(--background)/0.8)] backdrop-blur-[2px]">
|
|
30
|
-
<div className="flex flex-col items-center gap-2">
|
|
31
|
-
<Loader2 className="h-6 w-6 animate-spin text-[hsl(var(--primary))]" />
|
|
32
|
-
{text && <p className="text-xs text-[hsl(var(--muted-foreground))]">{text}</p>}
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
)}
|
|
36
|
-
</div>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
1
|
+
export * from './LoadingOverlay'
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
import { Avatar, AvatarFallback, AvatarImage } from '../primitives/avatar'
|
|
4
|
-
|
|
5
|
-
const sizeMap = {
|
|
6
|
-
sm: { avatar: 'h-7 w-7', name: 'text-sm', subtitle: 'text-xs' },
|
|
7
|
-
md: { avatar: 'h-9 w-9', name: 'text-sm font-medium', subtitle: 'text-xs' },
|
|
8
|
-
lg: { avatar: 'h-12 w-12', name: 'text-base font-medium', subtitle: 'text-sm' },
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface PersonaProps {
|
|
12
|
-
name: string
|
|
13
|
-
subtitle?: string
|
|
14
|
-
src?: string
|
|
15
|
-
size?: 'sm' | 'md' | 'lg'
|
|
16
|
-
badge?: React.ReactNode
|
|
17
|
-
className?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function Persona({ name, subtitle, src, size = 'md', badge, className }: PersonaProps) {
|
|
21
|
-
const s = sizeMap[size]
|
|
22
|
-
const initials = name.split(' ').map((w) => w[0]).slice(0, 2).join('').toUpperCase()
|
|
23
|
-
return (
|
|
24
|
-
<div className={cn('flex items-center gap-2.5', className)}>
|
|
25
|
-
<div className="relative shrink-0">
|
|
26
|
-
<Avatar className={s.avatar}>
|
|
27
|
-
{src && <AvatarImage src={src} alt={name} />}
|
|
28
|
-
<AvatarFallback className="text-xs">{initials}</AvatarFallback>
|
|
29
|
-
</Avatar>
|
|
30
|
-
{badge && <div className="absolute -bottom-0.5 -right-0.5">{badge}</div>}
|
|
31
|
-
</div>
|
|
32
|
-
<div className="min-w-0">
|
|
33
|
-
<p className={cn('truncate text-[hsl(var(--foreground))]', s.name)}>{name}</p>
|
|
34
|
-
{subtitle && <p className={cn('truncate text-[hsl(var(--muted-foreground))]', s.subtitle)}>{subtitle}</p>}
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
1
|
+
export * from './Persona/index'
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
interface PropertyListProps extends React.HTMLAttributes<HTMLDListElement> {}
|
|
5
|
-
|
|
6
|
-
export const PropertyList = React.forwardRef<HTMLDListElement, PropertyListProps>(
|
|
7
|
-
({ className, ...props }, ref) => (
|
|
8
|
-
<dl ref={ref} className={cn('divide-y divide-[hsl(var(--border))]', className)} {...props} />
|
|
9
|
-
)
|
|
10
|
-
)
|
|
11
|
-
PropertyList.displayName = 'PropertyList'
|
|
12
|
-
|
|
13
|
-
interface PropertyItemProps {
|
|
14
|
-
label: string
|
|
15
|
-
value: React.ReactNode
|
|
16
|
-
orientation?: 'horizontal' | 'vertical'
|
|
17
|
-
className?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function PropertyItem({ label, value, orientation = 'horizontal', className }: PropertyItemProps) {
|
|
21
|
-
return (
|
|
22
|
-
<div className={cn('py-3', orientation === 'horizontal' ? 'flex gap-4' : 'space-y-1', className)}>
|
|
23
|
-
<dt className={cn('text-sm font-medium text-[hsl(var(--muted-foreground))] shrink-0', orientation === 'horizontal' && 'w-[40%]')}>
|
|
24
|
-
{label}
|
|
25
|
-
</dt>
|
|
26
|
-
<dd className="text-sm text-[hsl(var(--foreground))] min-w-0">{value}</dd>
|
|
27
|
-
</div>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
1
|
+
export * from './PropertyList'
|
package/src/components/stat.tsx
CHANGED
|
@@ -1,51 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
import { Card, CardContent } from '../primitives/card'
|
|
5
|
-
|
|
6
|
-
interface StatProps {
|
|
7
|
-
label: string
|
|
8
|
-
value: string | number
|
|
9
|
-
trend?: number
|
|
10
|
-
trendLabel?: string
|
|
11
|
-
icon?: React.ReactNode
|
|
12
|
-
description?: string
|
|
13
|
-
className?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Stat({ label, value, trend, trendLabel, icon, description, className }: StatProps) {
|
|
17
|
-
const isPositive = trend !== undefined && trend >= 0
|
|
18
|
-
return (
|
|
19
|
-
<Card className={cn('', className)}>
|
|
20
|
-
<CardContent className="p-5">
|
|
21
|
-
<div className="flex items-start justify-between">
|
|
22
|
-
<div className="space-y-1 min-w-0">
|
|
23
|
-
<p className="text-sm font-medium text-[hsl(var(--muted-foreground))] truncate">{label}</p>
|
|
24
|
-
<p className="text-2xl font-bold text-[hsl(var(--foreground))]">{value}</p>
|
|
25
|
-
</div>
|
|
26
|
-
{icon && <div className="text-[hsl(var(--muted-foreground))] shrink-0 ml-2">{icon}</div>}
|
|
27
|
-
</div>
|
|
28
|
-
{(trend !== undefined || description) && (
|
|
29
|
-
<div className="mt-2 flex items-center gap-1">
|
|
30
|
-
{trend !== undefined && (
|
|
31
|
-
<span className={cn('flex items-center gap-0.5 text-xs font-medium', isPositive ? 'text-green-600' : 'text-red-600')}>
|
|
32
|
-
{isPositive ? <TrendingUp className="h-3 w-3" /> : <TrendingDown className="h-3 w-3" />}
|
|
33
|
-
{Math.abs(trend)}%
|
|
34
|
-
</span>
|
|
35
|
-
)}
|
|
36
|
-
{trendLabel && <span className="text-xs text-[hsl(var(--muted-foreground))]">{trendLabel}</span>}
|
|
37
|
-
{description && !trendLabel && <span className="text-xs text-[hsl(var(--muted-foreground))]">{description}</span>}
|
|
38
|
-
</div>
|
|
39
|
-
)}
|
|
40
|
-
</CardContent>
|
|
41
|
-
</Card>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface StatGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
46
|
-
|
|
47
|
-
export function StatGroup({ className, ...props }: StatGroupProps) {
|
|
48
|
-
return (
|
|
49
|
-
<div className={cn('grid gap-4', className)} style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 200px), 1fr))' }} {...props} />
|
|
50
|
-
)
|
|
51
|
-
}
|
|
1
|
+
export * from './Stat/index'
|
|
@@ -1,69 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Check } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
|
|
5
|
-
interface StepDef { title: string; description?: string }
|
|
6
|
-
|
|
7
|
-
interface StepperStepProps extends StepDef {
|
|
8
|
-
index: number
|
|
9
|
-
current: number
|
|
10
|
-
total: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function StepperStep({ index, current, total, title, description }: StepperStepProps) {
|
|
14
|
-
const done = index < current
|
|
15
|
-
const active = index === current
|
|
16
|
-
const isLast = index === total - 1
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div className="flex items-start gap-3 flex-1 min-w-0">
|
|
20
|
-
<div className="flex flex-col items-center shrink-0">
|
|
21
|
-
<div className={cn(
|
|
22
|
-
'h-8 w-8 rounded-full flex items-center justify-center border-2 text-sm font-semibold transition-colors',
|
|
23
|
-
done && 'bg-[hsl(var(--primary))] border-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]',
|
|
24
|
-
active && 'border-[hsl(var(--primary))] text-[hsl(var(--primary))] bg-transparent',
|
|
25
|
-
!done && !active && 'border-[hsl(var(--muted-foreground)/0.4)] text-[hsl(var(--muted-foreground))] bg-transparent',
|
|
26
|
-
)}>
|
|
27
|
-
{done ? <Check className="h-4 w-4" /> : index + 1}
|
|
28
|
-
</div>
|
|
29
|
-
{!isLast && <div className={cn('w-0.5 flex-1 min-h-[16px] mt-1', done ? 'bg-[hsl(var(--primary))]' : 'bg-[hsl(var(--border))]')} />}
|
|
30
|
-
</div>
|
|
31
|
-
<div className="min-w-0 pb-4">
|
|
32
|
-
<p className={cn('text-sm font-medium', active ? 'text-[hsl(var(--foreground))]' : 'text-[hsl(var(--muted-foreground))]')}>{title}</p>
|
|
33
|
-
{description && <p className="text-xs text-[hsl(var(--muted-foreground))] mt-0.5">{description}</p>}
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface StepperProps {
|
|
40
|
-
currentStep: number
|
|
41
|
-
steps: StepDef[]
|
|
42
|
-
className?: string
|
|
43
|
-
/** 'horizontal' (default) | 'vertical' — vertical stacks steps as rows */
|
|
44
|
-
direction?: 'horizontal' | 'vertical'
|
|
45
|
-
}
|
|
46
|
-
|
|
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
|
-
}
|
|
57
|
-
return (
|
|
58
|
-
<div className={cn('flex gap-0', className)}>
|
|
59
|
-
{steps.map((step, i) => (
|
|
60
|
-
<div key={i} className={cn('flex items-start', i < steps.length - 1 && 'flex-1')}>
|
|
61
|
-
<StepperStep {...step} index={i} current={currentStep} total={steps.length} />
|
|
62
|
-
{i < steps.length - 1 && (
|
|
63
|
-
<div className={cn('flex-1 h-0.5 mt-4 mx-2', i < currentStep ? 'bg-[hsl(var(--primary))]' : 'bg-[hsl(var(--border))]')} />
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
))}
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
1
|
+
export * from './Stepper/index'
|
|
@@ -1,51 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
interface StructuredListSectionProps {
|
|
5
|
-
title?: string
|
|
6
|
-
children: React.ReactNode
|
|
7
|
-
className?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function StructuredListSection({ title, children, className }: StructuredListSectionProps) {
|
|
11
|
-
return (
|
|
12
|
-
<div className={cn('', className)}>
|
|
13
|
-
{title && <p className="px-4 pb-1 pt-4 text-xs font-semibold uppercase tracking-wider text-[hsl(var(--muted-foreground))]">{title}</p>}
|
|
14
|
-
<div className="rounded-md border border-[hsl(var(--border))] overflow-hidden divide-y divide-[hsl(var(--border))]">
|
|
15
|
-
{children}
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface StructuredListItemProps {
|
|
22
|
-
left?: React.ReactNode
|
|
23
|
-
title: string
|
|
24
|
-
description?: string
|
|
25
|
-
right?: React.ReactNode
|
|
26
|
-
onClick?: () => void
|
|
27
|
-
className?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function StructuredListItem({ left, title, description, right, onClick, className }: StructuredListItemProps) {
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
onClick={onClick}
|
|
34
|
-
className={cn('flex items-center gap-3 px-4 py-3 bg-[hsl(var(--card))]', onClick && 'cursor-pointer hover:bg-[hsl(var(--muted)/0.5)] transition-colors', className)}
|
|
35
|
-
>
|
|
36
|
-
{left && <div className="shrink-0">{left}</div>}
|
|
37
|
-
<div className="min-w-0 flex-1">
|
|
38
|
-
<p className="text-sm font-medium text-[hsl(var(--foreground))] truncate">{title}</p>
|
|
39
|
-
{description && <p className="text-xs text-[hsl(var(--muted-foreground))] truncate">{description}</p>}
|
|
40
|
-
</div>
|
|
41
|
-
{right && <div className="shrink-0 text-[hsl(var(--muted-foreground))]">{right}</div>}
|
|
42
|
-
</div>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface StructuredListProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
47
|
-
|
|
48
|
-
export const StructuredList = React.forwardRef<HTMLDivElement, StructuredListProps>(
|
|
49
|
-
({ className, ...props }, ref) => <div ref={ref} className={cn('space-y-4', className)} {...props} />
|
|
50
|
-
)
|
|
51
|
-
StructuredList.displayName = 'StructuredList'
|
|
1
|
+
export * from './StructuredList'
|
|
@@ -1,69 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
type TimelineVariant = 'default' | 'success' | 'error' | 'warning'
|
|
5
|
-
|
|
6
|
-
const dotColors: Record<TimelineVariant, string> = {
|
|
7
|
-
default: 'bg-[hsl(var(--primary))]',
|
|
8
|
-
success: 'bg-green-500',
|
|
9
|
-
error: 'bg-red-500',
|
|
10
|
-
warning: 'bg-yellow-500',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface TimelineContentProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
14
|
-
export const TimelineContent = React.forwardRef<HTMLDivElement, TimelineContentProps>(
|
|
15
|
-
({ className, ...props }, ref) => <div ref={ref} className={cn('pb-1', className)} {...props} />
|
|
16
|
-
)
|
|
17
|
-
TimelineContent.displayName = 'TimelineContent'
|
|
18
|
-
|
|
19
|
-
export function TimelineConnector({ className }: { className?: string }) {
|
|
20
|
-
return <div className={cn('absolute left-[7px] top-5 bottom-0 w-0.5 bg-[hsl(var(--border))]', className)} />
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface TimelineItemProps {
|
|
24
|
-
icon?: React.ReactNode
|
|
25
|
-
title: string
|
|
26
|
-
description?: string
|
|
27
|
-
timestamp?: string
|
|
28
|
-
variant?: TimelineVariant
|
|
29
|
-
isLast?: boolean
|
|
30
|
-
className?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function TimelineItem({ icon, title, description, timestamp, variant = 'default', isLast, className }: TimelineItemProps) {
|
|
34
|
-
return (
|
|
35
|
-
<div className={cn('relative flex gap-4', !isLast && 'pb-6', className)}>
|
|
36
|
-
<div className="relative flex-none">
|
|
37
|
-
<div className={cn('h-4 w-4 rounded-full border-2 border-[hsl(var(--background))] flex items-center justify-center mt-0.5', dotColors[variant])}>
|
|
38
|
-
{icon && <span className="text-white text-[8px]">{icon}</span>}
|
|
39
|
-
</div>
|
|
40
|
-
{!isLast && <TimelineConnector />}
|
|
41
|
-
</div>
|
|
42
|
-
<TimelineContent>
|
|
43
|
-
<div className="flex items-baseline justify-between gap-2">
|
|
44
|
-
<p className="text-sm font-medium text-[hsl(var(--foreground))]">{title}</p>
|
|
45
|
-
{timestamp && <span className="text-xs text-[hsl(var(--muted-foreground))] shrink-0">{timestamp}</span>}
|
|
46
|
-
</div>
|
|
47
|
-
{description && <p className="text-sm text-[hsl(var(--muted-foreground))] mt-0.5">{description}</p>}
|
|
48
|
-
</TimelineContent>
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface TimelineProps {
|
|
54
|
-
children: React.ReactNode
|
|
55
|
-
className?: string
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function Timeline({ children, className }: TimelineProps) {
|
|
59
|
-
const items = React.Children.toArray(children)
|
|
60
|
-
return (
|
|
61
|
-
<div className={cn('', className)}>
|
|
62
|
-
{items.map((child, i) =>
|
|
63
|
-
React.isValidElement(child)
|
|
64
|
-
? React.cloneElement(child as React.ReactElement<TimelineItemProps>, { isLast: i === items.length - 1 })
|
|
65
|
-
: child
|
|
66
|
-
)}
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
1
|
+
export * from './Timeline/index'
|
|
@@ -1,42 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { Toaster as SonnerToaster, toast } from 'sonner'
|
|
5
|
-
import { useOtfUI } from '../provider/otf-ui-provider'
|
|
6
|
-
|
|
7
|
-
export interface ToasterProps extends React.ComponentProps<typeof SonnerToaster> {}
|
|
8
|
-
|
|
9
|
-
export function Toaster(props: ToasterProps) {
|
|
10
|
-
const ctx = useOtfUI?.()
|
|
11
|
-
const dark = ctx?.darkMode === 'dark'
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<SonnerToaster
|
|
15
|
-
theme={dark ? 'dark' : 'light'}
|
|
16
|
-
position="bottom-right"
|
|
17
|
-
toastOptions={{
|
|
18
|
-
classNames: {
|
|
19
|
-
toast: [
|
|
20
|
-
'group flex items-start gap-3 rounded-lg border px-4 py-3.5 text-sm shadow-md',
|
|
21
|
-
'border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--card-foreground))]',
|
|
22
|
-
].join(' '),
|
|
23
|
-
title: 'font-semibold text-sm leading-snug',
|
|
24
|
-
description: 'text-xs text-[hsl(var(--muted-foreground))] mt-0.5 leading-snug',
|
|
25
|
-
actionButton: [
|
|
26
|
-
'mt-2 inline-flex items-center gap-1 text-xs font-medium',
|
|
27
|
-
'text-[hsl(var(--primary))] underline-offset-2 hover:underline',
|
|
28
|
-
].join(' '),
|
|
29
|
-
cancelButton: 'mt-2 text-xs text-[hsl(var(--muted-foreground))] hover:opacity-80',
|
|
30
|
-
closeButton: 'text-[hsl(var(--muted-foreground))] opacity-60 hover:opacity-100 transition-opacity',
|
|
31
|
-
error: 'border-red-200/80 bg-red-50/60 dark:border-red-800/60 dark:bg-red-950/30',
|
|
32
|
-
success: 'border-green-200/80 bg-green-50/60 dark:border-green-800/60 dark:bg-green-950/30',
|
|
33
|
-
warning: 'border-yellow-200/80 bg-yellow-50/60 dark:border-yellow-800/60 dark:bg-yellow-950/30',
|
|
34
|
-
info: 'border-blue-200/80 bg-blue-50/60 dark:border-blue-800/60 dark:bg-blue-950/30',
|
|
35
|
-
},
|
|
36
|
-
}}
|
|
37
|
-
{...props}
|
|
38
|
-
/>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { toast }
|
|
1
|
+
export * from './Toaster'
|
package/src/data/DataList.tsx
CHANGED
|
@@ -31,7 +31,7 @@ export const DataList = React.forwardRef<HTMLUListElement, DataListProps>(
|
|
|
31
31
|
)
|
|
32
32
|
DataList.displayName = 'DataList'
|
|
33
33
|
|
|
34
|
-
export interface DataListItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
34
|
+
export interface DataListItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'title'> {
|
|
35
35
|
left?: React.ReactNode
|
|
36
36
|
title?: React.ReactNode
|
|
37
37
|
description?: React.ReactNode
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import { cn } from '../utils/cn'
|
|
3
|
-
import { Button } from '
|
|
3
|
+
import { Button } from '../../primitives/button'
|
|
4
4
|
|
|
5
5
|
interface TourContextValue {
|
|
6
6
|
currentStep: number
|