pgo-uiux2 1.0.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.
Files changed (180) hide show
  1. package/.env +1 -0
  2. package/.env.production +1 -0
  3. package/.prettierrc +13 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/BUTTON_GUIDE.md +257 -0
  6. package/README.md +49 -0
  7. package/THEME_REFERENCE.md +310 -0
  8. package/eslint.config.ts +27 -0
  9. package/index.html +13 -0
  10. package/package.json +85 -0
  11. package/public/favicon.ico +0 -0
  12. package/src/App.vue +368 -0
  13. package/src/assets/fonts/Faruma.ttf +0 -0
  14. package/src/components/examples/AppBarExample.vue +101 -0
  15. package/src/components/examples/AvatarExample.vue +47 -0
  16. package/src/components/examples/BannerExample.vue +287 -0
  17. package/src/components/examples/BaseInputExample.vue +25 -0
  18. package/src/components/examples/BreadcrumbExample.vue +53 -0
  19. package/src/components/examples/CardExample.vue +77 -0
  20. package/src/components/examples/ChipExample.vue +225 -0
  21. package/src/components/examples/DatePickerExample.vue +31 -0
  22. package/src/components/examples/DropdownExample.vue +84 -0
  23. package/src/components/examples/EditorExample.vue +200 -0
  24. package/src/components/examples/ExpansionPanelExample.vue +42 -0
  25. package/src/components/examples/FileUploadExample.vue +40 -0
  26. package/src/components/examples/FormExample.vue +121 -0
  27. package/src/components/examples/HugeTest.vue +8 -0
  28. package/src/components/examples/LayoutContainerExample.vue +80 -0
  29. package/src/components/examples/ModalExample.vue +82 -0
  30. package/src/components/examples/NavDrawerExample.vue +170 -0
  31. package/src/components/examples/NumberFieldExample.vue +145 -0
  32. package/src/components/examples/RadioButtonExample.vue +161 -0
  33. package/src/components/examples/SearchExample.vue +322 -0
  34. package/src/components/examples/SelectExample.vue +121 -0
  35. package/src/components/examples/StackedTableViewExample.vue +53 -0
  36. package/src/components/examples/TabExample.vue +336 -0
  37. package/src/components/examples/TableExample.vue +228 -0
  38. package/src/components/examples/TextFieldExample.vue +181 -0
  39. package/src/components/examples/TextareaExample.vue +173 -0
  40. package/src/components/examples/ThemeToggle.vue +50 -0
  41. package/src/components/examples/TimelineExample.vue +66 -0
  42. package/src/components/examples/TipTapEditorExample.vue +20 -0
  43. package/src/components/examples/TooltipExample.vue +53 -0
  44. package/src/components/examples/VueDatePickerShowcase.vue +214 -0
  45. package/src/components/examples/_DatePickerExample.vue +33 -0
  46. package/src/components/examples/__FormExample.vue +77 -0
  47. package/src/components/index.ts +25 -0
  48. package/src/components/pgo/AppBar.vue +347 -0
  49. package/src/components/pgo/Avatar.vue +139 -0
  50. package/src/components/pgo/Banner.vue +300 -0
  51. package/src/components/pgo/Breadcrumb.vue +101 -0
  52. package/src/components/pgo/Button.vue +171 -0
  53. package/src/components/pgo/Card.vue +178 -0
  54. package/src/components/pgo/ConfirmationModel.vue +32 -0
  55. package/src/components/pgo/DataTable.vue +845 -0
  56. package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
  57. package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
  58. package/src/components/pgo/DatePicker/types.ts +11 -0
  59. package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
  60. package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
  61. package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
  62. package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
  63. package/src/components/pgo/Dropdown.vue +296 -0
  64. package/src/components/pgo/DropdownItem.vue +40 -0
  65. package/src/components/pgo/Editor.vue +511 -0
  66. package/src/components/pgo/ExpansionPanel.vue +185 -0
  67. package/src/components/pgo/Footer.vue +39 -0
  68. package/src/components/pgo/HeroIcon.vue +124 -0
  69. package/src/components/pgo/InputSearch.vue +194 -0
  70. package/src/components/pgo/LayoutContainer.vue +104 -0
  71. package/src/components/pgo/Main.vue +37 -0
  72. package/src/components/pgo/Modal.vue +273 -0
  73. package/src/components/pgo/NavDrawer.vue +127 -0
  74. package/src/components/pgo/NavDrawerItem.vue +161 -0
  75. package/src/components/pgo/NavigationDrawer.vue +849 -0
  76. package/src/components/pgo/OLDNavDrawer.vue +661 -0
  77. package/src/components/pgo/OldAppBar.vue +223 -0
  78. package/src/components/pgo/PApp.vue +102 -0
  79. package/src/components/pgo/Pagination.vue +242 -0
  80. package/src/components/pgo/Search copy.vue +310 -0
  81. package/src/components/pgo/Search.vue +411 -0
  82. package/src/components/pgo/StackedTableView.vue +167 -0
  83. package/src/components/pgo/Tab.vue +617 -0
  84. package/src/components/pgo/TestInput.vue +395 -0
  85. package/src/components/pgo/Timeline.vue +367 -0
  86. package/src/components/pgo/TimelineItem.vue +80 -0
  87. package/src/components/pgo/TipTapEditor.vue +315 -0
  88. package/src/components/pgo/Tooltip.NOTES.md +12 -0
  89. package/src/components/pgo/Tooltip.PROPS.md +21 -0
  90. package/src/components/pgo/Tooltip.vue +281 -0
  91. package/src/components/pgo/base/Base.vue +444 -0
  92. package/src/components/pgo/buttons/Chip.vue +324 -0
  93. package/src/components/pgo/buttons/ChipGroup.vue +224 -0
  94. package/src/components/pgo/buttons/Radio.vue +424 -0
  95. package/src/components/pgo/filters/FilterSection.vue +188 -0
  96. package/src/components/pgo/filters/Searchbar.vue +216 -0
  97. package/src/components/pgo/forms/DynamicForm.vue +45 -0
  98. package/src/components/pgo/forms/Form.vue +132 -0
  99. package/src/components/pgo/index.ts +15 -0
  100. package/src/components/pgo/inputs/Checkbox.vue +320 -0
  101. package/src/components/pgo/inputs/DatePicker.vue +395 -0
  102. package/src/components/pgo/inputs/FileUpload.vue +326 -0
  103. package/src/components/pgo/inputs/NumberField.vue +243 -0
  104. package/src/components/pgo/inputs/Radio.vue +162 -0
  105. package/src/components/pgo/inputs/RadioGroup.vue +188 -0
  106. package/src/components/pgo/inputs/Select.vue +535 -0
  107. package/src/components/pgo/inputs/TextField.vue +194 -0
  108. package/src/components/pgo/inputs/Textarea.vue +181 -0
  109. package/src/main.js +12 -0
  110. package/src/pgo-components/_index.js +31 -0
  111. package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
  112. package/src/pgo-components/assets/fonts/logo.png +0 -0
  113. package/src/pgo-components/composables/useTheme.js +10 -0
  114. package/src/pgo-components/directives/tooltip-directive.ts +393 -0
  115. package/src/pgo-components/index.js +96 -0
  116. package/src/pgo-components/lib/componentConfig.js +147 -0
  117. package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
  118. package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
  119. package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
  120. package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
  121. package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
  122. package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
  123. package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
  124. package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
  125. package/src/pgo-components/lib/drawerState.ts +3 -0
  126. package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
  127. package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
  128. package/src/pgo-components/lib/i18n/useI18n.js +35 -0
  129. package/src/pgo-components/lib/index.ts +38 -0
  130. package/src/pgo-components/pages/Component.vue +7 -0
  131. package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
  132. package/src/pgo-components/pages/Home.vue +130 -0
  133. package/src/pgo-components/pages/ListView.vue +370 -0
  134. package/src/pgo-components/pages/Page1.vue +296 -0
  135. package/src/pgo-components/pages/_Page1.vue +180 -0
  136. package/src/pgo-components/plugins/SnackBar.vue +251 -0
  137. package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
  138. package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
  139. package/src/pgo-components/plugins/theme-plugin.js +114 -0
  140. package/src/pgo-components/plugins/types.ts +46 -0
  141. package/src/pgo-components/plugins/useSnackBar.js +11 -0
  142. package/src/pgo-components/plugins/useSnackBar.ts +21 -0
  143. package/src/pgo-components/plugins/validation-plugin.js +11 -0
  144. package/src/pgo-components/services/Entry.json +813 -0
  145. package/src/pgo-components/services/axios.js +54 -0
  146. package/src/pgo-components/services/data.json +90 -0
  147. package/src/pgo-components/services/person.json +260 -0
  148. package/src/pgo-components/services/toast.ts +44 -0
  149. package/src/pgo-components/styles/global.css +234 -0
  150. package/src/pgo-components/styles/reset.css +96 -0
  151. package/src/pgo-components/styles/tokens.css +18 -0
  152. package/src/pgo-components/styles/utilities/border-radius.css +57 -0
  153. package/src/pgo-components/styles/utilities/borders.css +85 -0
  154. package/src/pgo-components/styles/utilities/colors.css +38 -0
  155. package/src/pgo-components/styles/utilities/cursor.css +19 -0
  156. package/src/pgo-components/styles/utilities/display.css +78 -0
  157. package/src/pgo-components/styles/utilities/elevation.css +33 -0
  158. package/src/pgo-components/styles/utilities/flex.css +403 -0
  159. package/src/pgo-components/styles/utilities/float.css +41 -0
  160. package/src/pgo-components/styles/utilities/hover.css +9 -0
  161. package/src/pgo-components/styles/utilities/index.css +18 -0
  162. package/src/pgo-components/styles/utilities/opacity.css +27 -0
  163. package/src/pgo-components/styles/utilities/overflow.css +26 -0
  164. package/src/pgo-components/styles/utilities/palette.css +515 -0
  165. package/src/pgo-components/styles/utilities/position.css +14 -0
  166. package/src/pgo-components/styles/utilities/sizing.css +70 -0
  167. package/src/pgo-components/styles/utilities/spacing.css +578 -0
  168. package/src/pgo-components/styles/utilities/transitions.css +58 -0
  169. package/src/pgo-components/styles/utilities/typography.css +91 -0
  170. package/src/pgo-components/styles/utilities/z-index.css +11 -0
  171. package/src/pgo-components/tokens/index.js +337 -0
  172. package/src/router/index.js +88 -0
  173. package/src/shims-vue.d.ts +14 -0
  174. package/src/validations/validationRules.js +50 -0
  175. package/tailwind.config.js +73 -0
  176. package/test.php +5 -0
  177. package/tsconfig.json +25 -0
  178. package/ui +31 -0
  179. package/ui.pgo.mv.conf +18 -0
  180. package/vite.config.js +42 -0
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <div class="p-6 max-w-4xl mx-auto space-y-8">
3
+ <Card dir="ltr"
4
+ rounded="lg"
5
+ labels="dd"
6
+ padding="px-4"
7
+ >
8
+
9
+ <div class="my-4 flex gap-4">
10
+ <InputSearch
11
+ v-model="basicSearch"
12
+ dir="ltr"
13
+ :items="countries"
14
+ :label="SelectLabel"
15
+ prepend=""
16
+ item-title="text"
17
+ item-value="value"
18
+ append="magnifying-glass"
19
+ searchable
20
+ bg="bg-white"
21
+ size="md"
22
+ rounded="sm"
23
+ />
24
+
25
+ <Select
26
+ v-model="multiSelect"
27
+ dir="ltr"
28
+ :items="countries"
29
+ :label="SelectLabel"
30
+ prepend="user"
31
+ item-title="text"
32
+ item-value="value"
33
+ multiple
34
+ searchable
35
+ bg="bg-white"
36
+ size="md"
37
+ rounded="sm"
38
+
39
+ />
40
+ <div class="flex items-center">
41
+ <HeroIcon name="funnel" type="outline" size="18" />
42
+ </div>
43
+ </div>
44
+
45
+ </Card>
46
+ <Card dir="ltr" :labels="labels" bg="bg-gray-100">
47
+
48
+ <div class="my-4 flex gap-4">
49
+ <InputSearch
50
+ v-model="basicSearch"
51
+ dir="ltr"
52
+ :items="countries"
53
+ :label="SelectLabel"
54
+ prepend=""
55
+ item-title="text"
56
+ item-value="value"
57
+ append="magnifying-glass"
58
+ searchable
59
+ bg="bg-white"
60
+ size="md"
61
+ />
62
+
63
+ <Select
64
+ v-model="multiSelect"
65
+ dir="ltr"
66
+ :items="countries"
67
+ :label="SelectLabel"
68
+ prepend="user"
69
+ item-title="text"
70
+ item-value="value"
71
+ multiple
72
+ searchable
73
+ bg="bg-white"
74
+ size="md"
75
+ rounded=""
76
+ />
77
+ <div class="flex items-center">
78
+ <HeroIcon name="funnel" type="outline" size="18" />
79
+ </div>
80
+ </div>
81
+
82
+ <!-- <div class="flex gap-4 text-gray-700">
83
+ <Search
84
+ v-model="basicSearch"
85
+ label="Search here "
86
+ placeholder=""
87
+ hint=""
88
+ />
89
+ <Select v-model="basicSearch" searchable :items="countries" label="" />
90
+ <Select
91
+ v-model="Dropdown"
92
+ label="select"
93
+ :items="countries"
94
+ searchable
95
+ size="xl"
96
+ />
97
+ </div> -->
98
+
99
+ </Card>
100
+
101
+
102
+
103
+
104
+ </div>
105
+ </template>
106
+ <script setup>
107
+ import { ref } from 'vue'
108
+ import Card from '../Card.vue'
109
+ import Search from '../Search.vue'
110
+ import Select from '../inputs/Select.vue'
111
+ import InputSearch from '../InputSearch.vue'
112
+ import HeroIcon from '../HeroIcon.vue'
113
+ import TestInput from '../TestInput.vue'
114
+ // import BaseInput from "../base/BaseInput.vue";
115
+ const emit = defineEmits(["update:modelValue"]);
116
+
117
+ const props = defineProps({
118
+ modelValue: [String, Number, Boolean, Object, Array, ''],
119
+ fields: { type: [String, Array, Object], default: '' }, //search, select, filer, date,
120
+ });
121
+
122
+
123
+ const multiSelect = ref([])
124
+ const basicSearch = ref('')
125
+ const Dropdown = ref('')
126
+
127
+ const labels = {
128
+ en: {
129
+ title: 'title 1',
130
+ body: 'body'
131
+ },
132
+ dv: {
133
+ title: 'ދިވެހި ',
134
+ body: 'ބޮޑީ'
135
+ }
136
+ }
137
+
138
+ const SelectLabel = {
139
+ en: {
140
+ label: 'Country'
141
+ },
142
+ dv: {
143
+ label: 'ޖަމިލް'
144
+ }
145
+ }
146
+
147
+ const countries = [
148
+ { value: 1, text: 'Maldives' },
149
+ { value: 3, text: 'India' },
150
+ { value: 4, text: 'Sri Lanka' },
151
+ { value: 2, text: 'USA' },
152
+ ]
153
+ </script>
154
+ <style scoped>
155
+ /* .label-gap {
156
+ position: relative;
157
+ } */
158
+
159
+ /* .label-gap::before {
160
+ content: "";
161
+ position: absolute;
162
+ top: -1.5px;
163
+ left: 0;
164
+ height: 1.5px;
165
+ width: 100%;
166
+ background: black;
167
+
168
+ mask: linear-gradient(
169
+ to right,
170
+ black 0%,
171
+ black calc(50% - 30px),
172
+ transparent calc(50% - 30px),
173
+ transparent calc(50% + 30px),
174
+ black calc(50% + 30px),
175
+ black 100%
176
+ );
177
+ } */
178
+
179
+
180
+ </style>
@@ -0,0 +1,251 @@
1
+ <template>
2
+ <div
3
+ class="max-w-lg min-w-[200px] shadow-lg rounded-md px-4 py-3 flex items-start gap-3 cursor-pointer select-none"
4
+ :class="[variantClasses, positionClasses]"
5
+ role="status"
6
+ @mouseenter="onMouseEnter"
7
+ @mouseleave="onMouseLeave"
8
+ @click="onClick"
9
+ ref="root"
10
+ v-show="visible"
11
+ @pointerdown.passive="onPointerDown"
12
+ @pointermove.passive="onPointerMove"
13
+ @pointerup.passive="onPointerUp"
14
+ @pointercancel.passive="onPointerCancel"
15
+ >
16
+ <div class="flex-1">
17
+ <div class="text-sm leading-tight" v-html="item.message"></div>
18
+ <div v-if="item.actions?.length" class="mt-2 flex gap-2">
19
+ <button v-for="(a, i) in item.actions" :key="i" @click.stop="onActionClick(a)" class="text-sm font-medium underline">
20
+ {{ a.label }}
21
+ </button>
22
+ </div>
23
+ </div>
24
+
25
+ <button v-if="showClose" @click.stop="closeLocal" class="ml-3 flex-none">
26
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
27
+ <path
28
+ fill-rule="evenodd"
29
+ d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
30
+ clip-rule="evenodd"
31
+ />
32
+ </svg>
33
+ </button>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue'
39
+ import type { SnackInternal, SnackAction } from './types.ts'
40
+
41
+ const props = defineProps<{ item: SnackInternal }>()
42
+ const emit = defineEmits<{
43
+ close: (id: string) => void
44
+ }>()
45
+
46
+ const item = props.item
47
+
48
+ const visible = ref(true)
49
+ const paused = ref(false)
50
+ const root = ref<HTMLElement | null>(null)
51
+
52
+ let timer: number | null = null
53
+ let startTime = 0
54
+ let remaining = item.timeout
55
+
56
+ // pointer swipe state
57
+ let isPointerDown = false
58
+ let startX = 0
59
+ let currentX = 0
60
+ let translateX = 0
61
+
62
+ const showIcon = item.icon !== undefined ? true : true
63
+ const iconComponent = item.icon || defaultIconFor(item.variant)
64
+ const showClose = true
65
+
66
+ function startTimer() {
67
+ if (item.timeout === 0) return
68
+ clearTimer()
69
+ startTime = Date.now()
70
+ timer = window.setTimeout(() => {
71
+ closeLocal()
72
+ }, remaining)
73
+ }
74
+
75
+ function clearTimer() {
76
+ if (timer) {
77
+ clearTimeout(timer)
78
+ timer = null
79
+ }
80
+ }
81
+
82
+ function onMouseEnter() {
83
+ if (!item.pauseOnHover) return
84
+ paused.value = true
85
+
86
+ if (timer) {
87
+ clearTimer()
88
+ const elapsed = Date.now() - startTime
89
+ remaining = Math.max(0, remaining - elapsed)
90
+ }
91
+ }
92
+
93
+ function onMouseLeave() {
94
+ if (!item.pauseOnHover) return
95
+
96
+ if (paused.value) {
97
+ paused.value = false
98
+ startTimer()
99
+ }
100
+ }
101
+
102
+ function onClick() {
103
+ if (item.closeOnClick) closeLocal()
104
+ }
105
+
106
+ function onActionClick(a: SnackAction) {
107
+ if (a.onClick) {
108
+ try {
109
+ a.onClick(item.meta)
110
+ } catch (e) {
111
+ console.warn(e)
112
+ }
113
+ }
114
+ }
115
+
116
+ function closeLocal() {
117
+ visible.value = false
118
+ // let animation settle then emit
119
+ setTimeout(() => {
120
+ // emit to container
121
+ emit('close', item.id)
122
+ if (item.onClose) item.onClose()
123
+ }, 180)
124
+ }
125
+
126
+ // pointer swipe handlers: horizontal swipe to dismiss
127
+ function onPointerDown(e: PointerEvent) {
128
+ isPointerDown = true
129
+ root.value?.setPointerCapture(e.pointerId)
130
+ startX = e.clientX
131
+ }
132
+ function onPointerMove(e: PointerEvent) {
133
+ if (!isPointerDown) return
134
+ currentX = e.clientX
135
+ translateX = currentX - startX
136
+ if (root.value) root.value.style.transform = `translateX(${translateX}px)`
137
+ }
138
+ function onPointerUp(e: PointerEvent) {
139
+ if (!isPointerDown) return
140
+ isPointerDown = false
141
+ root.value?.releasePointerCapture(e.pointerId)
142
+ // if swiped sufficiently
143
+ if (Math.abs(translateX) > 80) {
144
+ // animate out
145
+ if (root.value) root.value.style.transition = 'transform .16s ease, opacity .16s ease'
146
+ root.value!.style.transform = `translateX(${translateX > 0 ? 1000 : -1000}px)`
147
+ root.value!.style.opacity = '0'
148
+ setTimeout(() => closeLocal(), 160)
149
+ } else {
150
+ // reset
151
+ if (root.value) {
152
+ root.value.style.transition = 'transform .12s ease'
153
+ root.value.style.transform = ''
154
+ }
155
+ }
156
+ translateX = 0
157
+ }
158
+ function onPointerCancel() {
159
+ isPointerDown = false
160
+ translateX = 0
161
+ }
162
+
163
+ // read global direction and position the snackbar accordingly
164
+ const dirRef = ref<'ltr' | 'rtl'>('ltr')
165
+ const positionClasses = computed(() =>
166
+ dirRef.value === 'ltr' ? 'fixed left-4 top-4' : 'fixed right-4 top-4'
167
+ )
168
+
169
+ function getGlobalDir(): 'ltr' | 'rtl' {
170
+ const g = (window as any)?.globalrtl
171
+ if (typeof g === 'string') {
172
+ const v = g.toLowerCase()
173
+ if (v === 'ltr' || v === 'rtl') return v as 'ltr' | 'rtl'
174
+ }
175
+ const attr = document.documentElement.getAttribute('dir') || 'ltr'
176
+ return attr.toLowerCase() === 'rtl' ? 'rtl' : 'ltr'
177
+ }
178
+
179
+ let dirObserver: MutationObserver | null = null
180
+
181
+ onMounted(() => {
182
+ remaining = item.timeout
183
+ if (item.timeout > 0) startTimer()
184
+
185
+ // initialize position
186
+ dirRef.value = getGlobalDir()
187
+ // watch for dir changes on <html>
188
+ dirObserver = new MutationObserver(() => {
189
+ dirRef.value = getGlobalDir()
190
+ })
191
+ dirObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] })
192
+ })
193
+
194
+ onBeforeUnmount(() => {
195
+ clearTimer()
196
+ if (dirObserver) {
197
+ dirObserver.disconnect()
198
+ dirObserver = null
199
+ }
200
+ })
201
+
202
+ function defaultIconFor(variant: string) {
203
+ const icons: Record<string, string> = {
204
+ success: `
205
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
206
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 00-1.414-1.414L8 11.172 5.707 8.879a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l8-8z" clip-rule="evenodd"/>
207
+ </svg>
208
+ `,
209
+ error: `
210
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
211
+ <path fill-rule="evenodd" d="M8.257 3.099c.366-.446.957-.699 1.57-.699h.346c.613 0 1.204.253 1.57.699l6.518 7.944c.36.438.39 1.07.073 1.53a1.5 1.5 0 01-1.2.642H3.068a1.5 1.5 0 01-1.2-.642c-.317-.46-.287-1.092.073-1.53L8.257 3.1zM9 9a1 1 0 100 2 1 1 0 000-2zm1 4a1 1 0 10-2 0 1 1 0 002 0z" clip-rule="evenodd"/>
212
+ </svg>
213
+ `,
214
+ info: `
215
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
216
+ <path d="M18 10A8 8 0 1110 2a8 8 0 018 8zM9 8a1 1 0 112 0 1 1 0 01-2 0zm0 2h2v4H9v-4z"/>
217
+ </svg>
218
+ `,
219
+ warning: `
220
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
221
+ <path d="M8.257 3.099c.366-.446.957-.699 1.57-.699h.346c.613 0 1.204.253 1.57.699l6.518 7.944c.36.438.39 1.07.073 1.53a1.5 1.5 0 01-1.2.642H3.068a1.5 1.5 0 01-1.2-.642c-.317-.46-.287-1.092.073-1.53L8.257 3.1z"/>
222
+ </svg>
223
+ `,
224
+ default: `
225
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
226
+ <path d="M2 5a2 2 0 012-2h12a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V5z"/>
227
+ </svg>
228
+ `
229
+ }
230
+
231
+ return icons[variant] || icons['default']
232
+ }
233
+
234
+ // compute tailwind classes by variant
235
+ const variantClasses = (() => {
236
+ switch (item.variant) {
237
+ case 'success':
238
+ return 'bg-green-50 text-green-800 border border-green-100'
239
+ case 'error':
240
+ return 'bg-red-50 text-red-800 border border-red-100'
241
+ case 'warning':
242
+ return 'bg-yellow-50 text-yellow-800 border border-yellow-100'
243
+ case 'info':
244
+ return 'bg-blue-50 text-blue-800 border border-blue-100'
245
+ default:
246
+ return 'bg-white text-slate-800 border border-slate-100'
247
+ }
248
+ })()
249
+ </script>
250
+
251
+ <style scoped></style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <!-- Teleport handled by mounting root to body in plugin. We render lists by position. -->
3
+ <div aria-hidden="false">
4
+ <div v-for="pos of positions" :key="pos" :class="containerClass(pos)">
5
+ <transition-group name="snack-list" tag="div">
6
+ <SnackBar v-for="item in itemsByPosition(pos)" :key="item.id" :item="item" @close="handleClose" />
7
+ </transition-group>
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from 'vue'
14
+ import type { SnackInternal } from './types.js'
15
+ import SnackBar from './SnackBar.vue'
16
+
17
+ const props = defineProps<{ state: { active: SnackInternal[]; queue: SnackInternal[] }; config: any }>()
18
+
19
+ const positions = ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right']
20
+
21
+ function containerClass(pos: string) {
22
+ // maping positions to tailwind utility container classes
23
+ const map: Record<string, string> = {
24
+ 'top-left': 'fixed top-4 left-4 z-50 flex flex-col items-start space-y-2',
25
+ 'top-center': 'fixed top-4 left-1/2 transform -translate-x-1/2 z-50 flex flex-col items-center space-y-2',
26
+ 'top-right': 'fixed top-4 right-4 z-50 flex flex-col items-end space-y-2',
27
+ 'bottom-left': 'fixed bottom-4 left-4 z-50 flex flex-col-reverse items-start space-y-2',
28
+ 'bottom-center': 'fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 flex flex-col-reverse items-center space-y-2',
29
+ 'bottom-right': 'fixed bottom-4 right-4 z-50 flex flex-col-reverse items-end space-y-2'
30
+ }
31
+
32
+ return map[pos] || map['bottom-left']
33
+ }
34
+
35
+ function itemsByPosition(pos: string) {
36
+ return props.state.active.filter(s => s.position === pos)
37
+ }
38
+
39
+ function handleClose(id: string) {
40
+ // emit an event into plugin state by calling global metho on window? We'll call provided state manipulation by pushing an event
41
+ // The plugin exposed functions via app.config.globalProperties but this instance doens't have direct access. We'll dispatch a custom event.
42
+
43
+ const e = new CustomEvent('snackbar-close', { detail: { id } })
44
+ window.dispatchEvent(e)
45
+ }
46
+ </script>
47
+
48
+ <style>
49
+ /* basis transiction styles for group */
50
+ .snack-list-move {
51
+ transition: transform 0.2s ease;
52
+ }
53
+ </style>
@@ -0,0 +1,136 @@
1
+ import { App, createApp, reactive, readonly } from 'vue'
2
+ import SnackBarContainer from './SnackBarContainer.vue'
3
+ import type { SnackOptions, SnackInternal } from './types.ts'
4
+
5
+ const DEFAULT_GLOBAL_OPTIONS = {
6
+ position: 'bottom-center', // top-left, top-center, top-right, bottom-left, bottom-center, bottom-right
7
+ timeout: 4000,
8
+ transition: 'slide-up', // fade, slide-up, slide-down, slide-left, slide-right
9
+ mode: 'stack', //default mode
10
+ pauseOnHover: true,
11
+ closeOnClick: true,
12
+ rtl: false
13
+ }
14
+
15
+ export function SnackBarPlugin(app: App, options: Partial<typeof DEFAULT_GLOBAL_OPTIONS> = {}) {
16
+ const config = { ...DEFAULT_GLOBAL_OPTIONS, ...options }
17
+
18
+ const state = reactive({
19
+ //visible snack items
20
+ active: [] as SnackInternal[],
21
+
22
+ // queued snack items when using mode: 'queue'
23
+ queue: [] as SnackInternal[]
24
+ })
25
+
26
+ function generateId() {
27
+ return Math.random().toString(36).slice(2, 9)
28
+ }
29
+
30
+ function show(payload: SnackOptions) {
31
+ const id = payload.id ?? generateId()
32
+ const item: SnackInternal = {
33
+ id,
34
+ message: payload.message || '',
35
+ variant: payload.variant || 'default',
36
+ timeout: typeof payload.timeout === 'number' ? payload.timeout : config.timeout,
37
+ actions: payload.actions || [],
38
+ position: payload.position || config.position,
39
+ transition: payload.transition || config.transition,
40
+ closeOnClick: payload.closeOnClick ?? config.closeOnClick,
41
+ pauseOnHover: payload.pauseOnHover ?? config.pauseOnHover,
42
+ mode: payload.mode || config.mode,
43
+ onClose: payload.onClose ?? (() => {}),
44
+ color: payload.color,
45
+ icon: payload.icon,
46
+ rtl: payload.rtl ?? config.rtl,
47
+ meta: payload.meta ?? null
48
+ }
49
+
50
+ if (item.mode === 'queue') {
51
+ // if queue and there is an active, push to queue
52
+
53
+ if (state.active.length > 0){
54
+ state.queue.push(item)
55
+ } else {
56
+ state.active.push(item)
57
+ }
58
+ } else {
59
+ // stack
60
+ state.active.push(item)
61
+ }
62
+
63
+ return id
64
+
65
+ }
66
+
67
+ function close(id?: string) {
68
+ if (!id) return
69
+ const idx = state.active.findIndex((s) => s.id === id)
70
+ if (idx >= 0) {
71
+ state.active.splice(idx, 1)
72
+ // if we were using queue mode and there is something queue, push next
73
+ if (state.queue.length > 0) {
74
+ const next = state.queue.shift()!
75
+ state.active.push(next)
76
+ } else {
77
+ // maybe it's in queue, remove there too
78
+ const qidx = state.queue.findIndex(s => s.id === id)
79
+ if (qidx >= 0) state.queue.splice(qidx, 1)
80
+ }
81
+ }
82
+ }
83
+
84
+ function clear() {
85
+ state.active.splice(0)
86
+ state.queue.splice(0)
87
+ }
88
+
89
+ function success(message: string | SnackOptions, opts: Partial<SnackOptions> = {}) {
90
+ const payload = typeof message === 'string' ? { message, ...opts } : message
91
+ return show({ ...payload, variant: 'success' })
92
+ }
93
+
94
+ function error(message: string | SnackOptions, opts: Partial<SnackOptions> = {}) {
95
+ const payload = typeof message === 'string' ? { message, ...opts } : message
96
+ return show({ ...payload, variant: 'error' })
97
+ }
98
+
99
+ function info(message: string | SnackOptions, opts: Partial<SnackOptions> = {}) {
100
+ const payload = typeof message === 'string' ? { message, ...opts } : message
101
+ return show({ ...payload, variant: 'info' })
102
+ }
103
+
104
+ function warning(message: string | SnackOptions, opts: Partial<SnackOptions> = {}) {
105
+ const payload = typeof message === 'string' ? { message, ...opts } : message
106
+ return show({ ...payload, variant: 'warning' })
107
+ }
108
+
109
+ const api = {
110
+ state: readonly(state),
111
+ show,
112
+ success,
113
+ error,
114
+ info,
115
+ warning,
116
+ close,
117
+ clear,
118
+ config
119
+ }
120
+
121
+ // provide to app and globalProperties
122
+ app.provide('snackbar', api)
123
+ app.config.globalProperties.$snackbar = api
124
+
125
+ ;(window as any).__snackbar_api__ = api
126
+ window.addEventListener('snackbar-close', (ev: any) => {
127
+ const id = ev.detail?.id
128
+ if (id) api.close(id)
129
+ })
130
+
131
+ // mount container automatically to body
132
+ const mountEl = document.createElement('div')
133
+ document.body.appendChild(mountEl)
134
+ const vm = createApp(SnackBarContainer, { state, config })
135
+ vm.mount(mountEl)
136
+ }