@srcroot/ui 0.0.52 → 0.0.54

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 (59) hide show
  1. package/dist/index.js +9 -3
  2. package/package.json +1 -1
  3. package/src/registry/analytics/google-analytics.tsx +3 -0
  4. package/src/registry/analytics/google-tag-manager.tsx +3 -0
  5. package/src/registry/analytics/meta-pixel.tsx +4 -1
  6. package/src/registry/analytics/microsoft-clarity.tsx +3 -0
  7. package/src/registry/analytics/tiktok-pixel.tsx +4 -1
  8. package/src/registry/ui/accordion.tsx +3 -0
  9. package/src/registry/ui/alert-dialog.tsx +3 -0
  10. package/src/registry/ui/alert.tsx +3 -0
  11. package/src/registry/ui/aspect-ratio.tsx +3 -0
  12. package/src/registry/ui/avatar.tsx +3 -0
  13. package/src/registry/ui/badge.tsx +3 -0
  14. package/src/registry/ui/breadcrumb.tsx +3 -0
  15. package/src/registry/ui/button-group.tsx +3 -0
  16. package/src/registry/ui/button.tsx +3 -0
  17. package/src/registry/ui/calendar.tsx +3 -0
  18. package/src/registry/ui/card.tsx +3 -0
  19. package/src/registry/ui/chatbot.tsx +3 -0
  20. package/src/registry/ui/checkbox.tsx +3 -0
  21. package/src/registry/ui/collapsible.tsx +3 -0
  22. package/src/registry/ui/container.tsx +3 -0
  23. package/src/registry/ui/dialog.tsx +3 -0
  24. package/src/registry/ui/dropdown-menu.tsx +3 -0
  25. package/src/registry/ui/empty-state.tsx +40 -0
  26. package/src/registry/ui/file-upload.tsx +65 -24
  27. package/src/registry/ui/floating-dock.tsx +22 -0
  28. package/src/registry/ui/form-field.tsx +3 -0
  29. package/src/registry/ui/image.tsx +3 -0
  30. package/src/registry/ui/input-group.tsx +3 -0
  31. package/src/registry/ui/kbd.tsx +4 -1
  32. package/src/registry/ui/label.tsx +3 -0
  33. package/src/registry/ui/loading-spinner.tsx +3 -0
  34. package/src/registry/ui/map.tsx +99 -0
  35. package/src/registry/ui/marquee.tsx +53 -0
  36. package/src/registry/ui/native-select.tsx +3 -0
  37. package/src/registry/ui/otp-input.tsx +3 -0
  38. package/src/registry/ui/pagination.tsx +3 -0
  39. package/src/registry/ui/popover.tsx +3 -0
  40. package/src/registry/ui/progress.tsx +3 -0
  41. package/src/registry/ui/radio.tsx +3 -0
  42. package/src/registry/ui/scroll-area.tsx +3 -0
  43. package/src/registry/ui/scroll-to-top.tsx +99 -0
  44. package/src/registry/ui/search.tsx +3 -0
  45. package/src/registry/ui/select.tsx +3 -0
  46. package/src/registry/ui/separator.tsx +3 -0
  47. package/src/registry/ui/skeleton.tsx +3 -0
  48. package/src/registry/ui/slider.tsx +3 -0
  49. package/src/registry/ui/slot.tsx +3 -0
  50. package/src/registry/ui/star-rating.tsx +3 -0
  51. package/src/registry/ui/switch.tsx +3 -0
  52. package/src/registry/ui/table-of-contents.tsx +96 -0
  53. package/src/registry/ui/table.tsx +3 -0
  54. package/src/registry/ui/tabs.tsx +3 -0
  55. package/src/registry/ui/text.tsx +3 -0
  56. package/src/registry/ui/textarea.tsx +3 -0
  57. package/src/registry/ui/toast.tsx +3 -0
  58. package/src/registry/ui/tooltip.tsx +3 -0
  59. package/src/registry/ui/whatsapp.tsx +36 -0
package/dist/index.js CHANGED
@@ -437,7 +437,7 @@ var REGISTRY = {
437
437
  description: "Text input field",
438
438
  category: "Forms",
439
439
  dependencies: [],
440
- registryDependencies: ["react-icons^5.5.0"]
440
+ registryDependencies: ["react-icons"]
441
441
  },
442
442
  textarea: {
443
443
  file: "ui/textarea.tsx",
@@ -684,7 +684,7 @@ var REGISTRY = {
684
684
  description: "Date picker with calendar",
685
685
  category: "Forms",
686
686
  dependencies: ["calendar", "popover", "button"],
687
- registryDependencies: ["date-fns^4.1.0"]
687
+ registryDependencies: ["date-fns"]
688
688
  },
689
689
  drawer: {
690
690
  file: "ui/drawer.tsx",
@@ -787,13 +787,19 @@ var REGISTRY = {
787
787
  description: "Charts using Recharts",
788
788
  category: "Data Display",
789
789
  dependencies: [],
790
- registryDependencies: ["recharts^2.15.4"]
790
+ registryDependencies: ["recharts"]
791
791
  },
792
792
  slot: {
793
793
  file: "ui/slot.tsx",
794
794
  description: "Slot utility for polymorphic components",
795
795
  category: "Core",
796
796
  dependencies: []
797
+ },
798
+ "table-of-contents": {
799
+ file: "ui/table-of-contents.tsx",
800
+ description: "Table of contents navigation",
801
+ category: "Navigation",
802
+ dependencies: []
797
803
  }
798
804
  };
799
805
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srcroot/ui",
3
- "version": "0.0.52",
3
+ "version": "0.0.54",
4
4
  "description": "A UI library with polymorphic, accessible React components",
5
5
  "author": "Shifaul Islam",
6
6
  "license": "MIT",
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import Script from 'next/script';
2
4
  import type { FC } from 'react';
3
5
 
@@ -34,3 +36,4 @@ const GoogleAnalytics: FC<GoogleAnalyticsProps> = ({ gaIds }) => {
34
36
  };
35
37
 
36
38
  export default GoogleAnalytics;
39
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import Script from 'next/script';
2
4
  import type { FC, ReactNode } from 'react';
3
5
 
@@ -60,3 +62,4 @@ const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
60
62
  };
61
63
 
62
64
  export default GoogleTagManager;
65
+
@@ -1,8 +1,10 @@
1
+ "use client"
2
+
1
3
  import Script from 'next/script';
2
4
  import type { FC } from 'react';
3
5
 
4
6
  interface MetaPixelProps {
5
- pixelIds: string[]; // array now
7
+ pixelIds: string[]; // ← array now
6
8
  }
7
9
 
8
10
  const MetaPixel: FC<MetaPixelProps> = ({ pixelIds }) => {
@@ -42,3 +44,4 @@ const MetaPixel: FC<MetaPixelProps> = ({ pixelIds }) => {
42
44
  };
43
45
 
44
46
  export default MetaPixel;
47
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import Script from 'next/script';
2
4
  import type { FC } from 'react';
3
5
 
@@ -29,3 +31,4 @@ const MicrosoftClarity: FC<MicrosoftClarityProps> = ({ clarityIds }) => {
29
31
  };
30
32
 
31
33
  export default MicrosoftClarity;
34
+
@@ -1,8 +1,10 @@
1
+ "use client"
2
+
1
3
  import Script from 'next/script';
2
4
  import type { FC } from 'react';
3
5
 
4
6
  interface TikTokPixelProps {
5
- pixelIds: string[]; // array
7
+ pixelIds: string[]; // ← array
6
8
  }
7
9
 
8
10
  const TikTokPixel: FC<TikTokPixelProps> = ({ pixelIds }) => {
@@ -32,3 +34,4 @@ const TikTokPixel: FC<TikTokPixelProps> = ({ pixelIds }) => {
32
34
  };
33
35
 
34
36
  export default TikTokPixel;
37
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -160,3 +162,4 @@ const AccordionContent = React.forwardRef<
160
162
  AccordionContent.displayName = "AccordionContent"
161
163
 
162
164
  export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
165
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -209,3 +211,4 @@ export {
209
211
  AlertDialogAction,
210
212
  AlertDialogCancel,
211
213
  }
214
+
@@ -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 { cn } from "@/lib/utils"
@@ -71,3 +73,4 @@ const AlertDescription = React.forwardRef<
71
73
  AlertDescription.displayName = "AlertDescription"
72
74
 
73
75
  export { Alert, AlertTitle, AlertDescription }
76
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -42,3 +44,4 @@ const AspectRatio = React.forwardRef<HTMLDivElement, AspectRatioProps>(
42
44
  AspectRatio.displayName = "AspectRatio"
43
45
 
44
46
  export { AspectRatio }
47
+
@@ -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 { cn } from "@/lib/utils"
@@ -92,3 +94,4 @@ const AvatarFallback = React.forwardRef<
92
94
  AvatarFallback.displayName = "AvatarFallback"
93
95
 
94
96
  export { Avatar, AvatarImage, AvatarFallback, avatarVariants }
97
+
@@ -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 { cn } from "@/lib/utils"
@@ -50,3 +52,4 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
50
52
  Badge.displayName = "Badge"
51
53
 
52
54
  export { Badge, badgeVariants }
55
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
  import { Slot } from "@/components/ui/slot"
@@ -145,3 +147,4 @@ export {
145
147
  BreadcrumbSeparator,
146
148
  BreadcrumbEllipsis,
147
149
  }
150
+
@@ -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 { cn } from "@/lib/utils"
@@ -62,3 +64,4 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
62
64
  ButtonGroup.displayName = "ButtonGroup"
63
65
 
64
66
  export { ButtonGroup, buttonGroupVariants }
67
+
@@ -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 { cn } from "@/lib/utils"
@@ -67,3 +69,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
67
69
  Button.displayName = "Button"
68
70
 
69
71
  export { Button, buttonVariants }
72
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -510,3 +512,4 @@ const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(
510
512
  Calendar.displayName = "Calendar"
511
513
 
512
514
  export { Calendar }
515
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -86,3 +88,4 @@ const CardFooter = React.forwardRef<
86
88
  CardFooter.displayName = "CardFooter"
87
89
 
88
90
  export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
91
+
@@ -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 { cn } from "@/lib/utils"
@@ -94,3 +96,4 @@ export {
94
96
  ChatbotMessage,
95
97
  ChatbotFooter,
96
98
  }
99
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -89,3 +91,4 @@ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
89
91
  Checkbox.displayName = "Checkbox"
90
92
 
91
93
  export { Checkbox }
94
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
  import { Slot } from "@/components/ui/slot"
@@ -103,3 +105,4 @@ const CollapsibleContent = React.forwardRef<
103
105
  CollapsibleContent.displayName = "CollapsibleContent"
104
106
 
105
107
  export { Collapsible, CollapsibleTrigger, CollapsibleContent }
108
+
@@ -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 { cn } from "@/lib/utils"
@@ -42,3 +44,4 @@ const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
42
44
  Container.displayName = "Container"
43
45
 
44
46
  export { Container, containerVariants }
47
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -265,3 +267,4 @@ export {
265
267
  DialogTitle,
266
268
  DialogDescription,
267
269
  }
270
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -525,3 +527,4 @@ export {
525
527
  DropdownMenuGroup,
526
528
  DropdownMenuPortal,
527
529
  }
530
+
@@ -0,0 +1,40 @@
1
+ "use client"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ icon?: React.ElementType
7
+ title: string
8
+ description: string
9
+ action?: React.ReactNode
10
+ }
11
+
12
+ export function EmptyState({
13
+ icon: Icon,
14
+ title,
15
+ description,
16
+ action,
17
+ className,
18
+ ...props
19
+ }: EmptyStateProps) {
20
+ return (
21
+ <div
22
+ className={cn(
23
+ "flex flex-col items-center justify-center py-12 text-center border-2 border-dashed rounded-lg bg-muted/5",
24
+ className
25
+ )}
26
+ {...props}
27
+ >
28
+ {Icon && (
29
+ <div className="flex h-20 w-20 items-center justify-center rounded-full bg-muted/50 mb-4">
30
+ <Icon className="h-10 w-10 text-muted-foreground" />
31
+ </div>
32
+ )}
33
+ <h3 className="text-lg font-semibold tracking-tight">{title}</h3>
34
+ <p className="text-muted-foreground text-sm max-w-sm mt-2 mb-6">
35
+ {description}
36
+ </p>
37
+ {action && <div>{action}</div>}
38
+ </div>
39
+ )
40
+ }
@@ -13,6 +13,7 @@ interface FileUploadProps {
13
13
  maxFiles?: number
14
14
  className?: string
15
15
  disabled?: boolean
16
+ variant?: 'default' | 'compact'
16
17
  }
17
18
 
18
19
  interface UploadedFile {
@@ -44,6 +45,7 @@ export function FileUpload({
44
45
  maxFiles = 5,
45
46
  className,
46
47
  disabled = false,
48
+ variant = 'default',
47
49
  }: FileUploadProps) {
48
50
  const [files, setFiles] = React.useState<UploadedFile[]>([])
49
51
  const [isDragging, setIsDragging] = React.useState(false)
@@ -141,7 +143,9 @@ export function FileUpload({
141
143
  onDragLeave={handleDragLeave}
142
144
  onDrop={handleDrop}
143
145
  className={cn(
144
- "relative flex flex-col items-center justify-center gap-4 rounded-lg border-2 border-dashed p-8 transition-colors cursor-pointer",
146
+ "relative flex cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors",
147
+ variant === 'default' && "flex-col gap-4 p-8",
148
+ variant === 'compact' && "flex-row gap-3 p-4",
145
149
  isDragging
146
150
  ? "border-primary bg-primary/5"
147
151
  : "border-muted-foreground/25 hover:border-primary/50 hover:bg-muted/50",
@@ -159,28 +163,55 @@ export function FileUpload({
159
163
  disabled={disabled}
160
164
  />
161
165
 
162
- <div className={cn(
163
- "flex h-14 w-14 items-center justify-center rounded-full bg-muted transition-colors",
164
- isDragging && "bg-primary/10"
165
- )}>
166
- <LuUpload className={cn(
167
- "h-6 w-6 text-muted-foreground",
168
- isDragging && "text-primary"
169
- )} />
170
- </div>
166
+ {variant === 'default' && (
167
+ <>
168
+ <div className={cn(
169
+ "flex h-14 w-14 items-center justify-center rounded-full bg-muted transition-colors",
170
+ isDragging && "bg-primary/10"
171
+ )}>
172
+ <LuUpload className={cn(
173
+ "h-6 w-6 text-muted-foreground",
174
+ isDragging && "text-primary"
175
+ )} />
176
+ </div>
171
177
 
172
- <div className="text-center">
173
- <p className="text-sm font-medium">
174
- {isDragging ? "Drop files here" : "Drag & drop files here"}
175
- </p>
176
- <p className="text-xs text-muted-foreground mt-1">
177
- or click to browse
178
- </p>
179
- </div>
178
+ <div className="text-center">
179
+ <p className="text-sm font-medium">
180
+ {isDragging ? "Drop files here" : "Drag & drop files here"}
181
+ </p>
182
+ <p className="text-xs text-muted-foreground mt-1">
183
+ or click to browse
184
+ </p>
185
+ </div>
186
+
187
+ <p className="text-xs text-muted-foreground">
188
+ {accept ? `Accepted: ${accept}` : "All file types accepted"} • Max {formatFileSize(maxSize)}
189
+ </p>
190
+ </>
191
+ )}
192
+
193
+ {variant === 'compact' && (
194
+ <>
195
+ <div className={cn(
196
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-muted transition-colors",
197
+ isDragging && "bg-primary/10"
198
+ )}>
199
+ <LuUpload className={cn(
200
+ "h-5 w-5 text-muted-foreground",
201
+ isDragging && "text-primary"
202
+ )} />
203
+ </div>
180
204
 
181
- <p className="text-xs text-muted-foreground">
182
- {accept ? `Accepted: ${accept}` : "All file types accepted"} • Max {formatFileSize(maxSize)}
183
- </p>
205
+ <div className="flex flex-col text-left">
206
+ <p className="text-sm font-medium">
207
+ {isDragging ? "Drop files" : "Upload files"}
208
+ </p>
209
+ <p className="text-xs text-muted-foreground">
210
+ {accept ? accept : `Max ${formatFileSize(maxSize)}`}
211
+ </p>
212
+ </div>
213
+ </>
214
+ )}
184
215
  </div>
185
216
 
186
217
  {/* Error Message */}
@@ -193,6 +224,11 @@ export function FileUpload({
193
224
  <div className="space-y-2">
194
225
  {files.map((uploadedFile, index) => {
195
226
  const FileIcon = getFileIcon(uploadedFile.file.type)
227
+ const dotIndex = uploadedFile.file.name.lastIndexOf('.')
228
+ const hasExtension = dotIndex !== -1
229
+ const name = hasExtension ? uploadedFile.file.name.slice(0, dotIndex) : uploadedFile.file.name
230
+ const extension = hasExtension ? uploadedFile.file.name.slice(dotIndex) : ''
231
+
196
232
  return (
197
233
  <div
198
234
  key={index}
@@ -211,9 +247,14 @@ export function FileUpload({
211
247
  )}
212
248
 
213
249
  <div className="flex-1 min-w-0">
214
- <p className="text-sm font-medium truncate">
215
- {uploadedFile.file.name}
216
- </p>
250
+ <div className="flex items-center min-w-0">
251
+ <span className="truncate text-sm font-medium">
252
+ {name}
253
+ </span>
254
+ <span className="text-sm font-medium whitespace-nowrap">
255
+ {extension}
256
+ </span>
257
+ </div>
217
258
  <p className="text-xs text-muted-foreground">
218
259
  {formatFileSize(uploadedFile.file.size)}
219
260
  </p>
@@ -0,0 +1,22 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ interface FloatingDockProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ children: React.ReactNode
8
+ }
9
+
10
+ export function FloatingDock({ className, children, ...props }: FloatingDockProps) {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "fixed bottom-4 right-4 md:bottom-6 md:right-6 z-50 flex flex-col items-center gap-2 md:gap-3",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ {children}
20
+ </div>
21
+ )
22
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { Label } from "@/components/ui/label"
3
5
  import { cn } from "@/lib/utils"
@@ -89,3 +91,4 @@ const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
89
91
  FormField.displayName = "FormField"
90
92
 
91
93
  export { FormField }
94
+
@@ -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 { cn } from "@/lib/utils"
@@ -142,3 +144,4 @@ const Image = React.forwardRef<HTMLImageElement, ImageProps>(
142
144
  Image.displayName = "Image"
143
145
 
144
146
  export { Image, imageVariants }
147
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -80,3 +82,4 @@ const InputAddon = React.forwardRef<
80
82
  InputAddon.displayName = "InputAddon"
81
83
 
82
84
  export { InputGroup, InputAddon }
85
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -10,7 +12,7 @@ interface KbdProps extends React.HTMLAttributes<HTMLElement> {
10
12
  * Kbd - Keyboard key display component
11
13
  *
12
14
  * Usage:
13
- * <Kbd>⌘</Kbd>
15
+ * <Kbd>⌘</Kbd>
14
16
  * <Kbd keys={["Ctrl", "Shift", "P"]} />
15
17
  */
16
18
  const Kbd = React.forwardRef<HTMLElement, KbdProps>(
@@ -58,3 +60,4 @@ const Kbd = React.forwardRef<HTMLElement, KbdProps>(
58
60
  Kbd.displayName = "Kbd"
59
61
 
60
62
  export { Kbd }
63
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -32,3 +34,4 @@ const Label = React.forwardRef<
32
34
  Label.displayName = "Label"
33
35
 
34
36
  export { Label }
37
+
@@ -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 { cn } from "@/lib/utils"
@@ -106,3 +108,4 @@ const LoadingOverlay = React.forwardRef<HTMLDivElement, LoadingOverlayProps>(
106
108
  LoadingOverlay.displayName = "LoadingOverlay"
107
109
 
108
110
  export { LoadingSpinner, LoadingOverlay, spinnerVariants }
111
+
@@ -0,0 +1,99 @@
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import { cn } from "@/lib/utils"
5
+ // import dynamic from "next/dynamic"
6
+
7
+ // Dynamically import Leaflet components only on client side
8
+ import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet"
9
+ import "leaflet/dist/leaflet.css"
10
+ import L from "leaflet"
11
+
12
+ // Fix for default marker icon in Next.js
13
+ const fixLeafletIcon = () => {
14
+ delete (L.Icon.Default.prototype as any)._getIconUrl
15
+ L.Icon.Default.mergeOptions({
16
+ iconRetinaUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
17
+ iconUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
18
+ shadowUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
19
+ })
20
+ }
21
+
22
+ interface MapProps {
23
+ lat: number
24
+ lng: number
25
+ zoom?: number
26
+ provider?: "osm" | "google"
27
+ className?: string
28
+ googleApiKey?: string
29
+ }
30
+
31
+ function ChangeView({ center, zoom }: { center: [number, number]; zoom: number }) {
32
+ const map = useMap()
33
+ map.setView(center, zoom)
34
+ return null
35
+ }
36
+
37
+ function OSMMap({ lat, lng, zoom = 13, className }: MapProps) {
38
+ useEffect(() => {
39
+ fixLeafletIcon()
40
+ }, [])
41
+
42
+ return (
43
+ <MapContainer center={[lat, lng]} zoom={zoom} scrollWheelZoom={false} className={cn("h-full w-full", className)}>
44
+ <ChangeView center={[lat, lng]} zoom={zoom} />
45
+ <TileLayer
46
+ attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
47
+ url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
48
+ />
49
+ <Marker position={[lat, lng]}>
50
+ <Popup>
51
+ We are here! <br /> Come visit us.
52
+ </Popup>
53
+ </Marker>
54
+ </MapContainer>
55
+ )
56
+ }
57
+
58
+ function GoogleMapEmbed({ lat, lng, zoom = 13, className, googleApiKey }: MapProps) {
59
+ // Falls back to simple no-key embed if no key provided (limited functionality)
60
+ // Or uses a proper specific place search if a key is provided.
61
+ // For agency demo without a key, generic iframe might be safer or just a placeholder message.
62
+
63
+ // Using unauthenticated iframe for location
64
+ const src = `https://maps.google.com/maps?q=${lat},${lng}&t=&z=${zoom}&ie=UTF8&iwloc=&output=embed`
65
+
66
+ return (
67
+ <iframe
68
+ width="100%"
69
+ height="100%"
70
+ className={cn("border-0", className)}
71
+ src={src}
72
+ allowFullScreen
73
+ loading="lazy"
74
+ referrerPolicy="no-referrer-when-downgrade"
75
+ />
76
+ )
77
+ }
78
+
79
+ export default function Map(props: MapProps) {
80
+ const [isMounted, setIsMounted] = useState(false)
81
+
82
+ useEffect(() => {
83
+ setIsMounted(true)
84
+ }, [])
85
+
86
+ if (!isMounted) {
87
+ return (
88
+ <div className={cn("flex items-center justify-center bg-muted animate-pulse", props.className)}>
89
+ <span className="text-muted-foreground">Loading Map...</span>
90
+ </div>
91
+ )
92
+ }
93
+
94
+ if (props.provider === "google") {
95
+ return <GoogleMapEmbed {...props} />
96
+ }
97
+
98
+ return <OSMMap {...props} />
99
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ interface MarqueeProps {
6
+ className?: string;
7
+ reverse?: boolean;
8
+ pauseOnHover?: boolean;
9
+ children?: React.ReactNode;
10
+ vertical?: boolean;
11
+ repeat?: number;
12
+ [key: string]: any;
13
+ }
14
+
15
+ export default function Marquee({
16
+ className,
17
+ reverse,
18
+ pauseOnHover = false,
19
+ children,
20
+ vertical = false,
21
+ repeat = 4,
22
+ ...props
23
+ }: MarqueeProps) {
24
+ return (
25
+ <div
26
+ {...props}
27
+ className={cn(
28
+ "group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
29
+ {
30
+ "flex-row": !vertical,
31
+ "flex-col": vertical,
32
+ },
33
+ className,
34
+ )}
35
+ >
36
+ {Array(repeat)
37
+ .fill(0)
38
+ .map((_, i) => (
39
+ <div
40
+ key={i}
41
+ className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
42
+ "animate-marquee flex-row": !vertical,
43
+ "animate-marquee-vertical flex-col": vertical,
44
+ "group-hover:[animation-play-state:paused]": pauseOnHover,
45
+ "[animation-direction:reverse]": reverse,
46
+ })}
47
+ >
48
+ {children}
49
+ </div>
50
+ ))}
51
+ </div>
52
+ );
53
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -47,3 +49,4 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
47
49
  NativeSelect.displayName = "NativeSelect"
48
50
 
49
51
  export { NativeSelect }
52
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -150,3 +152,4 @@ const OtpInput = React.forwardRef<HTMLDivElement, OtpInputProps>(
150
152
  OtpInput.displayName = "OtpInput"
151
153
 
152
154
  export { OtpInput }
155
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
  import { buttonVariants } from "./button"
@@ -147,3 +149,4 @@ export {
147
149
  PaginationNext,
148
150
  PaginationEllipsis,
149
151
  }
152
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -222,3 +224,4 @@ const PopoverContent = React.forwardRef<
222
224
  PopoverContent.displayName = "PopoverContent"
223
225
 
224
226
  export { Popover, PopoverTrigger, PopoverContent }
227
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -47,3 +49,4 @@ const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
47
49
  Progress.displayName = "Progress"
48
50
 
49
51
  export { Progress }
52
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -97,3 +99,4 @@ const RadioGroupItem = React.forwardRef<HTMLButtonElement, RadioGroupItemProps>(
97
99
  RadioGroupItem.displayName = "RadioGroupItem"
98
100
 
99
101
  export { RadioGroup, RadioGroupItem }
102
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -117,3 +119,4 @@ const ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(
117
119
  ScrollBar.displayName = "ScrollBar"
118
120
 
119
121
  export { ScrollArea, ScrollBar }
122
+
@@ -0,0 +1,99 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { LuArrowUp } from "react-icons/lu"
5
+ import { cn } from "@/lib/utils"
6
+ import { Button } from "@/components/ui/button"
7
+ import { FaArrowUp } from "react-icons/fa"
8
+
9
+ export function ScrollToTop() {
10
+ const [isVisible, setIsVisible] = React.useState(false)
11
+ const [scrollProgress, setScrollProgress] = React.useState(0)
12
+
13
+ React.useEffect(() => {
14
+ const handleScroll = () => {
15
+ // Visibility toggle
16
+ if (window.scrollY > 300) {
17
+ setIsVisible(true)
18
+ } else {
19
+ setIsVisible(false)
20
+ }
21
+
22
+ // Progress calculation
23
+ const winScroll = document.body.scrollTop || document.documentElement.scrollTop
24
+ const height = document.documentElement.scrollHeight - document.documentElement.clientHeight
25
+ const scrolled = (winScroll / height) * 100
26
+ setScrollProgress(scrolled)
27
+ }
28
+
29
+ window.addEventListener("scroll", handleScroll)
30
+ return () => window.removeEventListener("scroll", handleScroll)
31
+ }, [])
32
+
33
+ const scrollToTop = () => {
34
+ window.scrollTo({
35
+ top: 0,
36
+ behavior: "smooth",
37
+ })
38
+ }
39
+
40
+ if (!isVisible) {
41
+ return null
42
+ }
43
+
44
+ // Circumference of the circle (r=28) -> 2 * pi * 28 ≈ 175.929
45
+ const circumference = 175.929
46
+ const strokeDashoffset = circumference - (scrollProgress / 100) * circumference
47
+
48
+ return (
49
+ <Button
50
+ variant="default"
51
+ size="icon"
52
+ onClick={scrollToTop}
53
+ aria-label="Scroll to top"
54
+ className={cn(
55
+ "relative group flex items-center justify-center cursor-pointer",
56
+ "h-10 w-10 md:h-16 md:w-16 rounded-full",
57
+ "bg-primary",
58
+ "shadow-lg transition-all duration-300 hover:scale-110 z-50",
59
+ "text-white"
60
+ )}
61
+ >
62
+ {/* Progress Ring SVG */}
63
+ <svg
64
+ className="absolute inset-0 -rotate-90 pointer-events-none !w-full !h-full"
65
+ width="100%"
66
+ height="100%"
67
+ viewBox="0 0 64 64"
68
+ >
69
+ {/* Background Circle Track */}
70
+ <circle
71
+ cx="32"
72
+ cy="32"
73
+ r="28"
74
+ stroke="rgba(255,255,255,0.2)"
75
+ strokeWidth="4"
76
+ fill="none"
77
+ />
78
+ {/* Progress Circle */}
79
+ <circle
80
+ cx="32"
81
+ cy="32"
82
+ r="28"
83
+ stroke="currentColor"
84
+ strokeWidth="4"
85
+ fill="none"
86
+ strokeDasharray={circumference}
87
+ strokeDashoffset={strokeDashoffset}
88
+ strokeLinecap="round"
89
+ className="transition-all duration-150 ease-out text-white"
90
+ />
91
+ </svg>
92
+
93
+ {/* Icon */}
94
+ <div className="relative z-10">
95
+ <LuArrowUp className="!h-5 !w-5 md:!h-8 md:!w-8 drop-shadow-md" />
96
+ </div>
97
+ </Button>
98
+ )
99
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -145,3 +147,4 @@ const Search = React.forwardRef<HTMLInputElement, SearchProps>(
145
147
  Search.displayName = "Search"
146
148
 
147
149
  export { Search }
150
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -288,3 +290,4 @@ const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
288
290
  SelectItem.displayName = "SelectItem"
289
291
 
290
292
  export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem }
293
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -42,3 +44,4 @@ const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
42
44
  Separator.displayName = "Separator"
43
45
 
44
46
  export { Separator }
47
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -24,3 +26,4 @@ const Skeleton = React.forwardRef<
24
26
  Skeleton.displayName = "Skeleton"
25
27
 
26
28
  export { Skeleton }
29
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -192,3 +194,4 @@ const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
192
194
  Slider.displayName = "Slider"
193
195
 
194
196
  export { Slider }
197
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -67,3 +69,4 @@ const Slot = React.forwardRef<HTMLElement, SlotProps>(
67
69
  Slot.displayName = "Slot"
68
70
 
69
71
  export { Slot }
72
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -129,3 +131,4 @@ const StarRating = React.forwardRef<HTMLDivElement, StarRatingProps>(
129
131
  StarRating.displayName = "StarRating"
130
132
 
131
133
  export { StarRating }
134
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -68,3 +70,4 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
68
70
  Switch.displayName = "Switch"
69
71
 
70
72
  export { Switch }
73
+
@@ -0,0 +1,96 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export interface HeadingInfo {
7
+ id: string
8
+ text: string
9
+ level: number
10
+ children?: HeadingInfo[]
11
+ }
12
+
13
+ interface TableOfContentsProps {
14
+ headings?: HeadingInfo[]
15
+ activeSection?: string | null
16
+ className?: string
17
+ onSectionClick?: (id: string) => void
18
+ }
19
+
20
+ export function TableOfContents({
21
+ headings = [],
22
+ activeSection,
23
+ className,
24
+ onSectionClick,
25
+ }: TableOfContentsProps) {
26
+
27
+ const scrollToHeading = React.useCallback((headingId: string) => {
28
+ if (onSectionClick) {
29
+ onSectionClick(headingId)
30
+ return
31
+ }
32
+
33
+ const element = document.getElementById(headingId)
34
+ if (element) {
35
+ const offset = 80
36
+ const elementPosition =
37
+ element.getBoundingClientRect().top + window.pageYOffset
38
+ window.scrollTo({
39
+ top: elementPosition - offset,
40
+ behavior: "smooth",
41
+ })
42
+ }
43
+ }, [onSectionClick])
44
+
45
+ // Flatten all headings without hierarchy for simplicity
46
+ const flattenedHeadings = React.useMemo(() => {
47
+ const flatten = (headings: HeadingInfo[]): HeadingInfo[] => {
48
+ return headings.flatMap((heading) => [
49
+ heading,
50
+ ...(heading.children ? flatten(heading.children) : []),
51
+ ])
52
+ }
53
+ return flatten(headings)
54
+ }, [headings])
55
+
56
+ if (flattenedHeadings.length === 0) return null
57
+
58
+ return (
59
+ <div
60
+ className={cn(
61
+ "hidden lg:block sticky top-16 right-0 h-[calc(100vh-4rem)] w-full bg-background justify-self-end lg:max-w-64",
62
+ className
63
+ )}
64
+ >
65
+ <div className="flex flex-col h-full">
66
+ <div className="flex items-center justify-between p-4">
67
+ <span className="font-medium text-foreground text-sm">
68
+ On this page
69
+ </span>
70
+ </div>
71
+
72
+ <nav className="flex-1 overflow-y-auto p-4 scrollbar-thin">
73
+ <div className="space-y-1">
74
+ {flattenedHeadings.map((heading) => (
75
+ <button
76
+ key={heading.id}
77
+ onClick={() => scrollToHeading(heading.id)}
78
+ className={cn(
79
+ "w-full text-left px-2 py-1.5 rounded text-xs transition-colors duration-200",
80
+ activeSection === heading.id
81
+ ? "text-primary bg-accent font-medium"
82
+ : "text-muted-foreground hover:text-foreground",
83
+ )}
84
+ style={{
85
+ paddingLeft: `${(heading.level - 1) * 12 + 8}px`,
86
+ }}
87
+ >
88
+ {heading.text}
89
+ </button>
90
+ ))}
91
+ </div>
92
+ </nav>
93
+ </div>
94
+ </div>
95
+ )
96
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -134,3 +136,4 @@ export {
134
136
  TableCell,
135
137
  TableCaption,
136
138
  }
139
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -120,3 +122,4 @@ const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(
120
122
  TabsContent.displayName = "TabsContent"
121
123
 
122
124
  export { Tabs, TabsList, TabsTrigger, TabsContent }
125
+
@@ -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 { cn } from "@/lib/utils"
@@ -59,3 +61,4 @@ const Text = React.forwardRef<HTMLElement, TextProps>(
59
61
  Text.displayName = "Text"
60
62
 
61
63
  export { Text, textVariants }
64
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { cn } from "@/lib/utils"
3
5
 
@@ -37,3 +39,4 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
37
39
  Textarea.displayName = "Textarea"
38
40
 
39
41
  export { Textarea }
42
+
@@ -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 { cn } from "@/lib/utils"
@@ -93,3 +95,4 @@ const ToastAction = React.forwardRef<
93
95
  ToastAction.displayName = "ToastAction"
94
96
 
95
97
  export { Toast, ToastTitle, ToastDescription, ToastAction, toastVariants }
98
+
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { createPortal } from "react-dom"
3
5
  import { cn } from "@/lib/utils"
@@ -139,3 +141,4 @@ const TooltipContent = React.forwardRef<
139
141
  TooltipContent.displayName = "TooltipContent"
140
142
 
141
143
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
144
+
@@ -0,0 +1,36 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { FaWhatsapp } from "react-icons/fa"
5
+ import { cn } from "@/lib/utils"
6
+ import { Button } from "@/components/ui/button"
7
+
8
+ interface WhatsAppProps {
9
+ phoneNumber?: string
10
+ message?: string
11
+ }
12
+
13
+ export function WhatsApp({
14
+ phoneNumber = "1234567890", // Default placeholder
15
+ message = "Hello! I would like to know more about your services."
16
+ }: WhatsAppProps) {
17
+ const handleClick = () => {
18
+ const url = `https://wa.me/${phoneNumber}?text=${encodeURIComponent(message)}`
19
+ window.open(url, "_blank")
20
+ }
21
+
22
+ return (
23
+ <Button
24
+ variant="default"
25
+ size="icon"
26
+ onClick={handleClick}
27
+ className={cn(
28
+ "rounded-full h-10 w-10 md:h-16 md:w-16 shadow-lg transition-all duration-300 hover:scale-110",
29
+ "bg-[#25D366] hover:bg-[#20bd5a] text-white"
30
+ )}
31
+ aria-label="Contact us on WhatsApp"
32
+ >
33
+ <FaWhatsapp className="!h-5 !w-5 md:!h-8 md:!w-8" />
34
+ </Button>
35
+ )
36
+ }