From 569b624a00207ef9dc21ff773c9d43504cde48e6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:20:48 +0000 Subject: [PATCH 1/3] feat(react-chat): add DashboardCard component - Add new DashboardCard component with 6px border radius and specified box shadows - Create story file demonstrating simple and complex content usage - Add DASHBOARD_CARD to ClassName enum Co-Authored-By: mikaal.naik@voiceflow.com --- .../DashboardCard/DashboardCard.story.tsx | 34 +++++++++++++++++++ .../src/components/DashboardCard/index.tsx | 21 ++++++++++++ .../src/components/DashboardCard/styled.ts | 21 ++++++++++++ packages/react-chat/src/constants.ts | 1 + 4 files changed, 77 insertions(+) create mode 100644 packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx create mode 100644 packages/react-chat/src/components/DashboardCard/index.tsx create mode 100644 packages/react-chat/src/components/DashboardCard/styled.ts diff --git a/packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx b/packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx new file mode 100644 index 0000000000..f8e34d5888 --- /dev/null +++ b/packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import DashboardCard from '.'; + +const meta: Meta = { + title: 'Components/DashboardCard', + component: DashboardCard, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Simple: Story = { + args: { + children:
Sample content inside the DashboardCard
, + }, +}; + +export const WithComplexContent: Story = { + args: { + children: ( +
+

Dashboard Title

+

This is a sample dashboard card with multiple elements inside.

+ +
+ ), + }, +}; + +/** + * @see {@link https://voiceflow.github.io/react-chat/?path=/story/components-dashboard-card--simple} + */ diff --git a/packages/react-chat/src/components/DashboardCard/index.tsx b/packages/react-chat/src/components/DashboardCard/index.tsx new file mode 100644 index 0000000000..cd3203c2cd --- /dev/null +++ b/packages/react-chat/src/components/DashboardCard/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { Container } from './styled'; + +export interface DashboardCardProps extends React.HTMLAttributes { + /** + * The content to be rendered inside the card. + */ + children?: React.ReactNode; +} + +const DashboardCard: React.FC = ({ children, ...props }) => ( + {children} +); + +/** + * A flexible card component with a rounded rectangle shape and subtle shadow effects. + */ +export default Object.assign(DashboardCard, { + Container, +}); diff --git a/packages/react-chat/src/components/DashboardCard/styled.ts b/packages/react-chat/src/components/DashboardCard/styled.ts new file mode 100644 index 0000000000..d28657a700 --- /dev/null +++ b/packages/react-chat/src/components/DashboardCard/styled.ts @@ -0,0 +1,21 @@ +import { ClassName } from '@/constants'; +import { tagFactory } from '@/hocs'; +import { styled } from '@/styles'; + +const tag = tagFactory(ClassName.DASHBOARD_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', +}); diff --git a/packages/react-chat/src/constants.ts b/packages/react-chat/src/constants.ts index 33f988464d..47ff537e64 100644 --- a/packages/react-chat/src/constants.ts +++ b/packages/react-chat/src/constants.ts @@ -29,6 +29,7 @@ export enum ClassName { PROACTIVE_CLOSE = 'vfrc-proactive-close', PROACTIVE_MESSAGE = 'vfrc-proactive-message', PROACTIVE = 'vfrc-proactive', + DASHBOARD_CARD = 'vfrc-dashboard-card', } export const DEVICE_INFO = Bowser.parse(window.navigator.userAgent); From 4351adc11a7cdf7fb380b808034b89ac98963646 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:42:33 +0000 Subject: [PATCH 2/3] test(react-chat): add comprehensive tests for DashboardCard component Co-Authored-By: mikaal.naik@voiceflow.com --- .../DashboardCard/__tests__/index.test.tsx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx diff --git a/packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx b/packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx new file mode 100644 index 0000000000..cb85527128 --- /dev/null +++ b/packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx @@ -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 DashboardCard from '..'; + +describe('DashboardCard', () => { + const testContent = 'Test Content'; + + it('renders children correctly', () => { + render({testContent}); + expect(screen.getByText(testContent)).toBeInTheDocument(); + }); + + it('passes through additional props', () => { + const testId = 'test-dashboard-card'; + const ariaLabel = 'Dashboard Card'; + render({testContent}); + + const card = screen.getByTestId(testId); + expect(card).toHaveAttribute('aria-label', ariaLabel); + }); + + it('applies custom className', () => { + const customClass = 'custom-class'; + const { container } = render({testContent}); + expect(container.firstChild).toHaveClass(customClass); + expect(container.firstChild).toHaveClass(ClassName.DASHBOARD_CARD); + }); + + it('maintains correct styling', () => { + const { container } = render({testContent}); + 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({testContent}); + 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 = ( +
+

Title

+

Description

+ +
+ ); + + render({complexContent}); + 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(); + expect(container.firstChild).toBeInTheDocument(); + expect(container.firstChild).toHaveStyle({ + display: 'flex', + flexDirection: 'column', + }); + }); + + it('maintains layout with different content types', () => { + const { rerender, container } = render(Short text); + expect(container.firstChild).toHaveStyle({ + display: 'flex', + flexDirection: 'column', + }); + + rerender( + +
+

Large Content

+

With multiple paragraphs

+

And more content

+
+
+ ); + + expect(container.firstChild).toHaveStyle({ + display: 'flex', + flexDirection: 'column', + }); + }); +}); From 75dcd2ff818b15628874e8a61ef8c861f8084d6c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:47:28 +0000 Subject: [PATCH 3/3] refactor(react-chat): rename DashboardCard to AgentCard Co-Authored-By: mikaal.naik@voiceflow.com --- .../AgentCard.story.tsx} | 12 ++++---- .../__tests__/index.test.tsx | 30 +++++++++---------- .../{DashboardCard => AgentCard}/index.tsx | 6 ++-- .../{DashboardCard => AgentCard}/styled.ts | 2 +- packages/react-chat/src/constants.ts | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) rename packages/react-chat/src/components/{DashboardCard/DashboardCard.story.tsx => AgentCard/AgentCard.story.tsx} (77%) rename packages/react-chat/src/components/{DashboardCard => AgentCard}/__tests__/index.test.tsx (75%) rename packages/react-chat/src/components/{DashboardCard => AgentCard}/index.tsx (60%) rename packages/react-chat/src/components/{DashboardCard => AgentCard}/styled.ts (90%) diff --git a/packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx b/packages/react-chat/src/components/AgentCard/AgentCard.story.tsx similarity index 77% rename from packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx rename to packages/react-chat/src/components/AgentCard/AgentCard.story.tsx index f8e34d5888..a82b0ed7f4 100644 --- a/packages/react-chat/src/components/DashboardCard/DashboardCard.story.tsx +++ b/packages/react-chat/src/components/AgentCard/AgentCard.story.tsx @@ -1,19 +1,19 @@ import type { Meta, StoryObj } from '@storybook/react'; -import DashboardCard from '.'; +import AgentCard from '.'; -const meta: Meta = { - title: 'Components/DashboardCard', - component: DashboardCard, +const meta: Meta = { + title: 'Components/AgentCard', + component: AgentCard, tags: ['autodocs'], -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; export const Simple: Story = { args: { - children:
Sample content inside the DashboardCard
, + children:
Sample content inside the AgentCard
, }, }; diff --git a/packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx b/packages/react-chat/src/components/AgentCard/__tests__/index.test.tsx similarity index 75% rename from packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx rename to packages/react-chat/src/components/AgentCard/__tests__/index.test.tsx index cb85527128..35a7b6873a 100644 --- a/packages/react-chat/src/components/DashboardCard/__tests__/index.test.tsx +++ b/packages/react-chat/src/components/AgentCard/__tests__/index.test.tsx @@ -5,20 +5,20 @@ import { describe, expect, it } from 'vitest'; import '@testing-library/jest-dom'; import { ClassName } from '../../../constants'; -import DashboardCard from '..'; +import AgentCard from '..'; -describe('DashboardCard', () => { +describe('AgentCard', () => { const testContent = 'Test Content'; it('renders children correctly', () => { - render({testContent}); + render({testContent}); expect(screen.getByText(testContent)).toBeInTheDocument(); }); it('passes through additional props', () => { - const testId = 'test-dashboard-card'; - const ariaLabel = 'Dashboard Card'; - render({testContent}); + const testId = 'test-agent-card'; + const ariaLabel = 'Agent Card'; + render({testContent}); const card = screen.getByTestId(testId); expect(card).toHaveAttribute('aria-label', ariaLabel); @@ -26,13 +26,13 @@ describe('DashboardCard', () => { it('applies custom className', () => { const customClass = 'custom-class'; - const { container } = render({testContent}); + const { container } = render({testContent}); expect(container.firstChild).toHaveClass(customClass); - expect(container.firstChild).toHaveClass(ClassName.DASHBOARD_CARD); + expect(container.firstChild).toHaveClass(ClassName.AGENT_CARD); }); it('maintains correct styling', () => { - const { container } = render({testContent}); + const { container } = render({testContent}); const card = container.firstChild as HTMLElement; expect(card).toHaveStyle({ @@ -46,7 +46,7 @@ describe('DashboardCard', () => { }); it('applies box shadow correctly', () => { - const { container } = render({testContent}); + const { container } = render({testContent}); const card = container.firstChild as HTMLElement; const boxShadow = window.getComputedStyle(card).boxShadow; @@ -66,7 +66,7 @@ describe('DashboardCard', () => { ); - render({complexContent}); + render({complexContent}); const content = screen.getByTestId('complex-content'); expect(content).toBeInTheDocument(); expect(screen.getByText('Title')).toBeInTheDocument(); @@ -75,7 +75,7 @@ describe('DashboardCard', () => { }); it('handles empty content gracefully', () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toBeInTheDocument(); expect(container.firstChild).toHaveStyle({ display: 'flex', @@ -84,20 +84,20 @@ describe('DashboardCard', () => { }); it('maintains layout with different content types', () => { - const { rerender, container } = render(Short text); + const { rerender, container } = render(Short text); expect(container.firstChild).toHaveStyle({ display: 'flex', flexDirection: 'column', }); rerender( - +

Large Content

With multiple paragraphs

And more content

-
+
); expect(container.firstChild).toHaveStyle({ diff --git a/packages/react-chat/src/components/DashboardCard/index.tsx b/packages/react-chat/src/components/AgentCard/index.tsx similarity index 60% rename from packages/react-chat/src/components/DashboardCard/index.tsx rename to packages/react-chat/src/components/AgentCard/index.tsx index cd3203c2cd..151a5ef6ec 100644 --- a/packages/react-chat/src/components/DashboardCard/index.tsx +++ b/packages/react-chat/src/components/AgentCard/index.tsx @@ -2,20 +2,20 @@ import React from 'react'; import { Container } from './styled'; -export interface DashboardCardProps extends React.HTMLAttributes { +export interface AgentCardProps extends React.HTMLAttributes { /** * The content to be rendered inside the card. */ children?: React.ReactNode; } -const DashboardCard: React.FC = ({ children, ...props }) => ( +const AgentCard: React.FC = ({ children, ...props }) => ( {children} ); /** * A flexible card component with a rounded rectangle shape and subtle shadow effects. */ -export default Object.assign(DashboardCard, { +export default Object.assign(AgentCard, { Container, }); diff --git a/packages/react-chat/src/components/DashboardCard/styled.ts b/packages/react-chat/src/components/AgentCard/styled.ts similarity index 90% rename from packages/react-chat/src/components/DashboardCard/styled.ts rename to packages/react-chat/src/components/AgentCard/styled.ts index d28657a700..6e20862e83 100644 --- a/packages/react-chat/src/components/DashboardCard/styled.ts +++ b/packages/react-chat/src/components/AgentCard/styled.ts @@ -2,7 +2,7 @@ import { ClassName } from '@/constants'; import { tagFactory } from '@/hocs'; import { styled } from '@/styles'; -const tag = tagFactory(ClassName.DASHBOARD_CARD); +const tag = tagFactory(ClassName.AGENT_CARD); export const Container = styled(tag('div'), { borderRadius: '6px', diff --git a/packages/react-chat/src/constants.ts b/packages/react-chat/src/constants.ts index 47ff537e64..19828a1806 100644 --- a/packages/react-chat/src/constants.ts +++ b/packages/react-chat/src/constants.ts @@ -29,7 +29,7 @@ export enum ClassName { PROACTIVE_CLOSE = 'vfrc-proactive-close', PROACTIVE_MESSAGE = 'vfrc-proactive-message', PROACTIVE = 'vfrc-proactive', - DASHBOARD_CARD = 'vfrc-dashboard-card', + AGENT_CARD = 'vfrc-agent-card', } export const DEVICE_INFO = Bowser.parse(window.navigator.userAgent);