@sakoa/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -0
- package/dist/App.d.ts +2 -0
- package/dist/cli/index.js +9243 -0
- package/dist/components/DemoSection.d.ts +30 -0
- package/dist/components/SApiKeyboard.d.ts +22 -0
- package/dist/components/SApiSection.d.ts +21 -0
- package/dist/components/SApiTable.d.ts +46 -0
- package/dist/components/STableOfContents.d.ts +2 -0
- package/dist/components/ui/SAlert.d.ts +76 -0
- package/dist/components/ui/SBadge.d.ts +56 -0
- package/dist/components/ui/SButton.d.ts +67 -0
- package/dist/components/ui/SCheckbox.d.ts +64 -0
- package/dist/components/ui/SChip.d.ts +43 -0
- package/dist/components/ui/SDatePicker.d.ts +77 -0
- package/dist/components/ui/SGlassButton.d.ts +70 -0
- package/dist/components/ui/SIcon.d.ts +29 -0
- package/dist/components/ui/SInput.d.ts +129 -0
- package/dist/components/ui/SKbd.d.ts +24 -0
- package/dist/components/ui/SKbdShortcut.d.ts +14 -0
- package/dist/components/ui/SSelect.d.ts +148 -0
- package/dist/components/ui/SSkeleton.d.ts +37 -0
- package/dist/components/ui/SSwitch.d.ts +61 -0
- package/dist/components/ui/STooltip.d.ts +82 -0
- package/dist/components/ui/accordion/SAccordionContent.d.ts +23 -0
- package/dist/components/ui/accordion/SAccordionItem.d.ts +70 -0
- package/dist/components/ui/accordion/SAccordionTrigger.d.ts +37 -0
- package/dist/components/ui/accordion/index.d.ts +4 -0
- package/dist/components/ui/avatar/SAvatar.d.ts +36 -0
- package/dist/components/ui/avatar/SAvatarFallback.d.ts +26 -0
- package/dist/components/ui/avatar/SAvatarGroup.d.ts +30 -0
- package/dist/components/ui/avatar/SAvatarImage.d.ts +23 -0
- package/dist/components/ui/avatar/index.d.ts +4 -0
- package/dist/components/ui/breadcrumb/SBreadcrumb.d.ts +22 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbEllipsis.d.ts +17 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbItem.d.ts +17 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbLink.d.ts +26 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbList.d.ts +17 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbPage.d.ts +17 -0
- package/dist/components/ui/breadcrumb/SBreadcrumbSeparator.d.ts +17 -0
- package/dist/components/ui/breadcrumb/index.d.ts +7 -0
- package/dist/components/ui/card/SCard.d.ts +103 -0
- package/dist/components/ui/card/SCardActions.d.ts +44 -0
- package/dist/components/ui/card/SCardContent.d.ts +35 -0
- package/dist/components/ui/card/SCardFooter.d.ts +38 -0
- package/dist/components/ui/card/SCardHeader.d.ts +53 -0
- package/dist/components/ui/card/SCardMedia.d.ts +83 -0
- package/dist/components/ui/card/SGlassCard.d.ts +103 -0
- package/dist/components/ui/card/SMorphingCardContent.d.ts +18 -0
- package/dist/components/ui/card/index.d.ts +24 -0
- package/dist/components/ui/carousel/SCarousel.d.ts +166 -0
- package/dist/components/ui/carousel/index.d.ts +2 -0
- package/dist/components/ui/color-picker/SColorPickerAlphaSlider.d.ts +4 -0
- package/dist/components/ui/color-picker/SColorPickerCopy.d.ts +19 -0
- package/dist/components/ui/color-picker/SColorPickerEyeDropper.d.ts +17 -0
- package/dist/components/ui/color-picker/SColorPickerHueSlider.d.ts +4 -0
- package/dist/components/ui/color-picker/SColorPickerInputs.d.ts +2 -0
- package/dist/components/ui/color-picker/SColorPickerPresets.d.ts +9 -0
- package/dist/components/ui/color-picker/SColorPickerPreview.d.ts +2 -0
- package/dist/components/ui/color-picker/SColorPickerRecent.d.ts +7 -0
- package/dist/components/ui/color-picker/SColorPickerSpectrum.d.ts +4 -0
- package/dist/components/ui/color-picker/index.d.ts +11 -0
- package/dist/components/ui/drawer/index.d.ts +11 -0
- package/dist/components/ui/dropdown/SDropdownDivider.d.ts +8 -0
- package/dist/components/ui/dropdown/SDropdownGroup.d.ts +25 -0
- package/dist/components/ui/dropdown/SDropdownItem.d.ts +56 -0
- package/dist/components/ui/dropdown/index.d.ts +4 -0
- package/dist/components/ui/form/SForm.d.ts +38 -0
- package/dist/components/ui/form/SFormField.d.ts +31 -0
- package/dist/components/ui/form/index.d.ts +5 -0
- package/dist/components/ui/modal/index.d.ts +19 -0
- package/dist/components/ui/option/SOption.d.ts +32 -0
- package/dist/components/ui/option/SOptionGroup.d.ts +28 -0
- package/dist/components/ui/option/index.d.ts +2 -0
- package/dist/components/ui/otp/SOTP.d.ts +122 -0
- package/dist/components/ui/otp/SOTPGroup.d.ts +23 -0
- package/dist/components/ui/otp/SOTPSeparator.d.ts +17 -0
- package/dist/components/ui/otp/SOTPSlot.d.ts +49 -0
- package/dist/components/ui/otp/index.d.ts +7 -0
- package/dist/components/ui/otp/types.d.ts +26 -0
- package/dist/components/ui/otp/useOTPContext.d.ts +42 -0
- package/dist/components/ui/pagination/SPagination.d.ts +151 -0
- package/dist/components/ui/pagination/index.d.ts +2 -0
- package/dist/components/ui/progress/SProgress.d.ts +62 -0
- package/dist/components/ui/progress/SProgressRange.d.ts +91 -0
- package/dist/components/ui/progress/index.d.ts +4 -0
- package/dist/components/ui/radio/SRadio.d.ts +58 -0
- package/dist/components/ui/radio/SRadioGroup.d.ts +52 -0
- package/dist/components/ui/radio/index.d.ts +2 -0
- package/dist/components/ui/stepper/SStepper.d.ts +83 -0
- package/dist/components/ui/stepper/SStepperContent.d.ts +24 -0
- package/dist/components/ui/stepper/SStepperDescription.d.ts +20 -0
- package/dist/components/ui/stepper/SStepperIndicator.d.ts +37 -0
- package/dist/components/ui/stepper/SStepperItem.d.ts +37 -0
- package/dist/components/ui/stepper/SStepperSeparator.d.ts +5 -0
- package/dist/components/ui/stepper/SStepperTitle.d.ts +20 -0
- package/dist/components/ui/stepper/SStepperTrigger.d.ts +22 -0
- package/dist/components/ui/stepper/index.d.ts +11 -0
- package/dist/components/ui/table/STableBody.d.ts +27 -0
- package/dist/components/ui/table/STableCell.d.ts +55 -0
- package/dist/components/ui/table/STableColumn.d.ts +87 -0
- package/dist/components/ui/table/STableEmpty.d.ts +54 -0
- package/dist/components/ui/table/STableHeader.d.ts +25 -0
- package/dist/components/ui/table/STableRow.d.ts +38 -0
- package/dist/components/ui/table/STableSkeleton.d.ts +29 -0
- package/dist/components/ui/table/index.d.ts +98 -0
- package/dist/components/ui/table/useDataTable.d.ts +80 -0
- package/dist/components/ui/tabs/STabPane.d.ts +31 -0
- package/dist/components/ui/tabs/STabsContent.d.ts +21 -0
- package/dist/components/ui/tabs/STabsIndicator.d.ts +9 -0
- package/dist/components/ui/tabs/STabsTrigger.d.ts +28 -0
- package/dist/components/ui/tabs/index.d.ts +6 -0
- package/dist/components/ui/toast/SToast.d.ts +49 -0
- package/dist/components/ui/toast/SToastContainer.d.ts +21 -0
- package/dist/components/ui/toast/index.d.ts +2 -0
- package/dist/composables/useAsync.d.ts +134 -0
- package/dist/composables/useClickOutside.d.ts +69 -0
- package/dist/composables/useClipboard.d.ts +46 -0
- package/dist/composables/useDebounce.d.ts +150 -0
- package/dist/composables/useDialog.d.ts +118 -0
- package/dist/composables/useForm.d.ts +204 -0
- package/dist/composables/useHotkey.d.ts +128 -0
- package/dist/composables/useIntersectionObserver.d.ts +156 -0
- package/dist/composables/useLocalStorage.d.ts +120 -0
- package/dist/composables/useMediaQuery.d.ts +115 -0
- package/dist/composables/useTheme.d.ts +8 -0
- package/dist/composables/useToast.d.ts +1619 -0
- package/dist/index.d.ts +71 -0
- package/dist/layouts/UILayout.d.ts +2 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/main.d.ts +0 -0
- package/dist/router.d.ts +2 -0
- package/dist/saka-ui.css +1 -0
- package/dist/saka-ui.js +18513 -0
- package/dist/saka-ui.umd.cjs +38 -0
- package/dist/views/docs/CustomizationView.d.ts +2 -0
- package/dist/views/docs/FormValidationView.d.ts +2 -0
- package/dist/views/docs/StylingGuideView.d.ts +2 -0
- package/dist/views/docs/UseAsyncView.d.ts +2 -0
- package/dist/views/docs/UseClickOutsideView.d.ts +124 -0
- package/dist/views/docs/UseClipboardView.d.ts +4 -0
- package/dist/views/docs/UseDebounceView.d.ts +2 -0
- package/dist/views/docs/UseHotkeyView.d.ts +205 -0
- package/dist/views/docs/UseIntersectionObserverView.d.ts +5 -0
- package/dist/views/docs/UseLocalStorageView.d.ts +2 -0
- package/dist/views/docs/UseMediaQueryView.d.ts +2 -0
- package/dist/views/docs/UseThemeView.d.ts +2 -0
- package/dist/views/examples/AuthFormView.d.ts +2 -0
- package/dist/views/examples/CreditCardFormView.d.ts +6 -0
- package/dist/views/examples/FormFieldExampleView.d.ts +2 -0
- package/dist/views/examples/ProjectFormView.d.ts +2 -0
- package/dist/views/ui/AccordionView.d.ts +2 -0
- package/dist/views/ui/AlertView.d.ts +2 -0
- package/dist/views/ui/AvatarView.d.ts +2 -0
- package/dist/views/ui/BadgeView.d.ts +2 -0
- package/dist/views/ui/BreadcrumbView.d.ts +2 -0
- package/dist/views/ui/ButtonView.d.ts +2 -0
- package/dist/views/ui/CardView.d.ts +2 -0
- package/dist/views/ui/CarouselView.d.ts +274 -0
- package/dist/views/ui/CheckboxView.d.ts +2 -0
- package/dist/views/ui/ChipView.d.ts +2 -0
- package/dist/views/ui/ColorPickerView.d.ts +2 -0
- package/dist/views/ui/DatePickerView.d.ts +2 -0
- package/dist/views/ui/DialogView.d.ts +2 -0
- package/dist/views/ui/DrawerView.d.ts +2 -0
- package/dist/views/ui/DropdownView.d.ts +2 -0
- package/dist/views/ui/GlassButtonView.d.ts +2 -0
- package/dist/views/ui/GlassCardView.d.ts +2 -0
- package/dist/views/ui/HomeView.d.ts +2 -0
- package/dist/views/ui/IconsView.d.ts +2 -0
- package/dist/views/ui/InputView.d.ts +2 -0
- package/dist/views/ui/KbdView.d.ts +2 -0
- package/dist/views/ui/ModalView.d.ts +2 -0
- package/dist/views/ui/MorphingCardView.d.ts +2 -0
- package/dist/views/ui/MorphingModalView.d.ts +2 -0
- package/dist/views/ui/OTPView.d.ts +206 -0
- package/dist/views/ui/PaginationView.d.ts +2 -0
- package/dist/views/ui/ProgressView.d.ts +2 -0
- package/dist/views/ui/RadioView.d.ts +2 -0
- package/dist/views/ui/SelectView.d.ts +2 -0
- package/dist/views/ui/SkeletonView.d.ts +2 -0
- package/dist/views/ui/StepperView.d.ts +2 -0
- package/dist/views/ui/SwitchView.d.ts +2 -0
- package/dist/views/ui/TableView.d.ts +2 -0
- package/dist/views/ui/TabsView.d.ts +2 -0
- package/dist/views/ui/ToastView.d.ts +2 -0
- package/dist/views/ui/TooltipView.d.ts +2 -0
- package/dist/vite.svg +1 -0
- package/package.json +64 -0
- package/registry/components/accordion.json +19 -0
- package/registry/components/alert.json +17 -0
- package/registry/components/avatar.json +18 -0
- package/registry/components/badge.json +14 -0
- package/registry/components/breadcrumb.json +24 -0
- package/registry/components/button.json +17 -0
- package/registry/components/card.json +23 -0
- package/registry/components/carousel.json +19 -0
- package/registry/components/checkbox.json +17 -0
- package/registry/components/chip.json +17 -0
- package/registry/components/color-picker.json +24 -0
- package/registry/components/date-picker.json +17 -0
- package/registry/components/drawer.json +26 -0
- package/registry/components/dropdown.json +21 -0
- package/registry/components/form.json +16 -0
- package/registry/components/glass-button.json +17 -0
- package/registry/components/icon.json +17 -0
- package/registry/components/input.json +17 -0
- package/registry/components/kbd.json +16 -0
- package/registry/components/modal.json +32 -0
- package/registry/components/option.json +16 -0
- package/registry/components/otp.json +23 -0
- package/registry/components/pagination.json +18 -0
- package/registry/components/progress.json +16 -0
- package/registry/components/radio.json +19 -0
- package/registry/components/select.json +17 -0
- package/registry/components/skeleton.json +14 -0
- package/registry/components/switch.json +17 -0
- package/registry/components/table.json +26 -0
- package/registry/components/tabs.json +19 -0
- package/registry/components/toast.json +19 -0
- package/registry/components/tooltip.json +14 -0
- package/registry/index.json +4 -0
- package/registry/source/components/ui/SAlert.vue +388 -0
- package/registry/source/components/ui/SBadge.vue +243 -0
- package/registry/source/components/ui/SButton.vue +387 -0
- package/registry/source/components/ui/SCheckbox.vue +310 -0
- package/registry/source/components/ui/SChip.vue +130 -0
- package/registry/source/components/ui/SDatePicker.vue +1290 -0
- package/registry/source/components/ui/SGlassButton.vue +547 -0
- package/registry/source/components/ui/SIcon.vue +78 -0
- package/registry/source/components/ui/SInput.vue +1054 -0
- package/registry/source/components/ui/SKbd.vue +96 -0
- package/registry/source/components/ui/SKbdShortcut.vue +36 -0
- package/registry/source/components/ui/SSelect.vue +1290 -0
- package/registry/source/components/ui/SSkeleton.vue +185 -0
- package/registry/source/components/ui/SSwitch.vue +275 -0
- package/registry/source/components/ui/STooltip.vue +491 -0
- package/registry/source/components/ui/accordion/SAccordion.vue +248 -0
- package/registry/source/components/ui/accordion/SAccordionItem.vue +353 -0
- package/registry/source/components/ui/accordion/index.ts +5 -0
- package/registry/source/components/ui/avatar/SAvatar.vue +169 -0
- package/registry/source/components/ui/avatar/SAvatarFallback.vue +66 -0
- package/registry/source/components/ui/avatar/SAvatarGroup.vue +69 -0
- package/registry/source/components/ui/avatar/SAvatarImage.vue +92 -0
- package/registry/source/components/ui/avatar/index.ts +5 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumb.vue +23 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbEllipsis.vue +17 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbItem.vue +14 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbLink.vue +46 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbList.vue +17 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbPage.vue +15 -0
- package/registry/source/components/ui/breadcrumb/SBreadcrumbSeparator.vue +18 -0
- package/registry/source/components/ui/breadcrumb/index.ts +7 -0
- package/registry/source/components/ui/card/SCard.vue +517 -0
- package/registry/source/components/ui/card/SCardActions.vue +129 -0
- package/registry/source/components/ui/card/SCardContent.vue +117 -0
- package/registry/source/components/ui/card/SCardFooter.vue +103 -0
- package/registry/source/components/ui/card/SCardHeader.vue +163 -0
- package/registry/source/components/ui/card/SCardMedia.vue +312 -0
- package/registry/source/components/ui/card/index.ts +34 -0
- package/registry/source/components/ui/carousel/SCarousel.vue +1069 -0
- package/registry/source/components/ui/carousel/SCarouselSlide.vue +107 -0
- package/registry/source/components/ui/carousel/index.ts +3 -0
- package/registry/source/components/ui/color-picker/SColorPicker.vue +772 -0
- package/registry/source/components/ui/color-picker/SColorPickerAlphaSlider.vue +158 -0
- package/registry/source/components/ui/color-picker/SColorPickerCopy.vue +76 -0
- package/registry/source/components/ui/color-picker/SColorPickerEyeDropper.vue +68 -0
- package/registry/source/components/ui/color-picker/SColorPickerHueSlider.vue +138 -0
- package/registry/source/components/ui/color-picker/SColorPickerInputs.vue +227 -0
- package/registry/source/components/ui/color-picker/SColorPickerPresets.vue +87 -0
- package/registry/source/components/ui/color-picker/SColorPickerPreview.vue +46 -0
- package/registry/source/components/ui/color-picker/SColorPickerRecent.vue +74 -0
- package/registry/source/components/ui/color-picker/SColorPickerSpectrum.vue +149 -0
- package/registry/source/components/ui/color-picker/index.ts +11 -0
- package/registry/source/components/ui/drawer/SDrawer.vue +797 -0
- package/registry/source/components/ui/drawer/SDrawerClose.vue +64 -0
- package/registry/source/components/ui/drawer/SDrawerContent.vue +81 -0
- package/registry/source/components/ui/drawer/SDrawerDescription.vue +40 -0
- package/registry/source/components/ui/drawer/SDrawerFooter.vue +97 -0
- package/registry/source/components/ui/drawer/SDrawerHandle.vue +79 -0
- package/registry/source/components/ui/drawer/SDrawerHeader.vue +117 -0
- package/registry/source/components/ui/drawer/SDrawerTitle.vue +40 -0
- package/registry/source/components/ui/drawer/SDrawerTrigger.vue +51 -0
- package/registry/source/components/ui/drawer/index.ts +20 -0
- package/registry/source/components/ui/dropdown/SDropdown.vue +843 -0
- package/registry/source/components/ui/dropdown/SDropdownDivider.vue +23 -0
- package/registry/source/components/ui/dropdown/SDropdownGroup.vue +53 -0
- package/registry/source/components/ui/dropdown/SDropdownItem.vue +179 -0
- package/registry/source/components/ui/dropdown/index.ts +5 -0
- package/registry/source/components/ui/form/SForm.vue +84 -0
- package/registry/source/components/ui/form/SFormField.vue +78 -0
- package/registry/source/components/ui/form/index.ts +8 -0
- package/registry/source/components/ui/modal/SModal.vue +648 -0
- package/registry/source/components/ui/modal/SModalClose.vue +49 -0
- package/registry/source/components/ui/modal/SModalContent.vue +74 -0
- package/registry/source/components/ui/modal/SModalDescription.vue +39 -0
- package/registry/source/components/ui/modal/SModalFooter.vue +84 -0
- package/registry/source/components/ui/modal/SModalHeader.vue +107 -0
- package/registry/source/components/ui/modal/SModalTitle.vue +39 -0
- package/registry/source/components/ui/modal/SModalTrigger.vue +61 -0
- package/registry/source/components/ui/modal/SMorphingModal.vue +429 -0
- package/registry/source/components/ui/modal/SMorphingModalClose.vue +42 -0
- package/registry/source/components/ui/modal/SMorphingModalDescription.vue +49 -0
- package/registry/source/components/ui/modal/SMorphingModalImage.vue +44 -0
- package/registry/source/components/ui/modal/SMorphingModalSubtitle.vue +29 -0
- package/registry/source/components/ui/modal/SMorphingModalTitle.vue +34 -0
- package/registry/source/components/ui/modal/SMorphingModalTrigger.vue +95 -0
- package/registry/source/components/ui/modal/index.ts +32 -0
- package/registry/source/components/ui/option/SOption.vue +180 -0
- package/registry/source/components/ui/option/SOptionGroup.vue +77 -0
- package/registry/source/components/ui/option/index.ts +3 -0
- package/registry/source/components/ui/otp/SOTP.vue +843 -0
- package/registry/source/components/ui/otp/SOTPGroup.vue +29 -0
- package/registry/source/components/ui/otp/SOTPSeparator.vue +15 -0
- package/registry/source/components/ui/otp/SOTPSlot.vue +462 -0
- package/registry/source/components/ui/otp/index.ts +7 -0
- package/registry/source/components/ui/otp/types.ts +27 -0
- package/registry/source/components/ui/otp/useOTPContext.ts +62 -0
- package/registry/source/components/ui/pagination/SPagination.vue +923 -0
- package/registry/source/components/ui/pagination/index.ts +8 -0
- package/registry/source/components/ui/progress/SProgress.vue +635 -0
- package/registry/source/components/ui/progress/SProgressRange.vue +715 -0
- package/registry/source/components/ui/progress/index.ts +4 -0
- package/registry/source/components/ui/radio/SRadio.vue +407 -0
- package/registry/source/components/ui/radio/SRadioGroup.vue +200 -0
- package/registry/source/components/ui/radio/index.ts +3 -0
- package/registry/source/components/ui/table/SDataTable.vue +828 -0
- package/registry/source/components/ui/table/STableBody.vue +70 -0
- package/registry/source/components/ui/table/STableCell.vue +147 -0
- package/registry/source/components/ui/table/STableColumn.vue +120 -0
- package/registry/source/components/ui/table/STableEmpty.vue +159 -0
- package/registry/source/components/ui/table/STableHeader.vue +132 -0
- package/registry/source/components/ui/table/STableRow.vue +106 -0
- package/registry/source/components/ui/table/STableSkeleton.vue +208 -0
- package/registry/source/components/ui/table/index.ts +126 -0
- package/registry/source/components/ui/table/useDataTable.ts +519 -0
- package/registry/source/components/ui/tabs/STabPane.vue +130 -0
- package/registry/source/components/ui/tabs/STabs.vue +467 -0
- package/registry/source/components/ui/tabs/index.ts +7 -0
- package/registry/source/components/ui/toast/SToast.vue +261 -0
- package/registry/source/components/ui/toast/SToastContainer.vue +209 -0
- package/registry/source/components/ui/toast/index.ts +2 -0
- package/registry/source/composables/useForm.ts +960 -0
- package/registry/source/composables/useTheme.ts +86 -0
- package/registry/source/composables/useToast.ts +440 -0
- package/registry/source/lib/utils.ts +6 -0
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch, nextTick, onMounted, onBeforeUnmount, type VNode } from 'vue'
|
|
3
|
+
import { cn } from '../../../lib/utils'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type PaginationVariant = 'default' | 'outlined' | 'ghost' | 'minimal' | 'dots'
|
|
9
|
+
export type PaginationSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
10
|
+
export type PaginationShape = 'rounded' | 'square' | 'pill'
|
|
11
|
+
export type AnimationType = 'none' | 'slide' | 'fade' | 'scale' | 'flip'
|
|
12
|
+
|
|
13
|
+
export interface Props {
|
|
14
|
+
/** Current page (v-model) */
|
|
15
|
+
modelValue?: number
|
|
16
|
+
/** Total number of items */
|
|
17
|
+
total: number
|
|
18
|
+
/** Items per page */
|
|
19
|
+
pageSize?: number
|
|
20
|
+
/** Maximum visible page buttons */
|
|
21
|
+
maxVisiblePages?: number
|
|
22
|
+
/** Visual variant */
|
|
23
|
+
variant?: PaginationVariant
|
|
24
|
+
/** Size of pagination buttons */
|
|
25
|
+
size?: PaginationSize
|
|
26
|
+
/** Button shape */
|
|
27
|
+
shape?: PaginationShape
|
|
28
|
+
/** Primary color */
|
|
29
|
+
color?: string
|
|
30
|
+
/** Show first/last page buttons */
|
|
31
|
+
showFirstLast?: boolean
|
|
32
|
+
/** Show prev/next buttons */
|
|
33
|
+
showPrevNext?: boolean
|
|
34
|
+
/** Show total items info */
|
|
35
|
+
showTotal?: boolean
|
|
36
|
+
/** Show quick jump input */
|
|
37
|
+
showQuickJump?: boolean
|
|
38
|
+
/** Show page size selector */
|
|
39
|
+
showPageSize?: boolean
|
|
40
|
+
/** Page size options for selector */
|
|
41
|
+
pageSizeOptions?: number[]
|
|
42
|
+
/** Disabled state */
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
/** Loading state */
|
|
45
|
+
loading?: boolean
|
|
46
|
+
/** Animation type for page transitions */
|
|
47
|
+
animation?: AnimationType
|
|
48
|
+
/** Show progress bar */
|
|
49
|
+
showProgress?: boolean
|
|
50
|
+
/** Compact mode - combine prev/next into one button group */
|
|
51
|
+
compact?: boolean
|
|
52
|
+
/** Simple mode - only shows prev/next with page info */
|
|
53
|
+
simple?: boolean
|
|
54
|
+
/** Custom previous button text/icon */
|
|
55
|
+
prevText?: string
|
|
56
|
+
/** Custom next button text/icon */
|
|
57
|
+
nextText?: string
|
|
58
|
+
/** Custom first button text/icon */
|
|
59
|
+
firstText?: string
|
|
60
|
+
/** Custom last button text/icon */
|
|
61
|
+
lastText?: string
|
|
62
|
+
/** Enable keyboard navigation */
|
|
63
|
+
keyboard?: boolean
|
|
64
|
+
/** Background for the container */
|
|
65
|
+
background?: boolean
|
|
66
|
+
/** Hide single page */
|
|
67
|
+
hideSinglePage?: boolean
|
|
68
|
+
/** Format for total text */
|
|
69
|
+
totalFormat?: (total: number, range: [number, number]) => string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ellipsis hover state
|
|
73
|
+
const ellipsisStartHover = ref(false)
|
|
74
|
+
const ellipsisEndHover = ref(false)
|
|
75
|
+
const ellipsisStartRef = ref<HTMLElement | null>(null)
|
|
76
|
+
const ellipsisEndRef = ref<HTMLElement | null>(null)
|
|
77
|
+
let hoverTimeout: ReturnType<typeof setTimeout> | null = null
|
|
78
|
+
|
|
79
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
80
|
+
modelValue: 1,
|
|
81
|
+
pageSize: 10,
|
|
82
|
+
maxVisiblePages: 7,
|
|
83
|
+
variant: 'default',
|
|
84
|
+
size: 'md',
|
|
85
|
+
shape: 'rounded',
|
|
86
|
+
color: 'var(--s-primary)',
|
|
87
|
+
showFirstLast: false,
|
|
88
|
+
showPrevNext: true,
|
|
89
|
+
showTotal: false,
|
|
90
|
+
showQuickJump: false,
|
|
91
|
+
showPageSize: false,
|
|
92
|
+
pageSizeOptions: () => [10, 20, 50, 100],
|
|
93
|
+
disabled: false,
|
|
94
|
+
loading: false,
|
|
95
|
+
animation: 'scale',
|
|
96
|
+
showProgress: false,
|
|
97
|
+
compact: false,
|
|
98
|
+
simple: false,
|
|
99
|
+
prevText: undefined,
|
|
100
|
+
nextText: undefined,
|
|
101
|
+
firstText: undefined,
|
|
102
|
+
lastText: undefined,
|
|
103
|
+
keyboard: true,
|
|
104
|
+
background: false,
|
|
105
|
+
hideSinglePage: false,
|
|
106
|
+
totalFormat: undefined
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const emit = defineEmits<{
|
|
110
|
+
'update:modelValue': [page: number]
|
|
111
|
+
'update:pageSize': [size: number]
|
|
112
|
+
'change': [page: number]
|
|
113
|
+
'pageSizeChange': [size: number]
|
|
114
|
+
}>()
|
|
115
|
+
|
|
116
|
+
// Internal state
|
|
117
|
+
const internalPage = ref(props.modelValue)
|
|
118
|
+
const internalPageSize = ref(props.pageSize)
|
|
119
|
+
const jumpValue = ref('')
|
|
120
|
+
const isAnimating = ref(false)
|
|
121
|
+
const animationDirection = ref<'left' | 'right'>('right')
|
|
122
|
+
|
|
123
|
+
// Computed values
|
|
124
|
+
const totalPages = computed(() => Math.ceil(props.total / internalPageSize.value) || 1)
|
|
125
|
+
|
|
126
|
+
const currentPage = computed({
|
|
127
|
+
get: () => props.modelValue ?? internalPage.value,
|
|
128
|
+
set: (value) => {
|
|
129
|
+
const page = Math.max(1, Math.min(value, totalPages.value))
|
|
130
|
+
internalPage.value = page
|
|
131
|
+
emit('update:modelValue', page)
|
|
132
|
+
emit('change', page)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Calculate current range
|
|
137
|
+
const currentRange = computed<[number, number]>(() => {
|
|
138
|
+
const start = (currentPage.value - 1) * internalPageSize.value + 1
|
|
139
|
+
const end = Math.min(currentPage.value * internalPageSize.value, props.total)
|
|
140
|
+
return [start, end]
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Format total text
|
|
144
|
+
const totalText = computed(() => {
|
|
145
|
+
if (props.totalFormat) {
|
|
146
|
+
return props.totalFormat(props.total, currentRange.value)
|
|
147
|
+
}
|
|
148
|
+
return `${currentRange.value[0]}-${currentRange.value[1]} of ${props.total}`
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Progress percentage
|
|
152
|
+
const progressPercent = computed(() => {
|
|
153
|
+
if (totalPages.value <= 1) return 100
|
|
154
|
+
return ((currentPage.value - 1) / (totalPages.value - 1)) * 100
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Whether to show pagination
|
|
158
|
+
const shouldShow = computed(() => {
|
|
159
|
+
if (props.hideSinglePage && totalPages.value <= 1) return false
|
|
160
|
+
return true
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Generate page numbers with ellipsis
|
|
164
|
+
const visiblePages = computed(() => {
|
|
165
|
+
const pages: (number | 'ellipsis-start' | 'ellipsis-end')[] = []
|
|
166
|
+
const total = totalPages.value
|
|
167
|
+
const current = currentPage.value
|
|
168
|
+
const max = props.maxVisiblePages
|
|
169
|
+
|
|
170
|
+
if (total <= max) {
|
|
171
|
+
// Show all pages
|
|
172
|
+
for (let i = 1; i <= total; i++) {
|
|
173
|
+
pages.push(i)
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// Complex logic for ellipsis
|
|
177
|
+
const sideCount = Math.floor((max - 3) / 2) // pages on each side of current
|
|
178
|
+
|
|
179
|
+
// Always show first page
|
|
180
|
+
pages.push(1)
|
|
181
|
+
|
|
182
|
+
if (current <= sideCount + 2) {
|
|
183
|
+
// Near the start
|
|
184
|
+
for (let i = 2; i <= Math.max(max - 2, sideCount + 3); i++) {
|
|
185
|
+
pages.push(i)
|
|
186
|
+
}
|
|
187
|
+
pages.push('ellipsis-end')
|
|
188
|
+
pages.push(total)
|
|
189
|
+
} else if (current >= total - sideCount - 1) {
|
|
190
|
+
// Near the end
|
|
191
|
+
pages.push('ellipsis-start')
|
|
192
|
+
for (let i = Math.min(total - max + 3, total - sideCount - 2); i <= total; i++) {
|
|
193
|
+
pages.push(i)
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// In the middle
|
|
197
|
+
pages.push('ellipsis-start')
|
|
198
|
+
for (let i = current - sideCount; i <= current + sideCount; i++) {
|
|
199
|
+
pages.push(i)
|
|
200
|
+
}
|
|
201
|
+
pages.push('ellipsis-end')
|
|
202
|
+
pages.push(total)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return pages
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Compute hidden pages for ellipsis dropdown
|
|
210
|
+
const hiddenPagesStart = computed(() => {
|
|
211
|
+
const pages: number[] = []
|
|
212
|
+
const visible = visiblePages.value.filter(p => typeof p === 'number') as number[]
|
|
213
|
+
const minVisible = Math.min(...visible)
|
|
214
|
+
|
|
215
|
+
for (let i = 2; i < minVisible; i++) {
|
|
216
|
+
pages.push(i)
|
|
217
|
+
}
|
|
218
|
+
return pages
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const hiddenPagesEnd = computed(() => {
|
|
222
|
+
const pages: number[] = []
|
|
223
|
+
const visible = visiblePages.value.filter(p => typeof p === 'number') as number[]
|
|
224
|
+
const maxVisible = Math.max(...visible)
|
|
225
|
+
|
|
226
|
+
for (let i = maxVisible + 1; i < totalPages.value; i++) {
|
|
227
|
+
pages.push(i)
|
|
228
|
+
}
|
|
229
|
+
return pages
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// Ellipsis hover handlers
|
|
233
|
+
const handleEllipsisEnter = (type: 'start' | 'end') => {
|
|
234
|
+
if (hoverTimeout) clearTimeout(hoverTimeout)
|
|
235
|
+
if (type === 'start') {
|
|
236
|
+
ellipsisStartHover.value = true
|
|
237
|
+
} else {
|
|
238
|
+
ellipsisEndHover.value = true
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const handleEllipsisLeave = (type: 'start' | 'end') => {
|
|
243
|
+
hoverTimeout = setTimeout(() => {
|
|
244
|
+
if (type === 'start') {
|
|
245
|
+
ellipsisStartHover.value = false
|
|
246
|
+
} else {
|
|
247
|
+
ellipsisEndHover.value = false
|
|
248
|
+
}
|
|
249
|
+
}, 150)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const handleDropdownEnter = (type: 'start' | 'end') => {
|
|
253
|
+
if (hoverTimeout) clearTimeout(hoverTimeout)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const handleDropdownLeave = (type: 'start' | 'end') => {
|
|
257
|
+
if (type === 'start') {
|
|
258
|
+
ellipsisStartHover.value = false
|
|
259
|
+
} else {
|
|
260
|
+
ellipsisEndHover.value = false
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const selectFromDropdown = (page: number) => {
|
|
265
|
+
goToPage(page)
|
|
266
|
+
ellipsisStartHover.value = false
|
|
267
|
+
ellipsisEndHover.value = false
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Navigation
|
|
271
|
+
const canGoPrev = computed(() => currentPage.value > 1 && !props.disabled && !props.loading)
|
|
272
|
+
const canGoNext = computed(() => currentPage.value < totalPages.value && !props.disabled && !props.loading)
|
|
273
|
+
const canGoFirst = computed(() => currentPage.value > 1 && !props.disabled && !props.loading)
|
|
274
|
+
const canGoLast = computed(() => currentPage.value < totalPages.value && !props.disabled && !props.loading)
|
|
275
|
+
|
|
276
|
+
const goToPage = async (page: number) => {
|
|
277
|
+
if (props.disabled || props.loading) return
|
|
278
|
+
if (page < 1 || page > totalPages.value) return
|
|
279
|
+
if (page === currentPage.value) return
|
|
280
|
+
|
|
281
|
+
animationDirection.value = page > currentPage.value ? 'right' : 'left'
|
|
282
|
+
isAnimating.value = true
|
|
283
|
+
|
|
284
|
+
await nextTick()
|
|
285
|
+
currentPage.value = page
|
|
286
|
+
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
isAnimating.value = false
|
|
289
|
+
}, 100)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const goFirst = () => goToPage(1)
|
|
293
|
+
const goLast = () => goToPage(totalPages.value)
|
|
294
|
+
const goPrev = () => goToPage(currentPage.value - 1)
|
|
295
|
+
const goNext = () => goToPage(currentPage.value + 1)
|
|
296
|
+
|
|
297
|
+
// Quick jump
|
|
298
|
+
const handleQuickJump = () => {
|
|
299
|
+
const page = parseInt(jumpValue.value)
|
|
300
|
+
if (!isNaN(page) && page >= 1 && page <= totalPages.value) {
|
|
301
|
+
goToPage(page)
|
|
302
|
+
}
|
|
303
|
+
jumpValue.value = ''
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Page size change
|
|
307
|
+
const handlePageSizeChange = (event: Event) => {
|
|
308
|
+
const target = event.target as HTMLSelectElement
|
|
309
|
+
const newSize = parseInt(target.value)
|
|
310
|
+
internalPageSize.value = newSize
|
|
311
|
+
emit('update:pageSize', newSize)
|
|
312
|
+
emit('pageSizeChange', newSize)
|
|
313
|
+
|
|
314
|
+
// Reset to page 1 when page size changes
|
|
315
|
+
goToPage(1)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Keyboard navigation
|
|
319
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
320
|
+
if (!props.keyboard || props.disabled || props.loading) return
|
|
321
|
+
|
|
322
|
+
switch (event.key) {
|
|
323
|
+
case 'ArrowLeft':
|
|
324
|
+
event.preventDefault()
|
|
325
|
+
goPrev()
|
|
326
|
+
break
|
|
327
|
+
case 'ArrowRight':
|
|
328
|
+
event.preventDefault()
|
|
329
|
+
goNext()
|
|
330
|
+
break
|
|
331
|
+
case 'Home':
|
|
332
|
+
event.preventDefault()
|
|
333
|
+
goFirst()
|
|
334
|
+
break
|
|
335
|
+
case 'End':
|
|
336
|
+
event.preventDefault()
|
|
337
|
+
goLast()
|
|
338
|
+
break
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Size classes
|
|
343
|
+
const sizeClasses = computed(() => {
|
|
344
|
+
const sizes = {
|
|
345
|
+
xs: 'h-6 min-w-6 text-xs px-1.5',
|
|
346
|
+
sm: 'h-8 min-w-8 text-sm px-2',
|
|
347
|
+
md: 'h-10 min-w-10 text-sm px-3',
|
|
348
|
+
lg: 'h-12 min-w-12 text-base px-4',
|
|
349
|
+
xl: 'h-14 min-w-14 text-lg px-5'
|
|
350
|
+
}
|
|
351
|
+
return sizes[props.size]
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const iconSizes = computed(() => {
|
|
355
|
+
const sizes = {
|
|
356
|
+
xs: 'text-sm',
|
|
357
|
+
sm: 'text-base',
|
|
358
|
+
md: 'text-lg',
|
|
359
|
+
lg: 'text-xl',
|
|
360
|
+
xl: 'text-2xl'
|
|
361
|
+
}
|
|
362
|
+
return sizes[props.size]
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
const gapClasses = computed(() => {
|
|
366
|
+
const gaps = {
|
|
367
|
+
xs: 'gap-0.5',
|
|
368
|
+
sm: 'gap-1',
|
|
369
|
+
md: 'gap-1.5',
|
|
370
|
+
lg: 'gap-2',
|
|
371
|
+
xl: 'gap-2.5'
|
|
372
|
+
}
|
|
373
|
+
return gaps[props.size]
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
// Shape classes
|
|
377
|
+
const shapeClasses = computed(() => {
|
|
378
|
+
const shapes = {
|
|
379
|
+
rounded: 'rounded-lg',
|
|
380
|
+
square: 'rounded-none',
|
|
381
|
+
pill: 'rounded-full'
|
|
382
|
+
}
|
|
383
|
+
return shapes[props.shape]
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
// Active text color: use primary-foreground for default primary, white for custom colors
|
|
387
|
+
const activeTextColor = computed(() => props.color === 'var(--s-primary)' ? 'text-primary-foreground' : 'text-white')
|
|
388
|
+
|
|
389
|
+
// Button styles based on variant
|
|
390
|
+
const getButtonClasses = (isActive: boolean, isDisabled: boolean) => {
|
|
391
|
+
const base = `
|
|
392
|
+
relative flex items-center justify-center
|
|
393
|
+
font-medium transition-all duration-200 ease-out
|
|
394
|
+
select-none cursor-pointer
|
|
395
|
+
focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
|
|
396
|
+
${sizeClasses.value} ${shapeClasses.value}
|
|
397
|
+
`
|
|
398
|
+
|
|
399
|
+
if (isDisabled) {
|
|
400
|
+
return `${base} opacity-40 cursor-not-allowed pointer-events-none`
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (props.variant === 'default') {
|
|
404
|
+
return isActive
|
|
405
|
+
? `${base} ${activeTextColor.value} shadow-md hover:shadow-lg`
|
|
406
|
+
: `${base} bg-muted text-foreground hover:bg-accent border border-border`
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (props.variant === 'outlined') {
|
|
410
|
+
return isActive
|
|
411
|
+
? `${base} border-2 ${activeTextColor.value} shadow-md`
|
|
412
|
+
: `${base} border-2 border-border text-muted-foreground hover:border-muted-foreground hover:text-foreground`
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (props.variant === 'ghost') {
|
|
416
|
+
return isActive
|
|
417
|
+
? `${base} ${activeTextColor.value}`
|
|
418
|
+
: `${base} text-muted-foreground hover:bg-muted hover:text-foreground`
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (props.variant === 'minimal') {
|
|
422
|
+
return isActive
|
|
423
|
+
? `${base} font-bold`
|
|
424
|
+
: `${base} text-muted-foreground hover:text-foreground`
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (props.variant === 'dots') {
|
|
428
|
+
return isActive
|
|
429
|
+
? `${base} !w-3 !h-3 !min-w-3 !p-0 rounded-full shadow-md`
|
|
430
|
+
: `${base} !w-2 !h-2 !min-w-2 !p-0 rounded-full bg-muted-foreground hover:bg-muted-foreground`
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return base
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Active button style
|
|
437
|
+
const getActiveStyle = computed(() => {
|
|
438
|
+
const color = props.color
|
|
439
|
+
return {
|
|
440
|
+
backgroundColor: color,
|
|
441
|
+
'--tw-ring-color': `${color}50`,
|
|
442
|
+
borderColor: color
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// Animation classes
|
|
447
|
+
const animationClass = computed(() => {
|
|
448
|
+
if (props.animation === 'none') return ''
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
'slide': isAnimating.value
|
|
452
|
+
? `animate-pagination-slide-${animationDirection.value}`
|
|
453
|
+
: '',
|
|
454
|
+
'fade': isAnimating.value ? 'animate-pagination-fade' : '',
|
|
455
|
+
'scale': 'transition-transform hover:scale-110 active:scale-95',
|
|
456
|
+
'flip': isAnimating.value ? 'animate-pagination-flip' : ''
|
|
457
|
+
}[props.animation] || ''
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
// Watch for external page changes
|
|
461
|
+
watch(() => props.modelValue, (newVal) => {
|
|
462
|
+
if (newVal !== undefined) {
|
|
463
|
+
internalPage.value = newVal
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
watch(() => props.pageSize, (newVal) => {
|
|
468
|
+
internalPageSize.value = newVal
|
|
469
|
+
})
|
|
470
|
+
</script>
|
|
471
|
+
|
|
472
|
+
<template>
|
|
473
|
+
<nav
|
|
474
|
+
v-if="shouldShow"
|
|
475
|
+
v-bind="$attrs"
|
|
476
|
+
:class="cn(
|
|
477
|
+
's-pagination flex flex-wrap items-center',
|
|
478
|
+
gapClasses,
|
|
479
|
+
background && 'p-3 rounded-xl bg-muted border border-border',
|
|
480
|
+
(disabled || loading) && 'opacity-60 pointer-events-none',
|
|
481
|
+
$attrs.class ?? ''
|
|
482
|
+
)"
|
|
483
|
+
role="navigation"
|
|
484
|
+
aria-label="Pagination"
|
|
485
|
+
tabindex="0"
|
|
486
|
+
@keydown="handleKeydown"
|
|
487
|
+
>
|
|
488
|
+
<!-- Loading overlay -->
|
|
489
|
+
<Transition name="fade">
|
|
490
|
+
<div
|
|
491
|
+
v-if="loading"
|
|
492
|
+
class="absolute inset-0 flex items-center justify-center bg-background/50 rounded-xl z-10"
|
|
493
|
+
>
|
|
494
|
+
<span class="mdi mdi-loading animate-spin text-2xl" :style="{ color }"></span>
|
|
495
|
+
</div>
|
|
496
|
+
</Transition>
|
|
497
|
+
|
|
498
|
+
<!-- Total info -->
|
|
499
|
+
<div
|
|
500
|
+
v-if="showTotal && !simple"
|
|
501
|
+
class="flex items-center text-muted-foreground text-sm mr-2"
|
|
502
|
+
>
|
|
503
|
+
<slot name="total" :total="total" :range="currentRange">
|
|
504
|
+
<span>{{ totalText }}</span>
|
|
505
|
+
</slot>
|
|
506
|
+
</div>
|
|
507
|
+
|
|
508
|
+
<!-- Page size selector -->
|
|
509
|
+
<div v-if="showPageSize && !simple" class="flex items-center gap-1.5 mr-2">
|
|
510
|
+
<slot name="pageSize" :size="internalPageSize" :options="pageSizeOptions" :change="handlePageSizeChange">
|
|
511
|
+
<select
|
|
512
|
+
:value="internalPageSize"
|
|
513
|
+
class="h-9 px-2 rounded-lg bg-muted border border-border text-foreground text-sm cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
514
|
+
:disabled="disabled || loading"
|
|
515
|
+
@change="handlePageSizeChange"
|
|
516
|
+
>
|
|
517
|
+
<option
|
|
518
|
+
v-for="option in pageSizeOptions"
|
|
519
|
+
:key="option"
|
|
520
|
+
:value="option"
|
|
521
|
+
>
|
|
522
|
+
{{ option }} / page
|
|
523
|
+
</option>
|
|
524
|
+
</select>
|
|
525
|
+
</slot>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<!-- Simple mode -->
|
|
529
|
+
<template v-if="simple">
|
|
530
|
+
<button
|
|
531
|
+
type="button"
|
|
532
|
+
class="flex items-center gap-1.5"
|
|
533
|
+
:class="getButtonClasses(false, !canGoPrev)"
|
|
534
|
+
:disabled="!canGoPrev"
|
|
535
|
+
@click="goPrev"
|
|
536
|
+
>
|
|
537
|
+
<slot name="prev">
|
|
538
|
+
<span v-if="prevText">{{ prevText }}</span>
|
|
539
|
+
<span v-else class="mdi mdi-chevron-left" :class="iconSizes"></span>
|
|
540
|
+
</slot>
|
|
541
|
+
</button>
|
|
542
|
+
|
|
543
|
+
<div class="flex items-center gap-2 px-4 text-foreground font-medium">
|
|
544
|
+
<slot name="simple-content" :current="currentPage" :total="totalPages">
|
|
545
|
+
<span :style="{ color }">{{ currentPage }}</span>
|
|
546
|
+
<span class="text-muted-foreground">/</span>
|
|
547
|
+
<span class="text-muted-foreground">{{ totalPages }}</span>
|
|
548
|
+
</slot>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<button
|
|
552
|
+
type="button"
|
|
553
|
+
class="flex items-center gap-1.5"
|
|
554
|
+
:class="getButtonClasses(false, !canGoNext)"
|
|
555
|
+
:disabled="!canGoNext"
|
|
556
|
+
@click="goNext"
|
|
557
|
+
>
|
|
558
|
+
<slot name="next">
|
|
559
|
+
<span v-if="nextText">{{ nextText }}</span>
|
|
560
|
+
<span v-else class="mdi mdi-chevron-right" :class="iconSizes"></span>
|
|
561
|
+
</slot>
|
|
562
|
+
</button>
|
|
563
|
+
</template>
|
|
564
|
+
|
|
565
|
+
<!-- Full pagination -->
|
|
566
|
+
<template v-else>
|
|
567
|
+
<!-- First button -->
|
|
568
|
+
<button
|
|
569
|
+
v-if="showFirstLast"
|
|
570
|
+
type="button"
|
|
571
|
+
:class="getButtonClasses(false, !canGoFirst)"
|
|
572
|
+
:disabled="!canGoFirst"
|
|
573
|
+
aria-label="First page"
|
|
574
|
+
@click="goFirst"
|
|
575
|
+
>
|
|
576
|
+
<slot name="first">
|
|
577
|
+
<span v-if="firstText">{{ firstText }}</span>
|
|
578
|
+
<span v-else class="mdi mdi-chevron-double-left" :class="iconSizes"></span>
|
|
579
|
+
</slot>
|
|
580
|
+
</button>
|
|
581
|
+
|
|
582
|
+
<!-- Previous button -->
|
|
583
|
+
<button
|
|
584
|
+
v-if="showPrevNext"
|
|
585
|
+
type="button"
|
|
586
|
+
:class="getButtonClasses(false, !canGoPrev)"
|
|
587
|
+
:disabled="!canGoPrev"
|
|
588
|
+
aria-label="Previous page"
|
|
589
|
+
@click="goPrev"
|
|
590
|
+
>
|
|
591
|
+
<slot name="prev">
|
|
592
|
+
<span v-if="prevText">{{ prevText }}</span>
|
|
593
|
+
<span v-else class="mdi mdi-chevron-left" :class="iconSizes"></span>
|
|
594
|
+
</slot>
|
|
595
|
+
</button>
|
|
596
|
+
|
|
597
|
+
<!-- Page buttons -->
|
|
598
|
+
<div
|
|
599
|
+
class="flex items-center"
|
|
600
|
+
:class="gapClasses"
|
|
601
|
+
>
|
|
602
|
+
<template v-for="page in visiblePages" :key="page">
|
|
603
|
+
<!-- Ellipsis Start with Dropdown -->
|
|
604
|
+
<div
|
|
605
|
+
v-if="page === 'ellipsis-start'"
|
|
606
|
+
ref="ellipsisStartRef"
|
|
607
|
+
class="relative"
|
|
608
|
+
@mouseenter="handleEllipsisEnter('start')"
|
|
609
|
+
@mouseleave="handleEllipsisLeave('start')"
|
|
610
|
+
>
|
|
611
|
+
<span
|
|
612
|
+
class="flex items-center justify-center text-muted-foreground cursor-pointer hover:text-muted-foreground transition-colors"
|
|
613
|
+
:class="sizeClasses"
|
|
614
|
+
>
|
|
615
|
+
<slot name="ellipsis">
|
|
616
|
+
<span class="mdi mdi-dots-horizontal"></span>
|
|
617
|
+
</slot>
|
|
618
|
+
</span>
|
|
619
|
+
|
|
620
|
+
<!-- Dropdown for hidden pages -->
|
|
621
|
+
<Transition
|
|
622
|
+
enter-active-class="transition-all duration-100 ease-out"
|
|
623
|
+
enter-from-class="opacity-0 scale-95 -translate-y-1"
|
|
624
|
+
enter-to-class="opacity-100 scale-100 translate-y-0"
|
|
625
|
+
leave-active-class="transition-all duration-75 ease-in"
|
|
626
|
+
leave-from-class="opacity-100 scale-100"
|
|
627
|
+
leave-to-class="opacity-0 scale-95"
|
|
628
|
+
>
|
|
629
|
+
<div
|
|
630
|
+
v-if="ellipsisStartHover && hiddenPagesStart.length > 0"
|
|
631
|
+
class="absolute top-full left-1/2 -translate-x-1/2 mt-1 z-50 p-1.5 rounded-lg bg-background border border-border shadow-xl backdrop-blur-xl max-h-48 overflow-y-auto"
|
|
632
|
+
@mouseenter="handleDropdownEnter('start')"
|
|
633
|
+
@mouseleave="handleDropdownLeave('start')"
|
|
634
|
+
>
|
|
635
|
+
<div class="flex flex-wrap gap-1 min-w-max" :style="{ maxWidth: '200px' }">
|
|
636
|
+
<button
|
|
637
|
+
v-for="p in hiddenPagesStart"
|
|
638
|
+
:key="p"
|
|
639
|
+
type="button"
|
|
640
|
+
class="flex items-center justify-center text-sm min-w-8 h-8 px-2 rounded-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
|
641
|
+
@click="selectFromDropdown(p)"
|
|
642
|
+
>
|
|
643
|
+
{{ p }}
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
</Transition>
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
<!-- Ellipsis End with Dropdown -->
|
|
651
|
+
<div
|
|
652
|
+
v-else-if="page === 'ellipsis-end'"
|
|
653
|
+
ref="ellipsisEndRef"
|
|
654
|
+
class="relative"
|
|
655
|
+
@mouseenter="handleEllipsisEnter('end')"
|
|
656
|
+
@mouseleave="handleEllipsisLeave('end')"
|
|
657
|
+
>
|
|
658
|
+
<span
|
|
659
|
+
class="flex items-center justify-center text-muted-foreground cursor-pointer hover:text-muted-foreground transition-colors"
|
|
660
|
+
:class="sizeClasses"
|
|
661
|
+
>
|
|
662
|
+
<slot name="ellipsis">
|
|
663
|
+
<span class="mdi mdi-dots-horizontal"></span>
|
|
664
|
+
</slot>
|
|
665
|
+
</span>
|
|
666
|
+
|
|
667
|
+
<!-- Dropdown for hidden pages -->
|
|
668
|
+
<Transition
|
|
669
|
+
enter-active-class="transition-all duration-100 ease-out"
|
|
670
|
+
enter-from-class="opacity-0 scale-95 -translate-y-1"
|
|
671
|
+
enter-to-class="opacity-100 scale-100 translate-y-0"
|
|
672
|
+
leave-active-class="transition-all duration-75 ease-in"
|
|
673
|
+
leave-from-class="opacity-100 scale-100"
|
|
674
|
+
leave-to-class="opacity-0 scale-95"
|
|
675
|
+
>
|
|
676
|
+
<div
|
|
677
|
+
v-if="ellipsisEndHover && hiddenPagesEnd.length > 0"
|
|
678
|
+
class="absolute top-full left-1/2 -translate-x-1/2 mt-1 z-50 p-1.5 rounded-lg bg-background border border-border shadow-xl backdrop-blur-xl max-h-48 overflow-y-auto"
|
|
679
|
+
@mouseenter="handleDropdownEnter('end')"
|
|
680
|
+
@mouseleave="handleDropdownLeave('end')"
|
|
681
|
+
>
|
|
682
|
+
<div class="flex flex-wrap gap-1 min-w-max" :style="{ maxWidth: '200px' }">
|
|
683
|
+
<button
|
|
684
|
+
v-for="p in hiddenPagesEnd"
|
|
685
|
+
:key="p"
|
|
686
|
+
type="button"
|
|
687
|
+
class="flex items-center justify-center text-sm min-w-8 h-8 px-2 rounded-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
|
688
|
+
@click="selectFromDropdown(p)"
|
|
689
|
+
>
|
|
690
|
+
{{ p }}
|
|
691
|
+
</button>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
</Transition>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<!-- Page button -->
|
|
698
|
+
<button
|
|
699
|
+
v-else
|
|
700
|
+
type="button"
|
|
701
|
+
:class="[
|
|
702
|
+
getButtonClasses(page === currentPage, false),
|
|
703
|
+
animationClass,
|
|
704
|
+
{ 's-pagination__page--active': page === currentPage }
|
|
705
|
+
]"
|
|
706
|
+
:style="page === currentPage ? getActiveStyle : undefined"
|
|
707
|
+
:aria-current="page === currentPage ? 'page' : undefined"
|
|
708
|
+
:aria-label="`Page ${page}`"
|
|
709
|
+
@click="goToPage(page)"
|
|
710
|
+
>
|
|
711
|
+
<slot name="page" :page="page" :active="page === currentPage">
|
|
712
|
+
<template v-if="variant === 'dots'">
|
|
713
|
+
<span class="sr-only">{{ page }}</span>
|
|
714
|
+
</template>
|
|
715
|
+
<template v-else>
|
|
716
|
+
{{ page }}
|
|
717
|
+
</template>
|
|
718
|
+
</slot>
|
|
719
|
+
</button>
|
|
720
|
+
</template>
|
|
721
|
+
</div>
|
|
722
|
+
|
|
723
|
+
<!-- Next button -->
|
|
724
|
+
<button
|
|
725
|
+
v-if="showPrevNext"
|
|
726
|
+
type="button"
|
|
727
|
+
:class="getButtonClasses(false, !canGoNext)"
|
|
728
|
+
:disabled="!canGoNext"
|
|
729
|
+
aria-label="Next page"
|
|
730
|
+
@click="goNext"
|
|
731
|
+
>
|
|
732
|
+
<slot name="next">
|
|
733
|
+
<span v-if="nextText">{{ nextText }}</span>
|
|
734
|
+
<span v-else class="mdi mdi-chevron-right" :class="iconSizes"></span>
|
|
735
|
+
</slot>
|
|
736
|
+
</button>
|
|
737
|
+
|
|
738
|
+
<!-- Last button -->
|
|
739
|
+
<button
|
|
740
|
+
v-if="showFirstLast"
|
|
741
|
+
type="button"
|
|
742
|
+
:class="getButtonClasses(false, !canGoLast)"
|
|
743
|
+
:disabled="!canGoLast"
|
|
744
|
+
aria-label="Last page"
|
|
745
|
+
@click="goLast"
|
|
746
|
+
>
|
|
747
|
+
<slot name="last">
|
|
748
|
+
<span v-if="lastText">{{ lastText }}</span>
|
|
749
|
+
<span v-else class="mdi mdi-chevron-double-right" :class="iconSizes"></span>
|
|
750
|
+
</slot>
|
|
751
|
+
</button>
|
|
752
|
+
</template>
|
|
753
|
+
|
|
754
|
+
<!-- Quick jump -->
|
|
755
|
+
<div v-if="showQuickJump && !simple" class="flex items-center gap-2 ml-2">
|
|
756
|
+
<slot name="quickJump" :value="jumpValue" :jump="handleQuickJump">
|
|
757
|
+
<span class="text-sm text-muted-foreground">Go to</span>
|
|
758
|
+
<input
|
|
759
|
+
v-model="jumpValue"
|
|
760
|
+
type="number"
|
|
761
|
+
:min="1"
|
|
762
|
+
:max="totalPages"
|
|
763
|
+
class="w-16 h-9 px-2 rounded-lg bg-muted border border-border text-foreground text-sm text-center focus:outline-none focus:ring-2 focus:ring-primary/50 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
764
|
+
:disabled="disabled || loading"
|
|
765
|
+
placeholder="#"
|
|
766
|
+
@keydown.enter="handleQuickJump"
|
|
767
|
+
/>
|
|
768
|
+
</slot>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<!-- Progress bar -->
|
|
772
|
+
<div
|
|
773
|
+
v-if="showProgress"
|
|
774
|
+
class="w-full h-1 mt-2 rounded-full bg-accent overflow-hidden"
|
|
775
|
+
>
|
|
776
|
+
<div
|
|
777
|
+
class="h-full rounded-full transition-all duration-300 ease-out"
|
|
778
|
+
:style="{
|
|
779
|
+
width: `${progressPercent}%`,
|
|
780
|
+
backgroundColor: color
|
|
781
|
+
}"
|
|
782
|
+
></div>
|
|
783
|
+
</div>
|
|
784
|
+
</nav>
|
|
785
|
+
</template>
|
|
786
|
+
|
|
787
|
+
<style scoped>
|
|
788
|
+
.s-pagination {
|
|
789
|
+
position: relative;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/* Focus styles */
|
|
793
|
+
.s-pagination:focus-visible {
|
|
794
|
+
outline: 2px solid var(--s-primary);
|
|
795
|
+
outline-offset: 2px;
|
|
796
|
+
border-radius: 0.75rem;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
button:focus-visible {
|
|
800
|
+
outline: none;
|
|
801
|
+
box-shadow: 0 0 0 2px var(--s-background), 0 0 0 4px var(--s-primary);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* Ripple effect for buttons */
|
|
805
|
+
.s-pagination button::after {
|
|
806
|
+
content: '';
|
|
807
|
+
position: absolute;
|
|
808
|
+
inset: 0;
|
|
809
|
+
border-radius: inherit;
|
|
810
|
+
background: radial-gradient(circle at center, rgba(255,255,255,0.3) 0%, transparent 70%);
|
|
811
|
+
opacity: 0;
|
|
812
|
+
transform: scale(0);
|
|
813
|
+
transition: opacity 0.2s, transform 0.3s;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.s-pagination button:active::after {
|
|
817
|
+
opacity: 1;
|
|
818
|
+
transform: scale(1);
|
|
819
|
+
transition: opacity 0s, transform 0s;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/* Animations - Fast and clean to avoid digit overlap */
|
|
823
|
+
.pagination-slide-enter-active,
|
|
824
|
+
.pagination-slide-leave-active {
|
|
825
|
+
transition: all 0.1s ease-out;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.pagination-slide-enter-from {
|
|
829
|
+
opacity: 0;
|
|
830
|
+
transform: translateX(-6px);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.pagination-slide-leave-to {
|
|
834
|
+
opacity: 0;
|
|
835
|
+
transform: translateX(6px);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.pagination-fade-enter-active,
|
|
839
|
+
.pagination-fade-leave-active {
|
|
840
|
+
transition: opacity 0.08s ease;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.pagination-fade-enter-from,
|
|
844
|
+
.pagination-fade-leave-to {
|
|
845
|
+
opacity: 0;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.pagination-scale-enter-active,
|
|
849
|
+
.pagination-scale-leave-active {
|
|
850
|
+
transition: all 0.1s ease-out;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.pagination-scale-enter-from,
|
|
854
|
+
.pagination-scale-leave-to {
|
|
855
|
+
opacity: 0;
|
|
856
|
+
transform: scale(0.9);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.pagination-flip-enter-active,
|
|
860
|
+
.pagination-flip-leave-active {
|
|
861
|
+
transition: all 0.12s ease-out;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.pagination-flip-enter-from {
|
|
865
|
+
opacity: 0;
|
|
866
|
+
transform: rotateY(-45deg);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.pagination-flip-leave-to {
|
|
870
|
+
opacity: 0;
|
|
871
|
+
transform: rotateY(45deg);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/* Active page pulse animation */
|
|
875
|
+
.s-pagination__page--active {
|
|
876
|
+
animation: pagination-pulse 0.15s ease-out;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
@keyframes pagination-pulse {
|
|
880
|
+
0% {
|
|
881
|
+
transform: scale(0.9);
|
|
882
|
+
}
|
|
883
|
+
50% {
|
|
884
|
+
transform: scale(1.05);
|
|
885
|
+
}
|
|
886
|
+
100% {
|
|
887
|
+
transform: scale(1);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/* Hover glow effect */
|
|
892
|
+
.s-pagination button:not(:disabled):hover {
|
|
893
|
+
box-shadow: 0 0 0 2px var(--s-border);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.s-pagination__page--active:hover {
|
|
897
|
+
box-shadow: 0 4px 12px -2px var(--btn-glow, rgba(0,0,0,0.2));
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/* Fade transition */
|
|
901
|
+
.fade-enter-active,
|
|
902
|
+
.fade-leave-active {
|
|
903
|
+
transition: opacity 0.2s ease;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.fade-enter-from,
|
|
907
|
+
.fade-leave-to {
|
|
908
|
+
opacity: 0;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/* Screen reader only */
|
|
912
|
+
.sr-only {
|
|
913
|
+
position: absolute;
|
|
914
|
+
width: 1px;
|
|
915
|
+
height: 1px;
|
|
916
|
+
padding: 0;
|
|
917
|
+
margin: -1px;
|
|
918
|
+
overflow: hidden;
|
|
919
|
+
clip: rect(0, 0, 0, 0);
|
|
920
|
+
white-space: nowrap;
|
|
921
|
+
border: 0;
|
|
922
|
+
}
|
|
923
|
+
</style>
|