@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,197 @@
1
+ /**
2
+ * SearchResultsPanel Component
3
+ *
4
+ * Displays the search results panel with loading, error, and empty states.
5
+ * Renders a list of SearchResultCard components and pagination controls.
6
+ *
7
+ * @param columns - Array of column definitions for displaying result data
8
+ * @param results - Array of search result records to display
9
+ * @param columnsLoading - Whether column metadata is currently loading
10
+ * @param resultsLoading - Whether search results are currently loading
11
+ * @param columnsError - Error message if column fetch failed
12
+ * @param resultsError - Error message if results fetch failed
13
+ * @param currentPageToken - Current pagination token
14
+ * @param pageSize - Number of results per page
15
+ * @param onPageChange - Callback when pagination changes; second arg optional: "next" | "prev" | "first" (cursor-stack pagination)
16
+ * @param onPageSizeChange - Callback when page size changes
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * <SearchResultsPanel
21
+ * columns={columns}
22
+ * results={results}
23
+ * columnsLoading={false}
24
+ * resultsLoading={false}
25
+ * columnsError={null}
26
+ * resultsError={null}
27
+ * currentPageToken="0"
28
+ * pageSize={25}
29
+ * onPageChange={(token, direction) => handlePageChange(token, direction)}
30
+ * onPageSizeChange={(size) => setPageSize(size)}
31
+ * />
32
+ * ```
33
+ */
34
+ import { useMemo } from "react";
35
+ import { Alert, AlertDescription, AlertTitle } from "../../../../components/ui/alert";
36
+ import { Skeleton } from "../../../../components/ui/skeleton";
37
+ import { AlertCircle } from "lucide-react";
38
+ import {
39
+ Select,
40
+ SelectContent,
41
+ SelectItem,
42
+ SelectTrigger,
43
+ SelectValue,
44
+ } from "../../../../components/ui/select";
45
+ import { Label } from "../../../../components/ui/label";
46
+ import SearchResultCard from "./SearchResultCard";
47
+ import SearchPagination from "./SearchPagination";
48
+ import type { Column, SearchResultRecord } from "../../types/search/searchResults";
49
+ import { getSafeKey } from "../../utils/recordUtils";
50
+
51
+ interface SearchResultsPanelProps {
52
+ /** API name of the object being searched (e.g. for detail page navigation). */
53
+ objectApiName?: string;
54
+ columns: Column[];
55
+ results: SearchResultRecord[];
56
+ columnsLoading: boolean;
57
+ resultsLoading: boolean;
58
+ columnsError: string | null;
59
+ resultsError: string | null;
60
+ currentPageToken: string;
61
+ nextPageToken: string | null;
62
+ previousPageToken: string | null;
63
+ hasNextPage?: boolean;
64
+ hasPreviousPage?: boolean;
65
+ pageSize: number;
66
+ sortBy: string;
67
+ onPageChange: (newPageToken: string, direction?: "next" | "prev" | "first") => void;
68
+ onPageSizeChange: (newPageSize: number) => void;
69
+ onSortByChange: (newSortBy: string) => void;
70
+ }
71
+
72
+ export default function SearchResultsPanel({
73
+ objectApiName,
74
+ columns,
75
+ results,
76
+ columnsLoading,
77
+ resultsLoading,
78
+ columnsError,
79
+ resultsError,
80
+ currentPageToken,
81
+ nextPageToken,
82
+ previousPageToken,
83
+ hasNextPage = false,
84
+ hasPreviousPage = false,
85
+ pageSize,
86
+ sortBy,
87
+ onPageChange,
88
+ onPageSizeChange,
89
+ onSortByChange,
90
+ }: SearchResultsPanelProps) {
91
+ const sortableColumns = useMemo(() => columns.filter(({ sortable }) => sortable), [columns]);
92
+
93
+ const validResults = useMemo(
94
+ () => results.filter((record) => record && record.record && record.record.id),
95
+ [results],
96
+ );
97
+ if (columnsError || resultsError) {
98
+ return (
99
+ <Alert variant="destructive" role="alert">
100
+ <AlertCircle className="h-4 w-4" aria-hidden="true" />
101
+ <AlertTitle>Error</AlertTitle>
102
+ <AlertDescription>
103
+ {columnsError || resultsError || "Failed to load search results"}
104
+ </AlertDescription>
105
+ </Alert>
106
+ );
107
+ }
108
+
109
+ if (resultsLoading || columnsLoading) {
110
+ return (
111
+ <div
112
+ className="space-y-4"
113
+ role="status"
114
+ aria-live="polite"
115
+ aria-label="Loading search results"
116
+ >
117
+ <span className="sr-only">Loading search results</span>
118
+ {[1, 2, 3].map((i) => (
119
+ <div key={i} className="border rounded-lg p-6" aria-hidden="true">
120
+ <Skeleton className="h-6 w-3/4 mb-4" />
121
+ <Skeleton className="h-4 w-full mb-2" />
122
+ <Skeleton className="h-4 w-2/3" />
123
+ </div>
124
+ ))}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ if (results.length === 0) {
130
+ return (
131
+ <div className="text-center py-12" role="status" aria-live="polite">
132
+ <p className="text-lg mb-2">No results found</p>
133
+ <p className="text-sm">Try adjusting your search query or filters</p>
134
+ </div>
135
+ );
136
+ }
137
+
138
+ return (
139
+ <>
140
+ {sortableColumns.length > 0 && (
141
+ <div
142
+ className="mb-6 flex items-center gap-2 justify-end"
143
+ role="group"
144
+ aria-label="Sort options"
145
+ >
146
+ <Label htmlFor="sort-by-select" className="text-sm font-normal whitespace-nowrap">
147
+ Sort by:
148
+ </Label>
149
+ <Select value={sortBy || ""} onValueChange={onSortByChange}>
150
+ <SelectTrigger
151
+ id="sort-by-select"
152
+ className="w-[200px]"
153
+ aria-label="Sort search results by field"
154
+ >
155
+ <SelectValue placeholder="Select field..." />
156
+ </SelectTrigger>
157
+ <SelectContent>
158
+ <SelectItem value="relevance">Relevance</SelectItem>
159
+ {sortableColumns.map((column) => (
160
+ <SelectItem key={column.fieldApiName} value={column.fieldApiName}>
161
+ {column.label}
162
+ </SelectItem>
163
+ ))}
164
+ </SelectContent>
165
+ </Select>
166
+ </div>
167
+ )}
168
+
169
+ <div className="space-y-4 mb-6" role="list" aria-label="Search results list">
170
+ {validResults.map((record, index) => {
171
+ const recordId = record.record.id;
172
+ const safeKey = getSafeKey(recordId, index);
173
+ return (
174
+ <div key={safeKey} role="listitem">
175
+ <SearchResultCard
176
+ record={record.record}
177
+ columns={columns}
178
+ objectApiName={objectApiName}
179
+ />
180
+ </div>
181
+ );
182
+ })}
183
+ </div>
184
+
185
+ <SearchPagination
186
+ currentPageToken={currentPageToken}
187
+ nextPageToken={nextPageToken}
188
+ previousPageToken={previousPageToken}
189
+ hasNextPage={hasNextPage}
190
+ hasPreviousPage={hasPreviousPage}
191
+ pageSize={pageSize}
192
+ onPageChange={onPageChange}
193
+ onPageSizeChange={onPageSizeChange}
194
+ />
195
+ </>
196
+ );
197
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * LoadingFallback Component
3
+ *
4
+ * Loading fallback component for Suspense boundaries.
5
+ * Displays a centered spinner while lazy-loaded components are being fetched.
6
+ *
7
+ * @remarks
8
+ * - Used with React Suspense for code splitting
9
+ * - Simple centered spinner design
10
+ * - Responsive and accessible
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <Suspense fallback={<LoadingFallback />}>
15
+ * <LazyComponent />
16
+ * </Suspense>
17
+ * ```
18
+ */
19
+ import { cva, type VariantProps } from "class-variance-authority";
20
+ import { Spinner } from "../../../../components/ui/spinner";
21
+
22
+ /**
23
+ * Spinner size variants based on content width.
24
+ */
25
+ const spinnerVariants = cva("", {
26
+ variants: {
27
+ contentMaxWidth: {
28
+ sm: "size-6",
29
+ md: "size-8",
30
+ lg: "size-10",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ contentMaxWidth: "sm",
35
+ },
36
+ });
37
+
38
+ interface LoadingFallbackProps extends VariantProps<typeof spinnerVariants> {
39
+ /**
40
+ * Maximum width of the content container. Also scales the spinner size.
41
+ * @default "sm"
42
+ */
43
+ contentMaxWidth?: "sm" | "md" | "lg";
44
+ /**
45
+ * Accessible label for screen readers.
46
+ * @default "Loading…"
47
+ */
48
+ loadingText?: string;
49
+ }
50
+
51
+ export default function LoadingFallback({
52
+ contentMaxWidth = "sm",
53
+ loadingText = "Loading…",
54
+ }: LoadingFallbackProps) {
55
+ return (
56
+ <div className="flex justify-center" role="status" aria-live="polite">
57
+ <Spinner className={spinnerVariants({ contentMaxWidth })} aria-hidden="true" />
58
+ <span className="sr-only">{loadingText}</span>
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Application-wide Constants
3
+ *
4
+ * Defines constants used throughout the global search feature.
5
+ */
6
+
7
+ /**
8
+ * Object API names to search across in the global search feature.
9
+ * Currently supports single object search.
10
+ *
11
+ * @remarks
12
+ * - Array of Salesforce object API names
13
+ * - First element is used as the primary search object
14
+ * - Can be extended to support multiple objects in the future
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const objectApiName = OBJECT_API_NAMES[0]; // 'Account'
19
+ * ```
20
+ */
21
+ export const OBJECT_API_NAMES = ["Account"] as const;
22
+
23
+ /** Fallback title when record display name cannot be resolved (e.g. before load or no name field). */
24
+ export const DEFAULT_DETAIL_PAGE_TITLE = "Untitled";
25
+
26
+ /**
27
+ * Default page size for search results pagination.
28
+ * This should match one of the values in PAGE_SIZE_OPTIONS from paginationUtils.
29
+ *
30
+ * @remarks
31
+ * - Default value is 20 (second option in PAGE_SIZE_OPTIONS)
32
+ * - Can be changed by user via pagination controls
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
37
+ * ```
38
+ */
39
+ export const DEFAULT_PAGE_SIZE = 20;
@@ -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,209 @@
1
+ import React, { useId } from "react";
2
+ import { createFormHookContexts, createFormHook } from "@tanstack/react-form";
3
+ import {
4
+ Field,
5
+ FieldDescription,
6
+ FieldError,
7
+ FieldLabel,
8
+ } from "../../../components/ui/field";
9
+ import { Input } from "../../../components/ui/input";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../../components/ui/select";
17
+ import { cn } from "../../../lib/utils";
18
+ import type { PicklistValue } from "../types/filters/picklist";
19
+ import { getUniqueErrors } from "../utils/formUtils";
20
+
21
+ export type { FormError } from "../utils/formUtils";
22
+ export { validateRangeValues } from "../utils/formUtils";
23
+
24
+ export const { fieldContext, formContext, useFieldContext, useFormContext } =
25
+ createFormHookContexts();
26
+
27
+ interface FilterTextFieldProps extends Omit<
28
+ React.ComponentProps<typeof Input>,
29
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
30
+ > {
31
+ label: string;
32
+ description?: React.ReactNode;
33
+ placeholder?: string;
34
+ }
35
+
36
+ function FilterTextField({
37
+ label,
38
+ id: providedId,
39
+ description,
40
+ placeholder,
41
+ type = "text",
42
+ ...props
43
+ }: FilterTextFieldProps) {
44
+ const field = useFieldContext<string>();
45
+ const generatedId = useId();
46
+ const id = providedId ?? generatedId;
47
+ const descriptionId = `${id}-description`;
48
+ const errorId = `${id}-error`;
49
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
50
+
51
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
52
+
53
+ return (
54
+ <Field data-invalid={isInvalid}>
55
+ <FieldLabel htmlFor={id}>{label}</FieldLabel>
56
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
57
+ <Input
58
+ id={id}
59
+ name={field.name as string}
60
+ type={type}
61
+ value={field.state.value ?? ""}
62
+ onBlur={field.handleBlur}
63
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
64
+ placeholder={placeholder}
65
+ aria-invalid={isInvalid}
66
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
67
+ {...props}
68
+ />
69
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
70
+ </Field>
71
+ );
72
+ }
73
+
74
+ interface FilterSelectFieldProps {
75
+ label: string;
76
+ id?: string;
77
+ description?: React.ReactNode;
78
+ placeholder?: string;
79
+ options: PicklistValue[];
80
+ }
81
+
82
+ function FilterSelectField({
83
+ label,
84
+ id: providedId,
85
+ description,
86
+ placeholder = "Select...",
87
+ options,
88
+ }: FilterSelectFieldProps) {
89
+ const field = useFieldContext<string>();
90
+ const generatedId = useId();
91
+ const id = providedId ?? generatedId;
92
+ const descriptionId = `${id}-description`;
93
+ const errorId = `${id}-error`;
94
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
95
+
96
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
97
+
98
+ return (
99
+ <Field data-invalid={isInvalid}>
100
+ <FieldLabel htmlFor={id}>{label}</FieldLabel>
101
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
102
+ <Select value={field.state.value ?? ""} onValueChange={(value) => field.handleChange(value)}>
103
+ <SelectTrigger
104
+ id={id}
105
+ aria-invalid={isInvalid}
106
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
107
+ >
108
+ <SelectValue placeholder={placeholder} />
109
+ </SelectTrigger>
110
+ <SelectContent>
111
+ {options.map((option) => {
112
+ if (!option || !option.value) return null;
113
+ return (
114
+ <SelectItem key={option.value} value={option.value}>
115
+ {option.label || option.value}
116
+ </SelectItem>
117
+ );
118
+ })}
119
+ </SelectContent>
120
+ </Select>
121
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
122
+ </Field>
123
+ );
124
+ }
125
+
126
+ interface FilterRangeFieldProps extends Omit<
127
+ React.ComponentProps<typeof Input>,
128
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
129
+ > {
130
+ label?: string;
131
+ description?: React.ReactNode;
132
+ placeholder?: string;
133
+ }
134
+
135
+ function FilterRangeFieldBase({
136
+ label,
137
+ id: providedId,
138
+ description,
139
+ placeholder,
140
+ type = "text",
141
+ ...props
142
+ }: FilterRangeFieldProps) {
143
+ const field = useFieldContext<string>();
144
+ const generatedId = useId();
145
+ const id = providedId ?? generatedId;
146
+ const descriptionId = `${id}-description`;
147
+ const errorId = `${id}-error`;
148
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
149
+
150
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
151
+
152
+ return (
153
+ <div>
154
+ {label && <FieldLabel htmlFor={id}>{label}</FieldLabel>}
155
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
156
+ <Input
157
+ id={id}
158
+ name={field.name as string}
159
+ type={type}
160
+ value={field.state.value ?? ""}
161
+ onBlur={field.handleBlur}
162
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
163
+ placeholder={placeholder}
164
+ aria-invalid={isInvalid}
165
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
166
+ {...props}
167
+ />
168
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
169
+ </div>
170
+ );
171
+ }
172
+
173
+ interface FilterRangeMinFieldProps extends Omit<
174
+ React.ComponentProps<typeof Input>,
175
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
176
+ > {
177
+ label?: string;
178
+ description?: React.ReactNode;
179
+ placeholder?: string;
180
+ }
181
+
182
+ interface FilterRangeMaxFieldProps extends Omit<
183
+ React.ComponentProps<typeof Input>,
184
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
185
+ > {
186
+ label?: string;
187
+ description?: React.ReactNode;
188
+ placeholder?: string;
189
+ }
190
+
191
+ function FilterRangeMinField({ placeholder = "Min", ...props }: FilterRangeMinFieldProps) {
192
+ return <FilterRangeFieldBase placeholder={placeholder} {...props} />;
193
+ }
194
+
195
+ function FilterRangeMaxField({ placeholder = "Max", ...props }: FilterRangeMaxFieldProps) {
196
+ return <FilterRangeFieldBase placeholder={placeholder} {...props} />;
197
+ }
198
+
199
+ export const { useAppForm } = createFormHook({
200
+ fieldContext,
201
+ formContext,
202
+ fieldComponents: {
203
+ FilterTextField,
204
+ FilterSelectField,
205
+ FilterRangeMinField,
206
+ FilterRangeMaxField,
207
+ },
208
+ formComponents: {},
209
+ });