@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,153 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
|
+
import { MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
6
|
+
import { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, STICKY_PRIMARY_ACTION_PROP, } from "../../index.js";
|
|
7
|
+
import { cn } from "../lib/cn.js";
|
|
8
|
+
import { Button as UiButton } from "../ui/button.js";
|
|
9
|
+
function asFooterElement(node) {
|
|
10
|
+
return isValidElement(node) ? node : undefined;
|
|
11
|
+
}
|
|
12
|
+
function backAccessibleName(node) {
|
|
13
|
+
const { children, ariaLabel } = node.props;
|
|
14
|
+
return typeof children === "string" ? children : ariaLabel;
|
|
15
|
+
}
|
|
16
|
+
// Back collapses to an icon, so it needs a name that survives losing its children;
|
|
17
|
+
// without one it stays a regular overflow action.
|
|
18
|
+
function isIconizableBackNode(node) {
|
|
19
|
+
const element = asFooterElement(node);
|
|
20
|
+
return (element?.props[FOOTER_ACTION_ROLE_PROP] === "back" && backAccessibleName(element) !== undefined);
|
|
21
|
+
}
|
|
22
|
+
function isExplicitPrimaryNode(node) {
|
|
23
|
+
return asFooterElement(node)?.props[FOOTER_ACTION_ROLE_PROP] === "primary";
|
|
24
|
+
}
|
|
25
|
+
function isSubmitNode(node) {
|
|
26
|
+
return asFooterElement(node)?.props.type === "submit";
|
|
27
|
+
}
|
|
28
|
+
function isPrimaryNode(node) {
|
|
29
|
+
return isExplicitPrimaryNode(node) || isSubmitNode(node);
|
|
30
|
+
}
|
|
31
|
+
function isStickyMarkedNode(node) {
|
|
32
|
+
return asFooterElement(node)?.props[STICKY_PRIMARY_ACTION_PROP] === true;
|
|
33
|
+
}
|
|
34
|
+
function lastOf(items) {
|
|
35
|
+
return items[items.length - 1];
|
|
36
|
+
}
|
|
37
|
+
function splitNarrowFooterNodes(nodes) {
|
|
38
|
+
const back = nodes.find(isIconizableBackNode);
|
|
39
|
+
const primary = nodes.find(isExplicitPrimaryNode) ??
|
|
40
|
+
lastOf(nodes.filter(isSubmitNode)) ??
|
|
41
|
+
lastOf(nodes.filter(isStickyMarkedNode));
|
|
42
|
+
return {
|
|
43
|
+
back,
|
|
44
|
+
primary,
|
|
45
|
+
overflow: nodes.filter((node) => node !== back && node !== primary),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function BackIconButton({ node }) {
|
|
49
|
+
return cloneElement(node, {
|
|
50
|
+
size: "icon",
|
|
51
|
+
ariaLabel: backAccessibleName(node),
|
|
52
|
+
children: null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Stands in for the unsaved text and the overflow badge while the "…" trigger is not visible.
|
|
56
|
+
function UnsavedIndicator({ status, testId, }) {
|
|
57
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", "data-testid": testId, className: "absolute top-1 right-1 size-2 rounded-full bg-status-active" }), _jsx("span", { className: "sr-only", children: status })] }));
|
|
58
|
+
}
|
|
59
|
+
function PrimaryButton({ node, unsavedStatus, indicatorTestId, }) {
|
|
60
|
+
const indicator = unsavedStatus !== undefined ? (_jsx(UnsavedIndicator, { status: unsavedStatus, testId: indicatorTestId })) : null;
|
|
61
|
+
const element = asFooterElement(node);
|
|
62
|
+
if (element === undefined || !isPrimaryNode(node)) {
|
|
63
|
+
return (_jsxs("div", { className: "relative min-w-0 flex-1 [&>button]:w-full", children: [node, indicator] }));
|
|
64
|
+
}
|
|
65
|
+
const narrowLabel = element.props[NARROW_LABEL_PROP];
|
|
66
|
+
return cloneElement(element, {
|
|
67
|
+
className: "relative min-w-0 flex-1",
|
|
68
|
+
children: (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 truncate", children: narrowLabel ?? element.props.children }), indicator] })),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Overflow nodes are arbitrary (slot mounts may render nothing, buttons may be disabled),
|
|
72
|
+
// so "has something to offer" is read from the mounted DOM, not from the elements.
|
|
73
|
+
const ENABLED_ACTION_SELECTOR = "button:not(:disabled):not([aria-disabled='true']), a[href]:not([aria-disabled='true'])";
|
|
74
|
+
function useContainsEnabledAction(container) {
|
|
75
|
+
const [containsEnabledAction, setContainsEnabledAction] = useState(false);
|
|
76
|
+
useLayoutEffect(() => {
|
|
77
|
+
if (container === null)
|
|
78
|
+
return;
|
|
79
|
+
const update = () => setContainsEnabledAction(container.querySelector(ENABLED_ACTION_SELECTOR) !== null);
|
|
80
|
+
update();
|
|
81
|
+
const observer = new MutationObserver(update);
|
|
82
|
+
observer.observe(container, {
|
|
83
|
+
subtree: true,
|
|
84
|
+
childList: true,
|
|
85
|
+
attributes: true,
|
|
86
|
+
attributeFilter: ["disabled", "aria-disabled", "href"],
|
|
87
|
+
});
|
|
88
|
+
return () => observer.disconnect();
|
|
89
|
+
}, [container]);
|
|
90
|
+
return containsEnabledAction;
|
|
91
|
+
}
|
|
92
|
+
function createOverflowHolder() {
|
|
93
|
+
const holder = document.createElement("div");
|
|
94
|
+
holder.style.display = "contents";
|
|
95
|
+
return holder;
|
|
96
|
+
}
|
|
97
|
+
// The overflow nodes are rendered once into a detached holder element that is moved into the
|
|
98
|
+
// popover content while open and back into an always-mounted hidden host while closed. That keeps
|
|
99
|
+
// dialogs owned by overflow actions mounted, without keeping the popover's DismissableLayer and
|
|
100
|
+
// FocusScope (Escape handling, focus trap) alive while it is closed.
|
|
101
|
+
function OverflowPopover({ nodes, holder, label, unsavedCount, testId, hasEnabledAction, }) {
|
|
102
|
+
const [open, setOpen] = useState(false);
|
|
103
|
+
const hostRef = useRef(null);
|
|
104
|
+
const isDirty = unsavedCount !== undefined && unsavedCount > 0;
|
|
105
|
+
const setHost = useCallback((element) => {
|
|
106
|
+
hostRef.current = element;
|
|
107
|
+
if (element !== null && holder !== null)
|
|
108
|
+
element.appendChild(holder);
|
|
109
|
+
}, [holder]);
|
|
110
|
+
// Radix recomposes its content ref now and then, calling this with null in between; reacting to
|
|
111
|
+
// that would move the holder out and drop the focus the popover just placed on its first action.
|
|
112
|
+
const setContent = useCallback((element) => {
|
|
113
|
+
if (element !== null && holder !== null && holder.parentElement !== element) {
|
|
114
|
+
element.appendChild(holder);
|
|
115
|
+
}
|
|
116
|
+
}, [holder]);
|
|
117
|
+
// Closes the popover once an action was picked. Native listener: the holder moves between DOM
|
|
118
|
+
// parents, and no React ancestor of its content is an interactive element to hang onClick on.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (holder === null)
|
|
121
|
+
return;
|
|
122
|
+
const closeOnAction = (event) => {
|
|
123
|
+
if (event.target instanceof Element && event.target.closest("button") !== null) {
|
|
124
|
+
setOpen(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
holder.addEventListener("click", closeOnAction);
|
|
128
|
+
return () => holder.removeEventListener("click", closeOnAction);
|
|
129
|
+
}, [holder]);
|
|
130
|
+
const popoverShown = open && hasEnabledAction;
|
|
131
|
+
useLayoutEffect(() => {
|
|
132
|
+
if (!popoverShown && holder !== null && hostRef.current !== null) {
|
|
133
|
+
hostRef.current.appendChild(holder);
|
|
134
|
+
}
|
|
135
|
+
}, [popoverShown, holder]);
|
|
136
|
+
return (_jsxs(PopoverPrimitive.Root, { open: popoverShown, onOpenChange: setOpen, children: [_jsx(PopoverPrimitive.Trigger, { asChild: true, children: _jsxs(UiButton, { type: "button", variant: "outline", size: "icon", className: cn("relative shrink-0 border-input hover:bg-muted", !hasEnabledAction && "hidden"), "aria-label": label, "data-testid": testId !== undefined ? `${testId}-overflow` : undefined, children: [_jsx(MoreHorizontal, { className: "size-4", "aria-hidden": "true" }), isDirty && (_jsx("span", { "aria-hidden": "true", "data-testid": testId !== undefined ? `${testId}-overflow-badge` : undefined, className: "absolute top-1 right-1 size-2 rounded-full bg-status-active" }))] }) }), _jsx("div", { ref: setHost, className: "hidden", children: holder !== null && createPortal(nodes, holder) }), _jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ref: setContent, side: "top", align: "center", sideOffset: 8, className: cn("z-50 flex w-[calc(100vw-2rem)] flex-col gap-1 rounded-md border border-border bg-card p-1 shadow-md", "[&_button]:w-full [&_button]:justify-start [&_button]:text-left") }) })] }));
|
|
137
|
+
}
|
|
138
|
+
/** Pinned footer below `sm`: always one fixed-height row, Back as icon on the left,
|
|
139
|
+
* everything else in a "…" popover (hidden while none of it is enabled), the primary
|
|
140
|
+
* action filling the rest. */
|
|
141
|
+
export function NarrowPinnedFooter({ nodes, overflowLabel, unsavedCount, unsavedStatus, testId, keyboardInset, }) {
|
|
142
|
+
const { back, primary, overflow } = splitNarrowFooterNodes(nodes);
|
|
143
|
+
const backElement = asFooterElement(back);
|
|
144
|
+
const [holder] = useState(() => typeof document === "undefined" ? null : createOverflowHolder());
|
|
145
|
+
const hasEnabledAction = useContainsEnabledAction(holder);
|
|
146
|
+
const overflowTriggerVisible = overflow.length > 0 && hasEnabledAction;
|
|
147
|
+
const isDirty = unsavedCount !== undefined && unsavedCount > 0;
|
|
148
|
+
return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, className: "flex h-14 shrink-0 items-center gap-2 border-t border-border bg-card px-4",
|
|
149
|
+
// iOS ignores interactive-widget=resizes-content, so the in-flow footer would stay behind the keyboard.
|
|
150
|
+
style: keyboardInset > 0
|
|
151
|
+
? { transform: `translateY(-${keyboardInset}px)`, position: "relative", zIndex: 10 }
|
|
152
|
+
: undefined, children: [backElement !== undefined && _jsx(BackIconButton, { node: backElement }), overflow.length > 0 && (_jsx(OverflowPopover, { nodes: overflow, holder: holder, label: overflowLabel, unsavedCount: unsavedCount, testId: testId, hasEnabledAction: hasEnabledAction })), primary !== undefined && (_jsx(PrimaryButton, { node: primary, unsavedStatus: isDirty && !overflowTriggerVisible ? unsavedStatus : undefined, indicatorTestId: testId !== undefined ? `${testId}-primary-badge` : undefined }))] }));
|
|
153
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type NumberInputProps = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly value: number | "";
|
|
6
|
+
readonly onChange: (v: number | undefined) => void;
|
|
7
|
+
readonly locale?: string;
|
|
8
|
+
readonly grouping?: boolean;
|
|
9
|
+
readonly integer?: boolean;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
readonly required?: boolean;
|
|
12
|
+
readonly hasError?: boolean;
|
|
13
|
+
readonly placeholder?: string;
|
|
14
|
+
readonly testId?: string;
|
|
15
|
+
readonly className?: string;
|
|
16
|
+
readonly ariaDescribedBy?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function NumberInput({ id, name, value, onChange, locale, grouping, integer, disabled, required, hasError, placeholder, testId, className, ariaDescribedBy, }: NumberInputProps): ReactNode;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// NumberInput — type=text with a focus-aware locale format, so a blurred
|
|
3
|
+
// value shows thousands separators ("28.000") while editing shows the raw
|
|
4
|
+
// locale string ("28000"). Not type=number: browsers reject formatted
|
|
5
|
+
// strings and locale decimal commas there. The parent value is updated while
|
|
6
|
+
// typing (every parseable draft), so submit shortcuts that skip blur still
|
|
7
|
+
// see the typed value; blur only normalizes the display. A draft that does
|
|
8
|
+
// not parse clears the value, so the parent never holds a stale prefix.
|
|
9
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
10
|
+
import { cn } from "../lib/cn.js";
|
|
11
|
+
import { Input as UiInput } from "../ui/input.js";
|
|
12
|
+
import { parseLocaleNumber, resolveSafeLocale } from "./locale-number.js";
|
|
13
|
+
// 20 is the Intl maximum: the display must never round a stored value.
|
|
14
|
+
const MAX_FRACTION_DIGITS = 20;
|
|
15
|
+
export function NumberInput({ id, name, value, onChange, locale, grouping = true, integer = false, disabled, required, hasError, placeholder, testId, className, ariaDescribedBy, }) {
|
|
16
|
+
const resolvedLocale = resolveSafeLocale(locale);
|
|
17
|
+
const [focused, setFocused] = useState(false);
|
|
18
|
+
const [draft, setDraft] = useState("");
|
|
19
|
+
const inputRef = useRef(null);
|
|
20
|
+
// Focus swaps the displayed value ("10.000" → "10000") and the browser then
|
|
21
|
+
// collapses the cursor to the end, so typing or Playwright's fill() would
|
|
22
|
+
// append instead of replace. Select-all-on-focus matches MoneyInput; a click
|
|
23
|
+
// therefore does not place the caret.
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
if (focused)
|
|
26
|
+
inputRef.current?.select();
|
|
27
|
+
}, [focused]);
|
|
28
|
+
const format = (useGrouping) => value === ""
|
|
29
|
+
? ""
|
|
30
|
+
: new Intl.NumberFormat(resolvedLocale, {
|
|
31
|
+
maximumFractionDigits: MAX_FRACTION_DIGITS,
|
|
32
|
+
useGrouping,
|
|
33
|
+
}).format(value);
|
|
34
|
+
const handleChange = (raw) => {
|
|
35
|
+
setDraft(raw);
|
|
36
|
+
if (raw.trim() === "") {
|
|
37
|
+
onChange(undefined);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const parsed = parseLocaleNumber(raw, resolvedLocale);
|
|
41
|
+
// An unparseable draft clears the value instead of leaving the last
|
|
42
|
+
// parseable prefix behind ("12abc" must not keep submitting 12).
|
|
43
|
+
if (Number.isNaN(parsed) || (integer && !Number.isInteger(parsed))) {
|
|
44
|
+
onChange(undefined);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
onChange(parsed);
|
|
48
|
+
};
|
|
49
|
+
return (_jsx(UiInput, { ref: inputRef, type: "text", inputMode: integer ? "numeric" : "decimal", autoComplete: "off", id: id, name: name, disabled: disabled, "aria-required": required, "aria-invalid": hasError === true ? true : undefined, "aria-describedby": ariaDescribedBy, "data-testid": testId, value: focused ? draft : format(grouping), onFocus: () => {
|
|
50
|
+
setDraft(format(false));
|
|
51
|
+
setFocused(true);
|
|
52
|
+
}, onBlur: () => setFocused(false), onChange: (e) => handleChange(e.target.value), ...(placeholder !== undefined && { placeholder }), className: cn("text-left tabular-nums", className) }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type GroupableOption = {
|
|
2
|
+
readonly group?: string;
|
|
3
|
+
};
|
|
4
|
+
export type OptionGroup<T extends GroupableOption> = {
|
|
5
|
+
readonly heading: string | undefined;
|
|
6
|
+
readonly options: readonly T[];
|
|
7
|
+
};
|
|
8
|
+
export declare function groupOptions<T extends GroupableOption>(options: readonly T[]): readonly OptionGroup<T>[];
|
|
9
|
+
export declare function hasDescriptionOrGroup(options: readonly (GroupableOption & {
|
|
10
|
+
readonly description?: string;
|
|
11
|
+
})[]): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function isGrouped(option) {
|
|
2
|
+
return option.group !== undefined && option.group !== "";
|
|
3
|
+
}
|
|
4
|
+
// Ungrouped options come first (original order), then one section per `group`
|
|
5
|
+
// in order of its first occurrence. An empty-string group counts as ungrouped.
|
|
6
|
+
export function groupOptions(options) {
|
|
7
|
+
const ungrouped = [];
|
|
8
|
+
const byHeading = new Map();
|
|
9
|
+
for (const option of options) {
|
|
10
|
+
if (!isGrouped(option)) {
|
|
11
|
+
ungrouped.push(option);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const bucket = byHeading.get(option.group);
|
|
15
|
+
if (bucket === undefined)
|
|
16
|
+
byHeading.set(option.group, [option]);
|
|
17
|
+
else
|
|
18
|
+
bucket.push(option);
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
...(ungrouped.length > 0 ? [{ heading: undefined, options: ungrouped }] : []),
|
|
22
|
+
...[...byHeading].map(([heading, grouped]) => ({ heading, options: grouped })),
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
export function hasDescriptionOrGroup(options) {
|
|
26
|
+
return options.some((option) => option.description !== undefined || isGrouped(option));
|
|
27
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { usePageHeaderSlot } from "../layout/page-header-slot.js";
|
|
5
|
+
export function DefaultPageHeader({ title, recordTitle, status, actions, overflowItems, hideBreadcrumb, breadcrumbScreenId, }) {
|
|
6
|
+
const slot = usePageHeaderSlot();
|
|
7
|
+
const setOverflowItems = slot?.setOverflowItems;
|
|
8
|
+
const setTitle = slot?.setTitle;
|
|
9
|
+
const setRecordTitle = slot?.setRecordTitle;
|
|
10
|
+
const setBreadcrumbHidden = slot?.setBreadcrumbHidden;
|
|
11
|
+
const setBreadcrumbScreenId = slot?.setBreadcrumbScreenId;
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (setBreadcrumbHidden === undefined || hideBreadcrumb !== true)
|
|
14
|
+
return;
|
|
15
|
+
setBreadcrumbHidden(true);
|
|
16
|
+
return () => setBreadcrumbHidden(false);
|
|
17
|
+
}, [setBreadcrumbHidden, hideBreadcrumb]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (setBreadcrumbScreenId === undefined || breadcrumbScreenId === undefined)
|
|
20
|
+
return;
|
|
21
|
+
setBreadcrumbScreenId(breadcrumbScreenId);
|
|
22
|
+
return () => setBreadcrumbScreenId(undefined);
|
|
23
|
+
}, [setBreadcrumbScreenId, breadcrumbScreenId]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (setRecordTitle === undefined || recordTitle === undefined)
|
|
26
|
+
return;
|
|
27
|
+
setRecordTitle(recordTitle);
|
|
28
|
+
return () => setRecordTitle(undefined);
|
|
29
|
+
}, [setRecordTitle, recordTitle]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (setTitle === undefined || title === undefined)
|
|
32
|
+
return;
|
|
33
|
+
setTitle(title);
|
|
34
|
+
return () => setTitle(undefined);
|
|
35
|
+
}, [setTitle, title]);
|
|
36
|
+
// Without a slot no caller passes overflowItems: screens only render a
|
|
37
|
+
// PageHeader below a shell that offers one.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (setOverflowItems === undefined || overflowItems === undefined)
|
|
40
|
+
return;
|
|
41
|
+
setOverflowItems(overflowItems);
|
|
42
|
+
return () => setOverflowItems(undefined);
|
|
43
|
+
}, [setOverflowItems, overflowItems]);
|
|
44
|
+
if (slot === null) {
|
|
45
|
+
return (_jsxs("div", { "data-kumiko-layout": "page-header-inline", className: "flex items-center gap-2 px-4 py-2", children: [status, _jsx("div", { className: "ml-auto flex items-center gap-2", children: actions })] }));
|
|
46
|
+
}
|
|
47
|
+
return (_jsxs(_Fragment, { children: [status !== undefined &&
|
|
48
|
+
slot.statusElement !== null &&
|
|
49
|
+
createPortal(status, slot.statusElement), actions !== undefined &&
|
|
50
|
+
slot.actionsElement !== null &&
|
|
51
|
+
createPortal(actions, slot.actionsElement)] }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const PROMO_ACTION_CLASS = "inline-flex min-h-13 w-full items-center justify-center rounded-full bg-promo-accent px-6 text-base font-semibold text-promo-accent-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-promo-accent focus-visible:ring-offset-2 focus-visible:ring-offset-promo";
|
|
3
|
+
function PromoActionControl({ action }) {
|
|
4
|
+
if ("href" in action) {
|
|
5
|
+
return (_jsx("a", { href: action.href, "data-testid": action.testId, className: PROMO_ACTION_CLASS, children: action.label }));
|
|
6
|
+
}
|
|
7
|
+
return (_jsx("button", { type: "button", onClick: action.onClick, "data-testid": action.testId, className: PROMO_ACTION_CLASS, children: action.label }));
|
|
8
|
+
}
|
|
9
|
+
export function PromoPanel({ title, eyebrow, children, action, testId, }) {
|
|
10
|
+
return (_jsxs("section", { "data-testid": testId, className: "flex flex-col gap-4 rounded-(--card-radius) bg-promo p-6 text-promo-foreground", children: [eyebrow !== undefined && (_jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-promo-accent", children: eyebrow })), _jsx("h2", { className: "font-heading text-2xl font-bold leading-tight", children: title }), children !== undefined && (_jsx("div", { className: "flex flex-col gap-3 text-sm text-promo-foreground/85", children: children })), action !== undefined && _jsx(PromoActionControl, { action: action })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export declare function SegmentedControl({ id, name, value, onChange, options, disabled, required, hasError, labelProps, }: {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly onChange: (v: string) => void;
|
|
7
|
+
readonly options: readonly {
|
|
8
|
+
readonly value: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
}[];
|
|
12
|
+
readonly disabled?: boolean;
|
|
13
|
+
readonly required?: boolean;
|
|
14
|
+
readonly hasError?: boolean;
|
|
15
|
+
/** Accessible name: `aria-labelledby` into a Field label, or a plain `aria-label`. */
|
|
16
|
+
readonly labelProps: {
|
|
17
|
+
"aria-labelledby": string;
|
|
18
|
+
} | {
|
|
19
|
+
"aria-label": string;
|
|
20
|
+
};
|
|
21
|
+
}): ReactNode;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
// WAI-ARIA radiogroup pattern (role="radiogroup" + role="radio" children):
|
|
5
|
+
// arrow keys move focus AND selection in the same step, only the checked
|
|
6
|
+
// segment (or the first when none is checked) sits in the tab order.
|
|
7
|
+
export function SegmentedControl({ id, name, value, onChange, options, disabled, required, hasError, labelProps, }) {
|
|
8
|
+
const buttonRefs = useRef([]);
|
|
9
|
+
// An unknown/legacy value checks no segment; the first one must still be tabbable.
|
|
10
|
+
const selectedIndex = options.findIndex((opt) => opt.value === value);
|
|
11
|
+
const tabStopIndex = selectedIndex === -1 ? 0 : selectedIndex;
|
|
12
|
+
const selectAt = (index) => {
|
|
13
|
+
const target = options[index];
|
|
14
|
+
if (target === undefined)
|
|
15
|
+
return;
|
|
16
|
+
onChange(target.value);
|
|
17
|
+
buttonRefs.current[index]?.focus();
|
|
18
|
+
};
|
|
19
|
+
// Arrow keys skip disabled segments; with none enabled they stay put.
|
|
20
|
+
const nextEnabledIndex = (from, step) => {
|
|
21
|
+
for (let hop = 1; hop <= options.length; hop++) {
|
|
22
|
+
const candidate = (from + step * hop + options.length * hop) % options.length;
|
|
23
|
+
if (options[candidate]?.disabled !== true)
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
return from;
|
|
27
|
+
};
|
|
28
|
+
const handleKeyDown = (e, index) => {
|
|
29
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
selectAt(nextEnabledIndex(index, 1));
|
|
32
|
+
}
|
|
33
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
selectAt(nextEnabledIndex(index, -1));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return (_jsxs("div", { role: "radiogroup", ...labelProps, "aria-required": required, "aria-invalid": hasError === true ? true : undefined, "data-testid": `segmented-${id}`, className: cn("inline-flex min-h-9 w-fit flex-wrap overflow-hidden rounded-md border", hasError === true ? "border-destructive" : "border-input"), children: [_jsx("input", { type: "hidden", name: name, value: value }), options.map((opt, index) => {
|
|
39
|
+
const checked = opt.value === value;
|
|
40
|
+
return (_jsx("button", { ref: (node) => {
|
|
41
|
+
buttonRefs.current[index] = node;
|
|
42
|
+
}, type: "button", role: "radio", "aria-checked": checked, tabIndex: index === tabStopIndex ? 0 : -1, disabled: disabled === true || opt.disabled === true, "data-testid": `segmented-${id}-${opt.value}`, onClick: () => onChange(opt.value), onKeyDown: (e) => handleKeyDown(e, index), className: cn(
|
|
43
|
+
// Per-segment top/left border instead of the container's
|
|
44
|
+
// `divide-x`: divide-x only draws verticals between siblings
|
|
45
|
+
// in source order, which misplaces borders the moment a
|
|
46
|
+
// segment wraps to a new row (stray left border on the first
|
|
47
|
+
// item of row 2, no line between the rows). The -1px margin
|
|
48
|
+
// collapses each segment's own border onto its neighbour's —
|
|
49
|
+
// for row/column edges it overlaps the container's border
|
|
50
|
+
// instead, clipped by the container's `overflow-hidden`.
|
|
51
|
+
//
|
|
52
|
+
// `grow` lets segments share leftover space on a wrapped row
|
|
53
|
+
// so every row spans the container's full (widest-row) width —
|
|
54
|
+
// without it, a shorter last row left its right edge borderless
|
|
55
|
+
// and the next row's top border cut a notch into the row above.
|
|
56
|
+
// Harmless on a single-row layout: there's no leftover space to
|
|
57
|
+
// grow into, so `w-fit` on the container still holds.
|
|
58
|
+
"-ml-px -mt-px grow border-l border-t px-3 py-1.5 text-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-50", hasError === true ? "border-destructive/50" : "border-border", checked
|
|
59
|
+
? "bg-primary/10 font-semibold text-primary"
|
|
60
|
+
: "bg-transparent font-medium text-foreground hover:bg-accent"), children: opt.label }, opt.value));
|
|
61
|
+
})] }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { CopyButtonProps, ShareButtonProps } from "../../index.ts";
|
|
3
|
+
export declare function CopyButton({ text, label, copiedLabel, variant, className, testId, }: CopyButtonProps): ReactNode;
|
|
4
|
+
export declare function ShareButton(props: ShareButtonProps): ReactNode;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check, Copy, MessageCircle, Share2 } from "lucide-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { buildWhatsAppShareUrl } from "../../index.js";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { buttonVariants, Button as UiButton } from "../ui/button.js";
|
|
7
|
+
const COPIED_FEEDBACK_MS = 2000;
|
|
8
|
+
function uiVariantFor(variant) {
|
|
9
|
+
return variant === "primary" ? "default" : "outline";
|
|
10
|
+
}
|
|
11
|
+
export function CopyButton({ text, label, copiedLabel, variant, className, testId, }) {
|
|
12
|
+
const [copied, setCopied] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!copied)
|
|
15
|
+
return;
|
|
16
|
+
const timer = setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
|
|
17
|
+
return () => clearTimeout(timer);
|
|
18
|
+
}, [copied]);
|
|
19
|
+
async function copyText() {
|
|
20
|
+
try {
|
|
21
|
+
await navigator.clipboard.writeText(text);
|
|
22
|
+
setCopied(true);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Clipboard denied (insecure context, permission) — the label simply
|
|
26
|
+
// doesn't flip, so the user sees the copy did not happen.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return (_jsxs(UiButton, { type: "button", variant: uiVariantFor(variant), onClick: copyText, "data-testid": testId, "data-copied": copied ? "true" : undefined, className: cn("min-h-11", className), children: [copied ? (_jsx(Check, { "aria-hidden": "true", className: "text-status-ok" })) : (_jsx(Copy, { "aria-hidden": "true" })), _jsx("span", { "aria-live": "polite", children: copied ? copiedLabel : label })] }));
|
|
30
|
+
}
|
|
31
|
+
function hasSystemShareSheet() {
|
|
32
|
+
return typeof navigator !== "undefined" && typeof navigator.share === "function";
|
|
33
|
+
}
|
|
34
|
+
export function ShareButton(props) {
|
|
35
|
+
const { text, label, variant, className, testId } = props;
|
|
36
|
+
const classes = cn(buttonVariants({ variant: uiVariantFor(variant) }), "min-h-11", className);
|
|
37
|
+
if (props.target === "whatsapp") {
|
|
38
|
+
return (_jsxs("a", { href: buildWhatsAppShareUrl(text, props.phone), target: "_blank", rel: "noopener noreferrer", "data-testid": testId, className: classes, children: [_jsx(MessageCircle, { "aria-hidden": "true" }), label] }));
|
|
39
|
+
}
|
|
40
|
+
if (!hasSystemShareSheet())
|
|
41
|
+
return null;
|
|
42
|
+
const { title, url } = props;
|
|
43
|
+
async function openShareSheet() {
|
|
44
|
+
try {
|
|
45
|
+
await navigator.share({
|
|
46
|
+
text,
|
|
47
|
+
...(title !== undefined && { title }),
|
|
48
|
+
...(url !== undefined && { url }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// AbortError when the user closes the sheet — nothing to report.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return (_jsxs("button", { type: "button", onClick: openShareSheet, "data-testid": testId, className: classes, children: [_jsx(Share2, { "aria-hidden": "true" }), label] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { StickyActionBarProps } from "../../index.ts";
|
|
3
|
+
export declare const STICKY_FOOTER_SAFE_AREA_CLASS = "max-sm:pb-[max(1rem,env(safe-area-inset-bottom))]";
|
|
4
|
+
export declare const STICKY_FOOTER_SPACER_CLASS = "max-sm:pb-[calc(6rem_+_env(safe-area-inset-bottom))]";
|
|
5
|
+
export declare function StickyActionBar({ children, back, testId }: StickyActionBarProps): ReactNode;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowLeft } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Button as UiButton } from "../ui/button.js";
|
|
5
|
+
import { useKeyboardInset } from "./use-keyboard-inset.js";
|
|
6
|
+
// env(safe-area-inset-bottom) is 0 unless the page's viewport meta sets
|
|
7
|
+
// viewport-fit=cover; with it, the home indicator would otherwise cover the bar.
|
|
8
|
+
export const STICKY_FOOTER_SAFE_AREA_CLASS = "max-sm:pb-[max(1rem,env(safe-area-inset-bottom))]";
|
|
9
|
+
// Keeps the last content row reachable above the fixed bar (one 44px button
|
|
10
|
+
// row plus the bar's own padding), grown by the same safe-area inset.
|
|
11
|
+
export const STICKY_FOOTER_SPACER_CLASS = "max-sm:pb-[calc(6rem_+_env(safe-area-inset-bottom))]";
|
|
12
|
+
export function StickyActionBar({ children, back, testId }) {
|
|
13
|
+
const keyboardInset = useKeyboardInset();
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", className: cn("sm:hidden", STICKY_FOOTER_SPACER_CLASS) }), _jsxs("div", { "data-testid": testId, style: keyboardInset > 0 ? { bottom: keyboardInset } : undefined, className: cn("flex items-center gap-3", "max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:border-t max-sm:border-border max-sm:bg-background max-sm:px-4 max-sm:pt-3", STICKY_FOOTER_SAFE_AREA_CLASS), children: [back !== undefined && (_jsx(UiButton, { type: "button", variant: "outline", size: "icon", "aria-label": back.label, onClick: back.onBack, "data-testid": testId !== undefined ? `${testId}-back` : undefined, className: "size-13 shrink-0 rounded-full", children: _jsx(ArrowLeft, { "aria-hidden": "true", className: "size-5" }) })), _jsx("div", { className: "flex min-w-0 flex-1 gap-2 [&>*]:min-h-13 [&>*]:flex-1", children: children })] })] }));
|
|
15
|
+
}
|