@amirjalili1374/ui-kit 1.2.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 (221) hide show
  1. package/README.md +275 -0
  2. package/dist/_redirects +1 -0
  3. package/dist/components/Loading.vue.d.ts +3 -0
  4. package/dist/components/Loading.vue.d.ts.map +1 -0
  5. package/dist/components/common/AppStepper.vue.d.ts +75 -0
  6. package/dist/components/common/AppStepper.vue.d.ts.map +1 -0
  7. package/dist/components/shared/BaseBreadcrumb.vue.d.ts +18 -0
  8. package/dist/components/shared/BaseBreadcrumb.vue.d.ts.map +1 -0
  9. package/dist/components/shared/BaseIcon.vue.d.ts +33 -0
  10. package/dist/components/shared/BaseIcon.vue.d.ts.map +1 -0
  11. package/dist/components/shared/ConfirmDialog.vue.d.ts +38 -0
  12. package/dist/components/shared/ConfirmDialog.vue.d.ts.map +1 -0
  13. package/dist/components/shared/CustomAutocomplete.vue.d.ts +81 -0
  14. package/dist/components/shared/CustomAutocomplete.vue.d.ts.map +1 -0
  15. package/dist/components/shared/CustomDataTable.vue.d.ts +59 -0
  16. package/dist/components/shared/CustomDataTable.vue.d.ts.map +1 -0
  17. package/dist/components/shared/DescriptionInput.vue.d.ts +34 -0
  18. package/dist/components/shared/DescriptionInput.vue.d.ts.map +1 -0
  19. package/dist/components/shared/DownloadButton.vue.d.ts +25 -0
  20. package/dist/components/shared/DownloadButton.vue.d.ts.map +1 -0
  21. package/dist/components/shared/MoneyInput.vue.d.ts +127 -0
  22. package/dist/components/shared/MoneyInput.vue.d.ts.map +1 -0
  23. package/dist/components/shared/PdfViewer.vue.d.ts +67 -0
  24. package/dist/components/shared/PdfViewer.vue.d.ts.map +1 -0
  25. package/dist/components/shared/ShamsiDatePicker.vue.d.ts +48 -0
  26. package/dist/components/shared/ShamsiDatePicker.vue.d.ts.map +1 -0
  27. package/dist/components/shared/UiChildCard.vue.d.ts +14 -0
  28. package/dist/components/shared/UiChildCard.vue.d.ts.map +1 -0
  29. package/dist/components/shared/UiParentCard.vue.d.ts +18 -0
  30. package/dist/components/shared/UiParentCard.vue.d.ts.map +1 -0
  31. package/dist/components/shared/VPriceTextField.vue.d.ts +30 -0
  32. package/dist/components/shared/VPriceTextField.vue.d.ts.map +1 -0
  33. package/dist/composables/useDataTable.d.ts +36 -0
  34. package/dist/composables/useDataTable.d.ts.map +1 -0
  35. package/dist/composables/useTableActions.d.ts +294 -0
  36. package/dist/composables/useTableActions.d.ts.map +1 -0
  37. package/dist/composables/useTableHeaders.d.ts +80 -0
  38. package/dist/composables/useTableHeaders.d.ts.map +1 -0
  39. package/dist/composables/useTableSelection.d.ts +32 -0
  40. package/dist/composables/useTableSelection.d.ts.map +1 -0
  41. package/dist/constants/enums/booleanEnum.d.ts +13 -0
  42. package/dist/constants/enums/booleanEnum.d.ts.map +1 -0
  43. package/dist/directives/v-digit-limit.d.ts +6 -0
  44. package/dist/directives/v-digit-limit.d.ts.map +1 -0
  45. package/dist/directives/v-permission.d.ts +3 -0
  46. package/dist/directives/v-permission.d.ts.map +1 -0
  47. package/dist/favicon.svg +13 -0
  48. package/dist/index.d.ts +53 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/persian.json +1 -0
  51. package/dist/plugins/mdi-icon.d.ts +16 -0
  52. package/dist/plugins/mdi-icon.d.ts.map +1 -0
  53. package/dist/services/apiService.d.ts +21 -0
  54. package/dist/services/apiService.d.ts.map +1 -0
  55. package/dist/services/axiosInstance.d.ts +11 -0
  56. package/dist/services/axiosInstance.d.ts.map +1 -0
  57. package/dist/stores/customizer.d.ts +47 -0
  58. package/dist/stores/customizer.d.ts.map +1 -0
  59. package/dist/stores/permissions.d.ts +47 -0
  60. package/dist/stores/permissions.d.ts.map +1 -0
  61. package/dist/style.css +5 -0
  62. package/dist/types/componentTypes/DataTableType.d.ts +14 -0
  63. package/dist/types/componentTypes/DataTableType.d.ts.map +1 -0
  64. package/dist/types/componentTypes/DataTableTypes.d.ts +120 -0
  65. package/dist/types/componentTypes/DataTableTypes.d.ts.map +1 -0
  66. package/dist/ui-kit.cjs.js +2 -0
  67. package/dist/ui-kit.cjs.js.map +1 -0
  68. package/dist/ui-kit.es.js +36840 -0
  69. package/dist/ui-kit.es.js.map +1 -0
  70. package/dist/utils/NationalCodeValidator.d.ts +4 -0
  71. package/dist/utils/NationalCodeValidator.d.ts.map +1 -0
  72. package/dist/utils/date-convertor.d.ts +5 -0
  73. package/dist/utils/date-convertor.d.ts.map +1 -0
  74. package/dist/utils/greetingUtils.d.ts +35 -0
  75. package/dist/utils/greetingUtils.d.ts.map +1 -0
  76. package/dist/utils/helpers/fetch-wrapper.d.ts +23 -0
  77. package/dist/utils/helpers/fetch-wrapper.d.ts.map +1 -0
  78. package/dist/utils/number-formatter.d.ts +16 -0
  79. package/dist/utils/number-formatter.d.ts.map +1 -0
  80. package/dist/validators/nationalCodeRule.d.ts +2 -0
  81. package/dist/validators/nationalCodeRule.d.ts.map +1 -0
  82. package/package.json +134 -0
  83. package/src/assets/fonts/A Massir Spray.ttf +0 -0
  84. package/src/assets/fonts/BYekan.ttf +0 -0
  85. package/src/assets/fonts/BYekan.woff +0 -0
  86. package/src/assets/fonts/BYekan.woff2 +0 -0
  87. package/src/assets/fonts/Dima Shekasteh 2 Free.ttf +0 -0
  88. package/src/assets/fonts/Dima Shekasteh Free Regular.ttf +0 -0
  89. package/src/assets/fonts/IRANSansWeb.ts +1 -0
  90. package/src/assets/fonts/IRANSansWeb.ttf +0 -0
  91. package/src/assets/fonts/IRANSansXBlack.ttf +0 -0
  92. package/src/assets/fonts/IRANSansXBold.ttf +0 -0
  93. package/src/assets/fonts/IRANSansXDemiBold.ttf +0 -0
  94. package/src/assets/fonts/IRANSansXExtraBold.ttf +0 -0
  95. package/src/assets/fonts/IRANSansXLight.ttf +0 -0
  96. package/src/assets/fonts/IRANSansXMedium.ttf +0 -0
  97. package/src/assets/fonts/IRANSansXRegular.ttf +0 -0
  98. package/src/assets/fonts/IRANSansXThin.ttf +0 -0
  99. package/src/assets/fonts/IRANSansXUltraLight.ttf +0 -0
  100. package/src/assets/fonts/IranNastaliq.ttf +0 -0
  101. package/src/assets/fonts/Vazir-Medium-FD.ttf +0 -0
  102. package/src/assets/fonts/Vazir-Medium-FD.woff +0 -0
  103. package/src/assets/fonts/Vazir-Medium-FD.woff2 +0 -0
  104. package/src/assets/fonts/Vazir-Regular-FD.eot +0 -0
  105. package/src/assets/fonts/kalamehBold.woff +0 -0
  106. package/src/assets/fonts/kalamehBold.woff2 +0 -0
  107. package/src/assets/fonts/kalamehHeavy.woff +0 -0
  108. package/src/assets/fonts/kalamehHeavy.woff2 +0 -0
  109. package/src/assets/fonts/kalamehLight.woff +0 -0
  110. package/src/assets/fonts/kalamehLight.woff2 +0 -0
  111. package/src/assets/fonts/kalamehRegular.woff +0 -0
  112. package/src/assets/fonts/kalamehRegular.woff2 +0 -0
  113. package/src/assets/images/auth/social-google.svg +6 -0
  114. package/src/assets/images/favicon.svg +18 -0
  115. package/src/assets/images/icons/icon-card.svg +5 -0
  116. package/src/assets/images/logos/logo.svg +12 -0
  117. package/src/assets/images/logos/logolight.svg +12 -0
  118. package/src/assets/images/maintenance/img-error-bg.svg +34 -0
  119. package/src/assets/images/maintenance/img-error-blue.svg +43 -0
  120. package/src/assets/images/maintenance/img-error-purple.svg +42 -0
  121. package/src/assets/images/maintenance/img-error-text.svg +27 -0
  122. package/src/assets/images/profile/profile-user-account-svgrepo-com.svg +12 -0
  123. package/src/assets/images/profile/user-round.svg +15 -0
  124. package/src/assets/images/template/template-01.ts +1 -0
  125. package/src/assets/images/vectors/colorized-bg.svg +40 -0
  126. package/src/assets/images/vectors/logo_stroke_1px.svg +26 -0
  127. package/src/assets/images/vectors/logo_stroke_2px.svg +26 -0
  128. package/src/assets/scss/components/_approval-sections.scss +75 -0
  129. package/src/assets/styles/fonts.scss +77 -0
  130. package/src/components/Loading.vue +88 -0
  131. package/src/components/common/AppStepper.vue +139 -0
  132. package/src/components/shared/BaseBreadcrumb.vue +55 -0
  133. package/src/components/shared/BaseIcon.vue +27 -0
  134. package/src/components/shared/ConfirmDialog.vue +72 -0
  135. package/src/components/shared/CustomAutocomplete.vue +306 -0
  136. package/src/components/shared/CustomDataTable.vue +1859 -0
  137. package/src/components/shared/DescriptionInput.vue +204 -0
  138. package/src/components/shared/DownloadButton.vue +169 -0
  139. package/src/components/shared/MoneyInput.vue +105 -0
  140. package/src/components/shared/PdfViewer.vue +645 -0
  141. package/src/components/shared/ShamsiDatePicker.vue +444 -0
  142. package/src/components/shared/UiChildCard.vue +17 -0
  143. package/src/components/shared/UiParentCard.vue +21 -0
  144. package/src/components/shared/VPriceTextField.vue +136 -0
  145. package/src/composables/useDataTable.ts +152 -0
  146. package/src/composables/usePermissions.ts +90 -0
  147. package/src/composables/useRouteGuard.ts +36 -0
  148. package/src/composables/useTableActions.ts +207 -0
  149. package/src/composables/useTableHeaders.ts +172 -0
  150. package/src/composables/useTableSelection.ts +201 -0
  151. package/src/constants/enums/approval.ts +13 -0
  152. package/src/constants/enums/booleanEnum.ts +11 -0
  153. package/src/constants/enums/contractType.ts +11 -0
  154. package/src/constants/enums/lcProductType.ts +21 -0
  155. package/src/constants/enums/repaymentType.ts +11 -0
  156. package/src/directives/v-digit-limit.ts +15 -0
  157. package/src/directives/v-permission.ts +31 -0
  158. package/src/features/index.ts +48 -0
  159. package/src/index.ts +119 -0
  160. package/src/plugins/key-clock.ts +39 -0
  161. package/src/plugins/mdi-icon.ts +31 -0
  162. package/src/plugins/vuetify.ts +74 -0
  163. package/src/scss/_override.scss +72 -0
  164. package/src/scss/_variables.scss +124 -0
  165. package/src/scss/components/_VButtons.scss +23 -0
  166. package/src/scss/components/_VCard.scss +20 -0
  167. package/src/scss/components/_VCustomDataTable.scss +282 -0
  168. package/src/scss/components/_VField.scss +9 -0
  169. package/src/scss/components/_VInput.scss +17 -0
  170. package/src/scss/components/_VNavigationDrawer.scss +3 -0
  171. package/src/scss/components/_VShadow.scss +3 -0
  172. package/src/scss/components/_VStepper.scss +235 -0
  173. package/src/scss/components/_VTabs.scss +11 -0
  174. package/src/scss/components/_VTextField.scss +40 -0
  175. package/src/scss/components/_approval.scss +128 -0
  176. package/src/scss/layout/_container.scss +147 -0
  177. package/src/scss/layout/_sidebar.scss +138 -0
  178. package/src/scss/layout/_topbar.scss +39 -0
  179. package/src/scss/pages/_dashboards.scss +97 -0
  180. package/src/scss/style.scss +21 -0
  181. package/src/services/apiService.ts +59 -0
  182. package/src/services/axiosInstance.ts +14 -0
  183. package/src/stores/customizer.ts +55 -0
  184. package/src/stores/permissions.ts +237 -0
  185. package/src/theme/darkThemes/DarkModernTheme.ts +54 -0
  186. package/src/theme/darkThemes/DarkOrangeTheme.ts +53 -0
  187. package/src/theme/darkThemes/DarkPurpleTheme.ts +54 -0
  188. package/src/theme/darkThemes/DarkRedTheme.ts +54 -0
  189. package/src/theme/darkThemes/DarkSilverTheme.ts +53 -0
  190. package/src/theme/darkThemes/DarkSteelTealGreen.ts +53 -0
  191. package/src/theme/darkThemes/DarkTealTheme.ts +52 -0
  192. package/src/theme/lightThemes/ModernTheme.ts +55 -0
  193. package/src/theme/lightThemes/OrangeTheme.ts +54 -0
  194. package/src/theme/lightThemes/PurpleTheme.ts +54 -0
  195. package/src/theme/lightThemes/RedTheme.ts +55 -0
  196. package/src/theme/lightThemes/SilverTheme.ts +55 -0
  197. package/src/theme/lightThemes/SteelTealGreen.ts +54 -0
  198. package/src/theme/lightThemes/TealTheme.ts +54 -0
  199. package/src/types/approval/approvalType.ts +473 -0
  200. package/src/types/cartable/cartableTypes.ts +169 -0
  201. package/src/types/componentTypes/DataTableType.ts +14 -0
  202. package/src/types/componentTypes/DataTableTypes.ts +130 -0
  203. package/src/types/enums/global.ts +267 -0
  204. package/src/types/jalaali-js.d.ts +6 -0
  205. package/src/types/models/Base.ts +4 -0
  206. package/src/types/models/env.d.ts +10 -0
  207. package/src/types/models/person.ts +13 -0
  208. package/src/types/models/userInfo.ts +29 -0
  209. package/src/types/preApproval/preApprovalTypes.ts +67 -0
  210. package/src/types/shims-tabler-icons.d.ts +58 -0
  211. package/src/types/themeTypes/ThemeType.ts +47 -0
  212. package/src/types/vue-apexcharts.d.ts +1 -0
  213. package/src/types/vue3-print-nb.d.ts +1 -0
  214. package/src/types/vue_tabler_icon.d.ts +10 -0
  215. package/src/utils/NationalCodeValidator.ts +33 -0
  216. package/src/utils/date-convertor.ts +40 -0
  217. package/src/utils/greetingUtils.ts +97 -0
  218. package/src/utils/helpers/fake-backend.ts +68 -0
  219. package/src/utils/helpers/fetch-wrapper.ts +86 -0
  220. package/src/utils/number-formatter.ts +33 -0
  221. package/src/validators/nationalCodeRule.ts +6 -0
@@ -0,0 +1,645 @@
1
+ <template>
2
+ <div class="pdf-viewer-container" :class="{ 'fullscreen': isFullscreen }">
3
+ <!-- Header with controls -->
4
+ <div class="pdf-viewer-header" v-if="showHeader">
5
+ <div class="pdf-viewer-title">
6
+ <h3>{{ title || 'PDF Viewer' }}</h3>
7
+ </div>
8
+
9
+ <div class="pdf-viewer-controls">
10
+ <!-- Zoom Controls -->
11
+ <div class="zoom-controls" v-if="showZoomControls">
12
+ <v-btn
13
+ size="small"
14
+ variant="text"
15
+ @click="zoomOut"
16
+ :disabled="zoom <= minZoom"
17
+ title="Zoom Out"
18
+ >
19
+ <IconMinus size="16" color="white" />
20
+ </v-btn>
21
+ <span class="zoom-level">{{ Math.round(zoom * 100) }}%</span>
22
+ <v-btn
23
+ size="small"
24
+ variant="text"
25
+ @click="zoomIn"
26
+ :disabled="zoom >= maxZoom"
27
+ title="Zoom In"
28
+ >
29
+ <IconPlus size="16" color="white" />
30
+ </v-btn>
31
+ <v-btn
32
+ size="small"
33
+ variant="text"
34
+ @click="resetZoom"
35
+ title="Reset Zoom"
36
+ >
37
+ <IconRefresh size="16" color="white" />
38
+ </v-btn>
39
+ </div>
40
+
41
+ <!-- Navigation Controls -->
42
+ <div class="navigation-controls" v-if="showNavigationControls">
43
+ <v-btn
44
+ size="small"
45
+ variant="text"
46
+ @click="previousPage"
47
+ :disabled="currentPage <= 1"
48
+ title="Previous Page"
49
+ >
50
+ <IconChevronLeft size="16" color="white" />
51
+ </v-btn>
52
+ <span class="page-info">
53
+ {{ currentPage }} / {{ totalPages }}
54
+ </span>
55
+ <v-btn
56
+ size="small"
57
+ variant="text"
58
+ @click="nextPage"
59
+ :disabled="currentPage >= totalPages"
60
+ title="Next Page"
61
+ >
62
+ <IconChevronRight size="16" color="white" />
63
+ </v-btn>
64
+ </div>
65
+
66
+ <!-- Action Controls -->
67
+ <div class="action-controls">
68
+ <v-btn
69
+ v-if="showDownload"
70
+ size="small"
71
+ variant="text"
72
+ @click="downloadPdf"
73
+ :loading="downloading"
74
+ title="Download PDF"
75
+ >
76
+ <IconDownload size="16" color="white" />
77
+ </v-btn>
78
+ <v-btn
79
+ v-if="showPrint"
80
+ size="small"
81
+ variant="text"
82
+ @click="printPdf"
83
+ title="Print PDF"
84
+ >
85
+ <IconPrinter size="16" color="white" />
86
+ </v-btn>
87
+ <v-btn
88
+ v-if="showFullscreen"
89
+ size="small"
90
+ variant="text"
91
+ @click="toggleFullscreen"
92
+ :title="isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'"
93
+ >
94
+ <IconMaximize v-if="!isFullscreen" size="16" color="white" />
95
+ <IconMinimize v-else size="16" color="white" />
96
+ </v-btn>
97
+ <v-btn
98
+ v-if="showClose"
99
+ size="small"
100
+ variant="text"
101
+ @click="$emit('close')"
102
+ title="Close"
103
+ >
104
+ <IconX size="16" color="white" />
105
+ </v-btn>
106
+ </div>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- PDF Content -->
111
+ <div class="pdf-viewer-content" :style="contentStyle">
112
+ <!-- Loading State -->
113
+ <div v-if="loading" class="pdf-loading">
114
+ <v-progress-circular
115
+ indeterminate
116
+ color="primary"
117
+ size="64"
118
+ />
119
+ <p>{{ loadingText || 'Loading PDF...' }}</p>
120
+ </div>
121
+
122
+ <!-- Error State -->
123
+ <div v-else-if="error" class="pdf-error">
124
+ <IconFileText size="64" color="error" />
125
+ <h3>{{ errorTitle || 'Error Loading PDF' }}</h3>
126
+ <p>{{ error }}</p>
127
+ <v-btn
128
+ color="primary"
129
+ @click="loadPdf"
130
+ :loading="loading"
131
+ >
132
+ Retry
133
+ </v-btn>
134
+ </div>
135
+
136
+ <!-- PDF Display -->
137
+ <div v-else class="pdf-display">
138
+ <iframe
139
+ v-if="pdfUrl"
140
+ :src="pdfUrl"
141
+ :style="iframeStyle"
142
+ @load="onPdfLoad"
143
+ @error="onPdfError"
144
+ :title="title || 'PDF Document'"
145
+ />
146
+
147
+ <!-- Fallback for browsers that don't support PDF -->
148
+ <div v-else class="pdf-fallback">
149
+ <IconFileText size="64" color="grey" />
150
+ <h3>PDF Preview Not Available</h3>
151
+ <p>Your browser does not support PDF preview.</p>
152
+ <v-btn
153
+ color="primary"
154
+ @click="downloadPdf"
155
+ :loading="downloading"
156
+ >
157
+ Download PDF
158
+ </v-btn>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Footer with page info -->
164
+ <div class="pdf-viewer-footer" v-if="showFooter">
165
+ <div class="page-info">
166
+ Page {{ currentPage }} of {{ totalPages }}
167
+ </div>
168
+ <div class="file-info" v-if="fileSize">
169
+ {{ formatFileSize(fileSize) }}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </template>
174
+
175
+ <script lang="ts" setup>
176
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
177
+ import {
178
+ IconMinus,
179
+ IconPlus,
180
+ IconRefresh,
181
+ IconChevronLeft,
182
+ IconChevronRight,
183
+ IconDownload,
184
+ IconPrinter,
185
+ IconMaximize,
186
+ IconMinimize,
187
+ IconX,
188
+ IconFileText
189
+ } from '@tabler/icons-vue';
190
+
191
+ // Props
192
+ interface Props {
193
+ src?: string;
194
+ title?: string;
195
+ width?: string | number;
196
+ height?: string | number;
197
+ showHeader?: boolean;
198
+ showFooter?: boolean;
199
+ showZoomControls?: boolean;
200
+ showNavigationControls?: boolean;
201
+ showDownload?: boolean;
202
+ showPrint?: boolean;
203
+ showFullscreen?: boolean;
204
+ showClose?: boolean;
205
+ loadingText?: string;
206
+ errorTitle?: string;
207
+ fileSize?: number;
208
+ initialZoom?: number;
209
+ minZoom?: number;
210
+ maxZoom?: number;
211
+ autoLoad?: boolean;
212
+ downloadFileName?: string;
213
+ }
214
+
215
+ const props = withDefaults(defineProps<Props>(), {
216
+ showHeader: true,
217
+ showFooter: true,
218
+ showZoomControls: true,
219
+ showNavigationControls: true,
220
+ showDownload: true,
221
+ showPrint: true,
222
+ showFullscreen: true,
223
+ showClose: false,
224
+ loadingText: 'Loading PDF...',
225
+ errorTitle: 'Error Loading PDF',
226
+ initialZoom: 1,
227
+ minZoom: 0.25,
228
+ maxZoom: 3,
229
+ autoLoad: true,
230
+ downloadFileName: 'document.pdf'
231
+ });
232
+
233
+ // Emits
234
+ const emit = defineEmits<{
235
+ load: [pdf: any];
236
+ error: [error: string];
237
+ pageChange: [page: number];
238
+ zoomChange: [zoom: number];
239
+ download: [url: string];
240
+ print: [url: string];
241
+ close: [];
242
+ }>();
243
+
244
+ // Reactive state
245
+ const loading = ref(false);
246
+ const error = ref<string | null>(null);
247
+ const downloading = ref(false);
248
+ const isFullscreen = ref(false);
249
+ const currentPage = ref(1);
250
+ const totalPages = ref(1);
251
+ const zoom = ref(props.initialZoom);
252
+
253
+ // Computed properties
254
+ const pdfUrl = computed(() => {
255
+ if (!props.src) return null;
256
+
257
+ // If it's already a blob URL or data URL, return as is
258
+ if (props.src.startsWith('blob:') || props.src.startsWith('data:')) {
259
+ return props.src;
260
+ }
261
+
262
+ // For regular URLs, add zoom parameter if supported
263
+ const url = new URL(props.src, window.location.origin);
264
+ url.searchParams.set('zoom', zoom.value.toString());
265
+ return url.toString();
266
+ });
267
+
268
+ const contentStyle = computed(() => ({
269
+ width: typeof props.width === 'number' ? `${props.width}px` : props.width || '100%',
270
+ height: typeof props.height === 'number' ? `${props.height}px` : props.height || '500px',
271
+ transform: `scale(${zoom.value})`,
272
+ transformOrigin: 'top left'
273
+ }));
274
+
275
+ const iframeStyle = computed(() => ({
276
+ width: '100%',
277
+ height: '100%',
278
+ border: 'none'
279
+ }));
280
+
281
+ // Methods
282
+ const loadPdf = async () => {
283
+ if (!props.src) return;
284
+
285
+ loading.value = true;
286
+ error.value = null;
287
+
288
+ try {
289
+ // For now, we'll just set the URL and let the iframe handle loading
290
+ // In a real implementation, you might want to fetch and validate the PDF
291
+ loading.value = false;
292
+ } catch (err) {
293
+ error.value = err instanceof Error ? err.message : 'Failed to load PDF';
294
+ loading.value = false;
295
+ emit('error', error.value);
296
+ }
297
+ };
298
+
299
+ const onPdfLoad = () => {
300
+ loading.value = false;
301
+ // Note: Getting actual page count from iframe is complex due to CORS
302
+ // This is a simplified implementation
303
+ totalPages.value = 1; // You might want to implement actual page counting
304
+ emit('load', { currentPage: currentPage.value, totalPages: totalPages.value });
305
+ };
306
+
307
+ const onPdfError = () => {
308
+ error.value = 'Failed to load PDF document';
309
+ loading.value = false;
310
+ emit('error', error.value);
311
+ };
312
+
313
+ const zoomIn = () => {
314
+ if (zoom.value < props.maxZoom) {
315
+ zoom.value = Math.min(zoom.value + 0.25, props.maxZoom);
316
+ emit('zoomChange', zoom.value);
317
+ }
318
+ };
319
+
320
+ const zoomOut = () => {
321
+ if (zoom.value > props.minZoom) {
322
+ zoom.value = Math.max(zoom.value - 0.25, props.minZoom);
323
+ emit('zoomChange', zoom.value);
324
+ }
325
+ };
326
+
327
+ const resetZoom = () => {
328
+ zoom.value = props.initialZoom;
329
+ emit('zoomChange', zoom.value);
330
+ };
331
+
332
+ const previousPage = () => {
333
+ if (currentPage.value > 1) {
334
+ currentPage.value--;
335
+ emit('pageChange', currentPage.value);
336
+ }
337
+ };
338
+
339
+ const nextPage = () => {
340
+ if (currentPage.value < totalPages.value) {
341
+ currentPage.value++;
342
+ emit('pageChange', currentPage.value);
343
+ }
344
+ };
345
+
346
+ const downloadPdf = async () => {
347
+ if (!props.src) return;
348
+
349
+ downloading.value = true;
350
+
351
+ try {
352
+ const response = await fetch(props.src);
353
+ const blob = await response.blob();
354
+ const url = window.URL.createObjectURL(blob);
355
+
356
+ const link = document.createElement('a');
357
+ link.href = url;
358
+ link.download = props.downloadFileName;
359
+ document.body.appendChild(link);
360
+ link.click();
361
+ document.body.removeChild(link);
362
+
363
+ window.URL.revokeObjectURL(url);
364
+ emit('download', props.src);
365
+ } catch (err) {
366
+ console.error('Download failed:', err);
367
+ // Fallback: open in new tab
368
+ window.open(props.src, '_blank');
369
+ } finally {
370
+ downloading.value = false;
371
+ }
372
+ };
373
+
374
+ const printPdf = () => {
375
+ if (!props.src) return;
376
+
377
+ const printWindow = window.open(props.src, '_blank');
378
+ if (printWindow) {
379
+ printWindow.onload = () => {
380
+ printWindow.print();
381
+ };
382
+ }
383
+ emit('print', props.src);
384
+ };
385
+
386
+ const toggleFullscreen = () => {
387
+ isFullscreen.value = !isFullscreen.value;
388
+
389
+ if (isFullscreen.value) {
390
+ document.body.style.overflow = 'hidden';
391
+ } else {
392
+ document.body.style.overflow = '';
393
+ }
394
+ };
395
+
396
+ const formatFileSize = (bytes: number): string => {
397
+ if (bytes === 0) return '0 Bytes';
398
+
399
+ const k = 1024;
400
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
401
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
402
+
403
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
404
+ };
405
+
406
+ // Keyboard shortcuts
407
+ const handleKeydown = (event: KeyboardEvent) => {
408
+ if (!isFullscreen.value) return;
409
+
410
+ switch (event.key) {
411
+ case 'Escape':
412
+ if (isFullscreen.value) {
413
+ toggleFullscreen();
414
+ }
415
+ break;
416
+ case 'ArrowLeft':
417
+ previousPage();
418
+ break;
419
+ case 'ArrowRight':
420
+ nextPage();
421
+ break;
422
+ case '+':
423
+ case '=':
424
+ zoomIn();
425
+ break;
426
+ case '-':
427
+ zoomOut();
428
+ break;
429
+ case '0':
430
+ resetZoom();
431
+ break;
432
+ }
433
+ };
434
+
435
+ // Lifecycle
436
+ onMounted(() => {
437
+ if (props.autoLoad) {
438
+ loadPdf();
439
+ }
440
+
441
+ document.addEventListener('keydown', handleKeydown);
442
+ });
443
+
444
+ onUnmounted(() => {
445
+ document.removeEventListener('keydown', handleKeydown);
446
+ document.body.style.overflow = '';
447
+ });
448
+
449
+ // Watch for src changes
450
+ watch(() => props.src, () => {
451
+ if (props.autoLoad) {
452
+ loadPdf();
453
+ }
454
+ });
455
+
456
+ // Expose methods
457
+ defineExpose({
458
+ loadPdf,
459
+ zoomIn,
460
+ zoomOut,
461
+ resetZoom,
462
+ previousPage,
463
+ nextPage,
464
+ downloadPdf,
465
+ printPdf,
466
+ toggleFullscreen
467
+ });
468
+ </script>
469
+
470
+ <style lang="scss" scoped>
471
+ .pdf-viewer-container {
472
+ display: flex;
473
+ flex-direction: column;
474
+ border: 1px solid #e0e0e0;
475
+ border-radius: 8px;
476
+ background: white;
477
+ overflow: hidden;
478
+ height: 456px;
479
+
480
+ &.fullscreen {
481
+ position: fixed;
482
+ top: 0;
483
+ left: 0;
484
+ width: 100vw;
485
+ height: 100vh;
486
+ z-index: 9999;
487
+ border: none;
488
+ border-radius: 0;
489
+ }
490
+ }
491
+
492
+ .pdf-viewer-header {
493
+ display: flex;
494
+ justify-content: space-between;
495
+ align-items: center;
496
+ padding: 12px 16px;
497
+ background: #fff9f9;
498
+ border-bottom: 1px solid #e0e0e0;
499
+
500
+ .pdf-viewer-title {
501
+ h3 {
502
+ margin: 0;
503
+ font-size: 16px;
504
+ font-weight: 600;
505
+ color: #333;
506
+ }
507
+ }
508
+
509
+ .pdf-viewer-controls {
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 8px;
513
+
514
+ .zoom-controls,
515
+ .navigation-controls,
516
+ .action-controls {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 4px;
520
+
521
+ .zoom-level,
522
+ .page-info {
523
+ font-size: 14px;
524
+ color: #666;
525
+ min-width: 60px;
526
+ text-align: center;
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ .pdf-viewer-content {
533
+ flex: 1;
534
+ position: relative;
535
+ overflow: auto;
536
+ background: #f9f9f9;
537
+
538
+ .pdf-loading,
539
+ .pdf-error,
540
+ .pdf-fallback {
541
+ display: flex;
542
+ flex-direction: column;
543
+ align-items: center;
544
+ justify-content: center;
545
+ height: 100%;
546
+ padding: 40px;
547
+ text-align: center;
548
+
549
+ p {
550
+ margin: 16px 0 0 0;
551
+ color: #666;
552
+ }
553
+
554
+ h3 {
555
+ margin: 16px 0 8px 0;
556
+ color: #333;
557
+ }
558
+ }
559
+
560
+ .pdf-display {
561
+ width: 100%;
562
+ height: 100%;
563
+
564
+ iframe {
565
+ width: 100%;
566
+ height: 100%;
567
+ border: none;
568
+ }
569
+ }
570
+ }
571
+
572
+ .pdf-viewer-footer {
573
+ display: flex;
574
+ justify-content: space-between;
575
+ align-items: center;
576
+ padding: 8px 16px;
577
+ background: #f5f5f5;
578
+ border-top: 1px solid #e0e0e0;
579
+ font-size: 12px;
580
+ color: #666;
581
+
582
+ .page-info,
583
+ .file-info {
584
+ font-weight: 500;
585
+ }
586
+ }
587
+
588
+ // Responsive design
589
+ @media (max-width: 768px) {
590
+ .pdf-viewer-header {
591
+ flex-direction: column;
592
+ gap: 12px;
593
+
594
+ .pdf-viewer-controls {
595
+ flex-wrap: wrap;
596
+ justify-content: center;
597
+ }
598
+ }
599
+
600
+ .pdf-viewer-footer {
601
+ flex-direction: column;
602
+ gap: 4px;
603
+ text-align: center;
604
+ }
605
+ }
606
+
607
+ // Dark theme support
608
+ @media (prefers-color-scheme: dark) {
609
+ .pdf-viewer-container {
610
+ background: #1e1e1e;
611
+ border-color: #333;
612
+ }
613
+
614
+ .pdf-viewer-header,
615
+ .pdf-viewer-footer {
616
+ background: #2d2d2d;
617
+ border-color: #333;
618
+
619
+ .pdf-viewer-title h3 {
620
+ color: #fff;
621
+ }
622
+
623
+ .zoom-level,
624
+ .page-info {
625
+ color: #ccc;
626
+ }
627
+ }
628
+
629
+ .pdf-viewer-content {
630
+ background: #1e1e1e;
631
+
632
+ .pdf-loading,
633
+ .pdf-error,
634
+ .pdf-fallback {
635
+ p {
636
+ color: #ccc;
637
+ }
638
+
639
+ h3 {
640
+ color: #fff;
641
+ }
642
+ }
643
+ }
644
+ }
645
+ </style>