diff --git a/frontend/src/shared/components/ControlledInput/Input.stories.tsx b/frontend/src/shared/components/ControlledInput/Input.stories.tsx new file mode 100644 index 0000000..32cd832 --- /dev/null +++ b/frontend/src/shared/components/ControlledInput/Input.stories.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import BaseStyles from 'App/BaseStyles'; +import NormalizeStyles from 'App/NormalizeStyles'; +import { theme } from 'App/ThemeStyles'; +import styled, { ThemeProvider } from 'styled-components'; + +import Input from '.'; +import { User } from 'shared/icons'; + +export default { + component: Input, + title: 'Input', + parameters: { + backgrounds: [ + { name: 'white', value: '#ffffff', default: true }, + { name: 'gray', value: '#f8f8f8' }, + ], + }, +}; + +const Wrapper = styled.div` + background: rgba(${props => props.theme.colors.bg.primary}); + padding: 45px; + margin: 25px; + display: flex; + flex-direction: column; +`; + +export const Default = () => { + return ( + <> + + + + + + + } width="100%" placeholder="Placeholder" /> + + + + ); +}; diff --git a/frontend/src/shared/components/ControlledInput/index.tsx b/frontend/src/shared/components/ControlledInput/index.tsx new file mode 100644 index 0000000..342e602 --- /dev/null +++ b/frontend/src/shared/components/ControlledInput/index.tsx @@ -0,0 +1,155 @@ +import React, { useState, useEffect, useRef } from 'react'; +import styled, { css } from 'styled-components/macro'; + +const InputWrapper = styled.div<{ width: string }>` + position: relative; + width: auto; + display: flex; + align-items: flex-start; + flex-direction: column; + position: relative; + justify-content: center; + + margin-bottom: 2.2rem; + margin-top: 24px; +`; + +const InputLabel = styled.span<{ width: string }>` + width: ${props => props.width}; + padding: 0.7rem !important; + color: #c2c6dc; + left: 0; + top: 0; + transition: all 0.2s ease; + position: absolute; + border-radius: 5px; + overflow: hidden; + font-size: 0.85rem; + cursor: text; + font-size: 12px; + user-select: none; + pointer-events: none; +} +`; + +const InputInput = styled.input<{ + hasValue: boolean; + hasIcon: boolean; + width: string; + focusBg: string; + borderColor: string; +}>` + width: ${props => props.width}; + font-size: 14px; + border: 1px solid rgba(0, 0, 0, 0.2); + border-color: ${props => props.borderColor}; + background: #262c49; + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15); + ${props => (props.hasIcon ? 'padding: 0.7rem 1rem 0.7rem 3rem;' : 'padding: 0.7rem;')} + line-height: 16px; + color: #c2c6dc; + position: relative; + border-radius: 5px; + transition: all 0.3s ease; + &:focus { + box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15); + border: 1px solid rgba(115, 103, 240); + background: ${props => props.focusBg}; + } + &:focus ~ ${InputLabel} { + color: rgba(115, 103, 240); + transform: translate(-3px, -90%); + } + ${props => + props.hasValue && + css` + & ~ ${InputLabel} { + color: rgba(115, 103, 240); + transform: translate(-3px, -90%); + } + `} +`; + +const Icon = styled.div` + display: flex; + left: 16px; + position: absolute; +`; + +type ControlledInputProps = { + variant?: 'normal' | 'alternate'; + label?: string; + width?: string; + floatingLabel?: boolean; + placeholder?: string; + icon?: JSX.Element; + type?: string; + autocomplete?: boolean; + autoFocus?: boolean; + id?: string; + name?: string; + className?: string; + defaultValue?: string; + onChange?: (e: React.ChangeEvent) => void; + value?: string; + onClick?: (e: React.MouseEvent) => void; +}; + +const ControlledInput = ({ + width = 'auto', + variant = 'normal', + type = 'text', + autocomplete, + autoFocus = false, + label, + placeholder, + icon, + name, + className, + onChange, + value, + onClick, + floatingLabel = false, + defaultValue, + id, +}: ControlledInputProps) => { + const $input = useRef(null); + const [hasValue, setHasValue] = useState(false); + const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561'; + const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)'; + useEffect(() => { + if (autoFocus && $input && $input.current) { + $input.current.focus(); + } + }, []); + return ( + + { + if (onChange) { + setHasValue(e.currentTarget.value !== '' || floatingLabel); + onChange(e); + } + }} + value={value} + id={id} + type={type} + name={name} + ref={$input} + onClick={onClick} + autoComplete={autocomplete ? 'on' : 'off'} + defaultValue={defaultValue} + hasIcon={typeof icon !== 'undefined'} + width={width} + placeholder={placeholder} + focusBg={focusBg} + borderColor={borderColor} + /> + {label && {label}} + {icon && icon} + + ); +}; + +export default ControlledInput; diff --git a/frontend/src/shared/components/Input/index.tsx b/frontend/src/shared/components/Input/index.tsx index e997b5d..1857348 100644 --- a/frontend/src/shared/components/Input/index.tsx +++ b/frontend/src/shared/components/Input/index.tsx @@ -83,6 +83,7 @@ type InputProps = { floatingLabel?: boolean; placeholder?: string; icon?: JSX.Element; + type?: string; autocomplete?: boolean; id?: string; name?: string; @@ -96,6 +97,7 @@ const Input = React.forwardRef( { width = 'auto', variant = 'normal', + type = 'text', autocomplete, label, placeholder, @@ -125,6 +127,7 @@ const Input = React.forwardRef( hasValue={hasValue} ref={$ref} id={id} + type={type} name={name} onClick={onClick} autoComplete={autocomplete ? 'on' : 'off'} diff --git a/frontend/src/shared/components/PopupMenu/LabelManager.tsx b/frontend/src/shared/components/PopupMenu/LabelManager.tsx index 89f7ed5..edd5df9 100644 --- a/frontend/src/shared/components/PopupMenu/LabelManager.tsx +++ b/frontend/src/shared/components/PopupMenu/LabelManager.tsx @@ -22,24 +22,18 @@ type Props = { }; const LabelManager: React.FC = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => { - const $fieldName = useRef(null); const [currentLabel, setCurrentLabel] = useState(''); const [currentSearch, setCurrentSearch] = useState(''); - useEffect(() => { - if ($fieldName.current) { - $fieldName.current.focus(); - } - }, []); return ( <> { - setCurrentSearch(e.currentTarget.value); - }} + autoFocus value={currentSearch} + variant="alternate" + width="100%" + onChange={e => setCurrentSearch(e.currentTarget.value)} + type="text" + placeholder="search labels..." />
Labels diff --git a/frontend/src/shared/components/PopupMenu/Styles.ts b/frontend/src/shared/components/PopupMenu/Styles.ts index 10febb5..4f797cd 100644 --- a/frontend/src/shared/components/PopupMenu/Styles.ts +++ b/frontend/src/shared/components/PopupMenu/Styles.ts @@ -1,5 +1,7 @@ import styled, { css } from 'styled-components'; import { mixin } from 'shared/utils/styles'; +import Input from '../Input'; +import ControlledInput from 'shared/components/ControlledInput'; export const Container = styled.div<{ invertY: boolean; @@ -78,35 +80,13 @@ export const Content = styled.div` max-height: 632px; `; -export const LabelSearch = styled.input` - box-sizing: border-box; - display: block; - transition-property: background-color, border-color, box-shadow; - transition-duration: 85ms; - transition-timing-function: ease; - margin: 4px 0 12px; - width: 100%; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 3px; - line-height: 20px; - padding: 8px 12px; - font-size: 14px; - font-family: 'Droid Sans'; - font-weight: 400; - - background: #262c49; - outline: none; - color: #c2c6dc; - border-color: #414561; - - &:focus { - box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px; - background: ${mixin.darken('#262c49', 0.15)}; - } +export const LabelSearch = styled(ControlledInput)` + margin: 12px 12px 0 12px; `; export const Section = styled.div` margin-top: 12px; + margin: 12px 12px 0 12px; `; export const SectionTitle = styled.h4`