@shipsite.dev/components 0.2.55 → 0.2.64

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 (62) hide show
  1. package/components.json +102 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/use-form-submit.d.ts +7 -0
  7. package/dist/lib/use-form-submit.d.ts.map +1 -0
  8. package/dist/lib/use-form-submit.js +47 -0
  9. package/dist/lib/use-form-submit.js.map +1 -0
  10. package/dist/marketing/ContactForm.d.ts +16 -0
  11. package/dist/marketing/ContactForm.d.ts.map +1 -0
  12. package/dist/marketing/ContactForm.js +29 -0
  13. package/dist/marketing/ContactForm.js.map +1 -0
  14. package/dist/marketing/Form.d.ts +25 -0
  15. package/dist/marketing/Form.d.ts.map +1 -0
  16. package/dist/marketing/Form.js +18 -0
  17. package/dist/marketing/Form.js.map +1 -0
  18. package/dist/marketing/FormClient.d.ts +23 -0
  19. package/dist/marketing/FormClient.d.ts.map +1 -0
  20. package/dist/marketing/FormClient.js +41 -0
  21. package/dist/marketing/FormClient.js.map +1 -0
  22. package/dist/marketing/FormEmbed.d.ts +13 -0
  23. package/dist/marketing/FormEmbed.d.ts.map +1 -0
  24. package/dist/marketing/FormEmbed.js +27 -0
  25. package/dist/marketing/FormEmbed.js.map +1 -0
  26. package/dist/marketing/NewsletterForm.d.ts +13 -0
  27. package/dist/marketing/NewsletterForm.d.ts.map +1 -0
  28. package/dist/marketing/NewsletterForm.js +23 -0
  29. package/dist/marketing/NewsletterForm.js.map +1 -0
  30. package/dist/marketing/WaitlistForm.d.ts +16 -0
  31. package/dist/marketing/WaitlistForm.d.ts.map +1 -0
  32. package/dist/marketing/WaitlistForm.js +27 -0
  33. package/dist/marketing/WaitlistForm.js.map +1 -0
  34. package/dist/ui/input.d.ts +5 -0
  35. package/dist/ui/input.d.ts.map +1 -0
  36. package/dist/ui/input.js +7 -0
  37. package/dist/ui/input.js.map +1 -0
  38. package/dist/ui/label.d.ts +5 -0
  39. package/dist/ui/label.d.ts.map +1 -0
  40. package/dist/ui/label.js +9 -0
  41. package/dist/ui/label.js.map +1 -0
  42. package/dist/ui/select.d.ts +14 -0
  43. package/dist/ui/select.d.ts.map +1 -0
  44. package/dist/ui/select.js +28 -0
  45. package/dist/ui/select.js.map +1 -0
  46. package/dist/ui/textarea.d.ts +5 -0
  47. package/dist/ui/textarea.d.ts.map +1 -0
  48. package/dist/ui/textarea.js +7 -0
  49. package/dist/ui/textarea.js.map +1 -0
  50. package/package.json +3 -1
  51. package/src/index.ts +7 -0
  52. package/src/lib/use-form-submit.ts +53 -0
  53. package/src/marketing/ContactForm.tsx +157 -0
  54. package/src/marketing/Form.tsx +41 -0
  55. package/src/marketing/FormClient.tsx +194 -0
  56. package/src/marketing/FormEmbed.tsx +118 -0
  57. package/src/marketing/NewsletterForm.tsx +102 -0
  58. package/src/marketing/WaitlistForm.tsx +145 -0
  59. package/src/ui/input.tsx +21 -0
  60. package/src/ui/label.tsx +24 -0
  61. package/src/ui/select.tsx +160 -0
  62. package/src/ui/textarea.tsx +20 -0
@@ -0,0 +1,194 @@
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 { Textarea } from "../ui/textarea";
8
+ import { Label } from "../ui/label";
9
+ import { Button } from "../ui/button";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../ui/select";
17
+ import { cn } from "../lib/utils";
18
+ import { useFormSubmit } from "../lib/use-form-submit";
19
+
20
+ const gridColsMap: Record<number, string> = {
21
+ 2: "md:grid-cols-2",
22
+ 3: "md:grid-cols-3",
23
+ 4: "md:grid-cols-2 lg:grid-cols-4",
24
+ };
25
+
26
+ const colSpanMap: Record<number, string> = {
27
+ 2: "md:col-span-2",
28
+ 3: "md:col-span-3",
29
+ 4: "md:col-span-4",
30
+ };
31
+
32
+ export interface FormFieldDef {
33
+ name: string;
34
+ label: string;
35
+ type?: "text" | "email" | "tel" | "url" | "textarea" | "select";
36
+ placeholder?: string;
37
+ required?: boolean;
38
+ options?: string[];
39
+ colSpan?: number;
40
+ }
41
+
42
+ interface FormClientProps {
43
+ id?: string;
44
+ action: string;
45
+ title?: string;
46
+ description?: string;
47
+ columns?: number;
48
+ submitLabel?: string;
49
+ successTitle?: string;
50
+ successMessage?: string;
51
+ fields: FormFieldDef[];
52
+ }
53
+
54
+ export function FormClient({
55
+ id,
56
+ action,
57
+ title,
58
+ description,
59
+ columns = 1,
60
+ submitLabel = "Submit",
61
+ successTitle = "Submitted!",
62
+ successMessage = "Thank you. We'll be in touch soon.",
63
+ fields,
64
+ }: FormClientProps) {
65
+ const uid = useId();
66
+ const { status, errorMsg, submit } = useFormSubmit(action);
67
+ const [values, setValues] = useState<Record<string, string>>(() => {
68
+ const init: Record<string, string> = {};
69
+ for (const f of fields) init[f.name] = "";
70
+ return init;
71
+ });
72
+
73
+ function update(name: string, value: string) {
74
+ setValues((prev) => ({ ...prev, [name]: value }));
75
+ }
76
+
77
+ function handleSubmit(e: FormEvent) {
78
+ e.preventDefault();
79
+ submit(values);
80
+ }
81
+
82
+ return (
83
+ <Section id={id}>
84
+ <div className="container-main max-w-3xl">
85
+ {(title || description) && (
86
+ <div className="text-center mb-12">
87
+ {title && (
88
+ <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">
89
+ {title}
90
+ </h2>
91
+ )}
92
+ {description && (
93
+ <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
94
+ {description}
95
+ </p>
96
+ )}
97
+ </div>
98
+ )}
99
+
100
+ {status === "success" ? (
101
+ <div className="text-center py-12">
102
+ <div className="w-16 h-16 rounded-full bg-primary/10 mx-auto mb-4 flex items-center justify-center">
103
+ <CheckCircle className="w-8 h-8 text-primary" />
104
+ </div>
105
+ <h3 className="text-xl font-semibold text-foreground mb-2">
106
+ {successTitle}
107
+ </h3>
108
+ <p className="text-muted-foreground">{successMessage}</p>
109
+ </div>
110
+ ) : (
111
+ <form onSubmit={handleSubmit} className="glass-1 rounded-2xl p-8">
112
+ <div
113
+ className={cn("grid grid-cols-1 gap-6", gridColsMap[columns])}
114
+ >
115
+ {fields.map((field) => (
116
+ <div
117
+ key={field.name}
118
+ className={cn("space-y-2", field.colSpan && colSpanMap[field.colSpan])}
119
+ >
120
+ <Label htmlFor={`${uid}-${field.name}`}>{field.label}</Label>
121
+ {field.type === "textarea" ? (
122
+ <Textarea
123
+ id={`${uid}-${field.name}`}
124
+ name={field.name}
125
+ placeholder={field.placeholder}
126
+ required={field.required}
127
+ value={values[field.name] ?? ""}
128
+ onChange={(e) => update(field.name, e.target.value)}
129
+ disabled={status === "loading"}
130
+ />
131
+ ) : field.type === "select" && field.options ? (
132
+ <Select
133
+ value={values[field.name] ?? ""}
134
+ onValueChange={(v) => update(field.name, v)}
135
+ required={field.required}
136
+ disabled={status === "loading"}
137
+ >
138
+ <SelectTrigger id={`${uid}-${field.name}`}>
139
+ <SelectValue
140
+ placeholder={field.placeholder || "Select..."}
141
+ />
142
+ </SelectTrigger>
143
+ <SelectContent>
144
+ {field.options.map((opt) => (
145
+ <SelectItem key={opt} value={opt}>
146
+ {opt}
147
+ </SelectItem>
148
+ ))}
149
+ </SelectContent>
150
+ </Select>
151
+ ) : (
152
+ <Input
153
+ id={`${uid}-${field.name}`}
154
+ name={field.name}
155
+ type={field.type || "text"}
156
+ placeholder={field.placeholder}
157
+ required={field.required}
158
+ value={values[field.name] ?? ""}
159
+ onChange={(e) => update(field.name, e.target.value)}
160
+ disabled={status === "loading"}
161
+ />
162
+ )}
163
+ </div>
164
+ ))}
165
+ </div>
166
+ <div className="mt-8">
167
+ <Button
168
+ type="submit"
169
+ variant="default"
170
+ size="lg"
171
+ className="w-full"
172
+ disabled={status === "loading"}
173
+ >
174
+ {status === "loading" ? (
175
+ <>
176
+ <Loader2 className="size-4 animate-spin mr-2" />
177
+ Submitting...
178
+ </>
179
+ ) : (
180
+ submitLabel
181
+ )}
182
+ </Button>
183
+ {status === "error" && (
184
+ <p className="text-sm text-destructive mt-2" aria-live="polite">
185
+ {errorMsg}
186
+ </p>
187
+ )}
188
+ </div>
189
+ </form>
190
+ )}
191
+ </div>
192
+ </Section>
193
+ );
194
+ }
@@ -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-3 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-6"
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 };