@moontra/moonui 2.2.0 → 2.2.1
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/dist/index.d.mts +91 -14
- package/dist/index.d.ts +91 -14
- package/dist/index.js +1714 -369
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1546 -371
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/ui/alert.tsx +123 -93
- package/src/components/ui/avatar.tsx +2 -0
- package/src/components/ui/badge.tsx +19 -17
- package/src/components/ui/breadcrumb.tsx +2 -0
- package/src/components/ui/button.stories.tsx +4 -2
- package/src/components/ui/button.tsx +39 -33
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/card-input.tsx +231 -0
- package/src/components/ui/card.stories.tsx +2 -0
- package/src/components/ui/card.tsx +2 -0
- package/src/components/ui/data-table.stories.tsx +4 -2
- package/src/components/ui/data-table.tsx +7 -7
- package/src/components/ui/date-picker.stories.tsx +2 -0
- package/src/components/ui/date-picker.tsx +18 -9
- package/src/components/ui/draggable-list.tsx +2 -0
- package/src/components/ui/index.ts +357 -44
- package/src/components/ui/input.stories.tsx +2 -0
- package/src/components/ui/input.tsx +2 -0
- package/src/components/ui/locked-component.tsx +222 -0
- package/src/components/ui/pagination.tsx +2 -0
- package/src/components/ui/phone-input.tsx +172 -0
- package/src/components/ui/popover-pro.tsx +424 -0
- package/src/components/ui/rich-text-editor/index.tsx +2 -1
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/separator.tsx +2 -2
- package/src/components/ui/swipeable-card.tsx +2 -0
- package/src/components/ui/table.tsx +2 -0
- package/src/components/ui/tabs.tsx +2 -0
- package/src/components/ui/tags-input.tsx +130 -0
- package/src/components/ui/textarea.tsx +2 -0
- package/src/components/ui/toast.tsx +2 -0
- package/src/index.tsx +4 -46
- package/src/lib/performance-profiler.ts +79 -0
- package/src/styles/index.css +315 -2
- package/src/use-performance-optimizer.ts +26 -26
- package/src/use-scroll-animation.ts +5 -7
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { Input } from "@/components/ui"
|
|
6
|
+
import { CreditCard } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
// Card type detection
|
|
9
|
+
const getCardType = (number: string): string => {
|
|
10
|
+
const patterns = {
|
|
11
|
+
visa: /^4/,
|
|
12
|
+
mastercard: /^5[1-5]/,
|
|
13
|
+
amex: /^3[47]/,
|
|
14
|
+
discover: /^6(?:011|5)/,
|
|
15
|
+
diners: /^3(?:0[0-5]|[68])/,
|
|
16
|
+
jcb: /^35/,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const [type, pattern] of Object.entries(patterns)) {
|
|
20
|
+
if (pattern.test(number)) return type
|
|
21
|
+
}
|
|
22
|
+
return "unknown"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Format card number with spaces
|
|
26
|
+
const formatCardNumber = (value: string, cardType: string): string => {
|
|
27
|
+
const cleaned = value.replace(/\s+/g, "")
|
|
28
|
+
const groups = cardType === "amex" ? [4, 6, 5] : [4, 4, 4, 4]
|
|
29
|
+
|
|
30
|
+
let formatted = ""
|
|
31
|
+
let position = 0
|
|
32
|
+
|
|
33
|
+
for (const group of groups) {
|
|
34
|
+
if (position >= cleaned.length) break
|
|
35
|
+
if (formatted) formatted += " "
|
|
36
|
+
formatted += cleaned.slice(position, position + group)
|
|
37
|
+
position += group
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return formatted
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Card Number Input
|
|
44
|
+
export interface CardNumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
|
|
45
|
+
value?: string
|
|
46
|
+
onChange?: (value: string, cardType: string) => void
|
|
47
|
+
showIcon?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const CardNumberInput = React.forwardRef<HTMLInputElement, CardNumberInputProps>(
|
|
51
|
+
({ className, value = "", onChange, showIcon = true, size, ...props }, ref) => {
|
|
52
|
+
const [cardType, setCardType] = React.useState("unknown")
|
|
53
|
+
|
|
54
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
55
|
+
const rawValue = e.target.value.replace(/\D/g, "")
|
|
56
|
+
const detectedType = getCardType(rawValue)
|
|
57
|
+
setCardType(detectedType)
|
|
58
|
+
|
|
59
|
+
const maxLength = detectedType === "amex" ? 15 : 16
|
|
60
|
+
const truncated = rawValue.slice(0, maxLength)
|
|
61
|
+
const formatted = formatCardNumber(truncated, detectedType)
|
|
62
|
+
|
|
63
|
+
onChange?.(truncated, detectedType)
|
|
64
|
+
|
|
65
|
+
// Update the input value
|
|
66
|
+
e.target.value = formatted
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="relative">
|
|
71
|
+
<Input
|
|
72
|
+
ref={ref}
|
|
73
|
+
type="text"
|
|
74
|
+
inputMode="numeric"
|
|
75
|
+
autoComplete="cc-number"
|
|
76
|
+
placeholder="1234 5678 9012 3456"
|
|
77
|
+
value={formatCardNumber(value, cardType)}
|
|
78
|
+
onChange={handleChange}
|
|
79
|
+
className={cn(showIcon && "pl-10", className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
{showIcon && (
|
|
83
|
+
<CreditCard className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
CardNumberInput.displayName = "CardNumberInput"
|
|
90
|
+
|
|
91
|
+
// Card Expiry Input
|
|
92
|
+
export interface CardExpiryInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
|
|
93
|
+
value?: string
|
|
94
|
+
onChange?: (value: string) => void
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const CardExpiryInput = React.forwardRef<HTMLInputElement, CardExpiryInputProps>(
|
|
98
|
+
({ className, value = "", onChange, size, ...props }, ref) => {
|
|
99
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
100
|
+
let rawValue = e.target.value.replace(/\D/g, "")
|
|
101
|
+
|
|
102
|
+
if (rawValue.length >= 2) {
|
|
103
|
+
const month = parseInt(rawValue.slice(0, 2))
|
|
104
|
+
if (month > 12) {
|
|
105
|
+
rawValue = "12" + rawValue.slice(2)
|
|
106
|
+
} else if (month === 0) {
|
|
107
|
+
rawValue = "01" + rawValue.slice(2)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
rawValue = rawValue.slice(0, 4)
|
|
112
|
+
let formatted = rawValue
|
|
113
|
+
|
|
114
|
+
if (rawValue.length >= 2) {
|
|
115
|
+
formatted = rawValue.slice(0, 2) + "/" + rawValue.slice(2)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onChange?.(rawValue)
|
|
119
|
+
e.target.value = formatted
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const formattedValue = value.length >= 2 ? value.slice(0, 2) + "/" + value.slice(2) : value
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Input
|
|
126
|
+
ref={ref}
|
|
127
|
+
type="text"
|
|
128
|
+
inputMode="numeric"
|
|
129
|
+
autoComplete="cc-exp"
|
|
130
|
+
placeholder="MM/YY"
|
|
131
|
+
value={formattedValue}
|
|
132
|
+
onChange={handleChange}
|
|
133
|
+
className={className}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
CardExpiryInput.displayName = "CardExpiryInput"
|
|
140
|
+
|
|
141
|
+
// Card CVC Input
|
|
142
|
+
export interface CardCVCInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
|
|
143
|
+
value?: string
|
|
144
|
+
onChange?: (value: string) => void
|
|
145
|
+
cardType?: string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const CardCVCInput = React.forwardRef<HTMLInputElement, CardCVCInputProps>(
|
|
149
|
+
({ className, value = "", onChange, cardType = "unknown", size, ...props }, ref) => {
|
|
150
|
+
const maxLength = cardType === "amex" ? 4 : 3
|
|
151
|
+
|
|
152
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
153
|
+
const rawValue = e.target.value.replace(/\D/g, "").slice(0, maxLength)
|
|
154
|
+
onChange?.(rawValue)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Input
|
|
159
|
+
ref={ref}
|
|
160
|
+
type="text"
|
|
161
|
+
inputMode="numeric"
|
|
162
|
+
autoComplete="cc-csc"
|
|
163
|
+
placeholder={cardType === "amex" ? "1234" : "123"}
|
|
164
|
+
value={value}
|
|
165
|
+
onChange={handleChange}
|
|
166
|
+
maxLength={maxLength}
|
|
167
|
+
className={className}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
CardCVCInput.displayName = "CardCVCInput"
|
|
174
|
+
|
|
175
|
+
// Card Zip Input
|
|
176
|
+
export interface CardZipInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
|
|
177
|
+
value?: string
|
|
178
|
+
onChange?: (value: string) => void
|
|
179
|
+
format?: "US" | "CA" | "UK" | "other"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const CardZipInput = React.forwardRef<HTMLInputElement, CardZipInputProps>(
|
|
183
|
+
({ className, value = "", onChange, format = "US", size, ...props }, ref) => {
|
|
184
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
185
|
+
let rawValue = e.target.value
|
|
186
|
+
|
|
187
|
+
switch (format) {
|
|
188
|
+
case "US":
|
|
189
|
+
rawValue = rawValue.replace(/\D/g, "").slice(0, 5)
|
|
190
|
+
break
|
|
191
|
+
case "CA":
|
|
192
|
+
rawValue = rawValue.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 6)
|
|
193
|
+
if (rawValue.length >= 3) {
|
|
194
|
+
rawValue = rawValue.slice(0, 3) + " " + rawValue.slice(3)
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case "UK":
|
|
198
|
+
rawValue = rawValue.toUpperCase().replace(/[^A-Z0-9\s]/g, "").slice(0, 8)
|
|
199
|
+
break
|
|
200
|
+
default:
|
|
201
|
+
rawValue = rawValue.slice(0, 10)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
onChange?.(rawValue.replace(/\s/g, ""))
|
|
205
|
+
e.target.value = rawValue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const getPlaceholder = () => {
|
|
209
|
+
switch (format) {
|
|
210
|
+
case "US": return "12345"
|
|
211
|
+
case "CA": return "K1A 0B1"
|
|
212
|
+
case "UK": return "SW1A 1AA"
|
|
213
|
+
default: return "Postal Code"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Input
|
|
219
|
+
ref={ref}
|
|
220
|
+
type="text"
|
|
221
|
+
autoComplete="postal-code"
|
|
222
|
+
placeholder={getPlaceholder()}
|
|
223
|
+
value={value}
|
|
224
|
+
onChange={handleChange}
|
|
225
|
+
className={className}
|
|
226
|
+
{...props}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
CardZipInput.displayName = "CardZipInput"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
4
|
import { DataTable } from './data-table';
|
|
3
5
|
import { Badge } from './badge';
|
|
@@ -141,7 +143,7 @@ const userColumns: ColumnDef<User>[] = [
|
|
|
141
143
|
cell: ({ row }) => {
|
|
142
144
|
const role = row.getValue('role') as string;
|
|
143
145
|
return (
|
|
144
|
-
<Badge variant={role === 'admin' ? '
|
|
146
|
+
<Badge variant={role === 'admin' ? 'admin' : 'secondary'}>
|
|
145
147
|
{role}
|
|
146
148
|
</Badge>
|
|
147
149
|
);
|
|
@@ -156,7 +158,7 @@ const userColumns: ColumnDef<User>[] = [
|
|
|
156
158
|
<Badge
|
|
157
159
|
variant={
|
|
158
160
|
status === 'active'
|
|
159
|
-
? '
|
|
161
|
+
? 'success'
|
|
160
162
|
: status === 'inactive'
|
|
161
163
|
? 'secondary'
|
|
162
164
|
: 'outline'
|
|
@@ -302,13 +302,13 @@ export function DataTable<TData, TValue>({
|
|
|
302
302
|
)}
|
|
303
303
|
|
|
304
304
|
{/* Toolbar */}
|
|
305
|
-
<div className="flex
|
|
306
|
-
<div className="flex
|
|
305
|
+
<div className="flex items-center justify-between gap-4">
|
|
306
|
+
<div className="flex-1 max-w-sm">
|
|
307
307
|
{/* Search/Filter */}
|
|
308
308
|
{enableFiltering && (
|
|
309
309
|
<>
|
|
310
310
|
{customFilter || (
|
|
311
|
-
<div className="relative
|
|
311
|
+
<div className="relative">
|
|
312
312
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
313
313
|
<Input
|
|
314
314
|
placeholder={filterPlaceholder}
|
|
@@ -348,17 +348,17 @@ export function DataTable<TData, TValue>({
|
|
|
348
348
|
)}
|
|
349
349
|
</>
|
|
350
350
|
)}
|
|
351
|
+
</div>
|
|
351
352
|
|
|
353
|
+
{/* Actions */}
|
|
354
|
+
<div className="flex items-center gap-2">
|
|
352
355
|
{/* Selection info */}
|
|
353
356
|
{enableRowSelection && selectedRows.length > 0 && (
|
|
354
357
|
<Badge variant="secondary" className="whitespace-nowrap">
|
|
355
358
|
{selectedRows.length} selected
|
|
356
359
|
</Badge>
|
|
357
360
|
)}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
{/* Actions */}
|
|
361
|
-
<div className="flex items-center gap-2">
|
|
361
|
+
|
|
362
362
|
{toolbarActions}
|
|
363
363
|
|
|
364
364
|
{/* Export */}
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
} from "./popover";
|
|
14
14
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
15
15
|
import { motion, AnimatePresence } from "framer-motion";
|
|
16
|
-
|
|
16
|
+
// Define pageTransitions locally since micro-interactions might not exist
|
|
17
|
+
const pageTransitions = {
|
|
18
|
+
initial: { opacity: 0, x: 20 },
|
|
19
|
+
animate: { opacity: 1, x: 0 },
|
|
20
|
+
exit: { opacity: 0, x: -20 }
|
|
21
|
+
};
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* DatePicker Component
|
|
@@ -146,10 +151,14 @@ export function DatePicker({
|
|
|
146
151
|
setInternalDate(value);
|
|
147
152
|
}, [value]);
|
|
148
153
|
|
|
149
|
-
const handleSelect = (date: Date | undefined) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
const handleSelect = (date: Date | Date[] | { from?: Date; to?: Date } | undefined) => {
|
|
155
|
+
// Handle single date selection for now
|
|
156
|
+
const singleDate = Array.isArray(date) ? date[0] :
|
|
157
|
+
(date && typeof date === 'object' && 'from' in date) ? date.from :
|
|
158
|
+
date as Date | undefined;
|
|
159
|
+
setInternalDate(singleDate);
|
|
160
|
+
onChange?.(singleDate);
|
|
161
|
+
if (singleDate) {
|
|
153
162
|
setOpen(false);
|
|
154
163
|
}
|
|
155
164
|
};
|
|
@@ -246,12 +255,12 @@ export function DatePicker({
|
|
|
246
255
|
</Button>
|
|
247
256
|
</PopoverTrigger>
|
|
248
257
|
<PopoverContent
|
|
249
|
-
className="w-[360px] p-0 shadow-
|
|
258
|
+
className="w-[360px] p-0 shadow-2xl rounded-2xl border border-gray-200 dark:border-gray-700 bg-background/95 backdrop-blur-sm z-[1000]"
|
|
250
259
|
align="start"
|
|
251
260
|
sideOffset={12}
|
|
252
261
|
>
|
|
253
262
|
<motion.div
|
|
254
|
-
{...pageTransitions
|
|
263
|
+
{...pageTransitions}
|
|
255
264
|
className="rounded-2xl bg-background overflow-hidden"
|
|
256
265
|
>
|
|
257
266
|
<Calendar
|
|
@@ -361,7 +370,7 @@ export function DateRangePicker({
|
|
|
361
370
|
</span>
|
|
362
371
|
</Button>
|
|
363
372
|
</PopoverTrigger>
|
|
364
|
-
<PopoverContent className="w-auto p-0 shadow-
|
|
373
|
+
<PopoverContent className="w-auto p-0 shadow-2xl rounded-2xl border border-gray-200 dark:border-gray-700 bg-background/95 backdrop-blur-sm z-[1000]" align="start" sideOffset={12}>
|
|
365
374
|
<Calendar
|
|
366
375
|
mode="range"
|
|
367
376
|
defaultMonth={internalRange?.from}
|
|
@@ -557,7 +566,7 @@ export function MonthPicker({
|
|
|
557
566
|
{displayValue || placeholder}
|
|
558
567
|
</Button>
|
|
559
568
|
</PopoverTrigger>
|
|
560
|
-
<PopoverContent className="w-64 p-0" align="start">
|
|
569
|
+
<PopoverContent className="w-64 p-0 shadow-2xl border border-gray-200 dark:border-gray-700 bg-background z-[1000]" align="start">
|
|
561
570
|
<div className="p-3">
|
|
562
571
|
<div className="flex items-center justify-between mb-3">
|
|
563
572
|
<Button
|