Skip to content

Commit 2e8ff3c

Browse files
committed
improvement(landing): extract shared LandingField component
1 parent 0b7d689 commit 2e8ff3c

3 files changed

Lines changed: 38 additions & 68 deletions

File tree

apps/sim/app/(landing)/components/contact/contact-form.tsx

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { cloneElement, isValidElement, useState } from 'react'
3+
import { useState } from 'react'
44
import { useMutation } from '@tanstack/react-query'
55
import { Combobox, Input, Textarea } from '@/components/emcn'
66
import { Check } from '@/components/emcn/icons'
@@ -11,6 +11,7 @@ import {
1111
type ContactRequestPayload,
1212
contactRequestSchema,
1313
} from '@/app/(landing)/components/contact/consts'
14+
import { LandingField } from '@/app/(landing)/components/forms/landing-field'
1415

1516
type ContactField = keyof ContactRequestPayload
1617
type ContactErrors = Partial<Record<ContactField, string>>
@@ -38,39 +39,6 @@ const COMBOBOX_TOPICS = [...CONTACT_TOPIC_OPTIONS]
3839
const LANDING_INPUT =
3940
'h-[36px] rounded-[5px] border border-[var(--border-1)] bg-[var(--surface-5)] px-3 font-[430] font-season text-[14px] text-[var(--text-primary)] outline-none transition-colors placeholder:text-[var(--text-muted)]'
4041

41-
interface LandingFieldProps {
42-
label: string
43-
htmlFor: string
44-
optional?: boolean
45-
error?: string
46-
children: React.ReactNode
47-
}
48-
49-
function LandingField({ label, htmlFor, optional, error, children }: LandingFieldProps) {
50-
const errorId = error ? `${htmlFor}-error` : undefined
51-
const describedChild =
52-
errorId && isValidElement<{ 'aria-describedby'?: string; 'aria-invalid'?: boolean }>(children)
53-
? cloneElement(children, { 'aria-describedby': errorId, 'aria-invalid': true })
54-
: children
55-
return (
56-
<div className='flex flex-col gap-1.5'>
57-
<label
58-
htmlFor={htmlFor}
59-
className='font-[430] font-season text-[13px] text-[var(--text-secondary)] tracking-[0.02em]'
60-
>
61-
{label}
62-
{optional ? <span className='ml-1 text-[var(--text-muted)]'>(optional)</span> : null}
63-
</label>
64-
{describedChild}
65-
{error ? (
66-
<p id={errorId} role='alert' className='text-[12px] text-[var(--text-error)]'>
67-
{error}
68-
</p>
69-
) : null}
70-
</div>
71-
)
72-
}
73-
7442
async function submitContactRequest(payload: ContactRequestPayload) {
7543
const response = await fetch('/api/contact', {
7644
method: 'POST',

apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { cloneElement, isValidElement, useState } from 'react'
3+
import { useState } from 'react'
44
import { useMutation } from '@tanstack/react-query'
55
import {
66
Combobox,
@@ -20,6 +20,7 @@ import {
2020
type DemoRequestPayload,
2121
demoRequestSchema,
2222
} from '@/app/(landing)/components/demo-request/consts'
23+
import { LandingField } from '@/app/(landing)/components/forms/landing-field'
2324

2425
interface DemoRequestModalProps {
2526
children: React.ReactNode
@@ -50,39 +51,6 @@ const INITIAL_FORM_STATE: DemoRequestFormState = {
5051
details: '',
5152
}
5253

53-
interface LandingFieldProps {
54-
label: string
55-
htmlFor: string
56-
optional?: boolean
57-
error?: string
58-
children: React.ReactNode
59-
}
60-
61-
function LandingField({ label, htmlFor, optional, error, children }: LandingFieldProps) {
62-
const errorId = error ? `${htmlFor}-error` : undefined
63-
const describedChild =
64-
errorId && isValidElement<{ 'aria-describedby'?: string; 'aria-invalid'?: boolean }>(children)
65-
? cloneElement(children, { 'aria-describedby': errorId, 'aria-invalid': true })
66-
: children
67-
return (
68-
<div className='flex flex-col gap-1.5'>
69-
<label
70-
htmlFor={htmlFor}
71-
className='font-[430] font-season text-[13px] text-[var(--text-secondary)] tracking-[0.02em]'
72-
>
73-
{label}
74-
{optional ? <span className='ml-1 text-[var(--text-muted)]'>(optional)</span> : null}
75-
</label>
76-
{describedChild}
77-
{error ? (
78-
<p id={errorId} role='alert' className='text-[12px] text-[var(--text-error)]'>
79-
{error}
80-
</p>
81-
) : null}
82-
</div>
83-
)
84-
}
85-
8654
const LANDING_INPUT =
8755
'h-[32px] rounded-[5px] border border-[var(--border-1)] bg-[var(--surface-5)] px-2.5 font-[430] font-season text-[13.5px] text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none'
8856

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { cloneElement, isValidElement } from 'react'
2+
3+
interface LandingFieldProps {
4+
label: string
5+
htmlFor: string
6+
optional?: boolean
7+
error?: string
8+
children: React.ReactNode
9+
}
10+
11+
export function LandingField({ label, htmlFor, optional, error, children }: LandingFieldProps) {
12+
const errorId = error ? `${htmlFor}-error` : undefined
13+
const describedChild =
14+
errorId && isValidElement<{ 'aria-describedby'?: string; 'aria-invalid'?: boolean }>(children)
15+
? cloneElement(children, { 'aria-describedby': errorId, 'aria-invalid': true })
16+
: children
17+
return (
18+
<div className='flex flex-col gap-1.5'>
19+
<label
20+
htmlFor={htmlFor}
21+
className='font-[430] font-season text-[13px] text-[var(--text-secondary)] tracking-[0.02em]'
22+
>
23+
{label}
24+
{optional ? <span className='ml-1 text-[var(--text-muted)]'>(optional)</span> : null}
25+
</label>
26+
{describedChild}
27+
{error ? (
28+
<p id={errorId} role='alert' className='text-[12px] text-[var(--text-error)]'>
29+
{error}
30+
</p>
31+
) : null}
32+
</div>
33+
)
34+
}

0 commit comments

Comments
 (0)