braid-ui 1.0.99 → 1.0.101
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,35 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Controller, useFormContext, FieldPath, FieldValues } from "react-hook-form"
|
|
3
|
-
import { EnhancedSelect } from "@/components/ui/enhanced-select"
|
|
4
|
-
|
|
5
|
-
interface FormSelectProps<T extends FieldValues = FieldValues> {
|
|
6
|
-
name: FieldPath<T>
|
|
7
|
-
label?: string
|
|
8
|
-
hint?: string
|
|
9
|
-
placeholder?: string
|
|
10
|
-
options: { value: string; label: string; disabled?: boolean }[]
|
|
11
|
-
disabled?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const FormSelect = <T extends FieldValues = FieldValues>({
|
|
15
|
-
name,
|
|
16
|
-
...props
|
|
17
|
-
}: FormSelectProps<T>) => {
|
|
18
|
-
const { control, formState } = useFormContext<T>()
|
|
19
|
-
const error = formState.errors[name]?.message as string | undefined
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Controller
|
|
23
|
-
name={name}
|
|
24
|
-
control={control}
|
|
25
|
-
render={({ field }) => (
|
|
26
|
-
<EnhancedSelect
|
|
27
|
-
{...props}
|
|
28
|
-
value={field.value}
|
|
29
|
-
onValueChange={field.onChange}
|
|
30
|
-
error={error}
|
|
31
|
-
/>
|
|
32
|
-
)}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
interface InfoFieldProps {
|
|
2
|
-
label?: string
|
|
3
|
-
value: string | React.ReactNode
|
|
4
|
-
layout?: 'vertical' | 'horizontal' | 'compact'
|
|
5
|
-
className?: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
import React from "react"
|
|
9
|
-
|
|
10
|
-
export const InfoField = ({ label, value, layout = 'vertical', className }: InfoFieldProps) => {
|
|
11
|
-
if (layout === 'horizontal') {
|
|
12
|
-
return (
|
|
13
|
-
<div className={`flex items-start gap-3 ${className || ''}`}>
|
|
14
|
-
{label && <p className="text-sm font-medium text-muted-foreground flex-shrink-0 max-w-[50%]">{label}</p>}
|
|
15
|
-
<div className="text-sm text-foreground flex-1 min-w-0 break-words">{value}</div>
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (layout === 'compact') {
|
|
21
|
-
return (
|
|
22
|
-
<div className={`flex items-center gap-2 ${className || ''}`}>
|
|
23
|
-
{label && <p className="text-xs font-medium text-muted-foreground">{label}:</p>}
|
|
24
|
-
<div className="text-xs text-foreground">{value}</div>
|
|
25
|
-
</div>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Default vertical layout
|
|
30
|
-
return (
|
|
31
|
-
<div className={className}>
|
|
32
|
-
{label && <p className="text-sm font-medium text-muted-foreground">{label}</p>}
|
|
33
|
-
<div className={`text-sm text-foreground ${label ? 'mt-1' : ''}`}>{value}</div>
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useState } from "react"
|
|
2
|
-
import { ChevronDown, ChevronRight, Copy, Check } from "lucide-react"
|
|
3
|
-
import { Button } from "./button"
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
|
|
6
|
-
interface JsonViewerProps {
|
|
7
|
-
data: any
|
|
8
|
-
className?: string
|
|
9
|
-
compact?: boolean
|
|
10
|
-
maxHeight?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const JsonViewer = ({ data, className, compact = false, maxHeight = "60vh" }: JsonViewerProps) => {
|
|
14
|
-
const [copied, setCopied] = useState(false)
|
|
15
|
-
|
|
16
|
-
const handleCopy = () => {
|
|
17
|
-
navigator.clipboard.writeText(JSON.stringify(data, null, 2))
|
|
18
|
-
setCopied(true)
|
|
19
|
-
setTimeout(() => setCopied(false), 2000)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className={cn("relative", className)}>
|
|
24
|
-
<div className={cn("absolute z-10", compact ? "right-1 top-1" : "right-2 top-2")}>
|
|
25
|
-
<Button
|
|
26
|
-
variant="outline"
|
|
27
|
-
size={compact ? "sm" : "sm"}
|
|
28
|
-
onClick={handleCopy}
|
|
29
|
-
className={cn("gap-2", compact && "h-7 text-xs px-2")}
|
|
30
|
-
>
|
|
31
|
-
{copied ? (
|
|
32
|
-
<>
|
|
33
|
-
<Check className="h-3 w-3" />
|
|
34
|
-
{!compact && "Copied"}
|
|
35
|
-
</>
|
|
36
|
-
) : (
|
|
37
|
-
<>
|
|
38
|
-
<Copy className="h-3 w-3" />
|
|
39
|
-
{!compact && "Copy JSON"}
|
|
40
|
-
</>
|
|
41
|
-
)}
|
|
42
|
-
</Button>
|
|
43
|
-
</div>
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
"overflow-auto bg-muted/30 rounded-lg border font-mono",
|
|
47
|
-
compact ? "max-h-[300px] p-2 text-xs" : `max-h-[${maxHeight}] p-4 text-xs`
|
|
48
|
-
)}
|
|
49
|
-
style={{ maxHeight: compact ? "300px" : maxHeight }}
|
|
50
|
-
>
|
|
51
|
-
<JsonNode data={data} name="root" depth={0} compact={compact} />
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface JsonNodeProps {
|
|
58
|
-
data: any
|
|
59
|
-
name: string
|
|
60
|
-
depth: number
|
|
61
|
-
compact?: boolean
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const JsonNode = ({ data, name, depth, compact = false }: JsonNodeProps) => {
|
|
65
|
-
const [isExpanded, setIsExpanded] = useState(compact ? depth < 1 : depth < 2)
|
|
66
|
-
|
|
67
|
-
const indent = compact ? depth * 12 : depth * 16
|
|
68
|
-
|
|
69
|
-
if (data === null) {
|
|
70
|
-
return (
|
|
71
|
-
<div style={{ paddingLeft: indent }} className="text-muted-foreground">
|
|
72
|
-
<span className="text-primary">{name}</span>: <span className="text-orange-500">null</span>
|
|
73
|
-
</div>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (typeof data !== "object") {
|
|
78
|
-
const valueClass =
|
|
79
|
-
typeof data === "string" ? "text-green-600 dark:text-green-400" :
|
|
80
|
-
typeof data === "number" ? "text-blue-600 dark:text-blue-400" :
|
|
81
|
-
typeof data === "boolean" ? "text-orange-500" :
|
|
82
|
-
"text-foreground"
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div style={{ paddingLeft: indent }}>
|
|
86
|
-
<span className="text-primary">{name}</span>:{" "}
|
|
87
|
-
<span className={valueClass}>
|
|
88
|
-
{typeof data === "string" ? `"${data}"` : String(data)}
|
|
89
|
-
</span>
|
|
90
|
-
</div>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const isArray = Array.isArray(data)
|
|
95
|
-
const entries = isArray ? data : Object.entries(data)
|
|
96
|
-
const isEmpty = entries.length === 0
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div style={{ paddingLeft: indent }}>
|
|
100
|
-
<div
|
|
101
|
-
className="flex items-center gap-1 cursor-pointer hover:bg-muted/50 rounded"
|
|
102
|
-
onClick={() => !isEmpty && setIsExpanded(!isExpanded)}
|
|
103
|
-
>
|
|
104
|
-
{!isEmpty && (
|
|
105
|
-
isExpanded ? (
|
|
106
|
-
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
|
107
|
-
) : (
|
|
108
|
-
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
|
109
|
-
)
|
|
110
|
-
)}
|
|
111
|
-
<span className="text-primary">{name}</span>
|
|
112
|
-
<span className="text-muted-foreground">
|
|
113
|
-
{isArray ? "[" : "{"}
|
|
114
|
-
{!isExpanded && !isEmpty && "..."}
|
|
115
|
-
{isEmpty && (isArray ? "]" : "}")}
|
|
116
|
-
</span>
|
|
117
|
-
</div>
|
|
118
|
-
{isExpanded && !isEmpty && (
|
|
119
|
-
<div>
|
|
120
|
-
{isArray
|
|
121
|
-
? data.map((item: any, index: number) => (
|
|
122
|
-
<JsonNode
|
|
123
|
-
key={index}
|
|
124
|
-
data={item}
|
|
125
|
-
name={String(index)}
|
|
126
|
-
depth={depth + 1}
|
|
127
|
-
compact={compact}
|
|
128
|
-
/>
|
|
129
|
-
))
|
|
130
|
-
: Object.entries(data).map(([key, value]) => (
|
|
131
|
-
<JsonNode
|
|
132
|
-
key={key}
|
|
133
|
-
data={value}
|
|
134
|
-
name={key}
|
|
135
|
-
depth={depth + 1}
|
|
136
|
-
compact={compact}
|
|
137
|
-
/>
|
|
138
|
-
))}
|
|
139
|
-
<div style={{ paddingLeft: indent }} className="text-muted-foreground">
|
|
140
|
-
{isArray ? "]" : "}"}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
</div>
|
|
145
|
-
)
|
|
146
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
const labelVariants = cva(
|
|
8
|
-
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
const Label = React.forwardRef<
|
|
12
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
13
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
14
|
-
VariantProps<typeof labelVariants>
|
|
15
|
-
>(({ className, ...props }, ref) => (
|
|
16
|
-
<LabelPrimitive.Root
|
|
17
|
-
ref={ref}
|
|
18
|
-
className={cn(labelVariants(), className)}
|
|
19
|
-
{...props}
|
|
20
|
-
/>
|
|
21
|
-
))
|
|
22
|
-
Label.displayName = LabelPrimitive.Root.displayName
|
|
23
|
-
|
|
24
|
-
export { Label }
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
3
|
-
import { Badge } from "@/components/ui/badge"
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
import { TrendingUp, TrendingDown, Minus } from "lucide-react"
|
|
6
|
-
|
|
7
|
-
interface MetricCardProps {
|
|
8
|
-
title: string
|
|
9
|
-
value: string | number
|
|
10
|
-
description?: string
|
|
11
|
-
trend?: {
|
|
12
|
-
value: number
|
|
13
|
-
label: string
|
|
14
|
-
direction: "up" | "down" | "neutral"
|
|
15
|
-
}
|
|
16
|
-
icon?: React.ReactNode
|
|
17
|
-
variant?: "default" | "success" | "warning" | "destructive"
|
|
18
|
-
className?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const variantStyles = {
|
|
22
|
-
default: "",
|
|
23
|
-
success: "border-success/20 bg-success/5",
|
|
24
|
-
warning: "border-warning/20 bg-warning/5",
|
|
25
|
-
destructive: "border-destructive/20 bg-destructive/5"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const trendIcons = {
|
|
29
|
-
up: TrendingUp,
|
|
30
|
-
down: TrendingDown,
|
|
31
|
-
neutral: Minus
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const trendColors = {
|
|
35
|
-
up: "success",
|
|
36
|
-
down: "destructive",
|
|
37
|
-
neutral: "secondary"
|
|
38
|
-
} as const
|
|
39
|
-
|
|
40
|
-
export const MetricCard = React.forwardRef<HTMLDivElement, MetricCardProps>(
|
|
41
|
-
({ title, value, description, trend, icon, variant = "default", className }, ref) => {
|
|
42
|
-
const TrendIcon = trend ? trendIcons[trend.direction] : null
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<Card
|
|
46
|
-
ref={ref}
|
|
47
|
-
className={cn(variantStyles[variant], className)}
|
|
48
|
-
>
|
|
49
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
50
|
-
<CardTitle className="text-sm font-medium text-muted-foreground">
|
|
51
|
-
{title}
|
|
52
|
-
</CardTitle>
|
|
53
|
-
{icon && (
|
|
54
|
-
<div className="text-muted-foreground">
|
|
55
|
-
{icon}
|
|
56
|
-
</div>
|
|
57
|
-
)}
|
|
58
|
-
</CardHeader>
|
|
59
|
-
<CardContent>
|
|
60
|
-
<div className="text-2xl font-bold text-foreground">{value}</div>
|
|
61
|
-
{(description || trend) && (
|
|
62
|
-
<div className="flex items-center justify-between mt-2">
|
|
63
|
-
{description && (
|
|
64
|
-
<p className="text-xs text-muted-foreground">{description}</p>
|
|
65
|
-
)}
|
|
66
|
-
{trend && TrendIcon && (
|
|
67
|
-
<Badge variant={trendColors[trend.direction]} className="text-xs">
|
|
68
|
-
<TrendIcon className="w-3 h-3 mr-1" />
|
|
69
|
-
{trend.value > 0 ? "+" : ""}{trend.value}% {trend.label}
|
|
70
|
-
</Badge>
|
|
71
|
-
)}
|
|
72
|
-
</div>
|
|
73
|
-
)}
|
|
74
|
-
</CardContent>
|
|
75
|
-
</Card>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
MetricCard.displayName = "MetricCard"
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cn } from "@/lib/utils"
|
|
3
|
-
|
|
4
|
-
export interface PageAction {
|
|
5
|
-
label: string
|
|
6
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost"
|
|
7
|
-
onClick: () => void
|
|
8
|
-
icon?: React.ComponentType<{ className?: string }>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface PageCard {
|
|
12
|
-
component: React.ComponentType<any>
|
|
13
|
-
key: string
|
|
14
|
-
expandOnEdit?: boolean
|
|
15
|
-
props?: Record<string, any>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface PageLayoutProps {
|
|
19
|
-
title: string
|
|
20
|
-
description?: string
|
|
21
|
-
children?: React.ReactNode
|
|
22
|
-
actions?: PageAction[]
|
|
23
|
-
cards?: PageCard[]
|
|
24
|
-
mode?: "simple" | "detail" | "cards"
|
|
25
|
-
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full"
|
|
26
|
-
gridCols?: 1 | 2 | 3 | 4
|
|
27
|
-
responsive?: boolean
|
|
28
|
-
className?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const maxWidthClasses = {
|
|
32
|
-
sm: "max-w-2xl",
|
|
33
|
-
md: "max-w-4xl",
|
|
34
|
-
lg: "max-w-6xl",
|
|
35
|
-
xl: "max-w-7xl",
|
|
36
|
-
"2xl": "max-w-screen-2xl",
|
|
37
|
-
full: "max-w-none"
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const getGridClasses = (gridCols: number, responsive: boolean) => {
|
|
41
|
-
const colsMap = {
|
|
42
|
-
1: "grid-cols-1",
|
|
43
|
-
2: "grid-cols-2",
|
|
44
|
-
3: "grid-cols-3",
|
|
45
|
-
4: "grid-cols-4"
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!responsive) {
|
|
49
|
-
return colsMap[gridCols as keyof typeof colsMap] || "grid-cols-1"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Responsive grid classes
|
|
53
|
-
switch (gridCols) {
|
|
54
|
-
case 1:
|
|
55
|
-
return "grid-cols-1"
|
|
56
|
-
case 2:
|
|
57
|
-
return "grid-cols-1 lg:grid-cols-2"
|
|
58
|
-
case 3:
|
|
59
|
-
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
|
60
|
-
case 4:
|
|
61
|
-
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
|
62
|
-
default:
|
|
63
|
-
return "grid-cols-1 lg:grid-cols-2"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const PageLayout = React.forwardRef<HTMLDivElement, PageLayoutProps>(
|
|
68
|
-
({
|
|
69
|
-
title,
|
|
70
|
-
description,
|
|
71
|
-
children,
|
|
72
|
-
actions = [],
|
|
73
|
-
cards = [],
|
|
74
|
-
mode = "simple",
|
|
75
|
-
maxWidth = "xl",
|
|
76
|
-
gridCols = 2,
|
|
77
|
-
responsive = true,
|
|
78
|
-
className
|
|
79
|
-
}, ref) => {
|
|
80
|
-
const [editingCards, setEditingCards] = React.useState<Set<string>>(new Set())
|
|
81
|
-
|
|
82
|
-
const toggleEdit = (cardKey: string) => {
|
|
83
|
-
setEditingCards(prev => {
|
|
84
|
-
const next = new Set(prev)
|
|
85
|
-
if (next.has(cardKey)) {
|
|
86
|
-
next.delete(cardKey)
|
|
87
|
-
} else {
|
|
88
|
-
next.add(cardKey)
|
|
89
|
-
}
|
|
90
|
-
return next
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<div ref={ref} className="flex flex-col h-screen bg-gradient-subtle">
|
|
96
|
-
{/* Sticky Header */}
|
|
97
|
-
<div className={cn(
|
|
98
|
-
"sticky top-0 z-10 bg-gradient-subtle border-b border-border/40",
|
|
99
|
-
"container mx-auto px-4 pt-8 pb-4",
|
|
100
|
-
maxWidthClasses[maxWidth]
|
|
101
|
-
)}>
|
|
102
|
-
<div className="flex items-start justify-between">
|
|
103
|
-
<div>
|
|
104
|
-
<h1 className="text-3xl font-bold text-foreground mb-2">
|
|
105
|
-
{title}
|
|
106
|
-
</h1>
|
|
107
|
-
{description && (
|
|
108
|
-
<p className="text-muted-foreground">
|
|
109
|
-
{description}
|
|
110
|
-
</p>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
{actions.length > 0 && (
|
|
115
|
-
<div className="flex items-center gap-2">
|
|
116
|
-
{actions.map((action, index) => {
|
|
117
|
-
const Icon = action.icon
|
|
118
|
-
return (
|
|
119
|
-
<button
|
|
120
|
-
key={index}
|
|
121
|
-
onClick={action.onClick}
|
|
122
|
-
className={cn(
|
|
123
|
-
"inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors",
|
|
124
|
-
{
|
|
125
|
-
"bg-primary text-primary-foreground hover:bg-primary/90": action.variant === "default" || !action.variant,
|
|
126
|
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80": action.variant === "secondary",
|
|
127
|
-
"bg-destructive text-destructive-foreground hover:bg-destructive/90": action.variant === "destructive",
|
|
128
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground": action.variant === "outline",
|
|
129
|
-
"hover:bg-accent hover:text-accent-foreground": action.variant === "ghost"
|
|
130
|
-
}
|
|
131
|
-
)}
|
|
132
|
-
>
|
|
133
|
-
{Icon && <Icon className="h-4 w-4" />}
|
|
134
|
-
{action.label}
|
|
135
|
-
</button>
|
|
136
|
-
)
|
|
137
|
-
})}
|
|
138
|
-
</div>
|
|
139
|
-
)}
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
{/* Scrollable Content */}
|
|
144
|
-
<div className="flex-1 overflow-y-auto">
|
|
145
|
-
<div className={cn(
|
|
146
|
-
"container mx-auto px-4 py-6",
|
|
147
|
-
maxWidthClasses[maxWidth],
|
|
148
|
-
className
|
|
149
|
-
)}>
|
|
150
|
-
{mode === "cards" && cards.length > 0 ? (
|
|
151
|
-
<div className={cn("grid gap-6", getGridClasses(gridCols, responsive))}>
|
|
152
|
-
{cards.map((card) => {
|
|
153
|
-
const isEditing = editingCards.has(card.key)
|
|
154
|
-
const shouldExpand = card.expandOnEdit && isEditing
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<div
|
|
158
|
-
key={card.key}
|
|
159
|
-
className={cn(
|
|
160
|
-
"transition-all duration-300",
|
|
161
|
-
shouldExpand && (gridCols > 1 ? "col-span-full" : "")
|
|
162
|
-
)}
|
|
163
|
-
>
|
|
164
|
-
<card.component
|
|
165
|
-
{...card.props}
|
|
166
|
-
isEditing={isEditing}
|
|
167
|
-
onToggleEdit={() => toggleEdit(card.key)}
|
|
168
|
-
/>
|
|
169
|
-
</div>
|
|
170
|
-
)
|
|
171
|
-
})}
|
|
172
|
-
</div>
|
|
173
|
-
) : (
|
|
174
|
-
children
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
PageLayout.displayName = "PageLayout"
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
3
|
-
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
|
|
6
|
-
const Popover = PopoverPrimitive.Root
|
|
7
|
-
|
|
8
|
-
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
9
|
-
|
|
10
|
-
const PopoverContent = React.forwardRef<
|
|
11
|
-
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
12
|
-
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
13
|
-
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
|
14
|
-
<PopoverPrimitive.Portal>
|
|
15
|
-
<PopoverPrimitive.Content
|
|
16
|
-
ref={ref}
|
|
17
|
-
align={align}
|
|
18
|
-
sideOffset={sideOffset}
|
|
19
|
-
className={cn(
|
|
20
|
-
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
{...props}
|
|
24
|
-
/>
|
|
25
|
-
</PopoverPrimitive.Portal>
|
|
26
|
-
))
|
|
27
|
-
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
28
|
-
|
|
29
|
-
export { Popover, PopoverTrigger, PopoverContent }
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cn } from "@/lib/utils"
|
|
3
|
-
|
|
4
|
-
interface ResponsiveGridProps {
|
|
5
|
-
children: React.ReactNode
|
|
6
|
-
type?: "cards" | "forms" | "data" | "custom"
|
|
7
|
-
gap?: "sm" | "md" | "lg" | "xl"
|
|
8
|
-
editMode?: boolean
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const typeClasses = {
|
|
13
|
-
cards: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
|
|
14
|
-
forms: "grid-cols-1 lg:grid-cols-2",
|
|
15
|
-
data: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
16
|
-
custom: ""
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const gapClasses = {
|
|
20
|
-
sm: "gap-3",
|
|
21
|
-
md: "gap-4",
|
|
22
|
-
lg: "gap-6",
|
|
23
|
-
xl: "gap-8"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const ResponsiveGrid = React.forwardRef<HTMLDivElement, ResponsiveGridProps>(
|
|
27
|
-
({ children, type = "cards", gap = "md", editMode = false, className }, ref) => {
|
|
28
|
-
const gridClasses = editMode ? "grid-cols-1" : typeClasses[type]
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div
|
|
32
|
-
ref={ref}
|
|
33
|
-
className={cn(
|
|
34
|
-
"grid",
|
|
35
|
-
gridClasses,
|
|
36
|
-
gapClasses[gap],
|
|
37
|
-
className
|
|
38
|
-
)}
|
|
39
|
-
>
|
|
40
|
-
{children}
|
|
41
|
-
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
ResponsiveGrid.displayName = "ResponsiveGrid"
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
|
5
|
-
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
7
|
-
|
|
8
|
-
const Separator = React.forwardRef<
|
|
9
|
-
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
10
|
-
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
11
|
-
>(
|
|
12
|
-
(
|
|
13
|
-
{ className, orientation = "horizontal", decorative = true, ...props },
|
|
14
|
-
ref
|
|
15
|
-
) => (
|
|
16
|
-
<SeparatorPrimitive.Root
|
|
17
|
-
ref={ref}
|
|
18
|
-
decorative={decorative}
|
|
19
|
-
orientation={orientation}
|
|
20
|
-
className={cn(
|
|
21
|
-
"shrink-0 bg-border",
|
|
22
|
-
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
23
|
-
className
|
|
24
|
-
)}
|
|
25
|
-
{...props}
|
|
26
|
-
/>
|
|
27
|
-
)
|
|
28
|
-
)
|
|
29
|
-
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
30
|
-
|
|
31
|
-
export { Separator }
|