@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.
Files changed (281) hide show
  1. package/lib/templates/project/angularextapp/CHANGELOG.md +258 -0
  2. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
  3. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
  4. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
  5. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +680 -408
  6. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
  7. package/lib/templates/project/angularextapp/package.json +1 -1
  8. package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
  9. package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
  10. package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
  11. package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
  12. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
  13. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
  14. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
  15. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +690 -418
  16. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
  17. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
  18. package/lib/templates/project/angularintapp/package.json +1 -1
  19. package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
  20. package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
  21. package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
  22. package/lib/templates/project/reactexternalapp/CHANGELOG.md +1409 -0
  23. package/lib/templates/project/reactexternalapp/PRD.md +38 -0
  24. package/lib/templates/project/reactexternalapp/README.md +14 -10
  25. package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
  26. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +953 -714
  27. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
  28. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
  29. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/LanguageSwitcher.tsx +116 -0
  30. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/language-switcher-example.tsx +36 -0
  31. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/vite-config-site-example.ts +44 -0
  32. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/buildLanguageUrl.ts +158 -0
  33. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/index.ts +28 -0
  34. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/languages.ts +46 -0
  35. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
  36. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
  37. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +246 -0
  38. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
  39. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
  40. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
  41. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
  42. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
  43. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
  44. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
  45. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
  46. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
  47. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
  48. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
  49. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
  50. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
  51. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
  52. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
  53. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
  54. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -0
  55. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
  56. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +136 -0
  57. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
  58. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
  59. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
  60. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
  61. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -0
  62. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
  63. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +107 -0
  64. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
  65. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
  66. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
  67. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
  68. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
  69. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
  70. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
  71. 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
  72. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
  73. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
  74. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
  75. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
  76. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/CmsResultRow.tsx +101 -0
  77. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
  78. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/resolveResultRenderer.ts +49 -0
  79. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
  80. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -0
  81. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
  82. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
  83. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +966 -0
  84. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -0
  85. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
  86. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +100 -0
  87. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -0
  88. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
  89. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
  90. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
  91. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
  92. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
  93. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
  94. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
  95. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
  96. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
  97. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
  98. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
  99. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +8 -0
  100. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +53 -18
  101. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -27
  102. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
  103. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +21 -8
  104. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
  105. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/globals.d.ts +23 -0
  106. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
  107. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
  108. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
  109. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
  110. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
  111. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
  112. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
  113. package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
  114. package/lib/templates/project/reactexternalapp/package.json +8 -2
  115. package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
  116. package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
  117. package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
  118. package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
  119. package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
  120. package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
  121. package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
  122. package/lib/templates/project/reactinternalapp/CHANGELOG.md +1223 -0
  123. package/lib/templates/project/reactinternalapp/PRD.md +35 -0
  124. package/lib/templates/project/reactinternalapp/README.md +14 -10
  125. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +813 -538
  126. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +10 -9
  127. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +246 -0
  128. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
  129. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
  130. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
  131. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
  132. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
  133. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
  134. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
  135. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
  136. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
  137. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
  138. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
  139. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
  140. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
  141. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
  142. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
  143. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
  144. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -0
  145. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
  146. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +136 -0
  147. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
  148. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
  149. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
  150. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
  151. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -0
  152. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
  153. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +107 -0
  154. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
  155. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
  156. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
  157. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
  158. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
  159. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
  160. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
  161. 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
  162. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
  163. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
  164. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
  165. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
  166. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/CmsResultRow.tsx +101 -0
  167. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
  168. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/resolveResultRenderer.ts +49 -0
  169. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
  170. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -0
  171. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
  172. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
  173. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +966 -0
  174. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -0
  175. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
  176. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +100 -0
  177. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -0
  178. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
  179. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
  180. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
  181. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
  182. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
  183. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
  184. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
  185. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
  186. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
  187. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
  188. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -27
  189. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +12 -5
  190. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
  191. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
  192. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
  193. package/lib/templates/project/reactinternalapp/package.json +4 -2
  194. package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
  195. package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
  196. package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
  197. package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
  198. package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
  199. package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
  200. package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
  201. package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
  202. package/lib/templates/uiBundles/angularbasic/package-lock.json +680 -408
  203. package/lib/templates/uiBundles/angularbasic/package.json +2 -2
  204. package/lib/templates/uiBundles/reactbasic/package-lock.json +738 -440
  205. package/lib/utils/template-placeholders.js +14 -0
  206. package/lib/utils/template-placeholders.js.map +1 -1
  207. package/lib/utils/uiBundleTemplateUtils.d.ts +5 -1
  208. package/lib/utils/uiBundleTemplateUtils.js +7 -1
  209. package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
  210. package/package.json +7 -7
  211. package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
  212. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
  213. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
  214. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
  215. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
  216. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
  217. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
  218. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
  219. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
  220. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
  221. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
  222. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
  223. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
  224. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
  225. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
  226. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
  227. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
  228. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
  229. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
  230. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
  231. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
  232. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
  233. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
  234. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
  235. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
  236. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
  237. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
  238. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
  239. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
  240. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
  241. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
  242. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
  243. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
  244. package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
  245. package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
  246. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
  247. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
  248. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
  249. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
  250. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
  251. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
  252. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
  253. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
  254. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
  255. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
  256. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
  257. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
  258. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
  259. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
  260. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
  261. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
  262. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
  263. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
  264. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
  265. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
  266. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
  267. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
  268. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
  269. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
  270. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
  271. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
  272. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
  273. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
  274. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
  275. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
  276. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
  277. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
  278. package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
  279. /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
  280. /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
  281. /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Renders one source's slice of a search result: optional filter
3
+ * panel, sort dropdown, active-filter chips, the result list (delegated to
4
+ * the caller via `renderResult`), and pagination controls.
5
+ *
6
+ * The component is "smart" only about orchestration. The actual list-item UI
7
+ * is owned by the application via the `renderResult(node)` callback —
8
+ * search never decides how an Account or Contact card looks.
9
+ */
10
+
11
+ import type { ReactNode } from "react";
12
+ import { AlertCircle, SearchX } from "lucide-react";
13
+ import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
14
+ import { Skeleton } from "../../../components/ui/skeleton";
15
+ import { ActiveFilters } from "./filters/ActiveFilters";
16
+ import { FilterProvider, FilterResetButton } from "./filters/FilterContext";
17
+ import { PaginationControls } from "./controls/PaginationControls";
18
+ import { SortControl } from "./controls/SortControl";
19
+ import type { SourceController } from "../types";
20
+
21
+ interface SourceSectionProps {
22
+ controller: SourceController;
23
+ renderResult: (node: unknown) => ReactNode;
24
+ /** Optional filter UI (TextFilter, SelectFilter, ...) rendered above the results. */
25
+ renderFilters?: () => ReactNode;
26
+ /**
27
+ * Suppress the filter panel, the active-filter chips, and the sort
28
+ * dropdown. Pre-existing filter / sort selections stay in state (and
29
+ * apply to the GraphQL query) so the caller can restore the chrome
30
+ * without losing the user's selections, but no chrome surface is rendered.
31
+ *
32
+ * Set by `<SearchResults>` when the current scope is "all" — per-source
33
+ * controls would be unreachable while every source is on screen, and
34
+ * dangling chips would be confusing.
35
+ */
36
+ hideFilterChrome?: boolean;
37
+ emptyMessage?: string;
38
+ className?: string;
39
+ }
40
+
41
+ export function SourceSection({
42
+ controller,
43
+ renderResult,
44
+ renderFilters,
45
+ hideFilterChrome,
46
+ emptyMessage,
47
+ className,
48
+ }: SourceSectionProps) {
49
+ const { config, result, loading, error, filters, sort, pagination } = controller;
50
+ const nodes = result?.nodes ?? [];
51
+ const totalCount = result?.totalCount;
52
+ const showResults = !loading && !error && nodes.length > 0;
53
+ const showEmpty = !loading && !error && nodes.length === 0;
54
+
55
+ const pageSizeOptions = pagination.pageSizeOptions ?? [pagination.pageSize];
56
+
57
+ // `sortBy` / `idField` are SObject-only — narrow on `kind` so the union
58
+ // widening of `config` doesn't regress SObject behavior (CMS has neither).
59
+ const sortBy = config.kind === "sobject" ? config.sortBy : undefined;
60
+ const idField = config.kind === "sobject" ? config.idField : undefined;
61
+
62
+ const showFilterPanel = !!renderFilters && !hideFilterChrome;
63
+ const showActiveChips = !hideFilterChrome && filters.active.length > 0;
64
+ const showSortControl = !hideFilterChrome && (sortBy?.length ?? 0) > 0;
65
+
66
+ // Persistent, always-mounted live region: screen readers announce result
67
+ // updates (searching → count → empty → error) without an Enter keypress.
68
+ const liveMessage = loading
69
+ ? "Searching…"
70
+ : error
71
+ ? `Failed to load ${config.label}.`
72
+ : showEmpty
73
+ ? (emptyMessage ?? `No ${config.label.toLowerCase()} found.`)
74
+ : totalCount != null
75
+ ? `${config.label}: ${totalCount} ${totalCount === 1 ? "result" : "results"}`
76
+ : "";
77
+
78
+ return (
79
+ <section className={`flex flex-col gap-6 ${className ?? ""}`}>
80
+ <p className="sr-only" role="status" aria-live="polite">
81
+ {liveMessage}
82
+ </p>
83
+
84
+ {showFilterPanel && (
85
+ <FilterProvider
86
+ filters={filters.active}
87
+ onFilterChange={filters.set}
88
+ onFilterRemove={filters.remove}
89
+ onReset={() => filters.active.forEach((f) => filters.remove(f.field))}
90
+ >
91
+ <div className="rounded-md border p-3 space-y-3">
92
+ <div className="flex items-center justify-between">
93
+ <h2 className="text-sm font-semibold">Filters</h2>
94
+ <FilterResetButton size="sm" />
95
+ </div>
96
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
97
+ {renderFilters!()}
98
+ </div>
99
+ </div>
100
+ </FilterProvider>
101
+ )}
102
+
103
+ <div className="min-w-0">
104
+ <header className="flex flex-wrap items-baseline gap-3 mb-3">
105
+ <h2 className="text-lg font-semibold">{config.label}</h2>
106
+ </header>
107
+
108
+ {(showSortControl || showActiveChips) && (
109
+ <div className="flex flex-wrap items-center gap-2 mb-3">
110
+ {showSortControl && sortBy ? (
111
+ <SortControl configs={sortBy} sort={sort.current} onSortChange={sort.set} />
112
+ ) : null}
113
+ {showActiveChips && (
114
+ <ActiveFilters filters={filters.active} onRemove={filters.remove} />
115
+ )}
116
+ </div>
117
+ )}
118
+
119
+ {/* Result count, shown just above the results in every scope —
120
+ including a locked / single-source (restrictTo) view. */}
121
+ {!loading && !error && totalCount != null && (
122
+ <p className="text-sm text-muted-foreground mb-3">
123
+ {totalCount} {totalCount === 1 ? "result" : "results"}
124
+ </p>
125
+ )}
126
+
127
+ <div className="min-h-32">
128
+ {loading && (
129
+ <div className="space-y-2">
130
+ {Array.from({ length: Math.min(pagination.pageSize, 5) }, (_, i) => (
131
+ <Skeleton key={i} className="h-12 w-full" />
132
+ ))}
133
+ </div>
134
+ )}
135
+
136
+ {error && (
137
+ <Alert variant="destructive" role="alert">
138
+ <AlertCircle />
139
+ <AlertTitle>Failed to load {config.label}</AlertTitle>
140
+ <AlertDescription>{error}</AlertDescription>
141
+ </Alert>
142
+ )}
143
+
144
+ {showResults && (
145
+ <ul className="divide-y">
146
+ {nodes.map((node, i) => (
147
+ <li key={getNodeKey(node, idField, i)} className="py-3">
148
+ {renderResult(node)}
149
+ </li>
150
+ ))}
151
+ </ul>
152
+ )}
153
+
154
+ {showEmpty && (
155
+ <div className="flex flex-col items-center justify-center py-10 text-center">
156
+ <SearchX className="size-10 text-muted-foreground mb-3" />
157
+ <p className="text-sm text-muted-foreground">
158
+ {emptyMessage ?? `No ${config.label.toLowerCase()} found.`}
159
+ </p>
160
+ </div>
161
+ )}
162
+ </div>
163
+
164
+ <PaginationControls
165
+ pageIndex={pagination.pageIndex}
166
+ hasNextPage={pagination.hasNextPage}
167
+ hasPreviousPage={pagination.hasPreviousPage}
168
+ pageSize={pagination.pageSize}
169
+ pageSizeOptions={pageSizeOptions}
170
+ onNextPage={pagination.goToNextPage}
171
+ onPreviousPage={pagination.goToPreviousPage}
172
+ onPageSizeChange={pagination.setPageSize}
173
+ disabled={loading || !!error}
174
+ idPrefix={`page-size-${config.key}`}
175
+ />
176
+ </div>
177
+ </section>
178
+ );
179
+ }
180
+
181
+ function getNodeKey(node: unknown, idField: string | undefined, fallback: number): string | number {
182
+ if (node && typeof node === "object") {
183
+ const id = (node as Record<string, unknown>)[idField ?? "Id"];
184
+ if (typeof id === "string" || typeof id === "number") return id;
185
+ }
186
+ return fallback;
187
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ Pagination,
3
+ PaginationContent,
4
+ PaginationEllipsis,
5
+ PaginationItem,
6
+ PaginationLink,
7
+ PaginationNext,
8
+ PaginationPrevious,
9
+ } from "../../../../components/ui/pagination";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../../../components/ui/select";
17
+ import { Label } from "../../../../components/ui/label";
18
+ import type { KeyboardEvent } from "react";
19
+
20
+ interface PaginationControlsProps {
21
+ pageIndex: number;
22
+ hasNextPage: boolean;
23
+ hasPreviousPage: boolean;
24
+ pageSize: number;
25
+ pageSizeOptions: readonly number[];
26
+ onNextPage: () => void;
27
+ onPreviousPage: () => void;
28
+ onPageSizeChange: (size: number) => void;
29
+ /**
30
+ * Total number of pages. When provided (with {@link onGoToPage}) and `> 1`,
31
+ * the control renders numbered page buttons with ellipsis truncation instead
32
+ * of a plain "Page N" label. Omit for cursor-style sources where the total
33
+ * page count is unknown.
34
+ */
35
+ pageCount?: number;
36
+ /** Jump to a 0-based page. Required to render numbered page buttons. */
37
+ onGoToPage?: (pageIndex: number) => void;
38
+ disabled?: boolean;
39
+ idPrefix?: string;
40
+ }
41
+
42
+ /**
43
+ * Builds the list of page tokens to render: 0-based page numbers plus
44
+ * `"ellipsis"` gap markers. Always shows the first and last page, a window
45
+ * around the current page, and collapses the rest. e.g. for current=5,
46
+ * total=12 → [0, ellipsis, 4, 5, 6, ellipsis, 11].
47
+ */
48
+ function getPageTokens(currentIndex: number, pageCount: number): Array<number | "ellipsis"> {
49
+ const SIBLINGS = 1; // pages shown on each side of the current page
50
+ const last = pageCount - 1;
51
+ const pages = new Set<number>([0, last]);
52
+ for (let p = currentIndex - SIBLINGS; p <= currentIndex + SIBLINGS; p++) {
53
+ if (p >= 0 && p <= last) pages.add(p);
54
+ }
55
+ const sorted = [...pages].sort((a, b) => a - b);
56
+ const tokens: Array<number | "ellipsis"> = [];
57
+ let prev = -1;
58
+ for (const p of sorted) {
59
+ if (prev !== -1 && p - prev > 1) tokens.push("ellipsis");
60
+ tokens.push(p);
61
+ prev = p;
62
+ }
63
+ return tokens;
64
+ }
65
+
66
+ export function PaginationControls({
67
+ pageIndex,
68
+ hasNextPage,
69
+ hasPreviousPage,
70
+ pageSize,
71
+ pageSizeOptions,
72
+ onNextPage,
73
+ onPreviousPage,
74
+ onPageSizeChange,
75
+ pageCount,
76
+ onGoToPage,
77
+ disabled = false,
78
+ idPrefix = "page-size",
79
+ }: PaginationControlsProps) {
80
+ const handlePageSizeChange = (next: string) => {
81
+ const size = parseInt(next, 10);
82
+ if (!isNaN(size) && size !== pageSize) onPageSizeChange(size);
83
+ };
84
+ const prevDisabled = disabled || !hasPreviousPage;
85
+ const nextDisabled = disabled || !hasNextPage;
86
+ const selectId = `${idPrefix}-select`;
87
+
88
+ // Numbered page buttons require both a known page count and a seek callback.
89
+ const showNumberedPages = pageCount != null && pageCount > 1 && onGoToPage != null;
90
+
91
+ // The underlying pagination control renders an <a> without an href, so it is
92
+ // not keyboard-operable on its own. Activate it on Enter/Space to match the
93
+ // behaviour of a native button (WCAG 2.2 SC 2.1.1 Keyboard).
94
+ const handleActivationKey = (action: () => void) => (event: KeyboardEvent) => {
95
+ if (event.key === "Enter" || event.key === " ") {
96
+ event.preventDefault();
97
+ action();
98
+ }
99
+ };
100
+
101
+ return (
102
+ <div className="w-full grid grid-cols-1 sm:grid-cols-2 items-center gap-4 py-2">
103
+ <div className="flex justify-center sm:justify-start items-center gap-2">
104
+ <Label htmlFor={selectId} className="text-sm font-normal whitespace-nowrap">
105
+ Results per page:
106
+ </Label>
107
+ <Select
108
+ value={pageSize.toString()}
109
+ onValueChange={handlePageSizeChange}
110
+ disabled={disabled}
111
+ >
112
+ <SelectTrigger id={selectId} className="w-16">
113
+ <SelectValue />
114
+ </SelectTrigger>
115
+ <SelectContent>
116
+ {pageSizeOptions.map((size) => (
117
+ <SelectItem key={size} value={size.toString()}>
118
+ {size}
119
+ </SelectItem>
120
+ ))}
121
+ </SelectContent>
122
+ </Select>
123
+ </div>
124
+ <Pagination className="w-full mx-0 sm:justify-end">
125
+ <PaginationContent>
126
+ <PaginationItem>
127
+ <PaginationPrevious
128
+ role="button"
129
+ tabIndex={prevDisabled ? -1 : 0}
130
+ onClick={prevDisabled ? undefined : onPreviousPage}
131
+ onKeyDown={prevDisabled ? undefined : handleActivationKey(onPreviousPage)}
132
+ aria-disabled={prevDisabled}
133
+ className={prevDisabled ? "pointer-events-none opacity-50" : "cursor-pointer"}
134
+ />
135
+ </PaginationItem>
136
+
137
+ {showNumberedPages ? (
138
+ getPageTokens(pageIndex, pageCount).map((token, i) =>
139
+ token === "ellipsis" ? (
140
+ <PaginationItem key={`ellipsis-${i}`}>
141
+ <PaginationEllipsis />
142
+ </PaginationItem>
143
+ ) : (
144
+ <PaginationItem key={token}>
145
+ <PaginationLink
146
+ role="button"
147
+ tabIndex={disabled ? -1 : 0}
148
+ isActive={token === pageIndex}
149
+ aria-label={`Go to page ${token + 1}`}
150
+ aria-current={token === pageIndex ? "page" : undefined}
151
+ onClick={disabled ? undefined : () => onGoToPage(token)}
152
+ onKeyDown={disabled ? undefined : handleActivationKey(() => onGoToPage(token))}
153
+ aria-disabled={disabled}
154
+ className={disabled ? "pointer-events-none opacity-50" : "cursor-pointer"}
155
+ >
156
+ {token + 1}
157
+ </PaginationLink>
158
+ </PaginationItem>
159
+ ),
160
+ )
161
+ ) : (
162
+ <PaginationItem>
163
+ <span className="min-w-16 text-center text-sm text-muted-foreground px-2">
164
+ {pageCount != null && pageCount > 0
165
+ ? `Page ${pageIndex + 1} of ${pageCount}`
166
+ : `Page ${pageIndex + 1}`}
167
+ </span>
168
+ </PaginationItem>
169
+ )}
170
+
171
+ <PaginationItem>
172
+ <PaginationNext
173
+ role="button"
174
+ tabIndex={nextDisabled ? -1 : 0}
175
+ onClick={nextDisabled ? undefined : onNextPage}
176
+ onKeyDown={nextDisabled ? undefined : handleActivationKey(onNextPage)}
177
+ aria-disabled={nextDisabled}
178
+ className={nextDisabled ? "pointer-events-none opacity-50" : "cursor-pointer"}
179
+ />
180
+ </PaginationItem>
181
+ </PaginationContent>
182
+ </Pagination>
183
+ </div>
184
+ );
185
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Dropdown that narrows the search to a single source (or "All").
3
+ *
4
+ * Sits next to the search bar in the default {@link Search} layout. The
5
+ * options are derived from the configured sources: an "All" entry plus one
6
+ * entry per source (using each source's `label`). When CMS content types have
7
+ * been discovered at runtime, one extra entry per type is appended under its
8
+ * CMS source — value `"<cmsKey>:<fqn>"`, label the server-provided content-type
9
+ * label — so a user can narrow a CMS search to a single content type.
10
+ *
11
+ * Selecting a non-"All" scope tells {@link useSearch} to fetch and render
12
+ * only that source. Selecting a `"<cmsKey>:<fqn>"` entry runs a CMS-only search
13
+ * for that content type. Switching scope resets per-source pagination cursors —
14
+ * the visible result set is changing.
15
+ */
16
+
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from "../../../../components/ui/select";
24
+ import { ALL_SCOPE, CMS_SCOPE_SEPARATOR, type SearchConfig, type SearchScope } from "../../types";
25
+ import type { DiscoveredContentType } from "../../adapters/cms/contentTypeUtils";
26
+ import { isCmsSearchSupported } from "../../adapters/cms/api/orgApiVersionService";
27
+
28
+ interface ScopeSelectorProps {
29
+ config: SearchConfig;
30
+ scope: SearchScope;
31
+ onScopeChange: (scope: SearchScope) => void;
32
+ allLabel?: string;
33
+ /**
34
+ * CMS content types discovered at runtime (from
35
+ * {@link SearchHandle.discoveredContentTypes}) — each with its FQN and
36
+ * server-provided display `label`. Appended as per-type entries under the CMS
37
+ * source once discovery completes; omit or pass `[]` before then. No-op when
38
+ * the config has no CMS source.
39
+ */
40
+ discoveredContentTypes?: DiscoveredContentType[];
41
+ className?: string;
42
+ }
43
+
44
+ export function ScopeSelector({
45
+ config,
46
+ scope,
47
+ onScopeChange,
48
+ allLabel = "All",
49
+ discoveredContentTypes = [],
50
+ className,
51
+ }: ScopeSelectorProps) {
52
+ if (config.sources.length === 0) return null;
53
+ // Discovered content types attach to the (single) CMS source. Entries are
54
+ // injected only after discovery, so the dropdown starts with just the config
55
+ // sources and grows once the CMS channel's types are known.
56
+ const cmsSource = config.sources.find((s) => s.kind === "cms");
57
+ const hasDiscoveredTypes = !!cmsSource && discoveredContentTypes.length > 0;
58
+ const contentTypeEntries = hasDiscoveredTypes
59
+ ? discoveredContentTypes.map((ct) => ({
60
+ value: `${cmsSource!.key}${CMS_SCOPE_SEPARATOR}${ct.fqn}`,
61
+ label: ct.label,
62
+ }))
63
+ : [];
64
+ return (
65
+ <div className={className}>
66
+ <Select value={scope} onValueChange={onScopeChange}>
67
+ <SelectTrigger size="sm" className="min-w-[140px]" aria-label="Search scope">
68
+ <SelectValue />
69
+ </SelectTrigger>
70
+ <SelectContent>
71
+ <SelectItem value={ALL_SCOPE}>{allLabel}</SelectItem>
72
+ {config.sources.map((source) => {
73
+ // CMS search is skipped entirely on builds below the CMS-search API
74
+ // version (same gate `useSearch` uses to drop the CMS request), so
75
+ // its scope entry ("Content") would only ever yield an empty result.
76
+ // Hide it. Build-version-only — safe to evaluate during render.
77
+ if (source.kind === "cms" && !isCmsSearchSupported()) {
78
+ return null;
79
+ }
80
+ // Once the CMS channel's content types are known, the generic CMS
81
+ // source entry (e.g. "Content") is redundant — its per-type entries
82
+ // (appended below) cover the same source at finer granularity, so
83
+ // drop the catch-all to avoid a duplicate-looking scope option.
84
+ // EXCEPTION: keep it while it is the current scope. Discovery can
85
+ // complete AFTER the user has selected the generic CMS scope; if we
86
+ // removed the only matching item, the controlled Radix select would
87
+ // hold a value no item represents and render blank — leaving AT
88
+ // users unable to identify the active scope (WCAG 4.1.2).
89
+ if (source.kind === "cms" && hasDiscoveredTypes && scope !== source.key) {
90
+ return null;
91
+ }
92
+ return (
93
+ <SelectItem key={source.key} value={source.key}>
94
+ {source.label}
95
+ </SelectItem>
96
+ );
97
+ })}
98
+ {contentTypeEntries.map((entry) => (
99
+ <SelectItem key={entry.value} value={entry.value}>
100
+ {entry.label}
101
+ </SelectItem>
102
+ ))}
103
+ </SelectContent>
104
+ </Select>
105
+ </div>
106
+ );
107
+ }
@@ -0,0 +1,55 @@
1
+ import { Search } from "lucide-react";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Input } from "../../../../components/ui/input";
4
+ import { debounce } from "../../utils/debounce";
5
+
6
+ const TYPE_DEBOUNCE_MS = 250;
7
+
8
+ interface SearchBarProps {
9
+ value: string;
10
+ onChange: (next: string) => void;
11
+ placeholder?: string;
12
+ className?: string;
13
+ }
14
+
15
+ /**
16
+ * Debounced text input for the global `q` term. Keeps a local `draft` so
17
+ * keystrokes feel responsive while the upstream `onChange` is rate-limited.
18
+ */
19
+ export function SearchBar({ value, onChange, placeholder = "Search…", className }: SearchBarProps) {
20
+ const [draft, setDraft] = useState(value);
21
+ const onChangeRef = useRef(onChange);
22
+ useEffect(() => {
23
+ onChangeRef.current = onChange;
24
+ });
25
+
26
+ // Build the debounced caller in a mount effect, not during render: creating it
27
+ // inline would read onChangeRef.current at render time (react-hooks/refs). It
28
+ // stays a single stable instance and always calls the latest onChange via the ref.
29
+ const debouncedRef = useRef<((next: string) => void) | undefined>(undefined);
30
+ useEffect(() => {
31
+ debouncedRef.current = debounce((next: string) => onChangeRef.current(next), TYPE_DEBOUNCE_MS);
32
+ }, []);
33
+
34
+ // External value can change (URL restore, reset). Keep draft in sync.
35
+ useEffect(() => {
36
+ setDraft(value);
37
+ }, [value]);
38
+
39
+ return (
40
+ <div className={`relative flex-1 ${className ?? ""}`}>
41
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
42
+ <Input
43
+ type="text"
44
+ value={draft}
45
+ onChange={(e) => {
46
+ setDraft(e.target.value);
47
+ debouncedRef.current?.(e.target.value);
48
+ }}
49
+ placeholder={placeholder}
50
+ className="pl-9"
51
+ aria-label="Unified search"
52
+ />
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,62 @@
1
+ import { ArrowDown, ArrowUp } from "lucide-react";
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue,
8
+ } from "../../../../components/ui/select";
9
+ import { Button } from "../../../../components/ui/button";
10
+ import type { SortFieldConfig, SortState } from "../../utils/sortUtils";
11
+
12
+ const NONE_VALUE = "__none__";
13
+
14
+ interface SortControlProps {
15
+ configs: SortFieldConfig[];
16
+ sort: SortState | null;
17
+ onSortChange: (sort: SortState | null) => void;
18
+ className?: string;
19
+ }
20
+
21
+ export function SortControl({ configs, sort, onSortChange, className }: SortControlProps) {
22
+ if (configs.length === 0) return null;
23
+ return (
24
+ <div className={`flex items-center gap-2 ${className ?? ""}`}>
25
+ <span className="text-sm text-muted-foreground whitespace-nowrap">Sort by</span>
26
+ <Select
27
+ value={sort?.field ?? NONE_VALUE}
28
+ onValueChange={(v) => {
29
+ if (v === NONE_VALUE) onSortChange(null);
30
+ else onSortChange({ field: v, direction: sort?.direction ?? "ASC" });
31
+ }}
32
+ >
33
+ <SelectTrigger size="sm" className="w-[160px]" aria-label="Sort by">
34
+ <SelectValue placeholder="Default" />
35
+ </SelectTrigger>
36
+ <SelectContent>
37
+ <SelectItem value={NONE_VALUE}>Default</SelectItem>
38
+ {configs.map((c) => (
39
+ <SelectItem key={c.field} value={c.field}>
40
+ {c.label}
41
+ </SelectItem>
42
+ ))}
43
+ </SelectContent>
44
+ </Select>
45
+ {sort && (
46
+ <Button
47
+ variant="ghost"
48
+ size="icon-sm"
49
+ onClick={() =>
50
+ onSortChange({
51
+ ...sort,
52
+ direction: sort.direction === "ASC" ? "DESC" : "ASC",
53
+ })
54
+ }
55
+ aria-label={`Sort ${sort.direction === "ASC" ? "descending" : "ascending"}`}
56
+ >
57
+ {sort.direction === "ASC" ? <ArrowUp /> : <ArrowDown />}
58
+ </Button>
59
+ )}
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,61 @@
1
+ import { X } from "lucide-react";
2
+ import { Button } from "../../../../components/ui/button";
3
+ import type { ActiveFilterValue } from "../../utils/filterUtils";
4
+
5
+ function formatLabel(filter: ActiveFilterValue): string {
6
+ const { label, type, value, min, max } = filter;
7
+ switch (type) {
8
+ case "text":
9
+ case "picklist":
10
+ return `${label}: ${value ?? ""}`;
11
+ case "multipicklist": {
12
+ const values = value ? value.split(",") : [];
13
+ if (values.length <= 2) return `${label}: ${values.join(", ")}`;
14
+ return `${label}: ${values.length} selected`;
15
+ }
16
+ case "boolean":
17
+ return `${label}: ${value === "true" ? "Yes" : "No"}`;
18
+ case "numeric": {
19
+ if (min && max) return `${label}: ${min} – ${max}`;
20
+ if (min) return `${label}: ≥ ${min}`;
21
+ return `${label}: ≤ ${max ?? ""}`;
22
+ }
23
+ case "date":
24
+ case "daterange":
25
+ case "datetime":
26
+ case "datetimerange": {
27
+ if (min && max) return `${label}: ${min} → ${max}`;
28
+ if (min) return `${label}: from ${min}`;
29
+ return `${label}: until ${max ?? ""}`;
30
+ }
31
+ default:
32
+ return label;
33
+ }
34
+ }
35
+
36
+ interface ActiveFiltersProps {
37
+ filters: ActiveFilterValue[];
38
+ onRemove: (field: string) => void;
39
+ className?: string;
40
+ }
41
+
42
+ export function ActiveFilters({ filters, onRemove, className }: ActiveFiltersProps) {
43
+ if (filters.length === 0) return null;
44
+ return (
45
+ <div className={`flex flex-wrap gap-2 ${className ?? ""}`}>
46
+ {filters.map((filter) => (
47
+ <Button
48
+ key={filter.field}
49
+ variant="outline"
50
+ size="sm"
51
+ className="gap-1 h-7 text-xs"
52
+ aria-label={`Remove ${formatLabel(filter)}`}
53
+ onClick={() => onRemove(filter.field)}
54
+ >
55
+ {formatLabel(filter)}
56
+ <X className="h-3 w-3" aria-hidden="true" />
57
+ </Button>
58
+ ))}
59
+ </div>
60
+ );
61
+ }