npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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


Зміст


Архітектура проєкту

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 build

Vite збирає два бандли послідовно:

  1. dist/skyservice-ui-elements.{js,cjs} — веб-компоненти
  2. 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.json

CSS автоматично інжектується в JS — окремого .css-файлу немає та не потрібно.


Як додати нову логіку

Новий веб-компонент

  1. Створи директорію src/web-components/<назва>/
  2. Додай App.vue — кореневий Vue-компонент
  3. Додай 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,
})
  1. Зареєструй у точці входу src/webComponents.ts:
export { MyElement } from './web-components/my-element/index'
  1. Запусти npm run build.

Новий JS-модуль

  1. Реалізуй логіку в src/web-components/<назва>/modules/myFeature.ts
  2. Додай експорт у src/modulesCreator.ts:
import { myFeature } from './web-components/my-element/modules/myFeature'

export const myElementModule = { myFeature }
  1. Запусти 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 автоматично:

  1. Читає поточну версію з package.json
  2. Інкрементує patch-версію (якщо patch < 9) або minor
  3. Оновлює package.json
  4. Створює git-коміт і тег
  5. Очищає старі теги (залишає останні 10)
  6. Пушить коміт і тег до поточної гілки
# Залишити більше тегів (наприклад, 20)
npm run release 20

Тестовий реліз

npm run release-test

Теги позначаються суфіксом test і не потрапляють до списку production-тегів.

Видалити всі теги

npm run delete-tags