@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,429 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModal - Morphing Dialog Component
|
|
4
|
+
* A dialog that uses layout animations to transition content from trigger to expanded view.
|
|
5
|
+
* Inspired by motion-primitives MorphingDialog.
|
|
6
|
+
*/
|
|
7
|
+
import { type InjectionKey, type Ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
export interface SMorphingModalContext {
|
|
10
|
+
isOpen: Ref<boolean>
|
|
11
|
+
isExpanded: Ref<boolean>
|
|
12
|
+
triggerRect: Ref<DOMRect | null>
|
|
13
|
+
open: () => void
|
|
14
|
+
close: () => void
|
|
15
|
+
registerTrigger: (el: HTMLElement | null) => void
|
|
16
|
+
uniqueId: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SMorphingModalContextKey: InjectionKey<SMorphingModalContext> = Symbol('SMorphingModalContext')
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
defineOptions({ inheritAttrs: false })
|
|
24
|
+
|
|
25
|
+
import { ref, computed, provide, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
|
26
|
+
import { cn } from '../../../lib/utils'
|
|
27
|
+
|
|
28
|
+
export interface Props {
|
|
29
|
+
/** Control modal visibility */
|
|
30
|
+
modelValue?: boolean
|
|
31
|
+
/** Transition duration in ms for opening */
|
|
32
|
+
duration?: number
|
|
33
|
+
/** Transition duration in ms for closing (defaults to duration * 1.2) */
|
|
34
|
+
closeDuration?: number
|
|
35
|
+
/** Easing function for opening */
|
|
36
|
+
easing?: string
|
|
37
|
+
/** Easing function for closing (defaults to a smoother ease-out) */
|
|
38
|
+
closeEasing?: string
|
|
39
|
+
/** Close on backdrop click */
|
|
40
|
+
closeOnBackdrop?: boolean
|
|
41
|
+
/** Close on escape key */
|
|
42
|
+
closeOnEscape?: boolean
|
|
43
|
+
/** Lock body scroll when open */
|
|
44
|
+
lockScroll?: boolean
|
|
45
|
+
/** Z-index for the modal */
|
|
46
|
+
zIndex?: number
|
|
47
|
+
/** Backdrop blur effect */
|
|
48
|
+
backdropBlur?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
52
|
+
modelValue: false,
|
|
53
|
+
duration: 700,
|
|
54
|
+
closeDuration: undefined,
|
|
55
|
+
easing: 'cubic-bezier(0.32, 0.72, 0, 1)',
|
|
56
|
+
closeEasing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
57
|
+
closeOnBackdrop: true,
|
|
58
|
+
closeOnEscape: true,
|
|
59
|
+
lockScroll: true,
|
|
60
|
+
zIndex: 1000,
|
|
61
|
+
backdropBlur: true
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Computed durations
|
|
65
|
+
const actualCloseDuration = computed(() => props.closeDuration ?? Math.round(props.duration * 1.2))
|
|
66
|
+
|
|
67
|
+
const emit = defineEmits<{
|
|
68
|
+
'update:modelValue': [value: boolean]
|
|
69
|
+
'open': []
|
|
70
|
+
'close': []
|
|
71
|
+
'opened': []
|
|
72
|
+
'closed': []
|
|
73
|
+
}>()
|
|
74
|
+
|
|
75
|
+
// Generate unique ID
|
|
76
|
+
const uniqueId = `morphing-modal-${Math.random().toString(36).slice(2, 9)}`
|
|
77
|
+
|
|
78
|
+
// Refs
|
|
79
|
+
const isVisible = ref(false)
|
|
80
|
+
const isExpanded = ref(false)
|
|
81
|
+
const isOpen = ref(props.modelValue)
|
|
82
|
+
const isClosing = ref(false)
|
|
83
|
+
const triggerElement = ref<HTMLElement | null>(null)
|
|
84
|
+
const triggerRect = ref<DOMRect | null>(null)
|
|
85
|
+
const contentRef = ref<HTMLElement | null>(null)
|
|
86
|
+
const previousActiveElement = ref<HTMLElement | null>(null)
|
|
87
|
+
|
|
88
|
+
// Store the actual dimensions when expanded for smooth close transition
|
|
89
|
+
const expandedRect = ref<{ width: number; height: number; top: number; left: number } | null>(null)
|
|
90
|
+
|
|
91
|
+
// Trigger visibility strategy:
|
|
92
|
+
// - During open: overlay (0->1) expands while trigger (1->0) is visible.
|
|
93
|
+
// - During close: overlay (1->0) collapses while trigger (0->1) fades back in.
|
|
94
|
+
const triggerOpacity = computed(() => (isExpanded.value ? 0 : 1))
|
|
95
|
+
|
|
96
|
+
const triggerWrapperStyle = computed(() => {
|
|
97
|
+
const isClosingState = isClosing.value
|
|
98
|
+
const duration = isClosingState ? actualCloseDuration.value : props.duration
|
|
99
|
+
const easing = isClosingState ? props.closeEasing : props.easing
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
opacity: triggerOpacity.value,
|
|
103
|
+
pointerEvents: isVisible.value ? 'none' : 'auto',
|
|
104
|
+
transitionProperty: 'opacity',
|
|
105
|
+
transitionDuration: `${duration}ms`,
|
|
106
|
+
transitionTimingFunction: easing
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// Scrollbar width calculation
|
|
111
|
+
let scrollbarWidth = 0
|
|
112
|
+
|
|
113
|
+
const getScrollbarWidth = () => {
|
|
114
|
+
const outer = document.createElement('div')
|
|
115
|
+
outer.style.visibility = 'hidden'
|
|
116
|
+
outer.style.overflow = 'scroll'
|
|
117
|
+
document.body.appendChild(outer)
|
|
118
|
+
const inner = document.createElement('div')
|
|
119
|
+
outer.appendChild(inner)
|
|
120
|
+
const sw = outer.offsetWidth - inner.offsetWidth
|
|
121
|
+
outer.parentNode?.removeChild(outer)
|
|
122
|
+
return sw
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const lockBodyScroll = () => {
|
|
126
|
+
if (!props.lockScroll) return
|
|
127
|
+
scrollbarWidth = getScrollbarWidth()
|
|
128
|
+
const hasScrollbar = window.innerWidth > document.documentElement.clientWidth
|
|
129
|
+
document.body.style.overflow = 'hidden'
|
|
130
|
+
if (hasScrollbar) {
|
|
131
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const unlockBodyScroll = () => {
|
|
136
|
+
if (!props.lockScroll) return
|
|
137
|
+
document.body.style.overflow = ''
|
|
138
|
+
document.body.style.paddingRight = ''
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Computed styles for collapsed state (at trigger position)
|
|
142
|
+
const collapsedStyle = computed(() => {
|
|
143
|
+
if (!triggerRect.value) return {}
|
|
144
|
+
|
|
145
|
+
const rect = triggerRect.value
|
|
146
|
+
return {
|
|
147
|
+
top: `${rect.top}px`,
|
|
148
|
+
left: `${rect.left}px`,
|
|
149
|
+
width: `${rect.width}px`,
|
|
150
|
+
height: `${rect.height}px`,
|
|
151
|
+
borderRadius: '16px',
|
|
152
|
+
// Keep opacity 1 for closing morph - the morph IS the effect
|
|
153
|
+
opacity: 1,
|
|
154
|
+
transform: 'scale(1)'
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// Computed styles for expanded state (centered modal)
|
|
159
|
+
const expandedStyle = computed(() => {
|
|
160
|
+
const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 1024
|
|
161
|
+
const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 768
|
|
162
|
+
|
|
163
|
+
const modalWidth = Math.min(viewportWidth * 0.9, 512)
|
|
164
|
+
const modalHeight = Math.min(viewportHeight * 0.85, 600)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
top: `${(viewportHeight - modalHeight) / 2}px`,
|
|
168
|
+
left: `${(viewportWidth - modalWidth) / 2}px`,
|
|
169
|
+
width: `${modalWidth}px`,
|
|
170
|
+
height: 'auto',
|
|
171
|
+
maxHeight: `${modalHeight}px`,
|
|
172
|
+
borderRadius: '24px',
|
|
173
|
+
opacity: 1,
|
|
174
|
+
transform: 'scale(1)'
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Style for the "from" state when closing (captures actual expanded dimensions)
|
|
179
|
+
const closeFromStyle = computed(() => {
|
|
180
|
+
if (!expandedRect.value) return expandedStyle.value
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
top: `${expandedRect.value.top}px`,
|
|
184
|
+
left: `${expandedRect.value.left}px`,
|
|
185
|
+
width: `${expandedRect.value.width}px`,
|
|
186
|
+
height: `${expandedRect.value.height}px`,
|
|
187
|
+
borderRadius: '24px',
|
|
188
|
+
opacity: 1,
|
|
189
|
+
transform: 'scale(1)'
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// Current style based on expansion state
|
|
194
|
+
const contentStyle = computed(() => {
|
|
195
|
+
// Use different timing for open vs close
|
|
196
|
+
const isClosingState = isClosing.value
|
|
197
|
+
const currentDuration = isClosingState ? actualCloseDuration.value : props.duration
|
|
198
|
+
const currentEasing = isClosingState ? props.closeEasing : props.easing
|
|
199
|
+
|
|
200
|
+
const baseStyle = {
|
|
201
|
+
position: 'fixed' as const,
|
|
202
|
+
transitionProperty: 'top, left, width, height, border-radius, opacity, transform',
|
|
203
|
+
transitionDuration: `${currentDuration}ms`,
|
|
204
|
+
transitionTimingFunction: currentEasing,
|
|
205
|
+
willChange: 'top, left, width, height, border-radius, opacity, transform',
|
|
206
|
+
transformOrigin: 'top left'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// When expanded - show at expanded position
|
|
210
|
+
if (isExpanded.value && !isClosing.value) {
|
|
211
|
+
return { ...baseStyle, ...expandedStyle.value }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// When closing but animation hasn't started yet - stay at captured expanded position
|
|
215
|
+
if (isClosing.value && isExpanded.value) {
|
|
216
|
+
return { ...baseStyle, ...closeFromStyle.value }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// When closing and animation has started - animate to collapsed position
|
|
220
|
+
// Return to the exact same state the opening started from: collapsed position, opacity 0, scale 0.95.
|
|
221
|
+
if (isClosing.value && !isExpanded.value) {
|
|
222
|
+
return { ...baseStyle, ...collapsedStyle.value, opacity: 0, transform: 'scale(0.95)' }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Initial collapsed state (opening) - start faded and slightly scaled
|
|
226
|
+
return { ...baseStyle, ...collapsedStyle.value, opacity: 0, transform: 'scale(0.95)' }
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// Methods
|
|
230
|
+
const registerTrigger = (el: HTMLElement | null) => {
|
|
231
|
+
triggerElement.value = el
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const updateTriggerRect = () => {
|
|
235
|
+
if (triggerElement.value) {
|
|
236
|
+
triggerRect.value = triggerElement.value.getBoundingClientRect()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const open = () => {
|
|
241
|
+
if (isVisible.value) return
|
|
242
|
+
|
|
243
|
+
previousActiveElement.value = document.activeElement as HTMLElement
|
|
244
|
+
updateTriggerRect()
|
|
245
|
+
|
|
246
|
+
isVisible.value = true
|
|
247
|
+
isOpen.value = true
|
|
248
|
+
isClosing.value = false
|
|
249
|
+
|
|
250
|
+
lockBodyScroll()
|
|
251
|
+
emit('update:modelValue', true)
|
|
252
|
+
emit('open')
|
|
253
|
+
|
|
254
|
+
// Wait for DOM update, then trigger expansion
|
|
255
|
+
nextTick(() => {
|
|
256
|
+
// Force a reflow to ensure the collapsed position is applied first
|
|
257
|
+
contentRef.value?.offsetHeight
|
|
258
|
+
|
|
259
|
+
requestAnimationFrame(() => {
|
|
260
|
+
requestAnimationFrame(() => {
|
|
261
|
+
isExpanded.value = true
|
|
262
|
+
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
emit('opened')
|
|
265
|
+
contentRef.value?.focus()
|
|
266
|
+
}, props.duration)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const close = () => {
|
|
273
|
+
if (!isVisible.value || isClosing.value) return
|
|
274
|
+
|
|
275
|
+
// Capture current expanded dimensions before closing
|
|
276
|
+
// This is crucial for smooth transitions since 'height: auto' can't be animated
|
|
277
|
+
if (contentRef.value) {
|
|
278
|
+
const rect = contentRef.value.getBoundingClientRect()
|
|
279
|
+
expandedRect.value = {
|
|
280
|
+
width: rect.width,
|
|
281
|
+
height: rect.height,
|
|
282
|
+
top: rect.top,
|
|
283
|
+
left: rect.left
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
updateTriggerRect()
|
|
288
|
+
isClosing.value = true
|
|
289
|
+
|
|
290
|
+
emit('close')
|
|
291
|
+
|
|
292
|
+
// Use nextTick + requestAnimationFrame like opening for consistency
|
|
293
|
+
nextTick(() => {
|
|
294
|
+
// Force reflow to ensure the captured dimensions are applied
|
|
295
|
+
contentRef.value?.offsetHeight
|
|
296
|
+
|
|
297
|
+
requestAnimationFrame(() => {
|
|
298
|
+
requestAnimationFrame(() => {
|
|
299
|
+
isExpanded.value = false
|
|
300
|
+
|
|
301
|
+
// Wait for the morph + fade transition to complete using close duration
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
isVisible.value = false
|
|
304
|
+
isOpen.value = false
|
|
305
|
+
isClosing.value = false
|
|
306
|
+
expandedRect.value = null
|
|
307
|
+
unlockBodyScroll()
|
|
308
|
+
emit('update:modelValue', false)
|
|
309
|
+
emit('closed')
|
|
310
|
+
|
|
311
|
+
if (previousActiveElement.value) {
|
|
312
|
+
previousActiveElement.value.focus()
|
|
313
|
+
}
|
|
314
|
+
}, actualCloseDuration.value)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const handleBackdropClick = (event: MouseEvent) => {
|
|
321
|
+
if (props.closeOnBackdrop && event.target === event.currentTarget) {
|
|
322
|
+
close()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
327
|
+
if (event.key === 'Escape' && props.closeOnEscape && isVisible.value && !isClosing.value) {
|
|
328
|
+
event.preventDefault()
|
|
329
|
+
event.stopPropagation()
|
|
330
|
+
close()
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Watch modelValue
|
|
335
|
+
watch(() => props.modelValue, (value) => {
|
|
336
|
+
if (value && !isVisible.value) {
|
|
337
|
+
open()
|
|
338
|
+
} else if (!value && isVisible.value && !isClosing.value) {
|
|
339
|
+
close()
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// Lifecycle
|
|
344
|
+
onMounted(() => {
|
|
345
|
+
document.addEventListener('keydown', handleKeydown)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
onBeforeUnmount(() => {
|
|
349
|
+
document.removeEventListener('keydown', handleKeydown)
|
|
350
|
+
unlockBodyScroll()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Provide context
|
|
354
|
+
provide(SMorphingModalContextKey, {
|
|
355
|
+
isOpen,
|
|
356
|
+
isExpanded,
|
|
357
|
+
triggerRect,
|
|
358
|
+
open,
|
|
359
|
+
close,
|
|
360
|
+
registerTrigger,
|
|
361
|
+
uniqueId
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
// Expose for external control
|
|
365
|
+
defineExpose({
|
|
366
|
+
open,
|
|
367
|
+
close,
|
|
368
|
+
isOpen
|
|
369
|
+
})
|
|
370
|
+
</script>
|
|
371
|
+
|
|
372
|
+
<template>
|
|
373
|
+
<div
|
|
374
|
+
v-bind="$attrs"
|
|
375
|
+
:class="cn('s-morphing-modal-trigger-wrapper', $attrs.class ?? '')"
|
|
376
|
+
:style="triggerWrapperStyle"
|
|
377
|
+
:aria-hidden="isOpen"
|
|
378
|
+
>
|
|
379
|
+
<slot name="trigger" />
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<Teleport to="body">
|
|
383
|
+
<!-- Backdrop -->
|
|
384
|
+
<div
|
|
385
|
+
v-if="isVisible"
|
|
386
|
+
class="s-morphing-backdrop fixed inset-0 bg-black/60 dark:bg-black/70"
|
|
387
|
+
:class="[
|
|
388
|
+
backdropBlur ? 'backdrop-blur-md' : '',
|
|
389
|
+
isExpanded ? 'opacity-100' : 'opacity-0'
|
|
390
|
+
]"
|
|
391
|
+
:style="{
|
|
392
|
+
zIndex,
|
|
393
|
+
transitionProperty: 'opacity',
|
|
394
|
+
transitionDuration: `${isClosing ? actualCloseDuration : duration}ms`,
|
|
395
|
+
transitionTimingFunction: isClosing ? closeEasing : easing
|
|
396
|
+
}"
|
|
397
|
+
aria-hidden="true"
|
|
398
|
+
@click="handleBackdropClick"
|
|
399
|
+
/>
|
|
400
|
+
|
|
401
|
+
<!-- Modal Content -->
|
|
402
|
+
<div
|
|
403
|
+
v-if="isVisible"
|
|
404
|
+
ref="contentRef"
|
|
405
|
+
class="s-morphing-modal-content bg-background shadow-2xl overflow-hidden outline-none border border-border"
|
|
406
|
+
:style="{ ...contentStyle, zIndex: zIndex + 1 }"
|
|
407
|
+
role="dialog"
|
|
408
|
+
aria-modal="true"
|
|
409
|
+
tabindex="-1"
|
|
410
|
+
@click.stop
|
|
411
|
+
>
|
|
412
|
+
<slot name="content" />
|
|
413
|
+
</div>
|
|
414
|
+
</Teleport>
|
|
415
|
+
</template>
|
|
416
|
+
|
|
417
|
+
<style scoped>
|
|
418
|
+
.s-morphing-modal-content {
|
|
419
|
+
will-change: top, left, width, height, border-radius, opacity, transform;
|
|
420
|
+
backface-visibility: hidden;
|
|
421
|
+
-webkit-backface-visibility: hidden;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.s-morphing-backdrop {
|
|
425
|
+
will-change: opacity;
|
|
426
|
+
backface-visibility: hidden;
|
|
427
|
+
-webkit-backface-visibility: hidden;
|
|
428
|
+
}
|
|
429
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModalClose - Close button for morphing modal
|
|
4
|
+
*/
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
|
|
10
|
+
import { inject } from 'vue'
|
|
11
|
+
import { cn } from '../../../lib/utils'
|
|
12
|
+
import { SMorphingModalContextKey } from './SMorphingModal.vue'
|
|
13
|
+
|
|
14
|
+
export interface Props {
|
|
15
|
+
/** Additional class */
|
|
16
|
+
closeClass?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
20
|
+
closeClass: ''
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const context = inject(SMorphingModalContextKey)
|
|
24
|
+
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
context?.close()
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
v-bind="$attrs"
|
|
34
|
+
:class="cn('s-morphing-modal-close flex items-center justify-center w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent transition-all duration-150 outline-none focus:ring-2 focus:ring-primary/30', closeClass, $attrs.class ?? '')"
|
|
35
|
+
aria-label="Close dialog"
|
|
36
|
+
@click="handleClose"
|
|
37
|
+
>
|
|
38
|
+
<slot>
|
|
39
|
+
<span class="mdi mdi-close text-xl" />
|
|
40
|
+
</slot>
|
|
41
|
+
</button>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModalDescription - Description content for morphing modal
|
|
4
|
+
* Only visible when the modal is expanded
|
|
5
|
+
*/
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
defineOptions({ inheritAttrs: false })
|
|
10
|
+
|
|
11
|
+
import { inject, computed } from 'vue'
|
|
12
|
+
import { cn } from '../../../lib/utils'
|
|
13
|
+
import { SMorphingModalContextKey } from './SMorphingModal.vue'
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** Additional class */
|
|
17
|
+
descriptionClass?: string
|
|
18
|
+
/** Disable layout animation */
|
|
19
|
+
disableLayoutAnimation?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
descriptionClass: '',
|
|
24
|
+
disableLayoutAnimation: false
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const context = inject(SMorphingModalContextKey)
|
|
28
|
+
|
|
29
|
+
const isVisible = computed(() => context?.isExpanded.value ?? false)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<Transition
|
|
34
|
+
enter-active-class="transition-all duration-300 ease-out delay-150"
|
|
35
|
+
enter-from-class="opacity-0 translate-y-2"
|
|
36
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
37
|
+
leave-active-class="transition-all duration-150 ease-in"
|
|
38
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
39
|
+
leave-to-class="opacity-0 translate-y-2"
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
v-if="isVisible"
|
|
43
|
+
v-bind="$attrs"
|
|
44
|
+
:class="cn('s-morphing-modal-description text-muted-foreground', descriptionClass, $attrs.class ?? '')"
|
|
45
|
+
>
|
|
46
|
+
<slot />
|
|
47
|
+
</div>
|
|
48
|
+
</Transition>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModalImage - Image element for morphing modal
|
|
4
|
+
* Maintains aspect ratio during transition
|
|
5
|
+
*/
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
defineOptions({ inheritAttrs: false })
|
|
10
|
+
|
|
11
|
+
import { cn } from '../../../lib/utils'
|
|
12
|
+
|
|
13
|
+
export interface Props {
|
|
14
|
+
/** Image source URL */
|
|
15
|
+
src: string
|
|
16
|
+
/** Alt text for accessibility */
|
|
17
|
+
alt?: string
|
|
18
|
+
/** Additional class */
|
|
19
|
+
imageClass?: string
|
|
20
|
+
/** Object fit style */
|
|
21
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'none'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
25
|
+
alt: '',
|
|
26
|
+
imageClass: '',
|
|
27
|
+
objectFit: 'cover'
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<img
|
|
33
|
+
v-bind="$attrs"
|
|
34
|
+
:src="src"
|
|
35
|
+
:alt="alt"
|
|
36
|
+
:class="cn('s-morphing-modal-image w-full', imageClass, `object-${objectFit}`, $attrs.class ?? '')"
|
|
37
|
+
/>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<style scoped>
|
|
41
|
+
.s-morphing-modal-image {
|
|
42
|
+
transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModalSubtitle - Subtitle element for morphing modal
|
|
4
|
+
*/
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../../lib/utils'
|
|
11
|
+
|
|
12
|
+
export interface Props {
|
|
13
|
+
/** Additional class */
|
|
14
|
+
subtitleClass?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
subtitleClass: ''
|
|
19
|
+
})
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<p
|
|
24
|
+
v-bind="$attrs"
|
|
25
|
+
:class="cn('s-morphing-modal-subtitle text-sm text-muted-foreground', subtitleClass, $attrs.class ?? '')"
|
|
26
|
+
>
|
|
27
|
+
<slot />
|
|
28
|
+
</p>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SMorphingModalTitle - Title element for morphing modal
|
|
4
|
+
*/
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
|
|
10
|
+
import { inject } from 'vue'
|
|
11
|
+
import { cn } from '../../../lib/utils'
|
|
12
|
+
import { SMorphingModalContextKey } from './SMorphingModal.vue'
|
|
13
|
+
|
|
14
|
+
export interface Props {
|
|
15
|
+
/** Additional class */
|
|
16
|
+
titleClass?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
20
|
+
titleClass: ''
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const context = inject(SMorphingModalContextKey)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<h2
|
|
28
|
+
v-bind="$attrs"
|
|
29
|
+
:id="`${context?.uniqueId}-title`"
|
|
30
|
+
:class="cn('s-morphing-modal-title text-lg font-semibold text-foreground', titleClass, $attrs.class ?? '')"
|
|
31
|
+
>
|
|
32
|
+
<slot />
|
|
33
|
+
</h2>
|
|
34
|
+
</template>
|