@eifi1/ui-kit 0.48.2 → 0.50.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 +34 -16
- package/dist/admin/admin-action-confirm.js +1 -1
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.js +5 -1
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/user-roster.d.ts +24 -12
- package/dist/admin/user-roster.js +21 -6
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js +2 -2
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/not-found-page.js +1 -1
- package/dist/auth/not-found-page.js.map +1 -1
- package/dist/auth/reset-password-form.js +2 -2
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +5 -3
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/plan-card.js +2 -2
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js +3 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +1 -0
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/breadcrumbs.d.ts +19 -1
- package/dist/components/breadcrumbs.js +11 -4
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-look.d.ts +37 -7
- package/dist/components/button-look.js +33 -6
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +4 -1
- package/dist/components/button.js +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -1
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-edge.d.ts +10 -0
- package/dist/components/chart-edge.js +57 -0
- package/dist/components/chart-edge.js.map +1 -0
- package/dist/components/chart.js +2 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +8 -1
- package/dist/components/chip.js +4 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox-core.js +1 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox-suggestions.js +1 -0
- package/dist/components/combobox-suggestions.js.map +1 -1
- package/dist/components/confirm-context.d.ts +5 -0
- package/dist/components/confirm-context.js +7 -0
- package/dist/components/confirm-context.js.map +1 -0
- package/dist/components/confirm-dialog.js +3 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/currency-select.d.ts +28 -2
- package/dist/components/currency-select.js +56 -10
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +2 -2
- package/dist/components/danger-confirm.js +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-fit.js +21 -3
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +58 -3
- package/dist/components/data-table-mobile.js +147 -16
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -1
- package/dist/components/data-table-row-actions.js +7 -0
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table.d.ts +34 -1
- package/dist/components/data-table.js +37 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dropdown.js +1 -0
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/floating-panel.d.ts +36 -13
- package/dist/components/floating-panel.js +27 -7
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/form-actions.js +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +1 -0
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/icon-button.d.ts +11 -0
- package/dist/components/icon-button.js +7 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/modal.js +1 -0
- package/dist/components/modal.js.map +1 -1
- package/dist/components/nav-pills.d.ts +8 -1
- package/dist/components/nav-pills.js +61 -38
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -1
- package/dist/components/number-input.js +6 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.js +2 -0
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-header-context.d.ts +8 -0
- package/dist/components/page-header-context.js +7 -0
- package/dist/components/page-header-context.js.map +1 -0
- package/dist/components/page-header.d.ts +14 -2
- package/dist/components/page-header.js +10 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -0
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart.js +2 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.js +1 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress-bar.js +5 -4
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.js +1 -1
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/row-actions-labels.js +3 -1
- package/dist/components/row-actions-labels.js.map +1 -1
- package/dist/components/row-actions.d.ts +43 -9
- package/dist/components/row-actions.js +78 -15
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +37 -0
- package/dist/components/section-nav.js +33 -0
- package/dist/components/section-nav.js.map +1 -0
- package/dist/components/series-chart.js +8 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +1 -1
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/sparkline.js +2 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -7
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +5 -1
- package/dist/components/status-dot.js +4 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +2 -2
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap.d.ts +5 -2
- package/dist/components/treemap.js +26 -6
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.js +37 -6
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-banner.js +1 -1
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/demo/demo-ended.js +1 -1
- package/dist/demo/demo-ended.js.map +1 -1
- package/dist/demo/demo-start.js +1 -1
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox.js +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +9 -1
- package/dist/hooks/use-dismiss.js +25 -5
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-document-title.d.ts +14 -0
- package/dist/hooks/use-document-title.js +22 -0
- package/dist/hooks/use-document-title.js.map +1 -0
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +20 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +34 -1
- package/dist/i18n/kit-labels.js +43 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +22 -3
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +20 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +20 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +20 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +20 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +1 -0
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.js +2 -2
- package/dist/landing/landing-actions.js.map +1 -1
- package/dist/landing/public-header.js +2 -2
- package/dist/landing/public-header.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +27 -0
- package/dist/lib/clipboard-minus.js +63 -0
- package/dist/lib/clipboard-minus.js.map +1 -0
- package/dist/lib/data-attributes.d.ts +11 -0
- package/dist/lib/data-attributes.js +10 -0
- package/dist/lib/data-attributes.js.map +1 -0
- package/dist/lib/document-title.d.ts +17 -0
- package/dist/lib/document-title.js +29 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/float-clearance.js +8 -2
- package/dist/lib/float-clearance.js.map +1 -1
- package/dist/lib/format.d.ts +36 -10
- package/dist/lib/format.js +57 -11
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +32 -0
- package/dist/lib/minus.js +14 -0
- package/dist/lib/minus.js.map +1 -0
- package/dist/lib/status-tone.d.ts +30 -0
- package/dist/lib/status-tone.js +17 -0
- package/dist/lib/status-tone.js.map +1 -0
- package/dist/settings/settings-layout.js +3 -2
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +1 -1
- package/dist/shell/account-menu-language.js +9 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/app-shell-labels.js +2 -1
- package/dist/shell/app-shell-labels.js.map +1 -1
- package/dist/shell/app-shell.d.ts +58 -16
- package/dist/shell/app-shell.js +151 -43
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/language-code.d.ts +3 -0
- package/dist/shell/language-code.js +8 -0
- package/dist/shell/language-code.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +14 -6
- package/dist/shell/topbar-controls.js +14 -17
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +1 -1
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/testing/react.d.ts +25 -0
- package/dist/testing/react.js +19 -0
- package/dist/testing/react.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +82 -15
- package/dist/theme/chart-palette.js +51 -24
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +9 -0
- package/dist/theme/palette-presets.js +77 -62
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +42 -0
- package/dist/tour/tour-dock.js +80 -0
- package/dist/tour/tour-dock.js.map +1 -0
- package/dist/tour/tour-gates.d.ts +71 -0
- package/dist/tour/tour-gates.js +114 -0
- package/dist/tour/tour-gates.js.map +1 -0
- package/dist/tour/tour-guide-button.d.ts +27 -0
- package/dist/tour/tour-guide-button.js +45 -0
- package/dist/tour/tour-guide-button.js.map +1 -0
- package/dist/tour/tour-guide-labels.d.ts +8 -0
- package/dist/tour/tour-guide-labels.js +9 -0
- package/dist/tour/tour-guide-labels.js.map +1 -0
- package/dist/tour/tour-labels.js +9 -1
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +133 -9
- package/dist/tour/tour.js +594 -70
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-url-sync.d.ts +5 -2
- package/dist/tour/use-tour-url-sync.js +7 -1
- package/dist/tour/use-tour-url-sync.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +18 -5
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +11 -0
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +11 -5
- package/dist/wizard/use-wizard.js.map +1 -1
- package/package.json +9 -1
- package/src/admin/admin-action-confirm.tsx +1 -1
- package/src/admin/invitations-panel.tsx +7 -3
- package/src/admin/user-roster.tsx +41 -14
- package/src/auth/accept-invitation.tsx +3 -3
- package/src/auth/not-found-page.tsx +1 -1
- package/src/auth/reset-password-form.tsx +3 -3
- package/src/auth/verify-email.tsx +13 -3
- package/src/billing/plan-card.tsx +2 -2
- package/src/components/amount-input.tsx +5 -4
- package/src/components/autocomplete.tsx +2 -0
- package/src/components/breadcrumbs.tsx +32 -4
- package/src/components/button-look.ts +86 -15
- package/src/components/button.tsx +6 -3
- package/src/components/calendar-heatmap.tsx +2 -1
- package/src/components/chart-edge.ts +73 -0
- package/src/components/chart.tsx +3 -1
- package/src/components/chip.tsx +10 -1
- package/src/components/combobox-core.tsx +2 -0
- package/src/components/combobox-suggestions.tsx +2 -0
- package/src/components/confirm-context.ts +7 -0
- package/src/components/confirm-dialog.tsx +3 -3
- package/src/components/currency-select.tsx +95 -6
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-fit.ts +41 -6
- package/src/components/data-table-mobile.tsx +259 -17
- package/src/components/data-table-row-actions.tsx +9 -1
- package/src/components/data-table-row.tsx +3 -0
- package/src/components/data-table.tsx +91 -19
- package/src/components/dropdown.tsx +2 -0
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/floating-panel.tsx +67 -16
- package/src/components/form-actions.tsx +2 -2
- package/src/components/full-bleed-dialog.tsx +3 -0
- package/src/components/icon-button.tsx +21 -8
- package/src/components/modal.tsx +3 -0
- package/src/components/nav-pills.tsx +29 -2
- package/src/components/number-input.tsx +8 -3
- package/src/components/numpad-sheet.tsx +4 -0
- package/src/components/page-header-context.ts +8 -0
- package/src/components/page-header.tsx +34 -2
- package/src/components/picker-sheet.tsx +4 -0
- package/src/components/pie-chart.tsx +2 -1
- package/src/components/popover.tsx +2 -0
- package/src/components/progress-bar.tsx +6 -3
- package/src/components/reauth-dialog.tsx +1 -1
- package/src/components/row-actions-labels.ts +2 -0
- package/src/components/row-actions.tsx +154 -33
- package/src/components/section-nav.tsx +65 -0
- package/src/components/series-chart.tsx +12 -3
- package/src/components/share-card.tsx +1 -1
- package/src/components/signature-pad.tsx +1 -1
- package/src/components/sparkline.tsx +2 -1
- package/src/components/stat-tile.tsx +19 -7
- package/src/components/status-dot.tsx +7 -1
- package/src/components/tooltip.tsx +3 -1
- package/src/components/translation-review-editor.tsx +2 -2
- package/src/components/translation-review.tsx +2 -2
- package/src/components/treemap.tsx +34 -8
- package/src/components/use-table-state.ts +63 -8
- package/src/data-table.ts +3 -3
- package/src/demo/demo-banner.tsx +1 -1
- package/src/demo/demo-ended.tsx +1 -1
- package/src/demo/demo-start.tsx +1 -1
- package/src/feedback/feedback-inbox.tsx +1 -1
- package/src/hooks/use-dismiss.ts +43 -4
- package/src/hooks/use-document-title.ts +31 -0
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +17 -0
- package/src/i18n/kit-labels.tsx +79 -1
- package/src/i18n/locales/es.ts +17 -0
- package/src/i18n/locales/fr.ts +17 -0
- package/src/i18n/locales/hu.ts +17 -0
- package/src/i18n/locales/it.ts +17 -0
- package/src/i18n/locales/zh.ts +17 -0
- package/src/i18n/review.ts +3 -0
- package/src/i18n/shared-labels.ts +1 -0
- package/src/index.ts +13 -0
- package/src/landing/landing-actions.tsx +2 -2
- package/src/landing/public-header.tsx +2 -2
- package/src/lib/clipboard-minus.ts +94 -0
- package/src/lib/data-attributes.ts +14 -0
- package/src/lib/document-title.ts +41 -0
- package/src/lib/float-clearance.ts +19 -3
- package/src/lib/format.ts +117 -30
- package/src/lib/minus.ts +50 -0
- package/src/lib/status-tone.ts +43 -0
- package/src/settings/settings-layout.tsx +3 -2
- package/src/shell/account-menu-language.tsx +14 -2
- package/src/shell/app-shell-labels.ts +1 -0
- package/src/shell/app-shell.tsx +291 -68
- package/src/shell/language-code.ts +5 -0
- package/src/shell/topbar-controls.tsx +35 -15
- package/src/testing/css-variables.generated.ts +6 -6
- package/src/testing/react.ts +59 -0
- package/src/theme/chart-palette.ts +102 -20
- package/src/theme/palette-presets.ts +121 -84
- package/src/tour/tour-dock.ts +132 -0
- package/src/tour/tour-gates.ts +195 -0
- package/src/tour/tour-guide-button.tsx +64 -0
- package/src/tour/tour-guide-labels.ts +15 -0
- package/src/tour/tour-labels.ts +8 -0
- package/src/tour/tour.tsx +949 -86
- package/src/tour/use-tour-url-sync.ts +14 -4
- package/src/tour.ts +3 -0
- package/src/wizard/stepper-nav.tsx +27 -7
- package/src/wizard/types.ts +12 -0
- package/src/wizard/use-wizard.ts +16 -9
- package/tokens.css +28 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/reauth-dialog.tsx"],"sourcesContent":["import { useContext, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\n\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { CurrentPasswordInput, usePromisePending } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { ModalCloseContext } from \"./modal\";\nimport { Button } from \"./button\";\nimport { Spinner } from \"./spinner\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"./reauth-dialog-labels\";\n\n/**\n * Every string the dialog renders — the `reauthDialog` namespace of\n * `<UiKitProvider labels>`, and overridable per instance through `labels`. The title,\n * description and submit button are usually worded per action (\"Add a passkey\"),\n * which is what the `title` / `description` / `submitLabel` props are for; these are\n * the fallbacks.\n */\nexport interface ReauthDialogLabels {\n title: string;\n description: string;\n /** Label of the password field. */\n password: string;\n submit: string;\n cancel: string;\n}\n\n// Defined in ./reauth-dialog-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_REAUTH_DIALOG_LABELS };\n\nexport interface ReauthDialogProps {\n /**\n * Verifies the password — the app's call, e.g. the request the password guards.\n *\n * Return a promise and the dialog manages itself: busy until it settles, closes\n * (`onClose(true)`) when it resolves, STAYS OPEN with the password kept and selected\n * when it rejects — a wrong password is a typo to correct, not a sign-out. Pass the\n * reason as `error`. Return nothing and the app owns the rest: `busy` for the\n * pending state, unmounting the dialog once it is done.\n */\n onSubmit: (password: string) => void | Promise<unknown>;\n /**\n * The dialog has closed: `true` after a resolved `onSubmit`, `false` for Cancel,\n * Escape, the backdrop and Back. A dismissal while `onSubmit` is still running also\n * answers `false`, and that call's late result is then ignored.\n */\n onClose: (confirmed: boolean) => void;\n /**\n * Why the last attempt failed (\"Wrong password\"), shown under the field, which turns\n * invalid (`aria-invalid`, `--danger-border-strong`). The app decides the wording —\n * a 400 is a wrong password, a 500 is not. Hidden again as soon as the user edits\n * the field (the message is about the password they had typed), and shown again by\n * the next failed submit.\n */\n error?: ReactNode;\n /** The check is running, for a caller that tracks it itself (a mutation's\n * `isPending`); a promise returned from `onSubmit` does the same on its own. */\n busy?: boolean;\n /** Default: `labels.title` — \"Confirm it’s you\". */\n title?: ReactNode;\n /** The line under the title. Default: `labels.description`. */\n description?: ReactNode;\n /** Text of the submit button. Default: `labels.submit` — \"Continue\". */\n submitLabel?: ReactNode;\n /** `\"danger\"` when what the password unlocks cannot be undone: the submit is then the\n * destructive button. Default `\"neutral\"`. */\n tone?: \"neutral\" | \"danger\";\n /** Extra body above the field — what is about to happen, a warning. */\n children?: ReactNode;\n /** Kept-mounted mode, as on `Modal`: left out, the dialog is open while mounted. A\n * flip to `false` animates it out and wipes the password. */\n open?: boolean;\n /** User-facing strings; see {@link ReauthDialogLabels}. */\n labels?: LabelOverride<ReauthDialogLabels>;\n}\n\n/**\n * \"Enter your current password to continue\" — the step-up check in front of an\n * action that changes how an account signs in (adding a passkey) or what it can\n * reach. Kurvenschmiede asked for the password before registering a passkey in a\n * dialog of its own; keksdose asks for it inline in `DangerConfirm`'s\n * `requirePassword` tile. Both are the same three facts — the field is the CURRENT\n * password (the password manager fills it, never generates one), a wrong one stays in\n * the dialog with its error, and the APP verifies it — so the promise handling is\n * `DangerConfirm`'s own, shared, the field is its `CurrentPasswordInput` with an\n * `error`, and this is the modal form of them.\n *\n * A component rather than a `useReauth()` promise: the step that follows the check\n * (Kurvenschmiede's WebAuthn ceremony) needs what the server answered, and the error\n * wording needs the app's own status codes — both already live in the app's mutation,\n * which this dialog only has to be handed (`onSubmit={(pw) => begin.mutateAsync(pw)}`,\n * `error={…}`).\n *\n * The field is a `<form>`, so Enter submits once something is typed. Focus starts in\n * the field and returns to the trigger through `Modal`'s trap. While a check runs,\n * the field is read-only, Cancel is disabled and Escape is held: a request that has\n * left cannot be cancelled by closing the dialog, and the dialog should not pretend it\n * can.\n */\nexport function ReauthDialog({\n onSubmit,\n onClose,\n error,\n busy: busyProp,\n title,\n description,\n submitLabel,\n tone = \"neutral\",\n children,\n open,\n labels: labelsProp,\n}: ReauthDialogProps) {\n const labels = useKitLabels(\"reauthDialog\", DEFAULT_REAUTH_DIALOG_LABELS, labelsProp);\n const [password, setPassword] = useState(\"\");\n // The error describes the password that was submitted; once the user edits it, it\n // describes nothing on screen. Cleared by the next submit, so a second failure with\n // the same message shows again.\n const [edited, setEdited] = useState(false);\n const { pending, run } = usePromisePending();\n const busy = Boolean(busyProp) || pending;\n const inputRef = useRef<HTMLInputElement>(null);\n // `onSubmit` resolved: the actions row lowers the panel (state, so it re-renders),\n // and `onClose` answers true (a ref, because `Modal` may call the `onClose` it saw\n // BEFORE that re-render — its latest-callback ref is refreshed in an effect that runs\n // after the actions row's).\n const [done, setDone] = useState(false);\n const confirmed = useRef(false);\n const formId = useId();\n const isOpen = open ?? true;\n\n // A close (kept-mounted) wipes the password: it must not sit in a hidden dialog.\n // During render, like DangerConfirm's disarm, so no frame holds it.\n const [wasOpen, setWasOpen] = useState(isOpen);\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n if (!isOpen) {\n setPassword(\"\");\n setEdited(false);\n setDone(false);\n }\n }\n\n // After `Modal`'s own effect (a parent's effects run after its children's), so the\n // trap has recorded the trigger as its restore target before focus moves in.\n useEffect(() => {\n if (!isOpen) return;\n confirmed.current = false;\n inputRef.current?.focus();\n }, [isOpen]);\n\n const submit = () => {\n if (busy || password === \"\") return;\n setEdited(false);\n run(onSubmit(password), () => {\n confirmed.current = true;\n setDone(true);\n });\n };\n\n // A rejection leaves focus where it was (the field, or the submit button) — select\n // the password there so the retry replaces it rather than appending to it.\n const wasPending = useRef(false);\n useEffect(() => {\n if (wasPending.current && !pending && !done) inputRef.current?.select();\n wasPending.current = pending;\n }, [pending, done]);\n\n const shownError = edited ? undefined : error;\n\n return (\n <DialogFrame\n open={open}\n title={title ?? labels.title}\n description={description ?? labels.description}\n onClose={() => onClose(confirmed.current)}\n onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => {\n if (busy && e.key === \"Escape\") e.preventDefault();\n }}\n aria-busy={busy || undefined}\n actions={\n <ReauthActions\n formId={formId}\n done={done}\n busy={busy}\n canSubmit={password !== \"\"}\n danger={tone === \"danger\"}\n cancelLabel={labels.cancel}\n submitLabel={submitLabel ?? labels.submit}\n />\n }\n >\n {children}\n <form\n id={formId}\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n {/* The kit's CurrentPasswordInput (0.23; it drew the same field by hand until\n then): the password manager fills it with the CURRENT password and never\n offers to generate one; `busy` makes it `readOnly`, not `disabled`, because\n disabling the field that has focus drops focus to <body>. Labelled by this\n namespace's `password` (\"Current password\"), not `dangerConfirm.password`.\n The `error` is Input's own: it paints the field, sits under it and is its\n `aria-describedby`; passing the prop on every render keeps the field's box\n steady, so the message going away on the first keystroke of a retry does not\n remount the <input> under the typist. */}\n <CurrentPasswordInput\n ref={inputRef}\n label={labels.password}\n value={password}\n busy={busy}\n error={shownError}\n onValueChange={(next) => {\n setPassword(next);\n setEdited(true);\n }}\n />\n </form>\n </DialogFrame>\n );\n}\n\n/**\n * The actions row. A component of its own because it renders INSIDE the `Modal`, where\n * the panel's animated close is in context: a resolved `onSubmit` lowers the panel\n * through it, the way Cancel and Escape do, and `Modal` then calls `onClose`.\n */\nfunction ReauthActions({\n formId,\n done,\n busy,\n canSubmit,\n danger,\n cancelLabel,\n submitLabel,\n}: {\n formId: string;\n done: boolean;\n busy: boolean;\n canSubmit: boolean;\n danger: boolean;\n cancelLabel: ReactNode;\n submitLabel: ReactNode;\n}) {\n const close = useContext(ModalCloseContext);\n useEffect(() => {\n if (done) close?.();\n }, [done, close]);\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => close?.()}>\n {cancelLabel}\n </Button>\n <Button\n type=\"submit\"\n form={formId}\n variant={danger ? \"danger\" : \"primary\"}\n disabled={!canSubmit || busy}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {submitLabel}\n </Button>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/reauth-dialog.tsx"],"sourcesContent":["import { useContext, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\n\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { CurrentPasswordInput, usePromisePending } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { ModalCloseContext } from \"./modal\";\nimport { Button } from \"./button\";\nimport { Spinner } from \"./spinner\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"./reauth-dialog-labels\";\n\n/**\n * Every string the dialog renders — the `reauthDialog` namespace of\n * `<UiKitProvider labels>`, and overridable per instance through `labels`. The title,\n * description and submit button are usually worded per action (\"Add a passkey\"),\n * which is what the `title` / `description` / `submitLabel` props are for; these are\n * the fallbacks.\n */\nexport interface ReauthDialogLabels {\n title: string;\n description: string;\n /** Label of the password field. */\n password: string;\n submit: string;\n cancel: string;\n}\n\n// Defined in ./reauth-dialog-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_REAUTH_DIALOG_LABELS };\n\nexport interface ReauthDialogProps {\n /**\n * Verifies the password — the app's call, e.g. the request the password guards.\n *\n * Return a promise and the dialog manages itself: busy until it settles, closes\n * (`onClose(true)`) when it resolves, STAYS OPEN with the password kept and selected\n * when it rejects — a wrong password is a typo to correct, not a sign-out. Pass the\n * reason as `error`. Return nothing and the app owns the rest: `busy` for the\n * pending state, unmounting the dialog once it is done.\n */\n onSubmit: (password: string) => void | Promise<unknown>;\n /**\n * The dialog has closed: `true` after a resolved `onSubmit`, `false` for Cancel,\n * Escape, the backdrop and Back. A dismissal while `onSubmit` is still running also\n * answers `false`, and that call's late result is then ignored.\n */\n onClose: (confirmed: boolean) => void;\n /**\n * Why the last attempt failed (\"Wrong password\"), shown under the field, which turns\n * invalid (`aria-invalid`, `--danger-border-strong`). The app decides the wording —\n * a 400 is a wrong password, a 500 is not. Hidden again as soon as the user edits\n * the field (the message is about the password they had typed), and shown again by\n * the next failed submit.\n */\n error?: ReactNode;\n /** The check is running, for a caller that tracks it itself (a mutation's\n * `isPending`); a promise returned from `onSubmit` does the same on its own. */\n busy?: boolean;\n /** Default: `labels.title` — \"Confirm it’s you\". */\n title?: ReactNode;\n /** The line under the title. Default: `labels.description`. */\n description?: ReactNode;\n /** Text of the submit button. Default: `labels.submit` — \"Continue\". */\n submitLabel?: ReactNode;\n /** `\"danger\"` when what the password unlocks cannot be undone: the submit is then the\n * destructive button. Default `\"neutral\"`. */\n tone?: \"neutral\" | \"danger\";\n /** Extra body above the field — what is about to happen, a warning. */\n children?: ReactNode;\n /** Kept-mounted mode, as on `Modal`: left out, the dialog is open while mounted. A\n * flip to `false` animates it out and wipes the password. */\n open?: boolean;\n /** User-facing strings; see {@link ReauthDialogLabels}. */\n labels?: LabelOverride<ReauthDialogLabels>;\n}\n\n/**\n * \"Enter your current password to continue\" — the step-up check in front of an\n * action that changes how an account signs in (adding a passkey) or what it can\n * reach. Kurvenschmiede asked for the password before registering a passkey in a\n * dialog of its own; keksdose asks for it inline in `DangerConfirm`'s\n * `requirePassword` tile. Both are the same three facts — the field is the CURRENT\n * password (the password manager fills it, never generates one), a wrong one stays in\n * the dialog with its error, and the APP verifies it — so the promise handling is\n * `DangerConfirm`'s own, shared, the field is its `CurrentPasswordInput` with an\n * `error`, and this is the modal form of them.\n *\n * A component rather than a `useReauth()` promise: the step that follows the check\n * (Kurvenschmiede's WebAuthn ceremony) needs what the server answered, and the error\n * wording needs the app's own status codes — both already live in the app's mutation,\n * which this dialog only has to be handed (`onSubmit={(pw) => begin.mutateAsync(pw)}`,\n * `error={…}`).\n *\n * The field is a `<form>`, so Enter submits once something is typed. Focus starts in\n * the field and returns to the trigger through `Modal`'s trap. While a check runs,\n * the field is read-only, Cancel is disabled and Escape is held: a request that has\n * left cannot be cancelled by closing the dialog, and the dialog should not pretend it\n * can.\n */\nexport function ReauthDialog({\n onSubmit,\n onClose,\n error,\n busy: busyProp,\n title,\n description,\n submitLabel,\n tone = \"neutral\",\n children,\n open,\n labels: labelsProp,\n}: ReauthDialogProps) {\n const labels = useKitLabels(\"reauthDialog\", DEFAULT_REAUTH_DIALOG_LABELS, labelsProp);\n const [password, setPassword] = useState(\"\");\n // The error describes the password that was submitted; once the user edits it, it\n // describes nothing on screen. Cleared by the next submit, so a second failure with\n // the same message shows again.\n const [edited, setEdited] = useState(false);\n const { pending, run } = usePromisePending();\n const busy = Boolean(busyProp) || pending;\n const inputRef = useRef<HTMLInputElement>(null);\n // `onSubmit` resolved: the actions row lowers the panel (state, so it re-renders),\n // and `onClose` answers true (a ref, because `Modal` may call the `onClose` it saw\n // BEFORE that re-render — its latest-callback ref is refreshed in an effect that runs\n // after the actions row's).\n const [done, setDone] = useState(false);\n const confirmed = useRef(false);\n const formId = useId();\n const isOpen = open ?? true;\n\n // A close (kept-mounted) wipes the password: it must not sit in a hidden dialog.\n // During render, like DangerConfirm's disarm, so no frame holds it.\n const [wasOpen, setWasOpen] = useState(isOpen);\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n if (!isOpen) {\n setPassword(\"\");\n setEdited(false);\n setDone(false);\n }\n }\n\n // After `Modal`'s own effect (a parent's effects run after its children's), so the\n // trap has recorded the trigger as its restore target before focus moves in.\n useEffect(() => {\n if (!isOpen) return;\n confirmed.current = false;\n inputRef.current?.focus();\n }, [isOpen]);\n\n const submit = () => {\n if (busy || password === \"\") return;\n setEdited(false);\n run(onSubmit(password), () => {\n confirmed.current = true;\n setDone(true);\n });\n };\n\n // A rejection leaves focus where it was (the field, or the submit button) — select\n // the password there so the retry replaces it rather than appending to it.\n const wasPending = useRef(false);\n useEffect(() => {\n if (wasPending.current && !pending && !done) inputRef.current?.select();\n wasPending.current = pending;\n }, [pending, done]);\n\n const shownError = edited ? undefined : error;\n\n return (\n <DialogFrame\n open={open}\n title={title ?? labels.title}\n description={description ?? labels.description}\n onClose={() => onClose(confirmed.current)}\n onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => {\n if (busy && e.key === \"Escape\") e.preventDefault();\n }}\n aria-busy={busy || undefined}\n actions={\n <ReauthActions\n formId={formId}\n done={done}\n busy={busy}\n canSubmit={password !== \"\"}\n danger={tone === \"danger\"}\n cancelLabel={labels.cancel}\n submitLabel={submitLabel ?? labels.submit}\n />\n }\n >\n {children}\n <form\n id={formId}\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n {/* The kit's CurrentPasswordInput (0.23; it drew the same field by hand until\n then): the password manager fills it with the CURRENT password and never\n offers to generate one; `busy` makes it `readOnly`, not `disabled`, because\n disabling the field that has focus drops focus to <body>. Labelled by this\n namespace's `password` (\"Current password\"), not `dangerConfirm.password`.\n The `error` is Input's own: it paints the field, sits under it and is its\n `aria-describedby`; passing the prop on every render keeps the field's box\n steady, so the message going away on the first keystroke of a retry does not\n remount the <input> under the typist. */}\n <CurrentPasswordInput\n ref={inputRef}\n label={labels.password}\n value={password}\n busy={busy}\n error={shownError}\n onValueChange={(next) => {\n setPassword(next);\n setEdited(true);\n }}\n />\n </form>\n </DialogFrame>\n );\n}\n\n/**\n * The actions row. A component of its own because it renders INSIDE the `Modal`, where\n * the panel's animated close is in context: a resolved `onSubmit` lowers the panel\n * through it, the way Cancel and Escape do, and `Modal` then calls `onClose`.\n */\nfunction ReauthActions({\n formId,\n done,\n busy,\n canSubmit,\n danger,\n cancelLabel,\n submitLabel,\n}: {\n formId: string;\n done: boolean;\n busy: boolean;\n canSubmit: boolean;\n danger: boolean;\n cancelLabel: ReactNode;\n submitLabel: ReactNode;\n}) {\n const close = useContext(ModalCloseContext);\n useEffect(() => {\n if (done) close?.();\n }, [done, close]);\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => close?.()}>\n {cancelLabel}\n </Button>\n <Button\n type=\"submit\"\n form={formId}\n variant={danger ? \"danger\" : \"default\"}\n disabled={!canSubmit || busy}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {submitLabel}\n </Button>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
const DEFAULT_ROW_ACTIONS_LABELS = {
|
|
3
3
|
actions: "Actions",
|
|
4
|
-
actionsFor: (name) => `Actions for ${name}
|
|
4
|
+
actionsFor: (name) => `Actions for ${name}`,
|
|
5
|
+
confirmTitle: (action, name) => name ? `${action} \u201C${name}\u201D?` : `${action}?`,
|
|
6
|
+
confirmBody: "This cannot be undone."
|
|
5
7
|
};
|
|
6
8
|
export {
|
|
7
9
|
DEFAULT_ROW_ACTIONS_LABELS
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/row-actions-labels.ts"],"sourcesContent":["// The English words of ./row-actions, in a module with no component code: the English\n// catalogue (src/i18n/defaults.ts) imports them without the component, so `en` loads as one\n// chunk like the other languages (0.34, KS U8). ./row-actions re-exports them.\n\nimport type { RowActionsLabels } from \"./row-actions\";\n\nexport const DEFAULT_ROW_ACTIONS_LABELS: RowActionsLabels = {\n actions: \"Actions\",\n actionsFor: (name) => `Actions for ${name}`,\n};\n"],"mappings":";AAMO,MAAM,6BAA+C;AAAA,EAC1D,SAAS;AAAA,EACT,YAAY,CAAC,SAAS,eAAe,IAAI;
|
|
1
|
+
{"version":3,"sources":["../../src/components/row-actions-labels.ts"],"sourcesContent":["// The English words of ./row-actions, in a module with no component code: the English\n// catalogue (src/i18n/defaults.ts) imports them without the component, so `en` loads as one\n// chunk like the other languages (0.34, KS U8). ./row-actions re-exports them.\n\nimport type { RowActionsLabels } from \"./row-actions\";\n\nexport const DEFAULT_ROW_ACTIONS_LABELS: RowActionsLabels = {\n actions: \"Actions\",\n actionsFor: (name) => `Actions for ${name}`,\n confirmTitle: (action, name) => (name ? `${action} “${name}”?` : `${action}?`),\n confirmBody: \"This cannot be undone.\",\n};\n"],"mappings":";AAMO,MAAM,6BAA+C;AAAA,EAC1D,SAAS;AAAA,EACT,YAAY,CAAC,SAAS,eAAe,IAAI;AAAA,EACzC,cAAc,CAAC,QAAQ,SAAU,OAAO,GAAG,MAAM,UAAK,IAAI,YAAO,GAAG,MAAM;AAAA,EAC1E,aAAa;AACf;","names":[]}
|
|
@@ -21,6 +21,11 @@ import { DEFAULT_ROW_ACTIONS_LABELS } from "./row-actions-labels.js";
|
|
|
21
21
|
* The data is the roster's `UserRowActionList`, generalised: entries, and `false` /
|
|
22
22
|
* `null` for the ones a condition left out (`row.locked || {…}`).
|
|
23
23
|
*
|
|
24
|
+
* Destructive actions belong in the menu (0.49 KIT-12): a `tone: "danger"` action goes
|
|
25
|
+
* into the "⋯" menu while the others stay inline (`dangerPlacement`), and choosing it
|
|
26
|
+
* opens a `ConfirmDialog` before `onSelect` runs (`confirm`; `false` to opt out). A
|
|
27
|
+
* delete is then never a bin icon in every row, one stray tap from gone.
|
|
28
|
+
*
|
|
24
29
|
* 0.33 (§10.16): an action carries its state at both sizes — a toggle (`pressed`), a
|
|
25
30
|
* disclosure (`expanded`, `controls`), a tone, a tour anchor (`dataTour`) — so a row
|
|
26
31
|
* with an "on" share button or a tour step no longer keeps hand-rolled IconButtons at
|
|
@@ -36,11 +41,21 @@ export interface RowAction {
|
|
|
36
41
|
/** The action's name: the inline icon's name and tooltip, the menu entry's words. */
|
|
37
42
|
label: string;
|
|
38
43
|
/**
|
|
39
|
-
* Runs it. The kit sends nothing —
|
|
40
|
-
* Optional only beside `href`; an action has one or
|
|
41
|
-
* then runs as the link is followed).
|
|
44
|
+
* Runs it. The kit sends nothing — it calls this, after a `tone: "danger"` action's
|
|
45
|
+
* confirmation (see {@link confirm}). Optional only beside `href`; an action has one or
|
|
46
|
+
* the other (or both: `onSelect` then runs as the link is followed).
|
|
42
47
|
*/
|
|
43
48
|
onSelect?: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* 0.49 KIT-12: ask before `onSelect`. A `tone: "danger"` action asks by default — a
|
|
51
|
+
* `ConfirmDialog` (the provider's `useConfirm`, else one of its own) titled with the
|
|
52
|
+
* action and the row's name, "Delete “Ada Example”?", the consequence under it
|
|
53
|
+
* (`rowActions.confirmBody`), the action's label on the danger button and focus on
|
|
54
|
+
* Cancel. Pass the words to say more, or `false` when the app confirms in `onSelect`
|
|
55
|
+
* itself or offers an undo instead. Any other tone asks only when given one. Not for
|
|
56
|
+
* an `href` action: a link only navigates.
|
|
57
|
+
*/
|
|
58
|
+
confirm?: boolean | RowActionConfirm;
|
|
44
59
|
/**
|
|
45
60
|
* The action NAVIGATES — an edit page, a mail to compose (0.32.1, kastlan's 0.32
|
|
46
61
|
* report): a real link inline and in the menu, so open in a new tab, a middle click
|
|
@@ -121,6 +136,12 @@ export interface RowAction {
|
|
|
121
136
|
*/
|
|
122
137
|
dataTour?: string;
|
|
123
138
|
}
|
|
139
|
+
/** The words of a {@link RowAction.confirm}; each defaults as described there. */
|
|
140
|
+
export interface RowActionConfirm {
|
|
141
|
+
title?: ReactNode;
|
|
142
|
+
body?: ReactNode;
|
|
143
|
+
confirmLabel?: ReactNode;
|
|
144
|
+
}
|
|
124
145
|
/** What a row's actions may be: entries, and `false` / `null` / `undefined` for the ones
|
|
125
146
|
* a condition left out. */
|
|
126
147
|
export type RowActionList = readonly (RowAction | false | null | undefined)[];
|
|
@@ -130,6 +151,11 @@ export interface RowActionsLabels {
|
|
|
130
151
|
actions: string;
|
|
131
152
|
/** The menu button's name, given the row's: "Actions for Ada Example". */
|
|
132
153
|
actionsFor: (name: string) => string;
|
|
154
|
+
/** 0.49 KIT-12: a danger action's confirmation title, given the action's label and the
|
|
155
|
+
* row's name when it has one: "Delete “Ada Example”?", "Delete?". */
|
|
156
|
+
confirmTitle: (action: string, name?: string) => string;
|
|
157
|
+
/** 0.49 KIT-12: the line under it — "This cannot be undone." */
|
|
158
|
+
confirmBody: string;
|
|
133
159
|
}
|
|
134
160
|
export { DEFAULT_ROW_ACTIONS_LABELS };
|
|
135
161
|
/**
|
|
@@ -148,6 +174,13 @@ export type RowActionsCollapse = "auto" | "menu" | "inline";
|
|
|
148
174
|
export type RowActionsSize = Extract<IconButtonSize, "md" | "sm" | "xs" | "2xs">;
|
|
149
175
|
export interface RowActionsProps {
|
|
150
176
|
actions: RowActionList;
|
|
177
|
+
/**
|
|
178
|
+
* 0.49 KIT-12: where `tone: "danger"` actions go while the others are inline. `"menu"`
|
|
179
|
+
* (default): into the row's "⋯" menu, so a delete is never one stray tap in every
|
|
180
|
+
* row — the other actions stay icons beside it. `"inline"`: an icon like the rest.
|
|
181
|
+
* `collapse="inline"` keeps every action inline, `collapse="menu"` every one in the menu.
|
|
182
|
+
*/
|
|
183
|
+
dangerPlacement?: "menu" | "inline";
|
|
151
184
|
/** The row's name, for the menu button's name — "Actions for Ada Example". Without it
|
|
152
185
|
* (or `null`, as API fields often are) the button is "Actions". */
|
|
153
186
|
name?: string | null;
|
|
@@ -171,7 +204,7 @@ export interface RowActionsProps {
|
|
|
171
204
|
* A row's actions, inline or as a "⋯" menu — see {@link RowActionsCollapse}. Every
|
|
172
205
|
* control stops its click (and the Enter/Space it stands for) from reaching the row, so
|
|
173
206
|
* a clickable row or a DataTable `rowLink` does not open as well; in a DataTable column
|
|
174
|
-
* also set `noRowLink: true`, as {@link rowActionsColumn} does.
|
|
207
|
+
* also set `noRowLink: true` and `role: "actions"`, as {@link rowActionsColumn} does.
|
|
175
208
|
*
|
|
176
209
|
* Inline, the icons sit in a {@link CompactControls} region at Normal: their labels
|
|
177
210
|
* stay the names and the tooltips, and a locked one keeps its reason in the tooltip — a
|
|
@@ -183,7 +216,7 @@ export interface RowActionsProps {
|
|
|
183
216
|
* press close it, and focus goes back to the "⋯" button, which is where a confirmation
|
|
184
217
|
* dialog then returns it.
|
|
185
218
|
*/
|
|
186
|
-
export declare function RowActions(
|
|
219
|
+
export declare function RowActions(props: RowActionsProps): import("react").JSX.Element;
|
|
187
220
|
/** The actions cell's classes — `rowActionsColumn`'s `className`: the column hugs its
|
|
188
221
|
* buttons, on one line, at the row's end. A hand-built table's `<TableCell>` takes them
|
|
189
222
|
* around its {@link RowActions} (0.47, keksdose N17). */
|
|
@@ -238,7 +271,7 @@ export interface RowActionsColumnOptions<T> {
|
|
|
238
271
|
*/
|
|
239
272
|
header?: ReactNode | false;
|
|
240
273
|
labels?: LabelOverride<RowActionsLabels>;
|
|
241
|
-
/** Anything else for the column (`mobileHidden`, `cellProps`…), merged last. */
|
|
274
|
+
/** Anything else for the column (`mobileHidden`, `cellProps`, another `role`…), merged last. */
|
|
242
275
|
column?: Partial<DataTableColumn<T>>;
|
|
243
276
|
}
|
|
244
277
|
/**
|
|
@@ -249,8 +282,9 @@ export interface RowActionsColumnOptions<T> {
|
|
|
249
282
|
*
|
|
250
283
|
* The heading says "Actions" on screen (0.46.1; `header` changes or hides it), the
|
|
251
284
|
* column hugs its content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the
|
|
252
|
-
* buttons keep their own clicks.
|
|
253
|
-
*
|
|
254
|
-
*
|
|
285
|
+
* buttons keep their own clicks. It is `role: "actions"` (0.49 KIT-7): DataTable's default
|
|
286
|
+
* phone card draws it after its three fields, at the card's end, and never counts it as
|
|
287
|
+
* one of them. Set `column: { mobileHidden: true }` to drop it there (with
|
|
288
|
+
* `mobileDetailsInRow` it then shows in the opened row instead).
|
|
255
289
|
*/
|
|
256
290
|
export declare function rowActionsColumn<T>({ key, actions, name, collapse, size, glyphSize, tooltipSide, headerText, header, labels, column, }: RowActionsColumnOptions<T>): DataTableColumn<T>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { isValidElement } from "react";
|
|
3
|
+
import { isValidElement, useContext } from "react";
|
|
4
4
|
import { MoreHorizontal } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
6
|
import { useLargeText } from "../hooks/use-large-text.js";
|
|
@@ -12,6 +12,8 @@ import { IconButton } from "./icon-button.js";
|
|
|
12
12
|
import { useCommitReason } from "./write-lock.js";
|
|
13
13
|
import { TableHeaderCell } from "./table.js";
|
|
14
14
|
import { DEFAULT_ROW_ACTIONS_LABELS } from "./row-actions-labels.js";
|
|
15
|
+
import { ConfirmContext } from "./confirm-context.js";
|
|
16
|
+
import { ConfirmProvider } from "./confirm-dialog.js";
|
|
15
17
|
function visibleActions(actions) {
|
|
16
18
|
return actions.filter((a) => Boolean(a) && !a.hidden);
|
|
17
19
|
}
|
|
@@ -34,10 +36,41 @@ function stateAttrs(action) {
|
|
|
34
36
|
function toneAttr(tone) {
|
|
35
37
|
return { "data-tone": tone === void 0 || tone === "default" ? void 0 : tone };
|
|
36
38
|
}
|
|
37
|
-
function RowActions({
|
|
39
|
+
function RowActions(props) {
|
|
40
|
+
const outer = useContext(ConfirmContext);
|
|
41
|
+
if (outer === null && visibleActions(props.actions).some(asksFirst)) {
|
|
42
|
+
return /* @__PURE__ */ jsx(ConfirmProvider, { children: /* @__PURE__ */ jsx(RowActionsBody, { ...props }) });
|
|
43
|
+
}
|
|
44
|
+
return /* @__PURE__ */ jsx(RowActionsBody, { ...props });
|
|
45
|
+
}
|
|
46
|
+
function asksFirst(action) {
|
|
47
|
+
if (action.href !== void 0 || action.confirm === false) return false;
|
|
48
|
+
return action.confirm !== void 0 || action.tone === "danger";
|
|
49
|
+
}
|
|
50
|
+
function useRunAction(name, labels) {
|
|
51
|
+
const confirm = useContext(ConfirmContext);
|
|
52
|
+
return (action) => {
|
|
53
|
+
if (!asksFirst(action) || confirm === null) {
|
|
54
|
+
action.onSelect?.();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const words = typeof action.confirm === "object" ? action.confirm : {};
|
|
58
|
+
void confirm({
|
|
59
|
+
title: words.title ?? labels.confirmTitle(action.label, name ?? void 0),
|
|
60
|
+
body: words.body ?? labels.confirmBody,
|
|
61
|
+
confirmLabel: words.confirmLabel ?? action.label,
|
|
62
|
+
tone: "danger",
|
|
63
|
+
commit: action.commit
|
|
64
|
+
}).then((ok) => {
|
|
65
|
+
if (ok) action.onSelect?.();
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function RowActionsBody({
|
|
38
70
|
actions,
|
|
39
71
|
name,
|
|
40
72
|
collapse = "auto",
|
|
73
|
+
dangerPlacement = "menu",
|
|
41
74
|
size = "xs",
|
|
42
75
|
menuWidth = "15rem",
|
|
43
76
|
className,
|
|
@@ -47,23 +80,50 @@ function RowActions({
|
|
|
47
80
|
}) {
|
|
48
81
|
const labels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);
|
|
49
82
|
const large = useLargeText();
|
|
83
|
+
const run = useRunAction(name, labels);
|
|
50
84
|
const entries = visibleActions(actions);
|
|
51
85
|
if (entries.length === 0) return null;
|
|
52
86
|
const asMenu = collapse === "menu" || collapse === "auto" && large && entries.length > 1;
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
const toMenu = (action) => asMenu || collapse === "auto" && dangerPlacement === "menu" && action.tone === "danger";
|
|
88
|
+
const inline = entries.filter((action) => !toMenu(action));
|
|
89
|
+
const menu = entries.filter(toMenu);
|
|
90
|
+
const title = name ? labels.actionsFor(name) : labels.actions;
|
|
91
|
+
const menuButton = menu.length > 0 && /* @__PURE__ */ jsx(
|
|
92
|
+
RowActionsMenu,
|
|
93
|
+
{
|
|
94
|
+
entries: menu,
|
|
95
|
+
title,
|
|
96
|
+
size,
|
|
97
|
+
menuWidth,
|
|
98
|
+
run,
|
|
99
|
+
className: inline.length === 0 ? className : void 0
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
if (inline.length === 0) return menuButton || null;
|
|
103
|
+
const strip = /* @__PURE__ */ jsxs("div", { "data-slot": "row-actions", className: cn("inline-flex items-center gap-0.5", className), children: [
|
|
104
|
+
inline.map((action) => /* @__PURE__ */ jsx(
|
|
55
105
|
InlineAction,
|
|
56
106
|
{
|
|
57
107
|
action,
|
|
58
108
|
size,
|
|
59
109
|
glyphSize,
|
|
60
|
-
tooltipSide
|
|
110
|
+
tooltipSide,
|
|
111
|
+
run
|
|
61
112
|
},
|
|
62
113
|
action.key ?? action.label
|
|
63
|
-
))
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
114
|
+
)),
|
|
115
|
+
menuButton
|
|
116
|
+
] });
|
|
117
|
+
return large ? strip : /* @__PURE__ */ jsx(CompactControls, { children: strip });
|
|
118
|
+
}
|
|
119
|
+
function RowActionsMenu({
|
|
120
|
+
entries,
|
|
121
|
+
title,
|
|
122
|
+
size,
|
|
123
|
+
menuWidth,
|
|
124
|
+
run,
|
|
125
|
+
className
|
|
126
|
+
}) {
|
|
67
127
|
const busy = entries.some((action) => action.pending);
|
|
68
128
|
const anchored = entries.filter((action) => action.dataTour);
|
|
69
129
|
return (
|
|
@@ -94,7 +154,7 @@ function RowActions({
|
|
|
94
154
|
),
|
|
95
155
|
onClick: stopClick,
|
|
96
156
|
onKeyDown: stopActivationKeys,
|
|
97
|
-
children: (close) => /* @__PURE__ */ jsx("ul", { "data-slot": "row-actions-menu", className: "space-y-0.5", children: entries.map((action) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(MenuAction, { action, close }) }, action.key ?? action.label)) })
|
|
157
|
+
children: (close) => /* @__PURE__ */ jsx("ul", { "data-slot": "row-actions-menu", className: "space-y-0.5", children: entries.map((action) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(MenuAction, { action, close, run }) }, action.key ?? action.label)) })
|
|
98
158
|
}
|
|
99
159
|
),
|
|
100
160
|
anchored.map((action) => (
|
|
@@ -123,7 +183,7 @@ function useActionHref(action) {
|
|
|
123
183
|
const refused = reason !== void 0 && reason !== null && reason !== false && reason !== "";
|
|
124
184
|
return action.href !== void 0 && !action.pending && !refused ? action.href : void 0;
|
|
125
185
|
}
|
|
126
|
-
function MenuAction({ action, close }) {
|
|
186
|
+
function MenuAction({ action, close, run }) {
|
|
127
187
|
const href = useActionHref(action);
|
|
128
188
|
const glyph = glyphOf(action.icon);
|
|
129
189
|
const glyphTone = action.tone === void 0 ? void 0 : MENU_GLYPH_TONE[action.tone];
|
|
@@ -142,7 +202,7 @@ function MenuAction({ action, close }) {
|
|
|
142
202
|
const tone = action.tone === "danger" ? "danger" : void 0;
|
|
143
203
|
const select = () => {
|
|
144
204
|
close();
|
|
145
|
-
action
|
|
205
|
+
run(action);
|
|
146
206
|
};
|
|
147
207
|
if (href !== void 0) {
|
|
148
208
|
return /* @__PURE__ */ jsx(
|
|
@@ -185,7 +245,8 @@ function InlineAction({
|
|
|
185
245
|
action,
|
|
186
246
|
size,
|
|
187
247
|
glyphSize,
|
|
188
|
-
tooltipSide
|
|
248
|
+
tooltipSide,
|
|
249
|
+
run
|
|
189
250
|
}) {
|
|
190
251
|
const href = useActionHref(action);
|
|
191
252
|
const glyph = glyphOf(action.icon);
|
|
@@ -228,7 +289,7 @@ function InlineAction({
|
|
|
228
289
|
pending: action.pending,
|
|
229
290
|
disabled: action.disabled,
|
|
230
291
|
stopPropagation: true,
|
|
231
|
-
onClick: () => action
|
|
292
|
+
onClick: () => run(action),
|
|
232
293
|
children: glyph
|
|
233
294
|
}
|
|
234
295
|
);
|
|
@@ -236,7 +297,7 @@ function InlineAction({
|
|
|
236
297
|
const stop = {
|
|
237
298
|
onClick: (e) => {
|
|
238
299
|
e.stopPropagation();
|
|
239
|
-
action
|
|
300
|
+
run(action);
|
|
240
301
|
},
|
|
241
302
|
onKeyDown: (e) => {
|
|
242
303
|
if (e.key === "Enter" || e.key === " ") e.stopPropagation();
|
|
@@ -325,6 +386,8 @@ function rowActionsColumn({
|
|
|
325
386
|
// `RowActionsHead` (0.47).
|
|
326
387
|
headClassName: ROW_ACTIONS_HEAD_CLASS,
|
|
327
388
|
noRowLink: true,
|
|
389
|
+
// After the phone card's fields, never one of them (0.49 KIT-7).
|
|
390
|
+
role: "actions",
|
|
328
391
|
...column
|
|
329
392
|
};
|
|
330
393
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/row-actions.tsx"],"sourcesContent":["import { isValidElement } from \"react\";\nimport type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from \"react\";\nimport { MoreHorizontal } from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useLargeText } from \"../hooks/use-large-text\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Popover } from \"./popover\";\nimport { Button } from \"./button\";\nimport { CompactControls } from \"./field-parts\";\nimport { IconButton } from \"./icon-button\";\nimport { useCommitReason, type CommitScope } from \"./write-lock\";\nimport type { IconButtonGlyphSize, IconButtonSize } from \"./icon-button\";\nimport type { TooltipSide } from \"./tooltip\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { TableHeaderCell, type TableHeaderCellProps } from \"./table\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"./row-actions-labels\";\n\n/**\n * A row's actions — inline icons at Normal, one \"⋯\" menu at Large and above\n * (docs/text-size-harmonization.md §10.8).\n *\n * At Large an IconButton shows its label as text (§4). In a dense row — a register's\n * action column, a list row's end — every label at once does not fit: Kurvenschmiede\n * has 44 IconButtons in rows, kastlan 119. So a row's actions collapse instead, into\n * the menu the user roster has used since 0.31 (`UserRowActions`, now built on this):\n * one \"⋯\" button, and a menu with each action's icon AND words, where a locked one stays\n * listed and says why.\n *\n * The data is the roster's `UserRowActionList`, generalised: entries, and `false` /\n * `null` for the ones a condition left out (`row.locked || {…}`).\n *\n * 0.33 (§10.16): an action carries its state at both sizes — a toggle (`pressed`), a\n * disclosure (`expanded`, `controls`), a tone, a tour anchor (`dataTour`) — so a row\n * with an \"on\" share button or a tour step no longer keeps hand-rolled IconButtons at\n * Normal and this only at Large (keksdose k15).\n */\n\n/** An action's colour: IconButton's tone inline; in the menu the glyph takes it and the\n * words keep the text colour, except `danger`, whose words are the danger text. */\nexport type RowActionTone = \"default\" | \"muted\" | \"info\" | \"warning\" | \"danger\";\n\n/** One action of a row. */\nexport interface RowAction {\n /** React key. Default: the label. */\n key?: string;\n /** The action's name: the inline icon's name and tooltip, the menu entry's words. */\n label: string;\n /**\n * Runs it. The kit sends nothing — the app opens its confirmation or calls its API.\n * Optional only beside `href`; an action has one or the other (or both: `onSelect`\n * then runs as the link is followed).\n */\n onSelect?: () => void;\n /**\n * The action NAVIGATES — an edit page, a mail to compose (0.32.1, kastlan's 0.32\n * report): a real link inline and in the menu, so open in a new tab, a middle click\n * and the URL on hover work, which `onSelect` + `navigate` lost. The kit's link rule\n * applies ({@link Button}'s `href`): in-app through the provider's `linkComponent`, an\n * external or `#anchor` one a plain `<a>`, `mailto:` included.\n *\n * A link cannot carry a reason or a spinner, so an action with a `disabledReason`\n * (or under a write lock, with `commit`) or `pending` stays a button and says why.\n */\n href?: string;\n /** With `href`: leaves the app — a new tab, and a screen reader is told so. */\n external?: boolean;\n /**\n * The glyph: a Lucide icon (`Pencil`), or an element (`<Pencil />`, an app's own\n * svg) — DataTable's `rowActions` hand their `icon` node through. Inline, an action\n * without one is a small text button; in the menu it is the words alone.\n */\n icon?: LucideIcon | ReactElement;\n /**\n * `danger` for delete, revoke, erase: the quiet red of IconButton's `tone=\"danger\"`\n * inline, the danger text tone in the menu. `muted` (an open or add that should not\n * compete), `info` (a notice-worthy, harmless action: keksdose's reconcile) and\n * `warning` (a flag that wants attention): IconButton's tone inline; in the menu the\n * glyph takes it and the words keep the text colour, so `muted` looks like `default`\n * there (0.33).\n */\n tone?: RowActionTone;\n /** Why it is not available for this row. It stays listed, focusable, and says so — an\n * action that silently vanished would leave the reader looking for it. */\n disabledReason?: ReactNode;\n /**\n * This action COMMITS — it saves, sends, deletes: under a locked\n * {@link WriteLockProvider} it is refused with the lock's reason, as {@link Button}'s\n * `commit` is, inline and in the menu alike. No provider, or an unlocked one: no effect.\n */\n commit?: CommitScope;\n /**\n * In flight — the request it started has not answered. Inline, the icon turns into\n * IconButton's spinner (`pending`); collapsed, the \"⋯\" button does, since the menu\n * closed when the action was chosen. Either way nothing in the row can be pressed again\n * until it settles: the \"⋯\" opens no menu while busy.\n */\n pending?: boolean;\n /**\n * Not available for a moment, with nothing to explain — another action of the list\n * is in flight (an admin panel's `busy`). The plain `disabled`; a lasting refusal is a\n * `disabledReason`, which says why.\n */\n disabled?: boolean;\n /** Leave it out for this row. */\n hidden?: boolean;\n /**\n * A toggle (0.33): `aria-pressed` and IconButton's \"on\" look inline, `aria-pressed` on\n * the menu entry. Keep the label the same in both states (\"Share budget\"): the pressed\n * state already says whether it is on. Not on an `href` action — a link is not a\n * toggle — nor with `expanded`.\n */\n pressed?: boolean;\n /**\n * Opens or closes something IN THE PAGE (0.33) — a share card under the row, an\n * add-category row: `aria-expanded`, inline and on the menu entry. Not with `pressed`:\n * a disclosure says open, not on. Not on an `href` action.\n */\n expanded?: boolean;\n /** The `id` of what `expanded` opens: `aria-controls`, inline and on the menu entry. */\n controls?: string;\n /**\n * A tour anchor (0.33, §10.18): `data-tour` on ONE element per action at a time.\n * Inline, on the action's own control (the IconButton, the small text button, the\n * link). Collapsed, on an `aria-hidden` box laid over the \"⋯\" button — `[data-tour=x]`\n * resolves to a box the size of the \"⋯\", whose `data-slot=\"row-actions\"` box holds the\n * button, two anchored actions in one row don't fight over one attribute, and a tap\n * still reaches the button. Never on the entry in the open menu: the kit's tour takes\n * the first visible match and the menu is portalled after the row, so a copy there\n * would never be found, only make the selector match twice. The kit's name for it, as\n * `AppShellNavItem.dataTour`.\n */\n dataTour?: string;\n}\n\n/** What a row's actions may be: entries, and `false` / `null` / `undefined` for the ones\n * a condition left out. */\nexport type RowActionList = readonly (RowAction | false | null | undefined)[];\n\nexport interface RowActionsLabels {\n /** The menu button's name when the row has no name of its own; also the actions\n * column's (hidden) header in {@link rowActionsColumn}. */\n actions: string;\n /** The menu button's name, given the row's: \"Actions for Ada Example\". */\n actionsFor: (name: string) => string;\n}\n\n// Defined in ./row-actions-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ROW_ACTIONS_LABELS };\n\n/**\n * - `\"auto\"` (default): the icons inline at Normal; the \"⋯\" menu at Large and Extra\n * large once there are two or more (§10.8). A single action stays inline at every\n * size, with its label as text at Large — one label fits a row, and a menu of one is\n * a detour.\n * - `\"menu\"`: the menu at every size — the roster's choice, whose actions grow (§2.4\n * there) and do not fit a phone card as a strip.\n * - `\"inline\"`: the icons at every size — for a row the app knows has room; at Large\n * each then shows its label (IconButton's `labelVisible`).\n */\nexport type RowActionsCollapse = \"auto\" | \"menu\" | \"inline\";\n\n/** The sizes a row action comes in: IconButton's row steps. `2xs` is DataTable's\n * compact density. At Large every one is at least a 48 px target (IconButton). */\nexport type RowActionsSize = Extract<IconButtonSize, \"md\" | \"sm\" | \"xs\" | \"2xs\">;\n\nexport interface RowActionsProps {\n actions: RowActionList;\n /** The row's name, for the menu button's name — \"Actions for Ada Example\". Without it\n * (or `null`, as API fields often are) the button is \"Actions\". */\n name?: string | null;\n /** When the actions collapse into the menu — see {@link RowActionsCollapse}. */\n collapse?: RowActionsCollapse;\n /** The inline icons' and the \"⋯\" button's size. Default `xs`, a list row's action. */\n size?: RowActionsSize;\n /** The menu's width. Default `\"15rem\"`, in rem so it grows with the words (§3.2). */\n menuWidth?: `${number}rem`;\n /** On the wrapper: the inline strip, or the box round the \"⋯\" button. */\n className?: string;\n /** The inline glyphs' size, IconButton's `glyphSize` — e.g. 14 in a dense header strip\n * (keksdose's category group header). The \"⋯\" button keeps its box's own. */\n glyphSize?: IconButtonGlyphSize;\n /** The inline tooltips' side, IconButton's `tooltipSide` — `\"start\"` for a table's last\n * column, so a bubble does not run off the row's end. Per row: every inline action. */\n tooltipSide?: TooltipSide;\n labels?: LabelOverride<RowActionsLabels>;\n}\n\nfunction visibleActions(actions: RowActionList): RowAction[] {\n return actions.filter((a): a is RowAction => Boolean(a) && !(a as RowAction).hidden);\n}\n\n/** An action's glyph as a node: a component is rendered, an element is used as is. */\nfunction glyphOf(icon: RowAction[\"icon\"]): ReactNode {\n if (icon === undefined) return null;\n if (isValidElement(icon)) return icon;\n const Icon = icon as LucideIcon;\n return <Icon />;\n}\n\n/** The menu glyph's tint per tone (0.33): the words keep the text colour, so the glyph\n * carries `info` and `warning` — IconButton's toned colours. `danger` colours the words\n * through Button's tone, and `muted` is the default look in a menu. */\nconst MENU_GLYPH_TONE: Partial<Record<RowActionTone, string>> = {\n info: \"text-info\",\n warning: \"text-warning\",\n};\n\n/** The state an action's control says, inline and in the menu alike: `aria-expanded` and\n * `aria-controls` for a disclosure (a toggle's `aria-pressed` goes through the button's\n * own `pressed`, which also draws the \"on\" look). */\nfunction stateAttrs(action: RowAction) {\n return {\n \"aria-expanded\": action.expanded,\n \"aria-controls\": action.expanded === undefined ? undefined : action.controls,\n };\n}\n\n/** An action's tone on its inline control, for a caller's CSS and a test: `data-tone`,\n * left off for the default look. */\nfunction toneAttr(tone: RowActionTone | undefined) {\n return { \"data-tone\": tone === undefined || tone === \"default\" ? undefined : tone };\n}\n\n/**\n * A row's actions, inline or as a \"⋯\" menu — see {@link RowActionsCollapse}. Every\n * control stops its click (and the Enter/Space it stands for) from reaching the row, so\n * a clickable row or a DataTable `rowLink` does not open as well; in a DataTable column\n * also set `noRowLink: true`, as {@link rowActionsColumn} does.\n *\n * Inline, the icons sit in a {@link CompactControls} region at Normal: their labels\n * stay the names and the tooltips, and a locked one keeps its reason in the tooltip — a\n * line under one icon of a row would break the row. In the menu a locked action's\n * reason is a line under its words at Large and on touch (Button's\n * `disabledReasonDisplay`).\n *\n * The menu is portalled, so a table's scroller cannot clip it; Escape and an outside\n * press close it, and focus goes back to the \"⋯\" button, which is where a confirmation\n * dialog then returns it.\n */\nexport function RowActions({\n actions,\n name,\n collapse = \"auto\",\n size = \"xs\",\n menuWidth = \"15rem\",\n className,\n glyphSize,\n tooltipSide,\n labels: labelsProp,\n}: RowActionsProps) {\n const labels = useKitLabels(\"rowActions\", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);\n const large = useLargeText();\n const entries = visibleActions(actions);\n if (entries.length === 0) return null;\n const asMenu = collapse === \"menu\" || (collapse === \"auto\" && large && entries.length > 1);\n if (!asMenu) {\n const strip = (\n <div data-slot=\"row-actions\" className={cn(\"inline-flex items-center gap-0.5\", className)}>\n {entries.map((action) => (\n <InlineAction\n key={action.key ?? action.label}\n action={action}\n size={size}\n glyphSize={glyphSize}\n tooltipSide={tooltipSide}\n />\n ))}\n </div>\n );\n // Compact at Normal (no room for a reason line in a row); at Large an inline action\n // is the app's choice (\"inline\") or the row's only one, and shows its words.\n return large ? strip : <CompactControls>{strip}</CompactControls>;\n }\n const title = name ? labels.actionsFor(name) : labels.actions;\n // The menu closed when the action was chosen, so what is in flight shows on \"⋯\".\n const busy = entries.some((action) => action.pending);\n const anchored = entries.filter((action) => action.dataTour);\n return (\n // The \"⋯\" in a box of its own, the inline strip's slot: the tour anchors of the\n // collapsed actions lie over the button in it (see `dataTour`), and `className`\n // places the box as it places the strip.\n <div data-slot=\"row-actions\" className={cn(\"relative inline-flex\", className)}>\n <Popover\n width={menuWidth}\n aria-label={title}\n className=\"p-1\"\n trigger={({ open, toggle, ref }) => (\n <IconButton\n ref={ref}\n size={size}\n label={title}\n // \"⋯\" is read by everyone as \"more actions\"; the menu it opens is the words.\n labelVisible={false}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n pending={busy}\n stopPropagation\n onClick={toggle}\n >\n <MoreHorizontal />\n </IconButton>\n )}\n // React carries an event out of a portal along the COMPONENT tree, so a click in\n // the menu reached a clickable row or a DataTable `onRowClick` the menu was opened\n // from, though the panel is nowhere near it in the page — only the \"⋯\" trigger\n // stopped its own (keksdose's 0.32 report). Stopped on the PANEL itself, its padding\n // and border included (0.32.2: 0.32.1 stopped it on an inner box, and a click on\n // the panel's `p-1` still went through): clicks, and the Enter / Space a row also\n // listens for. Escape goes on, to the popover's own handler that closes it.\n onClick={stopClick}\n onKeyDown={stopActivationKeys}\n >\n {(close) => (\n <ul data-slot=\"row-actions-menu\" className=\"space-y-0.5\">\n {entries.map((action) => (\n <li key={action.key ?? action.label}>\n <MenuAction action={action} close={close} />\n </li>\n ))}\n </ul>\n )}\n </Popover>\n {anchored.map((action) => (\n // The More cell's TourAnchor: decoration over the button, which keeps every tap.\n <span\n key={action.key ?? action.label}\n aria-hidden\n data-tour={action.dataTour}\n className=\"pointer-events-none absolute inset-0\"\n />\n ))}\n </div>\n );\n}\n\nfunction stopClick(e: MouseEvent) {\n e.stopPropagation();\n}\n\nfunction stopActivationKeys(e: KeyboardEvent) {\n if (e.key === \"Enter\" || e.key === \" \") e.stopPropagation();\n}\n\n/**\n * The `href` an action renders as a link with, or `undefined` for a button: a link\n * cannot carry a reason or a spinner (Button's link form has neither), so a refused\n * action — its own `disabledReason`, or a write lock on a `commit` — and a pending one\n * stay buttons and say so.\n */\nfunction useActionHref(action: RowAction): string | undefined {\n const reason = useCommitReason(action.commit, action.disabledReason);\n const refused = reason !== undefined && reason !== null && reason !== false && reason !== \"\";\n return action.href !== undefined && !action.pending && !refused ? action.href : undefined;\n}\n\n/** One entry of the \"⋯\" menu: the words, as a link for an `href`, else a button. */\nfunction MenuAction({ action, close }: { action: RowAction; close: () => void }) {\n const href = useActionHref(action);\n const glyph = glyphOf(action.icon);\n const glyphTone = action.tone === undefined ? undefined : MENU_GLYPH_TONE[action.tone];\n const words = (\n <>\n {glyph && (\n <span\n aria-hidden\n data-tone={glyphTone ? action.tone : undefined}\n className={cn(\"flex shrink-0 [&_svg]:size-4\", glyphTone)}\n >\n {glyph}\n </span>\n )}\n {action.label}\n </>\n );\n const tone = action.tone === \"danger\" ? \"danger\" : undefined;\n const select = () => {\n close();\n action.onSelect?.();\n };\n if (href !== undefined) {\n return (\n <Button\n href={href}\n external={action.external}\n variant=\"ghost\"\n size=\"sm\"\n stretch\n tone={tone}\n disabled={action.disabled}\n className=\"w-full justify-start text-start\"\n onClick={select}\n >\n {words}\n </Button>\n );\n }\n return (\n <Button\n {...stateAttrs(action)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n stretch\n tone={tone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n className=\"w-full justify-start text-start\"\n onClick={select}\n >\n {words}\n </Button>\n );\n}\n\n/** One inline action: an IconButton with its icon, else a small text button — each a\n * link for an `href` (see {@link useActionHref}). The control carries the action's\n * state, its tone and its tour anchor. */\nfunction InlineAction({\n action,\n size,\n glyphSize,\n tooltipSide,\n}: {\n action: RowAction;\n size: RowActionsSize;\n glyphSize?: IconButtonGlyphSize;\n tooltipSide?: TooltipSide;\n}) {\n const href = useActionHref(action);\n const glyph = glyphOf(action.icon);\n const iconTone = action.tone === \"default\" ? undefined : action.tone;\n // A text button has Button's two tones; `info` and `warning` are a glyph's colours.\n const textTone = action.tone === \"danger\" || action.tone === \"muted\" ? action.tone : undefined;\n const marks = { \"data-tour\": action.dataTour, ...toneAttr(action.tone) };\n if (glyph) {\n if (href !== undefined) {\n return (\n <IconButton\n {...marks}\n href={href}\n external={action.external}\n size={size}\n glyphSize={glyphSize}\n label={action.label}\n tooltipSide={tooltipSide}\n tone={iconTone}\n disabled={action.disabled}\n stopPropagation\n onClick={() => action.onSelect?.()}\n >\n {glyph}\n </IconButton>\n );\n }\n return (\n <IconButton\n {...marks}\n {...stateAttrs(action)}\n size={size}\n glyphSize={glyphSize}\n label={action.label}\n tooltipSide={tooltipSide}\n tone={iconTone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n stopPropagation\n onClick={() => action.onSelect?.()}\n >\n {glyph}\n </IconButton>\n );\n }\n // A row listening for clicks must not open as well (IconButton's stopPropagation).\n const stop = {\n onClick: (e: MouseEvent) => {\n e.stopPropagation();\n action.onSelect?.();\n },\n onKeyDown: (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") e.stopPropagation();\n },\n };\n if (href !== undefined) {\n return (\n <Button\n {...marks}\n href={href}\n external={action.external}\n variant=\"ghost\"\n size=\"sm\"\n tone={textTone}\n disabled={action.disabled}\n {...stop}\n >\n {action.label}\n </Button>\n );\n }\n return (\n <Button\n {...marks}\n {...stateAttrs(action)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n tone={textTone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n {...stop}\n >\n {action.label}\n </Button>\n );\n}\n\n/** The actions column's heading, in the provider's words: on screen since 0.46.1 (keksdose\n * dev#598 rework), a screen reader's name only with `hidden`. */\nfunction RowActionsHeader({\n text,\n labels,\n hidden,\n}: {\n text?: string;\n labels?: LabelOverride<RowActionsLabels>;\n hidden?: boolean;\n}) {\n const resolved = useKitLabels(\"rowActions\", DEFAULT_ROW_ACTIONS_LABELS, labels);\n const word = text ?? resolved.actions;\n return hidden ? <span className=\"sr-only\">{word}</span> : <>{word}</>;\n}\n\n/** The actions cell's classes — `rowActionsColumn`'s `className`: the column hugs its\n * buttons, on one line, at the row's end. A hand-built table's `<TableCell>` takes them\n * around its {@link RowActions} (0.47, keksdose N17). */\nexport const ROW_ACTIONS_CELL_CLASS = \"w-px whitespace-nowrap text-end\";\n/** The actions heading's classes — `rowActionsColumn`'s `headClassName`, which\n * {@link RowActionsHead} draws: hugging, end-aligned over the buttons (0.47, N17). */\nexport const ROW_ACTIONS_HEAD_CLASS = \"w-px text-end\";\n\nexport interface RowActionsHeadProps extends Omit<TableHeaderCellProps, \"children\"> {\n /** The heading. Default: the provider's `rowActions.actions` (\"Actions\"). */\n children?: ReactNode;\n /** Named for a screen reader only — `rowActionsColumn`'s `header: false`. */\n hideLabel?: boolean;\n labels?: LabelOverride<RowActionsLabels>;\n}\n\n/**\n * A hand-built table's actions heading (0.47, keksdose N17): the `<th>` `rowActionsColumn`\n * gives a DataTable — \"Actions\" on screen in the provider's words, end-aligned over the\n * buttons, hugging them. Pair it with `<TableCell className={ROW_ACTIONS_CELL_CLASS}>`\n * around each row's {@link RowActions}. Every other `TableHeaderCell` prop passes on\n * (`size`, the app's `className`, merged last).\n *\n * <RowActionsHead size=\"sm\" className={TH} />\n */\nexport function RowActionsHead({ children, hideLabel, labels, className, ...rest }: RowActionsHeadProps) {\n return (\n <TableHeaderCell align=\"end\" {...rest} className={cn(ROW_ACTIONS_HEAD_CLASS, className)}>\n {typeof children === \"string\" || children == null ? (\n <RowActionsHeader text={children ?? undefined} labels={labels} hidden={hideLabel} />\n ) : (\n children\n )}\n </TableHeaderCell>\n );\n}\n\nexport interface RowActionsColumnOptions<T> {\n /** The column's key. Default `\"actions\"`. */\n key?: string;\n /** The row's actions — `false` / `null` for the ones a condition leaves out. */\n actions: (row: T) => RowActionList;\n /** The row's name, for the \"⋯\" button's name (\"Actions for Ada Example\"). `null` and\n * `undefined` both mean \"Actions\". */\n name?: (row: T) => string | null | undefined;\n collapse?: RowActionsCollapse;\n /** Default `xs`. */\n size?: RowActionsSize;\n /** See {@link RowActionsProps.glyphSize}. */\n glyphSize?: IconButtonGlyphSize;\n /** See {@link RowActionsProps.tooltipSide} — `\"start\"` in a table's last column. */\n tooltipSide?: TooltipSide;\n /**\n * The column's name as plain text — the column-settings checklist and the phone\n * card's label read it (`DataTableColumn.headerText`). Pass the app's own word; left\n * out, the heading shows the provider's `rowActions.actions` and the plain-text\n * places fall back to the column's `key`.\n */\n headerText?: string;\n /**\n * The heading (0.46.1, keksdose dev#598 rework — Marcel: a VISIBLE \"Actions\" heading on\n * every table with an actions column). Default: `headerText`, else the provider's\n * `rowActions.actions` (\"Actions\"), shown, end-aligned over the buttons. A node\n * replaces it; `false` keeps the pre-0.46.1 heading, named for a screen reader only.\n */\n header?: ReactNode | false;\n labels?: LabelOverride<RowActionsLabels>;\n /** Anything else for the column (`mobileHidden`, `cellProps`…), merged last. */\n column?: Partial<DataTableColumn<T>>;\n}\n\n/**\n * A DataTable column of {@link RowActions} — an app's action column in one line\n * (§10.8): inline icons at Normal, the \"⋯\" menu at Large with two or more actions.\n *\n * columns={[...columns, rowActionsColumn({ actions: (r) => [...], name: (r) => r.name })]}\n *\n * The heading says \"Actions\" on screen (0.46.1; `header` changes or hides it), the\n * column hugs its content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the\n * buttons keep their own clicks. On a phone it is a line of the card like any column;\n * set `column: { mobileHidden: true }` to drop it there (with `mobileDetailsInRow` it\n * then shows in the opened row instead).\n */\nexport function rowActionsColumn<T>({\n key = \"actions\",\n actions,\n name,\n collapse,\n size,\n glyphSize,\n tooltipSide,\n headerText,\n header,\n labels,\n column,\n}: RowActionsColumnOptions<T>): DataTableColumn<T> {\n return {\n key,\n header:\n header === false || header == null ? (\n <RowActionsHeader text={headerText} labels={labels} hidden={header === false} />\n ) : (\n header\n ),\n headerText,\n cell: (row) => (\n <RowActions\n actions={actions(row)}\n name={name?.(row)}\n collapse={collapse}\n size={size}\n glyphSize={glyphSize}\n tooltipSide={tooltipSide}\n labels={labels}\n />\n ),\n className: ROW_ACTIONS_CELL_CLASS,\n // The heading over the buttons, at the row's end (0.46.1); a hand-built table's\n // `RowActionsHead` (0.47).\n headClassName: ROW_ACTIONS_HEAD_CLASS,\n noRowLink: true,\n ...column,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/row-actions.tsx"],"sourcesContent":["import { isValidElement, useContext } from \"react\";\nimport type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from \"react\";\nimport { MoreHorizontal } from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useLargeText } from \"../hooks/use-large-text\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Popover } from \"./popover\";\nimport { Button } from \"./button\";\nimport { CompactControls } from \"./field-parts\";\nimport { IconButton } from \"./icon-button\";\nimport { useCommitReason, type CommitScope } from \"./write-lock\";\nimport type { IconButtonGlyphSize, IconButtonSize } from \"./icon-button\";\nimport type { TooltipSide } from \"./tooltip\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { TableHeaderCell, type TableHeaderCellProps } from \"./table\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"./row-actions-labels\";\nimport { ConfirmContext } from \"./confirm-context\";\nimport { ConfirmProvider } from \"./confirm-dialog\";\n\n/**\n * A row's actions — inline icons at Normal, one \"⋯\" menu at Large and above\n * (docs/text-size-harmonization.md §10.8).\n *\n * At Large an IconButton shows its label as text (§4). In a dense row — a register's\n * action column, a list row's end — every label at once does not fit: Kurvenschmiede\n * has 44 IconButtons in rows, kastlan 119. So a row's actions collapse instead, into\n * the menu the user roster has used since 0.31 (`UserRowActions`, now built on this):\n * one \"⋯\" button, and a menu with each action's icon AND words, where a locked one stays\n * listed and says why.\n *\n * The data is the roster's `UserRowActionList`, generalised: entries, and `false` /\n * `null` for the ones a condition left out (`row.locked || {…}`).\n *\n * Destructive actions belong in the menu (0.49 KIT-12): a `tone: \"danger\"` action goes\n * into the \"⋯\" menu while the others stay inline (`dangerPlacement`), and choosing it\n * opens a `ConfirmDialog` before `onSelect` runs (`confirm`; `false` to opt out). A\n * delete is then never a bin icon in every row, one stray tap from gone.\n *\n * 0.33 (§10.16): an action carries its state at both sizes — a toggle (`pressed`), a\n * disclosure (`expanded`, `controls`), a tone, a tour anchor (`dataTour`) — so a row\n * with an \"on\" share button or a tour step no longer keeps hand-rolled IconButtons at\n * Normal and this only at Large (keksdose k15).\n */\n\n/** An action's colour: IconButton's tone inline; in the menu the glyph takes it and the\n * words keep the text colour, except `danger`, whose words are the danger text. */\nexport type RowActionTone = \"default\" | \"muted\" | \"info\" | \"warning\" | \"danger\";\n\n/** One action of a row. */\nexport interface RowAction {\n /** React key. Default: the label. */\n key?: string;\n /** The action's name: the inline icon's name and tooltip, the menu entry's words. */\n label: string;\n /**\n * Runs it. The kit sends nothing — it calls this, after a `tone: \"danger\"` action's\n * confirmation (see {@link confirm}). Optional only beside `href`; an action has one or\n * the other (or both: `onSelect` then runs as the link is followed).\n */\n onSelect?: () => void;\n /**\n * 0.49 KIT-12: ask before `onSelect`. A `tone: \"danger\"` action asks by default — a\n * `ConfirmDialog` (the provider's `useConfirm`, else one of its own) titled with the\n * action and the row's name, \"Delete “Ada Example”?\", the consequence under it\n * (`rowActions.confirmBody`), the action's label on the danger button and focus on\n * Cancel. Pass the words to say more, or `false` when the app confirms in `onSelect`\n * itself or offers an undo instead. Any other tone asks only when given one. Not for\n * an `href` action: a link only navigates.\n */\n confirm?: boolean | RowActionConfirm;\n /**\n * The action NAVIGATES — an edit page, a mail to compose (0.32.1, kastlan's 0.32\n * report): a real link inline and in the menu, so open in a new tab, a middle click\n * and the URL on hover work, which `onSelect` + `navigate` lost. The kit's link rule\n * applies ({@link Button}'s `href`): in-app through the provider's `linkComponent`, an\n * external or `#anchor` one a plain `<a>`, `mailto:` included.\n *\n * A link cannot carry a reason or a spinner, so an action with a `disabledReason`\n * (or under a write lock, with `commit`) or `pending` stays a button and says why.\n */\n href?: string;\n /** With `href`: leaves the app — a new tab, and a screen reader is told so. */\n external?: boolean;\n /**\n * The glyph: a Lucide icon (`Pencil`), or an element (`<Pencil />`, an app's own\n * svg) — DataTable's `rowActions` hand their `icon` node through. Inline, an action\n * without one is a small text button; in the menu it is the words alone.\n */\n icon?: LucideIcon | ReactElement;\n /**\n * `danger` for delete, revoke, erase: the quiet red of IconButton's `tone=\"danger\"`\n * inline, the danger text tone in the menu. `muted` (an open or add that should not\n * compete), `info` (a notice-worthy, harmless action: keksdose's reconcile) and\n * `warning` (a flag that wants attention): IconButton's tone inline; in the menu the\n * glyph takes it and the words keep the text colour, so `muted` looks like `default`\n * there (0.33).\n */\n tone?: RowActionTone;\n /** Why it is not available for this row. It stays listed, focusable, and says so — an\n * action that silently vanished would leave the reader looking for it. */\n disabledReason?: ReactNode;\n /**\n * This action COMMITS — it saves, sends, deletes: under a locked\n * {@link WriteLockProvider} it is refused with the lock's reason, as {@link Button}'s\n * `commit` is, inline and in the menu alike. No provider, or an unlocked one: no effect.\n */\n commit?: CommitScope;\n /**\n * In flight — the request it started has not answered. Inline, the icon turns into\n * IconButton's spinner (`pending`); collapsed, the \"⋯\" button does, since the menu\n * closed when the action was chosen. Either way nothing in the row can be pressed again\n * until it settles: the \"⋯\" opens no menu while busy.\n */\n pending?: boolean;\n /**\n * Not available for a moment, with nothing to explain — another action of the list\n * is in flight (an admin panel's `busy`). The plain `disabled`; a lasting refusal is a\n * `disabledReason`, which says why.\n */\n disabled?: boolean;\n /** Leave it out for this row. */\n hidden?: boolean;\n /**\n * A toggle (0.33): `aria-pressed` and IconButton's \"on\" look inline, `aria-pressed` on\n * the menu entry. Keep the label the same in both states (\"Share budget\"): the pressed\n * state already says whether it is on. Not on an `href` action — a link is not a\n * toggle — nor with `expanded`.\n */\n pressed?: boolean;\n /**\n * Opens or closes something IN THE PAGE (0.33) — a share card under the row, an\n * add-category row: `aria-expanded`, inline and on the menu entry. Not with `pressed`:\n * a disclosure says open, not on. Not on an `href` action.\n */\n expanded?: boolean;\n /** The `id` of what `expanded` opens: `aria-controls`, inline and on the menu entry. */\n controls?: string;\n /**\n * A tour anchor (0.33, §10.18): `data-tour` on ONE element per action at a time.\n * Inline, on the action's own control (the IconButton, the small text button, the\n * link). Collapsed, on an `aria-hidden` box laid over the \"⋯\" button — `[data-tour=x]`\n * resolves to a box the size of the \"⋯\", whose `data-slot=\"row-actions\"` box holds the\n * button, two anchored actions in one row don't fight over one attribute, and a tap\n * still reaches the button. Never on the entry in the open menu: the kit's tour takes\n * the first visible match and the menu is portalled after the row, so a copy there\n * would never be found, only make the selector match twice. The kit's name for it, as\n * `AppShellNavItem.dataTour`.\n */\n dataTour?: string;\n}\n\n/** The words of a {@link RowAction.confirm}; each defaults as described there. */\nexport interface RowActionConfirm {\n title?: ReactNode;\n body?: ReactNode;\n confirmLabel?: ReactNode;\n}\n\n/** What a row's actions may be: entries, and `false` / `null` / `undefined` for the ones\n * a condition left out. */\nexport type RowActionList = readonly (RowAction | false | null | undefined)[];\n\nexport interface RowActionsLabels {\n /** The menu button's name when the row has no name of its own; also the actions\n * column's (hidden) header in {@link rowActionsColumn}. */\n actions: string;\n /** The menu button's name, given the row's: \"Actions for Ada Example\". */\n actionsFor: (name: string) => string;\n /** 0.49 KIT-12: a danger action's confirmation title, given the action's label and the\n * row's name when it has one: \"Delete “Ada Example”?\", \"Delete?\". */\n confirmTitle: (action: string, name?: string) => string;\n /** 0.49 KIT-12: the line under it — \"This cannot be undone.\" */\n confirmBody: string;\n}\n\n// Defined in ./row-actions-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ROW_ACTIONS_LABELS };\n\n/**\n * - `\"auto\"` (default): the icons inline at Normal; the \"⋯\" menu at Large and Extra\n * large once there are two or more (§10.8). A single action stays inline at every\n * size, with its label as text at Large — one label fits a row, and a menu of one is\n * a detour.\n * - `\"menu\"`: the menu at every size — the roster's choice, whose actions grow (§2.4\n * there) and do not fit a phone card as a strip.\n * - `\"inline\"`: the icons at every size — for a row the app knows has room; at Large\n * each then shows its label (IconButton's `labelVisible`).\n */\nexport type RowActionsCollapse = \"auto\" | \"menu\" | \"inline\";\n\n/** The sizes a row action comes in: IconButton's row steps. `2xs` is DataTable's\n * compact density. At Large every one is at least a 48 px target (IconButton). */\nexport type RowActionsSize = Extract<IconButtonSize, \"md\" | \"sm\" | \"xs\" | \"2xs\">;\n\nexport interface RowActionsProps {\n actions: RowActionList;\n /**\n * 0.49 KIT-12: where `tone: \"danger\"` actions go while the others are inline. `\"menu\"`\n * (default): into the row's \"⋯\" menu, so a delete is never one stray tap in every\n * row — the other actions stay icons beside it. `\"inline\"`: an icon like the rest.\n * `collapse=\"inline\"` keeps every action inline, `collapse=\"menu\"` every one in the menu.\n */\n dangerPlacement?: \"menu\" | \"inline\";\n /** The row's name, for the menu button's name — \"Actions for Ada Example\". Without it\n * (or `null`, as API fields often are) the button is \"Actions\". */\n name?: string | null;\n /** When the actions collapse into the menu — see {@link RowActionsCollapse}. */\n collapse?: RowActionsCollapse;\n /** The inline icons' and the \"⋯\" button's size. Default `xs`, a list row's action. */\n size?: RowActionsSize;\n /** The menu's width. Default `\"15rem\"`, in rem so it grows with the words (§3.2). */\n menuWidth?: `${number}rem`;\n /** On the wrapper: the inline strip, or the box round the \"⋯\" button. */\n className?: string;\n /** The inline glyphs' size, IconButton's `glyphSize` — e.g. 14 in a dense header strip\n * (keksdose's category group header). The \"⋯\" button keeps its box's own. */\n glyphSize?: IconButtonGlyphSize;\n /** The inline tooltips' side, IconButton's `tooltipSide` — `\"start\"` for a table's last\n * column, so a bubble does not run off the row's end. Per row: every inline action. */\n tooltipSide?: TooltipSide;\n labels?: LabelOverride<RowActionsLabels>;\n}\n\nfunction visibleActions(actions: RowActionList): RowAction[] {\n return actions.filter((a): a is RowAction => Boolean(a) && !(a as RowAction).hidden);\n}\n\n/** An action's glyph as a node: a component is rendered, an element is used as is. */\nfunction glyphOf(icon: RowAction[\"icon\"]): ReactNode {\n if (icon === undefined) return null;\n if (isValidElement(icon)) return icon;\n const Icon = icon as LucideIcon;\n return <Icon />;\n}\n\n/** The menu glyph's tint per tone (0.33): the words keep the text colour, so the glyph\n * carries `info` and `warning` — IconButton's toned colours. `danger` colours the words\n * through Button's tone, and `muted` is the default look in a menu. */\nconst MENU_GLYPH_TONE: Partial<Record<RowActionTone, string>> = {\n info: \"text-info\",\n warning: \"text-warning\",\n};\n\n/** The state an action's control says, inline and in the menu alike: `aria-expanded` and\n * `aria-controls` for a disclosure (a toggle's `aria-pressed` goes through the button's\n * own `pressed`, which also draws the \"on\" look). */\nfunction stateAttrs(action: RowAction) {\n return {\n \"aria-expanded\": action.expanded,\n \"aria-controls\": action.expanded === undefined ? undefined : action.controls,\n };\n}\n\n/** An action's tone on its inline control, for a caller's CSS and a test: `data-tone`,\n * left off for the default look. */\nfunction toneAttr(tone: RowActionTone | undefined) {\n return { \"data-tone\": tone === undefined || tone === \"default\" ? undefined : tone };\n}\n\n/**\n * A row's actions, inline or as a \"⋯\" menu — see {@link RowActionsCollapse}. Every\n * control stops its click (and the Enter/Space it stands for) from reaching the row, so\n * a clickable row or a DataTable `rowLink` does not open as well; in a DataTable column\n * also set `noRowLink: true` and `role: \"actions\"`, as {@link rowActionsColumn} does.\n *\n * Inline, the icons sit in a {@link CompactControls} region at Normal: their labels\n * stay the names and the tooltips, and a locked one keeps its reason in the tooltip — a\n * line under one icon of a row would break the row. In the menu a locked action's\n * reason is a line under its words at Large and on touch (Button's\n * `disabledReasonDisplay`).\n *\n * The menu is portalled, so a table's scroller cannot clip it; Escape and an outside\n * press close it, and focus goes back to the \"⋯\" button, which is where a confirmation\n * dialog then returns it.\n */\nexport function RowActions(props: RowActionsProps) {\n const outer = useContext(ConfirmContext);\n // A danger action asks first (0.49 KIT-12). Under the app's ConfirmProvider through\n // it; without one, through a provider of this row's own — only when there is\n // something to ask, so a plain row adds nothing.\n if (outer === null && visibleActions(props.actions).some(asksFirst)) {\n return (\n <ConfirmProvider>\n <RowActionsBody {...props} />\n </ConfirmProvider>\n );\n }\n return <RowActionsBody {...props} />;\n}\n\n/** Whether choosing `action` opens a confirmation first: a danger action unless it says\n * `confirm: false`, any other when it passes one; never a link. */\nfunction asksFirst(action: RowAction): boolean {\n if (action.href !== undefined || action.confirm === false) return false;\n return action.confirm !== undefined || action.tone === \"danger\";\n}\n\n/** `action.onSelect`, after its confirmation when it {@link asksFirst}. */\nfunction useRunAction(name: string | null | undefined, labels: RowActionsLabels) {\n const confirm = useContext(ConfirmContext);\n return (action: RowAction) => {\n if (!asksFirst(action) || confirm === null) {\n action.onSelect?.();\n return;\n }\n const words = typeof action.confirm === \"object\" ? action.confirm : {};\n void confirm({\n title: words.title ?? labels.confirmTitle(action.label, name ?? undefined),\n body: words.body ?? labels.confirmBody,\n confirmLabel: words.confirmLabel ?? action.label,\n tone: \"danger\",\n commit: action.commit,\n }).then((ok) => {\n if (ok) action.onSelect?.();\n });\n };\n}\n\nfunction RowActionsBody({\n actions,\n name,\n collapse = \"auto\",\n dangerPlacement = \"menu\",\n size = \"xs\",\n menuWidth = \"15rem\",\n className,\n glyphSize,\n tooltipSide,\n labels: labelsProp,\n}: RowActionsProps) {\n const labels = useKitLabels(\"rowActions\", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);\n const large = useLargeText();\n const run = useRunAction(name, labels);\n const entries = visibleActions(actions);\n if (entries.length === 0) return null;\n const asMenu = collapse === \"menu\" || (collapse === \"auto\" && large && entries.length > 1);\n // Inline, a danger action still goes into the \"⋯\" menu (KIT-12) unless the app keeps it\n // inline: the other actions stay icons, the menu holds the destructive ones.\n const toMenu = (action: RowAction) =>\n asMenu || (collapse === \"auto\" && dangerPlacement === \"menu\" && action.tone === \"danger\");\n const inline = entries.filter((action) => !toMenu(action));\n const menu = entries.filter(toMenu);\n const title = name ? labels.actionsFor(name) : labels.actions;\n const menuButton = menu.length > 0 && (\n <RowActionsMenu\n entries={menu}\n title={title}\n size={size}\n menuWidth={menuWidth}\n run={run}\n className={inline.length === 0 ? className : undefined}\n />\n );\n if (inline.length === 0) return menuButton || null;\n const strip = (\n <div data-slot=\"row-actions\" className={cn(\"inline-flex items-center gap-0.5\", className)}>\n {inline.map((action) => (\n <InlineAction\n key={action.key ?? action.label}\n action={action}\n size={size}\n glyphSize={glyphSize}\n tooltipSide={tooltipSide}\n run={run}\n />\n ))}\n {menuButton}\n </div>\n );\n // Compact at Normal (no room for a reason line in a row); at Large an inline action\n // is the app's choice (\"inline\") or the row's only one, and shows its words.\n return large ? strip : <CompactControls>{strip}</CompactControls>;\n}\n\n/** The \"⋯\" button and its menu of `entries`. */\nfunction RowActionsMenu({\n entries,\n title,\n size,\n menuWidth,\n run,\n className,\n}: {\n entries: RowAction[];\n title: string;\n size: RowActionsSize;\n menuWidth: `${number}rem`;\n run: (action: RowAction) => void;\n className?: string;\n}) {\n // The menu closed when the action was chosen, so what is in flight shows on \"⋯\".\n const busy = entries.some((action) => action.pending);\n const anchored = entries.filter((action) => action.dataTour);\n return (\n // The \"⋯\" in a box of its own, the inline strip's slot: the tour anchors of the\n // collapsed actions lie over the button in it (see `dataTour`), and `className`\n // places the box as it places the strip.\n <div data-slot=\"row-actions\" className={cn(\"relative inline-flex\", className)}>\n <Popover\n width={menuWidth}\n aria-label={title}\n className=\"p-1\"\n trigger={({ open, toggle, ref }) => (\n <IconButton\n ref={ref}\n size={size}\n label={title}\n // \"⋯\" is read by everyone as \"more actions\"; the menu it opens is the words.\n labelVisible={false}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n pending={busy}\n stopPropagation\n onClick={toggle}\n >\n <MoreHorizontal />\n </IconButton>\n )}\n // React carries an event out of a portal along the COMPONENT tree, so a click in\n // the menu reached a clickable row or a DataTable `onRowClick` the menu was opened\n // from, though the panel is nowhere near it in the page — only the \"⋯\" trigger\n // stopped its own (keksdose's 0.32 report). Stopped on the PANEL itself, its padding\n // and border included (0.32.2: 0.32.1 stopped it on an inner box, and a click on\n // the panel's `p-1` still went through): clicks, and the Enter / Space a row also\n // listens for. Escape goes on, to the popover's own handler that closes it.\n onClick={stopClick}\n onKeyDown={stopActivationKeys}\n >\n {(close) => (\n <ul data-slot=\"row-actions-menu\" className=\"space-y-0.5\">\n {entries.map((action) => (\n <li key={action.key ?? action.label}>\n <MenuAction action={action} close={close} run={run} />\n </li>\n ))}\n </ul>\n )}\n </Popover>\n {anchored.map((action) => (\n // The More cell's TourAnchor: decoration over the button, which keeps every tap.\n <span\n key={action.key ?? action.label}\n aria-hidden\n data-tour={action.dataTour}\n className=\"pointer-events-none absolute inset-0\"\n />\n ))}\n </div>\n );\n}\n\nfunction stopClick(e: MouseEvent) {\n e.stopPropagation();\n}\n\nfunction stopActivationKeys(e: KeyboardEvent) {\n if (e.key === \"Enter\" || e.key === \" \") e.stopPropagation();\n}\n\n/**\n * The `href` an action renders as a link with, or `undefined` for a button: a link\n * cannot carry a reason or a spinner (Button's link form has neither), so a refused\n * action — its own `disabledReason`, or a write lock on a `commit` — and a pending one\n * stay buttons and say so.\n */\nfunction useActionHref(action: RowAction): string | undefined {\n const reason = useCommitReason(action.commit, action.disabledReason);\n const refused = reason !== undefined && reason !== null && reason !== false && reason !== \"\";\n return action.href !== undefined && !action.pending && !refused ? action.href : undefined;\n}\n\n/** One entry of the \"⋯\" menu: the words, as a link for an `href`, else a button. */\nfunction MenuAction({ action, close, run }: { action: RowAction; close: () => void; run: (action: RowAction) => void }) {\n const href = useActionHref(action);\n const glyph = glyphOf(action.icon);\n const glyphTone = action.tone === undefined ? undefined : MENU_GLYPH_TONE[action.tone];\n const words = (\n <>\n {glyph && (\n <span\n aria-hidden\n data-tone={glyphTone ? action.tone : undefined}\n className={cn(\"flex shrink-0 [&_svg]:size-4\", glyphTone)}\n >\n {glyph}\n </span>\n )}\n {action.label}\n </>\n );\n const tone = action.tone === \"danger\" ? \"danger\" : undefined;\n const select = () => {\n close();\n run(action);\n };\n if (href !== undefined) {\n return (\n <Button\n href={href}\n external={action.external}\n variant=\"ghost\"\n size=\"sm\"\n stretch\n tone={tone}\n disabled={action.disabled}\n className=\"w-full justify-start text-start\"\n onClick={select}\n >\n {words}\n </Button>\n );\n }\n return (\n <Button\n {...stateAttrs(action)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n stretch\n tone={tone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n className=\"w-full justify-start text-start\"\n onClick={select}\n >\n {words}\n </Button>\n );\n}\n\n/** One inline action: an IconButton with its icon, else a small text button — each a\n * link for an `href` (see {@link useActionHref}). The control carries the action's\n * state, its tone and its tour anchor. */\nfunction InlineAction({\n action,\n size,\n glyphSize,\n tooltipSide,\n run,\n}: {\n action: RowAction;\n size: RowActionsSize;\n glyphSize?: IconButtonGlyphSize;\n tooltipSide?: TooltipSide;\n run: (action: RowAction) => void;\n}) {\n const href = useActionHref(action);\n const glyph = glyphOf(action.icon);\n const iconTone = action.tone === \"default\" ? undefined : action.tone;\n // A text button has Button's two tones; `info` and `warning` are a glyph's colours.\n const textTone = action.tone === \"danger\" || action.tone === \"muted\" ? action.tone : undefined;\n const marks = { \"data-tour\": action.dataTour, ...toneAttr(action.tone) };\n if (glyph) {\n if (href !== undefined) {\n return (\n <IconButton\n {...marks}\n href={href}\n external={action.external}\n size={size}\n glyphSize={glyphSize}\n label={action.label}\n tooltipSide={tooltipSide}\n tone={iconTone}\n disabled={action.disabled}\n stopPropagation\n onClick={() => action.onSelect?.()}\n >\n {glyph}\n </IconButton>\n );\n }\n return (\n <IconButton\n {...marks}\n {...stateAttrs(action)}\n size={size}\n glyphSize={glyphSize}\n label={action.label}\n tooltipSide={tooltipSide}\n tone={iconTone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n stopPropagation\n onClick={() => run(action)}\n >\n {glyph}\n </IconButton>\n );\n }\n // A row listening for clicks must not open as well (IconButton's stopPropagation).\n const stop = {\n onClick: (e: MouseEvent) => {\n e.stopPropagation();\n run(action);\n },\n onKeyDown: (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") e.stopPropagation();\n },\n };\n if (href !== undefined) {\n return (\n <Button\n {...marks}\n href={href}\n external={action.external}\n variant=\"ghost\"\n size=\"sm\"\n tone={textTone}\n disabled={action.disabled}\n {...stop}\n >\n {action.label}\n </Button>\n );\n }\n return (\n <Button\n {...marks}\n {...stateAttrs(action)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n tone={textTone}\n pressed={action.pressed}\n disabledReason={action.disabledReason}\n commit={action.commit}\n pending={action.pending}\n disabled={action.disabled}\n {...stop}\n >\n {action.label}\n </Button>\n );\n}\n\n/** The actions column's heading, in the provider's words: on screen since 0.46.1 (keksdose\n * dev#598 rework), a screen reader's name only with `hidden`. */\nfunction RowActionsHeader({\n text,\n labels,\n hidden,\n}: {\n text?: string;\n labels?: LabelOverride<RowActionsLabels>;\n hidden?: boolean;\n}) {\n const resolved = useKitLabels(\"rowActions\", DEFAULT_ROW_ACTIONS_LABELS, labels);\n const word = text ?? resolved.actions;\n return hidden ? <span className=\"sr-only\">{word}</span> : <>{word}</>;\n}\n\n/** The actions cell's classes — `rowActionsColumn`'s `className`: the column hugs its\n * buttons, on one line, at the row's end. A hand-built table's `<TableCell>` takes them\n * around its {@link RowActions} (0.47, keksdose N17). */\nexport const ROW_ACTIONS_CELL_CLASS = \"w-px whitespace-nowrap text-end\";\n/** The actions heading's classes — `rowActionsColumn`'s `headClassName`, which\n * {@link RowActionsHead} draws: hugging, end-aligned over the buttons (0.47, N17). */\nexport const ROW_ACTIONS_HEAD_CLASS = \"w-px text-end\";\n\nexport interface RowActionsHeadProps extends Omit<TableHeaderCellProps, \"children\"> {\n /** The heading. Default: the provider's `rowActions.actions` (\"Actions\"). */\n children?: ReactNode;\n /** Named for a screen reader only — `rowActionsColumn`'s `header: false`. */\n hideLabel?: boolean;\n labels?: LabelOverride<RowActionsLabels>;\n}\n\n/**\n * A hand-built table's actions heading (0.47, keksdose N17): the `<th>` `rowActionsColumn`\n * gives a DataTable — \"Actions\" on screen in the provider's words, end-aligned over the\n * buttons, hugging them. Pair it with `<TableCell className={ROW_ACTIONS_CELL_CLASS}>`\n * around each row's {@link RowActions}. Every other `TableHeaderCell` prop passes on\n * (`size`, the app's `className`, merged last).\n *\n * <RowActionsHead size=\"sm\" className={TH} />\n */\nexport function RowActionsHead({ children, hideLabel, labels, className, ...rest }: RowActionsHeadProps) {\n return (\n <TableHeaderCell align=\"end\" {...rest} className={cn(ROW_ACTIONS_HEAD_CLASS, className)}>\n {typeof children === \"string\" || children == null ? (\n <RowActionsHeader text={children ?? undefined} labels={labels} hidden={hideLabel} />\n ) : (\n children\n )}\n </TableHeaderCell>\n );\n}\n\nexport interface RowActionsColumnOptions<T> {\n /** The column's key. Default `\"actions\"`. */\n key?: string;\n /** The row's actions — `false` / `null` for the ones a condition leaves out. */\n actions: (row: T) => RowActionList;\n /** The row's name, for the \"⋯\" button's name (\"Actions for Ada Example\"). `null` and\n * `undefined` both mean \"Actions\". */\n name?: (row: T) => string | null | undefined;\n collapse?: RowActionsCollapse;\n /** Default `xs`. */\n size?: RowActionsSize;\n /** See {@link RowActionsProps.glyphSize}. */\n glyphSize?: IconButtonGlyphSize;\n /** See {@link RowActionsProps.tooltipSide} — `\"start\"` in a table's last column. */\n tooltipSide?: TooltipSide;\n /**\n * The column's name as plain text — the column-settings checklist and the phone\n * card's label read it (`DataTableColumn.headerText`). Pass the app's own word; left\n * out, the heading shows the provider's `rowActions.actions` and the plain-text\n * places fall back to the column's `key`.\n */\n headerText?: string;\n /**\n * The heading (0.46.1, keksdose dev#598 rework — Marcel: a VISIBLE \"Actions\" heading on\n * every table with an actions column). Default: `headerText`, else the provider's\n * `rowActions.actions` (\"Actions\"), shown, end-aligned over the buttons. A node\n * replaces it; `false` keeps the pre-0.46.1 heading, named for a screen reader only.\n */\n header?: ReactNode | false;\n labels?: LabelOverride<RowActionsLabels>;\n /** Anything else for the column (`mobileHidden`, `cellProps`, another `role`…), merged last. */\n column?: Partial<DataTableColumn<T>>;\n}\n\n/**\n * A DataTable column of {@link RowActions} — an app's action column in one line\n * (§10.8): inline icons at Normal, the \"⋯\" menu at Large with two or more actions.\n *\n * columns={[...columns, rowActionsColumn({ actions: (r) => [...], name: (r) => r.name })]}\n *\n * The heading says \"Actions\" on screen (0.46.1; `header` changes or hides it), the\n * column hugs its content at the row's end, and `noRowLink` keeps a `rowHref` anchor off it, so the\n * buttons keep their own clicks. It is `role: \"actions\"` (0.49 KIT-7): DataTable's default\n * phone card draws it after its three fields, at the card's end, and never counts it as\n * one of them. Set `column: { mobileHidden: true }` to drop it there (with\n * `mobileDetailsInRow` it then shows in the opened row instead).\n */\nexport function rowActionsColumn<T>({\n key = \"actions\",\n actions,\n name,\n collapse,\n size,\n glyphSize,\n tooltipSide,\n headerText,\n header,\n labels,\n column,\n}: RowActionsColumnOptions<T>): DataTableColumn<T> {\n return {\n key,\n header:\n header === false || header == null ? (\n <RowActionsHeader text={headerText} labels={labels} hidden={header === false} />\n ) : (\n header\n ),\n headerText,\n cell: (row) => (\n <RowActions\n actions={actions(row)}\n name={name?.(row)}\n collapse={collapse}\n size={size}\n glyphSize={glyphSize}\n tooltipSide={tooltipSide}\n labels={labels}\n />\n ),\n className: ROW_ACTIONS_CELL_CLASS,\n // The heading over the buttons, at the row's end (0.46.1); a hand-built table's\n // `RowActionsHead` (0.47).\n headClassName: ROW_ACTIONS_HEAD_CLASS,\n noRowLink: true,\n // After the phone card's fields, never one of them (0.49 KIT-7).\n role: \"actions\",\n ...column,\n };\n}\n"],"mappings":";AAyOS,SAqPL,UArPK,KA2HL,YA3HK;AAzOT,SAAS,gBAAgB,kBAAkB;AAE3C,SAAS,sBAAsB;AAE/B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,oBAAwC;AACjD,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,SAAS,uBAAyC;AAIlD,SAAS,uBAAkD;AAC3D,SAAS,kCAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AA8MhC,SAAS,eAAe,SAAqC;AAC3D,SAAO,QAAQ,OAAO,CAAC,MAAsB,QAAQ,CAAC,KAAK,CAAE,EAAgB,MAAM;AACrF;AAGA,SAAS,QAAQ,MAAoC;AACnD,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK;AACf;AAKA,MAAM,kBAA0D;AAAA,EAC9D,MAAM;AAAA,EACN,SAAS;AACX;AAKA,SAAS,WAAW,QAAmB;AACrC,SAAO;AAAA,IACL,iBAAiB,OAAO;AAAA,IACxB,iBAAiB,OAAO,aAAa,SAAY,SAAY,OAAO;AAAA,EACtE;AACF;AAIA,SAAS,SAAS,MAAiC;AACjD,SAAO,EAAE,aAAa,SAAS,UAAa,SAAS,YAAY,SAAY,KAAK;AACpF;AAkBO,SAAS,WAAW,OAAwB;AACjD,QAAM,QAAQ,WAAW,cAAc;AAIvC,MAAI,UAAU,QAAQ,eAAe,MAAM,OAAO,EAAE,KAAK,SAAS,GAAG;AACnE,WACE,oBAAC,mBACC,8BAAC,kBAAgB,GAAG,OAAO,GAC7B;AAAA,EAEJ;AACA,SAAO,oBAAC,kBAAgB,GAAG,OAAO;AACpC;AAIA,SAAS,UAAU,QAA4B;AAC7C,MAAI,OAAO,SAAS,UAAa,OAAO,YAAY,MAAO,QAAO;AAClE,SAAO,OAAO,YAAY,UAAa,OAAO,SAAS;AACzD;AAGA,SAAS,aAAa,MAAiC,QAA0B;AAC/E,QAAM,UAAU,WAAW,cAAc;AACzC,SAAO,CAAC,WAAsB;AAC5B,QAAI,CAAC,UAAU,MAAM,KAAK,YAAY,MAAM;AAC1C,aAAO,WAAW;AAClB;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,CAAC;AACrE,SAAK,QAAQ;AAAA,MACX,OAAO,MAAM,SAAS,OAAO,aAAa,OAAO,OAAO,QAAQ,MAAS;AAAA,MACzE,MAAM,MAAM,QAAQ,OAAO;AAAA,MAC3B,cAAc,MAAM,gBAAgB,OAAO;AAAA,MAC3C,MAAM;AAAA,MACN,QAAQ,OAAO;AAAA,IACjB,CAAC,EAAE,KAAK,CAAC,OAAO;AACd,UAAI,GAAI,QAAO,WAAW;AAAA,IAC5B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAoB;AAClB,QAAM,SAAS,aAAa,cAAc,4BAA4B,UAAU;AAChF,QAAM,QAAQ,aAAa;AAC3B,QAAM,MAAM,aAAa,MAAM,MAAM;AACrC,QAAM,UAAU,eAAe,OAAO;AACtC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,SAAS,aAAa,UAAW,aAAa,UAAU,SAAS,QAAQ,SAAS;AAGxF,QAAM,SAAS,CAAC,WACd,UAAW,aAAa,UAAU,oBAAoB,UAAU,OAAO,SAAS;AAClF,QAAM,SAAS,QAAQ,OAAO,CAAC,WAAW,CAAC,OAAO,MAAM,CAAC;AACzD,QAAM,OAAO,QAAQ,OAAO,MAAM;AAClC,QAAM,QAAQ,OAAO,OAAO,WAAW,IAAI,IAAI,OAAO;AACtD,QAAM,aAAa,KAAK,SAAS,KAC/B;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,OAAO,WAAW,IAAI,YAAY;AAAA;AAAA,EAC/C;AAEF,MAAI,OAAO,WAAW,EAAG,QAAO,cAAc;AAC9C,QAAM,QACJ,qBAAC,SAAI,aAAU,eAAc,WAAW,GAAG,oCAAoC,SAAS,GACrF;AAAA,WAAO,IAAI,CAAC,WACX;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,MALK,OAAO,OAAO,OAAO;AAAA,IAM5B,CACD;AAAA,IACA;AAAA,KACH;AAIF,SAAO,QAAQ,QAAQ,oBAAC,mBAAiB,iBAAM;AACjD;AAGA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AAED,QAAM,OAAO,QAAQ,KAAK,CAAC,WAAW,OAAO,OAAO;AACpD,QAAM,WAAW,QAAQ,OAAO,CAAC,WAAW,OAAO,QAAQ;AAC3D;AAAA;AAAA;AAAA;AAAA,IAIE,qBAAC,SAAI,aAAU,eAAc,WAAW,GAAG,wBAAwB,SAAS,GAC1E;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,cAAY;AAAA,UACZ,WAAU;AAAA,UACV,SAAS,CAAC,EAAE,MAAM,QAAQ,IAAI,MAC5B;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA,OAAO;AAAA,cAEP,cAAc;AAAA,cACd,iBAAc;AAAA,cACd,iBAAe;AAAA,cACf,SAAS;AAAA,cACT,iBAAe;AAAA,cACf,SAAS;AAAA,cAET,8BAAC,kBAAe;AAAA;AAAA,UAClB;AAAA,UASF,SAAS;AAAA,UACT,WAAW;AAAA,UAEV,WAAC,UACA,oBAAC,QAAG,aAAU,oBAAmB,WAAU,eACxC,kBAAQ,IAAI,CAAC,WACZ,oBAAC,QACC,8BAAC,cAAW,QAAgB,OAAc,KAAU,KAD7C,OAAO,OAAO,OAAO,KAE9B,CACD,GACH;AAAA;AAAA,MAEJ;AAAA,MACC,SAAS,IAAI,CAAC;AAAA;AAAA,QAEb;AAAA,UAAC;AAAA;AAAA,YAEC,eAAW;AAAA,YACX,aAAW,OAAO;AAAA,YAClB,WAAU;AAAA;AAAA,UAHL,OAAO,OAAO,OAAO;AAAA,QAI5B;AAAA,OACD;AAAA,OACH;AAAA;AAEJ;AAEA,SAAS,UAAU,GAAe;AAChC,IAAE,gBAAgB;AACpB;AAEA,SAAS,mBAAmB,GAAkB;AAC5C,MAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,GAAE,gBAAgB;AAC5D;AAQA,SAAS,cAAc,QAAuC;AAC5D,QAAM,SAAS,gBAAgB,OAAO,QAAQ,OAAO,cAAc;AACnE,QAAM,UAAU,WAAW,UAAa,WAAW,QAAQ,WAAW,SAAS,WAAW;AAC1F,SAAO,OAAO,SAAS,UAAa,CAAC,OAAO,WAAW,CAAC,UAAU,OAAO,OAAO;AAClF;AAGA,SAAS,WAAW,EAAE,QAAQ,OAAO,IAAI,GAA+E;AACtH,QAAM,OAAO,cAAc,MAAM;AACjC,QAAM,QAAQ,QAAQ,OAAO,IAAI;AACjC,QAAM,YAAY,OAAO,SAAS,SAAY,SAAY,gBAAgB,OAAO,IAAI;AACrF,QAAM,QACJ,iCACG;AAAA,aACC;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,aAAW,YAAY,OAAO,OAAO;AAAA,QACrC,WAAW,GAAG,gCAAgC,SAAS;AAAA,QAEtD;AAAA;AAAA,IACH;AAAA,IAED,OAAO;AAAA,KACV;AAEF,QAAM,OAAO,OAAO,SAAS,WAAW,WAAW;AACnD,QAAM,SAAS,MAAM;AACnB,UAAM;AACN,QAAI,MAAM;AAAA,EACZ;AACA,MAAI,SAAS,QAAW;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAO;AAAA,QACP;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,WAAU;AAAA,QACV,SAAS;AAAA,QAER;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG,WAAW,MAAM;AAAA,MACrB,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,SAAO;AAAA,MACP;AAAA,MACA,SAAS,OAAO;AAAA,MAChB,gBAAgB,OAAO;AAAA,MACvB,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB,WAAU;AAAA,MACV,SAAS;AAAA,MAER;AAAA;AAAA,EACH;AAEJ;AAKA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,OAAO,cAAc,MAAM;AACjC,QAAM,QAAQ,QAAQ,OAAO,IAAI;AACjC,QAAM,WAAW,OAAO,SAAS,YAAY,SAAY,OAAO;AAEhE,QAAM,WAAW,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO,OAAO;AACrF,QAAM,QAAQ,EAAE,aAAa,OAAO,UAAU,GAAG,SAAS,OAAO,IAAI,EAAE;AACvE,MAAI,OAAO;AACT,QAAI,SAAS,QAAW;AACtB,aACE;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ;AAAA,UACA,UAAU,OAAO;AAAA,UACjB;AAAA,UACA;AAAA,UACA,OAAO,OAAO;AAAA,UACd;AAAA,UACA,MAAM;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,iBAAe;AAAA,UACf,SAAS,MAAM,OAAO,WAAW;AAAA,UAEhC;AAAA;AAAA,MACH;AAAA,IAEJ;AACA,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACH,GAAG,WAAW,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,QACA,OAAO,OAAO;AAAA,QACd;AAAA,QACA,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,gBAAgB,OAAO;AAAA,QACvB,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO;AAAA,QACjB,iBAAe;AAAA,QACf,SAAS,MAAM,IAAI,MAAM;AAAA,QAExB;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,OAAO;AAAA,IACX,SAAS,CAAC,MAAkB;AAC1B,QAAE,gBAAgB;AAClB,UAAI,MAAM;AAAA,IACZ;AAAA,IACA,WAAW,CAAC,MAAqB;AAC/B,UAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,GAAE,gBAAgB;AAAA,IAC5D;AAAA,EACF;AACA,MAAI,SAAS,QAAW;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,MAAM;AAAA,QACN,UAAU,OAAO;AAAA,QAChB,GAAG;AAAA,QAEH,iBAAO;AAAA;AAAA,IACV;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAG,WAAW,MAAM;AAAA,MACrB,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,MAAM;AAAA,MACN,SAAS,OAAO;AAAA,MAChB,gBAAgB,OAAO;AAAA,MACvB,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MAChB,GAAG;AAAA,MAEH,iBAAO;AAAA;AAAA,EACV;AAEJ;AAIA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,WAAW,aAAa,cAAc,4BAA4B,MAAM;AAC9E,QAAM,OAAO,QAAQ,SAAS;AAC9B,SAAO,SAAS,oBAAC,UAAK,WAAU,WAAW,gBAAK,IAAU,gCAAG,gBAAK;AACpE;AAKO,MAAM,yBAAyB;AAG/B,MAAM,yBAAyB;AAmB/B,SAAS,eAAe,EAAE,UAAU,WAAW,QAAQ,WAAW,GAAG,KAAK,GAAwB;AACvG,SACE,oBAAC,mBAAgB,OAAM,OAAO,GAAG,MAAM,WAAW,GAAG,wBAAwB,SAAS,GACnF,iBAAO,aAAa,YAAY,YAAY,OAC3C,oBAAC,oBAAiB,MAAM,YAAY,QAAW,QAAgB,QAAQ,WAAW,IAElF,UAEJ;AAEJ;AAiDO,SAAS,iBAAoB;AAAA,EAClC,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmD;AACjD,SAAO;AAAA,IACL;AAAA,IACA,QACE,WAAW,SAAS,UAAU,OAC5B,oBAAC,oBAAiB,MAAM,YAAY,QAAgB,QAAQ,WAAW,OAAO,IAE9E;AAAA,IAEJ;AAAA,IACA,MAAM,CAAC,QACL;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,QAAQ,GAAG;AAAA,QACpB,MAAM,OAAO,GAAG;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEF,WAAW;AAAA;AAAA;AAAA,IAGX,eAAe;AAAA,IACf,WAAW;AAAA;AAAA,IAEX,MAAM;AAAA,IACN,GAAG;AAAA,EACL;AACF;","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NavPillItem, NavPillLinkProps } from "./nav-pills.js";
|
|
2
|
+
import type { ReactElement, Ref } from "react";
|
|
3
|
+
export interface SectionNavProps<T extends string> {
|
|
4
|
+
/** The section's pages, each with its `href` (a pill without one is a button that
|
|
5
|
+
* calls `onSelect`). Labels short: the row is one line. */
|
|
6
|
+
items: NavPillItem<T>[];
|
|
7
|
+
/** The page that is open. `null`: none of them. */
|
|
8
|
+
current: T | null;
|
|
9
|
+
/** The section's name — the landmark's accessible name ("Accounting"). */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Called with a pill's value on a click, before the router follows its link. */
|
|
12
|
+
onSelect?: (value: T) => void;
|
|
13
|
+
/** Your router's link. Default: the `<UiKitProvider linkComponent>`, then `<a>`. */
|
|
14
|
+
renderLink?: (props: NavPillLinkProps) => ReactElement;
|
|
15
|
+
/** Default `true`: only below `md`, where the sidebar that lists the same pages is
|
|
16
|
+
* gone. `false` shows it at every width. */
|
|
17
|
+
phoneOnly?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
ref?: Ref<HTMLElement>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The pages of a section on a phone, as one row of pills under the page title (0.49
|
|
23
|
+
* KIT-6) — kastlan's section pills, which each section page drew by hand.
|
|
24
|
+
*
|
|
25
|
+
* On a desktop the sidebar lists a group's pages; on a phone the AppShell's bar holds
|
|
26
|
+
* the groups, and a group's pages are a sheet away. This row puts them on the page
|
|
27
|
+
* itself: {@link NavPills} with `layout="scroll"` — one line that scrolls sideways, faded
|
|
28
|
+
* where it is cut, the open page scrolled into view (the row only, never the page). At
|
|
29
|
+
* most 48 px high: 44 px pills, the touch target a finger needs, and 2 px above and
|
|
30
|
+
* below. Each pill is a link (`aria-current="page"` on the open one) and its own tab
|
|
31
|
+
* stop; the row is a `<nav>` named by `label`.
|
|
32
|
+
*
|
|
33
|
+
* Place it right under the page's title (`PageHeader`); the AppShell's own row of a
|
|
34
|
+
* group's pages above the bar (`mobileSubNav`) is the shell-wide alternative — use one,
|
|
35
|
+
* not both, for the same pages.
|
|
36
|
+
*/
|
|
37
|
+
export declare function SectionNav<T extends string>({ items, current, label, onSelect, renderLink, phoneOnly, className, ref, }: SectionNavProps<T>): import("react").JSX.Element;
|