Skip to content

Commit

Permalink
feat: React version of M3TextField
Browse files Browse the repository at this point in the history
  • Loading branch information
magomedov_gusein committed Jun 21, 2024
1 parent 44ab1da commit 4968c05
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
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';
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>) => {
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'

0 comments on commit 4968c05

Please sign in to comment.