Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions m3-react/src/components/text-field/M3TextField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useState, useEffect, useRef, HTMLAttributes } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут бы разделить импорт типов и импорт хуков. Еще стоит убрать импорт React, лучше импортировать FC отдельно, чтобы при сборке лучше работал treeshaking

import makeId from '@/utils/id';
import { toClassName } from '@/utils/styling';

export interface M3TextFieldProps extends HTMLAttributes<HTMLElement> {
id?: string;
type?: string;
value?: string | number;
label?: string;
placeholder?: string;
lazy?: boolean;
multiline?: boolean;
invalid?: boolean;
disabled?: boolean;
readonly?: boolean;
outlined?: boolean;
onUpdateValue?: (value: string | number) => void;
}

const M3TextField: React.FC<M3TextFieldProps> = ({
id = makeId('m3-text-field'),
type = 'text',
value = '',
label = '',
placeholder = '',
lazy = false,
multiline = false,
invalid = false,
disabled = false,
readonly = false,
outlined = false,
className = '',
children,
onUpdateValue,
...props
}) => {
const [focused, setFocused] = useState(false);
const inputElement = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);

const onInput = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут надо бы использовать хук useCallback, иначе при перерисовке будет заново создаваться. Еще надо бы отдельно импортировать тип ChangeEvent. Аналогично для остальных обработчиков

const rawValue = event.target.value;
if (!lazy) {
onUpdateValue && onUpdateValue(rawValue);
}
};

const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const rawValue = event.target.value;
if (lazy) {
onUpdateValue && onUpdateValue(rawValue);
}
};

const onFocus = () => {
setFocused(true);
};

const onBlur = () => {
setFocused(false);
};

useEffect(() => {
const input = inputElement.current;
if (input) {
const valueStr = String(value);
if (valueStr.length) {
input.value = valueStr;
} else if (input.value.length) {
onUpdateValue && onUpdateValue(input.value);
}
}
}, [value, onUpdateValue]);

return (
<div
className={toClassName([className, {
'm3-text-field': true,
'm3-text-field_outlined': outlined,
'm3-text-field_focused': focused,
'm3-text-field_invalid': invalid,
'm3-text-field_disabled': disabled,
'm3-text-field_readonly': readonly,
}])}
onClick={() => inputElement.current?.focus()}
{...props}
>
{outlined && <div className="m3-text-field__outline">
<div className="m3-text-field__outline-leading" />
<div className="m3-text-field__outline-notch">
<label
id={`${id}-label`}
htmlFor={id}
className="m3-text-field__label"
>
{label}
</label>
</div>
<div className="m3-text-field__outline-trailing" />
</div>}

{!outlined && <label
id={`${id}-label`}
htmlFor={id}
className="m3-text-field__label"
>
{label}
</label>}

<div className="m3-text-field__state">
{children}

{multiline ? (
<textarea
id={id}
ref={inputElement as React.RefObject<HTMLTextAreaElement>}
defaultValue={String(value)}
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
aria-invalid={invalid ? 'true' : 'false'}
onInput={onInput}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
) : (
<input
id={id}
ref={inputElement as React.RefObject<HTMLInputElement>}
type={type}
defaultValue={String(value)}
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
aria-invalid={invalid ? 'true' : 'false'}
onInput={onInput}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
)}
</div>

{!outlined && <div className="m3-text-field__underline" />}
</div>
);
};

export default M3TextField;
24 changes: 24 additions & 0 deletions m3-react/src/components/text-field/M3TextFieldSupportText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { FC } from 'react';
import { toClassName } from '@/utils/styling';

export interface M3TextFieldSupportTextProps {
text?: string;
danger?: boolean;
muted?: boolean;
}

const M3TextFieldSupportText: FC<M3TextFieldSupportTextProps> = ({ text = '', danger = false, muted = false, children }) => {
return (
<div
className={toClassName({
'm3-text-field-support-text': true,
'm3-text-field-support-text_danger': danger,
'm3-text-field-support-text_muted': muted,
})}
>
{children || text}
</div>
);
};

export default M3TextFieldSupportText;
10 changes: 10 additions & 0 deletions m3-react/src/components/text-field/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type {
M3TextFieldProps,
} from './M3TextField'

export type {
M3TextFieldSupportTextProps,
} from './M3TextFieldSupportText'

export { default as M3TextField } from './M3TextField'
export { default as M3TextFieldSupportText } from './M3TextFieldSupportText'
10 changes: 10 additions & 0 deletions m3-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export type {
M3SwitchProps,
} from '@/components/switch'

export type {
M3TextFieldProps,
M3TextFieldSupportTextProps,
} from '@/components/text-field'

export {
M3Button,
} from '@/components/button'
Expand Down Expand Up @@ -111,3 +116,8 @@ export {
M3SwitchScope,
useM3SwitchScope,
} from '@/components/switch'

export type {
M3TextField,
M3TextFieldSupportText,
} from '@/components/text-field'