sky-service-ui-components
v0.1.3
Published
UI-бібліотека для Skyservice — набір веб-компонентів та JS-модулів, що підключаються до будь-якого фронтенд-проєкту незалежно від фреймворку.
Downloads
155
Readme
skyservice-ui-elements
UI-бібліотека для Skyservice — набір веб-компонентів та JS-модулів, що підключаються до будь-якого фронтенд-проєкту незалежно від фреймворку.
Поточна версія: 0.0.8
Пакет: skyservice-ui-elements
Зміст
- Архітектура проєкту
- Глобальна інфраструктура
- Build
- Як додати нову логіку
- Експорт та імпорт у зовнішньому проєкті
- Веб-компонент
<sky-toast-notification> - Кілька екземплярів на сторінці
- Release
Архітектура проєкту
src/
├── webComponents.ts ← точка входу для веб-компонентів (→ dist/skyservice-ui-elements.js)
├── modulesCreator.ts ← точка входу для JS-модулів (→ dist/skyservice-modules-elements.js)
│
├── globalStores/
│ ├── globalStore.ts ← спільний Pinia-стор для всіх WC
│ ├── icons.ts ← сховище SVG-іконок
│ └── langs.ts ← сховище перекладів (зарезервоване місце)
│
├── globalUtils/
│ ├── createWebComponent.ts ← фабрика для реєстрації Custom Elements
│ └── createEmit.ts ← утиліти для emit-ів назовні з WC
│
├── globalInterfaces/
│ └── interfaces.ts ← реекспортує всі глобальні інтерфейси
│ └── modules/
│ └── storeInterface.ts
│
├── components/
│ └── SvgIcon.vue ← глобальний компонент відображення SVG
│
└── web-components/
└── notification/ ← веб-компонент сповіщень
├── index.ts
├── App.vue
├── const/notifyConst.ts
├── types/notifyTypes.ts
├── modules/notify.ts
├── stores/notificationStore.ts
└── components/CustomNotification.vueДва незалежні бандли після збірки:
| Import path | Dist-файл | Призначення |
|---|---|---|
| skyservice-ui-elements | dist/skyservice-ui-elements.js | Реєстрація Custom Elements у DOM |
| skyservice-ui-elements/modules | dist/skyservice-modules-elements.js | JS API (notify, globalStore тощо) |
Чому два entry? Web Components несуть з собою CSS і реєструють HTML-теги. Модулі — це чистий JS-код, що викликається програмно. Розділення дозволяє імпортувати лише те, що потрібно.
Глобальна інфраструктура
globalStore
Файл: src/globalStores/globalStore.ts
Спільний Pinia-стор ('global'), що живе в window.__sharedPinia__. Це дозволяє кільком екземплярам веб-компонентів на одній сторінці ділитися одним стором — навіть якщо вони завантажені з різних бандлів.
import globalStore, { getSharedPinia } from '@/globalStores/globalStore'
getSharedPinia() // ініціалізує або повертає window.__sharedPinia__
const store = globalStore()State:
| Поле | Тип | Опис |
|---|---|---|
| activeToastInstance | Map<string, any> | Всі активні toast-екземпляри за їх id |
Actions:
| Метод | Параметри | Опис |
|---|---|---|
| registerToastInstance(id, toast) | id: string, toast: any | Реєструє новий toast-екземпляр |
| unregisterToastInstance(id) | id: string | Видаляє toast-екземпляр при анмаунті WC |
icons
Файл: src/globalStores/icons.ts
Статичне сховище SVG-іконок у форматі { viewBox: string, path: string[] }. Використовується компонентом SvgIcon.
Доступні іконки:
| Ключ | Призначення |
|---|---|
| info | Інформація (синя) |
| success | Успіх (зелена) |
| error | Помилка (червона) |
| warning | Попередження (помаранчева) |
| loader | Завантаження — анімована |
| close | Закрити (хрестик) |
SvgIcon
Файл: src/components/SvgIcon.vue
Vue-компонент для відображення SVG-іконок зі сховища icons.ts. Рендерить <svg> з масиву path.
Props:
| Prop | Тип | Default | Опис |
|---|---|---|---|
| iconName | string | обов'язковий | Ключ іконки зі сховища |
| width | string | '20px' | Ширина |
| height | string | '20px' | Висота |
| fill | string | 'none' | Колір заливки |
| iconClass | string | '' | Додатковий CSS-клас |
<SvgIcon iconName="success" width="24" height="24" fill="#fff" />createWebComponent
Файл: src/globalUtils/createWebComponent.ts
Фабрична функція для створення та реєстрації Vue-компонента як Custom Element.
createWebComponent({
name: 'sky-my-element', // тег за замовчуванням
App: MyApp, // кореневий Vue-компонент
autoRegister?: false, // реєструвати одразу при імпорті (default: false)
useLocalStore?: () => any, // локальний Pinia-стор компонента
exposeExtra?: (localStore, globalStore) => Record<string, any>, // публічне API елемента
})Повертає клас Custom Element з методом .register(tagName?).
Особливості:
shadowRoot: false— компонент рендериться в light DOM, тому CSS доступний глобально- Автоматично коерсує HTML-атрибути:
"true"/"false"→Boolean,"42"→Number - Повторний виклик
.register()ігнорується, якщо тег вже зареєстрований
createEmit
Файл: src/globalUtils/createEmit.ts
Утиліта для відправлення подій з веб-компонента назовні через CustomEvent.
import { createEmit } from '@/globalUtils/createEmit'
// Всередині WC:
createEmit(appId, 'save', { id: 1 })
// Хост-застосунок слухає:
document.getElementById('my-element').addEventListener('save', (e) => {
console.log(e.detail) // { id: 1 }
})Подія має bubbles: true, composed: true — спливає крізь Shadow DOM, якщо він є.
Інтерфейси та типи
Файл: src/globalInterfaces/interfaces.ts
| Інтерфейс | Тип | Опис |
|---|---|---|
| StoreIconsInterface | { viewBox: string; path: string[] } | Формат іконки у сховищі |
Build
npm run buildVite збирає два бандли послідовно:
dist/skyservice-ui-elements.{js,cjs}— веб-компонентиdist/skyservice-modules-elements.{js,cjs}— JS-модулі
(запускається автоматично після першого через Vite-плагінbuild-modules-entry)
Після білду автоматично оновлюється папка modules/ в корені — сумісна директорія для Webpack 4, який не підтримує поле exports у package.json.
Результат:
dist/
├── skyservice-ui-elements.js ← ESM веб-компоненти
├── skyservice-ui-elements.cjs ← CJS веб-компоненти
├── skyservice-ui-elements.d.ts ← TypeScript-типи
├── skyservice-modules-elements.js ← ESM модулі
├── skyservice-modules-elements.cjs ← CJS модулі
└── skyservice-modules-elements.d.ts ← TypeScript-типи
modules/
├── index.js ← реекспорт dist для Webpack 4
├── index.cjs
├── index.d.ts
└── package.jsonCSS автоматично інжектується в JS — окремого .css-файлу немає та не потрібно.
Як додати нову логіку
Новий веб-компонент
- Створи директорію
src/web-components/<назва>/ - Додай
App.vue— кореневий Vue-компонент - Додай
index.ts:
// src/web-components/my-element/index.ts
import App from './App.vue'
import { createWebComponent } from '@/globalUtils/createWebComponent'
export const MyElement = createWebComponent({
name: 'sky-my-element',
App,
})- Зареєструй у точці входу src/webComponents.ts:
export { MyElement } from './web-components/my-element/index'- Запусти
npm run build.
Новий JS-модуль
- Реалізуй логіку в
src/web-components/<назва>/modules/myFeature.ts - Додай експорт у src/modulesCreator.ts:
import { myFeature } from './web-components/my-element/modules/myFeature'
export const myElementModule = { myFeature }- Запусти
npm run build.
Нова іконка
Додай новий ключ у src/globalStores/icons.ts:
myIcon: {
viewBox: '0 0 24 24',
path: ['<svg-path-data>'],
}Після цього іконка доступна через <SvgIcon iconName="myIcon" />.
Експорт та імпорт у зовнішньому проєкті
Встановлення
Через Git (semver-тег) — рекомендований спосіб
У package.json цільового проєкту:
"skyservice-ui-elements": "git+https://login:[email protected]/skyservice/nodejs/sky-service-ui-components.git#semver:0.0.8"Після зміни встановити:
npm install| Мета | Запис |
|---|---|
| Стабільна версія (production) | #semver:0.0.8 |
| Тестова версія (перед релізом) | #semver:test |
Через npm link (локальна розробка)
# У папці бібліотеки
npm run build
npm link
# У папці цільового проєкту
npm link skyservice-ui-elementsЩо імпортується
З основного entry
import { NotificationElement } from 'skyservice-ui-elements'| Експорт | Тип | Опис |
|---|---|---|
| NotificationElement | Custom Element class | Клас веб-компонента <sky-toast-notification> |
З /modules entry
import { notificationModule, globalStore } from 'skyservice-ui-elements/modules'| Експорт | Тип | Опис |
|---|---|---|
| notificationModule | { notify } | JS API для виклику сповіщень |
| globalStore | Pinia store factory | Внутрішній стор (для розширених сценаріїв) |
Веб-компонент <sky-toast-notification>
Компонент сповіщень складається з двох частин:
- Web Component
<sky-toast-notification>— монтується на сторінку та створює DOM-контейнер для тостів notify— JS API для виклику тостів програмно з будь-якого місця коду
Реєстрація
Виконати один раз при старті застосунку (main.ts або index.ts):
import { NotificationElement } from 'skyservice-ui-elements'
NotificationElement.register()
// або з кастомною назвою тегу:
NotificationElement.register('my-toast')Vue 3 — щоб Vue не скаржився на невідомий тег, у vite.config.ts:
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.includes('-')
}
}
})Після реєстрації розмісти тег у кореневому компоненті. Достатньо одного тегу на застосунок:
<sky-toast-notification />HTML-атрибути (props)
| Атрибут | Тип | Default | Опис |
|---|---|---|---|
| element-id | string | '' (auto UUID) | Унікальний ID екземпляра. Потрібен якщо на сторінці кілька тегів |
| newest-on-top | boolean | true | Нові сповіщення з'являються зверху |
| max-toasts | number | 5 | Максимальна кількість одночасно видимих сповіщень |
| transition | string | 'Vue-Toastification__fade' | CSS-анімація появи |
| custom-class | string | '' | Додатковий CSS-клас для контейнера тостів |
<sky-toast-notification
element-id="main"
newest-on-top="true"
max-toasts="5"
transition="Vue-Toastification__fade"
custom-class="my-custom-class"
/>Виклик сповіщень через notify
import { notificationModule } from 'skyservice-ui-elements/modules'
const { notify } = notificationModule
notify.success({ toastData: { title: 'Збережено!' } })
notify.error({ toastData: { title: 'Помилка', description: 'Щось пішло не так' } })Типи сповіщень
| Метод | Колір іконки | Іконка | Анімація | Автозакриття |
|---|---|---|---|---|
| notify.success() | #39B362 (зелений) | success | ні | так (4 с) |
| notify.error() | #FE7066 (червоний) | error | ні | так (4 с) |
| notify.warning() | #FFA26B (помаранчевий) | warning | ні | так (4 с) |
| notify.info() | #00A5FF (блакитний) | info | ні | так (4 с) |
| notify.loading() | #5A5A5A (сірий) | loader | обертання | ні |
| notify.default() | без іконки | — | ні | так (4 с) |
notify.loading()завжди ігноруєduration— тост залишається на екрані до ручного закриття.
API notify
Методи показу — приймають NotifyConfig:
notify.success(config: NotifyConfig) => Promise<string | number | null>
notify.error(config: NotifyConfig) => Promise<string | number | null>
notify.warning(config: NotifyConfig) => Promise<string | number | null>
notify.info(config: NotifyConfig) => Promise<string | number | null>
notify.loading(config: NotifyConfig) => Promise<string | number | null>
notify.default(config: NotifyConfig) => Promise<string | number | null>Метод закриття:
notify.dismiss(toastId: string | number, elementId?: string) => Promise<void>elementId потрібен лише якщо на сторінці кілька тегів <sky-toast-notification>.
Повернені значення
Кожен метод показу повертає Promise<string | number | null>:
| Значення | Коли |
|---|---|
| string або number | Успішно показаний тост — це його унікальний ID |
| null | WC не знайдений або ще не ініціалізований |
ID тоста використовується для програматичного закриття через notify.dismiss(toastId).
const toastId = await notify.loading({ toastData: { title: 'Зберігається...' } })
await saveData()
if (toastId !== null) {
notify.dismiss(toastId)
}
notify.success({ toastData: { title: 'Збережено!' } })NotifyConfig — повна структура
type NotifyConfig = {
// Якщо на сторінці кілька тегів <sky-toast-notification> — вказати element-id потрібного.
// Якщо тег один — не потрібно.
elementId?: string
toastData?: {
title?: string // заголовок (жирний текст)
description?: string // опис під заголовком
useCloseButton?: boolean // показувати кнопку X (default: true)
onClose?: () => void // коллбек при закритті через кнопку X
}
toastOptions?: {
duration?: number | false // мс або false — не закривати (default: 4000)
position?: ToastPosition // позиція (default: 'bottom-right')
closeOnClick?: boolean // закрити при кліку на тіло (default: true)
pauseOnHover?: boolean // зупиняти таймер при наведенні (default: false)
draggable?: boolean // дозволяти перетягування (default: true)
hideProgressBar?: boolean // приховати прогрес-бар (default: true)
}
toastAdditionalInfo?: {
leftBtn?: {
buttonText: string
buttonAction: (ctx: { close: () => void }) => void
}
rightBtn?: {
buttonText: string
buttonAction: (ctx: { close: () => void }) => void
}
}
}Доступні позиції (ToastPosition):
'top-left' | 'top-center' | 'top-right'
'bottom-left' | 'bottom-center' | 'bottom-right'Приклади використання
Базове сповіщення
notify.success({
toastData: { title: 'Успішно збережено' }
})З описом та колбеком на закриття
notify.error({
toastData: {
title: 'Помилка завантаження',
description: 'Перевірте з\'єднання з інтернетом',
onClose: () => console.log('Тост закрито'),
}
})З кнопками дій
notify.warning({
toastData: {
title: 'Видалити запис?',
useCloseButton: false,
},
toastAdditionalInfo: {
leftBtn: {
buttonText: 'Скасувати',
buttonAction: ({ close }) => close(),
},
rightBtn: {
buttonText: 'Видалити',
buttonAction: ({ close }) => {
deleteItem()
close()
},
},
},
})Loading → dismiss (патерн асинхронної операції)
const loadingId = await notify.loading({
toastData: {
title: 'Збереження...',
useCloseButton: false,
},
toastOptions: { duration: false },
})
try {
await saveData()
if (loadingId !== null) notify.dismiss(loadingId)
notify.success({ toastData: { title: 'Збережено!' } })
} catch {
if (loadingId !== null) notify.dismiss(loadingId)
notify.error({ toastData: { title: 'Помилка збереження' } })
}Тост без автозакриття
notify.info({
toastData: { title: 'Важливе повідомлення' },
toastOptions: { duration: false },
})Кастомна позиція та тривалість
notify.info({
toastData: { title: 'Нове повідомлення' },
toastOptions: {
position: 'top-center',
duration: 6000,
pauseOnHover: true,
},
})Vue 3 — повний приклад підключення
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { NotificationElement } from 'skyservice-ui-elements'
NotificationElement.register()
createApp(App).mount('#app')<!-- App.vue -->
<template>
<RouterView />
<sky-toast-notification />
</template>// AnyComponent.vue
import { notificationModule } from 'skyservice-ui-elements/modules'
const { notify } = notificationModule
function onSave() {
notify.success({ toastData: { title: 'Збережено!' } })
}React — підключення
// index.tsx
import { NotificationElement } from 'skyservice-ui-elements'
NotificationElement.register()// App.tsx
export default function App() {
return (
<>
<Router />
<sky-toast-notification />
</>
)
}// anyComponent.ts
import { notificationModule } from 'skyservice-ui-elements/modules'
const { notify } = notificationModule
notify.error({ toastData: { title: 'Помилка!' } })Vanilla JS (без фреймворку)
<script type="module">
import { NotificationElement } from 'skyservice-ui-elements'
import { notificationModule } from 'skyservice-ui-elements/modules'
NotificationElement.register()
document.body.insertAdjacentHTML('beforeend', '<sky-toast-notification></sky-toast-notification>')
const { notify } = notificationModule
document.getElementById('btn').addEventListener('click', () => {
notify.success({ toastData: { title: 'Клік!' } })
})
</script>Кілька екземплярів на сторінці
Якщо потрібно кілька незалежних контейнерів (наприклад, різні зони екрану):
<sky-toast-notification element-id="top" />
<sky-toast-notification element-id="sidebar" newest-on-top="false" max-toasts="3" />notify.success({
elementId: 'top',
toastData: { title: 'Верхнє сповіщення' },
})
notify.error({
elementId: 'sidebar',
toastData: { title: 'Бічне сповіщення' },
})Якщо
elementIdне вказано і тег один — він використовується автоматично.
Якщо тегів кілька іelementIdне вказано — у консоль виводиться помилка.
Release
Релізний скрипт
npm run build
npm run releaseСкрипт scripts/release.cjs автоматично:
- Читає поточну версію з
package.json - Інкрементує patch-версію (якщо
patch < 9) або minor - Оновлює
package.json - Створює git-коміт і тег
- Очищає старі теги (залишає останні 10)
- Пушить коміт і тег до поточної гілки
# Залишити більше тегів (наприклад, 20)
npm run release 20Тестовий реліз
npm run release-testТеги позначаються суфіксом test і не потрапляють до списку production-тегів.
Видалити всі теги
npm run delete-tags