@eifi1/ui-kit 0.32.2 → 0.33.1
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 +17 -9
- package/dist/account/delete-account-setting.d.ts +11 -10
- package/dist/account/delete-account-setting.js +2 -1
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/account/email-change-setting.d.ts +5 -0
- package/dist/account/email-change-setting.js +4 -3
- package/dist/account/email-change-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +4 -0
- package/dist/account/sessions-setting.js +3 -2
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.d.ts +11 -10
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +11 -10
- package/dist/admin/invitations-panel.js +4 -3
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/plan-change.d.ts +101 -0
- package/dist/admin/plan-change.js +210 -0
- package/dist/admin/plan-change.js.map +1 -0
- package/dist/admin/reviewer-scope-editor.d.ts +11 -10
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/admin/role-select.d.ts +8 -7
- package/dist/admin/role-select.js.map +1 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +11 -10
- package/dist/admin/user-roster.d.ts +8 -7
- package/dist/admin/user-roster.js +2 -2
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/auth-errors.d.ts +13 -2
- package/dist/auth/auth-errors.js +5 -1
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.d.ts +4 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/register-form.d.ts +11 -10
- package/dist/billing/billing-banner.d.ts +14 -11
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +11 -10
- package/dist/billing/billing-labels.js +10 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/billing-lock.d.ts +40 -12
- package/dist/billing/billing-lock.js +9 -3
- package/dist/billing/billing-lock.js.map +1 -1
- package/dist/billing/billing-overview.d.ts +155 -0
- package/dist/billing/billing-overview.js +2 -0
- package/dist/billing/billing-overview.js.map +1 -0
- package/dist/billing/billing-standing.d.ts +11 -10
- package/dist/billing/checkout-processing.d.ts +218 -0
- package/dist/billing/checkout-processing.js +198 -0
- package/dist/billing/checkout-processing.js.map +1 -0
- package/dist/billing/checkout-return.d.ts +51 -0
- package/dist/billing/checkout-return.js +43 -0
- package/dist/billing/checkout-return.js.map +1 -0
- package/dist/billing/pay-page.d.ts +215 -0
- package/dist/billing/pay-page.js +113 -0
- package/dist/billing/pay-page.js.map +1 -0
- package/dist/billing/plan-card.d.ts +11 -10
- package/dist/billing/plan-limit-notice.d.ts +11 -10
- package/dist/billing/plan-limit-toast.d.ts +144 -0
- package/dist/billing/plan-limit-toast.js +40 -0
- package/dist/billing/plan-limit-toast.js.map +1 -0
- package/dist/billing/subscription-actions.d.ts +39 -15
- package/dist/billing/subscription-actions.js +9 -2
- package/dist/billing/subscription-actions.js.map +1 -1
- package/dist/billing/subscription-status-chip.d.ts +10 -9
- package/dist/bin/eifi1-pay-page.mjs +166 -0
- package/dist/chart.d.ts +11 -10
- package/dist/components/account-chips.d.ts +6 -1
- package/dist/components/account-settings.d.ts +4 -1
- package/dist/components/account-settings.js +2 -1
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +11 -10
- package/dist/components/autocomplete.d.ts +2 -1
- package/dist/components/autocomplete.js +5 -3
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +11 -10
- package/dist/components/button-group.js +1 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +11 -10
- package/dist/components/calculator.js +3 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/checkbox-group.d.ts +2 -1
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/checkbox.d.ts +2 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +19 -17
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +3 -1
- package/dist/components/choice-card.js +1 -1
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -1
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +4 -3
- package/dist/components/combobox.js +2 -2
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +11 -10
- package/dist/components/confirm-dialog.js +6 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +12 -11
- package/dist/components/country-select.d.ts +2 -1
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +11 -10
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +12 -1
- package/dist/components/data-table-filter-popover.d.ts +12 -1
- package/dist/components/data-table-filters.d.ts +12 -1
- package/dist/components/data-table-labels.d.ts +12 -1
- package/dist/components/data-table-pagination.d.ts +12 -1
- package/dist/components/data-table.d.ts +12 -1
- package/dist/components/data-table.js +3 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +6 -4
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.js +9 -1
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +3 -2
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +11 -10
- package/dist/components/field-parts.d.ts +32 -4
- package/dist/components/field-parts.js +54 -19
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/file-button.d.ts +11 -10
- package/dist/components/file-button.js +3 -2
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +12 -11
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.js +5 -5
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +10 -9
- package/dist/components/form-actions.js +3 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/iban-input.d.ts +11 -10
- package/dist/components/inline-edit-field.d.ts +2 -1
- package/dist/components/inline-edit-field.js +6 -6
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/language-select.d.ts +11 -10
- package/dist/components/legal-page.d.ts +11 -10
- package/dist/components/legal.d.ts +11 -10
- package/dist/components/list.d.ts +2 -1
- package/dist/components/list.js +4 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/money-field.d.ts +11 -10
- package/dist/components/month-picker.js +54 -44
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +3 -2
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +18 -10
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +37 -10
- package/dist/components/number-input.js +27 -12
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +11 -10
- package/dist/components/numpad-sheet.js +2 -2
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/phone-input.d.ts +11 -10
- package/dist/components/popover.js +6 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +6 -5
- package/dist/components/row-actions.js +130 -32
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart.d.ts +11 -10
- package/dist/components/settings-fields.d.ts +11 -10
- package/dist/components/share-card.d.ts +8 -7
- package/dist/components/share-card.js +26 -10
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/skeleton.d.ts +10 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/status-dot.d.ts +1 -0
- package/dist/components/status-dot.js +2 -2
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +2 -1
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +88 -17
- package/dist/components/swipeable-row.js +46 -9
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +2 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.d.ts +2 -1
- package/dist/components/table.js +6 -0
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +10 -9
- package/dist/components/time-input.d.ts +11 -10
- package/dist/components/toggle-group.d.ts +2 -1
- package/dist/components/toggle-group.js +1 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -0
- package/dist/components/translation-review-editor.js +4 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-parts.d.ts +1 -0
- package/dist/components/translation-review.d.ts +1 -0
- package/dist/components/translation-review.js +15 -18
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +10 -9
- package/dist/components/ui.js +47 -27
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +12 -1
- package/dist/components/user-avatar.d.ts +1 -0
- package/dist/components/write-lock.d.ts +101 -13
- package/dist/components/write-lock.js +37 -13
- package/dist/components/write-lock.js.map +1 -1
- package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
- package/dist/data-table.d.ts +12 -1
- package/dist/{delete-account-setting-Dy4kJgUm.d.ts → delete-account-setting-DAgGBrbn.d.ts} +393 -42
- package/dist/feedback/feedback-attachment.d.ts +11 -10
- package/dist/feedback/feedback-attachment.js +7 -7
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-context.d.ts +11 -10
- package/dist/feedback/feedback-crash.d.ts +11 -10
- package/dist/feedback/feedback-dialog.d.ts +11 -10
- package/dist/feedback/feedback-inbox.d.ts +10 -9
- package/dist/feedback/feedback-inbox.js +7 -3
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-labels.d.ts +11 -10
- package/dist/feedback/feedback-menu.d.ts +4 -3
- package/dist/feedback/feedback-record.d.ts +11 -10
- package/dist/feedback/feedback-row-detail.d.ts +11 -10
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +11 -10
- package/dist/feedback/feedback-submit.d.ts +11 -10
- package/dist/feedback/feedback-swipe.d.ts +11 -10
- package/dist/feedback/feedback-swipe.js +3 -6
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +11 -10
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +11 -10
- package/dist/feedback/feedback-thread.js +5 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +11 -10
- package/dist/hooks/use-anchored-panel.d.ts +37 -7
- package/dist/hooks/use-anchored-panel.js +6 -7
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +11 -10
- package/dist/hooks/use-focus-trap.d.ts +8 -0
- package/dist/hooks/use-focus-trap.js +17 -11
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/i18n/defaults.d.ts +11 -10
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +11 -10
- package/dist/i18n/german.js +37 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +11 -10
- package/dist/i18n/locales/de-CH.d.ts +11 -10
- package/dist/i18n/locales/en.d.ts +11 -10
- package/dist/i18n/locales/es.d.ts +11 -10
- package/dist/i18n/locales/es.js +35 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +11 -10
- package/dist/i18n/locales/fr.js +37 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +11 -10
- package/dist/i18n/locales/hu.js +38 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +11 -10
- package/dist/i18n/locales/it.js +35 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +11 -10
- package/dist/i18n/locales/zh.js +35 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/plurals.d.ts +20 -1
- package/dist/i18n/plurals.js +11 -0
- package/dist/i18n/plurals.js.map +1 -1
- package/dist/i18n/review.d.ts +11 -10
- package/dist/i18n/review.js +8 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.d.ts +11 -10
- package/dist/landing/landing-sections.d.ts +11 -10
- package/dist/landing/public-header.d.ts +11 -10
- package/dist/landing/routing.d.ts +11 -10
- package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
- package/dist/pay/index.html +30 -0
- package/dist/pay/pay.css +126 -0
- package/dist/pay/pay.js +198 -0
- package/dist/rhf/fields.d.ts +11 -10
- package/dist/rhf/form.d.ts +11 -10
- package/dist/rhf.d.ts +11 -10
- package/dist/settings/settings-section.d.ts +11 -10
- package/dist/shell/app-shell.d.ts +10 -9
- package/dist/shell/top-bar-brand.d.ts +11 -10
- package/dist/shell/topbar-action-menu.d.ts +1 -0
- package/dist/shell.d.ts +11 -10
- package/dist/testing/css-variables.d.ts +74 -0
- package/dist/testing/css-variables.generated.d.ts +7 -0
- package/dist/testing/css-variables.generated.js +8 -0
- package/dist/testing/css-variables.generated.js.map +1 -0
- package/dist/testing/css-variables.js +93 -0
- package/dist/testing/css-variables.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/theme/contrast-tokens.d.ts +44 -1
- package/dist/theme/contrast-tokens.js +21 -0
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -2
- package/dist/theme/palette-derive.js +60 -11
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.js +45 -43
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +12 -11
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +11 -10
- package/package.json +10 -2
- package/src/account/delete-account-setting.tsx +6 -1
- package/src/account/email-change-setting.tsx +9 -3
- package/src/account/sessions-setting.tsx +7 -2
- package/src/admin/admin-action-confirm.tsx +2 -1
- package/src/admin/invitations-panel.tsx +9 -3
- package/src/admin/plan-change.tsx +487 -0
- package/src/admin/reviewer-scope-editor.tsx +2 -1
- package/src/admin/role-select.tsx +2 -1
- package/src/admin/user-roster.tsx +5 -3
- package/src/auth/auth-errors.ts +25 -2
- package/src/auth/complete-name-dialog.tsx +6 -2
- package/src/billing/billing-banner.tsx +3 -1
- package/src/billing/billing-labels.ts +45 -0
- package/src/billing/billing-lock.ts +36 -3
- package/src/billing/billing-overview.ts +58 -0
- package/src/billing/checkout-processing.ts +391 -0
- package/src/billing/checkout-return.ts +90 -0
- package/src/billing/pay-page.ts +246 -0
- package/src/billing/plan-limit-toast.tsx +91 -0
- package/src/billing/subscription-actions.tsx +41 -8
- package/src/components/account-settings.tsx +6 -2
- package/src/components/autocomplete.tsx +10 -4
- package/src/components/button-group.tsx +4 -1
- package/src/components/calculator.tsx +10 -6
- package/src/components/checkbox-group.tsx +2 -2
- package/src/components/checkbox.tsx +2 -1
- package/src/components/chip.tsx +37 -32
- package/src/components/choice-card.tsx +4 -2
- package/src/components/combobox-core.tsx +5 -2
- package/src/components/combobox.tsx +8 -6
- package/src/components/confirm-dialog.tsx +13 -6
- package/src/components/country-select.tsx +2 -2
- package/src/components/danger-confirm.tsx +2 -2
- package/src/components/data-table.tsx +20 -9
- package/src/components/date-picker.tsx +10 -7
- package/src/components/dropdown.tsx +12 -2
- package/src/components/entity-combobox.tsx +3 -3
- package/src/components/field-parts.tsx +61 -14
- package/src/components/file-button.tsx +5 -4
- package/src/components/file-dropzone.tsx +2 -2
- package/src/components/floating-panel.tsx +7 -5
- package/src/components/form-actions.tsx +12 -5
- package/src/components/full-bleed-dialog.tsx +6 -0
- package/src/components/inline-edit-field.tsx +13 -13
- package/src/components/list.tsx +10 -9
- package/src/components/modal.tsx +5 -0
- package/src/components/month-picker.tsx +8 -2
- package/src/components/multi-entity-combobox.tsx +3 -3
- package/src/components/number-field.tsx +7 -0
- package/src/components/number-input.tsx +71 -11
- package/src/components/numpad-sheet.tsx +8 -4
- package/src/components/popover.tsx +6 -2
- package/src/components/row-actions.tsx +208 -58
- package/src/components/share-card.tsx +67 -13
- package/src/components/skeleton.tsx +10 -2
- package/src/components/status-dot.tsx +7 -7
- package/src/components/swatch-picker.tsx +2 -2
- package/src/components/swipeable-row.tsx +138 -27
- package/src/components/switch.tsx +2 -1
- package/src/components/table.tsx +8 -1
- package/src/components/toggle-group.tsx +5 -3
- package/src/components/translation-review-editor.tsx +8 -3
- package/src/components/translation-review.tsx +18 -22
- package/src/components/ui.tsx +92 -43
- package/src/components/write-lock.tsx +150 -24
- package/src/feedback/feedback-attachment.tsx +13 -9
- package/src/feedback/feedback-inbox.tsx +10 -6
- package/src/feedback/feedback-row-detail.tsx +3 -2
- package/src/feedback/feedback-swipe.tsx +6 -11
- package/src/feedback/feedback-table.tsx +2 -1
- package/src/feedback/feedback-thread.tsx +20 -1
- package/src/hooks/use-anchored-panel.ts +60 -14
- package/src/hooks/use-focus-trap.ts +55 -12
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +41 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/es.ts +35 -0
- package/src/i18n/locales/fr.ts +41 -0
- package/src/i18n/locales/hu.ts +39 -0
- package/src/i18n/locales/it.ts +36 -0
- package/src/i18n/locales/zh.ts +34 -0
- package/src/i18n/plurals.ts +29 -0
- package/src/i18n/review.ts +7 -0
- package/src/index.ts +51 -5
- package/src/testing/css-variables.generated.ts +112 -0
- package/src/testing/css-variables.ts +191 -0
- package/src/testing/index.ts +13 -0
- package/src/theme/contrast-tokens.ts +53 -2
- package/src/theme/palette-derive.ts +87 -10
- package/src/theme/palette-presets.ts +45 -43
- package/src/wizard/stepper-nav.tsx +2 -1
- package/tokens.css +324 -46
|
@@ -5,7 +5,7 @@ import { useAnnounce } from "../hooks/use-announce";
|
|
|
5
5
|
import { useKitFileLabels, useKitLabels } from "../i18n/kit-labels";
|
|
6
6
|
import { Button, Spinner } from "./ui";
|
|
7
7
|
import { Tooltip } from "./tooltip";
|
|
8
|
-
import { useCommitReason } from "./write-lock";
|
|
8
|
+
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A button that opens the file picker — the shape all three apps kept writing by hand
|
|
@@ -444,7 +444,7 @@ export interface FileButtonProps
|
|
|
444
444
|
* one: no effect. A picker that only fills a form the user saves later is not a
|
|
445
445
|
* commit, and is left live — the form's Save carries the lock.
|
|
446
446
|
*/
|
|
447
|
-
commit?:
|
|
447
|
+
commit?: CommitScope;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
/** A reason that is really there — `null`, `false` and `""` are no reason. */
|
|
@@ -612,7 +612,8 @@ export const FileButton = forwardRef<HTMLButtonElement, FileButtonProps>(functio
|
|
|
612
612
|
}
|
|
613
613
|
return (
|
|
614
614
|
// One inline row, so the name sits beside the button wherever the button sits; the
|
|
615
|
-
// name truncates rather than pushing the row wider
|
|
615
|
+
// name truncates rather than pushing the row wider (at Normal: from Large up it wraps,
|
|
616
|
+
// `truncate-until-large`, text size §10.17), and the full list is in the
|
|
616
617
|
// kit's own Tooltip — not a native `title` (0.16.0 shipped one: the one-tooltip rule
|
|
617
618
|
// since keksdose dev#523, and a title never opens on touch). Lazy, so the closed
|
|
618
619
|
// bubble adds nothing to the read-out's text.
|
|
@@ -628,7 +629,7 @@ export const FileButton = forwardRef<HTMLButtonElement, FileButtonProps>(functio
|
|
|
628
629
|
className="flex min-w-0 text-sm text-[var(--text-secondary)]"
|
|
629
630
|
>
|
|
630
631
|
<Tooltip label={picked.map((f) => f.name).join(", ")} lazy className="min-w-0">
|
|
631
|
-
<span className="block truncate">{pickedText}</span>
|
|
632
|
+
<span className="block truncate-until-large">{pickedText}</span>
|
|
632
633
|
</Tooltip>
|
|
633
634
|
</span>
|
|
634
635
|
{picker.element}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import type { FilePickHandler, FilePickerLabels, FileRejection } from "./file-button";
|
|
16
16
|
import { useDragTarget } from "../hooks/use-file-drop";
|
|
17
17
|
import { toast } from "./toast";
|
|
18
|
-
import { useCommitReason } from "./write-lock";
|
|
18
|
+
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Where a refused file's message goes.
|
|
@@ -160,7 +160,7 @@ export interface FileDropzoneProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
160
160
|
* reason (which wins over one of its own). No provider, or an unlocked one: no
|
|
161
161
|
* effect. A zone that only stages a file for a form saved later is not a commit.
|
|
162
162
|
*/
|
|
163
|
-
commit?:
|
|
163
|
+
commit?: CommitScope;
|
|
164
164
|
/**
|
|
165
165
|
* Replace the zone's body — the icon, the text and the chosen-file list — with your
|
|
166
166
|
* own, given the zone's state: keksdose's spinner-plus-"uploading", its multi-shot
|
|
@@ -243,7 +243,7 @@ export function FloatingActionButton({
|
|
|
243
243
|
pressed === false &&
|
|
244
244
|
"bg-[var(--bg-surface)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
|
|
245
245
|
pressed === true &&
|
|
246
|
-
"border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]",
|
|
246
|
+
"border-[var(--brand)] bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)] hover:text-brand-muted",
|
|
247
247
|
"outline-none focus-visible:ring-offset-2",
|
|
248
248
|
FOCUS_RING,
|
|
249
249
|
className,
|
|
@@ -650,10 +650,12 @@ export interface FloatingActionProps extends Omit<ButtonHTMLAttributes<HTMLButto
|
|
|
650
650
|
ref?: Ref<HTMLButtonElement>;
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
+
// Each fill under its own foreground (0.33): warning borrowed `--text-inverse`, the
|
|
654
|
+
// surface colour, which was 4.24:1 on the light warning fill.
|
|
653
655
|
const BADGE_TONE: Record<NonNullable<FloatingActionProps["badgeTone"]>, string> = {
|
|
654
|
-
warning: "bg-
|
|
655
|
-
danger: "bg-
|
|
656
|
-
brand: "bg-
|
|
656
|
+
warning: "bg-warning text-warning-contrast",
|
|
657
|
+
danger: "bg-danger text-danger-contrast",
|
|
658
|
+
brand: "bg-brand text-brand-contrast",
|
|
657
659
|
};
|
|
658
660
|
|
|
659
661
|
/**
|
|
@@ -695,7 +697,7 @@ export function FloatingAction({
|
|
|
695
697
|
variant === "primary"
|
|
696
698
|
? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]"
|
|
697
699
|
: pressed && !isLink && pressedStyle === "tint"
|
|
698
|
-
? "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)]"
|
|
700
|
+
? "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)] hover:text-brand-muted"
|
|
699
701
|
: "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]",
|
|
700
702
|
className,
|
|
701
703
|
);
|
|
@@ -8,6 +8,7 @@ import { useLargeText } from "../hooks/use-large-text";
|
|
|
8
8
|
import { isApplePlatform } from "../hooks/use-hotkey";
|
|
9
9
|
import { Button, Spinner, type ButtonProps, type ButtonSize, type ButtonVariant } from "./ui";
|
|
10
10
|
import { useWriteLock } from "./write-lock";
|
|
11
|
+
import type { CommitScope } from "./write-lock";
|
|
11
12
|
|
|
12
13
|
/** The words {@link FormActions} renders on its own behalf — the `form` namespace. */
|
|
13
14
|
export interface FormActionsLabels {
|
|
@@ -197,8 +198,11 @@ export interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">,
|
|
|
197
198
|
* keksdose wrote `submitDisabled={lock.locked || …}` and
|
|
198
199
|
* `submitDisabledReason={lock.locked ? lock.reason : undefined}` at each form; this is
|
|
199
200
|
* that pair, read from the provider.
|
|
201
|
+
*
|
|
202
|
+
* 0.33: a {@link CommitScope} — `COMMIT_EXCEPT_BILLING` for a form a lapsed plan still
|
|
203
|
+
* allows (the account's own settings, docs/billing-harmonization.md §12.36).
|
|
200
204
|
*/
|
|
201
|
-
commit?:
|
|
205
|
+
commit?: CommitScope;
|
|
202
206
|
/** The save button's variant. Default `brand`; `danger` for a save that destroys
|
|
203
207
|
* (kastlan's `destructive` flag on its own FormActions). */
|
|
204
208
|
submitVariant?: ButtonVariant;
|
|
@@ -392,7 +396,9 @@ export function FormActions({
|
|
|
392
396
|
// column stretches but which keeps its control at the control's own width.
|
|
393
397
|
const full = stacked ? "w-full" : undefined;
|
|
394
398
|
const labels = useKitLabels("form", DEFAULT_FORM_ACTIONS_LABELS);
|
|
395
|
-
|
|
399
|
+
// The lock as Save sees it: with its scope, so a billing lock leaves a
|
|
400
|
+
// `COMMIT_EXCEPT_BILLING` form's shortcut live too.
|
|
401
|
+
const lock = useWriteLock(commit);
|
|
396
402
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
397
403
|
const saveRef = useRef<HTMLButtonElement | null>(null);
|
|
398
404
|
// The caller's own `submitProps.ref` still gets the element.
|
|
@@ -422,7 +428,7 @@ export function FormActions({
|
|
|
422
428
|
}, [pending]);
|
|
423
429
|
// Read by the shortcut's listener at the moment of the key press, so the listener
|
|
424
430
|
// itself is attached once and never sees a stale `pending`.
|
|
425
|
-
const canSave = !pending && !submitDisabled && !
|
|
431
|
+
const canSave = !pending && !submitDisabled && !lock.locked;
|
|
426
432
|
const canSaveRef = useRef(canSave);
|
|
427
433
|
useEffect(() => {
|
|
428
434
|
canSaveRef.current = canSave;
|
|
@@ -535,8 +541,9 @@ export function FormActions({
|
|
|
535
541
|
disabled={pending || submitDisabled}
|
|
536
542
|
disabledReason={pending ? undefined : submitDisabledReason}
|
|
537
543
|
// Not while pending: the save already left, and the lock's look over the
|
|
538
|
-
// spinner would say it had not.
|
|
539
|
-
|
|
544
|
+
// spinner would say it had not. A ternary, not `commit && !pending`, which
|
|
545
|
+
// turned a `{ except }` scope into `true` (§12.36).
|
|
546
|
+
commit={pending ? false : commit}
|
|
540
547
|
aria-busy={pending || undefined}
|
|
541
548
|
className={cn(submitProps?.className, full)}
|
|
542
549
|
>
|
|
@@ -154,6 +154,12 @@ export function FullBleedDialog({
|
|
|
154
154
|
// This is the phone's row EDITOR and its first child is usually an input; focusing it
|
|
155
155
|
// would pull the software keyboard up over half the form before the user has said
|
|
156
156
|
// which field they came for. `Modal` made the same call for the same reason.
|
|
157
|
+
//
|
|
158
|
+
// That default only decides when nothing inside asked for focus. A field the app
|
|
159
|
+
// autofocuses on purpose keeps it (0.33.1): keksdose's create card autofocuses its
|
|
160
|
+
// amount, and the trap used to take focus straight back to the dialog — and on a
|
|
161
|
+
// phone the numpad sheet, which shows only while the field is focused, went with it
|
|
162
|
+
// (live #402). Closing still hands focus back to whatever opened the dialog.
|
|
157
163
|
useFocusTrap(dialogRef, { active: open });
|
|
158
164
|
|
|
159
165
|
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -6,7 +6,7 @@ import { useKitLabels } from "../i18n/kit-labels";
|
|
|
6
6
|
import { Input, Spinner } from "./ui";
|
|
7
7
|
import { FOCUS_RING } from "./focus-ring";
|
|
8
8
|
import { Tooltip } from "./tooltip";
|
|
9
|
-
import { useCommitReason } from "./write-lock";
|
|
9
|
+
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Every string {@link InlineEditField} says on its own behalf — the `inlineEdit`
|
|
@@ -101,7 +101,7 @@ interface InlineEditFieldBaseProps<T> {
|
|
|
101
101
|
/** This field COMMITS — under a locked {@link WriteLockProvider} it takes the
|
|
102
102
|
* `disabledReason` path with the lock's reason (which wins over its own). No
|
|
103
103
|
* provider, or an unlocked one: no effect. */
|
|
104
|
-
commit?:
|
|
104
|
+
commit?: CommitScope;
|
|
105
105
|
/** The value is the user's own data: `data-private` on the display and the editor,
|
|
106
106
|
* so the app's demo-mode blur covers both. */
|
|
107
107
|
redact?: boolean;
|
|
@@ -164,13 +164,13 @@ function RenderEditor<T>({ render, ...props }: InlineEditorProps<T> & { render:
|
|
|
164
164
|
* big type cannot recover a cut-off end by squinting. A `large:` class, so a table of
|
|
165
165
|
* these keeps its one-line cells at Normal.
|
|
166
166
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
167
|
+
* 0.33 (§10.17): the kit's one `truncate-until-large` (tokens.css) in place of this
|
|
168
|
+
* file's own `truncate` + `large:whitespace-normal large:break-words`. The utility
|
|
169
|
+
* breaks with `overflow-wrap: anywhere`, which lowers the value's min-content width: in
|
|
170
|
+
* an auto-sized table column (keksdose's budget and invoice lines) give the column a
|
|
171
|
+
* width, or an amount can be squeezed to a digit per line.
|
|
172
172
|
*/
|
|
173
|
-
const
|
|
173
|
+
const VALUE_TRUNCATE = "truncate-until-large";
|
|
174
174
|
|
|
175
175
|
/** `onCommit`'s result, if it is a promise. */
|
|
176
176
|
function asPromise(result: unknown): Promise<unknown> | null {
|
|
@@ -432,7 +432,7 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
432
432
|
let body: ReactNode;
|
|
433
433
|
if (readOnly) {
|
|
434
434
|
body = (
|
|
435
|
-
<span {...private_} className={cn("block min-w-0
|
|
435
|
+
<span {...private_} className={cn("block min-w-0", VALUE_TRUNCATE, alignClass)}>
|
|
436
436
|
{content}
|
|
437
437
|
</span>
|
|
438
438
|
);
|
|
@@ -508,8 +508,8 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
508
508
|
aria-describedby={reasonId}
|
|
509
509
|
onClick={(e) => e.preventDefault()}
|
|
510
510
|
className={cn(
|
|
511
|
-
"block w-full min-w-0 cursor-not-allowed
|
|
512
|
-
|
|
511
|
+
"block w-full min-w-0 cursor-not-allowed rounded-sm focus:outline-none",
|
|
512
|
+
VALUE_TRUNCATE,
|
|
513
513
|
FOCUS_RING,
|
|
514
514
|
alignClass,
|
|
515
515
|
)}
|
|
@@ -533,8 +533,8 @@ export function InlineEditField<T = string>(props: InlineEditFieldProps<T>) {
|
|
|
533
533
|
{...private_}
|
|
534
534
|
onClick={() => setOpen(true)}
|
|
535
535
|
className={cn(
|
|
536
|
-
"block w-full min-w-0
|
|
537
|
-
|
|
536
|
+
"block w-full min-w-0 rounded-sm underline-offset-2 hover:underline focus:outline-none",
|
|
537
|
+
VALUE_TRUNCATE,
|
|
538
538
|
FOCUS_RING,
|
|
539
539
|
alignClass,
|
|
540
540
|
)}
|
package/src/components/list.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { useKitLabels, useKitLink } from "../i18n/kit-labels";
|
|
|
12
12
|
import { pickLinkRenderer } from "./text-link";
|
|
13
13
|
import { mergeDescribedBy } from "./choice-parts";
|
|
14
14
|
import { LockedReason, useLockReason } from "./field-parts";
|
|
15
|
+
import type { CommitScope } from "./write-lock";
|
|
15
16
|
|
|
16
17
|
/* ── Labels ───────────────────────────────────────────────────────────────── */
|
|
17
18
|
|
|
@@ -122,11 +123,11 @@ const TARGET_RING = cn(FOCUS_RING, "focus-visible:ring-inset");
|
|
|
122
123
|
* §4 "ListItem: title, subtitle and meta wrap instead of truncating" at Large and Extra
|
|
123
124
|
* large (docs/text-size-harmonization.md). `large:` variants over the Normal look, so
|
|
124
125
|
* a list keeps its one-height rows at Normal and costs no render to switch: a one-line
|
|
125
|
-
* `truncate`
|
|
126
|
-
*
|
|
127
|
-
*
|
|
126
|
+
* line is `truncate-until-large` (tokens.css, 0.33 §10.17: `truncate`, and at Large a
|
|
127
|
+
* wrap that breaks a word too long for the row — the kit's one name for what this file
|
|
128
|
+
* spelt as `WRAP_AT_LARGE`), a `line-clamp-2` lets go. An ellipsis hides the end of a
|
|
129
|
+
* name, which is the part a reader who needs big type cannot recover by squinting.
|
|
128
130
|
*/
|
|
129
|
-
const WRAP_AT_LARGE = "large:whitespace-normal large:[overflow-wrap:anywhere]";
|
|
130
131
|
const UNCLAMP_AT_LARGE = "large:line-clamp-none";
|
|
131
132
|
|
|
132
133
|
interface ListItemBaseProps {
|
|
@@ -341,7 +342,7 @@ export type ListItemProps = ListItemBaseProps &
|
|
|
341
342
|
* the `disabledReason` way with the lock's reason (which wins over its own). No
|
|
342
343
|
* provider, or an unlocked one: no effect. Button's `commit`, for keksdose K3.
|
|
343
344
|
*/
|
|
344
|
-
commit?:
|
|
345
|
+
commit?: CommitScope;
|
|
345
346
|
href?: never;
|
|
346
347
|
renderLink?: never;
|
|
347
348
|
external?: never;
|
|
@@ -528,7 +529,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
|
|
|
528
529
|
now-wrapping title into a column one word wide (§4, 240 px effective width). */}
|
|
529
530
|
<span className={cn("flex min-w-0 flex-1 flex-col", trailing != null && "large:basis-40")}>
|
|
530
531
|
{overline != null && (
|
|
531
|
-
<span className=
|
|
532
|
+
<span className="block truncate-until-large text-caption font-medium leading-snug text-[var(--text-muted)]">
|
|
532
533
|
{overline}
|
|
533
534
|
</span>
|
|
534
535
|
)}
|
|
@@ -538,7 +539,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
|
|
|
538
539
|
className={cn(
|
|
539
540
|
"block text-sm text-[var(--text-primary)]",
|
|
540
541
|
titleLines === 1
|
|
541
|
-
?
|
|
542
|
+
? "truncate-until-large"
|
|
542
543
|
: titleLines === 2
|
|
543
544
|
? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE)
|
|
544
545
|
: "break-words",
|
|
@@ -553,7 +554,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
|
|
|
553
554
|
<span
|
|
554
555
|
className={cn(
|
|
555
556
|
"block text-xs text-[var(--text-muted)]",
|
|
556
|
-
subtitleLines === 2 ? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE) :
|
|
557
|
+
subtitleLines === 2 ? cn("line-clamp-2 break-words", UNCLAMP_AT_LARGE) : "truncate-until-large",
|
|
557
558
|
)}
|
|
558
559
|
>
|
|
559
560
|
{subtitle}
|
|
@@ -563,7 +564,7 @@ export const ListItem = forwardRef<HTMLElement, ListItemProps>(function ListItem
|
|
|
563
564
|
<span
|
|
564
565
|
className={cn(
|
|
565
566
|
"mt-0.5 text-caption leading-snug text-[var(--text-muted)]",
|
|
566
|
-
metaWrap ? "flex flex-wrap items-center gap-1" :
|
|
567
|
+
metaWrap ? "flex flex-wrap items-center gap-1" : "block truncate-until-large",
|
|
567
568
|
)}
|
|
568
569
|
>
|
|
569
570
|
{meta}
|
package/src/components/modal.tsx
CHANGED
|
@@ -333,6 +333,11 @@ function ModalPanel({
|
|
|
333
333
|
// hook adds per-keystroke recomputation, a guard on restoring to a detached node,
|
|
334
334
|
// and nesting, none of which this copy had. Released when `open` turns false, which
|
|
335
335
|
// hands focus back to the trigger while the panel is still on its way out.
|
|
336
|
+
//
|
|
337
|
+
// "The panel itself" only when nothing inside asked for focus: a field the app
|
|
338
|
+
// autofocuses on purpose keeps it (0.33.1, keksdose live #402 — the same trap took
|
|
339
|
+
// an autofocused amount straight back in `FullBleedDialog`). Its keyboard is then the
|
|
340
|
+
// one the app chose to show; the trigger still gets focus back on close.
|
|
336
341
|
useFocusTrap(panelRef, { active, initialFocus: "container" });
|
|
337
342
|
const leaving = closing || exiting;
|
|
338
343
|
|
|
@@ -10,7 +10,7 @@ import { Popover } from "./popover";
|
|
|
10
10
|
import { splitTriggerAria } from "./trigger-aria";
|
|
11
11
|
import type { TriggerAria } from "./trigger-aria";
|
|
12
12
|
import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels";
|
|
13
|
-
import { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from "./field-parts";
|
|
13
|
+
import { FieldBox, FieldLabelLine, hasContent, LABEL_ABOVE_BOX, useFieldMessages } from "./field-parts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Every string the month picker can speak. The month and year names are NOT here —
|
|
@@ -1005,7 +1005,13 @@ export function MonthPicker(props: MonthPickerProps) {
|
|
|
1005
1005
|
const field = (
|
|
1006
1006
|
// `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
|
|
1007
1007
|
// full-width box would park it at the far end of the row, away from its trigger.
|
|
1008
|
-
|
|
1008
|
+
// At Large a labelled box is a grid: the label above, the calendar glyph measured
|
|
1009
|
+
// against the trigger's row (LABEL_ABOVE_BOX, 0.33 §10.17).
|
|
1010
|
+
<div
|
|
1011
|
+
{...wrapperRest}
|
|
1012
|
+
ref={rootRef}
|
|
1013
|
+
className={cn("relative", small && "w-fit max-w-full", label !== undefined && LABEL_ABOVE_BOX, className)}
|
|
1014
|
+
>
|
|
1009
1015
|
{label !== undefined && <FieldLabelLine label={label} hint={messages.labelHint} />}
|
|
1010
1016
|
{/* The hidden twin the trigger is named by — see DateField. `sr-only-fixed`,
|
|
1011
1017
|
and inside this `relative` root either way. */}
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
useLockReason,
|
|
27
27
|
} from "./field-parts";
|
|
28
28
|
import { assignRef, mergeDescribedBy } from "./choice-parts";
|
|
29
|
-
import { useCommitReason } from "./write-lock";
|
|
29
|
+
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
30
30
|
|
|
31
31
|
/** `onChange` is the kit's — "a selection was made", carrying values — rather
|
|
32
32
|
* than the div's form event, so the DOM's spelling of it is omitted. */
|
|
@@ -65,7 +65,7 @@ export interface MultiEntityComboboxProps<V extends string | number>
|
|
|
65
65
|
disabledReason?: ReactNode;
|
|
66
66
|
/** This picker COMMITS — toggling saves. Locked the `disabledReason` way under a
|
|
67
67
|
* locked {@link WriteLockProvider}. See {@link EntityCombobox}'s `commit`. */
|
|
68
|
-
commit?:
|
|
68
|
+
commit?: CommitScope;
|
|
69
69
|
/** When set, a "create" row appears for a non-empty query with no exact match.
|
|
70
70
|
* The panel stays open (adding the new entity to `value` is the caller's job). */
|
|
71
71
|
onCreate?: (query: string) => void;
|
|
@@ -75,7 +75,7 @@ export interface MultiEntityComboboxProps<V extends string | number>
|
|
|
75
75
|
createEmptyLabel?: string;
|
|
76
76
|
/** The create row is a write while toggling is not: locked on its own under a locked
|
|
77
77
|
* {@link WriteLockProvider}. See {@link EntityCombobox}'s `createCommit`. */
|
|
78
|
-
createCommit?:
|
|
78
|
+
createCommit?: CommitScope;
|
|
79
79
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
80
80
|
invalid?: boolean;
|
|
81
81
|
/** What is wrong with the value, as {@link Input}'s `error`: rendered under the
|
|
@@ -141,6 +141,13 @@ function roundTo(n: number, digits: number | undefined): number {
|
|
|
141
141
|
* or a dot typed as the decimal in a comma locale); it is re-spelt in the locale's mark
|
|
142
142
|
* when it commits.
|
|
143
143
|
*
|
|
144
|
+
* LOCKED (0.33.1, Kurvenschmiede). A field that saves itself takes `commit` (or a
|
|
145
|
+
* `disabledReason` of its own), NumberInput's: under a locked `WriteLockProvider` it
|
|
146
|
+
* stays in the tab order showing its value, `readOnly`, with the lock's reason in the
|
|
147
|
+
* kit Tooltip and its description; the step keys, the calculator and the numpad are
|
|
148
|
+
* off, and nothing reaches `onValueChange` or `onCommit`. `disabled` takes it out of
|
|
149
|
+
* the tab order, which is what a lock must not do.
|
|
150
|
+
*
|
|
144
151
|
* NOT FOR MONEY. `digits={2}` rounds, but a currency is more than two decimals: JPY has
|
|
145
152
|
* none, a unit price has four, and the chip, the minor-unit default and `min`/`max` on
|
|
146
153
|
* the SIGNED figure are {@link MoneyField}'s (number value) and {@link AmountInput}'s
|
|
@@ -5,7 +5,8 @@ import { NumberPadSheet, type NumberPadSheetLabels } from "./numpad-sheet";
|
|
|
5
5
|
import { FIELD_BASE, FIELD_DISPLAY, FIELD_INVALID, FLOATING_INPUT_CLASS, FloatingField } from "./ui";
|
|
6
6
|
import { CURRENCIES } from "./currency-select";
|
|
7
7
|
import { hasMessage, mergeDescribedBy } from "./choice-parts";
|
|
8
|
-
import { useInlineHint } from "./field-parts";
|
|
8
|
+
import { LockedReason, useInlineHint, useLockReason } from "./field-parts";
|
|
9
|
+
import type { CommitScope } from "./write-lock";
|
|
9
10
|
import { cn } from "../lib/cn";
|
|
10
11
|
import { commitExpression, formatResult } from "../lib/calc";
|
|
11
12
|
import { decimalMark, localizeMark, readTyped, showsTyped } from "../lib/decimal-marks";
|
|
@@ -135,6 +136,32 @@ interface NumberInputProps {
|
|
|
135
136
|
* numeric field that clamps what is typed as well. */
|
|
136
137
|
min?: number;
|
|
137
138
|
max?: number;
|
|
139
|
+
/**
|
|
140
|
+
* Why the value cannot be changed — {@link Button}'s `disabledReason`, for a number
|
|
141
|
+
* that SAVES itself: a setting committed on blur, a figure in a row editor
|
|
142
|
+
* (Kurvenschmiede's session page, 0.33.1). `disabled` takes the field out of the tab
|
|
143
|
+
* order, so the reason never reached a keyboard, and Kurvenschmiede wrote its own
|
|
144
|
+
* `CommitInput` to keep it in.
|
|
145
|
+
*
|
|
146
|
+
* With a reason the field stays in the tab order and shows its value, `readOnly` (the
|
|
147
|
+
* settled grey FIELD_BASE gives a `[readonly]` field) and `aria-disabled`, as the
|
|
148
|
+
* Combobox family's lock is. Nothing changes the value: the step keys do nothing, the
|
|
149
|
+
* calculator and the phone's numpad are not offered, and neither blur nor Enter
|
|
150
|
+
* commits — so `onChange` and `onCommit` are never called. Enter is swallowed, so the
|
|
151
|
+
* field does not submit the form around it. The reason is in the kit {@link Tooltip}
|
|
152
|
+
* and on the input's `aria-describedby`, between the caption and the error. Wins over
|
|
153
|
+
* `disabled`.
|
|
154
|
+
*/
|
|
155
|
+
disabledReason?: ReactNode;
|
|
156
|
+
/**
|
|
157
|
+
* This field COMMITS — {@link Button}'s `commit`: under a locked
|
|
158
|
+
* {@link WriteLockProvider} it is locked the `disabledReason` way with the lock's reason
|
|
159
|
+
* (which wins over its own). A field whose value only reaches the server through the
|
|
160
|
+
* form's Save leaves this off and stays editable under the lock; the Save takes
|
|
161
|
+
* `commit`. No provider, or an unlocked one: no effect. {@link NumberField} and
|
|
162
|
+
* {@link IntegerField} pass it on.
|
|
163
|
+
*/
|
|
164
|
+
commit?: CommitScope;
|
|
138
165
|
}
|
|
139
166
|
|
|
140
167
|
/** Decimal places of `n` as written ("0.25" → 2, "1e-7" → 7), for decimal-exact
|
|
@@ -289,11 +316,18 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
289
316
|
step,
|
|
290
317
|
min,
|
|
291
318
|
max,
|
|
319
|
+
disabledReason,
|
|
320
|
+
commit: commitScope,
|
|
292
321
|
} = props;
|
|
293
322
|
const generatedId = useId();
|
|
294
323
|
const fieldId = id ?? generatedId;
|
|
295
324
|
const hintId = useId();
|
|
296
325
|
const errorId = useId();
|
|
326
|
+
// The lock: focusable, `readOnly`, nothing changes or commits — see `disabledReason`.
|
|
327
|
+
const lock = useLockReason(commitScope, disabledReason);
|
|
328
|
+
const locked = lock.locked;
|
|
329
|
+
// Locked wins over `disabled`: the point of a reason is a field a keyboard reaches.
|
|
330
|
+
const off = Boolean(disabled) && !locked;
|
|
297
331
|
// §4 "No fact only in a tooltip" (docs/text-size-harmonization.md): at Large and on a
|
|
298
332
|
// touch screen a FieldHint is its words, so it takes the caption's path below — the
|
|
299
333
|
// other kit fields do the same through `useFieldHint`.
|
|
@@ -303,15 +337,17 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
303
337
|
const hasError = hasMessage(error);
|
|
304
338
|
// Passed at all — even as `undefined` — the field keeps its box; see `error`.
|
|
305
339
|
const reserve = "error" in props || "hint" in props;
|
|
306
|
-
// The standing advice first, the news second: the caller's ids, the caption, the
|
|
307
|
-
|
|
340
|
+
// The standing advice first, the news second: the caller's ids, the caption, the
|
|
341
|
+
// error — a lock's reason between the last two, as on Select.
|
|
342
|
+
const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, locked && lock.reasonId, hasError && errorId);
|
|
308
343
|
const invalid = Boolean(invalidProp) || hasError || ariaInvalid === true || ariaInvalid === "true";
|
|
309
344
|
const labelled = label !== undefined;
|
|
310
345
|
// On phones we suppress the OS keyboard (inputMode="none" below) for our own
|
|
311
346
|
// calculator numpad, so the desktop popover trigger is hidden (feedback #334).
|
|
312
347
|
const isMobile = usePhoneLayout();
|
|
313
348
|
const asDisplay = isMobile && variant === "display";
|
|
314
|
-
|
|
349
|
+
// A locked field offers no calculator: its result would be a value.
|
|
350
|
+
const showCalc = calculator && !disabled && !locked && !isMobile;
|
|
315
351
|
const [focused, setFocused] = useState(false);
|
|
316
352
|
// The unit and the calculator's measured width, reserved as the field's end padding.
|
|
317
353
|
const endRef = useRef<HTMLDivElement>(null);
|
|
@@ -338,7 +374,7 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
338
374
|
}, [hasEnd]);
|
|
339
375
|
// On mobile, focusing opens the numpad bottom sheet instead of the native
|
|
340
376
|
// keyboard (#334); the ref lets its "Done" blur → commit + close.
|
|
341
|
-
const showNumpad = isMobile && !disabled && focused;
|
|
377
|
+
const showNumpad = isMobile && !disabled && !locked && focused;
|
|
342
378
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
343
379
|
|
|
344
380
|
// The mark is the locale's; see the component's note. `derived` is the value in that
|
|
@@ -395,6 +431,12 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
395
431
|
};
|
|
396
432
|
|
|
397
433
|
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
434
|
+
// Locked: Enter submits no form and commits nothing, and no key steps the value.
|
|
435
|
+
// The arrows and Home/End stay the caret's, for a long value someone is reading.
|
|
436
|
+
if (locked) {
|
|
437
|
+
if (e.key === "Enter") e.preventDefault();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
398
440
|
if (e.key === "Enter") return commit();
|
|
399
441
|
// A zero, negative or non-finite step is no step: the keys stay the caret's.
|
|
400
442
|
if (!step || !(step > 0) || !Number.isFinite(step) || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
@@ -406,9 +448,12 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
406
448
|
stepBy(count);
|
|
407
449
|
};
|
|
408
450
|
|
|
451
|
+
// While locked the field sits in the reason's Tooltip, which becomes its outermost
|
|
452
|
+
// box — so `className` (a width, a flex or grid cell) moves onto that, and the form
|
|
453
|
+
// keeps its layout, as on Select.
|
|
409
454
|
const field = (
|
|
410
455
|
<FloatingField
|
|
411
|
-
className={cn("w-full", className)}
|
|
456
|
+
className={cn("w-full", !locked && className)}
|
|
412
457
|
htmlFor={fieldId}
|
|
413
458
|
label={label}
|
|
414
459
|
srOnlyLabel={asDisplay}
|
|
@@ -425,16 +470,23 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
425
470
|
autoComplete="off"
|
|
426
471
|
// eslint-disable-next-line jsx-a11y/no-autofocus -- a documented prop the caller opts into (off by default); the field never takes focus on its own.
|
|
427
472
|
autoFocus={autoFocus}
|
|
428
|
-
disabled={
|
|
473
|
+
disabled={off}
|
|
474
|
+
// Locked: focusable, `readOnly`, `aria-disabled` — see `disabledReason`.
|
|
475
|
+
readOnly={locked || undefined}
|
|
476
|
+
aria-disabled={locked || undefined}
|
|
429
477
|
// Display mode has no floating label to feed the blank-placeholder trick,
|
|
430
478
|
// and an empty borderless figure shows nothing — so it keeps whatever
|
|
431
479
|
// placeholder the caller gave, falling back to a zero to aim at.
|
|
432
480
|
placeholder={asDisplay ? (placeholder ?? "0") : labelled ? " " : placeholder}
|
|
433
481
|
value={display}
|
|
434
|
-
onChange={(e) =>
|
|
482
|
+
onChange={(e) => {
|
|
483
|
+
if (!locked) typed(e.target.value);
|
|
484
|
+
}}
|
|
435
485
|
onFocus={() => setFocused(true)}
|
|
436
486
|
onBlur={() => {
|
|
437
|
-
|
|
487
|
+
// Nothing commits under the lock — not even a draft typed before it arrived,
|
|
488
|
+
// which stays in the field for when it lifts.
|
|
489
|
+
if (!locked) commit();
|
|
438
490
|
setFocused(false);
|
|
439
491
|
}}
|
|
440
492
|
onKeyDown={onKeyDown}
|
|
@@ -455,6 +507,7 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
455
507
|
inputClassName,
|
|
456
508
|
showCalc && suffix !== undefined ? "pe-16" : showCalc ? "pe-9" : suffix !== undefined ? "pe-8" : undefined,
|
|
457
509
|
invalid && FIELD_INVALID,
|
|
510
|
+
locked && "cursor-not-allowed",
|
|
458
511
|
)}
|
|
459
512
|
// The classes above are the fallback; once the end controls are measured the
|
|
460
513
|
// field reserves exactly their width (+4px). A fixed pe-8 for any unit left a
|
|
@@ -511,13 +564,20 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
511
564
|
)}
|
|
512
565
|
</FloatingField>
|
|
513
566
|
);
|
|
514
|
-
|
|
567
|
+
const box = locked ? (
|
|
568
|
+
<LockedReason lock={lock} className={cn("block w-full", className)}>
|
|
569
|
+
{field}
|
|
570
|
+
</LockedReason>
|
|
571
|
+
) : (
|
|
572
|
+
field
|
|
573
|
+
);
|
|
574
|
+
if (!textHint && !hasError && !reserve) return box;
|
|
515
575
|
// Outside the field's own `relative` box, as Select's caption is: the calculator
|
|
516
576
|
// and the unit are `inset-y-0` in it and would stretch down over a second line.
|
|
517
577
|
// `className` stays on the field, so adding a caption cannot change what it styles.
|
|
518
578
|
return (
|
|
519
579
|
<div>
|
|
520
|
-
{
|
|
580
|
+
{box}
|
|
521
581
|
{textHint && (
|
|
522
582
|
<p id={hintId} className="mt-1 text-caption leading-tight text-[var(--text-muted)]">
|
|
523
583
|
{hint}
|
|
@@ -87,11 +87,15 @@ const PAD_KEYS: PadKey[] = [
|
|
|
87
87
|
* thinner than an outward ring plus the neighbour it would spill onto — inset keeps the
|
|
88
88
|
* ring inside the key it describes.
|
|
89
89
|
*
|
|
90
|
-
* --brand rather than --border-strong: the operator keys are filled with
|
|
91
|
-
* a ring in the colour of the thing it surrounds is not an indicator. */
|
|
90
|
+
* --brand rather than --border-strong: the operator keys are filled with a step of the
|
|
91
|
+
* ink, and a ring in the colour of the thing it surrounds is not an indicator. */
|
|
92
92
|
const PAD_BTN = `flex h-14 items-center justify-center rounded-lg text-lg font-medium transition-transform select-none focus:outline-none ${FOCUS_RING} focus-visible:ring-inset active:scale-[0.97]`;
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// Digits on the well, operators a step down on `--bg-active`, each pressing toward the
|
|
94
|
+
// other. `--bg-active`, not `--border` (0.33, docs/colour-roles-harmonization.md §12.9):
|
|
95
|
+
// More contrast steps the border until it is a 3:1 LINE, and as a fill under the label
|
|
96
|
+
// that left 2.7–3.3:1; `--bg-active` carries the ink at 7:1 and up at both levels.
|
|
97
|
+
const PAD_DIGIT = "bg-surface-2 text-primary active:bg-active";
|
|
98
|
+
const PAD_ACCENT = "bg-active text-primary active:bg-surface-2";
|
|
95
99
|
|
|
96
100
|
export function NumberPadSheet({
|
|
97
101
|
value,
|
|
@@ -144,7 +144,10 @@ export function Popover({
|
|
|
144
144
|
: 0;
|
|
145
145
|
const pos = rect
|
|
146
146
|
? {
|
|
147
|
-
|
|
147
|
+
// Flipped above, the panel hangs from its bottom edge, so one shorter than its
|
|
148
|
+
// cap still meets the trigger (keksdose K1); the edge not in use is cleared.
|
|
149
|
+
top: placement.above ? undefined : placement.top,
|
|
150
|
+
bottom: placement.above ? placement.bottom : undefined,
|
|
148
151
|
left: Math.min(
|
|
149
152
|
Math.max(EDGE, dir === "rtl" ? rect.left : rect.right - panelWidth),
|
|
150
153
|
window.innerWidth - panelWidth - EDGE,
|
|
@@ -213,12 +216,13 @@ export function Popover({
|
|
|
213
216
|
tabIndex={-1}
|
|
214
217
|
onKeyDown={handleKeyDown}
|
|
215
218
|
style={{
|
|
216
|
-
// Merged over the caller's, never replaced by it: these
|
|
219
|
+
// Merged over the caller's, never replaced by it: these are measured
|
|
217
220
|
// against the trigger every scroll and resize, and a panel that dropped
|
|
218
221
|
// them would paint at the top-left of the viewport.
|
|
219
222
|
...style,
|
|
220
223
|
position: "fixed",
|
|
221
224
|
top: pos.top,
|
|
225
|
+
bottom: pos.bottom,
|
|
222
226
|
left: pos.left,
|
|
223
227
|
width: panelWidth,
|
|
224
228
|
maxHeight: placement.maxHeight,
|