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.
- package/.env +1 -0
- package/.env.production +1 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +3 -0
- package/BUTTON_GUIDE.md +257 -0
- package/README.md +49 -0
- package/THEME_REFERENCE.md +310 -0
- package/eslint.config.ts +27 -0
- package/index.html +13 -0
- package/package.json +85 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +368 -0
- package/src/assets/fonts/Faruma.ttf +0 -0
- package/src/components/examples/AppBarExample.vue +101 -0
- package/src/components/examples/AvatarExample.vue +47 -0
- package/src/components/examples/BannerExample.vue +287 -0
- package/src/components/examples/BaseInputExample.vue +25 -0
- package/src/components/examples/BreadcrumbExample.vue +53 -0
- package/src/components/examples/CardExample.vue +77 -0
- package/src/components/examples/ChipExample.vue +225 -0
- package/src/components/examples/DatePickerExample.vue +31 -0
- package/src/components/examples/DropdownExample.vue +84 -0
- package/src/components/examples/EditorExample.vue +200 -0
- package/src/components/examples/ExpansionPanelExample.vue +42 -0
- package/src/components/examples/FileUploadExample.vue +40 -0
- package/src/components/examples/FormExample.vue +121 -0
- package/src/components/examples/HugeTest.vue +8 -0
- package/src/components/examples/LayoutContainerExample.vue +80 -0
- package/src/components/examples/ModalExample.vue +82 -0
- package/src/components/examples/NavDrawerExample.vue +170 -0
- package/src/components/examples/NumberFieldExample.vue +145 -0
- package/src/components/examples/RadioButtonExample.vue +161 -0
- package/src/components/examples/SearchExample.vue +322 -0
- package/src/components/examples/SelectExample.vue +121 -0
- package/src/components/examples/StackedTableViewExample.vue +53 -0
- package/src/components/examples/TabExample.vue +336 -0
- package/src/components/examples/TableExample.vue +228 -0
- package/src/components/examples/TextFieldExample.vue +181 -0
- package/src/components/examples/TextareaExample.vue +173 -0
- package/src/components/examples/ThemeToggle.vue +50 -0
- package/src/components/examples/TimelineExample.vue +66 -0
- package/src/components/examples/TipTapEditorExample.vue +20 -0
- package/src/components/examples/TooltipExample.vue +53 -0
- package/src/components/examples/VueDatePickerShowcase.vue +214 -0
- package/src/components/examples/_DatePickerExample.vue +33 -0
- package/src/components/examples/__FormExample.vue +77 -0
- package/src/components/index.ts +25 -0
- package/src/components/pgo/AppBar.vue +347 -0
- package/src/components/pgo/Avatar.vue +139 -0
- package/src/components/pgo/Banner.vue +300 -0
- package/src/components/pgo/Breadcrumb.vue +101 -0
- package/src/components/pgo/Button.vue +171 -0
- package/src/components/pgo/Card.vue +178 -0
- package/src/components/pgo/ConfirmationModel.vue +32 -0
- package/src/components/pgo/DataTable.vue +845 -0
- package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
- package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
- package/src/components/pgo/DatePicker/types.ts +11 -0
- package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
- package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
- package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
- package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
- package/src/components/pgo/Dropdown.vue +296 -0
- package/src/components/pgo/DropdownItem.vue +40 -0
- package/src/components/pgo/Editor.vue +511 -0
- package/src/components/pgo/ExpansionPanel.vue +185 -0
- package/src/components/pgo/Footer.vue +39 -0
- package/src/components/pgo/HeroIcon.vue +124 -0
- package/src/components/pgo/InputSearch.vue +194 -0
- package/src/components/pgo/LayoutContainer.vue +104 -0
- package/src/components/pgo/Main.vue +37 -0
- package/src/components/pgo/Modal.vue +273 -0
- package/src/components/pgo/NavDrawer.vue +127 -0
- package/src/components/pgo/NavDrawerItem.vue +161 -0
- package/src/components/pgo/NavigationDrawer.vue +849 -0
- package/src/components/pgo/OLDNavDrawer.vue +661 -0
- package/src/components/pgo/OldAppBar.vue +223 -0
- package/src/components/pgo/PApp.vue +102 -0
- package/src/components/pgo/Pagination.vue +242 -0
- package/src/components/pgo/Search copy.vue +310 -0
- package/src/components/pgo/Search.vue +411 -0
- package/src/components/pgo/StackedTableView.vue +167 -0
- package/src/components/pgo/Tab.vue +617 -0
- package/src/components/pgo/TestInput.vue +395 -0
- package/src/components/pgo/Timeline.vue +367 -0
- package/src/components/pgo/TimelineItem.vue +80 -0
- package/src/components/pgo/TipTapEditor.vue +315 -0
- package/src/components/pgo/Tooltip.NOTES.md +12 -0
- package/src/components/pgo/Tooltip.PROPS.md +21 -0
- package/src/components/pgo/Tooltip.vue +281 -0
- package/src/components/pgo/base/Base.vue +444 -0
- package/src/components/pgo/buttons/Chip.vue +324 -0
- package/src/components/pgo/buttons/ChipGroup.vue +224 -0
- package/src/components/pgo/buttons/Radio.vue +424 -0
- package/src/components/pgo/filters/FilterSection.vue +188 -0
- package/src/components/pgo/filters/Searchbar.vue +216 -0
- package/src/components/pgo/forms/DynamicForm.vue +45 -0
- package/src/components/pgo/forms/Form.vue +132 -0
- package/src/components/pgo/index.ts +15 -0
- package/src/components/pgo/inputs/Checkbox.vue +320 -0
- package/src/components/pgo/inputs/DatePicker.vue +395 -0
- package/src/components/pgo/inputs/FileUpload.vue +326 -0
- package/src/components/pgo/inputs/NumberField.vue +243 -0
- package/src/components/pgo/inputs/Radio.vue +162 -0
- package/src/components/pgo/inputs/RadioGroup.vue +188 -0
- package/src/components/pgo/inputs/Select.vue +535 -0
- package/src/components/pgo/inputs/TextField.vue +194 -0
- package/src/components/pgo/inputs/Textarea.vue +181 -0
- package/src/main.js +12 -0
- package/src/pgo-components/_index.js +31 -0
- package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
- package/src/pgo-components/assets/fonts/logo.png +0 -0
- package/src/pgo-components/composables/useTheme.js +10 -0
- package/src/pgo-components/directives/tooltip-directive.ts +393 -0
- package/src/pgo-components/index.js +96 -0
- package/src/pgo-components/lib/componentConfig.js +147 -0
- package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
- package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
- package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
- package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
- package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
- package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
- package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
- package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
- package/src/pgo-components/lib/drawerState.ts +3 -0
- package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
- package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
- package/src/pgo-components/lib/i18n/useI18n.js +35 -0
- package/src/pgo-components/lib/index.ts +38 -0
- package/src/pgo-components/pages/Component.vue +7 -0
- package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
- package/src/pgo-components/pages/Home.vue +130 -0
- package/src/pgo-components/pages/ListView.vue +370 -0
- package/src/pgo-components/pages/Page1.vue +296 -0
- package/src/pgo-components/pages/_Page1.vue +180 -0
- package/src/pgo-components/plugins/SnackBar.vue +251 -0
- package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
- package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
- package/src/pgo-components/plugins/theme-plugin.js +114 -0
- package/src/pgo-components/plugins/types.ts +46 -0
- package/src/pgo-components/plugins/useSnackBar.js +11 -0
- package/src/pgo-components/plugins/useSnackBar.ts +21 -0
- package/src/pgo-components/plugins/validation-plugin.js +11 -0
- package/src/pgo-components/services/Entry.json +813 -0
- package/src/pgo-components/services/axios.js +54 -0
- package/src/pgo-components/services/data.json +90 -0
- package/src/pgo-components/services/person.json +260 -0
- package/src/pgo-components/services/toast.ts +44 -0
- package/src/pgo-components/styles/global.css +234 -0
- package/src/pgo-components/styles/reset.css +96 -0
- package/src/pgo-components/styles/tokens.css +18 -0
- package/src/pgo-components/styles/utilities/border-radius.css +57 -0
- package/src/pgo-components/styles/utilities/borders.css +85 -0
- package/src/pgo-components/styles/utilities/colors.css +38 -0
- package/src/pgo-components/styles/utilities/cursor.css +19 -0
- package/src/pgo-components/styles/utilities/display.css +78 -0
- package/src/pgo-components/styles/utilities/elevation.css +33 -0
- package/src/pgo-components/styles/utilities/flex.css +403 -0
- package/src/pgo-components/styles/utilities/float.css +41 -0
- package/src/pgo-components/styles/utilities/hover.css +9 -0
- package/src/pgo-components/styles/utilities/index.css +18 -0
- package/src/pgo-components/styles/utilities/opacity.css +27 -0
- package/src/pgo-components/styles/utilities/overflow.css +26 -0
- package/src/pgo-components/styles/utilities/palette.css +515 -0
- package/src/pgo-components/styles/utilities/position.css +14 -0
- package/src/pgo-components/styles/utilities/sizing.css +70 -0
- package/src/pgo-components/styles/utilities/spacing.css +578 -0
- package/src/pgo-components/styles/utilities/transitions.css +58 -0
- package/src/pgo-components/styles/utilities/typography.css +91 -0
- package/src/pgo-components/styles/utilities/z-index.css +11 -0
- package/src/pgo-components/tokens/index.js +337 -0
- package/src/router/index.js +88 -0
- package/src/shims-vue.d.ts +14 -0
- package/src/validations/validationRules.js +50 -0
- package/tailwind.config.js +73 -0
- package/test.php +5 -0
- package/tsconfig.json +25 -0
- package/ui +31 -0
- package/ui.pgo.mv.conf +18 -0
- 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
|
+
}
|