@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otfdashkit/ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OTF UI — production-quality React component library. 98 primitives + blocks + layouts + charts on Radix + Tailwind v4. Cross-platform parity API with @otfdashkit/ui-native.",
5
5
  "keywords": [
6
6
  "otfdashkit",
@@ -17,16 +17,17 @@
17
17
  "accessible"
18
18
  ],
19
19
  "license": "MIT",
20
- "author": "otfdashkit <dave@otf-kit.dev>",
21
- "homepage": "https://github.com/otf-kit/sdk/tree/main/packages/ui#readme",
20
+ "author": "otf-kit <dave@otf-kit.dev>",
21
+ "homepage": "https://otf-kit.dev",
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "https://github.com/otf-kit/sdk.git",
24
+ "url": "git+https://github.com/otf-kit/sdk.git",
25
25
  "directory": "packages/ui"
26
26
  },
27
27
  "bugs": {
28
28
  "url": "https://github.com/otf-kit/sdk/issues"
29
29
  },
30
+ "funding": "https://otf-kit.dev/pricing",
30
31
  "main": "dist/index.js",
31
32
  "module": "dist/index.mjs",
32
33
  "types": "dist/index.d.ts",
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Button } from '../primitives/button'
3
+ import { cn } from '../../utils/cn'
4
+ import { Button } from '../../primitives/button'
5
5
 
6
6
  interface ActionDef {
7
7
  label: string
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Search, X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Dialog, DialogContent, DialogTitle } from '../primitives/dialog'
3
+ import { cn } from '../../utils/cn'
4
+ import { Dialog, DialogContent, DialogTitle } from '../../primitives/dialog'
5
5
 
6
6
  interface UseCommandBarReturn { open: boolean; setOpen: (v: boolean) => void }
7
7
 
@@ -1,10 +1,10 @@
1
1
  import React from 'react'
2
2
  import { Plus, X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Button } from '../primitives/button'
5
- import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
6
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../primitives/select'
7
- import { Input } from '../primitives/input'
3
+ import { cn } from '../../utils/cn'
4
+ import { Button } from '../../primitives/button'
5
+ import { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../primitives/select'
7
+ import { Input } from '../../primitives/input'
8
8
 
9
9
  export interface FilterDef {
10
10
  key: string
@@ -33,7 +33,7 @@ function AddFilterPopover({ filters, onAdd }: { filters: FilterDef[]; onAdd: (v:
33
33
 
34
34
  const handleAdd = () => {
35
35
  if (!key || !val) return
36
- onAdd({ key, operator: op || ops[0], value: val })
36
+ onAdd({ key, operator: op || ops[0]!, value: val })
37
37
  setKey(''); setOp(''); setVal('')
38
38
  setOpen(false)
39
39
  }
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import { DndContext, DragEndEvent, DragOverEvent, DragOverlay, DragStartEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
3
3
  import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'
4
4
  import { CSS } from '@dnd-kit/utilities'
5
- import { cn } from '../utils/cn'
5
+ import { cn } from '../../utils/cn'
6
6
 
7
7
  export interface KanbanCardDef { id: string; title: string; description?: string; badge?: string }
8
8
  export interface KanbanColumnDef { id: string; title: string; cards: KanbanCardDef[] }
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { cn } from '../utils/cn'
2
+ import { cn } from '../../utils/cn'
3
3
 
4
4
  interface ToggleButtonProps {
5
5
  value: string
@@ -1,46 +1 @@
1
- import React from 'react'
2
- import { X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Button } from '../primitives/button'
5
-
6
- interface ActionDef {
7
- label: string
8
- icon?: React.ReactNode
9
- onClick: () => void
10
- variant?: 'default' | 'destructive'
11
- }
12
-
13
- interface BulkActionsProps {
14
- count: number
15
- actions: ActionDef[]
16
- onClear: () => void
17
- className?: string
18
- }
19
-
20
- export function BulkActions({ count, actions, onClear, className }: BulkActionsProps) {
21
- if (count === 0) return null
22
- return (
23
- <div className={cn('fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-2 shadow-lg', className)}>
24
- <span className="text-sm font-medium text-[hsl(var(--foreground))] pr-2 border-r border-[hsl(var(--border))]">
25
- {count} selected
26
- </span>
27
- <div className="flex items-center gap-1">
28
- {actions.map((action, i) => (
29
- <Button
30
- key={i}
31
- variant={action.variant === 'destructive' ? 'destructive' : 'ghost'}
32
- size="sm"
33
- className="h-7 gap-1.5 text-xs rounded-full px-3"
34
- onClick={action.onClick}
35
- >
36
- {action.icon}
37
- {action.label}
38
- </Button>
39
- ))}
40
- </div>
41
- <button onClick={onClear} className="ml-1 rounded-full p-0.5 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors">
42
- <X className="h-4 w-4" />
43
- </button>
44
- </div>
45
- )
46
- }
1
+ export * from './BulkActions'
@@ -1,115 +1 @@
1
- import React from 'react'
2
- import { Search, X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Dialog, DialogContent, DialogTitle } from '../primitives/dialog'
5
-
6
- interface UseCommandBarReturn { open: boolean; setOpen: (v: boolean) => void }
7
-
8
- export function useCommandBar(): UseCommandBarReturn {
9
- const [open, setOpen] = React.useState(false)
10
-
11
- React.useEffect(() => {
12
- const handler = (e: KeyboardEvent) => {
13
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setOpen((o) => !o) }
14
- }
15
- window.addEventListener('keydown', handler)
16
- return () => window.removeEventListener('keydown', handler)
17
- }, [])
18
-
19
- return { open, setOpen }
20
- }
21
-
22
- export function CommandBarEmpty({ children = 'No results found.' }: { children?: React.ReactNode }) {
23
- return <div className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">{children}</div>
24
- }
25
-
26
- export function CommandBarGroup({ heading, children }: { heading: string; children: React.ReactNode }) {
27
- return (
28
- <div className="px-2 py-1">
29
- <p className="mb-1 px-2 text-xs font-semibold text-[hsl(var(--muted-foreground))]">{heading}</p>
30
- {children}
31
- </div>
32
- )
33
- }
34
-
35
- interface CommandBarItemProps {
36
- icon?: React.ReactNode
37
- label: string
38
- shortcut?: string
39
- onSelect?: () => void
40
- active?: boolean
41
- className?: string
42
- }
43
-
44
- export function CommandBarItem({ icon, label, shortcut, onSelect, active, className }: CommandBarItemProps) {
45
- return (
46
- <button
47
- onClick={onSelect}
48
- className={cn(
49
- 'flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm transition-colors',
50
- active ? 'bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))]' : 'hover:bg-[hsl(var(--accent)/0.6)]',
51
- className
52
- )}
53
- >
54
- {icon && <span className="h-4 w-4 shrink-0 text-[hsl(var(--muted-foreground))]">{icon}</span>}
55
- <span className="flex-1 text-left truncate">{label}</span>
56
- {shortcut && (
57
- <kbd className="pointer-events-none shrink-0 inline-flex h-5 items-center rounded border border-[hsl(var(--border))] bg-[hsl(var(--muted))] px-1.5 text-[10px] font-medium text-[hsl(var(--muted-foreground))]">
58
- {shortcut}
59
- </kbd>
60
- )}
61
- </button>
62
- )
63
- }
64
-
65
- interface CommandBarProps {
66
- open: boolean
67
- onOpenChange: (v: boolean) => void
68
- children: React.ReactNode
69
- placeholder?: string
70
- onSearch?: (q: string) => void
71
- }
72
-
73
- export function CommandBar({ open, onOpenChange, children, placeholder = 'Search commands...', onSearch }: CommandBarProps) {
74
- const [query, setQuery] = React.useState('')
75
-
76
- const handleSearch = (v: string) => { setQuery(v); onSearch?.(v) }
77
-
78
- const items = React.useRef<HTMLDivElement>(null)
79
-
80
- const handleKeyDown = (e: React.KeyboardEvent) => {
81
- if (e.key === 'Escape') { onOpenChange(false); return }
82
- const focusable = items.current?.querySelectorAll<HTMLButtonElement>('button')
83
- if (!focusable?.length) return
84
- const arr = Array.from(focusable)
85
- const idx = arr.indexOf(document.activeElement as HTMLButtonElement)
86
- if (e.key === 'ArrowDown') { e.preventDefault(); arr[(idx + 1) % arr.length]?.focus() }
87
- if (e.key === 'ArrowUp') { e.preventDefault(); arr[(idx - 1 + arr.length) % arr.length]?.focus() }
88
- }
89
-
90
- return (
91
- <Dialog open={open} onOpenChange={onOpenChange}>
92
- <DialogContent className="p-0 gap-0 max-w-lg overflow-hidden" onKeyDown={handleKeyDown}>
93
- <DialogTitle className="sr-only">Command Bar</DialogTitle>
94
- <div className="flex items-center gap-2 border-b border-[hsl(var(--border))] px-4 py-3">
95
- <Search className="h-4 w-4 shrink-0 text-[hsl(var(--muted-foreground))]" />
96
- <input
97
- autoFocus
98
- className="flex-1 bg-transparent text-sm outline-none placeholder:text-[hsl(var(--muted-foreground))]"
99
- placeholder={placeholder}
100
- value={query}
101
- onChange={(e) => handleSearch(e.target.value)}
102
- />
103
- {query && (
104
- <button onClick={() => handleSearch('')} className="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]">
105
- <X className="h-4 w-4" />
106
- </button>
107
- )}
108
- </div>
109
- <div ref={items} className="max-h-80 overflow-y-auto py-2">
110
- {children}
111
- </div>
112
- </DialogContent>
113
- </Dialog>
114
- )
115
- }
1
+ export * from './CommandBar'
@@ -1,103 +1 @@
1
- import React from 'react'
2
- import { Plus, X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Button } from '../primitives/button'
5
- import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
6
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../primitives/select'
7
- import { Input } from '../primitives/input'
8
-
9
- export interface FilterDef {
10
- key: string
11
- label: string
12
- type: 'text' | 'select' | 'date' | 'number'
13
- options?: { label: string; value: string }[]
14
- }
15
-
16
- export interface FilterValue {
17
- key: string
18
- operator: string
19
- value: string
20
- }
21
-
22
- const textOps = ['contains', 'equals', 'starts with']
23
- const numOps = ['=', '>', '<', '>=', '<=']
24
-
25
- function AddFilterPopover({ filters, onAdd }: { filters: FilterDef[]; onAdd: (v: FilterValue) => void }) {
26
- const [open, setOpen] = React.useState(false)
27
- const [key, setKey] = React.useState('')
28
- const [op, setOp] = React.useState('')
29
- const [val, setVal] = React.useState('')
30
-
31
- const def = filters.find((f) => f.key === key)
32
- const ops = def?.type === 'number' ? numOps : textOps
33
-
34
- const handleAdd = () => {
35
- if (!key || !val) return
36
- onAdd({ key, operator: op || ops[0], value: val })
37
- setKey(''); setOp(''); setVal('')
38
- setOpen(false)
39
- }
40
-
41
- return (
42
- <Popover open={open} onOpenChange={setOpen}>
43
- <PopoverTrigger asChild>
44
- <Button variant="outline" size="sm" className="h-7 gap-1 text-xs"><Plus className="h-3.5 w-3.5" />Add Filter</Button>
45
- </PopoverTrigger>
46
- <PopoverContent className="w-72 p-3 space-y-2">
47
- <Select value={key} onValueChange={setKey}>
48
- <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="Field" /></SelectTrigger>
49
- <SelectContent>{filters.map((f) => <SelectItem key={f.key} value={f.key}>{f.label}</SelectItem>)}</SelectContent>
50
- </Select>
51
- {key && (
52
- <Select value={op || ops[0]} onValueChange={setOp}>
53
- <SelectTrigger className="h-8 text-xs"><SelectValue /></SelectTrigger>
54
- <SelectContent>{ops.map((o) => <SelectItem key={o} value={o}>{o}</SelectItem>)}</SelectContent>
55
- </Select>
56
- )}
57
- {key && def?.type === 'select' ? (
58
- <Select value={val} onValueChange={setVal}>
59
- <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="Value" /></SelectTrigger>
60
- <SelectContent>{def.options?.map((o) => <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>)}</SelectContent>
61
- </Select>
62
- ) : key && (
63
- <Input type={def?.type === 'number' ? 'number' : def?.type === 'date' ? 'date' : 'text'} className="h-8 text-xs" value={val} onChange={(e) => setVal(e.target.value)} placeholder="Value" />
64
- )}
65
- <Button size="sm" className="w-full h-7 text-xs" onClick={handleAdd} disabled={!key || !val}>Apply</Button>
66
- </PopoverContent>
67
- </Popover>
68
- )
69
- }
70
-
71
- export function FilterBadge({ filter, def, onRemove }: { filter: FilterValue; def?: FilterDef; onRemove: () => void }) {
72
- return (
73
- <span className="inline-flex items-center gap-1 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--muted))] px-2.5 py-0.5 text-xs font-medium text-[hsl(var(--foreground))]">
74
- <span className="text-[hsl(var(--muted-foreground))]">{def?.label ?? filter.key}</span>
75
- <span>{filter.operator}</span>
76
- <span className="font-semibold">{filter.value}</span>
77
- <button onClick={onRemove} className="ml-0.5 rounded-full hover:text-[hsl(var(--destructive))]"><X className="h-3 w-3" /></button>
78
- </span>
79
- )
80
- }
81
-
82
- export function FilterItem({ children }: { children: React.ReactNode }) {
83
- return <div className="flex items-center gap-1">{children}</div>
84
- }
85
-
86
- interface FiltersProps {
87
- filters: FilterDef[]
88
- value: FilterValue[]
89
- onChange: (v: FilterValue[]) => void
90
- className?: string
91
- }
92
-
93
- export function Filters({ filters, value, onChange, className }: FiltersProps) {
94
- const remove = (i: number) => onChange(value.filter((_, idx) => idx !== i))
95
- return (
96
- <div className={cn('flex flex-wrap items-center gap-2', className)}>
97
- {value.map((fv, i) => (
98
- <FilterBadge key={i} filter={fv} def={filters.find((f) => f.key === fv.key)} onRemove={() => remove(i)} />
99
- ))}
100
- <AddFilterPopover filters={filters} onAdd={(fv) => onChange([...value, fv])} />
101
- </div>
102
- )
103
- }
1
+ export * from './Filters/index'
@@ -1,95 +1 @@
1
- import React from 'react'
2
- import { DndContext, DragEndEvent, DragOverEvent, DragOverlay, DragStartEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
3
- import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'
4
- import { CSS } from '@dnd-kit/utilities'
5
- import { cn } from '../utils/cn'
6
-
7
- export interface KanbanCardDef { id: string; title: string; description?: string; badge?: string }
8
- export interface KanbanColumnDef { id: string; title: string; cards: KanbanCardDef[] }
9
-
10
- export function KanbanCard({ card, isDragging }: { card: KanbanCardDef; isDragging?: boolean }) {
11
- const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: card.id })
12
- const style = { transform: CSS.Transform.toString(transform), transition }
13
- return (
14
- <div ref={setNodeRef} style={style} {...attributes} {...listeners}
15
- className={cn('rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-3 cursor-grab active:cursor-grabbing select-none', isDragging && 'shadow-lg opacity-80')}>
16
- <div className="flex items-start justify-between gap-2">
17
- <p className="text-sm font-medium text-[hsl(var(--foreground))] leading-tight">{card.title}</p>
18
- {card.badge && (
19
- <span className="shrink-0 text-[10px] px-1.5 py-0.5 rounded-full bg-[hsl(var(--primary)/0.1)] text-[hsl(var(--primary))] font-medium">{card.badge}</span>
20
- )}
21
- </div>
22
- {card.description && <p className="mt-1 text-xs text-[hsl(var(--muted-foreground))] line-clamp-2">{card.description}</p>}
23
- </div>
24
- )
25
- }
26
-
27
- export function KanbanColumn({ column, isOver }: { column: KanbanColumnDef; isOver?: boolean }) {
28
- return (
29
- <div className={cn('flex w-64 shrink-0 flex-col gap-2 rounded-md bg-[hsl(var(--muted)/0.4)] p-3 transition-colors', isOver && 'bg-[hsl(var(--muted)/0.7)]')}>
30
- <div className="flex items-center justify-between">
31
- <h3 className="text-sm font-semibold text-[hsl(var(--foreground))]">{column.title}</h3>
32
- <span className="text-xs text-[hsl(var(--muted-foreground))] bg-[hsl(var(--muted))] rounded-full px-2 py-0.5">{column.cards.length}</span>
33
- </div>
34
- <SortableContext items={column.cards.map((c) => c.id)} strategy={verticalListSortingStrategy}>
35
- <div className="flex flex-col gap-2 min-h-[60px]">
36
- {column.cards.map((card) => <KanbanCard key={card.id} card={card} />)}
37
- </div>
38
- </SortableContext>
39
- </div>
40
- )
41
- }
42
-
43
- interface KanbanProps {
44
- columns: KanbanColumnDef[]
45
- onMoveCard?: (cardId: string, fromCol: string, toCol: string) => void
46
- className?: string
47
- }
48
-
49
- export function Kanban({ columns: initialColumns, onMoveCard, className }: KanbanProps) {
50
- const [cols, setCols] = React.useState<KanbanColumnDef[]>(initialColumns)
51
- const [activeCard, setActiveCard] = React.useState<KanbanCardDef | null>(null)
52
- const [overColId, setOverColId] = React.useState<string | null>(null)
53
-
54
- const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }))
55
-
56
- const findColByCard = (cardId: string) => cols.find((c) => c.cards.some((card) => card.id === cardId))
57
-
58
- const onDragStart = ({ active }: DragStartEvent) => {
59
- const col = findColByCard(active.id as string)
60
- setActiveCard(col?.cards.find((c) => c.id === active.id) ?? null)
61
- }
62
-
63
- const onDragOver = ({ over }: DragOverEvent) => {
64
- if (!over) { setOverColId(null); return }
65
- const colId = cols.find((c) => c.id === over.id)?.id ?? findColByCard(over.id as string)?.id ?? null
66
- setOverColId(colId)
67
- }
68
-
69
- const onDragEnd = ({ active, over }: DragEndEvent) => {
70
- setActiveCard(null); setOverColId(null)
71
- if (!over || active.id === over.id) return
72
- const fromCol = findColByCard(active.id as string)
73
- const toCol = cols.find((c) => c.id === over.id) ?? findColByCard(over.id as string)
74
- if (!fromCol || !toCol || fromCol.id === toCol.id) return
75
-
76
- const card = fromCol.cards.find((c) => c.id === active.id)!
77
- setCols((prev) => prev.map((c) => {
78
- if (c.id === fromCol.id) return { ...c, cards: c.cards.filter((card) => card.id !== active.id) }
79
- if (c.id === toCol.id) return { ...c, cards: [...c.cards, card] }
80
- return c
81
- }))
82
- onMoveCard?.(active.id as string, fromCol.id, toCol.id)
83
- }
84
-
85
- return (
86
- <DndContext sensors={sensors} onDragStart={onDragStart} onDragOver={onDragOver} onDragEnd={onDragEnd}>
87
- <div className={cn('flex gap-4 overflow-x-auto pb-4', className)}>
88
- {cols.map((col) => <KanbanColumn key={col.id} column={col} isOver={overColId === col.id} />)}
89
- </div>
90
- <DragOverlay>
91
- {activeCard && <KanbanCard card={activeCard} isDragging />}
92
- </DragOverlay>
93
- </DndContext>
94
- )
95
- }
1
+ export * from './Kanban/index'
@@ -1,78 +1 @@
1
- import React from 'react'
2
- import { cn } from '../utils/cn'
3
-
4
- interface ToggleButtonProps {
5
- value: string
6
- pressed?: boolean
7
- onClick?: () => void
8
- disabled?: boolean
9
- children: React.ReactNode
10
- className?: string
11
- }
12
-
13
- export function ToggleButton({ pressed, onClick, disabled, children, className }: ToggleButtonProps) {
14
- return (
15
- <button
16
- type="button"
17
- aria-pressed={pressed}
18
- disabled={disabled}
19
- onClick={onClick}
20
- className={cn(
21
- 'inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors border',
22
- 'disabled:pointer-events-none disabled:opacity-50',
23
- pressed
24
- ? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] border-[hsl(var(--primary))]'
25
- : 'bg-transparent text-[hsl(var(--foreground))] border-[hsl(var(--border))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]',
26
- className
27
- )}
28
- >
29
- {children}
30
- </button>
31
- )
32
- }
33
-
34
- interface ToggleButtonGroupSingle {
35
- type?: 'single'
36
- value: string
37
- onChange: (v: string) => void
38
- }
39
-
40
- interface ToggleButtonGroupMultiple {
41
- type: 'multiple'
42
- value: string[]
43
- onChange: (v: string[]) => void
44
- }
45
-
46
- type ToggleButtonGroupProps = (ToggleButtonGroupSingle | ToggleButtonGroupMultiple) & {
47
- children: React.ReactNode
48
- className?: string
49
- }
50
-
51
- export function ToggleButtonGroup(props: ToggleButtonGroupProps) {
52
- const { children, className } = props
53
-
54
- const isPressed = (val: string) =>
55
- props.type === 'multiple' ? (props.value as string[]).includes(val) : props.value === val
56
-
57
- const handleClick = (val: string) => {
58
- if (props.type === 'multiple') {
59
- const current = props.value as string[]
60
- props.onChange(current.includes(val) ? current.filter((v) => v !== val) : [...current, val])
61
- } else {
62
- ;(props as ToggleButtonGroupSingle).onChange(val)
63
- }
64
- }
65
-
66
- return (
67
- <div className={cn('inline-flex items-center gap-1 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.4)] p-0.5', className)}>
68
- {React.Children.map(children, (child) => {
69
- if (!React.isValidElement(child)) return child
70
- const tb = child as React.ReactElement<ToggleButtonProps>
71
- return React.cloneElement(tb, {
72
- pressed: isPressed(tb.props.value),
73
- onClick: () => handleClick(tb.props.value),
74
- })
75
- })}
76
- </div>
77
- )
78
- }
1
+ export * from './ToggleButton'
@@ -1,9 +1,9 @@
1
1
  import React, { useState } from 'react'
2
2
  import { Send } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Avatar, AvatarFallback } from '../primitives/avatar'
5
- import { Input } from '../primitives/input'
6
- import { Button } from '../primitives/button'
3
+ import { cn } from '../../utils/cn'
4
+ import { Avatar, AvatarFallback } from '../../primitives/avatar'
5
+ import { Input } from '../../primitives/input'
6
+ import { Button } from '../../primitives/button'
7
7
 
8
8
  interface Message { id: string; author: string; initials: string; text: string; time: string; self?: boolean }
9
9
 
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react'
2
- import { cn } from '../utils/cn'
3
- import { Button } from '../primitives/button'
4
- import { Textarea } from '../primitives/textarea'
2
+ import { cn } from '../../utils/cn'
3
+ import { Button } from '../../primitives/button'
4
+ import { Textarea } from '../../primitives/textarea'
5
5
 
6
6
  const REACTIONS = ['😊', '😐', '😕', '😡', '🤩']
7
7
 
@@ -1,8 +1,8 @@
1
1
  // @ts-nocheck
2
2
  import React from 'react'
3
3
  import { FileText, Image, FileCode, Film, Archive, File } from 'lucide-react'
4
- import { cn } from '../utils/cn'
5
- import { Card, CardContent } from '../primitives/card'
4
+ import { cn } from '../../utils/cn'
5
+ import { Card, CardContent } from '../../primitives/card'
6
6
 
7
7
  interface FileItem { id: string; name: string; type: string; size: string; modified: string }
8
8
 
@@ -1,11 +1,11 @@
1
1
  // @ts-nocheck
2
2
  import React from 'react'
3
3
  import { FileText, Image, FileCode, Film, Archive, MoreHorizontal, Download, Trash2 } from 'lucide-react'
4
- import { cn } from '../utils/cn'
5
- import { Button } from '../primitives/button'
4
+ import { cn } from '../../utils/cn'
5
+ import { Button } from '../../primitives/button'
6
6
  import {
7
7
  DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
8
- } from '../primitives/dropdown-menu'
8
+ } from '../../primitives/dropdown-menu'
9
9
 
10
10
  interface FileItem { id: string; name: string; type: string; size: string; modified: string }
11
11
 
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react'
2
- import { cn } from '../utils/cn'
3
- import { Badge } from '../primitives/badge'
4
- import { Button } from '../primitives/button'
2
+ import { cn } from '../../utils/cn'
3
+ import { Badge } from '../../primitives/badge'
4
+ import { Button } from '../../primitives/button'
5
5
 
6
6
  export interface IntegrationCardProps {
7
7
  icon?: React.ReactNode
@@ -1,11 +1,11 @@
1
1
  import React, { useState } from 'react'
2
2
  import { X } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Input } from '../primitives/input'
5
- import { Button } from '../primitives/button'
6
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../primitives/select'
7
- import { Avatar, AvatarFallback } from '../primitives/avatar'
8
- import { Badge } from '../primitives/badge'
3
+ import { cn } from '../../utils/cn'
4
+ import { Input } from '../../primitives/input'
5
+ import { Button } from '../../primitives/button'
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../primitives/select'
7
+ import { Avatar, AvatarFallback } from '../../primitives/avatar'
8
+ import { Badge } from '../../primitives/badge'
9
9
 
10
10
  const PENDING = [
11
11
  { email: 'alex@startup.io', role: 'Member' },
@@ -53,7 +53,7 @@ export function InviteModal({ className }: InviteModalProps) {
53
53
  <div className="divide-y divide-[hsl(var(--border))] rounded-md border border-[hsl(var(--border))]">
54
54
  {pending.map((p, i) => (
55
55
  <div key={i} className="flex items-center gap-2 px-3 py-2">
56
- <Avatar className="h-6 w-6"><AvatarFallback className="text-[10px]">{p.email[0].toUpperCase()}</AvatarFallback></Avatar>
56
+ <Avatar className="h-6 w-6"><AvatarFallback className="text-[10px]">{p.email.charAt(0).toUpperCase()}</AvatarFallback></Avatar>
57
57
  <span className="flex-1 text-sm truncate">{p.email}</span>
58
58
  <Badge variant="outline" className="text-[10px] px-1.5 py-0.5 shrink-0">{p.role}</Badge>
59
59
  <button onClick={() => setPending(ps => ps.filter((_, j) => j !== i))}>
@@ -1,8 +1,8 @@
1
1
  import React, { useState } from 'react'
2
2
  import { X, Pencil } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Input } from '../primitives/input'
5
- import { Button } from '../primitives/button'
3
+ import { cn } from '../../utils/cn'
4
+ import { Input } from '../../primitives/input'
5
+ import { Button } from '../../primitives/button'
6
6
 
7
7
  const COLORS = ['bg-blue-500', 'bg-green-500', 'bg-yellow-500', 'bg-purple-500', 'bg-red-500', 'bg-pink-500']
8
8
 
@@ -25,7 +25,7 @@ export function ManageTagsModal({ className }: ManageTagsModalProps) {
25
25
  const addTag = () => {
26
26
  if (!newTag.trim()) return
27
27
  const id = `tag-${Date.now()}`
28
- setTags(t => [...t, { id, name: newTag.trim(), color: COLORS[colorIdx % COLORS.length] }])
28
+ setTags(t => [...t, { id, name: newTag.trim(), color: COLORS[colorIdx % COLORS.length]! }])
29
29
  setNewTag('')
30
30
  setColorIdx(i => i + 1)
31
31
  }