@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.
Files changed (44) hide show
  1. package/dist/index.d.mts +91 -14
  2. package/dist/index.d.ts +91 -14
  3. package/dist/index.js +1714 -369
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1546 -371
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +2 -2
  8. package/src/components/ui/alert.tsx +123 -93
  9. package/src/components/ui/avatar.tsx +2 -0
  10. package/src/components/ui/badge.tsx +19 -17
  11. package/src/components/ui/breadcrumb.tsx +2 -0
  12. package/src/components/ui/button.stories.tsx +4 -2
  13. package/src/components/ui/button.tsx +39 -33
  14. package/src/components/ui/calendar.tsx +1 -1
  15. package/src/components/ui/card-input.tsx +231 -0
  16. package/src/components/ui/card.stories.tsx +2 -0
  17. package/src/components/ui/card.tsx +2 -0
  18. package/src/components/ui/data-table.stories.tsx +4 -2
  19. package/src/components/ui/data-table.tsx +7 -7
  20. package/src/components/ui/date-picker.stories.tsx +2 -0
  21. package/src/components/ui/date-picker.tsx +18 -9
  22. package/src/components/ui/draggable-list.tsx +2 -0
  23. package/src/components/ui/index.ts +357 -44
  24. package/src/components/ui/input.stories.tsx +2 -0
  25. package/src/components/ui/input.tsx +2 -0
  26. package/src/components/ui/locked-component.tsx +222 -0
  27. package/src/components/ui/pagination.tsx +2 -0
  28. package/src/components/ui/phone-input.tsx +172 -0
  29. package/src/components/ui/popover-pro.tsx +424 -0
  30. package/src/components/ui/rich-text-editor/index.tsx +2 -1
  31. package/src/components/ui/scroll-area.tsx +48 -0
  32. package/src/components/ui/select.tsx +5 -1
  33. package/src/components/ui/separator.tsx +2 -2
  34. package/src/components/ui/swipeable-card.tsx +2 -0
  35. package/src/components/ui/table.tsx +2 -0
  36. package/src/components/ui/tabs.tsx +2 -0
  37. package/src/components/ui/tags-input.tsx +130 -0
  38. package/src/components/ui/textarea.tsx +2 -0
  39. package/src/components/ui/toast.tsx +2 -0
  40. package/src/index.tsx +4 -46
  41. package/src/lib/performance-profiler.ts +79 -0
  42. package/src/styles/index.css +315 -2
  43. package/src/use-performance-optimizer.ts +26 -26
  44. 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 { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card';
3
5
  import { Button } from './button';
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react";
2
4
  import { cva, type VariantProps } from "class-variance-authority";
3
5
  import { motion, type HTMLMotionProps } from "framer-motion";
@@ -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' ? 'default' : 'secondary'}>
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
- ? 'default'
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 flex-col gap-4 md:flex-row md:items-center md:justify-between">
306
- <div className="flex flex-1 items-center gap-2">
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 flex-1 max-w-sm">
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
- </div>
359
-
360
- {/* Actions */}
361
- <div className="flex items-center gap-2">
361
+
362
362
  {toolbarActions}
363
363
 
364
364
  {/* Export */}
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import type { Meta, StoryObj } from '@storybook/react';
2
4
  import { DatePicker, DateRangePicker, DateTimePicker, MonthPicker } from './date-picker';
3
5
  import { Label } from './label';
@@ -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
- import { pageTransitions } from "@/lib/micro-interactions";
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
- setInternalDate(date);
151
- onChange?.(date);
152
- if (date) {
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-xl rounded-2xl border-0 bg-background/95 backdrop-blur-sm"
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.fadeIn}
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-xl rounded-2xl border-0 bg-background/95 backdrop-blur-sm" align="start" sideOffset={12}>
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
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  // Basic Draggable List - Free Version
2
4
  "use client"
3
5