@flamingo-stack/openframe-frontend-core 0.0.702 → 0.0.703-snapshot.20261006080901
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/dist/{chunk-CYYTD5ZK.cjs → chunk-262YQFOS.cjs} +47 -47
- package/dist/{chunk-SAA7B3DA.js → chunk-2G546OND.js} +500 -67
- package/dist/{chunk-6JPQDYYY.js → chunk-2JDVL7UV.js} +2614 -2001
- package/dist/{chunk-ASN65P3F.cjs → chunk-4DQMTBMK.cjs} +15 -15
- package/dist/{chunk-CZ34HXDW.js → chunk-4KGXLVCF.js} +686 -725
- package/dist/{chunk-TCZCMD5Q.js → chunk-5HBHO5TB.js} +1 -1
- package/dist/{chunk-446KS5BG.js → chunk-5QJDM6UV.js} +5 -5
- package/dist/{chunk-UJJWTJ6S.js → chunk-6WQIUQUC.js} +30 -16
- package/dist/{chunk-YR6JPJF4.js → chunk-6XIF2Z7I.js} +4 -4
- package/dist/{chunk-YGRHITPY.cjs → chunk-7JIL3WOH.cjs} +24 -24
- package/dist/{chunk-RS7HWAYX.js → chunk-7LFIKJX2.js} +3 -3
- package/dist/{chunk-4OGAXAEG.cjs → chunk-ABU3TGXA.cjs} +3 -3
- package/dist/{chunk-3WSFIV5G.cjs → chunk-AMYY3PNL.cjs} +69 -55
- package/dist/{chunk-BRXOGCH3.cjs → chunk-BIZNSF3U.cjs} +3 -3
- package/dist/{chunk-2A4XES7R.cjs → chunk-BM5MVGH3.cjs} +3 -3
- package/dist/{chunk-IIMICN7L.cjs → chunk-C3BWIU7R.cjs} +4 -44
- package/dist/{chunk-LZNJH23A.cjs → chunk-C7XGRLT5.cjs} +12 -12
- package/dist/{chunk-6YCXA7CN.js → chunk-CH5232HV.js} +20 -19
- package/dist/{chunk-UIBSSVON.cjs → chunk-D7BTSVMS.cjs} +2 -2
- package/dist/{chunk-BO5ZHDA4.js → chunk-ERCV6HHJ.js} +7 -7
- package/dist/{chunk-5V6O73EH.js → chunk-FLKJYMVB.js} +3 -3
- package/dist/{chunk-Z5HDCZAS.js → chunk-FXENTYE5.js} +15 -15
- package/dist/{chunk-HIXGAY2L.cjs → chunk-HFJW24JJ.cjs} +121 -121
- package/dist/{chunk-HPJ4XS2L.js → chunk-HJAYVKWQ.js} +24 -24
- package/dist/{chunk-PLK6GHN7.js → chunk-I7DFUQMN.js} +10 -10
- package/dist/{chunk-KJ5LWATV.js → chunk-IHU5ANUY.js} +7 -7
- package/dist/{chunk-AEQANLBW.cjs → chunk-IZGDG5X6.cjs} +505 -72
- package/dist/{chunk-2V4Z44G6.js → chunk-JEYRXT5L.js} +17 -17
- package/dist/{chunk-B3LZJ6J3.cjs → chunk-L3IC42IF.cjs} +32 -32
- package/dist/{chunk-RTGPIHVO.cjs → chunk-LDPEQGKT.cjs} +2243 -1630
- package/dist/{chunk-FD4DBTFO.cjs → chunk-LTSTC3N5.cjs} +1253 -1292
- package/dist/{chunk-OGLCD3UL.js → chunk-MFFCUVWG.js} +6 -6
- package/dist/{chunk-3L7KCOGO.cjs → chunk-MGGGBHYD.cjs} +30 -30
- package/dist/{chunk-QFW3RUZS.cjs → chunk-MI3JUJ75.cjs} +33 -33
- package/dist/{chunk-EMCQGFXD.cjs → chunk-MMOODW57.cjs} +28 -27
- package/dist/{chunk-Q76ZTHNV.js → chunk-MP2C3LJ4.js} +1 -1
- package/dist/{chunk-E3DH543A.cjs → chunk-N3TO7JV3.cjs} +98 -98
- package/dist/{chunk-2T6QWNXU.js → chunk-NDESEPW5.js} +99 -11
- package/dist/{chunk-ETFFEA3J.js → chunk-NRLJUBCS.js} +7 -7
- package/dist/{chunk-HUOLB7ZN.cjs → chunk-OHFS7VOU.cjs} +9 -9
- package/dist/{chunk-MWUBBBWZ.js → chunk-QVSFBSWV.js} +10 -10
- package/dist/{chunk-MDBHZBW7.cjs → chunk-RKV5B7EU.cjs} +92 -4
- package/dist/{chunk-3G76LRGL.cjs → chunk-SYXQLETR.cjs} +29 -29
- package/dist/{chunk-FHM7GD5B.cjs → chunk-UK62YTH6.cjs} +28 -14
- package/dist/{chunk-HUMWQMCU.js → chunk-UT4YHQZW.js} +3 -3
- package/dist/{chunk-NGJCWEYX.cjs → chunk-VHCACDKP.cjs} +46 -46
- package/dist/{chunk-5FFNNJM7.js → chunk-VR2ZLFCP.js} +1 -1
- package/dist/{chunk-IZKOQVXO.js → chunk-VTASHWER.js} +14 -14
- package/dist/{chunk-64O4L7WN.js → chunk-WUJNYGO2.js} +11 -51
- package/dist/{chunk-JOY6HVPV.js → chunk-XATTBO5X.js} +25 -11
- package/dist/{chunk-L7KZA7ZD.cjs → chunk-XIFOBQRQ.cjs} +6 -6
- package/dist/{chunk-CXDJUKC6.cjs → chunk-XJKZ4IKT.cjs} +4 -4
- package/dist/{chunk-XCIFHW4E.cjs → chunk-XOYH2YQE.cjs} +2 -2
- package/dist/{chunk-HYFLTFFB.js → chunk-ZQFZSFQI.js} +2 -2
- package/dist/components/case-studies/index.cjs +24 -24
- package/dist/components/case-studies/index.js +10 -10
- package/dist/components/chat/chat-archive-page.d.ts +3 -3
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
- package/dist/components/chat/chat-nav-sidebar.d.ts +4 -0
- package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-header.d.ts +3 -2
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +36 -2
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +14 -6
- package/dist/components/chat/index.d.ts +4 -1
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +13 -5
- package/dist/components/chat/mingo-archive-list.d.ts +17 -0
- package/dist/components/chat/mingo-archive-list.d.ts.map +1 -0
- package/dist/components/chat/mingo-chat-header.d.ts +31 -0
- package/dist/components/chat/mingo-chat-header.d.ts.map +1 -0
- package/dist/components/chat/mingo-chat-rail.d.ts +54 -0
- package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -0
- package/dist/components/chat/model-display.d.ts.map +1 -1
- package/dist/components/contact/contact-form.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +11 -11
- package/dist/components/contact/index.js +10 -10
- package/dist/components/content-loading-container.d.ts +1 -1
- package/dist/components/content-loading-container.d.ts.map +1 -1
- package/dist/components/docs/index.cjs +14 -14
- package/dist/components/docs/index.js +13 -13
- package/dist/components/docs/skeletons.d.ts.map +1 -1
- package/dist/components/embeds/index.cjs +11 -11
- package/dist/components/embeds/index.js +10 -10
- package/dist/components/faq/index.cjs +12 -12
- package/dist/components/faq/index.js +11 -11
- package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
- package/dist/components/features/index.cjs +10 -10
- package/dist/components/features/index.js +9 -9
- package/dist/components/features/media-gallery-manager.d.ts.map +1 -1
- package/dist/components/features/parallax-image-showcase.d.ts.map +1 -1
- package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
- package/dist/components/features/section-selector.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +119 -119
- package/dist/components/help-center-pages/index.js +39 -39
- package/dist/components/help-center-pages/trust-center-sections.d.ts +2 -2
- package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
- package/dist/components/index.cjs +94 -86
- package/dist/components/index.js +32 -24
- package/dist/components/layout/article-detail-layout.d.ts +1 -1
- package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
- package/dist/components/layout/page-container.d.ts.map +1 -1
- package/dist/components/layout/title-block.d.ts +2 -2
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/loading/page-layout-skeleton.d.ts.map +1 -1
- package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/booking-form.d.ts +4 -4
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +127 -127
- package/dist/components/meeting-scheduler/index.d.ts +2 -2
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +50 -50
- package/dist/components/navigation/app-header.d.ts +4 -0
- package/dist/components/navigation/app-header.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-side-panel.d.ts +142 -0
- package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -0
- package/dist/components/navigation/app-layout.d.ts +11 -1
- package/dist/components/navigation/app-layout.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts +4 -1
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +13 -13
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +12 -12
- package/dist/components/navigation/navigation-sidebar.d.ts +8 -1
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +14 -14
- package/dist/components/onboarding-guides/index.js +13 -13
- package/dist/components/persistent-filter-controls.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +7 -7
- package/dist/components/related-content/index.js +6 -6
- package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
- package/dist/components/tickets/help-center-create-form.d.ts +4 -4
- package/dist/components/tickets/index.cjs +15 -15
- package/dist/components/tickets/index.js +14 -14
- package/dist/components/ui/announcement-bar-view.d.ts.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts.map +1 -1
- package/dist/components/ui/checkbox-block.d.ts.map +1 -1
- package/dist/components/ui/dashboard-info-card.d.ts +1 -1
- package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-body.d.ts +2 -2
- package/dist/components/ui/data-table/data-table-skeleton.d.ts +2 -2
- package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
- package/dist/components/ui/data-table/utils.d.ts +2 -2
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/drawer.d.ts +7 -3
- package/dist/components/ui/drawer.d.ts.map +1 -1
- package/dist/components/ui/entity-image.d.ts +2 -2
- package/dist/components/ui/file-manager/index.cjs +36 -36
- package/dist/components/ui/file-manager/index.js +3 -3
- package/dist/components/ui/filter-modal.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +18 -10
- package/dist/components/ui/index.js +17 -9
- package/dist/components/ui/info-card-row.d.ts.map +1 -1
- package/dist/components/ui/menubar.d.ts +1 -1
- package/dist/components/ui/menubar.d.ts.map +1 -1
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/modal.d.ts.map +1 -1
- package/dist/components/ui/no-data/no-data-actions.d.ts.map +1 -1
- package/dist/components/ui/no-data/no-data-styles.d.ts +1 -1
- package/dist/components/ui/no-data/no-data-styles.d.ts.map +1 -1
- package/dist/components/ui/page-actions.d.ts.map +1 -1
- package/dist/components/ui/panel-resize-handle.d.ts +25 -0
- package/dist/components/ui/panel-resize-handle.d.ts.map +1 -0
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/table/table-skeleton.d.ts +1 -1
- package/dist/components/ui/table/table-skeleton.d.ts.map +1 -1
- package/dist/hooks/index.cjs +23 -9
- package/dist/hooks/index.js +24 -10
- package/dist/hooks/ui/index.d.ts +1 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/use-content-breakpoint.d.ts +32 -0
- package/dist/hooks/ui/use-content-breakpoint.d.ts.map +1 -0
- package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts +2 -2
- package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts.map +1 -1
- package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
- package/dist/hooks/ui/use-resizable-panel-size.d.ts +46 -0
- package/dist/hooks/ui/use-resizable-panel-size.d.ts.map +1 -0
- package/dist/index.cjs +39 -17
- package/dist/index.js +41 -19
- package/dist/styles/layout-steps.d.ts +30 -0
- package/dist/styles/layout-steps.d.ts.map +1 -0
- package/dist/tailwind.config.cjs +36 -11
- package/dist/tailwind.config.js +36 -11
- package/eslint-config/README.md +9 -0
- package/eslint-config/base.js +17 -1
- package/eslint-config/layout-variants.js +57 -0
- package/package.json +3 -1
- package/src/ODS_TOKEN_RULES.md +40 -0
- package/src/components/chat/chat-archive-page.tsx +9 -7
- package/src/components/chat/chat-nav-sidebar.tsx +10 -4
- package/src/components/chat/chat-panel-header.tsx +8 -5
- package/src/components/chat/embeddable-chat.tsx +686 -494
- package/src/components/chat/index.ts +4 -0
- package/src/components/chat/mingo-archive-list.tsx +108 -0
- package/src/components/chat/mingo-chat-header.tsx +104 -0
- package/src/components/chat/mingo-chat-rail.tsx +361 -0
- package/src/components/chat/model-display.tsx +8 -1
- package/src/components/contact/contact-form.tsx +11 -9
- package/src/components/content-loading-container.tsx +4 -2
- package/src/components/docs/doc-viewer.tsx +7 -7
- package/src/components/docs/skeletons.tsx +7 -4
- package/src/components/dynamic-skeleton.tsx +1 -1
- package/src/components/embeds/claude-embed.tsx +1 -1
- package/src/components/embeds/embed-viewer-frame.tsx +4 -4
- package/src/components/embeds/figma-embed.tsx +2 -2
- package/src/components/embeds/google-sheets-viewer.tsx +1 -1
- package/src/components/embeds/pdf-viewer.tsx +3 -3
- package/src/components/empty-state.tsx +6 -6
- package/src/components/faq/faq-section.tsx +2 -2
- package/src/components/faq-accordion.tsx +1 -1
- package/src/components/features/board/board-column-header.tsx +1 -1
- package/src/components/features/command-box.tsx +3 -3
- package/src/components/features/entity-video-section.tsx +2 -2
- package/src/components/features/figma-prototype-viewer.tsx +4 -1
- package/src/components/features/media-gallery-manager.tsx +3 -1
- package/src/components/features/parallax-image-showcase.tsx +9 -6
- package/src/components/features/policy-configuration-panel.tsx +9 -7
- package/src/components/features/provider-button.tsx +1 -1
- package/src/components/features/section-selector.tsx +11 -6
- package/src/components/features/select-button.tsx +4 -4
- package/src/components/features/seo-editor-preview.tsx +2 -2
- package/src/components/features/video-ratio-tabs.tsx +3 -3
- package/src/components/features/waitlist-form.tsx +1 -1
- package/src/components/help-center-pages/trust-center-page.tsx +1 -1
- package/src/components/help-center-pages/trust-center-sections.tsx +14 -14
- package/src/components/icon-display.tsx +1 -1
- package/src/components/layout/article-detail-layout.tsx +4 -2
- package/src/components/layout/list-page-layout.tsx +2 -2
- package/src/components/layout/page-container.tsx +26 -14
- package/src/components/layout/page-heading.tsx +1 -1
- package/src/components/layout/page-layout.tsx +1 -1
- package/src/components/layout/title-block.tsx +13 -13
- package/src/components/loading/card-skeleton.tsx +11 -11
- package/src/components/loading/category-vendor-selector-skeleton.tsx +1 -1
- package/src/components/loading/content-skeleton.tsx +6 -6
- package/src/components/loading/device-card-skeleton.tsx +1 -1
- package/src/components/loading/margin-report-skeleton.tsx +7 -7
- package/src/components/loading/msp-profile-form-skeleton.tsx +3 -3
- package/src/components/loading/organization-card-skeleton.tsx +2 -2
- package/src/components/loading/page-layout-skeleton.tsx +80 -68
- package/src/components/loading/unified-skeleton.tsx +16 -6
- package/src/components/media-carousel.tsx +3 -3
- package/src/components/meeting-scheduler/booking-form.tsx +9 -8
- package/src/components/meeting-scheduler/context-panel.tsx +8 -6
- package/src/components/meeting-scheduler/directory.tsx +4 -4
- package/src/components/meeting-scheduler/index.tsx +15 -15
- package/src/components/meeting-scheduler/slot-picker.tsx +12 -12
- package/src/components/navigation/app-header.tsx +6 -0
- package/src/components/navigation/app-layout-drawer.tsx +14 -237
- package/src/components/navigation/app-layout-side-panel.tsx +594 -0
- package/src/components/navigation/app-layout.tsx +69 -4
- package/src/components/navigation/header-mingo-button.tsx +13 -3
- package/src/components/navigation/index.ts +5 -0
- package/src/components/navigation/navigation-sidebar.tsx +17 -3
- package/src/components/navigation/sliding-sidebar.tsx +2 -1
- package/src/components/pagination.tsx +3 -3
- package/src/components/persistent-filter-controls.tsx +6 -2
- package/src/components/platform/ScriptArguments.tsx +1 -1
- package/src/components/platform/ScriptInfoSection.tsx +4 -4
- package/src/components/shared/article-author-byline.tsx +1 -1
- package/src/components/shared/delivery/delivery-lists.tsx +2 -2
- package/src/components/shared/delivery/delivery-row.tsx +7 -7
- package/src/components/shared/delivery/delivery-table.tsx +3 -3
- package/src/components/shared/detail-page-skeleton.tsx +11 -5
- package/src/components/shared/dev-section/dev-card-row.tsx +6 -6
- package/src/components/shared/dev-section/dev-section-view.tsx +1 -1
- package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -8
- package/src/components/shared/product-release/product-release-card.tsx +9 -9
- package/src/components/shared/product-release/product-releases-view.tsx +1 -1
- package/src/components/shared/product-release/release-detail-page.tsx +5 -5
- package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +1 -1
- package/src/components/shared/roadmap/roadmap-grid.tsx +3 -3
- package/src/components/tickets/help-center-card.tsx +1 -1
- package/src/components/tickets/help-center-create-form.tsx +12 -12
- package/src/components/ui/announcement-bar-view.tsx +2 -10
- package/src/components/ui/benefit-card.tsx +18 -18
- package/src/components/ui/brand-association-grid.tsx +4 -4
- package/src/components/ui/breadcrumb.tsx +4 -1
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/checkbox-block.tsx +9 -7
- package/src/components/ui/content-loader.tsx +3 -3
- package/src/components/ui/cursor-pagination.tsx +3 -3
- package/src/components/ui/dashboard-info-card.tsx +13 -9
- package/src/components/ui/data-attribution.tsx +2 -2
- package/src/components/ui/data-table/data-table-body.tsx +2 -2
- package/src/components/ui/data-table/data-table-header.tsx +15 -15
- package/src/components/ui/data-table/data-table-skeleton.tsx +6 -6
- package/src/components/ui/data-table/utils.ts +5 -5
- package/src/components/ui/dialog.tsx +4 -1
- package/src/components/ui/drawer.tsx +43 -189
- package/src/components/ui/duration-input.tsx +1 -1
- package/src/components/ui/entity-image.tsx +3 -3
- package/src/components/ui/feature-card.tsx +10 -10
- package/src/components/ui/file-manager/file-manager-action-bar.tsx +1 -1
- package/src/components/ui/file-manager/file-manager.tsx +1 -1
- package/src/components/ui/filter-modal.tsx +8 -1
- package/src/components/ui/highlight-card.tsx +7 -7
- package/src/components/ui/info-card-row.tsx +10 -6
- package/src/components/ui/modal-v2.tsx +7 -2
- package/src/components/ui/modal.tsx +3 -2
- package/src/components/ui/no-data/no-data-actions.tsx +10 -4
- package/src/components/ui/no-data/no-data-styles.ts +1 -1
- package/src/components/ui/no-data/no-data.tsx +1 -1
- package/src/components/ui/organization-card.tsx +1 -1
- package/src/components/ui/page-actions.tsx +34 -5
- package/src/components/ui/panel-resize-handle.tsx +184 -0
- package/src/components/ui/progress-bar.tsx +2 -2
- package/src/components/ui/query-report-table/query-report-table.tsx +1 -1
- package/src/components/ui/search-input.tsx +2 -2
- package/src/components/ui/service-card.tsx +2 -2
- package/src/components/ui/sheet.tsx +2 -1
- package/src/components/ui/skeleton.tsx +3 -3
- package/src/components/ui/stacked-rows-panel.tsx +4 -4
- package/src/components/ui/tab-navigation.tsx +2 -2
- package/src/components/ui/tab-selector.tsx +2 -2
- package/src/components/ui/table/table-header.tsx +3 -3
- package/src/components/ui/table/table-skeleton.tsx +3 -3
- package/src/components/ui/table/utils.ts +5 -5
- package/src/components/ui/tag-search-input.tsx +3 -3
- package/src/components/ui/tags-manager.tsx +5 -5
- package/src/components/ui/test-run.tsx +2 -2
- package/src/components/ui/ticket-info-section.tsx +1 -1
- package/src/components/ui/ticket-notes-section.tsx +1 -1
- package/src/components/ui/ticket-status-config-row.tsx +15 -15
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/use-content-breakpoint.ts +58 -0
- package/src/hooks/ui/use-media-query.ts +3 -2
- package/src/hooks/ui/use-resizable-panel-size.ts +114 -0
- package/src/styles/index.css +2 -0
- package/src/styles/layout-steps.ts +14 -0
- package/src/styles/ods-colors.css +15 -0
- package/src/styles/ods-content-area-tokens.css +27 -0
- package/src/styles/ods-content-area.css +31 -0
- package/src/styles/ods-effects.css +4 -2
- package/src/styles/ods-responsive-tokens.css +14 -4
- package/tailwind.config.ts +42 -7
|
@@ -37,6 +37,9 @@ export * from './msp-organization-card';
|
|
|
37
37
|
export * from './msp-organization-card-skeleton';
|
|
38
38
|
export * from './guide-mode-banner';
|
|
39
39
|
export * from './mingo-chat-history';
|
|
40
|
+
export * from './mingo-archive-list';
|
|
41
|
+
export * from './mingo-chat-header';
|
|
42
|
+
export * from './mingo-chat-rail';
|
|
40
43
|
export * from './mingo-history-rail';
|
|
41
44
|
export * from './mingo-chat-modals';
|
|
42
45
|
export * from './chat-header-icon-button';
|
|
@@ -95,5 +98,6 @@ export {
|
|
|
95
98
|
EmbeddableChat,
|
|
96
99
|
type EmbeddableChatHandle,
|
|
97
100
|
type EmbeddableChatProps,
|
|
101
|
+
MINGO_V2_RAIL_WIDTH,
|
|
98
102
|
} from './embeddable-chat';
|
|
99
103
|
export * from './proxy-credentials-panel';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { formatRelativeTime } from '../../utils/date-utils';
|
|
5
|
+
import { BoxArchiveIcon } from '../icons-v2-generated';
|
|
6
|
+
import { SquareAvatar } from '../ui/square-avatar';
|
|
7
|
+
import { ChatListEmptyState } from './chat-list-empty-state';
|
|
8
|
+
import { ChatListItem } from './chat-nav-sidebar';
|
|
9
|
+
import type { DialogItem } from './types/component.types';
|
|
10
|
+
|
|
11
|
+
export interface MingoArchiveListProps {
|
|
12
|
+
dialogs: ReadonlyArray<DialogItem>;
|
|
13
|
+
onSelectDialog?: (id: string) => void;
|
|
14
|
+
/** The first page is loading. */
|
|
15
|
+
isLoading?: boolean;
|
|
16
|
+
hasMore?: boolean;
|
|
17
|
+
isLoadingMore?: boolean;
|
|
18
|
+
onLoadMore?: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The Mingo v2 chat archive (Figma "chat archive"): it opens in the chat
|
|
23
|
+
* column with the chat list kept beside it, one row per archived chat with
|
|
24
|
+
* how long ago it was active and its owner.
|
|
25
|
+
*/
|
|
26
|
+
export function MingoArchiveList({
|
|
27
|
+
dialogs,
|
|
28
|
+
onSelectDialog,
|
|
29
|
+
isLoading = false,
|
|
30
|
+
hasMore = false,
|
|
31
|
+
isLoadingMore = false,
|
|
32
|
+
onLoadMore,
|
|
33
|
+
}: MingoArchiveListProps) {
|
|
34
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const onLoadMoreRef = useRef(onLoadMore);
|
|
36
|
+
const isLoadingMoreRef = useRef(isLoadingMore);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
onLoadMoreRef.current = onLoadMore;
|
|
39
|
+
isLoadingMoreRef.current = isLoadingMore;
|
|
40
|
+
});
|
|
41
|
+
const dialogCount = dialogs.length;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const sentinel = sentinelRef.current;
|
|
44
|
+
if (!sentinel || !hasMore || typeof IntersectionObserver === 'undefined') return undefined;
|
|
45
|
+
const observer = new IntersectionObserver(
|
|
46
|
+
([entry]) => {
|
|
47
|
+
if (entry?.isIntersecting && !isLoadingMoreRef.current) onLoadMoreRef.current?.();
|
|
48
|
+
},
|
|
49
|
+
{ rootMargin: '120px', threshold: 0.1 },
|
|
50
|
+
);
|
|
51
|
+
observer.observe(sentinel);
|
|
52
|
+
return () => observer.disconnect();
|
|
53
|
+
}, [hasMore, dialogCount, isLoadingMore]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<section
|
|
57
|
+
aria-label="Archived chats"
|
|
58
|
+
className="flex min-h-0 flex-1 flex-col gap-[var(--spacing-system-xxs)] px-[var(--spacing-system-xs)] pt-[var(--spacing-system-mf)]"
|
|
59
|
+
>
|
|
60
|
+
<p className="shrink-0 px-[var(--spacing-system-xs)] pt-[var(--spacing-system-xs)] text-ods-text-secondary text-h5">
|
|
61
|
+
Archived Chats
|
|
62
|
+
</p>
|
|
63
|
+
<div className="flex min-h-0 flex-1 flex-col gap-[var(--spacing-system-xxs)] overflow-y-auto overscroll-contain pb-[var(--spacing-system-mf)]">
|
|
64
|
+
{dialogs.length > 0 ? (
|
|
65
|
+
<>
|
|
66
|
+
{dialogs.map(dialog => (
|
|
67
|
+
<ChatListItem
|
|
68
|
+
key={dialog.id}
|
|
69
|
+
title={dialog.title || 'Untitled Chat'}
|
|
70
|
+
onClick={() => onSelectDialog?.(dialog.id)}
|
|
71
|
+
trailing={
|
|
72
|
+
<span className="flex items-center gap-[var(--spacing-system-xs)]">
|
|
73
|
+
{dialog.timestamp && (
|
|
74
|
+
<span className="whitespace-nowrap text-ods-text-secondary text-h6">
|
|
75
|
+
{formatRelativeTime(dialog.timestamp)}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
{dialog.owner && (dialog.owner.name || dialog.owner.avatarUrl) ? (
|
|
79
|
+
<SquareAvatar
|
|
80
|
+
variant="round"
|
|
81
|
+
sizePx={24}
|
|
82
|
+
src={dialog.owner.avatarUrl || undefined}
|
|
83
|
+
alt={dialog.owner.name || undefined}
|
|
84
|
+
fallback={dialog.owner.name || undefined}
|
|
85
|
+
title={dialog.owner.name || undefined}
|
|
86
|
+
/>
|
|
87
|
+
) : null}
|
|
88
|
+
</span>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
{hasMore && <div ref={sentinelRef} className="h-px shrink-0" aria-hidden />}
|
|
93
|
+
</>
|
|
94
|
+
) : isLoading ? (
|
|
95
|
+
Array.from({ length: 6 }, (_, i) => (
|
|
96
|
+
<div key={i} className="h-10 shrink-0 animate-pulse rounded-md bg-ods-skeleton" aria-hidden />
|
|
97
|
+
))
|
|
98
|
+
) : (
|
|
99
|
+
<ChatListEmptyState
|
|
100
|
+
icon={<BoxArchiveIcon size={24} />}
|
|
101
|
+
title="No Archived Chats"
|
|
102
|
+
description="Archived Mingo sessions will show here"
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</section>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Arrow02RightIcon, Ellipsis01Icon, Refresh01LeftIcon, SidebarIcon, XmarkIcon } from '../icons-v2-generated';
|
|
4
|
+
import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu';
|
|
5
|
+
import { SquareAvatar } from '../ui/square-avatar';
|
|
6
|
+
import { ChatHeaderIconButton } from './chat-header-icon-button';
|
|
7
|
+
|
|
8
|
+
export interface MingoChatHeaderProps {
|
|
9
|
+
title: string;
|
|
10
|
+
/** Second line: the chat's owner. */
|
|
11
|
+
subtitle?: string | null;
|
|
12
|
+
/** The owner's avatar at the title cell's end. */
|
|
13
|
+
avatar?: { name?: string | null; avatarUrl?: string | null } | null;
|
|
14
|
+
/** Shows the chat list toggle at the start. */
|
|
15
|
+
onToggleList?: () => void;
|
|
16
|
+
/** The list is on screen beside the chat; drives the toggle's state. */
|
|
17
|
+
listOpen?: boolean;
|
|
18
|
+
/** The chat's ⋯ menu. Empty or omitted leaves it out. */
|
|
19
|
+
menuItems?: ActionsMenuItem[];
|
|
20
|
+
/** An archived chat: offers "Unarchive". */
|
|
21
|
+
onRestore?: () => void;
|
|
22
|
+
/** Collapse the chat back to the list alone (the panel's minimum). */
|
|
23
|
+
onCollapse?: () => void;
|
|
24
|
+
/** Close the panel; only a panel that can close passes it. */
|
|
25
|
+
onClose?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Top bar of the Mingo v2 chat (Figma `chat-top-navigation`): the chat list
|
|
30
|
+
* toggle, the title over its owner with the owner's avatar, then the chat's ⋯
|
|
31
|
+
* menu and the collapse arrow, split by vertical dividers. It sits over the
|
|
32
|
+
* chat column only; the list beside it has no bar.
|
|
33
|
+
*/
|
|
34
|
+
export function MingoChatHeader({
|
|
35
|
+
title,
|
|
36
|
+
subtitle,
|
|
37
|
+
avatar,
|
|
38
|
+
onToggleList,
|
|
39
|
+
listOpen = false,
|
|
40
|
+
menuItems = [],
|
|
41
|
+
onRestore,
|
|
42
|
+
onCollapse,
|
|
43
|
+
onClose,
|
|
44
|
+
}: MingoChatHeaderProps) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="flex h-14 w-full shrink-0 overflow-hidden border-b border-ods-border">
|
|
47
|
+
{onToggleList && (
|
|
48
|
+
<ChatHeaderIconButton
|
|
49
|
+
divider="right"
|
|
50
|
+
onClick={onToggleList}
|
|
51
|
+
aria-label={listOpen ? 'Hide chat list' : 'Show chat list'}
|
|
52
|
+
aria-pressed={listOpen}
|
|
53
|
+
>
|
|
54
|
+
<SidebarIcon size={24} />
|
|
55
|
+
</ChatHeaderIconButton>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
<div className="flex min-w-0 flex-1 items-center gap-[var(--spacing-system-m)] px-[var(--spacing-system-m)] py-[var(--spacing-system-sf)]">
|
|
59
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
60
|
+
<p className="truncate text-ods-text-primary text-h3">{title}</p>
|
|
61
|
+
{subtitle && <p className="truncate text-ods-text-secondary text-h6">{subtitle}</p>}
|
|
62
|
+
</div>
|
|
63
|
+
{avatar && (avatar.name || avatar.avatarUrl) ? (
|
|
64
|
+
<SquareAvatar
|
|
65
|
+
variant="round"
|
|
66
|
+
sizePx={32}
|
|
67
|
+
src={avatar.avatarUrl || undefined}
|
|
68
|
+
alt={avatar.name || undefined}
|
|
69
|
+
fallback={avatar.name || undefined}
|
|
70
|
+
title={avatar.name || undefined}
|
|
71
|
+
/>
|
|
72
|
+
) : null}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{onRestore && (
|
|
76
|
+
<ChatHeaderIconButton onClick={onRestore} aria-label="Unarchive chat">
|
|
77
|
+
<Refresh01LeftIcon size={24} />
|
|
78
|
+
</ChatHeaderIconButton>
|
|
79
|
+
)}
|
|
80
|
+
{menuItems.length > 0 && (
|
|
81
|
+
<ActionsMenuDropdown
|
|
82
|
+
triggerAriaLabel="Chat actions"
|
|
83
|
+
onCloseAutoFocus={e => e.preventDefault()}
|
|
84
|
+
groups={[{ items: menuItems }]}
|
|
85
|
+
customTrigger={
|
|
86
|
+
<ChatHeaderIconButton aria-label="Chat actions">
|
|
87
|
+
<Ellipsis01Icon size={24} />
|
|
88
|
+
</ChatHeaderIconButton>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
{onCollapse && (
|
|
93
|
+
<ChatHeaderIconButton onClick={onCollapse} aria-label="Collapse chat">
|
|
94
|
+
<Arrow02RightIcon size={24} />
|
|
95
|
+
</ChatHeaderIconButton>
|
|
96
|
+
)}
|
|
97
|
+
{onClose && (
|
|
98
|
+
<ChatHeaderIconButton onClick={onClose} aria-label="Close">
|
|
99
|
+
<XmarkIcon size={24} />
|
|
100
|
+
</ChatHeaderIconButton>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import {
|
|
6
|
+
AlertCircleIcon,
|
|
7
|
+
ChatPlusIcon,
|
|
8
|
+
ChatQuestionIcon,
|
|
9
|
+
ChatsIcon,
|
|
10
|
+
ClockHistoryIcon,
|
|
11
|
+
DotsLoaderIcon,
|
|
12
|
+
Ellipsis01Icon,
|
|
13
|
+
MessageIndicatorIcon,
|
|
14
|
+
Refresh01RightIcon,
|
|
15
|
+
SearchIcon,
|
|
16
|
+
ShieldCheckIcon,
|
|
17
|
+
UserIcon,
|
|
18
|
+
UsersIcon,
|
|
19
|
+
} from '../icons-v2-generated';
|
|
20
|
+
import { MingoIcon } from '../icons/mingo-icon';
|
|
21
|
+
import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu';
|
|
22
|
+
import { Button } from '../ui/button';
|
|
23
|
+
import { chatDialogMenuItems } from './chat-dialog-menu-items';
|
|
24
|
+
import { ChatHeaderSearchField } from './chat-header-search-field';
|
|
25
|
+
import { ChatListEmptyState } from './chat-list-empty-state';
|
|
26
|
+
import { ChatListItem, ChatNavSidebar, type ChatNavSidebarItem } from './chat-nav-sidebar';
|
|
27
|
+
import type { MingoHistoryScope } from './mingo-history-rail';
|
|
28
|
+
import type { DialogItem } from './types/component.types';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What a chat is doing, shown at the end of its row:
|
|
32
|
+
* - `working`: Mingo is answering in it
|
|
33
|
+
* - `unread`: a reply the user has not seen
|
|
34
|
+
* - `approval`: Mingo waits for an approval there
|
|
35
|
+
*/
|
|
36
|
+
export type MingoDialogStatus = 'working' | 'unread' | 'approval';
|
|
37
|
+
|
|
38
|
+
export interface MingoChatRailProps {
|
|
39
|
+
/** Dialogs to list, newest first. */
|
|
40
|
+
dialogs: ReadonlyArray<DialogItem>;
|
|
41
|
+
activeDialogId?: string;
|
|
42
|
+
onSelectDialog?: (id: string) => void;
|
|
43
|
+
/** A chat being started and not saved yet: a selected row at the top
|
|
44
|
+
* (e.g. "New Chat"), so the list says what the chat beside it is. */
|
|
45
|
+
draftTitle?: string;
|
|
46
|
+
onNewChat?: () => void;
|
|
47
|
+
/** Open the archive. `archiveActive` marks it as the view on screen. */
|
|
48
|
+
onOpenArchive?: () => void;
|
|
49
|
+
archiveActive?: boolean;
|
|
50
|
+
/** "What to ask Mingo?": the suggestions a new chat starts from. */
|
|
51
|
+
onWhatToAsk?: () => void;
|
|
52
|
+
/** A row's status glyph. Unread replies are shown without it. */
|
|
53
|
+
statusOf?: (dialog: DialogItem) => MingoDialogStatus | undefined;
|
|
54
|
+
onRequestRename?: (dialog: DialogItem) => void;
|
|
55
|
+
onRequestArchive?: (dialog: DialogItem) => void;
|
|
56
|
+
onRequestCopyLink?: (dialog: DialogItem) => void;
|
|
57
|
+
onRequestCompact?: (dialog: DialogItem) => void;
|
|
58
|
+
/** "Show My Chats" / "Show All Chats" in the list's ⋯ menu; both or neither. */
|
|
59
|
+
scope?: MingoHistoryScope;
|
|
60
|
+
onScopeChange?: (scope: MingoHistoryScope) => void;
|
|
61
|
+
/** Server-side search. With a handler the ⋯ menu offers "Search for Chat". */
|
|
62
|
+
searchQuery?: string;
|
|
63
|
+
onSearchChange?: (query: string) => void;
|
|
64
|
+
hasMore?: boolean;
|
|
65
|
+
isLoadingMore?: boolean;
|
|
66
|
+
onLoadMore?: () => void;
|
|
67
|
+
/** The first page is still loading. */
|
|
68
|
+
isLoadingHistory?: boolean;
|
|
69
|
+
/** The list failed to load with nothing cached. */
|
|
70
|
+
loadError?: boolean;
|
|
71
|
+
onRetry?: () => void;
|
|
72
|
+
className?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const STATUS_GLYPHS: Record<MingoDialogStatus, { icon: ReactNode; label: string }> = {
|
|
76
|
+
working: { icon: <DotsLoaderIcon className="text-ods-text-secondary" />, label: 'Mingo is working' },
|
|
77
|
+
unread: { icon: <MessageIndicatorIcon className="text-ods-accent" />, label: 'New reply' },
|
|
78
|
+
approval: { icon: <ShieldCheckIcon className="text-ods-accent" />, label: 'Waiting for approval' },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function statusFor(dialog: DialogItem, statusOf: MingoChatRailProps['statusOf']): MingoDialogStatus | undefined {
|
|
82
|
+
return statusOf?.(dialog) ?? ((dialog.unreadMessagesCount ?? 0) > 0 ? 'unread' : undefined);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The Mingo chat list of the v2 layout (Figma `chat-sidebar`, type
|
|
87
|
+
* mingo-my-chats): the Mingo mark, the three actions, a "Your Current Chats"
|
|
88
|
+
* list with a ⋯ menu that scopes or searches it, and one row per chat with its
|
|
89
|
+
* status at the end. Row actions (rename, archive, ...) sit behind a ⋯ that
|
|
90
|
+
* replaces the status on hover.
|
|
91
|
+
*/
|
|
92
|
+
export function MingoChatRail({
|
|
93
|
+
dialogs,
|
|
94
|
+
activeDialogId,
|
|
95
|
+
onSelectDialog,
|
|
96
|
+
draftTitle,
|
|
97
|
+
onNewChat,
|
|
98
|
+
onOpenArchive,
|
|
99
|
+
archiveActive = false,
|
|
100
|
+
onWhatToAsk,
|
|
101
|
+
statusOf,
|
|
102
|
+
onRequestRename,
|
|
103
|
+
onRequestArchive,
|
|
104
|
+
onRequestCopyLink,
|
|
105
|
+
onRequestCompact,
|
|
106
|
+
scope,
|
|
107
|
+
onScopeChange,
|
|
108
|
+
searchQuery,
|
|
109
|
+
onSearchChange,
|
|
110
|
+
hasMore = false,
|
|
111
|
+
isLoadingMore = false,
|
|
112
|
+
onLoadMore,
|
|
113
|
+
isLoadingHistory = false,
|
|
114
|
+
loadError = false,
|
|
115
|
+
onRetry,
|
|
116
|
+
className,
|
|
117
|
+
}: MingoChatRailProps) {
|
|
118
|
+
const [searchOpen, setSearchOpen] = useState(!!searchQuery?.trim());
|
|
119
|
+
const hasSearch = !!searchQuery?.trim();
|
|
120
|
+
|
|
121
|
+
const items: ChatNavSidebarItem[] = [];
|
|
122
|
+
if (onNewChat) items.push({ id: 'new', label: 'Start New Chat', icon: <ChatPlusIcon />, onClick: onNewChat });
|
|
123
|
+
if (onOpenArchive) {
|
|
124
|
+
items.push({
|
|
125
|
+
id: 'archive',
|
|
126
|
+
label: 'Chat Archive',
|
|
127
|
+
icon: <ClockHistoryIcon />,
|
|
128
|
+
active: archiveActive,
|
|
129
|
+
onClick: onOpenArchive,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (onWhatToAsk)
|
|
133
|
+
items.push({ id: 'ask', label: 'What to ask Mingo?', icon: <ChatQuestionIcon />, onClick: onWhatToAsk });
|
|
134
|
+
|
|
135
|
+
const listMenu: ActionsMenuItem[] = [];
|
|
136
|
+
if (scope && onScopeChange) {
|
|
137
|
+
listMenu.push(
|
|
138
|
+
{
|
|
139
|
+
id: 'my',
|
|
140
|
+
label: 'Show My Chats',
|
|
141
|
+
icon: <UserIcon className="h-full w-full text-ods-text-secondary" />,
|
|
142
|
+
type: 'checkbox',
|
|
143
|
+
checked: scope === 'my',
|
|
144
|
+
onClick: () => onScopeChange('my'),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'all',
|
|
148
|
+
label: 'Show All Chats',
|
|
149
|
+
icon: <UsersIcon className="h-full w-full text-ods-text-secondary" />,
|
|
150
|
+
type: 'checkbox',
|
|
151
|
+
checked: scope === 'all',
|
|
152
|
+
onClick: () => onScopeChange('all'),
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (onSearchChange) {
|
|
157
|
+
if (listMenu.length > 0) listMenu.push({ id: 'divider', label: '', type: 'separator' });
|
|
158
|
+
listMenu.push({
|
|
159
|
+
id: 'search',
|
|
160
|
+
label: 'Search for Chat',
|
|
161
|
+
icon: <SearchIcon className="h-full w-full text-ods-text-secondary" />,
|
|
162
|
+
onClick: () => setSearchOpen(true),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Infinite scroll: the next page when the sentinel at the list's end shows.
|
|
167
|
+
// The observer clips against the scrolling list, so the viewport root works.
|
|
168
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
169
|
+
const onLoadMoreRef = useRef(onLoadMore);
|
|
170
|
+
const isLoadingMoreRef = useRef(isLoadingMore);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
onLoadMoreRef.current = onLoadMore;
|
|
173
|
+
isLoadingMoreRef.current = isLoadingMore;
|
|
174
|
+
});
|
|
175
|
+
const dialogCount = dialogs.length;
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
const sentinel = sentinelRef.current;
|
|
178
|
+
if (!sentinel || !hasMore || typeof IntersectionObserver === 'undefined') return undefined;
|
|
179
|
+
const observer = new IntersectionObserver(
|
|
180
|
+
([entry]) => {
|
|
181
|
+
if (entry?.isIntersecting && !isLoadingMoreRef.current) onLoadMoreRef.current?.();
|
|
182
|
+
},
|
|
183
|
+
{ rootMargin: '120px', threshold: 0.1 },
|
|
184
|
+
);
|
|
185
|
+
observer.observe(sentinel);
|
|
186
|
+
return () => observer.disconnect();
|
|
187
|
+
// Re-armed per page: a sentinel still in view after a short page keeps loading.
|
|
188
|
+
}, [hasMore, dialogCount, isLoadingMore]);
|
|
189
|
+
|
|
190
|
+
let list: ReactNode;
|
|
191
|
+
if (loadError) {
|
|
192
|
+
list = (
|
|
193
|
+
<div className="flex flex-col items-center gap-[var(--spacing-system-m)] px-[var(--spacing-system-xs)] py-[var(--spacing-system-l)] text-center">
|
|
194
|
+
<AlertCircleIcon className="size-8 shrink-0 text-ods-text-secondary" />
|
|
195
|
+
<div className="flex flex-col gap-[var(--spacing-system-xxs)]">
|
|
196
|
+
<p className="text-ods-text-primary text-h4">Couldn’t load your chats</p>
|
|
197
|
+
<p className="text-ods-text-secondary text-h6">Something went wrong reaching the server. Try again.</p>
|
|
198
|
+
</div>
|
|
199
|
+
{onRetry && (
|
|
200
|
+
<Button variant="outline" size="small" leftIcon={<Refresh01RightIcon />} onClick={onRetry}>
|
|
201
|
+
Try again
|
|
202
|
+
</Button>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
} else if (isLoadingHistory) {
|
|
207
|
+
list = Array.from({ length: 6 }, (_, i) => (
|
|
208
|
+
<div key={i} className="h-10 shrink-0 animate-pulse rounded-md bg-ods-skeleton" aria-hidden />
|
|
209
|
+
));
|
|
210
|
+
} else if (dialogs.length === 0 && !draftTitle) {
|
|
211
|
+
list = hasSearch ? (
|
|
212
|
+
<p className="px-[var(--spacing-system-xs)] py-[var(--spacing-system-s)] text-ods-text-secondary text-h6">
|
|
213
|
+
No chats found
|
|
214
|
+
</p>
|
|
215
|
+
) : (
|
|
216
|
+
<ChatListEmptyState
|
|
217
|
+
icon={<ChatsIcon size={24} />}
|
|
218
|
+
title="No Current Chats"
|
|
219
|
+
description="Previous Mingo sessions will show here"
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
list = (
|
|
224
|
+
<>
|
|
225
|
+
{draftTitle && <ChatListItem active title={draftTitle} tabIndex={-1} className="cursor-default" />}
|
|
226
|
+
{dialogs.map(dialog => (
|
|
227
|
+
<MingoChatRailRow
|
|
228
|
+
key={dialog.id}
|
|
229
|
+
dialog={dialog}
|
|
230
|
+
active={dialog.id === activeDialogId}
|
|
231
|
+
status={statusFor(dialog, statusOf)}
|
|
232
|
+
onSelect={onSelectDialog}
|
|
233
|
+
menuItems={chatDialogMenuItems({
|
|
234
|
+
onCopyLink: onRequestCopyLink && (() => onRequestCopyLink(dialog)),
|
|
235
|
+
onRename: onRequestRename && (() => onRequestRename(dialog)),
|
|
236
|
+
onCompact: onRequestCompact && (() => onRequestCompact(dialog)),
|
|
237
|
+
onArchive: onRequestArchive && (() => onRequestArchive(dialog)),
|
|
238
|
+
})}
|
|
239
|
+
/>
|
|
240
|
+
))}
|
|
241
|
+
{hasMore && <div ref={sentinelRef} className="h-px shrink-0" aria-hidden />}
|
|
242
|
+
</>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<ChatNavSidebar
|
|
248
|
+
className={className}
|
|
249
|
+
// Mingo's cyan for the selection and the status glyphs, whatever the
|
|
250
|
+
// tenant accent the rest of the panel carries.
|
|
251
|
+
data-accent="mingo"
|
|
252
|
+
aria-label="Mingo chats"
|
|
253
|
+
logo={
|
|
254
|
+
<MingoIcon
|
|
255
|
+
color="currentColor"
|
|
256
|
+
eyesColor="var(--ods-flamingo-cyan-base)"
|
|
257
|
+
cornerColor="var(--ods-flamingo-cyan-base)"
|
|
258
|
+
className="size-6 text-ods-text-primary"
|
|
259
|
+
aria-hidden
|
|
260
|
+
/>
|
|
261
|
+
}
|
|
262
|
+
items={items}
|
|
263
|
+
sectionLabel="Your Current Chats"
|
|
264
|
+
sectionActions={
|
|
265
|
+
listMenu.length > 0 ? (
|
|
266
|
+
<ActionsMenuDropdown
|
|
267
|
+
triggerAriaLabel="Chat list options"
|
|
268
|
+
groups={[{ items: listMenu }]}
|
|
269
|
+
onCloseAutoFocus={e => e.preventDefault()}
|
|
270
|
+
customTrigger={
|
|
271
|
+
<Button
|
|
272
|
+
variant="transparent"
|
|
273
|
+
size="icon"
|
|
274
|
+
aria-label="Chat list options"
|
|
275
|
+
className="size-6 p-0 text-ods-text-secondary hover:text-ods-text-primary md:size-6"
|
|
276
|
+
>
|
|
277
|
+
<Ellipsis01Icon size={24} />
|
|
278
|
+
</Button>
|
|
279
|
+
}
|
|
280
|
+
/>
|
|
281
|
+
) : undefined
|
|
282
|
+
}
|
|
283
|
+
listHeader={
|
|
284
|
+
searchOpen && onSearchChange ? (
|
|
285
|
+
<ChatHeaderSearchField
|
|
286
|
+
initialValue={searchQuery}
|
|
287
|
+
onSearchChange={onSearchChange}
|
|
288
|
+
onCollapse={() => setSearchOpen(false)}
|
|
289
|
+
className="h-10 rounded-md border border-ods-border bg-ods-card px-[var(--spacing-system-xs)]"
|
|
290
|
+
/>
|
|
291
|
+
) : undefined
|
|
292
|
+
}
|
|
293
|
+
>
|
|
294
|
+
{list}
|
|
295
|
+
</ChatNavSidebar>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
interface MingoChatRailRowProps {
|
|
300
|
+
dialog: DialogItem;
|
|
301
|
+
active: boolean;
|
|
302
|
+
status?: MingoDialogStatus;
|
|
303
|
+
onSelect?: (id: string) => void;
|
|
304
|
+
menuItems: ActionsMenuItem[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function MingoChatRailRow({ dialog, active, status, onSelect, menuItems }: MingoChatRailRowProps) {
|
|
308
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
309
|
+
const hasMenu = menuItems.length > 0;
|
|
310
|
+
const glyph = status ? STATUS_GLYPHS[status] : undefined;
|
|
311
|
+
const title = dialog.title || 'Untitled Chat';
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div className="group/row relative shrink-0">
|
|
315
|
+
<ChatListItem
|
|
316
|
+
active={active}
|
|
317
|
+
title={title}
|
|
318
|
+
onClick={() => onSelect?.(dialog.id)}
|
|
319
|
+
className={cn(hasMenu && 'pr-10')}
|
|
320
|
+
trailing={
|
|
321
|
+
glyph ? (
|
|
322
|
+
<span
|
|
323
|
+
role="img"
|
|
324
|
+
aria-label={glyph.label}
|
|
325
|
+
className={cn('flex transition-opacity', hasMenu && 'group-hover/row:opacity-0', menuOpen && 'opacity-0')}
|
|
326
|
+
>
|
|
327
|
+
{glyph.icon}
|
|
328
|
+
</span>
|
|
329
|
+
) : undefined
|
|
330
|
+
}
|
|
331
|
+
/>
|
|
332
|
+
{hasMenu && (
|
|
333
|
+
// Revealed on hover or its own focus, and kept while its menu is open.
|
|
334
|
+
<span
|
|
335
|
+
className={cn(
|
|
336
|
+
'absolute inset-y-0 right-[var(--spacing-system-xs)] flex items-center transition-opacity',
|
|
337
|
+
menuOpen ? 'opacity-100' : 'opacity-0 focus-within:opacity-100 group-hover/row:opacity-100',
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
<ActionsMenuDropdown
|
|
341
|
+
triggerAriaLabel={`Actions for ${title}`}
|
|
342
|
+
groups={[{ items: menuItems }]}
|
|
343
|
+
open={menuOpen}
|
|
344
|
+
onOpenChange={setMenuOpen}
|
|
345
|
+
onCloseAutoFocus={e => e.preventDefault()}
|
|
346
|
+
customTrigger={
|
|
347
|
+
<Button
|
|
348
|
+
variant="transparent"
|
|
349
|
+
size="icon"
|
|
350
|
+
aria-label={`Actions for ${title}`}
|
|
351
|
+
className="size-6 p-0 text-ods-text-secondary hover:text-ods-text-primary md:size-6"
|
|
352
|
+
>
|
|
353
|
+
<Ellipsis01Icon size={24} />
|
|
354
|
+
</Button>
|
|
355
|
+
}
|
|
356
|
+
/>
|
|
357
|
+
</span>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
@@ -5,7 +5,9 @@ import { cn } from '../../utils/cn';
|
|
|
5
5
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '../hover-card';
|
|
6
6
|
import { AnthropicLogoGreyIcon, GeminiLogoGreyIcon, OpenaiLogoGreyIcon } from '../icons-v2-generated';
|
|
7
7
|
import { Skeleton } from '../ui/skeleton';
|
|
8
|
+
import { useChatAppearance } from './chat-appearance-context';
|
|
8
9
|
import type { ModelDisplayProps, ModelUsageBreakdown } from './types';
|
|
10
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
9
11
|
|
|
10
12
|
const getProviderIcon = (provider?: string) => {
|
|
11
13
|
if (!provider) return null;
|
|
@@ -66,6 +68,9 @@ const ModelDisplay = React.forwardRef<HTMLDivElement, ModelDisplayProps>(
|
|
|
66
68
|
) => {
|
|
67
69
|
const icon = getProviderIcon(provider);
|
|
68
70
|
const name = displayName || modelName;
|
|
71
|
+
// v2 spells the counts out ("372 / 50,000 tokens used"); classic keeps them short.
|
|
72
|
+
const isV2 = useChatAppearance() === CHAT_APPEARANCE.V2;
|
|
73
|
+
const formatCount = (count: number) => (isV2 ? count.toLocaleString('en-US') : formatTokenCount(count));
|
|
69
74
|
|
|
70
75
|
// Inline pill: provider icon + model name + "X / Y tokens used".
|
|
71
76
|
// The "tokens used" tail renders as soon as `contextWindow` is known
|
|
@@ -79,7 +84,9 @@ const ModelDisplay = React.forwardRef<HTMLDivElement, ModelDisplayProps>(
|
|
|
79
84
|
<span className="font-body">{name}</span>
|
|
80
85
|
{contextWindow != null && (
|
|
81
86
|
<span className="ml-auto opacity-70 text-h6">
|
|
82
|
-
{usedTokens != null ?
|
|
87
|
+
{usedTokens != null ? formatCount(usedTokens) : '—'}
|
|
88
|
+
{isV2 ? ' / ' : '/'}
|
|
89
|
+
{formatCount(contextWindow)} tokens used
|
|
83
90
|
</span>
|
|
84
91
|
)}
|
|
85
92
|
</div>
|
|
@@ -262,11 +262,11 @@ export function ContactForm({
|
|
|
262
262
|
|
|
263
263
|
return (
|
|
264
264
|
<div
|
|
265
|
-
className={`flex h-full flex-col ${!noBorder ? 'rounded-2xl border border-ods-border md:rounded-3xl' : ''} ${!noPadding ? 'p-6 md:p-8 lg:p-10' : ''}`}
|
|
265
|
+
className={`flex h-full flex-col ${!noBorder ? 'rounded-2xl border border-ods-border content-md:rounded-3xl' : ''} ${!noPadding ? 'p-6 content-md:p-8 content-lg:p-10' : ''}`}
|
|
266
266
|
>
|
|
267
267
|
{(title || subtitle) && (
|
|
268
|
-
<div className="mb-6 md:mb-8">
|
|
269
|
-
{title && <h2 className={`${SECTION_HEADING_CLASS} mb-3 md:mb-4`}>{accentSentenceMarks(title)}</h2>}
|
|
268
|
+
<div className="mb-6 content-md:mb-8">
|
|
269
|
+
{title && <h2 className={`${SECTION_HEADING_CLASS} mb-3 content-md:mb-4`}>{accentSentenceMarks(title)}</h2>}
|
|
270
270
|
{subtitle && <p className="text-ods-text-primary text-h4">{subtitle}</p>}
|
|
271
271
|
</div>
|
|
272
272
|
)}
|
|
@@ -283,7 +283,7 @@ export function ContactForm({
|
|
|
283
283
|
Object.fromEntries(Object.entries(validationErrors).map(([k, v]) => [k, v?.message ?? v])),
|
|
284
284
|
);
|
|
285
285
|
})}
|
|
286
|
-
className="flex flex-grow flex-col space-y-4 md:space-y-6"
|
|
286
|
+
className="flex flex-grow flex-col space-y-4 content-md:space-y-6"
|
|
287
287
|
{...HUBSPOT_DO_NOT_COLLECT_FORM_PROPS}
|
|
288
288
|
>
|
|
289
289
|
{/* Hidden inputs for fields that are required by `ContactSchema`
|
|
@@ -308,7 +308,7 @@ export function ContactForm({
|
|
|
308
308
|
{extraTopField}
|
|
309
309
|
|
|
310
310
|
{showNameEmailRow && (
|
|
311
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
|
|
311
|
+
<div className="grid grid-cols-1 gap-4 content-md:grid-cols-2 content-md:gap-6">
|
|
312
312
|
{showName && (
|
|
313
313
|
<div className="flex flex-col">
|
|
314
314
|
<Label htmlFor="name">
|
|
@@ -359,7 +359,7 @@ export function ContactForm({
|
|
|
359
359
|
)}
|
|
360
360
|
|
|
361
361
|
{(showCompanySize || showReferralSource) && (
|
|
362
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6">
|
|
362
|
+
<div className="grid grid-cols-1 gap-4 content-md:grid-cols-2 content-md:gap-6">
|
|
363
363
|
{showCompanySize && (
|
|
364
364
|
<div className="flex flex-col">
|
|
365
365
|
<Label htmlFor="companySize">Company Size</Label>
|
|
@@ -511,14 +511,16 @@ export function ContactForm({
|
|
|
511
511
|
</div>
|
|
512
512
|
)}
|
|
513
513
|
|
|
514
|
-
<div className="mt-auto flex w-full flex-col items-center justify-end gap-4 pt-2 md:flex-row md:gap-6">
|
|
515
|
-
{footerText &&
|
|
514
|
+
<div className="mt-auto flex w-full flex-col items-center justify-end gap-4 pt-2 content-md:flex-row content-md:gap-6">
|
|
515
|
+
{footerText && (
|
|
516
|
+
<p className="text-center text-ods-text-secondary text-h6 content-md:text-left">{footerText}</p>
|
|
517
|
+
)}
|
|
516
518
|
<Button
|
|
517
519
|
type="submit"
|
|
518
520
|
loading={isSubmitting}
|
|
519
521
|
disabled={isSubmitting || isSuccess || (attachmentsEnabled && attachments.hasInflightUploads)}
|
|
520
522
|
variant={buttonVariant}
|
|
521
|
-
className={`w-full md:w-auto ${buttonClassName}`}
|
|
523
|
+
className={`w-full content-md:w-auto ${buttonClassName}`}
|
|
522
524
|
>
|
|
523
525
|
{isSuccess ? submitSuccessLabel : submitLabel}
|
|
524
526
|
</Button>
|