braid-ui 1.0.99 → 1.0.100
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/README.md +327 -44
- package/dist/css/braid-ui-variables.css +88 -0
- package/dist/css/braid-ui.css +4702 -0
- package/dist/css/braid-ui.min.css +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2027 -0
- package/dist/index.d.ts +2027 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +115 -55
- package/src/styles-only.css +121 -0
- package/src/{index.css → styles.css} +4 -10
- package/components.json +0 -20
- package/eslint.config.js +0 -29
- package/index.html +0 -24
- package/postcss.config.js +0 -6
- package/public/favicon.ico +0 -0
- package/public/placeholder.svg +0 -1
- package/public/robots.txt +0 -14
- package/src/App.css +0 -42
- package/src/App.tsx +0 -94
- package/src/components/MainLayout.tsx +0 -15
- package/src/components/alerts/AlertDocuments.tsx +0 -320
- package/src/components/alerts/AlertNotes.tsx +0 -185
- package/src/components/alerts/AlertTimeline.tsx +0 -79
- package/src/components/alerts/ContextSection.tsx +0 -155
- package/src/components/app-sidebar.tsx +0 -341
- package/src/components/form-sections/ACHBankCard.tsx +0 -78
- package/src/components/form-sections/ACHBasicInfoCard.tsx +0 -100
- package/src/components/form-sections/ACHTransferSection.tsx +0 -64
- package/src/components/form-sections/AddressForm.tsx +0 -94
- package/src/components/form-sections/BankAddressCard.tsx +0 -95
- package/src/components/form-sections/BankingDetailsCard.tsx +0 -46
- package/src/components/form-sections/BasicInfoCard.tsx +0 -103
- package/src/components/form-sections/BasicInfoSection.tsx +0 -34
- package/src/components/form-sections/BeneficiaryAddress.tsx +0 -19
- package/src/components/form-sections/BeneficiaryCard.tsx +0 -41
- package/src/components/form-sections/BeneficiaryDomesticWire.tsx +0 -23
- package/src/components/form-sections/BusinessProfileCard.tsx +0 -131
- package/src/components/form-sections/BusinessStatusCard.tsx +0 -53
- package/src/components/form-sections/ContactInfoCard.tsx +0 -63
- package/src/components/form-sections/CounterpartyBasicInfo.tsx +0 -101
- package/src/components/form-sections/CounterpartyProfileCard.tsx +0 -104
- package/src/components/form-sections/CounterpartyRecordsCard.tsx +0 -41
- package/src/components/form-sections/IntermediaryCard.tsx +0 -77
- package/src/components/form-sections/IntermediaryFI.tsx +0 -41
- package/src/components/form-sections/IntermediaryFIAddress.tsx +0 -14
- package/src/components/form-sections/OriginatorCard.tsx +0 -49
- package/src/components/form-sections/OriginatorFI.tsx +0 -42
- package/src/components/form-sections/OriginatorFIAddress.tsx +0 -14
- package/src/components/form-sections/PaymentInformationSection.tsx +0 -163
- package/src/components/form-sections/ReceiverCard.tsx +0 -94
- package/src/components/form-sections/WireTransferSection.tsx +0 -75
- package/src/components/layouts/list-page.tsx +0 -103
- package/src/components/transaction/ACHDetailsSection.tsx +0 -95
- package/src/components/transaction/WireDetailsSection.tsx +0 -112
- package/src/components/ui/account-card.tsx +0 -94
- package/src/components/ui/badge.tsx +0 -75
- package/src/components/ui/breadcrumb.tsx +0 -78
- package/src/components/ui/business-type-badge.tsx +0 -42
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/calendar.tsx +0 -49
- package/src/components/ui/card.tsx +0 -223
- package/src/components/ui/container.tsx +0 -45
- package/src/components/ui/counterparty-type-badge.tsx +0 -53
- package/src/components/ui/data-grid.tsx +0 -99
- package/src/components/ui/data-table.tsx +0 -152
- package/src/components/ui/detail-page-layout.tsx +0 -83
- package/src/components/ui/dialog.tsx +0 -120
- package/src/components/ui/dropdown-menu.tsx +0 -82
- package/src/components/ui/editable-form-card.tsx +0 -106
- package/src/components/ui/editable-info-field.tsx +0 -67
- package/src/components/ui/enhanced-input.tsx +0 -78
- package/src/components/ui/enhanced-select.tsx +0 -101
- package/src/components/ui/enhanced-textarea.tsx +0 -64
- package/src/components/ui/entity-card.tsx +0 -140
- package/src/components/ui/form-card.tsx +0 -40
- package/src/components/ui/form-field.tsx +0 -50
- package/src/components/ui/form-input.tsx +0 -29
- package/src/components/ui/form-provider.tsx +0 -18
- package/src/components/ui/form-section.tsx +0 -66
- package/src/components/ui/form-select.tsx +0 -35
- package/src/components/ui/info-field.tsx +0 -36
- package/src/components/ui/json-viewer.tsx +0 -146
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/metric-card.tsx +0 -80
- package/src/components/ui/page-layout.tsx +0 -183
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/responsive-grid.tsx +0 -46
- package/src/components/ui/separator.tsx +0 -31
- package/src/components/ui/sheet.tsx +0 -140
- package/src/components/ui/sidebar.tsx +0 -775
- package/src/components/ui/sonner.tsx +0 -29
- package/src/components/ui/stack.tsx +0 -77
- package/src/components/ui/status-badge.tsx +0 -68
- package/src/components/ui/tabs.tsx +0 -52
- package/src/components/ui/toast.tsx +0 -127
- package/src/components/ui/toaster.tsx +0 -33
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/components/ui/use-toast.ts +0 -3
- package/src/components/ui-kit/dashboard-demo.tsx +0 -156
- package/src/components/ui-kit/pattern-library.tsx +0 -248
- package/src/components/ui-kit/showcase.tsx +0 -211
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/hooks/use-toast.ts +0 -191
- package/src/hooks/useEditState.ts +0 -70
- package/src/hooks/useFormWithEditState.ts +0 -115
- package/src/lib/constants.ts +0 -25
- package/src/lib/mock-data/alert-data.ts +0 -275
- package/src/lib/mock-data/banking-data.ts +0 -72
- package/src/lib/mock-data/business-data.ts +0 -71
- package/src/lib/mock-data/counterparty-data.ts +0 -70
- package/src/lib/mock-data/index.ts +0 -5
- package/src/lib/mock-data/transaction-data.ts +0 -283
- package/src/lib/mock-data/wire-data.ts +0 -103
- package/src/lib/mock-data.tsx +0 -180
- package/src/lib/schemas/banking-schemas.ts +0 -30
- package/src/lib/schemas/business-schemas.ts +0 -36
- package/src/lib/schemas/counterparty-schemas.ts +0 -43
- package/src/lib/schemas/index.ts +0 -5
- package/src/lib/schemas/wire-schemas.ts +0 -44
- package/src/lib/utils.ts +0 -6
- package/src/main.tsx +0 -10
- package/src/pages/Cases.tsx +0 -16
- package/src/pages/Dashboard.tsx +0 -16
- package/src/pages/NotFound.tsx +0 -27
- package/src/pages/TransactionHistory.tsx +0 -532
- package/src/pages/UIKit.tsx +0 -51
- package/src/pages/alerts/AlertDetail.tsx +0 -193
- package/src/pages/alerts/Alerts.tsx +0 -373
- package/src/pages/business/Business.tsx +0 -48
- package/src/pages/business/Create.tsx +0 -173
- package/src/pages/counterparty/Create.tsx +0 -48
- package/src/pages/counterparty/DomesticWire.tsx +0 -78
- package/src/pages/counterparty/Manage.tsx +0 -79
- package/src/pages/transactions/NewTransaction.tsx +0 -527
- package/src/pages/transactions/TransactionDetail.tsx +0 -192
- package/src/vite-env.d.ts +0 -1
- package/tailwind.config.ts +0 -124
- package/tsconfig.app.json +0 -30
- package/tsconfig.json +0 -19
- package/tsconfig.node.json +0 -22
- package/vite.config.ts +0 -22
- /package/{src/assets/braid-logo.png → dist/braid-logo-343BOQZ2.png} +0 -0
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Badge } from "@/components/ui/badge"
|
|
2
|
-
import { Building2, User, Shield, Heart } from "lucide-react"
|
|
3
|
-
|
|
4
|
-
interface CounterpartyTypeBadgeProps {
|
|
5
|
-
type: "BUSINESS" | "INDIVIDUAL" | "GOVERNMENT" | "NONPROFIT"
|
|
6
|
-
className?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const typeConfig = {
|
|
10
|
-
BUSINESS: {
|
|
11
|
-
variant: "business" as const,
|
|
12
|
-
label: "Business",
|
|
13
|
-
icon: Building2
|
|
14
|
-
},
|
|
15
|
-
INDIVIDUAL: {
|
|
16
|
-
variant: "individual" as const,
|
|
17
|
-
label: "Individual",
|
|
18
|
-
icon: User
|
|
19
|
-
},
|
|
20
|
-
GOVERNMENT: {
|
|
21
|
-
variant: "government" as const,
|
|
22
|
-
label: "Government",
|
|
23
|
-
icon: Shield
|
|
24
|
-
},
|
|
25
|
-
NONPROFIT: {
|
|
26
|
-
variant: "nonprofit" as const,
|
|
27
|
-
label: "Non-Profit",
|
|
28
|
-
icon: Heart
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const CounterpartyTypeBadge = ({ type, className }: CounterpartyTypeBadgeProps) => {
|
|
33
|
-
const config = typeConfig[type]
|
|
34
|
-
|
|
35
|
-
// Fallback if config is not found
|
|
36
|
-
if (!config) {
|
|
37
|
-
return (
|
|
38
|
-
<Badge variant="secondary" className={className}>
|
|
39
|
-
<Building2 className="w-3 h-3 mr-1" />
|
|
40
|
-
{type || "Unknown"}
|
|
41
|
-
</Badge>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const Icon = config.icon
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<Badge variant={config.variant} className={className}>
|
|
49
|
-
<Icon className="w-3 h-3 mr-1" />
|
|
50
|
-
{config.label}
|
|
51
|
-
</Badge>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { InfoField } from "@/components/ui/info-field"
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
export interface DataGridItem {
|
|
6
|
-
label?: string
|
|
7
|
-
value: string
|
|
8
|
-
layout?: "vertical" | "horizontal"
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface DataGridSection {
|
|
12
|
-
title?: string
|
|
13
|
-
items: DataGridItem[]
|
|
14
|
-
className?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface DataGridProps {
|
|
18
|
-
data: DataGridItem[] | DataGridSection[]
|
|
19
|
-
columns?: 1 | 2 | 3 | 4
|
|
20
|
-
gap?: "sm" | "md" | "lg"
|
|
21
|
-
className?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const gapClasses = {
|
|
25
|
-
sm: "gap-2",
|
|
26
|
-
md: "gap-4",
|
|
27
|
-
lg: "gap-6"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const columnClasses = {
|
|
31
|
-
1: "grid-cols-1",
|
|
32
|
-
2: "grid-cols-1 md:grid-cols-2",
|
|
33
|
-
3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
|
|
34
|
-
4: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const DataGrid = React.forwardRef<HTMLDivElement, DataGridProps>(
|
|
38
|
-
({ data, columns = 2, gap = "md", className }, ref) => {
|
|
39
|
-
// Check if data is sections or simple items
|
|
40
|
-
const isItemArray = data.length > 0 && 'label' in data[0]
|
|
41
|
-
|
|
42
|
-
if (isItemArray) {
|
|
43
|
-
// Simple items array
|
|
44
|
-
const items = data as DataGridItem[]
|
|
45
|
-
return (
|
|
46
|
-
<div
|
|
47
|
-
ref={ref}
|
|
48
|
-
className={cn(
|
|
49
|
-
"grid",
|
|
50
|
-
columnClasses[columns],
|
|
51
|
-
gapClasses[gap],
|
|
52
|
-
className
|
|
53
|
-
)}
|
|
54
|
-
>
|
|
55
|
-
{items.map((item, index) => (
|
|
56
|
-
<InfoField
|
|
57
|
-
key={index}
|
|
58
|
-
label={item.label}
|
|
59
|
-
value={item.value}
|
|
60
|
-
layout={item.layout || "horizontal"}
|
|
61
|
-
/>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Sections array
|
|
68
|
-
const sections = data as DataGridSection[]
|
|
69
|
-
return (
|
|
70
|
-
<div ref={ref} className={cn("space-y-4", className)}>
|
|
71
|
-
{sections.map((section, sectionIndex) => (
|
|
72
|
-
<div key={sectionIndex} className={section.className}>
|
|
73
|
-
{section.title && (
|
|
74
|
-
<h4 className="text-sm font-medium text-foreground mb-2">
|
|
75
|
-
{section.title}
|
|
76
|
-
</h4>
|
|
77
|
-
)}
|
|
78
|
-
<div className={cn(
|
|
79
|
-
"grid",
|
|
80
|
-
columnClasses[columns],
|
|
81
|
-
gapClasses[gap]
|
|
82
|
-
)}>
|
|
83
|
-
{section.items.map((item, itemIndex) => (
|
|
84
|
-
<InfoField
|
|
85
|
-
key={itemIndex}
|
|
86
|
-
label={item.label}
|
|
87
|
-
value={item.value}
|
|
88
|
-
layout={item.layout || "horizontal"}
|
|
89
|
-
/>
|
|
90
|
-
))}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
))}
|
|
94
|
-
</div>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
DataGrid.displayName = "DataGrid"
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cn } from "@/lib/utils"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
import { Badge } from "@/components/ui/badge"
|
|
5
|
-
import { ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react"
|
|
6
|
-
|
|
7
|
-
export interface DataTableColumn<T = any> {
|
|
8
|
-
key: string
|
|
9
|
-
title: string
|
|
10
|
-
sortable?: boolean
|
|
11
|
-
render?: (value: any, row: T) => React.ReactNode
|
|
12
|
-
width?: string
|
|
13
|
-
align?: "left" | "center" | "right"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DataTableProps<T = any> {
|
|
17
|
-
columns: DataTableColumn<T>[]
|
|
18
|
-
data: T[]
|
|
19
|
-
sortBy?: string
|
|
20
|
-
sortDirection?: "asc" | "desc"
|
|
21
|
-
onSort?: (key: string) => void
|
|
22
|
-
loading?: boolean
|
|
23
|
-
emptyMessage?: string
|
|
24
|
-
className?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function DataTable<T extends Record<string, any>>({
|
|
28
|
-
columns,
|
|
29
|
-
data,
|
|
30
|
-
sortBy,
|
|
31
|
-
sortDirection,
|
|
32
|
-
onSort,
|
|
33
|
-
loading = false,
|
|
34
|
-
emptyMessage = "No data available",
|
|
35
|
-
className
|
|
36
|
-
}: DataTableProps<T>) {
|
|
37
|
-
const handleSort = (key: string) => {
|
|
38
|
-
if (onSort) {
|
|
39
|
-
onSort(key)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const renderSortIcon = (column: DataTableColumn<T>) => {
|
|
44
|
-
if (!column.sortable || !onSort) return null
|
|
45
|
-
|
|
46
|
-
if (sortBy === column.key) {
|
|
47
|
-
return sortDirection === "asc" ?
|
|
48
|
-
<ChevronUp className="w-4 h-4 ml-1" /> :
|
|
49
|
-
<ChevronDown className="w-4 h-4 ml-1" />
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return <ChevronsUpDown className="w-4 h-4 ml-1 opacity-50" />
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const renderCell = (column: DataTableColumn<T>, row: T) => {
|
|
56
|
-
const value = row[column.key]
|
|
57
|
-
|
|
58
|
-
if (column.render) {
|
|
59
|
-
return column.render(value, row)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Auto-render common types
|
|
63
|
-
if (typeof value === "boolean") {
|
|
64
|
-
return <Badge variant={value ? "success" : "secondary"}>{value ? "Yes" : "No"}</Badge>
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (Array.isArray(value)) {
|
|
68
|
-
return value.join(", ")
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return value?.toString() || "-"
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (loading) {
|
|
75
|
-
return (
|
|
76
|
-
<div className={cn("rounded-md border", className)}>
|
|
77
|
-
<div className="p-8 text-center">
|
|
78
|
-
<div className="animate-pulse text-muted-foreground">Loading...</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div className={cn("rounded-md border", className)}>
|
|
86
|
-
<div className="overflow-x-auto">
|
|
87
|
-
<table className="w-full">
|
|
88
|
-
<thead>
|
|
89
|
-
<tr className="border-b bg-muted/50">
|
|
90
|
-
{columns.map((column) => (
|
|
91
|
-
<th
|
|
92
|
-
key={column.key}
|
|
93
|
-
className={cn(
|
|
94
|
-
"h-12 px-4 text-left align-middle font-medium text-muted-foreground",
|
|
95
|
-
column.align === "center" && "text-center",
|
|
96
|
-
column.align === "right" && "text-right",
|
|
97
|
-
column.width && `w-[${column.width}]`
|
|
98
|
-
)}
|
|
99
|
-
>
|
|
100
|
-
{column.sortable && onSort ? (
|
|
101
|
-
<Button
|
|
102
|
-
variant="ghost"
|
|
103
|
-
size="sm"
|
|
104
|
-
className="h-auto p-0 font-medium hover:bg-transparent"
|
|
105
|
-
onClick={() => handleSort(column.key)}
|
|
106
|
-
>
|
|
107
|
-
<span className="flex items-center">
|
|
108
|
-
{column.title}
|
|
109
|
-
{renderSortIcon(column)}
|
|
110
|
-
</span>
|
|
111
|
-
</Button>
|
|
112
|
-
) : (
|
|
113
|
-
column.title
|
|
114
|
-
)}
|
|
115
|
-
</th>
|
|
116
|
-
))}
|
|
117
|
-
</tr>
|
|
118
|
-
</thead>
|
|
119
|
-
<tbody>
|
|
120
|
-
{data.length === 0 ? (
|
|
121
|
-
<tr>
|
|
122
|
-
<td colSpan={columns.length} className="h-24 text-center text-muted-foreground">
|
|
123
|
-
{emptyMessage}
|
|
124
|
-
</td>
|
|
125
|
-
</tr>
|
|
126
|
-
) : (
|
|
127
|
-
data.map((row, index) => (
|
|
128
|
-
<tr
|
|
129
|
-
key={index}
|
|
130
|
-
className="border-b transition-colors hover:bg-muted/50"
|
|
131
|
-
>
|
|
132
|
-
{columns.map((column) => (
|
|
133
|
-
<td
|
|
134
|
-
key={column.key}
|
|
135
|
-
className={cn(
|
|
136
|
-
"p-4 align-middle",
|
|
137
|
-
column.align === "center" && "text-center",
|
|
138
|
-
column.align === "right" && "text-right"
|
|
139
|
-
)}
|
|
140
|
-
>
|
|
141
|
-
{renderCell(column, row)}
|
|
142
|
-
</td>
|
|
143
|
-
))}
|
|
144
|
-
</tr>
|
|
145
|
-
))
|
|
146
|
-
)}
|
|
147
|
-
</tbody>
|
|
148
|
-
</table>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { ReactNode, useState } from "react"
|
|
2
|
-
import { PageLayout } from "@/components/ui/page-layout"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
|
|
5
|
-
interface DetailPageCard {
|
|
6
|
-
key: string
|
|
7
|
-
component: (props: { isEditing: boolean; onToggleEdit: () => void; className?: string }) => ReactNode
|
|
8
|
-
expandOnEdit?: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface DetailPageAction {
|
|
12
|
-
label: string
|
|
13
|
-
variant?: "default" | "outline" | "destructive" | "secondary" | "ghost"
|
|
14
|
-
onClick?: () => void
|
|
15
|
-
className?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface DetailPageLayoutProps {
|
|
19
|
-
title: string
|
|
20
|
-
description: string
|
|
21
|
-
cards: DetailPageCard[]
|
|
22
|
-
actions?: DetailPageAction[]
|
|
23
|
-
initialEditingState?: Record<string, boolean>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const DetailPageLayout = ({
|
|
27
|
-
title,
|
|
28
|
-
description,
|
|
29
|
-
cards,
|
|
30
|
-
actions,
|
|
31
|
-
initialEditingState = {}
|
|
32
|
-
}: DetailPageLayoutProps) => {
|
|
33
|
-
const [editingCards, setEditingCards] = useState<Record<string, boolean>>(
|
|
34
|
-
initialEditingState
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
const toggleEdit = (cardKey: string) => {
|
|
38
|
-
setEditingCards(prev => ({
|
|
39
|
-
...prev,
|
|
40
|
-
[cardKey]: !prev[cardKey]
|
|
41
|
-
}))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const isAnyCardEditing = Object.values(editingCards).some(Boolean)
|
|
45
|
-
|
|
46
|
-
const isSingleCard = cards.length === 1
|
|
47
|
-
const gridClasses = isSingleCard
|
|
48
|
-
? "space-y-4 mb-6 lg:space-y-6"
|
|
49
|
-
: "grid gap-4 mb-6 grid-cols-1 lg:grid-cols-2 lg:gap-6"
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<PageLayout
|
|
53
|
-
title={title}
|
|
54
|
-
description={description}
|
|
55
|
-
actions={actions?.map(action => ({
|
|
56
|
-
label: action.label,
|
|
57
|
-
variant: action.variant || "default",
|
|
58
|
-
onClick: action.onClick || (() => {})
|
|
59
|
-
}))}
|
|
60
|
-
>
|
|
61
|
-
{/* Cards - Full width for single card, max 2 per row for multiple */}
|
|
62
|
-
<div className={gridClasses}>
|
|
63
|
-
{cards.map((card) => {
|
|
64
|
-
const isEditing = editingCards[card.key] || false
|
|
65
|
-
const shouldExpand = !isSingleCard && card.expandOnEdit !== false && isEditing
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div
|
|
69
|
-
key={card.key}
|
|
70
|
-
className={shouldExpand ? "col-span-full" : ""}
|
|
71
|
-
>
|
|
72
|
-
{card.component({
|
|
73
|
-
isEditing,
|
|
74
|
-
onToggleEdit: () => toggleEdit(card.key),
|
|
75
|
-
className: shouldExpand ? "col-span-full" : ""
|
|
76
|
-
})}
|
|
77
|
-
</div>
|
|
78
|
-
)
|
|
79
|
-
})}
|
|
80
|
-
</div>
|
|
81
|
-
</PageLayout>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
3
|
-
import { X } from "lucide-react"
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
const Dialog = DialogPrimitive.Root
|
|
8
|
-
|
|
9
|
-
const DialogTrigger = DialogPrimitive.Trigger
|
|
10
|
-
|
|
11
|
-
const DialogPortal = DialogPrimitive.Portal
|
|
12
|
-
|
|
13
|
-
const DialogClose = DialogPrimitive.Close
|
|
14
|
-
|
|
15
|
-
const DialogOverlay = React.forwardRef<
|
|
16
|
-
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
17
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
18
|
-
>(({ className, ...props }, ref) => (
|
|
19
|
-
<DialogPrimitive.Overlay
|
|
20
|
-
ref={ref}
|
|
21
|
-
className={cn(
|
|
22
|
-
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
23
|
-
className
|
|
24
|
-
)}
|
|
25
|
-
{...props}
|
|
26
|
-
/>
|
|
27
|
-
))
|
|
28
|
-
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
29
|
-
|
|
30
|
-
const DialogContent = React.forwardRef<
|
|
31
|
-
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
32
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
33
|
-
>(({ className, children, ...props }, ref) => (
|
|
34
|
-
<DialogPortal>
|
|
35
|
-
<DialogOverlay />
|
|
36
|
-
<DialogPrimitive.Content
|
|
37
|
-
ref={ref}
|
|
38
|
-
className={cn(
|
|
39
|
-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-4xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg max-h-[90vh] overflow-hidden",
|
|
40
|
-
className
|
|
41
|
-
)}
|
|
42
|
-
{...props}
|
|
43
|
-
>
|
|
44
|
-
{children}
|
|
45
|
-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
46
|
-
<X className="h-4 w-4" />
|
|
47
|
-
<span className="sr-only">Close</span>
|
|
48
|
-
</DialogPrimitive.Close>
|
|
49
|
-
</DialogPrimitive.Content>
|
|
50
|
-
</DialogPortal>
|
|
51
|
-
))
|
|
52
|
-
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
53
|
-
|
|
54
|
-
const DialogHeader = ({
|
|
55
|
-
className,
|
|
56
|
-
...props
|
|
57
|
-
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
58
|
-
<div
|
|
59
|
-
className={cn(
|
|
60
|
-
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
61
|
-
className
|
|
62
|
-
)}
|
|
63
|
-
{...props}
|
|
64
|
-
/>
|
|
65
|
-
)
|
|
66
|
-
DialogHeader.displayName = "DialogHeader"
|
|
67
|
-
|
|
68
|
-
const DialogFooter = ({
|
|
69
|
-
className,
|
|
70
|
-
...props
|
|
71
|
-
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
72
|
-
<div
|
|
73
|
-
className={cn(
|
|
74
|
-
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
75
|
-
className
|
|
76
|
-
)}
|
|
77
|
-
{...props}
|
|
78
|
-
/>
|
|
79
|
-
)
|
|
80
|
-
DialogFooter.displayName = "DialogFooter"
|
|
81
|
-
|
|
82
|
-
const DialogTitle = React.forwardRef<
|
|
83
|
-
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
84
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
85
|
-
>(({ className, ...props }, ref) => (
|
|
86
|
-
<DialogPrimitive.Title
|
|
87
|
-
ref={ref}
|
|
88
|
-
className={cn(
|
|
89
|
-
"text-lg font-semibold leading-none tracking-tight",
|
|
90
|
-
className
|
|
91
|
-
)}
|
|
92
|
-
{...props}
|
|
93
|
-
/>
|
|
94
|
-
))
|
|
95
|
-
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
96
|
-
|
|
97
|
-
const DialogDescription = React.forwardRef<
|
|
98
|
-
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
99
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
100
|
-
>(({ className, ...props }, ref) => (
|
|
101
|
-
<DialogPrimitive.Description
|
|
102
|
-
ref={ref}
|
|
103
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
104
|
-
{...props}
|
|
105
|
-
/>
|
|
106
|
-
))
|
|
107
|
-
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
108
|
-
|
|
109
|
-
export {
|
|
110
|
-
Dialog,
|
|
111
|
-
DialogPortal,
|
|
112
|
-
DialogOverlay,
|
|
113
|
-
DialogClose,
|
|
114
|
-
DialogTrigger,
|
|
115
|
-
DialogContent,
|
|
116
|
-
DialogHeader,
|
|
117
|
-
DialogFooter,
|
|
118
|
-
DialogTitle,
|
|
119
|
-
DialogDescription,
|
|
120
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
6
|
-
|
|
7
|
-
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
8
|
-
|
|
9
|
-
const DropdownMenuContent = React.forwardRef<
|
|
10
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
11
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
12
|
-
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
13
|
-
<DropdownMenuPrimitive.Portal>
|
|
14
|
-
<DropdownMenuPrimitive.Content
|
|
15
|
-
ref={ref}
|
|
16
|
-
sideOffset={sideOffset}
|
|
17
|
-
className={cn(
|
|
18
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
19
|
-
className
|
|
20
|
-
)}
|
|
21
|
-
{...props}
|
|
22
|
-
/>
|
|
23
|
-
</DropdownMenuPrimitive.Portal>
|
|
24
|
-
))
|
|
25
|
-
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
26
|
-
|
|
27
|
-
const DropdownMenuItem = React.forwardRef<
|
|
28
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
29
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
30
|
-
inset?: boolean
|
|
31
|
-
}
|
|
32
|
-
>(({ className, inset, ...props }, ref) => (
|
|
33
|
-
<DropdownMenuPrimitive.Item
|
|
34
|
-
ref={ref}
|
|
35
|
-
className={cn(
|
|
36
|
-
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
37
|
-
inset && "pl-8",
|
|
38
|
-
className
|
|
39
|
-
)}
|
|
40
|
-
{...props}
|
|
41
|
-
/>
|
|
42
|
-
))
|
|
43
|
-
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
44
|
-
|
|
45
|
-
const DropdownMenuLabel = React.forwardRef<
|
|
46
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
47
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
48
|
-
inset?: boolean
|
|
49
|
-
}
|
|
50
|
-
>(({ className, inset, ...props }, ref) => (
|
|
51
|
-
<DropdownMenuPrimitive.Label
|
|
52
|
-
ref={ref}
|
|
53
|
-
className={cn(
|
|
54
|
-
"px-2 py-1.5 text-sm font-semibold",
|
|
55
|
-
inset && "pl-8",
|
|
56
|
-
className
|
|
57
|
-
)}
|
|
58
|
-
{...props}
|
|
59
|
-
/>
|
|
60
|
-
))
|
|
61
|
-
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
62
|
-
|
|
63
|
-
const DropdownMenuSeparator = React.forwardRef<
|
|
64
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
65
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
66
|
-
>(({ className, ...props }, ref) => (
|
|
67
|
-
<DropdownMenuPrimitive.Separator
|
|
68
|
-
ref={ref}
|
|
69
|
-
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
))
|
|
73
|
-
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
74
|
-
|
|
75
|
-
export {
|
|
76
|
-
DropdownMenu,
|
|
77
|
-
DropdownMenuTrigger,
|
|
78
|
-
DropdownMenuContent,
|
|
79
|
-
DropdownMenuItem,
|
|
80
|
-
DropdownMenuLabel,
|
|
81
|
-
DropdownMenuSeparator,
|
|
82
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { FormCard, type FormCardProps } from "@/components/ui/form-card"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
import { Edit } from "lucide-react"
|
|
5
|
-
import { useEditState } from "@/hooks/useEditState"
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
7
|
-
|
|
8
|
-
interface EditableFormCardProps extends Omit<FormCardProps, 'headerActions' | 'children'> {
|
|
9
|
-
title: string
|
|
10
|
-
description?: string
|
|
11
|
-
children?: React.ReactNode
|
|
12
|
-
editContent: React.ReactNode
|
|
13
|
-
viewContent: React.ReactNode
|
|
14
|
-
isEditing?: boolean
|
|
15
|
-
onToggleEdit?: () => void
|
|
16
|
-
hideActions?: boolean
|
|
17
|
-
onSave?: () => void | Promise<void>
|
|
18
|
-
onCancel?: () => void
|
|
19
|
-
className?: string
|
|
20
|
-
isFormValid?: boolean
|
|
21
|
-
isDirty?: boolean
|
|
22
|
-
isSubmitting?: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const EditableFormCard = React.forwardRef<HTMLDivElement, EditableFormCardProps>(
|
|
26
|
-
({
|
|
27
|
-
title,
|
|
28
|
-
description,
|
|
29
|
-
editContent,
|
|
30
|
-
viewContent,
|
|
31
|
-
isEditing,
|
|
32
|
-
onToggleEdit,
|
|
33
|
-
hideActions = false,
|
|
34
|
-
onSave,
|
|
35
|
-
onCancel,
|
|
36
|
-
className,
|
|
37
|
-
variant = "subtle",
|
|
38
|
-
isFormValid = true,
|
|
39
|
-
isDirty = false,
|
|
40
|
-
isSubmitting = false,
|
|
41
|
-
...props
|
|
42
|
-
}, ref) => {
|
|
43
|
-
const { isEditing: currentlyEditing, handleToggleEdit, handleCancel } = useEditState({
|
|
44
|
-
initialEditing: isEditing ?? false,
|
|
45
|
-
onToggleEdit
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const handleSaveAction = async () => {
|
|
49
|
-
if (onSave) {
|
|
50
|
-
await onSave()
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const handleCancelAction = () => {
|
|
55
|
-
if (onCancel) {
|
|
56
|
-
onCancel()
|
|
57
|
-
} else {
|
|
58
|
-
handleCancel()
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<FormCard
|
|
64
|
-
ref={ref}
|
|
65
|
-
title={title}
|
|
66
|
-
description={description}
|
|
67
|
-
variant={variant}
|
|
68
|
-
className={className}
|
|
69
|
-
headerActions={
|
|
70
|
-
!hideActions ? (
|
|
71
|
-
<div className="flex items-center gap-2">
|
|
72
|
-
{currentlyEditing ? (
|
|
73
|
-
<>
|
|
74
|
-
<Button variant="outline" size="sm" onClick={handleCancelAction}>
|
|
75
|
-
Cancel
|
|
76
|
-
</Button>
|
|
77
|
-
<Button
|
|
78
|
-
size="sm"
|
|
79
|
-
onClick={handleSaveAction}
|
|
80
|
-
disabled={!isFormValid || isSubmitting}
|
|
81
|
-
>
|
|
82
|
-
{isSubmitting ? "Saving..." : "Save"}
|
|
83
|
-
</Button>
|
|
84
|
-
</>
|
|
85
|
-
) : (
|
|
86
|
-
<Button
|
|
87
|
-
variant="ghost"
|
|
88
|
-
size="icon"
|
|
89
|
-
onClick={handleToggleEdit}
|
|
90
|
-
className="text-primary hover:text-primary/80 hover:bg-primary/10"
|
|
91
|
-
>
|
|
92
|
-
<Edit className="h-4 w-4" />
|
|
93
|
-
</Button>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
) : undefined
|
|
97
|
-
}
|
|
98
|
-
{...props}
|
|
99
|
-
>
|
|
100
|
-
{currentlyEditing ? editContent : viewContent}
|
|
101
|
-
</FormCard>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
EditableFormCard.displayName = "EditableFormCard"
|