Skip to content

Commit

Permalink
fix: Animation + Spacing improvements (DSN-000) (#345)
Browse files Browse the repository at this point in the history
<!-- You can erase any parts of this template not applicable to your Pull Request. -->

**Fixes or implements VF-XXX**

### Brief description. What is this change?

<!-- Build up some context for your teammates on the changes made here and potential tradeoffs made and/or highlight any topics for discussion -->

### Implementation details. How do you make this change?

<!-- Explain the way/approach you follow to make this change more deeply in order to help your teammates to understand much easier this change -->

### Setup information

<!-- Notes regarding local environment. These should note any new configurations, new environment variables, etc. -->

### Deployment Notes

<!-- Notes regarding deployment the contained body of work. These should note any db migrations, etc. -->

### Related PRs

<!-- List related PRs against other branches -->

- https://github.com/voiceflow/XXXXXXXXX/pull/123

### Checklist

- [ ] Breaking changes have been communicated, including:
    - New required environment variables
    - Renaming of interfaces (API routes, request/response interface, etc)
- [ ] New environment variables have [been deployed](https://www.notion.so/voiceflow/Add-Environment-Variables-be1b0136479f45f1adece7995a7adbfb)
- [ ] Appropriate tests have been written
    - Bug fixes are accompanied by an updated or new test
    - New features are accompanied by a new test
  • Loading branch information
mikaalnaik committed Nov 21, 2024
1 parent 25f8ce9 commit 1a69699
Show file tree
Hide file tree
Showing 15 changed files with 62 additions and 45 deletions.
3 changes: 2 additions & 1 deletion packages/chat/src/components/Header/styles.css.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { keyframes, style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { duration } from '@/styles/animations';
import { COLORS } from '@/styles/colors';
import { PALETTE } from '@/styles/colors.css';
import { FAMILY, hideTextOverflow } from '@/styles/font';
Expand All @@ -26,7 +27,7 @@ export const headerContainer = style({
backgroundColor: PALETTE.colors[500],
padding: '12px 16px 12px 20px',
height: parseInt(SIZES.sm, 10) + 24, // Add the top and bottom padding
animation: `${fadeIn} .3s ease-in`,
animation: `${fadeIn} ${duration.default} ease-in`,
});

export const headerInnerContainer = style({
Expand Down
8 changes: 6 additions & 2 deletions packages/chat/src/components/MessageContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { messageContainer } from './styles.css';
* Used to wrap a `SystemResponse` or a `UserResponse` object.
* A `SystemResponse` can have multiple messages in it.
*/
export const MessageContainer: React.FC<PropsWithChildren<{ className?: string }>> = ({ children, className }) => {
return <div className={clsx(messageContainer, className)}>{children}</div>;
export const MessageContainer: React.FC<PropsWithChildren<{ className?: string; isLast?: boolean }>> = ({
children,
className,
isLast,
}) => {
return <div className={clsx(messageContainer({ isLast }), className)}>{children}</div>;
};
17 changes: 13 additions & 4 deletions packages/chat/src/components/MessageContainer/styles.css.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

export const messageContainer = style({
position: 'relative',
margin: '16px 0',
export const messageContainer = recipe({
base: {
position: 'relative',
margin: '16px 0',
},
variants: {
isLast: {
true: {
marginBottom: 0,
},
},
},
});
2 changes: 1 addition & 1 deletion packages/chat/src/components/NewChat/NewChat.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const bottomSpacer = recipe({
height: '0px',
},
false: {
height: '8px',
height: '20px',
},
},
},
Expand Down
3 changes: 2 additions & 1 deletion packages/chat/src/components/NewFooter/NewFooter.css.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { keyframes, style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { duration } from '@/styles/animations';
import { COLORS } from '@/styles/colors';
import { PALETTE } from '@/styles/colors.css';
import { FAMILY } from '@/styles/font';
Expand Down Expand Up @@ -31,7 +32,7 @@ const fadeIn = keyframes({

export const footerContainer = style({
width: '100%',
animation: `${fadeInAndSlideUp} 0.5s ease-in-out`,
animation: `${fadeInAndSlideUp} ${duration.default} ease-in-out`,
});

export const buttonsContainer = style({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { indicatorContainer } from './Indicator.css';

export interface IndicatorProps {
avatar: string;
isLast?: boolean;
}

const Indicator: React.FC<IndicatorProps> = ({ avatar }) => (
<MessageContainer className={indicatorContainer}>
const Indicator: React.FC<IndicatorProps> = ({ avatar, isLast }) => (
<MessageContainer className={indicatorContainer} isLast={isLast}>
<Avatar avatar={avatar} />
<TypingIndicator />
</MessageContainer>
Expand Down
4 changes: 2 additions & 2 deletions packages/chat/src/components/SystemResponse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
if (!messages.length && !actions.length) return null;

return (
<MessageContainer>
<MessageContainer isLast={isLast}>
{visibleMessages.map((message, index) => {
const endConversation = message?.type === MessageType.END;
if (endConversation) {
Expand Down Expand Up @@ -141,7 +141,7 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
))}
</div>
)}
{showIndicator && <Indicator avatar={avatar} />}
{showIndicator && <Indicator avatar={avatar} isLast={isLast} />}
</MessageContainer>
);
};
5 changes: 1 addition & 4 deletions packages/chat/src/components/SystemResponse/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { keyframes, style } from '@vanilla-extract/css';

import { duration } from '@/styles/animations';
import { SIZES } from '@/styles/sizes';
import { transition } from '@/styles/transitions';

import { SMALL_AVATAR_SIZE } from '../Avatar/styles.css';

Expand All @@ -27,7 +26,7 @@ export const systemMessageContainer = style({
display: 'flex',
alignItems: 'flex-end',
marginBottom: 4,
animation: `${fadeInSlideUp} ${duration.default} ease-in`,
animation: `${fadeInSlideUp} ${duration.fast} ease-in`,
});

export const responseAvatar = style({
Expand Down Expand Up @@ -60,7 +59,5 @@ export const extensionMessageContainer = style({
export const feedbackContainer = style({
marginTop: 6,
zIndex: 1,
transition: transition(['opacity']),
animation: `${fadeInSlideUp} .2s ease-in`,
marginLeft: MESSAGE_PADDING + SMALL_AVATAR_SIZE - 6,
});
10 changes: 8 additions & 2 deletions packages/chat/src/components/UserResponse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@ export interface UserResponseProps {
* If provided, adds a caption and optional "debug" message with an action.
*/
debug?: DebugResponseProps;

/**
* If true, this is the last message in the chat.
*/
isLast?: boolean;
}

/**
* A user-sent text response.
*
* @see {@link https://voiceflow.github.io/react-chat/?path=/story/components-chat-userresponse--simple}
*/
export const UserResponse: React.FC<UserResponseProps> = ({ message, debug }) => {

export const UserResponse: React.FC<UserResponseProps> = ({ message, debug, isLast }) => {
useAutoScroll();

// TODO: Check this in different render modes
Expand All @@ -47,7 +53,7 @@ export const UserResponse: React.FC<UserResponseProps> = ({ message, debug }) =>
// const { config } = useContext(RuntimeStateAPIContext);

return (
<MessageContainer className={ClassName.USER_RESPONSE}>
<MessageContainer className={ClassName.USER_RESPONSE} isLast={isLast}>
<div className={messageContainer}>{message}</div>
{debug && (
<>
Expand Down
2 changes: 1 addition & 1 deletion packages/chat/src/components/UserResponse/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const messageContainer = style({
width: 'fit-content',
borderRadius: SIZES.radius.sm,
marginLeft: SMALL_AVATAR_SIZE + 12,
animation: `${fadeInSlideUp} ${duration.default} ease-out`,
animation: `${fadeInSlideUp} ${duration.fast} ease-out`,
});

export const debugMessage = style({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { keyframes, style } from '@vanilla-extract/css';

import { duration } from '@/styles/animations';
import { COLORS } from '@/styles/colors';
import { FAMILY } from '@/styles/font';

export const fadeIn = keyframes({
from: {
'0%': {
opacity: 0,
transform: 'translateY(-10px)',
},
to: {
'100%': {
opacity: 1,
transform: 'translateY(0)',
},
Expand All @@ -21,7 +22,8 @@ export const welcomeMessageContainer = style({
fontFamily: FAMILY,
padding: '48px 20px 28px 20px',
textAlign: 'center',
animation: `${fadeIn} 0.5s ease-in-out`,
opacity: 0,
animation: `${fadeIn} ${duration.default} ease-in-out ${duration.default} forwards`,
});

export const avatarContainer = style({
Expand Down
1 change: 1 addition & 0 deletions packages/chat/src/styles/animations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const duration = {
slow: '0.5s',
default: '0.15s',
fast: '0.08s',
fastest: '0.05s',
Expand Down
1 change: 0 additions & 1 deletion packages/chat/src/views/ChatWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ shadowRoot, chatAPI, rea
const toggleChat = () => {
if (isOpen) {
close();
setShowChatWindow(false);
} else {
open();
setTimeout(() => setShowChatWindow(true), 300);
Expand Down
27 changes: 9 additions & 18 deletions packages/chat/src/views/ChatWidget/styles.css.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { transition } from '@/styles/transitions';

export const CHAT_WIDTH = 400;
const MAX_CHAT_HEIGHT = 800;

Expand All @@ -27,17 +25,12 @@ export const widgetContainer = recipe({
},
},
});

export const chatContainer = style({
maxHeight: 0,
pointerEvents: 'none',
opacity: 0,
width: 0,
transition: transition(['opacity', 'width', 'max-height', 'transform'], {
duration: '300ms',
timingFunction: 'ease-in-out',
}),
transformOrigin: 'bottom right',
transform: 'scaleY(0)',
width: CHAT_WIDTH,
maxHeight: MAX_CHAT_HEIGHT,
pointerEvents: 'auto',

selectors: {
[`${widgetContainer.classNames.base} &`]: {
position: 'absolute',
Expand All @@ -46,16 +39,14 @@ export const chatContainer = style({
[`.${widgetContainer.classNames.variants.withChat.true} &`]: {
opacity: 1,
pointerEvents: 'auto',
width: CHAT_WIDTH,
maxHeight: MAX_CHAT_HEIGHT,
transform: 'scaleY(1)',
transform: 'translateY(0%)',
transition: 'transform 300ms cubic-bezier(0, 0.95, 0.1, 1), opacity 150ms linear',
},
[`.${widgetContainer.classNames.variants.withChat.false} &`]: {
opacity: 0,
pointerEvents: 'none',
width: 0,
maxHeight: 0,
transform: 'scaleY(0)',
transform: 'translateY(100%)',
transition: 'transform 300ms cubic-bezier(0.85, 0, 0.6, 1), opacity 150ms linear',
},
},
});
Expand Down
11 changes: 8 additions & 3 deletions packages/chat/src/views/ChatWindow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,19 @@ export const ChatWindow: React.FC<ChatWindowProps> = ({ isMobile }) => {
{state.session.turns.map((turn, turnIndex) => {
return match(turn)
.with({ type: TurnType.USER }, ({ id, ...props }) => {
return <UserResponse {...R.omit(props, ['type'])} key={id} />;
return (
<UserResponse
{...R.omit(props, ['type'])}
key={id}
isLast={turnIndex === state.session.turns.length - 1}
/>
);
})
.with({ type: TurnType.SYSTEM }, ({ id, ...props }) => (
<SystemResponse
key={id}
{...R.omit(props, ['type'])}
avatar={assistant.avatar}
isFirst={turnIndex === 0}
feedback={{
onClick: (feedback: FeedbackName) => {
runtime.feedback(feedback, props.messages, getPreviousUserTurn(turnIndex));
Expand All @@ -87,7 +92,7 @@ export const ChatWindow: React.FC<ChatWindowProps> = ({ isMobile }) => {
))
.exhaustive();
})}
{state.indicator && <Indicator avatar={assistant.avatar} />}
{state.indicator && <Indicator avatar={assistant.avatar} isLast={true} />}
</NewChat>
</div>
);
Expand Down

0 comments on commit 1a69699

Please sign in to comment.