@eifi1/ui-kit 0.47.0 → 0.48.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.
- package/README.md +5 -3
- package/dist/admin/admin-sections.d.ts +8 -0
- package/dist/admin/admin-sections.js +2 -1
- package/dist/admin/admin-sections.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +22 -13
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/components/alert-banner.d.ts +29 -1
- package/dist/components/alert-banner.js +84 -7
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/button-look.d.ts +4 -0
- package/dist/components/button-look.js +2 -0
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/combobox-core.js +5 -1
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +6 -1
- package/dist/components/data-table-column-settings.js +12 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +44 -0
- package/dist/components/data-table-fit.js +103 -0
- package/dist/components/data-table-fit.js.map +1 -0
- package/dist/components/data-table-groups.d.ts +95 -0
- package/dist/components/data-table-groups.js +165 -0
- package/dist/components/data-table-groups.js.map +1 -0
- package/dist/components/data-table-head.js +3 -1
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +7 -0
- package/dist/components/data-table-labels.js +3 -0
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-leading-row.d.ts +17 -0
- package/dist/components/data-table-leading-row.js +41 -0
- package/dist/components/data-table-leading-row.js.map +1 -0
- package/dist/components/data-table-resize.js +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table.d.ts +78 -2
- package/dist/components/data-table.js +348 -257
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/disclosure.d.ts +3 -1
- package/dist/components/disclosure.js +10 -4
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/dropdown.js +8 -1
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/floating-panel.d.ts +14 -1
- package/dist/components/floating-panel.js +7 -2
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/hover-menu.js +5 -1
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +2 -1
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/popover.js +5 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/table-skeleton.d.ts +35 -0
- package/dist/components/table-skeleton.js +75 -0
- package/dist/components/table-skeleton.js.map +1 -0
- package/dist/components/tabs.d.ts +10 -12
- package/dist/components/tabs.js +20 -10
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/use-table-state.d.ts +8 -7
- package/dist/components/use-table-state.js +37 -3
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-table.d.ts +5 -0
- package/dist/feedback/feedback-table.js +30 -7
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +30 -1
- package/dist/hooks/use-dismiss.js +30 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/german.js +6 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +6 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +6 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +6 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +6 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +6 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +6 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/shared-labels.js +3 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/float-clearance.d.ts +8 -0
- package/dist/lib/float-clearance.js +29 -0
- package/dist/lib/float-clearance.js.map +1 -0
- package/dist/settings/settings-catalogue.d.ts +10 -0
- package/dist/settings/settings-catalogue.js.map +1 -1
- package/dist/settings/settings-layout.js +10 -3
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/app-shell.d.ts +5 -0
- package/dist/shell/app-shell.js +5 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +2 -0
- package/dist/shell/option-switcher-menu.js +6 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +2 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.js +5 -3
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/tour/tour.js +1 -0
- package/dist/tour/tour.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/admin-sections.tsx +8 -0
- package/src/auth/verify-email.tsx +34 -9
- package/src/components/alert-banner.tsx +131 -7
- package/src/components/button-look.ts +6 -0
- package/src/components/combobox-core.tsx +7 -1
- package/src/components/data-table-column-settings.tsx +23 -2
- package/src/components/data-table-fit.ts +187 -0
- package/src/components/data-table-groups.tsx +275 -0
- package/src/components/data-table-head.tsx +4 -1
- package/src/components/data-table-labels.ts +13 -0
- package/src/components/data-table-leading-row.tsx +55 -0
- package/src/components/data-table-resize.ts +2 -1
- package/src/components/data-table.tsx +246 -43
- package/src/components/disclosure.tsx +22 -5
- package/src/components/dropdown.tsx +11 -1
- package/src/components/floating-panel.tsx +24 -1
- package/src/components/hover-menu.tsx +6 -1
- package/src/components/icon-button.tsx +3 -3
- package/src/components/popover.tsx +7 -1
- package/src/components/table-skeleton.tsx +133 -0
- package/src/components/tabs.tsx +67 -37
- package/src/components/use-table-state.ts +54 -3
- package/src/data-table.ts +2 -0
- package/src/feedback/feedback-table.tsx +33 -1
- package/src/hooks/use-dismiss.ts +65 -3
- package/src/i18n/german.ts +5 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/es.ts +5 -0
- package/src/i18n/locales/fr.ts +5 -0
- package/src/i18n/locales/hu.ts +5 -0
- package/src/i18n/locales/it.ts +5 -0
- package/src/i18n/locales/zh.ts +5 -0
- package/src/i18n/shared-labels.ts +2 -0
- package/src/index.ts +3 -0
- package/src/lib/float-clearance.ts +54 -0
- package/src/settings/settings-catalogue.ts +10 -0
- package/src/settings/settings-layout.tsx +18 -4
- package/src/shell/app-shell.tsx +17 -3
- package/src/shell/option-switcher-menu.tsx +16 -4
- package/src/shell/topbar-action-menu.tsx +22 -10
- package/src/shell/topbar-controls.tsx +18 -12
- package/src/tour/tour.tsx +5 -0
package/src/i18n/german.ts
CHANGED
|
@@ -339,6 +339,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
339
339
|
opensInNewTab: "öffnet in einem neuen Tab",
|
|
340
340
|
aboutField: (field) => `Hilfe zu „${field}“`,
|
|
341
341
|
help: "Hilfe",
|
|
342
|
+
showMore: "Mehr",
|
|
343
|
+
showLess: "Weniger",
|
|
342
344
|
},
|
|
343
345
|
dataTable: {
|
|
344
346
|
columns: "Spalten",
|
|
@@ -351,6 +353,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
351
353
|
cancelSelection: "Abbrechen",
|
|
352
354
|
rowDetails: "Details",
|
|
353
355
|
autoSize: "Spaltenbreiten anpassen",
|
|
356
|
+
hiddenForWidth: "aus Platzgründen ausgeblendet",
|
|
354
357
|
loading: "Wird geladen…",
|
|
355
358
|
filters: "Filter",
|
|
356
359
|
// Resets every active filter; "Alle löschen" would read as deleting the filters.
|
|
@@ -402,6 +405,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
402
405
|
booleanTrue: "Ja",
|
|
403
406
|
booleanFalse: "Nein",
|
|
404
407
|
booleanUnset: "Nicht festgelegt",
|
|
408
|
+
loadMore: "Mehr laden",
|
|
409
|
+
allLoaded: "Alle Zeilen geladen",
|
|
405
410
|
sortBy: "Sortieren nach",
|
|
406
411
|
sortDefault: "Standardreihenfolge",
|
|
407
412
|
sortAscending: "Aufsteigend",
|
package/src/i18n/kit-labels.tsx
CHANGED
|
@@ -189,6 +189,10 @@ export interface CommonLabels {
|
|
|
189
189
|
aboutField: (field: string) => string;
|
|
190
190
|
/** …and of a `help` "?" on a field with no label and no `aria-label` to name it by. */
|
|
191
191
|
help: string;
|
|
192
|
+
/** 0.48: the toggle that shows the rest of a clamped text (`AlertBanner clampMobile`)… */
|
|
193
|
+
showMore: string;
|
|
194
|
+
/** …and folds it back to its one line. */
|
|
195
|
+
showLess: string;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
/** `DatePicker` / `DateRangePicker` chrome. The calendar inside has its own
|
package/src/i18n/locales/es.ts
CHANGED
|
@@ -330,6 +330,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
330
330
|
opensInNewTab: "se abre en una pestaña nueva",
|
|
331
331
|
aboutField: (field) => `Acerca de «${field}»`,
|
|
332
332
|
help: "Ayuda",
|
|
333
|
+
showMore: "Más",
|
|
334
|
+
showLess: "Menos",
|
|
333
335
|
},
|
|
334
336
|
dataTable: {
|
|
335
337
|
columns: "Columnas",
|
|
@@ -342,6 +344,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
342
344
|
cancelSelection: "Cancelar",
|
|
343
345
|
rowDetails: "Detalles",
|
|
344
346
|
autoSize: "Ajustar ancho de columnas",
|
|
347
|
+
hiddenForWidth: "oculta por falta de espacio",
|
|
345
348
|
loading: "Cargando…",
|
|
346
349
|
filters: "Filtros",
|
|
347
350
|
clearAll: "Borrar todo",
|
|
@@ -391,6 +394,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
391
394
|
booleanTrue: "Sí",
|
|
392
395
|
booleanFalse: "No",
|
|
393
396
|
booleanUnset: "Sin definir",
|
|
397
|
+
loadMore: "Cargar más",
|
|
398
|
+
allLoaded: "Todas las filas cargadas",
|
|
394
399
|
sortBy: "Ordenar por",
|
|
395
400
|
sortDefault: "Orden predeterminado",
|
|
396
401
|
sortAscending: "Ascendente",
|
package/src/i18n/locales/fr.ts
CHANGED
|
@@ -336,6 +336,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
336
336
|
opensInNewTab: "s’ouvre dans un nouvel onglet",
|
|
337
337
|
aboutField: (field) => `À propos de «\u202f${field}\u202f»`,
|
|
338
338
|
help: "Aide",
|
|
339
|
+
showMore: "Plus",
|
|
340
|
+
showLess: "Moins",
|
|
339
341
|
},
|
|
340
342
|
dataTable: {
|
|
341
343
|
columns: "Colonnes",
|
|
@@ -348,6 +350,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
348
350
|
cancelSelection: "Annuler",
|
|
349
351
|
rowDetails: "Détails",
|
|
350
352
|
autoSize: "Ajuster la largeur des colonnes",
|
|
353
|
+
hiddenForWidth: "masquée faute de place",
|
|
351
354
|
loading: "Chargement…",
|
|
352
355
|
filters: "Filtres",
|
|
353
356
|
clearAll: "Tout effacer",
|
|
@@ -397,6 +400,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
|
|
|
397
400
|
booleanTrue: "Oui",
|
|
398
401
|
booleanFalse: "Non",
|
|
399
402
|
booleanUnset: "Non défini",
|
|
403
|
+
loadMore: "Charger plus",
|
|
404
|
+
allLoaded: "Toutes les lignes sont chargées",
|
|
400
405
|
sortBy: "Trier par",
|
|
401
406
|
sortDefault: "Ordre par défaut",
|
|
402
407
|
sortAscending: "Croissant",
|
package/src/i18n/locales/hu.ts
CHANGED
|
@@ -328,6 +328,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
328
328
|
opensInNewTab: "új lapon nyílik meg",
|
|
329
329
|
aboutField: (field) => `Súgó: ${field}`,
|
|
330
330
|
help: "Súgó",
|
|
331
|
+
showMore: "Több",
|
|
332
|
+
showLess: "Kevesebb",
|
|
331
333
|
},
|
|
332
334
|
dataTable: {
|
|
333
335
|
columns: "Oszlopok",
|
|
@@ -340,6 +342,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
340
342
|
cancelSelection: "Mégse",
|
|
341
343
|
rowDetails: "Részletek",
|
|
342
344
|
autoSize: "Oszlopszélességek igazítása",
|
|
345
|
+
hiddenForWidth: "szélesség miatt rejtve",
|
|
343
346
|
loading: "Betöltés…",
|
|
344
347
|
filters: "Szűrők",
|
|
345
348
|
clearAll: "Összes törlése",
|
|
@@ -389,6 +392,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
|
|
|
389
392
|
booleanTrue: "Igen",
|
|
390
393
|
booleanFalse: "Nem",
|
|
391
394
|
booleanUnset: "Nincs megadva",
|
|
395
|
+
loadMore: "Továbbiak betöltése",
|
|
396
|
+
allLoaded: "Minden sor betöltve",
|
|
392
397
|
sortBy: "Rendezés alapja",
|
|
393
398
|
sortDefault: "Alapértelmezett sorrend",
|
|
394
399
|
sortAscending: "Növekvő",
|
package/src/i18n/locales/it.ts
CHANGED
|
@@ -332,6 +332,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
332
332
|
opensInNewTab: "si apre in una nuova scheda",
|
|
333
333
|
aboutField: (field) => `Informazioni su «${field}»`,
|
|
334
334
|
help: "Aiuto",
|
|
335
|
+
showMore: "Altro",
|
|
336
|
+
showLess: "Meno",
|
|
335
337
|
},
|
|
336
338
|
dataTable: {
|
|
337
339
|
columns: "Colonne",
|
|
@@ -344,6 +346,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
344
346
|
cancelSelection: "Annulla",
|
|
345
347
|
rowDetails: "Dettagli",
|
|
346
348
|
autoSize: "Adatta larghezza colonne",
|
|
349
|
+
hiddenForWidth: "nascosta per mancanza di spazio",
|
|
347
350
|
loading: "Caricamento…",
|
|
348
351
|
filters: "Filtri",
|
|
349
352
|
clearAll: "Cancella tutto",
|
|
@@ -393,6 +396,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
393
396
|
booleanTrue: "Sì",
|
|
394
397
|
booleanFalse: "No",
|
|
395
398
|
booleanUnset: "Non impostato",
|
|
399
|
+
loadMore: "Carica altro",
|
|
400
|
+
allLoaded: "Tutte le righe caricate",
|
|
396
401
|
sortBy: "Ordina per",
|
|
397
402
|
sortDefault: "Ordine predefinito",
|
|
398
403
|
sortAscending: "Crescente",
|
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -320,6 +320,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
320
320
|
opensInNewTab: "在新标签页中打开",
|
|
321
321
|
aboutField: (field) => `关于“${field}”`,
|
|
322
322
|
help: "帮助",
|
|
323
|
+
showMore: "更多",
|
|
324
|
+
showLess: "收起",
|
|
323
325
|
},
|
|
324
326
|
dataTable: {
|
|
325
327
|
columns: "列",
|
|
@@ -332,6 +334,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
332
334
|
cancelSelection: "取消",
|
|
333
335
|
rowDetails: "详情",
|
|
334
336
|
autoSize: "自动调整列宽",
|
|
337
|
+
hiddenForWidth: "宽度不足,已隐藏",
|
|
335
338
|
loading: "加载中…",
|
|
336
339
|
filters: "筛选条件",
|
|
337
340
|
clearAll: "全部清除",
|
|
@@ -380,6 +383,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
380
383
|
booleanTrue: "是",
|
|
381
384
|
booleanFalse: "否",
|
|
382
385
|
booleanUnset: "未设置",
|
|
386
|
+
loadMore: "加载更多",
|
|
387
|
+
allLoaded: "已加载全部行",
|
|
383
388
|
sortBy: "排序依据",
|
|
384
389
|
sortDefault: "默认顺序",
|
|
385
390
|
sortAscending: "升序",
|
|
@@ -39,6 +39,8 @@ export const DEFAULT_COMMON_LABELS: CommonLabels = {
|
|
|
39
39
|
opensInNewTab: "opens in a new tab",
|
|
40
40
|
aboutField: (field) => `About “${field}”`,
|
|
41
41
|
help: "Help",
|
|
42
|
+
showMore: "More",
|
|
43
|
+
showLess: "Less",
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
export const DEFAULT_DATE_PICKER_LABELS: DatePickerLabels = {
|
package/src/index.ts
CHANGED
|
@@ -430,6 +430,9 @@ export * from "./components/progress-bar";
|
|
|
430
430
|
// Named: skeleton.tsx also holds SKELETON_CLASS, the look StatTile shares — internal.
|
|
431
431
|
export { Skeleton } from "./components/skeleton";
|
|
432
432
|
export type { SkeletonProps, SkeletonShape } from "./components/skeleton";
|
|
433
|
+
// 0.48 (keksdose dev #628): the table/list loading preset, one status label.
|
|
434
|
+
export { TableSkeleton } from "./components/table-skeleton";
|
|
435
|
+
export type { TableSkeletonProps } from "./components/table-skeleton";
|
|
433
436
|
// 0.12.0: a centred spinner with words, signed figures and deltas, a render-error fallback.
|
|
434
437
|
export * from "./components/loading-state";
|
|
435
438
|
export * from "./components/signed-amount";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The room the floating launchers take above the bottom nav, published as
|
|
5
|
+
* `--app-float-clearance` on `<html>` so `AppShell` can pad `<main>` past them.
|
|
6
|
+
*
|
|
7
|
+
* keksdose dev#612 (UX-1.3, "the help button covers content"): the shell padded `<main>`
|
|
8
|
+
* for the nav plus 1.5rem, so on a phone the last row of every page ended under the help
|
|
9
|
+
* launcher — and on /transactions under the action group below it, exactly where the
|
|
10
|
+
* row's amount is. keksdose patched it with its own `--help-launcher-clearance` and an
|
|
11
|
+
* unlayered app.css rule. Here every kit launcher (`FloatingActionButton`, so
|
|
12
|
+
* `FloatingPanel`'s too, and `FloatingActionGroup`) declares its own room while it is
|
|
13
|
+
* mounted and shown, and the shell takes the largest.
|
|
14
|
+
*
|
|
15
|
+
* Declared, not measured: a launcher knows its `offset` and its 3rem height exactly, as
|
|
16
|
+
* CSS lengths, and the variable then follows the nav and the text size by itself. A
|
|
17
|
+
* `hidden` launcher keeps its claim: the room is its own while it is mounted, so the page
|
|
18
|
+
* does not change length as it hides and shows (keksdose's launcher steps aside while an
|
|
19
|
+
* editor covers its corner). Unmount it to give the room back.
|
|
20
|
+
*
|
|
21
|
+
* Internal: not exported from the barrel.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const claims = new Map<symbol, string>();
|
|
25
|
+
|
|
26
|
+
function publish() {
|
|
27
|
+
if (typeof document === "undefined") return;
|
|
28
|
+
const root = document.documentElement;
|
|
29
|
+
const values = [...claims.values()];
|
|
30
|
+
if (values.length === 0) root.style.removeProperty("--app-float-clearance");
|
|
31
|
+
else root.style.setProperty("--app-float-clearance", values.length === 1 ? values[0] : `max(${values.join(", ")})`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Claim `value` (a CSS length: the launcher's top edge above the nav, plus a gap) until
|
|
35
|
+
* the returned function is called. */
|
|
36
|
+
export function claimFloatClearance(value: string): () => void {
|
|
37
|
+
const key = Symbol("float-clearance");
|
|
38
|
+
claims.set(key, value);
|
|
39
|
+
publish();
|
|
40
|
+
return () => {
|
|
41
|
+
claims.delete(key);
|
|
42
|
+
publish();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A launcher at `offset` above the nav claims its 3rem height plus a 0.5rem gap while
|
|
48
|
+
* mounted (`extra`: what its frame adds, the group's 1px borders).
|
|
49
|
+
*/
|
|
50
|
+
export function useFloatClearance(offset: string, extra?: string): void {
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
return claimFloatClearance(`calc(${offset} + 3.5rem${extra ? ` + ${extra}` : ""})`);
|
|
53
|
+
}, [offset, extra]);
|
|
54
|
+
}
|
|
@@ -23,6 +23,16 @@ export interface SettingsGroup<G extends string = string> {
|
|
|
23
23
|
title: string;
|
|
24
24
|
/** One line under the heading and in the phone list, where it is cut to one line. */
|
|
25
25
|
help: string;
|
|
26
|
+
/**
|
|
27
|
+
* 0.48 (keksdose live #450): a count or a mark drawn on the group's ROW — at the end of
|
|
28
|
+
* its desktop sidebar row and before the chevron of its phone list row — never on the
|
|
29
|
+
* heading over its cards. The admin area's waiting work: `<AdminWaitingBadge count={n} />`
|
|
30
|
+
* (nothing for 0), so the account menu's badge leads to the section that holds it. It
|
|
31
|
+
* sits inside the row's link and is read with its name, so say what the number is (the
|
|
32
|
+
* waiting pill does: "3, 3 waiting"). While the page is searched the sidebar shows the
|
|
33
|
+
* hit count in its place. Default: none.
|
|
34
|
+
*/
|
|
35
|
+
badge?: ReactNode;
|
|
26
36
|
/**
|
|
27
37
|
* `false` hides the group: no row, no hits, and its path acts as an unknown one — it
|
|
28
38
|
* lands on the default group (desktop) or the list (phone), never on a 404 (§3.1).
|
|
@@ -369,7 +369,17 @@ export function SettingsLayout<G extends string>({
|
|
|
369
369
|
}
|
|
370
370
|
title={g.title}
|
|
371
371
|
subtitle={g.help}
|
|
372
|
-
trailing={
|
|
372
|
+
trailing={
|
|
373
|
+
g.badge == null ? (
|
|
374
|
+
<ChevronRight aria-hidden className="size-4 rtl:-scale-x-100" />
|
|
375
|
+
) : (
|
|
376
|
+
// The group's badge (0.48) before the chevron, the two as one end.
|
|
377
|
+
<span className="flex items-center gap-2">
|
|
378
|
+
{g.badge}
|
|
379
|
+
<ChevronRight aria-hidden className="size-4 rtl:-scale-x-100" />
|
|
380
|
+
</span>
|
|
381
|
+
)
|
|
382
|
+
}
|
|
373
383
|
/>
|
|
374
384
|
))}
|
|
375
385
|
</List>
|
|
@@ -421,13 +431,17 @@ export function SettingsLayout<G extends string>({
|
|
|
421
431
|
name: g.title,
|
|
422
432
|
label: <span className={cn(dim && "opacity-40")}>{g.title}</span>,
|
|
423
433
|
icon: <span className={cn("inline-flex [&_svg]:size-4", dim && "opacity-40")}>{g.icon}</span>,
|
|
424
|
-
badge
|
|
425
|
-
|
|
434
|
+
// While searching, the hit count; otherwise the group's own badge (0.48).
|
|
435
|
+
badge: searching ? (
|
|
436
|
+
count > 0 ? (
|
|
426
437
|
<Chip size="sm" tone="brand">
|
|
427
438
|
<span aria-hidden>{count}</span>
|
|
428
439
|
<span className="sr-only-fixed">{labels.matchCount(count)}</span>
|
|
429
440
|
</Chip>
|
|
430
|
-
) : undefined
|
|
441
|
+
) : undefined
|
|
442
|
+
) : (
|
|
443
|
+
g.badge
|
|
444
|
+
),
|
|
431
445
|
};
|
|
432
446
|
})}
|
|
433
447
|
active={(open?.id ?? route.groups[0]?.id) as G}
|
package/src/shell/app-shell.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import { useAnchoredRect } from "../hooks/use-anchored-rect";
|
|
|
18
18
|
import { anchoredInline } from "../hooks/use-anchored-panel";
|
|
19
19
|
import { useDismissLayer } from "../hooks/use-dismiss";
|
|
20
20
|
import { useBreakpoint } from "../hooks/use-breakpoint";
|
|
21
|
-
import { useTextSize } from "../theme/text-size";
|
|
21
|
+
import { TEXT_SIZES, useTextSize } from "../theme/text-size";
|
|
22
22
|
import type { TextSize } from "../theme/text-size";
|
|
23
23
|
import { DEFAULT_APP_SHELL_LABELS, useKitLabels, useKitLink, type LabelOverride } from "../i18n/kit-labels";
|
|
24
24
|
import type { KitLinkComponent, KitLinkProps } from "../i18n/kit-labels";
|
|
@@ -45,6 +45,11 @@ export interface AppShellNavItem {
|
|
|
45
45
|
* shortening the real label, which the sidebar has ample room for and which is the
|
|
46
46
|
* name the rest of the product uses. */
|
|
47
47
|
shortLabel?: string;
|
|
48
|
+
/** 0.48: the text size from which the bar uses {@link shortLabel} — `"large"` keeps the
|
|
49
|
+
* full label at Normal, where five cells hold "Transactions" whole, and shortens it
|
|
50
|
+
* from Large up (keksdose dev#613, which read `useTextSize()` in its layout to decide).
|
|
51
|
+
* Default `"normal"`: the short label at every size, as before. */
|
|
52
|
+
shortLabelFrom?: TextSize;
|
|
48
53
|
/** 0.31.0: leave this entry out of the MOBILE BOTTOM BAR; the sidebar keeps it.
|
|
49
54
|
* For an entry a phone reaches another way — the brand link to the start page —
|
|
50
55
|
* when one cell fewer is what keeps the others' labels whole (the bar divides its
|
|
@@ -544,9 +549,13 @@ export function AppShell({
|
|
|
544
549
|
one that scrolls into empty space. Guarding it per component (see
|
|
545
550
|
sr-only-containment.test) could never be complete: any page's own
|
|
546
551
|
`sr-only` span re-opened it. Here it is closed for every child at once. */}
|
|
552
|
+
{/* Below `md` the bottom room is the nav plus the larger of 1.5rem and what the
|
|
553
|
+
floating launchers take above it (`--app-float-clearance`, published by
|
|
554
|
+
every kit FAB, FloatingPanel and FloatingActionGroup while mounted — keksdose
|
|
555
|
+
dev#612: the last row ended under the help launcher). */}
|
|
547
556
|
<main
|
|
548
557
|
className={cn(
|
|
549
|
-
"relative flex-1 max-w-full overflow-x-clip pb-[calc(var(--app-nav-h,4rem)+1.5rem)] md:pb-0 md:min-h-0 md:overflow-y-auto md:[scrollbar-gutter:stable_both-edges]",
|
|
558
|
+
"relative flex-1 max-w-full overflow-x-clip pb-[calc(var(--app-nav-h,4rem)+max(1.5rem,var(--app-float-clearance,0px)))] md:pb-0 md:min-h-0 md:overflow-y-auto md:[scrollbar-gutter:stable_both-edges]",
|
|
550
559
|
// The list page's fill (0.40, docs/page-layout-harmonization.md §1.3): while
|
|
551
560
|
// a `fillHeight` DataTable is inside, a flex column — a banner above the
|
|
552
561
|
// page keeps its own height and the page (a `FillHeight`) takes the rest,
|
|
@@ -764,6 +773,11 @@ function MobileCellLabel({ children }: { children: ReactNode }) {
|
|
|
764
773
|
* nothing marked on every sub-page (the same defect the sidebar had). */
|
|
765
774
|
function MobileNavItem({ item }: { item: AppShellNavItem }) {
|
|
766
775
|
const subActive = useSubItemActive(item);
|
|
776
|
+
const { size } = useTextSize();
|
|
777
|
+
const short =
|
|
778
|
+
item.shortLabel !== undefined && TEXT_SIZES.indexOf(size) >= TEXT_SIZES.indexOf(item.shortLabelFrom ?? "normal")
|
|
779
|
+
? item.shortLabel
|
|
780
|
+
: undefined;
|
|
767
781
|
return (
|
|
768
782
|
<NavLink
|
|
769
783
|
to={item.to}
|
|
@@ -774,7 +788,7 @@ function MobileNavItem({ item }: { item: AppShellNavItem }) {
|
|
|
774
788
|
<span className="relative">
|
|
775
789
|
<item.icon className="size-5" />
|
|
776
790
|
</span>
|
|
777
|
-
<MobileCellLabel>{
|
|
791
|
+
<MobileCellLabel>{short ?? item.label}</MobileCellLabel>
|
|
778
792
|
</NavLink>
|
|
779
793
|
);
|
|
780
794
|
}
|
|
@@ -28,6 +28,8 @@ export function OptionSwitcherMenu<T extends string>({
|
|
|
28
28
|
/** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
|
|
29
29
|
* 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
|
|
30
30
|
"aria-label": string;
|
|
31
|
+
/** The trigger's tooltip. Default: `aria-label` (0.48) — the name, shown on hover and
|
|
32
|
+
* focus. A different text also describes the trigger (`aria-describedby`). */
|
|
31
33
|
title?: string;
|
|
32
34
|
heading?: string;
|
|
33
35
|
options: OptionSwitcherOption<T>[];
|
|
@@ -37,13 +39,23 @@ export function OptionSwitcherMenu<T extends string>({
|
|
|
37
39
|
return (
|
|
38
40
|
<HoverMenu
|
|
39
41
|
aria-label={ariaLabel}
|
|
40
|
-
trigger={({ toggle }) =>
|
|
41
|
-
|
|
42
|
+
trigger={({ toggle }) => {
|
|
43
|
+
const button = (
|
|
42
44
|
<button type="button" onClick={toggle} aria-label={ariaLabel} className={TOPBAR_TRIGGER_CLASS}>
|
|
43
45
|
{icon}
|
|
44
46
|
</button>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
);
|
|
48
|
+
// The name on hover and focus even without a `title` (0.48, keksdose dev#610). A
|
|
49
|
+
// bubble that repeats the name sits round the button in a fragment, as IconButton's
|
|
50
|
+
// does, so it does not describe the button by its own name — a reader heard "Text
|
|
51
|
+
// size, button, Text size". A `title` that says more still describes it.
|
|
52
|
+
const tip = title ?? ariaLabel;
|
|
53
|
+
return (
|
|
54
|
+
<Tooltip label={tip} portal>
|
|
55
|
+
{tip === ariaLabel ? <>{button}</> : button}
|
|
56
|
+
</Tooltip>
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
47
59
|
>
|
|
48
60
|
{(close) => (
|
|
49
61
|
<ul className="py-1">
|
|
@@ -6,6 +6,7 @@ import { HoverMenu, type HoverMenuProps } from "../components/hover-menu";
|
|
|
6
6
|
import { MenuItem } from "../components/menu-item";
|
|
7
7
|
import type { MenuItemTone } from "../components/menu-item";
|
|
8
8
|
import { StatusDot } from "../components/status-dot";
|
|
9
|
+
import { Tooltip } from "../components/tooltip";
|
|
9
10
|
import type { UserAvatarBadge } from "../components/user-avatar";
|
|
10
11
|
import { TOPBAR_TRIGGER_CLASS } from "./topbar-controls";
|
|
11
12
|
|
|
@@ -296,16 +297,27 @@ export function TopBarActionMenu({
|
|
|
296
297
|
trigger={({ open, toggle, close }) => {
|
|
297
298
|
// One element for both kinds: the router's link when `href` is set, else the
|
|
298
299
|
// kit's button. Same classes and face either way.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
300
|
+
//
|
|
301
|
+
// Its name in the kit's Tooltip on hover and focus (0.48, keksdose dev#610): an
|
|
302
|
+
// icon alone told a sighted user nothing the screen reader was told. In a
|
|
303
|
+
// fragment, as IconButton does it, so the bubble is not the trigger's
|
|
304
|
+
// `aria-describedby` — the name it repeats is the trigger's own, and a reader
|
|
305
|
+
// would hear it twice. Portalled, like the top bar's other triggers.
|
|
306
|
+
const control = (className: string, children: ReactNode, label?: string) => (
|
|
307
|
+
<Tooltip label={ariaLabel} portal>
|
|
308
|
+
<>
|
|
309
|
+
{href ? (
|
|
310
|
+
<Link {...triggerAttributes} to={href} onClick={close} aria-label={label} className={className}>
|
|
311
|
+
{children}
|
|
312
|
+
</Link>
|
|
313
|
+
) : (
|
|
314
|
+
<button {...triggerAttributes} type="button" onClick={toggle} aria-label={label} className={className}>
|
|
315
|
+
{children}
|
|
316
|
+
</button>
|
|
317
|
+
)}
|
|
318
|
+
</>
|
|
319
|
+
</Tooltip>
|
|
320
|
+
);
|
|
309
321
|
if (trigger) {
|
|
310
322
|
return control(
|
|
311
323
|
cn(TOPBAR_TRIGGER_CLASS, "relative", triggerClassName),
|
|
@@ -58,19 +58,25 @@ export function ThemeToggle({
|
|
|
58
58
|
// icon button with no name — a reader heard "button" and nothing else.
|
|
59
59
|
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme: ariaLabel });
|
|
60
60
|
const Icon = mode === "dark" ? Moon : Sun;
|
|
61
|
+
const button = (
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
onClick={() => {
|
|
65
|
+
onChange?.(mode === "dark" ? "light" : "dark");
|
|
66
|
+
onToggle?.();
|
|
67
|
+
}}
|
|
68
|
+
aria-label={labels.theme}
|
|
69
|
+
className={cn(TOPBAR_TRIGGER_CLASS, className)}
|
|
70
|
+
>
|
|
71
|
+
<Icon className="size-5" />
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
// The name on hover and focus even without a `title` (0.48, keksdose dev#610); a bubble
|
|
75
|
+
// that only repeats it is visual, in a fragment as on IconButton — see OptionSwitcherMenu.
|
|
76
|
+
const tip = title ?? labels.theme;
|
|
61
77
|
return (
|
|
62
|
-
<Tooltip label={
|
|
63
|
-
|
|
64
|
-
type="button"
|
|
65
|
-
onClick={() => {
|
|
66
|
-
onChange?.(mode === "dark" ? "light" : "dark");
|
|
67
|
-
onToggle?.();
|
|
68
|
-
}}
|
|
69
|
-
aria-label={labels.theme}
|
|
70
|
-
className={cn(TOPBAR_TRIGGER_CLASS, className)}
|
|
71
|
-
>
|
|
72
|
-
<Icon className="size-5" />
|
|
73
|
-
</button>
|
|
78
|
+
<Tooltip label={tip} portal>
|
|
79
|
+
{tip === labels.theme ? <>{button}</> : button}
|
|
74
80
|
</Tooltip>
|
|
75
81
|
);
|
|
76
82
|
}
|
package/src/tour/tour.tsx
CHANGED
|
@@ -615,6 +615,11 @@ function TourOverlay({
|
|
|
615
615
|
className={cn("pointer-events-none fixed inset-0", interactive ? "z-[45]" : "z-[60]")}
|
|
616
616
|
role="dialog"
|
|
617
617
|
aria-modal={interactive ? undefined : "true"}
|
|
618
|
+
// A stable marker for the overlay, on every step (0.48, keksdose dev#611–613): an
|
|
619
|
+
// app that hides its floating help launcher while a modal is open excepts the
|
|
620
|
+
// tour, which may spotlight that launcher — `[aria-modal="true"]:not([data-tour-overlay])`.
|
|
621
|
+
// keksdose matched the `pointer-events-none` class until now, which is the kit's to change.
|
|
622
|
+
data-tour-overlay=""
|
|
618
623
|
data-tour-interactive={interactive ? "" : undefined}
|
|
619
624
|
aria-label={step.title}
|
|
620
625
|
dir={dir}
|