feature: fix label manager popup padding & switch input to ControlledInput

This commit is contained in:
Jordan Knott 2020-07-16 20:14:13 -05:00
parent 2cf6be082c
commit 8c610b11bf
5 changed files with 212 additions and 37 deletions

View File

@ -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 (
<>
<NormalizeStyles />
<BaseStyles />
<ThemeProvider theme={theme}>
<Wrapper>
<Input label="Label placeholder" />
<Input width="100%" placeholder="Placeholder" />
<Input icon={<User size={20} />} width="100%" placeholder="Placeholder" />
</Wrapper>
</ThemeProvider>
</>
);
};

View File

@ -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<HTMLInputElement>) => void;
value?: string;
onClick?: (e: React.MouseEvent<HTMLInputElement>) => 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<HTMLInputElement>(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 (
<InputWrapper className={className} width={width}>
<InputInput
hasValue={hasValue}
onChange={e => {
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 && <InputLabel width={width}>{label}</InputLabel>}
<Icon>{icon && icon}</Icon>
</InputWrapper>
);
};
export default ControlledInput;

View File

@ -83,6 +83,7 @@ type InputProps = {
floatingLabel?: boolean; floatingLabel?: boolean;
placeholder?: string; placeholder?: string;
icon?: JSX.Element; icon?: JSX.Element;
type?: string;
autocomplete?: boolean; autocomplete?: boolean;
id?: string; id?: string;
name?: string; name?: string;
@ -96,6 +97,7 @@ const Input = React.forwardRef(
{ {
width = 'auto', width = 'auto',
variant = 'normal', variant = 'normal',
type = 'text',
autocomplete, autocomplete,
label, label,
placeholder, placeholder,
@ -125,6 +127,7 @@ const Input = React.forwardRef(
hasValue={hasValue} hasValue={hasValue}
ref={$ref} ref={$ref}
id={id} id={id}
type={type}
name={name} name={name}
onClick={onClick} onClick={onClick}
autoComplete={autocomplete ? 'on' : 'off'} autoComplete={autocomplete ? 'on' : 'off'}

View File

@ -22,24 +22,18 @@ type Props = {
}; };
const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => { const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const $fieldName = useRef<HTMLInputElement>(null);
const [currentLabel, setCurrentLabel] = useState(''); const [currentLabel, setCurrentLabel] = useState('');
const [currentSearch, setCurrentSearch] = useState(''); const [currentSearch, setCurrentSearch] = useState('');
useEffect(() => {
if ($fieldName.current) {
$fieldName.current.focus();
}
}, []);
return ( return (
<> <>
<LabelSearch <LabelSearch
type="text" autoFocus
ref={$fieldName}
placeholder="search labels..."
onChange={e => {
setCurrentSearch(e.currentTarget.value);
}}
value={currentSearch} value={currentSearch}
variant="alternate"
width="100%"
onChange={e => setCurrentSearch(e.currentTarget.value)}
type="text"
placeholder="search labels..."
/> />
<Section> <Section>
<SectionTitle>Labels</SectionTitle> <SectionTitle>Labels</SectionTitle>

View File

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import Input from '../Input';
import ControlledInput from 'shared/components/ControlledInput';
export const Container = styled.div<{ export const Container = styled.div<{
invertY: boolean; invertY: boolean;
@ -78,35 +80,13 @@ export const Content = styled.div`
max-height: 632px; max-height: 632px;
`; `;
export const LabelSearch = styled.input` export const LabelSearch = styled(ControlledInput)`
box-sizing: border-box; margin: 12px 12px 0 12px;
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 Section = styled.div` export const Section = styled.div`
margin-top: 12px; margin-top: 12px;
margin: 12px 12px 0 12px;
`; `;
export const SectionTitle = styled.h4` export const SectionTitle = styled.h4`