feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -0,0 +1,554 @@
|
||||
# 🎨 UX/UI Улучшения - Документация
|
||||
|
||||
Этот документ описывает все внедренные UX/UI улучшения в проект S3 Lists Manager.
|
||||
|
||||
## 📋 Содержание
|
||||
|
||||
1. [CSS Улучшения](#css-улучшения)
|
||||
2. [Новые Компоненты](#новые-компоненты)
|
||||
3. [Улучшенные Компоненты](#улучшенные-компоненты)
|
||||
4. [Примеры Использования](#примеры-использования)
|
||||
|
||||
---
|
||||
|
||||
## CSS Улучшения
|
||||
|
||||
### Semantic Colors
|
||||
|
||||
Добавлена система семантических цветов для статусов и акцентов:
|
||||
|
||||
```css
|
||||
--status-online: #2fb344;
|
||||
--status-offline: #d63939;
|
||||
--status-warning: #f59f00;
|
||||
--status-unknown: #868e96;
|
||||
|
||||
--accent-primary: #206bc4;
|
||||
--accent-danger: #d63939;
|
||||
--accent-success: #2fb344;
|
||||
```
|
||||
|
||||
### Улучшенная Типографика
|
||||
|
||||
- Увеличенный размер заголовков с лучшей читаемостью
|
||||
- Оптимизированная межстрочная высота
|
||||
- Letter-spacing для заголовков
|
||||
|
||||
### Enhanced Spacing
|
||||
|
||||
- Единая система отступов через CSS переменные
|
||||
- Увеличенные padding в таблицах для лучшей читаемости
|
||||
- Consistent spacing между карточками
|
||||
|
||||
### Интерактивные Элементы
|
||||
|
||||
- **Hover states**: кнопки поднимаются на 1px с тенью
|
||||
- **Active states**: визуальный feedback при нажатии
|
||||
- **Focus states**: улучшенные для accessibility
|
||||
- **Touch targets**: минимум 44x44px на мобильных
|
||||
|
||||
---
|
||||
|
||||
## Новые Компоненты
|
||||
|
||||
### 1. Tooltip
|
||||
|
||||
Компонент для отображения подсказок с поддержкой keyboard shortcuts.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import Tooltip from './components/Tooltip.jsx'
|
||||
|
||||
<Tooltip content="Сохранить изменения" shortcut="Ctrl+S" position="top">
|
||||
<button className="btn btn-primary">
|
||||
<IconDeviceFloppy /> Сохранить
|
||||
</button>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
**Props:**
|
||||
- `content` (string) - текст подсказки
|
||||
- `shortcut` (string) - keyboard shortcut для отображения
|
||||
- `position` (string) - позиция: 'top', 'bottom', 'left', 'right'
|
||||
- `delay` (number) - задержка перед показом (ms)
|
||||
|
||||
---
|
||||
|
||||
### 2. TrendIndicator
|
||||
|
||||
Компонент для отображения изменения метрик со стрелками вверх/вниз.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import TrendIndicator from './components/TrendIndicator.jsx'
|
||||
|
||||
<TrendIndicator
|
||||
value={1250}
|
||||
previousValue={1180}
|
||||
format="number" // или "percent"
|
||||
inverse={false} // true если рост = плохо
|
||||
/>
|
||||
```
|
||||
|
||||
**Props:**
|
||||
- `value` (number) - текущее значение
|
||||
- `previousValue` (number) - предыдущее значение
|
||||
- `format` ('number' | 'percent') - формат отображения
|
||||
- `inverse` (boolean) - инвертировать цвета (рост = красный)
|
||||
|
||||
---
|
||||
|
||||
### 3. LastSaved
|
||||
|
||||
Компонент для отображения времени последнего сохранения с автообновлением.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import LastSaved from './components/LastSaved.jsx'
|
||||
|
||||
<LastSaved
|
||||
timestamp="2025-01-15T10:30:00Z"
|
||||
variant="compact" // 'default', 'badge', 'compact'
|
||||
/>
|
||||
```
|
||||
|
||||
**Варианты:**
|
||||
- `default` - полный вид с иконкой
|
||||
- `badge` - зеленый badge "Сохранено X мин назад"
|
||||
- `compact` - компактный вид для toolbar
|
||||
|
||||
---
|
||||
|
||||
### 4. ContextMenu
|
||||
|
||||
Компонент контекстного меню для действий с поддержкой правого клика.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import ContextMenu from './components/ContextMenu.jsx'
|
||||
import { IconEdit, IconTrash, IconCopy } from '@tabler/icons-react'
|
||||
|
||||
<ContextMenu
|
||||
items={[
|
||||
{ label: 'Редактировать', icon: IconEdit, onClick: handleEdit },
|
||||
{ label: 'Копировать', icon: IconCopy, onClick: handleCopy },
|
||||
{ divider: true },
|
||||
{ label: 'Удалить', icon: IconTrash, onClick: handleDelete, variant: 'danger' }
|
||||
]}
|
||||
align="right"
|
||||
>
|
||||
{/* Trigger element или оставить пустым для кнопки с тремя точками */}
|
||||
</ContextMenu>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. ValidatedInput
|
||||
|
||||
Input с real-time валидацией и визуальным feedback.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import ValidatedInput from './components/ValidatedInput.jsx'
|
||||
|
||||
const validateDomain = (value) => {
|
||||
const valid = /^([a-z0-9-]+\.)+[a-z]{2,}$/i.test(value)
|
||||
return {
|
||||
valid,
|
||||
message: valid ? 'Домен валиден' : 'Введите корректный домен (example.com)'
|
||||
}
|
||||
}
|
||||
|
||||
<ValidatedInput
|
||||
value={domain}
|
||||
onChange={(e) => setDomain(e.target.value)}
|
||||
validate={validateDomain}
|
||||
label="Домен"
|
||||
placeholder="example.com"
|
||||
hint="Введите доменное имя без http://"
|
||||
required
|
||||
debounce={300}
|
||||
/>
|
||||
```
|
||||
|
||||
**Props:**
|
||||
- `validate` (function) - функция валидации
|
||||
- `debounce` (number) - задержка перед валидацией
|
||||
- `showValidIcon` (boolean) - показывать иконку валидации
|
||||
- `validateOnChange` (boolean) - валидировать при вводе или только при blur
|
||||
|
||||
---
|
||||
|
||||
### 6. ProgressBar & MultiStepProgress
|
||||
|
||||
Компоненты для отображения прогресса операций.
|
||||
|
||||
**ProgressBar:**
|
||||
|
||||
```jsx
|
||||
import ProgressBar from './components/ProgressBar.jsx'
|
||||
|
||||
<ProgressBar
|
||||
progress={65}
|
||||
status="Загрузка данных..."
|
||||
estimatedTime={45} // секунды
|
||||
variant="primary"
|
||||
striped
|
||||
animated
|
||||
/>
|
||||
```
|
||||
|
||||
**MultiStepProgress:**
|
||||
|
||||
```jsx
|
||||
import { MultiStepProgress } from './components/ProgressBar.jsx'
|
||||
|
||||
<MultiStepProgress
|
||||
steps={['Валидация', 'Обработка', 'Сохранение', 'Завершение']}
|
||||
currentStep={1} // текущий шаг (0-indexed)
|
||||
variant="success"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. MobileCardView
|
||||
|
||||
Компонент для отображения таблиц в виде карточек на мобильных с swipe gestures.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import MobileCardView from './components/MobileCardView.jsx'
|
||||
|
||||
<MobileCardView
|
||||
items={domains}
|
||||
onItemClick={(item) => console.log('Clicked:', item)}
|
||||
renderContent={(item) => (
|
||||
<div>
|
||||
<div className="fw-bold">{item.domain}</div>
|
||||
<div className="text-muted small">{item.community}</div>
|
||||
</div>
|
||||
)}
|
||||
actions={[
|
||||
{ label: 'Редактировать', icon: IconEdit, onClick: handleEdit },
|
||||
{ label: 'Удалить', icon: IconTrash, onClick: handleDelete, variant: 'danger' }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. KeyboardShortcutHint
|
||||
|
||||
Компонент для отображения горячих клавиш.
|
||||
|
||||
**Использование:**
|
||||
|
||||
```jsx
|
||||
import KeyboardShortcutHint, { ShortcutsList } from './components/KeyboardShortcutHint.jsx'
|
||||
|
||||
// Внутри кнопки:
|
||||
<button className="btn btn-primary">
|
||||
Сохранить
|
||||
<KeyboardShortcutHint shortcut="Ctrl + S" />
|
||||
</button>
|
||||
|
||||
// Список горячих клавиш:
|
||||
<ShortcutsList
|
||||
shortcuts={[
|
||||
{ description: 'Сохранить изменения', keys: 'Ctrl + S' },
|
||||
{ description: 'Отменить', keys: 'Ctrl + Z' },
|
||||
{ description: 'Поиск', keys: 'Ctrl + F' }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Улучшенные Компоненты
|
||||
|
||||
### ErrorAlert
|
||||
|
||||
Добавлены новые возможности:
|
||||
- Кнопка "Копировать ошибку" для bug reports
|
||||
- Кнопка "Повторить" для retry операций
|
||||
- Раскрывающиеся детали ошибки
|
||||
- Отображение кода ошибки
|
||||
|
||||
**Новое использование:**
|
||||
|
||||
```jsx
|
||||
<ErrorAlert
|
||||
message="Не удалось загрузить данные"
|
||||
error={{ code: 'E_NETWORK', details: errorDetails }}
|
||||
onRetry={fetchData}
|
||||
onClose={() => setError(null)}
|
||||
showDetails={true}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### EmptyState
|
||||
|
||||
Добавлены:
|
||||
- Поддержка иллюстраций
|
||||
- Различные размеры (small, default, large)
|
||||
- Варианты цветов (success, info, warning)
|
||||
- Анимация иллюстраций
|
||||
|
||||
**Новое использование:**
|
||||
|
||||
```jsx
|
||||
<EmptyState
|
||||
icon={IconDatabase}
|
||||
title="Нет доменов"
|
||||
description="Начните с добавления первого домена"
|
||||
size="large"
|
||||
variant="info"
|
||||
illustration="/illustrations/empty-state.svg"
|
||||
action={<button className="btn btn-primary">Добавить домен</button>}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pagination
|
||||
|
||||
Улучшения:
|
||||
- Jump to Page для быстрого перехода (показывается при > 10 страниц)
|
||||
- Иконки вместо текста для навигации
|
||||
- Улучшенная accessibility
|
||||
- Адаптивность для мобильных
|
||||
|
||||
**Автоматически используется везде, где был старый Pagination**
|
||||
|
||||
---
|
||||
|
||||
### Dashboard
|
||||
|
||||
Улучшения:
|
||||
- StatCard теперь поддерживает trends
|
||||
- Добавлен LastSaved в header
|
||||
- Hover эффекты на карточках (card-hover)
|
||||
- Tooltips на кнопках
|
||||
|
||||
**Изменения в коде:**
|
||||
|
||||
```jsx
|
||||
<StatCard
|
||||
icon={IconWorld}
|
||||
value={stats.domainsCount}
|
||||
title="Доменов"
|
||||
to="/domains"
|
||||
trend={true}
|
||||
previousValue={previousStats?.domainsCount}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Примеры Использования
|
||||
|
||||
### Пример 1: Форма с валидацией
|
||||
|
||||
```jsx
|
||||
function AddDomainForm() {
|
||||
const [domain, setDomain] = useState('')
|
||||
const [community, setCommunity] = useState('')
|
||||
|
||||
const validateDomain = (value) => ({
|
||||
valid: /^([a-z0-9-]+\.)+[a-z]{2,}$/i.test(value),
|
||||
message: 'Введите корректный домен'
|
||||
})
|
||||
|
||||
const validateCommunity = (value) => ({
|
||||
valid: /^\d+$/.test(value),
|
||||
message: 'Введите числовой community'
|
||||
})
|
||||
|
||||
return (
|
||||
<form>
|
||||
<ValidatedInput
|
||||
value={domain}
|
||||
onChange={(e) => setDomain(e.target.value)}
|
||||
validate={validateDomain}
|
||||
label="Домен"
|
||||
placeholder="example.com"
|
||||
required
|
||||
/>
|
||||
|
||||
<ValidatedInput
|
||||
value={community}
|
||||
onChange={(e) => setCommunity(e.target.value)}
|
||||
validate={validateCommunity}
|
||||
label="Community"
|
||||
placeholder="65000"
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Добавить
|
||||
<KeyboardShortcutHint shortcut="Ctrl + Enter" />
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Пример 2: Таблица с ContextMenu и MobileCardView
|
||||
|
||||
```jsx
|
||||
function DomainsTable({ domains, onEdit, onDelete }) {
|
||||
return (
|
||||
<>
|
||||
{/* Desktop view */}
|
||||
<div className="table-responsive d-none d-md-block">
|
||||
<table className="table">
|
||||
<tbody>
|
||||
{domains.map(domain => (
|
||||
<tr key={domain.id}>
|
||||
<td>{domain.name}</td>
|
||||
<td>{domain.community}</td>
|
||||
<td>
|
||||
<ContextMenu
|
||||
items={[
|
||||
{ label: 'Редактировать', icon: IconEdit, onClick: () => onEdit(domain) },
|
||||
{ label: 'Удалить', icon: IconTrash, onClick: () => onDelete(domain), variant: 'danger' }
|
||||
]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Mobile view */}
|
||||
<MobileCardView
|
||||
items={domains}
|
||||
renderContent={(domain) => (
|
||||
<div>
|
||||
<div className="fw-bold">{domain.name}</div>
|
||||
<div className="text-muted small">{domain.community}</div>
|
||||
</div>
|
||||
)}
|
||||
actions={[
|
||||
{ label: 'Редактировать', icon: IconEdit, onClick: onEdit },
|
||||
{ label: 'Удалить', icon: IconTrash, onClick: onDelete, variant: 'danger' }
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Пример 3: Dashboard с трендами
|
||||
|
||||
```jsx
|
||||
function Dashboard() {
|
||||
const [stats, setStats] = useState({})
|
||||
const [previousStats, setPreviousStats] = useState(null)
|
||||
const [lastFetchTime, setLastFetchTime] = useState(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Панель"
|
||||
actions={
|
||||
<div className="d-flex gap-3">
|
||||
<LastSaved timestamp={lastFetchTime} variant="compact" />
|
||||
<Tooltip content="Обновить данные" shortcut="F5">
|
||||
<button className="btn btn-outline-primary">
|
||||
<IconRefresh /> Обновить
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="row g-3">
|
||||
<div className="col-md-3">
|
||||
<StatCard
|
||||
icon={IconWorld}
|
||||
value={stats.domains}
|
||||
title="Доменов"
|
||||
trend={true}
|
||||
previousValue={previousStats?.domains}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Accessibility
|
||||
|
||||
- Всегда добавляйте `aria-label` для иконочных кнопок
|
||||
- Используйте semantic HTML
|
||||
- Обеспечьте минимум 44x44px для touch targets на мобильных
|
||||
- Добавляйте keyboard shortcuts для частых действий
|
||||
|
||||
### 2. Performance
|
||||
|
||||
- Используйте `useMemo` для тяжелых вычислений
|
||||
- Debounce для валидации форм
|
||||
- Lazy loading для больших списков
|
||||
- Virtualization для таблиц > 1000 записей
|
||||
|
||||
### 3. User Experience
|
||||
|
||||
- Показывайте loading states для всех async операций
|
||||
- Предоставляйте clear error messages с действиями
|
||||
- Используйте optimistic updates где возможно
|
||||
- Сохраняйте состояние форм при навигации
|
||||
|
||||
### 4. Mobile First
|
||||
|
||||
- Используйте MobileCardView для таблиц
|
||||
- Убедитесь что все элементы кликабельны на touch screens
|
||||
- Тестируйте swipe gestures
|
||||
- Адаптируйте модальные окна для мобильных
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
Планируемые улучшения:
|
||||
|
||||
- [ ] Виртуальный скроллинг для больших таблиц
|
||||
- [ ] Drag & Drop для изменения порядка
|
||||
- [ ] Сохранение пользовательских настроек
|
||||
- [ ] Dark mode improvements
|
||||
- [ ] Анимированные transitions между страницами
|
||||
- [ ] PWA support
|
||||
- [ ] Offline mode
|
||||
|
||||
---
|
||||
|
||||
## Вклад
|
||||
|
||||
При добавлении новых компонентов:
|
||||
|
||||
1. Следуйте существующим паттернам
|
||||
2. Добавляйте JSDoc комментарии
|
||||
3. Обеспечьте accessibility
|
||||
4. Тестируйте на мобильных
|
||||
5. Обновляйте эту документацию
|
||||
|
||||
---
|
||||
|
||||
**Версия:** 1.0.0
|
||||
**Последнее обновление:** 2025-01-15
|
||||
|
||||
Reference in New Issue
Block a user