From 8df94a51ddd11517713cc8ec32a0710feb0b7b84 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Fri, 26 Jun 2026 10:47:08 +0300 Subject: [PATCH] feat: broker dashboard redesign with top tabs, single-column layout, and clickable chip filters - Move navigation from left sidebar to horizontal top tabs in BrokerAccountLayout - Change dashboard blocks to single-column layout (Events, Income, Analytics, Allocation) - Extend DS Chip with onClick/selected/disabled/aria-pressed - Add clickable chip filters to Events and Income cards with immediate filtering - Disable backend query when all types deselected (show validation message) - Add useBrokerEvents/useBrokerOperations options.enabled param - Update dashboard skeleton to single-column shape - Add userEvent tests for chip filter interactions --- .../broker-event/model/useBrokerEvents.ts | 8 +- .../model/useBrokerOperations.ts | 3 +- .../ui/BrokerAccountLayout.tsx | 70 ++++------ .../ui/BrokerDashboard.test.tsx | 79 ++++++++--- .../broker-dashboard/ui/BrokerDashboard.tsx | 124 +++++++++++------- .../ui/BrokerDashboardEventsCard.tsx | 33 ++++- .../ui/BrokerDashboardIncomeCard.tsx | 29 +++- .../ui/BrokerDashboardSkeleton.tsx | 12 +- .../broker-dashboard-redesign/spec.md | 27 ++-- .../broker-dashboard-redesign/tasks.md | 12 +- .../src/components/Chip/Chip.test.tsx | 18 +++ .../src/components/Chip/Chip.tsx | 18 ++- 12 files changed, 291 insertions(+), 142 deletions(-) diff --git a/apps/frontend/src/entities/broker-event/model/useBrokerEvents.ts b/apps/frontend/src/entities/broker-event/model/useBrokerEvents.ts index 5efa7fe..0206d56 100644 --- a/apps/frontend/src/entities/broker-event/model/useBrokerEvents.ts +++ b/apps/frontend/src/entities/broker-event/model/useBrokerEvents.ts @@ -2,11 +2,15 @@ import { useQuery } from '@tanstack/react-query' import type { BrokerEventsData } from '@/shared/api' import { type BrokerEventsQuery, getBrokerEvents } from '../api/brokerEventApi' -export function useBrokerEvents(accountId: string | undefined, query: BrokerEventsQuery) { +export function useBrokerEvents( + accountId: string | undefined, + query: BrokerEventsQuery, + options: { enabled?: boolean } = {}, +) { const { from, to, types } = query return useQuery({ queryKey: ['broker', 'events', accountId, from, to, types], - enabled: Boolean(accountId), + enabled: Boolean(accountId) && (options.enabled ?? true), queryFn: async () => (await getBrokerEvents(accountId!, { from, to, types })).data, staleTime: 300_000, retry: 2, diff --git a/apps/frontend/src/entities/broker-operation/model/useBrokerOperations.ts b/apps/frontend/src/entities/broker-operation/model/useBrokerOperations.ts index 20335c4..80de165 100644 --- a/apps/frontend/src/entities/broker-operation/model/useBrokerOperations.ts +++ b/apps/frontend/src/entities/broker-operation/model/useBrokerOperations.ts @@ -5,10 +5,11 @@ import { type BrokerOperationQuery, getBrokerOperations } from '../api/brokerOpe export function useBrokerOperations( accountId: string | undefined, query: BrokerOperationQuery = {}, + options: { enabled?: boolean } = {}, ) { return useQuery({ queryKey: ['broker', 'operations', accountId, query], - enabled: Boolean(accountId), + enabled: Boolean(accountId) && (options.enabled ?? true), queryFn: async () => (await getBrokerOperations(accountId!, query)).data, staleTime: 300_000, retry: 2, diff --git a/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx b/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx index e029550..f06e171 100644 --- a/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx +++ b/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx @@ -47,51 +47,39 @@ export function BrokerAccountLayout({ children }: { children: ReactNode }) { - - {links.map((link) => ( - - {link.label} - - ))} - - - {children} + {links.map((link) => ( + + {link.label} + + ))} + + {children} ) diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx index a800466..f6d182b 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx @@ -1,8 +1,14 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { beforeEach, describe, expect, it, vi } from 'vitest' import type { BrokerPortfolio } from '@/shared/api' import { BrokerDashboard } from './BrokerDashboard' +const hookMocks = vi.hoisted(() => ({ + useBrokerEvents: vi.fn(), + useBrokerOperations: vi.fn(), +})) + vi.mock('@tanstack/react-router', () => ({ Link: ({ children, to }: { children: React.ReactNode; to: string }) => ( {children} @@ -27,25 +33,11 @@ vi.mock('@/entities/broker-analytics', () => ({ })) vi.mock('@/entities/broker-event', () => ({ - useBrokerEvents: () => ({ - data: { items: [], summary: {}, asOf: '2026-06-26T00:00:00.000Z' }, - isLoading: false, - isError: false, - }), + useBrokerEvents: hookMocks.useBrokerEvents, })) vi.mock('@/entities/broker-operation', () => ({ - useBrokerOperations: () => ({ - data: { - accountId: 'acc-1', - items: [], - nextCursor: null, - hasNext: false, - asOf: '2026-06-26T00:00:00.000Z', - }, - isLoading: false, - isError: false, - }), + useBrokerOperations: hookMocks.useBrokerOperations, })) vi.mock('@/widgets/broker-allocation-chart', () => ({ @@ -80,6 +72,25 @@ const portfolio: BrokerPortfolio = { } describe('BrokerDashboard', () => { + beforeEach(() => { + hookMocks.useBrokerEvents.mockReturnValue({ + data: { items: [], summary: {}, asOf: '2026-06-26T00:00:00.000Z' }, + isLoading: false, + isError: false, + }) + hookMocks.useBrokerOperations.mockReturnValue({ + data: { + accountId: 'acc-1', + items: [], + nextCursor: null, + hasNext: false, + asOf: '2026-06-26T00:00:00.000Z', + }, + isLoading: false, + isError: false, + }) + }) + it('renders the dashboard sections', () => { render() @@ -90,4 +101,36 @@ describe('BrokerDashboard', () => { expect(screen.getByText('Аллокация')).toBeInTheDocument() expect(screen.getByText('allocation chart')).toBeInTheDocument() }) + + it('uses event chips as request filters', async () => { + const user = userEvent.setup() + render() + + await user.click(screen.getAllByRole('button', { name: 'Купоны' })[0]) + + await waitFor(() => { + expect(hookMocks.useBrokerEvents).toHaveBeenLastCalledWith( + 'acc-1', + expect.objectContaining({ types: 'dividend,maturity,offer' }), + { enabled: true }, + ) + }) + }) + + it('uses income chips as request filters', async () => { + const user = userEvent.setup() + render() + + await user.click(screen.getAllByRole('button', { name: 'Купоны' })[1]) + + await waitFor(() => { + expect(hookMocks.useBrokerOperations).toHaveBeenLastCalledWith( + 'acc-1', + expect.objectContaining({ + operationTypes: 'OPERATION_TYPE_DIVIDEND,OPERATION_TYPE_DIV_EXT', + }), + { enabled: true }, + ) + }) + }) }) diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx index a9b430b..5bbdb2b 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx @@ -6,6 +6,8 @@ import { useBrokerOperations } from '@/entities/broker-operation' import type { BrokerPortfolio } from '@/shared/api' import { useCursorPagination } from '@/shared/lib/useCursorPagination' import { + type DashboardEventType, + type DashboardIncomeType, defaultEventsFilters, defaultIncomeFilters, incomeTypesToOperationTypes, @@ -23,69 +25,99 @@ export function BrokerDashboard({ accountId: string portfolio: BrokerPortfolio }) { - const [eventFilters, _setEventFilters] = useState(defaultEventsFilters) + const [eventFilters, setEventFilters] = useState(defaultEventsFilters) const [eventPage, setEventPage] = useState(1) - const [incomeFilters, _setIncomeFilters] = useState(defaultIncomeFilters) + const [incomeFilters, setIncomeFilters] = useState(defaultIncomeFilters) const incomePagination = useCursorPagination() const analytics = useBrokerAnalytics(accountId) - const events = useBrokerEvents(accountId, { - from: eventFilters.from, - to: eventFilters.to, - types: eventFilters.types.join(','), - }) + const hasEventTypes = eventFilters.types.length > 0 + const hasIncomeTypes = incomeFilters.types.length > 0 + const events = useBrokerEvents( + accountId, + { + from: eventFilters.from, + to: eventFilters.to, + types: eventFilters.types.join(','), + }, + { enabled: hasEventTypes }, + ) const eventItems = events.data?.items ?? [] const eventPageSize = 10 const eventPageItems = eventItems.slice( (eventPage - 1) * eventPageSize, eventPage * eventPageSize, ) - const operations = useBrokerOperations(accountId, { - from: incomeFilters.from, - to: incomeFilters.to, - operationTypes: incomeTypesToOperationTypes(incomeFilters.types), - cursor: incomePagination.cursor, - limit: 10, - }) + const operations = useBrokerOperations( + accountId, + { + from: incomeFilters.from, + to: incomeFilters.to, + operationTypes: incomeTypesToOperationTypes(incomeFilters.types), + cursor: incomePagination.cursor, + limit: 10, + }, + { enabled: hasIncomeTypes }, + ) const nextCursor: string | undefined = operations.data?.nextCursor ? (operations.data.nextCursor as unknown as string) : undefined + function toggleEventType(type: DashboardEventType) { + setEventFilters((filters) => ({ + ...filters, + types: filters.types.includes(type) + ? filters.types.filter((item) => item !== type) + : [...filters.types, type], + })) + setEventPage(1) + } + + function toggleIncomeType(type: DashboardIncomeType) { + setIncomeFilters((filters) => ({ + ...filters, + types: filters.types.includes(type) + ? filters.types.filter((item) => item !== type) + : [...filters.types, type], + })) + incomePagination.reset() + } + return ( - - 1} - canGoForward={eventPage * eventPageSize < eventItems.length} - onPreviousPage={() => setEventPage((page) => Math.max(1, page - 1))} - onNextPage={() => setEventPage((page) => page + 1)} - /> - 1} - canGoForward={operations.data?.hasNext ?? false} - onPreviousPage={incomePagination.handlePrevious} - onNextPage={() => incomePagination.handleNext(nextCursor)} - /> - - - - - + 1} + canGoForward={eventPage * eventPageSize < eventItems.length} + onPreviousPage={() => setEventPage((page) => Math.max(1, page - 1))} + onNextPage={() => setEventPage((page) => page + 1)} + /> + 1} + canGoForward={operations.data?.hasNext ?? false} + onPreviousPage={incomePagination.handlePrevious} + onNextPage={() => incomePagination.handleNext(nextCursor)} + /> + + ) } diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx index 5d99e99..a189431 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx @@ -3,14 +3,28 @@ import { Box } from '@mui/material' import { Link } from '@tanstack/react-router' import type { BrokerEventItem, BrokerEventsData } from '@/shared/api' import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters' +import type { DashboardEventType } from '../lib/dashboardFilters' import { eventStatusLabel, eventTypeLabel } from '../lib/dashboardFormatters' import { BrokerDashboardCard } from './BrokerDashboardCard' +const EVENT_FILTERS: Array<{ + type: DashboardEventType + label: string + tone: 'success' | 'info' | 'warning' | 'neutral' +}> = [ + { type: 'dividend', label: 'Дивиденды', tone: 'success' }, + { type: 'coupon', label: 'Купоны', tone: 'info' }, + { type: 'maturity', label: 'Погашения', tone: 'warning' }, + { type: 'offer', label: 'Оферты', tone: 'neutral' }, +] + type BrokerDashboardEventsCardProps = { accountId: string data: BrokerEventsData | undefined isLoading: boolean isError: boolean + selectedTypes: DashboardEventType[] + onToggleType: (type: DashboardEventType) => void page: number onPreviousPage: () => void onNextPage: () => void @@ -30,6 +44,8 @@ export function BrokerDashboardEventsCard({ data, isLoading, isError, + selectedTypes, + onToggleType, page, onPreviousPage, onNextPage, @@ -44,12 +60,19 @@ export function BrokerDashboardEventsCard({ action={Все события} > - - - - + {EVENT_FILTERS.map((filter) => ( + onToggleType(filter.type)} + /> + ))} - {isError ? ( + {selectedTypes.length === 0 ? ( + Выберите хотя бы один тип событий + ) : isError ? ( Не удалось загрузить события ) : isLoading ? ( Загрузка событий… diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx index 0f4682c..b3e882d 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx @@ -3,14 +3,26 @@ import { Box } from '@mui/material' import { Link } from '@tanstack/react-router' import type { BrokerOperationsPage } from '@/shared/api' import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters' +import type { DashboardIncomeType } from '../lib/dashboardFilters' import { getDashboardIncomeRows, sumDashboardIncome } from '../lib/dashboardIncome' import { BrokerDashboardCard } from './BrokerDashboardCard' +const INCOME_FILTERS: Array<{ + type: DashboardIncomeType + label: string + tone: 'success' | 'info' +}> = [ + { type: 'dividend', label: 'Дивиденды', tone: 'success' }, + { type: 'coupon', label: 'Купоны', tone: 'info' }, +] + type BrokerDashboardIncomeCardProps = { accountId: string page: BrokerOperationsPage | undefined isLoading: boolean isError: boolean + selectedTypes: DashboardIncomeType[] + onToggleType: (type: DashboardIncomeType) => void pageNumber: number canGoBack: boolean canGoForward: boolean @@ -23,6 +35,8 @@ export function BrokerDashboardIncomeCard({ page, isLoading, isError, + selectedTypes, + onToggleType, pageNumber, canGoBack, canGoForward, @@ -38,10 +52,19 @@ export function BrokerDashboardIncomeCard({ action={Все операции} > - - + {INCOME_FILTERS.map((filter) => ( + onToggleType(filter.type)} + /> + ))} - {isError ? ( + {selectedTypes.length === 0 ? ( + Выберите хотя бы один тип доходов + ) : isError ? ( Не удалось загрузить доходные операции ) : isLoading ? ( Загрузка доходов… diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx index edaf886..a9c6181 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx @@ -5,14 +5,10 @@ export function BrokerDashboardSkeleton() { return ( - - - - - - - - + + + + ) } diff --git a/docs/features/broker-dashboard-redesign/spec.md b/docs/features/broker-dashboard-redesign/spec.md index bdf837f..272c0b5 100644 --- a/docs/features/broker-dashboard-redesign/spec.md +++ b/docs/features/broker-dashboard-redesign/spec.md @@ -53,13 +53,13 @@ - `/broker/:accountId` остаётся overview выбранного брокерского счёта. - Overview визуально становится dashboard-страницей, а не вертикальным списком независимых секций. -- На desktop первый экран содержит hero KPI и два основных информационных блока рядом: `События` и - `Доходы`. -- Ниже отображаются `Аналитика доходности` и `Аллокация`. +- Существующая навигация счёта отображается горизонтальными вкладками над контентом, чтобы не занимать + левую колонку и оставить больше пространства для таблиц dashboard. +- На desktop и mobile блоки dashboard отображаются по одному блоку на строке: hero KPI, `События`, + `Доходы`, `Аналитика доходности`, `Аллокация`. - Существующая навигация счёта сохраняет ссылки на `Обзор`, `Акции`, `Облигации`, `Операции`, `События`, `Аналитика`. -- На мобильном viewport дашборд перестраивается в одну колонку с порядком: hero KPI, события, доходы, - аналитика, аллокация. +- На мобильном viewport сохраняется тот же порядок блоков в одну колонку. ### 2. Визуальный стиль @@ -88,10 +88,12 @@ Hero показывает: - Блок использует существующий источник `useBrokerEvents(accountId, query)`. - По умолчанию применяется период `сегодня - 7 дней` / `сегодня + 7 дней` и типы `dividend,coupon,maturity,offer`, как в существующей вкладке событий. -- Блок содержит фильтр типов событий: `Дивиденды`, `Купоны`, `Погашения`, `Оферты`. +- Блок содержит кликабельные chip-фильтры типов событий: `Дивиденды`, `Купоны`, `Погашения`, `Оферты`. - Пользователь может выбрать несколько типов событий. - Если пользователь снимает все типы событий, запрос не выполняется, а блок показывает валидационное сообщение. +- Изменение chip-фильтров типов событий применяется сразу и возвращает локальную пагинацию на первую + страницу. - Блок содержит фильтр периода `from` / `to`. - Изменение черновых фильтров не запускает запрос до нажатия `Показать`. - Блок содержит быстрые пресеты периода `7д`, `30д`, `90д`, `1г`, `Всё` и действие `Сбросить`. @@ -113,10 +115,11 @@ Hero показывает: операций. - В первую версию входят операции с типами дивидендов и купонов, которые уже используются в backend analytics: `OPERATION_TYPE_DIVIDEND`, `OPERATION_TYPE_DIV_EXT`, `OPERATION_TYPE_COUPON`. -- Блок содержит фильтр типов доходов: `Дивиденды`, `Купоны`. +- Блок содержит кликабельные chip-фильтры типов доходов: `Дивиденды`, `Купоны`. - Пользователь может выбрать один или оба типа доходов. - Если пользователь снимает все типы доходов, запрос не выполняется, а блок показывает валидационное сообщение. +- Изменение chip-фильтров типов доходов применяется сразу и сбрасывает cursor-пагинацию. - Блок содержит фильтр периода `from` / `to`. - По умолчанию используется период с начала текущего календарного года до текущей даты, как в разделе операций. @@ -175,16 +178,16 @@ Hero показывает: - `/broker/:accountId` показывает dashboard-композицию: hero KPI, `События`, `Доходы`, `Аналитика доходности`, `Аллокация`. -- На desktop блоки `События` и `Доходы` расположены рядом. +- Навигация счёта отображается горизонтальными вкладками над dashboard-контентом. +- На desktop и mobile блоки `События`, `Доходы`, `Аналитика доходности`, `Аллокация` расположены по + одному блоку на строке. - На мобильном viewport dashboard читаемо перестраивается в одну колонку. - Hero показывает стоимость портфеля, доходность или fallback, дневное изменение или fallback, всего доходов или fallback. - Блок `События` использует существующие events data и показывает дату, инструмент, тип, сумму и статус. -- Блок `События` поддерживает multi-select фильтр типов, фильтр периода, быстрые пресеты, сброс и - локальную пагинацию по 10 событий. +- Блок `События` поддерживает multi-select chip-фильтр типов и локальную пагинацию по 10 событий. - Блок `Доходы` показывает доходные операции дивидендов и купонов и итог по отображаемым строкам. -- Блок `Доходы` поддерживает multi-select фильтр типов, фильтр периода, быстрые пресеты, сброс и - cursor-пагинацию по 10 операций. +- Блок `Доходы` поддерживает multi-select chip-фильтр типов и cursor-пагинацию по 10 операций. - Блок `Аналитика доходности` показывает данные существующего analytics endpoint. - Блок `Аллокация` показывает donut/легенду существующей структуры портфеля. - Ошибка одного вторичного блока не скрывает остальные блоки dashboard. diff --git a/docs/features/broker-dashboard-redesign/tasks.md b/docs/features/broker-dashboard-redesign/tasks.md index e6a19e2..f8912ab 100644 --- a/docs/features/broker-dashboard-redesign/tasks.md +++ b/docs/features/broker-dashboard-redesign/tasks.md @@ -23,9 +23,13 @@ - [x] Добавить `BrokerDashboardHero` с KPI по portfolio и analytics. - [x] Добавить `BrokerDashboardEventsCard` на основе `useBrokerEvents`. - [x] Добавить `BrokerDashboardIncomeCard` на основе `useBrokerOperations`. -- [ ] Добавить фильтры типов, фильтры периода, пресеты, reset/apply actions для `События`. +- [x] Перенести навигацию счёта из левой колонки в горизонтальные вкладки над контентом. +- [x] Перестроить dashboard на один блок на строке для `События`, `Доходы`, `Аналитика доходности`, `Аллокация`. +- [x] Добавить кликабельные chip-фильтры типов для `События`. +- [ ] Добавить фильтры периода, пресеты, reset/apply actions для `События`. - [x] Добавить локальную пагинацию по 10 событий в `События`. -- [ ] Добавить фильтры типов, фильтры периода, пресеты, reset/apply actions для `Доходы`. +- [x] Добавить кликабельные chip-фильтры типов для `Доходы`. +- [ ] Добавить фильтры периода, пресеты, reset/apply actions для `Доходы`. - [x] Добавить cursor-пагинацию по 10 операций в `Доходы`. - [x] Добавить `BrokerDashboardAnalyticsCard` на основе `useBrokerAnalytics`. - [x] Добавить `BrokerDashboardAllocationCard` на основе существующей аллокации. @@ -38,8 +42,8 @@ ## Definition of Done -- [x] `rtk npm run test:frontend` проходит (31 files, 133 tests). -- [x] `rtk npm run test:design-system` проходит (28 files, 160 tests). +- [x] `rtk npm run test:frontend` проходит (31 files, 135 tests). +- [x] `rtk npm run test:design-system` проходит (28 files, 162 tests). - [x] `rtk npm run lint -w apps/frontend` проходит. - [x] `rtk npm run build:frontend` проходит. - [x] Dashboard соответствует acceptance criteria из `spec.md`. diff --git a/packages/design-system/src/components/Chip/Chip.test.tsx b/packages/design-system/src/components/Chip/Chip.test.tsx index d5d91ea..8317ce5 100644 --- a/packages/design-system/src/components/Chip/Chip.test.tsx +++ b/packages/design-system/src/components/Chip/Chip.test.tsx @@ -59,4 +59,22 @@ describe('Chip', () => { renderWithTheme(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); + + it('calls onClick when clickable chip clicked', async () => { + const handleClick = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + + await user.click(screen.getByRole('button', { name: 'Clickable' })); + + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('renders unselected clickable chip as outlined', () => { + renderWithTheme( {}} selected={false} />); + + const chip = screen.getByText('Inactive').closest('.MuiChip-root')!; + expect(chip.classList.contains('MuiChip-outlined')).toBe(true); + expect(chip).toHaveAttribute('aria-pressed', 'false'); + }); }); diff --git a/packages/design-system/src/components/Chip/Chip.tsx b/packages/design-system/src/components/Chip/Chip.tsx index f6b328e..48d9c37 100644 --- a/packages/design-system/src/components/Chip/Chip.tsx +++ b/packages/design-system/src/components/Chip/Chip.tsx @@ -1,5 +1,5 @@ -import { Chip as MuiChip } from '@mui/material'; import CancelIcon from '@mui/icons-material/Cancel'; +import { Chip as MuiChip } from '@mui/material'; type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'error'; @@ -15,14 +15,28 @@ export interface ChipProps { label: string; tone?: Tone; onDelete?: () => void; + onClick?: () => void; + selected?: boolean; + disabled?: boolean; } -export function Chip({ label, tone = 'neutral', onDelete }: ChipProps) { +export function Chip({ + label, + tone = 'neutral', + onDelete, + onClick, + selected = true, + disabled, +}: ChipProps) { return ( } : {})} /> );