Splits text field into a field and plain input

This commit is contained in:
Chris Anderson 2023-03-26 16:21:31 -05:00
parent c93ae4996a
commit 8dc440742d
29 changed files with 231 additions and 326 deletions

View file

@ -32,7 +32,6 @@ export default class BasicAuthProvider extends AuthProvider {
async validate(ctx: Context) {
console.log('basic auth!', ctx.request.body)
const { email, password } = ctx.request.body
if (!email || !password) throw new RequestError(AuthError.InvalidCredentials)

View file

@ -4,7 +4,7 @@ import { useDebounceControl, useResolver } from '../hooks'
import { SearchParams, SearchResult } from '../types'
import { TagPicker } from '../views/settings/TagPicker'
import { DataTable, DataTableProps } from './DataTable'
import TextField from './form/TextField'
import TextInput from './form/TextInput'
import Heading from './Heading'
import { SearchIcon } from './icons'
import Pagination from './Pagination'
@ -128,12 +128,13 @@ export function SearchTable<T extends Record<string, any>>({
if (enableSearch) {
filters.push(
<TextField
<TextInput
key="search"
name="search"
value={search}
placeholder={searchPlaceholder}
onChange={setSearch}
hideLabel={true}
icon={<SearchIcon />}
/>,
)

View file

@ -1,12 +1,9 @@
.ui-stack {
--spacing: 20px;
--spacing: 10px;
display: flex;
flex-direction: row;
margin: calc(var(--spacing) / -2);
}
.ui-stack > * {
padding: calc(var(--spacing) / 2);
gap: var(--spacing);
padding-bottom: var(--spacing);
}
.ui-stack.ui-stack-vertical {

View file

@ -1,75 +0,0 @@
.ui-date-picker {
background: var(--color-background);
border-radius: var(--border-radius);
box-shadow: 0px 10px 20px rgba(16, 24, 40, 0.1);
z-index: 10;
max-width: 320px;
padding: 20px;
}
.ui-date-picker .calendar-grid {
display: grid;
grid-template-columns: repeat(7,1fr);
list-style: none;
margin: 0;
padding: 0;
}
.ui-date-picker .calendar-pagination {
display: flex;
}
.ui-date-picker .calendar-pagination p {
font-weight: 600;
}
.ui-date-picker .calendar-pagination button {
background: var(--color-background);
font-size: 16px;
cursor: pointer;
text-decoration: none;
color: var(--color-primary);
margin: 0;
border: 0;
}
.ui-date-picker .calendar-header {
padding-bottom: 5px;
}
.ui-date-picker .calendar-header li {
font-weight: 500;
color: var(--color-primary-soft);
padding: 2px;
text-align: center;
}
.ui-date-picker .calendar li {
position: relative;
padding: 2px;
}
.ui-date-picker .calendar button {
background: var(--color-background);
border: 0px;
text-align: center;
font-size: 15px;
border-radius: 50%;
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
cursor: pointer;
}
.ui-date-picker .calendar button:hover {
background: var(--color-background-soft);
}
.ui-date-picker .calendar button.today {
background: var(--color-grey);
}
.ui-date-picker .calendar button.selected {
background: var(--color-primary);
color: var(--color-on-primary);
}

View file

@ -1,72 +0,0 @@
import React, { useState } from 'react'
import { useDatePicker } from '@rehookify/datepicker'
import { FieldPath, FieldValues } from 'react-hook-form'
import './DateField.css'
import clsx from 'clsx'
import TextField, { TextFieldProps } from './TextField'
import { usePopper } from 'react-popper'
import { formatISO } from 'date-fns'
interface DateFieldProps<X extends FieldValues, P extends FieldPath<X>> extends TextFieldProps<X, P> { }
export default function DateField<X extends FieldValues, P extends FieldPath<X>>({ form, ...params }: DateFieldProps<X, P>) {
const [showCalendar, setShowCalendar] = useState(false)
const [referenceElement, setReferenceElement] = useState<Element | null | undefined>()
const [popperElement, setPopperElement] = useState<HTMLElement | null | undefined>()
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'bottom-start',
})
const [selectedDates, onDatesChange] = useState<Date[]>([])
const {
data: { weekDays, calendars },
propGetters: {
dayButton,
previousMonthButton,
nextMonthButton,
},
} = useDatePicker({
selectedDates,
onDatesChange: (dates: Date[]) => {
form?.setValue(params.name, formatISO(dates[0]) as any)
onDatesChange(dates)
},
})
const { year, month, days } = calendars[0]
return (
<div className="ui-date-field">
<TextField
form={form} {...params}
inputRef={setReferenceElement}
onFocus={() => setShowCalendar(true)} />
{showCalendar && <section
className="ui-date-picker"
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}>
<header>
<div className="calendar-pagination">
<button {...previousMonthButton()}>&lt;</button>
<p>{month} {year}</p>
<button {...nextMonthButton()}>&gt;</button>
</div>
<ul className="calendar-header calendar-grid">
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul className="calendar calendar-grid">
{days.map((dpDay) => (
<li key={dpDay.date}>
<button {...dayButton(dpDay)} className={clsx({ today: dpDay.isToday, selected: dpDay.selected })}>{dpDay.day}</button>
</li>
))}
</ul>
</section>}
</div>
)
}

View file

@ -26,6 +26,7 @@ export default function FormWrapper<T extends FieldValues>({
})
const handleSubmit = form.handleSubmit(async data => {
console.log('submitting', data)
setIsLoading(true)
onSubmit(data, navigate).finally(() => {
setIsLoading(false)

View file

@ -1,5 +1,5 @@
import { snakeToTitle } from '../../utils'
import TextField from './TextField'
import TextInput from './TextInput'
import './SchemaFields.css'
import SwitchField from './SwitchField'
@ -30,7 +30,7 @@ export default function SchemaFields({ title, parent, form, schema }: SchemaProp
const item = props[key]
const required = schema.required?.includes(key)
if (item.type === 'string' || item.type === 'number') {
return <TextField
return <TextInput.Field
key={key}
form={form}
name={`${parent}.${key}`}

View file

@ -1,104 +0,0 @@
import clsx from 'clsx'
import { useId, Ref, ReactNode } from 'react'
import { FieldPath, FieldValues } from 'react-hook-form'
import { snakeToTitle } from '../../utils'
import { FieldProps } from './Field'
import './TextField.css'
export interface TextFieldProps<X extends FieldValues, P extends FieldPath<X>> extends FieldProps<X, P> {
type?: 'text' | 'time' | 'date' | 'datetime-local' | 'number' | 'password'
textarea?: boolean
size?: 'small' | 'regular'
value?: string | number | readonly string[] | undefined
placeholder?: string
onChange?: (value: string) => void
onBlur?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
onFocus?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
inputRef?: Ref<HTMLLabelElement>
min?: number
max?: number
icon?: ReactNode
}
export default function TextField<X extends FieldValues, P extends FieldPath<X>>({
type = 'text',
disabled,
form,
label,
subtitle,
min,
max,
name,
required,
textarea,
size = 'regular',
value,
onChange,
onBlur: onInputBlur,
onFocus,
placeholder,
inputRef,
icon,
}: TextFieldProps<X, P>) {
const id = useId()
const formParams = form?.register(name, { disabled, required })
const onBlur = formParams?.onBlur
return (
<label ref={inputRef} className={clsx('ui-text-field', { 'hide-label': !form && !label })}>
{
(!!form || !!label) && (
<span>
{label ?? snakeToTitle(name)}
{required && <span style={{ color: 'red' }}>&nbsp;*</span>}
</span>
)
}
{subtitle && <span className="label-subtitle">{subtitle}</span>}
<div className={clsx(icon && 'ui-text-field-icon-wrapper')}>
{
textarea
? (
<textarea
value={value}
{...formParams}
onChange={(event) => onChange?.(event?.target.value)}
onBlur={async (event) => {
await onBlur?.(event)
onInputBlur?.(event)
}}
onFocus={onFocus}
id={id}
/>
)
: (
<input
type={type}
value={value}
className={size}
placeholder={placeholder}
{...formParams}
onChange={(event) => onChange?.(event?.target.value)}
onBlur={async (event) => {
await onBlur?.(event)
onInputBlur?.(event)
}}
onFocus={onFocus}
id={id}
min={min}
max={max}
disabled={disabled}
/>
)
}
{
icon && (
<span className="ui-text-field-icon">
{icon}
</span>
)
}
</div>
</label>
)
}

View file

@ -1,13 +1,13 @@
.ui-text-field-icon-wrapper {
.ui-text-input-icon-wrapper {
position: relative;
}
.ui-text-field-icon-wrapper input,
.ui-text-field-icon-wrapper textarea {
.ui-text-input-icon-wrapper input,
.ui-text-input-icon-wrapper textarea {
padding-left: 36px;
}
.ui-text-field-icon {
.ui-text-input-icon {
position: absolute;
top: 50%;
left: 12px;

View file

@ -0,0 +1,153 @@
import clsx from 'clsx'
import { Ref, ReactNode } from 'react'
import { FieldPath, FieldValues, useController } from 'react-hook-form'
import { ControlledInputProps, FieldProps } from '../../types'
import { snakeToTitle } from '../../utils'
import './TextInput.css'
type TextInputValue = string | number | readonly string[] | undefined
export interface BaseTextInputProps<T extends TextInputValue> extends Partial<ControlledInputProps<T>> {
type?: 'text' | 'time' | 'date' | 'datetime-local' | 'number' | 'password'
textarea?: boolean
size?: 'small' | 'regular'
value?: T
name: string
placeholder?: string
onChange?: (value: T) => void
onBlur?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
onFocus?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
labelRef?: Ref<HTMLLabelElement>
inputRef?: Ref<HTMLInputElement | HTMLTextAreaElement>
hideLabel?: boolean
min?: number
max?: number
icon?: ReactNode
}
export type TextInputProps<T extends TextInputValue> = BaseTextInputProps<T> & (
| {
textarea: true
inputRef?: Ref<HTMLTextAreaElement>
} | {
textarea: false
inputRef?: Ref<HTMLInputElement>
} | {
textarea?: undefined
inputRef?: Ref<HTMLInputElement>
}
)
export default function TextInput<X extends TextInputValue>({
type = 'text',
disabled,
label,
subtitle,
min,
max,
name,
required,
textarea,
size = 'regular',
value,
onChange,
onBlur,
onFocus,
placeholder,
labelRef,
inputRef,
hideLabel = false,
icon,
}: TextInputProps<X>) {
return (
<label ref={labelRef} className={clsx('ui-text-input', { 'hide-label': hideLabel })}>
{
!hideLabel && (
<span>
{label ?? snakeToTitle(name)}
{required && <span style={{ color: 'red' }}>&nbsp;*</span>}
</span>
)
}
{subtitle && <span className="label-subtitle">{subtitle}</span>}
<div className={clsx(icon && 'ui-text-input-icon-wrapper')}>
{
textarea
? (
<textarea
value={value}
onChange={(event) => onChange?.(event?.target.value as X)}
onBlur={onBlur}
onFocus={onFocus}
ref={inputRef}
disabled={disabled}
/>
)
: (
<input
type={type}
value={value}
className={size}
placeholder={placeholder}
onChange={(event) => {
const inputValue = typeof value === 'number'
? event?.target.valueAsNumber
: event?.target.value
onChange?.(inputValue as X)
}}
onBlur={onBlur}
onFocus={onFocus}
ref={inputRef}
min={min}
max={max}
disabled={disabled}
/>
)
}
{
icon && (
<span className="ui-text-input-icon">
{icon}
</span>
)
}
</div>
</label>
)
}
TextInput.Field = function TextInputField<X extends FieldValues, P extends FieldPath<X>>({
form,
name,
required,
value,
onChange,
onBlur,
...rest
}: TextInputProps<P> & FieldProps<X, P>) {
const { field: { ref, ...field }, fieldState } = useController({
control: form.control,
name,
rules: {
required,
},
})
return (
<TextInput
{...rest}
{...field}
inputRef={ref}
onBlur={async (event) => {
await field.onBlur?.()
onBlur?.(event)
}}
onChange={async (event) => {
await field.onChange?.(event)
onChange?.(event)
}}
required={required}
error={fieldState.error?.message}
/>
)
}

View file

@ -4,7 +4,7 @@ import api from '../../api'
import { ReactComponent as Logo } from '../../assets/logo.svg'
import Alert from '../../ui/Alert'
import FormWrapper from '../../ui/form/FormWrapper'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import './Auth.css'
interface LoginBasicParams {
@ -39,8 +39,8 @@ export default function Login() {
<FormWrapper<LoginBasicParams>
onSubmit={handleLogin}>
{form => <>
<TextField form={form} name="email" />
<TextField form={form} name="password" type="password" />
<TextInput.Field form={form} name="email" />
<TextInput.Field form={form} name="password" type="password" />
</>}
</FormWrapper>
</div>

View file

@ -3,7 +3,7 @@ import api from '../../api'
import { ProjectContext } from '../../contexts'
import { Campaign, CampaignCreateParams, List, Project, Provider, SearchParams, Subscription } from '../../types'
import { useController, UseFormReturn, useWatch } from 'react-hook-form'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Heading from '../../ui/Heading'
import ListTable from '../users/ListTable'
@ -222,7 +222,7 @@ export function CampaignForm({ campaign, disableListSelection, onSave }: Campaig
>
{form => (
<>
<TextField form={form}
<TextInput.Field form={form}
name="name"
label="Campaign Name"
required

View file

@ -11,7 +11,7 @@ import LocaleSelector from './LocaleSelector'
import Alert from '../../ui/Alert'
import Button from '../../ui/Button'
import { Column, Columns } from '../../ui/Columns'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import ButtonGroup from '../../ui/ButtonGroup'
import Modal, { ModalProps } from '../../ui/Modal'
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
@ -25,7 +25,7 @@ interface UserLookupProps extends Omit<ModalProps, 'title'> {
const UserLookup = ({ open, onClose, onSelected }: UserLookupProps) => {
const [project] = useContext(ProjectContext)
const state = useSearchTableState(useCallback(async params => await api.users.search(project.id, params), [project]))
const [value, setValue] = useState('')
const [value, setValue] = useState<string>('')
return <Modal
title="User Lookup"
@ -34,7 +34,7 @@ const UserLookup = ({ open, onClose, onSelected }: UserLookupProps) => {
size="regular">
<div className="user-lookup">
<ButtonGroup>
<TextField name="search" placeholder="Enter email..." onChange={setValue} />
<TextInput<string> name="search" placeholder="Enter email..." onChange={setValue} />
<Button onClick={() => state.setParams({
...state.params,
q: value,
@ -70,7 +70,7 @@ const SendProof = ({ open, onClose, onSubmit, type }: SendProofProps) => {
<FormWrapper<TemplateProofParams>
onSubmit={async ({ recipient }) => await onSubmit(recipient)}>
{form => (
<TextField form={form} name="recipient" required />
<TextInput.Field form={form} name="recipient" required />
)}
</FormWrapper>
</Modal>

View file

@ -1,5 +1,5 @@
import { Campaign } from '../../types'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Modal from '../../ui/Modal'
import { languageName } from '../../utils'
@ -26,7 +26,7 @@ const LocaleTextField = ({ form }: { form: UseFormReturn<{ locale: string }> })
}
return <>
<TextField form={form}
<TextInput.Field form={form}
name="locale"
label="Locale"
onChange={handlePreviewLanguage}

View file

@ -5,7 +5,7 @@ import { CampaignContext, ProjectContext } from '../../contexts'
import { CampaignLaunchParams } from '../../types'
import OptionField from '../../ui/form/OptionField'
import SwitchField from '../../ui/form/SwitchField'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Modal from '../../ui/Modal'
import Alert from '../../ui/Alert'
@ -52,7 +52,7 @@ export default function LaunchCampaign({ open, onClose }: LaunchCampaignParams)
value={launchType}
onChange={setLaunchType} />
{launchType === 'later' && <>
<TextField
<TextInput.Field
type="datetime-local"
form={form}
name="send_at"

View file

@ -9,7 +9,7 @@ import { InfoTable } from '../../ui/InfoTable'
import Modal from '../../ui/Modal'
import FormWrapper from '../../ui/form/FormWrapper'
import { EmailTemplateData, PushTemplateData, Template, TemplateUpdateParams, TextTemplateData } from '../../types'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import api from '../../api'
const EmailTable = ({ data }: { data: EmailTemplateData }) => <InfoTable rows={{
@ -22,21 +22,21 @@ const EmailTable = ({ data }: { data: EmailTemplateData }) => <InfoTable rows={{
}} />
const EmailForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
<TextField
<TextInput.Field
form={form}
name="data.subject"
label="Subject"
textarea
required />
<TextField
<TextInput.Field
form={form}
name="data.preheader"
label="Preheader"
textarea />
<TextField form={form} name="data.from" label="From Email" required />
<TextField form={form} name="data.reply_to" label="Reply To" />
<TextField form={form} name="data.cc" label="CC" />
<TextField form={form} name="data.bcc" label="BCC" />
<TextInput.Field form={form} name="data.from" label="From Email" required />
<TextInput.Field form={form} name="data.reply_to" label="Reply To" />
<TextInput.Field form={form} name="data.cc" label="CC" />
<TextInput.Field form={form} name="data.bcc" label="BCC" />
</>
const TextTable = ({ data: { text } }: { data: TextTemplateData }) => {
@ -51,7 +51,7 @@ const TextTable = ({ data: { text } }: { data: TextTemplateData }) => {
}
const TextForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
<TextField
<TextInput.Field
form={form}
name="data.text"
label="Message"
@ -66,18 +66,18 @@ const PushTable = ({ data }: { data: PushTemplateData }) => <InfoTable rows={{
}} />
const PushForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
<TextField
<TextInput.Field
form={form}
name="data.title"
label="Title"
required />
<TextField
<TextInput.Field
form={form}
name="data.body"
label="Body"
textarea
required />
<TextField
<TextInput.Field
form={form}
name="data.topic"
label="Topic"

View file

@ -4,7 +4,7 @@ import api from '../../api'
import { ProjectContext } from '../../contexts'
import { Journey } from '../../types'
import FormWrapper from '../../ui/form/FormWrapper'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { TagPicker } from '../settings/TagPicker'
interface JourneyFormProps {
@ -28,12 +28,12 @@ export function JourneyForm({ journey, onSaved }: JourneyFormProps) {
{
form => (
<>
<TextField
<TextInput.Field
form={form}
name="name"
required
/>
<TextField
<TextInput.Field
form={form}
name="description"
textarea

View file

@ -1,5 +1,5 @@
import { JourneyStepType } from '../../../types'
import TextField from '../../../ui/form/TextField'
import TextInput from '../../../ui/form/TextInput'
import { DelayStepIcon } from '../../../ui/icons'
import { snakeToTitle } from '../../../utils'
@ -27,7 +27,7 @@ export const delayStep: JourneyStepType<DelayStepConfig> = {
<>
{
['days', 'hours', 'minutes'].map(name => (
<TextField
<TextInput
key={name}
name={name}
label={snakeToTitle(name)}
@ -35,7 +35,7 @@ export const delayStep: JourneyStepType<DelayStepConfig> = {
size="small"
min={0}
value={value[name as keyof DelayStepConfig] ?? 0}
onChange={n => onChange({ ...value, [name]: parseInt(n) })}
onChange={n => onChange({ ...value, [name]: n })}
/>
))
}

View file

@ -1,5 +1,5 @@
import { JourneyStepType } from '../../../types'
import TextField from '../../../ui/form/TextField'
import TextInput from '../../../ui/form/TextInput'
import { ExperimentStepIcon } from '../../../ui/icons'
import { round } from '../../../utils'
@ -29,14 +29,14 @@ export const experimentStep: JourneyStepType<{}, ExperimentStepChildConfig> = {
const totalRatio = siblingData.reduce((a, c) => a + c.ratio ?? 0, ratio)
const percentage = totalRatio > 0 ? round(ratio / totalRatio * 100, 2) : 0
return (
<TextField
<TextInput
name="ratio"
label="Ratio"
subtitle={`${percentage}% of users will follow this path.`}
type="number"
size="small"
value={value.ratio ?? 0}
onChange={str => onChange({ ...value, ratio: parseFloat(str) })}
onChange={ratio => onChange({ ...value, ratio })}
/>
)
},

View file

@ -1,5 +1,5 @@
import api from '../../api'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { Project } from '../../types'
import FormWrapper from '../../ui/form/FormWrapper'
import { SingleSelect } from '../../ui/form/SingleSelect'
@ -28,9 +28,9 @@ export default function ProjectForm({ onSave }: ProjectFormProps) {
{
form => (
<>
<TextField form={form} name="name" required />
<TextField form={form} name="description" textarea />
<TextField form={form}
<TextInput.Field form={form} name="name" required />
<TextInput.Field form={form} name="description" textarea />
<TextInput.Field form={form}
name="locale"
label="Default Locale"
subtitle="This locale will be used as the default when creating campaigns and when a users locale does not match any available ones."

View file

@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
import { ProjectApiKey, projectRoles } from '../../types'
import Button from '../../ui/Button'
import OptionField from '../../ui/form/OptionField'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Modal from '../../ui/Modal'
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
@ -109,13 +109,13 @@ export default function ProjectApiKeys() {
const scope = form.watch('scope')
return (
<>
<TextField
<TextInput.Field
form={form}
name="name"
label="Name"
required
/>
<TextField
<TextInput.Field
form={form}
name="description"
label="Description"

View file

@ -6,7 +6,7 @@ import { Provider, ProviderCreateParams, ProviderMeta, ProviderUpdateParams } fr
import Alert from '../../ui/Alert'
import Button from '../../ui/Button'
import SchemaFields from '../../ui/form/SchemaFields'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Modal, { ModalProps } from '../../ui/Modal'
import Tile, { TileGrid } from '../../ui/Tile'
@ -79,18 +79,22 @@ export default function IntegrationModal({ onChange, provider, ...props }: Integ
{provider?.id
? <>
<h4>Details</h4>
<TextField name="id" label="ID" value={provider.id} disabled />
<TextInput
name="id"
label="ID"
value={provider.id}
disabled />
{meta.paths && Object.keys(meta.paths).map(key => {
const value = meta.paths?.[key]
const url = `${window.location.origin}/providers/${provider?.id}${value}`
return <TextField name="unsubscribe" key={key} label={key} value={url} disabled />
return <TextInput name="unsubscribe" key={key} label={key} value={url} disabled />
})}
</>
: <Alert title={meta.name} variant="plain">Fill out the fields below to setup this integration. For more information on this integration please see the documentation on our website</Alert>
}
<h4>Config</h4>
<TextField form={form} name="name" required />
<TextInput.Field form={form} name="name" required />
<SchemaFields parent="data" schema={meta.schema.properties.data} form={form} />
</>
}

View file

@ -2,7 +2,7 @@ import { useContext } from 'react'
import api from '../../api'
import { ProjectContext } from '../../contexts'
import { Project } from '../../types'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import FormWrapper from '../../ui/form/FormWrapper'
import Heading from '../../ui/Heading'
import { toast } from 'react-hot-toast'
@ -25,15 +25,15 @@ export default function ProjectSettings() {
{
form => (
<>
<TextField form={form} name="name" required />
<TextField form={form} name="description" textarea />
<TextField form={form}
<TextInput.Field form={form} name="name" required />
<TextInput.Field form={form} name="description" textarea />
<TextInput.Field form={form}
name="locale"
label="Default Locale"
subtitle="This locale will be used as the default when creating campaigns and when a users locale does not match any available ones."
required
/>
<TextField form={form}
<TextInput.Field form={form}
name="timezone"
label="Timezone"
required

View file

@ -6,7 +6,7 @@ import FormWrapper from '../../ui/form/FormWrapper'
import Modal from '../../ui/Modal'
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
import { Subscription } from '../../types'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { SingleSelect } from '../../ui/form/SingleSelect'
import Button from '../../ui/Button'
import { PlusIcon } from '../../ui/icons'
@ -59,7 +59,7 @@ export default function Subscriptions() {
{
form => (
<>
<TextField
<TextInput.Field
form={form}
name="name"
required

View file

@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
import { Tag } from '../../types'
import Button from '../../ui/Button'
import FormWrapper from '../../ui/form/FormWrapper'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { PlusIcon } from '../../ui/icons'
import Modal from '../../ui/Modal'
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
@ -67,7 +67,7 @@ export default function Tags() {
{
form => (
<>
<TextField form={form} name="name" required />
<TextInput.Field form={form} name="name" required />
</>
)
}

View file

@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
import { List, ListCreateParams } from '../../types'
import FormWrapper from '../../ui/form/FormWrapper'
import OptionField from '../../ui/form/OptionField'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { TagPicker } from '../settings/TagPicker'
import { createWrapperRule } from './RuleBuilder'
@ -31,7 +31,7 @@ export function ListCreateForm({ onCreated }: ListCreateFormProps) {
>
{form => (
<>
<TextField
<TextInput.Field
form={form}
name="name"
label="List Name"

View file

@ -9,7 +9,7 @@ import PageContent from '../../ui/PageContent'
import RuleBuilder from './RuleBuilder'
import Modal from '../../ui/Modal'
import FormWrapper from '../../ui/form/FormWrapper'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { ListTag } from './ListTable'
import { InfoTable } from '../../ui/InfoTable'
import { snakeToTitle } from '../../utils'
@ -103,7 +103,7 @@ export default function ListDetail() {
>
{form => (
<>
<TextField
<TextInput.Field
form={form}
name="name"
label="List Name"

View file

@ -2,7 +2,7 @@ import { Operator, Rule, RuleType, WrapperRule } from '../../types'
import Button from '../../ui/Button'
import ButtonGroup from '../../ui/ButtonGroup'
import { SingleSelect } from '../../ui/form/SingleSelect'
import TextField from '../../ui/form/TextField'
import TextInput from '../../ui/form/TextInput'
import { PlusIcon, TrashIcon } from '../../ui/icons'
import './RuleBuilder.css'
@ -188,7 +188,7 @@ const RuleView = ({ rule, onChange, onDelete }: RuleParams) => {
value={rule.type}
onChange={type => handleUpdate({ type })}
/>
<TextField
<TextInput
size="small"
type="text"
name="path"
@ -200,7 +200,7 @@ const RuleView = ({ rule, onChange, onDelete }: RuleParams) => {
type={rule.type}
value={rule.operator}
onChange={operator => handleUpdate({ operator })} />
<TextField
<TextInput
size="small"
type="text"
name="value"

View file

@ -36,6 +36,7 @@ export default function UserDetailEvents() {
}}
/>
<Modal title={event?.name}
size="regular"
open={event != null}
onClose={() => setEvent(undefined)}
>