Skip to content

Commit

Permalink
feat: form phase 2 (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcho authored Aug 29, 2024
1 parent 56d45fc commit ad1a032
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 116 deletions.
5 changes: 3 additions & 2 deletions src/components/CustomChannelComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const Root = styled.div<RootStyleProps>`
// @link: https://weblog.west-wind.com/posts/2023/Apr/17/Preventing-iOS-Safari-Textbox-Zooming
font-size: ${isIOSMobile ? 16 : 14}px;
font-family: 'Roboto', sans-serif;
line-height: 20px;
line-height: 24px; // because top & bottom padding is 8px each and the input height is 40px.
resize: none;
border: none;
outline: none;
Expand Down Expand Up @@ -112,7 +112,8 @@ const Root = styled.div<RootStyleProps>`
}
}
.sendbird-message-input--placeholder {
top: 9px;
top: 50%;
transform: translateY(-50%);
}
}
`;
Expand Down
3 changes: 2 additions & 1 deletion src/components/CustomTypingIndicatorBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const blink = keyframes`

const TypingDotsContainer = styled.div`
align-items: center;
border-radius: 16px;
border-radius: 100px;
display: flex;
gap: 6px;
justify-content: center;
Expand Down Expand Up @@ -67,6 +67,7 @@ const TypingDot = styled.span`
const Root = styled.div`
display: flex;
align-items: flex-end;
margin-top: 16px;
`;

function TypingDots() {
Expand Down
180 changes: 109 additions & 71 deletions src/components/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MessageFormItemStyle } from '@sendbird/chat/message';
import { ReactElement, ReactNode, useState } from 'react';
import { ReactElement, ReactNode } from 'react';
import styled from 'styled-components';

import Icon, { IconColors, IconTypes } from '@uikit/ui/Icon';
Expand Down Expand Up @@ -34,25 +34,26 @@ const OptionalText = styled.span`
const Root = styled.div<Pick<InputProps, 'errorMessage'>>`
padding-bottom: 12px;
width: 100%;
.sendbird-input .sendbird-input__input {
color: ${({ theme }) => theme.textColor.incomingMessage} !important;
height: fit-content;
background-color: ${({ theme }) => theme.bgColor.formInput} !important;
background-color: ${({ theme }) => theme.bgColor.formInput.default} !important;
border: ${({ theme, errorMessage }) =>
`solid 1px ${errorMessage ? theme.borderColor.formInput.error : theme.borderColor.formInput.default}`} !important;
::placeholder {
color: ${({ theme }) => theme.textColor.placeholder};
}
&:disabled {
pointer-events: none;
background-color: ${({ theme }) => theme.bgColor.formInputDisabled} !important;
background-color: ${({ theme }) => theme.bgColor.formInput.disabled} !important;
border: none !important;
}
&:focus {
border: ${({ theme }) => `solid 1px ${theme.borderColor.formInput.focus}`} !important;
outline: none;
box-shadow: 0 0 0 1px ${({ theme }) => theme.borderColor.formInput.focus};
}
&:active {
border: ${({ theme }) => `solid 1px ${theme.borderColor.formInput.active}`} !important;
}
Expand All @@ -64,6 +65,26 @@ interface ChipProps {
isSubmitted?: boolean;
}

interface PlaceholderProps {
numMaxLines?: number;
}

const Placeholder = styled.div<PlaceholderProps>`
width: calc(100% - 26px); // (12px side padding + 1px border) * 2 = 26px
height: calc(100% - 16px); // (7px padding + 1px border) * 2 = 16px
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: ${({ numMaxLines }) => numMaxLines ?? 1};
-webkit-box-orient: vertical;
position: absolute;
pointer-events: none;
top: 8px;
left: 13px;
font-size: 14px;
line-height: 1.43;
color: ${({ theme }) => theme.textColor.placeholder};
`;

const Chip = styled.div<ChipProps>`
border-radius: 100px;
padding: ${({ isSubmitted }) => (isSubmitted ? '6px 16px' : '5px 15px')};
Expand Down Expand Up @@ -170,7 +191,7 @@ const SubmittedTextInputContainer = styled.div<SubmittedTextInputContainerProps>
word-wrap: break-word;
width: calc(100% - 24px);
color: ${({ theme }) => theme.textColor.incomingMessage};
background-color: ${({ theme }) => theme.bgColor.formInputDisabled} !important;
background-color: ${({ theme }) => theme.bgColor.formInput.disabled} !important;
border: none;
pointer-events: none;
${({ isTextarea }) => {
Expand All @@ -189,6 +210,25 @@ const SubmittedTextInputContainer = styled.div<SubmittedTextInputContainerProps>
white-space: pre-wrap;
`;

interface SubmittedTextInputComponentProps {
layout: string;
currentValue: string;
isValid: boolean | undefined;
}

const SubmittedTextInputComponent = ({ layout, currentValue, isValid }: SubmittedTextInputComponentProps) => {
return (
<SubmittedTextInputContainer isTextarea={layout === 'textarea'}>
<SubmittedText>{currentValue}</SubmittedText>
{isValid && (
<CheckIconContainer>
<Icon type={IconTypes.DONE} fillColor={IconColors.SECONDARY_2} width="20px" height="20px" />
</CheckIconContainer>
)}
</SubmittedTextInputContainer>
);
};

const SubmittedText = styled.div`
width: calc(100% - 24px);
line-height: 20px;
Expand Down Expand Up @@ -216,21 +256,23 @@ const CheckIconForChip = styled(Icon)<CheckIconProps>`

const InputContainer = styled.div`
width: 100%;
position: relative;
`;

export interface InputProps {
name: string;
style: MessageFormItemStyle;
required?: boolean;
disabled?: boolean;
isValid?: boolean;
isSubmitted: boolean;
errorMessage: string | null;
values: string[];
placeHolder?: string;
onFocused?: (isFocus: boolean) => void;
isInvalidated: boolean;
isSubmitTried: boolean;
onChange: (values: string[]) => void;
isSubmitted: boolean;
layout: MessageFormItemStyle['layout'];
required?: boolean;
isValid?: boolean;
placeHolder?: string;
onFocused?: (isFocus: boolean) => void;
}

type ChipState = 'default' | 'selected' | 'submittedDefault' | 'submittedSelected';
Expand All @@ -245,10 +287,11 @@ const FormInput = (props: InputProps) => {
layout,
name,
required,
disabled,
errorMessage,
isValid,
values,
isInvalidated,
isSubmitTried,
style,
onFocused,
onChange,
Expand All @@ -260,15 +303,11 @@ const FormInput = (props: InputProps) => {
const { min = 1, max = 1 } = resultCount ?? {};
const chipDataList: ChipData[] = getInitialChipDataList();

const [isFocused, setIsFocused] = useState(false);

const handleFocus = () => {
setIsFocused(true);
onFocused?.(true);
};

const handleBlur = () => {
setIsFocused(false);
onFocused?.(false);
};

Expand All @@ -291,14 +330,9 @@ const FormInput = (props: InputProps) => {
let newDraftedValues: string[];
if (min === 1 && max === 1) {
// Single select
newDraftedValues = [chipDataList[index].option];
newDraftedValues = chipDataList[index].state === 'selected' ? [] : [chipDataList[index].option];
} else {
/**
* Multi select case
* Upon chip click, if it is:
* 1. not selected and can select more -> select the chip. Keep other selected chips as is.
* 2. already selected -> deselect the chip. Keep other selected chips as is.
*/
// Multi select
newDraftedValues = chipDataList.reduce((acc, chipData, i) => {
if (i === index) {
if (chipData.state === 'default' && values.length < max) {
Expand All @@ -312,7 +346,7 @@ const FormInput = (props: InputProps) => {
return acc;
}, [] as string[]);
}
if (newDraftedValues.length > 0) onChange(newDraftedValues);
onChange(newDraftedValues);
};

return (
Expand Down Expand Up @@ -356,28 +390,26 @@ const FormInput = (props: InputProps) => {
return (
<InputContainer>
{isSubmitted ? (
<SubmittedTextInputContainer isTextarea={true}>
<SubmittedText>{currentValue}</SubmittedText>
{isValid && (
<CheckIconContainer>
<Icon type={IconTypes.DONE} fillColor={IconColors.SECONDARY_2} width="20px" height="20px" />
</CheckIconContainer>
)}
</SubmittedTextInputContainer>
<>
<SubmittedTextInputComponent layout={layout} currentValue={currentValue} isValid={isValid} />
{!currentValue && <Placeholder>No Response</Placeholder>}
</>
) : (
<TextArea
className="sendbird-input__input"
required={required}
disabled={disabled}
value={currentValue}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(event) => {
const value = event.target.value;
onChange(value ? [value] : []);
}}
placeholder={!disabled ? placeHolder : ''}
/>
<>
<TextArea
className="sendbird-input__input"
required={required}
disabled={isSubmitted}
value={currentValue}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(event) => {
const value = event.target.value;
onChange(value ? [value] : []);
}}
/>
{placeHolder && !currentValue && <Placeholder numMaxLines={4}>{placeHolder}</Placeholder>}
</>
)}
</InputContainer>
);
Expand All @@ -390,30 +422,29 @@ const FormInput = (props: InputProps) => {
return (
<InputContainer>
{isSubmitted ? (
<SubmittedTextInputContainer>
<SubmittedText>{currentValue}</SubmittedText>
{isValid && (
<CheckIconContainer>
<Icon type={IconTypes.DONE} fillColor={IconColors.SECONDARY_2} width="20px" height="20px" />
</CheckIconContainer>
)}
</SubmittedTextInputContainer>
<>
<SubmittedTextInputComponent layout={layout} currentValue={currentValue} isValid={isValid} />
{!currentValue && <Placeholder>No Response</Placeholder>}
</>
) : (
<Input
type={layout}
className="sendbird-input__input"
name={name}
required={required}
disabled={disabled}
value={currentValue}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(event) => {
const value = event.target.value;
onChange(value ? [value] : []);
}}
placeholder={!disabled ? placeHolder : ''}
/>
<>
<Input
type={layout === 'number' ? 'text' : layout}
inputMode={layout === 'number' ? 'numeric' : 'text'}
className="sendbird-input__input"
name={name}
required={required}
disabled={isSubmitted}
value={currentValue}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(event) => {
const value = event.target.value;
onChange(value ? [value] : []);
}}
/>
{placeHolder && !currentValue && <Placeholder>{placeHolder}</Placeholder>}
</>
)}
</InputContainer>
);
Expand All @@ -423,7 +454,14 @@ const FormInput = (props: InputProps) => {
}
}
})()}
{!isFocused && errorMessage && <ErrorLabel type={LabelTypography.CAPTION_3}>{errorMessage}</ErrorLabel>}
{/*
If there is an error message for the input, display it IFF any of below is true:
1. Submit button has been clicked after initial load.
2. Input has been focused out.
*/}
{errorMessage && (isSubmitTried || isInvalidated) && (
<ErrorLabel type={LabelTypography.CAPTION_3}>{errorMessage}</ErrorLabel>
)}
</div>
</Root>
);
Expand Down
30 changes: 30 additions & 0 deletions src/components/messages/FallbackUserMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import styled from 'styled-components';

interface FallbackUserMessageProps {
text: string;
}

const Container = styled.div`
position: relative;
display: inline-block;
box-sizing: border-box;
padding: 8px 12px;
border-radius: 16px;
background-color: ${({ theme }) => theme.bgColor.incomingMessage};
`;

const Label = styled.div`
color: ${({ theme }) => theme.textColor.placeholder};
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
`;

export default function FallbackUserMessage({ text }: FallbackUserMessageProps) {
return (
<Container>
<Label>{text}</Label>
</Container>
);
}
Loading

0 comments on commit ad1a032

Please sign in to comment.