11"use client" ;
22
3- import { useState } from "react" ;
4- import { Button } from "@/components/ui/button" ;
5- import { OddIcon , oddIconComponent } from "@/components/ui/odd-icon" ;
6- import { cn } from "@/lib/utils" ;
3+ import { OddIcon } from "@/components/ui/odd-icon" ;
74
8- const GmailIcon = oddIconComponent ( "gmail" ) ;
9-
10- const EMAIL = "jasper.mceligott@gmail.com" ;
11-
12- const EMAIL_TEMPLATE = `hi jasper,
13-
14- i'd love to see an icon for:
15- [describe what you want]
16-
17- thanks!` ;
18-
19- function CopyBadgeIcon ( { size = 12 } : { size ?: number } ) {
20- return (
21- < svg
22- xmlns = "http://www.w3.org/2000/svg"
23- width = { size }
24- height = { size }
25- viewBox = "0 0 24 24"
26- fill = "none"
27- stroke = "currentColor"
28- strokeWidth = { 1.75 }
29- strokeLinecap = "round"
30- strokeLinejoin = "round"
31- aria-hidden
32- >
33- < rect x = "8" y = "8" width = "13" height = "13" rx = "2.5" />
34- < path d = "M5 15H4.5A2.5 2.5 0 0 1 2 12.5v-8A2.5 2.5 0 0 1 4.5 2h8A2.5 2.5 0 0 1 15 4.5V5" />
35- </ svg >
36- ) ;
37- }
38-
39- interface ContactRow {
40- id : string ;
41- label : string ;
42- iconName : string ;
43- href ?: string ;
44- copyValue ?: string ;
45- }
46-
47- const CONTACTS : ContactRow [ ] = [
48- { id : "x" , label : "@jasperdevs" , iconName : "x-twitter" , href : "https://x.com/jasperdevs" } ,
49- { id : "github" , label : "jasperdevs" , iconName : "github" , href : "https://github.com/jasperdevs" } ,
50- { id : "discord" , label : "jasperdevs" , iconName : "discord" , copyValue : "jasperdevs" } ,
51- ] ;
5+ const ISSUES_URL = "https://github.com/jasperdevs/oddicons/issues/new" ;
526
537interface RequestFormProps {
548 heading ?: string ;
@@ -57,125 +11,46 @@ interface RequestFormProps {
5711}
5812
5913export function RequestForm ( {
60- heading = "send an email " ,
61- subheading = "request an icon or just say hi " ,
14+ heading = "send an issue " ,
15+ subheading = "tell us what icon you want on GitHub " ,
6216 hideHeader = false ,
6317} : RequestFormProps ) {
64- const [ subject , setSubject ] = useState ( "icon request" ) ;
65- const [ message , setMessage ] = useState ( EMAIL_TEMPLATE ) ;
66- const [ copiedId , setCopiedId ] = useState < string | null > ( null ) ;
67-
68- const canSubmit = message . trim ( ) . length > 0 ;
69-
70- const handleSubmit = ( e : React . FormEvent ) => {
71- e . preventDefault ( ) ;
72- if ( ! canSubmit ) return ;
73- const subj = subject . trim ( ) || "icon request" ;
74- const href = `mailto:${ EMAIL } ?subject=${ encodeURIComponent ( subj ) } &body=${ encodeURIComponent ( message . trim ( ) ) } ` ;
75- window . open ( href , "_blank" , "noopener,noreferrer" ) ;
76- } ;
77-
78- const handleCopy = async ( id : string , value : string ) => {
79- try {
80- await navigator . clipboard . writeText ( value ) ;
81- setCopiedId ( id ) ;
82- window . setTimeout ( ( ) => setCopiedId ( ( c ) => ( c === id ? null : c ) ) , 1600 ) ;
83- } catch {
84- // ignore
85- }
86- } ;
87-
8818 return (
89- < >
90- < form onSubmit = { handleSubmit } className = "flex flex-col gap-3" >
91- { ! hideHeader && (
92- < div className = "flex flex-col gap-0.5" >
93- < h2 className = "text-[15px] font-semibold tracking-tight text-foreground" >
94- { heading }
95- </ h2 >
96- < p className = "text-[12px] text-muted-foreground" >
97- { subheading }
98- </ p >
99- </ div >
100- ) }
101-
102- < input
103- type = "text"
104- value = { subject }
105- onChange = { ( e ) => setSubject ( e . target . value ) }
106- placeholder = "subject (optional)"
107- className = "h-11 w-full rounded-lg border border-border bg-sidebar px-3 text-[13px] text-foreground placeholder:text-muted-foreground/70 outline-none transition-colors duration-[180ms] focus:border-foreground/30"
108- />
109-
110- < textarea
111- value = { message }
112- onChange = { ( e ) => setMessage ( e . target . value ) }
113- rows = { 6 }
114- required
115- className = "scrollbar-custom w-full resize-none rounded-lg border border-border bg-sidebar px-3 py-2.5 text-[13px] leading-[1.55] text-foreground placeholder:text-muted-foreground/70 outline-none transition-colors duration-[180ms] focus:border-foreground/30"
116- />
117-
118- < Button
119- type = "submit"
120- variant = "primary"
121- size = "lg"
122- leadingIcon = { GmailIcon }
123- disabled = { ! canSubmit }
124- className = "h-11 w-full px-5 text-[14px]"
125- >
126- send an email
127- </ Button >
128- </ form >
129-
130- < div className = "mt-4 grid grid-cols-3 gap-1.5 border-t border-border pt-4" >
131- { CONTACTS . map ( ( c ) => {
132- const copied = copiedId === c . id ;
133- const cls = cn (
134- "group/chip relative flex h-11 items-center justify-center gap-2 rounded-lg border border-border bg-sidebar text-[12px] font-medium text-muted-foreground transition-all duration-[180ms]" ,
135- "hover:-translate-y-0.5 hover:border-foreground/30 hover:bg-accent hover:text-foreground"
136- ) ;
137- const content = (
138- < >
139- < span className = "inline-flex shrink-0" >
140- { copied ? < OddIcon name = "check" size = { 18 } /> : < OddIcon name = { c . iconName } size = { 20 } /> }
141- </ span >
142- < span className = "truncate" >
143- { copied ? "copied" : c . label }
144- </ span >
145- </ >
146- ) ;
147- if ( c . href ) {
148- return (
149- < a
150- key = { c . id }
151- href = { c . href }
152- target = "_blank"
153- rel = "noreferrer"
154- className = { cls }
155- title = { c . id }
156- >
157- { content }
158- </ a >
159- ) ;
160- }
161- return (
162- < button
163- key = { c . id }
164- type = "button"
165- onClick = { ( ) => c . copyValue && handleCopy ( c . id , c . copyValue ) }
166- className = { cls }
167- title = { c . copyValue }
168- >
169- { content }
170- { ! copied && (
171- < span className = "absolute right-1.5 top-1.5 opacity-40 transition-opacity group-hover/chip:opacity-70" >
172- < CopyBadgeIcon size = { 12 } />
173- </ span >
174- ) }
175- </ button >
176- ) ;
177- } ) }
19+ < div className = "flex flex-col gap-4" >
20+ { ! hideHeader && (
21+ < div className = "flex flex-col gap-0.5" >
22+ < h2 className = "text-[15px] font-semibold tracking-tight text-foreground" >
23+ { heading }
24+ </ h2 >
25+ < p className = "text-[12px] text-muted-foreground" >
26+ { subheading }
27+ </ p >
28+ </ div >
29+ ) }
30+
31+ < div className = "flex items-start gap-3 rounded-xl border border-border bg-sidebar p-3.5" >
32+ < span className = "grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-card" >
33+ < OddIcon name = "github" size = { 28 } />
34+ </ span >
35+ < div className = "flex min-w-0 flex-1 flex-col gap-1" >
36+ < p className = "text-[13.5px] font-medium leading-[1.45] text-foreground" >
37+ Send an issue with the icon you want.
38+ </ p >
39+ < p className = "text-[12px] leading-[1.55] text-muted-foreground" >
40+ Requests are tracked in GitHub issues so they do not get lost.
41+ </ p >
42+ </ div >
17843 </ div >
179- </ >
44+
45+ < a
46+ href = { ISSUES_URL }
47+ target = "_blank"
48+ rel = "noreferrer"
49+ className = "inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-xl bg-foreground px-5 text-[14px] font-medium text-background outline-none transition-all duration-80 hover:bg-foreground/90 focus-visible:ring-1 focus-visible:ring-[#6B97FF] active:bg-foreground/80"
50+ >
51+ < OddIcon name = "github" size = { 20 } />
52+ send GitHub issue
53+ </ a >
54+ </ div >
18055 ) ;
18156}
0 commit comments