@stapel/search-react 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +1 -1
  3. package/dist/default/DegradationNotice.d.ts +7 -0
  4. package/dist/default/DegradationNotice.d.ts.map +1 -1
  5. package/dist/default/DegradationNotice.js +17 -6
  6. package/dist/default/DegradationNotice.js.map +1 -1
  7. package/dist/default/FacetPanelPane.d.ts +54 -3
  8. package/dist/default/FacetPanelPane.d.ts.map +1 -1
  9. package/dist/default/FacetPanelPane.js +119 -19
  10. package/dist/default/FacetPanelPane.js.map +1 -1
  11. package/dist/default/LanguageSelect.d.ts +9 -0
  12. package/dist/default/LanguageSelect.d.ts.map +1 -0
  13. package/dist/default/LanguageSelect.js +72 -0
  14. package/dist/default/LanguageSelect.js.map +1 -0
  15. package/dist/default/PageSizeSelect.d.ts +29 -0
  16. package/dist/default/PageSizeSelect.d.ts.map +1 -0
  17. package/dist/default/PageSizeSelect.js +25 -0
  18. package/dist/default/PageSizeSelect.js.map +1 -0
  19. package/dist/default/RangeFilterRow.d.ts +12 -0
  20. package/dist/default/RangeFilterRow.d.ts.map +1 -0
  21. package/dist/default/RangeFilterRow.js +75 -0
  22. package/dist/default/RangeFilterRow.js.map +1 -0
  23. package/dist/default/RankingDisclosurePane.d.ts +5 -0
  24. package/dist/default/RankingDisclosurePane.d.ts.map +1 -1
  25. package/dist/default/RankingDisclosurePane.js +15 -15
  26. package/dist/default/RankingDisclosurePane.js.map +1 -1
  27. package/dist/default/SearchBox.d.ts +13 -0
  28. package/dist/default/SearchBox.d.ts.map +1 -0
  29. package/dist/default/SearchBox.js +67 -0
  30. package/dist/default/SearchBox.js.map +1 -0
  31. package/dist/default/SearchPage.d.ts +54 -16
  32. package/dist/default/SearchPage.d.ts.map +1 -1
  33. package/dist/default/SearchPage.js +155 -8
  34. package/dist/default/SearchPage.js.map +1 -1
  35. package/dist/default/SearchResultCard.d.ts +19 -18
  36. package/dist/default/SearchResultCard.d.ts.map +1 -1
  37. package/dist/default/SearchResultCard.js +178 -5
  38. package/dist/default/SearchResultCard.js.map +1 -1
  39. package/dist/default/SearchResultsPane.d.ts +42 -3
  40. package/dist/default/SearchResultsPane.d.ts.map +1 -1
  41. package/dist/default/SearchResultsPane.js +38 -21
  42. package/dist/default/SearchResultsPane.js.map +1 -1
  43. package/dist/default/SortSelect.d.ts +10 -2
  44. package/dist/default/SortSelect.d.ts.map +1 -1
  45. package/dist/default/SortSelect.js +27 -24
  46. package/dist/default/SortSelect.js.map +1 -1
  47. package/dist/default/UrlIssueNotice.d.ts.map +1 -1
  48. package/dist/default/UrlIssueNotice.js +19 -1
  49. package/dist/default/UrlIssueNotice.js.map +1 -1
  50. package/dist/default/index.d.ts +32 -15
  51. package/dist/default/index.d.ts.map +1 -1
  52. package/dist/default/index.js +26 -12
  53. package/dist/default/index.js.map +1 -1
  54. package/dist/default/sortLabels.d.ts +5 -0
  55. package/dist/default/sortLabels.d.ts.map +1 -0
  56. package/dist/default/sortLabels.js +32 -0
  57. package/dist/default/sortLabels.js.map +1 -0
  58. package/dist/headless/FacetPanel.d.ts +17 -0
  59. package/dist/headless/FacetPanel.d.ts.map +1 -1
  60. package/dist/headless/FacetPanel.js +23 -2
  61. package/dist/headless/FacetPanel.js.map +1 -1
  62. package/dist/headless/useAppliedCount.d.ts +8 -0
  63. package/dist/headless/useAppliedCount.d.ts.map +1 -0
  64. package/dist/headless/useAppliedCount.js +30 -0
  65. package/dist/headless/useAppliedCount.js.map +1 -0
  66. package/dist/headless/useScorerNames.d.ts +4 -0
  67. package/dist/headless/useScorerNames.d.ts.map +1 -0
  68. package/dist/headless/useScorerNames.js +34 -0
  69. package/dist/headless/useScorerNames.js.map +1 -0
  70. package/dist/headless/useSearchBox.d.ts +44 -0
  71. package/dist/headless/useSearchBox.d.ts.map +1 -0
  72. package/dist/headless/useSearchBox.js +116 -0
  73. package/dist/headless/useSearchBox.js.map +1 -0
  74. package/dist/i18n/es.d.ts.map +1 -1
  75. package/dist/i18n/es.js +30 -4
  76. package/dist/i18n/es.js.map +1 -1
  77. package/dist/i18n/keys.d.ts +35 -0
  78. package/dist/i18n/keys.d.ts.map +1 -1
  79. package/dist/i18n/keys.js +72 -4
  80. package/dist/i18n/keys.js.map +1 -1
  81. package/dist/i18n/ru.d.ts.map +1 -1
  82. package/dist/i18n/ru.js +34 -4
  83. package/dist/i18n/ru.js.map +1 -1
  84. package/dist/index.d.ts +7 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +5 -2
  87. package/dist/index.js.map +1 -1
  88. package/dist/model/queries.d.ts +26 -2
  89. package/dist/model/queries.d.ts.map +1 -1
  90. package/dist/model/queries.js +38 -1
  91. package/dist/model/queries.js.map +1 -1
  92. package/dist/state/limits.d.ts +44 -0
  93. package/dist/state/limits.d.ts.map +1 -0
  94. package/dist/state/limits.js +44 -0
  95. package/dist/state/limits.js.map +1 -0
  96. package/dist/state/ranges.d.ts +52 -0
  97. package/dist/state/ranges.d.ts.map +1 -0
  98. package/dist/state/ranges.js +108 -0
  99. package/dist/state/ranges.js.map +1 -0
  100. package/llms.txt +15 -4
  101. package/manifest.json +238 -21
  102. package/nav-manifest.json +1 -1
  103. package/package.json +15 -10
  104. package/src/analytics/generated/events.json +1 -1
  105. package/src/default/DegradationNotice.tsx +34 -12
  106. package/src/default/FacetPanelPane.tsx +343 -64
  107. package/src/default/LanguageSelect.tsx +93 -0
  108. package/src/default/PageSizeSelect.tsx +80 -0
  109. package/src/default/RangeFilterRow.tsx +148 -0
  110. package/src/default/RankingDisclosurePane.tsx +67 -35
  111. package/src/default/SearchBox.tsx +130 -0
  112. package/src/default/SearchPage.tsx +351 -44
  113. package/src/default/SearchResultCard.tsx +231 -24
  114. package/src/default/SearchResultsPane.tsx +120 -70
  115. package/src/default/SortSelect.tsx +50 -37
  116. package/src/default/UrlIssueNotice.tsx +21 -3
  117. package/src/default/index.ts +38 -15
  118. package/src/default/sortLabels.ts +34 -0
  119. package/src/headless/FacetPanel.tsx +30 -2
  120. package/src/headless/useAppliedCount.ts +41 -0
  121. package/src/headless/useScorerNames.ts +36 -0
  122. package/src/headless/useSearchBox.ts +189 -0
  123. package/src/i18n/es.ts +37 -4
  124. package/src/i18n/keys.ts +83 -4
  125. package/src/i18n/ru.ts +41 -4
  126. package/src/index.ts +22 -2
  127. package/src/model/queries.ts +51 -1
  128. package/src/state/limits.ts +50 -0
  129. package/src/state/ranges.ts +144 -0
  130. package/dist/default/ErrorAlert.d.ts +0 -9
  131. package/dist/default/ErrorAlert.d.ts.map +0 -1
  132. package/dist/default/ErrorAlert.js +0 -27
  133. package/dist/default/ErrorAlert.js.map +0 -1
  134. package/dist/default/theme.d.ts +0 -24
  135. package/dist/default/theme.d.ts.map +0 -1
  136. package/dist/default/theme.js +0 -39
  137. package/dist/default/theme.js.map +0 -1
  138. package/src/default/ErrorAlert.tsx +0 -46
  139. package/src/default/theme.tsx +0 -66
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/search-react",
4
- "version": "0.4.0",
4
+ "version": "0.7.0",
5
5
  "backend": {
6
6
  "module": "stapel-search",
7
7
  "contract": ">=0.2 <0.3"
@@ -70,6 +70,17 @@
70
70
  "query",
71
71
  "{params}"
72
72
  ]
73
+ },
74
+ "useSuggest": {
75
+ "kind": "query",
76
+ "operation": "suggest",
77
+ "queryKey": [
78
+ "search",
79
+ "suggest",
80
+ "{type}",
81
+ "{q}",
82
+ "{…}"
83
+ ]
73
84
  }
74
85
  },
75
86
  "errors": {
@@ -393,47 +404,207 @@
393
404
  },
394
405
  "demos": [
395
406
  {
396
- "id": "search.facets",
397
- "title": "Facet panel",
398
- "description": "Counts are computed with each facet's own filter removed, so picking one value does not zero its siblings. Labels come from the category schema through @stapel/attributes-react; a slug the server skipped shows 'not counted', never 0.",
399
- "component": "FacetPanel",
407
+ "id": "search.box",
408
+ "title": "Search box",
409
+ "description": "Types into a draft and searches after a pause, so the address bar does not chase the keyboard; the URL still wins whenever it moves on its own, so the box can never show a word the results are not about. Capped at the server's own MAX_QUERY_CHARS, which makes 'query too long' a refusal this control cannot cause.",
410
+ "component": "SearchBox",
400
411
  "tokens": [
401
412
  "surface-raised"
402
413
  ],
403
414
  "variants": [
404
- "default"
415
+ "empty",
416
+ "typed"
405
417
  ],
406
- "source": "demo/FacetPanel.demo.tsx"
418
+ "source": "demo/SearchBox.demo.tsx"
407
419
  },
408
420
  {
409
- "id": "search.ranking",
410
- "title": "Ranking disclosure",
411
- "description": "Which parameters order the results and how much each weighs, straight from the scorer registry. Parameters the configured engine cannot evaluate are listed with their reason rather than filtered out.",
412
- "component": "RankingDisclosure",
421
+ "id": "search.degradation-notice",
422
+ "title": "Degradation notice",
423
+ "description": "What the engine could not do for THIS query, in words: the typo pass it skipped, the facet counts it sampled rather than counted, the scorer it could not evaluate. An unknown degradation still renders — with its raw literal because a build that predates a limitation should say 'the engine reported something we have no wording for', not nothing.",
424
+ "component": "DegradationNotice",
413
425
  "tokens": [
414
426
  "surface-raised"
415
427
  ],
416
428
  "variants": [
417
- "default"
429
+ "banner",
430
+ "inline"
418
431
  ],
419
- "source": "demo/RankingDisclosure.demo.tsx"
432
+ "source": "demo/DegradationNotice.demo.tsx"
420
433
  },
421
434
  {
422
- "id": "search.results",
423
- "title": "Search results",
424
- "description": "One keyset page, rendered through matchList so 'nothing found' can only be said about a search that actually ran. The count is marked as an estimate when the envelope says so, and degraded[] is surfaced rather than swallowed.",
425
- "component": "SearchResults",
435
+ "id": "search.facet-panel-pane",
436
+ "title": "Filter panel",
437
+ "description": "The drill-down facet panel with every count beside its option including the options you have not chosen, which is what makes them drill-down rather than naive — plus the server's honesty flags said in words: 'these counts are approximate' and the slugs it did not count at all, whose options read 'not counted' and never a zero that looks like an answer.",
438
+ "component": "FacetPanelPane",
426
439
  "covers": [
440
+ "FacetPanel"
441
+ ],
442
+ "tokens": [
443
+ "surface-raised"
444
+ ],
445
+ "variants": [
446
+ "open-search",
447
+ "narrowed"
448
+ ],
449
+ "source": "demo/FacetPanelPane.demo.tsx"
450
+ },
451
+ {
452
+ "id": "search.filter-sheet",
453
+ "title": "Filter sheet",
454
+ "description": "The filters as a phone actually meets them: a bottom sheet over the results, with the drill-down counts beside every option, a Clear all for the two constraints the link arrived with, and a footer that commits by saying how many results the current choices lead to rather than a bare 'Apply'.",
455
+ "component": "SearchPage",
456
+ "tokens": [
457
+ "surface-raised"
458
+ ],
459
+ "variants": [
460
+ "open"
461
+ ],
462
+ "source": "demo/FilterSheet.demo.tsx"
463
+ },
464
+ {
465
+ "id": "search.language-select",
466
+ "title": "Language filter",
467
+ "description": "The languages a deployment indexes, each named the way THIS reader would name it. With nothing offered and nothing applied the control does not render at all: a select whose only option is 'any' is a dead control, and the panel is thinner without it.",
468
+ "component": "LanguageSelect",
469
+ "tokens": [
470
+ "surface-raised"
471
+ ],
472
+ "variants": [
473
+ "offered",
474
+ "from-a-link"
475
+ ],
476
+ "source": "demo/LanguageSelect.demo.tsx"
477
+ },
478
+ {
479
+ "id": "search.page",
480
+ "title": "Search page",
481
+ "description": "The composed /s screen: the query box bound to setText, the filter panel (category and location slots, numeric ranges, drill-down facets), the sort and page-size toolbar, and one keyset page of cards with the DSA promoted marking explained in words rather than in a tooltip.",
482
+ "component": "SearchPage",
483
+ "covers": [
484
+ "SearchStateProvider",
427
485
  "SearchProvider",
428
- "SearchStateProvider"
486
+ "SearchResults",
487
+ "FacetPanel"
488
+ ],
489
+ "tokens": [
490
+ "surface-raised",
491
+ "warning-bg"
492
+ ],
493
+ "variants": [
494
+ "desktop",
495
+ "phone",
496
+ "unreadable-link"
497
+ ],
498
+ "source": "demo/SearchPage.demo.tsx"
499
+ },
500
+ {
501
+ "id": "search.page-size-select",
502
+ "title": "Page size control",
503
+ "description": "A short ladder around the server's DEFAULT_PAGE_SIZE. Choosing a size REPLACES the history entry rather than pushing one: a page size is a preference, not a step through the results, and Back should still undo the last filter.",
504
+ "component": "PageSizeSelect",
505
+ "tokens": [
506
+ "surface-raised"
507
+ ],
508
+ "variants": [
509
+ "ladder",
510
+ "off-ladder"
429
511
  ],
512
+ "source": "demo/PageSizeSelect.demo.tsx"
513
+ },
514
+ {
515
+ "id": "search.range-filter-row",
516
+ "title": "Numeric range filter",
517
+ "description": "A range row per numeric feature of the category schema, plus any slug the URL already constrains — because a constraint that is active must always have a control that removes it, even when the schema no longer explains it. Bounds and step come from the schema; the unit is shown in the field's accessible name.",
518
+ "component": "RangeFilterRow",
519
+ "tokens": [
520
+ "surface-raised"
521
+ ],
522
+ "variants": [
523
+ "untouched",
524
+ "applied"
525
+ ],
526
+ "source": "demo/RangeFilterRow.demo.tsx"
527
+ },
528
+ {
529
+ "id": "search.ranking-pane",
530
+ "title": "Ranking disclosure",
531
+ "description": "The statutory ranking page in the default skin: every scorer with its weight, the sorts it applies to, and — for a scorer the deployment's engine cannot evaluate — the reason it is inactive, so the disclosure describes the ranking that actually ran rather than the one that was configured.",
532
+ "component": "RankingDisclosurePane",
533
+ "covers": [
534
+ "RankingDisclosure"
535
+ ],
536
+ "tokens": [
537
+ "surface-raised"
538
+ ],
539
+ "variants": [
540
+ "desktop",
541
+ "phone"
542
+ ],
543
+ "source": "demo/RankingDisclosurePane.demo.tsx"
544
+ },
545
+ {
546
+ "id": "search.result-card",
547
+ "title": "Result card",
548
+ "description": "The default card a doc type gets for free: a 4:3 photo well drawn before the network answers (via @stapel/image, so a dead URL is a named placeholder rather than a torn-page icon), the title, the price with its currency, the location and distance — and the promoted marking painted from a token role rather than an antd preset, with its explanation as ordinary text beneath it.",
549
+ "component": "SearchResultCard",
550
+ "tokens": [
551
+ "warning-bg",
552
+ "warning-on",
553
+ "warning-border",
554
+ "surface-sunken"
555
+ ],
556
+ "variants": [
557
+ "promoted",
558
+ "plain"
559
+ ],
560
+ "source": "demo/SearchResultCard.demo.tsx"
561
+ },
562
+ {
563
+ "id": "search.results-pane",
564
+ "title": "Results pane",
565
+ "description": "One keyset page in the default skin: a counted heading (a capped count reads 'N+', never a flat number the server did not promise), the degradation banner, an auto-fill card grid, and a pager that is ABSENT when there is nothing to page rather than disabled with its reason in a tooltip.",
566
+ "component": "SearchResultsPane",
567
+ "covers": [
568
+ "SearchResults"
569
+ ],
570
+ "tokens": [
571
+ "surface-raised",
572
+ "warning-bg"
573
+ ],
574
+ "variants": [
575
+ "desktop",
576
+ "phone",
577
+ "empty"
578
+ ],
579
+ "source": "demo/SearchResultsPane.demo.tsx"
580
+ },
581
+ {
582
+ "id": "search.sort-select",
583
+ "title": "Sort control",
584
+ "description": "The five shipped sorts, plus whatever unknown sort a shared link already carries — offered rather than silently reset, because resetting it would rewrite the meaning of somebody's link on load and the server is the one entitled to refuse an unknown sort by name.",
585
+ "component": "SortSelect",
586
+ "tokens": [
587
+ "surface-raised"
588
+ ],
589
+ "variants": [
590
+ "no-centre",
591
+ "with-centre"
592
+ ],
593
+ "source": "demo/SortSelect.demo.tsx"
594
+ },
595
+ {
596
+ "id": "search.url-issue-notice",
597
+ "title": "Unreadable link notice",
598
+ "description": "The parameters a shared link carried that the codec could not read, named one by one. The alternative is not 'no notice' but a quietly WIDER search than the one that was shared, which is the difference between a page that is wrong and a page that says which part of the link it lost.",
599
+ "component": "UrlIssueNotice",
430
600
  "tokens": [
431
601
  "surface-raised"
432
602
  ],
433
603
  "variants": [
434
- "default"
604
+ "two-issues",
605
+ "one-issue"
435
606
  ],
436
- "source": "demo/SearchResults.demo.tsx"
607
+ "source": "demo/UrlIssueNotice.demo.tsx"
437
608
  }
438
609
  ],
439
610
  "i18nKeys": [
@@ -491,6 +662,14 @@
491
662
  "error.500.internal",
492
663
  "error.503.mandate_unavailable",
493
664
  "error.503.search_backend_unavailable",
665
+ "search.box.clear",
666
+ "search.box.label",
667
+ "search.box.placeholder",
668
+ "search.box.submit",
669
+ "search.box.suggestions",
670
+ "search.category.clear",
671
+ "search.category.current",
672
+ "search.category.title",
494
673
  "search.degraded.category_rollup",
495
674
  "search.degraded.exact_facet_counts",
496
675
  "search.degraded.exact_total",
@@ -509,15 +688,34 @@
509
688
  "search.facets.loading",
510
689
  "search.facets.not_counted",
511
690
  "search.facets.range_apply",
691
+ "search.facets.range_clear",
512
692
  "search.facets.range_from",
693
+ "search.facets.range_from_aria",
694
+ "search.facets.range_invalid",
513
695
  "search.facets.range_to",
696
+ "search.facets.range_to_aria",
514
697
  "search.facets.skipped",
515
698
  "search.facets.title",
699
+ "search.filters.apply",
700
+ "search.filters.dismiss",
701
+ "search.filters.open",
702
+ "search.filters.show_count",
703
+ "search.filters.show_count.one",
704
+ "search.filters.show_count.other",
705
+ "search.filters.show_count_at_least",
706
+ "search.filters.show_count_at_least.one",
707
+ "search.filters.show_count_at_least.other",
516
708
  "search.geo.box",
517
709
  "search.geo.center",
518
710
  "search.geo.clear",
519
711
  "search.geo.radius_km",
712
+ "search.geo.radius_label",
520
713
  "search.geo.title",
714
+ "search.language.any",
715
+ "search.language.label",
716
+ "search.limit.from_link",
717
+ "search.limit.label",
718
+ "search.limit.option",
521
719
  "search.ranking.applies_to",
522
720
  "search.ranking.empty",
523
721
  "search.ranking.inactive",
@@ -542,6 +740,7 @@
542
740
  "search.results.count_exact.other",
543
741
  "search.results.distance_km",
544
742
  "search.results.empty",
743
+ "search.results.image_alt",
545
744
  "search.results.load_failed",
546
745
  "search.results.loading",
547
746
  "search.results.next",
@@ -571,22 +770,31 @@
571
770
  "runtime": [
572
771
  "FILTER_PREFIX",
573
772
  "FacetPanel",
773
+ "RANGE_FEATURE_TYPES",
574
774
  "RANGE_PREFIX",
575
775
  "RankingDisclosure",
576
776
  "SEARCH_BACKEND_UNAVAILABLE",
777
+ "SEARCH_BOX_DEBOUNCE_MS",
778
+ "SEARCH_BOX_SUGGEST_DEBOUNCE_MS",
779
+ "SEARCH_DEFAULT_PAGE_SIZE",
577
780
  "SEARCH_ERRORS",
578
781
  "SEARCH_ERROR_CODES",
579
782
  "SEARCH_I18N_KEYS",
580
783
  "SEARCH_I18N_PLURAL_KEYS",
784
+ "SEARCH_MAX_PAGE_SIZE",
581
785
  "SEARCH_PARAM",
786
+ "SEARCH_QUERY_MAX_CHARS",
582
787
  "SEARCH_SORTS",
583
788
  "SEARCH_WINDOW_EXCEEDED",
789
+ "SUGGEST_MAX_LIMIT",
790
+ "SUGGEST_MIN_CHARS",
584
791
  "SearchProvider",
585
792
  "SearchResults",
586
793
  "SearchRuntimeContext",
587
794
  "SearchStateProvider",
588
795
  "activeFilterCount",
589
796
  "buildFacetGroups",
797
+ "buildRangeGroups",
590
798
  "clearFilters",
591
799
  "countIsEstimate",
592
800
  "countKind",
@@ -596,6 +804,8 @@
596
804
  "explainSearchError",
597
805
  "facetOptionLabel",
598
806
  "isCountNuanceOnly",
807
+ "isRangeFeature",
808
+ "isRangeUsable",
599
809
  "navEntries",
600
810
  "ownsParam",
601
811
  "parseDegradations",
@@ -610,16 +820,20 @@
610
820
  "setRangeValue",
611
821
  "toggleFilterValue",
612
822
  "useAppliedSort",
823
+ "useFacetPanel",
613
824
  "useRankingDisclosure",
614
825
  "useSearchAnalytics",
615
826
  "useSearchApi",
827
+ "useSearchBox",
616
828
  "useSearchQuery",
617
829
  "useSearchRuntime",
618
830
  "useSearchState",
831
+ "useSuggest",
619
832
  "writeSearchState"
620
833
  ],
621
834
  "types": [
622
835
  "BuildFacetGroupsInput",
836
+ "BuildRangeGroupsInput",
623
837
  "CreateSearchRuntimeOptions",
624
838
  "FacetGroup",
625
839
  "FacetMeta",
@@ -628,12 +842,14 @@
628
842
  "FacetSelection",
629
843
  "ParseSearchStateOptions",
630
844
  "ParsedSearchState",
845
+ "RangeGroup",
631
846
  "RankingDisclosureBag",
632
847
  "RankingResponse",
633
848
  "Remediation",
634
849
  "Schemas",
635
850
  "Scorer",
636
851
  "SearchApi",
852
+ "SearchBoxBag",
637
853
  "SearchCountKind",
638
854
  "SearchDegradation",
639
855
  "SearchDegradationKind",
@@ -658,7 +874,8 @@
658
874
  "SearchStatePatch",
659
875
  "SearchStateProviderProps",
660
876
  "SuggestParams",
661
- "SuggestResponse"
877
+ "SuggestResponse",
878
+ "UseSearchBoxOptions"
662
879
  ]
663
880
  }
664
881
  }
package/nav-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@stapel/search-react",
3
- "version": "0.4.0",
3
+ "version": "0.7.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "search.results",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/search-react",
3
- "version": "0.4.0",
3
+ "version": "0.7.0",
4
4
  "description": "Headless React pair for stapel-search: a typed query client, TanStack Query hooks, and a URL-first state codec that makes a search shareable by construction (filters, ranges, geo, sort and the keyset cursor all live in the query string). Drill-down facets rendered with their remaining counts and with the server's own honesty flags — approximate, skipped, degraded — never swallowed; keyset pagination with the window refusal named; DSA Art. 26 `promoted` marking carried into every card slot and the P2B Art. 5 ranking disclosure exposed as data. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin, and /router binds the codec to react-router's useSearchParams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,9 +57,9 @@
57
57
  "limit": "10 KB"
58
58
  },
59
59
  {
60
- "name": "default — the antd skin (results, facet panel, degradation notice, ranking disclosure) must stay out of the main bundle",
60
+ "name": "default — the antd skin (query box + typeahead, filters incl. ranges/geo/category slots, results, ranking) must stay out of the main bundle",
61
61
  "path": "dist/default/index.js",
62
- "limit": "13 KB"
62
+ "limit": "16 KB"
63
63
  },
64
64
  {
65
65
  "name": "router — the react-router binding is opt-in; the main entry must never pull a router",
@@ -79,14 +79,18 @@
79
79
  ],
80
80
  "peerDependencies": {
81
81
  "@stapel/attributes-react": ">=0.1.0",
82
- "@stapel/core": ">=0.17.0 <1.0.0",
83
- "@stapel/tokens-antd": ">=0.5.0",
82
+ "@stapel/core": ">=0.18.1 <1.0.0",
83
+ "@stapel/image": ">=0.3.0",
84
+ "@stapel/tokens-antd": ">=0.7.0",
84
85
  "@tanstack/react-query": "^5.0.0",
85
86
  "antd": ">=5.20.0 <7",
86
87
  "react": ">=19",
87
88
  "react-router": ">=7.0.0 <8"
88
89
  },
89
90
  "peerDependenciesMeta": {
91
+ "@stapel/image": {
92
+ "optional": true
93
+ },
90
94
  "@stapel/tokens-antd": {
91
95
  "optional": true
92
96
  },
@@ -111,11 +115,12 @@
111
115
  "size-limit": "^11.2.0",
112
116
  "typescript": "^5.8.3",
113
117
  "vitest": "^3.2.4",
114
- "@stapel/attributes-react": "^0.2.0",
115
- "@stapel/showcase": "^0.2.0",
116
- "@stapel/tokens": "^0.5.0",
117
- "@stapel/tokens-antd": "^0.5.0",
118
- "@stapel/core": "^0.17.0"
118
+ "@stapel/image": "^0.4.1",
119
+ "@stapel/showcase": "^0.3.0",
120
+ "@stapel/core": "^0.18.1",
121
+ "@stapel/tokens": "^0.5.1",
122
+ "@stapel/attributes-react": "^0.3.1",
123
+ "@stapel/tokens-antd": "^0.7.0"
119
124
  },
120
125
  "engines": {
121
126
  "node": ">=22"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/search-react",
4
- "version": "0.4.0",
4
+ "version": "0.7.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -29,6 +29,7 @@
29
29
  */
30
30
  import type { ReactElement } from "react";
31
31
  import { Alert, Flex, Typography } from "antd";
32
+ import { fontSize, spacing } from "@stapel/tokens";
32
33
  import { useT } from "@stapel/core";
33
34
  import type { SearchDegradation } from "../api/types.js";
34
35
  import { isCountNuanceOnly } from "../state/degradations.js";
@@ -47,6 +48,13 @@ export type DegradationNoticeVariant = "banner" | "inline" | "off";
47
48
  export interface DegradationNoticeProps {
48
49
  readonly degradations: readonly SearchDegradation[];
49
50
  readonly variant?: DegradationNoticeVariant;
51
+ /**
52
+ * Names a `scorer:` degradation's slug, when something on the page knows
53
+ * one. `<SearchResultsPane>` passes the ranking disclosure's own names
54
+ * (`useScorerNames`); with no answer the slug is printed, which is what a
55
+ * registry identifier is worth on its own.
56
+ */
57
+ readonly scorerName?: (slug: string) => string | undefined;
50
58
  }
51
59
 
52
60
  export function DegradationNotice(
@@ -59,25 +67,39 @@ export function DegradationNotice(
59
67
  // A count nuance is not a degraded search — the count already says "N+".
60
68
  if (isCountNuanceOnly(props.degradations)) return null;
61
69
 
70
+ const say = (degradation: SearchDegradation): string => {
71
+ const slug = degradation.scorer;
72
+ return t(degradation.messageKey, {
73
+ scorer:
74
+ slug === undefined
75
+ ? ""
76
+ : (props.scorerName?.(slug) ?? slug),
77
+ raw: degradation.raw,
78
+ });
79
+ };
80
+
81
+ // NOT `type="secondary"` in the banner: antd paints a warning Alert in the
82
+ // theme's warning tint, and grey body text on it measured under 3:1 in the
83
+ // visual pass. Inside a coloured box the readable colour is the box's own
84
+ // text colour, which is what a plain `<Typography.Text>` inherits.
62
85
  const lines = props.degradations.map((degradation) => (
63
86
  <li key={degradation.raw} data-degradation={degradation.raw}>
64
- <Typography.Text type="secondary">
65
- {t(degradation.messageKey, {
66
- scorer: degradation.scorer ?? "",
67
- raw: degradation.raw,
68
- })}
69
- </Typography.Text>
87
+ <Typography.Text>{say(degradation)}</Typography.Text>
70
88
  </li>
71
89
  ));
72
90
 
73
91
  if (variant === "inline") {
74
92
  return (
75
- <Flex vertical gap={2} data-testid="search-degraded" data-variant="inline">
76
- <Typography.Text type="secondary" style={{ fontSize: 12 }}>
93
+ <Flex vertical gap={spacing[1]} data-testid="search-degraded" data-variant="inline">
94
+ <Typography.Text type="secondary" style={{ fontSize: fontSize.xs.fontSize }}>
77
95
  {t(SEARCH_I18N_KEYS.degradedTitle)}
78
96
  </Typography.Text>
79
- <ul style={{ margin: 0, paddingInlineStart: 20, fontSize: 12 }}>
80
- {lines}
97
+ <ul style={{ margin: 0, paddingInlineStart: spacing[5], fontSize: fontSize.xs.fontSize }}>
98
+ {props.degradations.map((degradation) => (
99
+ <li key={degradation.raw} data-degradation={degradation.raw}>
100
+ <Typography.Text type="secondary">{say(degradation)}</Typography.Text>
101
+ </li>
102
+ ))}
81
103
  </ul>
82
104
  </Flex>
83
105
  );
@@ -89,9 +111,9 @@ export function DegradationNotice(
89
111
  showIcon
90
112
  data-testid="search-degraded"
91
113
  data-variant="banner"
92
- message={t(SEARCH_I18N_KEYS.degradedTitle)}
114
+ title={t(SEARCH_I18N_KEYS.degradedTitle)}
93
115
  description={
94
- <ul style={{ margin: 0, paddingInlineStart: 20 }}>{lines}</ul>
116
+ <ul style={{ margin: 0, paddingInlineStart: spacing[5] }}>{lines}</ul>
95
117
  }
96
118
  />
97
119
  );