@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.
Files changed (120) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +28 -41
  3. package/dist/index.js +972 -655
  4. package/dist/index.mjs +826 -525
  5. package/package.json +5 -4
  6. package/src/advanced/BulkActions/BulkActions.tsx +2 -2
  7. package/src/advanced/CommandBar/CommandBar.tsx +2 -2
  8. package/src/advanced/Filters/Filters.tsx +6 -6
  9. package/src/advanced/Kanban/Kanban.tsx +1 -1
  10. package/src/advanced/ToggleButton/ToggleButton.tsx +1 -1
  11. package/src/advanced/bulk-actions.tsx +1 -46
  12. package/src/advanced/command-bar.tsx +1 -115
  13. package/src/advanced/filters.tsx +1 -103
  14. package/src/advanced/kanban.tsx +1 -95
  15. package/src/advanced/toggle-button.tsx +1 -78
  16. package/src/blocks/ChatDetail/ChatDetail.tsx +4 -4
  17. package/src/blocks/FeedbackModal/FeedbackModal.tsx +3 -3
  18. package/src/blocks/FileCards/FileCards.tsx +2 -2
  19. package/src/blocks/FilesList/FilesList.tsx +3 -3
  20. package/src/blocks/IntegrationCard/IntegrationCard.tsx +3 -3
  21. package/src/blocks/InviteModal/InviteModal.tsx +7 -7
  22. package/src/blocks/ManageTagsModal/ManageTagsModal.tsx +4 -4
  23. package/src/blocks/MessagesCard/MessagesCard.tsx +3 -3
  24. package/src/blocks/MetricCard/MetricCard.tsx +3 -3
  25. package/src/blocks/NotificationSettings/NotificationSettings.tsx +4 -4
  26. package/src/blocks/OrganizationMenu/OrganizationMenu.tsx +30 -11
  27. package/src/blocks/RolesMenu/RolesMenu.tsx +4 -4
  28. package/src/blocks/SelectUsersModal/SelectUsersModal.tsx +5 -5
  29. package/src/blocks/SidebarLayoutDashboard/SidebarLayoutDashboard.tsx +5 -5
  30. package/src/blocks/SidebarLayoutGroups/SidebarLayoutGroups.tsx +2 -2
  31. package/src/blocks/SidebarLayoutMinimal/SidebarLayoutMinimal.tsx +3 -3
  32. package/src/blocks/SidebarLayoutSearch/SidebarLayoutSearch.tsx +3 -3
  33. package/src/blocks/SidebarLayoutUser/SidebarLayoutUser.tsx +4 -4
  34. package/src/blocks/SortableTaskList/SortableTaskList.tsx +3 -3
  35. package/src/blocks/StackedLayoutBranded/StackedLayoutBranded.tsx +4 -4
  36. package/src/blocks/StackedLayoutTabs/StackedLayoutTabs.tsx +4 -4
  37. package/src/blocks/TaskCard/TaskCard.tsx +4 -4
  38. package/src/blocks/UserMenu/UserMenu.tsx +3 -3
  39. package/src/blocks/WorkspaceMembers/WorkspaceMembers.tsx +5 -5
  40. package/src/blocks/chat-detail.tsx +1 -73
  41. package/src/blocks/feedback-modal.tsx +1 -68
  42. package/src/blocks/file-cards.tsx +1 -56
  43. package/src/blocks/files-list.tsx +1 -72
  44. package/src/blocks/integration-card.tsx +1 -50
  45. package/src/blocks/invite-modal.tsx +1 -69
  46. package/src/blocks/manage-tags-modal.tsx +1 -63
  47. package/src/blocks/messages-card.tsx +1 -59
  48. package/src/blocks/metric-card.tsx +1 -79
  49. package/src/blocks/notification-settings.tsx +1 -61
  50. package/src/blocks/organization-menu.tsx +1 -85
  51. package/src/blocks/roles-menu.tsx +1 -61
  52. package/src/blocks/select-users-modal.tsx +1 -64
  53. package/src/blocks/sidebar-layout-dashboard.tsx +1 -97
  54. package/src/blocks/sidebar-layout-groups.tsx +1 -79
  55. package/src/blocks/sidebar-layout-minimal.tsx +1 -73
  56. package/src/blocks/sidebar-layout-search.tsx +1 -79
  57. package/src/blocks/sidebar-layout-user.tsx +1 -82
  58. package/src/blocks/sortable-task-list.tsx +1 -70
  59. package/src/blocks/stacked-layout-branded.tsx +1 -64
  60. package/src/blocks/stacked-layout-tabs.tsx +1 -80
  61. package/src/blocks/task-card.tsx +1 -88
  62. package/src/blocks/user-menu.tsx +1 -62
  63. package/src/blocks/workspace-members.tsx +1 -61
  64. package/src/charts/sparkline.tsx +1 -1
  65. package/src/components/DataGrid/DataGrid.tsx +1 -1
  66. package/src/components/DataTable/DataTable.tsx +4 -4
  67. package/src/components/EmptyState/EmptyState.tsx +6 -22
  68. package/src/components/Persona/Persona.tsx +1 -1
  69. package/src/components/Stat/Stat.tsx +1 -1
  70. package/src/components/Stepper/Stepper.tsx +13 -2
  71. package/src/components/banner.tsx +1 -203
  72. package/src/components/breadcrumb.tsx +1 -58
  73. package/src/components/command-item.tsx +1 -36
  74. package/src/components/data-grid.tsx +1 -125
  75. package/src/components/data-table.tsx +1 -139
  76. package/src/components/empty-state.tsx +1 -31
  77. package/src/components/hotkeys.tsx +1 -31
  78. package/src/components/icon-badge.tsx +1 -35
  79. package/src/components/loading-overlay.tsx +1 -38
  80. package/src/components/persona.tsx +1 -38
  81. package/src/components/property-list.tsx +1 -29
  82. package/src/components/stat.tsx +1 -51
  83. package/src/components/stepper.tsx +1 -69
  84. package/src/components/structured-list.tsx +1 -51
  85. package/src/components/timeline.tsx +1 -69
  86. package/src/components/toaster.tsx +1 -42
  87. package/src/data/DataList.tsx +1 -1
  88. package/src/engagement/Tour/Tour.tsx +1 -1
  89. package/src/engagement/beacon.tsx +1 -50
  90. package/src/engagement/tour.tsx +1 -106
  91. package/src/forms/ArrayField/ArrayField.tsx +2 -2
  92. package/src/forms/AutoForm/AutoForm.tsx +9 -8
  93. package/src/forms/DatePicker/DatePicker.tsx +3 -3
  94. package/src/forms/FloatingLabelInput/FloatingLabelInput.tsx +104 -0
  95. package/src/forms/FloatingLabelInput/index.ts +1 -0
  96. package/src/forms/PasswordInput/PasswordInput.tsx +2 -2
  97. package/src/forms/SearchInput/SearchInput.tsx +2 -2
  98. package/src/forms/StepForm/StepForm.tsx +2 -2
  99. package/src/forms/array-field.tsx +1 -61
  100. package/src/forms/auto-form.tsx +1 -133
  101. package/src/forms/date-picker.tsx +1 -109
  102. package/src/forms/field.tsx +1 -62
  103. package/src/forms/file-upload.tsx +1 -101
  104. package/src/forms/floating-label-input.tsx +1 -0
  105. package/src/forms/form.tsx +1 -57
  106. package/src/forms/object-field.tsx +1 -53
  107. package/src/forms/password-input.tsx +1 -54
  108. package/src/forms/search-input.tsx +1 -64
  109. package/src/forms/step-form.tsx +1 -113
  110. package/src/index.ts +11 -3
  111. package/src/primitives/Button/Button.tsx +18 -0
  112. package/src/primitives/cart-line-item.tsx +81 -0
  113. package/src/primitives/code-block.tsx +1 -1
  114. package/src/primitives/comparison.tsx +1 -1
  115. package/src/primitives/empty-cart.tsx +52 -0
  116. package/src/primitives/price-tag.tsx +62 -0
  117. package/src/primitives/qr-code.tsx +5 -1
  118. package/src/primitives/quantity-stepper.tsx +92 -0
  119. package/src/primitives/variant-picker.tsx +74 -0
  120. package/src/themes/base.css +18 -0
@@ -1,125 +1 @@
1
- import React from 'react'
2
- import {
3
- ColumnDef,
4
- flexRender,
5
- getCoreRowModel,
6
- useReactTable,
7
- RowSelectionState,
8
- ColumnResizeMode,
9
- } from '@tanstack/react-table'
10
- import { cn } from '../utils/cn'
11
- import { Checkbox } from '../primitives/checkbox'
12
-
13
- interface DataGridProps<T extends object> {
14
- columns: ColumnDef<T, any>[]
15
- data: T[]
16
- onDataChange?: (data: T[]) => void
17
- editable?: boolean
18
- className?: string
19
- }
20
-
21
- function EditableCell<T>({ value: initial, onSave }: { value: unknown; onSave: (v: string) => void }) {
22
- const [editing, setEditing] = React.useState(false)
23
- const [val, setVal] = React.useState(String(initial ?? ''))
24
-
25
- const commit = () => { onSave(val); setEditing(false) }
26
-
27
- if (editing) {
28
- return (
29
- <input
30
- autoFocus
31
- className="w-full bg-transparent outline outline-1 outline-[hsl(var(--ring))] rounded px-1 text-sm"
32
- value={val}
33
- onChange={(e) => setVal(e.target.value)}
34
- onBlur={commit}
35
- onKeyDown={(e) => { if (e.key === 'Enter') commit(); if (e.key === 'Escape') setEditing(false) }}
36
- />
37
- )
38
- }
39
- return (
40
- <span className="cursor-pointer w-full block" onDoubleClick={() => setEditing(true)}>
41
- {String(initial ?? '')}
42
- </span>
43
- )
44
- }
45
-
46
- const selectionCol = <T,>(): ColumnDef<T> => ({
47
- id: '_select',
48
- header: ({ table }) => (
49
- <Checkbox checked={table.getIsAllRowsSelected()} onCheckedChange={(v) => table.toggleAllRowsSelected(!!v)} />
50
- ),
51
- cell: ({ row }) => (
52
- <Checkbox checked={row.getIsSelected()} onCheckedChange={(v) => row.toggleSelected(!!v)} />
53
- ),
54
- size: 40,
55
- enableResizing: false,
56
- })
57
-
58
- export function DataGrid<T extends object>({ columns, data: initialData, onDataChange, editable, className }: DataGridProps<T>) {
59
- const [data, setData] = React.useState<T[]>(initialData)
60
- const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
61
- const columnResizeMode: ColumnResizeMode = 'onChange'
62
-
63
- const editableColumns = React.useMemo(() => {
64
- if (!editable) return columns
65
- return columns.map((col) => ({
66
- ...col,
67
- cell: (info: any) => (
68
- <EditableCell
69
- value={info.getValue()}
70
- onSave={(v) => {
71
- const updated = data.map((row, i) =>
72
- i === info.row.index ? { ...row, [info.column.id]: v } : row
73
- ) as T[]
74
- setData(updated)
75
- onDataChange?.(updated)
76
- }}
77
- />
78
- ),
79
- }))
80
- }, [columns, data, editable, onDataChange])
81
-
82
- const allColumns = React.useMemo(() => [selectionCol<T>(), ...editableColumns], [editableColumns])
83
-
84
- const table = useReactTable({
85
- data,
86
- columns: allColumns,
87
- columnResizeMode,
88
- getCoreRowModel: getCoreRowModel(),
89
- onRowSelectionChange: setRowSelection,
90
- state: { rowSelection },
91
- enableColumnResizing: true,
92
- })
93
-
94
- return (
95
- <div className={cn('overflow-auto rounded-md border border-[hsl(var(--border))]', className)}>
96
- <table className="w-full text-sm" style={{ width: table.getCenterTotalSize() }}>
97
- <thead className="bg-[hsl(var(--muted)/0.4)]">
98
- {table.getHeaderGroups().map((hg) => (
99
- <tr key={hg.id}>
100
- {hg.headers.map((h) => (
101
- <th key={h.id} style={{ width: h.getSize() }} className="relative px-3 py-2 text-left font-medium text-[hsl(var(--muted-foreground))] border-r border-[hsl(var(--border))] last:border-r-0">
102
- {h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}
103
- {h.column.getCanResize() && (
104
- <div onMouseDown={h.getResizeHandler()} className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-[hsl(var(--primary)/0.4)]" />
105
- )}
106
- </th>
107
- ))}
108
- </tr>
109
- ))}
110
- </thead>
111
- <tbody>
112
- {table.getRowModel().rows.map((row) => (
113
- <tr key={row.id} className={cn('border-t border-[hsl(var(--border))] hover:bg-[hsl(var(--muted)/0.3)] transition-colors', row.getIsSelected() && 'bg-[hsl(var(--primary)/0.06)]')}>
114
- {row.getVisibleCells().map((cell) => (
115
- <td key={cell.id} style={{ width: cell.column.getSize() }} className="px-3 py-2 border-r border-[hsl(var(--border))] last:border-r-0">
116
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
117
- </td>
118
- ))}
119
- </tr>
120
- ))}
121
- </tbody>
122
- </table>
123
- </div>
124
- )
125
- }
1
+ export * from './DataGrid'
@@ -1,139 +1 @@
1
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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'
@@ -1,51 +1 @@
1
- import React from 'react'
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
- import React from 'react'
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
- import React from 'react'
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'