Input.tsx282 lines · main
1'use client'
2
3import { Copy } from 'lucide-react'
4import React, { useState } from 'react'
5
6import { HIDDEN_PLACEHOLDER } from '../../lib/constants'
7import { FormLayout } from '../../lib/Layout/FormLayout/FormLayout'
8import InputErrorIcon from '../../lib/Layout/InputErrorIcon'
9import InputIconContainer from '../../lib/Layout/InputIconContainer'
10import styleHandler from '../../lib/theme/styleHandler'
11import { copyToClipboard } from '../../lib/utils'
12import { cn } from '../../lib/utils/cn'
13import { Button } from '../Button'
14
15export interface Props extends Omit<
16 React.InputHTMLAttributes<HTMLInputElement>,
17 'size' | 'onCopy'
18> {
19 inputClassName?: string
20 iconContainerClassName?: string
21 copy?: boolean
22 onCopy?: () => void
23 defaultValue?: string | number
24 descriptionText?: string | React.ReactNode | undefined
25 disabled?: boolean
26 error?: string
27 icon?: any
28 inputRef?: React.LegacyRef<HTMLInputElement>
29 label?: string | React.ReactNode
30 afterLabel?: string
31 beforeLabel?: string
32 labelOptional?: string | React.ReactNode
33 layout?: 'horizontal' | 'vertical'
34 reveal?: boolean
35 actions?: React.ReactNode
36 size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
37 borderless?: boolean
38 validation?: (x: any) => void
39}
40
41/**
42 * @deprecated Use `import { Input } from 'ui'` instead or `import { Input } from 'ui-patterns/DataInputs/Input'`
43 */
44function Input({
45 autoComplete,
46 autoFocus,
47 className,
48 inputClassName,
49 iconContainerClassName,
50 copy,
51 defaultValue,
52 descriptionText,
53 disabled,
54 error,
55 icon,
56 id = '',
57 name = '',
58 inputRef,
59 label,
60 afterLabel,
61 beforeLabel,
62 labelOptional,
63 layout,
64 onCopy,
65 placeholder,
66 type = 'text',
67 value = undefined,
68 style,
69 reveal = false,
70 actions,
71 size = 'medium',
72 borderless = false,
73 validation,
74 ...props
75}: Props) {
76 const [copyLabel, setCopyLabel] = useState('Copy')
77 const [hidden, setHidden] = useState(true)
78
79 const __styles = styleHandler('input')
80
81 function _onCopy(value: any) {
82 copyToClipboard(value, () => {
83 setCopyLabel('Copied')
84 setTimeout(() => {
85 setCopyLabel('Copy')
86 }, 3000)
87 onCopy?.()
88 })
89 }
90
91 function onReveal() {
92 setHidden(false)
93 }
94
95 let inputClasses = ['peer/input', __styles.base]
96
97 if (error) inputClasses.push(__styles.variants.error)
98 if (!error) inputClasses.push(__styles.variants.standard)
99 if (size) inputClasses.push(__styles.size[size])
100 if (icon) inputClasses.push(__styles.with_icon[size])
101 if (disabled) inputClasses.push(__styles.disabled)
102 if (inputClassName) inputClasses.push(inputClassName)
103
104 return (
105 <FormLayout
106 label={label}
107 afterLabel={afterLabel}
108 beforeLabel={beforeLabel}
109 labelOptional={labelOptional}
110 layout={layout}
111 id={id}
112 error={error}
113 descriptionText={descriptionText}
114 style={style}
115 size={size}
116 className={className}
117 >
118 <div className={__styles.container}>
119 <input
120 data-size={size}
121 autoComplete={autoComplete}
122 autoFocus={autoFocus}
123 defaultValue={defaultValue}
124 disabled={disabled}
125 id={id}
126 name={name}
127 onCopy={onCopy}
128 placeholder={placeholder}
129 ref={inputRef}
130 type={type}
131 value={reveal && hidden ? HIDDEN_PLACEHOLDER : value}
132 className={cn(inputClasses)}
133 {...props}
134 />
135 {icon && <InputIconContainer size={size} icon={icon} className={iconContainerClassName} />}
136 {copy || error || actions ? (
137 <div className={__styles.actions_container}>
138 {error && <InputErrorIcon size={size} />}
139 {copy && !(reveal && hidden) ? (
140 <Button size="tiny" type="default" icon={<Copy />} onClick={() => _onCopy(value)}>
141 {copyLabel}
142 </Button>
143 ) : null}
144 {reveal && hidden ? (
145 <Button size="tiny" type="default" onClick={onReveal}>
146 Reveal
147 </Button>
148 ) : null}
149 {actions && actions}
150 </div>
151 ) : null}
152 </div>
153 </FormLayout>
154 )
155}
156
157/**
158 * @deprecated Use ./TextArea_Shadcn_ instead
159 */
160export interface TextAreaProps extends Omit<
161 React.InputHTMLAttributes<HTMLTextAreaElement>,
162 'size' | 'onCopy'
163> {
164 textAreaClassName?: string
165 descriptionText?: string | React.ReactNode | undefined
166 error?: string
167 icon?: any
168 label?: string | React.ReactNode
169 afterLabel?: string
170 beforeLabel?: string
171 labelOptional?: string | React.ReactNode
172 layout?: 'horizontal' | 'vertical'
173 rows?: number
174 limit?: number
175 size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
176 borderless?: boolean
177 validation?: (x: any) => void
178 copy?: boolean
179 onCopy?: () => void
180 actions?: React.ReactNode
181}
182
183function TextArea({
184 className,
185 textAreaClassName,
186 descriptionText,
187 disabled,
188 error,
189 icon,
190 id = '',
191 name = '',
192 label,
193 afterLabel,
194 beforeLabel,
195 labelOptional,
196 layout,
197 placeholder,
198 value,
199 style,
200 rows = 4,
201 limit,
202 size = 'medium',
203 borderless = false,
204 validation,
205 copy = false,
206 onCopy,
207 actions,
208 ...props
209}: TextAreaProps) {
210 const [copyLabel, setCopyLabel] = useState('Copy')
211
212 function _onCopy(value: any) {
213 copyToClipboard(value, () => {
214 /* clipboard successfully set */
215 setCopyLabel('Copied')
216 setTimeout(() => {
217 setCopyLabel('Copy')
218 }, 3000)
219 onCopy?.()
220 })
221 }
222
223 const __styles = styleHandler('input')
224
225 let classes = [__styles.base]
226
227 if (error) classes.push(__styles.variants.error)
228 if (!error) classes.push(__styles.variants.standard)
229 if (icon) classes.push(__styles.with_icon[size])
230 if (size) classes.push(__styles.size[size])
231 if (disabled) classes.push(__styles.disabled)
232 if (textAreaClassName) classes.push(textAreaClassName)
233
234 return (
235 <FormLayout
236 className={className}
237 label={label}
238 afterLabel={afterLabel}
239 beforeLabel={beforeLabel}
240 labelOptional={labelOptional}
241 layout={layout}
242 id={id}
243 error={error}
244 descriptionText={descriptionText}
245 style={style}
246 size={size}
247 >
248 <div className={__styles.container}>
249 <textarea
250 disabled={disabled}
251 id={id}
252 name={name}
253 rows={rows}
254 cols={100}
255 placeholder={placeholder}
256 onCopy={onCopy}
257 value={value}
258 className={classes.join(' ')}
259 maxLength={limit}
260 {...props}
261 />
262 {copy || error || actions ? (
263 <div className={__styles['textarea_actions_container']}>
264 <div className={__styles['textarea_actions_container_items']}>
265 {error && <InputErrorIcon size={size} />}
266 {copy && (
267 <Button size="tiny" type="default" onClick={() => _onCopy(value)} icon={<Copy />}>
268 {copyLabel}
269 </Button>
270 )}
271 {actions && actions}
272 </div>
273 </div>
274 ) : null}
275 </div>
276 </FormLayout>
277 )
278}
279
280Input.TextArea = TextArea
281
282export default Input