Skip to content
This repository was archived by the owner on Aug 5, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions packages/react-chat/src/components/AgentCard/AgentCard.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react';

import AgentCard from '.';

const meta: Meta<typeof AgentCard> = {
title: 'Components/AgentCard',
component: AgentCard,
tags: ['autodocs'],
} satisfies Meta<typeof AgentCard>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Simple: Story = {
args: {
children: <div style={{ padding: '16px' }}>Sample content inside the AgentCard</div>,
},
};

export const WithComplexContent: Story = {
args: {
children: (
<div style={{ padding: '16px' }}>
<h3 style={{ margin: '0 0 8px 0' }}>Dashboard Title</h3>
<p style={{ margin: 0 }}>This is a sample dashboard card with multiple elements inside.</p>
<button style={{ marginTop: '12px' }}>Action Button</button>
</div>
),
},
};

/**
* @see {@link https://voiceflow.github.io/react-chat/?path=/story/components-dashboard-card--simple}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Storybook documentation link contains an incorrect path - it points to components-dashboard-card but should point to components-agent-card to match the component name. The correct link should be:

https://voiceflow.github.io/react-chat/?path=/story/components-agent-card--simple

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

*/
108 changes: 108 additions & 0 deletions packages/react-chat/src/components/AgentCard/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it } from 'vitest';
import '@testing-library/jest-dom';

import { ClassName } from '../../../constants';
import AgentCard from '..';

describe('AgentCard', () => {
const testContent = 'Test Content';

it('renders children correctly', () => {
render(<AgentCard>{testContent}</AgentCard>);
expect(screen.getByText(testContent)).toBeInTheDocument();
});

it('passes through additional props', () => {
const testId = 'test-agent-card';
const ariaLabel = 'Agent Card';
render(<AgentCard data-testid={testId} aria-label={ariaLabel}>{testContent}</AgentCard>);

const card = screen.getByTestId(testId);
expect(card).toHaveAttribute('aria-label', ariaLabel);
});

it('applies custom className', () => {
const customClass = 'custom-class';
const { container } = render(<AgentCard className={customClass}>{testContent}</AgentCard>);
expect(container.firstChild).toHaveClass(customClass);
expect(container.firstChild).toHaveClass(ClassName.AGENT_CARD);
});

it('maintains correct styling', () => {
const { container } = render(<AgentCard>{testContent}</AgentCard>);
const card = container.firstChild as HTMLElement;

expect(card).toHaveStyle({
borderRadius: '6px',
display: 'flex',
flexDirection: 'column',
boxSizing: 'border-box',
overflow: 'hidden',
backgroundColor: 'rgb(255, 255, 255)', // $white
});
});

it('applies box shadow correctly', () => {
const { container } = render(<AgentCard>{testContent}</AgentCard>);
const card = container.firstChild as HTMLElement;

const boxShadow = window.getComputedStyle(card).boxShadow;
expect(boxShadow).toContain('0px 1px 3px 1px');
expect(boxShadow).toContain('0px 4px 8px -6px');
expect(boxShadow).toContain('0px 1px 5px -4px');
expect(boxShadow).toContain('0px 0px 0px 1px');
expect(boxShadow).toContain('0px 1px 0px 0px');
});

it('handles complex nested content', () => {
const complexContent = (
<div data-testid="complex-content">
<h3>Title</h3>
<p>Description</p>
<button>Action</button>
</div>
);

render(<AgentCard>{complexContent}</AgentCard>);
const content = screen.getByTestId('complex-content');
expect(content).toBeInTheDocument();
expect(screen.getByText('Title')).toBeInTheDocument();
expect(screen.getByText('Description')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeInTheDocument();
});

it('handles empty content gracefully', () => {
const { container } = render(<AgentCard />);
expect(container.firstChild).toBeInTheDocument();
expect(container.firstChild).toHaveStyle({
display: 'flex',
flexDirection: 'column',
});
});

it('maintains layout with different content types', () => {
const { rerender, container } = render(<AgentCard>Short text</AgentCard>);
expect(container.firstChild).toHaveStyle({
display: 'flex',
flexDirection: 'column',
});

rerender(
<AgentCard>
<div style={{ padding: '20px' }}>
<h1>Large Content</h1>
<p>With multiple paragraphs</p>
<p>And more content</p>
</div>
</AgentCard>
);

expect(container.firstChild).toHaveStyle({
display: 'flex',
flexDirection: 'column',
});
});
});
21 changes: 21 additions & 0 deletions packages/react-chat/src/components/AgentCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

import { Container } from './styled';

export interface AgentCardProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* The content to be rendered inside the card.
*/
children?: React.ReactNode;
}

const AgentCard: React.FC<AgentCardProps> = ({ children, ...props }) => (
<Container {...props}>{children}</Container>
);

/**
* A flexible card component with a rounded rectangle shape and subtle shadow effects.
*/
export default Object.assign(AgentCard, {
Container,
});
21 changes: 21 additions & 0 deletions packages/react-chat/src/components/AgentCard/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ClassName } from '@/constants';
import { tagFactory } from '@/hocs';
import { styled } from '@/styles';

const tag = tagFactory(ClassName.AGENT_CARD);

export const Container = styled(tag('div'), {
borderRadius: '6px',
boxShadow: `
0px 1px 3px 1px #161A1E03,
0px 4px 8px -6px #161A1E14,
0px 1px 5px -4px #161A1E14,
0px 0px 0px 1px #161A1E0A,
0px 1px 0px 0px #161A1E05
`,
display: 'flex',
flexDirection: 'column',
boxSizing: 'border-box',
overflow: 'hidden',
backgroundColor: '$white',
});
1 change: 1 addition & 0 deletions packages/react-chat/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum ClassName {
PROACTIVE_CLOSE = 'vfrc-proactive-close',
PROACTIVE_MESSAGE = 'vfrc-proactive-message',
PROACTIVE = 'vfrc-proactive',
AGENT_CARD = 'vfrc-agent-card',
}

export const DEVICE_INFO = Bowser.parse(window.navigator.userAgent);