@salesforce/templates 66.17.5 → 66.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/generators/projectGenerator.js +3 -13
- package/lib/generators/projectGenerator.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +205 -205
- package/lib/templates/project/angularintapp/CHANGELOG.md +8 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +2 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +212 -212
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +4 -4
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/components/ui/icon/icon.ts +44 -0
- package/lib/templates/project/angularintapp/package.json +1 -1
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +8 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/eslint.config.js +8 -12
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +188 -188
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +5 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/sonner.tsx +7 -1
- package/lib/templates/project/reactexternalapp/package.json +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +186 -186
- package/lib/templates/project/vueinternalapp/.forceignore +15 -0
- package/lib/templates/project/vueinternalapp/.husky/pre-commit +4 -0
- package/lib/templates/project/vueinternalapp/.prettierignore +11 -0
- package/lib/templates/project/vueinternalapp/.prettierrc +17 -0
- package/lib/templates/project/vueinternalapp/CHANGELOG.md +5375 -0
- package/lib/templates/project/vueinternalapp/PRD.md +35 -0
- package/lib/templates/project/vueinternalapp/README.md +125 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_ps_/vueinternalapp_Access.permissionset-meta.xml +13 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/.forceignore +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/.graphqlrc.yml +2 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/.prettierignore +9 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/.prettierrc +11 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/README.md +35 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/codegen.yml +95 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/components.json +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/e2e/app.spec.ts +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/eslint.config.js +124 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/index.html +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/package-lock.json +15530 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/package.json +73 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/playwright.config.ts +24 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +71 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/scripts/rewrite-e2e-assets.mjs +23 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/App.vue +5 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +119 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +108 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/composables/useSearchableContentTypes.ts +132 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +40 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +55 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +55 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +103 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +81 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +136 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.types.ts +18 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.vue +73 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.types.ts +24 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.vue +263 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.vue +200 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.vue +99 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.vue +204 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.vue +180 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.vue +91 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.vue +56 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.vue +55 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.vue +59 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterField.vue +63 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/FilterContext.ts +45 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/FilterProvider.vue +23 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/FilterResetButton.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/PicklistField.vue +35 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.vue +47 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.vue +144 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/FilterFieldWrapper.vue +23 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.vue +95 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.vue +117 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.vue +44 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.vue +49 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/CmsResultRow.vue +160 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.vue +102 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/RenderVNode.ts +10 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/resolveResultRenderer.ts +41 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/composables/useAsyncData.ts +66 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/composables/useDistinctValues.ts +67 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/composables/useSearch.ts +906 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +100 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +375 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +304 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/getAccountDetail.graphql +121 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +44 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/appLayout.vue +153 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.vue +76 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/AccountDetailContent.vue +124 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/AccountDetailError.vue +28 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/AccountDetailNotFound.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/AccountDetailSkeleton.vue +57 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/DetailSection.vue +25 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/FieldItem.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/FieldRow.vue +5 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/SkeletonFieldRow.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/TelephoneField.vue +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/account/types.ts +42 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/alerts/status-alert.vue +59 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/layouts/card-layout.vue +30 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/alert/Alert.vue +21 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/alert/AlertAction.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/alert/AlertDescription.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/alert/AlertTitle.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/alert/index.ts +25 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar/Avatar.vue +23 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar/AvatarFallback.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar/AvatarImage.vue +16 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/avatar/index.ts +3 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button/Button.vue +33 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button/index.ts +43 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/Card.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardAction.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardContent.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardDescription.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardFooter.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardHeader.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/CardTitle.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/card/index.ts +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/checkbox/Checkbox.vue +43 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/checkbox/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/collapsible/Collapsible.vue +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/collapsible/CollapsibleContent.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/collapsible/index.ts +3 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/Dialog.vue +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogClose.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogContent.vue +59 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogDescription.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogFooter.vue +33 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogHeader.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogOverlay.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogPortal.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogTitle.vue +26 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/DialogTrigger.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dialog/index.ts +10 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +51 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuItem.vue +44 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +30 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +52 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +26 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +37 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +38 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/dropdown-menu/index.ts +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/Field.vue +27 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldContent.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldDescription.vue +24 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldError.vue +48 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldGroup.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldLabel.vue +24 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldLegend.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldSeparator.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldSet.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/FieldTitle.vue +22 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/field/index.ts +33 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/index.ts +115 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/input/Input.vue +30 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/input/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/label/Label.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/label/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/Pagination.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationContent.vue +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationEllipsis.vue +23 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationItem.vue +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationLink.vue +31 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationNext.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/PaginationPrevious.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/pagination/index.ts +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/Popover.vue +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverAnchor.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverContent.vue +44 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverDescription.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverHeader.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverTitle.vue +14 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/PopoverTrigger.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/popover/index.ts +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/Select.vue +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectContent.vue +65 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectGroup.vue +26 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectItem.vue +41 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectLabel.vue +26 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectScrollDownButton.vue +32 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectScrollUpButton.vue +32 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectSeparator.vue +24 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectTrigger.vue +45 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/SelectValue.vue +12 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/select/index.ts +10 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/separator/Separator.vue +33 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/separator/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/skeleton/Skeleton.vue +15 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/skeleton/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/sonner/Sonner.vue +29 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/sonner/index.ts +3 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/spinner/Spinner.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/components/ui/spinner/index.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/composables/useAsyncData.ts +69 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/index.ts +10 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/lib/utils.ts +6 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/main.ts +6 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.vue +98 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.vue +20 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/pages/NotFound.vue +28 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/pages/SearchPage.vue +17 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/router-utils.ts +32 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/router.ts +51 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/styles/global.css +134 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/test/setup.ts +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/types/conversation.ts +54 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/types/globals.d.ts +13 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/src/utils/accountFields.ts +25 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +45 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/tsconfig.node.json +13 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/ui-bundle.json +7 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/vite-env.d.ts +14 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/vite.config.ts +106 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/vitest-env.d.ts +2 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/_w_/_a_/vueinternalapp.uibundle-meta.xml +8 -0
- package/lib/templates/project/vueinternalapp/_p_/_m_/applications/vueinternalapp.app-meta.xml +17 -0
- package/lib/templates/project/vueinternalapp/config/project-scratch-def.json +13 -0
- package/lib/templates/project/vueinternalapp/eslint.config.js +7 -0
- package/lib/templates/project/vueinternalapp/jest.config.js +6 -0
- package/lib/templates/project/vueinternalapp/package.json +46 -0
- package/lib/templates/project/vueinternalapp/scripts/apex/hello.apex +10 -0
- package/lib/templates/project/vueinternalapp/scripts/gitignore-templates.json +4 -0
- package/lib/templates/project/vueinternalapp/scripts/graphql-search.sh +191 -0
- package/lib/templates/project/vueinternalapp/scripts/network-senders.mjs +84 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup-config-schema.mjs +132 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup-dev.mjs +114 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup-url.mjs +108 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup-xml.mjs +283 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup.config.json +9 -0
- package/lib/templates/project/vueinternalapp/scripts/org-setup.mjs +2545 -0
- package/lib/templates/project/vueinternalapp/scripts/sf-project-setup.mjs +103 -0
- package/lib/templates/project/vueinternalapp/scripts/soql/account.soql +6 -0
- package/lib/templates/project/vueinternalapp/scripts/validate-org-setup-config.mjs +38 -0
- package/lib/templates/project/vueinternalapp/sfdx-project.json +12 -0
- package/lib/templates/uiBundles/angularbasic/package-lock.json +205 -205
- package/lib/templates/uiBundles/reactbasic/eslint.config.js +8 -12
- package/lib/templates/uiBundles/reactbasic/package-lock.json +188 -188
- package/lib/templates/uiBundles/reactbasic/package.json +5 -5
- package/lib/templates/uiBundles/reactbasic/src/components/ui/button.tsx +1 -1
- package/lib/templates/uiBundles/reactbasic/src/components/ui/sonner.tsx +7 -1
- package/lib/templates/uiBundles/vuebasic/eslint.config.js +6 -1
- package/lib/templates/uiBundles/vuebasic/package-lock.json +198 -198
- package/lib/templates/uiBundles/vuebasic/package.json +5 -5
- package/lib/utils/projectTemplates.d.ts +6 -0
- package/lib/utils/projectTemplates.js +26 -0
- package/lib/utils/projectTemplates.js.map +1 -0
- package/lib/utils/types.d.ts +2 -1
- package/lib/utils/types.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.js +5 -0
- package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Renders one source's slice of a search result: optional filter panel, sort
|
|
4
|
+
* dropdown, active-filter chips, the result list (delegated to the caller via
|
|
5
|
+
* `renderResult`), and pagination controls.
|
|
6
|
+
*
|
|
7
|
+
* The component is "smart" only about orchestration. The actual list-item UI is
|
|
8
|
+
* owned by the application via the `renderResult(node)` render function — search
|
|
9
|
+
* never decides how an Account or Contact card looks.
|
|
10
|
+
*/
|
|
11
|
+
import { computed, type VNodeChild } from "vue";
|
|
12
|
+
import { AlertCircle, Search, SearchX } from "@lucide/vue";
|
|
13
|
+
import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
|
|
14
|
+
import { Skeleton } from "../../../components/ui/skeleton";
|
|
15
|
+
import ActiveFilters from "./filters/ActiveFilters.vue";
|
|
16
|
+
import FilterProvider from "./filters/FilterProvider.vue";
|
|
17
|
+
import FilterResetButton from "./filters/FilterResetButton.vue";
|
|
18
|
+
import PaginationControls from "./controls/PaginationControls.vue";
|
|
19
|
+
import SortControl from "./controls/SortControl.vue";
|
|
20
|
+
import { RenderVNode } from "./results/RenderVNode";
|
|
21
|
+
import { MIN_QUERY_LENGTH } from "../constants";
|
|
22
|
+
import type { SourceController } from "../types";
|
|
23
|
+
|
|
24
|
+
function getNodeKey(node: unknown, idField: string | undefined, fallback: number): string | number {
|
|
25
|
+
if (node && typeof node === "object") {
|
|
26
|
+
const id = (node as Record<string, unknown>)[idField ?? "Id"];
|
|
27
|
+
if (typeof id === "string" || typeof id === "number") return id;
|
|
28
|
+
}
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(
|
|
33
|
+
defineProps<{
|
|
34
|
+
controller: SourceController;
|
|
35
|
+
renderResult: (node: unknown) => VNodeChild;
|
|
36
|
+
/** Optional filter UI (TextFilter, SelectFilter, ...) rendered above the results. */
|
|
37
|
+
renderFilters?: () => VNodeChild;
|
|
38
|
+
/**
|
|
39
|
+
* Suppress the filter panel, the active-filter chips, and the sort dropdown.
|
|
40
|
+
* Pre-existing filter / sort selections stay in state (and apply to the
|
|
41
|
+
* GraphQL query) so the caller can restore the chrome without losing them.
|
|
42
|
+
* Set by `<SearchResults>` when the current scope is "all".
|
|
43
|
+
*/
|
|
44
|
+
hideFilterChrome?: boolean;
|
|
45
|
+
emptyMessage?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The current search query. Used to distinguish a not-yet-started search
|
|
48
|
+
* (query below MIN_QUERY_LENGTH, so no fetch was issued) from a genuine
|
|
49
|
+
* zero-match result, so the empty state can prompt instead of claiming
|
|
50
|
+
* "No … found."
|
|
51
|
+
*/
|
|
52
|
+
q?: string;
|
|
53
|
+
/** Message shown when the query is below MIN_QUERY_LENGTH. Defaults to "Start your search…" */
|
|
54
|
+
shortQueryMessage?: string;
|
|
55
|
+
class?: string;
|
|
56
|
+
}>(),
|
|
57
|
+
{ q: "", shortQueryMessage: "Start your search…" },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const config = computed(() => props.controller.config);
|
|
61
|
+
const nodes = computed(() => props.controller.result?.nodes ?? []);
|
|
62
|
+
const totalCount = computed(() => props.controller.result?.totalCount);
|
|
63
|
+
const loading = computed(() => props.controller.loading);
|
|
64
|
+
const error = computed(() => props.controller.error);
|
|
65
|
+
const showResults = computed(() => !loading.value && !error.value && nodes.value.length > 0);
|
|
66
|
+
const showEmpty = computed(() => !loading.value && !error.value && nodes.value.length === 0);
|
|
67
|
+
// A query below MIN_QUERY_LENGTH never triggers a fetch, so an empty result
|
|
68
|
+
// means "not searched yet" — prompt rather than report zero matches.
|
|
69
|
+
const isShortQuery = computed(() => props.q.trim().length < MIN_QUERY_LENGTH);
|
|
70
|
+
|
|
71
|
+
const pageSizeOptions = computed(
|
|
72
|
+
() => props.controller.pagination.pageSizeOptions ?? [props.controller.pagination.pageSize],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// `sortBy` / `idField` are SObject-only — narrow on `kind` so the union
|
|
76
|
+
// widening of `config` doesn't regress SObject behavior (CMS has neither).
|
|
77
|
+
const sortBy = computed(() => (config.value.kind === "sobject" ? config.value.sortBy : undefined));
|
|
78
|
+
const idField = computed(() =>
|
|
79
|
+
config.value.kind === "sobject" ? config.value.idField : undefined,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const showFilterPanel = computed(() => !!props.renderFilters && !props.hideFilterChrome);
|
|
83
|
+
const showActiveChips = computed(
|
|
84
|
+
() => !props.hideFilterChrome && props.controller.filters.active.length > 0,
|
|
85
|
+
);
|
|
86
|
+
const showSortControl = computed(() => !props.hideFilterChrome && (sortBy.value?.length ?? 0) > 0);
|
|
87
|
+
|
|
88
|
+
const emptyText = computed(() =>
|
|
89
|
+
isShortQuery.value
|
|
90
|
+
? props.shortQueryMessage
|
|
91
|
+
: (props.emptyMessage ?? `No ${config.value.label.toLowerCase()} found.`),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Persistent, always-mounted live region: screen readers announce result
|
|
95
|
+
// updates (searching → count → empty → error) without an Enter keypress.
|
|
96
|
+
const liveMessage = computed(() => {
|
|
97
|
+
if (loading.value) return "Searching…";
|
|
98
|
+
if (error.value) return `Failed to load ${config.value.label}.`;
|
|
99
|
+
if (showEmpty.value) return emptyText.value;
|
|
100
|
+
if (totalCount.value != null) {
|
|
101
|
+
return `${config.value.label}: ${totalCount.value} ${totalCount.value === 1 ? "result" : "results"}`;
|
|
102
|
+
}
|
|
103
|
+
return "";
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const resetFilters = () =>
|
|
107
|
+
props.controller.filters.active.forEach((f) => props.controller.filters.remove(f.field));
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<section :class="['flex flex-col gap-6', $props.class]">
|
|
112
|
+
<p class="sr-only" role="status" aria-live="polite">{{ liveMessage }}</p>
|
|
113
|
+
|
|
114
|
+
<FilterProvider
|
|
115
|
+
v-if="showFilterPanel"
|
|
116
|
+
:filters="controller.filters.active"
|
|
117
|
+
@filter-change="controller.filters.set"
|
|
118
|
+
@filter-remove="controller.filters.remove"
|
|
119
|
+
@reset="resetFilters"
|
|
120
|
+
>
|
|
121
|
+
<div class="rounded-md border p-3 space-y-3">
|
|
122
|
+
<div class="flex items-center justify-between">
|
|
123
|
+
<h2 class="text-sm font-semibold">Filters</h2>
|
|
124
|
+
<FilterResetButton size="sm" />
|
|
125
|
+
</div>
|
|
126
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
127
|
+
<RenderVNode :render="renderFilters!" />
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</FilterProvider>
|
|
131
|
+
|
|
132
|
+
<div class="min-w-0">
|
|
133
|
+
<header class="flex flex-wrap items-baseline gap-3 mb-3">
|
|
134
|
+
<h2 class="text-lg font-semibold">{{ config.label }}</h2>
|
|
135
|
+
</header>
|
|
136
|
+
|
|
137
|
+
<div v-if="showSortControl || showActiveChips" class="flex flex-wrap items-center gap-2 mb-3">
|
|
138
|
+
<SortControl
|
|
139
|
+
v-if="showSortControl && sortBy"
|
|
140
|
+
:configs="sortBy"
|
|
141
|
+
:sort="controller.sort.current"
|
|
142
|
+
@sort-change="controller.sort.set"
|
|
143
|
+
/>
|
|
144
|
+
<ActiveFilters
|
|
145
|
+
v-if="showActiveChips"
|
|
146
|
+
:filters="controller.filters.active"
|
|
147
|
+
@remove="controller.filters.remove"
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Result count, shown just above the results in every scope —
|
|
152
|
+
including a locked / single-source (restrictTo) view. -->
|
|
153
|
+
<p v-if="!loading && !error && totalCount != null" class="text-sm text-muted-foreground mb-3">
|
|
154
|
+
{{ totalCount }} {{ totalCount === 1 ? "result" : "results" }}
|
|
155
|
+
</p>
|
|
156
|
+
|
|
157
|
+
<div class="min-h-32">
|
|
158
|
+
<div v-if="loading" class="space-y-2">
|
|
159
|
+
<Skeleton
|
|
160
|
+
v-for="i in Math.min(controller.pagination.pageSize, 5)"
|
|
161
|
+
:key="i"
|
|
162
|
+
class="h-12 w-full"
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<Alert v-if="error" variant="destructive" role="alert">
|
|
167
|
+
<AlertCircle aria-hidden="true" />
|
|
168
|
+
<AlertTitle>Failed to load {{ config.label }}</AlertTitle>
|
|
169
|
+
<AlertDescription>{{ error }}</AlertDescription>
|
|
170
|
+
</Alert>
|
|
171
|
+
|
|
172
|
+
<ul v-if="showResults" role="list" class="divide-y">
|
|
173
|
+
<li v-for="(node, i) in nodes" :key="getNodeKey(node, idField, i)" class="py-3">
|
|
174
|
+
<RenderVNode :render="() => renderResult(node)" />
|
|
175
|
+
</li>
|
|
176
|
+
</ul>
|
|
177
|
+
|
|
178
|
+
<div v-if="showEmpty" class="flex flex-col items-center justify-center py-10 text-center">
|
|
179
|
+
<Search
|
|
180
|
+
v-if="isShortQuery"
|
|
181
|
+
aria-hidden="true"
|
|
182
|
+
class="size-10 text-muted-foreground mb-3"
|
|
183
|
+
/>
|
|
184
|
+
<SearchX v-else aria-hidden="true" class="size-10 text-muted-foreground mb-3" />
|
|
185
|
+
<p class="text-sm text-muted-foreground">{{ emptyText }}</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<PaginationControls
|
|
190
|
+
:page-index="controller.pagination.pageIndex"
|
|
191
|
+
:has-next-page="controller.pagination.hasNextPage"
|
|
192
|
+
:has-previous-page="controller.pagination.hasPreviousPage"
|
|
193
|
+
:page-size="controller.pagination.pageSize"
|
|
194
|
+
:page-size-options="pageSizeOptions"
|
|
195
|
+
:on-next-page="controller.pagination.goToNextPage"
|
|
196
|
+
:on-previous-page="controller.pagination.goToPreviousPage"
|
|
197
|
+
:on-page-size-change="controller.pagination.setPageSize"
|
|
198
|
+
:disabled="loading || !!error"
|
|
199
|
+
:id-prefix="`page-size-${config.key}`"
|
|
200
|
+
:nav-label="`${config.label} pagination`"
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
</section>
|
|
204
|
+
</template>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
Pagination,
|
|
5
|
+
PaginationContent,
|
|
6
|
+
PaginationEllipsis,
|
|
7
|
+
PaginationItem,
|
|
8
|
+
PaginationLink,
|
|
9
|
+
PaginationNext,
|
|
10
|
+
PaginationPrevious,
|
|
11
|
+
} from "../../../../components/ui/pagination";
|
|
12
|
+
import {
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from "../../../../components/ui/select";
|
|
19
|
+
import { Label } from "../../../../components/ui/label";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Callbacks are declared as props (`onNextPage`, ...) rather than emits so that
|
|
23
|
+
* presence of `onGoToPage` can gate the numbered page buttons. `@next-page="fn"`
|
|
24
|
+
* and `:on-next-page="fn"` are equivalent.
|
|
25
|
+
*/
|
|
26
|
+
const props = withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
pageIndex: number;
|
|
29
|
+
hasNextPage: boolean;
|
|
30
|
+
hasPreviousPage: boolean;
|
|
31
|
+
pageSize: number;
|
|
32
|
+
pageSizeOptions: readonly number[];
|
|
33
|
+
onNextPage: () => void;
|
|
34
|
+
onPreviousPage: () => void;
|
|
35
|
+
onPageSizeChange: (size: number) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Total number of pages. When provided (with `onGoToPage`) and `> 1`, the
|
|
38
|
+
* control renders numbered page buttons with ellipsis truncation instead of
|
|
39
|
+
* a plain "Page N" label. Omit for cursor-style sources where the total page
|
|
40
|
+
* count is unknown.
|
|
41
|
+
*/
|
|
42
|
+
pageCount?: number;
|
|
43
|
+
/** Jump to a 0-based page. Required to render numbered page buttons. */
|
|
44
|
+
onGoToPage?: (pageIndex: number) => void;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
idPrefix?: string;
|
|
47
|
+
/** Accessible name for the pagination `<nav>` landmark; keep it unique per instance (WCAG 1.3.1, landmark-unique). */
|
|
48
|
+
navLabel?: string;
|
|
49
|
+
}>(),
|
|
50
|
+
{ disabled: false, idPrefix: "page-size", navLabel: "Pagination" },
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds the list of page tokens to render: 0-based page numbers plus
|
|
55
|
+
* "ellipsis" gap markers. Always shows the first and last page, a window
|
|
56
|
+
* around the current page, and collapses the rest. e.g. for current=5,
|
|
57
|
+
* total=12 -> [0, ellipsis, 4, 5, 6, ellipsis, 11].
|
|
58
|
+
*/
|
|
59
|
+
function getPageTokens(currentIndex: number, pageCount: number): Array<number | "ellipsis"> {
|
|
60
|
+
const SIBLINGS = 1; // pages shown on each side of the current page
|
|
61
|
+
const last = pageCount - 1;
|
|
62
|
+
const pages = new Set<number>([0, last]);
|
|
63
|
+
for (let p = currentIndex - SIBLINGS; p <= currentIndex + SIBLINGS; p++) {
|
|
64
|
+
if (p >= 0 && p <= last) pages.add(p);
|
|
65
|
+
}
|
|
66
|
+
const sorted = [...pages].sort((a, b) => a - b);
|
|
67
|
+
const tokens: Array<number | "ellipsis"> = [];
|
|
68
|
+
let prev = -1;
|
|
69
|
+
for (const p of sorted) {
|
|
70
|
+
if (prev !== -1 && p - prev > 1) tokens.push("ellipsis");
|
|
71
|
+
tokens.push(p);
|
|
72
|
+
prev = p;
|
|
73
|
+
}
|
|
74
|
+
return tokens;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handlePageSizeChange = (next: unknown) => {
|
|
78
|
+
const size = parseInt(String(next), 10);
|
|
79
|
+
if (!isNaN(size) && size !== props.pageSize) props.onPageSizeChange(size);
|
|
80
|
+
};
|
|
81
|
+
const prevDisabled = computed(() => props.disabled || !props.hasPreviousPage);
|
|
82
|
+
const nextDisabled = computed(() => props.disabled || !props.hasNextPage);
|
|
83
|
+
const selectId = computed(() => `${props.idPrefix}-select`);
|
|
84
|
+
|
|
85
|
+
// Numbered page buttons require both a known page count and a seek callback.
|
|
86
|
+
const showNumberedPages = computed(
|
|
87
|
+
() => props.pageCount != null && props.pageCount > 1 && props.onGoToPage != null,
|
|
88
|
+
);
|
|
89
|
+
const tokens = computed(() =>
|
|
90
|
+
showNumberedPages.value ? getPageTokens(props.pageIndex, props.pageCount as number) : [],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// The underlying pagination control renders an <a> without an href, so it is
|
|
94
|
+
// not keyboard-operable on its own. Activate it on Enter/Space to match the
|
|
95
|
+
// behaviour of a native button (WCAG 2.2 SC 2.1.1 Keyboard).
|
|
96
|
+
const activationKey = (action: () => void) => (event: KeyboardEvent) => {
|
|
97
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
action();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<div class="w-full grid grid-cols-1 sm:grid-cols-2 items-center gap-4 py-2">
|
|
106
|
+
<div class="flex justify-center sm:justify-start items-center gap-2">
|
|
107
|
+
<Label :for="selectId" class="text-sm font-normal whitespace-nowrap">Results per page:</Label>
|
|
108
|
+
<Select :model-value="pageSize.toString()" @update:model-value="handlePageSizeChange">
|
|
109
|
+
<SelectTrigger :id="selectId" class="w-16">
|
|
110
|
+
<SelectValue />
|
|
111
|
+
</SelectTrigger>
|
|
112
|
+
<SelectContent>
|
|
113
|
+
<SelectItem v-for="size in pageSizeOptions" :key="size" :value="size.toString()">
|
|
114
|
+
{{ size }}
|
|
115
|
+
</SelectItem>
|
|
116
|
+
</SelectContent>
|
|
117
|
+
</Select>
|
|
118
|
+
</div>
|
|
119
|
+
<Pagination :aria-label="navLabel" class="w-full mx-0 sm:justify-end">
|
|
120
|
+
<PaginationContent>
|
|
121
|
+
<PaginationItem>
|
|
122
|
+
<PaginationPrevious
|
|
123
|
+
role="button"
|
|
124
|
+
:tabindex="prevDisabled ? -1 : 0"
|
|
125
|
+
:aria-disabled="prevDisabled"
|
|
126
|
+
:class="prevDisabled ? 'pointer-events-none opacity-50' : 'cursor-pointer'"
|
|
127
|
+
@click="prevDisabled ? undefined : onPreviousPage()"
|
|
128
|
+
@keydown="prevDisabled ? undefined : activationKey(onPreviousPage)($event)"
|
|
129
|
+
/>
|
|
130
|
+
</PaginationItem>
|
|
131
|
+
|
|
132
|
+
<template v-if="showNumberedPages">
|
|
133
|
+
<template
|
|
134
|
+
v-for="(token, i) in tokens"
|
|
135
|
+
:key="token === 'ellipsis' ? `ellipsis-${i}` : token"
|
|
136
|
+
>
|
|
137
|
+
<PaginationItem v-if="token === 'ellipsis'">
|
|
138
|
+
<PaginationEllipsis />
|
|
139
|
+
</PaginationItem>
|
|
140
|
+
<PaginationItem v-else>
|
|
141
|
+
<PaginationLink
|
|
142
|
+
role="button"
|
|
143
|
+
:tabindex="disabled ? -1 : 0"
|
|
144
|
+
:is-active="token === pageIndex"
|
|
145
|
+
:aria-label="`Go to page ${token + 1}`"
|
|
146
|
+
:aria-current="token === pageIndex ? 'page' : undefined"
|
|
147
|
+
:aria-disabled="disabled"
|
|
148
|
+
:class="disabled ? 'pointer-events-none opacity-50' : 'cursor-pointer'"
|
|
149
|
+
@click="disabled ? undefined : onGoToPage?.(token)"
|
|
150
|
+
@keydown="disabled ? undefined : activationKey(() => onGoToPage?.(token))($event)"
|
|
151
|
+
>
|
|
152
|
+
{{ token + 1 }}
|
|
153
|
+
</PaginationLink>
|
|
154
|
+
</PaginationItem>
|
|
155
|
+
</template>
|
|
156
|
+
</template>
|
|
157
|
+
<PaginationItem v-else>
|
|
158
|
+
<span class="min-w-16 text-center text-sm text-muted-foreground px-2">
|
|
159
|
+
{{
|
|
160
|
+
pageCount != null && pageCount > 0
|
|
161
|
+
? `Page ${pageIndex + 1} of ${pageCount}`
|
|
162
|
+
: `Page ${pageIndex + 1}`
|
|
163
|
+
}}
|
|
164
|
+
</span>
|
|
165
|
+
</PaginationItem>
|
|
166
|
+
|
|
167
|
+
<PaginationItem>
|
|
168
|
+
<PaginationNext
|
|
169
|
+
role="button"
|
|
170
|
+
:tabindex="nextDisabled ? -1 : 0"
|
|
171
|
+
:aria-disabled="nextDisabled"
|
|
172
|
+
:class="nextDisabled ? 'pointer-events-none opacity-50' : 'cursor-pointer'"
|
|
173
|
+
@click="nextDisabled ? undefined : onNextPage()"
|
|
174
|
+
@keydown="nextDisabled ? undefined : activationKey(onNextPage)($event)"
|
|
175
|
+
/>
|
|
176
|
+
</PaginationItem>
|
|
177
|
+
</PaginationContent>
|
|
178
|
+
</Pagination>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Dropdown that narrows the search to a single source (or "All").
|
|
4
|
+
*
|
|
5
|
+
* Sits next to the search bar in the default `<Search>` layout. The options are
|
|
6
|
+
* derived from the configured sources: an "All" entry plus one entry per source
|
|
7
|
+
* (using each source's `label`). When CMS content types have been discovered at
|
|
8
|
+
* runtime, one extra entry per type is appended under its CMS source — value
|
|
9
|
+
* `"<cmsKey>:<fqn>"`, label the server-provided content-type label — so a user
|
|
10
|
+
* can narrow a CMS search to a single content type.
|
|
11
|
+
*
|
|
12
|
+
* Selecting a non-"All" scope tells `useSearch` to fetch and render only that
|
|
13
|
+
* source. Switching scope resets per-source pagination cursors.
|
|
14
|
+
*/
|
|
15
|
+
import { computed } from "vue";
|
|
16
|
+
import {
|
|
17
|
+
Select,
|
|
18
|
+
SelectContent,
|
|
19
|
+
SelectItem,
|
|
20
|
+
SelectTrigger,
|
|
21
|
+
SelectValue,
|
|
22
|
+
} from "../../../../components/ui/select";
|
|
23
|
+
import { ALL_SCOPE, CMS_SCOPE_SEPARATOR, type SearchConfig, type SearchScope } from "../../types";
|
|
24
|
+
import type { DiscoveredContentType } from "../../adapters/cms/contentTypeUtils";
|
|
25
|
+
import { isCmsSearchSupported } from "../../adapters/cms/api/orgApiVersionService";
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(
|
|
28
|
+
defineProps<{
|
|
29
|
+
config: SearchConfig;
|
|
30
|
+
scope: SearchScope;
|
|
31
|
+
allLabel?: string;
|
|
32
|
+
/**
|
|
33
|
+
* CMS content types discovered at runtime (from `SearchHandle.discoveredContentTypes`).
|
|
34
|
+
* Appended as per-type entries under the CMS source once discovery completes.
|
|
35
|
+
*/
|
|
36
|
+
discoveredContentTypes?: DiscoveredContentType[];
|
|
37
|
+
class?: string;
|
|
38
|
+
}>(),
|
|
39
|
+
{ allLabel: "All", discoveredContentTypes: () => [] },
|
|
40
|
+
);
|
|
41
|
+
const emit = defineEmits<{ scopeChange: [scope: SearchScope] }>();
|
|
42
|
+
|
|
43
|
+
// Discovered content types attach to the (single) CMS source. Entries are
|
|
44
|
+
// injected only after discovery, so the dropdown starts with just the config
|
|
45
|
+
// sources and grows once the CMS channel's types are known.
|
|
46
|
+
const cmsSource = computed(() => props.config.sources.find((s) => s.kind === "cms"));
|
|
47
|
+
const hasDiscoveredTypes = computed(
|
|
48
|
+
() => !!cmsSource.value && props.discoveredContentTypes.length > 0,
|
|
49
|
+
);
|
|
50
|
+
const contentTypeEntries = computed(() =>
|
|
51
|
+
hasDiscoveredTypes.value
|
|
52
|
+
? props.discoveredContentTypes.map((ct) => ({
|
|
53
|
+
value: `${cmsSource.value!.key}${CMS_SCOPE_SEPARATOR}${ct.fqn}`,
|
|
54
|
+
label: ct.label,
|
|
55
|
+
}))
|
|
56
|
+
: [],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const visibleSources = computed(() =>
|
|
60
|
+
props.config.sources.filter((source) => {
|
|
61
|
+
// CMS search is skipped entirely on builds below the CMS-search API version,
|
|
62
|
+
// so its scope entry would only ever yield an empty result. Hide it.
|
|
63
|
+
if (source.kind === "cms" && !isCmsSearchSupported()) return false;
|
|
64
|
+
// Once the CMS channel's content types are known, the generic CMS source
|
|
65
|
+
// entry is redundant — drop it, EXCEPT while it is the current scope, so the
|
|
66
|
+
// controlled select never holds a value no item represents (WCAG 4.1.2).
|
|
67
|
+
if (source.kind === "cms" && hasDiscoveredTypes.value && props.scope !== source.key)
|
|
68
|
+
return false;
|
|
69
|
+
return true;
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<div v-if="config.sources.length > 0" :class="$props.class">
|
|
76
|
+
<Select :model-value="scope" @update:model-value="(v) => emit('scopeChange', String(v))">
|
|
77
|
+
<SelectTrigger size="sm" class="min-w-[140px]" aria-label="Search scope">
|
|
78
|
+
<SelectValue />
|
|
79
|
+
</SelectTrigger>
|
|
80
|
+
<SelectContent>
|
|
81
|
+
<SelectItem :value="ALL_SCOPE">{{ allLabel }}</SelectItem>
|
|
82
|
+
<SelectItem v-for="source in visibleSources" :key="source.key" :value="source.key">
|
|
83
|
+
{{ source.label }}
|
|
84
|
+
</SelectItem>
|
|
85
|
+
<SelectItem v-for="entry in contentTypeEntries" :key="entry.value" :value="entry.value">
|
|
86
|
+
{{ entry.label }}
|
|
87
|
+
</SelectItem>
|
|
88
|
+
</SelectContent>
|
|
89
|
+
</Select>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Debounced text input for the global `q` term. Keeps a local `draft` so
|
|
4
|
+
* keystrokes feel responsive while the upstream `change` is rate-limited.
|
|
5
|
+
*/
|
|
6
|
+
import { ref, watch } from "vue";
|
|
7
|
+
import { Search } from "@lucide/vue";
|
|
8
|
+
import { Input } from "../../../../components/ui/input";
|
|
9
|
+
import { debounce } from "../../utils/debounce";
|
|
10
|
+
|
|
11
|
+
const TYPE_DEBOUNCE_MS = 250;
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<{
|
|
15
|
+
value: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
}>(),
|
|
19
|
+
{ placeholder: "Search…" },
|
|
20
|
+
);
|
|
21
|
+
const emit = defineEmits<{ change: [next: string] }>();
|
|
22
|
+
|
|
23
|
+
const draft = ref(props.value);
|
|
24
|
+
const emitDebounced = debounce((next: string) => emit("change", next), TYPE_DEBOUNCE_MS);
|
|
25
|
+
|
|
26
|
+
// External value can change (URL restore, reset). Keep draft in sync.
|
|
27
|
+
watch(
|
|
28
|
+
() => props.value,
|
|
29
|
+
(v) => {
|
|
30
|
+
draft.value = v;
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const onInput = (v: string | number | undefined) => {
|
|
35
|
+
const text = String(v ?? "");
|
|
36
|
+
draft.value = text;
|
|
37
|
+
emitDebounced(text);
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div :class="['relative flex-1', $props.class]">
|
|
43
|
+
<Search
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
|
|
46
|
+
/>
|
|
47
|
+
<Input
|
|
48
|
+
type="text"
|
|
49
|
+
:model-value="draft"
|
|
50
|
+
:placeholder="placeholder"
|
|
51
|
+
class="pl-9"
|
|
52
|
+
aria-label="Unified search"
|
|
53
|
+
@update:model-value="onInput"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ArrowDown, ArrowUp } from "@lucide/vue";
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SelectContent,
|
|
6
|
+
SelectItem,
|
|
7
|
+
SelectTrigger,
|
|
8
|
+
SelectValue,
|
|
9
|
+
} from "../../../../components/ui/select";
|
|
10
|
+
import { Button } from "../../../../components/ui/button";
|
|
11
|
+
import type { SortFieldConfig, SortState } from "../../utils/sortUtils";
|
|
12
|
+
|
|
13
|
+
const NONE_VALUE = "__none__";
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
configs: SortFieldConfig[];
|
|
17
|
+
sort: SortState | null;
|
|
18
|
+
class?: string;
|
|
19
|
+
}>();
|
|
20
|
+
const emit = defineEmits<{ sortChange: [sort: SortState | null] }>();
|
|
21
|
+
|
|
22
|
+
const onFieldChange = (v: unknown) => {
|
|
23
|
+
if (v === NONE_VALUE) emit("sortChange", null);
|
|
24
|
+
else emit("sortChange", { field: String(v), direction: props.sort?.direction ?? "ASC" });
|
|
25
|
+
};
|
|
26
|
+
const toggleDirection = () => {
|
|
27
|
+
if (!props.sort) return;
|
|
28
|
+
emit("sortChange", { ...props.sort, direction: props.sort.direction === "ASC" ? "DESC" : "ASC" });
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div v-if="configs.length > 0" :class="['flex items-center gap-2', $props.class]">
|
|
34
|
+
<span class="text-sm text-muted-foreground whitespace-nowrap">Sort by</span>
|
|
35
|
+
<Select :model-value="sort?.field ?? NONE_VALUE" @update:model-value="onFieldChange">
|
|
36
|
+
<SelectTrigger size="sm" class="w-[160px]" aria-label="Sort by">
|
|
37
|
+
<SelectValue placeholder="Default" />
|
|
38
|
+
</SelectTrigger>
|
|
39
|
+
<SelectContent>
|
|
40
|
+
<SelectItem :value="NONE_VALUE">Default</SelectItem>
|
|
41
|
+
<SelectItem v-for="c in configs" :key="c.field" :value="c.field">{{ c.label }}</SelectItem>
|
|
42
|
+
</SelectContent>
|
|
43
|
+
</Select>
|
|
44
|
+
<Button
|
|
45
|
+
v-if="sort"
|
|
46
|
+
variant="ghost"
|
|
47
|
+
size="icon-sm"
|
|
48
|
+
:aria-label="`Sort ${sort.direction === 'ASC' ? 'descending' : 'ascending'}`"
|
|
49
|
+
@click="toggleDirection"
|
|
50
|
+
>
|
|
51
|
+
<ArrowUp v-if="sort.direction === 'ASC'" />
|
|
52
|
+
<ArrowDown v-else />
|
|
53
|
+
</Button>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { X } from "@lucide/vue";
|
|
3
|
+
import { Button } from "../../../../components/ui/button";
|
|
4
|
+
import type { ActiveFilterValue } from "../../utils/filterUtils";
|
|
5
|
+
|
|
6
|
+
defineProps<{
|
|
7
|
+
filters: ActiveFilterValue[];
|
|
8
|
+
class?: string;
|
|
9
|
+
}>();
|
|
10
|
+
const emit = defineEmits<{ remove: [field: string] }>();
|
|
11
|
+
|
|
12
|
+
function formatLabel(filter: ActiveFilterValue): string {
|
|
13
|
+
const { label, type, value, min, max } = filter;
|
|
14
|
+
switch (type) {
|
|
15
|
+
case "text":
|
|
16
|
+
case "picklist":
|
|
17
|
+
return `${label}: ${value ?? ""}`;
|
|
18
|
+
case "multipicklist": {
|
|
19
|
+
const values = value ? value.split(",") : [];
|
|
20
|
+
if (values.length <= 2) return `${label}: ${values.join(", ")}`;
|
|
21
|
+
return `${label}: ${values.length} selected`;
|
|
22
|
+
}
|
|
23
|
+
case "boolean":
|
|
24
|
+
return `${label}: ${value === "true" ? "Yes" : "No"}`;
|
|
25
|
+
case "numeric": {
|
|
26
|
+
if (min && max) return `${label}: ${min} – ${max}`;
|
|
27
|
+
if (min) return `${label}: ≥ ${min}`;
|
|
28
|
+
return `${label}: ≤ ${max ?? ""}`;
|
|
29
|
+
}
|
|
30
|
+
case "date":
|
|
31
|
+
case "daterange":
|
|
32
|
+
case "datetime":
|
|
33
|
+
case "datetimerange": {
|
|
34
|
+
if (min && max) return `${label}: ${min} → ${max}`;
|
|
35
|
+
if (min) return `${label}: from ${min}`;
|
|
36
|
+
return `${label}: until ${max ?? ""}`;
|
|
37
|
+
}
|
|
38
|
+
default:
|
|
39
|
+
return label;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div v-if="filters.length > 0" :class="['flex flex-wrap gap-2', $props.class]">
|
|
46
|
+
<Button
|
|
47
|
+
v-for="filter in filters"
|
|
48
|
+
:key="filter.field"
|
|
49
|
+
variant="outline"
|
|
50
|
+
size="sm"
|
|
51
|
+
class="gap-1 h-7 text-xs"
|
|
52
|
+
:aria-label="`Remove ${formatLabel(filter)}`"
|
|
53
|
+
@click="emit('remove', filter.field)"
|
|
54
|
+
>
|
|
55
|
+
{{ formatLabel(filter) }}
|
|
56
|
+
<X class="h-3 w-3" aria-hidden="true" />
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|