@otfdashkit/ui 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +28 -41
- package/dist/index.js +206 -125
- package/dist/index.mjs +194 -124
- package/package.json +5 -4
- package/src/advanced/BulkActions/BulkActions.tsx +2 -2
- package/src/advanced/CommandBar/CommandBar.tsx +2 -2
- package/src/advanced/Filters/Filters.tsx +6 -6
- package/src/advanced/Kanban/Kanban.tsx +1 -1
- package/src/advanced/ToggleButton/ToggleButton.tsx +1 -1
- package/src/advanced/bulk-actions.tsx +1 -46
- package/src/advanced/command-bar.tsx +1 -115
- package/src/advanced/filters.tsx +1 -103
- package/src/advanced/kanban.tsx +1 -95
- package/src/advanced/toggle-button.tsx +1 -78
- package/src/blocks/ChatDetail/ChatDetail.tsx +4 -4
- package/src/blocks/FeedbackModal/FeedbackModal.tsx +3 -3
- package/src/blocks/FileCards/FileCards.tsx +2 -2
- package/src/blocks/FilesList/FilesList.tsx +3 -3
- package/src/blocks/IntegrationCard/IntegrationCard.tsx +3 -3
- package/src/blocks/InviteModal/InviteModal.tsx +7 -7
- package/src/blocks/ManageTagsModal/ManageTagsModal.tsx +4 -4
- package/src/blocks/MessagesCard/MessagesCard.tsx +3 -3
- package/src/blocks/MetricCard/MetricCard.tsx +3 -3
- package/src/blocks/NotificationSettings/NotificationSettings.tsx +4 -4
- package/src/blocks/OrganizationMenu/OrganizationMenu.tsx +30 -11
- package/src/blocks/RolesMenu/RolesMenu.tsx +4 -4
- package/src/blocks/SelectUsersModal/SelectUsersModal.tsx +5 -5
- package/src/blocks/SidebarLayoutDashboard/SidebarLayoutDashboard.tsx +5 -5
- package/src/blocks/SidebarLayoutGroups/SidebarLayoutGroups.tsx +2 -2
- package/src/blocks/SidebarLayoutMinimal/SidebarLayoutMinimal.tsx +3 -3
- package/src/blocks/SidebarLayoutSearch/SidebarLayoutSearch.tsx +3 -3
- package/src/blocks/SidebarLayoutUser/SidebarLayoutUser.tsx +4 -4
- package/src/blocks/SortableTaskList/SortableTaskList.tsx +3 -3
- package/src/blocks/StackedLayoutBranded/StackedLayoutBranded.tsx +4 -4
- package/src/blocks/StackedLayoutTabs/StackedLayoutTabs.tsx +4 -4
- package/src/blocks/TaskCard/TaskCard.tsx +4 -4
- package/src/blocks/UserMenu/UserMenu.tsx +3 -3
- package/src/blocks/WorkspaceMembers/WorkspaceMembers.tsx +5 -5
- package/src/blocks/chat-detail.tsx +1 -73
- package/src/blocks/feedback-modal.tsx +1 -68
- package/src/blocks/file-cards.tsx +1 -56
- package/src/blocks/files-list.tsx +1 -72
- package/src/blocks/integration-card.tsx +1 -50
- package/src/blocks/invite-modal.tsx +1 -69
- package/src/blocks/manage-tags-modal.tsx +1 -63
- package/src/blocks/messages-card.tsx +1 -59
- package/src/blocks/metric-card.tsx +1 -79
- package/src/blocks/notification-settings.tsx +1 -61
- package/src/blocks/organization-menu.tsx +1 -85
- package/src/blocks/roles-menu.tsx +1 -61
- package/src/blocks/select-users-modal.tsx +1 -64
- package/src/blocks/sidebar-layout-dashboard.tsx +1 -97
- package/src/blocks/sidebar-layout-groups.tsx +1 -79
- package/src/blocks/sidebar-layout-minimal.tsx +1 -73
- package/src/blocks/sidebar-layout-search.tsx +1 -79
- package/src/blocks/sidebar-layout-user.tsx +1 -82
- package/src/blocks/sortable-task-list.tsx +1 -70
- package/src/blocks/stacked-layout-branded.tsx +1 -64
- package/src/blocks/stacked-layout-tabs.tsx +1 -80
- package/src/blocks/task-card.tsx +1 -88
- package/src/blocks/user-menu.tsx +1 -62
- package/src/blocks/workspace-members.tsx +1 -61
- package/src/charts/sparkline.tsx +1 -1
- package/src/components/DataGrid/DataGrid.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +4 -4
- package/src/components/EmptyState/EmptyState.tsx +6 -22
- package/src/components/Persona/Persona.tsx +1 -1
- package/src/components/Stat/Stat.tsx +1 -1
- package/src/components/Stepper/Stepper.tsx +13 -2
- package/src/components/banner.tsx +1 -203
- package/src/components/breadcrumb.tsx +1 -58
- package/src/components/command-item.tsx +1 -36
- package/src/components/data-grid.tsx +1 -125
- package/src/components/data-table.tsx +1 -139
- package/src/components/empty-state.tsx +1 -31
- package/src/components/hotkeys.tsx +1 -31
- package/src/components/icon-badge.tsx +1 -35
- package/src/components/loading-overlay.tsx +1 -38
- package/src/components/persona.tsx +1 -38
- package/src/components/property-list.tsx +1 -29
- package/src/components/stat.tsx +1 -51
- package/src/components/stepper.tsx +1 -69
- package/src/components/structured-list.tsx +1 -51
- package/src/components/timeline.tsx +1 -69
- package/src/components/toaster.tsx +1 -42
- package/src/data/DataList.tsx +1 -1
- package/src/engagement/Tour/Tour.tsx +1 -1
- package/src/engagement/beacon.tsx +1 -50
- package/src/engagement/tour.tsx +1 -106
- package/src/forms/ArrayField/ArrayField.tsx +2 -2
- package/src/forms/AutoForm/AutoForm.tsx +9 -8
- package/src/forms/DatePicker/DatePicker.tsx +3 -3
- package/src/forms/FloatingLabelInput/FloatingLabelInput.tsx +104 -0
- package/src/forms/FloatingLabelInput/index.ts +1 -0
- package/src/forms/PasswordInput/PasswordInput.tsx +2 -2
- package/src/forms/SearchInput/SearchInput.tsx +2 -2
- package/src/forms/StepForm/StepForm.tsx +2 -2
- package/src/forms/array-field.tsx +1 -61
- package/src/forms/auto-form.tsx +1 -133
- package/src/forms/date-picker.tsx +1 -109
- package/src/forms/field.tsx +1 -62
- package/src/forms/file-upload.tsx +1 -101
- package/src/forms/floating-label-input.tsx +1 -0
- package/src/forms/form.tsx +1 -57
- package/src/forms/object-field.tsx +1 -53
- package/src/forms/password-input.tsx +1 -54
- package/src/forms/search-input.tsx +1 -64
- package/src/forms/step-form.tsx +1 -113
- package/src/index.ts +6 -3
- package/src/primitives/Button/Button.tsx +18 -0
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/comparison.tsx +1 -1
- package/src/primitives/qr-code.tsx +5 -1
- package/src/themes/base.css +18 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { ChevronDown, Check } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuContent,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
DropdownMenuLabel,
|
|
10
10
|
DropdownMenuSeparator,
|
|
11
11
|
DropdownMenuTrigger,
|
|
12
|
-
} from '
|
|
13
|
-
import { Button } from '
|
|
12
|
+
} from '../../primitives/dropdown-menu'
|
|
13
|
+
import { Button } from '../../primitives/button'
|
|
14
14
|
|
|
15
15
|
const ROLES = [
|
|
16
16
|
{ value: 'owner', label: 'Owner', description: 'Full control over the workspace' },
|
|
@@ -27,7 +27,7 @@ export interface RolesMenuProps {
|
|
|
27
27
|
|
|
28
28
|
export function RolesMenu({ value = 'member', onChange, className }: RolesMenuProps) {
|
|
29
29
|
const [role, setRole] = useState(value)
|
|
30
|
-
const current = ROLES.find(r => r.value === role) ?? ROLES[
|
|
30
|
+
const current = ROLES.find(r => r.value === role) ?? ROLES[0]!
|
|
31
31
|
|
|
32
32
|
const handleChange = (v: string) => {
|
|
33
33
|
setRole(v)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { Search } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Input } from '
|
|
5
|
-
import { Button } from '
|
|
6
|
-
import { Checkbox } from '
|
|
7
|
-
import { Avatar, AvatarFallback } from '
|
|
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
8
|
|
|
9
9
|
const USERS = [
|
|
10
10
|
{ id: '1', name: 'Jane Doe', email: 'jane@acme.com', initials: 'JD' },
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { LayoutDashboard, FolderOpen, Users, Settings, Bell, TrendingUp } from 'lucide-react'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import { Avatar, AvatarFallback } from '
|
|
6
|
-
import { Badge } from '
|
|
7
|
-
import { Separator } from '
|
|
8
|
-
import { Card, CardContent } from '
|
|
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
9
|
|
|
10
10
|
const NAV_ITEMS = [
|
|
11
11
|
{ icon: LayoutDashboard, label: 'Dashboard', active: true },
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { LayoutDashboard, BarChart2, FolderOpen, Users, CreditCard, Settings, HelpCircle } from 'lucide-react'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import { Separator } from '
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { Separator } from '../../primitives/separator'
|
|
6
6
|
|
|
7
7
|
const NAV_GROUPS = [
|
|
8
8
|
{
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { LayoutDashboard, FolderOpen, Users, Settings } from 'lucide-react'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import { Avatar, AvatarFallback } from '
|
|
6
|
-
import { Separator } from '
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
6
|
+
import { Separator } from '../../primitives/separator'
|
|
7
7
|
|
|
8
8
|
const NAV_ITEMS = [
|
|
9
9
|
{ icon: LayoutDashboard, label: 'Dashboard', active: true },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React, { useState } from 'react'
|
|
3
3
|
import { LayoutDashboard, FolderOpen, Users, Settings, BarChart2, Bell } from 'lucide-react'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import { Input } from '
|
|
6
|
-
import { Separator } from '
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { Input } from '../../primitives/input'
|
|
6
|
+
import { Separator } from '../../primitives/separator'
|
|
7
7
|
|
|
8
8
|
const ALL_ITEMS = [
|
|
9
9
|
{ icon: LayoutDashboard, label: 'Dashboard', active: true },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { LayoutDashboard, FolderOpen, Users, Settings, Bell } from 'lucide-react'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import { Avatar, AvatarFallback } from '
|
|
6
|
-
import { Badge } from '
|
|
7
|
-
import { Separator } from '
|
|
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
8
|
|
|
9
9
|
const NAV_ITEMS = [
|
|
10
10
|
{ icon: LayoutDashboard, label: 'Dashboard', active: true },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Checkbox } from '
|
|
4
|
-
import { Avatar, AvatarFallback } from '
|
|
2
|
+
import { cn } from '../../utils/cn'
|
|
3
|
+
import { Checkbox } from '../../primitives/checkbox'
|
|
4
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
5
5
|
|
|
6
6
|
type Priority = 'High' | 'Medium' | 'Low'
|
|
7
7
|
type Status = 'In Progress' | 'Todo' | 'Done'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Bell, ChevronDown, ArrowRight } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Avatar, AvatarFallback } from '
|
|
5
|
-
import { Button } from '
|
|
6
|
-
import { Card, CardContent, CardHeader, CardTitle } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
5
|
+
import { Button } from '../../primitives/button'
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../../primitives/card'
|
|
7
7
|
|
|
8
8
|
const NAV_LINKS = ['Features', 'Pricing', 'Docs', 'Blog']
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { Bell, ChevronDown } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Avatar, AvatarFallback } from '
|
|
5
|
-
import { Button } from '
|
|
6
|
-
import { Separator } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
5
|
+
import { Button } from '../../primitives/button'
|
|
6
|
+
import { Separator } from '../../primitives/separator'
|
|
7
7
|
|
|
8
8
|
const NAV_LINKS = ['Home', 'Projects', 'Analytics', 'Team']
|
|
9
9
|
const TABS = ['Overview', 'Activity', 'Settings', 'Billing']
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Calendar } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Card, CardContent } from '
|
|
5
|
-
import { Avatar, AvatarFallback } from '
|
|
6
|
-
import { Badge } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Card, CardContent } from '../../primitives/card'
|
|
5
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
6
|
+
import { Badge } from '../../primitives/badge'
|
|
7
7
|
|
|
8
8
|
const PRIORITY_COLOR: Record<string, string> = {
|
|
9
9
|
High: 'bg-red-500/10 text-red-600 border-red-200',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ChevronDown, User, Settings, CreditCard, LogOut } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Avatar, AvatarFallback } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
5
5
|
import {
|
|
6
6
|
DropdownMenu,
|
|
7
7
|
DropdownMenuContent,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DropdownMenuLabel,
|
|
10
10
|
DropdownMenuSeparator,
|
|
11
11
|
DropdownMenuTrigger,
|
|
12
|
-
} from '
|
|
12
|
+
} from '../../primitives/dropdown-menu'
|
|
13
13
|
|
|
14
14
|
export interface UserMenuProps {
|
|
15
15
|
name?: string
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { Search, X } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Input } from '
|
|
5
|
-
import { Avatar, AvatarFallback } from '
|
|
6
|
-
import { Badge } from '
|
|
7
|
-
import { Button } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Input } from '../../primitives/input'
|
|
5
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
6
|
+
import { Badge } from '../../primitives/badge'
|
|
7
|
+
import { Button } from '../../primitives/button'
|
|
8
8
|
|
|
9
9
|
const MEMBERS = [
|
|
10
10
|
{ id: '1', name: 'Jane Doe', email: 'jane@acme.com', role: 'Owner', initials: 'JD' },
|
|
@@ -1,73 +1 @@
|
|
|
1
|
-
|
|
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'
|
|
7
|
-
|
|
8
|
-
interface Message { id: string; author: string; initials: string; text: string; time: string; self?: boolean }
|
|
9
|
-
|
|
10
|
-
const INITIAL_MESSAGES: Message[] = [
|
|
11
|
-
{ id: '1', author: 'Alice Chen', initials: 'AC', text: 'Hey team, just pushed the new design files to Figma.', time: '9:41 AM' },
|
|
12
|
-
{ id: '2', author: 'Bob Smith', initials: 'BS', text: "Awesome! I'll take a look and share feedback by EOD.", time: '9:43 AM' },
|
|
13
|
-
{ id: '3', author: 'You', initials: 'JD', text: 'Looks great, the new color palette really pops!', time: '9:45 AM', self: true },
|
|
14
|
-
{ id: '4', author: 'Alice Chen', initials: 'AC', text: "Thanks! Let's sync tomorrow to go through the comments.", time: '9:47 AM' },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
function ChatBubble({ msg }: { msg: Message }) {
|
|
18
|
-
return (
|
|
19
|
-
<div className={cn('flex gap-2.5', msg.self && 'flex-row-reverse')}>
|
|
20
|
-
<Avatar className="h-7 w-7 shrink-0 mt-0.5">
|
|
21
|
-
<AvatarFallback className="text-xs">{msg.initials}</AvatarFallback>
|
|
22
|
-
</Avatar>
|
|
23
|
-
<div className={cn('max-w-[70%]', msg.self && 'items-end flex flex-col')}>
|
|
24
|
-
<div className="flex items-baseline gap-2 mb-0.5">
|
|
25
|
-
<span className="text-xs font-medium">{msg.author}</span>
|
|
26
|
-
<span className="text-[10px] text-[hsl(var(--muted-foreground))]">{msg.time}</span>
|
|
27
|
-
</div>
|
|
28
|
-
<div className={cn(
|
|
29
|
-
'rounded-md px-3 py-2 text-sm',
|
|
30
|
-
msg.self
|
|
31
|
-
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
|
|
32
|
-
: 'bg-[hsl(var(--muted))]'
|
|
33
|
-
)}>
|
|
34
|
-
{msg.text}
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ChatDetailProps { className?: string }
|
|
42
|
-
|
|
43
|
-
export function ChatDetail({ className }: ChatDetailProps) {
|
|
44
|
-
const [messages, setMessages] = useState(INITIAL_MESSAGES)
|
|
45
|
-
const [input, setInput] = useState('')
|
|
46
|
-
|
|
47
|
-
const send = () => {
|
|
48
|
-
if (!input.trim()) return
|
|
49
|
-
const now = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
50
|
-
setMessages(m => [...m, { id: `${Date.now()}`, author: 'You', initials: 'JD', text: input.trim(), time: now, self: true }])
|
|
51
|
-
setInput('')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div className={cn('flex h-[480px] flex-col overflow-hidden rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))]', className)}>
|
|
56
|
-
<header className="flex items-center gap-3 border-b border-[hsl(var(--border))] px-4 py-3">
|
|
57
|
-
<Avatar className="h-8 w-8"><AvatarFallback className="text-xs">AC</AvatarFallback></Avatar>
|
|
58
|
-
<div>
|
|
59
|
-
<p className="text-sm font-semibold">Design Team</p>
|
|
60
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))]">4 members</p>
|
|
61
|
-
</div>
|
|
62
|
-
</header>
|
|
63
|
-
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
64
|
-
{messages.map(m => <ChatBubble key={m.id} msg={m} />)}
|
|
65
|
-
</div>
|
|
66
|
-
<div className="flex items-center gap-2 border-t border-[hsl(var(--border))] p-3">
|
|
67
|
-
<Input className="flex-1 h-9 text-sm" placeholder="Type a message..." value={input}
|
|
68
|
-
onChange={e => setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()} />
|
|
69
|
-
<Button size="icon" className="h-9 w-9 shrink-0" onClick={send}><Send className="h-4 w-4" /></Button>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
1
|
+
export * from './ChatDetail'
|
|
@@ -1,68 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
import { Button } from '../primitives/button'
|
|
4
|
-
import { Textarea } from '../primitives/textarea'
|
|
5
|
-
|
|
6
|
-
const REACTIONS = ['😊', '😐', '😕', '😡', '🤩']
|
|
7
|
-
|
|
8
|
-
export interface FeedbackModalProps {
|
|
9
|
-
onSubmit?: (data: { reaction: string; text: string }) => void
|
|
10
|
-
className?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function FeedbackModal({ onSubmit, className }: FeedbackModalProps) {
|
|
14
|
-
const [reaction, setReaction] = useState('')
|
|
15
|
-
const [text, setText] = useState('')
|
|
16
|
-
const [submitted, setSubmitted] = useState(false)
|
|
17
|
-
|
|
18
|
-
const handleSubmit = () => {
|
|
19
|
-
onSubmit?.({ reaction, text })
|
|
20
|
-
setSubmitted(true)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (submitted) {
|
|
24
|
-
return (
|
|
25
|
-
<div className={cn('flex flex-col items-center gap-3 py-8 px-6 text-center', className)}>
|
|
26
|
-
<span className="text-4xl">🎉</span>
|
|
27
|
-
<p className="font-semibold">Thanks for your feedback!</p>
|
|
28
|
-
<p className="text-sm text-[hsl(var(--muted-foreground))]">We'll use it to improve your experience.</p>
|
|
29
|
-
</div>
|
|
30
|
-
)
|
|
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">Send Feedback</h2>
|
|
37
|
-
<p className="text-sm text-[hsl(var(--muted-foreground))] mt-0.5">How are you feeling about the product?</p>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="flex gap-2">
|
|
40
|
-
{REACTIONS.map(r => (
|
|
41
|
-
<button
|
|
42
|
-
key={r}
|
|
43
|
-
onClick={() => setReaction(r)}
|
|
44
|
-
className={cn(
|
|
45
|
-
'flex h-10 w-10 items-center justify-center rounded-md border text-xl transition-colors',
|
|
46
|
-
reaction === r
|
|
47
|
-
? 'border-[hsl(var(--primary))] bg-[hsl(var(--primary)/0.1)]'
|
|
48
|
-
: 'border-[hsl(var(--border))] hover:bg-[hsl(var(--accent))]'
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
{r}
|
|
52
|
-
</button>
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
<Textarea
|
|
56
|
-
placeholder="Tell us more (optional)..."
|
|
57
|
-
value={text}
|
|
58
|
-
onChange={e => setText(e.target.value)}
|
|
59
|
-
className="resize-none"
|
|
60
|
-
rows={4}
|
|
61
|
-
/>
|
|
62
|
-
<div className="flex justify-end gap-2">
|
|
63
|
-
<Button variant="outline" size="sm">Cancel</Button>
|
|
64
|
-
<Button size="sm" onClick={handleSubmit} disabled={!reaction}>Submit</Button>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
1
|
+
export * from './FeedbackModal'
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { FileText, Image, FileCode, Film, Archive, File } from 'lucide-react'
|
|
4
|
-
import { cn } from '../utils/cn'
|
|
5
|
-
import { Card, CardContent } from '../primitives/card'
|
|
6
|
-
|
|
7
|
-
interface FileItem { id: string; name: string; type: string; size: string; modified: string }
|
|
8
|
-
|
|
9
|
-
const FILES: FileItem[] = [
|
|
10
|
-
{ id: '1', name: 'design-system.fig', type: 'figma', size: '14.2 MB', modified: 'Mar 12' },
|
|
11
|
-
{ id: '2', name: 'hero-banner.png', type: 'image', size: '3.8 MB', modified: 'Mar 11' },
|
|
12
|
-
{ id: '3', name: 'api-spec.yaml', type: 'code', size: '42 KB', modified: 'Mar 10' },
|
|
13
|
-
{ id: '4', name: 'demo-video.mp4', type: 'video', size: '128 MB', modified: 'Mar 9' },
|
|
14
|
-
{ id: '5', name: 'assets.zip', type: 'archive', size: '56 MB', modified: 'Mar 8' },
|
|
15
|
-
{ id: '6', name: 'meeting-notes.pdf', type: 'pdf', size: '1.1 MB', modified: 'Mar 7' },
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
const TYPE_ICON: Record<string, React.ElementType> = {
|
|
19
|
-
figma: FileText, image: Image, code: FileCode, video: Film, archive: Archive, pdf: FileText,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const TYPE_COLOR: Record<string, string> = {
|
|
23
|
-
figma: 'text-purple-500', image: 'text-green-500', code: 'text-blue-500',
|
|
24
|
-
video: 'text-red-500', archive: 'text-yellow-500', pdf: 'text-orange-500',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function FileCard({ file }: { file: FileItem }) {
|
|
28
|
-
const Icon = TYPE_ICON[file.type] ?? File
|
|
29
|
-
return (
|
|
30
|
-
<Card className="cursor-pointer hover:shadow-md transition-shadow">
|
|
31
|
-
<CardContent className="p-4">
|
|
32
|
-
<div className={cn('flex h-10 w-10 items-center justify-center rounded-md bg-[hsl(var(--muted))] mb-3', TYPE_COLOR[file.type])}>
|
|
33
|
-
<Icon className="h-5 w-5" />
|
|
34
|
-
</div>
|
|
35
|
-
<p className="text-sm font-medium truncate">{file.name}</p>
|
|
36
|
-
<div className="flex items-center justify-between mt-1">
|
|
37
|
-
<span className="text-xs text-[hsl(var(--muted-foreground))]">{file.size}</span>
|
|
38
|
-
<span className="text-xs text-[hsl(var(--muted-foreground))]">{file.modified}</span>
|
|
39
|
-
</div>
|
|
40
|
-
</CardContent>
|
|
41
|
-
</Card>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface FileCardsProps {
|
|
46
|
-
files?: FileItem[]
|
|
47
|
-
className?: string
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function FileCards({ files = FILES, className }: FileCardsProps) {
|
|
51
|
-
return (
|
|
52
|
-
<div className={cn('grid gap-3', className)} style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%,160px),1fr))' }}>
|
|
53
|
-
{files.map(f => <FileCard key={f.id} file={f} />)}
|
|
54
|
-
</div>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
1
|
+
export * from './FileCards'
|
|
@@ -1,72 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import React from 'react'
|
|
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'
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
|
|
8
|
-
} from '../primitives/dropdown-menu'
|
|
9
|
-
|
|
10
|
-
interface FileItem { id: string; name: string; type: string; size: string; modified: string }
|
|
11
|
-
|
|
12
|
-
const FILES: FileItem[] = [
|
|
13
|
-
{ id: '1', name: 'design-system.fig', type: 'Figma', size: '14.2 MB', modified: 'Mar 12, 2024' },
|
|
14
|
-
{ id: '2', name: 'hero-banner.png', type: 'PNG Image', size: '3.8 MB', modified: 'Mar 11, 2024' },
|
|
15
|
-
{ id: '3', name: 'api-spec.yaml', type: 'YAML', size: '42 KB', modified: 'Mar 10, 2024' },
|
|
16
|
-
{ id: '4', name: 'demo-video.mp4', type: 'MP4 Video', size: '128 MB', modified: 'Mar 9, 2024' },
|
|
17
|
-
{ id: '5', name: 'assets.zip', type: 'ZIP Archive', size: '56 MB', modified: 'Mar 8, 2024' },
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
const TYPE_ICON: Record<string, React.ElementType> = {
|
|
21
|
-
Figma: FileText, 'PNG Image': Image, YAML: FileCode, 'MP4 Video': Film, 'ZIP Archive': Archive,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function FileRow({ file, onDelete }: { file: FileItem; onDelete: () => void }) {
|
|
25
|
-
const Icon = TYPE_ICON[file.type] ?? FileText
|
|
26
|
-
return (
|
|
27
|
-
<tr className="border-b border-[hsl(var(--border))] last:border-0 hover:bg-[hsl(var(--accent)/0.5)] transition-colors">
|
|
28
|
-
<td className="px-4 py-3">
|
|
29
|
-
<div className="flex items-center gap-2.5">
|
|
30
|
-
<Icon className="h-4 w-4 shrink-0 text-[hsl(var(--muted-foreground))]" />
|
|
31
|
-
<span className="text-sm font-medium">{file.name}</span>
|
|
32
|
-
</div>
|
|
33
|
-
</td>
|
|
34
|
-
<td className="px-4 py-3 text-sm text-[hsl(var(--muted-foreground))]">{file.type}</td>
|
|
35
|
-
<td className="px-4 py-3 text-sm text-[hsl(var(--muted-foreground))]">{file.size}</td>
|
|
36
|
-
<td className="px-4 py-3 text-sm text-[hsl(var(--muted-foreground))]">{file.modified}</td>
|
|
37
|
-
<td className="px-4 py-3 text-right">
|
|
38
|
-
<DropdownMenu>
|
|
39
|
-
<DropdownMenuTrigger asChild>
|
|
40
|
-
<Button size="icon" variant="ghost" className="h-7 w-7"><MoreHorizontal className="h-4 w-4" /></Button>
|
|
41
|
-
</DropdownMenuTrigger>
|
|
42
|
-
<DropdownMenuContent align="end">
|
|
43
|
-
<DropdownMenuItem><Download className="h-4 w-4" />Download</DropdownMenuItem>
|
|
44
|
-
<DropdownMenuItem onClick={onDelete} className="text-[hsl(var(--destructive))]">
|
|
45
|
-
<Trash2 className="h-4 w-4" />Delete
|
|
46
|
-
</DropdownMenuItem>
|
|
47
|
-
</DropdownMenuContent>
|
|
48
|
-
</DropdownMenu>
|
|
49
|
-
</td>
|
|
50
|
-
</tr>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface FilesListProps { className?: string }
|
|
55
|
-
|
|
56
|
-
export function FilesList({ className }: FilesListProps) {
|
|
57
|
-
const [files, setFiles] = React.useState(FILES)
|
|
58
|
-
return (
|
|
59
|
-
<div className={cn('rounded-md border border-[hsl(var(--border))] overflow-hidden', className)}>
|
|
60
|
-
<table className="w-full">
|
|
61
|
-
<thead className="bg-[hsl(var(--muted)/0.5)]">
|
|
62
|
-
<tr>{['Name', 'Type', 'Size', 'Modified', ''].map(h => (
|
|
63
|
-
<th key={h} className="px-4 py-2.5 text-left text-xs font-medium text-[hsl(var(--muted-foreground))]">{h}</th>
|
|
64
|
-
))}</tr>
|
|
65
|
-
</thead>
|
|
66
|
-
<tbody>
|
|
67
|
-
{files.map(f => <FileRow key={f.id} file={f} onDelete={() => setFiles(fs => fs.filter(x => x.id !== f.id))} />)}
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
1
|
+
export * from './FilesList'
|
|
@@ -1,50 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
import { Badge } from '../primitives/badge'
|
|
4
|
-
import { Button } from '../primitives/button'
|
|
5
|
-
|
|
6
|
-
export interface IntegrationCardProps {
|
|
7
|
-
icon?: React.ReactNode
|
|
8
|
-
name?: string
|
|
9
|
-
description?: string
|
|
10
|
-
connected?: boolean
|
|
11
|
-
className?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function IntegrationCard({
|
|
15
|
-
icon,
|
|
16
|
-
name = 'Slack',
|
|
17
|
-
description = 'Send notifications and updates to your Slack workspace.',
|
|
18
|
-
connected = false,
|
|
19
|
-
className,
|
|
20
|
-
}: IntegrationCardProps) {
|
|
21
|
-
const [isConnected, setIsConnected] = useState(connected)
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className={cn('flex items-center gap-4 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4', className)}>
|
|
25
|
-
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-[hsl(var(--muted))] text-[hsl(var(--muted-foreground))]">
|
|
26
|
-
{icon ?? <span className="text-base font-bold">{name[0]}</span>}
|
|
27
|
-
</div>
|
|
28
|
-
<div className="flex-1 min-w-0">
|
|
29
|
-
<div className="flex items-center gap-2">
|
|
30
|
-
<p className="text-sm font-semibold">{name}</p>
|
|
31
|
-
<Badge
|
|
32
|
-
variant={isConnected ? 'default' : 'outline'}
|
|
33
|
-
className="text-[10px] px-1.5 py-0.5 shrink-0"
|
|
34
|
-
>
|
|
35
|
-
{isConnected ? 'Connected' : 'Not connected'}
|
|
36
|
-
</Badge>
|
|
37
|
-
</div>
|
|
38
|
-
<p className="text-xs text-[hsl(var(--muted-foreground))] mt-0.5 truncate">{description}</p>
|
|
39
|
-
</div>
|
|
40
|
-
<Button
|
|
41
|
-
size="sm"
|
|
42
|
-
variant={isConnected ? 'outline' : 'default'}
|
|
43
|
-
className="shrink-0"
|
|
44
|
-
onClick={() => setIsConnected(v => !v)}
|
|
45
|
-
>
|
|
46
|
-
{isConnected ? 'Disconnect' : 'Connect'}
|
|
47
|
-
</Button>
|
|
48
|
-
</div>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
1
|
+
export * from './IntegrationCard'
|
|
@@ -1,69 +1 @@
|
|
|
1
|
-
|
|
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'
|
|
9
|
-
|
|
10
|
-
const PENDING = [
|
|
11
|
-
{ email: 'alex@startup.io', role: 'Member' },
|
|
12
|
-
{ email: 'sam@startup.io', role: 'Viewer' },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
export interface InviteModalProps { className?: string }
|
|
16
|
-
|
|
17
|
-
export function InviteModal({ className }: InviteModalProps) {
|
|
18
|
-
const [email, setEmail] = useState('')
|
|
19
|
-
const [role, setRole] = useState('Member')
|
|
20
|
-
const [pending, setPending] = useState(PENDING)
|
|
21
|
-
|
|
22
|
-
const invite = () => {
|
|
23
|
-
if (!email.trim()) return
|
|
24
|
-
setPending(p => [...p, { email: email.trim(), role }])
|
|
25
|
-
setEmail('')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className={cn('flex flex-col gap-4 p-6', className)}>
|
|
30
|
-
<div>
|
|
31
|
-
<h2 className="text-base font-semibold">Invite People</h2>
|
|
32
|
-
<p className="text-sm text-[hsl(var(--muted-foreground))] mt-0.5">Invite team members to collaborate.</p>
|
|
33
|
-
</div>
|
|
34
|
-
<div className="flex gap-2">
|
|
35
|
-
<Input
|
|
36
|
-
className="flex-1 h-9 text-sm"
|
|
37
|
-
placeholder="colleague@company.com"
|
|
38
|
-
value={email}
|
|
39
|
-
onChange={e => setEmail(e.target.value)}
|
|
40
|
-
onKeyDown={e => e.key === 'Enter' && invite()}
|
|
41
|
-
/>
|
|
42
|
-
<Select value={role} onValueChange={setRole}>
|
|
43
|
-
<SelectTrigger className="h-9 w-28 text-sm"><SelectValue /></SelectTrigger>
|
|
44
|
-
<SelectContent>
|
|
45
|
-
{['Admin', 'Member', 'Viewer'].map(r => <SelectItem key={r} value={r}>{r}</SelectItem>)}
|
|
46
|
-
</SelectContent>
|
|
47
|
-
</Select>
|
|
48
|
-
<Button size="sm" className="h-9" onClick={invite}>Send</Button>
|
|
49
|
-
</div>
|
|
50
|
-
{pending.length > 0 && (
|
|
51
|
-
<div className="space-y-1">
|
|
52
|
-
<p className="text-xs font-medium text-[hsl(var(--muted-foreground))]">Pending invites</p>
|
|
53
|
-
<div className="divide-y divide-[hsl(var(--border))] rounded-md border border-[hsl(var(--border))]">
|
|
54
|
-
{pending.map((p, i) => (
|
|
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>
|
|
57
|
-
<span className="flex-1 text-sm truncate">{p.email}</span>
|
|
58
|
-
<Badge variant="outline" className="text-[10px] px-1.5 py-0.5 shrink-0">{p.role}</Badge>
|
|
59
|
-
<button onClick={() => setPending(ps => ps.filter((_, j) => j !== i))}>
|
|
60
|
-
<X className="h-3.5 w-3.5 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]" />
|
|
61
|
-
</button>
|
|
62
|
-
</div>
|
|
63
|
-
))}
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
)}
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
1
|
+
export * from './InviteModal'
|