@studio-west/component-sw 0.11.36 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,6 +32,8 @@ The `Component SW` library provides a set of ready-to-use UI components on Vue 3
32
32
 
33
33
  - [SwInput](#swinput)
34
34
 
35
+ - [SwMenu](#swmenu)
36
+
35
37
  - [SwMessage](#swmessage)
36
38
 
37
39
  - [SwSelect](#swselect)
@@ -503,6 +505,275 @@ focusInput - focus на поле ввода
503
505
  ```
504
506
  ---
505
507
 
508
+ ## SwMenu
509
+
510
+ Компонент `SwMenu` представляет собой динамическое меню, которое генерируется на основе конфигурационного объекта или массива. Поддерживает рендеринг любых компонентов с возможностью настройки слотов и свойств.
511
+
512
+ ### Свойства / Properties
513
+
514
+ | Имя | Тип | По умолчанию | Значения/Описание |
515
+ |-------------|------------------------|--------------|-------------------------------------------------------------------------------------------------------|
516
+ | `menuItems` | `Object \| Array` | `required` | Конфигурация меню. Объект или массив объектов с компонентами и их настройками. |
517
+ | `class` | `String` | `''` | Добавляет пользовательский CSS-класс к компоненту. |
518
+
519
+ ### Модель / Model
520
+
521
+ model - массив булевых значений для двусторонней привязки (v-model) с компонентами первого уровня.
522
+
523
+ ### События / Events
524
+
525
+ click - событие клика по компоненту. Параметры: `{ index, componentName, event, value }`
526
+
527
+ change - событие изменения значения компонента. Параметры: `{ index, componentName, event?, value }`
528
+
529
+ ### Формат конфигурации menuItems
530
+
531
+ #### Вариант 1: Объект
532
+ ```js
533
+ {
534
+ 'SwButton': {
535
+ label: 'Кнопка',
536
+ type: 'primary',
537
+ slot: 'Текст кнопки'
538
+ },
539
+ 'SwSwitch': [
540
+ {
541
+ label: 'Переключатель 1',
542
+ on: 'Вкл',
543
+ off: 'Выкл'
544
+ },
545
+ {
546
+ label: 'Переключатель 2',
547
+ checkbox: true
548
+ }
549
+ ]
550
+ }
551
+ ```
552
+
553
+ #### Вариант 2: Массив
554
+ ```js
555
+ [
556
+ {
557
+ 'SwButton': {
558
+ label: 'Кнопка 1',
559
+ type: 'success'
560
+ }
561
+ },
562
+ {
563
+ 'SwInput': {
564
+ name: 'input1',
565
+ placeholder: 'Введите текст',
566
+ before: 'search'
567
+ }
568
+ }
569
+ ]
570
+ ```
571
+
572
+ ### Использование слотов
573
+
574
+ Для добавления контента в слоты компонентов используйте свойство `slot`:
575
+
576
+ ```js
577
+ {
578
+ 'SwDropdown': {
579
+ trigger: 'click',
580
+ 'slot.dropdown': {
581
+ 'SwDropdownItem': {
582
+ slot: 'Пункт меню'
583
+ }
584
+ }
585
+ }
586
+ }
587
+ ```
588
+
589
+ Или используйте префикс `slot.` для именованных слотов:
590
+
591
+ ```js
592
+ {
593
+ 'SwSection': {
594
+ name: 'Секция',
595
+ slot: 'Содержимое секции',
596
+ 'slot.footer': {
597
+ 'SwButton': {
598
+ label: 'Сохранить',
599
+ type: 'primary'
600
+ }
601
+ }
602
+ }
603
+ }
604
+ ```
605
+
606
+ ### Особенности реализации
607
+
608
+ - Автоматический рендеринг компонентов на основе конфигурации
609
+ - Поддержка вложенных компонентов через слоты
610
+ - Двусторонняя привязка (v-model) для компонентов первого уровня
611
+ - Эмит событий click и change для всех компонентов
612
+ - Рекурсивная обработка конфигурации любой сложности
613
+ - Поддержка как объектного, так и массивного формата menuItems
614
+
615
+ ### Пример использования / Example Usage
616
+
617
+ #### Базовый пример с объектом:
618
+
619
+ ```html
620
+ <script setup>
621
+ import { ref } from 'vue'
622
+
623
+ const menuState = ref([false, false])
624
+
625
+ const menuConfig = {
626
+ 'SwButton': {
627
+ label: 'Нажми меня',
628
+ type: 'primary',
629
+ prefix: 'star'
630
+ },
631
+ 'SwSwitch': [
632
+ {
633
+ label: 'Уведомления',
634
+ on: 'Вкл',
635
+ off: 'Выкл'
636
+ },
637
+ {
638
+ label: 'Звук',
639
+ checkbox: true
640
+ }
641
+ ]
642
+ }
643
+
644
+ const handleClick = (data) => {
645
+ console.log('Clicked:', data)
646
+ }
647
+
648
+ const handleChange = (data) => {
649
+ console.log('Changed:', data)
650
+ }
651
+ </script>
652
+
653
+ <sw-menu
654
+ :menu-items="menuConfig"
655
+ v-model="menuState"
656
+ @click="handleClick"
657
+ @change="handleChange"
658
+ />
659
+ ```
660
+
661
+ #### Пример с массивом:
662
+
663
+ ```html
664
+ <script setup>
665
+ import { ref } from 'vue'
666
+
667
+ const menuItems = [
668
+ {
669
+ 'SwButton': {
670
+ label: 'Главная',
671
+ type: 'info',
672
+ link: true,
673
+ href: '/'
674
+ }
675
+ },
676
+ {
677
+ 'SwButton': {
678
+ label: 'О нас',
679
+ type: 'info',
680
+ link: true,
681
+ href: '/about'
682
+ }
683
+ },
684
+ {
685
+ 'SwInput': {
686
+ name: 'search',
687
+ placeholder: 'Поиск...',
688
+ before: 'search',
689
+ size: 'small'
690
+ }
691
+ }
692
+ ]
693
+ </script>
694
+
695
+ <sw-menu
696
+ :menu-items="menuItems"
697
+ class="custom-menu"
698
+ />
699
+ ```
700
+
701
+ #### Пример с вложенными компонентами:
702
+
703
+ ```html
704
+ <script setup>
705
+ import { ref } from 'vue'
706
+
707
+ const dropdownMenu = {
708
+ 'SwDropdown': {
709
+ trigger: 'hover',
710
+ placement: 'bottom-left',
711
+ slot: {
712
+ 'SwButton': {
713
+ label: 'Меню',
714
+ type: 'primary',
715
+ postfix: 'arrow-down'
716
+ }
717
+ },
718
+ 'slot.dropdown': [
719
+ {
720
+ 'SwDropdownItem': {
721
+ iconBefore: 'user',
722
+ slot: 'Профиль'
723
+ }
724
+ },
725
+ {
726
+ 'SwDropdownItem': {
727
+ iconBefore: 'settings',
728
+ slot: 'Настройки'
729
+ }
730
+ }
731
+ ]
732
+ }
733
+ }
734
+ </script>
735
+
736
+ <sw-menu :menu-items="dropdownMenu" />
737
+ ```
738
+
739
+ #### Пример с использованием слотов:
740
+
741
+ ```html
742
+ <script setup>
743
+ import { h } from 'vue'
744
+ import { components } from '@studio-west/component-sw'
745
+
746
+ const complexMenu = {
747
+ 'SwSection': {
748
+ name: 'Панель управления',
749
+ iconAfter: 'chevron-down',
750
+ slot: 'Основное содержимое секции',
751
+ 'slot.footer': {
752
+ 'SwButtonGroup': {
753
+ radio: true,
754
+ slot: [
755
+ {
756
+ 'SwButton': {
757
+ label: 'Опция 1',
758
+ type: 'primary'
759
+ }
760
+ },
761
+ {
762
+ 'SwButton': {
763
+ label: 'Опция 2'
764
+ }
765
+ }
766
+ ]
767
+ }
768
+ }
769
+ }
770
+ }
771
+ </script>
772
+
773
+ <sw-menu :menu-items="complexMenu" />
774
+ ```
775
+ ---
776
+
506
777
  ## SwMessage
507
778
 
508
779
  Компонент `SwMessage` это выплывающее окно, центрируется по центру.
@@ -1,5 +1,5 @@
1
- import { n as e } from "./SwIcon-0PtPyq2k.js";
2
- import { t } from "./SwSelect-CuBxoXju.js";
1
+ import { n as e } from "./SwIcon-Ppc9lrFd.js";
2
+ import { t } from "./SwSelect-Y4RMcbqb.js";
3
3
  import { Transition as n, computed as r, createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createTextVNode as c, normalizeClass as l, onMounted as u, onUnmounted as d, openBlock as f, ref as p, renderSlot as m, toDisplayString as h, withCtx as g } from "vue";
4
4
  //#region src/components/SwAlert.vue
5
5
  var _ = /* @__PURE__ */ t({ default: () => v }), v = {
@@ -1,4 +1,4 @@
1
- import { n as e } from "./SwIcon-0PtPyq2k.js";
1
+ import { n as e } from "./SwIcon-Ppc9lrFd.js";
2
2
  import { computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, normalizeClass as a, openBlock as o, renderSlot as s } from "vue";
3
3
  //#region src/components/SwDropdownItem.vue
4
4
  var c = {
@@ -1,4 +1,4 @@
1
- import { n as e } from "./SwIcon-0PtPyq2k.js";
1
+ import { n as e } from "./SwIcon-Ppc9lrFd.js";
2
2
  import t from "./SwButton-Bvn3JLcT.js";
3
3
  import n from "./SwDropdown-DMkEn-su.js";
4
4
  import { createElementBlock as r, createElementVNode as i, createTextVNode as a, createVNode as o, mergeModels as s, normalizeClass as c, onMounted as l, openBlock as u, ref as d, renderSlot as f, toDisplayString as p, useModel as m, watchEffect as h, withCtx as g } from "vue";
@@ -1,5 +1,5 @@
1
1
  import { r as e } from "./utils-CGgSSFR1.js";
2
- import { t } from "./SwSelect-CuBxoXju.js";
2
+ import { t } from "./SwSelect-Y4RMcbqb.js";
3
3
  import { createElementBlock as n, createElementVNode as r, mergeProps as i, openBlock as a, unref as o } from "vue";
4
4
  //#region src/components/SwIcon.vue
5
5
  var s = /* @__PURE__ */ t({ default: () => l }), c = ["href"], l = {
@@ -1,5 +1,5 @@
1
1
  import { n as e } from "./utils-CGgSSFR1.js";
2
- import { n as t } from "./SwIcon-0PtPyq2k.js";
2
+ import { n as t } from "./SwIcon-Ppc9lrFd.js";
3
3
  import { computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, defineComponent as c, mergeModels as l, mergeProps as u, normalizeClass as d, openBlock as f, ref as p, renderSlot as m, toDisplayString as h, useModel as g, vModelDynamic as _, watch as v, withDirectives as y } from "vue";
4
4
  //#region src/components/SwInput.vue?vue&type=script&setup=true&lang.ts
5
5
  var b = ["for"], x = {
@@ -0,0 +1,94 @@
1
+ import { Fragment as e, computed as t, createBlock as n, createElementBlock as r, defineComponent as i, getCurrentInstance as a, h as o, mergeModels as s, normalizeClass as c, openBlock as l, renderList as u, resolveComponent as d, resolveDynamicComponent as f, useModel as p } from "vue";
2
+ //#endregion
3
+ //#region src/components/SwMenu.vue
4
+ var m = /* @__PURE__ */ i({
5
+ __name: "SwMenu",
6
+ props: /* @__PURE__ */ s({
7
+ menuItems: {},
8
+ class: { default: "" }
9
+ }, {
10
+ modelValue: {},
11
+ modelModifiers: {}
12
+ }),
13
+ emits: /* @__PURE__ */ s(["click", "change"], ["update:modelValue"]),
14
+ setup(i, { emit: s }) {
15
+ let m = i, h = p(i, "modelValue"), g = s, _ = a(), v = (e, t, n = null, r = null) => {
16
+ let i, a = null;
17
+ try {
18
+ if (typeof e == "string") {
19
+ if (i = _?.appContext?.components?.[e], !i) try {
20
+ a = d(e), a && typeof a == "object" && (i = a);
21
+ } catch (t) {
22
+ console.warn(`Failed to resolve component: ${e}`, t);
23
+ }
24
+ if (!i || typeof i == "string") return console.error(`Component "${e}" NOT FOUND`), o("div", { class: "error" }, `Component "${e}" not found`);
25
+ } else i = e;
26
+ } catch (t) {
27
+ return console.warn(`Component "${e}" not found`, t), o("div", { class: "error" }, `Component "${e}" not found`);
28
+ }
29
+ if (typeof t == "object" && t && !Array.isArray(t)) {
30
+ let r = {}, a = {};
31
+ for (let [e, n] of Object.entries(t)) if (e === "slot") r.default = () => typeof n == "object" && n && !Array.isArray(n) ? "text" in n ? o("span", { textContent: n.text }) : "html" in n ? o("span", { innerHTML: n.html }) : Object.entries(n).map(([e, t]) => Array.isArray(t) ? t.map((t) => v(e, t)) : v(e, t)).flat() : Array.isArray(n) ? n.map((e) => {
32
+ if (typeof e == "object" && !Array.isArray(e)) {
33
+ let [t, n] = Object.entries(e)[0];
34
+ return v(t, n);
35
+ }
36
+ return e;
37
+ }) : n;
38
+ else if (e.startsWith("slot.")) {
39
+ let t = e.substring(5);
40
+ r[t] = () => typeof n == "object" && n && !Array.isArray(n) ? "text" in n ? o("span", { textContent: n.text }) : "html" in n ? o("span", { innerHTML: n.html }) : Object.entries(n).map(([e, t]) => Array.isArray(t) ? t.map((t) => v(e, t)) : v(e, t)).flat() : Array.isArray(n) ? n.map((e) => {
41
+ if (typeof e == "object" && !Array.isArray(e)) {
42
+ let [t, n] = Object.entries(e)[0];
43
+ return v(t, n);
44
+ }
45
+ return e;
46
+ }) : n;
47
+ } else a[e] = n;
48
+ return n !== null && (a.onClick = (t) => {
49
+ g("click", {
50
+ index: n,
51
+ componentName: String(e),
52
+ event: t,
53
+ value: h.value?.[n]
54
+ });
55
+ }, a.onChange = (t) => {
56
+ g("change", {
57
+ index: n,
58
+ componentName: String(e),
59
+ event: t,
60
+ value: h.value?.[n]
61
+ });
62
+ }, a["onUpdate:modelValue"] = (t) => {
63
+ h.value ||= [];
64
+ let r = [...h.value];
65
+ for (; r.length <= n;) r.push(!1);
66
+ r[n] = t, h.value = r, g("change", {
67
+ index: n,
68
+ componentName: String(e),
69
+ value: t
70
+ });
71
+ }, a.modelValue = h.value && n < h.value.length ? h.value[n] : !1), Object.keys(r).length > 0 ? o(i, a, r) : o(i, a);
72
+ }
73
+ return o(i, t);
74
+ }, y = t(() => {
75
+ let e = [];
76
+ if (Array.isArray(m.menuItems)) m.menuItems.forEach((t, n) => {
77
+ if (typeof t == "object" && t) {
78
+ let [r, i] = Object.entries(t)[0];
79
+ e.push({ render: () => v(r, i, n) });
80
+ }
81
+ });
82
+ else {
83
+ let t = 0;
84
+ for (let [n, r] of Object.entries(m.menuItems)) Array.isArray(r) ? r.forEach((r) => {
85
+ e.push({ render: () => v(n, r, t++) });
86
+ }) : typeof r == "object" && r && e.push({ render: () => v(n, r, t) });
87
+ }
88
+ return e;
89
+ });
90
+ return (t, i) => (l(), r("nav", { class: c("sw-menu " + m.class) }, [(l(!0), r(e, null, u(y.value, (e, t) => (l(), n(f(e.render), { key: t }))), 128))], 2));
91
+ }
92
+ });
93
+ //#endregion
94
+ export { m as default };
@@ -1,4 +1,4 @@
1
- import { n as e } from "./SwIcon-0PtPyq2k.js";
1
+ import { n as e } from "./SwIcon-Ppc9lrFd.js";
2
2
  import t from "./SwButton-Bvn3JLcT.js";
3
3
  import { createCommentVNode as n, createElementBlock as r, createElementVNode as i, createTextVNode as a, createVNode as o, mergeModels as s, nextTick as c, normalizeClass as l, onUnmounted as u, openBlock as d, ref as f, renderSlot as p, toDisplayString as m, unref as h, useModel as g, useSlots as _, watch as v, withCtx as y } from "vue";
4
4
  //#region src/components/SwMessage.vue
@@ -1,4 +1,4 @@
1
- import e from "./SwSkeletonItem-D3Wjgl7J.js";
1
+ import e from "./SwSkeletonItem-BnXZXE54.js";
2
2
  import { createElementBlock as t, createVNode as n, normalizeClass as r, openBlock as i, renderSlot as a } from "vue";
3
3
  //#region src/components/SwSkeleton.vue
4
4
  var o = {
@@ -1,4 +1,4 @@
1
- import e from "./SwTableColumn-CUaP7Ncj.js";
1
+ import e from "./SwTableColumn-S8ldvuhL.js";
2
2
  import { Fragment as t, computed as n, createElementBlock as r, createElementVNode as i, createTextVNode as a, normalizeStyle as o, onMounted as s, openBlock as c, ref as l, renderList as u, renderSlot as d, toDisplayString as f, unref as p, useSlots as m } from "vue";
3
3
  //#region src/components/SwTable.vue
4
4
  var h = { class: "sw-table" }, g = ["colspan", "rowspan"], _ = ["colspan", "rowspan"], v = {