@iqual/nuxt-icms 1.1.0 → 1.2.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/assets/css/ckeditor.css +4 -0
- package/assets/css/components/container.css +2 -2
- package/assets/css/tailwind.css +1 -1
- package/assets/css/typography.css +4 -0
- package/assets/symbols/download.svg +3 -0
- package/bundles.ts +4 -1
- package/components/Blokkli/TextMedia/index.vue +1 -2
- package/components/DesignSystem/Atoms/Caption.vue +15 -0
- package/components/DesignSystem/Atoms/LinkListItem.vue +21 -0
- package/components/DesignSystem/Molecules/MediaSlider.vue +25 -8
- package/components/DesignSystem/Organisms/Map.vue +61 -0
- package/components/Media/Image/index.vue +4 -162
- package/components/Node/Icms/Page/SearchResult/fragment.graphql +8 -0
- package/components/Node/Icms/Page/SearchResult/index.vue +12 -0
- package/components/Node/Icms/Page/Teaser/fragment.graphql +17 -0
- package/components/Node/Icms/Page/Teaser/index.vue +20 -0
- package/components/Node/Icms/Page/fragment.graphql +18 -0
- package/components/Paragraph/CallToAction/index.vue +12 -8
- package/components/Paragraph/Card/Layout/index.vue +2 -2
- package/components/Paragraph/Downloads/index.vue +3 -14
- package/components/Paragraph/FAQ/Layout/index.vue +23 -1
- package/components/Paragraph/FlipTeasers/index.vue +2 -2
- package/components/Paragraph/Gallery/index.vue +32 -171
- package/components/Paragraph/HeroImage/index.vue +13 -4
- package/components/Paragraph/Media/index.vue +7 -2
- package/components/Paragraph/Table/index.vue +2 -2
- package/components/Paragraph/Team/Layout/index.vue +2 -2
- package/components/Paragraph/Teasers/fragment.graphql +2 -15
- package/components/Paragraph/Teasers/index.vue +11 -14
- package/components/Paragraph/TextMedia/index.vue +40 -33
- package/components/Paragraph/Timeline/Element/index.vue +1 -1
- package/components/Paragraph/Timeline/Layout/fragment.graphql +1 -0
- package/components/Paragraph/Timeline/Layout/index.vue +4 -4
- package/components/Paragraph/iFrame/index.vue +1 -1
- package/components/Search/Facets/FacetDropdownUI.vue +222 -0
- package/components/Search/Facets/FacetedSearchUI.vue +397 -0
- package/components/Search/Facets/fragment.graphql +40 -0
- package/components/Section/Header/LanguageDropdown.vue +60 -73
- package/components/Section/Header/LanguageSwitcher.vue +37 -0
- package/components/Section/Header/MetaMenu.vue +10 -1
- package/components/Section/Header/index.vue +80 -67
- package/components/ui/accordion/Accordion.vue +2 -2
- package/components/ui/accordion/AccordionContent.vue +5 -5
- package/components/ui/accordion/AccordionItem.vue +9 -10
- package/components/ui/accordion/AccordionTrigger.vue +5 -5
- package/components/ui/alert/Alert.vue +17 -0
- package/components/ui/alert/AlertDescription.vue +14 -0
- package/components/ui/alert/AlertTitle.vue +14 -0
- package/components/ui/alert/index.ts +24 -0
- package/components/ui/alert-dialog/AlertDialog.vue +15 -0
- package/components/ui/alert-dialog/AlertDialogAction.vue +22 -0
- package/components/ui/alert-dialog/AlertDialogCancel.vue +29 -0
- package/components/ui/alert-dialog/AlertDialogContent.vue +43 -0
- package/components/ui/alert-dialog/AlertDialogDescription.vue +27 -0
- package/components/ui/alert-dialog/AlertDialogFooter.vue +21 -0
- package/components/ui/alert-dialog/AlertDialogHeader.vue +16 -0
- package/components/ui/alert-dialog/AlertDialogTitle.vue +24 -0
- package/components/ui/alert-dialog/AlertDialogTrigger.vue +12 -0
- package/components/ui/alert-dialog/index.ts +9 -0
- package/components/ui/aspect-ratio/AspectRatio.vue +12 -0
- package/components/ui/aspect-ratio/index.ts +1 -0
- package/components/ui/auto-form/AutoForm.vue +106 -0
- package/components/ui/auto-form/AutoFormField.vue +45 -0
- package/components/ui/auto-form/AutoFormFieldArray.vue +110 -0
- package/components/ui/auto-form/AutoFormFieldBoolean.vue +41 -0
- package/components/ui/auto-form/AutoFormFieldDate.vue +57 -0
- package/components/ui/auto-form/AutoFormFieldEnum.vue +49 -0
- package/components/ui/auto-form/AutoFormFieldFile.vue +74 -0
- package/components/ui/auto-form/AutoFormFieldInput.vue +36 -0
- package/components/ui/auto-form/AutoFormFieldNumber.vue +32 -0
- package/components/ui/auto-form/AutoFormFieldObject.vue +78 -0
- package/components/ui/auto-form/AutoFormLabel.vue +14 -0
- package/components/ui/auto-form/constant.ts +40 -0
- package/components/ui/auto-form/dependencies.ts +93 -0
- package/components/ui/auto-form/index.ts +15 -0
- package/components/ui/auto-form/interface.ts +95 -0
- package/components/ui/auto-form/utils.ts +188 -0
- package/components/ui/avatar/Avatar.vue +22 -0
- package/components/ui/avatar/AvatarFallback.vue +12 -0
- package/components/ui/avatar/AvatarImage.vue +10 -0
- package/components/ui/avatar/index.ts +25 -0
- package/components/ui/badge/Badge.vue +17 -0
- package/components/ui/badge/index.ts +26 -0
- package/components/ui/breadcrumb/Breadcrumb.vue +13 -0
- package/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
- package/components/ui/breadcrumb/BreadcrumbItem.vue +16 -0
- package/components/ui/breadcrumb/BreadcrumbLink.vue +20 -0
- package/components/ui/breadcrumb/BreadcrumbList.vue +16 -0
- package/components/ui/breadcrumb/BreadcrumbPage.vue +19 -0
- package/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
- package/components/ui/breadcrumb/index.ts +7 -0
- package/components/ui/button/Button.vue +4 -2
- package/components/ui/button/index.ts +9 -9
- package/components/ui/calendar/Calendar.vue +62 -0
- package/components/ui/calendar/CalendarCell.vue +26 -0
- package/components/ui/calendar/CalendarCellTrigger.vue +40 -0
- package/components/ui/calendar/CalendarGrid.vue +26 -0
- package/components/ui/calendar/CalendarGridBody.vue +12 -0
- package/components/ui/calendar/CalendarGridHead.vue +12 -0
- package/components/ui/calendar/CalendarGridRow.vue +23 -0
- package/components/ui/calendar/CalendarHeadCell.vue +23 -0
- package/components/ui/calendar/CalendarHeader.vue +23 -0
- package/components/ui/calendar/CalendarHeading.vue +33 -0
- package/components/ui/calendar/CalendarNextButton.vue +34 -0
- package/components/ui/calendar/CalendarPrevButton.vue +34 -0
- package/components/ui/calendar/index.ts +12 -0
- package/components/ui/card/Card.vue +21 -0
- package/components/ui/card/CardContent.vue +14 -0
- package/components/ui/card/CardDescription.vue +14 -0
- package/components/ui/card/CardFooter.vue +14 -0
- package/components/ui/card/CardHeader.vue +14 -0
- package/components/ui/card/CardTitle.vue +18 -0
- package/components/ui/card/index.ts +6 -0
- package/components/ui/carousel/Carousel.vue +3 -23
- package/components/ui/carousel/CarouselContent.vue +1 -2
- package/components/ui/carousel/CarouselItem.vue +5 -7
- package/components/ui/carousel/CarouselNext.vue +7 -9
- package/components/ui/carousel/CarouselPrevious.vue +7 -9
- package/components/ui/carousel/index.ts +3 -1
- package/components/ui/carousel/interface.ts +3 -1
- package/components/ui/carousel/useCarousel.ts +13 -23
- package/components/ui/chart/ChartCrosshair.vue +45 -0
- package/components/ui/chart/ChartLegend.vue +50 -0
- package/components/ui/chart/ChartSingleTooltip.vue +64 -0
- package/components/ui/chart/ChartTooltip.vue +40 -0
- package/components/ui/chart/index.ts +18 -0
- package/components/ui/chart/interface.ts +64 -0
- package/components/ui/chart-area/AreaChart.vue +141 -0
- package/components/ui/chart-area/index.ts +66 -0
- package/components/ui/chart-bar/BarChart.vue +116 -0
- package/components/ui/chart-bar/index.ts +66 -0
- package/components/ui/chart-donut/DonutChart.vue +101 -0
- package/components/ui/chart-donut/index.ts +39 -0
- package/components/ui/chart-line/LineChart.vue +107 -0
- package/components/ui/chart-line/index.ts +66 -0
- package/components/ui/checkbox/Checkbox.vue +1 -1
- package/components/ui/collapsible/Collapsible.vue +15 -0
- package/components/ui/collapsible/CollapsibleContent.vue +12 -0
- package/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
- package/components/ui/collapsible/index.ts +3 -0
- package/components/ui/command/Command.vue +31 -0
- package/components/ui/command/CommandDialog.vue +21 -0
- package/components/ui/command/CommandEmpty.vue +21 -0
- package/components/ui/command/CommandGroup.vue +30 -0
- package/components/ui/command/CommandInput.vue +35 -0
- package/components/ui/command/CommandItem.vue +27 -0
- package/components/ui/command/CommandList.vue +28 -0
- package/components/ui/command/CommandSeparator.vue +24 -0
- package/components/ui/command/CommandShortcut.vue +14 -0
- package/components/ui/command/index.ts +9 -0
- package/components/ui/context-menu/ContextMenu.vue +15 -0
- package/components/ui/context-menu/ContextMenuCheckboxItem.vue +41 -0
- package/components/ui/context-menu/ContextMenuContent.vue +37 -0
- package/components/ui/context-menu/ContextMenuGroup.vue +12 -0
- package/components/ui/context-menu/ContextMenuItem.vue +35 -0
- package/components/ui/context-menu/ContextMenuLabel.vue +27 -0
- package/components/ui/context-menu/ContextMenuPortal.vue +12 -0
- package/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
- package/components/ui/context-menu/ContextMenuRadioItem.vue +41 -0
- package/components/ui/context-menu/ContextMenuSeparator.vue +22 -0
- package/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
- package/components/ui/context-menu/ContextMenuSub.vue +19 -0
- package/components/ui/context-menu/ContextMenuSubContent.vue +36 -0
- package/components/ui/context-menu/ContextMenuSubTrigger.vue +36 -0
- package/components/ui/context-menu/ContextMenuTrigger.vue +14 -0
- package/components/ui/context-menu/index.ts +14 -0
- package/components/ui/dialog/Dialog.vue +2 -6
- package/components/ui/dialog/DialogClose.vue +2 -1
- package/components/ui/dialog/DialogContent.vue +10 -12
- package/components/ui/dialog/DialogDescription.vue +6 -10
- package/components/ui/dialog/DialogScrollContent.vue +15 -21
- package/components/ui/dialog/DialogTitle.vue +9 -6
- package/components/ui/dialog/DialogTrigger.vue +2 -1
- package/components/ui/drawer/Drawer.vue +19 -0
- package/components/ui/drawer/DrawerContent.vue +28 -0
- package/components/ui/drawer/DrawerDescription.vue +21 -0
- package/components/ui/drawer/DrawerFooter.vue +14 -0
- package/components/ui/drawer/DrawerHeader.vue +14 -0
- package/components/ui/drawer/DrawerOverlay.vue +19 -0
- package/components/ui/drawer/DrawerTitle.vue +21 -0
- package/components/ui/drawer/index.ts +8 -0
- package/components/ui/dropdown-menu/DropdownMenu.vue +15 -0
- package/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +41 -0
- package/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -0
- package/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/components/ui/dropdown-menu/DropdownMenuItem.vue +30 -0
- package/components/ui/dropdown-menu/DropdownMenuLabel.vue +26 -0
- package/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
- package/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +42 -0
- package/components/ui/dropdown-menu/DropdownMenuSeparator.vue +24 -0
- package/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- package/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/components/ui/dropdown-menu/DropdownMenuSubContent.vue +31 -0
- package/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +35 -0
- package/components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
- package/components/ui/dropdown-menu/index.ts +16 -0
- package/components/ui/form/FormControl.vue +16 -0
- package/components/ui/form/FormDescription.vue +20 -0
- package/components/ui/form/FormItem.vue +20 -0
- package/components/ui/form/FormLabel.vue +23 -0
- package/components/ui/form/FormMessage.vue +16 -0
- package/components/ui/form/index.ts +7 -0
- package/components/ui/form/injectionKeys.ts +4 -0
- package/components/ui/form/useFormField.ts +30 -0
- package/components/ui/hover-card/HoverCard.vue +15 -0
- package/components/ui/hover-card/HoverCardContent.vue +43 -0
- package/components/ui/hover-card/HoverCardTrigger.vue +12 -0
- package/components/ui/hover-card/index.ts +3 -0
- package/components/ui/input/Input.vue +1 -1
- package/components/ui/menubar/Menubar.vue +36 -0
- package/components/ui/menubar/MenubarCheckboxItem.vue +41 -0
- package/components/ui/menubar/MenubarContent.vue +45 -0
- package/components/ui/menubar/MenubarGroup.vue +12 -0
- package/components/ui/menubar/MenubarItem.vue +36 -0
- package/components/ui/menubar/MenubarLabel.vue +14 -0
- package/components/ui/menubar/MenubarMenu.vue +12 -0
- package/components/ui/menubar/MenubarRadioGroup.vue +20 -0
- package/components/ui/menubar/MenubarRadioItem.vue +41 -0
- package/components/ui/menubar/MenubarSeparator.vue +21 -0
- package/components/ui/menubar/MenubarShortcut.vue +14 -0
- package/components/ui/menubar/MenubarSub.vue +20 -0
- package/components/ui/menubar/MenubarSubContent.vue +40 -0
- package/components/ui/menubar/MenubarSubTrigger.vue +32 -0
- package/components/ui/menubar/MenubarTrigger.vue +31 -0
- package/components/ui/menubar/index.ts +15 -0
- package/components/ui/navigation-menu/NavigationMenu.vue +34 -0
- package/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
- package/components/ui/navigation-menu/NavigationMenuIndicator.vue +26 -0
- package/components/ui/navigation-menu/NavigationMenuItem.vue +12 -0
- package/components/ui/navigation-menu/NavigationMenuLink.vue +19 -0
- package/components/ui/navigation-menu/NavigationMenuList.vue +31 -0
- package/components/ui/navigation-menu/NavigationMenuTrigger.vue +36 -0
- package/components/ui/navigation-menu/NavigationMenuViewport.vue +35 -0
- package/components/ui/navigation-menu/index.ts +14 -0
- package/components/ui/number-field/NumberField.vue +24 -0
- package/components/ui/number-field/NumberFieldContent.vue +14 -0
- package/components/ui/number-field/NumberFieldDecrement.vue +26 -0
- package/components/ui/number-field/NumberFieldIncrement.vue +26 -0
- package/components/ui/number-field/NumberFieldInput.vue +16 -0
- package/components/ui/number-field/index.ts +5 -0
- package/components/ui/pagination/PaginationEllipsis.vue +24 -0
- package/components/ui/pagination/PaginationFirst.vue +31 -0
- package/components/ui/pagination/PaginationLast.vue +31 -0
- package/components/ui/pagination/PaginationNext.vue +31 -0
- package/components/ui/pagination/PaginationPrev.vue +31 -0
- package/components/ui/pagination/index.ts +10 -0
- package/components/ui/pin-input/PinInput.vue +25 -0
- package/components/ui/pin-input/PinInputGroup.vue +20 -0
- package/components/ui/pin-input/PinInputInput.vue +20 -0
- package/components/ui/pin-input/PinInputSeparator.vue +16 -0
- package/components/ui/pin-input/index.ts +4 -0
- package/components/ui/popover/Popover.vue +15 -0
- package/components/ui/popover/PopoverContent.vue +49 -0
- package/components/ui/popover/PopoverTrigger.vue +12 -0
- package/components/ui/popover/index.ts +3 -0
- package/components/ui/progress/Progress.vue +41 -0
- package/components/ui/progress/index.ts +1 -0
- package/components/ui/radio-group/RadioGroupItem.vue +1 -1
- package/components/ui/range-calendar/RangeCalendar.vue +62 -0
- package/components/ui/range-calendar/RangeCalendarCell.vue +26 -0
- package/components/ui/range-calendar/RangeCalendarCellTrigger.vue +42 -0
- package/components/ui/range-calendar/RangeCalendarGrid.vue +26 -0
- package/components/ui/range-calendar/RangeCalendarGridBody.vue +12 -0
- package/components/ui/range-calendar/RangeCalendarGridHead.vue +12 -0
- package/components/ui/range-calendar/RangeCalendarGridRow.vue +23 -0
- package/components/ui/range-calendar/RangeCalendarHeadCell.vue +23 -0
- package/components/ui/range-calendar/RangeCalendarHeader.vue +23 -0
- package/components/ui/range-calendar/RangeCalendarHeading.vue +33 -0
- package/components/ui/range-calendar/RangeCalendarNextButton.vue +34 -0
- package/components/ui/range-calendar/RangeCalendarPrevButton.vue +34 -0
- package/components/ui/range-calendar/index.ts +12 -0
- package/components/ui/resizable/ResizableHandle.vue +28 -0
- package/components/ui/resizable/ResizablePanelGroup.vue +23 -0
- package/components/ui/resizable/index.ts +3 -0
- package/components/ui/scroll-area/ScrollArea.vue +31 -0
- package/components/ui/scroll-area/ScrollBar.vue +32 -0
- package/components/ui/scroll-area/index.ts +2 -0
- package/components/ui/select/SelectContent.vue +1 -1
- package/components/ui/select/SelectItem.vue +1 -1
- package/components/ui/select/SelectSeparator.vue +1 -1
- package/components/ui/select/SelectTrigger.vue +1 -1
- package/components/ui/separator/Separator.vue +37 -0
- package/components/ui/separator/index.ts +1 -0
- package/components/ui/sheet/Sheet.vue +15 -0
- package/components/ui/sheet/SheetClose.vue +12 -0
- package/components/ui/sheet/SheetContent.vue +58 -0
- package/components/ui/sheet/SheetDescription.vue +24 -0
- package/components/ui/sheet/SheetFooter.vue +19 -0
- package/components/ui/sheet/SheetHeader.vue +16 -0
- package/components/ui/sheet/SheetTitle.vue +24 -0
- package/components/ui/sheet/SheetTrigger.vue +12 -0
- package/components/ui/sheet/index.ts +32 -0
- package/components/ui/sidebar/Sidebar.vue +85 -0
- package/components/ui/sidebar/SidebarContent.vue +17 -0
- package/components/ui/sidebar/SidebarFooter.vue +17 -0
- package/components/ui/sidebar/SidebarGroup.vue +17 -0
- package/components/ui/sidebar/SidebarGroupAction.vue +26 -0
- package/components/ui/sidebar/SidebarGroupContent.vue +17 -0
- package/components/ui/sidebar/SidebarGroupLabel.vue +24 -0
- package/components/ui/sidebar/SidebarHeader.vue +17 -0
- package/components/ui/sidebar/SidebarInput.vue +21 -0
- package/components/ui/sidebar/SidebarInset.vue +20 -0
- package/components/ui/sidebar/SidebarMenu.vue +17 -0
- package/components/ui/sidebar/SidebarMenuAction.vue +34 -0
- package/components/ui/sidebar/SidebarMenuBadge.vue +25 -0
- package/components/ui/sidebar/SidebarMenuButton.vue +51 -0
- package/components/ui/sidebar/SidebarMenuButtonChild.vue +35 -0
- package/components/ui/sidebar/SidebarMenuItem.vue +17 -0
- package/components/ui/sidebar/SidebarMenuSkeleton.vue +34 -0
- package/components/ui/sidebar/SidebarMenuSub.vue +21 -0
- package/components/ui/sidebar/SidebarMenuSubButton.vue +35 -0
- package/components/ui/sidebar/SidebarMenuSubItem.vue +8 -0
- package/components/ui/sidebar/SidebarProvider.vue +81 -0
- package/components/ui/sidebar/SidebarRail.vue +32 -0
- package/components/ui/sidebar/SidebarSeparator.vue +18 -0
- package/components/ui/sidebar/SidebarTrigger.vue +26 -0
- package/components/ui/sidebar/index.ts +60 -0
- package/components/ui/sidebar/utils.ts +19 -0
- package/components/ui/skeleton/Skeleton.vue +14 -0
- package/components/ui/skeleton/index.ts +1 -0
- package/components/ui/slider/Slider.vue +37 -0
- package/components/ui/slider/index.ts +1 -0
- package/components/ui/sonner/Sonner.vue +23 -0
- package/components/ui/sonner/index.ts +1 -0
- package/components/ui/stepper/Stepper.vue +32 -0
- package/components/ui/stepper/StepperDescription.vue +24 -0
- package/components/ui/stepper/StepperIndicator.vue +36 -0
- package/components/ui/stepper/StepperItem.vue +28 -0
- package/components/ui/stepper/StepperSeparator.vue +32 -0
- package/components/ui/stepper/StepperTitle.vue +24 -0
- package/components/ui/stepper/StepperTrigger.vue +27 -0
- package/components/ui/stepper/index.ts +7 -0
- package/components/ui/switch/Switch.vue +40 -0
- package/components/ui/switch/index.ts +1 -0
- package/components/ui/table/Table.vue +16 -0
- package/components/ui/table/TableBody.vue +14 -0
- package/components/ui/table/TableCaption.vue +14 -0
- package/components/ui/table/TableCell.vue +21 -0
- package/components/ui/table/TableEmpty.vue +38 -0
- package/components/ui/table/TableFooter.vue +14 -0
- package/components/ui/table/TableHead.vue +14 -0
- package/components/ui/table/TableHeader.vue +14 -0
- package/components/ui/table/TableRow.vue +14 -0
- package/components/ui/table/index.ts +9 -0
- package/components/ui/tabs/TabsContent.vue +6 -11
- package/components/ui/tabs/TabsList.vue +8 -8
- package/components/ui/tabs/TabsTrigger.vue +9 -11
- package/components/ui/tags-input/TagsInput.vue +24 -0
- package/components/ui/tags-input/TagsInputInput.vue +21 -0
- package/components/ui/tags-input/TagsInputItem.vue +24 -0
- package/components/ui/tags-input/TagsInputItemDelete.vue +26 -0
- package/components/ui/tags-input/TagsInputItemText.vue +21 -0
- package/components/ui/tags-input/index.ts +5 -0
- package/components/ui/textarea/Textarea.vue +1 -1
- package/components/ui/toast/Toast.vue +30 -0
- package/components/ui/toast/ToastAction.vue +21 -0
- package/components/ui/toast/ToastClose.vue +24 -0
- package/components/ui/toast/ToastDescription.vue +21 -0
- package/components/ui/toast/ToastProvider.vue +12 -0
- package/components/ui/toast/ToastTitle.vue +21 -0
- package/components/ui/toast/ToastViewport.vue +19 -0
- package/components/ui/toast/Toaster.vue +30 -0
- package/components/ui/toast/index.ts +39 -0
- package/components/ui/toast/use-toast.ts +165 -0
- package/components/ui/toggle/Toggle.vue +38 -0
- package/components/ui/toggle/index.ts +28 -0
- package/components/ui/toggle-group/ToggleGroup.vue +36 -0
- package/components/ui/toggle-group/ToggleGroupItem.vue +37 -0
- package/components/ui/toggle-group/index.ts +2 -0
- package/components/ui/tooltip/Tooltip.vue +15 -0
- package/components/ui/tooltip/TooltipContent.vue +33 -0
- package/components/ui/tooltip/TooltipProvider.vue +12 -0
- package/components/ui/tooltip/TooltipTrigger.vue +12 -0
- package/components/ui/tooltip/index.ts +4 -0
- package/components/ui/v-calendar/Calendar.vue +343 -0
- package/components/ui/v-calendar/index.ts +22 -0
- package/composables/useAccordionScroll.ts +1 -1
- package/composables/useContentSearch.ts +0 -0
- package/composables/useImageSources.ts +208 -0
- package/composables/useLightbox.ts +81 -0
- package/composables/useMainMenuLinks.ts +1 -1
- package/composables/useMessageBus.ts +74 -0
- package/composables/useMetaMenuLinks.ts +1 -1
- package/composables/useNavigationOffset.ts +1 -1
- package/layouts/default.vue +2 -2
- package/layouts/error.vue +8 -10
- package/modules/dynamicEntityComponentResolver.ts +182 -0
- package/modules/dynamicParagraphResolver.ts +1 -1
- package/nuxt.config.ts +11 -22
- package/package.json +3 -2
- package/pages/[...slug]/index.vue +2 -2
- package/pages/index.vue +2 -2
- package/pages/node/[nid]/index.vue +2 -2
- package/queries/query.contentSearch.graphql +17 -0
- package/tailwind.config.ts +1 -0
- package/types/fslightbox-vue.d.ts +1 -0
- package/composables/languageLinks.ts +0 -31
- package/modules/dynamicNodeResolver.ts +0 -95
- package/pages/search/index.vue +0 -69
- package/pages/static-page/example.vue +0 -40
- package/queries/fragment.routeQuery.graphql +0 -51
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import Popover from '@/components/ui/popover/Popover.vue'
|
|
4
|
+
import PopoverContent from '@/components/ui/popover/PopoverContent.vue'
|
|
5
|
+
import PopoverTrigger from '@/components/ui/popover/PopoverTrigger.vue'
|
|
6
|
+
|
|
7
|
+
// Incoming facet shape from GraphQL API.
|
|
8
|
+
export interface GraphQLFacetItem {
|
|
9
|
+
field: string
|
|
10
|
+
label: string
|
|
11
|
+
filter: {
|
|
12
|
+
id: string
|
|
13
|
+
label: string
|
|
14
|
+
}
|
|
15
|
+
count: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Internal normalized option shape (label == filter by default; could be mapped later)
|
|
19
|
+
interface NormalizedFacetOption { value: string; label: string; count: number }
|
|
20
|
+
|
|
21
|
+
type FacetSelections = Record<string, string[]>
|
|
22
|
+
|
|
23
|
+
const props = defineProps<{
|
|
24
|
+
facets?: GraphQLFacetItem[]
|
|
25
|
+
/** grouped selections keyed by facet field */
|
|
26
|
+
modelValue?: FacetSelections
|
|
27
|
+
placeholder?: string
|
|
28
|
+
label?: string
|
|
29
|
+
/** Total count of results for display */
|
|
30
|
+
totalCount?: number
|
|
31
|
+
/** Currently displayed count for display */
|
|
32
|
+
displayedCount?: number
|
|
33
|
+
/** Loading state */
|
|
34
|
+
loading?: boolean
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
'update:modelValue': [FacetSelections]
|
|
39
|
+
/** Emitted whenever a selection changes so parent can notify siblings */
|
|
40
|
+
'facets-change': [FacetSelections]
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
// Simple deep clone utility (data is plain: Record<string, string[]>)
|
|
44
|
+
function cloneSelections(val?: FacetSelections | null): FacetSelections {
|
|
45
|
+
try {
|
|
46
|
+
return val ? JSON.parse(JSON.stringify(val)) : {}
|
|
47
|
+
} catch {
|
|
48
|
+
// Fallback shallow copy in worst case.
|
|
49
|
+
return Object.assign({}, val || {}) as FacetSelections
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Reactive selections
|
|
54
|
+
const selections = ref<FacetSelections>(cloneSelections(props.modelValue))
|
|
55
|
+
|
|
56
|
+
// Track open state for each popover to handle chevron rotation
|
|
57
|
+
const openPopovers = ref<Record<string, boolean>>({})
|
|
58
|
+
|
|
59
|
+
// Group facets by field.
|
|
60
|
+
const grouped = computed(() => {
|
|
61
|
+
// Map key: { field, label } (label is fieldLabel if present, else field)
|
|
62
|
+
const map = new Map<{ field: string, label: string }, NormalizedFacetOption[]>()
|
|
63
|
+
const source = props.facets || []
|
|
64
|
+
for (const item of source) {
|
|
65
|
+
const field = item.field
|
|
66
|
+
const key = Array.from(map.keys()).find(k => k.field === field) || { field, label: item.label }
|
|
67
|
+
if (!map.has(key)) map.set(key, [])
|
|
68
|
+
map.get(key)!.push({
|
|
69
|
+
"value": item.filter.id,
|
|
70
|
+
"label": item.filter.label,
|
|
71
|
+
"count": item.count ,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
// Sort each group alphabetically by label for stable UI.
|
|
75
|
+
for (const [, arr] of map) arr.sort((a, b) => a.label.localeCompare(b.label))
|
|
76
|
+
return map
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Flatten for utility lookups.
|
|
80
|
+
const optionMap = computed(() => {
|
|
81
|
+
// Map key: filter string -> NormalizedFacetOption
|
|
82
|
+
const map = new Map<string, NormalizedFacetOption>()
|
|
83
|
+
for (const [, arr] of grouped.value) {
|
|
84
|
+
for (const opt of arr) {
|
|
85
|
+
map.set(opt.value, opt)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return map
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Stable array of [field, options[]] to simplify template & avoid calling entries() on undefined.
|
|
92
|
+
// Array of [{ filter, label }, options[]] for template
|
|
93
|
+
const groupEntries = computed(() => Array.from(grouped.value?.entries?.() || []))
|
|
94
|
+
|
|
95
|
+
// Per-field trigger labels
|
|
96
|
+
function fieldTriggerLabel(fieldData: { field: string, label: string }) {
|
|
97
|
+
let field: string = fieldData.field
|
|
98
|
+
let label: string = fieldData.label
|
|
99
|
+
const arr = selections.value[field] || []
|
|
100
|
+
if (!arr.length) return props.placeholder || `Select ${label}`
|
|
101
|
+
if (arr.length === 1) return optionMap.value.get(arr[0])?.label || arr[0]
|
|
102
|
+
return `${arr.length} selected`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toggle(field: string, value: string) {
|
|
106
|
+
if (!selections.value[field]) selections.value[field] = []
|
|
107
|
+
const arr = selections.value[field]
|
|
108
|
+
const idx = arr.indexOf(value)
|
|
109
|
+
if (idx === -1) arr.push(value)
|
|
110
|
+
else arr.splice(idx, 1)
|
|
111
|
+
// Remove empty arrays to keep payload small.
|
|
112
|
+
if (!arr.length) delete selections.value[field]
|
|
113
|
+
emitAll()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function clearField(field: string) {
|
|
117
|
+
if (selections.value[field]) {
|
|
118
|
+
delete selections.value[field]
|
|
119
|
+
emitAll()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function clearAll() {
|
|
124
|
+
if (Object.keys(selections.value || {}).length) {
|
|
125
|
+
selections.value = {}
|
|
126
|
+
emitAll()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function emitAll() {
|
|
131
|
+
// Clone to avoid mutation issues in parent watchers.
|
|
132
|
+
const base = selections.value && typeof selections.value === 'object' ? selections.value : {}
|
|
133
|
+
const payload: FacetSelections = JSON.parse(JSON.stringify(base))
|
|
134
|
+
emit('update:modelValue', payload)
|
|
135
|
+
emit('facets-change', payload)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Keep internal selections in sync if modelValue changes externally.
|
|
139
|
+
watch(
|
|
140
|
+
() => props.modelValue,
|
|
141
|
+
(val) => {
|
|
142
|
+
if (val) selections.value = cloneSelections(val)
|
|
143
|
+
},
|
|
144
|
+
{ deep: true }
|
|
145
|
+
)
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<template>
|
|
149
|
+
<div class="facets-ui space-y-4" v-if="groupEntries.length">
|
|
150
|
+
<!-- Clear Filters Button -->
|
|
151
|
+
<div class="flex">
|
|
152
|
+
<Button
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="sm"
|
|
155
|
+
:disabled="!(selections && Object.keys(selections || {}).length)"
|
|
156
|
+
@click="clearAll"
|
|
157
|
+
class="flex items-center gap-2"
|
|
158
|
+
>
|
|
159
|
+
{{ $texts('listing.clear_filters', 'Clear all filters') }}
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Facet Groups -->
|
|
164
|
+
<div class="flex flex-wrap gap-4">
|
|
165
|
+
<template v-for="([key, opts]) in groupEntries" :key="key.field + '-' + key.label">
|
|
166
|
+
<div class="w-72">
|
|
167
|
+
<Label class="mb-1 block text-xs font-medium uppercase tracking-wide text-muted-foreground">{{ key.label }}</Label>
|
|
168
|
+
<Popover @update:open="(open: boolean) => openPopovers[key.field] = open">
|
|
169
|
+
<PopoverTrigger as-child>
|
|
170
|
+
<Button variant="outline" class="w-full justify-between" :aria-label="`Facet ${key.label}`">
|
|
171
|
+
<span>{{ fieldTriggerLabel(key) }}</span>
|
|
172
|
+
<SpriteSymbol
|
|
173
|
+
name="chevron-down"
|
|
174
|
+
aria-hidden="true"
|
|
175
|
+
:class="[
|
|
176
|
+
'size-7 cursor-pointer text-black transition-all duration-300 group-hover/main:text-primary-500',
|
|
177
|
+
openPopovers[key.field] ? 'rotate-180' : 'rotate-0'
|
|
178
|
+
]"
|
|
179
|
+
/>
|
|
180
|
+
</Button>
|
|
181
|
+
</PopoverTrigger>
|
|
182
|
+
<PopoverContent class="p-0 w-72 bg-white" align="start">
|
|
183
|
+
<div class="max-h-72 overflow-y-auto py-1">
|
|
184
|
+
<template v-for="opt in opts" :key="opt.value">
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
class="flex w-full items-center gap-3 px-3 py-2 text-left text-sm hover:bg-muted/60 focus:bg-muted/70 focus:outline-none"
|
|
188
|
+
@click="toggle(key.field, opt.value)"
|
|
189
|
+
>
|
|
190
|
+
<Checkbox
|
|
191
|
+
:checked="selections[key.field]?.includes(opt.value)"
|
|
192
|
+
@update:checked="toggle(key.field, opt.value)"
|
|
193
|
+
class="pointer-events-none"
|
|
194
|
+
/>
|
|
195
|
+
<span class="flex-1 truncate">{{ opt.label }}</span>
|
|
196
|
+
<span class="tabular-nums text-muted-foreground text-xs">{{ opt.count }}</span>
|
|
197
|
+
</button>
|
|
198
|
+
</template>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="flex items-center justify-between border-t px-2 py-1.5 gap-2">
|
|
201
|
+
<Button size="sm" variant="ghost" :disabled="!(selections[key.field] && selections[key.field].length)" @click="clearField(key.field)">Clear</Button>
|
|
202
|
+
</div>
|
|
203
|
+
</PopoverContent>
|
|
204
|
+
</Popover>
|
|
205
|
+
</div>
|
|
206
|
+
</template>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Results Counter -->
|
|
210
|
+
<div v-if="totalCount !== undefined" class="mb-4">
|
|
211
|
+
<div class="text-lg font-medium text-gray-900">
|
|
212
|
+
<span v-if="loading">Loading...</span>
|
|
213
|
+
<span v-else>
|
|
214
|
+
{{ totalCount }} {{ totalCount === 1 ? $texts('listing.result', 'result') : $texts('listing.results', 'results') }}
|
|
215
|
+
<span v-if="displayedCount && displayedCount < totalCount" class="text-sm font-normal text-gray-600">
|
|
216
|
+
({{ $texts('listing.showing', 'result') }} {{ displayedCount }})
|
|
217
|
+
</span>
|
|
218
|
+
</span>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</template>
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<!-- Facets Section (only for 'all' listing type) -->
|
|
4
|
+
<div v-if="listingType === 'all' && facets.length > 0">
|
|
5
|
+
<SearchFacetsFacetDropdownUI
|
|
6
|
+
:facets="facetData"
|
|
7
|
+
v-model="facetSelections"
|
|
8
|
+
@facets-change="onFacetsChange"
|
|
9
|
+
:total-count="totalCount"
|
|
10
|
+
:displayed-count="displayedResults.length"
|
|
11
|
+
:loading="isLoading"
|
|
12
|
+
class="mb-6"
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<template v-if="debug">
|
|
16
|
+
<!-- Debug info (only if enabled) -->
|
|
17
|
+
<pre class="mt-4 text-xs whitespace-pre bg-gray-50 p-3 rounded" v-if="Object.keys(facetSelections).length">{{ JSON.stringify(facetSelections, null, 2) }}</pre>
|
|
18
|
+
<pre class="mt-2 text-xs whitespace-pre bg-blue-50 p-3 rounded" v-if="facetData.length">Facets: {{ facetData.length }} items</pre>
|
|
19
|
+
</template>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<template v-if="debug && listingType !== 'all'">
|
|
23
|
+
<!-- Debug info for manual/contextual filters -->
|
|
24
|
+
<pre class="mb-4 text-xs whitespace-pre bg-yellow-50 p-3 rounded">Active Filters ({{ listingType }}): {{ JSON.stringify(activeFilters, null, 2) }}</pre>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<!-- Results Section -->
|
|
28
|
+
<div class="results-container">
|
|
29
|
+
<template v-if="displayedResults.length > 0">
|
|
30
|
+
<slot name="results" :results="displayedResults" :loading="loadingMore">
|
|
31
|
+
<!-- Default slot content if no slot is provided -->
|
|
32
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
33
|
+
<div v-for="result in displayedResults" :key="result?.id || Math.random()" class="result-item">
|
|
34
|
+
<pre class="text-xs bg-gray-100 p-2 rounded">{{ JSON.stringify(result, null, 2) }}</pre>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</slot>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template v-else-if="!isLoading">
|
|
41
|
+
<slot name="no-results">
|
|
42
|
+
<div class="col-span-12 py-8 text-center text-gray-500">
|
|
43
|
+
{{ defaultNoResultsText }}
|
|
44
|
+
</div>
|
|
45
|
+
</slot>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<template v-if="isLoading">
|
|
49
|
+
<slot name="loading">
|
|
50
|
+
<div class="col-span-12 py-8 text-center text-gray-500">
|
|
51
|
+
{{ defaultLoadingText }}
|
|
52
|
+
</div>
|
|
53
|
+
</slot>
|
|
54
|
+
</template>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Load More Section -->
|
|
58
|
+
<div v-if="showLoadMore" class="mt-10 flex justify-center lg:mt-20">
|
|
59
|
+
<slot name="load-more" :loadMore="loadMore" :loading="loadingMore">
|
|
60
|
+
<button
|
|
61
|
+
@click="loadMore"
|
|
62
|
+
:disabled="loadingMore"
|
|
63
|
+
:class="props.loadMoreButtonClass || defaultButtonClass"
|
|
64
|
+
>
|
|
65
|
+
<span v-if="!loadingMore">{{ defaultLoadMoreText }}</span>
|
|
66
|
+
<span v-else>{{ defaultLoadingText }}</span>
|
|
67
|
+
</button>
|
|
68
|
+
</slot>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script lang="ts" setup>
|
|
74
|
+
import type { SearchApiQueryFilterInput } from '#graphql-operations'
|
|
75
|
+
|
|
76
|
+
export interface FacetFilterItem {
|
|
77
|
+
id: string
|
|
78
|
+
label: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ManualFilters {
|
|
82
|
+
topics?: FacetFilterItem[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface FacetedSearchUIProps {
|
|
86
|
+
/**
|
|
87
|
+
* Base filter conditions that are always applied (e.g., content type)
|
|
88
|
+
* Example: { conditions: [{ field: "type", value: ["icms_news"] }] }
|
|
89
|
+
*/
|
|
90
|
+
baseFilter: SearchApiQueryFilterInput
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Array of facet field names to request from the search API
|
|
94
|
+
* Example: ["topics", "project_phase", "locations"]
|
|
95
|
+
*/
|
|
96
|
+
facets: string[]
|
|
97
|
+
|
|
98
|
+
/** Listing type: 'all' (faceted search), 'manual' (props filters), 'contextual' (injected filters) */
|
|
99
|
+
listingType?: 'all' | 'manual' | 'contextual'
|
|
100
|
+
|
|
101
|
+
/** Manual filters for 'manual' listing type */
|
|
102
|
+
manualFilters?: ManualFilters
|
|
103
|
+
|
|
104
|
+
/** Maximum number of results to display per page */
|
|
105
|
+
itemsPerPage?: number
|
|
106
|
+
|
|
107
|
+
/** Text labels for internationalization */
|
|
108
|
+
noResultsText?: string
|
|
109
|
+
loadingText?: string
|
|
110
|
+
loadMoreText?: string
|
|
111
|
+
|
|
112
|
+
/** Enable debug mode to show facet selection info */
|
|
113
|
+
debug?: boolean
|
|
114
|
+
|
|
115
|
+
/** Color scheme for default button styling */
|
|
116
|
+
colorScheme?: 'none' | 'dark' | 'light' | 'accent'
|
|
117
|
+
|
|
118
|
+
/** Custom CSS classes for the load more button */
|
|
119
|
+
loadMoreButtonClass?: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const props = withDefaults(defineProps<FacetedSearchUIProps>(), {
|
|
123
|
+
itemsPerPage: 12,
|
|
124
|
+
debug: false,
|
|
125
|
+
colorScheme: 'none',
|
|
126
|
+
listingType: 'all'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Get the $texts function for internationalization
|
|
130
|
+
const { $texts } = useNuxtApp()
|
|
131
|
+
|
|
132
|
+
// Default text values with fallbacks
|
|
133
|
+
const defaultNoResultsText = computed(() =>
|
|
134
|
+
props.noResultsText || $texts('listing.no_results', 'Currently there is no content available.')
|
|
135
|
+
)
|
|
136
|
+
const defaultLoadingText = computed(() =>
|
|
137
|
+
props.loadingText || $texts('listing.loading', 'Loading...')
|
|
138
|
+
)
|
|
139
|
+
const defaultLoadMoreText = computed(() =>
|
|
140
|
+
props.loadMoreText || $texts('listing.load_more', 'Load more')
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// Reactive state
|
|
144
|
+
const facetSelections = ref<Record<string, string[]>>({})
|
|
145
|
+
const allFacets = ref<any[]>([])
|
|
146
|
+
const currentPage = ref(1)
|
|
147
|
+
const loadingMore = ref(false)
|
|
148
|
+
const isLoading = ref(false)
|
|
149
|
+
|
|
150
|
+
// Contextual filters (injected from parent)
|
|
151
|
+
const contextualFilters = {
|
|
152
|
+
topics: inject<FacetFilterItem[]>('topics', [])
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Get active filters based on listing type
|
|
156
|
+
const activeFilters = computed(() => {
|
|
157
|
+
if (props.listingType === 'manual' && props.manualFilters) {
|
|
158
|
+
return props.manualFilters
|
|
159
|
+
} else if (props.listingType === 'contextual') {
|
|
160
|
+
return contextualFilters
|
|
161
|
+
}
|
|
162
|
+
return {}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Build GraphQL filter from base filter and facet selections or manual/contextual filters
|
|
166
|
+
const buildSearchFilter = (selections?: Record<string, string[]>): SearchApiQueryFilterInput => {
|
|
167
|
+
const baseConditions = props.baseFilter.conditions || []
|
|
168
|
+
const conditions = [...baseConditions]
|
|
169
|
+
|
|
170
|
+
if (props.listingType === 'all') {
|
|
171
|
+
// For 'all' listing type, use facet selections from dropdowns
|
|
172
|
+
const facetSelections = selections || {}
|
|
173
|
+
for (const [field, values] of Object.entries(facetSelections)) {
|
|
174
|
+
if (values.length > 0) {
|
|
175
|
+
conditions.push({
|
|
176
|
+
field,
|
|
177
|
+
value: values
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
// For 'manual' and 'contextual' listing types, use activeFilters
|
|
183
|
+
const filters = activeFilters.value
|
|
184
|
+
for (const [field, items] of Object.entries(filters)) {
|
|
185
|
+
if (items && Array.isArray(items) && items.length > 0) {
|
|
186
|
+
const values = items.map(item => item.id).filter(Boolean)
|
|
187
|
+
if (values.length > 0) {
|
|
188
|
+
// Convert camelCase field names to snake_case for GraphQL
|
|
189
|
+
const graphqlField = field.replace(/([A-Z])/g, '_$1').toLowerCase()
|
|
190
|
+
conditions.push({
|
|
191
|
+
field: graphqlField,
|
|
192
|
+
value: values
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { conditions }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Initial query to get all available facets (only for 'all' listing type)
|
|
203
|
+
const { data: initialFacetsData } = await useAsyncData(
|
|
204
|
+
props.listingType === 'all' ? `facetedSearch-facets-${JSON.stringify(props.baseFilter)}` : 'skip-facets',
|
|
205
|
+
async () => {
|
|
206
|
+
if (props.listingType !== 'all') {
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
return await useGraphqlQuery('layoutOverviewFacetsSearch', {
|
|
210
|
+
facets: props.facets,
|
|
211
|
+
filter: props.baseFilter
|
|
212
|
+
}).then((v) => {
|
|
213
|
+
return v.data?.searchApiQuery ?? null
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// Initial query to get search results
|
|
219
|
+
const { data: initialResultsData, refresh: refreshResults } = await useAsyncData(
|
|
220
|
+
`facetedSearch-results-${props.listingType}-${JSON.stringify(props.baseFilter)}-${JSON.stringify(activeFilters.value)}`,
|
|
221
|
+
async () => {
|
|
222
|
+
const filter = buildSearchFilter()
|
|
223
|
+
const response = await useGraphqlQuery('layoutOverviewResultsSearch', {
|
|
224
|
+
filter,
|
|
225
|
+
limit: props.itemsPerPage,
|
|
226
|
+
offset: 0
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
isLoading.value = false
|
|
230
|
+
return response.data?.searchApiQuery ?? null
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Initialize facets from initial query (only for 'all' listing type)
|
|
235
|
+
if (initialFacetsData.value) {
|
|
236
|
+
allFacets.value = initialFacetsData.value.facets || []
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Function to update search results when facets change (only for 'all' listing type)
|
|
240
|
+
async function updateSearchResults(selections: Record<string, string[]>) {
|
|
241
|
+
if (props.listingType !== 'all') {
|
|
242
|
+
return // No dynamic updates for manual/contextual
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
isLoading.value = true
|
|
247
|
+
currentPage.value = 1 // Reset pagination when filters change
|
|
248
|
+
const filter = buildSearchFilter(selections)
|
|
249
|
+
|
|
250
|
+
const response = await useGraphqlQuery('layoutOverviewResultsSearch', {
|
|
251
|
+
filter,
|
|
252
|
+
limit: props.itemsPerPage,
|
|
253
|
+
offset: 0
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
if (response.data?.searchApiQuery?.results) {
|
|
257
|
+
// Update the results data to reflect new search results
|
|
258
|
+
initialResultsData.value = {
|
|
259
|
+
...initialResultsData.value,
|
|
260
|
+
results: response.data.searchApiQuery.results,
|
|
261
|
+
count: response.data.searchApiQuery.count
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Error updating search results:', error)
|
|
266
|
+
} finally {
|
|
267
|
+
isLoading.value = false
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Computed properties
|
|
272
|
+
const displayedResults = computed(() => {
|
|
273
|
+
return initialResultsData.value?.results || []
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const totalCount = computed(() => {
|
|
277
|
+
return initialResultsData.value?.count || 0
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
const showLoadMore = computed(() => {
|
|
281
|
+
const currentlyDisplayed = displayedResults.value.length
|
|
282
|
+
return currentlyDisplayed > 0 && currentlyDisplayed < totalCount.value && !isLoading.value
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// Transform allFacets data into the format expected by FacetsUI component
|
|
286
|
+
const facetData = computed(() => {
|
|
287
|
+
if (!allFacets.value || !Array.isArray(allFacets.value)) {
|
|
288
|
+
return []
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const transformedFacets: Array<{ field: string; label: string, filter: {id: string, label: string}; count: number; }> = []
|
|
292
|
+
|
|
293
|
+
allFacets.value.forEach((facet: any) => {
|
|
294
|
+
if (facet?.field && facet?.filter) {
|
|
295
|
+
transformedFacets.push({
|
|
296
|
+
field: facet.field,
|
|
297
|
+
label: facet.label,
|
|
298
|
+
filter: {
|
|
299
|
+
id: facet.filter.id,
|
|
300
|
+
label: facet.filter.label
|
|
301
|
+
},
|
|
302
|
+
count: facet.count || 0,
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
return transformedFacets
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Event handlers
|
|
311
|
+
function onFacetsChange(payload: Record<string, string[]>) {
|
|
312
|
+
facetSelections.value = payload
|
|
313
|
+
updateSearchResults(payload)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Default button styling based on color scheme
|
|
317
|
+
const defaultButtonClass = computed(() => {
|
|
318
|
+
const baseClasses = 'shadow-none h-auto block text-h7 font-medium py-3 px-20 rounded-full duration-300 ease-in-out w-full md:w-fit border disabled:opacity-50 disabled:cursor-not-allowed'
|
|
319
|
+
|
|
320
|
+
const colorSchemeClasses = {
|
|
321
|
+
none: 'bg-white text-black hover:bg-black hover:text-white border-black',
|
|
322
|
+
dark: 'bg-white text-black hover:bg-black hover:text-white border-black',
|
|
323
|
+
light: 'bg-gray-50 text-black hover:bg-black hover:text-white border-black',
|
|
324
|
+
accent: 'bg-white text-black hover:bg-black hover:text-white border-black'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return `${baseClasses} ${colorSchemeClasses[props.colorScheme] || colorSchemeClasses.none}`
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Load more handler
|
|
331
|
+
async function loadMore() {
|
|
332
|
+
if (loadingMore.value || isLoading.value) return
|
|
333
|
+
loadingMore.value = true
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const filter = props.listingType === 'all'
|
|
337
|
+
? buildSearchFilter(facetSelections.value)
|
|
338
|
+
: buildSearchFilter()
|
|
339
|
+
const offset = currentPage.value * props.itemsPerPage
|
|
340
|
+
|
|
341
|
+
const response = await useGraphqlQuery('layoutOverviewResultsSearch', {
|
|
342
|
+
filter,
|
|
343
|
+
limit: props.itemsPerPage,
|
|
344
|
+
offset
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
if (response.data?.searchApiQuery?.results) {
|
|
348
|
+
// Append new results to existing ones
|
|
349
|
+
const currentResults = initialResultsData.value?.results || []
|
|
350
|
+
const newResults = response.data.searchApiQuery.results
|
|
351
|
+
|
|
352
|
+
initialResultsData.value = {
|
|
353
|
+
...initialResultsData.value,
|
|
354
|
+
results: [...currentResults, ...newResults],
|
|
355
|
+
count: response.data.searchApiQuery.count
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
currentPage.value += 1
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.error('Error loading more results:', error)
|
|
362
|
+
} finally {
|
|
363
|
+
loadingMore.value = false
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Debug logging
|
|
368
|
+
watchEffect(() => {
|
|
369
|
+
if (props.debug) {
|
|
370
|
+
console.log('FacetedSearch - Listing type:', props.listingType)
|
|
371
|
+
|
|
372
|
+
if (props.listingType === 'all') {
|
|
373
|
+
if (facetData.value.length > 0) {
|
|
374
|
+
console.log('FacetedSearch - Transformed facet data:', facetData.value)
|
|
375
|
+
}
|
|
376
|
+
if (allFacets.value.length > 0) {
|
|
377
|
+
console.log('FacetedSearch - Raw facets from GraphQL:', allFacets.value)
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
console.log('FacetedSearch - Active filters:', activeFilters.value)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (displayedResults.value.length > 0) {
|
|
384
|
+
console.log('FacetedSearch - Results loaded:', displayedResults.value.length, 'items')
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Expose functions for parent components if needed
|
|
390
|
+
defineExpose({
|
|
391
|
+
refreshResults,
|
|
392
|
+
loadMore,
|
|
393
|
+
facetSelections: readonly(facetSelections),
|
|
394
|
+
totalCount: readonly(totalCount),
|
|
395
|
+
displayedResults: readonly(displayedResults)
|
|
396
|
+
})
|
|
397
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
query layoutOverviewFacetsSearch(
|
|
2
|
+
$facets: [String!],
|
|
3
|
+
$filter: SearchApiQueryFilterInput,
|
|
4
|
+
) {
|
|
5
|
+
searchApiQuery(
|
|
6
|
+
indexId: "content_search",
|
|
7
|
+
facets: $facets
|
|
8
|
+
filter: $filter
|
|
9
|
+
) {
|
|
10
|
+
count
|
|
11
|
+
facets {
|
|
12
|
+
field
|
|
13
|
+
label
|
|
14
|
+
filter {
|
|
15
|
+
id
|
|
16
|
+
label
|
|
17
|
+
}
|
|
18
|
+
count
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
query layoutOverviewResultsSearch(
|
|
24
|
+
$filter: SearchApiQueryFilterInput,
|
|
25
|
+
$limit: Int,
|
|
26
|
+
$offset: Int
|
|
27
|
+
) {
|
|
28
|
+
searchApiQuery(
|
|
29
|
+
indexId: "content_search",
|
|
30
|
+
filter: $filter
|
|
31
|
+
limit: $limit
|
|
32
|
+
offset: $offset
|
|
33
|
+
) {
|
|
34
|
+
count
|
|
35
|
+
results {
|
|
36
|
+
bundle: __typename
|
|
37
|
+
...entityTeaser
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|