@eifi1/ui-kit 0.51.4 → 0.51.5
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 +3 -3
- package/dist/account/account-settings-labels.js.map +1 -1
- package/dist/account/account-settings.js +11 -9
- package/dist/account/account-settings.js.map +1 -1
- package/dist/account/delete-account-setting.js +56 -65
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/account/email-change-setting-labels.js.map +1 -1
- package/dist/account/email-change-setting.js.map +1 -1
- package/dist/account/passkeys-setting.js.map +1 -1
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.js +1 -3
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/admin-area-labels.js +4 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.js +19 -11
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/admin-sections.js.map +1 -1
- package/dist/admin/admin-tool-links.d.ts +1 -1
- package/dist/admin/admin-tool-links.js +7 -1
- package/dist/admin/admin-tool-links.js.map +1 -1
- package/dist/admin/invitations-panel.js +2 -13
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/mail-test-card.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.js +4 -11
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/admin/role-select.js.map +1 -1
- package/dist/admin/user-roster.js +1 -9
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/forgot-password-form.js +23 -31
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form.js.map +1 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form.js +13 -5
- package/dist/auth/sign-in-form.js.map +1 -1
- package/dist/auth/verify-email.js +1 -8
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/checkout-processing.js.map +1 -1
- package/dist/billing/plan-card.js +91 -85
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-limit-toast.js.map +1 -1
- package/dist/billing/plan-limit.js.map +1 -1
- package/dist/billing/plan-price.js.map +1 -1
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/billing/subscription-actions.js.map +1 -1
- package/dist/bin/eifi1-pay-page.mjs +6 -1
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.js +1 -5
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.js +29 -12
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.js.map +1 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.js +1 -4
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.js +1 -7
- package/dist/components/button.js.map +1 -1
- package/dist/components/calculator.js +5 -5
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/calendar-heatmap.js +4 -10
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/card.js +1 -8
- package/dist/components/card.js.map +1 -1
- package/dist/components/chart-zoom-core.js +3 -17
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/chart.d.ts +1 -1
- package/dist/components/chart.js +4 -22
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox-group.js +1 -4
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/chip-input.js +4 -1
- package/dist/components/chip-input.js.map +1 -1
- package/dist/components/chip.js +10 -5
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.js +58 -74
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/column-mapper-labels.js.map +1 -1
- package/dist/components/column-mapper.js +167 -227
- package/dist/components/column-mapper.js.map +1 -1
- package/dist/components/combobox-core.js +1 -4
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.js +1 -4
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.d.ts +5 -4
- package/dist/components/country-select.js +3 -19
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-option-list.js +10 -8
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/currency-select.js +1 -6
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.js.map +1 -1
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filter-popover.js +2 -18
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.js +3 -1
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.js +44 -56
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.js +31 -44
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-row-actions.js +19 -16
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table-virtual.js +4 -1
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/data-table.js +30 -39
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +1 -6
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/description-list.js +10 -10
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.js +9 -2
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/disclosure.js +17 -27
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/dropdown.js +25 -35
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/empty-state.js.map +1 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/field-base.js +5 -5
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-parts.d.ts +1 -1
- package/dist/components/field-parts.js +10 -14
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-sync.js +8 -10
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.js +4 -1
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/focus-ring.js.map +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/grouped-picker.js +6 -8
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.js +1 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/iban-input-labels.js.map +1 -1
- package/dist/components/icon-button.js +4 -1
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/icon-picker.js +2 -7
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/image-grid.js +41 -50
- package/dist/components/image-grid.js.map +1 -1
- package/dist/components/inline-edit-field.js.map +1 -1
- package/dist/components/inline-entity-combobox.js +2 -8
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +14 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +1 -7
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal-labels.js.map +1 -1
- package/dist/components/legal-page.js +14 -2
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.js +12 -2
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/line-items.js +26 -36
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.js +22 -10
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.js +29 -53
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.js +13 -31
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.js +10 -21
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.js +3 -16
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/nav-pills.js +10 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.js +1 -7
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.js +1 -4
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/one-time-code-input.js +1 -7
- package/dist/components/one-time-code-input.js.map +1 -1
- package/dist/components/page-contents.js +13 -22
- package/dist/components/page-contents.js.map +1 -1
- package/dist/components/page-header.js +1 -11
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/password-strength-labels.js.map +1 -1
- package/dist/components/password-strength.js +2 -8
- package/dist/components/password-strength.js.map +1 -1
- package/dist/components/phone-input.js +42 -57
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart.js +1 -8
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress-bar.js +16 -9
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.js +1 -8
- package/dist/components/qr-code.js.map +1 -1
- package/dist/components/row-actions.js +5 -1
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/select.js +28 -38
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.js +1 -5
- package/dist/components/separator.js.map +1 -1
- package/dist/components/series-chart-axes.js +4 -1
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart-budget.js.map +1 -1
- package/dist/components/series-chart-ticks.js.map +1 -1
- package/dist/components/series-chart.js +29 -45
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.js +21 -17
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.js +4 -9
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/sign-chip.js.map +1 -1
- package/dist/components/signature-pad.js +4 -13
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/signed-amount.js +12 -5
- package/dist/components/signed-amount.js.map +1 -1
- package/dist/components/skeleton.js +2 -17
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/slider.js +13 -36
- package/dist/components/slider.js.map +1 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -10
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swatch-picker.js +14 -4
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +1 -1
- package/dist/components/swipeable-row.js +1 -8
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/table-skeleton.js +4 -1
- package/dist/components/table-skeleton.js.map +1 -1
- package/dist/components/table.js +12 -9
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.js +5 -24
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/text-link.js.map +1 -1
- package/dist/components/text.js.map +1 -1
- package/dist/components/tile-radio.js +1 -7
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.js +6 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.js +2 -11
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tooltip.js +2 -22
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -4
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-parts.d.ts +1 -1
- package/dist/components/translation-review-parts.js +7 -1
- package/dist/components/translation-review-parts.js.map +1 -1
- package/dist/components/translation-review.js +18 -7
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/tree-view.js +14 -10
- package/dist/components/tree-view.js.map +1 -1
- package/dist/components/treemap.js +1 -13
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/use-table-state.js +14 -11
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.js +3 -26
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/demo/demo-start.js +8 -4
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-attachment-labels.js.map +1 -1
- package/dist/feedback/feedback-attachment.js +1 -8
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-capture.d.ts +1 -1
- package/dist/feedback/feedback-capture.js +3 -1
- package/dist/feedback/feedback-capture.js.map +1 -1
- package/dist/feedback/feedback-comment-thread.js +27 -13
- package/dist/feedback/feedback-comment-thread.js.map +1 -1
- package/dist/feedback/feedback-context.js +1 -4
- package/dist/feedback/feedback-context.js.map +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-public-list.js +7 -1
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-record.js +1 -5
- package/dist/feedback/feedback-record.js.map +1 -1
- package/dist/feedback/feedback-replies.js +25 -10
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-table.js +4 -1
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread.js +2 -13
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback/feedback-vocabulary.js.map +1 -1
- package/dist/feedback/feedback-vote-button.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-document-title.js.map +1 -1
- package/dist/hooks/use-file-drop.js.map +1 -1
- package/dist/hooks/use-file-picker-labels.js.map +1 -1
- package/dist/hooks/use-file-picker.js.map +1 -1
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +10 -5
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.d.ts +1 -1
- package/dist/hooks/use-row-swipe.js +1 -6
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.js +1 -4
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +4 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.js +2 -8
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.js.map +1 -1
- package/dist/i18n/locales/en.js +4 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +4 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +4 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +4 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +4 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/plurals.js.map +1 -1
- package/dist/i18n/review.js +13 -7
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +16 -16
- package/dist/index.js +40 -23
- package/dist/index.js.map +1 -1
- package/dist/landing/access.js +1 -4
- package/dist/landing/access.js.map +1 -1
- package/dist/landing/landing-sections.js +35 -38
- package/dist/landing/landing-sections.js.map +1 -1
- package/dist/landing/page-seo.js.map +1 -1
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/clipboard-minus.js.map +1 -1
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/decimal-marks.js.map +1 -1
- package/dist/lib/device-settings.js +2 -1
- package/dist/lib/device-settings.js.map +1 -1
- package/dist/lib/direction.js.map +1 -1
- package/dist/lib/iban.js.map +1 -1
- package/dist/lib/logger.js +1 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/person-name.js.map +1 -1
- package/dist/lib/phone.js.map +1 -1
- package/dist/lib/qr-encode.js +344 -8
- package/dist/lib/qr-encode.js.map +1 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/table-text.js +15 -3
- package/dist/lib/table-text.js.map +1 -1
- package/dist/lib/text-match.d.ts +4 -0
- package/dist/lib/text-match.js.map +1 -1
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/notifications/notification-inbox.js +63 -44
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-parts.js.map +1 -1
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-device-setting.js +6 -1
- package/dist/notifications/push-device-setting.js.map +1 -1
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/use-push-device.js +4 -1
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/pwa-update-prompt.d.ts +1 -1
- package/dist/offline/pwa-update-prompt.js +7 -1
- package/dist/offline/pwa-update-prompt.js.map +1 -1
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/offline.d.ts +1 -1
- package/dist/offline.js +6 -1
- package/dist/offline.js.map +1 -1
- package/dist/pay/pay.css +8 -1
- package/dist/rhf/fields.d.ts +1 -2
- package/dist/rhf/fields.js +1 -10
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form-core.js +1 -4
- package/dist/rhf/form-core.js.map +1 -1
- package/dist/rhf/line-items.js.map +1 -1
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
- package/dist/search/command-palette.js +71 -62
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.js +34 -5
- package/dist/search/global-search.js.map +1 -1
- package/dist/search/search-index.js.map +1 -1
- package/dist/settings/device-reset-setting.js +1 -6
- package/dist/settings/device-reset-setting.js.map +1 -1
- package/dist/settings/settings-heading.js.map +1 -1
- package/dist/settings/settings-layout.js +9 -20
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/settings/use-settings-route.js.map +1 -1
- package/dist/shell/app-shell.js +62 -31
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.js +1 -10
- package/dist/shell/auth-layout.js.map +1 -1
- package/dist/shell/dev-menu.js +12 -6
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/sync-status-indicator.js +7 -6
- package/dist/shell/sync-status-indicator.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.js +1 -10
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell/tour-menu.js +9 -1
- package/dist/shell/tour-menu.js.map +1 -1
- package/dist/sync/db.js.map +1 -1
- package/dist/sync/engine.js +24 -6
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +7 -1
- package/dist/sync.js.map +1 -1
- package/dist/testing/css-variables.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/react.js.map +1 -1
- package/dist/testing/viewport.js +7 -4
- package/dist/testing/viewport.js.map +1 -1
- package/dist/theme/contrast-tokens.js +13 -2
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/palette-derive.d.ts +1 -1
- package/dist/theme/palette-derive.js +18 -29
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +1 -3
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-dock.js +5 -1
- package/dist/tour/tour-dock.js.map +1 -1
- package/dist/tour/tour-engine.js +1 -4
- package/dist/tour/tour-engine.js.map +1 -1
- package/dist/tour/tour.js +21 -12
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/tour/use-tour-url-sync.js.map +1 -1
- package/dist/wizard/stepper-nav.js +3 -24
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +24 -28
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +1 -1
- package/dist/wizard/wizard-step.js +1 -8
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/package.json +5 -2
- package/src/account/account-settings-labels.ts +1 -2
- package/src/account/account-settings.tsx +11 -17
- package/src/account/delete-account-setting.tsx +1 -6
- package/src/account/email-change-setting-labels.ts +1 -2
- package/src/account/email-change-setting.tsx +3 -1
- package/src/account/passkeys-setting.tsx +1 -3
- package/src/account/sessions-setting.tsx +4 -2
- package/src/admin/admin-action-confirm.tsx +4 -7
- package/src/admin/admin-action-log.tsx +29 -29
- package/src/admin/admin-area-labels.ts +4 -1
- package/src/admin/admin-overview.tsx +8 -2
- package/src/admin/admin-sections.tsx +3 -1
- package/src/admin/admin-tool-links.tsx +7 -1
- package/src/admin/invitations-panel.tsx +5 -11
- package/src/admin/mail-test-card.tsx +1 -2
- package/src/admin/reviewer-scope-editor.tsx +4 -9
- package/src/admin/role-select.tsx +4 -2
- package/src/admin/user-roster.tsx +2 -11
- package/src/auth/accept-invitation.tsx +1 -7
- package/src/auth/auth-errors.ts +1 -6
- package/src/auth/forgot-password-form.tsx +2 -7
- package/src/auth/register-form.tsx +7 -8
- package/src/auth/reset-password-form.tsx +2 -5
- package/src/auth/sign-in-form.tsx +15 -14
- package/src/auth/verify-email.tsx +2 -7
- package/src/billing/billing-banner.tsx +3 -7
- package/src/billing/billing-labels.ts +6 -4
- package/src/billing/checkout-processing.ts +0 -1
- package/src/billing/plan-card.tsx +9 -8
- package/src/billing/plan-limit-toast.tsx +1 -2
- package/src/billing/plan-limit.ts +1 -2
- package/src/billing/plan-price.ts +1 -4
- package/src/billing/plan-switch.tsx +1 -2
- package/src/billing/subscription-actions.tsx +5 -2
- package/src/components/account-chips.tsx +1 -7
- package/src/components/alert-banner.tsx +2 -8
- package/src/components/amount-input.tsx +206 -178
- package/src/components/authed-image.tsx +7 -4
- package/src/components/autocomplete.tsx +7 -9
- package/src/components/bulk-action-bar.tsx +2 -7
- package/src/components/button-group.tsx +1 -2
- package/src/components/button-look.ts +6 -17
- package/src/components/button.tsx +1 -4
- package/src/components/calculator.tsx +6 -9
- package/src/components/calendar-heatmap.tsx +5 -10
- package/src/components/card.tsx +9 -13
- package/src/components/chart-zoom-core.tsx +6 -33
- package/src/components/chart.tsx +8 -31
- package/src/components/checkbox-group.tsx +1 -4
- package/src/components/chip-input.tsx +6 -6
- package/src/components/chip.tsx +14 -26
- package/src/components/choice-card.tsx +4 -18
- package/src/components/column-mapper-labels.ts +3 -10
- package/src/components/column-mapper.tsx +33 -122
- package/src/components/combobox-core.tsx +12 -42
- package/src/components/combobox.tsx +2 -6
- package/src/components/confirm-dialog.tsx +1 -3
- package/src/components/copy-button.tsx +2 -4
- package/src/components/country-select.tsx +9 -21
- package/src/components/currency-option-list.tsx +13 -14
- package/src/components/currency-select.tsx +2 -7
- package/src/components/danger-confirm.tsx +8 -9
- package/src/components/data-table-cells.tsx +1 -2
- package/src/components/data-table-column-settings.tsx +1 -3
- package/src/components/data-table-filter-popover.tsx +5 -19
- package/src/components/data-table-filters.ts +1 -6
- package/src/components/data-table-fit.ts +1 -3
- package/src/components/data-table-groups.tsx +3 -1
- package/src/components/data-table-head.tsx +27 -45
- package/src/components/data-table-mobile.tsx +99 -108
- package/src/components/data-table-pagination.tsx +2 -6
- package/src/components/data-table-row-actions.tsx +25 -24
- package/src/components/data-table-row.tsx +16 -17
- package/src/components/data-table-shared.tsx +10 -16
- package/src/components/data-table-sort.ts +1 -2
- package/src/components/data-table-virtual.ts +9 -4
- package/src/components/data-table.tsx +489 -501
- package/src/components/date-picker.tsx +1 -7
- package/src/components/date-range-picker.tsx +3 -13
- package/src/components/description-list.tsx +7 -13
- package/src/components/dialog-frame.tsx +12 -3
- package/src/components/disclosure.tsx +8 -12
- package/src/components/dropdown.tsx +3 -11
- package/src/components/empty-state.tsx +1 -3
- package/src/components/entity-combobox.tsx +2 -6
- package/src/components/error-boundary.tsx +7 -7
- package/src/components/field-base.tsx +8 -14
- package/src/components/field-parts.tsx +12 -23
- package/src/components/field-sync.tsx +7 -11
- package/src/components/file-button.tsx +7 -3
- package/src/components/file-dropzone.tsx +7 -12
- package/src/components/floating-panel.tsx +44 -51
- package/src/components/focus-ring.ts +1 -2
- package/src/components/form-actions.tsx +1 -4
- package/src/components/grouped-picker.tsx +6 -8
- package/src/components/hover-menu.tsx +1 -4
- package/src/components/iban-input-labels.ts +1 -2
- package/src/components/icon-button.tsx +46 -48
- package/src/components/icon-picker.tsx +4 -17
- package/src/components/image-grid.tsx +1 -6
- package/src/components/inline-edit-field.tsx +1 -5
- package/src/components/inline-entity-combobox.tsx +6 -21
- package/src/components/input.tsx +14 -2
- package/src/components/jump-calendar.tsx +2 -6
- package/src/components/legal-labels.ts +1 -2
- package/src/components/legal-page.tsx +18 -7
- package/src/components/legal.tsx +14 -5
- package/src/components/lightbox.tsx +1 -3
- package/src/components/line-items.tsx +7 -15
- package/src/components/list.tsx +29 -25
- package/src/components/measured-grid.tsx +8 -30
- package/src/components/menu-item.tsx +1 -3
- package/src/components/mini-calendar.tsx +129 -159
- package/src/components/money-field.tsx +4 -2
- package/src/components/month-picker.tsx +14 -21
- package/src/components/multi-entity-combobox.tsx +6 -26
- package/src/components/multi-select.tsx +5 -9
- package/src/components/nav-pills.tsx +10 -2
- package/src/components/number-field.tsx +18 -15
- package/src/components/number-input.tsx +3 -19
- package/src/components/numpad-sheet.tsx +9 -13
- package/src/components/one-time-code-input.tsx +6 -11
- package/src/components/page-contents.tsx +2 -9
- package/src/components/page-header.tsx +5 -10
- package/src/components/password-strength-labels.ts +1 -2
- package/src/components/password-strength.tsx +6 -9
- package/src/components/phone-input.tsx +2 -13
- package/src/components/picker-sheet.tsx +2 -5
- package/src/components/pie-chart.tsx +6 -12
- package/src/components/popover.tsx +2 -1
- package/src/components/progress-bar.tsx +16 -25
- package/src/components/qr-code.tsx +2 -9
- package/src/components/row-actions.tsx +9 -1
- package/src/components/search-field.tsx +4 -5
- package/src/components/select.tsx +2 -10
- package/src/components/separator.tsx +1 -5
- package/src/components/series-chart-axes.ts +5 -5
- package/src/components/series-chart-budget.ts +1 -4
- package/src/components/series-chart-ticks.ts +1 -4
- package/src/components/series-chart.tsx +43 -83
- package/src/components/settings-fields.tsx +20 -15
- package/src/components/share-card.tsx +6 -14
- package/src/components/sign-chip.tsx +16 -16
- package/src/components/signature-pad.tsx +21 -13
- package/src/components/signed-amount.tsx +6 -14
- package/src/components/skeleton.tsx +2 -13
- package/src/components/slider.tsx +9 -20
- package/src/components/sparkline.tsx +2 -13
- package/src/components/stat-tile.tsx +13 -24
- package/src/components/swatch-picker.tsx +8 -3
- package/src/components/swipeable-row.tsx +2 -11
- package/src/components/table-skeleton.tsx +4 -1
- package/src/components/table.tsx +12 -8
- package/src/components/tabs.tsx +24 -39
- package/src/components/text-link.tsx +6 -6
- package/src/components/text.tsx +1 -2
- package/src/components/tile-radio.tsx +6 -19
- package/src/components/time-input.tsx +4 -5
- package/src/components/toast.tsx +3 -12
- package/src/components/toggle-group.tsx +9 -9
- package/src/components/toggle-legend.tsx +4 -16
- package/src/components/tooltip.tsx +9 -48
- package/src/components/translation-review-editor.tsx +4 -4
- package/src/components/translation-review-parts.tsx +7 -1
- package/src/components/translation-review.tsx +15 -16
- package/src/components/tree-view.tsx +17 -20
- package/src/components/treemap.tsx +3 -15
- package/src/components/trigger-aria.ts +1 -4
- package/src/components/use-table-state.ts +16 -17
- package/src/components/user-avatar.tsx +4 -21
- package/src/demo/demo-start.tsx +8 -4
- package/src/feedback/feedback-attachment-labels.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +16 -29
- package/src/feedback/feedback-capture.ts +3 -1
- package/src/feedback/feedback-comment-thread.tsx +9 -6
- package/src/feedback/feedback-context.tsx +1 -4
- package/src/feedback/feedback-dialog.tsx +3 -4
- package/src/feedback/feedback-inbox.tsx +3 -9
- package/src/feedback/feedback-public-list.tsx +7 -1
- package/src/feedback/feedback-record.ts +6 -14
- package/src/feedback/feedback-replies.tsx +10 -5
- package/src/feedback/feedback-row-detail.tsx +2 -5
- package/src/feedback/feedback-status-undo.ts +1 -4
- package/src/feedback/feedback-submit-dialog.tsx +4 -4
- package/src/feedback/feedback-table.tsx +12 -8
- package/src/feedback/feedback-thread.tsx +4 -16
- package/src/feedback/feedback-vocabulary.ts +72 -82
- package/src/feedback/feedback-vote-button.tsx +9 -8
- package/src/hooks/use-anchored-rect.ts +1 -4
- package/src/hooks/use-document-title.ts +3 -1
- package/src/hooks/use-file-drop.ts +1 -3
- package/src/hooks/use-file-picker-labels.ts +1 -2
- package/src/hooks/use-file-picker.tsx +1 -5
- package/src/hooks/use-focus-trap.ts +1 -2
- package/src/hooks/use-overlay-history.ts +10 -5
- package/src/hooks/use-row-swipe.ts +1 -6
- package/src/i18n/defaults.ts +1 -4
- package/src/i18n/german.ts +33 -49
- package/src/i18n/kit-labels.tsx +5 -20
- package/src/i18n/languages.ts +2 -8
- package/src/i18n/locales/en.ts +11 -31
- package/src/i18n/locales/es.ts +35 -49
- package/src/i18n/locales/fr.ts +34 -51
- package/src/i18n/locales/hu.ts +26 -37
- package/src/i18n/locales/it.ts +35 -52
- package/src/i18n/locales/zh.ts +14 -32
- package/src/i18n/plurals.ts +4 -1
- package/src/i18n/review.ts +18 -7
- package/src/i18n/shared-labels.ts +1 -2
- package/src/index.ts +106 -32
- package/src/landing/access.ts +2 -7
- package/src/landing/landing-sections.tsx +14 -15
- package/src/landing/page-seo.ts +1 -3
- package/src/lib/calc.ts +4 -1
- package/src/lib/clipboard-minus.ts +4 -1
- package/src/lib/dates.ts +2 -10
- package/src/lib/decimal-marks.ts +1 -3
- package/src/lib/device-settings.ts +2 -1
- package/src/lib/direction.ts +1 -3
- package/src/lib/iban.ts +89 -9
- package/src/lib/logger.ts +1 -5
- package/src/lib/person-name.ts +1 -3
- package/src/lib/phone.ts +5 -8
- package/src/lib/qr-encode.ts +32 -8
- package/src/lib/session-refresh.ts +5 -6
- package/src/lib/table-text.ts +24 -6
- package/src/lib/text-match.ts +15 -3
- package/src/lib/translation-review.ts +1 -3
- package/src/notifications/notification-inbox.tsx +17 -4
- package/src/notifications/notification-parts.tsx +1 -2
- package/src/notifications/notification-types-setting.tsx +19 -18
- package/src/notifications/notifications-labels.ts +1 -2
- package/src/notifications/push-device-setting.tsx +7 -1
- package/src/notifications/push-devices-setting.tsx +7 -3
- package/src/notifications/push-worker.ts +10 -2
- package/src/notifications/quiet-hours-setting.tsx +4 -1
- package/src/notifications/use-push-device.ts +4 -1
- package/src/offline/pwa-update-prompt.tsx +7 -1
- package/src/offline/query-persistence.ts +9 -6
- package/src/offline.ts +6 -1
- package/src/rhf/fields.tsx +50 -19
- package/src/rhf/form-core.tsx +2 -6
- package/src/rhf/line-items.tsx +5 -7
- package/src/rhf/use-rhf-wizard-step.ts +3 -12
- package/src/search/command-palette.tsx +15 -23
- package/src/search/global-search.tsx +38 -8
- package/src/search/search-index.ts +5 -1
- package/src/settings/device-reset-setting.tsx +5 -8
- package/src/settings/settings-heading.tsx +3 -1
- package/src/settings/settings-layout.tsx +6 -13
- package/src/settings/use-settings-route.ts +4 -3
- package/src/shell/app-shell.tsx +48 -24
- package/src/shell/auth-layout.tsx +1 -6
- package/src/shell/dev-menu.tsx +12 -6
- package/src/shell/option-switcher-menu.tsx +1 -3
- package/src/shell/sync-status-indicator.tsx +14 -12
- package/src/shell/top-bar-parts-labels.ts +1 -3
- package/src/shell/topbar-action-menu.tsx +9 -8
- package/src/shell/topbar-controls.tsx +5 -19
- package/src/shell/tour-menu.tsx +9 -1
- package/src/sync/db.ts +5 -1
- package/src/sync/engine.ts +44 -12
- package/src/sync.ts +16 -2
- package/src/testing/css-variables.ts +1 -2
- package/src/testing/index.ts +1 -6
- package/src/testing/react.ts +1 -2
- package/src/testing/viewport.ts +9 -9
- package/src/theme/contrast-tokens.ts +13 -2
- package/src/theme/palette-derive.ts +24 -50
- package/src/theme/palette-presets.ts +0 -1
- package/src/theme/palette-store.ts +1 -4
- package/src/theme/text-size.ts +1 -1
- package/src/theme/theme-store.ts +2 -6
- package/src/tour/tour-catalogue.tsx +4 -2
- package/src/tour/tour-dock.ts +5 -1
- package/src/tour/tour-engine.ts +2 -6
- package/src/tour/tour.tsx +28 -12
- package/src/tour/use-tour-progress.ts +8 -2
- package/src/tour/use-tour-url-sync.ts +3 -1
- package/src/vite-env.d.ts +1 -4
- package/src/wizard/stepper-nav.tsx +10 -35
- package/src/wizard/types.ts +1 -4
- package/src/wizard/use-wizard.ts +28 -43
- package/src/wizard/validation.ts +1 -5
- package/src/wizard/wizard-context.tsx +2 -6
- package/src/wizard/wizard-step.tsx +1 -8
- package/src/wizard/wizard-summary.tsx +2 -6
- package/tokens.css +393 -152
|
@@ -8,12 +8,7 @@ import { splitTriggerAria } from "./trigger-aria.js";
|
|
|
8
8
|
import { FIELD_BASE } from "./field-base.js";
|
|
9
9
|
import { FieldBox, useFieldMessages } from "./field-parts.js";
|
|
10
10
|
import { Tooltip } from "./tooltip.js";
|
|
11
|
-
import {
|
|
12
|
-
DateField,
|
|
13
|
-
useDatePickerLabels,
|
|
14
|
-
useTriggerFormat,
|
|
15
|
-
withEndHint
|
|
16
|
-
} from "./date-field.js";
|
|
11
|
+
import { DateField, useDatePickerLabels, useTriggerFormat, withEndHint } from "./date-field.js";
|
|
17
12
|
import { PanelCalendar } from "./jump-calendar.js";
|
|
18
13
|
import { DateRangePicker } from "./date-range-picker.js";
|
|
19
14
|
const STEP_ROW_STACK_AT_LARGE = cn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/date-picker.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\nimport { CalendarClock, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { addDaysIso } from \"../lib/dates\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport { FIELD_BASE } from \"./field-base\";\nimport { FieldBox, useFieldMessages } from \"./field-parts\";\nimport { Tooltip } from \"./tooltip\";\nimport {\n DateField,\n useDatePickerLabels,\n useTriggerFormat,\n withEndHint,\n type DatePickerBaseProps,\n} from \"./date-field\";\nimport { PanelCalendar } from \"./jump-calendar\";\n// The shared field, the month-jump calendar and the range picker live in date-field.tsx,\n// jump-calendar.tsx and date-range-picker.tsx (MOD-9); their public names are still this\n// module's, so the barrel exports them as before.\nexport type { DateTriggerAttributes } from \"./date-field\";\nexport { DateRangePicker } from \"./date-range-picker\";\nexport type {\n DateRangeCommit,\n DateRangeDraftSummary,\n DateRangePickerPreset,\n DateRangePickerProps,\n DateRangeTriggerRenderProps,\n} from \"./date-range-picker\";\n\n/**\n * The ‹ date › (today) row on a phone at Large and Extra large (0.32.1, the 360 px\n * Extra-large sweep; docs/text-size-harmonization.md §4): the field takes the first line\n * on its own, whole and with all four corners, and the buttons share the line under it\n * as one joined strip. Three 2.5rem buttons are 180 px at 150 %, and on a 360 px phone\n * they left the field 84 px — \"Book… 0\" for \"Booking date 07/09/2026\" — where a 360 px\n * row would hold the date only by cutting it. `large:max-sm:`, the scaled phone width,\n * so a tablet or a desktop at Large keeps the one row, and Normal is unchanged.\n */\nconst STEP_ROW_STACK_AT_LARGE = cn(\n \"large:max-sm:flex-wrap large:max-sm:gap-y-2\",\n \"large:max-sm:[&>[data-slot=date-step-field]]:order-first large:max-sm:[&>[data-slot=date-step-field]]:basis-full\",\n \"large:max-sm:[&>[data-slot=date-step-field]]:ms-0 large:max-sm:[&_[role=combobox]]:rounded-md\",\n // The buttons (each in its tooltip's wrapper) share the line under the field.\n \"large:max-sm:[&>span]:flex-1 large:max-sm:[&>span>button]:w-full\",\n);\n\n// The focused part of the ‹ date › (today) row framed 2px wide, not 1 (keksdose live\n// #394, fourth round; kk K5). The row raises the focused part over the neighbour it\n// overlaps (`[&>*:focus-within]:z-20`), so all four of its sides are its own brand\n// border — but at a fractional device-pixel ratio a 1px line covers 1.25 device pixels\n// (at 125 %), and on whichever sides fall across a pixel boundary it is spread over two\n// pixels at partial strength: ‹'s left side and the date field's right read grey beside\n// the crisp ones (dark theme, measured: 82,87,146 against 139,147,248).\n//\n// A 1px INSET ring in the focus colour beside the 1px border doubles the line inward, so\n// every side keeps at least one fully covered device pixel at any scale — FIELD_INVALID's\n// border-plus-ring, for the same reason (live #295). A box-shadow, and inset: nothing\n// outside the part moves or is covered. `focus`, not `focus-visible`, as the row's\n// raise: a tap focuses too.\n//\n// 0.34 drew it here, on the parts — `focus:ring-1 focus:ring-inset` on each step and\n// `[&_[role=combobox]:focus]:…` from the row — and the K5 follow-up then moved that very\n// ring into FIELD_BASE for every field (keksdose run 78). Each part of this row is\n// FIELD_BASE, the steps directly and the trigger through FIELD_TRIGGER, so the copies went\n// (round 0.34 Wave 2). Not on every button in the row: keksdose's local fix\n// (`[&_button:focus]:ring-1 [&_button:focus]:ring-inset` on the row) also reached the\n// label's \"?\" and thinned its 2px focus frame to 1.\n\n/** One ‹ / › day-step button, sized to sit flush beside the field. */\nfunction StepButton({\n icon: Icon,\n label,\n disabled,\n onClick,\n mirror,\n className,\n}: {\n icon: ComponentType<{ className?: string }>;\n label: string;\n disabled: boolean;\n onClick: () => void;\n /** Flip the icon in RTL — for the ‹ › day steps, whose \"previous\" points to the\n * reading start. Not for the today icon, which has no direction. */\n mirror?: boolean;\n /** The button's corners in the joined group (see DatePicker). */\n className?: string;\n}) {\n return (\n <Tooltip label={label} portal>\n <button\n type=\"button\"\n aria-label={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n FIELD_BASE,\n // FIELD_BASE is `block w-full` for text inputs; a step button is neither, and\n // px-3 would make it wider than it needs to be. twMerge lets these win.\n \"relative flex h-full w-10 shrink-0 items-center justify-center px-0 text-muted\",\n \"hover:bg-hover hover:text-secondary\",\n // Dimmed ICON, not a dimmed box: at 40% opacity the whole button — border\n // included — faded out of the group and read as a broken, detached control.\n \"disabled:cursor-not-allowed disabled:text-placeholder disabled:opacity-60 disabled:hover:bg-surface\",\n // A part of a joined row: FIELD_BASE's 2px focus line, whole at 125 % (K5, the\n // note above this function).\n className,\n )}\n >\n <Icon className={cn(\"size-4\", mirror && \"rtl:-scale-x-100\")} />\n </button>\n </Tooltip>\n );\n}\n\nexport interface DatePickerProps extends DatePickerBaseProps {\n /** ISO \"YYYY-MM-DD\", or \"\" for empty. */\n value: string;\n onChange: (iso: string) => void;\n /**\n * Flank the field with ‹ › buttons that move the value one day. For fields whose\n * typical edit is a day or two — opening a calendar to go \"yesterday\" is three\n * interactions for what should be one.\n *\n * Off by default: on a filter or a far-away date the buttons are dead weight, and\n * they cost ~5rem of width that a narrow layout may not have.\n */\n step?: boolean;\n /** Accessible names for the step buttons. Optional: they resolve from\n * `datePicker.previousDay` / `nextDay` in `<UiKitProvider labels>`, then from the\n * English defaults. This prop wins over both, for the one field that says it\n * differently. */\n stepLabels?: { prev: string; next: string };\n /**\n * Today's date as ISO \"YYYY-MM-DD\". Adds a jump-to-today button beside the field,\n * disabled once the value already is that day.\n *\n * Passed in rather than read from a clock here: a component that decides what \"today\"\n * is cannot be tested at a fixed date, and the consumer already has one definition of\n * it. The button is always RENDERED — going flat instead of disappearing — because a\n * control that comes and goes with the value reflows whatever sits beside it, which\n * is the report this exists to answer.\n */\n today?: string;\n /** Accessible name for the today button; wins over `datePicker.today`. */\n todayLabel?: string;\n}\n\n/** Single-date picker: a field showing the formatted date, opening a calendar. */\nexport function DatePicker(props: DatePickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n step,\n stepLabels,\n today,\n todayLabel,\n className,\n calendarLabels,\n monthJump,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n hint,\n help,\n error,\n // Named so it reaches the field (and so its trigger) when a step/today row is the\n // root and takes the rest of the caller's props.\n \"aria-required\": ariaRequired,\n // The trigger's, wherever the rest go (API-3).\n ref,\n ...rest\n } = props;\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, stepLabels, todayLabel);\n const render = useTriggerFormat(\"datePicker\", locale, formatOptions, formatValue);\n // keksdose K4: the caption and the error under the field, attached to the TRIGGER\n // (the element a screen reader is on) after any description of the caller's own.\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n invalid,\n describedBy: rest[\"aria-describedby\"],\n ariaInvalid: rest[\"aria-invalid\"],\n });\n // With a step or a today button beside it the field is no longer the outermost\n // element — the flex row at the bottom is, and a caller's attributes belong on\n // whichever of the two is actually the root. Naming the shared props rather than\n // letting `...rest` carry them is what makes that choice possible.\n const wrapped = Boolean(step || today);\n const [triggerAria, wrapperRest] = splitTriggerAria(rest);\n const field = (\n <DateField\n // With step/today buttons the flex row is the root and takes the caller's props —\n // except the ones that name the field, which still belong on its trigger.\n {...(wrapped ? triggerAria : rest)}\n fieldRef={ref}\n aria-describedby={messages.describedBy}\n aria-required={ariaRequired}\n label={label}\n labelHint={messages.labelHint}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.panel}\n disabled={disabled}\n invalid={messages.isInvalid}\n className={step ? \"min-w-0 flex-1\" : className}\n // Named for STEP_ROW_STACK_AT_LARGE; only in the row, so a caller's own\n // `data-slot` on a bare field is left alone.\n {...(wrapped ? { \"data-slot\": \"date-step-field\" } : {})}\n hasValue={Boolean(value)}\n triggerText={value ? render(value) : (placeholder ?? \"\")}\n onClear={() => onChange(\"\")}\n >\n {(close) => (\n <PanelCalendar\n monthJump={monthJump}\n mode=\"single\"\n focusOnOpen\n from={value}\n to={value}\n locale={locale}\n min={min}\n max={max}\n labels={calendarLabels}\n onSelect={(iso) => {\n onChange(iso);\n close();\n }}\n />\n )}\n </DateField>\n );\n // Passed at all — even as `undefined` — the field keeps its box; see FieldBox.\n const reserve = \"error\" in props || \"hint\" in props;\n const endHint = label === undefined ? messages.labelHint : undefined;\n if (!wrapped) {\n return (\n <FieldBox below={messages.below} reserve={reserve}>\n {withEndHint(field, endHint)}\n </FieldBox>\n );\n }\n\n // An empty field has nothing to step from, so both buttons are dead until a date\n // is picked. Bounds are compared as strings: \"YYYY-MM-DD\" sorts chronologically.\n const target = (days: number) => (value ? addDaysIso(value, days) : \"\");\n const outOfBounds = (iso: string) => Boolean((min && iso < min) || (max && iso > max));\n const blocked = (days: number) => {\n const next = target(days);\n if (!next || disabled) return true;\n return outOfBounds(next);\n };\n const row = (\n // ONE joined control, not three boxes in a row: the buttons and the field share\n // their borders (each piece overlaps the previous by a pixel) and only the outer\n // ends are rounded — the button-group shape, so ‹ date › reads as one field with\n // controls rather than a field with loose buttons parked beside it, which is what\n // the `gap-1` version looked like.\n //\n // items-stretch, not items-center: the buttons match the field's height, which\n // varies with whether it carries a floating label. The field's trigger is nested\n // inside DateField, so its inner corners are squared from here.\n <div\n {...wrapperRest}\n className={cn(\n \"flex items-stretch [&>*:not(:first-child)]:-ms-px\",\n step && \"[&_[role=combobox]]:rounded-s-none\",\n (step || today) && \"[&_[role=combobox]]:rounded-e-none\",\n // The PART that is focused, pressed or invalid is raised over the neighbour it\n // overlaps by a pixel, so its blue (or red) border shows on all four sides\n // (keksdose live #394). `:focus-within`, not `:focus-visible`: a tap focuses\n // without it, as does the sheet handing focus back, and the next part — later in\n // the DOM — painted its grey left edge over the tapped one's right. Raising the\n // whole part (each is `relative`) keeps the floating label and the icon above\n // the trigger, which raising the trigger alone covered. Hover raises nothing: it\n // changes no border, and would let a hovered › cover a focused field's edge.\n \"[&>*:focus-within]:z-20 [&>*:active]:z-20 [&>*:has([aria-invalid=true])]:z-10\",\n // …and its line is 2px, so no side of it reads half-strength at 125 % (live #394,\n // fourth round): FIELD_BASE's inset ring, which every part of the row carries.\n STEP_ROW_STACK_AT_LARGE,\n className,\n )}\n >\n {step && (\n <StepButton\n icon={ChevronLeft}\n mirror\n label={text.previousDay}\n disabled={blocked(-1)}\n onClick={() => onChange(target(-1))}\n className=\"rounded-e-none\"\n />\n )}\n {field}\n {step && (\n <StepButton\n icon={ChevronRight}\n mirror\n label={text.nextDay}\n disabled={blocked(1)}\n onClick={() => onChange(target(1))}\n className={cn(\"rounded-s-none\", today && \"rounded-e-none\")}\n />\n )}\n {today && (\n <StepButton\n icon={CalendarClock}\n label={text.today}\n disabled={Boolean(disabled) || value === today || outOfBounds(today)}\n onClick={() => onChange(today)}\n className=\"rounded-s-none\"\n />\n )}\n </div>\n );\n // Under the whole row, not under the field between the buttons: the message is about\n // the date, and the steppers are part of how it is entered.\n return (\n <FieldBox below={messages.below} reserve={reserve}>\n {withEndHint(row, endHint)}\n </FieldBox>\n );\n}\n"],"mappings":";AA6GQ,cAoKJ,YApKI;AA5GR,SAAS,eAAe,aAAa,oBAAoB;AACzD,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAC3B,SAAS,UAAU,wBAAwB;AAC3C,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,qBAAqB;AAK9B,SAAS,uBAAuB;AAkBhC,MAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAyBA,SAAS,WAAW;AAAA,EAClB,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUG;AACD,SACE,oBAAC,WAAQ,OAAc,QAAM,MAC3B;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA;AAAA;AAAA,QAGA;AAAA;AAAA;AAAA,QAGA;AAAA,MACF;AAAA,MAEA,8BAAC,QAAK,WAAW,GAAG,UAAU,UAAU,kBAAkB,GAAG;AAAA;AAAA,EAC/D,GACF;AAEJ;AAoCO,SAAS,WAAW,OAAwB;AACjD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA,iBAAiB;AAAA;AAAA,IAEjB;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,OAAO,oBAAoB,YAAY,YAAY,UAAU;AACnE,QAAM,SAAS,iBAAiB,cAAc,QAAQ,eAAe,WAAW;AAGhF,QAAM,WAAW,iBAAiB;AAAA,IAChC;AAAA,IACA;AAAA,IACA,MAAM,SAAS,KAAK,YAAY;AAAA,IAChC;AAAA,IACA;AAAA,IACA,aAAa,KAAK,kBAAkB;AAAA,IACpC,aAAa,KAAK,cAAc;AAAA,EAClC,CAAC;AAKD,QAAM,UAAU,QAAQ,QAAQ,KAAK;AACrC,QAAM,CAAC,aAAa,WAAW,IAAI,iBAAiB,IAAI;AACxD,QAAM,QACJ;AAAA,IAAC;AAAA;AAAA,MAGE,GAAI,UAAU,cAAc;AAAA,MAC7B,UAAU;AAAA,MACV,oBAAkB,SAAS;AAAA,MAC3B,iBAAe;AAAA,MACf;AAAA,MACA,WAAW,SAAS;AAAA,MACpB;AAAA,MACA,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB;AAAA,MACA,SAAS,SAAS;AAAA,MAClB,WAAW,OAAO,mBAAmB;AAAA,MAGpC,GAAI,UAAU,EAAE,aAAa,kBAAkB,IAAI,CAAC;AAAA,MACrD,UAAU,QAAQ,KAAK;AAAA,MACvB,aAAa,QAAQ,OAAO,KAAK,IAAK,eAAe;AAAA,MACrD,SAAS,MAAM,SAAS,EAAE;AAAA,MAEzB,WAAC,UACA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,aAAW;AAAA,UACX,MAAM;AAAA,UACN,IAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,UAAU,CAAC,QAAQ;AACjB,qBAAS,GAAG;AACZ,kBAAM;AAAA,UACR;AAAA;AAAA,MACF;AAAA;AAAA,EAEJ;AAGF,QAAM,UAAU,WAAW,SAAS,UAAU;AAC9C,QAAM,UAAU,UAAU,SAAY,SAAS,YAAY;AAC3D,MAAI,CAAC,SAAS;AACZ,WACE,oBAAC,YAAS,OAAO,SAAS,OAAO,SAC9B,sBAAY,OAAO,OAAO,GAC7B;AAAA,EAEJ;AAIA,QAAM,SAAS,CAAC,SAAkB,QAAQ,WAAW,OAAO,IAAI,IAAI;AACpE,QAAM,cAAc,CAAC,QAAgB,QAAS,OAAO,MAAM,OAAS,OAAO,MAAM,GAAI;AACrF,QAAM,UAAU,CAAC,SAAiB;AAChC,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,CAAC,QAAQ,SAAU,QAAO;AAC9B,WAAO,YAAY,IAAI;AAAA,EACzB;AACA,QAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUJ;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA,QAAQ;AAAA,WACP,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UASnB;AAAA;AAAA;AAAA,UAGA;AAAA,UACA;AAAA,QACF;AAAA,QAEC;AAAA,kBACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,QAAM;AAAA,cACN,OAAO,KAAK;AAAA,cACZ,UAAU,QAAQ,EAAE;AAAA,cACpB,SAAS,MAAM,SAAS,OAAO,EAAE,CAAC;AAAA,cAClC,WAAU;AAAA;AAAA,UACZ;AAAA,UAED;AAAA,UACA,QACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,QAAM;AAAA,cACN,OAAO,KAAK;AAAA,cACZ,UAAU,QAAQ,CAAC;AAAA,cACnB,SAAS,MAAM,SAAS,OAAO,CAAC,CAAC;AAAA,cACjC,WAAW,GAAG,kBAAkB,SAAS,gBAAgB;AAAA;AAAA,UAC3D;AAAA,UAED,SACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,OAAO,KAAK;AAAA,cACZ,UAAU,QAAQ,QAAQ,KAAK,UAAU,SAAS,YAAY,KAAK;AAAA,cACnE,SAAS,MAAM,SAAS,KAAK;AAAA,cAC7B,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,IAEJ;AAAA;AAIF,SACE,oBAAC,YAAS,OAAO,SAAS,OAAO,SAC9B,sBAAY,KAAK,OAAO,GAC3B;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/date-picker.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\nimport { CalendarClock, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { addDaysIso } from \"../lib/dates\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { splitTriggerAria } from \"./trigger-aria\";\nimport { FIELD_BASE } from \"./field-base\";\nimport { FieldBox, useFieldMessages } from \"./field-parts\";\nimport { Tooltip } from \"./tooltip\";\nimport { DateField, useDatePickerLabels, useTriggerFormat, withEndHint, type DatePickerBaseProps } from \"./date-field\";\nimport { PanelCalendar } from \"./jump-calendar\";\n// The shared field, the month-jump calendar and the range picker live in date-field.tsx,\n// jump-calendar.tsx and date-range-picker.tsx (MOD-9); their public names are still this\n// module's, so the barrel exports them as before.\nexport type { DateTriggerAttributes } from \"./date-field\";\nexport { DateRangePicker } from \"./date-range-picker\";\nexport type {\n DateRangeCommit,\n DateRangeDraftSummary,\n DateRangePickerPreset,\n DateRangePickerProps,\n DateRangeTriggerRenderProps,\n} from \"./date-range-picker\";\n\n/**\n * The ‹ date › (today) row on a phone at Large and Extra large (0.32.1, the 360 px\n * Extra-large sweep; docs/text-size-harmonization.md §4): the field takes the first line\n * on its own, whole and with all four corners, and the buttons share the line under it\n * as one joined strip. Three 2.5rem buttons are 180 px at 150 %, and on a 360 px phone\n * they left the field 84 px — \"Book… 0\" for \"Booking date 07/09/2026\" — where a 360 px\n * row would hold the date only by cutting it. `large:max-sm:`, the scaled phone width,\n * so a tablet or a desktop at Large keeps the one row, and Normal is unchanged.\n */\nconst STEP_ROW_STACK_AT_LARGE = cn(\n \"large:max-sm:flex-wrap large:max-sm:gap-y-2\",\n \"large:max-sm:[&>[data-slot=date-step-field]]:order-first large:max-sm:[&>[data-slot=date-step-field]]:basis-full\",\n \"large:max-sm:[&>[data-slot=date-step-field]]:ms-0 large:max-sm:[&_[role=combobox]]:rounded-md\",\n // The buttons (each in its tooltip's wrapper) share the line under the field.\n \"large:max-sm:[&>span]:flex-1 large:max-sm:[&>span>button]:w-full\",\n);\n\n// The focused part of the ‹ date › (today) row framed 2px wide, not 1 (keksdose live\n// #394, fourth round; kk K5). The row raises the focused part over the neighbour it\n// overlaps (`[&>*:focus-within]:z-20`), so all four of its sides are its own brand\n// border — but at a fractional device-pixel ratio a 1px line covers 1.25 device pixels\n// (at 125 %), and on whichever sides fall across a pixel boundary it is spread over two\n// pixels at partial strength: ‹'s left side and the date field's right read grey beside\n// the crisp ones (dark theme, measured: 82,87,146 against 139,147,248).\n//\n// A 1px INSET ring in the focus colour beside the 1px border doubles the line inward, so\n// every side keeps at least one fully covered device pixel at any scale — FIELD_INVALID's\n// border-plus-ring, for the same reason (live #295). A box-shadow, and inset: nothing\n// outside the part moves or is covered. `focus`, not `focus-visible`, as the row's\n// raise: a tap focuses too.\n//\n// 0.34 drew it here, on the parts — `focus:ring-1 focus:ring-inset` on each step and\n// `[&_[role=combobox]:focus]:…` from the row — and the K5 follow-up then moved that very\n// ring into FIELD_BASE for every field (keksdose run 78). Each part of this row is\n// FIELD_BASE, the steps directly and the trigger through FIELD_TRIGGER, so the copies went\n// (round 0.34 Wave 2). Not on every button in the row: keksdose's local fix\n// (`[&_button:focus]:ring-1 [&_button:focus]:ring-inset` on the row) also reached the\n// label's \"?\" and thinned its 2px focus frame to 1.\n\n/** One ‹ / › day-step button, sized to sit flush beside the field. */\nfunction StepButton({\n icon: Icon,\n label,\n disabled,\n onClick,\n mirror,\n className,\n}: {\n icon: ComponentType<{ className?: string }>;\n label: string;\n disabled: boolean;\n onClick: () => void;\n /** Flip the icon in RTL — for the ‹ › day steps, whose \"previous\" points to the\n * reading start. Not for the today icon, which has no direction. */\n mirror?: boolean;\n /** The button's corners in the joined group (see DatePicker). */\n className?: string;\n}) {\n return (\n <Tooltip label={label} portal>\n <button\n type=\"button\"\n aria-label={label}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n FIELD_BASE,\n // FIELD_BASE is `block w-full` for text inputs; a step button is neither, and\n // px-3 would make it wider than it needs to be. twMerge lets these win.\n \"relative flex h-full w-10 shrink-0 items-center justify-center px-0 text-muted\",\n \"hover:bg-hover hover:text-secondary\",\n // Dimmed ICON, not a dimmed box: at 40% opacity the whole button — border\n // included — faded out of the group and read as a broken, detached control.\n \"disabled:cursor-not-allowed disabled:text-placeholder disabled:opacity-60 disabled:hover:bg-surface\",\n // A part of a joined row: FIELD_BASE's 2px focus line, whole at 125 % (K5, the\n // note above this function).\n className,\n )}\n >\n <Icon className={cn(\"size-4\", mirror && \"rtl:-scale-x-100\")} />\n </button>\n </Tooltip>\n );\n}\n\nexport interface DatePickerProps extends DatePickerBaseProps {\n /** ISO \"YYYY-MM-DD\", or \"\" for empty. */\n value: string;\n onChange: (iso: string) => void;\n /**\n * Flank the field with ‹ › buttons that move the value one day. For fields whose\n * typical edit is a day or two — opening a calendar to go \"yesterday\" is three\n * interactions for what should be one.\n *\n * Off by default: on a filter or a far-away date the buttons are dead weight, and\n * they cost ~5rem of width that a narrow layout may not have.\n */\n step?: boolean;\n /** Accessible names for the step buttons. Optional: they resolve from\n * `datePicker.previousDay` / `nextDay` in `<UiKitProvider labels>`, then from the\n * English defaults. This prop wins over both, for the one field that says it\n * differently. */\n stepLabels?: { prev: string; next: string };\n /**\n * Today's date as ISO \"YYYY-MM-DD\". Adds a jump-to-today button beside the field,\n * disabled once the value already is that day.\n *\n * Passed in rather than read from a clock here: a component that decides what \"today\"\n * is cannot be tested at a fixed date, and the consumer already has one definition of\n * it. The button is always RENDERED — going flat instead of disappearing — because a\n * control that comes and goes with the value reflows whatever sits beside it, which\n * is the report this exists to answer.\n */\n today?: string;\n /** Accessible name for the today button; wins over `datePicker.today`. */\n todayLabel?: string;\n}\n\n/** Single-date picker: a field showing the formatted date, opening a calendar. */\nexport function DatePicker(props: DatePickerProps) {\n const {\n value,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n step,\n stepLabels,\n today,\n todayLabel,\n className,\n calendarLabels,\n monthJump,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n hint,\n help,\n error,\n // Named so it reaches the field (and so its trigger) when a step/today row is the\n // root and takes the rest of the caller's props.\n \"aria-required\": ariaRequired,\n // The trigger's, wherever the rest go (API-3).\n ref,\n ...rest\n } = props;\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, stepLabels, todayLabel);\n const render = useTriggerFormat(\"datePicker\", locale, formatOptions, formatValue);\n // keksdose K4: the caption and the error under the field, attached to the TRIGGER\n // (the element a screen reader is on) after any description of the caller's own.\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n invalid,\n describedBy: rest[\"aria-describedby\"],\n ariaInvalid: rest[\"aria-invalid\"],\n });\n // With a step or a today button beside it the field is no longer the outermost\n // element — the flex row at the bottom is, and a caller's attributes belong on\n // whichever of the two is actually the root. Naming the shared props rather than\n // letting `...rest` carry them is what makes that choice possible.\n const wrapped = Boolean(step || today);\n const [triggerAria, wrapperRest] = splitTriggerAria(rest);\n const field = (\n <DateField\n // With step/today buttons the flex row is the root and takes the caller's props —\n // except the ones that name the field, which still belong on its trigger.\n {...(wrapped ? triggerAria : rest)}\n fieldRef={ref}\n aria-describedby={messages.describedBy}\n aria-required={ariaRequired}\n label={label}\n labelHint={messages.labelHint}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.panel}\n disabled={disabled}\n invalid={messages.isInvalid}\n className={step ? \"min-w-0 flex-1\" : className}\n // Named for STEP_ROW_STACK_AT_LARGE; only in the row, so a caller's own\n // `data-slot` on a bare field is left alone.\n {...(wrapped ? { \"data-slot\": \"date-step-field\" } : {})}\n hasValue={Boolean(value)}\n triggerText={value ? render(value) : (placeholder ?? \"\")}\n onClear={() => onChange(\"\")}\n >\n {(close) => (\n <PanelCalendar\n monthJump={monthJump}\n mode=\"single\"\n focusOnOpen\n from={value}\n to={value}\n locale={locale}\n min={min}\n max={max}\n labels={calendarLabels}\n onSelect={(iso) => {\n onChange(iso);\n close();\n }}\n />\n )}\n </DateField>\n );\n // Passed at all — even as `undefined` — the field keeps its box; see FieldBox.\n const reserve = \"error\" in props || \"hint\" in props;\n const endHint = label === undefined ? messages.labelHint : undefined;\n if (!wrapped) {\n return (\n <FieldBox below={messages.below} reserve={reserve}>\n {withEndHint(field, endHint)}\n </FieldBox>\n );\n }\n\n // An empty field has nothing to step from, so both buttons are dead until a date\n // is picked. Bounds are compared as strings: \"YYYY-MM-DD\" sorts chronologically.\n const target = (days: number) => (value ? addDaysIso(value, days) : \"\");\n const outOfBounds = (iso: string) => Boolean((min && iso < min) || (max && iso > max));\n const blocked = (days: number) => {\n const next = target(days);\n if (!next || disabled) return true;\n return outOfBounds(next);\n };\n const row = (\n // ONE joined control, not three boxes in a row: the buttons and the field share\n // their borders (each piece overlaps the previous by a pixel) and only the outer\n // ends are rounded — the button-group shape, so ‹ date › reads as one field with\n // controls rather than a field with loose buttons parked beside it, which is what\n // the `gap-1` version looked like.\n //\n // items-stretch, not items-center: the buttons match the field's height, which\n // varies with whether it carries a floating label. The field's trigger is nested\n // inside DateField, so its inner corners are squared from here.\n <div\n {...wrapperRest}\n className={cn(\n \"flex items-stretch [&>*:not(:first-child)]:-ms-px\",\n step && \"[&_[role=combobox]]:rounded-s-none\",\n (step || today) && \"[&_[role=combobox]]:rounded-e-none\",\n // The PART that is focused, pressed or invalid is raised over the neighbour it\n // overlaps by a pixel, so its blue (or red) border shows on all four sides\n // (keksdose live #394). `:focus-within`, not `:focus-visible`: a tap focuses\n // without it, as does the sheet handing focus back, and the next part — later in\n // the DOM — painted its grey left edge over the tapped one's right. Raising the\n // whole part (each is `relative`) keeps the floating label and the icon above\n // the trigger, which raising the trigger alone covered. Hover raises nothing: it\n // changes no border, and would let a hovered › cover a focused field's edge.\n \"[&>*:focus-within]:z-20 [&>*:active]:z-20 [&>*:has([aria-invalid=true])]:z-10\",\n // …and its line is 2px, so no side of it reads half-strength at 125 % (live #394,\n // fourth round): FIELD_BASE's inset ring, which every part of the row carries.\n STEP_ROW_STACK_AT_LARGE,\n className,\n )}\n >\n {step && (\n <StepButton\n icon={ChevronLeft}\n mirror\n label={text.previousDay}\n disabled={blocked(-1)}\n onClick={() => onChange(target(-1))}\n className=\"rounded-e-none\"\n />\n )}\n {field}\n {step && (\n <StepButton\n icon={ChevronRight}\n mirror\n label={text.nextDay}\n disabled={blocked(1)}\n onClick={() => onChange(target(1))}\n className={cn(\"rounded-s-none\", today && \"rounded-e-none\")}\n />\n )}\n {today && (\n <StepButton\n icon={CalendarClock}\n label={text.today}\n disabled={Boolean(disabled) || value === today || outOfBounds(today)}\n onClick={() => onChange(today)}\n className=\"rounded-s-none\"\n />\n )}\n </div>\n );\n // Under the whole row, not under the field between the buttons: the message is about\n // the date, and the steppers are part of how it is entered.\n return (\n <FieldBox below={messages.below} reserve={reserve}>\n {withEndHint(row, endHint)}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/date-range-picker.tsx"],"sourcesContent":["// The two-date range picker: presets beside the calendar, `commit=\"apply\"` drafts, a\n// custom trigger. Moved out of date-picker.tsx as it was (MOD-9, refactor audit\n// 2026-10); date-picker still exports every public name here.\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLocale, type DatePickerLabels } from \"../i18n/kit-labels\";\nimport { Button } from \"./button\";\nimport { FieldBox, useFieldMessages } from \"./field-parts\";\nimport { usePhoneLayout } from \"../hooks/use-breakpoint\";\nimport {\n DateField,\n useDatePickerLabels,\n useTriggerFormat,\n withEndHint,\n type DatePickerBaseProps,\n type DateTriggerAttributes,\n type PanelFrame,\n} from \"./date-field\";\nimport { PanelCalendar, type PanelCalendarProps } from \"./jump-calendar\";\n\nexport interface DateRangePickerPreset {\n /**\n * The preset's identity, independent of its dates — `\"last_3_months\"`, not two ISO\n * strings. Optional for the 0.x callers that pass anonymous pairs.\n *\n * keksdose's report range field (report-range-field.tsx, \"Why this is app-level\")\n * is why it exists: its URL stores `?preset=last_3_months` and recomputes the dates\n * from today on every read, so a preset that is only its dates froze \"last 3 months\"\n * at whatever day the panel rendered, and two presets that happen to cover the same\n * days (this week and today, on a Monday) could not be told apart. With an `id` the\n * choice is reported to `onChange` and shown as selected for as long as the range\n * still matches it. Recompute `from`/`to` on every render (`calendarMonthPresets()`,\n * `dateRangePresets()`) and pass the key as the id.\n */\n id?: string;\n label: ReactNode;\n from: string;\n to: string;\n}\n\n/**\n * When the picker hands its range to `onChange`.\n *\n * - `\"immediate\"` (the default, and all of 0.7): a preset click commits and closes,\n * and each calendar click is reported as it happens — the half-made `(from, \"\")`\n * included. The right shape for quick ranges on a filter, where one click is one\n * answer.\n * - `\"apply\"`: the panel works on a DRAFT. A preset ARMS the calendar instead of\n * committing, days can then be nudged (\"last 3 months, but end it last Friday\"),\n * and nothing reaches `onChange` until Apply — which stays disabled until both ends\n * exist. Cancel, Escape and an outside click discard the draft, and every open\n * starts again from the committed value. keksdose's report range asked for exactly\n * this: each commit there re-runs a whole page of charts, so an intermediate state\n * must never escape the panel.\n */\nexport type DateRangeCommit = \"immediate\" | \"apply\";\n\nexport interface DateRangePickerProps extends DatePickerBaseProps {\n from: string;\n to: string;\n /** The committed range. `presetId` is the chosen preset's `id` when a preset\n * produced it (and the range was not nudged since), `undefined` for days picked by\n * hand. A 0.x two-argument handler simply ignores it. */\n onChange: (from: string, to: string, presetId?: string) => void;\n /** Separator between the two formatted dates in the trigger (default \" – \"). */\n separator?: string;\n /** Optional named ranges rendered as a start-side column (last month / YTD / …). */\n presets?: DateRangePickerPreset[];\n /**\n * The active preset's `id`, controlled — e.g. straight from keksdose's `?preset=`.\n * `null` (or an id no preset has, such as `\"custom\"`) says \"no preset\": nothing in\n * the column is marked even if a preset happens to cover the same days. A preset is\n * marked only while its dates still equal `from`/`to`.\n *\n * Leave it `undefined` and the picker remembers the last preset it committed\n * itself; with no memory at all it falls back to the first preset whose dates match.\n */\n preset?: string | null;\n /** See {@link DateRangeCommit}. Default `\"immediate\"`. */\n commit?: DateRangeCommit;\n /**\n * Draw the trigger yourself. keksdose's report range field\n * (reports/report-range-field.tsx, \"Why this is app-level\") is why: its trigger\n * NAMES the active preset (\"Last 3 months\", the window as a muted suffix) where the\n * kit's shows two dates, sits inside a Tooltip, and is squared off on one side so the\n * granularity control can join it flush at the field's 42px.\n *\n * Spread `props.triggerProps` onto a `<button>` whole and put `props.valueProps` on\n * the element that shows the value — see {@link DateTriggerAttributes} for what each\n * part is for. The field around it (label, hidden name twin, clear/calendar glyph,\n * popover or phone sheet) is unchanged.\n */\n renderTrigger?: (props: DateRangeTriggerRenderProps) => ReactNode;\n /**\n * In the phone layout (`usePhoneLayout()`) the panel opens as a full-screen sheet (`FullBleedDialog`)\n * with presets in a grid above the calendar and Apply pinned in the footer, instead\n * of the 440px popover that hung off a 360px screen (keksdose's report range field,\n * which hand-rolled exactly this). This is its `backCloses`: on by default, so Back\n * dismisses the sheet; off for a caller whose commit rewrites the URL with\n * `replaceState` in the same tick — keksdose's `?preset/from/to` — where the router\n * would overwrite the sheet's history marker.\n */\n sheetBackCloses?: boolean;\n /**\n * With `commit=\"apply\"`: a line above the calendar saying what Apply would commit —\n * keksdose's report range writes \"Custom · 1 Mar 2026 – …\" there\n * (report-range-field.tsx), the one place a preset armed and then nudged by a day is\n * seen to have become \"Custom\", and a half-made range to still lack its end. Called\n * with the DRAFT on every change, in the popover and the phone sheet alike; `preset`\n * is the preset the column marks for it (none once a day was picked by hand), `to` is\n * `\"\"` until the second click. The line is a polite live region, so a screen reader\n * hears the draft change as the calendar is used.\n *\n * Ignored in `\"immediate\"` mode, where there is no draft: every click is the value.\n */\n renderDraftSummary?: (draft: DateRangeDraftSummary) => ReactNode;\n}\n\n/** What {@link DateRangePickerProps.renderDraftSummary} is called with. */\nexport interface DateRangeDraftSummary {\n from: string;\n to: string;\n /** The preset the column marks for the draft, if any. */\n preset: DateRangePickerPreset | undefined;\n}\n\n/** What {@link DateRangePickerProps.renderTrigger} is called with. */\nexport interface DateRangeTriggerRenderProps {\n /** Spread onto the `<button>`, whole. Merge your class after `triggerProps.className`. */\n triggerProps: DateTriggerAttributes;\n /** Put on the element showing the value: the trigger is named \"label, value\" by\n * reference to this id. Without it the name is the label alone. */\n valueProps: { id: string };\n open: boolean;\n from: string;\n to: string;\n /** The preset the column marks for the committed range, if any — the one to name. */\n preset: DateRangePickerPreset | undefined;\n /** What the kit's own trigger would show: the formatted range, or the placeholder. */\n text: string;\n}\n\ninterface RangeDraft {\n from: string;\n to: string;\n presetId: string | undefined;\n}\n\n/** Which preset (by index) the column marks, or -1. See `DateRangePickerProps.preset`. */\nfunction markedPreset(\n presets: readonly DateRangePickerPreset[],\n from: string,\n to: string,\n activeId: string | null | undefined,\n controlled: boolean,\n): number {\n const matches = (p: DateRangePickerPreset) => p.from === from && p.to === to;\n if (activeId != null) {\n const byId = presets.findIndex((p) => p.id === activeId && matches(p));\n if (byId !== -1 || controlled) return byId;\n } else if (controlled) {\n return -1;\n }\n return presets.findIndex(matches);\n}\n\n/**\n * The preset column: a named group of toggle buttons, `aria-pressed` on the marked one.\n * Pressed-state rather than colour alone, because \"which range is this\" is the one\n * thing a screen-reader user opening the panel needs to hear.\n */\nfunction PresetColumn({\n presets,\n marked,\n label,\n onPick,\n sheet,\n}: {\n presets: readonly DateRangePickerPreset[];\n marked: number;\n label: string;\n onPick: (p: DateRangePickerPreset) => void;\n /** The phone sheet's shape: a two-column grid ABOVE the calendar with 44px rows —\n * a 128px column beside a month grid leaves the grid ~200px on a 360px screen. */\n sheet?: boolean;\n}) {\n return (\n // `border-e`/`pe`, not `-r`: the preset column is on the START side, and the rule\n // between it and the calendar has to follow it in a right-to-left UI.\n <div\n role=\"group\"\n aria-label={label}\n className={\n sheet\n ? \"grid grid-cols-2 gap-0.5\"\n : \"flex w-32 shrink-0 flex-col gap-0.5 border-e border-subtle pe-2\"\n }\n >\n {presets.map((p, i) => {\n const selected = i === marked;\n return (\n <button\n key={p.id ?? i}\n type=\"button\"\n aria-pressed={selected}\n data-preset={p.id}\n onClick={() => onPick(p)}\n className={cn(\n \"rounded px-2 text-start\",\n // 44px on a phone: the kit's touch-target size (`SHEET_ROW_CLASS`).\n sheet ? \"min-h-11 py-2 text-sm\" : \"py-1.5 text-xs\",\n selected\n ? \"bg-active font-medium text-primary\"\n : \"text-secondary hover:bg-hover\",\n )}\n >\n {p.label}\n </button>\n );\n })}\n </div>\n );\n}\n\n/**\n * The panel's body. A component of its own so `commit=\"apply\"` can keep its draft in\n * state that is born when the panel opens: `Popover` unmounts its children on close,\n * so the draft is re-seeded from the committed value on every open, and an abandoned\n * half-selection cannot survive into the next one.\n */\nfunction RangePanel({\n from,\n to,\n presets,\n activeId,\n controlled,\n commit,\n close,\n frame,\n sheet,\n commitRange,\n calendarProps,\n labels,\n renderDraftSummary,\n}: {\n from: string;\n to: string;\n presets: readonly DateRangePickerPreset[] | undefined;\n activeId: string | null | undefined;\n controlled: boolean;\n commit: DateRangeCommit;\n close: () => void;\n /** Where the body and the Apply row go — see `PanelFrame`. */\n frame: PanelFrame;\n sheet: boolean;\n commitRange: (from: string, to: string, presetId: string | undefined) => void;\n calendarProps: Pick<PanelCalendarProps, \"locale\" | \"min\" | \"max\" | \"labels\" | \"monthJump\">;\n labels: DatePickerLabels;\n renderDraftSummary?: DateRangePickerProps[\"renderDraftSummary\"];\n}) {\n const drafting = commit === \"apply\";\n const [draft, setDraft] = useState<RangeDraft>({\n from,\n to,\n presetId: activeId ?? undefined,\n });\n // What the panel SHOWS: the draft while drafting, the committed value otherwise.\n const shown = drafting ? draft : { from, to, presetId: activeId ?? undefined };\n const marked = presets\n ? markedPreset(presets, shown.from, shown.to, drafting ? draft.presetId : activeId, drafting || controlled)\n : -1;\n const complete = Boolean(draft.from && draft.to);\n\n const summary =\n drafting && renderDraftSummary\n ? renderDraftSummary({ from: draft.from, to: draft.to, preset: presets?.[marked] })\n : null;\n\n const picker = (\n <PanelCalendar\n {...calendarProps}\n focusOnOpen\n from={shown.from}\n to={shown.to}\n onSelect={(f, t) => {\n if (drafting) {\n setDraft({ from: f, to: t, presetId: undefined });\n return;\n }\n commitRange(f, t, undefined);\n // Two-click range: only dismiss once both ends are chosen.\n if (f && t) close();\n }}\n />\n );\n // The summary heads the CALENDAR's column, not the panel: beside a preset column it\n // reads as the calendar's caption, and in the sheet it sits between the presets and\n // the month it describes. `null` renders no line — a caller may opt out per draft —\n // but keeps the wrapper, so the calendar is not remounted (and its month and focus\n // lost) when a line comes or goes.\n const calendar =\n drafting && renderDraftSummary ? (\n <div className=\"flex flex-col gap-2\">\n {summary != null && (\n <div aria-live=\"polite\" data-draft-summary=\"\" className=\"text-xs text-muted\">\n {summary}\n </div>\n )}\n {picker}\n </div>\n ) : (\n picker\n );\n\n const body =\n presets && presets.length > 0 ? (\n <div className={sheet ? \"flex flex-col gap-3\" : \"flex gap-3\"}>\n <PresetColumn\n sheet={sheet}\n presets={presets}\n marked={marked}\n label={labels.presets}\n onPick={(p) => {\n if (drafting) {\n setDraft({ from: p.from, to: p.to, presetId: p.id });\n return;\n }\n commitRange(p.from, p.to, p.id);\n close();\n }}\n />\n {/* `flex-1`: the calendar takes the rest of the panel. Content-sized it was\n seven tiny cells beside a wide preset column. */}\n <div className=\"min-w-0 flex-1\">{calendar}</div>\n </div>\n ) : (\n calendar\n );\n\n if (!drafting) return frame(body);\n return frame(\n body,\n <>\n {/* `flex-1` in the sheet: two thumb-wide halves of the footer, not two small\n buttons in its corner. */}\n <Button type=\"button\" variant=\"ghost\" className={sheet ? \"flex-1\" : undefined} onClick={close}>\n {labels.cancel}\n </Button>\n <Button\n type=\"button\"\n className={sheet ? \"flex-1\" : undefined}\n // A half-made `(from, \"\")` would commit a window with no end — a blank\n // report, one click into a two-click gesture.\n disabled={!complete}\n onClick={() => {\n if (!complete) return;\n commitRange(draft.from, draft.to, draft.presetId);\n close();\n }}\n >\n {labels.apply}\n </Button>\n </>,\n );\n}\n\n/** Two-date range picker: click a start then an end; closes once both are set.\n * With `presets`, a column of named ranges is shown beside the calendar; with\n * `commit=\"apply\"` the panel drafts and commits on Apply (see {@link DateRangeCommit}). */\nexport function DateRangePicker(props: DateRangePickerProps) {\n const {\n from,\n to,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n separator = \" – \",\n presets,\n preset,\n commit = \"immediate\",\n renderTrigger,\n sheetBackCloses,\n renderDraftSummary,\n calendarLabels,\n monthJump,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n hint,\n help,\n error,\n // The trigger's (API-3).\n ref,\n ...rest\n } = props;\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, undefined, undefined);\n const render = useTriggerFormat(\"dateRangePicker\", locale, formatOptions, formatValue);\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n invalid,\n describedBy: rest[\"aria-describedby\"],\n ariaInvalid: rest[\"aria-invalid\"],\n });\n // The preset this picker committed itself, for a caller that does not control\n // `preset`. Cleared by a hand-picked range or a clear.\n const [ownPreset, setOwnPreset] = useState<string | undefined>(undefined);\n const controlled = preset !== undefined;\n const activeId = controlled ? preset : ownPreset;\n const a = from ? render(from) : \"\";\n const b = to ? render(to) : \"\";\n const triggerText = from\n ? to\n ? `${a}${separator}${b}`\n : `${a}${separator}…`\n : (placeholder ?? \"\");\n const hasPresets = Boolean(presets && presets.length > 0);\n const sheet = usePhoneLayout();\n const commitRange = (f: string, t: string, presetId: string | undefined) => {\n setOwnPreset(presetId);\n // Two arguments when no preset is involved — exactly the 0.7 call, so a caller's\n // `toHaveBeenCalledWith(from, to)` and a variadic handler see no difference.\n if (presetId === undefined) onChange(f, t);\n else onChange(f, t, presetId);\n };\n const field = (\n <DateField\n {...rest}\n fieldRef={ref}\n aria-describedby={messages.describedBy}\n label={label}\n labelHint={messages.labelHint}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.rangePanel}\n disabled={disabled}\n invalid={messages.isInvalid}\n // Widen so the preset column sits beside the calendar (default otherwise).\n width={hasPresets ? \"27.5rem\" : undefined}\n hasValue={Boolean(from || to)}\n triggerText={triggerText}\n onClear={() => commitRange(\"\", \"\", undefined)}\n sheet={sheet}\n sheetBackCloses={sheetBackCloses}\n renderTrigger={\n renderTrigger &&\n ((triggerProps, valueId) =>\n renderTrigger({\n triggerProps,\n valueProps: { id: valueId },\n open: triggerProps[\"aria-expanded\"],\n from,\n to,\n preset: presets?.[markedPreset(presets, from, to, activeId, controlled)],\n text: triggerText,\n }))\n }\n >\n {(close, frame) => (\n <RangePanel\n from={from}\n to={to}\n presets={presets}\n activeId={activeId}\n controlled={controlled}\n commit={commit}\n close={close}\n frame={frame}\n sheet={sheet}\n commitRange={commitRange}\n calendarProps={{ locale, min, max, labels: calendarLabels, monthJump }}\n labels={text}\n renderDraftSummary={renderDraftSummary}\n />\n )}\n </DateField>\n );\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {withEndHint(field, label === undefined ? messages.labelHint : undefined)}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/date-range-picker.tsx"],"sourcesContent":["// The two-date range picker: presets beside the calendar, `commit=\"apply\"` drafts, a\n// custom trigger. Moved out of date-picker.tsx as it was (MOD-9, refactor audit\n// 2026-10); date-picker still exports every public name here.\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLocale, type DatePickerLabels } from \"../i18n/kit-labels\";\nimport { Button } from \"./button\";\nimport { FieldBox, useFieldMessages } from \"./field-parts\";\nimport { usePhoneLayout } from \"../hooks/use-breakpoint\";\nimport {\n DateField,\n useDatePickerLabels,\n useTriggerFormat,\n withEndHint,\n type DatePickerBaseProps,\n type DateTriggerAttributes,\n type PanelFrame,\n} from \"./date-field\";\nimport { PanelCalendar, type PanelCalendarProps } from \"./jump-calendar\";\n\nexport interface DateRangePickerPreset {\n /**\n * The preset's identity, independent of its dates — `\"last_3_months\"`, not two ISO\n * strings. Optional for the 0.x callers that pass anonymous pairs.\n *\n * keksdose's report range field (report-range-field.tsx, \"Why this is app-level\")\n * is why it exists: its URL stores `?preset=last_3_months` and recomputes the dates\n * from today on every read, so a preset that is only its dates froze \"last 3 months\"\n * at whatever day the panel rendered, and two presets that happen to cover the same\n * days (this week and today, on a Monday) could not be told apart. With an `id` the\n * choice is reported to `onChange` and shown as selected for as long as the range\n * still matches it. Recompute `from`/`to` on every render (`calendarMonthPresets()`,\n * `dateRangePresets()`) and pass the key as the id.\n */\n id?: string;\n label: ReactNode;\n from: string;\n to: string;\n}\n\n/**\n * When the picker hands its range to `onChange`.\n *\n * - `\"immediate\"` (the default, and all of 0.7): a preset click commits and closes,\n * and each calendar click is reported as it happens — the half-made `(from, \"\")`\n * included. The right shape for quick ranges on a filter, where one click is one\n * answer.\n * - `\"apply\"`: the panel works on a DRAFT. A preset ARMS the calendar instead of\n * committing, days can then be nudged (\"last 3 months, but end it last Friday\"),\n * and nothing reaches `onChange` until Apply — which stays disabled until both ends\n * exist. Cancel, Escape and an outside click discard the draft, and every open\n * starts again from the committed value. keksdose's report range asked for exactly\n * this: each commit there re-runs a whole page of charts, so an intermediate state\n * must never escape the panel.\n */\nexport type DateRangeCommit = \"immediate\" | \"apply\";\n\nexport interface DateRangePickerProps extends DatePickerBaseProps {\n from: string;\n to: string;\n /** The committed range. `presetId` is the chosen preset's `id` when a preset\n * produced it (and the range was not nudged since), `undefined` for days picked by\n * hand. A 0.x two-argument handler simply ignores it. */\n onChange: (from: string, to: string, presetId?: string) => void;\n /** Separator between the two formatted dates in the trigger (default \" – \"). */\n separator?: string;\n /** Optional named ranges rendered as a start-side column (last month / YTD / …). */\n presets?: DateRangePickerPreset[];\n /**\n * The active preset's `id`, controlled — e.g. straight from keksdose's `?preset=`.\n * `null` (or an id no preset has, such as `\"custom\"`) says \"no preset\": nothing in\n * the column is marked even if a preset happens to cover the same days. A preset is\n * marked only while its dates still equal `from`/`to`.\n *\n * Leave it `undefined` and the picker remembers the last preset it committed\n * itself; with no memory at all it falls back to the first preset whose dates match.\n */\n preset?: string | null;\n /** See {@link DateRangeCommit}. Default `\"immediate\"`. */\n commit?: DateRangeCommit;\n /**\n * Draw the trigger yourself. keksdose's report range field\n * (reports/report-range-field.tsx, \"Why this is app-level\") is why: its trigger\n * NAMES the active preset (\"Last 3 months\", the window as a muted suffix) where the\n * kit's shows two dates, sits inside a Tooltip, and is squared off on one side so the\n * granularity control can join it flush at the field's 42px.\n *\n * Spread `props.triggerProps` onto a `<button>` whole and put `props.valueProps` on\n * the element that shows the value — see {@link DateTriggerAttributes} for what each\n * part is for. The field around it (label, hidden name twin, clear/calendar glyph,\n * popover or phone sheet) is unchanged.\n */\n renderTrigger?: (props: DateRangeTriggerRenderProps) => ReactNode;\n /**\n * In the phone layout (`usePhoneLayout()`) the panel opens as a full-screen sheet (`FullBleedDialog`)\n * with presets in a grid above the calendar and Apply pinned in the footer, instead\n * of the 440px popover that hung off a 360px screen (keksdose's report range field,\n * which hand-rolled exactly this). This is its `backCloses`: on by default, so Back\n * dismisses the sheet; off for a caller whose commit rewrites the URL with\n * `replaceState` in the same tick — keksdose's `?preset/from/to` — where the router\n * would overwrite the sheet's history marker.\n */\n sheetBackCloses?: boolean;\n /**\n * With `commit=\"apply\"`: a line above the calendar saying what Apply would commit —\n * keksdose's report range writes \"Custom · 1 Mar 2026 – …\" there\n * (report-range-field.tsx), the one place a preset armed and then nudged by a day is\n * seen to have become \"Custom\", and a half-made range to still lack its end. Called\n * with the DRAFT on every change, in the popover and the phone sheet alike; `preset`\n * is the preset the column marks for it (none once a day was picked by hand), `to` is\n * `\"\"` until the second click. The line is a polite live region, so a screen reader\n * hears the draft change as the calendar is used.\n *\n * Ignored in `\"immediate\"` mode, where there is no draft: every click is the value.\n */\n renderDraftSummary?: (draft: DateRangeDraftSummary) => ReactNode;\n}\n\n/** What {@link DateRangePickerProps.renderDraftSummary} is called with. */\nexport interface DateRangeDraftSummary {\n from: string;\n to: string;\n /** The preset the column marks for the draft, if any. */\n preset: DateRangePickerPreset | undefined;\n}\n\n/** What {@link DateRangePickerProps.renderTrigger} is called with. */\nexport interface DateRangeTriggerRenderProps {\n /** Spread onto the `<button>`, whole. Merge your class after `triggerProps.className`. */\n triggerProps: DateTriggerAttributes;\n /** Put on the element showing the value: the trigger is named \"label, value\" by\n * reference to this id. Without it the name is the label alone. */\n valueProps: { id: string };\n open: boolean;\n from: string;\n to: string;\n /** The preset the column marks for the committed range, if any — the one to name. */\n preset: DateRangePickerPreset | undefined;\n /** What the kit's own trigger would show: the formatted range, or the placeholder. */\n text: string;\n}\n\ninterface RangeDraft {\n from: string;\n to: string;\n presetId: string | undefined;\n}\n\n/** Which preset (by index) the column marks, or -1. See `DateRangePickerProps.preset`. */\nfunction markedPreset(\n presets: readonly DateRangePickerPreset[],\n from: string,\n to: string,\n activeId: string | null | undefined,\n controlled: boolean,\n): number {\n const matches = (p: DateRangePickerPreset) => p.from === from && p.to === to;\n if (activeId != null) {\n const byId = presets.findIndex((p) => p.id === activeId && matches(p));\n if (byId !== -1 || controlled) return byId;\n } else if (controlled) {\n return -1;\n }\n return presets.findIndex(matches);\n}\n\n/**\n * The preset column: a named group of toggle buttons, `aria-pressed` on the marked one.\n * Pressed-state rather than colour alone, because \"which range is this\" is the one\n * thing a screen-reader user opening the panel needs to hear.\n */\nfunction PresetColumn({\n presets,\n marked,\n label,\n onPick,\n sheet,\n}: {\n presets: readonly DateRangePickerPreset[];\n marked: number;\n label: string;\n onPick: (p: DateRangePickerPreset) => void;\n /** The phone sheet's shape: a two-column grid ABOVE the calendar with 44px rows —\n * a 128px column beside a month grid leaves the grid ~200px on a 360px screen. */\n sheet?: boolean;\n}) {\n return (\n // `border-e`/`pe`, not `-r`: the preset column is on the START side, and the rule\n // between it and the calendar has to follow it in a right-to-left UI.\n <div\n role=\"group\"\n aria-label={label}\n className={sheet ? \"grid grid-cols-2 gap-0.5\" : \"flex w-32 shrink-0 flex-col gap-0.5 border-e border-subtle pe-2\"}\n >\n {presets.map((p, i) => {\n const selected = i === marked;\n return (\n <button\n key={p.id ?? i}\n type=\"button\"\n aria-pressed={selected}\n data-preset={p.id}\n onClick={() => onPick(p)}\n className={cn(\n \"rounded px-2 text-start\",\n // 44px on a phone: the kit's touch-target size (`SHEET_ROW_CLASS`).\n sheet ? \"min-h-11 py-2 text-sm\" : \"py-1.5 text-xs\",\n selected ? \"bg-active font-medium text-primary\" : \"text-secondary hover:bg-hover\",\n )}\n >\n {p.label}\n </button>\n );\n })}\n </div>\n );\n}\n\n/**\n * The panel's body. A component of its own so `commit=\"apply\"` can keep its draft in\n * state that is born when the panel opens: `Popover` unmounts its children on close,\n * so the draft is re-seeded from the committed value on every open, and an abandoned\n * half-selection cannot survive into the next one.\n */\nfunction RangePanel({\n from,\n to,\n presets,\n activeId,\n controlled,\n commit,\n close,\n frame,\n sheet,\n commitRange,\n calendarProps,\n labels,\n renderDraftSummary,\n}: {\n from: string;\n to: string;\n presets: readonly DateRangePickerPreset[] | undefined;\n activeId: string | null | undefined;\n controlled: boolean;\n commit: DateRangeCommit;\n close: () => void;\n /** Where the body and the Apply row go — see `PanelFrame`. */\n frame: PanelFrame;\n sheet: boolean;\n commitRange: (from: string, to: string, presetId: string | undefined) => void;\n calendarProps: Pick<PanelCalendarProps, \"locale\" | \"min\" | \"max\" | \"labels\" | \"monthJump\">;\n labels: DatePickerLabels;\n renderDraftSummary?: DateRangePickerProps[\"renderDraftSummary\"];\n}) {\n const drafting = commit === \"apply\";\n const [draft, setDraft] = useState<RangeDraft>({\n from,\n to,\n presetId: activeId ?? undefined,\n });\n // What the panel SHOWS: the draft while drafting, the committed value otherwise.\n const shown = drafting ? draft : { from, to, presetId: activeId ?? undefined };\n const marked = presets\n ? markedPreset(presets, shown.from, shown.to, drafting ? draft.presetId : activeId, drafting || controlled)\n : -1;\n const complete = Boolean(draft.from && draft.to);\n\n const summary =\n drafting && renderDraftSummary\n ? renderDraftSummary({ from: draft.from, to: draft.to, preset: presets?.[marked] })\n : null;\n\n const picker = (\n <PanelCalendar\n {...calendarProps}\n focusOnOpen\n from={shown.from}\n to={shown.to}\n onSelect={(f, t) => {\n if (drafting) {\n setDraft({ from: f, to: t, presetId: undefined });\n return;\n }\n commitRange(f, t, undefined);\n // Two-click range: only dismiss once both ends are chosen.\n if (f && t) close();\n }}\n />\n );\n // The summary heads the CALENDAR's column, not the panel: beside a preset column it\n // reads as the calendar's caption, and in the sheet it sits between the presets and\n // the month it describes. `null` renders no line — a caller may opt out per draft —\n // but keeps the wrapper, so the calendar is not remounted (and its month and focus\n // lost) when a line comes or goes.\n const calendar =\n drafting && renderDraftSummary ? (\n <div className=\"flex flex-col gap-2\">\n {summary != null && (\n <div aria-live=\"polite\" data-draft-summary=\"\" className=\"text-xs text-muted\">\n {summary}\n </div>\n )}\n {picker}\n </div>\n ) : (\n picker\n );\n\n const body =\n presets && presets.length > 0 ? (\n <div className={sheet ? \"flex flex-col gap-3\" : \"flex gap-3\"}>\n <PresetColumn\n sheet={sheet}\n presets={presets}\n marked={marked}\n label={labels.presets}\n onPick={(p) => {\n if (drafting) {\n setDraft({ from: p.from, to: p.to, presetId: p.id });\n return;\n }\n commitRange(p.from, p.to, p.id);\n close();\n }}\n />\n {/* `flex-1`: the calendar takes the rest of the panel. Content-sized it was\n seven tiny cells beside a wide preset column. */}\n <div className=\"min-w-0 flex-1\">{calendar}</div>\n </div>\n ) : (\n calendar\n );\n\n if (!drafting) return frame(body);\n return frame(\n body,\n <>\n {/* `flex-1` in the sheet: two thumb-wide halves of the footer, not two small\n buttons in its corner. */}\n <Button type=\"button\" variant=\"ghost\" className={sheet ? \"flex-1\" : undefined} onClick={close}>\n {labels.cancel}\n </Button>\n <Button\n type=\"button\"\n className={sheet ? \"flex-1\" : undefined}\n // A half-made `(from, \"\")` would commit a window with no end — a blank\n // report, one click into a two-click gesture.\n disabled={!complete}\n onClick={() => {\n if (!complete) return;\n commitRange(draft.from, draft.to, draft.presetId);\n close();\n }}\n >\n {labels.apply}\n </Button>\n </>,\n );\n}\n\n/** Two-date range picker: click a start then an end; closes once both are set.\n * With `presets`, a column of named ranges is shown beside the calendar; with\n * `commit=\"apply\"` the panel drafts and commits on Apply (see {@link DateRangeCommit}). */\nexport function DateRangePicker(props: DateRangePickerProps) {\n const {\n from,\n to,\n onChange,\n locale: localeProp,\n placeholder,\n min,\n max,\n formatOptions,\n formatValue,\n separator = \" – \",\n presets,\n preset,\n commit = \"immediate\",\n renderTrigger,\n sheetBackCloses,\n renderDraftSummary,\n calendarLabels,\n monthJump,\n label,\n clearable,\n clearLabel,\n disabled,\n invalid,\n hint,\n help,\n error,\n // The trigger's (API-3).\n ref,\n ...rest\n } = props;\n const locale = useKitLocale(localeProp);\n const text = useDatePickerLabels(clearLabel, undefined, undefined);\n const render = useTriggerFormat(\"dateRangePicker\", locale, formatOptions, formatValue);\n const messages = useFieldMessages({\n hint,\n help,\n name: label ?? rest[\"aria-label\"],\n error,\n invalid,\n describedBy: rest[\"aria-describedby\"],\n ariaInvalid: rest[\"aria-invalid\"],\n });\n // The preset this picker committed itself, for a caller that does not control\n // `preset`. Cleared by a hand-picked range or a clear.\n const [ownPreset, setOwnPreset] = useState<string | undefined>(undefined);\n const controlled = preset !== undefined;\n const activeId = controlled ? preset : ownPreset;\n const a = from ? render(from) : \"\";\n const b = to ? render(to) : \"\";\n const triggerText = from ? (to ? `${a}${separator}${b}` : `${a}${separator}…`) : (placeholder ?? \"\");\n const hasPresets = Boolean(presets && presets.length > 0);\n const sheet = usePhoneLayout();\n const commitRange = (f: string, t: string, presetId: string | undefined) => {\n setOwnPreset(presetId);\n // Two arguments when no preset is involved — exactly the 0.7 call, so a caller's\n // `toHaveBeenCalledWith(from, to)` and a variadic handler see no difference.\n if (presetId === undefined) onChange(f, t);\n else onChange(f, t, presetId);\n };\n const field = (\n <DateField\n {...rest}\n fieldRef={ref}\n aria-describedby={messages.describedBy}\n label={label}\n labelHint={messages.labelHint}\n clearable={clearable}\n clearLabel={text.clear}\n panelLabel={text.rangePanel}\n disabled={disabled}\n invalid={messages.isInvalid}\n // Widen so the preset column sits beside the calendar (default otherwise).\n width={hasPresets ? \"27.5rem\" : undefined}\n hasValue={Boolean(from || to)}\n triggerText={triggerText}\n onClear={() => commitRange(\"\", \"\", undefined)}\n sheet={sheet}\n sheetBackCloses={sheetBackCloses}\n renderTrigger={\n renderTrigger &&\n ((triggerProps, valueId) =>\n renderTrigger({\n triggerProps,\n valueProps: { id: valueId },\n open: triggerProps[\"aria-expanded\"],\n from,\n to,\n preset: presets?.[markedPreset(presets, from, to, activeId, controlled)],\n text: triggerText,\n }))\n }\n >\n {(close, frame) => (\n <RangePanel\n from={from}\n to={to}\n presets={presets}\n activeId={activeId}\n controlled={controlled}\n commit={commit}\n close={close}\n frame={frame}\n sheet={sheet}\n commitRange={commitRange}\n calendarProps={{ locale, min, max, labels: calendarLabels, monthJump }}\n labels={text}\n renderDraftSummary={renderDraftSummary}\n />\n )}\n </DateField>\n );\n return (\n <FieldBox below={messages.below} reserve={\"error\" in props || \"hint\" in props}>\n {withEndHint(field, label === undefined ? messages.labelHint : undefined)}\n </FieldBox>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -128,9 +128,7 @@ function DescriptionItem({
|
|
|
128
128
|
const prose = proseProp ?? listProse;
|
|
129
129
|
const cards = layout === "cards";
|
|
130
130
|
const stacked = layout === "stacked";
|
|
131
|
-
const shownHint = useInlineHint(
|
|
132
|
-
hint ? /* @__PURE__ */ jsx(FieldHint, { label: hint, side: "top", className: "shrink-0" }) : void 0
|
|
133
|
-
);
|
|
131
|
+
const shownHint = useInlineHint(hint ? /* @__PURE__ */ jsx(FieldHint, { label: hint, side: "top", className: "shrink-0" }) : void 0);
|
|
134
132
|
const spanClass = span === "full" ? "col-span-full" : stacked && span !== void 0 && span > 1 ? STACKED_SPAN[Math.min(Math.floor(span), columns)] : void 0;
|
|
135
133
|
const termNode = /* @__PURE__ */ jsxs(
|
|
136
134
|
"dt",
|
|
@@ -147,7 +145,14 @@ function DescriptionItem({
|
|
|
147
145
|
typeof shownHint === "string" ? (
|
|
148
146
|
// Its own line under the term, in the reading case of a sentence rather than a
|
|
149
147
|
// card term's capitals — StatTile's inline hint.
|
|
150
|
-
/* @__PURE__ */ jsx(
|
|
148
|
+
/* @__PURE__ */ jsx(
|
|
149
|
+
"span",
|
|
150
|
+
{
|
|
151
|
+
"data-slot": "description-hint",
|
|
152
|
+
className: "basis-full text-caption leading-tight normal-case tracking-normal",
|
|
153
|
+
children: shownHint
|
|
154
|
+
}
|
|
155
|
+
)
|
|
151
156
|
) : shownHint
|
|
152
157
|
]
|
|
153
158
|
}
|
|
@@ -171,12 +176,7 @@ function DescriptionItem({
|
|
|
171
176
|
"div",
|
|
172
177
|
{
|
|
173
178
|
...rest,
|
|
174
|
-
className: cn(
|
|
175
|
-
"min-w-0 rounded-md border border-subtle bg-surface",
|
|
176
|
-
CARD_PAD[density],
|
|
177
|
-
spanClass,
|
|
178
|
-
className
|
|
179
|
-
),
|
|
179
|
+
className: cn("min-w-0 rounded-md border border-subtle bg-surface", CARD_PAD[density], spanClass, className),
|
|
180
180
|
children: [
|
|
181
181
|
termNode,
|
|
182
182
|
detail
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/description-list.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { FieldHint } from \"./field-parts\";\nimport { useInlineHint } from \"./field-parts\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"./description-list-labels\";\n\n/** The words a {@link DescriptionList} renders on its own behalf. */\nexport interface DescriptionListLabels {\n /** What an item with no value shows (see {@link DescriptionItemProps.placeholder}).\n * Default \"—\", an em dash. */\n empty: string;\n}\n\n// Defined in ./description-list-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_DESCRIPTION_LIST_LABELS };\n\nexport type DescriptionListLayout = \"rows\" | \"cards\" | \"stacked\";\nexport type DescriptionListDensity = \"comfortable\" | \"compact\" | \"tight\";\n/** How many columns a `stacked` or `cards` list may use. */\nexport type DescriptionListColumns = 1 | 2 | 3 | 4 | 5 | 6;\n\ninterface ListContextValue {\n layout: DescriptionListLayout;\n numeric: boolean;\n prose: boolean;\n density: DescriptionListDensity;\n columns: DescriptionListColumns;\n placeholder?: ReactNode;\n}\n\nconst ListContext = createContext<ListContextValue>({\n layout: \"rows\",\n numeric: false,\n prose: false,\n density: \"comfortable\",\n columns: 2,\n});\n\n/**\n * `stacked` columns, by the width of the LIST (a container query on a wrapper — the\n * `<dl>` is the grid and cannot query itself). One column on a phone-narrow pane, two\n * from 20rem, then one more column each time every column would still get ~12rem.\n * The same shape as kastlan's DetailGrid (1 → 2 at `sm` → n at `md`), measured on the\n * pane rather than the viewport, so the four preview dialogs and a full-width detail\n * page can share one list. Literal strings, because Tailwind only emits what it reads.\n */\nconst STACKED_COLS: Record<DescriptionListColumns, string> = {\n 1: \"grid-cols-1\",\n 2: \"grid-cols-1 @xs:grid-cols-2\",\n 3: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3\",\n 4: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4\",\n 5: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4 @5xl:grid-cols-5\",\n 6: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4 @5xl:grid-cols-5 @6xl:grid-cols-6\",\n};\n\n/** An item's span at each of those thresholds, never more columns than exist there —\n * so a span can never open an implicit column and push the grid past its pane. */\nconst STACKED_SPAN: Record<DescriptionListColumns, string> = {\n 1: \"\",\n 2: \"@xs:col-span-2\",\n 3: \"@xs:col-span-2 @xl:col-span-3\",\n 4: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4\",\n 5: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4 @5xl:col-span-5\",\n 6: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4 @5xl:col-span-5 @6xl:col-span-6\",\n};\n\n/** The cards grid's gap per density, as a class and as the length the column formula\n * subtracts — one table so the two cannot disagree. */\nconst CARD_GAP: Record<DescriptionListDensity, { cls: string; len: string }> = {\n comfortable: { cls: \"gap-3\", len: \"0.75rem\" },\n compact: { cls: \"gap-2\", len: \"0.5rem\" },\n tight: { cls: \"gap-1\", len: \"0.25rem\" },\n};\n\nexport interface DescriptionListProps extends ComponentPropsWithRef<\"dl\"> {\n /**\n * `rows` (default): term beside detail, a two-column list that stacks the pair\n * when the LIST is narrower than 24rem — a container query, so a detail panel\n * beside a sidebar stacks where it is actually cramped, not where the viewport is.\n * `cards`: a grid of small bordered cards, term above body — keksdose's overview\n * figures, lenkbank's account facts.\n * `stacked` (0.10.0): a borderless grid, term ABOVE detail, {@link columns} wide —\n * kastlan's DetailField/DetailGrid and the label-over-value grids of its lease,\n * invoice, contact and unit preview dialogs. Items can {@link DescriptionItemProps.span}\n * several columns (a notes field across the whole row).\n */\n layout?: DescriptionListLayout;\n /** Every detail is a figure: tabular digits, end-aligned. A `numeric` list of\n * rows does not stack on a phone — a figure is short, and putting it under its\n * term would double the list's height to gain no width. Per item: see\n * {@link DescriptionItemProps.numeric}. */\n numeric?: boolean;\n /** `compact` tightens the rhythm for a dense side panel. `tight` (0.10.0) is 11px\n * type on a 2px rhythm with no rules between rows — keksdose's admin metrics\n * breakdowns (metrics-panel's label-left, count-right lists), which are read as a\n * block of small print, not scanned row by row. */\n density?: DescriptionListDensity;\n /** `cards` only: the narrowest a card may get before the grid drops a column. */\n minCardWidth?: string;\n /**\n * `stacked`: how many columns the grid grows to on a wide pane (default 2), see\n * {@link STACKED_COLS}. `cards` (0.10.0): the MOST cards per row; the grid still\n * drops columns below {@link minCardWidth}. keksdose's category editor faked \"two\n * side by side\" with `minCardWidth=\"calc(50% - 0.5rem)\"`, which is `columns={2}\n * minCardWidth=\"0\"` now, without knowing the gap. Ignored by `rows`.\n */\n columns?: DescriptionListColumns;\n /**\n * The details are sentences, not values: regular weight in the secondary ink.\n * lenkbank's control explainer cards each carry a paragraph, and at the card's\n * `font-medium` primary ink a grid of paragraphs reads as a wall of headings — it\n * spelled `font-normal text-slate-700` into every item. A flag rather than a new\n * default because every other card list in the apps holds values (dates, figures,\n * counts) that are not flagged `numeric` and ARE meant to stand out; lightening them\n * all would not be an improvement. Per item: {@link DescriptionItemProps.prose}.\n */\n prose?: boolean;\n /** What an item with no value shows, for every item that does not set its own —\n * see {@link DescriptionItemProps.placeholder}. */\n placeholder?: ReactNode;\n}\n\n/**\n * A real `<dl>` — terms and their details, which is what keksdose's eight and\n * lenkbank's four hand-built \"label: value\" blocks were, and none of them said so.\n * As a `<dl>` a reader announces \"list, 6 items\" and pairs each term with its detail;\n * as a stack of `<div>`s it read one undifferentiated run of text.\n *\n * Composed from {@link DescriptionItem} children rather than taking an `items`\n * array. The call sites this replaces are mostly CONDITIONAL — \"IBAN, if there is\n * one\", \"closing date, once closed\" — and `{iban && <DescriptionItem …>}` reads as\n * what it means, where an array wants `.filter(Boolean)` and a cast. Children also\n * give each item its own props (a numeric figure in an otherwise textual list, a\n * `data-private` detail) with no parallel config object. A data-driven list loses\n * nothing: `items.map((i) => <DescriptionItem key={i.term} {...i} />)`.\n *\n * Each pair is wrapped in a `<div>`, which HTML permits inside a `<dl>` for exactly\n * this: styling a pair as a unit (a row, a card) without breaking the list.\n */\nexport function DescriptionList({\n layout = \"rows\",\n numeric = false,\n prose = false,\n density = \"comfortable\",\n minCardWidth = \"10rem\",\n columns,\n placeholder,\n className,\n style,\n ...rest\n}: DescriptionListProps) {\n const cards = layout === \"cards\";\n const stacked = layout === \"stacked\";\n const gap = CARD_GAP[density];\n // Cards: auto-fill at `minCardWidth`, and with `columns` a floor on the track of one\n // n-th of the row (less the gaps) — so auto-fill can never fit more than n. The\n // outer `min(100%, …)` keeps a single card from overflowing a pane narrower than\n // its minimum.\n const track =\n columns !== undefined\n ? `max(${minCardWidth}, (100% - ${columns - 1} * ${gap.len}) / ${columns})`\n : minCardWidth;\n const grid: CSSProperties | undefined = cards\n ? { gridTemplateColumns: `repeat(auto-fill, minmax(min(100%, ${track}), 1fr))` }\n : undefined;\n const ctx: ListContextValue = { layout, numeric, prose, density, columns: columns ?? 2, placeholder };\n const text = density === \"tight\" ? \"text-caption leading-4\" : density === \"compact\" ? \"text-xs\" : \"text-sm\";\n\n if (stacked) {\n return (\n <ListContext.Provider value={ctx}>\n {/* The container the columns are measured on — see STACKED_COLS. */}\n <div className=\"@container\">\n <dl\n {...rest}\n data-layout={layout}\n style={style}\n className={cn(\n \"grid\",\n STACKED_COLS[ctx.columns],\n density === \"tight\" ? \"gap-x-3 gap-y-1\" : density === \"compact\" ? \"gap-x-4 gap-y-2\" : \"gap-x-6 gap-y-4\",\n text,\n className,\n )}\n />\n </div>\n </ListContext.Provider>\n );\n }\n\n return (\n <ListContext.Provider value={ctx}>\n <dl\n {...rest}\n data-layout={layout}\n style={grid ? { ...grid, ...style } : style}\n className={cn(\n cards\n ? cn(\"grid\", gap.cls)\n : // Tight rows carry no rules: at a 2px rhythm a line under every row\n // turns a block of small print into a ruled table.\n cn(\"@container\", density !== \"tight\" && \"divide-y divide-subtle\"),\n text,\n className,\n )}\n />\n </ListContext.Provider>\n );\n}\n\nexport interface DescriptionItemProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n /** What is described: \"Opened\", \"IBAN\", \"Net rent\". */\n term: ReactNode;\n /** The description itself. */\n children?: ReactNode;\n /** The explanation behind a \"?\" beside the term (a {@link FieldHint}) — the same\n * meaning `hint` has on `StatTile` and the labelled fields. At Large and on a touch\n * screen it is the term's caption instead, in words (§4 \"No fact only in a tooltip\"). */\n hint?: string;\n /** This detail is a figure (see {@link DescriptionListProps.numeric}); overrides\n * the list's setting either way. */\n numeric?: boolean;\n /** Classes for the `<dd>`. */\n detailClassName?: string;\n /** This detail is prose (see {@link DescriptionListProps.prose}); overrides the\n * list's setting either way. */\n prose?: boolean;\n /**\n * `stacked`: how many columns this item spans, clamped to the list's `columns` and\n * to however many the pane has room for; `\"full\"` is the whole row. kastlan's\n * DetailField `span={2}` for notes and descriptions. `cards`: only `\"full\"` applies,\n * because a card grid's column count is not known to CSS ahead of layout.\n */\n span?: number | \"full\";\n /**\n * Shown in place of a value that is not there — `children` of `null`, `undefined`,\n * `false` or `\"\"` (a `0` is a value and is shown). Default: the list's own\n * `placeholder`, else `descriptionList.empty` from the provider, else \"—\". `null`\n * shows nothing, as an empty detail did before. kastlan's DetailField put its\n * `EMPTY_VALUE` into every one of its call sites' values by hand.\n *\n * Drawn in the muted ink, so an absent value does not read as a value of \"—\".\n */\n placeholder?: ReactNode;\n}\n\nfunction isEmptyValue(node: ReactNode): boolean {\n return node === undefined || node === null || node === false || node === \"\";\n}\n\nconst ROW_PAD: Record<DescriptionListDensity, string> = {\n comfortable: \"py-2.5\",\n compact: \"py-1.5\",\n tight: \"py-px\",\n};\nconst CARD_PAD: Record<DescriptionListDensity, string> = {\n comfortable: \"px-3 py-2.5\",\n compact: \"px-2.5 py-1.5\",\n tight: \"px-2 py-1\",\n};\n\n/** One term and its detail inside a {@link DescriptionList}. */\nexport function DescriptionItem({\n term,\n children,\n hint,\n numeric: numericProp,\n prose: proseProp,\n span,\n placeholder: placeholderProp,\n detailClassName,\n className,\n ...rest\n}: DescriptionItemProps) {\n const {\n layout,\n numeric: listNumeric,\n prose: listProse,\n density,\n columns,\n placeholder: listPlaceholder,\n } = useContext(ListContext);\n const labels = useKitLabels(\"descriptionList\", DEFAULT_DESCRIPTION_LIST_LABELS);\n const empty = isEmptyValue(children);\n const placeholder =\n placeholderProp !== undefined ? placeholderProp : listPlaceholder !== undefined ? listPlaceholder : labels.empty;\n const numeric = numericProp ?? listNumeric;\n const prose = proseProp ?? listProse;\n const cards = layout === \"cards\";\n const stacked = layout === \"stacked\";\n // A hint is a fact: on a touch screen and at Large it is words under the term, not a\n // 14 px \"?\" nobody can hover open — the fields' rule (DUP-2, refactor audit 2026-10),\n // which this string hint had been left out of. Elsewhere the \"?\" as before.\n const shownHint = useInlineHint(\n hint ? <FieldHint label={hint} side=\"top\" className=\"shrink-0\" /> : undefined,\n );\n const spanClass =\n span === \"full\"\n ? \"col-span-full\"\n : stacked && span !== undefined && span > 1\n ? STACKED_SPAN[Math.min(Math.floor(span), columns) as DescriptionListColumns]\n : undefined;\n\n const termNode = (\n <dt\n className={cn(\n \"flex min-w-0 flex-wrap items-center gap-x-1 text-muted\",\n cards && (density === \"tight\" ? \"text-micro uppercase tracking-wide\" : \"text-xs uppercase tracking-wide\"),\n // Stacked terms are a step smaller than the detail, as kastlan's dialogs set\n // them; in a list that is already small they stay the list's own size.\n stacked && density === \"comfortable\" && \"text-xs\",\n )}\n >\n <span className=\"min-w-0 break-words\">{term}</span>\n {typeof shownHint === \"string\" ? (\n // Its own line under the term, in the reading case of a sentence rather than a\n // card term's capitals — StatTile's inline hint.\n <span data-slot=\"description-hint\" className=\"basis-full text-caption leading-tight normal-case tracking-normal\">\n {shownHint}\n </span>\n ) : (\n shownHint\n )}\n </dt>\n );\n const detail = (\n <dd\n className={cn(\n \"min-w-0 break-words text-primary\",\n // A stacked figure sits under its term, so it keeps the term's start edge.\n numeric && (stacked ? \"tabular-nums\" : \"tabular-nums text-end\"),\n (cards || stacked) && \"mt-0.5 font-medium\",\n prose && \"font-normal text-secondary\",\n detailClassName,\n )}\n >\n {empty && !isEmptyValue(placeholder) ? (\n <span data-slot=\"description-empty\" className=\"font-normal text-muted\">\n {placeholder}\n </span>\n ) : (\n children\n )}\n </dd>\n );\n\n if (cards) {\n return (\n <div\n {...rest}\n className={cn(\n \"min-w-0 rounded-md border border-subtle bg-surface\",\n CARD_PAD[density],\n spanClass,\n className,\n )}\n >\n {termNode}\n {detail}\n </div>\n );\n }\n\n if (stacked) {\n return (\n <div {...rest} className={cn(\"min-w-0\", spanClass, className)}>\n {termNode}\n {detail}\n </div>\n );\n }\n\n return (\n <div\n {...rest}\n className={cn(\n \"grid gap-x-4 gap-y-0.5\",\n numeric\n ? // Never stacks: a figure is short (see DescriptionListProps.numeric).\n \"grid-cols-[minmax(0,1fr)_auto] items-baseline\"\n : \"grid-cols-1 @sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] @sm:items-baseline\",\n ROW_PAD[density],\n className,\n )}\n >\n {termNode}\n {detail}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/description-list.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { FieldHint } from \"./field-parts\";\nimport { useInlineHint } from \"./field-parts\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"./description-list-labels\";\n\n/** The words a {@link DescriptionList} renders on its own behalf. */\nexport interface DescriptionListLabels {\n /** What an item with no value shows (see {@link DescriptionItemProps.placeholder}).\n * Default \"—\", an em dash. */\n empty: string;\n}\n\n// Defined in ./description-list-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_DESCRIPTION_LIST_LABELS };\n\nexport type DescriptionListLayout = \"rows\" | \"cards\" | \"stacked\";\nexport type DescriptionListDensity = \"comfortable\" | \"compact\" | \"tight\";\n/** How many columns a `stacked` or `cards` list may use. */\nexport type DescriptionListColumns = 1 | 2 | 3 | 4 | 5 | 6;\n\ninterface ListContextValue {\n layout: DescriptionListLayout;\n numeric: boolean;\n prose: boolean;\n density: DescriptionListDensity;\n columns: DescriptionListColumns;\n placeholder?: ReactNode;\n}\n\nconst ListContext = createContext<ListContextValue>({\n layout: \"rows\",\n numeric: false,\n prose: false,\n density: \"comfortable\",\n columns: 2,\n});\n\n/**\n * `stacked` columns, by the width of the LIST (a container query on a wrapper — the\n * `<dl>` is the grid and cannot query itself). One column on a phone-narrow pane, two\n * from 20rem, then one more column each time every column would still get ~12rem.\n * The same shape as kastlan's DetailGrid (1 → 2 at `sm` → n at `md`), measured on the\n * pane rather than the viewport, so the four preview dialogs and a full-width detail\n * page can share one list. Literal strings, because Tailwind only emits what it reads.\n */\nconst STACKED_COLS: Record<DescriptionListColumns, string> = {\n 1: \"grid-cols-1\",\n 2: \"grid-cols-1 @xs:grid-cols-2\",\n 3: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3\",\n 4: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4\",\n 5: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4 @5xl:grid-cols-5\",\n 6: \"grid-cols-1 @xs:grid-cols-2 @xl:grid-cols-3 @3xl:grid-cols-4 @5xl:grid-cols-5 @6xl:grid-cols-6\",\n};\n\n/** An item's span at each of those thresholds, never more columns than exist there —\n * so a span can never open an implicit column and push the grid past its pane. */\nconst STACKED_SPAN: Record<DescriptionListColumns, string> = {\n 1: \"\",\n 2: \"@xs:col-span-2\",\n 3: \"@xs:col-span-2 @xl:col-span-3\",\n 4: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4\",\n 5: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4 @5xl:col-span-5\",\n 6: \"@xs:col-span-2 @xl:col-span-3 @3xl:col-span-4 @5xl:col-span-5 @6xl:col-span-6\",\n};\n\n/** The cards grid's gap per density, as a class and as the length the column formula\n * subtracts — one table so the two cannot disagree. */\nconst CARD_GAP: Record<DescriptionListDensity, { cls: string; len: string }> = {\n comfortable: { cls: \"gap-3\", len: \"0.75rem\" },\n compact: { cls: \"gap-2\", len: \"0.5rem\" },\n tight: { cls: \"gap-1\", len: \"0.25rem\" },\n};\n\nexport interface DescriptionListProps extends ComponentPropsWithRef<\"dl\"> {\n /**\n * `rows` (default): term beside detail, a two-column list that stacks the pair\n * when the LIST is narrower than 24rem — a container query, so a detail panel\n * beside a sidebar stacks where it is actually cramped, not where the viewport is.\n * `cards`: a grid of small bordered cards, term above body — keksdose's overview\n * figures, lenkbank's account facts.\n * `stacked` (0.10.0): a borderless grid, term ABOVE detail, {@link columns} wide —\n * kastlan's DetailField/DetailGrid and the label-over-value grids of its lease,\n * invoice, contact and unit preview dialogs. Items can {@link DescriptionItemProps.span}\n * several columns (a notes field across the whole row).\n */\n layout?: DescriptionListLayout;\n /** Every detail is a figure: tabular digits, end-aligned. A `numeric` list of\n * rows does not stack on a phone — a figure is short, and putting it under its\n * term would double the list's height to gain no width. Per item: see\n * {@link DescriptionItemProps.numeric}. */\n numeric?: boolean;\n /** `compact` tightens the rhythm for a dense side panel. `tight` (0.10.0) is 11px\n * type on a 2px rhythm with no rules between rows — keksdose's admin metrics\n * breakdowns (metrics-panel's label-left, count-right lists), which are read as a\n * block of small print, not scanned row by row. */\n density?: DescriptionListDensity;\n /** `cards` only: the narrowest a card may get before the grid drops a column. */\n minCardWidth?: string;\n /**\n * `stacked`: how many columns the grid grows to on a wide pane (default 2), see\n * {@link STACKED_COLS}. `cards` (0.10.0): the MOST cards per row; the grid still\n * drops columns below {@link minCardWidth}. keksdose's category editor faked \"two\n * side by side\" with `minCardWidth=\"calc(50% - 0.5rem)\"`, which is `columns={2}\n * minCardWidth=\"0\"` now, without knowing the gap. Ignored by `rows`.\n */\n columns?: DescriptionListColumns;\n /**\n * The details are sentences, not values: regular weight in the secondary ink.\n * lenkbank's control explainer cards each carry a paragraph, and at the card's\n * `font-medium` primary ink a grid of paragraphs reads as a wall of headings — it\n * spelled `font-normal text-slate-700` into every item. A flag rather than a new\n * default because every other card list in the apps holds values (dates, figures,\n * counts) that are not flagged `numeric` and ARE meant to stand out; lightening them\n * all would not be an improvement. Per item: {@link DescriptionItemProps.prose}.\n */\n prose?: boolean;\n /** What an item with no value shows, for every item that does not set its own —\n * see {@link DescriptionItemProps.placeholder}. */\n placeholder?: ReactNode;\n}\n\n/**\n * A real `<dl>` — terms and their details, which is what keksdose's eight and\n * lenkbank's four hand-built \"label: value\" blocks were, and none of them said so.\n * As a `<dl>` a reader announces \"list, 6 items\" and pairs each term with its detail;\n * as a stack of `<div>`s it read one undifferentiated run of text.\n *\n * Composed from {@link DescriptionItem} children rather than taking an `items`\n * array. The call sites this replaces are mostly CONDITIONAL — \"IBAN, if there is\n * one\", \"closing date, once closed\" — and `{iban && <DescriptionItem …>}` reads as\n * what it means, where an array wants `.filter(Boolean)` and a cast. Children also\n * give each item its own props (a numeric figure in an otherwise textual list, a\n * `data-private` detail) with no parallel config object. A data-driven list loses\n * nothing: `items.map((i) => <DescriptionItem key={i.term} {...i} />)`.\n *\n * Each pair is wrapped in a `<div>`, which HTML permits inside a `<dl>` for exactly\n * this: styling a pair as a unit (a row, a card) without breaking the list.\n */\nexport function DescriptionList({\n layout = \"rows\",\n numeric = false,\n prose = false,\n density = \"comfortable\",\n minCardWidth = \"10rem\",\n columns,\n placeholder,\n className,\n style,\n ...rest\n}: DescriptionListProps) {\n const cards = layout === \"cards\";\n const stacked = layout === \"stacked\";\n const gap = CARD_GAP[density];\n // Cards: auto-fill at `minCardWidth`, and with `columns` a floor on the track of one\n // n-th of the row (less the gaps) — so auto-fill can never fit more than n. The\n // outer `min(100%, …)` keeps a single card from overflowing a pane narrower than\n // its minimum.\n const track =\n columns !== undefined ? `max(${minCardWidth}, (100% - ${columns - 1} * ${gap.len}) / ${columns})` : minCardWidth;\n const grid: CSSProperties | undefined = cards\n ? { gridTemplateColumns: `repeat(auto-fill, minmax(min(100%, ${track}), 1fr))` }\n : undefined;\n const ctx: ListContextValue = { layout, numeric, prose, density, columns: columns ?? 2, placeholder };\n const text = density === \"tight\" ? \"text-caption leading-4\" : density === \"compact\" ? \"text-xs\" : \"text-sm\";\n\n if (stacked) {\n return (\n <ListContext.Provider value={ctx}>\n {/* The container the columns are measured on — see STACKED_COLS. */}\n <div className=\"@container\">\n <dl\n {...rest}\n data-layout={layout}\n style={style}\n className={cn(\n \"grid\",\n STACKED_COLS[ctx.columns],\n density === \"tight\" ? \"gap-x-3 gap-y-1\" : density === \"compact\" ? \"gap-x-4 gap-y-2\" : \"gap-x-6 gap-y-4\",\n text,\n className,\n )}\n />\n </div>\n </ListContext.Provider>\n );\n }\n\n return (\n <ListContext.Provider value={ctx}>\n <dl\n {...rest}\n data-layout={layout}\n style={grid ? { ...grid, ...style } : style}\n className={cn(\n cards\n ? cn(\"grid\", gap.cls)\n : // Tight rows carry no rules: at a 2px rhythm a line under every row\n // turns a block of small print into a ruled table.\n cn(\"@container\", density !== \"tight\" && \"divide-y divide-subtle\"),\n text,\n className,\n )}\n />\n </ListContext.Provider>\n );\n}\n\nexport interface DescriptionItemProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n /** What is described: \"Opened\", \"IBAN\", \"Net rent\". */\n term: ReactNode;\n /** The description itself. */\n children?: ReactNode;\n /** The explanation behind a \"?\" beside the term (a {@link FieldHint}) — the same\n * meaning `hint` has on `StatTile` and the labelled fields. At Large and on a touch\n * screen it is the term's caption instead, in words (§4 \"No fact only in a tooltip\"). */\n hint?: string;\n /** This detail is a figure (see {@link DescriptionListProps.numeric}); overrides\n * the list's setting either way. */\n numeric?: boolean;\n /** Classes for the `<dd>`. */\n detailClassName?: string;\n /** This detail is prose (see {@link DescriptionListProps.prose}); overrides the\n * list's setting either way. */\n prose?: boolean;\n /**\n * `stacked`: how many columns this item spans, clamped to the list's `columns` and\n * to however many the pane has room for; `\"full\"` is the whole row. kastlan's\n * DetailField `span={2}` for notes and descriptions. `cards`: only `\"full\"` applies,\n * because a card grid's column count is not known to CSS ahead of layout.\n */\n span?: number | \"full\";\n /**\n * Shown in place of a value that is not there — `children` of `null`, `undefined`,\n * `false` or `\"\"` (a `0` is a value and is shown). Default: the list's own\n * `placeholder`, else `descriptionList.empty` from the provider, else \"—\". `null`\n * shows nothing, as an empty detail did before. kastlan's DetailField put its\n * `EMPTY_VALUE` into every one of its call sites' values by hand.\n *\n * Drawn in the muted ink, so an absent value does not read as a value of \"—\".\n */\n placeholder?: ReactNode;\n}\n\nfunction isEmptyValue(node: ReactNode): boolean {\n return node === undefined || node === null || node === false || node === \"\";\n}\n\nconst ROW_PAD: Record<DescriptionListDensity, string> = {\n comfortable: \"py-2.5\",\n compact: \"py-1.5\",\n tight: \"py-px\",\n};\nconst CARD_PAD: Record<DescriptionListDensity, string> = {\n comfortable: \"px-3 py-2.5\",\n compact: \"px-2.5 py-1.5\",\n tight: \"px-2 py-1\",\n};\n\n/** One term and its detail inside a {@link DescriptionList}. */\nexport function DescriptionItem({\n term,\n children,\n hint,\n numeric: numericProp,\n prose: proseProp,\n span,\n placeholder: placeholderProp,\n detailClassName,\n className,\n ...rest\n}: DescriptionItemProps) {\n const {\n layout,\n numeric: listNumeric,\n prose: listProse,\n density,\n columns,\n placeholder: listPlaceholder,\n } = useContext(ListContext);\n const labels = useKitLabels(\"descriptionList\", DEFAULT_DESCRIPTION_LIST_LABELS);\n const empty = isEmptyValue(children);\n const placeholder =\n placeholderProp !== undefined ? placeholderProp : listPlaceholder !== undefined ? listPlaceholder : labels.empty;\n const numeric = numericProp ?? listNumeric;\n const prose = proseProp ?? listProse;\n const cards = layout === \"cards\";\n const stacked = layout === \"stacked\";\n // A hint is a fact: on a touch screen and at Large it is words under the term, not a\n // 14 px \"?\" nobody can hover open — the fields' rule (DUP-2, refactor audit 2026-10),\n // which this string hint had been left out of. Elsewhere the \"?\" as before.\n const shownHint = useInlineHint(hint ? <FieldHint label={hint} side=\"top\" className=\"shrink-0\" /> : undefined);\n const spanClass =\n span === \"full\"\n ? \"col-span-full\"\n : stacked && span !== undefined && span > 1\n ? STACKED_SPAN[Math.min(Math.floor(span), columns) as DescriptionListColumns]\n : undefined;\n\n const termNode = (\n <dt\n className={cn(\n \"flex min-w-0 flex-wrap items-center gap-x-1 text-muted\",\n cards && (density === \"tight\" ? \"text-micro uppercase tracking-wide\" : \"text-xs uppercase tracking-wide\"),\n // Stacked terms are a step smaller than the detail, as kastlan's dialogs set\n // them; in a list that is already small they stay the list's own size.\n stacked && density === \"comfortable\" && \"text-xs\",\n )}\n >\n <span className=\"min-w-0 break-words\">{term}</span>\n {typeof shownHint === \"string\" ? (\n // Its own line under the term, in the reading case of a sentence rather than a\n // card term's capitals — StatTile's inline hint.\n <span\n data-slot=\"description-hint\"\n className=\"basis-full text-caption leading-tight normal-case tracking-normal\"\n >\n {shownHint}\n </span>\n ) : (\n shownHint\n )}\n </dt>\n );\n const detail = (\n <dd\n className={cn(\n \"min-w-0 break-words text-primary\",\n // A stacked figure sits under its term, so it keeps the term's start edge.\n numeric && (stacked ? \"tabular-nums\" : \"tabular-nums text-end\"),\n (cards || stacked) && \"mt-0.5 font-medium\",\n prose && \"font-normal text-secondary\",\n detailClassName,\n )}\n >\n {empty && !isEmptyValue(placeholder) ? (\n <span data-slot=\"description-empty\" className=\"font-normal text-muted\">\n {placeholder}\n </span>\n ) : (\n children\n )}\n </dd>\n );\n\n if (cards) {\n return (\n <div\n {...rest}\n className={cn(\"min-w-0 rounded-md border border-subtle bg-surface\", CARD_PAD[density], spanClass, className)}\n >\n {termNode}\n {detail}\n </div>\n );\n }\n\n if (stacked) {\n return (\n <div {...rest} className={cn(\"min-w-0\", spanClass, className)}>\n {termNode}\n {detail}\n </div>\n );\n }\n\n return (\n <div\n {...rest}\n className={cn(\n \"grid gap-x-4 gap-y-0.5\",\n numeric\n ? // Never stacks: a figure is short (see DescriptionListProps.numeric).\n \"grid-cols-[minmax(0,1fr)_auto] items-baseline\"\n : \"grid-cols-1 @sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] @sm:items-baseline\",\n ROW_PAD[density],\n className,\n )}\n >\n {termNode}\n {detail}\n </div>\n );\n}\n"],"mappings":";AA6KU,cAiIN,YAjIM;AA7KV,SAAS,eAAe,kBAAkB;AAE1C,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,uCAAuC;AA0BhD,MAAM,cAAc,cAAgC;AAAA,EAClD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AACX,CAAC;AAUD,MAAM,eAAuD;AAAA,EAC3D,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAIA,MAAM,eAAuD;AAAA,EAC3D,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAIA,MAAM,WAAyE;AAAA,EAC7E,aAAa,EAAE,KAAK,SAAS,KAAK,UAAU;AAAA,EAC5C,SAAS,EAAE,KAAK,SAAS,KAAK,SAAS;AAAA,EACvC,OAAO,EAAE,KAAK,SAAS,KAAK,UAAU;AACxC;AAmEO,SAAS,gBAAgB;AAAA,EAC9B,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM,QAAQ,WAAW;AACzB,QAAM,UAAU,WAAW;AAC3B,QAAM,MAAM,SAAS,OAAO;AAK5B,QAAM,QACJ,YAAY,SAAY,OAAO,YAAY,aAAa,UAAU,CAAC,MAAM,IAAI,GAAG,OAAO,OAAO,MAAM;AACtG,QAAM,OAAkC,QACpC,EAAE,qBAAqB,sCAAsC,KAAK,WAAW,IAC7E;AACJ,QAAM,MAAwB,EAAE,QAAQ,SAAS,OAAO,SAAS,SAAS,WAAW,GAAG,YAAY;AACpG,QAAM,OAAO,YAAY,UAAU,2BAA2B,YAAY,YAAY,YAAY;AAElG,MAAI,SAAS;AACX,WACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,KAE3B,8BAAC,SAAI,WAAU,cACb;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,eAAa;AAAA,QACb;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,aAAa,IAAI,OAAO;AAAA,UACxB,YAAY,UAAU,oBAAoB,YAAY,YAAY,oBAAoB;AAAA,UACtF;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,KAC3B;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,eAAa;AAAA,MACb,OAAO,OAAO,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AAAA,MACtC,WAAW;AAAA,QACT,QACI,GAAG,QAAQ,IAAI,GAAG;AAAA;AAAA;AAAA,UAGlB,GAAG,cAAc,YAAY,WAAW,wBAAwB;AAAA;AAAA,QACpE;AAAA,QACA;AAAA,MACF;AAAA;AAAA,EACF,GACF;AAEJ;AAsCA,SAAS,aAAa,MAA0B;AAC9C,SAAO,SAAS,UAAa,SAAS,QAAQ,SAAS,SAAS,SAAS;AAC3E;AAEA,MAAM,UAAkD;AAAA,EACtD,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO;AACT;AACA,MAAM,WAAmD;AAAA,EACvD,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO;AACT;AAGO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,OAAO;AAAA,EACP;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,QAAM;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,IACT,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf,IAAI,WAAW,WAAW;AAC1B,QAAM,SAAS,aAAa,mBAAmB,+BAA+B;AAC9E,QAAM,QAAQ,aAAa,QAAQ;AACnC,QAAM,cACJ,oBAAoB,SAAY,kBAAkB,oBAAoB,SAAY,kBAAkB,OAAO;AAC7G,QAAM,UAAU,eAAe;AAC/B,QAAM,QAAQ,aAAa;AAC3B,QAAM,QAAQ,WAAW;AACzB,QAAM,UAAU,WAAW;AAI3B,QAAM,YAAY,cAAc,OAAO,oBAAC,aAAU,OAAO,MAAM,MAAK,OAAM,WAAU,YAAW,IAAK,MAAS;AAC7G,QAAM,YACJ,SAAS,SACL,kBACA,WAAW,SAAS,UAAa,OAAO,IACtC,aAAa,KAAK,IAAI,KAAK,MAAM,IAAI,GAAG,OAAO,CAA2B,IAC1E;AAER,QAAM,WACJ;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,UAAU,YAAY,UAAU,uCAAuC;AAAA;AAAA;AAAA,QAGvE,WAAW,YAAY,iBAAiB;AAAA,MAC1C;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,uBAAuB,gBAAK;AAAA,QAC3C,OAAO,cAAc;AAAA;AAAA;AAAA,UAGpB;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAET;AAAA;AAAA,UACH;AAAA,YAEA;AAAA;AAAA;AAAA,EAEJ;AAEF,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA;AAAA,QAEA,YAAY,UAAU,iBAAiB;AAAA,SACtC,SAAS,YAAY;AAAA,QACtB,SAAS;AAAA,QACT;AAAA,MACF;AAAA,MAEC,mBAAS,CAAC,aAAa,WAAW,IACjC,oBAAC,UAAK,aAAU,qBAAoB,WAAU,0BAC3C,uBACH,IAEA;AAAA;AAAA,EAEJ;AAGF,MAAI,OAAO;AACT,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,GAAG,sDAAsD,SAAS,OAAO,GAAG,WAAW,SAAS;AAAA,QAE1G;AAAA;AAAA,UACA;AAAA;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,WACE,qBAAC,SAAK,GAAG,MAAM,WAAW,GAAG,WAAW,WAAW,SAAS,GACzD;AAAA;AAAA,MACA;AAAA,OACH;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,WAAW;AAAA,QACT;AAAA,QACA;AAAA;AAAA,UAEI;AAAA,YACA;AAAA,QACJ,QAAQ,OAAO;AAAA,QACf;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,QACA;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -76,14 +76,21 @@ function DialogFrame({
|
|
|
76
76
|
}
|
|
77
77
|
function FrameClose({ label, onClose }) {
|
|
78
78
|
const close = useContext(ModalCloseContext) ?? onClose;
|
|
79
|
-
const labels = useKitLabels(
|
|
79
|
+
const labels = useKitLabels(
|
|
80
|
+
"dialogFrame",
|
|
81
|
+
DEFAULT_DIALOG_FRAME_LABELS,
|
|
82
|
+
label === void 0 ? void 0 : { close: label }
|
|
83
|
+
);
|
|
80
84
|
return /* @__PURE__ */ jsx(
|
|
81
85
|
"button",
|
|
82
86
|
{
|
|
83
87
|
type: "button",
|
|
84
88
|
onClick: close,
|
|
85
89
|
"aria-label": labels.close,
|
|
86
|
-
className: cn(
|
|
90
|
+
className: cn(
|
|
91
|
+
"-me-1.5 -mt-1 shrink-0 rounded p-1.5 text-muted outline-none hover:bg-hover hover:text-secondary",
|
|
92
|
+
FOCUS_RING
|
|
93
|
+
),
|
|
87
94
|
children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-5" })
|
|
88
95
|
}
|
|
89
96
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/dialog-frame.tsx"],"sourcesContent":["import { useContext, useId } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Modal, ModalCloseContext } from \"./modal\";\nimport type { ModalProps } from \"./modal\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"./dialog-frame-labels\";\n\nexport interface DialogFrameLabels {\n /** Accessible name of the header's X, when {@link DialogFrameProps.closeButton} shows it. */\n close: string;\n}\n\n// Defined in ./dialog-frame-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_DIALOG_FRAME_LABELS };\n\n/**\n * `ModalProps` minus the three this component owns: the NAME (`labelledBy` — the\n * heading's id is generated inside and never reaches the caller), the content\n * (`children` is the body here, not the whole panel) and `title`, which is the dialog's\n * HEADING and a ReactNode rather than the browser's tooltip string — the collision\n * `PickerSheet` met first. Everything else, `size`, `draggable`, `fullBleed`,\n * `onKeyDown`, a `data-tour` anchor, still reaches the `Modal` — and since 0.35 `ref`\n * (round 0.35 R4): the panel, the `role=\"dialog\"` element the frame's header, body and\n * actions sit in.\n */\nexport interface DialogFrameProps extends Omit<ModalProps, \"labelledBy\" | \"children\" | \"title\"> {\n /** The heading, and therefore the dialog's accessible name (`aria-labelledby`). */\n title: ReactNode;\n /** The smaller line under the heading; wired to `aria-describedby`. */\n description?: ReactNode;\n /**\n * The heading's level. `h2` by default, which is what every dialog in both apps\n * writes; a prop for a page that nests its demos under a real heading.\n */\n headingAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n /**\n * The row under the body — buttons, in the caller's own order and variants (the two\n * apps disagree about the cancel button's variant, so the frame has no opinion on\n * it). Stays put while the body scrolls.\n *\n * A FUNCTION receives the panel's animated close: `(close) => <Button\n * onClick={close}>Cancel</Button>` lowers the panel the way Escape does, where\n * calling `onClose` directly unmounts it at once.\n */\n actions?: ReactNode | ((close: () => void) => ReactNode);\n /**\n * Controls that belong to the HEADER rather than to the actions row — an \"Edit\"\n * toggle, a status badge with a menu, a \"Copy link\" (kastlan). Rendered beside the\n * title, before the optional X; on a phone, where title and controls do not fit on\n * one line, they wrap under the title and the X keeps its corner.\n *\n * A function receives the animated close, as `actions` does.\n */\n headerActions?: ReactNode | ((close: () => void) => ReactNode);\n /**\n * Show an X in the header. Off by default: a centred dialog has a backdrop and\n * Escape, and a form dialog has a Cancel. On for a dialog that commits as it goes\n * and has no actions row, where the X is the only visible way out.\n */\n closeButton?: boolean;\n /** Default: `dialogFrame.close` from the {@link UiKitProvider}, else \"Close\". */\n closeLabel?: string;\n /** Extra classes for the scrolling body (default spacing `space-y-3`). */\n bodyClassName?: string;\n /**\n * Extra classes for the header row (default padding `px-4 pt-4 pb-3`), merged last —\n * `px-3` for a phone sheet whose body runs at `px-3`, so heading and fields share\n * one gutter.\n */\n headerClassName?: string;\n /**\n * A rule under the header, the twin of the one over the actions row — for a tall or\n * full-screen body that scrolls under a header which stays. The body then starts a\n * step below the rule instead of flush against it.\n */\n headerDivider?: boolean;\n /**\n * The body. Optional: a dialog whose title, description and actions are the whole\n * of it (a \"you have unsynced changes\" question) leaves it out, and the frame then\n * renders no body at all rather than an empty padded one.\n */\n children?: ReactNode;\n}\n\n/**\n * A {@link Modal} with the frame every caller was writing by hand: a heading, an\n * optional description, an optional X, a body that scrolls, and an actions row that\n * does not.\n *\n * 34 dialogs across the two apps framed themselves — a heading with an id invented per\n * file (and spelt three ways), four visible type sizes for one thing, ten spellings of\n * one right-aligned button row — and the package's own feedback dialog shipped with no\n * accessible name at all. This makes the name unforgettable: the heading's id comes\n * from `useId()` and goes straight to `Modal`'s `labelledBy`, so a framed dialog cannot\n * announce as just \"dialog\", and two open instances cannot share an id.\n *\n * ## It wraps, it does not change `Modal`\n *\n * `Modal` keeps `labelledBy` and every existing caller compiles untouched. What the\n * frame changes is inside the panel: the panel becomes a flex column with no padding\n * of its own, and only the BODY scrolls. The panel's `max-h-full` is still the outer\n * bound, so a tall form keeps its heading and its Save button on screen instead of\n * scrolling them away with the fields. A caller's own `className` still wins (it is\n * tailwind-merged last), which is how a full-screen phone sheet is spelt:\n * `fullBleed className=\"h-[100dvh] max-w-full rounded-none md:h-auto md:rounded-lg\"`.\n *\n * ## Kept mounted\n *\n * `open` reaches the `Modal` like every other prop, so `<DialogFrame open={open} …>`\n * replaces the `{open && <DialogFrame …/>}` gate (kastlan's `FormModal` was only\n * that gate) and gains the exit a caller-driven close otherwise skips. See\n * {@link ModalProps.open}.\n *\n * ## What it is not\n *\n * Not `FullBleedDialog`: that is the phone's full-screen editor with its own `open`,\n * Back handling and a required X, and it already draws a frame of its own. And not a\n * form: submit handling, a pending label and close-on-success stay the caller's.\n * Focus lands on the panel, as `Modal` decides — not on the first field, so opening\n * does not pop a phone's keyboard; an `autoFocus` in the body overrides that from the\n * caller's side, and should be a decision rather than a habit.\n */\nexport function DialogFrame({\n title,\n description,\n headingAs: Heading = \"h2\",\n actions,\n headerActions,\n closeButton = false,\n closeLabel,\n bodyClassName,\n headerClassName,\n headerDivider = false,\n className,\n children,\n \"aria-describedby\": describedBy,\n ...modal\n}: DialogFrameProps) {\n const titleId = useId();\n const descriptionId = useId();\n const hasDescription = description !== undefined && description !== null;\n // `false` too, so `{cond && <Body/>}` with a false `cond` means \"no body\".\n const hasBody = children !== undefined && children !== null && children !== false;\n\n return (\n <Modal\n {...modal}\n labelledBy={titleId}\n // The caller's own description (a warning inside the body, say) is ADDED to the\n // frame's, not traded for it: both are the dialog's.\n aria-describedby={[hasDescription ? descriptionId : undefined, describedBy].filter(Boolean).join(\" \") || undefined}\n // `overflow-hidden` replaces the panel's own `overflow-y-auto` (tailwind-merge\n // treats them as one group), `p-0` its `p-4`: the scroller and the padding move\n // to the body, which is the only part that should move.\n className={cn(\"flex flex-col overflow-hidden p-0\", className)}\n >\n <div\n className={cn(\n // `last:pb-4`: a frame with neither body nor actions closes on its header.\n \"flex shrink-0 items-start justify-between gap-2 px-4 pt-4 pb-3 last:pb-4\",\n headerDivider && \"border-b border-subtle\",\n headerClassName,\n )}\n >\n {/* Title and header actions share a wrapping row of their own, so it is THEY\n that wrap on a phone — the actions drop under a title that needs its 12rem —\n while the X stays outside it, pinned to the top-end corner. */}\n <div className=\"flex min-w-0 flex-1 flex-wrap items-start justify-between gap-x-3 gap-y-2\">\n <div className=\"min-w-0 grow basis-48\">\n <Heading id={titleId} className=\"text-lg font-semibold leading-snug text-primary\">\n {title}\n </Heading>\n {hasDescription && (\n <p id={descriptionId} className=\"mt-0.5 text-sm text-muted\">\n {description}\n </p>\n )}\n </div>\n {headerActions !== undefined && headerActions !== null && headerActions !== false && (\n <FrameHeaderActions actions={headerActions} onClose={modal.onClose} />\n )}\n </div>\n {closeButton && <FrameClose label={closeLabel} onClose={modal.onClose} />}\n </div>\n {hasBody && (\n <div\n className={cn(\n // `min-h-0` is what lets a flex child shrink below its content and scroll;\n // `last:pb-4` closes a frame that has no actions row under it.\n \"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-4 pb-3 last:pb-4\",\n headerDivider && \"pt-3\",\n bodyClassName,\n )}\n >\n {children}\n </div>\n )}\n {actions !== undefined && actions !== null && (\n <FrameActions actions={actions} onClose={modal.onClose} divider={hasBody} />\n )}\n </Modal>\n );\n}\n\n/** The X. A component of its own so it can read the panel's animated close, which\n * only exists INSIDE the `Modal` (the frame's own body runs outside it). */\nfunction FrameClose({ label, onClose }: { label?: string; onClose: () => void }) {\n const close = useContext(ModalCloseContext) ?? onClose;\n const labels = useKitLabels(\"dialogFrame\", DEFAULT_DIALOG_FRAME_LABELS, label === undefined ? undefined : { close: label });\n return (\n <button\n type=\"button\"\n onClick={close}\n aria-label={labels.close}\n className={cn(\"-me-1.5 -mt-1 shrink-0 rounded p-1.5 text-muted outline-none hover:bg-hover hover:text-secondary\", FOCUS_RING)}\n >\n <X aria-hidden className=\"size-5\" />\n </button>\n );\n}\n\n/** The header's own controls; a component for the same reason as {@link FrameClose}. */\nfunction FrameHeaderActions({\n actions,\n onClose,\n}: {\n actions: NonNullable<DialogFrameProps[\"headerActions\"]>;\n onClose: () => void;\n}) {\n const close = useContext(ModalCloseContext) ?? onClose;\n return (\n <div data-dialog-header-actions=\"\" className=\"flex shrink-0 flex-wrap items-center gap-2\">\n {typeof actions === \"function\" ? actions(close) : actions}\n </div>\n );\n}\n\nfunction FrameActions({\n actions,\n onClose,\n divider,\n}: {\n actions: NonNullable<DialogFrameProps[\"actions\"]>;\n onClose: () => void;\n /** Off when there is no body: nothing scrolls, so there is no edge to mark, and a\n * rule straight under the description would cut the question from its answers. */\n divider: boolean;\n}) {\n const close = useContext(ModalCloseContext) ?? onClose;\n return (\n // The border marks where the scrolling stops; `flex-wrap` keeps three long\n // translated labels on a phone from pushing the row wider than the sheet.\n <div\n className={cn(\n \"flex shrink-0 flex-wrap items-center justify-end gap-2 px-4 py-3\",\n divider && \"border-t border-subtle\",\n )}\n >\n {typeof actions === \"function\" ? actions(close) : actions}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/dialog-frame.tsx"],"sourcesContent":["import { useContext, useId } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { Modal, ModalCloseContext } from \"./modal\";\nimport type { ModalProps } from \"./modal\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"./dialog-frame-labels\";\n\nexport interface DialogFrameLabels {\n /** Accessible name of the header's X, when {@link DialogFrameProps.closeButton} shows it. */\n close: string;\n}\n\n// Defined in ./dialog-frame-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_DIALOG_FRAME_LABELS };\n\n/**\n * `ModalProps` minus the three this component owns: the NAME (`labelledBy` — the\n * heading's id is generated inside and never reaches the caller), the content\n * (`children` is the body here, not the whole panel) and `title`, which is the dialog's\n * HEADING and a ReactNode rather than the browser's tooltip string — the collision\n * `PickerSheet` met first. Everything else, `size`, `draggable`, `fullBleed`,\n * `onKeyDown`, a `data-tour` anchor, still reaches the `Modal` — and since 0.35 `ref`\n * (round 0.35 R4): the panel, the `role=\"dialog\"` element the frame's header, body and\n * actions sit in.\n */\nexport interface DialogFrameProps extends Omit<ModalProps, \"labelledBy\" | \"children\" | \"title\"> {\n /** The heading, and therefore the dialog's accessible name (`aria-labelledby`). */\n title: ReactNode;\n /** The smaller line under the heading; wired to `aria-describedby`. */\n description?: ReactNode;\n /**\n * The heading's level. `h2` by default, which is what every dialog in both apps\n * writes; a prop for a page that nests its demos under a real heading.\n */\n headingAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n /**\n * The row under the body — buttons, in the caller's own order and variants (the two\n * apps disagree about the cancel button's variant, so the frame has no opinion on\n * it). Stays put while the body scrolls.\n *\n * A FUNCTION receives the panel's animated close: `(close) => <Button\n * onClick={close}>Cancel</Button>` lowers the panel the way Escape does, where\n * calling `onClose` directly unmounts it at once.\n */\n actions?: ReactNode | ((close: () => void) => ReactNode);\n /**\n * Controls that belong to the HEADER rather than to the actions row — an \"Edit\"\n * toggle, a status badge with a menu, a \"Copy link\" (kastlan). Rendered beside the\n * title, before the optional X; on a phone, where title and controls do not fit on\n * one line, they wrap under the title and the X keeps its corner.\n *\n * A function receives the animated close, as `actions` does.\n */\n headerActions?: ReactNode | ((close: () => void) => ReactNode);\n /**\n * Show an X in the header. Off by default: a centred dialog has a backdrop and\n * Escape, and a form dialog has a Cancel. On for a dialog that commits as it goes\n * and has no actions row, where the X is the only visible way out.\n */\n closeButton?: boolean;\n /** Default: `dialogFrame.close` from the {@link UiKitProvider}, else \"Close\". */\n closeLabel?: string;\n /** Extra classes for the scrolling body (default spacing `space-y-3`). */\n bodyClassName?: string;\n /**\n * Extra classes for the header row (default padding `px-4 pt-4 pb-3`), merged last —\n * `px-3` for a phone sheet whose body runs at `px-3`, so heading and fields share\n * one gutter.\n */\n headerClassName?: string;\n /**\n * A rule under the header, the twin of the one over the actions row — for a tall or\n * full-screen body that scrolls under a header which stays. The body then starts a\n * step below the rule instead of flush against it.\n */\n headerDivider?: boolean;\n /**\n * The body. Optional: a dialog whose title, description and actions are the whole\n * of it (a \"you have unsynced changes\" question) leaves it out, and the frame then\n * renders no body at all rather than an empty padded one.\n */\n children?: ReactNode;\n}\n\n/**\n * A {@link Modal} with the frame every caller was writing by hand: a heading, an\n * optional description, an optional X, a body that scrolls, and an actions row that\n * does not.\n *\n * 34 dialogs across the two apps framed themselves — a heading with an id invented per\n * file (and spelt three ways), four visible type sizes for one thing, ten spellings of\n * one right-aligned button row — and the package's own feedback dialog shipped with no\n * accessible name at all. This makes the name unforgettable: the heading's id comes\n * from `useId()` and goes straight to `Modal`'s `labelledBy`, so a framed dialog cannot\n * announce as just \"dialog\", and two open instances cannot share an id.\n *\n * ## It wraps, it does not change `Modal`\n *\n * `Modal` keeps `labelledBy` and every existing caller compiles untouched. What the\n * frame changes is inside the panel: the panel becomes a flex column with no padding\n * of its own, and only the BODY scrolls. The panel's `max-h-full` is still the outer\n * bound, so a tall form keeps its heading and its Save button on screen instead of\n * scrolling them away with the fields. A caller's own `className` still wins (it is\n * tailwind-merged last), which is how a full-screen phone sheet is spelt:\n * `fullBleed className=\"h-[100dvh] max-w-full rounded-none md:h-auto md:rounded-lg\"`.\n *\n * ## Kept mounted\n *\n * `open` reaches the `Modal` like every other prop, so `<DialogFrame open={open} …>`\n * replaces the `{open && <DialogFrame …/>}` gate (kastlan's `FormModal` was only\n * that gate) and gains the exit a caller-driven close otherwise skips. See\n * {@link ModalProps.open}.\n *\n * ## What it is not\n *\n * Not `FullBleedDialog`: that is the phone's full-screen editor with its own `open`,\n * Back handling and a required X, and it already draws a frame of its own. And not a\n * form: submit handling, a pending label and close-on-success stay the caller's.\n * Focus lands on the panel, as `Modal` decides — not on the first field, so opening\n * does not pop a phone's keyboard; an `autoFocus` in the body overrides that from the\n * caller's side, and should be a decision rather than a habit.\n */\nexport function DialogFrame({\n title,\n description,\n headingAs: Heading = \"h2\",\n actions,\n headerActions,\n closeButton = false,\n closeLabel,\n bodyClassName,\n headerClassName,\n headerDivider = false,\n className,\n children,\n \"aria-describedby\": describedBy,\n ...modal\n}: DialogFrameProps) {\n const titleId = useId();\n const descriptionId = useId();\n const hasDescription = description !== undefined && description !== null;\n // `false` too, so `{cond && <Body/>}` with a false `cond` means \"no body\".\n const hasBody = children !== undefined && children !== null && children !== false;\n\n return (\n <Modal\n {...modal}\n labelledBy={titleId}\n // The caller's own description (a warning inside the body, say) is ADDED to the\n // frame's, not traded for it: both are the dialog's.\n aria-describedby={\n [hasDescription ? descriptionId : undefined, describedBy].filter(Boolean).join(\" \") || undefined\n }\n // `overflow-hidden` replaces the panel's own `overflow-y-auto` (tailwind-merge\n // treats them as one group), `p-0` its `p-4`: the scroller and the padding move\n // to the body, which is the only part that should move.\n className={cn(\"flex flex-col overflow-hidden p-0\", className)}\n >\n <div\n className={cn(\n // `last:pb-4`: a frame with neither body nor actions closes on its header.\n \"flex shrink-0 items-start justify-between gap-2 px-4 pt-4 pb-3 last:pb-4\",\n headerDivider && \"border-b border-subtle\",\n headerClassName,\n )}\n >\n {/* Title and header actions share a wrapping row of their own, so it is THEY\n that wrap on a phone — the actions drop under a title that needs its 12rem —\n while the X stays outside it, pinned to the top-end corner. */}\n <div className=\"flex min-w-0 flex-1 flex-wrap items-start justify-between gap-x-3 gap-y-2\">\n <div className=\"min-w-0 grow basis-48\">\n <Heading id={titleId} className=\"text-lg font-semibold leading-snug text-primary\">\n {title}\n </Heading>\n {hasDescription && (\n <p id={descriptionId} className=\"mt-0.5 text-sm text-muted\">\n {description}\n </p>\n )}\n </div>\n {headerActions !== undefined && headerActions !== null && headerActions !== false && (\n <FrameHeaderActions actions={headerActions} onClose={modal.onClose} />\n )}\n </div>\n {closeButton && <FrameClose label={closeLabel} onClose={modal.onClose} />}\n </div>\n {hasBody && (\n <div\n className={cn(\n // `min-h-0` is what lets a flex child shrink below its content and scroll;\n // `last:pb-4` closes a frame that has no actions row under it.\n \"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-4 pb-3 last:pb-4\",\n headerDivider && \"pt-3\",\n bodyClassName,\n )}\n >\n {children}\n </div>\n )}\n {actions !== undefined && actions !== null && (\n <FrameActions actions={actions} onClose={modal.onClose} divider={hasBody} />\n )}\n </Modal>\n );\n}\n\n/** The X. A component of its own so it can read the panel's animated close, which\n * only exists INSIDE the `Modal` (the frame's own body runs outside it). */\nfunction FrameClose({ label, onClose }: { label?: string; onClose: () => void }) {\n const close = useContext(ModalCloseContext) ?? onClose;\n const labels = useKitLabels(\n \"dialogFrame\",\n DEFAULT_DIALOG_FRAME_LABELS,\n label === undefined ? undefined : { close: label },\n );\n return (\n <button\n type=\"button\"\n onClick={close}\n aria-label={labels.close}\n className={cn(\n \"-me-1.5 -mt-1 shrink-0 rounded p-1.5 text-muted outline-none hover:bg-hover hover:text-secondary\",\n FOCUS_RING,\n )}\n >\n <X aria-hidden className=\"size-5\" />\n </button>\n );\n}\n\n/** The header's own controls; a component for the same reason as {@link FrameClose}. */\nfunction FrameHeaderActions({\n actions,\n onClose,\n}: {\n actions: NonNullable<DialogFrameProps[\"headerActions\"]>;\n onClose: () => void;\n}) {\n const close = useContext(ModalCloseContext) ?? onClose;\n return (\n <div data-dialog-header-actions=\"\" className=\"flex shrink-0 flex-wrap items-center gap-2\">\n {typeof actions === \"function\" ? actions(close) : actions}\n </div>\n );\n}\n\nfunction FrameActions({\n actions,\n onClose,\n divider,\n}: {\n actions: NonNullable<DialogFrameProps[\"actions\"]>;\n onClose: () => void;\n /** Off when there is no body: nothing scrolls, so there is no edge to mark, and a\n * rule straight under the description would cut the question from its answers. */\n divider: boolean;\n}) {\n const close = useContext(ModalCloseContext) ?? onClose;\n return (\n // The border marks where the scrolling stops; `flex-wrap` keeps three long\n // translated labels on a phone from pushing the row wider than the sheet.\n <div\n className={cn(\n \"flex shrink-0 flex-wrap items-center justify-end gap-2 px-4 py-3\",\n divider && \"border-t border-subtle\",\n )}\n >\n {typeof actions === \"function\" ? actions(close) : actions}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -137,33 +137,23 @@ function Disclosure({
|
|
|
137
137
|
}
|
|
138
138
|
);
|
|
139
139
|
const header = Heading ? /* @__PURE__ */ jsx(Heading, { className: trailingBeside ? "min-w-0 flex-1" : void 0, children: button }) : button;
|
|
140
|
-
return /* @__PURE__ */ jsxs(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
header,
|
|
158
|
-
/* @__PURE__ */ jsx("div", { className: "relative flex shrink-0 items-center gap-2", children: trailing }),
|
|
159
|
-
chevronAtEnd && /* @__PURE__ */ jsx(Chevron, { open, small: !card })
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
) : header,
|
|
163
|
-
hasBody && /* @__PURE__ */ jsx(Collapse, { id: bodyId, open, keepMounted, children: /* @__PURE__ */ jsx("div", { className: cn(card ? "space-y-3 px-4 pb-4" : !menu && "space-y-2 pt-2", bodyClassName), children }) })
|
|
164
|
-
]
|
|
165
|
-
}
|
|
166
|
-
);
|
|
140
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, className: cn(card && "rounded-lg border border-subtle bg-surface shadow-sm", className), children: [
|
|
141
|
+
trailingBeside ? /* @__PURE__ */ jsxs(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
className: cn(
|
|
145
|
+
"relative flex items-center gap-2",
|
|
146
|
+
card && cn("rounded-lg pe-4 has-[[data-disclosure-trigger]:enabled:hover]:bg-hover", joined && "rounded-b-none")
|
|
147
|
+
),
|
|
148
|
+
children: [
|
|
149
|
+
header,
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "relative flex shrink-0 items-center gap-2", children: trailing }),
|
|
151
|
+
chevronAtEnd && /* @__PURE__ */ jsx(Chevron, { open, small: !card })
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
) : header,
|
|
155
|
+
hasBody && /* @__PURE__ */ jsx(Collapse, { id: bodyId, open, keepMounted, children: /* @__PURE__ */ jsx("div", { className: cn(card ? "space-y-3 px-4 pb-4" : !menu && "space-y-2 pt-2", bodyClassName), children }) })
|
|
156
|
+
] });
|
|
167
157
|
}
|
|
168
158
|
function Chevron({
|
|
169
159
|
open,
|