@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.
Files changed (115) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +28 -41
  3. package/dist/index.js +206 -125
  4. package/dist/index.mjs +194 -124
  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 +6 -3
  111. package/src/primitives/Button/Button.tsx +18 -0
  112. package/src/primitives/code-block.tsx +1 -1
  113. package/src/primitives/comparison.tsx +1 -1
  114. package/src/primitives/qr-code.tsx +5 -1
  115. package/src/themes/base.css +18 -0
@@ -1,63 +1 @@
1
- import React, { useState } from 'react'
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'
6
-
7
- const COLORS = ['bg-blue-500', 'bg-green-500', 'bg-yellow-500', 'bg-purple-500', 'bg-red-500', 'bg-pink-500']
8
-
9
- interface Tag { id: string; name: string; color: string }
10
-
11
- const INITIAL_TAGS: Tag[] = [
12
- { id: '1', name: 'Design', color: 'bg-blue-500' },
13
- { id: '2', name: 'Engineering', color: 'bg-green-500' },
14
- { id: '3', name: 'Marketing', color: 'bg-yellow-500' },
15
- { id: '4', name: 'Research', color: 'bg-purple-500' },
16
- ]
17
-
18
- export interface ManageTagsModalProps { className?: string }
19
-
20
- export function ManageTagsModal({ className }: ManageTagsModalProps) {
21
- const [tags, setTags] = useState<Tag[]>(INITIAL_TAGS)
22
- const [newTag, setNewTag] = useState('')
23
- const [colorIdx, setColorIdx] = useState(0)
24
-
25
- const addTag = () => {
26
- if (!newTag.trim()) return
27
- const id = `tag-${Date.now()}`
28
- setTags(t => [...t, { id, name: newTag.trim(), color: COLORS[colorIdx % COLORS.length] }])
29
- setNewTag('')
30
- setColorIdx(i => i + 1)
31
- }
32
-
33
- return (
34
- <div className={cn('flex flex-col gap-4 p-6', className)}>
35
- <div>
36
- <h2 className="text-base font-semibold">Manage Tags</h2>
37
- <p className="text-sm text-[hsl(var(--muted-foreground))] mt-0.5">Create and organize your tags.</p>
38
- </div>
39
- <div className="flex gap-2">
40
- <div className={cn('h-9 w-9 shrink-0 rounded-md cursor-pointer transition-opacity', COLORS[colorIdx % COLORS.length])}
41
- onClick={() => setColorIdx(i => i + 1)} title="Click to change color" />
42
- <Input className="flex-1 h-9 text-sm" placeholder="New tag name..." value={newTag}
43
- onChange={e => setNewTag(e.target.value)} onKeyDown={e => e.key === 'Enter' && addTag()} />
44
- <Button size="sm" className="h-9" onClick={addTag}>Add</Button>
45
- </div>
46
- <div className="space-y-1.5">
47
- {tags.map(tag => (
48
- <div key={tag.id} className="flex items-center gap-2 rounded-md border border-[hsl(var(--border))] px-3 py-2">
49
- <span className={cn('h-3 w-3 rounded-full shrink-0', tag.color)} />
50
- <span className="flex-1 text-sm">{tag.name}</span>
51
- <button className="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors">
52
- <Pencil className="h-3.5 w-3.5" />
53
- </button>
54
- <button onClick={() => setTags(ts => ts.filter(t => t.id !== tag.id))}
55
- className="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors">
56
- <X className="h-3.5 w-3.5" />
57
- </button>
58
- </div>
59
- ))}
60
- </div>
61
- </div>
62
- )
63
- }
1
+ export * from './ManageTagsModal'
@@ -1,59 +1 @@
1
- import React from 'react'
2
- import { cn } from '../utils/cn'
3
- import { Avatar, AvatarFallback } from '../primitives/avatar'
4
- import { Card, CardContent, CardHeader, CardTitle } from '../primitives/card'
5
-
6
- interface Message {
7
- id: string
8
- name: string
9
- initials: string
10
- preview: string
11
- time: string
12
- unread?: boolean
13
- }
14
-
15
- const MESSAGES: Message[] = [
16
- { id: '1', name: 'Alice Chen', initials: 'AC', preview: 'Just pushed the new design files to Figma.', time: '9:41 AM', unread: true },
17
- { id: '2', name: 'Bob Smith', initials: 'BS', preview: "I'll take a look and share feedback by EOD.", time: '9:43 AM', unread: true },
18
- { id: '3', name: 'Tom Ray', initials: 'TR', preview: 'Can we reschedule the standup to 10am?', time: 'Yesterday' },
19
- { id: '4', name: 'Sara Kim', initials: 'SK', preview: 'Shipped the new auth flow. Ready for QA.', time: 'Yesterday' },
20
- { id: '5', name: 'Mike Liu', initials: 'ML', preview: 'The latest metrics look promising 📈', time: 'Mon' },
21
- ]
22
-
23
- function MessageRow({ msg }: { msg: Message }) {
24
- return (
25
- <div className="flex items-center gap-3 px-4 py-3 hover:bg-[hsl(var(--accent))] cursor-pointer transition-colors">
26
- <div className="relative shrink-0">
27
- <Avatar className="h-9 w-9"><AvatarFallback className="text-xs">{msg.initials}</AvatarFallback></Avatar>
28
- {msg.unread && <span className="absolute -top-0.5 -right-0.5 h-2.5 w-2.5 rounded-full bg-[hsl(var(--primary))] ring-2 ring-[hsl(var(--card))]" />}
29
- </div>
30
- <div className="flex-1 min-w-0">
31
- <div className="flex items-center justify-between gap-2">
32
- <p className={cn('text-sm truncate', msg.unread ? 'font-semibold' : 'font-medium')}>{msg.name}</p>
33
- <span className="text-xs text-[hsl(var(--muted-foreground))] shrink-0">{msg.time}</span>
34
- </div>
35
- <p className={cn('text-xs mt-0.5 truncate', msg.unread ? 'text-[hsl(var(--foreground))]' : 'text-[hsl(var(--muted-foreground))]')}>
36
- {msg.preview}
37
- </p>
38
- </div>
39
- </div>
40
- )
41
- }
42
-
43
- export interface MessagesCardProps { className?: string }
44
-
45
- export function MessagesCard({ className }: MessagesCardProps) {
46
- return (
47
- <Card className={cn('overflow-hidden', className)}>
48
- <CardHeader className="px-4 py-3 border-b border-[hsl(var(--border))]">
49
- <CardTitle className="text-sm font-semibold flex items-center justify-between">
50
- Messages
51
- <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-[hsl(var(--primary)/0.1)] text-[hsl(var(--primary))] font-medium">2 new</span>
52
- </CardTitle>
53
- </CardHeader>
54
- <div className="divide-y divide-[hsl(var(--border))]">
55
- {MESSAGES.map(m => <MessageRow key={m.id} msg={m} />)}
56
- </div>
57
- </Card>
58
- )
59
- }
1
+ export * from './MessagesCard'
@@ -1,79 +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
- import { Button } from '../primitives/button'
6
-
7
- export interface MetricCardProps {
8
- label?: string
9
- value?: string
10
- change?: string
11
- trend?: 'up' | 'down'
12
- className?: string
13
- }
14
-
15
- export function MetricCard({ label = 'Total Revenue', value = '$48,295', change = '+12.5%', trend = 'up', className }: MetricCardProps) {
16
- const TrendIcon = trend === 'up' ? TrendingUp : TrendingDown
17
- const trendColor = trend === 'up' ? 'text-green-600' : 'text-red-500'
18
- return (
19
- <Card className={cn('', className)}>
20
- <CardContent className="p-4">
21
- <p className="text-sm text-[hsl(var(--muted-foreground))]">{label}</p>
22
- <p className="text-2xl font-bold mt-1">{value}</p>
23
- <p className={cn('flex items-center gap-1 text-sm mt-1', trendColor)}>
24
- <TrendIcon className="h-4 w-4" />{change} from last month
25
- </p>
26
- </CardContent>
27
- </Card>
28
- )
29
- }
30
-
31
- export interface MetricCardWithIconProps extends MetricCardProps {
32
- icon?: React.ReactNode
33
- iconColor?: string
34
- }
35
-
36
- export function MetricCardWithIcon({ icon, iconColor = 'bg-[hsl(var(--primary)/0.1)] text-[hsl(var(--primary))]', ...props }: MetricCardWithIconProps) {
37
- const TrendIcon = (props.trend ?? 'up') === 'up' ? TrendingUp : TrendingDown
38
- const trendColor = (props.trend ?? 'up') === 'up' ? 'text-green-600' : 'text-red-500'
39
- return (
40
- <Card className={cn('', props.className)}>
41
- <CardContent className="p-4">
42
- <div className="flex items-start justify-between">
43
- <div>
44
- <p className="text-sm text-[hsl(var(--muted-foreground))]">{props.label ?? 'Total Revenue'}</p>
45
- <p className="text-2xl font-bold mt-1">{props.value ?? '$48,295'}</p>
46
- <p className={cn('flex items-center gap-1 text-sm mt-1', trendColor)}>
47
- <TrendIcon className="h-4 w-4" />{props.change ?? '+12.5%'}
48
- </p>
49
- </div>
50
- <div className={cn('flex h-10 w-10 items-center justify-center rounded-md', iconColor)}>
51
- {icon ?? <TrendingUp className="h-5 w-5" />}
52
- </div>
53
- </div>
54
- </CardContent>
55
- </Card>
56
- )
57
- }
58
-
59
- export interface MetricCardWithButtonProps extends MetricCardProps {
60
- actionLabel?: string
61
- onAction?: () => void
62
- }
63
-
64
- export function MetricCardWithButton({ actionLabel = 'View details', onAction, ...props }: MetricCardWithButtonProps) {
65
- const TrendIcon = (props.trend ?? 'up') === 'up' ? TrendingUp : TrendingDown
66
- const trendColor = (props.trend ?? 'up') === 'up' ? 'text-green-600' : 'text-red-500'
67
- return (
68
- <Card className={cn('', props.className)}>
69
- <CardContent className="p-4">
70
- <p className="text-sm text-[hsl(var(--muted-foreground))]">{props.label ?? 'Total Revenue'}</p>
71
- <p className="text-2xl font-bold mt-1">{props.value ?? '$48,295'}</p>
72
- <p className={cn('flex items-center gap-1 text-sm mt-1 mb-3', trendColor)}>
73
- <TrendIcon className="h-4 w-4" />{props.change ?? '+12.5%'}
74
- </p>
75
- <Button size="sm" variant="outline" className="w-full" onClick={onAction}>{actionLabel}</Button>
76
- </CardContent>
77
- </Card>
78
- )
79
- }
1
+ export * from './MetricCard'
@@ -1,61 +1 @@
1
- import React, { useState } from 'react'
2
- import { cn } from '../utils/cn'
3
- import { Switch } from '../primitives/switch'
4
- import { Separator } from '../primitives/separator'
5
-
6
- const NOTIFICATIONS = [
7
- { id: 'comments', label: 'Comments', description: 'Get notified when someone comments on your post', default: true },
8
- { id: 'mentions', label: 'Mentions', description: 'Get notified when you are mentioned', default: true },
9
- { id: 'followers', label: 'New followers', description: 'Get notified when someone follows you', default: false },
10
- { id: 'updates', label: 'Product updates', description: 'Receive emails about new features and improvements', default: true },
11
- { id: 'digest', label: 'Weekly digest', description: 'A summary of activity from the past week', default: false },
12
- { id: 'security', label: 'Security alerts', description: 'Important alerts about your account security', default: true },
13
- ]
14
-
15
- interface NotifRow {
16
- id: string
17
- label: string
18
- description: string
19
- checked: boolean
20
- onChange: (checked: boolean) => void
21
- }
22
-
23
- function NotifRow({ label, description, checked, onChange }: NotifRow) {
24
- return (
25
- <div className="flex items-center justify-between gap-4 py-4">
26
- <div>
27
- <p className="text-sm font-medium">{label}</p>
28
- <p className="text-xs text-[hsl(var(--muted-foreground))] mt-0.5">{description}</p>
29
- </div>
30
- <Switch checked={checked} onCheckedChange={onChange} />
31
- </div>
32
- )
33
- }
34
-
35
- export interface NotificationSettingsProps { className?: string }
36
-
37
- export function NotificationSettings({ className }: NotificationSettingsProps) {
38
- const [states, setStates] = useState<Record<string, boolean>>(
39
- Object.fromEntries(NOTIFICATIONS.map(n => [n.id, n.default]))
40
- )
41
-
42
- return (
43
- <div className={cn('rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-6', className)}>
44
- <div className="mb-4">
45
- <h2 className="text-base font-semibold">Email Notifications</h2>
46
- <p className="text-sm text-[hsl(var(--muted-foreground))] mt-0.5">Choose what you want to be notified about.</p>
47
- </div>
48
- <Separator />
49
- <div className="divide-y divide-[hsl(var(--border))]">
50
- {NOTIFICATIONS.map(n => (
51
- <NotifRow
52
- key={n.id}
53
- {...n}
54
- checked={states[n.id]}
55
- onChange={val => setStates(s => ({ ...s, [n.id]: val }))}
56
- />
57
- ))}
58
- </div>
59
- </div>
60
- )
61
- }
1
+ export * from './NotificationSettings'
@@ -1,85 +1 @@
1
- import React, { useState } from 'react'
2
- import { ChevronDown, Check, Plus } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuItem,
8
- DropdownMenuLabel,
9
- DropdownMenuSeparator,
10
- DropdownMenuTrigger,
11
- } from '../primitives/dropdown-menu'
12
- import { useSidebar } from '../app-shell/Sidebar'
13
-
14
- const ORGS = [
15
- { id: '1', name: 'Acme Corp', slug: 'acme', plan: 'Pro' },
16
- { id: '2', name: 'Beta Labs', slug: 'beta-labs', plan: 'Starter' },
17
- { id: '3', name: 'Gamma Inc', slug: 'gamma', plan: 'Free' },
18
- ]
19
-
20
- export interface OrgMenuProps {
21
- /** Controlled mode — pass to manage selection externally. If omitted, the menu manages its own state. */
22
- currentOrgId?: string
23
- /** Default selection in uncontrolled mode. Defaults to the first org. */
24
- defaultOrgId?: string
25
- onSwitch?: (orgId: string) => void
26
- onCreateOrg?: () => void
27
- className?: string
28
- }
29
-
30
- export function OrgMenu({
31
- currentOrgId,
32
- defaultOrgId,
33
- onSwitch,
34
- onCreateOrg,
35
- className,
36
- }: OrgMenuProps) {
37
- const [internalId, setInternalId] = useState(defaultOrgId ?? ORGS[0]?.id ?? '1')
38
- const activeId = currentOrgId ?? internalId
39
- const current = ORGS.find(o => o.id === activeId) ?? ORGS[0]
40
- const { collapsed } = useSidebar()
41
-
42
- function handleSwitch(orgId: string) {
43
- if (currentOrgId === undefined) setInternalId(orgId)
44
- onSwitch?.(orgId)
45
- }
46
-
47
- return (
48
- <DropdownMenu>
49
- <DropdownMenuTrigger asChild>
50
- <button
51
- className={cn(
52
- 'flex items-center rounded-md py-1.5 text-sm transition-colors hover:bg-[hsl(var(--accent))] focus:outline-none',
53
- collapsed ? 'justify-center px-0' : 'gap-2 px-2',
54
- className,
55
- )}
56
- >
57
- <div className="flex h-6 w-6 items-center justify-center rounded bg-[hsl(var(--primary))] text-[10px] font-bold text-[hsl(var(--primary-foreground))] shrink-0">
58
- {current.name[0]}
59
- </div>
60
- {!collapsed && <span className="font-medium max-w-[120px] truncate">{current.name}</span>}
61
- {!collapsed && <ChevronDown className="h-3.5 w-3.5 text-[hsl(var(--muted-foreground))]" />}
62
- </button>
63
- </DropdownMenuTrigger>
64
- <DropdownMenuContent align="start" className="w-52">
65
- <DropdownMenuLabel>Organizations</DropdownMenuLabel>
66
- <DropdownMenuSeparator />
67
- {ORGS.map(org => (
68
- <DropdownMenuItem key={org.id} onClick={() => handleSwitch(org.id)} className="justify-between">
69
- <div className="flex items-center gap-2">
70
- <div className="flex h-5 w-5 items-center justify-center rounded bg-[hsl(var(--muted))] text-[10px] font-bold">
71
- {org.name[0]}
72
- </div>
73
- <span>{org.name}</span>
74
- </div>
75
- {org.id === activeId && <Check className="h-3.5 w-3.5 text-[hsl(var(--primary))]" />}
76
- </DropdownMenuItem>
77
- ))}
78
- <DropdownMenuSeparator />
79
- <DropdownMenuItem onClick={onCreateOrg}>
80
- <Plus className="h-4 w-4" />Create organization
81
- </DropdownMenuItem>
82
- </DropdownMenuContent>
83
- </DropdownMenu>
84
- )
85
- }
1
+ export * from './OrganizationMenu'
@@ -1,61 +1 @@
1
- import React, { useState } from 'react'
2
- import { ChevronDown, Check } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuRadioGroup,
8
- DropdownMenuRadioItem,
9
- DropdownMenuLabel,
10
- DropdownMenuSeparator,
11
- DropdownMenuTrigger,
12
- } from '../primitives/dropdown-menu'
13
- import { Button } from '../primitives/button'
14
-
15
- const ROLES = [
16
- { value: 'owner', label: 'Owner', description: 'Full control over the workspace' },
17
- { value: 'admin', label: 'Admin', description: 'Manage members and settings' },
18
- { value: 'member', label: 'Member', description: 'Can create and edit content' },
19
- { value: 'viewer', label: 'Viewer', description: 'Read-only access' },
20
- ]
21
-
22
- export interface RolesMenuProps {
23
- value?: string
24
- onChange?: (role: string) => void
25
- className?: string
26
- }
27
-
28
- export function RolesMenu({ value = 'member', onChange, className }: RolesMenuProps) {
29
- const [role, setRole] = useState(value)
30
- const current = ROLES.find(r => r.value === role) ?? ROLES[2]
31
-
32
- const handleChange = (v: string) => {
33
- setRole(v)
34
- onChange?.(v)
35
- }
36
-
37
- return (
38
- <DropdownMenu>
39
- <DropdownMenuTrigger asChild>
40
- <Button variant="outline" size="sm" className={cn('gap-1.5', className)}>
41
- {current.label}
42
- <ChevronDown className="h-3.5 w-3.5 opacity-60" />
43
- </Button>
44
- </DropdownMenuTrigger>
45
- <DropdownMenuContent align="end" className="w-56">
46
- <DropdownMenuLabel>Change role</DropdownMenuLabel>
47
- <DropdownMenuSeparator />
48
- <DropdownMenuRadioGroup value={role} onValueChange={handleChange}>
49
- {ROLES.map(r => (
50
- <DropdownMenuRadioItem key={r.value} value={r.value} className="items-start py-2.5">
51
- <div className="ml-1">
52
- <p className="text-sm font-medium leading-none">{r.label}</p>
53
- <p className="text-xs text-[hsl(var(--muted-foreground))] mt-0.5">{r.description}</p>
54
- </div>
55
- </DropdownMenuRadioItem>
56
- ))}
57
- </DropdownMenuRadioGroup>
58
- </DropdownMenuContent>
59
- </DropdownMenu>
60
- )
61
- }
1
+ export * from './RolesMenu'
@@ -1,64 +1 @@
1
- import React, { useState } from 'react'
2
- import { Search } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
- import { Input } from '../primitives/input'
5
- import { Button } from '../primitives/button'
6
- import { Checkbox } from '../primitives/checkbox'
7
- import { Avatar, AvatarFallback } from '../primitives/avatar'
8
-
9
- const USERS = [
10
- { id: '1', name: 'Jane Doe', email: 'jane@acme.com', initials: 'JD' },
11
- { id: '2', name: 'Bob Smith', email: 'bob@acme.com', initials: 'BS' },
12
- { id: '3', name: 'Alice Chen', email: 'alice@acme.com', initials: 'AC' },
13
- { id: '4', name: 'Tom Ray', email: 'tom@acme.com', initials: 'TR' },
14
- { id: '5', name: 'Sara Kim', email: 'sara@acme.com', initials: 'SK' },
15
- { id: '6', name: 'Mike Liu', email: 'mike@acme.com', initials: 'ML' },
16
- ]
17
-
18
- export interface SelectUsersModalProps {
19
- onConfirm?: (ids: string[]) => void
20
- className?: string
21
- }
22
-
23
- export function SelectUsersModal({ onConfirm, className }: SelectUsersModalProps) {
24
- const [query, setQuery] = useState('')
25
- const [selected, setSelected] = useState<Set<string>>(new Set())
26
-
27
- const filtered = USERS.filter(u =>
28
- u.name.toLowerCase().includes(query.toLowerCase()) ||
29
- u.email.toLowerCase().includes(query.toLowerCase())
30
- )
31
-
32
- const toggle = (id: string) =>
33
- setSelected(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n })
34
-
35
- return (
36
- <div className={cn('flex flex-col', className)}>
37
- <div className="p-4 border-b border-[hsl(var(--border))]">
38
- <h2 className="text-sm font-semibold mb-3">Select Users</h2>
39
- <div className="relative">
40
- <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[hsl(var(--muted-foreground))]" />
41
- <Input className="pl-8 h-8 text-sm" placeholder="Search users..." value={query} onChange={e => setQuery(e.target.value)} />
42
- </div>
43
- </div>
44
- <div className="overflow-auto max-h-64 divide-y divide-[hsl(var(--border))]">
45
- {filtered.map(u => (
46
- <label key={u.id} className="flex cursor-pointer items-center gap-3 px-4 py-3 hover:bg-[hsl(var(--accent))] transition-colors">
47
- <Checkbox checked={selected.has(u.id)} onCheckedChange={() => toggle(u.id)} />
48
- <Avatar className="h-7 w-7"><AvatarFallback className="text-xs">{u.initials}</AvatarFallback></Avatar>
49
- <div className="flex-1 min-w-0">
50
- <p className="text-sm font-medium truncate">{u.name}</p>
51
- <p className="text-xs text-[hsl(var(--muted-foreground))] truncate">{u.email}</p>
52
- </div>
53
- </label>
54
- ))}
55
- </div>
56
- <div className="flex items-center justify-between p-4 border-t border-[hsl(var(--border))]">
57
- <span className="text-sm text-[hsl(var(--muted-foreground))]">{selected.size} selected</span>
58
- <Button size="sm" disabled={selected.size === 0} onClick={() => onConfirm?.(Array.from(selected))}>
59
- Confirm
60
- </Button>
61
- </div>
62
- </div>
63
- )
64
- }
1
+ export * from './SelectUsersModal'
@@ -1,97 +1 @@
1
- // @ts-nocheck
2
- import React from 'react'
3
- import { LayoutDashboard, FolderOpen, Users, Settings, Bell, TrendingUp } from 'lucide-react'
4
- import { cn } from '../utils/cn'
5
- import { Avatar, AvatarFallback } from '../primitives/avatar'
6
- import { Badge } from '../primitives/badge'
7
- import { Separator } from '../primitives/separator'
8
- import { Card, CardContent } from '../primitives/card'
9
-
10
- const NAV_ITEMS = [
11
- { icon: LayoutDashboard, label: 'Dashboard', active: true },
12
- { icon: FolderOpen, label: 'Projects' },
13
- { icon: Users, label: 'Team' },
14
- { icon: Settings, label: 'Settings' },
15
- ]
16
-
17
- const KPIS = [
18
- { label: 'Total Revenue', value: '$48,295', change: '+12.5%' },
19
- { label: 'Active Users', value: '2,847', change: '+8.3%' },
20
- { label: 'New Orders', value: '384', change: '+3.1%' },
21
- { label: 'Conversion', value: '3.24%', change: '-0.4%' },
22
- ]
23
-
24
- const TABLE_ROWS = [
25
- { name: 'Alpha Project', status: 'Active', team: 'Design', progress: 72 },
26
- { name: 'Beta Launch', status: 'Review', team: 'Engineering', progress: 45 },
27
- { name: 'Gamma Research', status: 'Active', team: 'Product', progress: 89 },
28
- ]
29
-
30
- function NavItem({ icon: Icon, label, active }: { icon: React.ElementType; label: string; active?: boolean }) {
31
- return (
32
- <button className={cn(
33
- 'flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
34
- active ? 'bg-[hsl(var(--accent))] font-medium' : 'text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))]'
35
- )}>
36
- <Icon className="h-4 w-4 shrink-0" />
37
- {label}
38
- </button>
39
- )
40
- }
41
-
42
- export interface SidebarLayoutDashboardProps { className?: string }
43
-
44
- export function SidebarLayoutDashboard({ className }: SidebarLayoutDashboardProps) {
45
- return (
46
- <div className={cn('flex h-[600px] overflow-hidden rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--background))]', className)}>
47
- <aside className="flex w-56 flex-col border-r border-[hsl(var(--border))] bg-[hsl(var(--card))]">
48
- <div className="flex h-12 items-center px-4 font-semibold text-sm">Acme Inc</div>
49
- <Separator />
50
- <nav className="flex-1 space-y-0.5 p-2">
51
- {NAV_ITEMS.map(i => <NavItem key={i.label} {...i} />)}
52
- </nav>
53
- <Separator />
54
- <div className="flex items-center gap-2 p-3">
55
- <Avatar className="h-7 w-7"><AvatarFallback className="text-xs">JD</AvatarFallback></Avatar>
56
- <span className="truncate text-xs font-medium">Jane Doe</span>
57
- </div>
58
- </aside>
59
- <div className="flex flex-1 flex-col overflow-hidden">
60
- <header className="flex h-12 items-center justify-between border-b border-[hsl(var(--border))] px-4">
61
- <h1 className="text-sm font-semibold">Dashboard</h1>
62
- <button className="relative"><Bell className="h-4 w-4" /><span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-[hsl(var(--primary))]" /></button>
63
- </header>
64
- <div className="flex-1 overflow-auto p-4 space-y-4">
65
- <div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%,130px),1fr))' }}>
66
- {KPIS.map(k => (
67
- <Card key={k.label}>
68
- <CardContent className="p-3">
69
- <p className="text-xs text-[hsl(var(--muted-foreground))]">{k.label}</p>
70
- <p className="text-lg font-bold mt-0.5">{k.value}</p>
71
- <p className={cn('text-xs flex items-center gap-0.5 mt-0.5', k.change.startsWith('+') ? 'text-green-600' : 'text-red-500')}>
72
- <TrendingUp className="h-3 w-3" />{k.change}
73
- </p>
74
- </CardContent>
75
- </Card>
76
- ))}
77
- </div>
78
- <div className="rounded-md border border-[hsl(var(--border))]">
79
- <table className="w-full text-xs">
80
- <thead><tr className="border-b border-[hsl(var(--border))]">
81
- {['Project', 'Status', 'Team', 'Progress'].map(h => <th key={h} className="px-3 py-2 text-left font-medium text-[hsl(var(--muted-foreground))]">{h}</th>)}
82
- </tr></thead>
83
- <tbody>{TABLE_ROWS.map(r => (
84
- <tr key={r.name} className="border-b border-[hsl(var(--border))] last:border-0">
85
- <td className="px-3 py-2 font-medium">{r.name}</td>
86
- <td className="px-3 py-2"><Badge variant="secondary" className="text-[10px]">{r.status}</Badge></td>
87
- <td className="px-3 py-2 text-[hsl(var(--muted-foreground))]">{r.team}</td>
88
- <td className="px-3 py-2"><div className="h-1.5 w-20 rounded-full bg-[hsl(var(--muted))]"><div className="h-full rounded-full bg-[hsl(var(--primary))]" style={{ width: `${r.progress}%` }} /></div></td>
89
- </tr>
90
- ))}</tbody>
91
- </table>
92
- </div>
93
- </div>
94
- </div>
95
- </div>
96
- )
97
- }
1
+ export * from './SidebarLayoutDashboard'
@@ -1,79 +1 @@
1
- // @ts-nocheck
2
- import React from 'react'
3
- import { LayoutDashboard, BarChart2, FolderOpen, Users, CreditCard, Settings, HelpCircle } from 'lucide-react'
4
- import { cn } from '../utils/cn'
5
- import { Separator } from '../primitives/separator'
6
-
7
- const NAV_GROUPS = [
8
- {
9
- label: 'Main',
10
- items: [{ icon: LayoutDashboard, label: 'Dashboard', active: true }, { icon: BarChart2, label: 'Analytics' }],
11
- },
12
- {
13
- label: 'Management',
14
- items: [{ icon: FolderOpen, label: 'Projects' }, { icon: Users, label: 'Team' }, { icon: CreditCard, label: 'Billing' }],
15
- },
16
- {
17
- label: 'Account',
18
- items: [{ icon: Settings, label: 'Settings' }, { icon: HelpCircle, label: 'Help' }],
19
- },
20
- ]
21
-
22
- interface NavItemProps { icon: React.ElementType; label: string; active?: boolean }
23
-
24
- function NavItem({ icon: Icon, label, active }: NavItemProps) {
25
- return (
26
- <button className={cn(
27
- 'flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
28
- active
29
- ? 'bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))] font-medium'
30
- : 'text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]'
31
- )}>
32
- <Icon className="h-4 w-4 shrink-0" />
33
- {label}
34
- </button>
35
- )
36
- }
37
-
38
- export interface SidebarLayoutGroupsProps {
39
- brandName?: string
40
- pageTitle?: string
41
- children?: React.ReactNode
42
- className?: string
43
- }
44
-
45
- export function SidebarLayoutGroups({
46
- brandName = 'Acme Inc',
47
- pageTitle = 'Dashboard',
48
- children,
49
- className,
50
- }: SidebarLayoutGroupsProps) {
51
- return (
52
- <div className={cn('flex h-[600px] overflow-hidden rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--background))]', className)}>
53
- <aside className="flex w-60 flex-col border-r border-[hsl(var(--border))] bg-[hsl(var(--card))]">
54
- <div className="flex h-14 items-center px-4 font-semibold text-sm">{brandName}</div>
55
- <Separator />
56
- <nav className="flex-1 overflow-auto p-2 space-y-4">
57
- {NAV_GROUPS.map(group => (
58
- <div key={group.label}>
59
- <p className="mb-1 px-3 text-[10px] font-semibold uppercase tracking-wider text-[hsl(var(--muted-foreground))]">
60
- {group.label}
61
- </p>
62
- <div className="space-y-0.5">
63
- {group.items.map(item => <NavItem key={item.label} {...item} />)}
64
- </div>
65
- </div>
66
- ))}
67
- </nav>
68
- </aside>
69
- <main className="flex flex-1 flex-col overflow-auto">
70
- <header className="flex h-14 items-center border-b border-[hsl(var(--border))] px-6">
71
- <h1 className="text-sm font-semibold">{pageTitle}</h1>
72
- </header>
73
- <div className="flex-1 p-6 text-sm text-[hsl(var(--muted-foreground))]">
74
- {children ?? 'Page content goes here.'}
75
- </div>
76
- </main>
77
- </div>
78
- )
79
- }
1
+ export * from './SidebarLayoutGroups'