Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): add TextField components #38

Merged
merged 12 commits into from
Feb 27, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ exports[`SignIn should match the snapshot 1`] = `
class="oxygen-text-field"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink oxygen-text-field__label css-1lym8jl-MuiFormLabel-root-MuiInputLabel-root"
data-shrink="true"
for="id"
aria-describedby="name"
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-animated oxygen-input-label css-1w93pcl-MuiFormLabel-root-MuiInputLabel-root"
for="name"
>
Username
</label>
<div
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root oxygen-text-field__input css-wb57ya-MuiFormControl-root-MuiTextField-root"
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root css-wb57ya-MuiFormControl-root-MuiTextField-root"
>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth Mui-focused MuiInputBase-formControl css-l6ws16-MuiInputBase-root-MuiOutlinedInput-root"
Expand Down Expand Up @@ -69,29 +69,79 @@ exports[`SignIn should match the snapshot 1`] = `
class="oxygen-text-field"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink oxygen-text-field__label css-1lym8jl-MuiFormLabel-root-MuiInputLabel-root"
data-shrink="true"
for="id"
aria-describedby="password"
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-animated oxygen-input-label css-1w93pcl-MuiFormLabel-root-MuiInputLabel-root"
for="password"
>
Password
</label>
<div
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root oxygen-text-field__input css-wb57ya-MuiFormControl-root-MuiTextField-root"
class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root css-wb57ya-MuiFormControl-root-MuiTextField-root"
>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl css-l6ws16-MuiInputBase-root-MuiOutlinedInput-root"
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-adornedEnd css-1kso3qa-MuiInputBase-root-MuiOutlinedInput-root"
>
<input
aria-invalid="false"
autocomplete="current-password"
class="MuiInputBase-input MuiOutlinedInput-input css-d9o7zs-MuiInputBase-input-MuiOutlinedInput-input"
id="password"
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputAdornedEnd css-11fn00k-MuiInputBase-input-MuiOutlinedInput-input"
id=":r4:"
name="password"
placeholder="Enter your password"
required=""
type="password"
value=""
/>
<div
class="MuiInputAdornment-root MuiInputAdornment-positionEnd MuiInputAdornment-outlined MuiInputAdornment-sizeMedium css-103jkr5-MuiInputAdornment-root"
>
<button
aria-label="toggle password visibility"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd MuiIconButton-sizeMedium oxygen-icon-button css-1q9q5zz-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="oxygen-icon oxygen-icon-visibility"
fill="currentColor"
focusable="false"
height="16"
role="img"
style="display: inline-block; overflow: visible; user-select: none; vertical-align: text-bottom;"
viewBox="0 0 16 16"
width="16"
>
<path
clip-rule="evenodd"
d="M7.99969 2C5.38552 2 2.88413 3.04002 0.456376 7.21515L0 8L0.456376 8.78485L0.677111 9.15847C3.03624 13.0871 5.46521 14 7.99969 14C10.6138 14 13.1152 12.96 15.543 8.78485L16 7.99892L15.3234 6.84341C12.9631 2.91293 10.5342 2 7.99969 2ZM8.21026 3.09654C10.3162 3.20912 12.4309 3.92049 14.5603 7.46652L14.8724 8.00109L14.7751 8.16996C12.503 12.0775 10.2462 12.9091 7.99969 12.9091C5.82409 12.9091 3.63943 12.1977 1.43904 8.53348L1.12513 7.99891L1.22426 7.83004C3.49642 3.9225 5.75314 3.09091 7.99969 3.09091L8.21026 3.09654ZM8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5ZM8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6Z"
fill=""
fill-rule="evenodd"
/>
<mask
height="12"
id="mask0_421_1547"
maskUnits="userSpaceOnUse"
width="16"
x="0"
y="2"
>
<path
clip-rule="evenodd"
d="M7.99969 2C5.38552 2 2.88413 3.04002 0.456376 7.21515L0 8L0.456376 8.78485L0.677111 9.15847C3.03624 13.0871 5.46521 14 7.99969 14C10.6138 14 13.1152 12.96 15.543 8.78485L16 7.99892L15.3234 6.84341C12.9631 2.91293 10.5342 2 7.99969 2ZM8.21026 3.09654C10.3162 3.20912 12.4309 3.92049 14.5603 7.46652L14.8724 8.00109L14.7751 8.16996C12.503 12.0775 10.2462 12.9091 7.99969 12.9091C5.82409 12.9091 3.63943 12.1977 1.43904 8.53348L1.12513 7.99891L1.22426 7.83004C3.49642 3.9225 5.75314 3.09091 7.99969 3.09091L8.21026 3.09654ZM8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5ZM8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6Z"
fill=""
fill-rule="evenodd"
/>
</mask>
<g
mask="url(#mask0_421_1547)"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
<fieldset
aria-hidden="true"
class="MuiOutlinedInput-notchedOutline css-116asjz-MuiOutlinedInput-notchedOutline"
Expand Down
153 changes: 97 additions & 56 deletions packages/react/src/components/TextField/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@ import InputAdornment from '@mui/material/InputAdornment';
import MuiTextField, {TextFieldProps as MuiTextFieldProps} from '@mui/material/TextField';
import {DoubleCircleIcon, VisibilityIcon, VisibilityOffIcon} from '@oxygen-ui/react-icons';
import clsx from 'clsx';
import {FC, MouseEvent, ReactElement, ReactNode, useState} from 'react';
import {
FC,
forwardRef,
ForwardRefExoticComponent,
MouseEvent,
MutableRefObject,
ReactElement,
ReactNode,
useState,
} from 'react';
import {TextFieldInputTypes} from './constants';
import {WithWrapperProps} from '../../models';
import {composeComponentDisplayName} from '../../utils';
import IconButton from '../IconButton';
Expand All @@ -41,83 +51,114 @@ export type TextFieldProps = {

const COMPONENT_NAME: string = 'TextField';

const TextField: FC<TextFieldProps> & WithWrapperProps = (props: TextFieldProps): ReactElement => {
const {className, criteria, InputLabelProps, id, label, type, ...rest} = props;
const PasswordTextField: ForwardRefExoticComponent<TextFieldProps> = forwardRef(
(props: TextFieldProps, ref: MutableRefObject<HTMLDivElement>): ReactElement => {
const {type, ...rest} = props;

const classes: string = clsx('oxygen-text-field', className);
const [showPassword, setShowPassword] = useState(false);

const handleClickShowPassword = (): void => setShowPassword((show: boolean) => !show);

const handleMouseDownPassword = (event: MouseEvent<HTMLButtonElement>): void => {
event.preventDefault();
};

const [open, setOpen] = useState<boolean>(false);
const [showPassword, setShowPassword] = useState(false);
return (
<MuiTextField
ref={ref}
type={showPassword ? TextFieldInputTypes.INPUT_TEXT : TextFieldInputTypes.INPUT_PASSWORD}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
</InputAdornment>
),
}}
{...rest}
/>
);
},
) as ForwardRefExoticComponent<TextFieldProps>;

const handleClick = (): void => {
if (type === 'password' && criteria?.length > 0) {
const TooltipPasswordTextField: ForwardRefExoticComponent<TextFieldProps> = forwardRef(
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
(props: TextFieldProps, ref: MutableRefObject<HTMLDivElement>): ReactElement => {
const {criteria, id, type, ...rest} = props;

const [open, setOpen] = useState<boolean>(false);

const handleClick = (): void => {
setOpen(true);
}
};
};

const handleClose = (): void => {
if (open) {
setOpen(false);
const handleClose = (): void => {
if (open) {
setOpen(false);
}
};

const tooltipContent = (): ReactNode => (
<List>
{criteria?.map((criterion: string) => (
<ListItem disablePadding key={criteria.indexOf(criterion)}>
<ListItemIcon>
<DoubleCircleIcon />
</ListItemIcon>
<ListItemText primary={criterion} />
</ListItem>
))}
</List>
);

if (!criteria) {
return <PasswordTextField {...rest} />;
}
};

const handleClickShowPassword = (): void => setShowPassword((show: boolean) => !show);

const handleMouseDownPassword = (event: MouseEvent<HTMLButtonElement>): void => {
event.preventDefault();
};

const tooltipContent = (): ReactNode => (
<List>
{criteria?.map((criterion: string) => (
<ListItem disablePadding>
<ListItemIcon>
<DoubleCircleIcon />
</ListItemIcon>
<ListItemText primary={criterion} />
</ListItem>
))}
</List>
);

return (
<div className={classes}>
<InputLabel htmlFor={id} aria-describedby={id} {...InputLabelProps}>
{label}
</InputLabel>
return (
<Tooltip
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
arrow
placement="right-end"
describeChild
open={open}
title={tooltipContent()}
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
classes={{arrow: 'oxygen-text-field-tooltip-arrow', tooltip: 'oxygen-text-field-tooltip'}}
ref={ref}
>
<MuiTextField
<PasswordTextField
ref={ref}
id={id}
type={type === 'password' && (showPassword ? 'text' : 'password')}
InputProps={
type === 'password' && {
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
</InputAdornment>
),
}
}
type={type}
onClick={handleClick}
onBlurCapture={handleClose}
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
onFocus={handleClick}
{...rest}
/>
</Tooltip>
);
},
) as ForwardRefExoticComponent<TextFieldProps>;

const TextField: FC<TextFieldProps> & WithWrapperProps = (props: TextFieldProps): ReactElement => {
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
const {className, id, label, type, InputLabelProps, ...rest} = props;

const classes: string = clsx('oxygen-text-field', className);

return (
<div className={classes}>
<InputLabel htmlFor={id} aria-describedby={id} {...InputLabelProps}>
{label}
</InputLabel>
{type === TextFieldInputTypes.INPUT_PASSWORD ? (
<TooltipPasswordTextField id={id} type={type} {...rest} />
) : (
<MuiTextField id={id} type={type} {...rest} />
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ exports[`TextField should match the snapshot 1`] = `
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-animated oxygen-input-label css-1w93pcl-MuiFormLabel-root-MuiInputLabel-root"
/>
<div
class="MuiFormControl-root MuiTextField-root oxygen-tooltip css-1u3bzj6-MuiFormControl-root-MuiTextField-root"
data-mui-internal-clone-element="true"
class="MuiFormControl-root MuiTextField-root css-1u3bzj6-MuiFormControl-root-MuiTextField-root"
>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl css-kfyxoi-MuiInputBase-root-MuiOutlinedInput-root"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input css-d9o7zs-MuiInputBase-input-MuiOutlinedInput-input"
id=":r3:"
id=":r1:"
type="text"
value=""
/>
<fieldset
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/components/TextField/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export {TextFieldInputTypes} from './types';
22 changes: 22 additions & 0 deletions packages/react/src/components/TextField/constants/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export enum TextFieldInputTypes {
INPUT_PASSWORD = 'password',
INPUT_TEXT = 'text',
}
1 change: 1 addition & 0 deletions packages/react/src/components/TextField/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@

export {default} from './TextField';
export type {TextFieldProps} from './TextField';
export {TextFieldInputTypes} from './constants';