@salesforce/ui-bundle-template-feature-react-global-search 1.117.2

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 (231) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +250 -0
  3. package/dist/.forceignore +15 -0
  4. package/dist/.husky/pre-commit +4 -0
  5. package/dist/.prettierignore +11 -0
  6. package/dist/.prettierrc +17 -0
  7. package/dist/AGENT.md +193 -0
  8. package/dist/CHANGELOG.md +2128 -0
  9. package/dist/README.md +28 -0
  10. package/dist/config/project-scratch-def.json +13 -0
  11. package/dist/eslint.config.js +7 -0
  12. package/dist/force-app/main/default/uiBundles/feature-react-global-search/.forceignore +15 -0
  13. package/dist/force-app/main/default/uiBundles/feature-react-global-search/.graphqlrc.yml +2 -0
  14. package/dist/force-app/main/default/uiBundles/feature-react-global-search/.prettierignore +9 -0
  15. package/dist/force-app/main/default/uiBundles/feature-react-global-search/.prettierrc +11 -0
  16. package/dist/force-app/main/default/uiBundles/feature-react-global-search/CHANGELOG.md +10 -0
  17. package/dist/force-app/main/default/uiBundles/feature-react-global-search/README.md +75 -0
  18. package/dist/force-app/main/default/uiBundles/feature-react-global-search/codegen.yml +95 -0
  19. package/dist/force-app/main/default/uiBundles/feature-react-global-search/components.json +18 -0
  20. package/dist/force-app/main/default/uiBundles/feature-react-global-search/e2e/app.spec.ts +17 -0
  21. package/dist/force-app/main/default/uiBundles/feature-react-global-search/eslint.config.js +169 -0
  22. package/dist/force-app/main/default/uiBundles/feature-react-global-search/feature-react-global-search.uibundle-meta.xml +7 -0
  23. package/dist/force-app/main/default/uiBundles/feature-react-global-search/index.html +12 -0
  24. package/dist/force-app/main/default/uiBundles/feature-react-global-search/package.json +70 -0
  25. package/dist/force-app/main/default/uiBundles/feature-react-global-search/playwright.config.ts +24 -0
  26. package/dist/force-app/main/default/uiBundles/feature-react-global-search/scripts/get-graphql-schema.mjs +68 -0
  27. package/dist/force-app/main/default/uiBundles/feature-react-global-search/scripts/rewrite-e2e-assets.mjs +23 -0
  28. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/api/graphqlClient.ts +25 -0
  29. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/app.tsx +17 -0
  30. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/appLayout.tsx +83 -0
  31. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/icons/book.svg +3 -0
  32. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/icons/copy.svg +4 -0
  33. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/icons/rocket.svg +3 -0
  34. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/icons/star.svg +3 -0
  35. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/images/codey-1.png +0 -0
  36. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/images/codey-2.png +0 -0
  37. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/images/codey-3.png +0 -0
  38. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/assets/images/vibe-codey.svg +194 -0
  39. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/alerts/status-alert.tsx +49 -0
  40. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/layouts/card-layout.tsx +29 -0
  41. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/alert.tsx +76 -0
  42. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/badge.tsx +48 -0
  43. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/breadcrumb.tsx +109 -0
  44. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/button.tsx +67 -0
  45. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/calendar.tsx +232 -0
  46. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/card.tsx +103 -0
  47. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/checkbox.tsx +32 -0
  48. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/collapsible.tsx +33 -0
  49. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/datePicker.tsx +127 -0
  50. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/dialog.tsx +162 -0
  51. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/field.tsx +237 -0
  52. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/index.ts +84 -0
  53. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/input.tsx +19 -0
  54. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/label.tsx +22 -0
  55. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/pagination.tsx +132 -0
  56. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/popover.tsx +89 -0
  57. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/select.tsx +193 -0
  58. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/separator.tsx +26 -0
  59. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/skeleton.tsx +14 -0
  60. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/sonner.tsx +20 -0
  61. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/spinner.tsx +16 -0
  62. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/table.tsx +114 -0
  63. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/tabs.tsx +88 -0
  64. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +102 -0
  65. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +137 -0
  66. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +95 -0
  67. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/recordListGraphQLService.ts +364 -0
  68. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailFields.tsx +55 -0
  69. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailForm.tsx +146 -0
  70. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailHeader.tsx +34 -0
  71. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailLayoutSections.tsx +80 -0
  72. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/Section.tsx +108 -0
  73. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/SectionRow.tsx +20 -0
  74. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/UiApiDetailForm.tsx +140 -0
  75. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  76. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +29 -0
  77. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +17 -0
  78. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +24 -0
  79. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedText.tsx +11 -0
  80. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +29 -0
  81. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterField.tsx +54 -0
  82. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterInput.tsx +55 -0
  83. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterSelect.tsx +72 -0
  84. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FiltersPanel.tsx +380 -0
  85. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/forms/filters-form.tsx +114 -0
  86. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/forms/submit-button.tsx +47 -0
  87. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/GlobalSearchInput.tsx +114 -0
  88. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/ResultCardFields.tsx +71 -0
  89. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchHeader.tsx +31 -0
  90. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchPagination.tsx +144 -0
  91. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchResultCard.tsx +138 -0
  92. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchResultsPanel.tsx +197 -0
  93. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/shared/LoadingFallback.tsx +61 -0
  94. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/constants.ts +39 -0
  95. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/filters/FilterInput.tsx +55 -0
  96. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/filters/FilterSelect.tsx +72 -0
  97. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/form.tsx +209 -0
  98. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +72 -0
  99. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +174 -0
  100. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +137 -0
  101. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useRecordListGraphQL.ts +135 -0
  102. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/pages/DetailPage.tsx +109 -0
  103. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx +235 -0
  104. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/filters/filters.ts +121 -0
  105. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/filters/picklist.ts +6 -0
  106. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/objectInfo/objectInfo.ts +49 -0
  107. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/recordDetail/recordDetail.ts +61 -0
  108. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/schema.d.ts +200 -0
  109. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/search/searchResults.ts +229 -0
  110. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +59 -0
  111. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/cacheUtils.ts +76 -0
  112. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/debounce.ts +90 -0
  113. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/fieldUtils.ts +354 -0
  114. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/fieldValueExtractor.ts +67 -0
  115. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/filterUtils.ts +32 -0
  116. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/formDataTransformUtils.ts +260 -0
  117. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/formUtils.ts +142 -0
  118. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLNodeFieldUtils.ts +186 -0
  119. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +77 -0
  120. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLRecordAdapter.ts +90 -0
  121. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/layoutTransformUtils.ts +236 -0
  122. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/linkUtils.ts +14 -0
  123. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/paginationUtils.ts +49 -0
  124. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/recordUtils.ts +159 -0
  125. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/sanitizationUtils.ts +50 -0
  126. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/index.ts +120 -0
  127. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/lib/utils.ts +6 -0
  128. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/navigationMenu.tsx +80 -0
  129. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/pages/Home.tsx +13 -0
  130. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/pages/NotFound.tsx +18 -0
  131. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/router-utils.tsx +35 -0
  132. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/routes.tsx +44 -0
  133. package/dist/force-app/main/default/uiBundles/feature-react-global-search/src/styles/global.css +135 -0
  134. package/dist/force-app/main/default/uiBundles/feature-react-global-search/tsconfig.json +42 -0
  135. package/dist/force-app/main/default/uiBundles/feature-react-global-search/tsconfig.node.json +13 -0
  136. package/dist/force-app/main/default/uiBundles/feature-react-global-search/ui-bundle.json +7 -0
  137. package/dist/force-app/main/default/uiBundles/feature-react-global-search/vite-env.d.ts +1 -0
  138. package/dist/force-app/main/default/uiBundles/feature-react-global-search/vite.config.ts +106 -0
  139. package/dist/force-app/main/default/uiBundles/feature-react-global-search/vitest-env.d.ts +2 -0
  140. package/dist/force-app/main/default/uiBundles/feature-react-global-search/vitest.config.ts +11 -0
  141. package/dist/force-app/main/default/uiBundles/feature-react-global-search/vitest.setup.ts +1 -0
  142. package/dist/jest.config.js +6 -0
  143. package/dist/package-lock.json +9995 -0
  144. package/dist/package.json +40 -0
  145. package/dist/scripts/apex/hello.apex +10 -0
  146. package/dist/scripts/graphql-search.sh +191 -0
  147. package/dist/scripts/prepare-import-unique-fields.js +122 -0
  148. package/dist/scripts/setup-cli.mjs +563 -0
  149. package/dist/scripts/sf-project-setup.mjs +66 -0
  150. package/dist/scripts/soql/account.soql +6 -0
  151. package/dist/sfdx-project.json +12 -0
  152. package/package.json +53 -0
  153. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/__inherit__appLayout.tsx +9 -0
  154. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/alerts/__inherit__status-alert.tsx +27 -0
  155. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/layouts/__inherit__card-layout.tsx +9 -0
  156. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__alert.tsx +41 -0
  157. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__button.tsx +45 -0
  158. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__card.tsx +33 -0
  159. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__field.tsx +62 -0
  160. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__input.tsx +5 -0
  161. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__label.tsx +8 -0
  162. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__pagination.tsx +47 -0
  163. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__select.tsx +57 -0
  164. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__skeleton.tsx +5 -0
  165. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/components/ui/__inherit__spinner.tsx +7 -0
  166. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +102 -0
  167. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +137 -0
  168. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +95 -0
  169. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/api/recordListGraphQLService.ts +364 -0
  170. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailFields.tsx +55 -0
  171. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailForm.tsx +146 -0
  172. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailHeader.tsx +34 -0
  173. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/DetailLayoutSections.tsx +80 -0
  174. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/Section.tsx +108 -0
  175. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/SectionRow.tsx +20 -0
  176. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/UiApiDetailForm.tsx +140 -0
  177. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  178. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +29 -0
  179. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +17 -0
  180. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +24 -0
  181. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedText.tsx +11 -0
  182. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +29 -0
  183. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterField.tsx +54 -0
  184. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterInput.tsx +55 -0
  185. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FilterSelect.tsx +72 -0
  186. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/filters/FiltersPanel.tsx +380 -0
  187. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/forms/filters-form.tsx +114 -0
  188. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/forms/submit-button.tsx +47 -0
  189. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/GlobalSearchInput.tsx +114 -0
  190. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/ResultCardFields.tsx +71 -0
  191. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchHeader.tsx +31 -0
  192. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchPagination.tsx +144 -0
  193. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchResultCard.tsx +138 -0
  194. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/search/SearchResultsPanel.tsx +197 -0
  195. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/shared/LoadingFallback.tsx +61 -0
  196. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/constants.ts +39 -0
  197. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/filters/FilterInput.tsx +55 -0
  198. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/filters/FilterSelect.tsx +72 -0
  199. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/form.tsx +209 -0
  200. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +72 -0
  201. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +174 -0
  202. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +137 -0
  203. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/hooks/useRecordListGraphQL.ts +135 -0
  204. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/pages/DetailPage.tsx +109 -0
  205. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx +235 -0
  206. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/filters/filters.ts +121 -0
  207. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/filters/picklist.ts +6 -0
  208. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/objectInfo/objectInfo.ts +49 -0
  209. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/recordDetail/recordDetail.ts +61 -0
  210. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/schema.d.ts +200 -0
  211. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/types/search/searchResults.ts +229 -0
  212. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +59 -0
  213. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/cacheUtils.ts +76 -0
  214. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/debounce.ts +90 -0
  215. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/fieldUtils.ts +354 -0
  216. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/fieldValueExtractor.ts +67 -0
  217. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/filterUtils.ts +32 -0
  218. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/formDataTransformUtils.ts +260 -0
  219. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/formUtils.ts +142 -0
  220. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLNodeFieldUtils.ts +186 -0
  221. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +77 -0
  222. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/graphQLRecordAdapter.ts +90 -0
  223. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/layoutTransformUtils.ts +236 -0
  224. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/linkUtils.ts +14 -0
  225. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/paginationUtils.ts +49 -0
  226. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/recordUtils.ts +159 -0
  227. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/utils/sanitizationUtils.ts +50 -0
  228. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/index.ts +120 -0
  229. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/lib/__inherit__utils.ts +5 -0
  230. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/pages/Home.tsx +13 -0
  231. package/src/force-app/main/default/uiBundles/feature-react-global-search/src/routes.tsx +52 -0
@@ -0,0 +1,17 @@
1
+ /** Email as mailto: link. */
2
+
3
+ export interface FormattedEmailProps {
4
+ value: string;
5
+ className?: string;
6
+ }
7
+
8
+ export function FormattedEmail({ value, className }: FormattedEmailProps) {
9
+ const str = (value || "").trim();
10
+ if (!str) return null;
11
+ const href = `mailto:${encodeURIComponent(str)}`;
12
+ return (
13
+ <a href={href} className={className} aria-label={`Email ${str}`}>
14
+ {str}
15
+ </a>
16
+ );
17
+ }
@@ -0,0 +1,24 @@
1
+ /** Phone value as tel: link for dialer/VoIP. */
2
+
3
+ export interface FormattedPhoneProps {
4
+ value: string;
5
+ className?: string;
6
+ }
7
+
8
+ /** Normalizes value for tel: (digits and + only). */
9
+ function telHref(value: string): string {
10
+ const cleaned = value.replace(/[^\d+]/g, "");
11
+ return cleaned ? `tel:${cleaned}` : "#";
12
+ }
13
+
14
+ export function FormattedPhone({ value, className }: FormattedPhoneProps) {
15
+ if (!value) return null;
16
+ const trimmed = (value || "").trim();
17
+ const href = telHref(trimmed);
18
+ if (href === "#") return <span className={className}>{trimmed}</span>;
19
+ return (
20
+ <a href={href} className={className} aria-label={`Call ${trimmed}`}>
21
+ {trimmed}
22
+ </a>
23
+ );
24
+ }
@@ -0,0 +1,11 @@
1
+ /** Plain-text field value when dataType has no dedicated formatter. */
2
+
3
+ export interface FormattedTextProps {
4
+ value: string | number | boolean | null | undefined;
5
+ className?: string;
6
+ }
7
+
8
+ export function FormattedText({ value, className }: FormattedTextProps) {
9
+ if (!value) return null;
10
+ return <span className={className}>{value as string}</span>;
11
+ }
@@ -0,0 +1,29 @@
1
+ /** URL as external link (new tab, noopener noreferrer). Falls back to plain text if not http(s). */
2
+
3
+ import { isAllowedLinkUrl } from "../../../utils/linkUtils";
4
+
5
+ export interface FormattedUrlProps {
6
+ value: string;
7
+ className?: string;
8
+ /** Optional display text; defaults to the URL. */
9
+ displayText?: string;
10
+ }
11
+
12
+ export function FormattedUrl({ value, className, displayText }: FormattedUrlProps) {
13
+ const str = (value || "").trim();
14
+ if (!str) return null;
15
+ const href = str.startsWith("http://") || str.startsWith("https://") ? str : `https://${str}`;
16
+ if (!isAllowedLinkUrl(href)) return <span className={className}>{str}</span>;
17
+ const label = displayText ?? str;
18
+ return (
19
+ <a
20
+ href={href}
21
+ target="_blank"
22
+ rel="noopener noreferrer"
23
+ className={className}
24
+ aria-label={`Open link in new tab: ${label}`}
25
+ >
26
+ {label}
27
+ </a>
28
+ );
29
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * FilterField Component
3
+ *
4
+ * Wrapper component that renders the appropriate filter input type based on filter affordance.
5
+ * Routes to FilterInput for text fields or FilterSelect for picklist fields.
6
+ *
7
+ * @param filter - Filter definition containing field path, label, and affordance
8
+ * @param value - Current filter value
9
+ * @param picklistValues - Array of picklist options (for select fields)
10
+ * @param onChange - Callback when filter value changes
11
+ *
12
+ * @remarks
13
+ * - Automatically determines input type from filter.affordance
14
+ * - Returns null if filter is invalid
15
+ * - Defaults to text input if affordance is not 'select'
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <FilterField
20
+ * filter={filter}
21
+ * value={filterValue}
22
+ * picklistValues={picklistOptions}
23
+ * onChange={(value) => setFilterValue(value)}
24
+ * />
25
+ * ```
26
+ */
27
+ import FilterInput from "./FilterInput";
28
+ import FilterSelect from "./FilterSelect";
29
+ import type { Filter } from "../../types/filters/filters";
30
+ import type { PicklistValue } from "../../types/filters/picklist";
31
+
32
+ interface FilterFieldProps {
33
+ filter: Filter;
34
+ value: string;
35
+ picklistValues: PicklistValue[];
36
+ onChange: (value: string) => void;
37
+ }
38
+
39
+ export default function FilterField({ filter, value, picklistValues, onChange }: FilterFieldProps) {
40
+ // Guard against invalid filter objects
41
+ if (!filter || !filter.targetFieldPath) {
42
+ return null;
43
+ }
44
+
45
+ const affordance = filter.affordance?.toLowerCase() || "";
46
+
47
+ if (affordance === "select") {
48
+ const options = picklistValues || [];
49
+ return <FilterSelect filter={filter} value={value} options={options} onChange={onChange} />;
50
+ }
51
+
52
+ // Default to text input
53
+ return <FilterInput filter={filter} value={value} onChange={onChange} />;
54
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * FilterInput Component
3
+ *
4
+ * Renders a text input field for filter values.
5
+ * Used for filters that don't have a picklist (affordance !== 'select').
6
+ *
7
+ * @param filter - Filter definition containing field path, label, and attributes
8
+ * @param value - Current filter input value
9
+ * @param onChange - Callback when input value changes
10
+ *
11
+ * @remarks
12
+ * - Displays filter label or field path as the label
13
+ * - Shows placeholder text from filter attributes or generates default
14
+ * - Displays help message if available
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <FilterInput
19
+ * filter={textFilter}
20
+ * value={filterValue}
21
+ * onChange={(value) => setFilterValue(value)}
22
+ * />
23
+ * ```
24
+ */
25
+ import { Input } from "../../../../components/ui/input";
26
+ import { Field, FieldLabel, FieldDescription } from "../../../../components/ui/field";
27
+ import type { Filter } from "../../types/filters/filters";
28
+
29
+ interface FilterInputProps {
30
+ filter: Filter;
31
+ value: string;
32
+ onChange: (value: string) => void;
33
+ }
34
+
35
+ export default function FilterInput({ filter, value, onChange }: FilterInputProps) {
36
+ return (
37
+ <Field>
38
+ <FieldLabel htmlFor={filter.targetFieldPath}>
39
+ {filter.label || filter.targetFieldPath}
40
+ </FieldLabel>
41
+ <Input
42
+ id={filter.targetFieldPath}
43
+ type="text"
44
+ value={value}
45
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
46
+ placeholder={
47
+ filter.attributes?.placeholder ||
48
+ `Enter ${(filter.label || filter.targetFieldPath).toLowerCase()}`
49
+ }
50
+ aria-label={filter.label || filter.targetFieldPath}
51
+ />
52
+ {filter.helpMessage && <FieldDescription>{filter.helpMessage}</FieldDescription>}
53
+ </Field>
54
+ );
55
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * FilterSelect Component
3
+ *
4
+ * Renders a dropdown select field for filter values with picklist options.
5
+ * Used for filters with affordance === 'select'.
6
+ *
7
+ * @param filter - Filter definition containing field path, label, and attributes
8
+ * @param value - Currently selected filter value
9
+ * @param options - Array of picklist values to display as options
10
+ * @param onChange - Callback when selection changes
11
+ *
12
+ * @remarks
13
+ * - Filters out invalid options (null/undefined values)
14
+ * - Displays option label if available, otherwise uses value
15
+ * - Shows placeholder from filter attributes or default "Select..."
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <FilterSelect
20
+ * filter={selectFilter}
21
+ * value={selectedValue}
22
+ * options={picklistOptions}
23
+ * onChange={(value) => setSelectedValue(value)}
24
+ * />
25
+ * ```
26
+ */
27
+ import {
28
+ Select,
29
+ SelectContent,
30
+ SelectItem,
31
+ SelectTrigger,
32
+ SelectValue,
33
+ } from "../../../../components/ui/select";
34
+ import { Field, FieldLabel, FieldDescription } from "../../../../components/ui/field";
35
+ import type { Filter } from "../../types/filters/filters";
36
+ import type { PicklistValue } from "../../types/filters/picklist";
37
+
38
+ interface FilterSelectProps {
39
+ filter: Filter;
40
+ value: string;
41
+ options: PicklistValue[];
42
+ onChange: (value: string) => void;
43
+ }
44
+
45
+ export default function FilterSelect({ filter, value, options, onChange }: FilterSelectProps) {
46
+ return (
47
+ <Field>
48
+ <FieldLabel htmlFor={filter.targetFieldPath}>
49
+ {filter.label || filter.targetFieldPath}
50
+ </FieldLabel>
51
+ <Select value={value} onValueChange={onChange}>
52
+ <SelectTrigger
53
+ id={filter.targetFieldPath}
54
+ aria-label={filter.label || filter.targetFieldPath}
55
+ >
56
+ <SelectValue placeholder={filter.attributes?.placeholder || "Select..."} />
57
+ </SelectTrigger>
58
+ <SelectContent>
59
+ {options.map((option) => {
60
+ if (!option || !option.value) return null;
61
+ return (
62
+ <SelectItem key={option.value} value={option.value}>
63
+ {option.label || option.value}
64
+ </SelectItem>
65
+ );
66
+ })}
67
+ </SelectContent>
68
+ </Select>
69
+ {filter.helpMessage && <FieldDescription>{filter.helpMessage}</FieldDescription>}
70
+ </Field>
71
+ );
72
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * FiltersPanel Component
3
+ *
4
+ * Displays a panel of filter inputs for refining search results.
5
+ * Supports both text inputs and select dropdowns based on filter affordance.
6
+ *
7
+ * @param filters - Array of filter definitions to display
8
+ * @param picklistValues - Record of picklist values keyed by field path
9
+ * @param loading - Whether filters are currently loading
10
+ * @param onApplyFilters - Callback when filters are applied, receives filter values object
11
+ *
12
+ * @remarks
13
+ * - Automatically initializes filter values from defaultValues
14
+ * - Shows loading skeleton while filters are being fetched
15
+ * - Supports "Apply Filters" and "Reset" actions
16
+ * - Uses TanStack Form for form state management (similar to Login page)
17
+ * - Uses FiltersForm wrapper for consistent UX/UI (similar to AuthForm pattern)
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <FiltersPanel
22
+ * filters={filters}
23
+ * picklistValues={picklistValues}
24
+ * loading={false}
25
+ * onApplyFilters={(values) => applyFilters(values)}
26
+ * />
27
+ * ```
28
+ */
29
+ import { useState, useMemo, useCallback, useEffect, useRef } from "react";
30
+ import {
31
+ Card,
32
+ CardContent,
33
+ CardHeader,
34
+ CardTitle,
35
+ } from "../../../../components/ui/card";
36
+ import { Skeleton } from "../../../../components/ui/skeleton";
37
+ import { FiltersForm } from "../forms/filters-form";
38
+ import { Field, FieldLabel, FieldDescription } from "../../../../components/ui/field";
39
+ import { useAppForm, validateRangeValues } from "../../hooks/form";
40
+ import type { Filter, FilterCriteria } from "../../types/filters/filters";
41
+ import type { PicklistValue } from "../../types/filters/picklist";
42
+ import { parseFilterValue } from "../../utils/filterUtils";
43
+ import { sanitizeFilterValue } from "../../utils/sanitizationUtils";
44
+ import { getFormValueByPath } from "../../utils/formUtils";
45
+
46
+ interface FiltersPanelProps {
47
+ filters: Filter[];
48
+ picklistValues: Record<string, PicklistValue[]>;
49
+ loading: boolean;
50
+ objectApiName: string;
51
+ onApplyFilters: (filterCriteria: FilterCriteria[]) => void;
52
+ }
53
+
54
+ export default function FiltersPanel({
55
+ filters,
56
+ picklistValues,
57
+ loading,
58
+ objectApiName,
59
+ onApplyFilters,
60
+ }: FiltersPanelProps) {
61
+ const [submitError, setSubmitError] = useState<string | null>(null);
62
+ const [submitSuccess, setSubmitSuccess] = useState<string | null>(null);
63
+
64
+ const defaultValues = useMemo(() => {
65
+ if (!filters || !Array.isArray(filters)) {
66
+ return {};
67
+ }
68
+
69
+ const values: Record<string, string> = {};
70
+ filters.forEach((filter) => {
71
+ if (filter && filter.targetFieldPath) {
72
+ const affordance = filter.affordance?.toLowerCase() || "";
73
+
74
+ if (affordance === "range") {
75
+ const minFieldName = `${filter.targetFieldPath}_min`;
76
+ const maxFieldName = `${filter.targetFieldPath}_max`;
77
+
78
+ if (filter.defaultValues && filter.defaultValues.length >= 2) {
79
+ values[minFieldName] = filter.defaultValues[0] || "";
80
+ values[maxFieldName] = filter.defaultValues[1] || "";
81
+ } else {
82
+ values[minFieldName] = "";
83
+ values[maxFieldName] = "";
84
+ }
85
+ } else {
86
+ if (filter.defaultValues && filter.defaultValues.length > 0) {
87
+ values[filter.targetFieldPath] = filter.defaultValues[0];
88
+ } else {
89
+ values[filter.targetFieldPath] = "";
90
+ }
91
+ }
92
+ }
93
+ });
94
+ return values;
95
+ }, [filters]);
96
+
97
+ const form = useAppForm({
98
+ defaultValues,
99
+ onSubmit: async ({ value }) => {
100
+ setSubmitError(null);
101
+ setSubmitSuccess(null);
102
+ try {
103
+ const filterCriteria: FilterCriteria[] = [];
104
+
105
+ for (const filter of filters) {
106
+ if (!filter || !filter.targetFieldPath) {
107
+ continue;
108
+ }
109
+
110
+ const affordance = filter.affordance?.toLowerCase() || "";
111
+
112
+ if (affordance === "range") {
113
+ const minFieldName = `${filter.targetFieldPath}_min`;
114
+ const maxFieldName = `${filter.targetFieldPath}_max`;
115
+ const minValueRaw = value[minFieldName] || "";
116
+ const maxValueRaw = value[maxFieldName] || "";
117
+
118
+ const minValue = sanitizeFilterValue(minValueRaw);
119
+ const maxValue = sanitizeFilterValue(maxValueRaw);
120
+
121
+ if (minValue && maxValue) {
122
+ const rangeError = validateRangeValues(minValue, maxValue);
123
+ if (rangeError) {
124
+ setSubmitError(rangeError);
125
+ return;
126
+ }
127
+ }
128
+
129
+ if (minValue) {
130
+ const parsedMin = parseFilterValue(minValue);
131
+ if (parsedMin !== "") {
132
+ filterCriteria.push({
133
+ objectApiName,
134
+ fieldPath: filter.targetFieldPath,
135
+ operator: "gte",
136
+ values: [parsedMin],
137
+ });
138
+ }
139
+ }
140
+
141
+ if (maxValue) {
142
+ const parsedMax = parseFilterValue(maxValue);
143
+ if (parsedMax !== "") {
144
+ filterCriteria.push({
145
+ objectApiName,
146
+ fieldPath: filter.targetFieldPath,
147
+ operator: "lte",
148
+ values: [parsedMax],
149
+ });
150
+ }
151
+ }
152
+ } else {
153
+ const fieldValueRaw =
154
+ getFormValueByPath(value as Record<string, unknown>, filter.targetFieldPath) || "";
155
+ const fieldValue = sanitizeFilterValue(fieldValueRaw);
156
+
157
+ if (fieldValue) {
158
+ if (affordance === "select") {
159
+ filterCriteria.push({
160
+ objectApiName,
161
+ fieldPath: filter.targetFieldPath,
162
+ operator: "eq",
163
+ values: [fieldValue],
164
+ });
165
+ } else {
166
+ const likeValue = `%${fieldValue}%`;
167
+ filterCriteria.push({
168
+ objectApiName,
169
+ fieldPath: filter.targetFieldPath,
170
+ operator: "like",
171
+ values: [likeValue],
172
+ });
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ if (filterCriteria.length === 0) {
179
+ setSubmitSuccess("No filters applied. Showing all results.");
180
+ } else {
181
+ setSubmitSuccess("Filters applied successfully");
182
+ }
183
+
184
+ onApplyFilters(filterCriteria);
185
+ } catch (err) {
186
+ const errorMessage = err instanceof Error ? err.message : "Failed to apply filters";
187
+ setSubmitError(errorMessage);
188
+ }
189
+ },
190
+ onSubmitInvalid: () => {},
191
+ });
192
+
193
+ const previousDefaultValuesRef = useRef<Record<string, string>>({});
194
+ const previousLoadingRef = useRef<boolean>(true);
195
+
196
+ useEffect(() => {
197
+ const loadingJustCompleted = previousLoadingRef.current && !loading;
198
+ const defaultValuesChanged =
199
+ JSON.stringify(previousDefaultValuesRef.current) !== JSON.stringify(defaultValues);
200
+
201
+ if (loadingJustCompleted && defaultValues && Object.keys(defaultValues).length > 0) {
202
+ form.reset(defaultValues);
203
+ previousDefaultValuesRef.current = defaultValues;
204
+ } else if (defaultValuesChanged && !loading && Object.keys(defaultValues).length > 0) {
205
+ form.reset(defaultValues);
206
+ previousDefaultValuesRef.current = defaultValues;
207
+ }
208
+
209
+ previousLoadingRef.current = loading;
210
+ }, [loading, defaultValues, form]);
211
+
212
+ const handleSuccessDismiss = useCallback(() => {
213
+ setSubmitSuccess(null);
214
+ }, []);
215
+
216
+ const handleReset = useCallback(() => {
217
+ if (!filters || !Array.isArray(filters)) {
218
+ form.reset();
219
+ onApplyFilters([]);
220
+ setSubmitError(null);
221
+ setSubmitSuccess(null);
222
+ return;
223
+ }
224
+
225
+ const resetValues: Record<string, string> = {};
226
+ filters.forEach((filter) => {
227
+ if (filter && filter.targetFieldPath) {
228
+ const affordance = filter.affordance?.toLowerCase() || "";
229
+
230
+ if (affordance === "range") {
231
+ resetValues[`${filter.targetFieldPath}_min`] = "";
232
+ resetValues[`${filter.targetFieldPath}_max`] = "";
233
+ } else {
234
+ resetValues[filter.targetFieldPath] = "";
235
+ }
236
+ }
237
+ });
238
+ form.reset(resetValues);
239
+ onApplyFilters([]);
240
+ setSubmitError(null);
241
+ setSubmitSuccess(null);
242
+ }, [filters, onApplyFilters, form]);
243
+
244
+ if (loading) {
245
+ return (
246
+ <Card className="w-full" role="region" aria-label="Filters panel">
247
+ <CardHeader>
248
+ <CardTitle>Filters</CardTitle>
249
+ </CardHeader>
250
+ <CardContent
251
+ className="space-y-4"
252
+ role="status"
253
+ aria-live="polite"
254
+ aria-label="Loading filters"
255
+ >
256
+ <span className="sr-only">Loading filters</span>
257
+ {[1, 2, 3].map((i) => (
258
+ <div key={i} className="space-y-2" aria-hidden="true">
259
+ <Skeleton className="h-4 w-24" />
260
+ <Skeleton className="h-9 w-full" />
261
+ </div>
262
+ ))}
263
+ </CardContent>
264
+ </Card>
265
+ );
266
+ }
267
+
268
+ if (!filters || !Array.isArray(filters) || filters.length === 0) {
269
+ return (
270
+ <Card className="w-full" role="region" aria-label="Filters panel">
271
+ <CardHeader>
272
+ <CardTitle>Filters</CardTitle>
273
+ </CardHeader>
274
+ <CardContent>
275
+ <p className="text-sm text-muted-foreground">No filters available</p>
276
+ </CardContent>
277
+ </Card>
278
+ );
279
+ }
280
+
281
+ return (
282
+ <form.AppForm>
283
+ <FiltersForm
284
+ title="Filters"
285
+ description="Refine your search results by applying filters"
286
+ error={submitError}
287
+ success={submitSuccess}
288
+ onSuccessDismiss={handleSuccessDismiss}
289
+ submit={{
290
+ text: "Apply Filters",
291
+ loadingText: "Applying filters…",
292
+ }}
293
+ reset={{
294
+ text: "Reset",
295
+ onReset: handleReset,
296
+ }}
297
+ >
298
+ {filters.map((filter) => {
299
+ if (!filter || !filter.targetFieldPath) {
300
+ return null;
301
+ }
302
+
303
+ const fieldPicklistValues = picklistValues[filter.targetFieldPath] || [];
304
+ const affordance = filter.affordance?.toLowerCase() || "";
305
+
306
+ if (affordance === "range") {
307
+ const minFieldName = `${filter.targetFieldPath}_min`;
308
+ const maxFieldName = `${filter.targetFieldPath}_max`;
309
+ const inputType = "text";
310
+ const placeholder =
311
+ filter.attributes?.placeholder === "null"
312
+ ? undefined
313
+ : filter.attributes?.placeholder;
314
+
315
+ return (
316
+ <Field key={filter.targetFieldPath}>
317
+ <FieldLabel>{filter.label || filter.targetFieldPath}</FieldLabel>
318
+ {filter.helpMessage && <FieldDescription>{filter.helpMessage}</FieldDescription>}
319
+ <div
320
+ className="grid grid-cols-2 gap-3"
321
+ role="group"
322
+ aria-label={`${filter.label || filter.targetFieldPath} range filter`}
323
+ >
324
+ <form.AppField name={minFieldName}>
325
+ {(field) => (
326
+ <field.FilterRangeMinField
327
+ placeholder={placeholder || "Min"}
328
+ type={inputType}
329
+ aria-label={`${filter.label || filter.targetFieldPath} - Minimum`}
330
+ />
331
+ )}
332
+ </form.AppField>
333
+ <form.AppField name={maxFieldName}>
334
+ {(field) => (
335
+ <field.FilterRangeMaxField
336
+ placeholder={placeholder || "Max"}
337
+ type={inputType}
338
+ aria-label={`${filter.label || filter.targetFieldPath} - Maximum`}
339
+ />
340
+ )}
341
+ </form.AppField>
342
+ </div>
343
+ </Field>
344
+ );
345
+ }
346
+
347
+ if (affordance === "select") {
348
+ return (
349
+ <form.AppField key={filter.targetFieldPath} name={filter.targetFieldPath}>
350
+ {(field) => (
351
+ <field.FilterSelectField
352
+ label={filter.label || filter.targetFieldPath}
353
+ description={filter.helpMessage || undefined}
354
+ placeholder={filter.attributes?.placeholder || "Select..."}
355
+ options={fieldPicklistValues}
356
+ />
357
+ )}
358
+ </form.AppField>
359
+ );
360
+ }
361
+
362
+ return (
363
+ <form.AppField key={filter.targetFieldPath} name={filter.targetFieldPath}>
364
+ {(field) => (
365
+ <field.FilterTextField
366
+ label={filter.label || filter.targetFieldPath}
367
+ description={filter.helpMessage || undefined}
368
+ placeholder={
369
+ filter.attributes?.placeholder ||
370
+ `Enter ${(filter.label || filter.targetFieldPath).toLowerCase()}`
371
+ }
372
+ />
373
+ )}
374
+ </form.AppField>
375
+ );
376
+ })}
377
+ </FiltersForm>
378
+ </form.AppForm>
379
+ );
380
+ }