@shipsite.dev/components 0.2.53 → 0.2.60

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 (75) hide show
  1. package/components.json +102 -0
  2. package/dist/context/ShipSiteProvider.d.ts +13 -0
  3. package/dist/context/ShipSiteProvider.d.ts.map +1 -1
  4. package/dist/context/ShipSiteProvider.js.map +1 -1
  5. package/dist/index.d.ts +5 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +6 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/layout/Header.d.ts.map +1 -1
  10. package/dist/layout/Header.js +22 -2
  11. package/dist/layout/Header.js.map +1 -1
  12. package/dist/lib/use-form-submit.d.ts +7 -0
  13. package/dist/lib/use-form-submit.d.ts.map +1 -0
  14. package/dist/lib/use-form-submit.js +47 -0
  15. package/dist/lib/use-form-submit.js.map +1 -0
  16. package/dist/marketing/ContactForm.d.ts +16 -0
  17. package/dist/marketing/ContactForm.d.ts.map +1 -0
  18. package/dist/marketing/ContactForm.js +29 -0
  19. package/dist/marketing/ContactForm.js.map +1 -0
  20. package/dist/marketing/Form.d.ts +25 -0
  21. package/dist/marketing/Form.d.ts.map +1 -0
  22. package/dist/marketing/Form.js +18 -0
  23. package/dist/marketing/Form.js.map +1 -0
  24. package/dist/marketing/FormClient.d.ts +23 -0
  25. package/dist/marketing/FormClient.d.ts.map +1 -0
  26. package/dist/marketing/FormClient.js +41 -0
  27. package/dist/marketing/FormClient.js.map +1 -0
  28. package/dist/marketing/FormEmbed.d.ts +13 -0
  29. package/dist/marketing/FormEmbed.d.ts.map +1 -0
  30. package/dist/marketing/FormEmbed.js +27 -0
  31. package/dist/marketing/FormEmbed.js.map +1 -0
  32. package/dist/marketing/NewsletterForm.d.ts +13 -0
  33. package/dist/marketing/NewsletterForm.d.ts.map +1 -0
  34. package/dist/marketing/NewsletterForm.js +23 -0
  35. package/dist/marketing/NewsletterForm.js.map +1 -0
  36. package/dist/marketing/WaitlistForm.d.ts +16 -0
  37. package/dist/marketing/WaitlistForm.d.ts.map +1 -0
  38. package/dist/marketing/WaitlistForm.js +27 -0
  39. package/dist/marketing/WaitlistForm.js.map +1 -0
  40. package/dist/ui/input.d.ts +5 -0
  41. package/dist/ui/input.d.ts.map +1 -0
  42. package/dist/ui/input.js +7 -0
  43. package/dist/ui/input.js.map +1 -0
  44. package/dist/ui/label.d.ts +5 -0
  45. package/dist/ui/label.d.ts.map +1 -0
  46. package/dist/ui/label.js +9 -0
  47. package/dist/ui/label.js.map +1 -0
  48. package/dist/ui/navigation-menu.d.ts +15 -0
  49. package/dist/ui/navigation-menu.d.ts.map +1 -0
  50. package/dist/ui/navigation-menu.js +32 -0
  51. package/dist/ui/navigation-menu.js.map +1 -0
  52. package/dist/ui/select.d.ts +14 -0
  53. package/dist/ui/select.d.ts.map +1 -0
  54. package/dist/ui/select.js +28 -0
  55. package/dist/ui/select.js.map +1 -0
  56. package/dist/ui/textarea.d.ts +5 -0
  57. package/dist/ui/textarea.d.ts.map +1 -0
  58. package/dist/ui/textarea.js +7 -0
  59. package/dist/ui/textarea.js.map +1 -0
  60. package/package.json +4 -1
  61. package/src/context/ShipSiteProvider.tsx +13 -1
  62. package/src/index.ts +7 -0
  63. package/src/layout/Header.tsx +155 -19
  64. package/src/lib/use-form-submit.ts +53 -0
  65. package/src/marketing/ContactForm.tsx +157 -0
  66. package/src/marketing/Form.tsx +41 -0
  67. package/src/marketing/FormClient.tsx +194 -0
  68. package/src/marketing/FormEmbed.tsx +118 -0
  69. package/src/marketing/NewsletterForm.tsx +102 -0
  70. package/src/marketing/WaitlistForm.tsx +145 -0
  71. package/src/ui/input.tsx +21 -0
  72. package/src/ui/label.tsx +24 -0
  73. package/src/ui/navigation-menu.tsx +168 -0
  74. package/src/ui/select.tsx +160 -0
  75. package/src/ui/textarea.tsx +20 -0
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { XIcon } from "lucide-react";
6
+ import { Section } from "../ui/section";
7
+ import { Button } from "../ui/button";
8
+
9
+ function resolveUrl(src: string, provider: "tally" | "typeform" | "custom"): string {
10
+ switch (provider) {
11
+ case "tally":
12
+ return `https://tally.so/embed/${src}?transparentBackground=1`;
13
+ case "typeform":
14
+ return `https://form.typeform.com/to/${src}`;
15
+ case "custom":
16
+ return src;
17
+ }
18
+ }
19
+
20
+ interface FormEmbedProps {
21
+ id?: string;
22
+ src: string;
23
+ provider?: "tally" | "typeform" | "custom";
24
+ title?: string;
25
+ description?: string;
26
+ height?: number;
27
+ mode?: "iframe" | "popup";
28
+ buttonLabel?: string;
29
+ }
30
+
31
+ export function FormEmbed({
32
+ id,
33
+ src,
34
+ provider = "custom",
35
+ title,
36
+ description,
37
+ height = 500,
38
+ mode = "iframe",
39
+ buttonLabel = "Open Form",
40
+ }: FormEmbedProps) {
41
+ const resolvedUrl = resolveUrl(src, provider);
42
+
43
+ if (mode === "popup") {
44
+ return <FormEmbedPopup url={resolvedUrl} buttonLabel={buttonLabel} height={height} />;
45
+ }
46
+
47
+ return (
48
+ <Section id={id}>
49
+ <div className="container-main max-w-3xl">
50
+ {(title || description) && (
51
+ <div className="text-center mb-12">
52
+ {title && (
53
+ <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">
54
+ {title}
55
+ </h2>
56
+ )}
57
+ {description && (
58
+ <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
59
+ {description}
60
+ </p>
61
+ )}
62
+ </div>
63
+ )}
64
+ <div className="glass-1 rounded-2xl overflow-hidden">
65
+ <iframe
66
+ src={resolvedUrl}
67
+ height={height}
68
+ className="w-full border-0"
69
+ loading="lazy"
70
+ title={title || "Embedded form"}
71
+ />
72
+ </div>
73
+ </div>
74
+ </Section>
75
+ );
76
+ }
77
+
78
+ function FormEmbedPopup({
79
+ url,
80
+ buttonLabel,
81
+ height,
82
+ }: {
83
+ url: string;
84
+ buttonLabel: string;
85
+ height: number;
86
+ }) {
87
+ return (
88
+ <DialogPrimitive.Root>
89
+ <DialogPrimitive.Trigger asChild>
90
+ <Button variant="default" size="lg">
91
+ {buttonLabel}
92
+ </Button>
93
+ </DialogPrimitive.Trigger>
94
+ <DialogPrimitive.Portal>
95
+ <DialogPrimitive.Overlay className="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80" />
96
+ <DialogPrimitive.Content className="bg-background border-border dark:border-border/15 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-2xl -translate-x-1/2 -translate-y-1/2 rounded-2xl border p-0 shadow-lg overflow-hidden">
97
+ <DialogPrimitive.Title className="sr-only">
98
+ {buttonLabel}
99
+ </DialogPrimitive.Title>
100
+ <DialogPrimitive.Description className="sr-only">
101
+ Embedded form
102
+ </DialogPrimitive.Description>
103
+ <iframe
104
+ src={url}
105
+ height={height}
106
+ className="w-full border-0"
107
+ loading="lazy"
108
+ title={buttonLabel}
109
+ />
110
+ <DialogPrimitive.Close className="ring-offset-background focus:ring-ring absolute top-4 right-4 z-[100] rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden">
111
+ <XIcon className="size-5" />
112
+ <span className="sr-only">Close</span>
113
+ </DialogPrimitive.Close>
114
+ </DialogPrimitive.Content>
115
+ </DialogPrimitive.Portal>
116
+ </DialogPrimitive.Root>
117
+ );
118
+ }
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ import React, { FormEvent, useState } from "react";
4
+ import { CheckCircle, Loader2 } from "lucide-react";
5
+ import { Section } from "../ui/section";
6
+ import { Input } from "../ui/input";
7
+ import { Button } from "../ui/button";
8
+ import { useFormSubmit } from "../lib/use-form-submit";
9
+
10
+ interface NewsletterFormProps {
11
+ id?: string;
12
+ action: string;
13
+ title?: string;
14
+ description?: string;
15
+ placeholder?: string;
16
+ submitLabel?: string;
17
+ successMessage?: string;
18
+ variant?: "section" | "inline";
19
+ }
20
+
21
+ export function NewsletterForm({
22
+ id,
23
+ action,
24
+ title,
25
+ description,
26
+ placeholder = "Enter your email",
27
+ submitLabel = "Subscribe",
28
+ successMessage = "You're subscribed!",
29
+ variant = "section",
30
+ }: NewsletterFormProps) {
31
+ const { status, errorMsg, submit } = useFormSubmit(action);
32
+ const [email, setEmail] = useState("");
33
+
34
+ function handleSubmit(e: FormEvent) {
35
+ e.preventDefault();
36
+ submit({ email });
37
+ }
38
+
39
+ const successContent = (
40
+ <div className="flex items-center justify-center gap-2 text-sm text-primary">
41
+ <CheckCircle className="size-4" />
42
+ <span>{successMessage}</span>
43
+ </div>
44
+ );
45
+
46
+ const formContent =
47
+ status === "success" ? (
48
+ successContent
49
+ ) : (
50
+ <>
51
+ <form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-2 max-w-md mx-auto">
52
+ <Input
53
+ type="email"
54
+ placeholder={placeholder}
55
+ value={email}
56
+ onChange={(e) => setEmail(e.target.value)}
57
+ required
58
+ className="flex-1"
59
+ disabled={status === "loading"}
60
+ />
61
+ <Button type="submit" variant="default" disabled={status === "loading"}>
62
+ {status === "loading" ? (
63
+ <Loader2 className="size-4 animate-spin" />
64
+ ) : (
65
+ submitLabel
66
+ )}
67
+ </Button>
68
+ </form>
69
+ {status === "error" && (
70
+ <p className="text-sm text-destructive text-center mt-2" aria-live="polite">
71
+ {errorMsg}
72
+ </p>
73
+ )}
74
+ </>
75
+ );
76
+
77
+ if (variant === "inline") {
78
+ return formContent;
79
+ }
80
+
81
+ return (
82
+ <Section id={id}>
83
+ <div className="container-main max-w-2xl">
84
+ {(title || description) && (
85
+ <div className="text-center mb-12">
86
+ {title && (
87
+ <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">
88
+ {title}
89
+ </h2>
90
+ )}
91
+ {description && (
92
+ <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
93
+ {description}
94
+ </p>
95
+ )}
96
+ </div>
97
+ )}
98
+ {formContent}
99
+ </div>
100
+ </Section>
101
+ );
102
+ }
@@ -0,0 +1,145 @@
1
+ "use client";
2
+
3
+ import React, { FormEvent, useId, useState } from "react";
4
+ import { CheckCircle, Loader2 } from "lucide-react";
5
+ import { Section } from "../ui/section";
6
+ import { Input } from "../ui/input";
7
+ import { Label } from "../ui/label";
8
+ import { Button } from "../ui/button";
9
+ import { Badge } from "../ui/badge";
10
+ import { useFormSubmit } from "../lib/use-form-submit";
11
+
12
+ interface WaitlistFormProps {
13
+ id?: string;
14
+ action: string;
15
+ title?: string;
16
+ description?: string;
17
+ badge?: string;
18
+ showName?: boolean;
19
+ nameLabel?: string;
20
+ emailLabel?: string;
21
+ submitLabel?: string;
22
+ successTitle?: string;
23
+ successMessage?: string;
24
+ }
25
+
26
+ export function WaitlistForm({
27
+ id,
28
+ action,
29
+ title = "Join the Waitlist",
30
+ description,
31
+ badge,
32
+ showName = false,
33
+ nameLabel = "Name",
34
+ emailLabel = "Email",
35
+ submitLabel = "Join Waitlist",
36
+ successTitle = "You're on the list!",
37
+ successMessage = "We'll notify you when it's your turn.",
38
+ }: WaitlistFormProps) {
39
+ const uid = useId();
40
+ const { status, errorMsg, submit } = useFormSubmit(action);
41
+ const [fields, setFields] = useState({ name: "", email: "" });
42
+
43
+ function update(key: string, value: string) {
44
+ setFields((prev) => ({ ...prev, [key]: value }));
45
+ }
46
+
47
+ function handleSubmit(e: FormEvent) {
48
+ e.preventDefault();
49
+ const data: Record<string, string> = { email: fields.email };
50
+ if (showName) data.name = fields.name;
51
+ submit(data);
52
+ }
53
+
54
+ return (
55
+ <Section id={id}>
56
+ <div className="container-main max-w-2xl text-center">
57
+ {badge && (
58
+ <div className="mb-4">
59
+ <Badge variant="outline">{badge}</Badge>
60
+ </div>
61
+ )}
62
+ {(title || description) && (
63
+ <div className="mb-12">
64
+ {title && (
65
+ <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">
66
+ {title}
67
+ </h2>
68
+ )}
69
+ {description && (
70
+ <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
71
+ {description}
72
+ </p>
73
+ )}
74
+ </div>
75
+ )}
76
+
77
+ {status === "success" ? (
78
+ <div className="text-center py-12">
79
+ <div className="w-16 h-16 rounded-full bg-primary/10 mx-auto mb-4 flex items-center justify-center">
80
+ <CheckCircle className="w-8 h-8 text-primary" />
81
+ </div>
82
+ <h3 className="text-xl font-semibold text-foreground mb-2">
83
+ {successTitle}
84
+ </h3>
85
+ <p className="text-muted-foreground">{successMessage}</p>
86
+ </div>
87
+ ) : (
88
+ <form
89
+ onSubmit={handleSubmit}
90
+ className="glass-1 rounded-2xl p-8 max-w-md mx-auto space-y-4"
91
+ >
92
+ {showName && (
93
+ <div className="space-y-2 text-left">
94
+ <Label htmlFor={`${uid}-name`}>{nameLabel}</Label>
95
+ <Input
96
+ id={`${uid}-name`}
97
+ name="name"
98
+ placeholder="Your name"
99
+ value={fields.name}
100
+ onChange={(e) => update("name", e.target.value)}
101
+ required
102
+ disabled={status === "loading"}
103
+ />
104
+ </div>
105
+ )}
106
+ <div className="space-y-2 text-left">
107
+ <Label htmlFor={`${uid}-email`}>{emailLabel}</Label>
108
+ <Input
109
+ id={`${uid}-email`}
110
+ name="email"
111
+ type="email"
112
+ placeholder="you@example.com"
113
+ value={fields.email}
114
+ onChange={(e) => update("email", e.target.value)}
115
+ required
116
+ disabled={status === "loading"}
117
+ />
118
+ </div>
119
+ <Button
120
+ type="submit"
121
+ variant="default"
122
+ size="lg"
123
+ className="w-full"
124
+ disabled={status === "loading"}
125
+ >
126
+ {status === "loading" ? (
127
+ <>
128
+ <Loader2 className="size-4 animate-spin mr-2" />
129
+ Joining...
130
+ </>
131
+ ) : (
132
+ submitLabel
133
+ )}
134
+ </Button>
135
+ {status === "error" && (
136
+ <p className="text-sm text-destructive" aria-live="polite">
137
+ {errorMsg}
138
+ </p>
139
+ )}
140
+ </form>
141
+ )}
142
+ </div>
143
+ </Section>
144
+ );
145
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../lib/utils";
4
+
5
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
6
+
7
+ function Input({ className, type, ...props }: InputProps) {
8
+ return (
9
+ <input
10
+ type={type}
11
+ data-slot="input"
12
+ className={cn(
13
+ "border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import * as LabelPrimitive from "@radix-ui/react-label";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../lib/utils";
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ export { Label };
@@ -0,0 +1,168 @@
1
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
2
+ import { cva } from "class-variance-authority";
3
+ import { ChevronDownIcon } from "lucide-react";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../lib/utils";
7
+
8
+ function NavigationMenu({
9
+ className,
10
+ children,
11
+ viewport = true,
12
+ ...props
13
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
14
+ viewport?: boolean;
15
+ }) {
16
+ return (
17
+ <NavigationMenuPrimitive.Root
18
+ data-slot="navigation-menu"
19
+ data-viewport={viewport}
20
+ className={cn(
21
+ "group/navigation-menu relative z-10 flex max-w-max flex-1 items-center justify-center",
22
+ className,
23
+ )}
24
+ {...props}
25
+ >
26
+ {children}
27
+ {viewport && <NavigationMenuViewport />}
28
+ </NavigationMenuPrimitive.Root>
29
+ );
30
+ }
31
+
32
+ function NavigationMenuList({
33
+ className,
34
+ ...props
35
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
36
+ return (
37
+ <NavigationMenuPrimitive.List
38
+ data-slot="navigation-menu-list"
39
+ className={cn(
40
+ "group flex flex-1 list-none items-center justify-center gap-1",
41
+ className,
42
+ )}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ function NavigationMenuItem({
49
+ className,
50
+ ...props
51
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
52
+ return (
53
+ <NavigationMenuPrimitive.Item
54
+ data-slot="navigation-menu-item"
55
+ className={cn("relative", className)}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ const navigationMenuTriggerStyle = cva(
62
+ "group inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium hover:bg-foreground/5 hover:text-accent-foreground focus:bg-foreground/10 focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-foreground/10 data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-foreground/10 data-[state=open]:bg-foreground/5 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
63
+ );
64
+
65
+ function NavigationMenuTrigger({
66
+ className,
67
+ children,
68
+ ...props
69
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
70
+ return (
71
+ <NavigationMenuPrimitive.Trigger
72
+ data-slot="navigation-menu-trigger"
73
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
74
+ {...props}
75
+ >
76
+ {children}{" "}
77
+ <ChevronDownIcon
78
+ className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
79
+ aria-hidden="true"
80
+ />
81
+ </NavigationMenuPrimitive.Trigger>
82
+ );
83
+ }
84
+
85
+ function NavigationMenuContent({
86
+ className,
87
+ ...props
88
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
89
+ return (
90
+ <NavigationMenuPrimitive.Content
91
+ data-slot="navigation-menu-content"
92
+ className={cn(
93
+ "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
94
+ "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
95
+ className,
96
+ )}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ function NavigationMenuLink({
103
+ className,
104
+ ...props
105
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
106
+ return (
107
+ <NavigationMenuPrimitive.Link
108
+ data-slot="navigation-menu-link"
109
+ className={cn(
110
+ "data-[active=true]:focus:bg-foreground/10 data-[active=true]:hover:bg-foreground/10 data-[active=true]:bg-foreground/10 data-[active=true]:text-accent-foreground hover:bg-foreground/10 hover:text-accent-foreground focus:bg-foreground/10 focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
111
+ className,
112
+ )}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ function NavigationMenuViewport({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
122
+ return (
123
+ <div
124
+ className={cn(
125
+ "absolute top-full left-0 isolate z-50 flex justify-center",
126
+ )}
127
+ >
128
+ <NavigationMenuPrimitive.Viewport
129
+ data-slot="navigation-menu-viewport"
130
+ className={cn(
131
+ "origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 border-border dark:border-border/15 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-sm md:w-[var(--radix-navigation-menu-viewport-width)]",
132
+ className,
133
+ )}
134
+ {...props}
135
+ />
136
+ </div>
137
+ );
138
+ }
139
+
140
+ function NavigationMenuIndicator({
141
+ className,
142
+ ...props
143
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
144
+ return (
145
+ <NavigationMenuPrimitive.Indicator
146
+ data-slot="navigation-menu-indicator"
147
+ className={cn(
148
+ "data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
149
+ className,
150
+ )}
151
+ {...props}
152
+ >
153
+ <div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
154
+ </NavigationMenuPrimitive.Indicator>
155
+ );
156
+ }
157
+
158
+ export {
159
+ NavigationMenu,
160
+ NavigationMenuContent,
161
+ NavigationMenuIndicator,
162
+ NavigationMenuItem,
163
+ NavigationMenuLink,
164
+ NavigationMenuList,
165
+ NavigationMenuTrigger,
166
+ navigationMenuTriggerStyle,
167
+ NavigationMenuViewport,
168
+ };