notification —— Уведомления
Уведомление — это «тяжеловесная версия» сообщения: с заголовком, закрепляется в одном из четырёх углов экрана, живёт дольше, поддерживает переход по клику. Применяется для системных или асинхронных событий — приход новых сообщений, завершение задачи, результат фоновой операции. core/notification занимается группировкой по четырём углам и смещением стопки — в этом его ключевое отличие от message (один столбец сверху по центру).
Когда использовать
Заголовок раздела «Когда использовать»- В каждой платформе реализован
LhNotification(сервисный API): системные уведомления, результаты асинхронных задач, события, которые можно посмотреть позже. - Сценарии с объёмным содержимым (заголовок + основной текст), требующие перехода пользователя.
- Не использовать для: мгновенной обратной связи по действию (используйте
message, он легче); подтверждений, требующих немедленной обработки (используйтеdialog); постоянных подсказок на странице (используйтеalert).
import { LH_NOTIFICATION_DURATION_DEFAULT, LH_NOTIFICATION_GAP_DEFAULT, getNotificationIconName, getNotificationClassNames, isTopPosition, isRightPosition, groupNotificationsByPosition, computeStackOffsets, getNotificationRole, getNotificationAria,} from '@lh-ui/share/core/notification'import type { LhNotificationType, LhNotificationPosition, LhNotificationRecord,} from '@lh-ui/share/core/notification'Замысел дизайна
Заголовок раздела «Замысел дизайна»Группировка по четырём углам, а не один столбец: groupNotificationsByPosition разрезает очередь на четыре группы по позициям, а сервисный слой рендерит группы в угловые контейнеры. В комментарии зафиксирован откат на случай непредвиденных данных: неизвестная позиция (грязные данные со стороны JS) падает в заданный по умолчанию top-right, и одна грязная запись не роняет всю группировку очереди.
Направление накопления computeStackOffsets: возвращается смещение каждой записи относительно края окна просмотра — первая, прижатая к краю, получает 0, а последующие накапливают «высота предыдущих записей + промежуток». Контейнеры, прижатые к низу, обращают направление сами — core не знает, с какой стороны находится контейнер, и считает только «далеко от края».
Длительность по умолчанию больше, чем у message: LH_NOTIFICATION_DURATION_DEFAULT = 4500 против 3000 у message — по своей природе уведомление «можно посмотреть позже», поэтому более долгий показ разумен.
Строковые проверки isTopPosition / isRightPosition: position.startsWith('top') / endsWith('right') — раскладывают составные позиции вроде 'top-left' на семантические флаги, по которым UI-слой определяет позиционирование контейнера и направление анимации появления (прижатые к верху въезжают сверху, прижатые к низу — снизу).
Таблица функций
Заголовок раздела «Таблица функций»| Функция | Сигнатура | Возвращаемое значение | Назначение |
|---|---|---|---|
getNotificationIconName |
(type) => LhIconName |
имя значка | четыре семантических типа |
getNotificationClassNames |
(state: { type, showClose, clickable }) => string[] |
имена классов | тип / возможность закрытия / кликабельность |
isTopPosition |
(position) => boolean |
прижата ли к верху | позиционирование контейнера и направление въезда |
isRightPosition |
(position) => boolean |
прижата ли к правому краю | позиционирование контейнера |
groupNotificationsByPosition |
(items) => Record<Position, T[]> |
четыре группы | неизвестная позиция откатывается в top-right |
computeStackOffsets |
(heights: number[], gap?) => number[] |
массив смещений | накопление высот + промежутки |
getNotificationRole |
(type) => 'alert' | 'status' |
роль live region | та же парадигма, что у message |
getNotificationAria |
(type) => LhAriaAttrs |
пакет aria-атрибутов | role |
Константы: LH_NOTIFICATION_DURATION_DEFAULT = 4500, LH_NOTIFICATION_GAP_DEFAULT = 16.
Определения типов
Заголовок раздела «Определения типов»export type LhNotificationType = 'info' | 'success' | 'warning' | 'danger' | 'error' // без loadingexport type LhNotificationPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
export interface LhNotificationRecord { id: number type: LhNotificationType title: string content: string duration: number // 0 означает «не закрывать автоматически» position: LhNotificationPosition showClose: boolean sx?: LhSxProps onClick?: () => void onClose?: () => void}Доступность
Заголовок раздела «Доступность»getNotificationAria('danger') // { role: 'alert' }getNotificationAria('success') // { role: 'status' }- Отображение ролей — та же парадигма, что у
message/alert: warning/danger/error зачитываются немедленно, info/success — в очереди. - Зачитывание заголовка: у уведомления есть заголовок; рекомендуется рендерить его как
<h4>(илиrole="heading"), и программа чтения экрана обработает его по иерархии заголовков; live region зачитывает только content, а заголовок доходит через семантику DOM. - Кликабельные уведомления: в состоянии
clickableвся карточка кликабельна и ведёт по ссылке — оборачивайте её в<button>илиrole="link", не вешайте на<div>только onclick, иначе пользователи клавиатуры не смогут её активировать. - Кнопка закрытия: у значка-кнопки должен быть
aria-label.
Примеры использования
Заголовок раздела «Примеры использования»import { groupNotificationsByPosition, computeStackOffsets, isTopPosition,} from '@lh-ui/share/core/notification'
isTopPosition('top-right') // trueisTopPosition('bottom-left') // falseisRightPosition('top-right') // trueisRightPosition('top-left') // false
groupNotificationsByPosition([ { position: 'top-right' }, { position: 'bottom-left' }, { position: 'top-right' },])// { 'top-right': [2 записи], 'top-left': [], 'bottom-right': [], 'bottom-left': [1 запись] }
groupNotificationsByPosition([{ position: 'middle' as never }])// неизвестная позиция → откат в 'top-right'
computeStackOffsets([40, 60, 30])// [0, 76, 152] ← 0, 40+16, 40+16+60+16computeStackOffsets([40]) // [0]// Сервисный слой: четыре угловых контейнера, каждый ведёт свою очередьconst groups = computed(() => { const byPos = groupNotificationsByPosition(queue.value) return Object.entries(byPos).map(([position, items]) => ({ position: position as LhNotificationPosition, items, // Смещение стопки: для контейнеров, прижатых к низу, нужно обратное направление // (первая прижата к низу, следующие складываются вверх) offsets: computeStackOffsets(items.map((i) => heights.value[i.id] ?? 60)), }))})<!-- Vue3 --><teleport to="body"> <div v-for="group in groups" :key="group.position" class="lh-notification" :class="[`lh-notification--${group.position}`]" > <transition-group :name="isTopPosition(group.position) ? 'lh-notification-slide-top' : 'lh-notification-slide-bottom'" tag="div"> <div v-for="(record, i) in group.items" :key="record.id" :class="getNotificationClassNames({ type: record.type, showClose: record.showClose, clickable: !!record.onClick })" :role="getNotificationRole(record.type)" :style="{ transform: `translateY(${(isTopPosition(group.position) ? 1 : -1) * group.offsets[i]}px)` }" @click="record.onClick?.()" > <LhIcon :name="getNotificationIconName(record.type)" /> <div class="lh-notification__main"> <h4 class="lh-notification__title">{{ record.title }}</h4> <p class="lh-notification__content">{{ record.content }}</p> </div> <button v-if="record.showClose" type="button" aria-label="Закрыть" @click.stop="close(record.id)">✕</button> </div> </transition-group> </div></teleport>// React: высоты нужно измерить после рендеринга (ref + useCallbackLayoutEffect),// затем считать смещение стопки; первый кадр использует оценочную высоту,// а после точного измерения обновляем// uniapp: <view> + фиксированное позиционирование по четырём углам;// измерение высот через createSelectorQueryСтили и токены
Заголовок раздела «Стили и токены»| Имя класса | Семантика |
|---|---|
.lh-notification |
контейнер (fixed в четырёх углах) |
.lh-notification--{top-right|top-left|bottom-right|bottom-left} |
позиция |
.lh-notification__item |
отдельная карточка |
.lh-notification__item--{info|success|warning|danger|error} |
семантический тип |
.lh-notification__item--closable / --clickable |
можно закрыть / кликабельно (подсветка при hover + указатель) |
.lh-notification__main / __title / __content |
основная область / заголовок / основной текст |
Фиксированная ширина (например, 320–360 px), фон --lh-bg-color + тень; четыре семантических цвета применяются к значку и левой цветовой полосе.
Замечания
Заголовок раздела «Замечания»- Для смещения стопки нужны реальные высоты:
computeStackOffsetsпринимает массив heights, и UI-слой должен измерять фактическую высоту рендера каждой записи. На первом кадре с оценочной высотой произойдёт смещение — после точного измерения обновите состояние один раз. - Обращение направления для нижних контейнеров:
computeStackOffsetsвозвращает «расстояние от края», для контейнеров bottom его нужно умножить на -1 (вниз — положительное направление). Это ответственность вызывающей стороны, core не знает стороны контейнера. - Уведомления с
duration: 0: не закрываются автоматически, подходят для таких случаев, как «непрочитанные сообщения», которые пользователь должен убрать сам — в сочетании сshowClose: trueили закрытием по клику. - Конфликт событий
onClickиshowClose: на кликабельной карточке кнопке закрытия нужен@click.stop, иначе клик по закрытию одновременно вызовет переход карточки. - Количество контейнеров при смешанных позициях: четыре угловых контейнера, даже пустые, обычно постоянно находятся в DOM (так нужно transition-group), а
v-ifсломает анимацию. - Граница с message: уведомление тяжелее (занимает угол, имеет заголовок, живёт дольше), для обратной связи по действию достаточно message. Оба могут сосуществовать (после действия выскакивает message, а системные уведомления идут через notification).
- Строгость типа
position: в TS неправильная позиция будет перехвачена, но вызывающая сторона на JS может передать что-то вроде'topCenter'— откат в top-right гарантирует, что ничего не сломается, но позиция окажется неожиданной, и вызывающей стороне нужно проверять себя.
@lh-ui/share/core/notification → groupNotificationsByPosition( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue