@eifi1/ui-kit 0.51.3 → 0.51.4

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 (96) hide show
  1. package/README.md +5 -5
  2. package/dist/components/combobox-core.js +4 -5
  3. package/dist/components/combobox-core.js.map +1 -1
  4. package/dist/components/currency-option-list.js +3 -3
  5. package/dist/components/currency-option-list.js.map +1 -1
  6. package/dist/components/data-table-fit.js +7 -5
  7. package/dist/components/data-table-fit.js.map +1 -1
  8. package/dist/components/data-table-head.js +5 -1
  9. package/dist/components/data-table-head.js.map +1 -1
  10. package/dist/components/data-table.d.ts +8 -0
  11. package/dist/components/data-table.js.map +1 -1
  12. package/dist/components/grouped-picker.js +5 -4
  13. package/dist/components/grouped-picker.js.map +1 -1
  14. package/dist/components/icon-picker.js +5 -4
  15. package/dist/components/icon-picker.js.map +1 -1
  16. package/dist/components/inline-entity-combobox.js +4 -4
  17. package/dist/components/inline-entity-combobox.js.map +1 -1
  18. package/dist/components/legal-labels.js +8 -2
  19. package/dist/components/legal-labels.js.map +1 -1
  20. package/dist/components/legal-page.d.ts +18 -1
  21. package/dist/components/legal-page.js +13 -0
  22. package/dist/components/legal-page.js.map +1 -1
  23. package/dist/components/legal.d.ts +40 -5
  24. package/dist/components/legal.js +29 -3
  25. package/dist/components/legal.js.map +1 -1
  26. package/dist/components/list-labels.js +3 -1
  27. package/dist/components/list-labels.js.map +1 -1
  28. package/dist/components/list.d.ts +18 -1
  29. package/dist/components/list.js +32 -3
  30. package/dist/components/list.js.map +1 -1
  31. package/dist/eslint/index.js +1 -1
  32. package/dist/feedback/feedback-table.d.ts +3 -1
  33. package/dist/feedback/feedback-table.js +18 -4
  34. package/dist/feedback/feedback-table.js.map +1 -1
  35. package/dist/i18n/german.js +11 -3
  36. package/dist/i18n/german.js.map +1 -1
  37. package/dist/i18n/locales/en.js +15 -3
  38. package/dist/i18n/locales/en.js.map +2 -2
  39. package/dist/i18n/locales/es.js +11 -3
  40. package/dist/i18n/locales/es.js.map +1 -1
  41. package/dist/i18n/locales/fr.js +11 -3
  42. package/dist/i18n/locales/fr.js.map +1 -1
  43. package/dist/i18n/locales/hu.js +11 -3
  44. package/dist/i18n/locales/hu.js.map +1 -1
  45. package/dist/i18n/locales/it.js +11 -3
  46. package/dist/i18n/locales/it.js.map +1 -1
  47. package/dist/i18n/locales/zh.js +11 -3
  48. package/dist/i18n/locales/zh.js.map +1 -1
  49. package/dist/i18n/review.js +2 -0
  50. package/dist/i18n/review.js.map +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/lib/text-match.d.ts +16 -0
  54. package/dist/lib/text-match.js +11 -1
  55. package/dist/lib/text-match.js.map +1 -1
  56. package/dist/settings/use-settings-focus.d.ts +7 -0
  57. package/dist/settings/use-settings-focus.js +2 -0
  58. package/dist/settings/use-settings-focus.js.map +1 -1
  59. package/dist/shell/dev-menu.d.ts +5 -0
  60. package/dist/shell/dev-menu.js +2 -1
  61. package/dist/shell/dev-menu.js.map +1 -1
  62. package/dist/shell/topbar-action-menu.d.ts +14 -1
  63. package/dist/shell/topbar-action-menu.js +85 -34
  64. package/dist/shell/topbar-action-menu.js.map +1 -1
  65. package/dist/shell.d.ts +1 -1
  66. package/dist/shell.js +2 -1
  67. package/dist/shell.js.map +1 -1
  68. package/package.json +1 -1
  69. package/src/components/combobox-core.tsx +5 -5
  70. package/src/components/currency-option-list.tsx +4 -5
  71. package/src/components/data-table-fit.ts +7 -5
  72. package/src/components/data-table-head.tsx +5 -1
  73. package/src/components/data-table.tsx +8 -0
  74. package/src/components/grouped-picker.tsx +15 -8
  75. package/src/components/icon-picker.tsx +6 -4
  76. package/src/components/inline-entity-combobox.tsx +5 -4
  77. package/src/components/legal-labels.ts +7 -1
  78. package/src/components/legal-page.tsx +39 -0
  79. package/src/components/legal.tsx +69 -5
  80. package/src/components/list-labels.ts +2 -0
  81. package/src/components/list.tsx +52 -2
  82. package/src/feedback/feedback-table.tsx +17 -1
  83. package/src/i18n/german.ts +9 -1
  84. package/src/i18n/locales/en.ts +4 -0
  85. package/src/i18n/locales/es.ts +9 -1
  86. package/src/i18n/locales/fr.ts +9 -1
  87. package/src/i18n/locales/hu.ts +9 -1
  88. package/src/i18n/locales/it.ts +9 -1
  89. package/src/i18n/locales/zh.ts +9 -1
  90. package/src/i18n/review.ts +2 -0
  91. package/src/index.ts +1 -0
  92. package/src/lib/text-match.ts +30 -0
  93. package/src/settings/use-settings-focus.ts +8 -0
  94. package/src/shell/dev-menu.tsx +6 -0
  95. package/src/shell/topbar-action-menu.tsx +122 -38
  96. package/src/shell.ts +1 -1
@@ -453,7 +453,9 @@ function statusRank(status: FeedbackStatus): number {
453
453
  * - Fitted to the table's width (0.48, keksdose live #451): Subject `grow`s and
454
454
  * ellipsises, and a narrow table leaves out URL, then Email, then Resolved
455
455
  * (`fitPriority` 1–3) — the open report repeats all three — then Votes and Category
456
- * (4, 5) before Subject would drop below 10rem. Drop the two keys with
456
+ * (4, 5), then User, Date and # (6–8, 0.51.4) before Subject would drop below 8rem
457
+ * (`growMin`, 0.51.4; the table's default floor is 10).
458
+ * Drop the two keys with
457
459
  * `{ ...col, grow: undefined, fitPriority: undefined }` for the 0.47 layout.
458
460
  * - `/my-feedback` shows the same nine: the Email cell's context fallback exists for it
459
461
  * (the admin list's join is absent there, §3.1), and its phone card drops the submitter
@@ -499,6 +501,9 @@ export function feedbackColumns<T extends FeedbackTableRow>({
499
501
  sortBy: (row) => row.id,
500
502
  filter: { type: "number", getValue: (row) => row.id },
501
503
  mobileHidden: true,
504
+ // The very last to go (0.51.4, Kurvenschmiede): replies and the feedback loop name a
505
+ // report by its number, so it stays while anything else can give way.
506
+ fitPriority: 8,
502
507
  },
503
508
  {
504
509
  key: "date",
@@ -506,6 +511,8 @@ export function feedbackColumns<T extends FeedbackTableRow>({
506
511
  cell: (row) => <FeedbackDate value={row.created_at} renderDate={renderDate} />,
507
512
  sortBy: (row) => instant(row.created_at),
508
513
  filter: { type: "date", getValue: (row) => row.created_at },
514
+ // Seventh to go (0.51.4): the open report shows it.
515
+ fitPriority: 7,
509
516
  },
510
517
  {
511
518
  key: "category",
@@ -536,6 +543,11 @@ export function feedbackColumns<T extends FeedbackTableRow>({
536
543
  // The rest of the row, one line and an ellipsis (0.48, keksdose live #451: one long
537
544
  // crash title pushed Status and the row actions off the edge).
538
545
  grow: true,
546
+ // 8rem, not the table's 10 (0.51.4, Kurvenschmiede): at 768px beside a sidebar only
547
+ // Subject, Status (a fixed 13.5rem) and the row actions are left, and they were up
548
+ // to 20px too wide (Hungarian). A subject's first words carry it; the open report
549
+ // has the rest.
550
+ growMin: 8,
539
551
  },
540
552
  {
541
553
  key: "user",
@@ -559,6 +571,10 @@ export function feedbackColumns<T extends FeedbackTableRow>({
559
571
  row.user_id == null
560
572
  ? labels.deletedUser
561
573
  : `${feedbackSubmitter(row, labels)} ${feedbackSubmitterEmail(row)}`,
574
+ // Sixth to go (0.51.4, Kurvenschmiede): with only 1–5, Subject reached its floor while
575
+ // #, Date and User were still drawn, and the inbox scrolled sideways from 768 to
576
+ // ~1115px beside an open sidebar. The open report names the submitter.
577
+ fitPriority: 6,
562
578
  },
563
579
  {
564
580
  key: "email",
@@ -782,6 +782,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
782
782
  list: {
783
783
  unread: "Ungelesen",
784
784
  opensInNewTab: "öffnet in einem neuen Tab",
785
+ showAll: (count) => `Alle anzeigen (${n(count)})`,
786
+ showFewer: "Weniger anzeigen",
785
787
  },
786
788
  breadcrumbs: {
787
789
  label: "Brotkrümelnavigation",
@@ -917,6 +919,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
917
919
  impressum: "Impressum",
918
920
  privacy: "Datenschutz",
919
921
  terms: "Nutzungsbedingungen",
922
+ security: "Sicherheit",
923
+ accessibility: "Barrierefreiheit",
920
924
  },
921
925
  titles: {
922
926
  impressum: "Impressum",
@@ -936,6 +940,10 @@ export function germanLabels(numberLocale: string): UiKitLabels {
936
940
  show: (o) => `Fassung auf ${o.bindingLanguage} anzeigen`,
937
941
  },
938
942
  updated: (date) => `Stand: ${date}`,
943
+ summary: {
944
+ title: "Das Wichtigste in Kürze",
945
+ note: "Diese Zusammenfassung dient der Orientierung; verbindlich ist allein der vollständige Text.",
946
+ },
939
947
  sections: {
940
948
  impressum: {
941
949
  operator: {
@@ -992,7 +1000,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
992
1000
  },
993
1001
  changes: {
994
1002
  title: "Änderungen dieser Bedingungen",
995
- body: "Wir können diese Bedingungen im Zuge der Weiterentwicklung des Dienstes anpassen. Wesentliche Änderungen kündigen wir mindestens 30 Tage vor ihrem Inkrafttreten per E-Mail und in der App an, zusammen mit dem neuen Text. Wenn Sie nicht einverstanden sind, können Sie vor diesem Datum widersprechen oder Ihr Konto löschen und bis dahin Ihre Daten exportieren. Nutzen Sie den Dienst nach diesem Datum weiter, gelten die geänderten Bedingungen.",
1003
+ body: "Wir können diese Bedingungen im Zuge der Weiterentwicklung des Dienstes anpassen. Wesentliche Änderungen kündigen wir mindestens 30 Tage vor ihrem Inkrafttreten per E-Mail und in der App an, zusammen mit dem neuen Text. Wenn Sie nicht einverstanden sind, können Sie vor diesem Datum widersprechen oder Ihr Konto löschen und bis dahin Ihre Daten exportieren. Nutzen Sie den Dienst nach diesem Datum weiter, gelten die geänderten Bedingungen. Änderungen, die nur zu Ihrem Vorteil sind oder gesetzlich vorgeschrieben sind, gelten ohne diese Ankündigung; ihnen kann nicht widersprochen werden. Widersprechen Sie einer Änderung, können wir Ihr Konto auf den Tag ihres Inkrafttretens schließen. Haben Sie einen Zeitraum bezahlt, der über dieses Datum hinausgeht, erstatten wir den nicht genutzten Teil.",
996
1004
  },
997
1005
  language: {
998
1006
  title: "Sprache",
@@ -95,6 +95,10 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
95
95
  ...d.chipInput,
96
96
  atLimit: (max) => `Limit of ${n(max)} reached`,
97
97
  },
98
+ list: {
99
+ ...d.list,
100
+ showAll: (count) => `Show all (${n(count)})`,
101
+ },
98
102
  iconPicker: {
99
103
  ...d.iconPicker,
100
104
  resultCount: (count) => (count === 1 ? "1 icon" : `${n(count)} icons`),
@@ -765,6 +765,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
765
765
  list: {
766
766
  unread: "No leído",
767
767
  opensInNewTab: "se abre en una pestaña nueva",
768
+ showAll: (count) => `Mostrar todo (${n(count)})`,
769
+ showFewer: "Mostrar menos",
768
770
  },
769
771
  breadcrumbs: {
770
772
  label: "Ruta de navegación",
@@ -897,6 +899,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
897
899
  impressum: "Aviso legal",
898
900
  privacy: "Política de privacidad",
899
901
  terms: "Condiciones de uso",
902
+ security: "Seguridad",
903
+ accessibility: "Accesibilidad",
900
904
  },
901
905
  titles: {
902
906
  impressum: "Aviso legal",
@@ -916,6 +920,10 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
916
920
  show: (o) => `Ver la versión en ${o.bindingLanguage}`,
917
921
  },
918
922
  updated: (date) => `Última actualización: ${date}`,
923
+ summary: {
924
+ title: "En resumen",
925
+ note: "Este resumen es solo orientativo; únicamente el texto completo es vinculante.",
926
+ },
919
927
  sections: {
920
928
  impressum: {
921
929
  operator: {
@@ -972,7 +980,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
972
980
  },
973
981
  changes: {
974
982
  title: "Cambios en estas condiciones",
975
- body: "Podemos actualizar estas condiciones a medida que evolucione el servicio. Anunciamos cualquier cambio importante al menos 30 días antes de que entre en vigor, por correo electrónico y en la aplicación, junto con el nuevo texto. Si no está de acuerdo, puede oponerse o eliminar su cuenta antes de esa fecha, y puede exportar sus datos hasta entonces. Si sigue usando el servicio después de esa fecha, se aplican las condiciones modificadas.",
983
+ body: "Podemos actualizar estas condiciones a medida que evolucione el servicio. Anunciamos cualquier cambio importante al menos 30 días antes de que entre en vigor, por correo electrónico y en la aplicación, junto con el nuevo texto. Si no está de acuerdo, puede oponerse o eliminar su cuenta antes de esa fecha, y puede exportar sus datos hasta entonces. Si sigue usando el servicio después de esa fecha, se aplican las condiciones modificadas. Los cambios que solo le benefician, o que exige la ley, se aplican sin este preaviso y no admiten oposición. Si se opone a un cambio, podemos cerrar su cuenta en la fecha en que entre en vigor. Si ha pagado un periodo que va más allá de esa fecha, le reembolsamos la parte no utilizada.",
976
984
  },
977
985
  language: {
978
986
  title: "Idioma",
@@ -781,6 +781,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
781
781
  list: {
782
782
  unread: "Non lu",
783
783
  opensInNewTab: "s’ouvre dans un nouvel onglet",
784
+ showAll: (count) => `Tout afficher (${n(count)})`,
785
+ showFewer: "Afficher moins",
784
786
  },
785
787
  breadcrumbs: {
786
788
  label: "Fil d’Ariane",
@@ -916,6 +918,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
916
918
  impressum: "Mentions légales",
917
919
  privacy: "Confidentialité",
918
920
  terms: "Conditions d’utilisation",
921
+ security: "Sécurité",
922
+ accessibility: "Accessibilité",
919
923
  },
920
924
  titles: {
921
925
  impressum: "Mentions légales",
@@ -935,6 +939,10 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
935
939
  show: (o) => `Afficher la version en ${o.bindingLanguage}`,
936
940
  },
937
941
  updated: (date) => `Dernière mise à jour\u00a0: ${date}`,
942
+ summary: {
943
+ title: "En bref",
944
+ note: "Ce résumé sert d’orientation\u202f; seul le texte complet fait foi.",
945
+ },
938
946
  sections: {
939
947
  impressum: {
940
948
  operator: {
@@ -991,7 +999,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
991
999
  },
992
1000
  changes: {
993
1001
  title: "Modifications des présentes conditions",
994
- body: "Nous pouvons mettre à jour ces conditions au fil de l’évolution du service. Nous annonçons toute modification importante au moins 30 jours avant son entrée en vigueur, par e-mail et dans l’app, avec le nouveau texte. Si vous n’êtes pas d’accord, vous pouvez vous y opposer ou supprimer votre compte avant cette date, et exporter vos données jusque-là. Si vous continuez à utiliser le service après cette date, les conditions modifiées s’appliquent.",
1002
+ body: "Nous pouvons mettre à jour ces conditions au fil de l’évolution du service. Nous annonçons toute modification importante au moins 30 jours avant son entrée en vigueur, par e-mail et dans l’app, avec le nouveau texte. Si vous n’êtes pas d’accord, vous pouvez vous y opposer ou supprimer votre compte avant cette date, et exporter vos données jusque-là. Si vous continuez à utiliser le service après cette date, les conditions modifiées s’appliquent. Les modifications qui sont uniquement à votre avantage, ou que la loi impose, s’appliquent sans ce préavis et ne peuvent pas faire l’objet d’une opposition. Si vous vous opposez à une modification, nous pouvons fermer votre compte à la date de son entrée en vigueur. Si vous avez payé une période qui s’étend au-delà de cette date, nous remboursons la part non utilisée.",
995
1003
  },
996
1004
  language: {
997
1005
  title: "Langue",
@@ -766,6 +766,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
766
766
  list: {
767
767
  unread: "Olvasatlan",
768
768
  opensInNewTab: "új lapon nyílik meg",
769
+ showAll: (count) => `Összes megjelenítése (${n(count)})`,
770
+ showFewer: "Kevesebb megjelenítése",
769
771
  },
770
772
  breadcrumbs: {
771
773
  label: "Morzsamenü",
@@ -901,6 +903,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
901
903
  impressum: "Impresszum",
902
904
  privacy: "Adatvédelmi tájékoztató",
903
905
  terms: "Felhasználási feltételek",
906
+ security: "Biztonság",
907
+ accessibility: "Akadálymentesség",
904
908
  },
905
909
  titles: {
906
910
  impressum: "Impresszum",
@@ -923,6 +927,10 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
923
927
  show: (o) => `Az irányadó változat megjelenítése (${o.bindingLanguage})`,
924
928
  },
925
929
  updated: (date) => `Utolsó frissítés: ${date}`,
930
+ summary: {
931
+ title: "Röviden",
932
+ note: "Ez az összefoglaló tájékoztató jellegű; kizárólag a teljes szöveg az irányadó.",
933
+ },
926
934
  sections: {
927
935
  impressum: {
928
936
  operator: {
@@ -979,7 +987,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
979
987
  },
980
988
  changes: {
981
989
  title: "A feltételek módosítása",
982
- body: "Ezeket a feltételeket a szolgáltatás fejlődésével módosíthatjuk. A lényeges változásokat legalább 30 nappal a hatálybalépésük előtt bejelentjük e-mailben és az alkalmazásban, az új szöveggel együtt. Ha nem ért egyet, ezen időpont előtt tiltakozhat, vagy törölheti a fiókját, és addig exportálhatja az adatait. Ha ezen időpont után is használja a szolgáltatást, a módosított feltételek érvényesek.",
990
+ body: "Ezeket a feltételeket a szolgáltatás fejlődésével módosíthatjuk. A lényeges változásokat legalább 30 nappal a hatálybalépésük előtt bejelentjük e-mailben és az alkalmazásban, az új szöveggel együtt. Ha nem ért egyet, ezen időpont előtt tiltakozhat, vagy törölheti a fiókját, és addig exportálhatja az adatait. Ha ezen időpont után is használja a szolgáltatást, a módosított feltételek érvényesek. Azok a változások, amelyek kizárólag az Ön javát szolgálják, vagy amelyeket jogszabály ír elő, e bejelentés nélkül is hatályosak, és ellenük nem lehet tiltakozni. Ha tiltakozik egy változás ellen, a hatálybalépés napjával megszüntethetjük a fiókját. Ha olyan időszakra fizetett, amely ezen a napon túlnyúlik, a fel nem használt részt visszatérítjük.",
983
991
  },
984
992
  language: {
985
993
  title: "Nyelv",
@@ -769,6 +769,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
769
769
  list: {
770
770
  unread: "Non letto",
771
771
  opensInNewTab: "si apre in una nuova scheda",
772
+ showAll: (count) => `Mostra tutto (${n(count)})`,
773
+ showFewer: "Mostra meno",
772
774
  },
773
775
  breadcrumbs: {
774
776
  label: "Percorso di navigazione",
@@ -902,6 +904,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
902
904
  impressum: "Impressum",
903
905
  privacy: "Privacy",
904
906
  terms: "Condizioni d’uso",
907
+ security: "Sicurezza",
908
+ accessibility: "Accessibilità",
905
909
  },
906
910
  titles: {
907
911
  impressum: "Impressum",
@@ -922,6 +926,10 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
922
926
  show: (o) => `Mostra la versione in ${o.bindingLanguage}`,
923
927
  },
924
928
  updated: (date) => `Ultimo aggiornamento: ${date}`,
929
+ summary: {
930
+ title: "In breve",
931
+ note: "Questo riepilogo serve da orientamento; fa fede solo il testo completo.",
932
+ },
925
933
  sections: {
926
934
  impressum: {
927
935
  operator: {
@@ -980,7 +988,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
980
988
  },
981
989
  changes: {
982
990
  title: "Modifiche a queste condizioni",
983
- body: "Possiamo aggiornare queste condizioni con l’evoluzione del servizio. Annunciamo ogni modifica sostanziale almeno 30 giorni prima della sua entrata in vigore, via email e nell’app, insieme al nuovo testo. Se non è d’accordo, può opporsi o eliminare il Suo account prima di tale data, e può esportare i Suoi dati fino ad allora. Se continua a usare il servizio dopo tale data, si applicano le condizioni modificate.",
991
+ body: "Possiamo aggiornare queste condizioni con l’evoluzione del servizio. Annunciamo ogni modifica sostanziale almeno 30 giorni prima della sua entrata in vigore, via email e nell’app, insieme al nuovo testo. Se non è d’accordo, può opporsi o eliminare il Suo account prima di tale data, e può esportare i Suoi dati fino ad allora. Se continua a usare il servizio dopo tale data, si applicano le condizioni modificate. Le modifiche che vanno solo a Suo vantaggio, o che sono imposte dalla legge, si applicano senza questo preavviso e non è possibile opporvisi. Se si oppone a una modifica, possiamo chiudere il Suo account alla data della sua entrata in vigore. Se ha pagato un periodo che va oltre tale data, Le rimborsiamo la parte non utilizzata.",
984
992
  },
985
993
  language: {
986
994
  title: "Lingua",
@@ -746,6 +746,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
746
746
  list: {
747
747
  unread: "未读",
748
748
  opensInNewTab: "在新标签页中打开",
749
+ showAll: (count) => `显示全部(${n(count)})`,
750
+ showFewer: "收起",
749
751
  },
750
752
  breadcrumbs: {
751
753
  label: "面包屑导航",
@@ -876,6 +878,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
876
878
  impressum: "运营者信息",
877
879
  privacy: "隐私政策",
878
880
  terms: "使用条款",
881
+ security: "安全",
882
+ accessibility: "无障碍",
879
883
  },
880
884
  titles: {
881
885
  impressum: "运营者信息",
@@ -896,6 +900,10 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
896
900
  show: (o) => `查看${o.bindingLanguage}版本`,
897
901
  },
898
902
  updated: (date) => `最后更新:${date}`,
903
+ summary: {
904
+ title: "要点",
905
+ note: "本摘要仅供参考;只有完整文本具有约束力。",
906
+ },
899
907
  sections: {
900
908
  impressum: {
901
909
  operator: {
@@ -952,7 +960,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
952
960
  },
953
961
  changes: {
954
962
  title: "条款变更",
955
- body: "我们可能会随着服务的发展更新本条款。重大变更将至少在生效前 30 天通过电子邮件和应用程序公布,并附上新文本。如您不同意,可在该日期前提出异议或删除您的账户,并可在此之前导出您的数据。如您在该日期后继续使用本服务,则适用变更后的条款。",
963
+ body: "我们可能会随着服务的发展更新本条款。重大变更将至少在生效前 30 天通过电子邮件和应用程序公布,并附上新文本。如您不同意,可在该日期前提出异议或删除您的账户,并可在此之前导出您的数据。如您在该日期后继续使用本服务,则适用变更后的条款。仅对您有利的变更或法律要求的变更,无需上述提前通知即可生效,且不得提出异议。如您对某项变更提出异议,我们可在该变更生效之日关闭您的账户。如您已付费的期间超过该日期,我们将退还未使用部分的费用。",
956
964
  },
957
965
  language: {
958
966
  title: "语言",
@@ -311,6 +311,8 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
311
311
  "legal.translation.show": operatorSample,
312
312
  // 0.51.3: the as-of line, with the date already written out.
313
313
  "legal.updated": [["{{date}}"]],
314
+ // 0.51.4: a limited list's "Show all (n)", with the whole list's count.
315
+ "list.showAll": [[12], [1240]],
314
316
  // 0.29.0: the sign-in and sign-up parts (docs/auth-harmonization.md).
315
317
  "signIn.tagHint": [["{{taggedAddress}}"]],
316
318
  "register.emailTagUse": [["{{address}}"]],
package/src/index.ts CHANGED
@@ -548,6 +548,7 @@ export type {
548
548
  LegalOperator,
549
549
  LegalOperatorText,
550
550
  LegalPageKey,
551
+ LegalInfoPageKey,
551
552
  LegalTextSectionLabels,
552
553
  LegalOperatorSectionLabels,
553
554
  LegalFramedSectionLabels,
@@ -29,6 +29,36 @@ export function foldForSearch(text: string, locale?: string): string {
29
29
  return lower.normalize("NFD").replace(/\p{M}+/gu, "").replace(/ß/g, "ss");
30
30
  }
31
31
 
32
+ /**
33
+ * A picker's query as its words, each folded (0.51.4, keksdose's /prices shop picker):
34
+ * "Migros Zürich" → `["migros", "zurich"]`. An empty or blank query has no words.
35
+ */
36
+ export function searchWords(query: string, locale?: string): string[] {
37
+ return foldForSearch(query, locale).split(/\s+/).filter((word) => word !== "");
38
+ }
39
+
40
+ /**
41
+ * Whether a row matches a picker's query: EVERY word appears in at least one of the row's
42
+ * texts — "migros 8004" finds a row named "Migros · 8004 Zürich", and a row whose chain is
43
+ * its group and whose place is its label (pass both). Until 0.51.4 the whole query had to
44
+ * be ONE substring of one text, so two words found nothing unless they stood side by side
45
+ * in that order. One word matches exactly as before, and so does the old query when it
46
+ * was a substring. No words match everything.
47
+ *
48
+ * `words` from {@link searchWords}; `texts` as they are (folded here), `undefined` skipped.
49
+ */
50
+ export function matchesSearch(
51
+ words: readonly string[],
52
+ texts: readonly (string | undefined)[],
53
+ locale?: string,
54
+ ): boolean {
55
+ if (words.length === 0) return true;
56
+ // One haystack per row; a word holds no whitespace, so it cannot match across the
57
+ // newline between two texts.
58
+ const hay = texts.filter((t): t is string => t !== undefined).map((t) => foldForSearch(t, locale)).join("\n");
59
+ return words.every((word) => hay.includes(word));
60
+ }
61
+
32
62
  /** {@link rankMatch}'s answer: lower is better, `-1` is no match. */
33
63
  export type MatchRank = -1 | 0 | 1 | 2;
34
64
 
@@ -115,3 +115,11 @@ export function useSettingsFocus(anchor?: string | null): void {
115
115
  [],
116
116
  );
117
117
  }
118
+
119
+ /**
120
+ * {@link useSettingsFocus} under a name for any page (0.51.4, keksdose run 89): the hook
121
+ * was always allowed on an app's own pages (keksdose's /reports/matching lands
122
+ * `?focus=matching-unmatched` on a card), but its name said Settings at every such call
123
+ * site. The same function, so the two cannot drift; `useSettingsFocus` stays.
124
+ */
125
+ export const useFocusParam: typeof useSettingsFocus = useSettingsFocus;
@@ -126,6 +126,11 @@ const isRow = (entry: TopBarMenuEntry) => entry.kind !== "heading" && entry.kind
126
126
  *
127
127
  * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls
128
128
  * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.
129
+ *
130
+ * From `md` up the groups stand side by side, one column each, in that order (0.51.4,
131
+ * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: "Each group gets a column").
132
+ * "Back to {role}" stays under the roles. A desktop too narrow for all of them wraps
133
+ * the columns onto a second row; a phone keeps the one scrolling column.
129
134
  */
130
135
  export function DevMenu<R extends string, P extends string>({
131
136
  enabled,
@@ -254,6 +259,7 @@ export function DevMenu<R extends string, P extends string>({
254
259
  aria-label={name}
255
260
  entries={entries}
256
261
  scrollable
262
+ columns
257
263
  />
258
264
  );
259
265
  }
@@ -1,5 +1,5 @@
1
1
  import { useId, useLayoutEffect, useRef } from "react";
2
- import type { KeyboardEvent as ReactKeyboardEvent, ReactNode } from "react";
2
+ import type { CSSProperties, KeyboardEvent as ReactKeyboardEvent, ReactNode } from "react";
3
3
  import { Link } from "react-router";
4
4
  import { cn } from "../lib/cn";
5
5
  import { HoverMenu, type HoverMenuProps } from "../components/hover-menu";
@@ -109,6 +109,40 @@ function toBlocks(entries: readonly TopBarMenuEntry[]): Block[] {
109
109
  return blocks.filter((block) => block.kind === "loose" || block.rows.length > 0);
110
110
  }
111
111
 
112
+ /** One column of `columns` (0.51.4): a group and the loose entries after it, up to the
113
+ * next group — so a group's own way out behind a rule (DevMenu's "Back to {role}") stays
114
+ * under it instead of running across the panel. */
115
+ type LooseBlock = Extract<Block, { kind: "loose" }>;
116
+ type Column = { group: Extract<Block, { kind: "group" }>; after: Array<RowEntry | DividerEntry>; index: number };
117
+
118
+ /** `blocks` as the columns layout draws them: what comes before the first group, then a
119
+ * column per group. A rule that would end a column is dropped: the column's edge is the
120
+ * separation there. */
121
+ function toColumns(blocks: readonly Block[]): { lead: LooseBlock[]; columns: Column[] } {
122
+ const lead: LooseBlock[] = [];
123
+ const columns: Column[] = [];
124
+ blocks.forEach((block, index) => {
125
+ if (block.kind === "group") columns.push({ group: block, after: [], index });
126
+ else if (columns.length === 0) lead.push(block);
127
+ else columns[columns.length - 1]!.after.push(block.entry);
128
+ });
129
+ for (const column of columns) {
130
+ while (column.after.at(-1)?.kind === "divider") column.after.pop();
131
+ }
132
+ return { lead, columns };
133
+ }
134
+
135
+ /**
136
+ * `columns`' panel: each column 14rem, the panel as wide as its columns (a CSS variable
137
+ * the list sets) but never wider than the glass, and from `md` up only. The grid places
138
+ * as many 14rem columns as that width holds, so a narrow desktop wraps the groups onto a
139
+ * second row instead of scrolling sideways. The header, the title and the footer span
140
+ * the row.
141
+ */
142
+ const COLUMNS_PANEL =
143
+ "md:grid md:grid-cols-[repeat(auto-fill,minmax(14rem,1fr))] md:items-start md:gap-y-2 md:w-[min(calc(100vw-2rem),var(--menu-columns-width))]";
144
+ const SPAN_ALL = "md:col-span-full";
145
+
112
146
  /**
113
147
  * The non-interactive identity block at the top of an account menu — kastlan's name +
114
148
  * role, keksdose's display name + email + role chip. It is text, not a `menuitem`: the
@@ -225,6 +259,7 @@ export function TopBarActionMenu({
225
259
  align,
226
260
  panelClassName,
227
261
  scrollable = false,
262
+ columns = false,
228
263
  triggerAttributes,
229
264
  }: {
230
265
  /** The trigger's face for the usual icon button. Ignored when `trigger` is given. */
@@ -308,6 +343,19 @@ export function TopBarActionMenu({
308
343
  * header and the footer are inside the list and scroll with the rows.
309
344
  */
310
345
  scrollable?: boolean;
346
+ /**
347
+ * From `md` up, lay the heading groups side by side, one column per group (0.51.4,
348
+ * `DevMenu`; keksdose dev #640, Marcel: "Each group gets a column. That way it would be
349
+ * much better visible for desktop users"). Below `md`, the one column as before.
350
+ *
351
+ * A column is a group and the loose rows after it up to the next heading, so a row
352
+ * behind a rule at the end of a group (DevMenu's "Back to {role}") stays in that
353
+ * group's column. Loose rows before the first heading, the header, the menu-wide
354
+ * `heading` and the footer span the panel. The DOM order is unchanged, so ↓/↑ walk the
355
+ * rows down one column and on into the next, as a reader reads them. `leading` and
356
+ * `children` rows take a cell each; add `md:col-span-full` to one that should span.
357
+ */
358
+ columns?: boolean;
311
359
  /** `data-*` attributes for the trigger itself — a tour's anchor (`data-tour`), a test
312
360
  * hook. The parts built on this menu (`TourMenu`) pass theirs through here. */
313
361
  triggerAttributes?: { [key: `data-${string}`]: string | number | boolean | undefined };
@@ -316,6 +364,10 @@ export function TopBarActionMenu({
316
364
  // entry's `key`, which is the caller's text and may hold a space — and a space in
317
365
  // `aria-labelledby` is a list separator.
318
366
  const uid = useId();
367
+ const blocks = toBlocks(entries);
368
+ const grouped = columns ? toColumns(blocks) : null;
369
+ // A menu with no group has nothing to put side by side: the list as always.
370
+ const laidOut = grouped && grouped.columns.length > 0 ? grouped : null;
319
371
  return (
320
372
  <HoverMenu
321
373
  aria-label={ariaLabel}
@@ -380,8 +432,8 @@ export function TopBarActionMenu({
380
432
  const footerClass =
381
433
  "flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted [&_a:hover]:text-primary [&_a]:rounded-sm [&_a:focus-visible]:outline-none [&_a:focus-visible]:ring-focus [&_a:focus-visible]:ring-brand";
382
434
  /** One action or link row — loose, or inside a heading's group. */
383
- const row = (entry: RowEntry) => (
384
- <li key={entry.key}>
435
+ const row = (entry: RowEntry, className?: string) => (
436
+ <li key={entry.key} className={className}>
385
437
  {entry.kind === "link" ? (
386
438
  <MenuItem
387
439
  href={entry.to}
@@ -413,11 +465,46 @@ export function TopBarActionMenu({
413
465
  )}
414
466
  </li>
415
467
  );
468
+ /** A rule or a row outside any group; `span` stretches it across the columns. */
469
+ const loose = (block: LooseBlock, span: boolean) => {
470
+ const entry = block.entry;
471
+ if (entry.kind === "divider") {
472
+ return <li key={entry.key} className={cn("my-1 border-t border-subtle", span && SPAN_ALL)} />;
473
+ }
474
+ return row(entry, span ? SPAN_ALL : undefined);
475
+ };
476
+ /** A heading's group: its name, then its rows in a named `role="group"`. */
477
+ const group = (block: Extract<Block, { kind: "group" }>, i: number, tight: boolean, inColumn = false) => {
478
+ const headingId = `${uid}-group-${i}`;
479
+ return (
480
+ <li key={block.heading.key} role="none">
481
+ <div
482
+ id={headingId}
483
+ role="presentation"
484
+ className={cn(
485
+ "px-3 pb-0.5 text-caption font-semibold uppercase tracking-wide text-muted [overflow-wrap:anywhere]",
486
+ // A column's heading lines up with its neighbours' at the top of the
487
+ // grid row; under `md` the columns stack, and it spaces like the list's.
488
+ tight ? "pt-1" : cn("pt-2.5", inColumn && "md:pt-1"),
489
+ )}
490
+ >
491
+ {block.heading.label}
492
+ </div>
493
+ <ul role="group" aria-labelledby={headingId}>
494
+ {block.rows.map((entry) => row(entry))}
495
+ </ul>
496
+ </li>
497
+ );
498
+ };
416
499
  return (
417
500
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- a Tab bubbling up from a row or footer link, see keepTabInsidePanel
418
- <ul className={cn("py-1", scrollable && SCROLLABLE_PANEL, panelClassName)} onKeyDown={keepTabInsidePanel}>
501
+ <ul
502
+ className={cn("py-1", scrollable && SCROLLABLE_PANEL, laidOut && COLUMNS_PANEL, panelClassName)}
503
+ style={laidOut ? ({ "--menu-columns-width": `${laidOut.columns.length * 14}rem` } as CSSProperties) : undefined}
504
+ onKeyDown={keepTabInsidePanel}
505
+ >
419
506
  {header && (
420
- <MenuSection className="mb-1 border-b border-subtle px-3 py-2">
507
+ <MenuSection className={cn("mb-1 border-b border-subtle px-3 py-2", laidOut && SPAN_ALL)}>
421
508
  <div className="truncate text-sm font-medium text-primary">{header.title}</div>
422
509
  {header.subtitle != null && (
423
510
  <div className="truncate text-xs text-muted">{header.subtitle}</div>
@@ -426,45 +513,42 @@ export function TopBarActionMenu({
426
513
  </MenuSection>
427
514
  )}
428
515
  {heading && (
429
- <li className="px-3 py-1 text-micro font-semibold uppercase tracking-wide text-placeholder">
516
+ <li
517
+ className={cn(
518
+ "px-3 py-1 text-micro font-semibold uppercase tracking-wide text-placeholder",
519
+ laidOut && SPAN_ALL,
520
+ )}
521
+ >
430
522
  {heading}
431
523
  </li>
432
524
  )}
433
525
  {leading?.(close)}
434
- {toBlocks(entries).map((block, i, blocks) => {
435
- if (block.kind === "loose") {
436
- const entry = block.entry;
437
- if (entry.kind === "divider") {
438
- return <li key={entry.key} className="my-1 border-t border-subtle" />;
439
- }
440
- return row(entry);
441
- }
442
- const headingId = `${uid}-group-${i}`;
443
- const before = blocks[i - 1];
444
- // Under a rule (or at the top of the rows) the rule's margin is the gap;
445
- // straight under another group's last row the heading makes its own.
446
- const tight = !before || (before.kind === "loose" && before.entry.kind === "divider");
447
- return (
448
- <li key={block.heading.key} role="none">
449
- <div
450
- id={headingId}
451
- role="presentation"
452
- className={cn(
453
- "px-3 pb-0.5 text-caption font-semibold uppercase tracking-wide text-muted [overflow-wrap:anywhere]",
454
- tight ? "pt-1" : "pt-2.5",
455
- )}
456
- >
457
- {block.heading.label}
458
- </div>
459
- <ul role="group" aria-labelledby={headingId}>
460
- {block.rows.map(row)}
461
- </ul>
462
- </li>
463
- );
464
- })}
526
+ {laidOut ? (
527
+ <>
528
+ {laidOut.lead.map((block) => loose(block, true))}
529
+ {laidOut.columns.map((column, c) => (
530
+ // A column of the grid; under `md` a group as in the list, its rows
531
+ // following.
532
+ <li key={column.group.heading.key} role="none" data-menu-column="" className="min-w-0">
533
+ <ul role="none">
534
+ {group(column.group, column.index, c === 0 && laidOut.lead.length === 0, true)}
535
+ {column.after.map((entry) => loose({ kind: "loose", entry }, false))}
536
+ </ul>
537
+ </li>
538
+ ))}
539
+ </>
540
+ ) : (
541
+ blocks.map((block, i) => {
542
+ if (block.kind === "loose") return loose(block, false);
543
+ const before = blocks[i - 1];
544
+ // Under a rule (or at the top of the rows) the rule's margin is the gap;
545
+ // straight under another group's last row the heading makes its own.
546
+ return group(block, i, !before || (before.kind === "loose" && before.entry.kind === "divider"));
547
+ })
548
+ )}
465
549
  {children?.(close)}
466
550
  {footerBody != null && footerBody !== false && (
467
- <MenuSection className="mt-1 border-t border-subtle px-3 py-2">
551
+ <MenuSection className={cn("mt-1 border-t border-subtle px-3 py-2", laidOut && SPAN_ALL)}>
468
552
  {footerLabel ? (
469
553
  <nav aria-label={footerLabel} className={footerClass}>
470
554
  {footerBody}
package/src/shell.ts CHANGED
@@ -41,7 +41,7 @@ export type {
41
41
  SettingsLocationRules,
42
42
  SettingsLocationResult,
43
43
  } from "./settings/use-settings-route";
44
- export { useSettingsFocus, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from "./settings/use-settings-focus";
44
+ export { useSettingsFocus, useFocusParam, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } from "./settings/use-settings-focus";
45
45
  export { SettingsSection } from "./settings/settings-section";
46
46
  export type { SettingsSectionProps } from "./settings/settings-section";
47
47
  export { useSettingsLayout } from "./settings/settings-context";