@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,115 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Data table: sortable header (v-model:sort), row selection (v-model:selected), row actions, empty state,
|
|
4
|
+
* density modifiers. Slots: `cell-<key>` (scoped: { row, value }), `actions` (scoped: { row }), `empty`.
|
|
5
|
+
* Classes: .oc-table-wrap > .oc-table(--striped/--compact/--borderless/--sticky), .oc-table-sort,
|
|
6
|
+
* .oc-table-actions, .oc-table-empty; aria-sort / aria-selected / data-numeric attributes.
|
|
7
|
+
*/
|
|
8
|
+
import { computed, useSlots } from 'vue'
|
|
9
|
+
|
|
10
|
+
export interface TableColumn {
|
|
11
|
+
key: string
|
|
12
|
+
label: string
|
|
13
|
+
sortable?: boolean
|
|
14
|
+
numeric?: boolean
|
|
15
|
+
width?: string
|
|
16
|
+
}
|
|
17
|
+
export interface TableSort {
|
|
18
|
+
key: string
|
|
19
|
+
direction: 'ascending' | 'descending'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(
|
|
23
|
+
defineProps<{
|
|
24
|
+
columns: TableColumn[]
|
|
25
|
+
rows: Record<string, any>[]
|
|
26
|
+
rowKey?: string
|
|
27
|
+
caption?: string
|
|
28
|
+
striped?: boolean
|
|
29
|
+
compact?: boolean
|
|
30
|
+
borderless?: boolean
|
|
31
|
+
sticky?: boolean
|
|
32
|
+
selectable?: boolean
|
|
33
|
+
actionsLabel?: string
|
|
34
|
+
selectAllLabel?: string
|
|
35
|
+
}>(),
|
|
36
|
+
{ rowKey: 'id', actionsLabel: 'Actions', selectAllLabel: 'Select all' },
|
|
37
|
+
)
|
|
38
|
+
const selected = defineModel<(string | number)[]>('selected', { default: () => [] })
|
|
39
|
+
const sort = defineModel<TableSort | null>('sort', { default: null })
|
|
40
|
+
const slots = useSlots()
|
|
41
|
+
|
|
42
|
+
const hasActions = computed(() => !!slots.actions)
|
|
43
|
+
const colspan = computed(() => props.columns.length + (props.selectable ? 1 : 0) + (hasActions.value ? 1 : 0))
|
|
44
|
+
const allSelected = computed(() => props.rows.length > 0 && props.rows.every((r) => selected.value.includes(r[props.rowKey])))
|
|
45
|
+
|
|
46
|
+
function toggleAll(e: Event) {
|
|
47
|
+
selected.value = (e.target as HTMLInputElement).checked ? props.rows.map((r) => r[props.rowKey]) : []
|
|
48
|
+
}
|
|
49
|
+
function toggleRow(id: string | number, checked: boolean) {
|
|
50
|
+
selected.value = checked ? [...selected.value, id] : selected.value.filter((x) => x !== id)
|
|
51
|
+
}
|
|
52
|
+
function ariaSort(key: string) {
|
|
53
|
+
return sort.value?.key === key ? sort.value.direction : undefined
|
|
54
|
+
}
|
|
55
|
+
function toggleSort(e: Event, key: string) {
|
|
56
|
+
e.preventDefault()
|
|
57
|
+
const next = sort.value?.key === key && sort.value.direction === 'ascending' ? 'descending' : 'ascending'
|
|
58
|
+
sort.value = { key, direction: next }
|
|
59
|
+
}
|
|
60
|
+
function rowLabel(row: Record<string, any>) {
|
|
61
|
+
return `Select ${String(row[props.columns[0]?.key] ?? '')}`.trim()
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<div class="oc-table-wrap">
|
|
67
|
+
<table
|
|
68
|
+
:class="['oc-table', striped && 'oc-table--striped', compact && 'oc-table--compact', borderless && 'oc-table--borderless', sticky && 'oc-table--sticky']"
|
|
69
|
+
>
|
|
70
|
+
<caption v-if="caption">{{ caption }}</caption>
|
|
71
|
+
<thead>
|
|
72
|
+
<tr>
|
|
73
|
+
<th v-if="selectable" style="width: 2.5rem">
|
|
74
|
+
<input class="oc-checkbox" type="checkbox" :checked="allSelected" :aria-label="selectAllLabel" @change="toggleAll" />
|
|
75
|
+
</th>
|
|
76
|
+
<th
|
|
77
|
+
v-for="col in columns"
|
|
78
|
+
:key="col.key"
|
|
79
|
+
:aria-sort="ariaSort(col.key)"
|
|
80
|
+
:data-numeric="col.numeric ? '' : undefined"
|
|
81
|
+
:style="col.width ? { width: col.width } : undefined"
|
|
82
|
+
>
|
|
83
|
+
<a v-if="col.sortable" class="oc-table-sort" :href="`?sort=${col.key}`" @click="toggleSort($event, col.key)">{{ col.label }}</a>
|
|
84
|
+
<template v-else>{{ col.label }}</template>
|
|
85
|
+
</th>
|
|
86
|
+
<th v-if="hasActions"><span class="oc-visually-hidden">{{ actionsLabel }}</span></th>
|
|
87
|
+
</tr>
|
|
88
|
+
</thead>
|
|
89
|
+
<tbody>
|
|
90
|
+
<tr
|
|
91
|
+
v-for="row in rows"
|
|
92
|
+
:key="row[rowKey]"
|
|
93
|
+
:aria-selected="selected.includes(row[rowKey]) ? 'true' : undefined"
|
|
94
|
+
>
|
|
95
|
+
<td v-if="selectable">
|
|
96
|
+
<input
|
|
97
|
+
class="oc-checkbox"
|
|
98
|
+
type="checkbox"
|
|
99
|
+
:checked="selected.includes(row[rowKey])"
|
|
100
|
+
:aria-label="rowLabel(row)"
|
|
101
|
+
@change="toggleRow(row[rowKey], ($event.target as HTMLInputElement).checked)"
|
|
102
|
+
/>
|
|
103
|
+
</td>
|
|
104
|
+
<td v-for="col in columns" :key="col.key" :data-numeric="col.numeric ? '' : undefined">
|
|
105
|
+
<slot :name="`cell-${col.key}`" :row="row" :value="row[col.key]">{{ row[col.key] }}</slot>
|
|
106
|
+
</td>
|
|
107
|
+
<td v-if="hasActions" class="oc-table-actions"><slot name="actions" :row="row" /></td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr v-if="rows.length === 0 && $slots.empty" class="oc-table-empty">
|
|
110
|
+
<td :colspan="colspan"><slot name="empty" :row="{}" /></td>
|
|
111
|
+
</tr>
|
|
112
|
+
</tbody>
|
|
113
|
+
</table>
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTableActions.vue | Map: td.oc-table-actions -> icon actions (default slot) OR overflow trigger + popover menu (default slot = menu items) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Row actions cell. Two modes:
|
|
5
|
+
* - no `menuLabel`: the default slot holds icon actions (`a` / `button` with class oc-table-actions-icon(--primary/--success/--destructive)).
|
|
6
|
+
* - with `menuLabel`: a three dots trigger opens a native popover; the default slot holds `.oc-dropdown-item`s.
|
|
7
|
+
* Classes: td.oc-table-actions(--icons), .oc-table-actions-icon, .oc-table-actions-more, .oc-dropdown-menu[popover]
|
|
8
|
+
*/
|
|
9
|
+
import { useUid } from '../composables/useUid'
|
|
10
|
+
|
|
11
|
+
defineProps<{
|
|
12
|
+
/** Accessible name of the overflow trigger, e.g. "Actions for Ada". Turns on menu mode. */
|
|
13
|
+
menuLabel?: string
|
|
14
|
+
}>()
|
|
15
|
+
const menuId = useUid('oc-table-actions-menu')
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<td :class="['oc-table-actions', !menuLabel && 'oc-table-actions--icons']">
|
|
20
|
+
<template v-if="menuLabel">
|
|
21
|
+
<button class="oc-table-actions-more" type="button" :popovertarget="menuId" :aria-label="menuLabel"></button>
|
|
22
|
+
<div :id="menuId" class="oc-dropdown-menu" popover><slot /></div>
|
|
23
|
+
</template>
|
|
24
|
+
<slot v-else />
|
|
25
|
+
</td>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTableCompact.vue | Map: wrapper div -> table.oc-table (thead from columns, tbody from rows, tfoot from footer) OR default slot -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Small striped table for inside cards. Pass `columns` + `rows` (+ `footer` total row), or put your own
|
|
5
|
+
* <table class="oc-table"> in the default slot. Scoped slot `cell` ({ row, column }) customises body cells.
|
|
6
|
+
* Classes: .oc-table-compact(--bordered/--plain), .oc-table-compact-muted, [data-numeric], [data-wrap]
|
|
7
|
+
*/
|
|
8
|
+
export interface TableCompactColumn {
|
|
9
|
+
key: string
|
|
10
|
+
label: string
|
|
11
|
+
numeric?: boolean
|
|
12
|
+
wrap?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
columns?: TableCompactColumn[]
|
|
18
|
+
rows?: Array<Record<string, unknown>>
|
|
19
|
+
footer?: Record<string, unknown>
|
|
20
|
+
bordered?: boolean
|
|
21
|
+
plain?: boolean
|
|
22
|
+
}>(),
|
|
23
|
+
{ rows: () => [] },
|
|
24
|
+
)
|
|
25
|
+
// data-* attributes only when switched on (an empty string means "present").
|
|
26
|
+
const cellAttrs = (c: TableCompactColumn) => ({ 'data-numeric': c.numeric ? '' : undefined, 'data-wrap': c.wrap ? '' : undefined })
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div :class="['oc-table-compact', bordered && 'oc-table-compact--bordered', plain && 'oc-table-compact--plain']">
|
|
31
|
+
<table v-if="columns" class="oc-table">
|
|
32
|
+
<thead>
|
|
33
|
+
<tr><th v-for="c in columns" :key="c.key" v-bind="cellAttrs(c)">{{ c.label }}</th></tr>
|
|
34
|
+
</thead>
|
|
35
|
+
<tbody>
|
|
36
|
+
<tr v-for="(row, i) in rows" :key="i">
|
|
37
|
+
<td v-for="c in columns" :key="c.key" v-bind="cellAttrs(c)"><slot name="cell" :row="row" :column="c">{{ row[c.key] }}</slot></td>
|
|
38
|
+
</tr>
|
|
39
|
+
</tbody>
|
|
40
|
+
<tfoot v-if="footer">
|
|
41
|
+
<tr><td v-for="c in columns" :key="c.key" v-bind="cellAttrs(c)">{{ footer[c.key] }}</td></tr>
|
|
42
|
+
</tfoot>
|
|
43
|
+
</table>
|
|
44
|
+
<slot v-else />
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Tabs with radio inputs and :has() (the CSS switches the panels). v-model = value of the active tab.
|
|
4
|
+
* One named slot per tab value renders its panel: <template #account>...</template>.
|
|
5
|
+
* Classes: .oc-tabset, .oc-tab-list, .oc-tab-trigger, .oc-tab-panel
|
|
6
|
+
*/
|
|
7
|
+
import { useUid } from '../composables/useUid'
|
|
8
|
+
|
|
9
|
+
export interface TabItem {
|
|
10
|
+
label: string
|
|
11
|
+
/** Slot name and model value; defaults to the label. */
|
|
12
|
+
value?: string
|
|
13
|
+
}
|
|
14
|
+
defineProps<{ tabs: TabItem[]; name?: string }>()
|
|
15
|
+
const model = defineModel<string>()
|
|
16
|
+
const uid = useUid('oc-tabs')
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="oc-tabset">
|
|
21
|
+
<div class="oc-tab-list" role="tablist">
|
|
22
|
+
<label v-for="(tab, i) in tabs" :key="tab.value ?? tab.label" class="oc-tab-trigger">
|
|
23
|
+
<input
|
|
24
|
+
v-model="model"
|
|
25
|
+
type="radio"
|
|
26
|
+
:name="name ?? uid"
|
|
27
|
+
:value="tab.value ?? tab.label"
|
|
28
|
+
:checked="model === undefined && i === 0"
|
|
29
|
+
/>
|
|
30
|
+
{{ tab.label }}
|
|
31
|
+
</label>
|
|
32
|
+
</div>
|
|
33
|
+
<div v-for="tab in tabs" :key="tab.value ?? tab.label" class="oc-tab-panel">
|
|
34
|
+
<slot :name="tab.value ?? tab.label" />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Chips with remove buttons and a free-text input with suggestions. v-model: string[].
|
|
4
|
+
* Enter or comma adds the typed tag, Backspace on an empty input removes the last one.
|
|
5
|
+
* Classes: .oc-tag, .oc-tag-remove, .oc-tag-input, .oc-tag-suggestions
|
|
6
|
+
*/
|
|
7
|
+
import { computed, ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
suggestions?: string[]
|
|
12
|
+
placeholder?: string
|
|
13
|
+
label?: string
|
|
14
|
+
}>(),
|
|
15
|
+
{ suggestions: () => [], placeholder: 'Add tag...', label: 'Add tag' },
|
|
16
|
+
)
|
|
17
|
+
const tags = defineModel<string[]>({ default: () => [] })
|
|
18
|
+
const draft = ref('')
|
|
19
|
+
const available = computed(() => props.suggestions.filter((s) => !tags.value.includes(s)))
|
|
20
|
+
|
|
21
|
+
function add(value: string) {
|
|
22
|
+
const tag = value.trim()
|
|
23
|
+
if (tag && !tags.value.includes(tag)) tags.value = [...tags.value, tag]
|
|
24
|
+
draft.value = ''
|
|
25
|
+
}
|
|
26
|
+
function remove(tag: string) {
|
|
27
|
+
tags.value = tags.value.filter((t) => t !== tag)
|
|
28
|
+
}
|
|
29
|
+
function onKeydown(e: KeyboardEvent) {
|
|
30
|
+
if (e.key === 'Enter' || e.key === ',') {
|
|
31
|
+
e.preventDefault()
|
|
32
|
+
add(draft.value)
|
|
33
|
+
} else if (e.key === 'Backspace' && !draft.value && tags.value.length) {
|
|
34
|
+
tags.value = tags.value.slice(0, -1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div class="oc-tag-input">
|
|
41
|
+
<span v-for="tag in tags" :key="tag" class="oc-tag">
|
|
42
|
+
{{ tag }}
|
|
43
|
+
<button class="oc-tag-remove" type="button" :aria-label="`Remove ${tag}`" @click="remove(tag)">×</button>
|
|
44
|
+
</span>
|
|
45
|
+
<input v-model="draft" type="text" :placeholder="placeholder" :aria-label="label" @keydown="onKeydown" />
|
|
46
|
+
</div>
|
|
47
|
+
<div v-if="available.length" class="oc-tag-suggestions">
|
|
48
|
+
<button v-for="s in available" :key="s" type="button" @click="add(s)">{{ s }}</button>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTerminalLog.vue | Map: props -> frame -> bar (dots, title, status) -> body (lines + prompt) -> auto-scroll -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Dark console window (same markup as snippets/terminal-log.html).
|
|
5
|
+
* Props: lines ({ level: info|success|warn|error|debug, message, time?, label? }[]), title, status ("Live" + pulsing dot),
|
|
6
|
+
* prompt (text, or true for an empty prompt with cursor), height (max body height), autoScroll (default true), label.
|
|
7
|
+
* Classes: .oc-terminal-log, __bar, __title, __status, __body, __line(--info/--success/--warn/--error/--debug), __time, __level, __msg, __prompt
|
|
8
|
+
*/
|
|
9
|
+
import { nextTick, ref, watch } from 'vue'
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
lines: { level: 'info' | 'success' | 'warn' | 'error' | 'debug'; message: string; time?: string; label?: string }[]
|
|
14
|
+
title?: string
|
|
15
|
+
status?: string
|
|
16
|
+
prompt?: string | boolean
|
|
17
|
+
height?: string
|
|
18
|
+
autoScroll?: boolean
|
|
19
|
+
label?: string
|
|
20
|
+
}>(),
|
|
21
|
+
{ autoScroll: true, label: 'Log output', prompt: false },
|
|
22
|
+
)
|
|
23
|
+
const body = ref<HTMLDivElement | null>(null)
|
|
24
|
+
// Keep the newest line in view.
|
|
25
|
+
watch(
|
|
26
|
+
() => props.lines.length,
|
|
27
|
+
async () => {
|
|
28
|
+
await nextTick()
|
|
29
|
+
if (props.autoScroll && body.value) body.value.scrollTop = body.value.scrollHeight
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="oc-terminal-log" :style="height ? { '--oc-terminal-log-height': height } : undefined">
|
|
36
|
+
<div class="oc-terminal-log__bar">
|
|
37
|
+
<i /><i /><i />
|
|
38
|
+
<span v-if="title" class="oc-terminal-log__title">{{ title }}</span>
|
|
39
|
+
<span v-if="status" class="oc-terminal-log__status">{{ status }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div ref="body" class="oc-terminal-log__body" role="log" aria-live="polite" :aria-label="label" tabindex="0">
|
|
42
|
+
<p v-for="(l, i) in lines" :key="i" :class="['oc-terminal-log__line', `oc-terminal-log__line--${l.level}`]">
|
|
43
|
+
<time v-if="l.time" class="oc-terminal-log__time">{{ l.time }}</time>
|
|
44
|
+
<span class="oc-terminal-log__level">{{ l.label ?? (l.level === 'success' ? 'ok' : l.level) }}</span>
|
|
45
|
+
<span class="oc-terminal-log__msg">{{ l.message }}</span>
|
|
46
|
+
</p>
|
|
47
|
+
<p v-if="prompt !== false" class="oc-terminal-log__prompt">{{ prompt === true ? '' : prompt }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Multi-line input (uses .oc-input). v-model. */
|
|
3
|
+
defineProps<{ error?: boolean }>()
|
|
4
|
+
const model = defineModel<string>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<textarea v-model="model" :class="['oc-input', error && 'oc-input--error']" :aria-invalid="error ? 'true' : undefined"></textarea>
|
|
9
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* System / light / dark switch (CSS only: color-scheme + :has()). v-model: 'system' | 'light' | 'dark'.
|
|
4
|
+
* `options` limits the choices; `as="div"` renders a role="radiogroup" div instead of a fieldset.
|
|
5
|
+
*/
|
|
6
|
+
import { useUid } from '../composables/useUid'
|
|
7
|
+
|
|
8
|
+
type Theme = 'system' | 'light' | 'dark'
|
|
9
|
+
const labels: Record<Theme, string> = { system: 'System', light: 'Light', dark: 'Dark' }
|
|
10
|
+
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
options?: Theme[]
|
|
14
|
+
as?: 'fieldset' | 'div'
|
|
15
|
+
label?: string
|
|
16
|
+
name?: string
|
|
17
|
+
}>(),
|
|
18
|
+
{ options: () => ['system', 'light', 'dark'], as: 'fieldset', label: 'Color theme' },
|
|
19
|
+
)
|
|
20
|
+
const model = defineModel<Theme>({ default: 'system' })
|
|
21
|
+
const uid = useUid('oc-theme')
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<component :is="as" class="oc-theme-switch" :role="as === 'div' ? 'radiogroup' : undefined" :aria-label="label">
|
|
26
|
+
<label v-for="o in options" :key="o">
|
|
27
|
+
<input v-model="model" type="radio" :name="name ?? uid" :value="o" />
|
|
28
|
+
{{ labels[o] }}
|
|
29
|
+
</label>
|
|
30
|
+
</component>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTimeInput.vue | Map: props + model -> format helpers -> template (text field + datalist) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* 24h time field (HH:mm). v-model: "HH:mm" or "". `slots` fills a datalist with suggestions.
|
|
5
|
+
* Same markup as packages/ui/snippets/time-input.html. JS only adds the colon ("0930" -> "09:30").
|
|
6
|
+
* All attributes go to the input.
|
|
7
|
+
*/
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
defineOptions({ inheritAttrs: false })
|
|
11
|
+
defineProps<{ slots?: string[]; error?: boolean; placeholder?: string }>()
|
|
12
|
+
const model = defineModel<string>({ default: '' })
|
|
13
|
+
const listId = useUid('oc-time')
|
|
14
|
+
|
|
15
|
+
/** Up to 4 digits with a colon after the first two. A picked datalist option is kept as is. */
|
|
16
|
+
function onInput(e: Event) {
|
|
17
|
+
const input = e.target as HTMLInputElement
|
|
18
|
+
const v = input.value
|
|
19
|
+
const digits = v.replace(/\D/g, '').slice(0, 4)
|
|
20
|
+
model.value = /^\d{2}:\d{2}$/.test(v) ? v : digits.length <= 2 ? digits : `${digits.slice(0, 2)}:${digits.slice(2)}`
|
|
21
|
+
input.value = model.value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** On blur: "9" -> "09:00", "930" -> "09:30". */
|
|
25
|
+
function onBlur() {
|
|
26
|
+
const d = model.value.replace(/\D/g, '')
|
|
27
|
+
if (d.length === 1 || d.length === 2) model.value = `${d.padStart(2, '0')}:00`
|
|
28
|
+
else if (d.length === 3) model.value = `0${d[0]}:${d.slice(1)}`
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<input
|
|
34
|
+
v-bind="$attrs"
|
|
35
|
+
class="oc-input oc-time-input"
|
|
36
|
+
type="text"
|
|
37
|
+
inputmode="numeric"
|
|
38
|
+
maxlength="5"
|
|
39
|
+
pattern="([01][0-9]|2[0-3]):[0-5][0-9]"
|
|
40
|
+
:placeholder="placeholder ?? 'hh:mm'"
|
|
41
|
+
:list="slots?.length ? listId : undefined"
|
|
42
|
+
:aria-invalid="error ? 'true' : undefined"
|
|
43
|
+
:value="model"
|
|
44
|
+
@input="onInput"
|
|
45
|
+
@blur="onBlur"
|
|
46
|
+
/>
|
|
47
|
+
<datalist v-if="slots?.length" :id="listId">
|
|
48
|
+
<option v-for="s in slots" :key="s" :value="s"></option>
|
|
49
|
+
</datalist>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTimeline.vue | Map: props (items, size, accent) -> ol.oc-timeline -> li.oc-timeline__item -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Vertical timeline. Pass `items`, or put your own li.oc-timeline__item elements in the default slot.
|
|
5
|
+
* Classes: .oc-timeline (--lg, --accent), .oc-timeline__item (--current), __date, __title, __text
|
|
6
|
+
*/
|
|
7
|
+
export interface TimelineEntry {
|
|
8
|
+
date: string
|
|
9
|
+
/** Renders a <time datetime> when set. */
|
|
10
|
+
dateTime?: string
|
|
11
|
+
title?: string
|
|
12
|
+
text?: string
|
|
13
|
+
current?: boolean
|
|
14
|
+
}
|
|
15
|
+
withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
items?: TimelineEntry[]
|
|
18
|
+
size?: 'default' | 'lg'
|
|
19
|
+
accent?: boolean
|
|
20
|
+
}>(),
|
|
21
|
+
{ items: () => [], size: 'default', accent: false },
|
|
22
|
+
)
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<ol :class="['oc-timeline', size === 'lg' && 'oc-timeline--lg', accent && 'oc-timeline--accent']">
|
|
27
|
+
<slot>
|
|
28
|
+
<li v-for="(item, i) in items" :key="i" :class="['oc-timeline__item', item.current && 'oc-timeline__item--current']">
|
|
29
|
+
<time v-if="item.dateTime" class="oc-timeline__date" :datetime="item.dateTime">{{ item.date }}</time>
|
|
30
|
+
<span v-else class="oc-timeline__date">{{ item.date }}</span>
|
|
31
|
+
<h3 v-if="item.title" class="oc-timeline__title">{{ item.title }}</h3>
|
|
32
|
+
<p v-if="item.text" class="oc-timeline__text">{{ item.text }}</p>
|
|
33
|
+
</li>
|
|
34
|
+
</slot>
|
|
35
|
+
</ol>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One notification. Emits `dismiss`. role="alert" for errors, role="status" otherwise.
|
|
4
|
+
* Slots: icon, title, default (description). Classes: .oc-toast--success/error/warning/info, -icon, -body,
|
|
5
|
+
* -title, -description, -close
|
|
6
|
+
*/
|
|
7
|
+
const glyphs = { success: '✓', error: '!', warning: '!', info: 'i' } as const
|
|
8
|
+
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
variant?: 'success' | 'error' | 'warning' | 'info'
|
|
12
|
+
title?: string
|
|
13
|
+
description?: string
|
|
14
|
+
dismissible?: boolean
|
|
15
|
+
dismissLabel?: string
|
|
16
|
+
}>(),
|
|
17
|
+
{ variant: 'info', dismissible: true, dismissLabel: 'Dismiss' },
|
|
18
|
+
)
|
|
19
|
+
defineEmits<{ dismiss: [] }>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div :class="['oc-toast', `oc-toast--${variant}`]" :role="variant === 'error' ? 'alert' : 'status'">
|
|
24
|
+
<span class="oc-toast-icon" aria-hidden="true"><slot name="icon">{{ glyphs[variant] }}</slot></span>
|
|
25
|
+
<div class="oc-toast-body">
|
|
26
|
+
<p class="oc-toast-title"><slot name="title">{{ title }}</slot></p>
|
|
27
|
+
<p v-if="description || $slots.default" class="oc-toast-description"><slot>{{ description }}</slot></p>
|
|
28
|
+
</div>
|
|
29
|
+
<button v-if="dismissible" class="oc-toast-close" type="button" :aria-label="dismissLabel" @click="$emit('dismiss')">×</button>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Fixed stack that renders the useToast() queue. Mount once near the app root. Classes: .oc-toast-container(--top/--static) */
|
|
3
|
+
import { useToast } from '../composables/useToast'
|
|
4
|
+
import OcToast from './OcToast.vue'
|
|
5
|
+
|
|
6
|
+
withDefaults(defineProps<{ position?: 'bottom' | 'top'; static?: boolean }>(), { position: 'bottom' })
|
|
7
|
+
const { toasts, dismiss } = useToast()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div :class="['oc-toast-container', position === 'top' && 'oc-toast-container--top', static && 'oc-toast-container--static']">
|
|
12
|
+
<OcToast
|
|
13
|
+
v-for="t in toasts"
|
|
14
|
+
:key="t.id"
|
|
15
|
+
:variant="t.variant"
|
|
16
|
+
:title="t.title"
|
|
17
|
+
:description="t.description"
|
|
18
|
+
@dismiss="dismiss(t.id)"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Toggle. Inside OcToggleGroup (give it a `value`) it is a label around a radio/checkbox bound to the group's v-model.
|
|
4
|
+
* Standalone: v-model boolean on a label + checkbox, or `button` for a <button aria-pressed>.
|
|
5
|
+
* Pass an aria-label attribute for icon-only toggles (it lands on the input/button).
|
|
6
|
+
* Classes: .oc-toggle(--outline/--sm/--lg/--icon)
|
|
7
|
+
*/
|
|
8
|
+
import { computed, inject } from 'vue'
|
|
9
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
10
|
+
import { toggleGroupKey } from '../composables/keys'
|
|
11
|
+
|
|
12
|
+
defineOptions({ inheritAttrs: false })
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
value?: string
|
|
15
|
+
variant?: 'default' | 'outline'
|
|
16
|
+
size?: 'sm' | 'md' | 'lg'
|
|
17
|
+
icon?: boolean
|
|
18
|
+
button?: boolean
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
}>()
|
|
21
|
+
const pressed = defineModel<boolean>({ default: false })
|
|
22
|
+
const group = inject(toggleGroupKey, null)
|
|
23
|
+
const { root, control } = useAttrsSplit()
|
|
24
|
+
|
|
25
|
+
const classes = computed(() => [
|
|
26
|
+
'oc-toggle',
|
|
27
|
+
props.variant === 'outline' && 'oc-toggle--outline',
|
|
28
|
+
props.size && props.size !== 'md' && `oc-toggle--${props.size}`,
|
|
29
|
+
props.icon && 'oc-toggle--icon',
|
|
30
|
+
])
|
|
31
|
+
const inGroup = computed(() => !!group && props.value !== undefined)
|
|
32
|
+
const isOn = computed(() => (inGroup.value ? group!.values.value.includes(props.value!) : pressed.value))
|
|
33
|
+
function onChange() {
|
|
34
|
+
if (inGroup.value) group!.toggle(props.value!)
|
|
35
|
+
else pressed.value = !pressed.value
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<button
|
|
41
|
+
v-if="button && !inGroup"
|
|
42
|
+
v-bind="{ ...root, ...control }"
|
|
43
|
+
type="button"
|
|
44
|
+
:class="classes"
|
|
45
|
+
:aria-pressed="pressed ? 'true' : 'false'"
|
|
46
|
+
:disabled="disabled"
|
|
47
|
+
@click="pressed = !pressed"
|
|
48
|
+
>
|
|
49
|
+
<slot />
|
|
50
|
+
</button>
|
|
51
|
+
<label v-else v-bind="root" :class="classes">
|
|
52
|
+
<input
|
|
53
|
+
v-bind="control"
|
|
54
|
+
:type="inGroup && group!.type.value === 'single' ? 'radio' : 'checkbox'"
|
|
55
|
+
:name="inGroup ? group!.name.value : (control.name as string | undefined)"
|
|
56
|
+
:value="value"
|
|
57
|
+
:checked="isOn"
|
|
58
|
+
:disabled="disabled"
|
|
59
|
+
@change="onChange"
|
|
60
|
+
/>
|
|
61
|
+
<slot />
|
|
62
|
+
</label>
|
|
63
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcToggleField.vue | Map: props + model -> template (label row: text + span.oc-switch) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Settings row: title, description and a switch. v-model: boolean. Slots `label` / `description` override the props.
|
|
5
|
+
* Same markup as packages/ui/snippets/toggle-field.html. class/style on the row, other attributes on the checkbox.
|
|
6
|
+
*/
|
|
7
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
defineOptions({ inheritAttrs: false })
|
|
11
|
+
defineProps<{ label?: string; description?: string; card?: boolean; reverse?: boolean }>()
|
|
12
|
+
const model = defineModel<boolean>({ default: false })
|
|
13
|
+
const { root, control } = useAttrsSplit()
|
|
14
|
+
const descId = useUid('oc-toggle-field')
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<label v-bind="root" :class="['oc-toggle-field', card && 'oc-toggle-field--card', reverse && 'oc-toggle-field--reverse']">
|
|
19
|
+
<span class="oc-toggle-field-text">
|
|
20
|
+
<span class="oc-toggle-field-label"><slot name="label">{{ label }}</slot></span>
|
|
21
|
+
<span v-if="description || $slots.description" :id="descId" class="oc-toggle-field-description"><slot name="description">{{ description }}</slot></span>
|
|
22
|
+
</span>
|
|
23
|
+
<span class="oc-switch">
|
|
24
|
+
<input v-bind="control" v-model="model" type="checkbox" role="switch" :aria-describedby="description || $slots.description ? descId : undefined" />
|
|
25
|
+
<span class="oc-switch-track"></span>
|
|
26
|
+
</span>
|
|
27
|
+
</label>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Connected toggles. v-model = array of pressed values (type "multiple", checkbox inputs) or a single value
|
|
4
|
+
* string (type "single", radio inputs). Put OcToggle items with a `value` inside.
|
|
5
|
+
* Classes: .oc-toggle-group(--spaced)
|
|
6
|
+
*/
|
|
7
|
+
import { computed, provide } from 'vue'
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
import { toggleGroupKey } from '../composables/keys'
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<{ type?: 'single' | 'multiple'; spaced?: boolean; label?: string; name?: string }>(), { type: 'multiple' })
|
|
12
|
+
const model = defineModel<string | string[]>()
|
|
13
|
+
const uid = useUid('oc-toggle')
|
|
14
|
+
|
|
15
|
+
const values = computed(() => (Array.isArray(model.value) ? model.value : model.value ? [model.value] : []))
|
|
16
|
+
function toggle(value: string) {
|
|
17
|
+
if (props.type === 'single') {
|
|
18
|
+
model.value = value
|
|
19
|
+
} else {
|
|
20
|
+
const list = values.value
|
|
21
|
+
model.value = list.includes(value) ? list.filter((v) => v !== value) : [...list, value]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
provide(toggleGroupKey, {
|
|
25
|
+
values,
|
|
26
|
+
type: computed(() => props.type),
|
|
27
|
+
name: computed(() => props.name ?? uid),
|
|
28
|
+
toggle,
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
:class="['oc-toggle-group', spaced && 'oc-toggle-group--spaced']"
|
|
35
|
+
:role="type === 'single' ? 'radiogroup' : 'group'"
|
|
36
|
+
:aria-label="label"
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|