@birdycom/omniaui-vue 0.0.0-stage → 0.11.1
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/LICENSE +21 -0
- package/README.md +49 -3
- package/dist/blocks/OcBlockAboutTimeline.vue.d.ts +29 -0
- package/dist/blocks/OcBlockAppHeader.vue.d.ts +33 -0
- package/dist/blocks/OcBlockAppShell.vue.d.ts +33 -0
- package/dist/blocks/OcBlockAuth.vue.d.ts +28 -0
- package/dist/blocks/OcBlockAuthSplit.vue.d.ts +45 -0
- package/dist/blocks/OcBlockContactGrid.vue.d.ts +32 -0
- package/dist/blocks/OcBlockCta.vue.d.ts +17 -0
- package/dist/blocks/OcBlockDataTable.vue.d.ts +33 -0
- package/dist/blocks/OcBlockFaq.vue.d.ts +32 -0
- package/dist/blocks/OcBlockFeatures.vue.d.ts +25 -0
- package/dist/blocks/OcBlockFooter.vue.d.ts +27 -0
- package/dist/blocks/OcBlockFormPage.vue.d.ts +36 -0
- package/dist/blocks/OcBlockHero.vue.d.ts +18 -0
- package/dist/blocks/OcBlockHeroCode.vue.d.ts +32 -0
- package/dist/blocks/OcBlockHeroOrbit.vue.d.ts +36 -0
- package/dist/blocks/OcBlockHeroSplit.vue.d.ts +19 -0
- package/dist/blocks/OcBlockModuleHeader.vue.d.ts +24 -0
- package/dist/blocks/OcBlockPageHero.vue.d.ts +18 -0
- package/dist/blocks/OcBlockPortfolioGrid.vue.d.ts +34 -0
- package/dist/blocks/OcBlockPricing.vue.d.ts +38 -0
- package/dist/blocks/OcBlockProcess.vue.d.ts +18 -0
- package/dist/blocks/OcBlockProductGrid.vue.d.ts +25 -0
- package/dist/blocks/OcBlockProfileHeader.vue.d.ts +36 -0
- package/dist/blocks/OcBlockShowcase.vue.d.ts +23 -0
- package/dist/blocks/OcBlockSiteFooter.vue.d.ts +42 -0
- package/dist/blocks/OcBlockSiteHeader.vue.d.ts +32 -0
- package/dist/blocks/OcBlockSpotlight.vue.d.ts +32 -0
- package/dist/blocks/OcBlockStats.vue.d.ts +10 -0
- package/dist/blocks/OcBlockStatsCounter.vue.d.ts +10 -0
- package/dist/blocks/OcBlockTeamList.vue.d.ts +42 -0
- package/dist/blocks/OcBlockTwoFactor.vue.d.ts +38 -0
- package/dist/blocks/OcBlockWizardDialog.vue.d.ts +52 -0
- package/dist/components/OcAccordion.vue.d.ts +15 -0
- package/dist/components/OcAccordionAccent.vue.d.ts +20 -0
- package/dist/components/OcAccordionItem.vue.d.ts +24 -0
- package/dist/components/OcAffixInput.vue.d.ts +31 -0
- package/dist/components/OcAlert.vue.d.ts +21 -0
- package/dist/components/OcAlertDialog.vue.d.ts +48 -0
- package/dist/components/OcAlphaTabs.vue.d.ts +23 -0
- package/dist/components/OcAspectRatio.vue.d.ts +16 -0
- package/dist/components/OcAttachment.vue.d.ts +26 -0
- package/dist/components/OcAttachmentImage.vue.d.ts +10 -0
- package/dist/components/OcAttachmentList.vue.d.ts +31 -0
- package/dist/components/OcAttachmentThumb.vue.d.ts +16 -0
- package/dist/components/OcAvatar.vue.d.ts +21 -0
- package/dist/components/OcAvatarGroup.vue.d.ts +12 -0
- package/dist/components/OcBackLink.vue.d.ts +18 -0
- package/dist/components/OcBadge.vue.d.ts +18 -0
- package/dist/components/OcBarcodeInput.vue.d.ts +27 -0
- package/dist/components/OcBreadcrumb.vue.d.ts +24 -0
- package/dist/components/OcBrowserFrame.vue.d.ts +35 -0
- package/dist/components/OcBusyOverlay.vue.d.ts +30 -0
- package/dist/components/OcButton.vue.d.ts +31 -0
- package/dist/components/OcButtonGroup.vue.d.ts +17 -0
- package/dist/components/OcButtonGroupText.vue.d.ts +12 -0
- package/dist/components/OcCard.vue.d.ts +27 -0
- package/dist/components/OcChartLine.vue.d.ts +28 -0
- package/dist/components/OcChatComposer.vue.d.ts +33 -0
- package/dist/components/OcCheckbox.vue.d.ts +26 -0
- package/dist/components/OcChecklist.vue.d.ts +32 -0
- package/dist/components/OcChipFilter.vue.d.ts +24 -0
- package/dist/components/OcCollapsible.vue.d.ts +28 -0
- package/dist/components/OcColorField.vue.d.ts +17 -0
- package/dist/components/OcColorSelect.vue.d.ts +24 -0
- package/dist/components/OcColumnMenu.vue.d.ts +31 -0
- package/dist/components/OcCommand.vue.d.ts +28 -0
- package/dist/components/OcConfirmDialog.vue.d.ts +44 -0
- package/dist/components/OcCookieConsent.vue.d.ts +52 -0
- package/dist/components/OcCopyField.vue.d.ts +14 -0
- package/dist/components/OcCounter.vue.d.ts +9 -0
- package/dist/components/OcDataTable.vue.d.ts +37 -0
- package/dist/components/OcDecimalInput.vue.d.ts +19 -0
- package/dist/components/OcDefinitionList.vue.d.ts +34 -0
- package/dist/components/OcDocViewer.vue.d.ts +24 -0
- package/dist/components/OcDropdown.vue.d.ts +19 -0
- package/dist/components/OcDropdownItem.vue.d.ts +18 -0
- package/dist/components/OcDropdownLabel.vue.d.ts +12 -0
- package/dist/components/OcDropdownSeparator.vue.d.ts +2 -0
- package/dist/components/OcDropzone.vue.d.ts +29 -0
- package/dist/components/OcEmojiPicker.vue.d.ts +32 -0
- package/dist/components/OcEmptyState.vue.d.ts +24 -0
- package/dist/components/OcEntityCard.vue.d.ts +37 -0
- package/dist/components/OcFactList.vue.d.ts +29 -0
- package/dist/components/OcFilterCounts.vue.d.ts +25 -0
- package/dist/components/OcFlipCard.vue.d.ts +31 -0
- package/dist/components/OcFormField.vue.d.ts +26 -0
- package/dist/components/OcGreeting.vue.d.ts +26 -0
- package/dist/components/OcHeading.vue.d.ts +24 -0
- package/dist/components/OcHoverCard.vue.d.ts +22 -0
- package/dist/components/OcImageZoom.vue.d.ts +28 -0
- package/dist/components/OcInput.vue.d.ts +13 -0
- package/dist/components/OcInputGroup.vue.d.ts +21 -0
- package/dist/components/OcItem.vue.d.ts +39 -0
- package/dist/components/OcItemGroup.vue.d.ts +12 -0
- package/dist/components/OcItemSeparator.vue.d.ts +2 -0
- package/dist/components/OcKbd.vue.d.ts +12 -0
- package/dist/components/OcKbdGroup.vue.d.ts +17 -0
- package/dist/components/OcLabel.vue.d.ts +19 -0
- package/dist/components/OcLanguageSelect.vue.d.ts +32 -0
- package/dist/components/OcLauncherCard.vue.d.ts +26 -0
- package/dist/components/OcLightbox.vue.d.ts +45 -0
- package/dist/components/OcLinkArrow.vue.d.ts +23 -0
- package/dist/components/OcListToolbar.vue.d.ts +44 -0
- package/dist/components/OcMarquee.vue.d.ts +16 -0
- package/dist/components/OcMediaCard.vue.d.ts +24 -0
- package/dist/components/OcMediaRow.vue.d.ts +21 -0
- package/dist/components/OcMediaThumb.vue.d.ts +32 -0
- package/dist/components/OcMessage.vue.d.ts +63 -0
- package/dist/components/OcMessageList.vue.d.ts +35 -0
- package/dist/components/OcMessageThread.vue.d.ts +80 -0
- package/dist/components/OcModal.vue.d.ts +36 -0
- package/dist/components/OcNavmenu.vue.d.ts +24 -0
- package/dist/components/OcNavmenuList.vue.d.ts +29 -0
- package/dist/components/OcNextTeaser.vue.d.ts +29 -0
- package/dist/components/OcNumberInput.vue.d.ts +25 -0
- package/dist/components/OcOtpInput.vue.d.ts +19 -0
- package/dist/components/OcPageHeader.vue.d.ts +36 -0
- package/dist/components/OcPagination.vue.d.ts +20 -0
- package/dist/components/OcPasswordInput.vue.d.ts +18 -0
- package/dist/components/OcPasswordRequirements.vue.d.ts +19 -0
- package/dist/components/OcPhoneInput.vue.d.ts +25 -0
- package/dist/components/OcPopover.vue.d.ts +24 -0
- package/dist/components/OcPriceCard.vue.d.ts +41 -0
- package/dist/components/OcProductMark.vue.d.ts +25 -0
- package/dist/components/OcProgress.vue.d.ts +11 -0
- package/dist/components/OcProjectCard.vue.d.ts +35 -0
- package/dist/components/OcProse.vue.d.ts +25 -0
- package/dist/components/OcRadio.vue.d.ts +27 -0
- package/dist/components/OcRadioGroup.vue.d.ts +32 -0
- package/dist/components/OcRecoveryCodes.vue.d.ts +36 -0
- package/dist/components/OcScrollArea.vue.d.ts +18 -0
- package/dist/components/OcSegmented.vue.d.ts +21 -0
- package/dist/components/OcSelect.vue.d.ts +28 -0
- package/dist/components/OcSeparator.vue.d.ts +21 -0
- package/dist/components/OcSeverityCard.vue.d.ts +36 -0
- package/dist/components/OcSheet.vue.d.ts +37 -0
- package/dist/components/OcSidebar.vue.d.ts +28 -0
- package/dist/components/OcSidebarCollapsible.vue.d.ts +27 -0
- package/dist/components/OcSidebarGroup.vue.d.ts +16 -0
- package/dist/components/OcSidebarMenu.vue.d.ts +12 -0
- package/dist/components/OcSidebarMenuButton.vue.d.ts +21 -0
- package/dist/components/OcSidebarSeparator.vue.d.ts +2 -0
- package/dist/components/OcSkeleton.vue.d.ts +8 -0
- package/dist/components/OcSlider.vue.d.ts +18 -0
- package/dist/components/OcSortableRow.vue.d.ts +32 -0
- package/dist/components/OcSpinner.vue.d.ts +8 -0
- package/dist/components/OcStackCard.vue.d.ts +22 -0
- package/dist/components/OcStackList.vue.d.ts +28 -0
- package/dist/components/OcStatCard.vue.d.ts +25 -0
- package/dist/components/OcStepper.vue.d.ts +24 -0
- package/dist/components/OcSwitch.vue.d.ts +9 -0
- package/dist/components/OcTable.vue.d.ts +61 -0
- package/dist/components/OcTableActions.vue.d.ts +18 -0
- package/dist/components/OcTableCompact.vue.d.ts +37 -0
- package/dist/components/OcTabs.vue.d.ts +28 -0
- package/dist/components/OcTagInput.vue.d.ts +18 -0
- package/dist/components/OcTerminalLog.vue.d.ts +20 -0
- package/dist/components/OcTextarea.vue.d.ts +13 -0
- package/dist/components/OcThemeSwitch.vue.d.ts +20 -0
- package/dist/components/OcTimeInput.vue.d.ts +14 -0
- package/dist/components/OcTimeline.vue.d.ts +33 -0
- package/dist/components/OcToast.vue.d.ts +31 -0
- package/dist/components/OcToastContainer.vue.d.ts +8 -0
- package/dist/components/OcToggle.vue.d.ts +29 -0
- package/dist/components/OcToggleField.vue.d.ts +27 -0
- package/dist/components/OcToggleGroup.vue.d.ts +27 -0
- package/dist/components/OcTooltip.vue.d.ts +16 -0
- package/dist/components/OcTopProgress.vue.d.ts +18 -0
- package/dist/components/OcTree.vue.d.ts +40 -0
- package/dist/components/OcUpgradeBanner.vue.d.ts +41 -0
- package/dist/components/OcUser.vue.d.ts +20 -0
- package/dist/components/OcVersionBanner.vue.d.ts +32 -0
- package/dist/composables/chartLine.d.ts +6 -0
- package/dist/composables/keys.d.ts +19 -0
- package/dist/composables/useAttrsSplit.d.ts +14 -0
- package/dist/composables/useDialog.d.ts +9 -0
- package/dist/composables/useToast.d.ts +34 -0
- package/dist/composables/useUid.d.ts +5 -0
- package/dist/index.d.ts +233 -0
- package/dist/omniaui-vue.js +8811 -0
- package/dist/omniaui-vue.js.map +1 -0
- package/manifest.json +1036 -0
- package/package.json +69 -6
- package/src/blocks/OcBlockAboutTimeline.vue +44 -0
- package/src/blocks/OcBlockAppHeader.vue +36 -0
- package/src/blocks/OcBlockAppShell.vue +29 -0
- package/src/blocks/OcBlockAuth.vue +24 -0
- package/src/blocks/OcBlockAuthSplit.vue +47 -0
- package/src/blocks/OcBlockContactGrid.vue +53 -0
- package/src/blocks/OcBlockCta.vue +12 -0
- package/src/blocks/OcBlockDataTable.vue +27 -0
- package/src/blocks/OcBlockFaq.vue +49 -0
- package/src/blocks/OcBlockFeatures.vue +23 -0
- package/src/blocks/OcBlockFooter.vue +25 -0
- package/src/blocks/OcBlockFormPage.vue +24 -0
- package/src/blocks/OcBlockHero.vue +13 -0
- package/src/blocks/OcBlockHeroCode.vue +42 -0
- package/src/blocks/OcBlockHeroOrbit.vue +69 -0
- package/src/blocks/OcBlockHeroSplit.vue +15 -0
- package/src/blocks/OcBlockModuleHeader.vue +21 -0
- package/src/blocks/OcBlockPageHero.vue +13 -0
- package/src/blocks/OcBlockPortfolioGrid.vue +63 -0
- package/src/blocks/OcBlockPricing.vue +39 -0
- package/src/blocks/OcBlockProcess.vue +39 -0
- package/src/blocks/OcBlockProductGrid.vue +41 -0
- package/src/blocks/OcBlockProfileHeader.vue +50 -0
- package/src/blocks/OcBlockShowcase.vue +43 -0
- package/src/blocks/OcBlockSiteFooter.vue +54 -0
- package/src/blocks/OcBlockSiteHeader.vue +34 -0
- package/src/blocks/OcBlockSpotlight.vue +47 -0
- package/src/blocks/OcBlockStats.vue +17 -0
- package/src/blocks/OcBlockStatsCounter.vue +20 -0
- package/src/blocks/OcBlockTeamList.vue +53 -0
- package/src/blocks/OcBlockTwoFactor.vue +61 -0
- package/src/blocks/OcBlockWizardDialog.vue +67 -0
- package/src/components/OcAccordion.vue +12 -0
- package/src/components/OcAccordionAccent.vue +27 -0
- package/src/components/OcAccordionItem.vue +19 -0
- package/src/components/OcAffixInput.vue +40 -0
- package/src/components/OcAlert.vue +17 -0
- package/src/components/OcAlertDialog.vue +66 -0
- package/src/components/OcAlphaTabs.vue +54 -0
- package/src/components/OcAspectRatio.vue +21 -0
- package/src/components/OcAttachment.vue +59 -0
- package/src/components/OcAttachmentImage.vue +13 -0
- package/src/components/OcAttachmentList.vue +31 -0
- package/src/components/OcAttachmentThumb.vue +14 -0
- package/src/components/OcAvatar.vue +18 -0
- package/src/components/OcAvatarGroup.vue +7 -0
- package/src/components/OcBackLink.vue +8 -0
- package/src/components/OcBadge.vue +13 -0
- package/src/components/OcBarcodeInput.vue +51 -0
- package/src/components/OcBreadcrumb.vue +22 -0
- package/src/components/OcBrowserFrame.vue +33 -0
- package/src/components/OcBusyOverlay.vue +32 -0
- package/src/components/OcButton.vue +47 -0
- package/src/components/OcButtonGroup.vue +10 -0
- package/src/components/OcButtonGroupText.vue +7 -0
- package/src/components/OcCard.vue +20 -0
- package/src/components/OcChartLine.vue +80 -0
- package/src/components/OcChatComposer.vue +42 -0
- package/src/components/OcCheckbox.vue +26 -0
- package/src/components/OcChecklist.vue +29 -0
- package/src/components/OcChipFilter.vue +48 -0
- package/src/components/OcCollapsible.vue +19 -0
- package/src/components/OcColorField.vue +15 -0
- package/src/components/OcColorSelect.vue +33 -0
- package/src/components/OcColumnMenu.vue +50 -0
- package/src/components/OcCommand.vue +119 -0
- package/src/components/OcConfirmDialog.vue +65 -0
- package/src/components/OcCookieConsent.vue +119 -0
- package/src/components/OcCopyField.vue +37 -0
- package/src/components/OcCounter.vue +15 -0
- package/src/components/OcDataTable.vue +144 -0
- package/src/components/OcDecimalInput.vue +53 -0
- package/src/components/OcDefinitionList.vue +35 -0
- package/src/components/OcDocViewer.vue +79 -0
- package/src/components/OcDropdown.vue +23 -0
- package/src/components/OcDropdownItem.vue +9 -0
- package/src/components/OcDropdownLabel.vue +7 -0
- package/src/components/OcDropdownSeparator.vue +7 -0
- package/src/components/OcDropzone.vue +36 -0
- package/src/components/OcEmojiPicker.vue +45 -0
- package/src/components/OcEmptyState.vue +13 -0
- package/src/components/OcEntityCard.vue +47 -0
- package/src/components/OcFactList.vue +37 -0
- package/src/components/OcFilterCounts.vue +38 -0
- package/src/components/OcFlipCard.vue +36 -0
- package/src/components/OcFormField.vue +36 -0
- package/src/components/OcGreeting.vue +52 -0
- package/src/components/OcHeading.vue +19 -0
- package/src/components/OcHoverCard.vue +25 -0
- package/src/components/OcImageZoom.vue +39 -0
- package/src/components/OcInput.vue +9 -0
- package/src/components/OcInputGroup.vue +12 -0
- package/src/components/OcItem.vue +40 -0
- package/src/components/OcItemGroup.vue +7 -0
- package/src/components/OcItemSeparator.vue +7 -0
- package/src/components/OcKbd.vue +7 -0
- package/src/components/OcKbdGroup.vue +15 -0
- package/src/components/OcLabel.vue +10 -0
- package/src/components/OcLanguageSelect.vue +40 -0
- package/src/components/OcLauncherCard.vue +34 -0
- package/src/components/OcLightbox.vue +63 -0
- package/src/components/OcLinkArrow.vue +25 -0
- package/src/components/OcListToolbar.vue +44 -0
- package/src/components/OcMarquee.vue +36 -0
- package/src/components/OcMediaCard.vue +28 -0
- package/src/components/OcMediaRow.vue +28 -0
- package/src/components/OcMediaThumb.vue +38 -0
- package/src/components/OcMessage.vue +91 -0
- package/src/components/OcMessageList.vue +43 -0
- package/src/components/OcMessageThread.vue +155 -0
- package/src/components/OcModal.vue +42 -0
- package/src/components/OcNavmenu.vue +18 -0
- package/src/components/OcNavmenuList.vue +38 -0
- package/src/components/OcNextTeaser.vue +25 -0
- package/src/components/OcNumberInput.vue +47 -0
- package/src/components/OcOtpInput.vue +34 -0
- package/src/components/OcPageHeader.vue +34 -0
- package/src/components/OcPagination.vue +33 -0
- package/src/components/OcPasswordInput.vue +27 -0
- package/src/components/OcPasswordRequirements.vue +28 -0
- package/src/components/OcPhoneInput.vue +72 -0
- package/src/components/OcPopover.vue +25 -0
- package/src/components/OcPriceCard.vue +69 -0
- package/src/components/OcProductMark.vue +31 -0
- package/src/components/OcProgress.vue +21 -0
- package/src/components/OcProjectCard.vue +36 -0
- package/src/components/OcProse.vue +57 -0
- package/src/components/OcRadio.vue +40 -0
- package/src/components/OcRadioGroup.vue +32 -0
- package/src/components/OcRecoveryCodes.vue +55 -0
- package/src/components/OcScrollArea.vue +15 -0
- package/src/components/OcSegmented.vue +28 -0
- package/src/components/OcSelect.vue +21 -0
- package/src/components/OcSeparator.vue +18 -0
- package/src/components/OcSeverityCard.vue +46 -0
- package/src/components/OcSheet.vue +49 -0
- package/src/components/OcSidebar.vue +16 -0
- package/src/components/OcSidebarCollapsible.vue +19 -0
- package/src/components/OcSidebarGroup.vue +11 -0
- package/src/components/OcSidebarMenu.vue +7 -0
- package/src/components/OcSidebarMenuButton.vue +31 -0
- package/src/components/OcSidebarSeparator.vue +7 -0
- package/src/components/OcSkeleton.vue +8 -0
- package/src/components/OcSlider.vue +27 -0
- package/src/components/OcSortableRow.vue +40 -0
- package/src/components/OcSpinner.vue +13 -0
- package/src/components/OcStackCard.vue +24 -0
- package/src/components/OcStackList.vue +31 -0
- package/src/components/OcStatCard.vue +39 -0
- package/src/components/OcStepper.vue +34 -0
- package/src/components/OcSwitch.vue +15 -0
- package/src/components/OcTable.vue +115 -0
- package/src/components/OcTableActions.vue +26 -0
- package/src/components/OcTableCompact.vue +46 -0
- package/src/components/OcTabs.vue +37 -0
- package/src/components/OcTagInput.vue +50 -0
- package/src/components/OcTerminalLog.vue +50 -0
- package/src/components/OcTextarea.vue +9 -0
- package/src/components/OcThemeSwitch.vue +31 -0
- package/src/components/OcTimeInput.vue +50 -0
- package/src/components/OcTimeline.vue +36 -0
- package/src/components/OcToast.vue +31 -0
- package/src/components/OcToastContainer.vue +21 -0
- package/src/components/OcToggle.vue +63 -0
- package/src/components/OcToggleField.vue +28 -0
- package/src/components/OcToggleGroup.vue +40 -0
- package/src/components/OcTooltip.vue +8 -0
- package/src/components/OcTopProgress.vue +54 -0
- package/src/components/OcTree.vue +70 -0
- package/src/components/OcUpgradeBanner.vue +42 -0
- package/src/components/OcUser.vue +25 -0
- package/src/components/OcVersionBanner.vue +34 -0
- package/src/composables/chartLine.ts +28 -0
- package/src/composables/keys.ts +22 -0
- package/src/composables/useAttrsSplit.ts +16 -0
- package/src/composables/useDialog.ts +21 -0
- package/src/composables/useToast.ts +66 -0
- package/src/composables/useUid.ts +12 -0
- package/src/index.ts +589 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcCookieConsent.vue | Map: props -> storage helpers -> reopen button -> popover (title, text, categories, actions) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Cookie banner on popover="manual" (same markup as snippets/cookie-consent.html). Like the snippet script it shows itself
|
|
5
|
+
* when no choice is stored, saves { necessary, <categories>, date } in localStorage and fires "oc-consent" on document.
|
|
6
|
+
* Emits: consent (the choice, also on load when one is stored). Slot: text (replaces the paragraph content).
|
|
7
|
+
* Classes: .oc-cookie-consent(--bar) __title __text __prefs __category __category-name __category-text __actions
|
|
8
|
+
*/
|
|
9
|
+
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|
10
|
+
|
|
11
|
+
export interface CookieCategory {
|
|
12
|
+
name: string
|
|
13
|
+
label: string
|
|
14
|
+
text?: string
|
|
15
|
+
required?: boolean
|
|
16
|
+
}
|
|
17
|
+
export type ConsentChoice = { necessary: boolean; date: string } & Record<string, boolean | string>
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
categories?: CookieCategory[]
|
|
22
|
+
title?: string
|
|
23
|
+
policyHref?: string
|
|
24
|
+
customizeLabel?: string
|
|
25
|
+
necessaryLabel?: string
|
|
26
|
+
selectedLabel?: string
|
|
27
|
+
allLabel?: string
|
|
28
|
+
/** Text of the "Cookie settings" button; empty string hides it. */
|
|
29
|
+
openLabel?: string
|
|
30
|
+
bar?: boolean
|
|
31
|
+
manual?: boolean
|
|
32
|
+
storageKey?: string
|
|
33
|
+
id?: string
|
|
34
|
+
}>(),
|
|
35
|
+
{
|
|
36
|
+
categories: () => [
|
|
37
|
+
{ name: 'necessary', label: 'Necessary', text: 'Session, security and your cookie choice. Always on.', required: true },
|
|
38
|
+
{ name: 'statistics', label: 'Statistics', text: 'Anonymous visit counts.' },
|
|
39
|
+
{ name: 'marketing', label: 'Marketing', text: 'Embedded videos and ads that track you.' },
|
|
40
|
+
],
|
|
41
|
+
title: 'We use cookies',
|
|
42
|
+
policyHref: '#cookie-policy',
|
|
43
|
+
customizeLabel: 'Customize',
|
|
44
|
+
necessaryLabel: 'Only necessary',
|
|
45
|
+
selectedLabel: 'Save choice',
|
|
46
|
+
allLabel: 'Accept all',
|
|
47
|
+
openLabel: 'Cookie settings',
|
|
48
|
+
storageKey: 'oc-consent',
|
|
49
|
+
id: 'oc-cookie-consent',
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
const emit = defineEmits<{ consent: [choice: ConsentChoice] }>()
|
|
53
|
+
defineOptions({ inheritAttrs: false })
|
|
54
|
+
const box = ref<HTMLElement | null>(null)
|
|
55
|
+
|
|
56
|
+
const read = (): ConsentChoice | null => {
|
|
57
|
+
try { return JSON.parse(localStorage.getItem(props.storageKey) ?? 'null') } catch { return null }
|
|
58
|
+
}
|
|
59
|
+
const inputs = () => Array.from(box.value?.querySelectorAll<HTMLInputElement>('input[name]') ?? [])
|
|
60
|
+
function tell(choice: ConsentChoice) {
|
|
61
|
+
document.dispatchEvent(new CustomEvent('oc-consent', { detail: choice }))
|
|
62
|
+
emit('consent', choice)
|
|
63
|
+
}
|
|
64
|
+
// Any [data-oc-consent-open] (also outside this component) opens the banner with the stored choice.
|
|
65
|
+
function reopen(e: Event) {
|
|
66
|
+
if (!(e.target as Element | null)?.closest?.('[data-oc-consent-open]')) return
|
|
67
|
+
const choice = read()
|
|
68
|
+
for (const i of inputs()) if (!i.disabled) i.checked = !!choice?.[i.name]
|
|
69
|
+
box.value?.showPopover?.()
|
|
70
|
+
}
|
|
71
|
+
function choose(e: MouseEvent) {
|
|
72
|
+
const mode = (e.target as HTMLElement).closest<HTMLElement>('[data-oc-consent]')?.dataset.ocConsent
|
|
73
|
+
if (!mode) return
|
|
74
|
+
const choice: ConsentChoice = { necessary: true, date: new Date().toISOString() }
|
|
75
|
+
for (const i of inputs()) if (!i.disabled) choice[i.name] = mode === 'all' || (mode === 'selected' && i.checked)
|
|
76
|
+
try { localStorage.setItem(props.storageKey, JSON.stringify(choice)) } catch { /* storage blocked */ }
|
|
77
|
+
box.value?.hidePopover?.()
|
|
78
|
+
tell(choice)
|
|
79
|
+
}
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
const stored = read()
|
|
82
|
+
if (stored) tell(stored)
|
|
83
|
+
else if (!props.manual) box.value?.showPopover?.()
|
|
84
|
+
document.addEventListener('click', reopen)
|
|
85
|
+
})
|
|
86
|
+
onBeforeUnmount(() => document.removeEventListener('click', reopen))
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<button v-if="openLabel" type="button" class="oc-btn oc-btn--outline oc-btn--sm" data-oc-consent-open>{{ openLabel }}</button>
|
|
91
|
+
<aside
|
|
92
|
+
:id="id"
|
|
93
|
+
ref="box"
|
|
94
|
+
v-bind="$attrs"
|
|
95
|
+
:class="['oc-cookie-consent', bar && 'oc-cookie-consent--bar']"
|
|
96
|
+
popover="manual"
|
|
97
|
+
:data-oc-consent-manual="manual ? '' : undefined"
|
|
98
|
+
:aria-labelledby="`${id}-title`"
|
|
99
|
+
@click="choose"
|
|
100
|
+
>
|
|
101
|
+
<h2 :id="`${id}-title`" class="oc-cookie-consent__title"><span aria-hidden="true">🍪</span> {{ title }}</h2>
|
|
102
|
+
<p class="oc-cookie-consent__text">
|
|
103
|
+
<slot name="text">Necessary cookies keep the site working. With your permission we also measure visits to improve the site. Read the <a :href="policyHref">cookie policy</a>.</slot>
|
|
104
|
+
</p>
|
|
105
|
+
<details class="oc-cookie-consent__prefs">
|
|
106
|
+
<summary>{{ customizeLabel }}</summary>
|
|
107
|
+
<div v-for="c in categories" :key="c.name" class="oc-cookie-consent__category">
|
|
108
|
+
<span class="oc-cookie-consent__category-name">{{ c.label }}</span>
|
|
109
|
+
<span v-if="c.text" class="oc-cookie-consent__category-text">{{ c.text }}</span>
|
|
110
|
+
<label class="oc-switch"><input type="checkbox" role="switch" :name="c.name" :checked="c.required" :disabled="c.required" :aria-label="`${c.label} cookies`" /><span class="oc-switch-track"></span></label>
|
|
111
|
+
</div>
|
|
112
|
+
</details>
|
|
113
|
+
<div class="oc-cookie-consent__actions">
|
|
114
|
+
<button type="button" class="oc-btn oc-btn--outline" data-oc-consent="necessary">{{ necessaryLabel }}</button>
|
|
115
|
+
<button type="button" class="oc-btn oc-btn--secondary" data-oc-consent="selected">{{ selectedLabel }}</button>
|
|
116
|
+
<button type="button" class="oc-btn" data-oc-consent="all">{{ allLabel }}</button>
|
|
117
|
+
</div>
|
|
118
|
+
</aside>
|
|
119
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Read-only monospace value with a copy button. Copies via navigator.clipboard and
|
|
4
|
+
* adds .is-copied for 2 seconds. Emits `copy` with the value.
|
|
5
|
+
*/
|
|
6
|
+
import { onBeforeUnmount, ref } from 'vue'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
value: string
|
|
11
|
+
label?: string
|
|
12
|
+
buttonLabel?: string
|
|
13
|
+
}>(),
|
|
14
|
+
{ label: 'Value', buttonLabel: 'Copy' },
|
|
15
|
+
)
|
|
16
|
+
const emit = defineEmits<{ copy: [value: string] }>()
|
|
17
|
+
|
|
18
|
+
const copied = ref(false)
|
|
19
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
20
|
+
|
|
21
|
+
async function copy() {
|
|
22
|
+
await navigator.clipboard.writeText(props.value)
|
|
23
|
+
copied.value = true
|
|
24
|
+
emit('copy', props.value)
|
|
25
|
+
clearTimeout(timer)
|
|
26
|
+
timer = setTimeout(() => (copied.value = false), 2000)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onBeforeUnmount(() => clearTimeout(timer))
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="oc-copy-field">
|
|
34
|
+
<input class="oc-copy-field-value" type="text" readonly :value="value" :aria-label="label" />
|
|
35
|
+
<button :class="['oc-copy-field-button', copied && 'is-copied']" type="button" @click="copy">{{ buttonLabel }}</button>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Animated number (@property). Starts when scrolled into view unless `onView` is false. */
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<{ to: number; suffix?: string; onView?: boolean }>(), { onView: true })
|
|
6
|
+
const style = computed(() => {
|
|
7
|
+
const s: Record<string, string> = { '--oc-counter-to': String(props.to) }
|
|
8
|
+
if (props.suffix) s['--oc-counter-suffix'] = `'${props.suffix}'`
|
|
9
|
+
return s
|
|
10
|
+
})
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<strong :class="['oc-counter', onView && 'oc-counter--on-view']" :style="style"></strong>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcDataTable.vue | Map: props -> markup (toolbar, table, footer) -> bind the list-table script on mount -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Korrekt / OmniaDesk list table. Same markup as packages/ui/snippets/data-table.html.
|
|
5
|
+
* Search, sort, pages, columns, resize, drag and bulk actions come from data-table.js.
|
|
6
|
+
* Listen on the root for oc-datatable-bulk, oc-datatable-reorder, oc-datatable-search, oc-datatable-import.
|
|
7
|
+
*/
|
|
8
|
+
import { onMounted, useTemplateRef } from 'vue'
|
|
9
|
+
import { bindDataTable } from '../../../ui/src/data-table.js'
|
|
10
|
+
|
|
11
|
+
export interface DataTableColumn {
|
|
12
|
+
key: string
|
|
13
|
+
header: string
|
|
14
|
+
numeric?: boolean
|
|
15
|
+
sortable?: boolean
|
|
16
|
+
hideable?: boolean
|
|
17
|
+
defaultHidden?: boolean
|
|
18
|
+
}
|
|
19
|
+
export interface DataTableRow {
|
|
20
|
+
key: string
|
|
21
|
+
status?: 'active' | 'deleted' | 'archived'
|
|
22
|
+
cells: Record<string, string>
|
|
23
|
+
values?: Record<string, string>
|
|
24
|
+
href?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(
|
|
28
|
+
defineProps<{
|
|
29
|
+
tableId?: string
|
|
30
|
+
title?: string
|
|
31
|
+
columns: DataTableColumn[]
|
|
32
|
+
rows: DataTableRow[]
|
|
33
|
+
pageSize?: number
|
|
34
|
+
searchPlaceholder?: string
|
|
35
|
+
selectable?: boolean
|
|
36
|
+
draggable?: boolean
|
|
37
|
+
filters?: boolean
|
|
38
|
+
live?: boolean
|
|
39
|
+
}>(),
|
|
40
|
+
{ title: 'List', pageSize: 15, searchPlaceholder: 'Search...', selectable: true, draggable: true, filters: true, live: false },
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const root = useTemplateRef<HTMLElement>('root')
|
|
44
|
+
const sizes = [...new Set([10, 15, 20, 50, 100, props.pageSize])].sort((a, b) => a - b)
|
|
45
|
+
const lead = (props.selectable ? 1 : 0) + (props.draggable ? 1 : 0)
|
|
46
|
+
const id = props.tableId || `oc-dt-${Math.random().toString(36).slice(2, 8)}`
|
|
47
|
+
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
if (root.value) bindDataTable(root.value)
|
|
50
|
+
})
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<div ref="root" class="oc-data-table oc-column-menu-scope" :id="id" :data-page-size="pageSize" :data-oc-live="live ? 'true' : undefined">
|
|
55
|
+
<div class="oc-data-table-toolbar">
|
|
56
|
+
<div class="oc-data-table-heading">
|
|
57
|
+
<span class="oc-data-table-title">{{ title }}</span>
|
|
58
|
+
<button class="oc-btn oc-btn--outline oc-btn--icon oc-btn--sm" type="button" :popovertarget="`${id}-menu`" aria-label="Table actions">⚙</button>
|
|
59
|
+
<div class="oc-dropdown-menu" :id="`${id}-menu`" popover>
|
|
60
|
+
<button class="oc-dropdown-item" type="button" data-oc-export>Export CSV</button>
|
|
61
|
+
<button class="oc-dropdown-item" type="button" data-oc-import>Import CSV</button>
|
|
62
|
+
<input class="oc-data-table-file" type="file" accept=".csv,text/csv" data-oc-file>
|
|
63
|
+
<div class="oc-dropdown-separator"></div>
|
|
64
|
+
<button class="oc-dropdown-item" type="button" data-oc-density="comfortable">Comfortable rows</button>
|
|
65
|
+
<button class="oc-dropdown-item" type="button" data-oc-density="compact">Compact rows</button>
|
|
66
|
+
<button class="oc-dropdown-item" type="button" data-oc-reset-widths>Reset column widths</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<input class="oc-input oc-data-table-search" type="search" :placeholder="searchPlaceholder" :aria-label="`Search ${title}`">
|
|
70
|
+
<div v-if="filters" class="oc-filter-pills" role="group" aria-label="Trash filter">
|
|
71
|
+
<button class="oc-filter-pill" type="button" data-oc-filter="active" aria-current="true">Active only</button>
|
|
72
|
+
<button class="oc-filter-pill" type="button" data-oc-filter="all">Including trash</button>
|
|
73
|
+
<button class="oc-filter-pill" type="button" data-oc-filter="deleted">Trash only</button>
|
|
74
|
+
<button class="oc-filter-pill" type="button" data-oc-filter="archived">Archive only</button>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="oc-data-table-view" role="group" aria-label="View">
|
|
77
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-view="table" aria-pressed="true">Table</button>
|
|
78
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-view="grid">Cards</button>
|
|
79
|
+
</div>
|
|
80
|
+
<p class="oc-data-table-sort-summary" data-oc-sort-summary role="status"></p>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="oc-table-toolbar">
|
|
83
|
+
<span class="oc-table-toolbar-spacer"></span>
|
|
84
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" :popovertarget="`${id}-columns`">Columns</button>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="oc-column-menu" :id="`${id}-columns`" popover>
|
|
87
|
+
<p class="oc-column-menu-title">Visible columns</p>
|
|
88
|
+
<form class="oc-column-menu-list">
|
|
89
|
+
<template v-for="(col, index) in columns" :key="col.key">
|
|
90
|
+
<label v-if="col.hideable !== false" class="oc-column-menu-item">
|
|
91
|
+
<input type="checkbox" :data-col="lead + index + 1" :checked="!col.defaultHidden"> {{ col.header }}
|
|
92
|
+
</label>
|
|
93
|
+
</template>
|
|
94
|
+
</form>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="oc-data-table-bulk" hidden>
|
|
97
|
+
<span data-oc-selected-count>0 selected</span>
|
|
98
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-bulk="export">Export selection</button>
|
|
99
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-bulk="duplicate">Duplicate</button>
|
|
100
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-bulk="archive">Move to trash</button>
|
|
101
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" data-oc-bulk="restore">Restore</button>
|
|
102
|
+
<button class="oc-btn oc-btn--destructive oc-btn--sm" type="button" data-oc-bulk="delete">Delete permanently</button>
|
|
103
|
+
<button class="oc-btn oc-btn--ghost oc-btn--sm" type="button" data-oc-bulk="clear">Clear</button>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="oc-data-table-frame">
|
|
106
|
+
<div class="oc-data-table-scroll">
|
|
107
|
+
<table class="oc-table oc-data-table-table">
|
|
108
|
+
<thead>
|
|
109
|
+
<tr>
|
|
110
|
+
<th v-if="selectable" data-oc-col="select"><input class="oc-checkbox" type="checkbox" data-oc-select-all aria-label="Select all"></th>
|
|
111
|
+
<th v-if="draggable" data-oc-col="order"><span class="oc-visually-hidden">Order</span></th>
|
|
112
|
+
<th v-for="col in columns" :key="col.key" :data-oc-col="col.key" :data-oc-sort="col.numeric ? 'numeric' : 'text'" :data-numeric="col.numeric ? '' : undefined">
|
|
113
|
+
<span class="oc-data-table-th">
|
|
114
|
+
<button v-if="col.sortable !== false" class="oc-table-sort" type="button">{{ col.header }}</button>
|
|
115
|
+
<template v-else>{{ col.header }}</template>
|
|
116
|
+
<span class="oc-data-table-resize" role="separator" aria-orientation="vertical" :aria-label="`Resize ${col.header}`"></span>
|
|
117
|
+
</span>
|
|
118
|
+
</th>
|
|
119
|
+
<th data-oc-col="actions"><span class="oc-visually-hidden">Actions</span></th>
|
|
120
|
+
</tr>
|
|
121
|
+
</thead>
|
|
122
|
+
<tbody>
|
|
123
|
+
<tr v-for="row in rows" :key="row.key" data-oc-row :data-oc-key="row.key" :data-oc-status="row.status || 'active'" :data-oc-href="row.href">
|
|
124
|
+
<td v-if="selectable" data-oc-col="select"><input class="oc-checkbox" type="checkbox" data-oc-select :aria-label="`Select ${row.cells.name || row.key}`"></td>
|
|
125
|
+
<td v-if="draggable" data-oc-col="order" class="oc-sortable-row-grip-cell"><button class="oc-sortable-row-grip" type="button" :aria-label="`Move ${row.cells.name || row.key}`"></button></td>
|
|
126
|
+
<td v-for="col in columns" :key="col.key" :data-oc-col="col.key" :data-numeric="col.numeric ? '' : undefined" :data-oc-value="row.values?.[col.key]">{{ row.cells[col.key] ?? '' }}</td>
|
|
127
|
+
<td data-oc-col="actions" class="oc-table-actions"><button class="oc-table-row-action" type="button">Edit</button></td>
|
|
128
|
+
</tr>
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
131
|
+
</div>
|
|
132
|
+
<p class="oc-data-table-empty" hidden>No results</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="oc-data-table-footer">
|
|
135
|
+
<p class="oc-data-table-summary" data-oc-summary></p>
|
|
136
|
+
<label class="oc-data-table-per-page">Rows per page
|
|
137
|
+
<select class="oc-select" data-oc-page-size aria-label="Rows per page">
|
|
138
|
+
<option v-for="n in sizes" :key="n" :value="n" :selected="n === pageSize">{{ n }}</option>
|
|
139
|
+
</select>
|
|
140
|
+
</label>
|
|
141
|
+
<nav class="oc-data-table-pager" data-oc-pager aria-label="Pagination"></nav>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcDecimalInput.vue | Map: props + model -> locale helpers (parse / format) -> template (field, optional unit frame) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Locale decimal field (1.234,56). v-model: number | null. Formats on blur with Intl.NumberFormat.
|
|
5
|
+
* Same markup as packages/ui/snippets/decimal-input.html. All attributes (also class) go to the input.
|
|
6
|
+
*/
|
|
7
|
+
import { ref, watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
defineOptions({ inheritAttrs: false })
|
|
10
|
+
const props = withDefaults(defineProps<{ locale?: string; decimals?: number; unit?: string; alignStart?: boolean; error?: boolean }>(), {
|
|
11
|
+
locale: 'nl-BE',
|
|
12
|
+
decimals: 2,
|
|
13
|
+
})
|
|
14
|
+
const model = defineModel<number | null>({ default: null })
|
|
15
|
+
|
|
16
|
+
/** Group and decimal separator of the locale, e.g. nl-BE -> "." and ",". */
|
|
17
|
+
function separators() {
|
|
18
|
+
const parts = new Intl.NumberFormat(props.locale).formatToParts(1234.5)
|
|
19
|
+
return { group: parts.find((p) => p.type === 'group')?.value ?? ',', decimal: parts.find((p) => p.type === 'decimal')?.value ?? '.' }
|
|
20
|
+
}
|
|
21
|
+
function parse(text: string): number | null {
|
|
22
|
+
const { group, decimal } = separators()
|
|
23
|
+
const clean = text.trim().split(group).join('').replace(decimal, '.').replace(/\s/g, '')
|
|
24
|
+
const n = Number(clean)
|
|
25
|
+
return clean && Number.isFinite(n) ? n : null
|
|
26
|
+
}
|
|
27
|
+
function format(n: number | null): string {
|
|
28
|
+
return n == null ? '' : new Intl.NumberFormat(props.locale, { minimumFractionDigits: props.decimals, maximumFractionDigits: props.decimals }).format(n)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const text = ref(format(model.value))
|
|
32
|
+
const focused = ref(false)
|
|
33
|
+
// Show outside changes, but never while the user is typing.
|
|
34
|
+
watch(model, (n) => { if (!focused.value) text.value = format(n) })
|
|
35
|
+
|
|
36
|
+
function onInput(e: Event) {
|
|
37
|
+
text.value = (e.target as HTMLInputElement).value
|
|
38
|
+
model.value = parse(text.value)
|
|
39
|
+
}
|
|
40
|
+
function onBlur() {
|
|
41
|
+
focused.value = false
|
|
42
|
+
const n = parse(text.value)
|
|
43
|
+
if (n != null || !text.value.trim()) text.value = format(n)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div v-if="unit" class="oc-decimal-field">
|
|
49
|
+
<input v-bind="$attrs" :class="['oc-input', 'oc-decimal-input', alignStart && 'oc-decimal-input--start']" type="text" inputmode="decimal" :aria-invalid="error ? 'true' : undefined" :value="text" @focus="focused = true" @input="onInput" @blur="onBlur" />
|
|
50
|
+
<span class="oc-decimal-field-unit">{{ unit }}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<input v-else v-bind="$attrs" :class="['oc-input', 'oc-decimal-input', alignStart && 'oc-decimal-input--start']" type="text" inputmode="decimal" :aria-invalid="error ? 'true' : undefined" :value="text" @focus="focused = true" @input="onInput" @blur="onBlur" />
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Label / value rows in a panel (dl). Pass `rows`, or put your own .oc-dl-row elements in the default slot.
|
|
4
|
+
* Slots: title, description, default (rows), footer.
|
|
5
|
+
*/
|
|
6
|
+
export interface DefinitionRow {
|
|
7
|
+
term: string
|
|
8
|
+
detail: string
|
|
9
|
+
}
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
title?: string
|
|
13
|
+
description?: string
|
|
14
|
+
rows?: DefinitionRow[]
|
|
15
|
+
label?: string
|
|
16
|
+
}>(),
|
|
17
|
+
{ rows: () => [] },
|
|
18
|
+
)
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div class="oc-dl-panel" role="region" :aria-label="label ?? title">
|
|
23
|
+
<h3 v-if="title || $slots.title" class="oc-dl-title"><slot name="title">{{ title }}</slot></h3>
|
|
24
|
+
<p v-if="description || $slots.description" class="oc-dl-description"><slot name="description">{{ description }}</slot></p>
|
|
25
|
+
<dl class="oc-dl">
|
|
26
|
+
<slot>
|
|
27
|
+
<div v-for="(row, i) in rows" :key="i" class="oc-dl-row">
|
|
28
|
+
<dt class="oc-dl-term">{{ row.term }}</dt>
|
|
29
|
+
<dd class="oc-dl-detail">{{ row.detail }}</dd>
|
|
30
|
+
</div>
|
|
31
|
+
</slot>
|
|
32
|
+
</dl>
|
|
33
|
+
<div v-if="$slots.footer" class="oc-dl-footer"><slot name="footer" /></div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcDocViewer.vue | Map: props + demo items -> grid of cards (bar, frame) -> one full-screen <dialog> per card -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* PDF, video or image cards with a full-screen view (same markup as snippets/doc-viewer.html). No JavaScript:
|
|
5
|
+
* the buttons open the dialog with invoker commands (command="show-modal" commandfor), closedby="any" closes it.
|
|
6
|
+
* items: { label, meta?, src?, type?: pdf|video|image, id?, alt?, art?, download?, open?: bar|frame, fullscreen? }[]
|
|
7
|
+
* Classes: .oc-doc-viewer-grid, .oc-doc-viewer(--video/--image) __bar __label __meta __expand __frame __open;
|
|
8
|
+
* .oc-doc-viewer-dialog(--video) __bar __title __actions __close __body
|
|
9
|
+
*/
|
|
10
|
+
import { useUid } from '../composables/useUid'
|
|
11
|
+
|
|
12
|
+
export interface DocViewerItem {
|
|
13
|
+
label: string
|
|
14
|
+
meta?: string
|
|
15
|
+
src?: string
|
|
16
|
+
type?: 'pdf' | 'video' | 'image'
|
|
17
|
+
id?: string
|
|
18
|
+
alt?: string
|
|
19
|
+
art?: number
|
|
20
|
+
download?: string
|
|
21
|
+
open?: 'bar' | 'frame'
|
|
22
|
+
fullscreen?: boolean
|
|
23
|
+
}
|
|
24
|
+
const props = withDefaults(
|
|
25
|
+
defineProps<{ items?: DocViewerItem[]; openText?: string; downloadText?: string; idPrefix?: string }>(),
|
|
26
|
+
{
|
|
27
|
+
items: () => [
|
|
28
|
+
{ label: 'Brochure 2026', meta: 'PDF · 2.4 MB', alt: 'First page of Brochure 2026', art: 2, download: '#brochure.pdf' },
|
|
29
|
+
{ label: 'Site visit', meta: '1:42', type: 'video', alt: 'Video still: site visit', art: 5 },
|
|
30
|
+
],
|
|
31
|
+
openText: 'Click for full screen',
|
|
32
|
+
downloadText: 'Download',
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
defineOptions({ inheritAttrs: false })
|
|
36
|
+
const auto = useUid('oc-doc-viewer')
|
|
37
|
+
const idOf = (doc: DocViewerItem, i: number) => doc.id ?? `${props.idPrefix ?? auto}-${i + 1}`
|
|
38
|
+
const full = (doc: DocViewerItem) => doc.fullscreen !== false
|
|
39
|
+
const inFrame = (doc: DocViewerItem) => full(doc) && (doc.open ?? (doc.type === 'video' ? 'frame' : 'bar')) === 'frame'
|
|
40
|
+
const art = (doc: DocViewerItem) => ['oc-media-art', doc.art != null && `oc-media-art--${doc.art}`]
|
|
41
|
+
const dialogAlt = (doc: DocViewerItem) => (doc.type === 'video' ? `${doc.label} video` : doc.label)
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div class="oc-doc-viewer-grid" v-bind="$attrs">
|
|
46
|
+
<figure v-for="(doc, i) in items" :key="i" :class="['oc-doc-viewer', doc.type === 'video' && 'oc-doc-viewer--video', doc.type === 'image' && 'oc-doc-viewer--image']">
|
|
47
|
+
<figcaption class="oc-doc-viewer__bar">
|
|
48
|
+
<span class="oc-doc-viewer__label">{{ doc.label }}</span>
|
|
49
|
+
<span v-if="doc.meta" class="oc-doc-viewer__meta">{{ doc.meta }}</span>
|
|
50
|
+
<button v-if="full(doc) && !inFrame(doc)" type="button" class="oc-doc-viewer__expand" command="show-modal" :commandfor="idOf(doc, i)" :aria-label="`Open ${doc.label} full screen`">⤢</button>
|
|
51
|
+
</figcaption>
|
|
52
|
+
<div class="oc-doc-viewer__frame">
|
|
53
|
+
<div v-if="!doc.src" :class="art(doc)" role="img" :aria-label="doc.alt ?? doc.label"></div>
|
|
54
|
+
<video v-else-if="doc.type === 'video'" :src="doc.src" controls preload="metadata" :aria-label="doc.alt ?? doc.label"></video>
|
|
55
|
+
<img v-else-if="doc.type === 'image'" :src="doc.src" :alt="doc.alt ?? doc.label" />
|
|
56
|
+
<iframe v-else :src="`${doc.src}#toolbar=0&view=FitH`" :title="doc.alt ?? doc.label" loading="lazy"></iframe>
|
|
57
|
+
<button v-if="inFrame(doc)" type="button" class="oc-doc-viewer__open" command="show-modal" :commandfor="idOf(doc, i)">{{ openText }}</button>
|
|
58
|
+
</div>
|
|
59
|
+
</figure>
|
|
60
|
+
</div>
|
|
61
|
+
<template v-for="(doc, i) in items" :key="i">
|
|
62
|
+
<dialog v-if="full(doc)" :id="idOf(doc, i)" :class="['oc-doc-viewer-dialog', doc.type === 'video' && 'oc-doc-viewer-dialog--video']" closedby="any" :aria-labelledby="`${idOf(doc, i)}-title`">
|
|
63
|
+
<div class="oc-doc-viewer-dialog__bar">
|
|
64
|
+
<h2 :id="`${idOf(doc, i)}-title`" class="oc-doc-viewer-dialog__title">{{ doc.label }}</h2>
|
|
65
|
+
<div v-if="doc.download" class="oc-doc-viewer-dialog__actions">
|
|
66
|
+
<a class="oc-btn oc-btn--outline oc-btn--sm" :href="doc.download" download>{{ downloadText }}</a>
|
|
67
|
+
<button type="button" class="oc-doc-viewer-dialog__close" command="close" :commandfor="idOf(doc, i)" aria-label="Close">✕</button>
|
|
68
|
+
</div>
|
|
69
|
+
<button v-else type="button" class="oc-doc-viewer-dialog__close" command="close" :commandfor="idOf(doc, i)" aria-label="Close">✕</button>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="oc-doc-viewer-dialog__body">
|
|
72
|
+
<div v-if="!doc.src" :class="art(doc)" role="img" :aria-label="dialogAlt(doc)"></div>
|
|
73
|
+
<video v-else-if="doc.type === 'video'" :src="doc.src" controls preload="metadata" :aria-label="dialogAlt(doc)"></video>
|
|
74
|
+
<img v-else-if="doc.type === 'image'" :src="doc.src" :alt="dialogAlt(doc)" />
|
|
75
|
+
<iframe v-else :src="`${doc.src}#toolbar=0&view=FitH`" :title="dialogAlt(doc)" loading="lazy"></iframe>
|
|
76
|
+
</div>
|
|
77
|
+
</dialog>
|
|
78
|
+
</template>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Dropdown menu on the popover attribute + anchor positioning. The trigger is the `trigger` slot
|
|
4
|
+
* (scoped: { id }) or a default outline button showing `label`. Default slot = menu content
|
|
5
|
+
* (OcDropdownLabel / OcDropdownItem / OcDropdownSeparator). Attributes land on the menu element.
|
|
6
|
+
*/
|
|
7
|
+
import { useAttrs } from 'vue'
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
defineOptions({ inheritAttrs: false })
|
|
11
|
+
defineProps<{ label?: string }>()
|
|
12
|
+
const attrs = useAttrs()
|
|
13
|
+
const id = (attrs.id as string | undefined) ?? useUid('oc-menu')
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<slot name="trigger" :id="id">
|
|
18
|
+
<button class="oc-btn oc-btn--outline" type="button" :popovertarget="id">{{ label }}</button>
|
|
19
|
+
</slot>
|
|
20
|
+
<div v-bind="attrs" :id="id" class="oc-dropdown-menu" popover>
|
|
21
|
+
<slot />
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Menu item: <a> with `href`, otherwise <button>. */
|
|
3
|
+
defineProps<{ href?: string }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<a v-if="href" class="oc-dropdown-item" :href="href"><slot /></a>
|
|
8
|
+
<button v-else class="oc-dropdown-item" type="button"><slot /></button>
|
|
9
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* File upload target: a label around a real file input. v-model: File[].
|
|
4
|
+
* .is-dragover is toggled while dragging over. Slots: icon, default (text), hint.
|
|
5
|
+
*/
|
|
6
|
+
import { ref } from 'vue'
|
|
7
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
8
|
+
|
|
9
|
+
defineOptions({ inheritAttrs: false })
|
|
10
|
+
defineProps<{ accept?: string; multiple?: boolean; disabled?: boolean; hint?: string }>()
|
|
11
|
+
const model = defineModel<File[]>({ default: () => [] })
|
|
12
|
+
const { root, control } = useAttrsSplit()
|
|
13
|
+
const dragover = ref(false)
|
|
14
|
+
|
|
15
|
+
function onChange(e: Event) {
|
|
16
|
+
model.value = Array.from((e.target as HTMLInputElement).files ?? [])
|
|
17
|
+
}
|
|
18
|
+
function onDrop() {
|
|
19
|
+
dragover.value = false
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<label
|
|
25
|
+
v-bind="root"
|
|
26
|
+
:class="['oc-dropzone', dragover && 'is-dragover']"
|
|
27
|
+
@dragover.prevent="dragover = true"
|
|
28
|
+
@dragleave="dragover = false"
|
|
29
|
+
@drop="onDrop"
|
|
30
|
+
>
|
|
31
|
+
<input v-bind="control" type="file" :accept="accept" :multiple="multiple" :disabled="disabled" @change="onChange" />
|
|
32
|
+
<span v-if="$slots.icon" class="oc-dropzone-icon" aria-hidden="true"><slot name="icon" /></span>
|
|
33
|
+
<span><slot /></span>
|
|
34
|
+
<span v-if="hint || $slots.hint" class="oc-dropzone-hint"><slot name="hint">{{ hint }}</slot></span>
|
|
35
|
+
</label>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcEmojiPicker.vue | Map: props + emit -> trigger button -> popover panel -> groups (label + grid) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Emoji picker on the popover attribute (same markup as snippets/emoji-picker.html).
|
|
5
|
+
* Props: groups ({ label?, emojis[] }[]), inline (always visible, no popover), triggerLabel, label.
|
|
6
|
+
* Emits: select(emoji) and then closes the popover. Slot: trigger (button content, default 🙂).
|
|
7
|
+
* Classes: .oc-emoji-picker(--inline), __trigger, __label, __grid, __item
|
|
8
|
+
*/
|
|
9
|
+
import { useUid } from '../composables/useUid'
|
|
10
|
+
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{ groups?: { label?: string; emojis: string[] }[]; inline?: boolean; triggerLabel?: string; label?: string }>(),
|
|
13
|
+
{
|
|
14
|
+
groups: () => [
|
|
15
|
+
{ label: 'Smileys', emojis: ['😀', '😃', '😄', '😁', '😂', '🙂', '😉', '😍', '🤔', '😎', '😢', '😴', '😯', '🥳', '😤', '🤗'] },
|
|
16
|
+
{ label: 'Gestures', emojis: ['👍', '👎', '👏', '🙌', '👋', '🙏', '✌️', '💪'] },
|
|
17
|
+
{ label: 'Objects', emojis: ['❤️', '⭐', '🔥', '🎉', '✅', '🚀', '💡', '☕'] },
|
|
18
|
+
],
|
|
19
|
+
triggerLabel: 'Insert emoji',
|
|
20
|
+
label: 'Emoji',
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
const emit = defineEmits<{ select: [emoji: string] }>()
|
|
24
|
+
const id = useUid('oc-emoji')
|
|
25
|
+
|
|
26
|
+
function pick(e: MouseEvent, emoji: string) {
|
|
27
|
+
emit('select', emoji)
|
|
28
|
+
const panel = (e.currentTarget as HTMLElement).closest('[popover]') as (HTMLElement & { hidePopover?: () => void }) | null
|
|
29
|
+
panel?.hidePopover?.()
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<button v-if="!inline" type="button" class="oc-emoji-picker__trigger" :popovertarget="id" :aria-label="triggerLabel">
|
|
35
|
+
<slot name="trigger">🙂</slot>
|
|
36
|
+
</button>
|
|
37
|
+
<div :id="id" :class="['oc-emoji-picker', inline && 'oc-emoji-picker--inline']" :popover="inline ? undefined : ''" :role="inline ? 'group' : undefined" :aria-label="label">
|
|
38
|
+
<div v-for="(g, gi) in groups" :key="gi">
|
|
39
|
+
<p v-if="g.label" class="oc-emoji-picker__label">{{ g.label }}</p>
|
|
40
|
+
<div class="oc-emoji-picker__grid">
|
|
41
|
+
<button v-for="emoji in g.emojis" :key="emoji" type="button" class="oc-emoji-picker__item" @click="pick($event, emoji)">{{ emoji }}</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Icon, title, text and actions for lists without data. Slots: icon, title, default (text), actions. */
|
|
3
|
+
defineProps<{ icon?: string; title?: string; text?: string }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="oc-empty">
|
|
8
|
+
<span v-if="icon || $slots.icon" class="oc-empty-icon" aria-hidden="true"><slot name="icon">{{ icon }}</slot></span>
|
|
9
|
+
<h3 v-if="title || $slots.title" class="oc-empty-title"><slot name="title">{{ title }}</slot></h3>
|
|
10
|
+
<p v-if="text || $slots.default" class="oc-empty-text"><slot>{{ text }}</slot></p>
|
|
11
|
+
<div v-if="$slots.actions" class="oc-empty-actions"><slot name="actions" /></div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|