Skip to content
This repository has been archived by the owner on Feb 5, 2025. It is now read-only.

Commit

Permalink
fix: carousel hitbox edges fix (COR-4364) (#486)
Browse files Browse the repository at this point in the history
**Fixes or implements VF-4364**

### Brief description. What is this change?

Fixed the carousel button hitbox issue by making the 'surrounding' button component a little bit bigger so when the image is enlarged it will still be inside of the 'hitbox'.

Added the `NewChat` component wrapper to the Carousel story, so that it looks good in storybook.
Added a `2px` left margin to the first carousel card to align it with the other messages displayed above or below it.

Also updated `@vanilla-extract/css` and `@vanilla-extract/vite` packages.
Initially did this cause I thought there was a vanilla-extract related bug - there wasn't, but the update is still helpful to stay updated.
  • Loading branch information
gillyb committed Jan 3, 2025
1 parent 9443b34 commit eb119f0
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 124 deletions.
3 changes: 2 additions & 1 deletion apps/documentation/src/components/ChatScript/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const ChatScript = ({ projectID, embedded = false }: { projectID: string;
verify: { projectID: "${projectID}" },
assistant: {
stylesheet: '../../bundle/style.css',
}
},
versionID: 'production'
${embedded ? ', render: { mode: "embedded", target: document.getElementById("chat_embed") }' : ''}
});
};
Expand Down
4 changes: 2 additions & 2 deletions packages/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"test:unit": "yarn g:vitest run --coverage"
},
"dependencies": {
"@vanilla-extract/css": "1.15.5",
"@vanilla-extract/css": "1.16.1",
"@vanilla-extract/recipes": "0.5.5",
"@voiceflow/base-types": "2.113.1",
"@voiceflow/dtos-interact": "1.10.0",
Expand Down Expand Up @@ -123,7 +123,7 @@
"@types/react-speech-recognition": "^3.9.5",
"@types/react-syntax-highlighter": "15.5.13",
"@vanilla-extract/dynamic": "2.1.2",
"@vanilla-extract/vite-plugin": "4.0.15",
"@vanilla-extract/vite-plugin": "4.0.18",
"@vitejs/plugin-react": "4.2.1",
"@voiceflow/test-common": "1.10.3",
"chromatic": "11.2.0",
Expand Down
135 changes: 83 additions & 52 deletions packages/chat/src/components/Carousel/Carousel.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RuntimeProvider } from '@/contexts';
import { MOCK_IMAGE } from '@/fixtures';
import { DEFAULT_AVATAR, RenderMode } from '@/main';
import { WithDefaultPalette } from '@/storybook/decorators';
import { widgetContainer } from '@/views/ChatWidget/styles.css';

import { NewChat } from '../NewChat';
import { MessageType } from '../SystemResponse/constants';
Expand All @@ -30,7 +31,32 @@ const meta: Meta<typeof Carousel> = {
...DEFAULT_WIDGET_SETTINGS,
}}
>
{Story()}
<div style={{ width: '380px' }} className={widgetContainer.classNames.variants.withChat.true}>
<NewChat
welcomeMessageProps={{
enabled: true,
title: 'Your awesome assistant',
description: 'Im hot, youre not, deal with it',
}}
headerProps={{
showImage: true,
title: 'Your awesome assistant',
}}
footerProps={{
showPoweredBy: true,
extraLinkText: 'Privacy',
extraLinkUrl: 'https://voiceflow.com',
messageInputProps: {
onSubmit: async (_) => Promise.resolve(),
placeholder: 'Message...',
},
}}
isLoading={false}
hasEnded={false}
>
{Story()}
</NewChat>
</div>
</RuntimeProvider>
),
WithDefaultPalette,
Expand Down Expand Up @@ -72,64 +98,69 @@ const MULTIPLE_CARDS = [

export const Default: Story = {
render: () => (
<div style={{ width: '380px' }}>
<NewChat
welcomeMessageProps={{
enabled: true,
title: 'Your awesome assistant',
description: 'Im hot, youre not, deal with it',
<>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.CAROUSEL,
cards: MULTIPLE_CARDS,
}}
headerProps={{
showImage: true,
title: 'Your awesome assistant',
withImage={false}
/>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.TEXT,
text: 'Do you like this carousel ?',
}}
footerProps={{
showPoweredBy: true,
extraLinkText: 'Privacy',
extraLinkUrl: 'https://voiceflow.com',
messageInputProps: {
onSubmit: async (_) => Promise.resolve(),
placeholder: 'Message...',
},
}}
isLoading={false}
hasEnded={false}
>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.CAROUSEL,
cards: MULTIPLE_CARDS,
}}
withImage={false}
/>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.TEXT,
text: 'Do you like this carousel ?',
}}
withImage={true}
/>
</NewChat>
</div>
withImage={true}
/>
</>
),
};

export const SingleCard: Story = {
args: {
cards: [FIRST_CARD],
},
render: () => (
<>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.CAROUSEL,
cards: [FIRST_CARD],
}}
withImage={false}
/>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.TEXT,
text: 'Do you like this carousel ?',
}}
withImage={true}
/>
</>
),
};

export const MultipleCards: Story = {
args: {
cards: MULTIPLE_CARDS,
},
};

export const WithControls: Story = {
args: {
cards: MULTIPLE_CARDS,
},
render: () => (
<>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.CAROUSEL,
cards: MULTIPLE_CARDS,
}}
withImage={false}
/>
<SystemMessage
avatar={DEFAULT_AVATAR}
message={{
type: MessageType.TEXT,
text: 'Do you like this carousel ?',
}}
withImage={true}
/>
</>
),
};
14 changes: 11 additions & 3 deletions packages/chat/src/components/Carousel/CarouselButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import clsx from 'clsx';
import type { MouseEventHandler } from 'react';
import { forwardRef } from 'react';

import { Icon } from '@/components/Icon';

import { carouselButton, rotate180 } from './carouselButtonStyles.css';
import { buttonReset } from '../Button/reset.css';
import { buttonWrapper, carouselButton, rotate180 } from './carouselButtonStyles.css';

export interface CarouselButtonProps {
/**
Expand Down Expand Up @@ -33,8 +35,14 @@ export interface CarouselButtonProps {
*/
export const CarouselButton = forwardRef<HTMLButtonElement, CarouselButtonProps>(
({ onClick, visible, direction, noAvatar }, ref) => (
<button ref={ref} className={carouselButton({ visible, direction, withAvatar: !noAvatar })} onClick={onClick}>
<Icon svg="arrowRight" className={direction === 'left' ? rotate180 : ''} />
<button
ref={ref}
className={clsx(buttonReset, buttonWrapper({ direction, visible, withAvatar: !noAvatar }))}
onClick={onClick}
>
<div className={carouselButton()}>
<Icon svg="arrowRight" className={direction === 'left' ? rotate180 : ''} />
</div>
</button>
)
);
80 changes: 41 additions & 39 deletions packages/chat/src/components/Carousel/carouselButtonStyles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,46 +55,17 @@ const fadeOutToRight = keyframes({
},
});

export const carouselButton = recipe({
base: [
buttonStyles({ round: true }),
{
height: BUTTON_SIZE,
width: BUTTON_SIZE,
color: COLORS.NEUTRAL_DARK[100],
border: `solid 1px ${COLORS.NEUTRAL_LIGHT[100]}`,
backgroundColor: COLORS.white,
boxShadow: '0px 3px 4px 0px rgba(0, 0, 0, 0.02), 0px 8px 42px -16px rgba(0, 0, 0, 0.06)',
transition: transition(['opacity', 'color', 'transform']),
':hover': {
color: COLORS.NEUTRAL_DARK[600],
transform: 'scale(1.15)',
},
':active': {
color: COLORS.NEUTRAL_DARK[800],
transform: 'scale(0.8)',
},

// When the buttons are inside a carousel
[`.${carouselContainer} &`]: {
position: 'absolute',
top: '64px',
},
},
],
export const buttonWrapper = recipe({
base: {
backgroundColor: 'transparent',
height: BUTTON_SIZE + 6,
width: BUTTON_SIZE + 6,
padding: 2,
position: 'absolute',
top: '64px',
},

variants: {
visible: {
true: {
opacity: 1,
pointerEvents: 'auto',
},
false: {
opacity: 0,
pointerEvents: 'none',
},
},

direction: {
right: {
[`.${carouselContainer} &`]: {
Expand All @@ -107,11 +78,20 @@ export const carouselButton = recipe({
},
},
},
visible: {
true: {
opacity: 1,
pointerEvents: 'auto',
},
false: {
opacity: 0,
pointerEvents: 'none',
},
},
withAvatar: {
false: {},
},
},

compoundVariants: [
{
variants: {
Expand Down Expand Up @@ -179,6 +159,28 @@ export const carouselButton = recipe({
],
});

export const carouselButton = recipe({
base: [
buttonStyles({ round: true }),
{
borderRadius: '50%',
color: COLORS.NEUTRAL_DARK[100],
border: `solid 1px ${COLORS.NEUTRAL_LIGHT[100]}`,
backgroundColor: COLORS.white,
boxShadow: '0px 3px 4px 0px rgba(0, 0, 0, 0.02), 0px 8px 42px -16px rgba(0, 0, 0, 0.06)',
transition: transition(['opacity', 'color', 'transform']),
':hover': {
color: COLORS.NEUTRAL_DARK[600],
transform: 'scale(1.15)',
},
':active': {
color: COLORS.NEUTRAL_DARK[800],
transform: 'scale(0.8)',
},
},
],
});

export const rotate180 = style({
transform: 'rotate(180deg)',
});
3 changes: 2 additions & 1 deletion packages/chat/src/components/Carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
cardsContainer,
cardsInnerContainer,
carouselContainer,
firstCard,
GUTTER_WIDTH,
lastCardSpacer,
} from './styles.css';
Expand Down Expand Up @@ -65,7 +66,7 @@ export const Carousel: React.FC<CarouselProps> = ({ cards, avatar, withImage, fe
{avatar && <Avatar avatar={avatar} className={clsx(withImage ? '' : hide, responseAvatar, avatarStyle)} />}
{cards.map((card, i) => (
<div
className={fadeInAndUp}
className={clsx(fadeInAndUp, i === 0 ? firstCard : '')}
key={i}
style={{
animationDelay: `${i * 0.1}s`,
Expand Down
4 changes: 4 additions & 0 deletions packages/chat/src/components/Carousel/styles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const cardsContainer = style({
paddingRight: `${DIALOG_PADDING}px`,
});

export const firstCard = style({
marginLeft: 2,
});

export const cardsInnerContainer = style({
display: 'flex',
alignItems: 'start',
Expand Down
Loading

0 comments on commit eb119f0

Please sign in to comment.