braid-ui 1.0.98 → 1.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +44 -327
  2. package/components.json +20 -0
  3. package/eslint.config.js +29 -0
  4. package/index.html +24 -0
  5. package/package.json +55 -115
  6. package/postcss.config.js +6 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/placeholder.svg +1 -0
  9. package/public/robots.txt +14 -0
  10. package/src/App.css +42 -0
  11. package/src/App.tsx +94 -0
  12. package/src/components/MainLayout.tsx +15 -0
  13. package/src/components/alerts/AlertDocuments.tsx +320 -0
  14. package/src/components/alerts/AlertNotes.tsx +185 -0
  15. package/src/components/alerts/AlertTimeline.tsx +79 -0
  16. package/src/components/alerts/ContextSection.tsx +155 -0
  17. package/src/components/app-sidebar.tsx +341 -0
  18. package/src/components/form-sections/ACHBankCard.tsx +78 -0
  19. package/src/components/form-sections/ACHBasicInfoCard.tsx +100 -0
  20. package/src/components/form-sections/ACHTransferSection.tsx +64 -0
  21. package/src/components/form-sections/AddressForm.tsx +94 -0
  22. package/src/components/form-sections/BankAddressCard.tsx +95 -0
  23. package/src/components/form-sections/BankingDetailsCard.tsx +46 -0
  24. package/src/components/form-sections/BasicInfoCard.tsx +103 -0
  25. package/src/components/form-sections/BasicInfoSection.tsx +34 -0
  26. package/src/components/form-sections/BeneficiaryAddress.tsx +19 -0
  27. package/src/components/form-sections/BeneficiaryCard.tsx +41 -0
  28. package/src/components/form-sections/BeneficiaryDomesticWire.tsx +23 -0
  29. package/src/components/form-sections/BusinessProfileCard.tsx +131 -0
  30. package/src/components/form-sections/BusinessStatusCard.tsx +53 -0
  31. package/src/components/form-sections/ContactInfoCard.tsx +63 -0
  32. package/src/components/form-sections/CounterpartyBasicInfo.tsx +101 -0
  33. package/src/components/form-sections/CounterpartyProfileCard.tsx +104 -0
  34. package/src/components/form-sections/CounterpartyRecordsCard.tsx +41 -0
  35. package/src/components/form-sections/IntermediaryCard.tsx +77 -0
  36. package/src/components/form-sections/IntermediaryFI.tsx +41 -0
  37. package/src/components/form-sections/IntermediaryFIAddress.tsx +14 -0
  38. package/src/components/form-sections/OriginatorCard.tsx +49 -0
  39. package/src/components/form-sections/OriginatorFI.tsx +42 -0
  40. package/src/components/form-sections/OriginatorFIAddress.tsx +14 -0
  41. package/src/components/form-sections/PaymentInformationSection.tsx +163 -0
  42. package/src/components/form-sections/ReceiverCard.tsx +94 -0
  43. package/src/components/form-sections/WireTransferSection.tsx +75 -0
  44. package/src/components/layouts/list-page.tsx +103 -0
  45. package/src/components/transaction/ACHDetailsSection.tsx +95 -0
  46. package/src/components/transaction/WireDetailsSection.tsx +112 -0
  47. package/src/components/ui/account-card.tsx +94 -0
  48. package/src/components/ui/badge.tsx +75 -0
  49. package/src/components/ui/breadcrumb.tsx +78 -0
  50. package/src/components/ui/business-type-badge.tsx +42 -0
  51. package/src/components/ui/button.tsx +56 -0
  52. package/src/components/ui/calendar.tsx +49 -0
  53. package/src/components/ui/card.tsx +223 -0
  54. package/src/components/ui/container.tsx +45 -0
  55. package/src/components/ui/counterparty-type-badge.tsx +53 -0
  56. package/src/components/ui/data-grid.tsx +99 -0
  57. package/src/components/ui/data-table.tsx +152 -0
  58. package/src/components/ui/detail-page-layout.tsx +83 -0
  59. package/src/components/ui/dialog.tsx +120 -0
  60. package/src/components/ui/dropdown-menu.tsx +82 -0
  61. package/src/components/ui/editable-form-card.tsx +106 -0
  62. package/src/components/ui/editable-info-field.tsx +67 -0
  63. package/src/components/ui/enhanced-input.tsx +78 -0
  64. package/src/components/ui/enhanced-select.tsx +101 -0
  65. package/src/components/ui/enhanced-textarea.tsx +64 -0
  66. package/src/components/ui/entity-card.tsx +140 -0
  67. package/src/components/ui/form-card.tsx +40 -0
  68. package/src/components/ui/form-field.tsx +50 -0
  69. package/src/components/ui/form-input.tsx +29 -0
  70. package/src/components/ui/form-provider.tsx +18 -0
  71. package/src/components/ui/form-section.tsx +66 -0
  72. package/src/components/ui/form-select.tsx +35 -0
  73. package/src/components/ui/info-field.tsx +36 -0
  74. package/src/components/ui/json-viewer.tsx +146 -0
  75. package/src/components/ui/label.tsx +24 -0
  76. package/src/components/ui/metric-card.tsx +80 -0
  77. package/src/components/ui/page-layout.tsx +183 -0
  78. package/src/components/ui/popover.tsx +29 -0
  79. package/src/components/ui/responsive-grid.tsx +46 -0
  80. package/src/components/ui/separator.tsx +31 -0
  81. package/src/components/ui/sheet.tsx +140 -0
  82. package/src/components/ui/sidebar.tsx +775 -0
  83. package/src/components/ui/sonner.tsx +29 -0
  84. package/src/components/ui/stack.tsx +77 -0
  85. package/src/components/ui/status-badge.tsx +68 -0
  86. package/src/components/ui/tabs.tsx +52 -0
  87. package/src/components/ui/toast.tsx +127 -0
  88. package/src/components/ui/toaster.tsx +33 -0
  89. package/src/components/ui/tooltip.tsx +28 -0
  90. package/src/components/ui/use-toast.ts +3 -0
  91. package/src/components/ui-kit/dashboard-demo.tsx +156 -0
  92. package/src/components/ui-kit/pattern-library.tsx +248 -0
  93. package/src/components/ui-kit/showcase.tsx +211 -0
  94. package/src/hooks/use-mobile.tsx +19 -0
  95. package/src/hooks/use-toast.ts +191 -0
  96. package/src/hooks/useEditState.ts +70 -0
  97. package/src/hooks/useFormWithEditState.ts +115 -0
  98. package/src/{styles.css → index.css} +10 -4
  99. package/src/lib/constants.ts +25 -0
  100. package/src/lib/mock-data/alert-data.ts +275 -0
  101. package/src/lib/mock-data/banking-data.ts +72 -0
  102. package/src/lib/mock-data/business-data.ts +71 -0
  103. package/src/lib/mock-data/counterparty-data.ts +70 -0
  104. package/src/lib/mock-data/index.ts +5 -0
  105. package/src/lib/mock-data/transaction-data.ts +283 -0
  106. package/src/lib/mock-data/wire-data.ts +103 -0
  107. package/src/lib/mock-data.tsx +180 -0
  108. package/src/lib/schemas/banking-schemas.ts +30 -0
  109. package/src/lib/schemas/business-schemas.ts +36 -0
  110. package/src/lib/schemas/counterparty-schemas.ts +43 -0
  111. package/src/lib/schemas/index.ts +5 -0
  112. package/src/lib/schemas/wire-schemas.ts +44 -0
  113. package/src/lib/utils.ts +6 -0
  114. package/src/main.tsx +10 -0
  115. package/src/pages/Cases.tsx +16 -0
  116. package/src/pages/Dashboard.tsx +16 -0
  117. package/src/pages/NotFound.tsx +27 -0
  118. package/src/pages/TransactionHistory.tsx +532 -0
  119. package/src/pages/UIKit.tsx +51 -0
  120. package/src/pages/alerts/AlertDetail.tsx +193 -0
  121. package/src/pages/alerts/Alerts.tsx +373 -0
  122. package/src/pages/business/Business.tsx +48 -0
  123. package/src/pages/business/Create.tsx +173 -0
  124. package/src/pages/counterparty/Create.tsx +48 -0
  125. package/src/pages/counterparty/DomesticWire.tsx +78 -0
  126. package/src/pages/counterparty/Manage.tsx +79 -0
  127. package/src/pages/transactions/NewTransaction.tsx +527 -0
  128. package/src/pages/transactions/TransactionDetail.tsx +192 -0
  129. package/src/vite-env.d.ts +1 -0
  130. package/tailwind.config.ts +124 -0
  131. package/tsconfig.app.json +30 -0
  132. package/tsconfig.json +19 -0
  133. package/tsconfig.node.json +22 -0
  134. package/vite.config.ts +22 -0
  135. package/dist/css/braid-ui-variables.css +0 -88
  136. package/dist/css/braid-ui.css +0 -4484
  137. package/dist/css/braid-ui.min.css +0 -1
  138. package/dist/index.cjs +0 -4
  139. package/dist/index.cjs.map +0 -1
  140. package/dist/index.d.cts +0 -2429
  141. package/dist/index.d.ts +0 -2429
  142. package/dist/index.js +0 -4
  143. package/dist/index.js.map +0 -1
  144. package/src/styles-only.css +0 -121
  145. /package/{dist/braid-logo-343BOQZ2.png → src/assets/braid-logo.png} +0 -0
@@ -0,0 +1,120 @@
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
+ }
@@ -0,0 +1,82 @@
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
+ }
@@ -0,0 +1,106 @@
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"
@@ -0,0 +1,67 @@
1
+ import { useState } from "react"
2
+ import { InfoField } from "./info-field"
3
+ import { EnhancedSelect } from "./enhanced-select"
4
+ import { Badge } from "./badge"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ interface EditableInfoFieldProps {
8
+ label: string
9
+ value: string | null | undefined
10
+ options: { value: string; label: string }[]
11
+ onChange: (value: string) => void
12
+ placeholder?: string
13
+ renderValue?: (value: string) => React.ReactNode
14
+ className?: string
15
+ }
16
+
17
+ export const EditableInfoField = ({
18
+ label,
19
+ value,
20
+ options,
21
+ onChange,
22
+ placeholder = "Select...",
23
+ renderValue,
24
+ className
25
+ }: EditableInfoFieldProps) => {
26
+ const [isEditing, setIsEditing] = useState(false)
27
+
28
+ const handleChange = (newValue: string) => {
29
+ onChange(newValue)
30
+ setIsEditing(false)
31
+ }
32
+
33
+ if (isEditing) {
34
+ return (
35
+ <div className={className}>
36
+ <EnhancedSelect
37
+ label={label}
38
+ value={value || ""}
39
+ onValueChange={handleChange}
40
+ options={options}
41
+ placeholder={placeholder}
42
+ />
43
+ </div>
44
+ )
45
+ }
46
+
47
+ const displayValue = value
48
+ ? (renderValue ? renderValue(value) : value)
49
+ : placeholder
50
+
51
+ return (
52
+ <div
53
+ className={cn("cursor-pointer transition-colors hover:bg-muted/50 rounded-md -mx-2 px-2 -my-1 py-1", className)}
54
+ onClick={() => setIsEditing(true)}
55
+ role="button"
56
+ tabIndex={0}
57
+ onKeyDown={(e) => {
58
+ if (e.key === 'Enter' || e.key === ' ') {
59
+ e.preventDefault()
60
+ setIsEditing(true)
61
+ }
62
+ }}
63
+ >
64
+ <InfoField label={label} value={displayValue} />
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,78 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ const inputVariants = cva(
6
+ "flex w-full rounded-md bg-form-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground transition-form",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "border border-form-border focus-visible:border-form-border-focus focus-visible:outline-none focus-visible:shadow-form-focus",
11
+ error: "border border-form-border-error focus-visible:border-form-border-error focus-visible:outline-none focus-visible:shadow-form-error",
12
+ success: "border border-form-border-success focus-visible:border-form-border-success focus-visible:outline-none",
13
+ disabled: "border border-form-border bg-muted cursor-not-allowed opacity-50",
14
+ readonly: "border border-form-border bg-muted/50 cursor-default"
15
+ },
16
+ size: {
17
+ default: "h-10",
18
+ sm: "h-9",
19
+ lg: "h-11"
20
+ }
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ size: "default"
25
+ }
26
+ }
27
+ )
28
+
29
+ export interface InputProps
30
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
31
+ VariantProps<typeof inputVariants> {
32
+ label?: string
33
+ hint?: string
34
+ error?: string
35
+ success?: string
36
+ isLoading?: boolean
37
+ }
38
+
39
+ const EnhancedInput = React.forwardRef<HTMLInputElement, InputProps>(
40
+ ({ className, variant, size, label, hint, error, success, isLoading, ...props }, ref) => {
41
+ const inputVariant = error ? "error" : success ? "success" : props.disabled ? "disabled" : props.readOnly ? "readonly" : variant
42
+
43
+ return (
44
+ <div className="space-y-2">
45
+ {label && (
46
+ <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
47
+ {label}
48
+ {props.required && <span className="text-destructive ml-1">*</span>}
49
+ </label>
50
+ )}
51
+ <div className="relative">
52
+ <input
53
+ className={cn(inputVariants({ variant: inputVariant, size, className }))}
54
+ ref={ref}
55
+ {...props}
56
+ />
57
+ {isLoading && (
58
+ <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
59
+ <div className="animate-spin rounded-full h-4 w-4 border-2 border-primary border-t-transparent"></div>
60
+ </div>
61
+ )}
62
+ </div>
63
+ {hint && !error && !success && (
64
+ <p className="text-xs text-muted-foreground">{hint}</p>
65
+ )}
66
+ {error && (
67
+ <p className="text-xs text-destructive">{error}</p>
68
+ )}
69
+ {success && (
70
+ <p className="text-xs text-success">{success}</p>
71
+ )}
72
+ </div>
73
+ )
74
+ }
75
+ )
76
+ EnhancedInput.displayName = "EnhancedInput"
77
+
78
+ export { EnhancedInput, inputVariants }
@@ -0,0 +1,101 @@
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+ import { cn } from "@/lib/utils"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ const selectVariants = cva(
8
+ "flex h-10 w-full items-center justify-between rounded-md bg-form-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none transition-form",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "border border-form-border focus:border-form-border-focus focus:shadow-form-focus",
13
+ error: "border border-form-border-error focus:border-form-border-error focus:shadow-form-error",
14
+ success: "border border-form-border-success focus:border-form-border-success",
15
+ disabled: "border border-form-border bg-muted cursor-not-allowed opacity-50",
16
+ }
17
+ },
18
+ defaultVariants: {
19
+ variant: "default"
20
+ }
21
+ }
22
+ )
23
+
24
+ interface EnhancedSelectProps extends VariantProps<typeof selectVariants> {
25
+ label?: string
26
+ hint?: string
27
+ error?: string
28
+ success?: string
29
+ placeholder?: string
30
+ options: { value: string; label: string; disabled?: boolean }[]
31
+ value?: string
32
+ onValueChange?: (value: string) => void
33
+ disabled?: boolean
34
+ }
35
+
36
+ const EnhancedSelect = React.forwardRef<
37
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
38
+ EnhancedSelectProps
39
+ >(({ variant, label, hint, error, success, placeholder, options, disabled, ...props }, ref) => {
40
+ const selectVariant = error ? "error" : success ? "success" : disabled ? "disabled" : variant
41
+
42
+ return (
43
+ <div className="space-y-2">
44
+ {label && (
45
+ <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
46
+ {label}
47
+ </label>
48
+ )}
49
+ <SelectPrimitive.Root {...props} disabled={disabled}>
50
+ <SelectPrimitive.Trigger
51
+ ref={ref}
52
+ className={cn(selectVariants({ variant: selectVariant }))}
53
+ >
54
+ <SelectPrimitive.Value placeholder={placeholder} />
55
+ <SelectPrimitive.Icon asChild>
56
+ <ChevronDown className="h-4 w-4 opacity-50" />
57
+ </SelectPrimitive.Icon>
58
+ </SelectPrimitive.Trigger>
59
+ <SelectPrimitive.Portal>
60
+ <SelectPrimitive.Content className="relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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">
61
+ <SelectPrimitive.ScrollUpButton className="flex cursor-default items-center justify-center py-1">
62
+ <ChevronUp className="h-4 w-4" />
63
+ </SelectPrimitive.ScrollUpButton>
64
+ <SelectPrimitive.Viewport className="p-1">
65
+ {options.map((option) => (
66
+ <SelectPrimitive.Item
67
+ key={option.value}
68
+ value={option.value}
69
+ disabled={option.disabled}
70
+ className="relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
71
+ >
72
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
73
+ <SelectPrimitive.ItemIndicator>
74
+ <Check className="h-4 w-4" />
75
+ </SelectPrimitive.ItemIndicator>
76
+ </span>
77
+ <SelectPrimitive.ItemText>{option.label}</SelectPrimitive.ItemText>
78
+ </SelectPrimitive.Item>
79
+ ))}
80
+ </SelectPrimitive.Viewport>
81
+ <SelectPrimitive.ScrollDownButton className="flex cursor-default items-center justify-center py-1">
82
+ <ChevronDown className="h-4 w-4" />
83
+ </SelectPrimitive.ScrollDownButton>
84
+ </SelectPrimitive.Content>
85
+ </SelectPrimitive.Portal>
86
+ </SelectPrimitive.Root>
87
+ {hint && !error && !success && (
88
+ <p className="text-xs text-muted-foreground">{hint}</p>
89
+ )}
90
+ {error && (
91
+ <p className="text-xs text-destructive">{error}</p>
92
+ )}
93
+ {success && (
94
+ <p className="text-xs text-success">{success}</p>
95
+ )}
96
+ </div>
97
+ )
98
+ })
99
+ EnhancedSelect.displayName = "EnhancedSelect"
100
+
101
+ export { EnhancedSelect }
@@ -0,0 +1,64 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ const textareaVariants = cva(
6
+ "flex min-h-[80px] w-full rounded-md bg-form-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground transition-form resize-y",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "border border-form-border focus-visible:border-form-border-focus focus-visible:outline-none focus-visible:shadow-form-focus",
11
+ error: "border border-form-border-error focus-visible:border-form-border-error focus-visible:outline-none focus-visible:shadow-form-error",
12
+ success: "border border-form-border-success focus-visible:border-form-border-success focus-visible:outline-none",
13
+ disabled: "border border-form-border bg-muted cursor-not-allowed opacity-50 resize-none",
14
+ readonly: "border border-form-border bg-muted/50 cursor-default resize-none"
15
+ }
16
+ },
17
+ defaultVariants: {
18
+ variant: "default"
19
+ }
20
+ }
21
+ )
22
+
23
+ export interface TextareaProps
24
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
25
+ VariantProps<typeof textareaVariants> {
26
+ label?: string
27
+ hint?: string
28
+ error?: string
29
+ success?: string
30
+ }
31
+
32
+ const EnhancedTextarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
33
+ ({ className, variant, label, hint, error, success, ...props }, ref) => {
34
+ const textareaVariant = error ? "error" : success ? "success" : props.disabled ? "disabled" : props.readOnly ? "readonly" : variant
35
+
36
+ return (
37
+ <div className="space-y-2">
38
+ {label && (
39
+ <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
40
+ {label}
41
+ {props.required && <span className="text-destructive ml-1">*</span>}
42
+ </label>
43
+ )}
44
+ <textarea
45
+ className={cn(textareaVariants({ variant: textareaVariant, className }))}
46
+ ref={ref}
47
+ {...props}
48
+ />
49
+ {hint && !error && !success && (
50
+ <p className="text-xs text-muted-foreground">{hint}</p>
51
+ )}
52
+ {error && (
53
+ <p className="text-xs text-destructive">{error}</p>
54
+ )}
55
+ {success && (
56
+ <p className="text-xs text-success">{success}</p>
57
+ )}
58
+ </div>
59
+ )
60
+ }
61
+ )
62
+ EnhancedTextarea.displayName = "EnhancedTextarea"
63
+
64
+ export { EnhancedTextarea, textareaVariants }