feat: integrate omnichannel design library
This commit is contained in:
parent
aa63b40017
commit
45179d2a2c
18 changed files with 154 additions and 79 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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`, обеспечивая бесшовное переключение всей цветовой палитры интерфейса.
|
||||||
|
|
|
||||||
|
|
@ -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
52
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue