@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.
- package/LICENSE +1 -1
- package/README.md +28 -41
- package/dist/index.js +972 -655
- package/dist/index.mjs +826 -525
- 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 +11 -3
- package/src/primitives/Button/Button.tsx +18 -0
- package/src/primitives/cart-line-item.tsx +81 -0
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/comparison.tsx +1 -1
- package/src/primitives/empty-cart.tsx +52 -0
- package/src/primitives/price-tag.tsx +62 -0
- package/src/primitives/qr-code.tsx +5 -1
- package/src/primitives/quantity-stepper.tsx +92 -0
- package/src/primitives/variant-picker.tsx +74 -0
- package/src/themes/base.css +18 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Avatar, AvatarFallback } from '
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '
|
|
2
|
+
import { cn } from '../../utils/cn'
|
|
3
|
+
import { Avatar, AvatarFallback } from '../../primitives/avatar'
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../../primitives/card'
|
|
5
5
|
|
|
6
6
|
interface Message {
|
|
7
7
|
id: string
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
4
|
-
import { Card, CardContent } from '
|
|
5
|
-
import { Button } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
|
+
import { Card, CardContent } from '../../primitives/card'
|
|
5
|
+
import { Button } from '../../primitives/button'
|
|
6
6
|
|
|
7
7
|
export interface MetricCardProps {
|
|
8
8
|
label?: string
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import { cn } from '
|
|
3
|
-
import { Switch } from '
|
|
4
|
-
import { Separator } from '
|
|
2
|
+
import { cn } from '../../utils/cn'
|
|
3
|
+
import { Switch } from '../../primitives/switch'
|
|
4
|
+
import { Separator } from '../../primitives/separator'
|
|
5
5
|
|
|
6
6
|
const NOTIFICATIONS = [
|
|
7
7
|
{ id: 'comments', label: 'Comments', description: 'Get notified when someone comments on your post', default: true },
|
|
@@ -51,7 +51,7 @@ export function NotificationSettings({ className }: NotificationSettingsProps) {
|
|
|
51
51
|
<NotifRow
|
|
52
52
|
key={n.id}
|
|
53
53
|
{...n}
|
|
54
|
-
checked={states[n.id]}
|
|
54
|
+
checked={states[n.id] ?? false}
|
|
55
55
|
onChange={val => setStates(s => ({ ...s, [n.id]: val }))}
|
|
56
56
|
/>
|
|
57
57
|
))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
import { ChevronDown, Check, Plus } from 'lucide-react'
|
|
3
|
-
import { cn } from '
|
|
3
|
+
import { cn } from '../../utils/cn'
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuContent,
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
DropdownMenuLabel,
|
|
9
9
|
DropdownMenuSeparator,
|
|
10
10
|
DropdownMenuTrigger,
|
|
11
|
-
} from '
|
|
11
|
+
} from '../../primitives/dropdown-menu'
|
|
12
|
+
import { useSidebar } from '../../app-shell/Sidebar'
|
|
12
13
|
|
|
13
14
|
const ORGS = [
|
|
14
15
|
{ id: '1', name: 'Acme Corp', slug: 'acme', plan: 'Pro' },
|
|
@@ -17,43 +18,61 @@ const ORGS = [
|
|
|
17
18
|
]
|
|
18
19
|
|
|
19
20
|
export interface OrgMenuProps {
|
|
21
|
+
/** Controlled mode — pass to manage selection externally. If omitted, the menu manages its own state. */
|
|
20
22
|
currentOrgId?: string
|
|
23
|
+
/** Default selection in uncontrolled mode. Defaults to the first org. */
|
|
24
|
+
defaultOrgId?: string
|
|
21
25
|
onSwitch?: (orgId: string) => void
|
|
22
26
|
onCreateOrg?: () => void
|
|
23
27
|
className?: string
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function OrgMenu({
|
|
27
|
-
currentOrgId
|
|
31
|
+
currentOrgId,
|
|
32
|
+
defaultOrgId,
|
|
28
33
|
onSwitch,
|
|
29
34
|
onCreateOrg,
|
|
30
35
|
className,
|
|
31
36
|
}: OrgMenuProps) {
|
|
32
|
-
const
|
|
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
|
+
}
|
|
33
46
|
|
|
34
47
|
return (
|
|
35
48
|
<DropdownMenu>
|
|
36
49
|
<DropdownMenuTrigger asChild>
|
|
37
|
-
<button
|
|
38
|
-
|
|
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">
|
|
39
58
|
{current.name[0]}
|
|
40
59
|
</div>
|
|
41
|
-
<span className="font-medium max-w-[120px] truncate">{current.name}</span>
|
|
42
|
-
<ChevronDown className="h-3.5 w-3.5 text-[hsl(var(--muted-foreground))]" />
|
|
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))]" />}
|
|
43
62
|
</button>
|
|
44
63
|
</DropdownMenuTrigger>
|
|
45
64
|
<DropdownMenuContent align="start" className="w-52">
|
|
46
65
|
<DropdownMenuLabel>Organizations</DropdownMenuLabel>
|
|
47
66
|
<DropdownMenuSeparator />
|
|
48
67
|
{ORGS.map(org => (
|
|
49
|
-
<DropdownMenuItem key={org.id} onClick={() =>
|
|
68
|
+
<DropdownMenuItem key={org.id} onClick={() => handleSwitch(org.id)} className="justify-between">
|
|
50
69
|
<div className="flex items-center gap-2">
|
|
51
70
|
<div className="flex h-5 w-5 items-center justify-center rounded bg-[hsl(var(--muted))] text-[10px] font-bold">
|
|
52
71
|
{org.name[0]}
|
|
53
72
|
</div>
|
|
54
73
|
<span>{org.name}</span>
|
|
55
74
|
</div>
|
|
56
|
-
{org.id ===
|
|
75
|
+
{org.id === activeId && <Check className="h-3.5 w-3.5 text-[hsl(var(--primary))]" />}
|
|
57
76
|
</DropdownMenuItem>
|
|
58
77
|
))}
|
|
59
78
|
<DropdownMenuSeparator />
|
|
@@ -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'
|