@cosmicdrift/kumiko-renderer 0.373.0 → 0.377.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/app/action-form-support.d.ts +58 -0
- package/dist/app/action-form-support.js +96 -0
- package/dist/app/client-actions.d.ts +46 -0
- package/dist/app/client-actions.js +60 -0
- package/dist/app/column-renderers.js +1 -1
- package/dist/app/config-edit-shim.js +1 -1
- package/dist/app/content-preview.js +1 -6
- package/dist/app/custom-screens.js +1 -1
- package/dist/app/dashboard-body.d.ts +1 -1
- package/dist/app/entity-list-expanded-row.d.ts +1 -1
- package/dist/app/entity-list-expanded-row.js +4 -0
- package/dist/app/form-schema.d.ts +1 -1
- package/dist/app/form-schema.js +2 -2
- package/dist/app/header-actions-bar.d.ts +19 -0
- package/dist/app/header-actions-bar.js +30 -0
- package/dist/app/kumiko-screen.d.ts +1 -1
- package/dist/app/kumiko-screen.js +134 -66
- package/dist/app/list-empty-content.d.ts +2 -2
- package/dist/app/list-empty-content.js +3 -2
- package/dist/app/list-facets.d.ts +1 -1
- package/dist/app/nav.js +2 -2
- package/dist/app/query-state-banners.d.ts +1 -1
- package/dist/app/reference-link.d.ts +1 -1
- package/dist/app/row-actions.d.ts +10 -2
- package/dist/app/row-actions.js +85 -6
- package/dist/app/secret-mint-body.d.ts +1 -1
- package/dist/app/secrets-edit-body.d.ts +1 -1
- package/dist/app/use-record-actions.d.ts +12 -0
- package/dist/app/use-record-actions.js +30 -0
- package/dist/app/write-failed-error.d.ts +1 -1
- package/dist/components/availability-options-select.d.ts +1 -1
- package/dist/components/embedded-list-field.d.ts +1 -1
- package/dist/components/embedded-list-field.js +1 -1
- package/dist/components/file-list-field.d.ts +1 -1
- package/dist/components/grid-cell-for-field.d.ts +4 -2
- package/dist/components/grid-cell-for-field.js +2 -2
- package/dist/components/multi-select-checkboxes.d.ts +1 -1
- package/dist/components/multi-select-checkboxes.js +1 -1
- package/dist/components/query-options-select.d.ts +1 -1
- package/dist/components/related-list-section.d.ts +1 -1
- package/dist/components/related-list-section.js +16 -3
- package/dist/components/render-edit-logic.d.ts +1 -1
- package/dist/components/render-edit-types.d.ts +2 -2
- package/dist/components/render-edit.d.ts +1 -1
- package/dist/components/render-edit.js +5 -3
- package/dist/components/render-field.d.ts +5 -2
- package/dist/components/render-field.js +103 -9
- package/dist/components/render-list.d.ts +1 -1
- package/dist/components/render-list.js +20 -4
- package/dist/components/write-form-section.d.ts +1 -1
- package/dist/components/write-form-section.js +1 -1
- package/dist/context/dispatcher-context.d.ts +1 -1
- package/dist/format-when.js +1 -1
- package/dist/headless/apex/css.d.ts +3 -0
- package/dist/headless/apex/css.js +298 -0
- package/dist/headless/apex/index.d.ts +236 -0
- package/dist/headless/apex/index.js +377 -0
- package/dist/headless/apex/lightbox.d.ts +4 -0
- package/dist/headless/apex/lightbox.js +81 -0
- package/dist/headless/contracts/asset.d.ts +15 -0
- package/dist/headless/contracts/asset.js +17 -0
- package/dist/headless/contracts/index.d.ts +3 -0
- package/dist/headless/contracts/index.js +1 -0
- package/dist/headless/contracts/locale.d.ts +7 -0
- package/dist/headless/contracts/locale.js +11 -0
- package/dist/headless/contracts/primitives.d.ts +93 -0
- package/dist/headless/contracts/primitives.js +21 -0
- package/dist/headless/dispatcher/index.d.ts +2 -0
- package/dist/headless/dispatcher/index.js +1 -0
- package/dist/headless/dispatcher/types.d.ts +86 -0
- package/dist/headless/dispatcher/types.js +34 -0
- package/dist/headless/form/form-controller.d.ts +2 -0
- package/dist/headless/form/form-controller.js +409 -0
- package/dist/headless/form/index.d.ts +3 -0
- package/dist/headless/form/index.js +2 -0
- package/dist/headless/form/types.d.ts +76 -0
- package/dist/headless/form/types.js +1 -0
- package/dist/headless/form/zod-bridge.d.ts +10 -0
- package/dist/headless/form/zod-bridge.js +103 -0
- package/dist/headless/format/document-values.d.ts +12 -0
- package/dist/headless/format/document-values.js +70 -0
- package/dist/headless/format/escape.d.ts +5 -0
- package/dist/headless/format/escape.js +75 -0
- package/dist/headless/format/html-template.d.ts +9 -0
- package/dist/headless/format/html-template.js +39 -0
- package/dist/headless/format/index.d.ts +12 -0
- package/dist/headless/format/index.js +237 -0
- package/dist/headless/format/money.d.ts +3 -0
- package/dist/headless/format/money.js +39 -0
- package/dist/headless/index.d.ts +14 -0
- package/dist/headless/index.js +9 -0
- package/dist/headless/locale-routing/index.d.ts +41 -0
- package/dist/headless/locale-routing/index.js +145 -0
- package/dist/headless/nav/index.d.ts +2 -0
- package/dist/headless/nav/index.js +1 -0
- package/dist/headless/nav/resolve.d.ts +2 -0
- package/dist/headless/nav/resolve.js +71 -0
- package/dist/headless/nav/types.d.ts +26 -0
- package/dist/headless/nav/types.js +1 -0
- package/dist/headless/store/create-store.d.ts +2 -0
- package/dist/headless/store/create-store.js +43 -0
- package/dist/headless/store/equality.d.ts +1 -0
- package/dist/headless/store/equality.js +34 -0
- package/dist/headless/store/index.d.ts +3 -0
- package/dist/headless/store/index.js +2 -0
- package/dist/headless/store/types.d.ts +7 -0
- package/dist/headless/store/types.js +10 -0
- package/dist/headless/view-model/edit.d.ts +18 -0
- package/dist/headless/view-model/edit.js +501 -0
- package/dist/headless/view-model/embedded-list.d.ts +27 -0
- package/dist/headless/view-model/embedded-list.js +63 -0
- package/dist/headless/view-model/index.d.ts +7 -0
- package/dist/headless/view-model/index.js +3 -0
- package/dist/headless/view-model/list-file-display.d.ts +3 -0
- package/dist/headless/view-model/list-file-display.js +27 -0
- package/dist/headless/view-model/list.d.ts +16 -0
- package/dist/headless/view-model/list.js +229 -0
- package/dist/headless/view-model/types.d.ts +382 -0
- package/dist/headless/view-model/types.js +1 -0
- package/dist/hooks/use-ai-text.d.ts +1 -1
- package/dist/hooks/use-form.d.ts +1 -1
- package/dist/hooks/use-form.js +1 -1
- package/dist/hooks/use-mutation.d.ts +1 -1
- package/dist/hooks/use-query.d.ts +1 -1
- package/dist/hooks/use-store.d.ts +1 -1
- package/dist/hooks/use-store.js +7 -7
- package/dist/hooks/use-stream-handler.d.ts +1 -1
- package/dist/i18n-defaults.js +7 -0
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -4
- package/dist/live/csrf.d.ts +2 -0
- package/dist/live/csrf.js +60 -0
- package/dist/live/dispatcher-live.d.ts +7 -0
- package/dist/live/dispatcher-live.js +286 -0
- package/dist/live/envelopes.d.ts +101 -0
- package/dist/live/envelopes.js +35 -0
- package/dist/live/error-mapping.d.ts +16 -0
- package/dist/live/error-mapping.js +82 -0
- package/dist/live/index.d.ts +7 -0
- package/dist/live/index.js +5 -0
- package/dist/live/locale.d.ts +2 -0
- package/dist/live/locale.js +25 -0
- package/dist/live/sse-stream.d.ts +14 -0
- package/dist/live/sse-stream.js +128 -0
- package/dist/primitives.d.ts +53 -36
- package/dist/primitives.js +1 -1
- package/dist/session/session-ended.d.ts +1 -1
- package/dist/tokens.d.ts +3 -4
- package/dist/tokens.js +4 -5
- package/dist/web/app/app-schema-boot.d.ts +19 -0
- package/dist/web/app/app-schema-boot.js +124 -0
- package/dist/web/app/browser-locale.d.ts +22 -0
- package/dist/web/app/browser-locale.js +78 -0
- package/dist/web/app/client-plugin.d.ts +106 -0
- package/dist/web/app/client-plugin.js +12 -0
- package/dist/web/app/create-app.d.ts +104 -0
- package/dist/web/app/create-app.js +498 -0
- package/dist/web/app/create-public-surface.d.ts +36 -0
- package/dist/web/app/create-public-surface.js +40 -0
- package/dist/web/app/dashboard-body.d.ts +3 -0
- package/dist/web/app/dashboard-body.js +865 -0
- package/dist/web/app/dashboard-refresh.d.ts +6 -0
- package/dist/web/app/dashboard-refresh.js +15 -0
- package/dist/web/app/document-lang-sync.d.ts +4 -0
- package/dist/web/app/document-lang-sync.js +27 -0
- package/dist/web/app/draft-storage.d.ts +11 -0
- package/dist/web/app/draft-storage.js +53 -0
- package/dist/web/app/nav-providers-context.d.ts +13 -0
- package/dist/web/app/nav-providers-context.js +18 -0
- package/dist/web/app/nav.d.ts +40 -0
- package/dist/web/app/nav.js +231 -0
- package/dist/web/app/plain-content-editor.d.ts +3 -0
- package/dist/web/app/plain-content-editor.js +30 -0
- package/dist/web/app/relative-time.d.ts +10 -0
- package/dist/web/app/relative-time.js +56 -0
- package/dist/web/app/resolvers-context.d.ts +12 -0
- package/dist/web/app/resolvers-context.js +10 -0
- package/dist/web/app/rich-content-editor.d.ts +3 -0
- package/dist/web/app/rich-content-editor.js +15 -0
- package/dist/web/app/root-error-fallback.d.ts +2 -0
- package/dist/web/app/root-error-fallback.js +21 -0
- package/dist/web/app/screen-error-fallback.d.ts +6 -0
- package/dist/web/app/screen-error-fallback.js +17 -0
- package/dist/web/app/tiptap-editor.d.ts +3 -0
- package/dist/web/app/tiptap-editor.js +120 -0
- package/dist/web/app/toast-action-notifier.d.ts +5 -0
- package/dist/web/app/toast-action-notifier.js +17 -0
- package/dist/web/components/config-cascade.d.ts +13 -0
- package/dist/web/components/config-cascade.js +155 -0
- package/dist/web/components/config-source-badge.d.ts +6 -0
- package/dist/web/components/config-source-badge.js +55 -0
- package/dist/web/default-color-scheme.d.ts +2 -0
- package/dist/web/default-color-scheme.js +3 -0
- package/dist/web/icons.d.ts +94 -0
- package/dist/web/icons.js +100 -0
- package/dist/web/index.d.ts +78 -0
- package/dist/web/index.js +53 -0
- package/dist/web/layout/app-layout.d.ts +21 -0
- package/dist/web/layout/app-layout.js +8 -0
- package/dist/web/layout/avatar.d.ts +12 -0
- package/dist/web/layout/avatar.js +63 -0
- package/dist/web/layout/default-app-shell.d.ts +41 -0
- package/dist/web/layout/default-app-shell.js +16 -0
- package/dist/web/layout/editor-panel.d.ts +10 -0
- package/dist/web/layout/editor-panel.js +29 -0
- package/dist/web/layout/fill-classes.d.ts +9 -0
- package/dist/web/layout/fill-classes.js +15 -0
- package/dist/web/layout/filter-app-schema-navs.d.ts +7 -0
- package/dist/web/layout/filter-app-schema-navs.js +68 -0
- package/dist/web/layout/header-action-group.d.ts +6 -0
- package/dist/web/layout/header-action-group.js +8 -0
- package/dist/web/layout/header-overflow-menu.d.ts +4 -0
- package/dist/web/layout/header-overflow-menu.js +8 -0
- package/dist/web/layout/language-menu-items.d.ts +8 -0
- package/dist/web/layout/language-menu-items.js +16 -0
- package/dist/web/layout/language-switcher.d.ts +25 -0
- package/dist/web/layout/language-switcher.js +46 -0
- package/dist/web/layout/nav-tree.d.ts +19 -0
- package/dist/web/layout/nav-tree.js +536 -0
- package/dist/web/layout/page-header-slot.d.ts +25 -0
- package/dist/web/layout/page-header-slot.js +50 -0
- package/dist/web/layout/profile-menu.d.ts +13 -0
- package/dist/web/layout/profile-menu.js +6 -0
- package/dist/web/layout/shell-breadcrumb.d.ts +11 -0
- package/dist/web/layout/shell-breadcrumb.js +34 -0
- package/dist/web/layout/shell-header.d.ts +12 -0
- package/dist/web/layout/shell-header.js +159 -0
- package/dist/web/layout/sidebar-brand.d.ts +15 -0
- package/dist/web/layout/sidebar-brand.js +9 -0
- package/dist/web/layout/sidebar-panel.d.ts +41 -0
- package/dist/web/layout/sidebar-panel.js +107 -0
- package/dist/web/layout/sidebar-user.d.ts +8 -0
- package/dist/web/layout/sidebar-user.js +17 -0
- package/dist/web/layout/sidebar.d.ts +17 -0
- package/dist/web/layout/sidebar.js +11 -0
- package/dist/web/layout/target-resolver-stub.d.ts +13 -0
- package/dist/web/layout/target-resolver-stub.js +61 -0
- package/dist/web/layout/target-url.d.ts +12 -0
- package/dist/web/layout/target-url.js +77 -0
- package/dist/web/layout/theme-label.d.ts +15 -0
- package/dist/web/layout/theme-label.js +35 -0
- package/dist/web/layout/theme-menu-item.d.ts +12 -0
- package/dist/web/layout/theme-menu-item.js +16 -0
- package/dist/web/layout/theme-toggle.d.ts +17 -0
- package/dist/web/layout/theme-toggle.js +22 -0
- package/dist/web/layout/topbar.d.ts +8 -0
- package/dist/web/layout/topbar.js +4 -0
- package/dist/web/layout/workspace-shell.d.ts +63 -0
- package/dist/web/layout/workspace-shell.js +220 -0
- package/dist/web/layout/workspace-switcher.d.ts +15 -0
- package/dist/web/layout/workspace-switcher.js +36 -0
- package/dist/web/lib/accept-attr.d.ts +1 -0
- package/dist/web/lib/accept-attr.js +6 -0
- package/dist/web/lib/clamp.d.ts +1 -0
- package/dist/web/lib/clamp.js +3 -0
- package/dist/web/lib/cn.d.ts +6 -0
- package/dist/web/lib/cn.js +9 -0
- package/dist/web/lib/download.d.ts +2 -0
- package/dist/web/lib/download.js +24 -0
- package/dist/web/lib/file-ref-download.d.ts +15 -0
- package/dist/web/lib/file-ref-download.js +62 -0
- package/dist/web/lib/file-upload-form-data.d.ts +7 -0
- package/dist/web/lib/file-upload-form-data.js +11 -0
- package/dist/web/lib/resize-image.d.ts +1 -0
- package/dist/web/lib/resize-image.js +66 -0
- package/dist/web/lib/upload-file-ref.d.ts +9 -0
- package/dist/web/lib/upload-file-ref.js +29 -0
- package/dist/web/primitives/action-menu.d.ts +34 -0
- package/dist/web/primitives/action-menu.js +30 -0
- package/dist/web/primitives/calendar-popover.d.ts +13 -0
- package/dist/web/primitives/calendar-popover.js +76 -0
- package/dist/web/primitives/combobox.d.ts +56 -0
- package/dist/web/primitives/combobox.js +127 -0
- package/dist/web/primitives/date-field.d.ts +18 -0
- package/dist/web/primitives/date-field.js +74 -0
- package/dist/web/primitives/date-input.d.ts +1 -0
- package/dist/web/primitives/date-input.js +5 -0
- package/dist/web/primitives/date-parse.d.ts +11 -0
- package/dist/web/primitives/date-parse.js +159 -0
- package/dist/web/primitives/dialog.d.ts +3 -0
- package/dist/web/primitives/dialog.js +42 -0
- package/dist/web/primitives/drawer.d.ts +3 -0
- package/dist/web/primitives/drawer.js +17 -0
- package/dist/web/primitives/dropdown-menu.d.ts +15 -0
- package/dist/web/primitives/dropdown-menu.js +56 -0
- package/dist/web/primitives/embedded-list-input.d.ts +9 -0
- package/dist/web/primitives/embedded-list-input.js +382 -0
- package/dist/web/primitives/file-thumbnail.d.ts +25 -0
- package/dist/web/primitives/file-thumbnail.js +90 -0
- package/dist/web/primitives/file-upload.d.ts +21 -0
- package/dist/web/primitives/file-upload.js +56 -0
- package/dist/web/primitives/index.d.ts +34 -0
- package/dist/web/primitives/index.js +2021 -0
- package/dist/web/primitives/json-view.d.ts +7 -0
- package/dist/web/primitives/json-view.js +84 -0
- package/dist/web/primitives/layout.d.ts +27 -0
- package/dist/web/primitives/layout.js +30 -0
- package/dist/web/primitives/lightbox.d.ts +3 -0
- package/dist/web/primitives/lightbox.js +36 -0
- package/dist/web/primitives/locale-number.d.ts +2 -0
- package/dist/web/primitives/locale-number.js +54 -0
- package/dist/web/primitives/located-timestamp-input.d.ts +22 -0
- package/dist/web/primitives/located-timestamp-input.js +22 -0
- package/dist/web/primitives/metric.d.ts +5 -0
- package/dist/web/primitives/metric.js +18 -0
- package/dist/web/primitives/modal-shell.d.ts +17 -0
- package/dist/web/primitives/modal-shell.js +9 -0
- package/dist/web/primitives/modal.d.ts +3 -0
- package/dist/web/primitives/modal.js +13 -0
- package/dist/web/primitives/money-input.d.ts +18 -0
- package/dist/web/primitives/money-input.js +122 -0
- package/dist/web/primitives/narrow-pinned-footer.d.ts +12 -0
- package/dist/web/primitives/narrow-pinned-footer.js +153 -0
- package/dist/web/primitives/number-input.d.ts +18 -0
- package/dist/web/primitives/number-input.js +53 -0
- package/dist/web/primitives/option-groups.d.ts +11 -0
- package/dist/web/primitives/option-groups.js +27 -0
- package/dist/web/primitives/page-header.d.ts +3 -0
- package/dist/web/primitives/page-header.js +52 -0
- package/dist/web/primitives/promo-panel.d.ts +3 -0
- package/dist/web/primitives/promo-panel.js +11 -0
- package/dist/web/primitives/segmented-select.d.ts +21 -0
- package/dist/web/primitives/segmented-select.js +62 -0
- package/dist/web/primitives/share-actions.d.ts +4 -0
- package/dist/web/primitives/share-actions.js +56 -0
- package/dist/web/primitives/status-badge.d.ts +3 -0
- package/dist/web/primitives/status-badge.js +5 -0
- package/dist/web/primitives/sticky-action-bar.d.ts +5 -0
- package/dist/web/primitives/sticky-action-bar.js +15 -0
- package/dist/web/primitives/tabs.d.ts +3 -0
- package/dist/web/primitives/tabs.js +107 -0
- package/dist/web/primitives/timestamp-input.d.ts +27 -0
- package/dist/web/primitives/timestamp-input.js +87 -0
- package/dist/web/primitives/toast.d.ts +19 -0
- package/dist/web/primitives/toast.js +63 -0
- package/dist/web/primitives/tz-input.d.ts +11 -0
- package/dist/web/primitives/tz-input.js +8 -0
- package/dist/web/primitives/tz-options.d.ts +4 -0
- package/dist/web/primitives/tz-options.js +17 -0
- package/dist/web/primitives/use-keyboard-inset.d.ts +1 -0
- package/dist/web/primitives/use-keyboard-inset.js +28 -0
- package/dist/web/primitives/use-narrow-viewport.d.ts +4 -0
- package/dist/web/primitives/use-narrow-viewport.js +46 -0
- package/dist/web/react-css-custom-properties.d.ts +6 -0
- package/dist/web/react-css-custom-properties.js +1 -0
- package/dist/web/sse/live-events.d.ts +15 -0
- package/dist/web/sse/live-events.js +148 -0
- package/dist/web/theme-plugin.d.ts +41 -0
- package/dist/web/theme-plugin.js +123 -0
- package/dist/web/tokens.d.ts +24 -0
- package/dist/web/tokens.js +203 -0
- package/dist/web/ui/avatar.d.ts +11 -0
- package/dist/web/ui/avatar.js +25 -0
- package/dist/web/ui/badge.d.ts +9 -0
- package/dist/web/ui/badge.js +26 -0
- package/dist/web/ui/breadcrumb.d.ts +11 -0
- package/dist/web/ui/breadcrumb.js +29 -0
- package/dist/web/ui/button.d.ts +10 -0
- package/dist/web/ui/button.js +37 -0
- package/dist/web/ui/checkbox.d.ts +4 -0
- package/dist/web/ui/checkbox.js +11 -0
- package/dist/web/ui/input.d.ts +3 -0
- package/dist/web/ui/input.js +8 -0
- package/dist/web/ui/label.d.ts +4 -0
- package/dist/web/ui/label.js +10 -0
- package/dist/web/ui/resizable.d.ts +7 -0
- package/dist/web/ui/resizable.js +16 -0
- package/dist/web/ui/separator.d.ts +4 -0
- package/dist/web/ui/separator.js +10 -0
- package/dist/web/ui/sheet.d.ts +14 -0
- package/dist/web/ui/sheet.js +42 -0
- package/dist/web/ui/sidebar.d.ts +73 -0
- package/dist/web/ui/sidebar.js +211 -0
- package/dist/web/ui/skeleton.d.ts +2 -0
- package/dist/web/ui/skeleton.js +7 -0
- package/dist/web/ui/switch.d.ts +6 -0
- package/dist/web/ui/switch.js +10 -0
- package/dist/web/ui/table.d.ts +10 -0
- package/dist/web/ui/table.js +30 -0
- package/dist/web/ui/tabs.d.ts +11 -0
- package/dist/web/ui/tabs.js +31 -0
- package/dist/web/ui/textarea.d.ts +3 -0
- package/dist/web/ui/textarea.js +8 -0
- package/dist/web/ui/tooltip.d.ts +7 -0
- package/dist/web/ui/tooltip.js +19 -0
- package/dist/web/ui/use-mobile.d.ts +1 -0
- package/dist/web/ui/use-mobile.js +16 -0
- package/dist/web/version/update-checker.d.ts +9 -0
- package/dist/web/version/update-checker.js +88 -0
- package/dist/web/widgets/ai-text-field.d.ts +36 -0
- package/dist/web/widgets/ai-text-field.js +137 -0
- package/dist/web/widgets/chart-window.d.ts +58 -0
- package/dist/web/widgets/chart-window.js +190 -0
- package/dist/web/widgets/charts.d.ts +208 -0
- package/dist/web/widgets/charts.js +685 -0
- package/dist/web/widgets/collapsible-section.d.ts +10 -0
- package/dist/web/widgets/collapsible-section.js +17 -0
- package/dist/web/widgets/dashboard-list.d.ts +21 -0
- package/dist/web/widgets/dashboard-list.js +45 -0
- package/dist/web/widgets/detail-list.d.ts +15 -0
- package/dist/web/widgets/detail-list.js +8 -0
- package/dist/web/widgets/drawer.d.ts +65 -0
- package/dist/web/widgets/drawer.js +142 -0
- package/dist/web/widgets/feed-list.d.ts +13 -0
- package/dist/web/widgets/feed-list.js +9 -0
- package/dist/web/widgets/floating-panel.d.ts +30 -0
- package/dist/web/widgets/floating-panel.js +242 -0
- package/dist/web/widgets/form-fields.d.ts +93 -0
- package/dist/web/widgets/form-fields.js +50 -0
- package/dist/web/widgets/index.d.ts +27 -0
- package/dist/web/widgets/index.js +30 -0
- package/dist/web/widgets/infinity-list.d.ts +46 -0
- package/dist/web/widgets/infinity-list.js +211 -0
- package/dist/web/widgets/mode-switch.d.ts +21 -0
- package/dist/web/widgets/mode-switch.js +25 -0
- package/dist/web/widgets/photo-slots.d.ts +29 -0
- package/dist/web/widgets/photo-slots.js +52 -0
- package/dist/web/widgets/plan-card.d.ts +36 -0
- package/dist/web/widgets/plan-card.js +13 -0
- package/dist/web/widgets/progress-bar.d.ts +19 -0
- package/dist/web/widgets/progress-bar.js +24 -0
- package/dist/web/widgets/progress-list.d.ts +19 -0
- package/dist/web/widgets/progress-list.js +11 -0
- package/dist/web/widgets/public-shell.d.ts +22 -0
- package/dist/web/widgets/public-shell.js +15 -0
- package/dist/web/widgets/query-table.d.ts +29 -0
- package/dist/web/widgets/query-table.js +30 -0
- package/dist/web/widgets/result-panel.d.ts +69 -0
- package/dist/web/widgets/result-panel.js +30 -0
- package/dist/web/widgets/section-card.d.ts +12 -0
- package/dist/web/widgets/section-card.js +9 -0
- package/dist/web/widgets/sheet-parts.d.ts +9 -0
- package/dist/web/widgets/sheet-parts.js +11 -0
- package/dist/web/widgets/side-by-side-table.d.ts +31 -0
- package/dist/web/widgets/side-by-side-table.js +11 -0
- package/dist/web/widgets/stat.d.ts +54 -0
- package/dist/web/widgets/stat.js +65 -0
- package/dist/web/widgets/states.d.ts +23 -0
- package/dist/web/widgets/states.js +20 -0
- package/dist/web/widgets/status-badge.d.ts +15 -0
- package/dist/web/widgets/status-badge.js +24 -0
- package/dist/web/widgets/step-bar.d.ts +35 -0
- package/dist/web/widgets/step-bar.js +60 -0
- package/dist/web/widgets/upload-zone.d.ts +24 -0
- package/dist/web/widgets/upload-zone.js +108 -0
- package/dist/web/widgets/use-draft.d.ts +20 -0
- package/dist/web/widgets/use-draft.js +21 -0
- package/dist/web/widgets/use-pointer-drag.d.ts +21 -0
- package/dist/web/widgets/use-pointer-drag.js +53 -0
- package/package.json +105 -7
- package/src/changes.json +77 -0
- package/src/web/fonts/OFL.txt +93 -0
- package/src/web/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-600.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-600.woff2 +0 -0
- package/src/web/styles.css +401 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
|
|
3
|
+
* springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
|
|
4
|
+
* zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
|
|
5
|
+
export declare function CollapsibleSection({ title, defaultOpen, children, testId, }: {
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly defaultOpen?: boolean;
|
|
8
|
+
readonly children: ReactNode;
|
|
9
|
+
readonly testId?: string;
|
|
10
|
+
}): ReactNode;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
/** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
|
|
5
|
+
* springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
|
|
6
|
+
* zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
|
|
7
|
+
export function CollapsibleSection({ title, defaultOpen = false, children, testId, }) {
|
|
8
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
9
|
+
// useState(defaultOpen) liest nur den Mount-Wert — ein async hydrierter
|
|
10
|
+
// Draft flippt `defaultOpen` zu spät für den Lazy-Initializer. Open-only
|
|
11
|
+
// Effect: kämpft nie gegen ein manuelles Zuklappen des Users.
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (defaultOpen)
|
|
14
|
+
setOpen(true);
|
|
15
|
+
}, [defaultOpen]);
|
|
16
|
+
return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4 py-2.5 text-sm font-medium hover:bg-muted/50 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: title }), _jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 text-muted-foreground transition-transform group-open:rotate-90" })] }), _jsx("div", { className: "border-t px-4 py-3", children: children })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type DashboardListColumn = {
|
|
3
|
+
readonly field: string;
|
|
4
|
+
/** Translated by the caller. */
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly display?: "bar" | "badge" | "datetime";
|
|
7
|
+
readonly badgeToneField?: string;
|
|
8
|
+
};
|
|
9
|
+
type Row = Readonly<Record<string, unknown>>;
|
|
10
|
+
/** Compact 40px-row table for dashboard lists with optional bar/badge
|
|
11
|
+
* columns. */
|
|
12
|
+
export declare function DashboardListTable({ columns, rows, formatPercent, formatNumber, formatDateTime, translate, testId, }: {
|
|
13
|
+
readonly columns: readonly DashboardListColumn[];
|
|
14
|
+
readonly rows: readonly Row[];
|
|
15
|
+
readonly formatPercent: (fraction: number) => string;
|
|
16
|
+
readonly formatNumber: (value: number) => string;
|
|
17
|
+
readonly formatDateTime: (atMs: number) => string;
|
|
18
|
+
readonly translate: (key: string) => string;
|
|
19
|
+
readonly testId?: string;
|
|
20
|
+
}): ReactNode;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EMPTY_VALUE_PLACEHOLDER } from "../../headless/index.js";
|
|
3
|
+
import { StatusBadge } from "./status-badge.js";
|
|
4
|
+
const BADGE_TONE = {
|
|
5
|
+
positive: "ok",
|
|
6
|
+
warn: "warn",
|
|
7
|
+
negative: "bad",
|
|
8
|
+
neutral: "muted",
|
|
9
|
+
};
|
|
10
|
+
function fractionOf(raw) {
|
|
11
|
+
return typeof raw === "number" && Number.isFinite(raw) ? Math.max(0, Math.min(1, raw)) : 0;
|
|
12
|
+
}
|
|
13
|
+
// Rows may carry i18n keys (quota labels, failure reasons); translate falls back to the key itself.
|
|
14
|
+
const I18N_KEY_SHAPE = /^[a-z][\w-]*(?:[:.][\w-]+)+$/i;
|
|
15
|
+
function Cell({ column, row, formatPercent, formatNumber, formatDateTime, translate, }) {
|
|
16
|
+
const raw = row[column.field];
|
|
17
|
+
if (column.display === "bar") {
|
|
18
|
+
const fraction = fractionOf(raw);
|
|
19
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { role: "progressbar", "aria-valuenow": Math.round(fraction * 100), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": column.label, className: "h-1 min-w-16 flex-1 overflow-hidden rounded-full bg-border", children: _jsx("div", { className: "h-full rounded-full bg-status-active", style: { width: `${fraction * 100}%` } }) }), _jsx("span", { className: "w-10 shrink-0 text-right tabular-nums text-muted-foreground", children: formatPercent(fraction) })] }));
|
|
20
|
+
}
|
|
21
|
+
if (column.display === "badge") {
|
|
22
|
+
if (raw === undefined || raw === null || raw === "")
|
|
23
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
24
|
+
const toneKey = column.badgeToneField !== undefined ? row[column.badgeToneField] : undefined;
|
|
25
|
+
const tone = (typeof toneKey === "string" ? BADGE_TONE[toneKey] : undefined) ?? "muted";
|
|
26
|
+
return _jsx(StatusBadge, { tone: tone, children: String(raw) });
|
|
27
|
+
}
|
|
28
|
+
if (column.display === "datetime") {
|
|
29
|
+
const atMs = typeof raw === "number" ? raw : typeof raw === "string" ? Date.parse(raw) : Number.NaN;
|
|
30
|
+
if (!Number.isFinite(atMs))
|
|
31
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
32
|
+
return _jsx("span", { className: "tabular-nums", children: formatDateTime(atMs) });
|
|
33
|
+
}
|
|
34
|
+
if (typeof raw === "number")
|
|
35
|
+
return _jsx("span", { className: "tabular-nums", children: formatNumber(raw) });
|
|
36
|
+
if (raw === undefined || raw === null || raw === "")
|
|
37
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
38
|
+
const text = String(raw);
|
|
39
|
+
return I18N_KEY_SHAPE.test(text) ? translate(text) : text;
|
|
40
|
+
}
|
|
41
|
+
/** Compact 40px-row table for dashboard lists with optional bar/badge
|
|
42
|
+
* columns. */
|
|
43
|
+
export function DashboardListTable({ columns, rows, formatPercent, formatNumber, formatDateTime, translate, testId, }) {
|
|
44
|
+
return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { "data-testid": testId, className: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "h-9 border-b border-border-row text-left text-xs text-muted-foreground", children: columns.map((column) => (_jsx("th", { scope: "col", className: "px-4 font-medium", children: column.label }, column.field))) }) }), _jsx("tbody", { children: rows.map((row, index) => (_jsx("tr", { className: "h-10 border-b border-border-row last:border-b-0", children: columns.map((column) => (_jsx("td", { className: "px-4", children: _jsx(Cell, { column: column, row: row, formatPercent: formatPercent, formatNumber: formatNumber, formatDateTime: formatDateTime, translate: translate }) }, column.field))) }, typeof row["id"] === "string" ? row["id"] : index))) })] }) }));
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
|
|
3
|
+
* Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
|
|
4
|
+
* eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
|
|
5
|
+
export declare function DetailList({ rows, testId, }: {
|
|
6
|
+
readonly rows: readonly {
|
|
7
|
+
/** Optional stable key -- falls back to the label when omitted (fine as
|
|
8
|
+
* long as labels are unique; set id when they can repeat). */
|
|
9
|
+
readonly id?: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly value: ReactNode;
|
|
12
|
+
readonly emphasize?: boolean;
|
|
13
|
+
}[];
|
|
14
|
+
readonly testId?: string;
|
|
15
|
+
}): ReactNode;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
/** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
|
|
4
|
+
* Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
|
|
5
|
+
* eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
|
|
6
|
+
export function DetailList({ rows, testId, }) {
|
|
7
|
+
return (_jsx("dl", { "data-testid": testId, className: "@container flex flex-col divide-y", children: rows.map((row) => (_jsxs("div", { className: "grid grid-cols-1 gap-0.5 py-2.5 @md:grid-cols-[200px_1fr] @md:gap-4", children: [_jsx("dt", { className: cn("text-sm text-muted-foreground", row.emphasize === true && "font-semibold text-foreground"), children: row.label }), _jsx("dd", { className: cn("text-sm font-medium", row.emphasize === true && "font-semibold"), children: row.value })] }, row.id ?? row.label))) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type DrawerProps = {
|
|
3
|
+
readonly open: boolean;
|
|
4
|
+
readonly onOpenChange: (open: boolean) => void;
|
|
5
|
+
readonly side?: "left" | "right" | "top" | "bottom";
|
|
6
|
+
readonly title?: ReactNode;
|
|
7
|
+
readonly description?: ReactNode;
|
|
8
|
+
readonly footer?: ReactNode;
|
|
9
|
+
readonly children: ReactNode;
|
|
10
|
+
readonly testId?: string;
|
|
11
|
+
/** Header close (X). Default `true`. Turn off when the caller's own
|
|
12
|
+
* footer already has a dedicated close/cancel action, so there is only
|
|
13
|
+
* one way to dismiss the drawer. */
|
|
14
|
+
readonly showCloseButton?: boolean;
|
|
15
|
+
/** Panel treatment. `"flush"` (default) docks the panel against the edge —
|
|
16
|
+
* full extent, no radius, and a border only on the edge facing the app
|
|
17
|
+
* content. `"floating"` gives the detached-panel look (margin to the
|
|
18
|
+
* viewport edge, full corner radius). Ignored in the narrow-viewport
|
|
19
|
+
* fullscreen layout. */
|
|
20
|
+
readonly variant?: "floating" | "flush";
|
|
21
|
+
/** `variant="flush"` only: dock the panel below the app's ShellHeader
|
|
22
|
+
* (offset top by `--shell-header-height`, height shrunk to match)
|
|
23
|
+
* instead of covering it. Default `false` is edge-to-edge. No ShellHeader
|
|
24
|
+
* mounted → the variable is `0`, so this is a no-op. Ignored in the
|
|
25
|
+
* narrow-viewport fullscreen layout, which already takes over the whole
|
|
26
|
+
* screen including the header. */
|
|
27
|
+
readonly belowHeader?: boolean;
|
|
28
|
+
/** Panel width for `side="left"|"right"` (ignored for top/bottom and in
|
|
29
|
+
* the narrow-viewport layout). A number is pixels, a string any CSS
|
|
30
|
+
* length. Superseded by `resize` when that's set. Default matches the
|
|
31
|
+
* panel's built-in `max(600px, 37.5vw)`. */
|
|
32
|
+
readonly width?: number | string;
|
|
33
|
+
/** Opt-in drag-to-resize + maximize toggle (left/right sides only). */
|
|
34
|
+
readonly resize?: {
|
|
35
|
+
readonly defaultWidthPx?: number;
|
|
36
|
+
readonly minWidthPx?: number;
|
|
37
|
+
readonly maxWidthPx?: number;
|
|
38
|
+
};
|
|
39
|
+
/** Backdrop behind the panel. A drawer exists so the content behind it
|
|
40
|
+
* stays readable, so the blur is off by default. Ignored when
|
|
41
|
+
* `modal={false}` — there is no overlay to style then. */
|
|
42
|
+
readonly backdrop?: {
|
|
43
|
+
readonly blurPx?: number;
|
|
44
|
+
readonly dimPercent?: number;
|
|
45
|
+
};
|
|
46
|
+
/** `false` runs the drawer as a panel beside the page instead of over it:
|
|
47
|
+
* no overlay, no focus trap, and the page behind stays interactive, so
|
|
48
|
+
* text there can be selected and copied. Escape closes in both modes, a
|
|
49
|
+
* click beside the panel only in the modal one — the non-modal case
|
|
50
|
+
* exists to be used next to the page content, where a stray click must
|
|
51
|
+
* not dismiss it. With `showCloseButton={false}` and no footer action
|
|
52
|
+
* that leaves Escape as the only way out, so keep one of the two.
|
|
53
|
+
* Default `true`. */
|
|
54
|
+
readonly modal?: boolean;
|
|
55
|
+
/** Extra classes for the panel, merged over the built-in ones. Not applied
|
|
56
|
+
* in the narrow-viewport fullscreen layout. */
|
|
57
|
+
readonly panelClassName?: string;
|
|
58
|
+
/** The body becomes a plain flex column with no padding or scrolling of its
|
|
59
|
+
* own, for children that bring their own scroll area and pinned footer. */
|
|
60
|
+
readonly fillBody?: boolean;
|
|
61
|
+
};
|
|
62
|
+
/** Slide-in panel beside a list (e.g. mail reader next to the inbox) —
|
|
63
|
+
* thin wrapper over the Sheet primitive with header/body/footer slots
|
|
64
|
+
* so screens skip per-route Radix boilerplate. */
|
|
65
|
+
export declare function Drawer({ open, onOpenChange, side, title, description, footer, children, testId, showCloseButton, variant, belowHeader, width, resize, backdrop, modal, panelClassName, fillBody, }: DrawerProps): ReactNode;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Maximize2Icon, Minimize2Icon } from "lucide-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useTranslation } from "../../index.js";
|
|
5
|
+
import { clamp } from "../lib/clamp.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { useIsNarrowViewport } from "../primitives/use-narrow-viewport.js";
|
|
8
|
+
import { Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../ui/sheet.js";
|
|
9
|
+
import { DrawerSheetContent } from "./sheet-parts.js";
|
|
10
|
+
import { arrowKeyDelta, usePointerDrag } from "./use-pointer-drag.js";
|
|
11
|
+
// Mirrors primitives/index.tsx's cardFooter + cardFooterBorder (row layout,
|
|
12
|
+
// end-justified actions, top border, panel surface instead of the card
|
|
13
|
+
// footer's bg-muted/30) — those two consts aren't exported, so the
|
|
14
|
+
// equivalent classes are inlined here rather than growing the vendored
|
|
15
|
+
// SheetFooter's own default className with panel-specific styling.
|
|
16
|
+
const DRAWER_FOOTER_CLASS = "flex-row flex-wrap items-center justify-end border-t bg-background px-[var(--card-padding)] py-4";
|
|
17
|
+
const MIN_WIDTH_PX = 320;
|
|
18
|
+
const MAX_WIDTH_PX = 1000;
|
|
19
|
+
// Must match the static `max(600px,37.5vw)` in floatingSideClass — Tailwind's
|
|
20
|
+
// JIT can't read these at runtime, so the two are kept in sync by hand.
|
|
21
|
+
// 600px (vs. the old 520px floor) gives a two-column field row (e.g.
|
|
22
|
+
// street/number, zip/city) room to breathe, while staying well under half
|
|
23
|
+
// of a 1280px viewport so a narrow window isn't dominated by the drawer.
|
|
24
|
+
// 37.5vw (vs. the old 25vw) keeps that same 600px on a 1280–1600px window
|
|
25
|
+
// (25vw was still under the floor there, making the ratio dead weight up
|
|
26
|
+
// to 2400px) but actually grows past the floor by ~1600px, so a 1920px
|
|
27
|
+
// window — where the two-column row is cramped just as often — lands
|
|
28
|
+
// around 720px instead of being stuck at 600px alongside a 1280px window.
|
|
29
|
+
const DEFAULT_WIDTH_MIN_PX = 600;
|
|
30
|
+
const DEFAULT_WIDTH_VIEWPORT_RATIO = 0.375;
|
|
31
|
+
const DEFAULT_BLUR_PX = 0;
|
|
32
|
+
const DEFAULT_DIM_PERCENT = 20;
|
|
33
|
+
function defaultWidthFromViewport() {
|
|
34
|
+
return typeof window === "undefined"
|
|
35
|
+
? DEFAULT_WIDTH_MIN_PX
|
|
36
|
+
: Math.max(DEFAULT_WIDTH_MIN_PX, Math.round(window.innerWidth * DEFAULT_WIDTH_VIEWPORT_RATIO));
|
|
37
|
+
}
|
|
38
|
+
function normalizeWidth(width) {
|
|
39
|
+
return typeof width === "number" ? `${width}px` : width;
|
|
40
|
+
}
|
|
41
|
+
// Must match the static `max(600px,37.5vw)` in sidePanelClass — Tailwind's
|
|
42
|
+
// JIT can't read DEFAULT_WIDTH_MIN_PX/DEFAULT_WIDTH_VIEWPORT_RATIO at
|
|
43
|
+
// runtime, so the two are kept in sync by hand.
|
|
44
|
+
// Same cap as WIDTH_CLASS: an explicit `width` must not outgrow the viewport.
|
|
45
|
+
const MAX_VIEWPORT_WIDTH = "85vw";
|
|
46
|
+
const WIDTH_CLASS = "w-[max(600px,37.5vw)] max-w-[85vw] sm:max-w-[max(600px,37.5vw)]";
|
|
47
|
+
// "floating": 32px margin + 32px radius so the panel reads as detached from
|
|
48
|
+
// the viewport edge, unlike the sheet primitive's flush-edge default.
|
|
49
|
+
// "flush" docks it back to that flush-edge default instead, borrowing the
|
|
50
|
+
// vendored Sheet's per-side border (only the edge facing the app content).
|
|
51
|
+
// Below the narrow-viewport breakpoint neither treatment fits — the JS hook
|
|
52
|
+
// decides, not a `sm:` Tailwind prefix, since the resize width below already
|
|
53
|
+
// comes from JS and two decision sources is the bug this avoids.
|
|
54
|
+
function sidePanelClass(side, narrow, variant, belowHeader) {
|
|
55
|
+
if (narrow)
|
|
56
|
+
return "inset-0 h-full w-full max-w-none rounded-none border-0 overflow-hidden";
|
|
57
|
+
if (variant === "flush") {
|
|
58
|
+
switch (side) {
|
|
59
|
+
// h-full would pin height to 100vh despite the shifted top; h-auto lets inset-y-0 size it.
|
|
60
|
+
case "left":
|
|
61
|
+
return `inset-y-0 left-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-r shadow-2xl overflow-hidden`;
|
|
62
|
+
case "top":
|
|
63
|
+
return `inset-x-0 ${belowHeader ? "top-(--shell-header-height)" : "top-0"} h-auto max-h-[80vh] border-b shadow-2xl overflow-hidden`;
|
|
64
|
+
case "bottom":
|
|
65
|
+
return "inset-x-0 bottom-0 h-auto max-h-[80vh] border-t shadow-2xl overflow-hidden";
|
|
66
|
+
default:
|
|
67
|
+
return `inset-y-0 right-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-l shadow-2xl overflow-hidden`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
switch (side) {
|
|
71
|
+
case "left":
|
|
72
|
+
return `inset-y-8 left-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
|
|
73
|
+
case "top":
|
|
74
|
+
return "inset-x-8 top-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
|
|
75
|
+
case "bottom":
|
|
76
|
+
return "inset-x-8 bottom-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
|
|
77
|
+
default:
|
|
78
|
+
return `inset-y-8 right-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Slide-in panel beside a list (e.g. mail reader next to the inbox) —
|
|
82
|
+
* thin wrapper over the Sheet primitive with header/body/footer slots
|
|
83
|
+
* so screens skip per-route Radix boilerplate. */
|
|
84
|
+
export function Drawer({ open, onOpenChange, side = "right", title, description, footer, children, testId, showCloseButton = true, variant = "flush", belowHeader = false, width, resize, backdrop, modal = true, panelClassName, fillBody = false, }) {
|
|
85
|
+
const t = useTranslation();
|
|
86
|
+
const narrow = useIsNarrowViewport();
|
|
87
|
+
const canResize = resize !== undefined && (side === "left" || side === "right");
|
|
88
|
+
const customWidthStyle = !canResize && !narrow && (side === "left" || side === "right") && width !== undefined
|
|
89
|
+
? { width: normalizeWidth(width), maxWidth: MAX_VIEWPORT_WIDTH }
|
|
90
|
+
: undefined;
|
|
91
|
+
const minWidthPx = resize?.minWidthPx ?? MIN_WIDTH_PX;
|
|
92
|
+
const maxWidthPx = resize?.maxWidthPx ?? MAX_WIDTH_PX;
|
|
93
|
+
const effectiveMaxWidthPx = () => typeof window === "undefined"
|
|
94
|
+
? maxWidthPx
|
|
95
|
+
: Math.min(maxWidthPx, Math.round(window.innerWidth * 0.9));
|
|
96
|
+
const [resizedWidthPx, setResizedWidthPx] = useState(() => clamp(resize?.defaultWidthPx ?? defaultWidthFromViewport(), minWidthPx, effectiveMaxWidthPx()));
|
|
97
|
+
const [maximized, setMaximized] = useState(false);
|
|
98
|
+
const blurPx = backdrop?.blurPx ?? DEFAULT_BLUR_PX;
|
|
99
|
+
const dimPercent = backdrop?.dimPercent ?? DEFAULT_DIM_PERCENT;
|
|
100
|
+
const overlayStyle = {
|
|
101
|
+
backgroundColor: `rgb(0 0 0 / ${dimPercent}%)`,
|
|
102
|
+
...(blurPx > 0 ? { backdropFilter: `blur(${blurPx}px)` } : {}),
|
|
103
|
+
};
|
|
104
|
+
const effectiveWidthPx = maximized ? effectiveMaxWidthPx() : resizedWidthPx;
|
|
105
|
+
// Inline style because tailwind-merge doesn't dedupe `top-*`/`bottom-*` against the base `inset-y-0`;
|
|
106
|
+
// style wins over class regardless of generated CSS order.
|
|
107
|
+
const verticalOffsetStyle = belowHeader && variant === "flush" && !narrow && (side === "left" || side === "right")
|
|
108
|
+
? { top: "var(--shell-header-height)", bottom: 0 }
|
|
109
|
+
: undefined;
|
|
110
|
+
const widthStyle = canResize && !narrow ? { width: effectiveWidthPx, maxWidth: "none" } : customWidthStyle;
|
|
111
|
+
const sheetStyle = widthStyle === undefined && verticalOffsetStyle === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: { ...widthStyle, ...verticalOffsetStyle };
|
|
114
|
+
const handleDrag = usePointerDrag({
|
|
115
|
+
onStart: () => {
|
|
116
|
+
setMaximized(false);
|
|
117
|
+
return { startWidth: effectiveWidthPx };
|
|
118
|
+
},
|
|
119
|
+
onMove: ({ startWidth }, { dx }) => {
|
|
120
|
+
const signedDelta = side === "right" ? -dx : dx;
|
|
121
|
+
setResizedWidthPx(clamp(startWidth + signedDelta, minWidthPx, effectiveMaxWidthPx()));
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const onHandleKeyDown = (event) => {
|
|
125
|
+
const keyDelta = arrowKeyDelta(event);
|
|
126
|
+
if (keyDelta === undefined || keyDelta.dx === 0)
|
|
127
|
+
return;
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
setMaximized(false);
|
|
130
|
+
const delta = side === "right" ? -keyDelta.dx : keyDelta.dx;
|
|
131
|
+
// Seed from the currently visible width, not the stale `resizedWidthPx`
|
|
132
|
+
// state — while maximized, it still holds the pre-maximize value, so a
|
|
133
|
+
// key press would otherwise jump the drawer back to that old size
|
|
134
|
+
// instead of resizing relative to what's on screen.
|
|
135
|
+
setResizedWidthPx((current) => clamp((maximized ? effectiveWidthPx : current) + delta, minWidthPx, effectiveMaxWidthPx()));
|
|
136
|
+
};
|
|
137
|
+
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, modal: modal, children: _jsxs(DrawerSheetContent, { side: side, "data-testid": testId, overlayStyle: overlayStyle,
|
|
138
|
+
// Radix already drops the overlay and the focus trap for a
|
|
139
|
+
// non-modal Root; only the dismiss-on-outside-interaction is left
|
|
140
|
+
// to suppress, so a click into the page behind keeps the panel open.
|
|
141
|
+
onInteractOutside: modal ? undefined : (event) => event.preventDefault(), showCloseButton: showCloseButton, closeLabel: t("kumiko.dialog.close"), className: cn(sidePanelClass(side, narrow, variant, belowHeader), fillBody && "gap-0", !narrow && panelClassName), style: sheetStyle, children: [canResize && !narrow && (_jsx("button", { type: "button", onClick: () => setMaximized((m) => !m), "aria-pressed": maximized, "aria-label": maximized ? t("kumiko.widget.drawer.restore") : t("kumiko.widget.drawer.maximize"), className: "absolute top-4 right-14 z-10 rounded-xs p-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:bg-secondary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden", children: maximized ? (_jsx(Minimize2Icon, { className: "size-4" })) : (_jsx(Maximize2Icon, { className: "size-4" })) })), (title !== undefined || description !== undefined) && (_jsxs(SheetHeader, { children: [title !== undefined && _jsx(SheetTitle, { children: title }), description !== undefined && _jsx(SheetDescription, { children: description })] })), _jsx("div", { className: fillBody ? "flex min-h-0 flex-1 flex-col" : "flex-1 overflow-y-auto px-4", children: children }), footer !== undefined && (_jsx(SheetFooter, { className: DRAWER_FOOTER_CLASS, children: footer })), canResize && !narrow && (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t("kumiko.widget.drawer.resize"), "aria-valuenow": effectiveWidthPx, "aria-valuemin": minWidthPx, "aria-valuemax": effectiveMaxWidthPx(), tabIndex: 0, ...handleDrag, onKeyDown: onHandleKeyDown, className: cn("absolute inset-y-0 z-10 w-1 cursor-col-resize touch-none after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2 hover:bg-border", side === "right" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2") }))] }) }));
|
|
142
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type FeedRow = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly primary: string;
|
|
5
|
+
readonly trailing?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
|
|
8
|
+
* optionaler rechtsbündiger Trailing-Wert pro Zeile. */
|
|
9
|
+
export declare function FeedList({ rows, emptyContent, testId, }: {
|
|
10
|
+
readonly rows: readonly FeedRow[];
|
|
11
|
+
readonly emptyContent?: ReactNode;
|
|
12
|
+
readonly testId?: string;
|
|
13
|
+
}): ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
|
|
3
|
+
* optionaler rechtsbündiger Trailing-Wert pro Zeile. */
|
|
4
|
+
export function FeedList({ rows, emptyContent, testId, }) {
|
|
5
|
+
if (rows.length === 0) {
|
|
6
|
+
return _jsx("div", { "data-testid": testId, children: emptyContent });
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("ul", { "data-testid": testId, className: "flex max-h-64 flex-col overflow-y-auto pr-1 text-sm", children: rows.map((row) => (_jsxs("li", { className: "flex justify-between gap-4 border-b py-1.5 last:border-b-0", children: [_jsx("span", { children: row.primary }), row.trailing !== undefined && (_jsx("span", { className: "tabular-nums text-muted-foreground", children: row.trailing }))] }, row.id))) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type FloatingPanelGeometry = {
|
|
3
|
+
readonly x: number;
|
|
4
|
+
readonly y: number;
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
};
|
|
8
|
+
export type FloatingPanelProps = {
|
|
9
|
+
/** Header content; the header is the move handle. */
|
|
10
|
+
readonly title: ReactNode;
|
|
11
|
+
/** Header buttons (dock, minimize, close). Pointer-down on interactive
|
|
12
|
+
* elements does not start a drag. */
|
|
13
|
+
readonly headerActions?: ReactNode;
|
|
14
|
+
readonly children: ReactNode;
|
|
15
|
+
/** localStorage key; without it the geometry isn't persisted. */
|
|
16
|
+
readonly storageKey?: string;
|
|
17
|
+
/** Default 400x560, bottom-right with a 24px margin. */
|
|
18
|
+
readonly defaultGeometry?: Partial<FloatingPanelGeometry>;
|
|
19
|
+
readonly minWidthPx?: number;
|
|
20
|
+
readonly minHeightPx?: number;
|
|
21
|
+
/** Default: viewport width minus twice the margin. */
|
|
22
|
+
readonly maxWidthPx?: number;
|
|
23
|
+
/** Default: viewport height minus twice the margin. */
|
|
24
|
+
readonly maxHeightPx?: number;
|
|
25
|
+
readonly testId?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Non-modal panel floating above the page: draggable by its header,
|
|
28
|
+
* resizable from all edges and corners, clamped to the viewport, geometry
|
|
29
|
+
* optionally remembered. On narrow viewports it becomes a full-screen sheet. */
|
|
30
|
+
export declare function FloatingPanel({ title, headerActions, children, storageKey, defaultGeometry, minWidthPx, minHeightPx, maxWidthPx, maxHeightPx, testId, }: FloatingPanelProps): ReactNode;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { GripVerticalIcon } from "lucide-react";
|
|
3
|
+
import { useCallback, useEffect, useId, useRef, useState, } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { useTranslation } from "../../index.js";
|
|
6
|
+
import { clamp } from "../lib/clamp.js";
|
|
7
|
+
import { cn } from "../lib/cn.js";
|
|
8
|
+
import { useIsNarrowViewport } from "../primitives/use-narrow-viewport.js";
|
|
9
|
+
import { arrowKeyDelta, usePointerDrag } from "./use-pointer-drag.js";
|
|
10
|
+
const MARGIN_PX = 24;
|
|
11
|
+
const DEFAULT_WIDTH_PX = 400;
|
|
12
|
+
const DEFAULT_HEIGHT_PX = 560;
|
|
13
|
+
const DEFAULT_MIN_WIDTH_PX = 280;
|
|
14
|
+
const DEFAULT_MIN_HEIGHT_PX = 200;
|
|
15
|
+
const EDGES = ["top", "right", "bottom", "left"];
|
|
16
|
+
const EDGE_HANDLE_BASE = "absolute z-10 touch-none hover:bg-border focus-visible:bg-ring";
|
|
17
|
+
const CORNER_HANDLE_BASE = "absolute z-20 size-3 touch-none";
|
|
18
|
+
const EDGE_HANDLE_CLASS = {
|
|
19
|
+
top: cn(EDGE_HANDLE_BASE, "inset-x-3 top-0 h-1.5 cursor-ns-resize"),
|
|
20
|
+
bottom: cn(EDGE_HANDLE_BASE, "inset-x-3 bottom-0 h-1.5 cursor-ns-resize"),
|
|
21
|
+
left: cn(EDGE_HANDLE_BASE, "inset-y-3 left-0 w-1.5 cursor-ew-resize"),
|
|
22
|
+
right: cn(EDGE_HANDLE_BASE, "inset-y-3 right-0 w-1.5 cursor-ew-resize"),
|
|
23
|
+
};
|
|
24
|
+
const CORNER_HANDLES = [
|
|
25
|
+
{ edges: ["top", "left"], className: cn(CORNER_HANDLE_BASE, "top-0 left-0 cursor-nwse-resize") },
|
|
26
|
+
{
|
|
27
|
+
edges: ["top", "right"],
|
|
28
|
+
className: cn(CORNER_HANDLE_BASE, "top-0 right-0 cursor-nesw-resize"),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
edges: ["bottom", "left"],
|
|
32
|
+
className: cn(CORNER_HANDLE_BASE, "bottom-0 left-0 cursor-nesw-resize"),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
edges: ["bottom", "right"],
|
|
36
|
+
className: cn(CORNER_HANDLE_BASE, "right-0 bottom-0 cursor-nwse-resize"),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
const EDGE_LABEL_KEYS = {
|
|
40
|
+
top: "kumiko.widget.floatingPanel.resizeTop",
|
|
41
|
+
right: "kumiko.widget.floatingPanel.resizeRight",
|
|
42
|
+
bottom: "kumiko.widget.floatingPanel.resizeBottom",
|
|
43
|
+
left: "kumiko.widget.floatingPanel.resizeLeft",
|
|
44
|
+
};
|
|
45
|
+
const DRAG_HANDLE_SELECTOR = "[data-drag-handle]";
|
|
46
|
+
const INTERACTIVE_HEADER_TARGET = "button, a, input, select, textarea, [role=button]";
|
|
47
|
+
function readLimits(options) {
|
|
48
|
+
const viewportWidth = window.innerWidth;
|
|
49
|
+
const viewportHeight = window.innerHeight;
|
|
50
|
+
const maxWidth = Math.min(options.maxWidthPx ?? viewportWidth - 2 * MARGIN_PX, viewportWidth);
|
|
51
|
+
const maxHeight = Math.min(options.maxHeightPx ?? viewportHeight - 2 * MARGIN_PX, viewportHeight);
|
|
52
|
+
return {
|
|
53
|
+
// A viewport smaller than the configured minimum wins: the panel must stay fully visible.
|
|
54
|
+
minWidth: Math.min(options.minWidthPx ?? DEFAULT_MIN_WIDTH_PX, maxWidth),
|
|
55
|
+
minHeight: Math.min(options.minHeightPx ?? DEFAULT_MIN_HEIGHT_PX, maxHeight),
|
|
56
|
+
maxWidth,
|
|
57
|
+
maxHeight,
|
|
58
|
+
viewportWidth,
|
|
59
|
+
viewportHeight,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Size first, then position, so a shrunk panel is placed against its final size.
|
|
63
|
+
function fitGeometry(geometry, limits) {
|
|
64
|
+
const width = clamp(geometry.width, limits.minWidth, limits.maxWidth);
|
|
65
|
+
const height = clamp(geometry.height, limits.minHeight, limits.maxHeight);
|
|
66
|
+
return {
|
|
67
|
+
width,
|
|
68
|
+
height,
|
|
69
|
+
x: clamp(geometry.x, 0, Math.max(0, limits.viewportWidth - width)),
|
|
70
|
+
y: clamp(geometry.y, 0, Math.max(0, limits.viewportHeight - height)),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// The edge being dragged follows the pointer; the opposite edge stays put.
|
|
74
|
+
function resizeAxis(start, delta, movesStartEdge, min, max, viewportSize) {
|
|
75
|
+
if (movesStartEdge) {
|
|
76
|
+
const end = start.position + start.size;
|
|
77
|
+
const size = clamp(start.size - delta, min, Math.min(max, end));
|
|
78
|
+
return { position: end - size, size };
|
|
79
|
+
}
|
|
80
|
+
const size = clamp(start.size + delta, min, Math.min(max, viewportSize - start.position));
|
|
81
|
+
return { position: start.position, size };
|
|
82
|
+
}
|
|
83
|
+
function resizeGeometry(start, edges, delta, limits) {
|
|
84
|
+
let { x, y, width, height } = start;
|
|
85
|
+
for (const edge of edges) {
|
|
86
|
+
if (edge === "left" || edge === "right") {
|
|
87
|
+
({ position: x, size: width } = resizeAxis({ position: start.x, size: start.width }, delta.dx, edge === "left", limits.minWidth, limits.maxWidth, limits.viewportWidth));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
({ position: y, size: height } = resizeAxis({ position: start.y, size: start.height }, delta.dy, edge === "top", limits.minHeight, limits.maxHeight, limits.viewportHeight));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { x, y, width, height };
|
|
94
|
+
}
|
|
95
|
+
function isFiniteNumber(value) {
|
|
96
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
97
|
+
}
|
|
98
|
+
function isFloatingPanelGeometry(value) {
|
|
99
|
+
return (typeof value === "object" &&
|
|
100
|
+
value !== null &&
|
|
101
|
+
"x" in value &&
|
|
102
|
+
isFiniteNumber(value.x) &&
|
|
103
|
+
"y" in value &&
|
|
104
|
+
isFiniteNumber(value.y) &&
|
|
105
|
+
"width" in value &&
|
|
106
|
+
isFiniteNumber(value.width) &&
|
|
107
|
+
"height" in value &&
|
|
108
|
+
isFiniteNumber(value.height));
|
|
109
|
+
}
|
|
110
|
+
function readStoredGeometry(storageKey) {
|
|
111
|
+
if (storageKey === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
115
|
+
if (raw === null)
|
|
116
|
+
return undefined;
|
|
117
|
+
const parsed = JSON.parse(raw);
|
|
118
|
+
return isFloatingPanelGeometry(parsed) ? parsed : undefined;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Unavailable or corrupt storage just means the default geometry is used.
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function writeStoredGeometry(storageKey, geometry) {
|
|
126
|
+
if (storageKey === undefined)
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
window.localStorage.setItem(storageKey, JSON.stringify(geometry));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Private mode or quota: the position is simply not remembered.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function initialGeometry(storageKey, defaults, limits) {
|
|
136
|
+
const stored = readStoredGeometry(storageKey);
|
|
137
|
+
if (stored !== undefined)
|
|
138
|
+
return fitGeometry(stored, limits);
|
|
139
|
+
const width = defaults?.width ?? DEFAULT_WIDTH_PX;
|
|
140
|
+
const height = defaults?.height ?? DEFAULT_HEIGHT_PX;
|
|
141
|
+
return fitGeometry({
|
|
142
|
+
width,
|
|
143
|
+
height,
|
|
144
|
+
x: defaults?.x ?? limits.viewportWidth - width - MARGIN_PX,
|
|
145
|
+
y: defaults?.y ?? limits.viewportHeight - height - MARGIN_PX,
|
|
146
|
+
}, limits);
|
|
147
|
+
}
|
|
148
|
+
/** Non-modal panel floating above the page: draggable by its header,
|
|
149
|
+
* resizable from all edges and corners, clamped to the viewport, geometry
|
|
150
|
+
* optionally remembered. On narrow viewports it becomes a full-screen sheet. */
|
|
151
|
+
export function FloatingPanel({ title, headerActions, children, storageKey, defaultGeometry, minWidthPx, minHeightPx, maxWidthPx, maxHeightPx, testId, }) {
|
|
152
|
+
const t = useTranslation();
|
|
153
|
+
const narrow = useIsNarrowViewport();
|
|
154
|
+
const titleId = useId();
|
|
155
|
+
const getLimits = () => readLimits({ minWidthPx, minHeightPx, maxWidthPx, maxHeightPx });
|
|
156
|
+
const getLimitsRef = useRef(getLimits);
|
|
157
|
+
getLimitsRef.current = getLimits;
|
|
158
|
+
const [geometry, setGeometry] = useState(() => initialGeometry(storageKey, defaultGeometry, getLimits()));
|
|
159
|
+
const geometryRef = useRef(geometry);
|
|
160
|
+
geometryRef.current = geometry;
|
|
161
|
+
const applyGeometry = useCallback((next) => {
|
|
162
|
+
geometryRef.current = next;
|
|
163
|
+
setGeometry(next);
|
|
164
|
+
}, []);
|
|
165
|
+
const persistGeometry = () => writeStoredGeometry(storageKey, geometryRef.current);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (narrow)
|
|
168
|
+
return;
|
|
169
|
+
const onWindowResize = () => {
|
|
170
|
+
applyGeometry(fitGeometry(geometryRef.current, getLimitsRef.current()));
|
|
171
|
+
};
|
|
172
|
+
// Resize events fired while narrow were ignored, so re-fit once on the way back to wide.
|
|
173
|
+
onWindowResize();
|
|
174
|
+
window.addEventListener("resize", onWindowResize);
|
|
175
|
+
return () => window.removeEventListener("resize", onWindowResize);
|
|
176
|
+
}, [narrow, applyGeometry]);
|
|
177
|
+
const moveDrag = usePointerDrag({
|
|
178
|
+
onStart: () => geometryRef.current,
|
|
179
|
+
onMove: (start, { dx, dy }) => applyGeometry(fitGeometry({ ...start, x: start.x + dx, y: start.y + dy }, getLimits())),
|
|
180
|
+
onEnd: persistGeometry,
|
|
181
|
+
});
|
|
182
|
+
const onHeaderPointerDown = (event) => {
|
|
183
|
+
// React events from portalled children (menus) bubble through the header although their DOM is elsewhere.
|
|
184
|
+
if (!(event.target instanceof Node) || !event.currentTarget.contains(event.target))
|
|
185
|
+
return;
|
|
186
|
+
if (event.target instanceof Element &&
|
|
187
|
+
event.target.closest(INTERACTIVE_HEADER_TARGET) &&
|
|
188
|
+
!event.target.closest(DRAG_HANDLE_SELECTOR)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
moveDrag.onPointerDown(event);
|
|
192
|
+
};
|
|
193
|
+
const onGripKeyDown = (event) => {
|
|
194
|
+
const delta = arrowKeyDelta(event);
|
|
195
|
+
if (delta === undefined)
|
|
196
|
+
return;
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
const current = geometryRef.current;
|
|
199
|
+
applyGeometry(fitGeometry({ ...current, x: current.x + delta.dx, y: current.y + delta.dy }, getLimits()));
|
|
200
|
+
persistGeometry();
|
|
201
|
+
};
|
|
202
|
+
if (typeof document === "undefined")
|
|
203
|
+
return null;
|
|
204
|
+
if (narrow) {
|
|
205
|
+
return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "false", "aria-labelledby": titleId, "data-testid": testId, className: "fixed inset-0 z-50 flex flex-col bg-background text-foreground", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b px-3 py-2", children: [_jsx("div", { id: titleId, className: "min-w-0 flex-1 truncate font-medium", children: title }), headerActions] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children })] }), document.body);
|
|
206
|
+
}
|
|
207
|
+
const limits = getLimits();
|
|
208
|
+
const handleContext = {
|
|
209
|
+
geometryRef,
|
|
210
|
+
getLimits,
|
|
211
|
+
applyGeometry,
|
|
212
|
+
persistGeometry,
|
|
213
|
+
};
|
|
214
|
+
return createPortal(_jsxs("div", { role: "dialog", "aria-modal": "false", "aria-labelledby": titleId, "data-testid": testId, className: "fixed z-50 flex flex-col overflow-hidden rounded-lg border bg-background text-foreground shadow-2xl", style: {
|
|
215
|
+
left: geometry.x,
|
|
216
|
+
top: geometry.y,
|
|
217
|
+
width: geometry.width,
|
|
218
|
+
height: geometry.height,
|
|
219
|
+
}, children: [_jsxs("div", { ...moveDrag, onPointerDown: onHeaderPointerDown, className: "flex shrink-0 cursor-grab touch-none select-none items-center gap-2 border-b bg-muted px-3 py-2 active:cursor-grabbing", children: [_jsx("button", { type: "button", "data-drag-handle": "", "aria-label": t("kumiko.widget.floatingPanel.move"), onKeyDown: onGripKeyDown, className: "rounded-xs p-1 text-muted-foreground hover:bg-secondary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden", children: _jsx(GripVerticalIcon, { className: "size-4" }) }), _jsx("div", { id: titleId, className: "min-w-0 flex-1 truncate font-medium", children: title }), headerActions] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children }), EDGES.map((edge) => (_jsx(PanelResizeHandle, { edges: [edge], className: EDGE_HANDLE_CLASS[edge], context: handleContext, separator: { label: t(EDGE_LABEL_KEYS[edge]), geometry, limits } }, edge))), CORNER_HANDLES.map((corner) => (_jsx(PanelResizeHandle, { edges: corner.edges, className: corner.className, context: handleContext }, corner.edges.join("-"))))] }), document.body);
|
|
220
|
+
}
|
|
221
|
+
function PanelResizeHandle({ edges, className, context, separator, }) {
|
|
222
|
+
const drag = usePointerDrag({
|
|
223
|
+
onStart: () => context.geometryRef.current,
|
|
224
|
+
onMove: (start, delta) => context.applyGeometry(resizeGeometry(start, edges, delta, context.getLimits())),
|
|
225
|
+
onEnd: context.persistGeometry,
|
|
226
|
+
});
|
|
227
|
+
if (separator === undefined) {
|
|
228
|
+
return _jsx("div", { "aria-hidden": "true", ...drag, className: className });
|
|
229
|
+
}
|
|
230
|
+
const [edge] = edges;
|
|
231
|
+
const horizontalEdge = edge === "left" || edge === "right";
|
|
232
|
+
const { geometry, limits } = separator;
|
|
233
|
+
const onKeyDown = (event) => {
|
|
234
|
+
const delta = arrowKeyDelta(event);
|
|
235
|
+
if (delta === undefined || (horizontalEdge ? delta.dx === 0 : delta.dy === 0))
|
|
236
|
+
return;
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
context.applyGeometry(resizeGeometry(context.geometryRef.current, edges, delta, context.getLimits()));
|
|
239
|
+
context.persistGeometry();
|
|
240
|
+
};
|
|
241
|
+
return (_jsx("div", { role: "separator", "aria-orientation": horizontalEdge ? "vertical" : "horizontal", "aria-label": separator.label, "aria-valuenow": horizontalEdge ? geometry.width : geometry.height, "aria-valuemin": horizontalEdge ? limits.minWidth : limits.minHeight, "aria-valuemax": horizontalEdge ? limits.maxWidth : limits.maxHeight, tabIndex: 0, ...drag, onKeyDown: onKeyDown, className: className }));
|
|
242
|
+
}
|