Skip to content

Commit

Permalink
feat: add input component (#429)
Browse files Browse the repository at this point in the history
adds an input component with the following features:

- Controlled component
- Forwards the native input props
- Placeholder
- 2 sizes
- With / without labels
- trailing / leading component (inline or not)
- Required field
  • Loading branch information
Sebastien-Ahkrin authored Jan 3, 2023
1 parent 8e2351f commit 438bb50
Show file tree
Hide file tree
Showing 6 changed files with 432 additions and 0 deletions.
168 changes: 168 additions & 0 deletions src/components/forms/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import styled from '@emotion/styled';
import { InputHTMLAttributes, ReactNode } from 'react';

import { useFieldsContext } from './context/FieldsContext';

interface StyledProps {
variant: 'default' | 'small';
hasLeading: boolean;
hasTrailing: boolean;
}

function getSpecialSize(props: Pick<StyledProps, 'variant'>) {
return {
fontSize: props.variant === 'small' ? '1em' : '1.125em',
lineHeight: props.variant === 'small' ? '15px' : '17px',
};
}

const LabelStyled = styled.label<StyledProps>`
padding: ${(props) =>
props.variant === 'default'
? props.hasTrailing
? '4px 11px 4px 11px'
: '4px 11px'
: props.hasTrailing
? '0px 7px 0px 7px'
: '0 7px'};
font-size: ${(props) => getSpecialSize(props).fontSize};
line-height: ${(props) => getSpecialSize(props).lineHeight};
background-color: white;
border-width: 1px;
align-items: center;
flex-direction: row;
flex: 1 1 0%;
display: flex;
position: relative;
border-top-right-radius: ${(props) =>
props.hasLeading && !props.hasTrailing && '0.375rem'};
border-bottom-right-radius: ${(props) =>
props.hasLeading && !props.hasTrailing && '0.375rem'};
border-top-left-radius: ${(props) =>
props.hasTrailing && !props.hasLeading && '0.375rem'};
border-bottom-left-radius: ${(props) =>
props.hasTrailing && !props.hasLeading && '0.375rem'};
border-radius: ${(props) =>
!props.hasLeading && !props.hasTrailing && '0.375rem'};
border-color: var(--custom-border-color);
`;

const GroupStyled = styled.div`
display: flex;
border-radius: 0.375rem;
margin-top: 0.25rem;
--custom-border-color: rgb(217, 217, 217);
:hover,
:focus-within {
--custom-border-color: #4096ff;
}
`;

const InputStyled = styled.input`
padding: 0;
flex: 1 1 0%;
border: none;
position: relative;
outline: none;
`;

const LeadingAddonStyled = styled.div`
padding-left: 0.75rem;
padding-right: 0.75rem;
border-right-width: 0px;
border-width: 1px;
border-top-left-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
align-items: center;
display: inline-flex;
border-right: none;
border-color: var(--custom-border-color);
`;

const TrailingAddonStyled = styled.div`
padding-left: 0.75rem;
padding-right: 0.75rem;
border-left-width: 0px;
border-width: 1px;
border-top-right-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
align-items: center;
display: inline-flex;
border-left: none;
border-color: var(--custom-border-color);
`;

const LeadingInlineAddonStyled = styled.div`
display: flex;
align-items: center;
padding-right: 0.5rem;
`;

const TrailingInlineAddonStyled = styled.div`
display: flex;
align-items: center;
padding-left: 0.5rem;
`;

interface RenderAddon {
addon: ReactNode;
inline?: boolean;
}

export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
variant?: 'default' | 'small';

leadingAddon?: RenderAddon;
trailingAddon?: RenderAddon;
}

export function Input(props: InputProps) {
const { variant, trailingAddon, leadingAddon, ...otherProps } = props;

const { name, variant: contextVariant } = useFieldsContext();

const hasLeading = (leadingAddon && !leadingAddon.inline) || false;
const hasTrailing = (trailingAddon && !trailingAddon.inline) || false;

return (
<GroupStyled>
{leadingAddon && !leadingAddon.inline && (
<LeadingAddonStyled>{leadingAddon.addon}</LeadingAddonStyled>
)}
<LabelStyled
variant={variant || contextVariant}
hasLeading={hasLeading}
hasTrailing={hasTrailing}
>
{leadingAddon?.inline && (
<LeadingInlineAddonStyled>
{leadingAddon.addon}
</LeadingInlineAddonStyled>
)}
<InputStyled id={name} name={name} {...otherProps} />
{trailingAddon?.inline && (
<TrailingInlineAddonStyled>
{trailingAddon.addon}
</TrailingInlineAddonStyled>
)}
</LabelStyled>
{trailingAddon && !trailingAddon.inline && (
<TrailingAddonStyled>{trailingAddon.addon}</TrailingAddonStyled>
)}
</GroupStyled>
);
}
59 changes: 59 additions & 0 deletions src/components/forms/context/FieldsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { createContext, ReactNode, useContext, useMemo } from 'react';

interface FieldContext {
name?: string;
variant: 'default' | 'small';
}

interface FieldProps {
name: string;
label: string;
children: ReactNode;
variant?: 'default' | 'small';
required?: boolean;
}

const context = createContext<FieldContext | null>(null);

const styles = {
root: css`
display: flex;
flex-direction: row;
gap: 5px;
align-items: center;
`,
required: css`
color: red;
`,
};

export function useFieldsContext(): FieldContext {
const ctx = useContext(context);

if (!ctx) {
return { name: undefined, variant: 'default' };
}

return ctx;
}

export function Field(props: FieldProps) {
const { label, name, children, required, variant } = props;

const memoized = useMemo(() => {
return { name, variant: variant || 'default' };
}, [name, variant]);

return (
<context.Provider value={memoized}>
<div css={styles.root}>
<label htmlFor={name}>
{label} {required && <span css={styles.required}>*</span>}:{' '}
</label>
{children}
</div>
</context.Provider>
);
}
1 change: 1 addition & 0 deletions src/components/forms/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './FieldsContext';
2 changes: 2 additions & 0 deletions src/components/forms/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Input';
export * from './context';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './button/index';
export * from './color-picker/index';
export * from './drop-zone/index';
export * from './dropdown-menu/index';
export * from './forms/index';
export * from './fullscreen/index';
export * from './header/index';
export * from './hooks/index';
Expand Down
Loading

0 comments on commit 438bb50

Please sign in to comment.