feat: integrate omnichannel design library

This commit is contained in:
ochenstarik 2026-09-20 09:45:29 +07:00
parent aa63b40017
commit 45179d2a2c
18 changed files with 154 additions and 79 deletions

2
.gitignore vendored
View file

@ -8,6 +8,8 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
.library-update/
.pnpm-store/
dist dist
dist-ssr dist-ssr
*.local *.local

View file

@ -1,30 +1,33 @@
# Использование UI-библиотеки krona-library в krona-front # Использование UI-библиотеки `ochenstarik/library` в krona-front
## 1. Архитектурный принцип ## 1. Архитектурный принцип
В рамках технического задания **запрещено копирование компонентов из `krona-library` в `krona-front`**. В рамках технического задания **запрещено копирование компонентов из общей библиотеки в `krona-front`**.
Все компоненты, токены и темы дизайна подключаются как внешние пакетные зависимости: Все компоненты, токены и темы дизайна подключаются как внешние пакетные зависимости:
- `@krona/tokens` и `@krona/tokens.css` - `@omnichannel/tokens` и `@omnichannel/tokens/css`
- `@krona/themes` и `@krona/themes.css` - `@omnichannel/themes` и `@omnichannel/themes/css`
- `@krona/ui` - `@omnichannel/ui`
В директории `src/` проекта `krona-front` нет ни одного форкнутого или дублированного файла из `krona-library/packages/ui/src/components/`. В директории `src/` проекта `krona-front` нет ни одного форкнутого или дублированного файла из `krona-library/packages/ui/src/components/`.
--- ---
## 2. Реестр используемых компонентов @krona/ui ## 2. Реестр используемых компонентов @omnichannel/ui
| Компонент | Импорт | Где используется в `krona-front` | Передаваемые props и варианты | | Компонент | Импорт | Где используется в `krona-front` | Передаваемые props и варианты |
|---|---|---|---| |---|---|---|---|
| **Tabs** | `import { Tabs } from '@krona/ui'` | `CommunicationsHeader.tsx` | `items`: `[{ id, label, icon, badge }]`, `activeId`, `onChange`, `ariaLabel` | | **Tabs** | `import { Tabs } from '@omnichannel/ui'` | `CommunicationsHeader.tsx` | `items`: `[{ id, label, icon, badge }]`, `activeId`, `onChange`, `ariaLabel` |
| **ConnectorPill** | `import { ConnectorPill } from '@krona/ui'` | `MessengersView.tsx` | `active`, `onClick`, `count`, `alertCount`, `icon`, `children` | | **ConnectorPill** | `import { ConnectorPill } from '@omnichannel/ui'` | `MessengersView.tsx` | `active`, `onClick`, `count`, `alertCount`, `icon`, `children` |
| **MessageBubble** | `import { MessageBubble } from '@krona/ui'` | `MessengersView.tsx`, `ChannelsView.tsx` | `direction` (`'incoming' | 'outgoing'`), `senderName`, `timestamp`, `status` (`'sent' | 'delivered' | 'read'`), `connector`, `isInternal`, `attachments`, `avatarUrl` | | **MessageBubble** | `import { MessageBubble } from '@omnichannel/ui'` | `MessengersView.tsx`, `ChannelsView.tsx` | `direction` (`'incoming' | 'outgoing'`), `senderName`, `timestamp`, `status` (`'sent' | 'delivered' | 'read'`), `connector`, `isInternal`, `attachments`, `avatarUrl` |
| **Card** | `import { Card } from '@krona/ui'` | `MessengersView.tsx`, `SettingsView.tsx`, `ChannelsView.tsx` | `variant` (`'default' | 'muted' | 'interactive'`), `padding` (`'sm' | 'md' | 'lg'`), `header`, `footer`, `children` | | **Card** | `import { Card } from '@omnichannel/ui'` | `MessengersView.tsx`, `SettingsView.tsx`, `ChannelsView.tsx` | `variant` (`'default' | 'muted' | 'interactive'`), `padding` (`'sm' | 'md' | 'lg'`), `header`, `footer`, `children` |
| **Modal** | `import { Modal } from '@krona/ui'` | `Modals.tsx` (ComposeEmail, NewChannel, QuickReply, NewContact) | `isOpen`, `onClose`, `title`, `size` (`'sm' | 'md' | 'lg' | 'xl'`), `footer`, `children` | | **Modal** | `import { Modal } from '@omnichannel/ui'` | `Modals.tsx` (ComposeEmail, NewChannel, QuickReply, NewContact) | `isOpen`, `onClose`, `title`, `size` (`'sm' | 'md' | 'lg' | 'xl'`), `footer`, `children` |
| **Button** | `import { Button } from '@krona/ui'` | Во всех представлениях `/communications` | `variant` (`'primary' | 'secondary' | 'ghost' | 'danger'`), `size` (`'sm' | 'md' | 'lg'`), `disabled`, `icon`, `onClick` | | **Button** | `import { Button } from '@omnichannel/ui'` | Во всех представлениях `/communications` | `variant` (`'primary' | 'secondary' | 'ghost' | 'danger'`), `size` (`'sm' | 'md' | 'lg'`), `disabled`, `icon`, `onClick` |
| **Input** | `import { Input } from '@krona/ui'` | Поиск, модальные окна, фильтры | `label`, `placeholder`, `error`, `leftIcon`, `value`, `onChange` | | **Input** | `import { Input } from '@omnichannel/ui'` | Поиск, модальные окна, фильтры | `label`, `placeholder`, `error`, `leftIcon`, `value`, `onChange` |
| **Badge** | `import { Badge } from '@krona/ui'` | Карточка контакта, список писем, каналы | `variant` (`'default' | 'primary' | 'success' | 'warning' | 'danger' | 'outline'`), `size` (`'sm' | 'md'`), `children` | | **Badge** | `import { Badge } from '@omnichannel/ui'` | Карточка контакта, список писем, каналы | `variant` (`'default' | 'primary' | 'success' | 'warning' | 'danger' | 'outline'`), `size` (`'sm' | 'md'`), `children` |
| **Avatar** | `import { Avatar } from '@krona/ui'` | Список диалогов, заголовок чата, участники | `name`, `src`, `size` (`'sm' | 'md' | 'lg'`), `status` (`'online' | 'offline' | 'busy'`) | | **Avatar** | `import { Avatar } from '@omnichannel/ui'` | Список диалогов, заголовок чата, участники | `name`, `src`, `size` (`'sm' | 'md' | 'lg'`), `status` (`'online' | 'offline' | 'busy'`) |
| **SearchInput** | `import { SearchInput } from '@omnichannel/ui'` | Поиск диалогов | Подсказки, клавиатурная навигация, очистка, loading/error/no-results |
| **StatePanel** | `import { StatePanel } from '@omnichannel/ui'` | Восстановление после пустого поиска | `state`, описание и действия восстановления |
| **BottomSheet** | `import { BottomSheet } from '@omnichannel/ui'` | Готов для мобильных фильтров и действий | `open`, `onClose`, `title`, `footer` |
--- ---
@ -32,8 +35,8 @@
В `src/index.css` подключены: В `src/index.css` подключены:
```css ```css
@import '@krona/tokens.css'; @import '@omnichannel/tokens/tokens.css';
@import '@krona/themes.css'; @import '@omnichannel/themes/themes.css';
``` ```
В кодовой базе `krona-front` при оформлении коммуникаций используются исключительно семантические переменные: В кодовой базе `krona-front` при оформлении коммуникаций используются исключительно семантические переменные:
@ -52,4 +55,4 @@ Root элемент приложения (`document.documentElement`) управ
- `data-krona-theme="parchment-light"` - `data-krona-theme="parchment-light"`
- `data-krona-theme="graphite-forest-dark"` - `data-krona-theme="graphite-forest-dark"`
Каждая тема активирует свой набор CSS-переменных из `@krona/themes.css`, обеспечивая бесшовное переключение всей цветовой палитры интерфейса. Каждая тема активирует свой набор CSS-переменных из `@omnichannel/themes/themes.css`, обеспечивая бесшовное переключение всей цветовой палитры интерфейса.

View file

@ -30,7 +30,7 @@ const modules = [
]; ];
const nav=['Главная','Аналитика','Маркетинг','Коммуникации','CRM','Продажи','Склад','Закупки','Финансы','Сотрудники','Проекты','Задачи','Документы','Автоматизация','Сайты','Поддержка','Настройки']; const nav=['Главная','Аналитика','Маркетинг','Коммуникации','CRM','Продажи','Склад','Закупки','Финансы','Сотрудники','Проекты','Задачи','Документы','Автоматизация','Сайты','Поддержка','Настройки'];
function make([slug,module,title,statsRaw,rowsRaw]){ function make([_slug,module,title,statsRaw,rowsRaw]){
const stats=statsRaw.split(';').map(x=>x.split('|')), rows=rowsRaw.split(';').map(x=>x.split('|')); const stats=statsRaw.split(';').map(x=>x.split('|')), rows=rowsRaw.split(';').map(x=>x.split('|'));
const navSvg=nav.map((n,i)=>`<g transform="translate(22 ${112+i*43})"><rect x="-10" y="-25" width="194" height="36" rx="8" fill="${module.startsWith(n)||title.startsWith(n)?'#486B55':'transparent'}"/><circle cx="6" cy="-7" r="6" fill="${module.startsWith(n)||title.startsWith(n)?'#E8C47A':'#829286'}"/><text x="22" y="-2" font-size="12" fill="#F7F0DF">${esc(n)}</text></g>`).join(''); const navSvg=nav.map((n,i)=>`<g transform="translate(22 ${112+i*43})"><rect x="-10" y="-25" width="194" height="36" rx="8" fill="${module.startsWith(n)||title.startsWith(n)?'#486B55':'transparent'}"/><circle cx="6" cy="-7" r="6" fill="${module.startsWith(n)||title.startsWith(n)?'#E8C47A':'#829286'}"/><text x="22" y="-2" font-size="12" fill="#F7F0DF">${esc(n)}</text></g>`).join('');
const statSvg=stats.map((s,i)=>`<g transform="translate(${258+i*300} 190)"><text y="0" font-size="11" fill="#77756C">${esc(s[0])}</text><text y="32" font-size="22" font-weight="700" fill="#23332A">${esc(s[1])}</text><line y1="50" x2="268" y2="50" stroke="#D8CDB7"/></g>`).join(''); const statSvg=stats.map((s,i)=>`<g transform="translate(${258+i*300} 190)"><text y="0" font-size="11" fill="#77756C">${esc(s[0])}</text><text y="32" font-size="22" font-weight="700" fill="#23332A">${esc(s[1])}</text><line y1="50" x2="268" y2="50" stroke="#D8CDB7"/></g>`).join('');

52
package-lock.json generated
View file

@ -8,6 +8,9 @@
"name": "krona-front", "name": "krona-front",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@omnichannel/themes": "file:../library/packages/themes",
"@omnichannel/tokens": "file:../library/packages/tokens",
"@omnichannel/ui": "file:../library/packages/ui",
"@tanstack/react-table": "^9.2.4", "@tanstack/react-table": "^9.2.4",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"idb": "^8.0.3", "idb": "^8.0.3",
@ -39,6 +42,43 @@
"vitest": "^5.0.0" "vitest": "^5.0.0"
} }
}, },
"../library/packages/themes": {
"name": "@omnichannel/themes",
"version": "0.1.2",
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.18",
"vite": "^5.1.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"../library/packages/tokens": {
"name": "@omnichannel/tokens",
"version": "0.1.0",
"devDependencies": {
"vite": "^5.1.0"
}
},
"../library/packages/ui": {
"name": "@omnichannel/ui",
"version": "0.1.2",
"dependencies": {
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.1"
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.18",
"vite": "^5.1.0"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@adobe/css-tools": { "node_modules/@adobe/css-tools": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz",
@ -377,6 +417,18 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@omnichannel/themes": {
"resolved": "../library/packages/themes",
"link": true
},
"node_modules/@omnichannel/tokens": {
"resolved": "../library/packages/tokens",
"link": true
},
"node_modules/@omnichannel/ui": {
"resolved": "../library/packages/ui",
"link": true
},
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.149.0", "version": "0.149.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz",

View file

@ -25,9 +25,9 @@
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"ulidx": "^2.4.1", "ulidx": "^2.4.1",
"zustand": "^5.0.15", "zustand": "^5.0.15",
"@krona/tokens": "file:../krona-library/packages/tokens", "@omnichannel/tokens": "file:../library/packages/tokens",
"@krona/themes": "file:../krona-library/packages/themes", "@omnichannel/themes": "file:../library/packages/themes",
"@krona/ui": "file:../krona-library/packages/ui" "@omnichannel/ui": "file:../library/packages/ui"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.63.0", "@playwright/test": "^1.63.0",

View file

@ -8,10 +8,11 @@ import { useThemeStore } from '../../stores/theme';
describe('Communications Module & Design System V3 Test Suite', () => { describe('Communications Module & Design System V3 Test Suite', () => {
beforeEach(() => { beforeEach(() => {
localStorage.clear();
useThemeStore.getState().setTheme('noble-green'); useThemeStore.getState().setTheme('noble-green');
}); });
it('renders Communications header with 5 main tabs and slogan watermark', () => { it('renders the compact Communications header and keeps optional sections hidden by default', () => {
render( render(
<MemoryRouter> <MemoryRouter>
<CommunicationsModule /> <CommunicationsModule />
@ -19,17 +20,14 @@ describe('Communications Module & Design System V3 Test Suite', () => {
); );
expect(screen.getByText('Коммуникации')).toBeInTheDocument(); expect(screen.getByText('Коммуникации')).toBeInTheDocument();
expect(
screen.getByText('Все сообщения, почта и каналы компании в одном пространстве')
).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /мессенджеры/i })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: /мессенджеры/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /email/i })).toBeInTheDocument(); expect(screen.queryByRole('tab', { name: /email/i })).not.toBeInTheDocument();
expect(screen.getByRole('tab', { name: /каналы/i })).toBeInTheDocument(); expect(screen.queryByRole('tab', { name: /каналы/i })).not.toBeInTheDocument();
expect(screen.getByRole('tab', { name: /контакты/i })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: /контакты/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /настройки/i })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: /настройки/i })).toBeInTheDocument();
}); });
it('renders connectors row with Telegram, WhatsApp, VK, MAX, Email pills', () => { it('renders only messenger connectors in the messenger workspace', () => {
render( render(
<MemoryRouter> <MemoryRouter>
<CommunicationsModule /> <CommunicationsModule />
@ -41,7 +39,7 @@ describe('Communications Module & Design System V3 Test Suite', () => {
expect(screen.getByRole('button', { name: /whatsapp.*8/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /whatsapp.*8/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /vk.*6/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /vk.*6/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /max.*4/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /max.*4/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /email.*10/i })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: /email.*10/i })).not.toBeInTheDocument();
}); });
it('allows sending an interactive message in active chat and displays outgoing bubble', async () => { it('allows sending an interactive message in active chat and displays outgoing bubble', async () => {
@ -101,6 +99,7 @@ describe('Communications Module & Design System V3 Test Suite', () => {
}); });
it('switches to Email tab and displays mailboxes and messages', async () => { it('switches to Email tab and displays mailboxes and messages', async () => {
localStorage.setItem('krona-show-email', 'true');
render( render(
<MemoryRouter> <MemoryRouter>
<CommunicationsModule /> <CommunicationsModule />
@ -119,6 +118,7 @@ describe('Communications Module & Design System V3 Test Suite', () => {
}); });
it('switches to Channels tab and supports toggling message reactions', () => { it('switches to Channels tab and supports toggling message reactions', () => {
localStorage.setItem('krona-show-bot-channels', 'true');
render( render(
<MemoryRouter> <MemoryRouter>
<CommunicationsModule /> <CommunicationsModule />
@ -166,6 +166,7 @@ describe('Communications Module & Design System V3 Test Suite', () => {
const settingsTab = screen.getByRole('tab', { name: /настройки/i }); const settingsTab = screen.getByRole('tab', { name: /настройки/i });
fireEvent.click(settingsTab); fireEvent.click(settingsTab);
fireEvent.click(screen.getByRole('tab', { name: 'Организация' }));
expect(screen.getByText('Подключенные мессенджеры и коннекторы')).toBeInTheDocument(); expect(screen.getByText('Подключенные мессенджеры и коннекторы')).toBeInTheDocument();
expect(screen.getByText(/Сетевой шлюз и VPN/i)).toBeInTheDocument(); expect(screen.getByText(/Сетевой шлюз и VPN/i)).toBeInTheDocument();

View file

@ -1,7 +1,7 @@
import { useState, type FormEvent } from 'react'; import { useState, type FormEvent } from 'react';
import { Hash, Lock, Users, Pin, Send, Plus } from 'lucide-react'; import { Hash, Lock, Users, Pin, Send, Plus } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { Button } from '@krona/ui'; import { Button } from '@omnichannel/ui';
interface ChannelsViewProps { interface ChannelsViewProps {
onCreateChannelModal: () => void; onCreateChannelModal: () => void;

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { MessageSquare, Mail, Hash, Users, Settings } from 'lucide-react'; import { MessageSquare, Mail, Hash, Users, Settings } from 'lucide-react';
import { CommunicationTab } from '../types'; import { CommunicationTab } from '../types';
import { Tabs } from '@krona/ui'; import { Tabs } from '@omnichannel/ui';
interface CommunicationsHeaderProps { interface CommunicationsHeaderProps {
activeTab: CommunicationTab; activeTab: CommunicationTab;

View file

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { Search, Plus, MessageSquare, Star, Clock } from 'lucide-react'; import { Search, Plus, MessageSquare, Star, Clock } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { Badge, Button } from '@krona/ui'; import { Badge, Button } from '@omnichannel/ui';
interface ContactsViewProps { interface ContactsViewProps {
onAddContactModal: () => void; onAddContactModal: () => void;

View file

@ -4,7 +4,7 @@ import {
Reply, Forward, ArrowLeft Reply, Forward, ArrowLeft
} from 'lucide-react'; } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { Button } from '@krona/ui'; import { Button } from '@omnichannel/ui';
import { useToast } from '../../../shared/ui'; import { useToast } from '../../../shared/ui';
interface EmailViewProps { interface EmailViewProps {

View file

@ -1,13 +1,13 @@
import React, { useState, useRef, useEffect, type FormEvent } from 'react'; import React, { useState, useRef, useEffect, type FormEvent } from 'react';
import { import {
Search, ArrowUpDown, Star, Phone, Video, UserPlus, ArrowUpDown, Star, Phone, Video, UserPlus,
MoreVertical, Paperclip, Sparkles, Smile, Mic, Send, Download, FileText, MoreVertical, Paperclip, Sparkles, Smile, Mic, Send, Download, FileText,
ArrowLeft, CheckCheck, Plus, ArrowLeft, CheckCheck, Plus,
Briefcase, CheckSquare, ShieldCheck Briefcase, CheckSquare, ShieldCheck
} from 'lucide-react'; } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { ChatConversation, ConnectorType } from '../types'; import { ChatConversation, ConnectorType } from '../types';
import { Avatar, Badge, Button, ConnectorPill } from '@krona/ui'; import { Avatar, Badge, Button, ConnectorPill, SearchInput, StatePanel } from '@omnichannel/ui';
import { useToast, Drawer } from '../../../shared/ui'; import { useToast, Drawer } from '../../../shared/ui';
interface MessengersViewProps { interface MessengersViewProps {
@ -180,16 +180,18 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
> >
{/* Search and filters header */} {/* Search and filters header */}
<div className={`${layoutMode === 'compact' ? 'p-2 space-y-1.5' : 'p-3.5 space-y-2.5'} border-b border-[var(--krona-border-subtle)] shrink-0`}> <div className={`${layoutMode === 'compact' ? 'p-2 space-y-1.5' : 'p-3.5 space-y-2.5'} border-b border-[var(--krona-border-subtle)] shrink-0`}>
<div className="relative"> <SearchInput
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-[var(--krona-text-muted)]" /> value={searchQuery}
<input onChange={setSearchQuery}
type="text" placeholder="Поиск по чатам..."
value={searchQuery} aria-label="Поиск по чатам"
onChange={(e) => setSearchQuery(e.target.value)} suggestions={conversations.map((item) => ({
placeholder="Поиск по чатам..." id: item.id,
className="w-full h-9 pl-9 pr-3 rounded-lg bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] text-sm text-[var(--krona-text-primary)] placeholder-[var(--krona-text-muted)] focus:outline-none focus:border-[var(--krona-primary)] transition-colors" label: item.contact.name,
/> description: item.contact.company,
</div> }))}
onSelect={(item) => setActiveChatId(item.id)}
/>
<div className="flex items-center justify-between text-xs text-[var(--krona-text-secondary)]"> <div className="flex items-center justify-between text-xs text-[var(--krona-text-secondary)]">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -244,9 +246,13 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
{/* Conversation list */} {/* Conversation list */}
<div className="flex-1 overflow-y-auto divide-y divide-[var(--krona-border-subtle)]"> <div className="flex-1 overflow-y-auto divide-y divide-[var(--krona-border-subtle)]">
{filteredConversations.length === 0 ? ( {filteredConversations.length === 0 ? (
<div className="p-8 text-center text-sm text-[var(--krona-text-muted)]"> <StatePanel
Диалоги не найдены compact
</div> state="no-results"
title="Диалоги не найдены"
description="Измените запрос или очистите поиск."
primaryAction={{ label: 'Очистить', onClick: () => setSearchQuery('') }}
/>
) : ( ) : (
filteredConversations.map((item) => { filteredConversations.map((item) => {
const isActive = item.id === activeChat.id; const isActive = item.id === activeChat.id;

View file

@ -1,5 +1,5 @@
import { useState, type FormEvent } from 'react'; import { useState, type FormEvent } from 'react';
import { Modal, Button, Input } from '@krona/ui'; import { Modal, Button, Input } from '@omnichannel/ui';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { useToast } from '../../../shared/ui'; import { useToast } from '../../../shared/ui';
import { Paperclip } from 'lucide-react'; import { Paperclip } from 'lucide-react';

View file

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { RefreshCw, Trash2, Globe, UserCog, Bell, Volume2, PanelTop } from 'lucide-react'; import { RefreshCw, Trash2, Globe, UserCog, Bell, Volume2, PanelTop } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService'; import { useCommunicationsStore } from '../services/communicationsService';
import { Button, Badge } from '@krona/ui'; import { Button, Badge } from '@omnichannel/ui';
import { useToast } from '../../../shared/ui'; import { useToast } from '../../../shared/ui';
interface SettingsViewProps { interface SettingsViewProps {

View file

@ -1,6 +1,5 @@
@import '@krona/tokens.css'; @import '@omnichannel/tokens/css';
@import '@krona/themes.css'; @import '@omnichannel/themes/css';
@import '@krona/ui.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View file

@ -57,6 +57,11 @@ function applyThemeToDom(theme: ThemeMode) {
const activeLegacy = legacyThemeMap[theme] || 'light'; const activeLegacy = legacyThemeMap[theme] || 'light';
document.documentElement.setAttribute('data-krona-theme', activeKrona); document.documentElement.setAttribute('data-krona-theme', activeKrona);
document.documentElement.setAttribute('data-omni-theme', activeKrona);
document.documentElement.setAttribute(
'data-omni-density',
window.matchMedia('(max-width: 600px)').matches ? 'touch' : 'workspace',
);
document.documentElement.setAttribute('data-theme', activeLegacy); document.documentElement.setAttribute('data-theme', activeLegacy);
} }

View file

@ -17,9 +17,14 @@
/* Paths */ /* Paths */
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": ["./src/*"],
"@krona/tokens": ["../krona-library/packages/tokens/dist/index.d.ts"], "react": ["./node_modules/@types/react/index.d.ts"],
"@krona/themes": ["../krona-library/packages/themes/dist/index.d.ts"], "react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime.d.ts"],
"@krona/ui": ["../krona-library/packages/ui/dist/index.d.ts"] "react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime.d.ts"],
"clsx": ["./node_modules/clsx/clsx.d.ts"],
"tailwind-merge": ["./node_modules/tailwind-merge/dist/types.d.ts"],
"@omnichannel/tokens": ["../library/packages/tokens/src/index.ts"],
"@omnichannel/themes": ["../library/packages/themes/src/index.tsx"],
"@omnichannel/ui": ["../library/packages/ui/src/index.ts"]
}, },
/* Linting */ /* Linting */

View file

@ -9,17 +9,18 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
resolve: { resolve: {
alias: { alias: [
'@': path.resolve(currentDir, './src'), { find: '@', replacement: path.resolve(currentDir, './src') },
react: path.resolve(currentDir, './node_modules/react'), { find: 'react', replacement: path.resolve(currentDir, './node_modules/react') },
'react-dom': path.resolve(currentDir, './node_modules/react-dom'), { find: 'react-dom', replacement: path.resolve(currentDir, './node_modules/react-dom') },
'@krona/tokens': path.resolve(currentDir, '../krona-library/packages/tokens/dist/index.mjs'), { find: 'clsx', replacement: path.resolve(currentDir, './node_modules/clsx/dist/clsx.mjs') },
'@krona/themes': path.resolve(currentDir, '../krona-library/packages/themes/dist/index.mjs'), { find: 'tailwind-merge', replacement: path.resolve(currentDir, './node_modules/tailwind-merge/dist/bundle-mjs.mjs') },
'@krona/ui': path.resolve(currentDir, '../krona-library/packages/ui/dist/index.mjs'), { find: '@omnichannel/tokens/css', replacement: path.resolve(currentDir, '../library/packages/tokens/src/tokens.css') },
'@krona/tokens.css': path.resolve(currentDir, '../krona-library/packages/tokens/src/tokens.css'), { find: '@omnichannel/themes/css', replacement: path.resolve(currentDir, '../library/packages/themes/src/themes.css') },
'@krona/themes.css': path.resolve(currentDir, '../krona-library/packages/themes/src/themes.css'), { find: '@omnichannel/tokens', replacement: path.resolve(currentDir, '../library/packages/tokens/src/index.ts') },
'@krona/ui.css': path.resolve(currentDir, '../krona-library/packages/ui/dist/style.css'), { find: '@omnichannel/themes', replacement: path.resolve(currentDir, '../library/packages/themes/src/index.tsx') },
}, { find: '@omnichannel/ui', replacement: path.resolve(currentDir, '../library/packages/ui/src/index.ts') },
],
}, },
build: { build: {
chunkSizeWarningLimit: 1200, chunkSizeWarningLimit: 1200,

View file

@ -8,17 +8,18 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
resolve: { resolve: {
alias: { alias: [
'@': path.resolve(currentDir, './src'), { find: '@', replacement: path.resolve(currentDir, './src') },
react: path.resolve(currentDir, './node_modules/react'), { find: 'react', replacement: path.resolve(currentDir, './node_modules/react') },
'react-dom': path.resolve(currentDir, './node_modules/react-dom'), { find: 'react-dom', replacement: path.resolve(currentDir, './node_modules/react-dom') },
'@krona/tokens': path.resolve(currentDir, '../krona-library/packages/tokens/dist/index.mjs'), { find: 'clsx', replacement: path.resolve(currentDir, './node_modules/clsx/dist/clsx.mjs') },
'@krona/themes': path.resolve(currentDir, '../krona-library/packages/themes/dist/index.mjs'), { find: 'tailwind-merge', replacement: path.resolve(currentDir, './node_modules/tailwind-merge/dist/bundle-mjs.mjs') },
'@krona/ui': path.resolve(currentDir, '../krona-library/packages/ui/dist/index.mjs'), { find: '@omnichannel/tokens/css', replacement: path.resolve(currentDir, '../library/packages/tokens/src/tokens.css') },
'@krona/tokens.css': path.resolve(currentDir, '../krona-library/packages/tokens/src/tokens.css'), { find: '@omnichannel/themes/css', replacement: path.resolve(currentDir, '../library/packages/themes/src/themes.css') },
'@krona/themes.css': path.resolve(currentDir, '../krona-library/packages/themes/src/themes.css'), { find: '@omnichannel/tokens', replacement: path.resolve(currentDir, '../library/packages/tokens/src/index.ts') },
'@krona/ui.css': path.resolve(currentDir, '../krona-library/packages/ui/dist/style.css'), { find: '@omnichannel/themes', replacement: path.resolve(currentDir, '../library/packages/themes/src/index.tsx') },
}, { find: '@omnichannel/ui', replacement: path.resolve(currentDir, '../library/packages/ui/src/index.ts') },
],
}, },
test: { test: {
environment: 'jsdom', environment: 'jsdom',