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,223 @@
1
+ <template>
2
+ <header
3
+ :class="[
4
+ 'w-full flex flex-col',
5
+ appBarZClass,
6
+ appBarZClass,
7
+ fixed
8
+ ? 'fixed top-0 left-0'
9
+ : floating
10
+ ? 'absolute top-0 left-1/2 -translate-x-1/2 rounded-xl shadow-lg'
11
+ : '',
12
+ elevation ? `shadow-${elevation}` : shadow ? shadow : '',
13
+ hidden ? 'hidden' : '',
14
+ color,
15
+ border,
16
+ p,
17
+ m,
18
+ customClass,
19
+ ]"
20
+ :dir="rtl ? 'rtl' : 'ltr'"
21
+ :style="[customStyle, appBarZStyle]"
22
+ data-component="appbar"
23
+ >
24
+ <div
25
+ :class="[
26
+ 'max-w-7xl mx-auto w-full flex items-center h-16 justify-between',
27
+ innerDirectionClass,
28
+ ]"
29
+ >
30
+ <!-- ORDERED REGIONS -->
31
+ <template v-for="region in orderedRegions" :key="region">
32
+ <!-- PREPEND REGION -->
33
+ <template v-if="region === 'prepend'">
34
+ <div :class="['flex items-center', prependClass]">
35
+ <slot name="prepend" />
36
+
37
+ <button
38
+ v-if="drawer || menu"
39
+ @click="$emit('toggle-drawer')"
40
+ class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-600 hover:bg-gray-100"
41
+ >
42
+ <slot name="drawer-icon">
43
+ <HeroIcon name="bars-3" size="24" color="text-gray-600" />
44
+ </slot>
45
+ </button>
46
+
47
+ <div
48
+ :class="[
49
+ 'flex-shrink-0 flex items-center',
50
+ titleMarginClass,
51
+ titleAlignClass,
52
+ ]"
53
+ >
54
+ <slot name="title">
55
+ <span :class="['font-semibold', titleClass]">{{ title }}</span>
56
+ </slot>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <!-- NAV REGION -->
62
+ <template v-if="region === 'nav'">
63
+ <nav :class="navClass">
64
+ <slot name="nav" />
65
+ </nav>
66
+ </template>
67
+
68
+ <!-- ACTIONS REGION -->
69
+ <template v-if="region === 'actions'">
70
+ <div :class="['flex items-center', actionsSpaceClass]">
71
+ <slot name="actions" />
72
+
73
+ <!-- Overflow actions -->
74
+ <div v-if="overflowActions.length" class="relative">
75
+ <button
76
+ @click="toggleOverflow"
77
+ class="p-2 rounded-full hover:bg-gray-100 focus:outline-none"
78
+ >
79
+ <svg
80
+ class="h-5 w-5"
81
+ viewBox="0 0 24 24"
82
+ fill="none"
83
+ stroke="currentColor"
84
+ >
85
+ <circle cx="12" cy="12" r="2" />
86
+ <circle cx="19" cy="12" r="2" />
87
+ <circle cx="5" cy="12" r="2" />
88
+ </svg>
89
+ </button>
90
+
91
+ <div
92
+ v-if="isOverflowOpen"
93
+ :class="[
94
+ 'absolute mt-2 w-40 bg-white rounded shadow-lg z-50',
95
+ overflowPositionClass,
96
+ ]"
97
+ >
98
+ <ul>
99
+ <li v-for="(action, i) in overflowActions" :key="i">
100
+ <button
101
+ @click="action.onClick"
102
+ class="w-full text-left px-4 py-2 hover:bg-gray-100"
103
+ >
104
+ <span v-if="action.icon" class="mr-2">
105
+ <component :is="action.icon" />
106
+ </span>
107
+ {{ action.label }}
108
+ </button>
109
+ </li>
110
+ </ul>
111
+ </div>
112
+ </div>
113
+
114
+ <slot name="append" />
115
+ </div>
116
+ </template>
117
+ </template>
118
+ </div>
119
+
120
+ <!-- MOBILE NAV -->
121
+ <div v-show="isOpen" class="md:hidden bg-white border-t">
122
+ <div class="px-2 pt-2 pb-3 space-y-1">
123
+ <slot name="mobile-nav" />
124
+ </div>
125
+ </div>
126
+ </header>
127
+ </template>
128
+
129
+ <script setup>
130
+ import { ref, computed, onMounted } from "vue";
131
+ import { HeroIcon } from "../pgo/";
132
+
133
+ const props = defineProps({
134
+ title: String,
135
+ titleAlign: { type: String, default: "left" },
136
+ titleClass: String,
137
+ fixed: Boolean,
138
+ floating: Boolean,
139
+ elevation: [String, Number],
140
+ shadow: String,
141
+ color: String,
142
+ border: String,
143
+ p: String,
144
+ m: String,
145
+ hidden: Boolean,
146
+ customClass: String,
147
+ customStyle: [String, Object],
148
+ zIndex: { type: [String, Number], default: undefined },
149
+ drawer: Boolean,
150
+ menu: Boolean,
151
+ overflowActions: { type: Array, default: () => [] },
152
+ rtl: Boolean,
153
+ });
154
+
155
+ const emit = defineEmits(["toggle-drawer"]);
156
+
157
+ const isOpen = ref(false);
158
+ const isOverflowOpen = ref(false);
159
+
160
+ const toggleOverflow = () => (isOverflowOpen.value = !isOverflowOpen.value);
161
+
162
+ const orderedRegions = computed(() =>
163
+ props.rtl ? ["actions", "nav", "prepend"] : ["prepend", "nav", "actions"]
164
+ );
165
+
166
+ const innerDirectionClass = computed(() =>
167
+ props.rtl ? "flex-row-reverse" : "flex-row"
168
+ );
169
+
170
+ const prependClass = computed(() => (props.rtl ? "mr-2" : ""));
171
+
172
+ const titleMarginClass = computed(() => (props.rtl ? "mr-2" : "ml-2"));
173
+
174
+ const titleAlignClass = computed(() => {
175
+ const align = props.titleAlign;
176
+ if (props.rtl) {
177
+ return {
178
+ center: "justify-center text-center w-full",
179
+ left: "justify-end text-right w-full",
180
+ right: "",
181
+ }[align];
182
+ }
183
+ return {
184
+ center: "justify-center text-center w-full",
185
+ right: "justify-end text-right w-full",
186
+ left: "",
187
+ }[align];
188
+ });
189
+
190
+ const navClass = computed(() =>
191
+ props.rtl
192
+ ? "hidden md:flex md:mr-6 md:space-x-4 items-center"
193
+ : "hidden md:flex md:ml-6 md:space-x-4 items-center"
194
+ );
195
+
196
+ const actionsSpaceClass = computed(() =>
197
+ props.rtl ? "space-x-2 space-x-reverse" : "space-x-2"
198
+ );
199
+
200
+ const overflowPositionClass = computed(() =>
201
+ props.rtl ? "left-0" : "right-0"
202
+ );
203
+
204
+ // z-index support for appbar
205
+ const appBarZClass = computed(() => {
206
+ if (props.zIndex !== undefined && typeof props.zIndex === "string") {
207
+ if (String(props.zIndex).startsWith("z-")) return String(props.zIndex);
208
+ }
209
+ return "z-30";
210
+ });
211
+ const appBarZStyle = computed(() => {
212
+ if (props.zIndex !== undefined && typeof props.zIndex === "number") {
213
+ return { zIndex: props.zIndex };
214
+ }
215
+ return undefined;
216
+ });
217
+
218
+ onMounted(() => {
219
+ const savedRtl = localStorage.getItem("rtlSetting");
220
+
221
+
222
+ });
223
+ </script>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <div :class="['min-h-screen flex flex-col w-full', rtlClass]" ref="root">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import {
9
+ provide,
10
+ ref,
11
+ reactive,
12
+ watch,
13
+ onMounted,
14
+ onBeforeUnmount,
15
+ computed,
16
+ toRaw,
17
+ } from "vue";
18
+ import { globalRtl } from "../../pgo-components/lib/core/rtl/rtl";
19
+
20
+ // Layout registry (v-app coordinator)
21
+ const app = reactive({
22
+ appBars: new Map(),
23
+ footers: new Map(),
24
+ drawers: new Map(),
25
+ });
26
+
27
+ function register(type: "appBar" | "footer" | "drawer", id: string, meta: any) {
28
+ if (type === "appBar") app.appBars.set(id, meta);
29
+ if (type === "footer") app.footers.set(id, meta);
30
+ if (type === "drawer") app.drawers.set(id, meta);
31
+ }
32
+ function unregister(type: "appBar" | "footer" | "drawer", id: string) {
33
+ if (type === "appBar") app.appBars.delete(id);
34
+ if (type === "footer") app.footers.delete(id);
35
+ if (type === "drawer") app.drawers.delete(id);
36
+ }
37
+
38
+ const root = ref<HTMLElement | null>(null);
39
+
40
+ // Derived metrics
41
+ const topOffset = computed(() => {
42
+ let h = 0;
43
+ for (const [, meta] of app.appBars) {
44
+ if (meta.dense) h += meta.height || 0;
45
+ else h += meta.height || 0;
46
+ }
47
+ return h;
48
+ });
49
+
50
+ const bottomOffset = computed(() => {
51
+ let h = 0;
52
+ for (const [, meta] of app.footers) h += meta.height || 0;
53
+ return h;
54
+ });
55
+
56
+ // For left/right drawer offset use only permanent drawers
57
+ const horizontalOffset = computed(() => {
58
+ let left = 0,
59
+ right = 0;
60
+
61
+ // console.log("App drawers:", toRaw(app.drawers));
62
+ for (const [, meta] of app.drawers) {
63
+
64
+ if (meta.mode === "permanent") {
65
+ // console.log("Drawer meta:", toRaw(meta));
66
+ if (meta.right) right += meta.width || 0;
67
+ else left += meta.width || 0;
68
+ }
69
+ // rail with expanded state contributes width when expanded
70
+ if (meta.mode === "rail" && meta.expanded) {
71
+ if (meta.right) right += meta.width || 0;
72
+ else left += meta.width || 0;
73
+ }
74
+ }
75
+ // console.log("Raw app contents:", toRaw(app));
76
+ // console.log("horizontalOffset", toRaw(app.drawers), left, right);
77
+ return { left, right };
78
+ });
79
+
80
+ provide("layout", {
81
+ register,
82
+ unregister,
83
+ topOffset,
84
+ bottomOffset,
85
+ horizontalOffset,
86
+ app,
87
+ });
88
+
89
+ const rtlClass = computed(() =>
90
+ globalRtl.value ? "direction-rtl" : "direction-ltr"
91
+ );
92
+ </script>
93
+
94
+ <style>
95
+ /* small helpers in case Tailwind is insufficient */
96
+ .direction-rtl {
97
+ direction: rtl;
98
+ }
99
+ .direction-ltr {
100
+ direction: ltr;
101
+ }
102
+ </style>
@@ -0,0 +1,242 @@
1
+ <template>
2
+ <div :class="[bg, 'flex items-center justify-between px-2']">
3
+ <!-- Items per page and info -->
4
+ <bdi class="flex items-center">
5
+ <div class="flex items-center gap-2">
6
+ <p :class="['text-sm font-medium', textColor]">{{ t('pagination.itemsPerPage') }}</p>
7
+ <select
8
+ :value="itemsPerPage"
9
+ :class="[
10
+ 'h-8 w-[70px] border bg-transparent px-2 py-1 text-sm',
11
+ 'focus:outline-none focus:ring-2 focus:ring-offset-2',
12
+ inputBorder,
13
+ rounded
14
+ ]"
15
+ @change="handleItemsPerPageChange"
16
+ >
17
+ <option v-for="option in itemsPerPageOptions" :key="option" :value="option">
18
+ {{ option }}
19
+ </option>
20
+ </select>
21
+ </div>
22
+ </bdi>
23
+
24
+ <div :class="['flex items-center gap-2 text-base font-medium', textColor]">
25
+ <bdi :class="language == 'dv' ? 'text-right faruma' : 'text-left'">{{ startItem }}-{{ endItem }} {{ t('pagination.of') }} {{ itemsLength }}</bdi>
26
+ </div>
27
+
28
+ <!-- Pagination controls -->
29
+ <bdi class="flex items-center gap-1">
30
+ <!-- First Page -->
31
+ <button
32
+ :disabled="page <= 1"
33
+ :class="[
34
+ 'inline-flex items-center justify-center h-8 w-8 text-sm font-medium transition-colors',
35
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
36
+ 'disabled:pointer-events-none disabled:opacity-50',
37
+ buttonSecondary,
38
+ rounded
39
+ ]"
40
+ @click="handlePageChange(1)"
41
+ :title="t('pagination.page') + ' 1'"
42
+ >
43
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
45
+ </svg>
46
+ </button>
47
+
48
+ <!-- Previous Page -->
49
+ <button
50
+ :disabled="page <= 1"
51
+ :class="[
52
+ 'inline-flex items-center justify-center h-8 w-8 text-sm font-medium transition-colors',
53
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
54
+ 'disabled:pointer-events-none disabled:opacity-50',
55
+ buttonSecondary,
56
+ rounded
57
+ ]"
58
+ @click="handlePageChange(page - 1)"
59
+ :title="t('pagination.previous')"
60
+ >
61
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
62
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
63
+ </svg>
64
+ </button>
65
+
66
+ <!-- Page Numbers -->
67
+ <div class="flex items-center gap-1">
68
+ <button
69
+ v-for="pageNum in visiblePages"
70
+ :key="pageNum"
71
+ :disabled="pageNum === '...'"
72
+ :class="[
73
+ 'inline-flex items-center justify-center text-[#fff] h-8 min-w-8 px-3 text-sm font-medium transition-colors',
74
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
75
+ pageNum === '...' ? 'cursor-default pointer-events-none' : '',
76
+ pageNum === page ? buttonPrimary : buttonSecondary,
77
+ rounded
78
+ ]"
79
+ @click="pageNum !== '...' && handlePageChange(pageNum)"
80
+ :title="pageNum !== '...' ? t('pagination.page') + ' ' + pageNum : ''"
81
+ >
82
+ {{ pageNum }}
83
+ </button>
84
+ </div>
85
+
86
+ <!-- Next Page -->
87
+ <button
88
+ :disabled="page >= totalPages"
89
+ :class="[
90
+ 'inline-flex items-center justify-center h-8 w-8 text-sm font-medium transition-colors',
91
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
92
+ 'disabled:pointer-events-none disabled:opacity-50',
93
+ buttonSecondary,
94
+ rounded
95
+ ]"
96
+ @click="handlePageChange(page + 1)"
97
+ :title="t('pagination.next')"
98
+ >
99
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
100
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
101
+ </svg>
102
+ </button>
103
+
104
+ <!-- Last Page -->
105
+ <button
106
+ :disabled="page >= totalPages"
107
+ :class="[
108
+ 'inline-flex items-center justify-center h-8 w-8 text-sm font-medium transition-colors',
109
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
110
+ 'disabled:pointer-events-none disabled:opacity-50',
111
+ buttonSecondary,
112
+ rounded
113
+ ]"
114
+ @click="handlePageChange(totalPages)"
115
+ :title="t('pagination.page') + ' ' + totalPages"
116
+ >
117
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
118
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
119
+ </svg>
120
+ </button>
121
+ </bdi>
122
+ </div>
123
+ </template>
124
+
125
+ <script setup>
126
+ import { computed, inject } from 'vue'
127
+
128
+ const { t, language } = inject('i18n')
129
+
130
+ const props = defineProps({
131
+ // Pagination data
132
+ page: {
133
+ type: Number,
134
+ required: true
135
+ },
136
+ itemsPerPage: {
137
+ type: Number,
138
+ required: true
139
+ },
140
+ itemsLength: {
141
+ type: Number,
142
+ required: true
143
+ },
144
+ itemsPerPageOptions: {
145
+ type: Array,
146
+ default: () => [10, 20, 30, 40, 50]
147
+ },
148
+
149
+ // Styling
150
+ bg: {
151
+ type: String,
152
+ default: 'bg-background'
153
+ },
154
+ border: {
155
+ type: String,
156
+ default: 'border-t border-input-border'
157
+ },
158
+ textColor: {
159
+ type: String,
160
+ default: 'text-textcolor'
161
+ },
162
+ inputBorder: {
163
+ type: String,
164
+ default: 'border-input-border focus:none'
165
+ },
166
+ buttonPrimary: {
167
+ type: String,
168
+ default: 'bg-primary hover:bg-primary/90'
169
+ },
170
+ buttonSecondary: {
171
+ type: String,
172
+ default: 'border border-input-border bg-background hover:bg-accent hover:text-accent-foreground'
173
+ },
174
+ rounded: {
175
+ type: String,
176
+ default: 'rounded-none'
177
+ }
178
+ })
179
+
180
+ const emit = defineEmits(['update:page', 'update:itemsPerPage', 'change'])
181
+
182
+ // Computed properties
183
+ const totalPages = computed(() => {
184
+ return Math.ceil(props.itemsLength / props.itemsPerPage) || 1
185
+ })
186
+
187
+ const startItem = computed(() => {
188
+ if (props.itemsLength === 0) return 0
189
+ return (props.page - 1) * props.itemsPerPage + 1
190
+ })
191
+
192
+ const endItem = computed(() => {
193
+ const end = props.page * props.itemsPerPage
194
+ return Math.min(end, props.itemsLength)
195
+ })
196
+
197
+ const visiblePages = computed(() => {
198
+ const current = props.page
199
+ const total = totalPages.value
200
+ const delta = 1 // Show 1 page on each side
201
+ const range = []
202
+
203
+ for (let i = Math.max(2, current - delta); i <= Math.min(total - 1, current + delta); i++) {
204
+ range.push(i)
205
+ }
206
+
207
+ if (current - delta > 2) {
208
+ range.unshift('...')
209
+ }
210
+ if (current + delta < total - 1) {
211
+ range.push('...')
212
+ }
213
+
214
+ range.unshift(1)
215
+ if (total > 1) {
216
+ range.push(total)
217
+ }
218
+
219
+ return range.filter((item, index, arr) => arr.indexOf(item) === index)
220
+ })
221
+
222
+ // Methods
223
+ const handlePageChange = (newPage) => {
224
+ if (newPage >= 1 && newPage <= totalPages.value && newPage !== props.page) {
225
+ emit('update:page', newPage)
226
+ emit('change', {
227
+ page: newPage,
228
+ itemsPerPage: props.itemsPerPage
229
+ })
230
+ }
231
+ }
232
+
233
+ const handleItemsPerPageChange = (event) => {
234
+ const newItemsPerPage = parseInt(event.target.value)
235
+ emit('update:itemsPerPage', newItemsPerPage)
236
+ emit('update:page', 1) // Reset to first page
237
+ emit('change', {
238
+ page: 1,
239
+ itemsPerPage: newItemsPerPage
240
+ })
241
+ }
242
+ </script>