@nikala-ui/core 0.10.1 → 0.11.0

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 (55) hide show
  1. package/package.json +1 -1
  2. package/registry/bubble.json +18 -0
  3. package/registry/button-group.json +20 -0
  4. package/registry/create-chat-scroll.json +13 -0
  5. package/registry/create-drop-zone.json +13 -0
  6. package/registry/create-pagination.json +13 -0
  7. package/registry/dropzone.json +21 -0
  8. package/registry/footer.json +18 -0
  9. package/registry/forgot-password-01.json +28 -0
  10. package/registry/hero-01.json +22 -0
  11. package/registry/index.json +331 -0
  12. package/registry/login-01.json +28 -0
  13. package/registry/marker.json +17 -0
  14. package/registry/marquee.json +17 -0
  15. package/registry/message.json +17 -0
  16. package/registry/navbar.json +19 -0
  17. package/registry/navigation-menu.json +22 -0
  18. package/registry/otp-verification-01.json +26 -0
  19. package/registry/pagination.json +22 -0
  20. package/registry/rating.json +19 -0
  21. package/registry/register-01.json +31 -0
  22. package/registry/review-card.json +23 -0
  23. package/registry/scroll-area.json +1 -1
  24. package/registry/sidebar.json +24 -0
  25. package/registry/spinner.json +1 -1
  26. package/registry/stat.json +19 -0
  27. package/registry/table.json +17 -0
  28. package/registry/timeline.json +18 -0
  29. package/registry/toggle-group.json +21 -0
  30. package/src/registry/blocks/forgot-password-01.tsx +141 -0
  31. package/src/registry/blocks/hero-01.tsx +28 -0
  32. package/src/registry/blocks/login-01.tsx +231 -0
  33. package/src/registry/blocks/otp-verification-01.tsx +170 -0
  34. package/src/registry/blocks/register-01.tsx +318 -0
  35. package/src/registry/components/ui/bubble.tsx +142 -0
  36. package/src/registry/components/ui/button-group.tsx +42 -0
  37. package/src/registry/components/ui/dropzone.tsx +189 -0
  38. package/src/registry/components/ui/footer.tsx +247 -0
  39. package/src/registry/components/ui/marker.tsx +102 -0
  40. package/src/registry/components/ui/marquee.tsx +118 -0
  41. package/src/registry/components/ui/message.tsx +168 -0
  42. package/src/registry/components/ui/navbar.tsx +368 -0
  43. package/src/registry/components/ui/navigation-menu.tsx +358 -0
  44. package/src/registry/components/ui/pagination.tsx +258 -0
  45. package/src/registry/components/ui/rating.tsx +185 -0
  46. package/src/registry/components/ui/review-card.tsx +195 -0
  47. package/src/registry/components/ui/scroll-area.tsx +2 -2
  48. package/src/registry/components/ui/sidebar.tsx +692 -0
  49. package/src/registry/components/ui/spinner.tsx +1 -1
  50. package/src/registry/components/ui/stat.tsx +245 -0
  51. package/src/registry/components/ui/table.tsx +160 -0
  52. package/src/registry/components/ui/timeline.tsx +350 -0
  53. package/src/registry/components/ui/toggle-group.tsx +203 -0
  54. package/src/registry/index.ts +3 -3
  55. package/src/registry/metadata.ts +141 -0
@@ -74,6 +74,30 @@
74
74
  "tailwind-merge"
75
75
  ]
76
76
  },
77
+ {
78
+ "name": "bubble",
79
+ "title": "Bubble",
80
+ "description": "Chat message bubble container supporting variants, grouped consecutive bubbles, and emoji reactions.",
81
+ "type": "registry:ui",
82
+ "dependencies": [
83
+ "clsx",
84
+ "tailwind-merge",
85
+ "class-variance-authority"
86
+ ]
87
+ },
88
+ {
89
+ "name": "button-group",
90
+ "title": "Button Group",
91
+ "description": "Groups related buttons into a connected horizontal or vertical control.",
92
+ "type": "registry:ui",
93
+ "dependencies": [
94
+ "clsx",
95
+ "tailwind-merge"
96
+ ],
97
+ "registryDependencies": [
98
+ "button"
99
+ ]
100
+ },
77
101
  {
78
102
  "name": "button",
79
103
  "title": "Button",
@@ -197,6 +221,20 @@
197
221
  "scroll-area"
198
222
  ]
199
223
  },
224
+ {
225
+ "name": "dropzone",
226
+ "title": "Dropzone",
227
+ "description": "A compound drag-and-drop file upload container with file list previews and validation feedback.",
228
+ "type": "registry:ui",
229
+ "dependencies": [
230
+ "clsx",
231
+ "tailwind-merge",
232
+ "lucide-solid"
233
+ ],
234
+ "registryDependencies": [
235
+ "create-drop-zone"
236
+ ]
237
+ },
200
238
  {
201
239
  "name": "empty",
202
240
  "title": "Empty",
@@ -220,6 +258,17 @@
220
258
  "label"
221
259
  ]
222
260
  },
261
+ {
262
+ "name": "footer",
263
+ "title": "Footer",
264
+ "description": "A responsive, accessible, and structured bottom navigation layout suite supporting multi-column link directories, brand sections, and newsletter inputs.",
265
+ "type": "registry:ui",
266
+ "dependencies": [
267
+ "clsx",
268
+ "tailwind-merge",
269
+ "class-variance-authority"
270
+ ]
271
+ },
223
272
  {
224
273
  "name": "form-message",
225
274
  "title": "Form Message",
@@ -332,6 +381,63 @@
332
381
  "tailwind-merge"
333
382
  ]
334
383
  },
384
+ {
385
+ "name": "marker",
386
+ "title": "Marker",
387
+ "description": "System chat events, date dividers, and live typing indicator badges.",
388
+ "type": "registry:ui",
389
+ "dependencies": [
390
+ "clsx",
391
+ "tailwind-merge"
392
+ ]
393
+ },
394
+ {
395
+ "name": "marquee",
396
+ "title": "Marquee",
397
+ "description": "A smooth, GPU-accelerated infinite scrolling ticker component for logo clouds, testimonials, and live ribbons.",
398
+ "type": "registry:ui",
399
+ "dependencies": [
400
+ "clsx",
401
+ "tailwind-merge"
402
+ ]
403
+ },
404
+ {
405
+ "name": "message",
406
+ "title": "Message",
407
+ "description": "A structured chat and conversation message layout with avatars, alignment, headers, footers, and actions.",
408
+ "type": "registry:ui",
409
+ "dependencies": [
410
+ "clsx",
411
+ "tailwind-merge"
412
+ ]
413
+ },
414
+ {
415
+ "name": "navbar",
416
+ "title": "Navbar",
417
+ "description": "A responsive, accessible, and composable top navigation header suite supporting nested dropdown flyouts, floating card containers, and mobile navigation drawers.",
418
+ "type": "registry:ui",
419
+ "dependencies": [
420
+ "clsx",
421
+ "tailwind-merge",
422
+ "class-variance-authority",
423
+ "lucide-solid"
424
+ ]
425
+ },
426
+ {
427
+ "name": "navigation-menu",
428
+ "title": "Navigation Menu",
429
+ "description": "A responsive and accessible top header navigation menu with mega-menu dropdowns and link previews.",
430
+ "type": "registry:ui",
431
+ "dependencies": [
432
+ "clsx",
433
+ "tailwind-merge",
434
+ "class-variance-authority",
435
+ "lucide-solid"
436
+ ],
437
+ "registryDependencies": [
438
+ "create-click-outside"
439
+ ]
440
+ },
335
441
  {
336
442
  "name": "number-input",
337
443
  "title": "Number Input",
@@ -349,6 +455,21 @@
349
455
  "create-long-press"
350
456
  ]
351
457
  },
458
+ {
459
+ "name": "pagination",
460
+ "title": "Pagination",
461
+ "description": "An accessible multi-page navigation bar with previous, next, page numbers, and ellipsis controls.",
462
+ "type": "registry:ui",
463
+ "dependencies": [
464
+ "clsx",
465
+ "tailwind-merge",
466
+ "class-variance-authority",
467
+ "lucide-solid"
468
+ ],
469
+ "registryDependencies": [
470
+ "create-pagination"
471
+ ]
472
+ },
352
473
  {
353
474
  "name": "pin-input",
354
475
  "title": "Pin Input",
@@ -393,6 +514,18 @@
393
514
  "@kobalte/core"
394
515
  ]
395
516
  },
517
+ {
518
+ "name": "rating",
519
+ "title": "Rating",
520
+ "description": "An accessible star rating component supporting interactive inputs, hover preview states, and read-only score badges.",
521
+ "type": "registry:ui",
522
+ "dependencies": [
523
+ "clsx",
524
+ "tailwind-merge",
525
+ "class-variance-authority",
526
+ "lucide-solid"
527
+ ]
528
+ },
396
529
  {
397
530
  "name": "resizable",
398
531
  "title": "Resizable",
@@ -407,6 +540,22 @@
407
540
  "create-resize-observer"
408
541
  ]
409
542
  },
543
+ {
544
+ "name": "review-card",
545
+ "title": "Review Card",
546
+ "description": "A versatile, structured card component for customer testimonials, product ratings, verified buyer badges, and social proof.",
547
+ "type": "registry:ui",
548
+ "dependencies": [
549
+ "clsx",
550
+ "tailwind-merge",
551
+ "class-variance-authority",
552
+ "lucide-solid"
553
+ ],
554
+ "registryDependencies": [
555
+ "avatar",
556
+ "rating"
557
+ ]
558
+ },
410
559
  {
411
560
  "name": "scroll-area",
412
561
  "title": "Scroll Area",
@@ -460,6 +609,23 @@
460
609
  "scroll-area"
461
610
  ]
462
611
  },
612
+ {
613
+ "name": "sidebar",
614
+ "title": "Sidebar",
615
+ "description": "A composable, collapsible, and accessible application sidebar navigation suite with icon mode, mobile drawer, and keyboard shortcuts.",
616
+ "type": "registry:ui",
617
+ "dependencies": [
618
+ "clsx",
619
+ "tailwind-merge",
620
+ "class-variance-authority",
621
+ "lucide-solid"
622
+ ],
623
+ "registryDependencies": [
624
+ "tooltip",
625
+ "skeleton",
626
+ "separator"
627
+ ]
628
+ },
463
629
  {
464
630
  "name": "skeleton",
465
631
  "title": "Skeleton",
@@ -492,6 +658,18 @@
492
658
  "class-variance-authority"
493
659
  ]
494
660
  },
661
+ {
662
+ "name": "stat",
663
+ "title": "Stat",
664
+ "description": "Display key performance indicators, statistics, financial data, and metrics with trends and icons.",
665
+ "type": "registry:ui",
666
+ "dependencies": [
667
+ "clsx",
668
+ "tailwind-merge",
669
+ "class-variance-authority",
670
+ "lucide-solid"
671
+ ]
672
+ },
495
673
  {
496
674
  "name": "status",
497
675
  "title": "Status",
@@ -513,6 +691,16 @@
513
691
  "tailwind-merge"
514
692
  ]
515
693
  },
694
+ {
695
+ "name": "table",
696
+ "title": "Table",
697
+ "description": "A responsive and accessible data table component with headers, rows, cells, and footer summaries.",
698
+ "type": "registry:ui",
699
+ "dependencies": [
700
+ "clsx",
701
+ "tailwind-merge"
702
+ ]
703
+ },
516
704
  {
517
705
  "name": "tabs",
518
706
  "title": "Tabs",
@@ -549,6 +737,17 @@
549
737
  "dropdown-menu"
550
738
  ]
551
739
  },
740
+ {
741
+ "name": "timeline",
742
+ "title": "Timeline",
743
+ "description": "A responsive chronological display for event streams, activity logs, order tracking, and multi-step workflows.",
744
+ "type": "registry:ui",
745
+ "dependencies": [
746
+ "clsx",
747
+ "tailwind-merge",
748
+ "class-variance-authority"
749
+ ]
750
+ },
552
751
  {
553
752
  "name": "toast",
554
753
  "title": "Toast / Sonner",
@@ -562,6 +761,20 @@
562
761
  "@kobalte/core"
563
762
  ]
564
763
  },
764
+ {
765
+ "name": "toggle-group",
766
+ "title": "Toggle Group",
767
+ "description": "A set of two-state buttons that can be toggled on or off with single or multiple selection modes.",
768
+ "type": "registry:ui",
769
+ "dependencies": [
770
+ "clsx",
771
+ "tailwind-merge",
772
+ "class-variance-authority"
773
+ ],
774
+ "registryDependencies": [
775
+ "create-controllable-signal"
776
+ ]
777
+ },
565
778
  {
566
779
  "name": "toggle",
567
780
  "title": "Toggle",
@@ -584,6 +797,106 @@
584
797
  "@kobalte/core"
585
798
  ]
586
799
  },
800
+ {
801
+ "name": "forgot-password-01",
802
+ "title": "Forgot Password 01 — Account Recovery Flow",
803
+ "description": "A sleek password recovery block with email instructions submission and success confirmation states.",
804
+ "type": "registry:block",
805
+ "dependencies": [
806
+ "clsx",
807
+ "tailwind-merge",
808
+ "lucide-solid"
809
+ ],
810
+ "registryDependencies": [
811
+ "card",
812
+ "input",
813
+ "label",
814
+ "button",
815
+ "alert",
816
+ "form",
817
+ "field",
818
+ "form-message"
819
+ ]
820
+ },
821
+ {
822
+ "name": "hero-01",
823
+ "title": "Hero 01 — Simple Centered with Actions",
824
+ "description": "A clean centered hero section with badge pill, high-contrast headline, and dual CTA buttons.",
825
+ "type": "registry:block",
826
+ "dependencies": [
827
+ "clsx",
828
+ "tailwind-merge",
829
+ "lucide-solid"
830
+ ],
831
+ "registryDependencies": [
832
+ "button",
833
+ "badge"
834
+ ]
835
+ },
836
+ {
837
+ "name": "login-01",
838
+ "title": "Login 01 — Split Screen with Social Auth & Testimonial",
839
+ "description": "A modern split-screen authentication page block with OAuth providers, email sign-in form, and brand testimonial visual.",
840
+ "type": "registry:block",
841
+ "dependencies": [
842
+ "clsx",
843
+ "tailwind-merge",
844
+ "lucide-solid"
845
+ ],
846
+ "registryDependencies": [
847
+ "button",
848
+ "input",
849
+ "label",
850
+ "checkbox",
851
+ "separator",
852
+ "form",
853
+ "field",
854
+ "form-message"
855
+ ]
856
+ },
857
+ {
858
+ "name": "otp-verification-01",
859
+ "title": "OTP Verification 01 — Two-Factor Security Code",
860
+ "description": "A clean 2-Factor Authentication block with a 6-digit PIN input, countdown resend timer, and security notifications.",
861
+ "type": "registry:block",
862
+ "dependencies": [
863
+ "clsx",
864
+ "tailwind-merge",
865
+ "lucide-solid"
866
+ ],
867
+ "registryDependencies": [
868
+ "card",
869
+ "pin-input",
870
+ "button",
871
+ "alert",
872
+ "badge",
873
+ "form"
874
+ ]
875
+ },
876
+ {
877
+ "name": "register-01",
878
+ "title": "Register 01 — Sign-Up Card with Password Strength",
879
+ "description": "A comprehensive sign-up card featuring social logins, live password strength meter with validation checklist, and terms agreement.",
880
+ "type": "registry:block",
881
+ "dependencies": [
882
+ "clsx",
883
+ "tailwind-merge",
884
+ "lucide-solid"
885
+ ],
886
+ "registryDependencies": [
887
+ "card",
888
+ "input",
889
+ "label",
890
+ "button",
891
+ "checkbox",
892
+ "progress",
893
+ "badge",
894
+ "separator",
895
+ "form",
896
+ "field",
897
+ "form-message"
898
+ ]
899
+ },
587
900
  {
588
901
  "name": "create-controllable-signal",
589
902
  "title": "createControllableSignal",
@@ -823,5 +1136,23 @@
823
1136
  "title": "createScrollIntoView",
824
1137
  "description": "SolidJS reactive primitive for scrolling a target element into view smooth or auto behavior",
825
1138
  "type": "registry:hook"
1139
+ },
1140
+ {
1141
+ "name": "create-drop-zone",
1142
+ "title": "createDropZone",
1143
+ "description": "SolidJS reactive primitive for file drag & drop operations, validation, and file chooser dialogs",
1144
+ "type": "registry:hook"
1145
+ },
1146
+ {
1147
+ "name": "create-pagination",
1148
+ "title": "createPagination",
1149
+ "description": "SolidJS reactive primitive for computing pagination state, dynamic page ranges with ellipses, and navigation controls",
1150
+ "type": "registry:hook"
1151
+ },
1152
+ {
1153
+ "name": "create-chat-scroll",
1154
+ "title": "createChatScroll",
1155
+ "description": "SolidJS reactive primitive for automated chat container scrolling with manual scroll-up detection",
1156
+ "type": "registry:hook"
826
1157
  }
827
1158
  ]
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "login-01",
3
+ "title": "Login 01 — Split Screen with Social Auth & Testimonial",
4
+ "description": "A modern split-screen authentication page block with OAuth providers, email sign-in form, and brand testimonial visual.",
5
+ "type": "registry:block",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "button",
13
+ "input",
14
+ "label",
15
+ "checkbox",
16
+ "separator",
17
+ "form",
18
+ "field",
19
+ "form-message"
20
+ ],
21
+ "files": [
22
+ {
23
+ "path": "blocks/login-01.tsx",
24
+ "content": "import { createSignal, Show, type Component } from \"solid-js\";\nimport { createForm } from \"@nikala-ui/hooks\";\nimport { Form } from \"../components/ui/form\";\nimport { Field, FieldLabel } from \"../components/ui/field\";\nimport { FormMessage } from \"../components/ui/form-message\";\nimport { Button } from \"../components/ui/button\";\nimport { Input } from \"../components/ui/input\";\nimport { Checkbox } from \"../components/ui/checkbox\";\nimport { Label } from \"../components/ui/label\";\nimport { Separator } from \"../components/ui/separator\";\nimport { Eye, EyeOff, Sparkles, ArrowRight } from \"lucide-solid\";\n\nexport const Login01: Component = () => {\n const [showPassword, setShowPassword] = createSignal(false);\n\n const form = createForm({\n initialValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n validate: (values) => {\n const errors: Record<string, string> = {};\n if (!values.email) {\n errors.email = \"Email is required\";\n } else if (!values.email.includes(\"@\")) {\n errors.email = \"Please enter a valid email address\";\n }\n if (!values.password) {\n errors.password = \"Password is required\";\n }\n return errors;\n },\n onSubmit: async (values) => {\n // Simulate API request\n await new Promise((resolve) => setTimeout(resolve, 1200));\n },\n });\n\n const isFormValid = () =>\n form.values().email.trim() !== \"\" &&\n form.values().email.includes(\"@\") &&\n form.values().password.trim() !== \"\";\n\n return (\n <div class=\"@container w-full min-h-[560px] @5xl:min-h-[700px] grid grid-cols-1 @5xl:grid-cols-2 bg-background text-foreground rounded-lg border border-border overflow-hidden shadow-xs\">\n {/* Left Column: Login Form */}\n <div class=\"flex flex-col justify-between p-6 @md:p-10 @5xl:p-12 w-full\">\n {/* Brand Header */}\n <div class=\"flex items-center gap-2.5\">\n <div class=\"size-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm shadow-2xs shrink-0\">\n N\n </div>\n <span class=\"font-bold text-lg tracking-tight\">Nikala UI</span>\n </div>\n\n {/* Form Container */}\n <div class=\"mx-auto w-full max-w-sm my-auto py-6 @md:py-8\">\n <div class=\"space-y-1.5 text-left mb-6\">\n <h1 class=\"text-2xl @md:text-3xl font-bold tracking-tight text-foreground\">\n Welcome back\n </h1>\n <p class=\"text-xs @md:text-sm text-muted-foreground\">\n Enter your credentials to access your account dashboard\n </p>\n </div>\n\n {/* Social OAuth Buttons */}\n <div class=\"grid grid-cols-1 @xs:grid-cols-2 gap-2.5 mb-6\">\n <Button variant=\"outline\" class=\"w-full justify-center text-xs @md:text-sm h-9\">\n <svg class=\"size-4 mr-2 shrink-0\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z\" />\n </svg>\n GitHub\n </Button>\n <Button variant=\"outline\" class=\"w-full justify-center text-xs @md:text-sm h-9\">\n <svg class=\"size-4 mr-2 shrink-0\" viewBox=\"0 0 24 24\">\n <path\n fill=\"#4285F4\"\n d=\"M23.745 12.27c0-.7-.06-1.4-.19-2.07H12v4.51h6.6c-.29 1.52-1.14 2.82-2.4 3.68v3.05h3.88c2.27-2.09 3.66-5.17 3.66-9.17z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M12 24c3.24 0 5.95-1.08 7.93-2.91l-3.88-3.05c-1.08.72-2.45 1.16-4.05 1.16-3.12 0-5.77-2.1-6.72-4.93H1.25v3.15C3.26 21.36 7.33 24 12 24z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M5.28 14.27c-.25-.72-.38-1.49-.38-2.27s.13-1.55.38-2.27V6.58H1.25C.45 8.18 0 10.03 0 12s.45 3.82 1.25 5.42l4.03-3.15z\"\n />\n <path\n fill=\"#EA4335\"\n d=\"M12 4.75c1.77 0 3.35.61 4.6 1.8l3.42-3.42C17.95 1.19 15.24 0 12 0 7.33 0 3.26 2.64 1.25 6.58l4.03 3.15c.95-2.83 3.6-4.98 6.72-4.98z\"\n />\n </svg>\n Google\n </Button>\n </div>\n\n <div class=\"relative mb-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <Separator />\n </div>\n <div class=\"relative flex justify-center text-xs uppercase\">\n <span class=\"bg-background px-3 text-muted-foreground font-medium text-[11px]\">\n Or continue with email\n </span>\n </div>\n </div>\n\n {/* Form with Nikala UI Form & Field ecosystem */}\n <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()} class=\"space-y-4\">\n <Field>\n <FieldLabel for=\"login-email\" class=\"text-xs @md:text-sm\">Email address</FieldLabel>\n <Input\n id=\"login-email\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={form.values().email}\n onInput={form.handleChange(\"email\")}\n onBlur={form.handleBlur(\"email\")}\n autocomplete=\"email\"\n />\n <FormMessage form={form} name=\"email\" />\n </Field>\n\n <Field>\n <div class=\"flex items-center justify-between\">\n <FieldLabel for=\"login-password\" class=\"text-xs @md:text-sm\">Password</FieldLabel>\n <a\n href=\"/blocks/forgot-password-01\"\n class=\"text-[11px] @md:text-xs text-primary font-medium hover:underline\"\n >\n Forgot password?\n </a>\n </div>\n <div class=\"relative\">\n <Input\n id=\"login-password\"\n type={showPassword() ? \"text\" : \"password\"}\n placeholder=\"••••••••\"\n value={form.values().password}\n onInput={form.handleChange(\"password\")}\n onBlur={form.handleBlur(\"password\")}\n autocomplete=\"current-password\"\n class=\"pr-10\"\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword())}\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors p-1 cursor-pointer\"\n aria-label={showPassword() ? \"Hide password\" : \"Show password\"}\n >\n <Show when={showPassword()} fallback={<Eye class=\"size-4\" />}>\n <EyeOff class=\"size-4\" />\n </Show>\n </button>\n </div>\n <FormMessage form={form} name=\"password\" />\n </Field>\n\n <div class=\"flex items-center space-x-2 pt-1\">\n <Checkbox\n id=\"remember\"\n checked={form.values().rememberMe}\n onChange={(checked) => form.setFieldValue(\"rememberMe\", checked)}\n />\n <Label\n for=\"remember\"\n class=\"text-xs font-normal text-muted-foreground cursor-pointer select-none\"\n >\n Remember me for 30 days\n </Label>\n </div>\n\n <Button\n type=\"submit\"\n class=\"w-full mt-2 font-medium\"\n disabled={form.isSubmitting() || !isFormValid()}\n >\n <Show when={form.isSubmitting()} fallback={<>Sign In <ArrowRight class=\"ml-2 size-4\" /></>}>\n Signing in...\n </Show>\n </Button>\n </Form>\n\n <p class=\"text-center text-xs text-muted-foreground mt-6\">\n Don't have an account?{\" \"}\n <a href=\"/blocks/register-01\" class=\"text-primary font-semibold hover:underline\">\n Sign up\n </a>\n </p>\n </div>\n\n {/* Footer info */}\n <div class=\"text-center @md:text-left text-xs text-muted-foreground pt-4 @md:pt-0\">\n By signing in, you agree to our{\" \"}\n <a href=\"#terms\" class=\"underline hover:text-foreground\">Terms of Service</a>{\" \"}\n and{\" \"}\n <a href=\"#privacy\" class=\"underline hover:text-foreground\">Privacy Policy</a>.\n </div>\n </div>\n\n {/* Right Column: Hero Visual & Art / Testimonial Showcase */}\n <div class=\"hidden @5xl:flex relative flex-col justify-between p-10 bg-muted/40 border-l border-border overflow-hidden\">\n <div class=\"absolute inset-0 bg-gradient-to-tr from-primary/10 via-transparent to-primary/5 pointer-events-none\" />\n\n <div class=\"relative z-10 flex items-center gap-2 text-sm font-semibold text-foreground/80\">\n <Sparkles class=\"size-4 text-primary\" />\n <span>Production Ready SolidJS UI</span>\n </div>\n\n <div class=\"relative z-10 max-w-md space-y-6\">\n <blockquote class=\"text-xl @md:text-2xl font-semibold tracking-tight text-foreground leading-snug\">\n \"Nikala UI transformed the way our team builds web applications with SolidJS. Fine-grained reactivity meets Tailwind v4 simplicity.\"\n </blockquote>\n <div class=\"space-y-1\">\n <p class=\"text-sm font-semibold text-foreground\">Davit Kakhidze</p>\n <p class=\"text-xs text-muted-foreground\">Head of Engineering at Studio</p>\n </div>\n </div>\n\n <div class=\"relative z-10 flex items-center justify-between text-xs text-muted-foreground\">\n <span>Honoring Niko Pirosmani</span>\n <span>© 2026 Nikala UI</span>\n </div>\n </div>\n </div>\n );\n};\n\nexport default Login01;\n",
25
+ "type": "registry:block"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "marker",
3
+ "title": "Marker",
4
+ "description": "System chat events, date dividers, and live typing indicator badges.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/marker.tsx",
13
+ "content": "import {\n splitProps,\n type Component,\n type JSX,\n type ParentComponent,\n Show,\n} from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- 1. Marker Root --- */\nexport interface MarkerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const Marker: ParentComponent<MarkerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n role=\"status\"\n class={cn(\"flex w-full items-center justify-center my-3 text-center select-none\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 2. MarkerContent --- */\nexport interface MarkerContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MarkerContent: ParentComponent<MarkerContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-lg border border-border/60 bg-muted/40 px-3 py-1 text-xs text-muted-foreground shadow-2xs backdrop-blur-xs\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 3. MarkerDate (Date Divider) --- */\nexport interface MarkerDateProps extends JSX.HTMLAttributes<HTMLDivElement> {\n date?: string;\n class?: string;\n}\n\nexport const MarkerDate: ParentComponent<MarkerDateProps> = (props) => {\n const [local, rest] = splitProps(props, [\"date\", \"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"relative flex w-full items-center justify-center my-4\", local.class)}\n {...rest}\n >\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-border/50\" />\n </div>\n <div class=\"relative flex items-center gap-1 bg-background px-3 py-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground rounded-md border border-border/40\">\n {local.children || local.date}\n </div>\n </div>\n );\n};\n\n/* --- 4. MarkerTyping (Live Typing Indicator) --- */\nexport interface MarkerTypingProps extends JSX.HTMLAttributes<HTMLDivElement> {\n name?: string;\n class?: string;\n}\n\nexport const MarkerTyping: Component<MarkerTypingProps> = (props) => {\n const [local, rest] = splitProps(props, [\"name\", \"class\"]);\n\n return (\n <div\n role=\"status\"\n aria-label={`${local.name || \"Someone\"} is typing`}\n class={cn(\"flex w-full items-center gap-2 text-xs text-muted-foreground my-2\", local.class)}\n {...rest}\n >\n <Show when={local.name}>\n <span class=\"font-medium text-foreground\">{local.name}</span> is typing\n </Show>\n\n {/* 3 Animated Bouncing Dots */}\n <span class=\"inline-flex items-center gap-1 px-2 py-1 rounded-md bg-muted/60 border border-border/40\">\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce [animation-delay:-0.3s]\" />\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce [animation-delay:-0.15s]\" />\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce\" />\n </span>\n </div>\n );\n};\n",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "marquee",
3
+ "title": "Marquee",
4
+ "description": "A smooth, GPU-accelerated infinite scrolling ticker component for logo clouds, testimonials, and live ribbons.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/marquee.tsx",
13
+ "content": "import {\n splitProps,\n createMemo,\n Index,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface MarqueeProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n reverse?: boolean;\n pauseOnHover?: boolean;\n vertical?: boolean;\n repeat?: number;\n duration?: number | string;\n gap?: string;\n fadeEdges?: boolean;\n}\n\nexport const Marquee: ParentComponent<MarqueeProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"class\",\n \"reverse\",\n \"pauseOnHover\",\n \"vertical\",\n \"repeat\",\n \"duration\",\n \"gap\",\n \"fadeEdges\",\n \"children\",\n ]);\n\n const repeatCount = () => local.repeat ?? 4;\n const durationValue = () =>\n typeof local.duration === \"number\"\n ? `${local.duration}s`\n : local.duration || \"40s\";\n const gapValue = () => local.gap || \"1rem\";\n\n const repeatArray = createMemo(() =>\n Array.from({ length: repeatCount() }, (_, i) => i)\n );\n\n return (\n <>\n <style>{`\n @keyframes nikala-marquee {\n from {\n transform: translateX(0);\n }\n to {\n transform: translateX(calc(-100% - var(--marquee-gap, 1rem)));\n }\n }\n @keyframes nikala-marquee-vertical {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(calc(-100% - var(--marquee-gap, 1rem)));\n }\n }\n .nikala-marquee-track {\n animation: nikala-marquee var(--marquee-duration, 40s) linear infinite;\n }\n .nikala-marquee-track-vertical {\n animation: nikala-marquee-vertical var(--marquee-duration, 40s) linear infinite;\n }\n [data-reverse=\"true\"] .nikala-marquee-track,\n [data-reverse=\"true\"] .nikala-marquee-track-vertical {\n animation-direction: reverse;\n }\n [data-pause=\"true\"]:hover .nikala-marquee-track,\n [data-pause=\"true\"]:hover .nikala-marquee-track-vertical {\n animation-play-state: paused;\n }\n `}</style>\n <div\n data-reverse={local.reverse ? \"true\" : \"false\"}\n data-pause={local.pauseOnHover ? \"true\" : \"false\"}\n style={{\n \"--marquee-duration\": durationValue(),\n \"--marquee-gap\": gapValue(),\n }}\n class={cn(\n \"group flex overflow-hidden p-2 select-none\",\n local.vertical ? \"flex-col h-full\" : \"flex-row w-full\",\n local.fadeEdges &&\n (local.vertical\n ? \"[mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)]\"\n : \"[mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]\"),\n local.class\n )}\n {...rest}\n >\n <Index each={repeatArray()}>\n {() => (\n <div\n class={cn(\n \"flex shrink-0 justify-around items-center\",\n local.vertical\n ? \"flex-col min-h-full nikala-marquee-track-vertical\"\n : \"flex-row min-w-full nikala-marquee-track\"\n )}\n style={{\n gap: gapValue(),\n [local.vertical ? \"margin-bottom\" : \"margin-right\"]: gapValue(),\n }}\n >\n {local.children}\n </div>\n )}\n </Index>\n </div>\n </>\n );\n};\n",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "message",
3
+ "title": "Message",
4
+ "description": "A structured chat and conversation message layout with avatars, alignment, headers, footers, and actions.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/message.tsx",
13
+ "content": "import {\n createContext,\n useContext,\n splitProps,\n type Component,\n type JSX,\n type ParentComponent,\n type Accessor,\n} from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- 1. Message Context --- */\nexport type MessageAlignment = \"start\" | \"end\";\n\ninterface MessageContextValue {\n align: Accessor<MessageAlignment>;\n}\n\nconst MessageContext = createContext<MessageContextValue>();\n\nexport function useMessage() {\n const context = useContext(MessageContext);\n if (!context) {\n throw new Error(\"useMessage must be used within a <Message />\");\n }\n return context;\n}\n\n/* --- 2. Message Root --- */\nexport interface MessageProps extends JSX.HTMLAttributes<HTMLDivElement> {\n align?: MessageAlignment;\n class?: string;\n}\n\nexport const Message: ParentComponent<MessageProps> = (props) => {\n const [local, rest] = splitProps(props, [\"align\", \"class\", \"children\"]);\n const align = () => local.align ?? \"start\";\n\n const contextValue: MessageContextValue = {\n align,\n };\n\n return (\n <MessageContext.Provider value={contextValue}>\n <div\n data-align={align()}\n class={cn(\n \"group/message flex w-full gap-3\",\n align() === \"end\" ? \"flex-row-reverse items-start\" : \"flex-row items-start\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </MessageContext.Provider>\n );\n};\n\n/* --- 3. MessageAvatar --- */\nexport interface MessageAvatarProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MessageAvatar: ParentComponent<MessageAvatarProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"shrink-0 select-none pt-0.5\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 4. MessageHeader --- */\nexport interface MessageHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MessageHeader: ParentComponent<MessageHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { align } = useMessage();\n\n return (\n <div\n class={cn(\n \"flex items-center gap-2 text-xs text-muted-foreground select-none mb-1\",\n align() === \"end\" ? \"justify-end\" : \"justify-start\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 5. MessageContent --- */\nexport interface MessageContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MessageContent: ParentComponent<MessageContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { align } = useMessage();\n\n return (\n <div\n class={cn(\n \"flex flex-col gap-1.5 max-w-[85%] sm:max-w-[75%]\",\n align() === \"end\" ? \"items-end text-right\" : \"items-start text-left\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 6. MessageFooter --- */\nexport interface MessageFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MessageFooter: ParentComponent<MessageFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { align } = useMessage();\n\n return (\n <div\n class={cn(\n \"flex items-center gap-1.5 text-[11px] text-muted-foreground select-none mt-1\",\n align() === \"end\" ? \"justify-end\" : \"justify-start\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 7. MessageActions --- */\nexport interface MessageActionsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MessageActions: ParentComponent<MessageActionsProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { align } = useMessage();\n\n return (\n <div\n class={cn(\n \"opacity-0 group-hover/message:opacity-100 focus-within:opacity-100 transition-opacity flex items-center gap-1 p-0.5 rounded-md border border-border bg-background shadow-xs\",\n align() === \"end\" ? \"flex-row-reverse\" : \"flex-row\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "navbar",
3
+ "title": "Navbar",
4
+ "description": "A responsive, accessible, and composable top navigation header suite supporting nested dropdown flyouts, floating card containers, and mobile navigation drawers.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority",
10
+ "lucide-solid"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "ui/navbar.tsx",
15
+ "content": "import {\n createContext,\n createSignal,\n useContext,\n splitProps,\n Show,\n type JSX,\n type ParentComponent,\n type Component,\n type Accessor,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Menu, X } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- Navbar Context --- */\ninterface NavbarContextValue {\n isOpen: Accessor<boolean>;\n setIsOpen: (open: boolean) => void;\n toggleMobileMenu: () => void;\n}\n\nconst NavbarContext = createContext<NavbarContextValue>();\n\nexport function useNavbar() {\n const context = useContext(NavbarContext);\n if (!context) {\n throw new Error(\"useNavbar must be used within a <Navbar /> component\");\n }\n return context;\n}\n\n/* --- Root Navbar --- */\nexport const navbarVariants = cva(\n \"w-full transition-all duration-200 z-40\",\n {\n variants: {\n variant: {\n default: \"border-b border-border/60 bg-background/80 backdrop-blur-md\",\n floating: \"my-3 rounded-lg border border-border/80 bg-background/90 backdrop-blur-md shadow-sm\",\n bordered: \"border-b border-border bg-background\",\n transparent: \"bg-transparent border-transparent\",\n },\n isSticky: {\n true: \"sticky top-0\",\n false: \"relative\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n isSticky: true,\n },\n }\n);\n\nexport interface NavbarProps\n extends JSX.HTMLAttributes<HTMLElement>,\n VariantProps<typeof navbarVariants> {\n maxWidth?: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"full\";\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport const Navbar: ParentComponent<NavbarProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"class\",\n \"variant\",\n \"isSticky\",\n \"maxWidth\",\n \"open\",\n \"onOpenChange\",\n \"children\",\n ]);\n\n const [internalOpen, setInternalOpen] = createSignal(false);\n const isOpen = () => (local.open !== undefined ? local.open : internalOpen());\n\n const setIsOpen = (open: boolean) => {\n if (local.open === undefined) {\n setInternalOpen(open);\n }\n local.onOpenChange?.(open);\n };\n\n const toggleMobileMenu = () => setIsOpen(!isOpen());\n\n const maxWidthClass = () => {\n switch (local.maxWidth) {\n case \"sm\":\n return \"max-w-screen-sm\";\n case \"md\":\n return \"max-w-screen-md\";\n case \"lg\":\n return \"max-w-screen-lg\";\n case \"xl\":\n return \"max-w-screen-xl\";\n case \"full\":\n return \"max-w-full\";\n case \"2xl\":\n default:\n return \"max-w-screen-2xl\";\n }\n };\n\n return (\n <NavbarContext.Provider value={{ isOpen, setIsOpen, toggleMobileMenu }}>\n <header\n class={cn(\n navbarVariants({\n variant: local.variant,\n isSticky: local.isSticky,\n }),\n local.variant === \"floating\" && cn(\"mx-auto\", maxWidthClass()),\n local.class\n )}\n {...rest}\n >\n <div class={cn(\"w-full\", local.variant !== \"floating\" && cn(\"mx-auto\", maxWidthClass()))}>\n {local.children}\n </div>\n </header>\n </NavbarContext.Provider>\n );\n};\n\n/* --- Navbar Container (Top Flex Row) --- */\nexport interface NavbarContainerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavbarContainer: ParentComponent<NavbarContainerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"flex h-14 md:h-16 items-center justify-between px-4 sm:px-6 lg:px-8 gap-3 w-full\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Navbar Brand / Logo Container --- */\nexport interface NavbarBrandProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n class?: string;\n}\n\nexport const NavbarBrand: ParentComponent<NavbarBrandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <a\n class={cn(\n \"flex items-center gap-2 font-bold text-foreground transition-opacity hover:opacity-90 cursor-pointer shrink-0\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </a>\n );\n};\n\n/* --- Navbar Content / Section --- */\nexport const navbarContentVariants = cva(\"flex items-center gap-1 sm:gap-2 h-full\", {\n variants: {\n justify: {\n start: \"justify-start flex-1\",\n center: \"justify-center flex-1\",\n end: \"justify-end flex-1\",\n },\n },\n defaultVariants: {\n justify: \"center\",\n },\n});\n\nexport interface NavbarContentProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof navbarContentVariants> {\n class?: string;\n hideOnMobile?: boolean;\n}\n\nexport const NavbarContent: ParentComponent<NavbarContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"justify\", \"hideOnMobile\", \"children\"]);\n\n return (\n <div\n class={cn(\n navbarContentVariants({ justify: local.justify }),\n local.hideOnMobile !== false && \"hidden md:flex\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Navbar Item --- */\nexport interface NavbarItemProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n isActive?: boolean;\n}\n\nexport const NavbarItem: ParentComponent<NavbarItemProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"isActive\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"relative flex items-center h-full text-sm font-medium\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <Show when={local.isActive}>\n <div class=\"absolute -bottom-px left-0 right-0 h-0.5 bg-primary rounded-lg z-10\" />\n </Show>\n </div>\n );\n};\n\n/* --- Navbar Link --- */\nexport const navbarLinkVariants = cva(\n \"inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium transition-colors cursor-pointer select-none focus-visible:outline-hidden\",\n {\n variants: {\n variant: {\n default:\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n active:\n \"text-foreground font-semibold bg-muted/80\",\n ghost:\n \"text-foreground hover:text-primary\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nexport interface NavbarLinkProps\n extends JSX.AnchorHTMLAttributes<HTMLAnchorElement>,\n VariantProps<typeof navbarLinkVariants> {\n class?: string;\n isActive?: boolean;\n}\n\nexport const NavbarLink: ParentComponent<NavbarLinkProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"variant\", \"isActive\", \"children\"]);\n\n return (\n <a\n class={cn(\n navbarLinkVariants({\n variant: local.isActive ? \"active\" : local.variant,\n }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </a>\n );\n};\n\n/* --- Navbar Actions Container --- */\nexport interface NavbarActionsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavbarActions: ParentComponent<NavbarActionsProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div class={cn(\"flex items-center gap-2 shrink-0\", local.class)} {...rest}>\n {local.children}\n </div>\n );\n};\n\n/* --- Navbar Mobile Toggle Button --- */\nexport interface NavbarMobileToggleProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {\n class?: string;\n}\n\nexport const NavbarMobileToggle: Component<NavbarMobileToggleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n const { isOpen, toggleMobileMenu } = useNavbar();\n\n return (\n <button\n type=\"button\"\n onClick={toggleMobileMenu}\n aria-label=\"Toggle navigation menu\"\n aria-expanded={isOpen()}\n class={cn(\n \"inline-flex md:hidden size-8 items-center justify-center rounded-md border border-border/60 bg-muted/30 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors cursor-pointer focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring shrink-0\",\n local.class\n )}\n {...rest}\n >\n <Show when={isOpen()} fallback={<Menu class=\"size-4\" />}>\n <X class=\"size-4\" />\n </Show>\n </button>\n );\n};\n\n/* --- Navbar Mobile Menu Container --- */\nexport interface NavbarMobileMenuProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavbarMobileMenu: ParentComponent<NavbarMobileMenuProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { isOpen } = useNavbar();\n\n return (\n <Show when={isOpen()}>\n <div\n class={cn(\n \"md:hidden w-full border-t border-border/60 bg-background/95 backdrop-blur-lg px-4 py-3 space-y-1 shadow-md transition-all\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </Show>\n );\n};\n\n/* --- Navbar Mobile Item & Link --- */\nexport interface NavbarMobileLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n class?: string;\n isActive?: boolean;\n}\n\nexport const NavbarMobileLink: ParentComponent<NavbarMobileLinkProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"isActive\", \"children\"]);\n const { setIsOpen } = useNavbar();\n\n return (\n <a\n onClick={() => setIsOpen(false)}\n class={cn(\n \"flex w-full items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors cursor-pointer\",\n local.isActive\n ? \"bg-primary text-primary-foreground font-semibold shadow-2xs\"\n : \"text-muted-foreground hover:bg-muted/80 hover:text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </a>\n );\n};\n",
16
+ "type": "registry:ui"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "navigation-menu",
3
+ "title": "Navigation Menu",
4
+ "description": "A responsive and accessible top header navigation menu with mega-menu dropdowns and link previews.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority",
10
+ "lucide-solid"
11
+ ],
12
+ "registryDependencies": [
13
+ "create-click-outside"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "ui/navigation-menu.tsx",
18
+ "content": "import {\n createContext,\n createSignal,\n useContext,\n splitProps,\n onCleanup,\n type Component,\n type JSX,\n type ParentComponent,\n type Accessor,\n Show,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\nimport { ChevronDown } from \"lucide-solid\";\n\n/* --- Context Definition --- */\ninterface NavigationMenuContextValue {\n activeValue: Accessor<string | null>;\n setActiveValue: (value: string | null) => void;\n onItemMouseEnter: (value: string) => void;\n onItemMouseLeave: () => void;\n delayMs: Accessor<number>;\n}\n\nconst NavigationMenuContext = createContext<NavigationMenuContextValue>();\n\nexport function useNavigationMenu() {\n const context = useContext(NavigationMenuContext);\n if (!context) {\n throw new Error(\"useNavigationMenu must be used within a <NavigationMenu />\");\n }\n return context;\n}\n\n/* --- 1. NavigationMenu Root --- */\nexport interface NavigationMenuProps extends JSX.HTMLAttributes<HTMLElement> {\n class?: string;\n delayMs?: number;\n value?: string | null;\n onValueChange?: (value: string | null) => void;\n}\n\nexport const NavigationMenu: ParentComponent<NavigationMenuProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"delayMs\", \"value\", \"onValueChange\", \"children\"]);\n\n const [internalValue, setInternalValue] = createSignal<string | null>(local.value ?? null);\n const activeValue = () => (local.value !== undefined ? local.value : internalValue());\n\n const setActiveValue = (val: string | null) => {\n if (local.value === undefined) {\n setInternalValue(val);\n }\n local.onValueChange?.(val);\n };\n\n let rootRef!: HTMLElement;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const delay = () => local.delayMs ?? 150;\n\n const onItemMouseEnter = (val: string) => {\n clearTimeout(timer);\n const ms = delay();\n if (ms <= 0) {\n setActiveValue(val);\n } else {\n timer = setTimeout(() => {\n setActiveValue(val);\n }, ms);\n }\n };\n\n const onItemMouseLeave = () => {\n clearTimeout(timer);\n const ms = delay();\n timer = setTimeout(() => {\n setActiveValue(null);\n }, Math.max(50, ms));\n };\n\n createClickOutside({\n target: () => rootRef,\n onInteractOutside: () => {\n setActiveValue(null);\n },\n });\n\n onCleanup(() => {\n clearTimeout(timer);\n });\n\n const contextValue: NavigationMenuContextValue = {\n activeValue,\n setActiveValue,\n onItemMouseEnter,\n onItemMouseLeave,\n delayMs: delay,\n };\n\n return (\n <NavigationMenuContext.Provider value={contextValue}>\n <nav\n ref={rootRef}\n aria-label=\"Main Navigation\"\n class={cn(\"relative z-10 flex max-w-max flex-1 items-center justify-center\", local.class)}\n {...rest}\n >\n {local.children}\n </nav>\n </NavigationMenuContext.Provider>\n );\n};\n\n/* --- 2. NavigationMenuList --- */\nexport interface NavigationMenuListProps extends JSX.HTMLAttributes<HTMLUListElement> {\n class?: string;\n}\n\nexport const NavigationMenuList: ParentComponent<NavigationMenuListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <ul\n class={cn(\n \"group flex flex-1 list-none items-center justify-center space-x-1 p-1\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </ul>\n );\n};\n\n/* --- 3. NavigationMenuItem Context & Component --- */\ninterface NavigationMenuItemContextValue {\n value: string;\n isOpen: Accessor<boolean>;\n}\n\nconst NavigationMenuItemContext = createContext<NavigationMenuItemContextValue>();\n\nfunction useNavigationMenuItem() {\n const context = useContext(NavigationMenuItemContext);\n if (!context) {\n throw new Error(\"useNavigationMenuItem must be used within a <NavigationMenuItem />\");\n }\n return context;\n}\n\nexport interface NavigationMenuItemProps extends JSX.HTMLAttributes<HTMLLIElement> {\n value?: string;\n class?: string;\n}\n\nlet itemCounter = 0;\n\nexport const NavigationMenuItem: ParentComponent<NavigationMenuItemProps> = (props) => {\n const itemId = props.value ?? `nav-item-${++itemCounter}`;\n const [local, rest] = splitProps(props, [\"class\", \"value\", \"children\"]);\n const rootContext = useNavigationMenu();\n\n const isOpen = () => rootContext.activeValue() === itemId;\n\n const itemContext: NavigationMenuItemContextValue = {\n value: itemId,\n isOpen,\n };\n\n return (\n <NavigationMenuItemContext.Provider value={itemContext}>\n <li\n class={cn(\"relative\", local.class)}\n onMouseEnter={() => rootContext.onItemMouseEnter(itemId)}\n onMouseLeave={() => rootContext.onItemMouseLeave()}\n {...rest}\n >\n {local.children}\n </li>\n </NavigationMenuItemContext.Provider>\n );\n};\n\n/* --- 4. Trigger Style Helper --- */\nexport const navigationMenuTriggerVariants = cva(\n \"group inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 cursor-pointer select-none gap-1\",\n {\n variants: {\n variant: {\n default:\n \"bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground\",\n filled:\n \"bg-muted/60 text-foreground hover:bg-muted border border-border/40 data-[state=open]:bg-muted\",\n ghost:\n \"bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent/60\",\n outline:\n \"border border-border bg-background hover:bg-accent hover:text-accent-foreground shadow-2xs data-[state=open]:bg-accent\",\n },\n size: {\n default: \"h-9 px-3 text-sm\",\n sm: \"h-8 px-2.5 text-xs\",\n lg: \"h-10 px-4 text-base\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\nexport function navigationMenuTriggerStyle(\n variants?: VariantProps<typeof navigationMenuTriggerVariants>\n) {\n return navigationMenuTriggerVariants(variants);\n}\n\n/* --- 5. NavigationMenuTrigger --- */\nexport interface NavigationMenuTriggerProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof navigationMenuTriggerVariants> {\n class?: string;\n hideChevron?: boolean;\n}\n\nexport const NavigationMenuTrigger: ParentComponent<NavigationMenuTriggerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"variant\", \"size\", \"hideChevron\", \"children\"]);\n const rootContext = useNavigationMenu();\n const itemContext = useNavigationMenuItem();\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault();\n if (itemContext.isOpen()) {\n rootContext.setActiveValue(null);\n } else {\n rootContext.setActiveValue(itemContext.value);\n }\n };\n\n return (\n <button\n type=\"button\"\n aria-expanded={itemContext.isOpen()}\n data-state={itemContext.isOpen() ? \"open\" : \"closed\"}\n onClick={handleClick}\n class={cn(\n navigationMenuTriggerVariants({ variant: local.variant, size: local.size }),\n \"gap-1.5\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <Show when={!local.hideChevron}>\n <ChevronDown\n class={cn(\n \"size-3.5 text-muted-foreground transition-transform duration-200\",\n itemContext.isOpen() && \"rotate-180 text-foreground\"\n )}\n aria-hidden=\"true\"\n />\n </Show>\n </button>\n );\n};\n\n/* --- 6. NavigationMenuContent (Mega Menu Flyout) --- */\nexport interface NavigationMenuContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavigationMenuContent: ParentComponent<NavigationMenuContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const itemContext = useNavigationMenuItem();\n\n return (\n <Show when={itemContext.isOpen()}>\n <div\n data-state={itemContext.isOpen() ? \"open\" : \"closed\"}\n class={cn(\n \"absolute left-0 top-full mt-1.5 w-auto rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg outline-hidden z-50\",\n \"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 duration-200\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </Show>\n );\n};\n\n/* --- 7. NavigationMenuLink --- */\nexport interface NavigationMenuLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n class?: string;\n active?: boolean;\n}\n\nexport const NavigationMenuLink: ParentComponent<NavigationMenuLinkProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"active\", \"children\"]);\n\n return (\n <a\n data-active={local.active ? \"\" : undefined}\n class={cn(\n \"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n local.active && \"bg-accent/60 text-accent-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </a>\n );\n};\n\n/* --- 8. NavigationMenuViewport --- */\nexport interface NavigationMenuViewportProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavigationMenuViewport: Component<NavigationMenuViewportProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div class=\"absolute left-0 top-full flex justify-center\">\n <div\n class={cn(\n \"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-lg transition-all\",\n local.class\n )}\n {...rest}\n />\n </div>\n );\n};\n\n/* --- 9. NavigationMenuIndicator --- */\nexport interface NavigationMenuIndicatorProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const NavigationMenuIndicator: Component<NavigationMenuIndicatorProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden transition-[width,transform] duration-200\",\n local.class\n )}\n {...rest}\n >\n <div class=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-xs bg-border shadow-md\" />\n </div>\n );\n};\n",
19
+ "type": "registry:ui"
20
+ }
21
+ ]
22
+ }