@salesforce/templates 66.13.5 → 66.13.7
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/templates/project/angularextapp/CHANGELOG.md +258 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +680 -408
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularextapp/package.json +1 -1
- package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +690 -418
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
- package/lib/templates/project/angularintapp/package.json +1 -1
- package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +1409 -0
- package/lib/templates/project/reactexternalapp/PRD.md +38 -0
- package/lib/templates/project/reactexternalapp/README.md +14 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +953 -714
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/LanguageSwitcher.tsx +116 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/language-switcher-example.tsx +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/vite-config-site-example.ts +44 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/buildLanguageUrl.ts +158 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/index.ts +28 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/languages.ts +46 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +246 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +136 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +107 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/CmsResultRow.tsx +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/resolveResultRenderer.ts +49 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +966 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +100 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- 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/datePicker.tsx +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +8 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +53 -18
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -27
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +21 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/globals.d.ts +23 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
- package/lib/templates/project/reactexternalapp/package.json +8 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
- package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
- package/lib/templates/project/reactinternalapp/CHANGELOG.md +1223 -0
- package/lib/templates/project/reactinternalapp/PRD.md +35 -0
- package/lib/templates/project/reactinternalapp/README.md +14 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +813 -538
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +10 -9
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +246 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +136 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +107 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/CmsResultRow.tsx +101 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/resolveResultRenderer.ts +49 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +966 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +100 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -27
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +12 -5
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactinternalapp/package.json +4 -2
- package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
- package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
- package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
- package/lib/templates/uiBundles/angularbasic/package-lock.json +680 -408
- package/lib/templates/uiBundles/angularbasic/package.json +2 -2
- package/lib/templates/uiBundles/reactbasic/package-lock.json +738 -440
- package/lib/utils/template-placeholders.js +14 -0
- package/lib/utils/template-placeholders.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.d.ts +5 -1
- package/lib/utils/uiBundleTemplateUtils.js +7 -1
- package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
- package/package.json +7 -7
- package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
- package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
- package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
- package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
- /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merged results layout — a single combined grid across every in-scope source,
|
|
3
|
+
* driven by one global pager over the cumulative result set.
|
|
4
|
+
*
|
|
5
|
+
* This is the counterpart to {@link SearchResults} (which renders one stacked
|
|
6
|
+
* section per source). Use it when you want a unified "search everything" grid:
|
|
7
|
+
* heterogeneous cards laid out together, one shared pagination control, and a
|
|
8
|
+
* single results count — the layout the drop-in `<Search>` renders when the
|
|
9
|
+
* config sets `pagination.mode: "merged"`, and the same shape most apps
|
|
10
|
+
* hand-build on top of `useSearch`.
|
|
11
|
+
*
|
|
12
|
+
* It reads three aggregate accessors off the {@link SearchHandle}
|
|
13
|
+
* (`mergedResults`, `globalPagination`, `inScopeSources`), so it honours the
|
|
14
|
+
* config's `pagination.mode` / `mergeOrder` automatically. For a true combined
|
|
15
|
+
* grid, pair it with `pagination.mode: "merged"` (see the feature README, §9).
|
|
16
|
+
*
|
|
17
|
+
* Layout, top to bottom:
|
|
18
|
+
* - **Single-source filter chrome** — when the scope is narrowed to one
|
|
19
|
+
* source (selected or `lockedScope`), that source's filter panel + sort +
|
|
20
|
+
* active-filter chips render here. Under "All", per-source filters can't be
|
|
21
|
+
* combined across heterogeneous objects, so this row is omitted.
|
|
22
|
+
* - **Results count** — the cumulative in-scope total, on its own row directly
|
|
23
|
+
* above the grid, in every scope. Hidden when there are no results.
|
|
24
|
+
* - **The grid** — one card per `mergedResults` item, rendered via the
|
|
25
|
+
* per-source `renderResult[sourceKey]` callback. In a mixed grid (2+ sources
|
|
26
|
+
* in scope) each card is topped with an auto source badge — the source's
|
|
27
|
+
* `labelSingular` (falling back to `label`), colored from a palette by its
|
|
28
|
+
* config order — so heterogeneous results stay self-identifying with no
|
|
29
|
+
* configuration. Skeletons while loading; an empty-state message when there
|
|
30
|
+
* are none.
|
|
31
|
+
* - **Global pager** — one {@link PaginationControls} over the cumulative set.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { AlertCircle } from "lucide-react";
|
|
35
|
+
import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
|
|
36
|
+
import { Skeleton } from "../../../components/ui/skeleton";
|
|
37
|
+
import { ActiveFilters } from "./filters/ActiveFilters";
|
|
38
|
+
import { DefaultFilterPanel } from "./filters/DefaultFilterPanel";
|
|
39
|
+
import { FilterProvider, FilterResetButton } from "./filters/FilterContext";
|
|
40
|
+
import { PaginationControls } from "./controls/PaginationControls";
|
|
41
|
+
import { SortControl } from "./controls/SortControl";
|
|
42
|
+
import { resolveResultRenderer, type ResultRenderer } from "./results/resolveResultRenderer";
|
|
43
|
+
import { ALL_SCOPE, type SearchHandle, type SourceController } from "../types";
|
|
44
|
+
|
|
45
|
+
export interface MergedSearchResultsProps {
|
|
46
|
+
handle: SearchHandle;
|
|
47
|
+
/**
|
|
48
|
+
* Per source key: how to render one node in the merged grid. Falls back to
|
|
49
|
+
* the built-in {@link DefaultResultRow} for any source without an entry.
|
|
50
|
+
* Pass `false` to skip a source's cards entirely.
|
|
51
|
+
*/
|
|
52
|
+
renderResult?: Record<string, ResultRenderer>;
|
|
53
|
+
/** Message shown when there are no results. Defaults to "No results found." */
|
|
54
|
+
emptyMessage?: string;
|
|
55
|
+
/** Number of skeleton placeholders to show while loading. Defaults to `pageSize`. */
|
|
56
|
+
skeletonCount?: number;
|
|
57
|
+
/** Extra classes on the grid container. Defaults to a responsive 1/2/3-column grid. */
|
|
58
|
+
gridClassName?: string;
|
|
59
|
+
/** Extra classes on the outer wrapper. */
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEFAULT_GRID_CLASS = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Palette for the auto source badges in a mixed grid. A source's color is its
|
|
67
|
+
* index in the config's `sources` array, wrapped around the palette — so badges
|
|
68
|
+
* stay stable and distinct without any per-source configuration. Add more
|
|
69
|
+
* entries here if you routinely mix more source types than there are colors.
|
|
70
|
+
*/
|
|
71
|
+
const BADGE_PALETTE = [
|
|
72
|
+
"bg-blue-100 text-blue-700",
|
|
73
|
+
"bg-amber-100 text-amber-700",
|
|
74
|
+
"bg-purple-100 text-purple-700",
|
|
75
|
+
"bg-pink-100 text-pink-700",
|
|
76
|
+
"bg-emerald-100 text-emerald-700",
|
|
77
|
+
"bg-sky-100 text-sky-700",
|
|
78
|
+
] as const;
|
|
79
|
+
|
|
80
|
+
export function MergedSearchResults({
|
|
81
|
+
handle,
|
|
82
|
+
renderResult,
|
|
83
|
+
emptyMessage,
|
|
84
|
+
skeletonCount,
|
|
85
|
+
gridClassName = DEFAULT_GRID_CLASS,
|
|
86
|
+
className,
|
|
87
|
+
}: MergedSearchResultsProps) {
|
|
88
|
+
const { globalPagination: page, mergedResults, loading, error } = handle;
|
|
89
|
+
|
|
90
|
+
// A single source is in scope when the scope is narrowed (selected from the
|
|
91
|
+
// dropdown) or locked (`lockedScope` / restrictTo). Its filter chrome can
|
|
92
|
+
// render meaningfully; under "All" it can't (heterogeneous sources).
|
|
93
|
+
const singleSourceInScope = handle.scope !== ALL_SCOPE;
|
|
94
|
+
const soleController = singleSourceInScope ? handle.inScopeSources[0] : undefined;
|
|
95
|
+
// Per-card source badges only make sense when the grid is genuinely mixed —
|
|
96
|
+
// a single-source grid (narrowed or locked) has one type throughout.
|
|
97
|
+
const showSourceBadges = !singleSourceInScope;
|
|
98
|
+
// Assign each source a stable badge color by its declaration order. Badges
|
|
99
|
+
// only show in "All" scope, where `inScopeSources` is every source in config
|
|
100
|
+
// order, so the index here is the source's config position.
|
|
101
|
+
const badgeColorByKey: Record<string, string> = {};
|
|
102
|
+
handle.inScopeSources.forEach((controller, i) => {
|
|
103
|
+
badgeColorByKey[controller.config.key] = BADGE_PALETTE[i % BADGE_PALETTE.length];
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const skeletons = skeletonCount ?? page.pageSize;
|
|
107
|
+
|
|
108
|
+
// Persistent, always-mounted live region: screen readers announce result
|
|
109
|
+
// updates (searching → count → empty → error) without an Enter keypress.
|
|
110
|
+
const liveMessage = loading
|
|
111
|
+
? "Searching…"
|
|
112
|
+
: error
|
|
113
|
+
? "Search failed."
|
|
114
|
+
: mergedResults.length === 0
|
|
115
|
+
? (emptyMessage ?? "No results found.")
|
|
116
|
+
: `${page.totalCount} result${page.totalCount === 1 ? "" : "s"}`;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className={`space-y-6 ${className ?? ""}`}>
|
|
120
|
+
<p className="sr-only" role="status" aria-live="polite">
|
|
121
|
+
{liveMessage}
|
|
122
|
+
</p>
|
|
123
|
+
|
|
124
|
+
{soleController && <SingleSourceChrome controller={soleController} />}
|
|
125
|
+
|
|
126
|
+
{/* Page-level error — set only when every in-scope source failed (or a
|
|
127
|
+
whole-request/network failure). Shown once, above the grid. */}
|
|
128
|
+
{error && (
|
|
129
|
+
<Alert variant="destructive" role="alert">
|
|
130
|
+
<AlertCircle />
|
|
131
|
+
<AlertTitle>Search failed</AlertTitle>
|
|
132
|
+
<AlertDescription>{error}</AlertDescription>
|
|
133
|
+
</Alert>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{/* Per-source errors — a source that failed while a sibling succeeded
|
|
137
|
+
surfaces its own banner without blanking the shared grid. Suppressed
|
|
138
|
+
when the page-level error already covers the all-failed case. */}
|
|
139
|
+
{!error &&
|
|
140
|
+
handle.inScopeSources
|
|
141
|
+
.filter((c) => !!c.error)
|
|
142
|
+
.map((c) => (
|
|
143
|
+
<Alert key={c.config.key} variant="destructive" role="alert">
|
|
144
|
+
<AlertCircle />
|
|
145
|
+
<AlertTitle>Failed to load {c.config.label}</AlertTitle>
|
|
146
|
+
<AlertDescription>{c.error}</AlertDescription>
|
|
147
|
+
</Alert>
|
|
148
|
+
))}
|
|
149
|
+
|
|
150
|
+
{/* Results count — its own row, directly above the grid, in every scope.
|
|
151
|
+
Hidden when there are none (the empty state covers that case). */}
|
|
152
|
+
{!loading && !error && page.totalCount > 0 && (
|
|
153
|
+
<p className="text-sm text-muted-foreground">
|
|
154
|
+
{page.totalCount} result{page.totalCount === 1 ? "" : "s"}
|
|
155
|
+
</p>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{loading ? (
|
|
159
|
+
<div className={gridClassName}>
|
|
160
|
+
{Array.from({ length: Math.min(skeletons, 6) }, (_, i) => (
|
|
161
|
+
<Skeleton key={i} className="h-48 w-full rounded-lg" />
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
) : mergedResults.length === 0 ? (
|
|
165
|
+
<p className="py-12 text-center text-sm text-muted-foreground">
|
|
166
|
+
{emptyMessage ?? "No results found."}
|
|
167
|
+
</p>
|
|
168
|
+
) : (
|
|
169
|
+
<ul className={gridClassName}>
|
|
170
|
+
{mergedResults.map(({ sourceKey, node }, i) => {
|
|
171
|
+
const source = handle.sources[sourceKey]?.config;
|
|
172
|
+
if (!source) return null;
|
|
173
|
+
// Resolve via the shared helper so CMS nodes render through
|
|
174
|
+
// CmsResultRow (never DefaultResultRow) in the merged grid too;
|
|
175
|
+
// a `renderResult[key]` override — including `false` — still wins.
|
|
176
|
+
const renderer = resolveResultRenderer(source, renderResult);
|
|
177
|
+
if (renderer === false) return null;
|
|
178
|
+
const rendered = renderer(node);
|
|
179
|
+
const badge =
|
|
180
|
+
showSourceBadges && source ? (
|
|
181
|
+
<SourceBadge
|
|
182
|
+
label={source.labelSingular ?? source.label}
|
|
183
|
+
colorClass={badgeColorByKey[sourceKey]}
|
|
184
|
+
/>
|
|
185
|
+
) : null;
|
|
186
|
+
return (
|
|
187
|
+
<li
|
|
188
|
+
key={getItemKey(sourceKey, node, i)}
|
|
189
|
+
className="flex flex-col gap-1.5 [&>:last-child]:min-h-0 [&>:last-child]:flex-1"
|
|
190
|
+
>
|
|
191
|
+
{badge}
|
|
192
|
+
{rendered}
|
|
193
|
+
</li>
|
|
194
|
+
);
|
|
195
|
+
})}
|
|
196
|
+
</ul>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{page.totalCount > 0 && (
|
|
200
|
+
<PaginationControls
|
|
201
|
+
pageIndex={page.pageIndex}
|
|
202
|
+
pageCount={page.pageCount}
|
|
203
|
+
hasNextPage={page.hasNextPage}
|
|
204
|
+
hasPreviousPage={page.hasPreviousPage}
|
|
205
|
+
pageSize={page.pageSize}
|
|
206
|
+
pageSizeOptions={page.pageSizeOptions}
|
|
207
|
+
onNextPage={page.goToNextPage}
|
|
208
|
+
onPreviousPage={page.goToPreviousPage}
|
|
209
|
+
onGoToPage={page.goToPage}
|
|
210
|
+
onPageSizeChange={page.setPageSize}
|
|
211
|
+
disabled={loading || !!error}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getItemKey(sourceKey: string, node: unknown, fallback: number): string {
|
|
219
|
+
const id = node && typeof node === "object" ? (node as { Id?: unknown }).Id : undefined;
|
|
220
|
+
return typeof id === "string" ? `${sourceKey}:${id}` : `${sourceKey}:${fallback}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface SourceBadgeProps {
|
|
224
|
+
label: string;
|
|
225
|
+
colorClass: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Auto source badge shown above each card in a mixed grid — the source's
|
|
230
|
+
* `labelSingular` (falling back to `label`), colored from {@link BADGE_PALETTE}
|
|
231
|
+
* by its config order. Identifies which object type a heterogeneous card came
|
|
232
|
+
* from with no per-app config.
|
|
233
|
+
*/
|
|
234
|
+
function SourceBadge({ label, colorClass }: SourceBadgeProps) {
|
|
235
|
+
return (
|
|
236
|
+
<span className={`w-fit rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}`}>
|
|
237
|
+
{label}
|
|
238
|
+
</span>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
interface SingleSourceChromeProps {
|
|
243
|
+
controller: SourceController;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Filter panel + sort + active-filter chips for the one source in scope. Shown
|
|
248
|
+
* whenever the search is narrowed to a single object (selected or locked).
|
|
249
|
+
* Hidden under "All", where heterogeneous per-source filters can't be combined.
|
|
250
|
+
*/
|
|
251
|
+
function SingleSourceChrome({ controller }: SingleSourceChromeProps) {
|
|
252
|
+
const { config: source, filters, sort } = controller;
|
|
253
|
+
// Filter/sort chrome is SObject-only — narrow on `kind` so the union widening
|
|
254
|
+
// doesn't regress SObject behavior. CMS sources render no filter/sort chrome.
|
|
255
|
+
const sObjectSource = source.kind === "sobject" ? source : null;
|
|
256
|
+
const sortBy = sObjectSource?.sortBy;
|
|
257
|
+
const showFilterPanel = (sObjectSource?.filterBy?.length ?? 0) > 0;
|
|
258
|
+
const showSortControl = (sortBy?.length ?? 0) > 0;
|
|
259
|
+
const showActiveChips = filters.active.length > 0;
|
|
260
|
+
|
|
261
|
+
if (!showFilterPanel && !showSortControl && !showActiveChips) return null;
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<div className="flex flex-col gap-4">
|
|
265
|
+
{showFilterPanel && sObjectSource && (
|
|
266
|
+
<FilterProvider
|
|
267
|
+
filters={filters.active}
|
|
268
|
+
onFilterChange={filters.set}
|
|
269
|
+
onFilterRemove={filters.remove}
|
|
270
|
+
onReset={() => filters.active.forEach((f) => filters.remove(f.field))}
|
|
271
|
+
>
|
|
272
|
+
<div className="rounded-md border p-3 space-y-3">
|
|
273
|
+
<div className="flex items-center justify-between">
|
|
274
|
+
<h2 className="text-sm font-semibold">Filters</h2>
|
|
275
|
+
<FilterResetButton size="sm" />
|
|
276
|
+
</div>
|
|
277
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
278
|
+
<DefaultFilterPanel source={sObjectSource} />
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</FilterProvider>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{(showSortControl || showActiveChips) && (
|
|
285
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
286
|
+
{showSortControl && sortBy && (
|
|
287
|
+
<SortControl configs={sortBy} sort={sort.current} onSortChange={sort.set} />
|
|
288
|
+
)}
|
|
289
|
+
{showActiveChips && <ActiveFilters filters={filters.active} onRemove={filters.remove} />}
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
294
|
+
}
|
package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in search component. Given a `SearchConfig`, renders a complete
|
|
3
|
+
* search experience: search bar, an aggregate result count, and results.
|
|
4
|
+
*
|
|
5
|
+
* The results layout follows the config's pagination mode — no separate prop:
|
|
6
|
+
* - `pagination.mode: "per-source"` (default): one stacked section per
|
|
7
|
+
* source, each with its own filters, sort, and pagination.
|
|
8
|
+
* - `pagination.mode: "merged"`: a single combined grid across every in-scope
|
|
9
|
+
* source, driven by one global pager over the cumulative result set (with a
|
|
10
|
+
* results count and, under a single scope, that source's filter chrome).
|
|
11
|
+
*
|
|
12
|
+
* The simplest possible integration:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { Search, config } from ".../features/search";
|
|
16
|
+
* export default function MySearchPage() {
|
|
17
|
+
* return <Search config={config} />;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Override slots:
|
|
22
|
+
* - `renderResult[sourceKey]` — replace the default row renderer for one
|
|
23
|
+
* source. Pass `false` to hide that source entirely.
|
|
24
|
+
* - `renderFilters[sourceKey]` — replace the default filter sidebar for
|
|
25
|
+
* one source. Pass `false` to suppress filters for that source.
|
|
26
|
+
* - `renderHeader` — replace the default header (title + subtitle).
|
|
27
|
+
* - `searchPlaceholder` — placeholder text for the search input.
|
|
28
|
+
* - `title`, `subtitle` — defaults passed to the built-in header.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { useMemo, type ReactNode } from "react";
|
|
32
|
+
import { AlertCircle } from "lucide-react";
|
|
33
|
+
import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
|
|
34
|
+
import { useSearch } from "../hooks/useSearch";
|
|
35
|
+
import { SearchBar } from "./controls/SearchBar";
|
|
36
|
+
import { ScopeSelector } from "./controls/ScopeSelector";
|
|
37
|
+
import { SearchResults } from "./SearchResults";
|
|
38
|
+
import { MergedSearchResults } from "./MergedSearchResults";
|
|
39
|
+
import { ALL_SCOPE } from "../types";
|
|
40
|
+
import type { SearchConfig, SearchHandle, SObjectSourceConfig } from "../types";
|
|
41
|
+
|
|
42
|
+
type ResultRenderer = ((node: unknown) => ReactNode) | false;
|
|
43
|
+
type FilterRenderer = (() => ReactNode) | false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Locks the search to a single source. Reuses the same shared `config.json`
|
|
47
|
+
* but shows only results matching the given (kind, key) tuple.
|
|
48
|
+
*
|
|
49
|
+
* When set:
|
|
50
|
+
* - The scope dropdown is hidden.
|
|
51
|
+
* - URL `?scope=` is not written (the page route already implies the source).
|
|
52
|
+
* - Only the matching source is fetched and rendered.
|
|
53
|
+
*/
|
|
54
|
+
export interface RestrictTo {
|
|
55
|
+
kind: SObjectSourceConfig["kind"];
|
|
56
|
+
key: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SearchProps {
|
|
60
|
+
config: SearchConfig;
|
|
61
|
+
renderResult?: Record<string, ResultRenderer>;
|
|
62
|
+
renderFilters?: Record<string, FilterRenderer>;
|
|
63
|
+
emptyMessages?: Record<string, string>;
|
|
64
|
+
searchPlaceholder?: string;
|
|
65
|
+
title?: string;
|
|
66
|
+
subtitle?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Lock the search to a single source by `(kind, key)`. Hides the scope
|
|
69
|
+
* dropdown and forces results to that one source. Useful for embedding
|
|
70
|
+
* a single-object search inside a page where the source is implicit.
|
|
71
|
+
*/
|
|
72
|
+
restrictTo?: RestrictTo;
|
|
73
|
+
/**
|
|
74
|
+
* Show the source-scope dropdown next to the search bar.
|
|
75
|
+
* Defaults to `true` when the config has 2+ sources, `false` otherwise
|
|
76
|
+
* (a one-source dropdown would be redundant). Always hidden when
|
|
77
|
+
* `restrictTo` is set.
|
|
78
|
+
*/
|
|
79
|
+
showScopeSelector?: boolean;
|
|
80
|
+
/** Label for the "search everything" entry in the scope dropdown. */
|
|
81
|
+
allScopeLabel?: string;
|
|
82
|
+
/** Optional override for the entire header region. */
|
|
83
|
+
renderHeader?: (handle: SearchHandle) => ReactNode;
|
|
84
|
+
className?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function Search({
|
|
88
|
+
config,
|
|
89
|
+
renderResult,
|
|
90
|
+
renderFilters,
|
|
91
|
+
emptyMessages,
|
|
92
|
+
searchPlaceholder = "Search…",
|
|
93
|
+
title = "Search",
|
|
94
|
+
subtitle,
|
|
95
|
+
restrictTo,
|
|
96
|
+
showScopeSelector,
|
|
97
|
+
allScopeLabel,
|
|
98
|
+
renderHeader,
|
|
99
|
+
className,
|
|
100
|
+
}: SearchProps) {
|
|
101
|
+
// Resolve restrictTo to a lockedScope value, surfacing a clear error if
|
|
102
|
+
// the (kind, key) pair doesn't match any source. Memoize so it's
|
|
103
|
+
// stable across renders.
|
|
104
|
+
const lockedScope = useMemo<string | undefined>(() => {
|
|
105
|
+
if (!restrictTo) return undefined;
|
|
106
|
+
const match = config.sources.find(
|
|
107
|
+
(s) => s.kind === restrictTo.kind && s.key === restrictTo.key,
|
|
108
|
+
);
|
|
109
|
+
if (!match) {
|
|
110
|
+
console.warn(
|
|
111
|
+
`<Search restrictTo>: no source with kind="${restrictTo.kind}" and key="${restrictTo.key}" in config. Falling back to "all".`,
|
|
112
|
+
);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return match.key;
|
|
116
|
+
}, [restrictTo, config.sources]);
|
|
117
|
+
|
|
118
|
+
const handle = useSearch(config, { lockedScope });
|
|
119
|
+
// The results layout follows the pagination mode — a "merged" config means a
|
|
120
|
+
// single combined grid, "per-source" (default) means one section per source.
|
|
121
|
+
// There is no separate `layout` prop: the two would only ever be set in lockstep.
|
|
122
|
+
const merged = config.pagination?.mode === "merged";
|
|
123
|
+
const totalResults = Object.values(handle.sources).reduce(
|
|
124
|
+
(acc, controller) => acc + (controller.result?.totalCount ?? 0),
|
|
125
|
+
0,
|
|
126
|
+
);
|
|
127
|
+
const shouldShowScope = !handle.scopeLocked && (showScopeSelector ?? config.sources.length >= 2);
|
|
128
|
+
// The aggregate count is only meaningful when results from more than one
|
|
129
|
+
// source are combined. With a single configured source, a locked scope, or
|
|
130
|
+
// a specific source picked in the dropdown, exactly one source is on screen
|
|
131
|
+
// and that source's own section already shows its count — so suppress the
|
|
132
|
+
// redundant total here.
|
|
133
|
+
const sourcesInScope = handle.scope === ALL_SCOPE ? config.sources.length : 1;
|
|
134
|
+
// In merged mode the results grid renders its own count row directly above the
|
|
135
|
+
// grid (in every scope), so the inline aggregate here would be redundant.
|
|
136
|
+
const showTotalResults = !merged && sourcesInScope >= 2;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div className={`max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6 ${className ?? ""}`}>
|
|
140
|
+
{renderHeader ? (
|
|
141
|
+
renderHeader(handle)
|
|
142
|
+
) : (
|
|
143
|
+
<header className="space-y-2">
|
|
144
|
+
<h1 className="text-2xl font-bold">{title}</h1>
|
|
145
|
+
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
|
|
146
|
+
</header>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
150
|
+
<SearchBar value={handle.q} onChange={handle.setQ} placeholder={searchPlaceholder} />
|
|
151
|
+
{shouldShowScope && (
|
|
152
|
+
<ScopeSelector
|
|
153
|
+
config={config}
|
|
154
|
+
scope={handle.scope}
|
|
155
|
+
onScopeChange={handle.setScope}
|
|
156
|
+
allLabel={allScopeLabel}
|
|
157
|
+
discoveredContentTypes={handle.discoveredContentTypes}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{merged ? (
|
|
163
|
+
<MergedSearchResults
|
|
164
|
+
handle={handle}
|
|
165
|
+
renderResult={renderResult}
|
|
166
|
+
emptyMessage={emptyMessages?.[handle.scope]}
|
|
167
|
+
/>
|
|
168
|
+
) : (
|
|
169
|
+
<>
|
|
170
|
+
{handle.error ? (
|
|
171
|
+
/* Page-level failure (every queried source failed, or a
|
|
172
|
+
whole-request/network error). Show the single consolidated
|
|
173
|
+
banner and nothing else — the per-source sections would each
|
|
174
|
+
repeat the same "Failed to load <source>" error and their
|
|
175
|
+
(now empty) filter/sort chrome, so suppress them entirely. */
|
|
176
|
+
<Alert variant="destructive" role="alert">
|
|
177
|
+
<AlertCircle aria-hidden="true" />
|
|
178
|
+
<AlertTitle>Search failed</AlertTitle>
|
|
179
|
+
<AlertDescription>{handle.error}</AlertDescription>
|
|
180
|
+
</Alert>
|
|
181
|
+
) : (
|
|
182
|
+
<>
|
|
183
|
+
{/* Aggregate count on its own row, directly above the results —
|
|
184
|
+
only in "All" scope, where the per-source sections don't each
|
|
185
|
+
carry a single meaningful total. */}
|
|
186
|
+
{showTotalResults && !handle.loading && (
|
|
187
|
+
<p className="text-sm text-muted-foreground" role="status">
|
|
188
|
+
{totalResults} total result{totalResults === 1 ? "" : "s"}
|
|
189
|
+
</p>
|
|
190
|
+
)}
|
|
191
|
+
<SearchResults
|
|
192
|
+
handle={handle}
|
|
193
|
+
renderResult={renderResult}
|
|
194
|
+
renderFilters={renderFilters}
|
|
195
|
+
emptyMessages={emptyMessages}
|
|
196
|
+
/>
|
|
197
|
+
</>
|
|
198
|
+
)}
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders one SourceSection per configured source, in declaration order.
|
|
3
|
+
*
|
|
4
|
+
* Customization is optional — sensible defaults kick in for any source you
|
|
5
|
+
* don't override:
|
|
6
|
+
* - `renderResult[sourceKey]` — per-node renderer. Default: title from
|
|
7
|
+
* `displayFields[0]`, subtitle from the rest, optional `<Link>` driven
|
|
8
|
+
* by `routePattern`.
|
|
9
|
+
* - `renderFilters[sourceKey]` — sidebar filter UI. Default: one input per
|
|
10
|
+
* `filterBy` entry, with picklist options resolved from inline
|
|
11
|
+
* config or auto-fetched from the GraphQL aggregate API.
|
|
12
|
+
*
|
|
13
|
+
* Pass `false` for either entry to suppress the default for that source.
|
|
14
|
+
*
|
|
15
|
+
* Filter chrome (sidebar + active-filter chips + sort dropdown) is
|
|
16
|
+
* automatically hidden when `handle.scope === "all"` — per-source controls
|
|
17
|
+
* would be unreachable with every source on screen, and dangling chips
|
|
18
|
+
* would be confusing. Pre-existing filter / sort selections stay in state,
|
|
19
|
+
* so narrowing the scope brings them back unchanged.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ReactNode } from "react";
|
|
23
|
+
import { SourceSection } from "./SourceSection";
|
|
24
|
+
import { DefaultFilterPanel } from "./filters/DefaultFilterPanel";
|
|
25
|
+
import { resolveResultRenderer, type ResultRenderer } from "./results/resolveResultRenderer";
|
|
26
|
+
import { ALL_SCOPE, parseScope, type SearchHandle } from "../types";
|
|
27
|
+
import { isCmsSearchSupported } from "../adapters/cms/api/orgApiVersionService";
|
|
28
|
+
|
|
29
|
+
type FilterRenderer = (() => ReactNode) | false;
|
|
30
|
+
|
|
31
|
+
interface SearchResultsProps {
|
|
32
|
+
handle: SearchHandle;
|
|
33
|
+
renderResult?: Record<string, ResultRenderer>;
|
|
34
|
+
renderFilters?: Record<string, FilterRenderer>;
|
|
35
|
+
emptyMessages?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function SearchResults({
|
|
39
|
+
handle,
|
|
40
|
+
renderResult,
|
|
41
|
+
renderFilters,
|
|
42
|
+
emptyMessages,
|
|
43
|
+
}: SearchResultsProps) {
|
|
44
|
+
const filtersHidden = handle.scope === ALL_SCOPE;
|
|
45
|
+
// Match sections against the base scope so a "<cmsKey>:<fqn>" scope still
|
|
46
|
+
// renders the CMS source's section (the fqn only narrows the query).
|
|
47
|
+
const { base: scopeBase } = parseScope(handle.scope);
|
|
48
|
+
return (
|
|
49
|
+
<div className="space-y-10">
|
|
50
|
+
{Object.entries(handle.sources).map(([key, controller]) => {
|
|
51
|
+
if (scopeBase !== ALL_SCOPE && scopeBase !== key) return null;
|
|
52
|
+
// CMS search is skipped on builds below the CMS-search API version, so a
|
|
53
|
+
// CMS source never returns rows — rendering its section would only show
|
|
54
|
+
// an empty "Content" heading. Hide it (same gate as the CMS fetch skip).
|
|
55
|
+
if (controller.config.kind === "cms" && !isCmsSearchSupported()) return null;
|
|
56
|
+
// Resolve the renderer through the shared helper so a CMS source
|
|
57
|
+
// renders via CmsResultRow (never DefaultResultRow) in this mode; a
|
|
58
|
+
// `renderResult[key]` override — including `false` (hide) — still wins.
|
|
59
|
+
const resolvedRenderResult = resolveResultRenderer(controller.config, renderResult);
|
|
60
|
+
if (resolvedRenderResult === false) return null;
|
|
61
|
+
|
|
62
|
+
// CMS sources have no `filterBy`; the guard below keeps their chrome off.
|
|
63
|
+
const overrideFilters = renderFilters?.[key];
|
|
64
|
+
const sourceConfig = controller.config;
|
|
65
|
+
const hasFilterFields =
|
|
66
|
+
sourceConfig.kind === "sobject" && (sourceConfig.filterBy?.length ?? 0) > 0;
|
|
67
|
+
const resolvedRenderFilters: (() => ReactNode) | undefined =
|
|
68
|
+
overrideFilters === false
|
|
69
|
+
? undefined
|
|
70
|
+
: (overrideFilters ??
|
|
71
|
+
(hasFilterFields && sourceConfig.kind === "sobject"
|
|
72
|
+
? () => <DefaultFilterPanel source={sourceConfig} />
|
|
73
|
+
: undefined));
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<SourceSection
|
|
77
|
+
key={key}
|
|
78
|
+
controller={controller}
|
|
79
|
+
renderResult={resolvedRenderResult}
|
|
80
|
+
renderFilters={resolvedRenderFilters}
|
|
81
|
+
hideFilterChrome={filtersHidden}
|
|
82
|
+
emptyMessage={emptyMessages?.[key]}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|