mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
comment form
This commit is contained in:
parent
96bdbb0e04
commit
c541278686
15 changed files with 357 additions and 89 deletions
|
@ -1,12 +1,12 @@
|
|||
import classnames from 'classnames';
|
||||
import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC } from 'react';
|
||||
import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC, createElement } from 'react';
|
||||
import * as styles from './styles.scss';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { IIcon } from '~/redux/types';
|
||||
|
||||
type IButtonProps = DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
size?: 'mini' | 'normal' | 'big' | 'giant' | 'micro';
|
||||
iconLeft?: IIcon;
|
||||
|
@ -35,26 +35,29 @@ export const Button: FC<IButtonProps> = ({
|
|||
is_loading,
|
||||
title,
|
||||
stretchy,
|
||||
disabled,
|
||||
...props
|
||||
}) => React.createElement(seamless || non_submitting ? 'div' : 'button', {
|
||||
className: classnames(styles.button, className, styles[size], {
|
||||
red,
|
||||
grey,
|
||||
seamless,
|
||||
transparent,
|
||||
disabled: props.disabled,
|
||||
icon: (iconLeft || iconRight) && !title && !children,
|
||||
is_loading,
|
||||
stretchy
|
||||
}),
|
||||
children: [
|
||||
iconLeft && <Icon icon={iconLeft} size={20} key={0} />,
|
||||
title ? (
|
||||
<span key={1}>{title}</span>
|
||||
) : (
|
||||
(children && <span key={1}>{children}</span>) || null
|
||||
),
|
||||
iconRight && <Icon icon={iconRight} size={20} key={2} />
|
||||
],
|
||||
...props
|
||||
});
|
||||
}) =>
|
||||
createElement(
|
||||
seamless || non_submitting ? 'div' : 'button',
|
||||
{
|
||||
className: classnames(styles.button, className, styles[size], {
|
||||
red,
|
||||
grey,
|
||||
seamless,
|
||||
transparent,
|
||||
disabled,
|
||||
is_loading,
|
||||
stretchy,
|
||||
icon: (iconLeft || iconRight) && !title && !children,
|
||||
has_icon_left: !!iconLeft,
|
||||
has_icon_right: !!iconRight,
|
||||
}),
|
||||
...props,
|
||||
},
|
||||
[
|
||||
iconLeft && <Icon icon={iconLeft} size={20} key={0} />,
|
||||
title ? <span key={1}>{title}</span> : (children && <span key={1}>{children}</span>) || null,
|
||||
iconRight && <Icon icon={iconRight} size={20} key={2} />,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -85,8 +85,8 @@
|
|||
&:global(.disabled),
|
||||
&:global(.grey) {
|
||||
opacity: 0.3;
|
||||
//background: black;
|
||||
filter: grayscale(100%);
|
||||
background: lighten(black, 2%);
|
||||
// filter: grayscale(100%);
|
||||
}
|
||||
|
||||
&:global(.icon) {
|
||||
|
@ -101,6 +101,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:global(.has_icon_left) {
|
||||
padding-left: $gap;
|
||||
padding-right: $gap;
|
||||
}
|
||||
|
||||
&:global(.has_icon_right) {
|
||||
padding-left: $gap;
|
||||
padding-right: $gap;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin: 0 5px;
|
||||
|
||||
|
@ -117,9 +127,11 @@
|
|||
height: 20px;
|
||||
font: $font_12_semibold;
|
||||
padding: 0 15px;
|
||||
border-radius: $radius / 2;
|
||||
}
|
||||
.mini {
|
||||
height: 28px;
|
||||
border-radius: $radius / 2;
|
||||
}
|
||||
.normal {
|
||||
height: 38px;
|
||||
|
@ -135,6 +147,7 @@
|
|||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.icon_left {
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
|
|
124
src/components/input/Textarea/index.tsx
Normal file
124
src/components/input/Textarea/index.tsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import React, {
|
||||
ChangeEvent,
|
||||
LegacyRef,
|
||||
memo,
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { getStyle } from '~/utils/dom';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import * as styles from '~/styles/inputs.scss';
|
||||
import { Icon } from '../Icon';
|
||||
|
||||
interface IProps {
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
className?: string;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
handler: (value: string) => void;
|
||||
required?: boolean;
|
||||
status?: 'error' | 'success' | '';
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const Textarea = memo<IProps>(
|
||||
({
|
||||
value,
|
||||
placeholder,
|
||||
className,
|
||||
minRows = 3,
|
||||
maxRows = 30,
|
||||
handler,
|
||||
required = false,
|
||||
title = '',
|
||||
status = '',
|
||||
}) => {
|
||||
const [rows, setRows] = useState(minRows || 1);
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
const textarea: LegacyRef<HTMLTextAreaElement> = useRef(null);
|
||||
|
||||
const onInput = useCallback(
|
||||
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
||||
[handler]
|
||||
);
|
||||
|
||||
const onFocus = useCallback(() => setFocused(true), [setFocused]);
|
||||
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const lineHeight = parseInt(getStyle(textarea.current, 'line-height'), 10) || 15;
|
||||
|
||||
textarea.current.rows = 1; // reset number of rows in textarea
|
||||
|
||||
const paddingTop = parseInt(getStyle(textarea.current, 'padding-top'), 10) || 0;
|
||||
const paddingBottom = parseInt(getStyle(textarea.current, 'padding-bottom'), 10) || 0;
|
||||
|
||||
const actualScrollHeight =
|
||||
(textarea.current.scrollHeight || 0) - (paddingTop + paddingBottom);
|
||||
|
||||
const rowsCount = Math.round(actualScrollHeight / lineHeight);
|
||||
|
||||
let currentRows = minRows;
|
||||
|
||||
if (rowsCount > maxRows) {
|
||||
currentRows = maxRows;
|
||||
textarea.current.scrollTop = textarea.current.scrollHeight;
|
||||
} else if (rowsCount <= minRows) {
|
||||
currentRows = minRows;
|
||||
} else {
|
||||
currentRows = rowsCount;
|
||||
}
|
||||
|
||||
textarea.current.rows = currentRows;
|
||||
|
||||
setRows(currentRows);
|
||||
}, [value, minRows, maxRows]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.input_text_wrapper, {
|
||||
[styles.required]: required,
|
||||
[styles.focused]: focused,
|
||||
[styles.has_status]: !!status,
|
||||
[styles.has_value]: !!value,
|
||||
})}
|
||||
>
|
||||
<div className={styles.input}>
|
||||
<textarea
|
||||
rows={rows}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
className={classNames(styles.textarea, className)}
|
||||
onChange={onInput}
|
||||
ref={textarea}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.status}>
|
||||
<div className={classNames({ active: status === 'success' })}>
|
||||
<Icon icon="check" size={20} />
|
||||
</div>
|
||||
<div className={classNames({ active: status === 'error' })}>
|
||||
<Icon icon="hide" size={20} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{title && (
|
||||
<div className={styles.title}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { Textarea };
|
Loading…
Add table
Add a link
Reference in a new issue