@flamingo-stack/openframe-frontend-core 0.0.284 → 0.0.285-snapshot.20260618135145
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-3MCHAFHB.js +89 -0
- package/dist/chunk-3MCHAFHB.js.map +1 -0
- package/dist/{chunk-IMNGGLKL.js → chunk-3ZXUQQL4.js} +2 -2
- package/dist/{chunk-BRLGG3EZ.cjs → chunk-4WACBTZU.cjs} +30 -30
- package/dist/{chunk-BRLGG3EZ.cjs.map → chunk-4WACBTZU.cjs.map} +1 -1
- package/dist/{chunk-P4ARMFW3.js → chunk-5BTZOVDQ.js} +4 -4
- package/dist/chunk-5KD3S25X.cjs +312 -0
- package/dist/chunk-5KD3S25X.cjs.map +1 -0
- package/dist/chunk-6N26CURS.cjs +2417 -0
- package/dist/chunk-6N26CURS.cjs.map +1 -0
- package/dist/{chunk-XX7P4IL2.js → chunk-7NM7DEUK.js} +4 -143
- package/dist/chunk-7NM7DEUK.js.map +1 -0
- package/dist/chunk-BBZ7AX5H.cjs +290 -0
- package/dist/chunk-BBZ7AX5H.cjs.map +1 -0
- package/dist/chunk-C6SCWXDP.js +290 -0
- package/dist/chunk-C6SCWXDP.js.map +1 -0
- package/dist/{chunk-GVSTRN36.cjs → chunk-D3LEFMOA.cjs} +3 -3
- package/dist/{chunk-GVSTRN36.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
- package/dist/{chunk-3SM6DA24.cjs → chunk-D5YY5U6J.cjs} +19 -15
- package/dist/chunk-D5YY5U6J.cjs.map +1 -0
- package/dist/chunk-DDAT4RKX.js +2417 -0
- package/dist/chunk-DDAT4RKX.js.map +1 -0
- package/dist/{chunk-PVXCVPJD.cjs → chunk-EI4WALN2.cjs} +67 -1626
- package/dist/chunk-EI4WALN2.cjs.map +1 -0
- package/dist/{chunk-ZIODAAFY.cjs → chunk-EL5GCMPU.cjs} +20 -159
- package/dist/chunk-EL5GCMPU.cjs.map +1 -0
- package/dist/{chunk-RGXDDOQR.js → chunk-FSBDVT6R.js} +655 -2419
- package/dist/chunk-FSBDVT6R.js.map +1 -0
- package/dist/{chunk-24L4FLRC.cjs → chunk-GDF2R2ER.cjs} +1079 -2843
- package/dist/chunk-GDF2R2ER.cjs.map +1 -0
- package/dist/chunk-HLQW7MWJ.cjs +1594 -0
- package/dist/chunk-HLQW7MWJ.cjs.map +1 -0
- package/dist/chunk-I6ZPGKZ2.cjs +151 -0
- package/dist/chunk-I6ZPGKZ2.cjs.map +1 -0
- package/dist/{chunk-J66U3LPZ.cjs → chunk-KBKZYJRI.cjs} +33 -332
- package/dist/chunk-KBKZYJRI.cjs.map +1 -0
- package/dist/{chunk-USPATRSC.js → chunk-N6ZM5PYZ.js} +45 -344
- package/dist/chunk-N6ZM5PYZ.js.map +1 -0
- package/dist/{chunk-RGALV4UX.js → chunk-POKKCWKF.js} +3 -82
- package/dist/chunk-POKKCWKF.js.map +1 -0
- package/dist/{chunk-XAS7JVHX.cjs → chunk-QCKN37OP.cjs} +45 -41
- package/dist/chunk-QCKN37OP.cjs.map +1 -0
- package/dist/{chunk-VKDJXB7L.js → chunk-QF2X6PTD.js} +10 -6
- package/dist/{chunk-VKDJXB7L.js.map → chunk-QF2X6PTD.js.map} +1 -1
- package/dist/{chunk-Z3V2SGQZ.js → chunk-R2KT5GDD.js} +324 -1883
- package/dist/chunk-R2KT5GDD.js.map +1 -0
- package/dist/{chunk-HX7ZGKQ4.cjs → chunk-RQ6RTBKF.cjs} +16 -12
- package/dist/chunk-RQ6RTBKF.cjs.map +1 -0
- package/dist/chunk-TFSYSWPS.cjs +89 -0
- package/dist/chunk-TFSYSWPS.cjs.map +1 -0
- package/dist/chunk-TYZEMPPH.js +312 -0
- package/dist/chunk-TYZEMPPH.js.map +1 -0
- package/dist/{chunk-33RLTUN2.js → chunk-VCQ3CTYK.js} +12 -8
- package/dist/{chunk-33RLTUN2.js.map → chunk-VCQ3CTYK.js.map} +1 -1
- package/dist/chunk-VRSXJ5QJ.js +1599 -0
- package/dist/chunk-VRSXJ5QJ.js.map +1 -0
- package/dist/{chunk-OUI2MBU7.js → chunk-XQL4WDML.js} +11 -7
- package/dist/{chunk-OUI2MBU7.js.map → chunk-XQL4WDML.js.map} +1 -1
- package/dist/chunk-Y4JNA4W6.cjs +1599 -0
- package/dist/chunk-Y4JNA4W6.cjs.map +1 -0
- package/dist/{chunk-ZSUNQJRO.cjs → chunk-YETA25JW.cjs} +2 -81
- package/dist/chunk-YETA25JW.cjs.map +1 -0
- package/dist/chunk-YVB3VDIQ.js +1594 -0
- package/dist/chunk-YVB3VDIQ.js.map +1 -0
- package/dist/chunk-ZLN6SM2U.js +151 -0
- package/dist/chunk-ZLN6SM2U.js.map +1 -0
- package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +12 -6
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +45 -39
- package/dist/components/contact/index.cjs +11 -7
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +10 -6
- package/dist/components/docs/doc-navigation-context.d.ts +39 -0
- package/dist/components/docs/doc-navigation-context.d.ts.map +1 -0
- package/dist/components/docs/doc-viewer.d.ts +66 -0
- package/dist/components/docs/doc-viewer.d.ts.map +1 -0
- package/dist/components/docs/index.cjs +34 -0
- package/dist/components/docs/index.cjs.map +1 -0
- package/dist/components/docs/index.d.ts +8 -0
- package/dist/components/docs/index.d.ts.map +1 -0
- package/dist/components/docs/index.js +34 -0
- package/dist/components/docs/index.js.map +1 -0
- package/dist/components/docs/use-document-tree.d.ts +41 -0
- package/dist/components/docs/use-document-tree.d.ts.map +1 -0
- package/dist/components/docs/use-scroll-spy.d.ts +16 -0
- package/dist/components/docs/use-scroll-spy.d.ts.map +1 -0
- package/dist/components/embeds/embed-iframe.d.ts +29 -0
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -0
- package/dist/components/embeds/figma-embed.d.ts +27 -0
- package/dist/components/embeds/figma-embed.d.ts.map +1 -0
- package/dist/components/embeds/google-sheets-viewer.d.ts +8 -0
- package/dist/components/embeds/google-sheets-viewer.d.ts.map +1 -0
- package/dist/components/embeds/index.cjs +37 -0
- package/dist/components/embeds/index.cjs.map +1 -0
- package/dist/components/embeds/index.d.ts +9 -0
- package/dist/components/embeds/index.d.ts.map +1 -0
- package/dist/components/embeds/index.js +37 -0
- package/dist/components/embeds/index.js.map +1 -0
- package/dist/components/embeds/pdf-viewer.d.ts +10 -0
- package/dist/components/embeds/pdf-viewer.d.ts.map +1 -0
- package/dist/components/faq/index.cjs +12 -8
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +11 -7
- package/dist/components/features/board/board-column.d.ts +3 -1
- package/dist/components/features/board/board-column.d.ts.map +1 -1
- package/dist/components/features/board/board-ticket-approval.d.ts +16 -0
- package/dist/components/features/board/board-ticket-approval.d.ts.map +1 -0
- package/dist/components/features/board/board.d.ts +3 -1
- package/dist/components/features/board/board.d.ts.map +1 -1
- package/dist/components/features/board/index.d.ts +3 -1
- package/dist/components/features/board/index.d.ts.map +1 -1
- package/dist/components/features/board/ticket-card.d.ts +3 -1
- package/dist/components/features/board/ticket-card.d.ts.map +1 -1
- package/dist/components/features/board/types.d.ts +11 -0
- package/dist/components/features/board/types.d.ts.map +1 -1
- package/dist/components/features/index.cjs +12 -6
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +11 -5
- package/dist/components/index.cjs +249 -1120
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +647 -1518
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +15 -6
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +15 -6
- package/dist/components/navigation/multi-level-navigation.d.ts +28 -0
- package/dist/components/navigation/multi-level-navigation.d.ts.map +1 -0
- package/dist/components/onboarding-guides/index.cjs +34 -29
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +13 -8
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/related-content/index.cjs +12 -8
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +11 -7
- package/dist/components/tickets/index.cjs +75 -70
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +16 -11
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +12 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +65 -59
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +8 -8
- package/dist/hooks/index.cjs +6 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +7 -4
- package/dist/index.cjs +64 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +259 -201
- package/dist/types/doc-source.cjs +4 -0
- package/dist/types/doc-source.cjs.map +1 -0
- package/dist/types/doc-source.d.ts +86 -0
- package/dist/types/doc-source.d.ts.map +1 -0
- package/dist/types/doc-source.js +3 -0
- package/dist/types/doc-source.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/doc-path-utils.d.ts +24 -0
- package/dist/utils/doc-path-utils.d.ts.map +1 -0
- package/dist/utils/doc-tree-nav.d.ts +35 -0
- package/dist/utils/doc-tree-nav.d.ts.map +1 -0
- package/dist/utils/embed-url-converters.d.ts +16 -0
- package/dist/utils/embed-url-converters.d.ts.map +1 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/index.cjs +297 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +7 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +276 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-section-extractor.d.ts +23 -0
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -0
- package/dist/utils/markdown-to-plain.d.ts +32 -0
- package/dist/utils/markdown-to-plain.d.ts.map +1 -0
- package/dist/utils/tree-builder.d.ts +30 -0
- package/dist/utils/tree-builder.d.ts.map +1 -0
- package/package.json +19 -1
- package/src/components/chat/chat-attachment-bar.tsx +6 -11
- package/src/components/docs/doc-navigation-context.tsx +123 -0
- package/src/components/docs/doc-viewer.tsx +345 -0
- package/src/components/docs/index.ts +12 -0
- package/src/components/docs/use-document-tree.ts +427 -0
- package/src/components/docs/use-scroll-spy.ts +93 -0
- package/src/components/embeds/embed-iframe.tsx +103 -0
- package/src/components/embeds/figma-embed.tsx +147 -0
- package/src/components/embeds/google-sheets-viewer.tsx +54 -0
- package/src/components/embeds/index.ts +11 -0
- package/src/components/embeds/pdf-viewer.tsx +64 -0
- package/src/components/features/board/board-column.tsx +11 -1
- package/src/components/features/board/board-ticket-approval.tsx +76 -0
- package/src/components/features/board/board.tsx +6 -0
- package/src/components/features/board/index.ts +3 -0
- package/src/components/features/board/ticket-card.tsx +12 -0
- package/src/components/features/board/types.ts +12 -0
- package/src/components/index.ts +2 -0
- package/src/components/navigation/index.ts +5 -0
- package/src/components/navigation/multi-level-navigation.tsx +358 -0
- package/src/types/doc-source.ts +88 -0
- package/src/types/index.ts +4 -0
- package/src/utils/doc-path-utils.ts +47 -0
- package/src/utils/doc-tree-nav.ts +111 -0
- package/src/utils/embed-url-converters.ts +77 -0
- package/src/utils/format.ts +16 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/markdown-section-extractor.ts +106 -0
- package/src/utils/markdown-to-plain.ts +69 -0
- package/src/utils/tree-builder.ts +114 -0
- package/dist/chunk-24L4FLRC.cjs.map +0 -1
- package/dist/chunk-3SM6DA24.cjs.map +0 -1
- package/dist/chunk-HX7ZGKQ4.cjs.map +0 -1
- package/dist/chunk-J66U3LPZ.cjs.map +0 -1
- package/dist/chunk-PVXCVPJD.cjs.map +0 -1
- package/dist/chunk-RGALV4UX.js.map +0 -1
- package/dist/chunk-RGXDDOQR.js.map +0 -1
- package/dist/chunk-USPATRSC.js.map +0 -1
- package/dist/chunk-XAS7JVHX.cjs.map +0 -1
- package/dist/chunk-XX7P4IL2.js.map +0 -1
- package/dist/chunk-Z3V2SGQZ.js.map +0 -1
- package/dist/chunk-ZIODAAFY.cjs.map +0 -1
- package/dist/chunk-ZSUNQJRO.cjs.map +0 -1
- /package/dist/{chunk-IMNGGLKL.js.map → chunk-3ZXUQQL4.js.map} +0 -0
- /package/dist/{chunk-P4ARMFW3.js.map → chunk-5BTZOVDQ.js.map} +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Button, ToggleGroup, ToggleGroupItem } from '../ui'
|
|
5
|
+
import { FigmaIcon } from '../icons-v2-generated'
|
|
6
|
+
import { ExternalLink, Play, LayoutGrid } from 'lucide-react'
|
|
7
|
+
import { toFigmaEmbedUrl, toFigmaOriginalUrl, isFigmaSlidesUrl } from '../../utils/embed-url-converters'
|
|
8
|
+
import { EmbedIframe } from './embed-iframe'
|
|
9
|
+
|
|
10
|
+
export interface FigmaEmbedProps {
|
|
11
|
+
/** Any Figma URL (design/file/proto/board/slides/deck) or an already-resolved embed URL. */
|
|
12
|
+
url: string
|
|
13
|
+
/** Heading shown above the embed. Defaults to "Figma Design". */
|
|
14
|
+
title?: string
|
|
15
|
+
/**
|
|
16
|
+
* iframe height (CSS value). The data-room document viewer omits it (full
|
|
17
|
+
* height, `calc(100vh - 250px)`); inline markdown passes e.g. `"70vh"` so the
|
|
18
|
+
* embed sits naturally inside article content.
|
|
19
|
+
*/
|
|
20
|
+
height?: string
|
|
21
|
+
/** iframe loading strategy. Defaults to `"lazy"`; the data-room viewer passes `"eager"`. */
|
|
22
|
+
loading?: 'eager' | 'lazy'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type SlidesView = 'present' | 'browse'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Two-state present/browse toggle for Figma Slides. `present` (default) uses
|
|
29
|
+
* Figma's deck viewer (full-bleed slide + `‹ n/N ›` nav bar + keyboard nav);
|
|
30
|
+
* `browse` uses the thumbnail-rail + zoom viewer.
|
|
31
|
+
*/
|
|
32
|
+
function SlidesViewToggle({
|
|
33
|
+
view,
|
|
34
|
+
onChange,
|
|
35
|
+
}: {
|
|
36
|
+
view: SlidesView
|
|
37
|
+
onChange: (v: SlidesView) => void
|
|
38
|
+
}) {
|
|
39
|
+
const options: { key: SlidesView; label: string; Icon: typeof Play }[] = [
|
|
40
|
+
{ key: 'present', label: 'Present', Icon: Play },
|
|
41
|
+
{ key: 'browse', label: 'Browse', Icon: LayoutGrid },
|
|
42
|
+
]
|
|
43
|
+
return (
|
|
44
|
+
<ToggleGroup
|
|
45
|
+
type="single"
|
|
46
|
+
value={view}
|
|
47
|
+
onValueChange={(v: string) => {
|
|
48
|
+
if (v && v !== view) onChange(v as SlidesView)
|
|
49
|
+
}}
|
|
50
|
+
aria-label="Figma slides view mode"
|
|
51
|
+
className="flex shrink-0 items-center gap-0.5 rounded-lg border border-ods-border bg-ods-card p-0.5"
|
|
52
|
+
>
|
|
53
|
+
{options.map(({ key, label, Icon }) => {
|
|
54
|
+
const active = view === key
|
|
55
|
+
return (
|
|
56
|
+
<ToggleGroupItem
|
|
57
|
+
key={key}
|
|
58
|
+
value={key}
|
|
59
|
+
aria-label={label}
|
|
60
|
+
className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-sm font-medium transition-colors ${
|
|
61
|
+
active
|
|
62
|
+
? 'bg-ods-accent text-ods-text-on-accent'
|
|
63
|
+
: 'text-ods-text-secondary hover:text-ods-text-primary hover:bg-ods-bg-hover'
|
|
64
|
+
}`}
|
|
65
|
+
>
|
|
66
|
+
<Icon className="h-4 w-4 shrink-0" />
|
|
67
|
+
{label}
|
|
68
|
+
</ToggleGroupItem>
|
|
69
|
+
)
|
|
70
|
+
})}
|
|
71
|
+
</ToggleGroup>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Single source of truth for every Figma surface — the data-room document viewer
|
|
77
|
+
* and in-article markdown both render this. A header (icon + title + "Open in
|
|
78
|
+
* Figma") over an interactive Figma iframe, built from the canonical
|
|
79
|
+
* `toFigmaEmbedUrl` / `toFigmaOriginalUrl` converters + the shared `<EmbedIframe>`.
|
|
80
|
+
* Only height/loading differ per surface.
|
|
81
|
+
*
|
|
82
|
+
* For Slides decks, a present/browse toggle (default = present) lets viewers flip
|
|
83
|
+
* slides with Figma's native nav bar + keyboard, or switch to the thumbnail-rail
|
|
84
|
+
* browse view.
|
|
85
|
+
*/
|
|
86
|
+
export function FigmaEmbed({ url, title, height, loading = 'lazy' }: FigmaEmbedProps) {
|
|
87
|
+
const [view, setView] = useState<SlidesView>('present')
|
|
88
|
+
const isSlides = url ? isFigmaSlidesUrl(url) : false
|
|
89
|
+
const embedSrc = url ? toFigmaEmbedUrl(url, { slidesView: view }) : null
|
|
90
|
+
const originalUrl = (() => {
|
|
91
|
+
if (!url) return null
|
|
92
|
+
try {
|
|
93
|
+
const parsed = new URL(toFigmaOriginalUrl(url))
|
|
94
|
+
const host = parsed.hostname.toLowerCase()
|
|
95
|
+
const okHost = host === 'figma.com' || host.endsWith('.figma.com')
|
|
96
|
+
const okProtocol = parsed.protocol === 'https:' || parsed.protocol === 'http:'
|
|
97
|
+
return okHost && okProtocol ? parsed.toString() : null
|
|
98
|
+
} catch {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
})()
|
|
102
|
+
const heading = title || 'Figma Design'
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="my-6 space-y-3">
|
|
106
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
107
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
108
|
+
<FigmaIcon className="w-5 h-5 shrink-0" />
|
|
109
|
+
<span className="font-sans text-base font-semibold text-ods-text-primary truncate">
|
|
110
|
+
{heading}
|
|
111
|
+
</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
|
114
|
+
{isSlides && embedSrc && <SlidesViewToggle view={view} onChange={setView} />}
|
|
115
|
+
{originalUrl && (
|
|
116
|
+
<Button
|
|
117
|
+
variant="outline"
|
|
118
|
+
size="small-legacy"
|
|
119
|
+
href={originalUrl}
|
|
120
|
+
openInNewTab
|
|
121
|
+
leftIcon={<FigmaIcon className="w-4 h-4" />}
|
|
122
|
+
rightIcon={<ExternalLink className="w-4 h-4" />}
|
|
123
|
+
className="w-full sm:w-auto"
|
|
124
|
+
>
|
|
125
|
+
Open in Figma
|
|
126
|
+
</Button>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
{embedSrc ? (
|
|
131
|
+
<EmbedIframe
|
|
132
|
+
src={embedSrc}
|
|
133
|
+
title={heading}
|
|
134
|
+
allow="clipboard-write; clipboard-read; fullscreen"
|
|
135
|
+
loading={loading}
|
|
136
|
+
height={height}
|
|
137
|
+
allowFullScreen
|
|
138
|
+
/>
|
|
139
|
+
) : (
|
|
140
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
141
|
+
<FigmaIcon className="w-16 h-16 text-ods-text-secondary mb-4" />
|
|
142
|
+
<p className="text-ods-text-secondary">Figma URL not configured</p>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Button } from '../ui'
|
|
5
|
+
import { ExternalLink } from 'lucide-react'
|
|
6
|
+
import { GoogleSheetsIcon } from '../icons-v2-generated'
|
|
7
|
+
import { EmbedIframe } from './embed-iframe'
|
|
8
|
+
import { toGoogleSheetsEmbedUrl, toGoogleSheetsOriginalUrl } from '../../utils/embed-url-converters'
|
|
9
|
+
|
|
10
|
+
export interface GoogleSheetsViewerProps {
|
|
11
|
+
externalUrl: string
|
|
12
|
+
fileName?: string
|
|
13
|
+
height?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function GoogleSheetsViewer({ externalUrl, fileName, height }: GoogleSheetsViewerProps) {
|
|
17
|
+
const displayName = fileName || 'Google Sheet'
|
|
18
|
+
|
|
19
|
+
if (!externalUrl) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
22
|
+
<GoogleSheetsIcon className="w-16 h-16 text-ods-text-secondary mb-4" />
|
|
23
|
+
<p className="text-ods-text-secondary">Google Sheet URL not configured</p>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="space-y-4">
|
|
30
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
31
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
32
|
+
<GoogleSheetsIcon className="w-5 h-5 shrink-0" />
|
|
33
|
+
<h2 className="text-xl font-semibold text-ods-text-primary truncate">{displayName}</h2>
|
|
34
|
+
</div>
|
|
35
|
+
<Button
|
|
36
|
+
variant="outline"
|
|
37
|
+
size="small-legacy"
|
|
38
|
+
href={toGoogleSheetsOriginalUrl(externalUrl)}
|
|
39
|
+
openInNewTab
|
|
40
|
+
leftIcon={<GoogleSheetsIcon className="w-4 h-4" />}
|
|
41
|
+
rightIcon={<ExternalLink className="w-4 h-4" />}
|
|
42
|
+
className="w-full sm:w-auto"
|
|
43
|
+
>
|
|
44
|
+
Open in Google Sheets
|
|
45
|
+
</Button>
|
|
46
|
+
</div>
|
|
47
|
+
<EmbedIframe
|
|
48
|
+
src={toGoogleSheetsEmbedUrl(externalUrl)}
|
|
49
|
+
title={displayName}
|
|
50
|
+
height={height}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { EmbedIframe } from './embed-iframe'
|
|
2
|
+
export type { EmbedIframeProps } from './embed-iframe'
|
|
3
|
+
|
|
4
|
+
export { PdfViewer } from './pdf-viewer'
|
|
5
|
+
export type { PdfViewerProps } from './pdf-viewer'
|
|
6
|
+
|
|
7
|
+
export { GoogleSheetsViewer } from './google-sheets-viewer'
|
|
8
|
+
export type { GoogleSheetsViewerProps } from './google-sheets-viewer'
|
|
9
|
+
|
|
10
|
+
export { FigmaEmbed } from './figma-embed'
|
|
11
|
+
export type { FigmaEmbedProps } from './figma-embed'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Button } from '../ui'
|
|
5
|
+
import { Download, Eye } from 'lucide-react'
|
|
6
|
+
import { AdobePdfIcon } from '../icons-v2-generated'
|
|
7
|
+
import { EmbedIframe } from './embed-iframe'
|
|
8
|
+
|
|
9
|
+
export interface PdfViewerProps {
|
|
10
|
+
src: string
|
|
11
|
+
fileName?: string
|
|
12
|
+
onPreview?: () => void
|
|
13
|
+
onDownload?: () => void
|
|
14
|
+
height?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PdfViewer({ src, fileName, onPreview, onDownload, height }: PdfViewerProps) {
|
|
18
|
+
const displayName = fileName || 'PDF Document'
|
|
19
|
+
|
|
20
|
+
if (!src) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
23
|
+
<AdobePdfIcon className="w-16 h-16 text-ods-text-secondary mb-4" />
|
|
24
|
+
<p className="text-ods-text-secondary">PDF file not available</p>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-4">
|
|
31
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
32
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
33
|
+
<AdobePdfIcon className="w-5 h-5 shrink-0" />
|
|
34
|
+
<h2 className="text-xl font-semibold text-ods-text-primary truncate">{displayName}</h2>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex items-center gap-2 w-full sm:w-auto">
|
|
37
|
+
<Button
|
|
38
|
+
variant="outline"
|
|
39
|
+
size="small-legacy"
|
|
40
|
+
href={onPreview ? undefined : src}
|
|
41
|
+
openInNewTab={!onPreview}
|
|
42
|
+
onClick={onPreview}
|
|
43
|
+
leftIcon={<Eye className="w-4 h-4" />}
|
|
44
|
+
className="flex-1 sm:flex-initial"
|
|
45
|
+
>
|
|
46
|
+
Preview
|
|
47
|
+
</Button>
|
|
48
|
+
<Button
|
|
49
|
+
variant="outline"
|
|
50
|
+
size="small-legacy"
|
|
51
|
+
href={onDownload ? undefined : src}
|
|
52
|
+
openInNewTab={!onDownload}
|
|
53
|
+
onClick={onDownload}
|
|
54
|
+
leftIcon={<Download className="w-4 h-4" />}
|
|
55
|
+
className="flex-1 sm:flex-initial"
|
|
56
|
+
>
|
|
57
|
+
Download
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<EmbedIframe src={src} title={displayName} height={height} />
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -19,6 +19,8 @@ export interface BoardColumnProps {
|
|
|
19
19
|
onArchive?: (columnId: string) => void
|
|
20
20
|
getTicketHref?: (ticketId: string) => string
|
|
21
21
|
renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
|
|
22
|
+
onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
23
|
+
onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
22
24
|
onLoadMore?: (columnId: string) => void
|
|
23
25
|
loadMoreRootMargin?: string
|
|
24
26
|
joinLeft?: boolean
|
|
@@ -33,6 +35,8 @@ export function BoardColumn({
|
|
|
33
35
|
onArchive,
|
|
34
36
|
getTicketHref,
|
|
35
37
|
renderAssignSlot,
|
|
38
|
+
onApprove,
|
|
39
|
+
onReject,
|
|
36
40
|
onLoadMore,
|
|
37
41
|
loadMoreRootMargin = '200px 0px',
|
|
38
42
|
joinLeft = false,
|
|
@@ -64,6 +68,8 @@ export function BoardColumn({
|
|
|
64
68
|
column={column}
|
|
65
69
|
getTicketHref={getTicketHref}
|
|
66
70
|
renderAssignSlot={renderAssignSlot}
|
|
71
|
+
onApprove={onApprove}
|
|
72
|
+
onReject={onReject}
|
|
67
73
|
onLoadMore={onLoadMore}
|
|
68
74
|
loadMoreRootMargin={loadMoreRootMargin}
|
|
69
75
|
/>
|
|
@@ -77,11 +83,13 @@ interface ColumnBodyProps {
|
|
|
77
83
|
column: BoardColumnDef
|
|
78
84
|
getTicketHref?: (ticketId: string) => string
|
|
79
85
|
renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
|
|
86
|
+
onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
87
|
+
onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
80
88
|
onLoadMore?: (columnId: string) => void
|
|
81
89
|
loadMoreRootMargin: string
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
function ColumnBody({ column, getTicketHref, renderAssignSlot, onLoadMore, loadMoreRootMargin }: ColumnBodyProps) {
|
|
92
|
+
function ColumnBody({ column, getTicketHref, renderAssignSlot, onApprove, onReject, onLoadMore, loadMoreRootMargin }: ColumnBodyProps) {
|
|
85
93
|
const ticketIds = React.useMemo(() => column.tickets.map(t => t.id), [column.tickets])
|
|
86
94
|
|
|
87
95
|
const droppableData = React.useMemo(
|
|
@@ -150,6 +158,8 @@ function ColumnBody({ column, getTicketHref, renderAssignSlot, onLoadMore, loadM
|
|
|
150
158
|
columnColor={column.color}
|
|
151
159
|
href={getTicketHref?.(t.id)}
|
|
152
160
|
renderAssignSlot={renderAssignSlot}
|
|
161
|
+
onApprove={onApprove}
|
|
162
|
+
onReject={onReject}
|
|
153
163
|
dragDisabled={column.dragDisabled}
|
|
154
164
|
/>
|
|
155
165
|
))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { ApprovalBatchMessage } from '../../chat/approval-batch-message'
|
|
6
|
+
import { useCollapsible } from '../../chat/hooks/use-collapsible'
|
|
7
|
+
import { Chevron02DownIcon, Chevron02UpIcon, DotsLoaderIcon, ShieldCheckIcon } from '../../icons-v2-generated'
|
|
8
|
+
import type { BoardTicketPendingApproval } from './types'
|
|
9
|
+
|
|
10
|
+
export interface BoardTicketApprovalProps {
|
|
11
|
+
pendingApproval: BoardTicketPendingApproval
|
|
12
|
+
onApprove?: (requestId?: string) => void | Promise<void>
|
|
13
|
+
onReject?: (requestId?: string) => void | Promise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Inline, collapsed-by-default approval section for a board ticket card. Expands to
|
|
18
|
+
* the shared {@link ApprovalBatchMessage} so the request can be approved/rejected
|
|
19
|
+
* without leaving the board. The header switches between the client variant (grey,
|
|
20
|
+
* "Pending client approval") and the technician/admin variant (yellow shield,
|
|
21
|
+
* "Technician approval required") on `approvalType`.
|
|
22
|
+
*/
|
|
23
|
+
export function BoardTicketApproval({ pendingApproval, onApprove, onReject }: BoardTicketApprovalProps) {
|
|
24
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
25
|
+
const { innerRef, containerStyle } = useCollapsible({ expanded })
|
|
26
|
+
const isAdmin = pendingApproval.approvalType === 'ADMIN'
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="pointer-events-auto flex flex-col">
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={() => setExpanded(v => !v)}
|
|
33
|
+
aria-expanded={expanded}
|
|
34
|
+
className="flex w-full cursor-pointer items-center gap-[var(--spacing-system-xxs)] text-h6"
|
|
35
|
+
>
|
|
36
|
+
<span
|
|
37
|
+
className={cn(
|
|
38
|
+
'flex min-w-0 flex-1 items-center gap-[var(--spacing-system-xxs)]',
|
|
39
|
+
isAdmin ? 'text-ods-open-yellow' : 'text-ods-text-secondary',
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{isAdmin ? (
|
|
43
|
+
<ShieldCheckIcon className="size-4 shrink-0" />
|
|
44
|
+
) : (
|
|
45
|
+
<DotsLoaderIcon size={16} className="shrink-0" />
|
|
46
|
+
)}
|
|
47
|
+
<span className="flex-1 truncate text-left">
|
|
48
|
+
{isAdmin ? 'Technician approval required' : 'Pending client approval'}
|
|
49
|
+
</span>
|
|
50
|
+
</span>
|
|
51
|
+
{expanded ? (
|
|
52
|
+
<Chevron02UpIcon className="size-4 shrink-0 text-ods-text-secondary" />
|
|
53
|
+
) : (
|
|
54
|
+
<Chevron02DownIcon className="size-4 shrink-0 text-ods-text-secondary" />
|
|
55
|
+
)}
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<div style={containerStyle}>
|
|
59
|
+
<div ref={innerRef} className="pt-[var(--spacing-system-xs)]">
|
|
60
|
+
<ApprovalBatchMessage
|
|
61
|
+
className="mb-0"
|
|
62
|
+
data={{
|
|
63
|
+
approvalRequestId: pendingApproval.id,
|
|
64
|
+
approvalType: pendingApproval.approvalType ?? 'CLIENT',
|
|
65
|
+
toolCalls: pendingApproval.toolCalls ?? [],
|
|
66
|
+
}}
|
|
67
|
+
status="pending"
|
|
68
|
+
showExecutionStatus={false}
|
|
69
|
+
onApprove={onApprove}
|
|
70
|
+
onReject={onReject}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -32,6 +32,8 @@ export interface BoardProps {
|
|
|
32
32
|
onArchiveColumn?: (columnId: string) => void
|
|
33
33
|
getTicketHref?: (ticketId: string) => string
|
|
34
34
|
renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
|
|
35
|
+
onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
36
|
+
onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
35
37
|
collapseStorageKey?: string
|
|
36
38
|
loadMoreRootMargin?: string
|
|
37
39
|
className?: string
|
|
@@ -45,6 +47,8 @@ export function Board({
|
|
|
45
47
|
onArchiveColumn,
|
|
46
48
|
getTicketHref,
|
|
47
49
|
renderAssignSlot,
|
|
50
|
+
onApprove,
|
|
51
|
+
onReject,
|
|
48
52
|
collapseStorageKey,
|
|
49
53
|
loadMoreRootMargin,
|
|
50
54
|
className,
|
|
@@ -271,6 +275,8 @@ export function Board({
|
|
|
271
275
|
onArchive={onArchiveColumn}
|
|
272
276
|
getTicketHref={getTicketHref}
|
|
273
277
|
renderAssignSlot={renderAssignSlot}
|
|
278
|
+
onApprove={onApprove}
|
|
279
|
+
onReject={onReject}
|
|
274
280
|
onLoadMore={onLoadMore}
|
|
275
281
|
loadMoreRootMargin={loadMoreRootMargin}
|
|
276
282
|
joinLeft={joinLeft}
|
|
@@ -8,6 +8,8 @@ export { BoardColumnHeader } from './board-column-header'
|
|
|
8
8
|
export type { BoardColumnHeaderProps } from './board-column-header'
|
|
9
9
|
export { TicketCard } from './ticket-card'
|
|
10
10
|
export type { TicketCardProps } from './ticket-card'
|
|
11
|
+
export { BoardTicketApproval } from './board-ticket-approval'
|
|
12
|
+
export type { BoardTicketApprovalProps } from './board-ticket-approval'
|
|
11
13
|
export { TicketCardSkeleton } from './ticket-card-skeleton'
|
|
12
14
|
export type { TicketCardSkeletonProps } from './ticket-card-skeleton'
|
|
13
15
|
export { useBoardCollapse } from './use-board-collapse'
|
|
@@ -23,4 +25,5 @@ export type {
|
|
|
23
25
|
BoardPriority,
|
|
24
26
|
BoardTicket,
|
|
25
27
|
BoardTicketAssignee,
|
|
28
|
+
BoardTicketPendingApproval,
|
|
26
29
|
} from './types'
|
|
@@ -11,6 +11,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../
|
|
|
11
11
|
import { cn } from '../../../utils/cn'
|
|
12
12
|
import { getReadableTextColor } from '../../../utils/ods-color-utils'
|
|
13
13
|
import { formatTicketRelativeTime, formatTicketFullTimestamp } from '../../../utils/date-utils'
|
|
14
|
+
import { BoardTicketApproval } from './board-ticket-approval'
|
|
14
15
|
import type { BoardPriority, BoardTicket } from './types'
|
|
15
16
|
|
|
16
17
|
const PRIORITY_COLOR_CLASS: Record<BoardPriority, string> = {
|
|
@@ -31,6 +32,8 @@ export interface TicketCardProps {
|
|
|
31
32
|
isOverlay?: boolean
|
|
32
33
|
dragDisabled?: boolean
|
|
33
34
|
renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
|
|
35
|
+
onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
36
|
+
onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export function TicketCard({
|
|
@@ -41,6 +44,8 @@ export function TicketCard({
|
|
|
41
44
|
isOverlay = false,
|
|
42
45
|
dragDisabled,
|
|
43
46
|
renderAssignSlot,
|
|
47
|
+
onApprove,
|
|
48
|
+
onReject,
|
|
44
49
|
}: TicketCardProps) {
|
|
45
50
|
const sortableData = React.useMemo(
|
|
46
51
|
() => ({ columnId, type: 'ticket' as const }),
|
|
@@ -143,6 +148,13 @@ export function TicketCard({
|
|
|
143
148
|
style={{ backgroundColor: columnColor, color: newMessageTextColor }}
|
|
144
149
|
/>
|
|
145
150
|
)}
|
|
151
|
+
{ticket.pendingApproval && (
|
|
152
|
+
<BoardTicketApproval
|
|
153
|
+
pendingApproval={ticket.pendingApproval}
|
|
154
|
+
onApprove={onApprove ? requestId => onApprove(ticket.id, requestId) : undefined}
|
|
155
|
+
onReject={onReject ? requestId => onReject(ticket.id, requestId) : undefined}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
146
158
|
</>
|
|
147
159
|
)
|
|
148
160
|
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
import type { PendingToolCallData } from '../../chat/types'
|
|
1
2
|
import type { TicketStatus } from '../../ui/ticket-status-tag'
|
|
2
3
|
|
|
3
4
|
export type BoardPriority = 'low' | 'medium' | 'high' | 'urgent'
|
|
4
5
|
|
|
6
|
+
/** Latest pending tool-approval request attached to a board ticket (from `Ticket.pendingApproval`). */
|
|
7
|
+
export interface BoardTicketPendingApproval {
|
|
8
|
+
id: string
|
|
9
|
+
approvalType?: string
|
|
10
|
+
command?: string
|
|
11
|
+
explanation?: string
|
|
12
|
+
createdAt?: string
|
|
13
|
+
toolCalls?: PendingToolCallData[]
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
export interface BoardTicketAssignee {
|
|
6
17
|
id: string
|
|
7
18
|
initials?: string
|
|
@@ -22,6 +33,7 @@ export interface BoardTicket {
|
|
|
22
33
|
tags?: string[]
|
|
23
34
|
createdAt?: string
|
|
24
35
|
hasNewMessage?: boolean
|
|
36
|
+
pendingApproval?: BoardTicketPendingApproval
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
export interface BoardColumnDef {
|
package/src/components/index.ts
CHANGED
|
@@ -56,6 +56,11 @@ export type { HeaderGlobalSearchProps } from './header-global-search'
|
|
|
56
56
|
export { HeaderOrganizationFilter } from './header-organization-filter'
|
|
57
57
|
export type { HeaderOrganizationFilterOrganization, HeaderOrganizationFilterProps } from './header-organization-filter'
|
|
58
58
|
|
|
59
|
+
// Multi-level navigation — sidebar + mobile dropdown for the doc-viewer
|
|
60
|
+
// (DocViewer / DocSourceViewer) and any other tree-shaped navigation surface.
|
|
61
|
+
export { MultiLevelNavigation, MobileNavigationDropdown } from './multi-level-navigation'
|
|
62
|
+
export type { NavigationNode } from './multi-level-navigation'
|
|
63
|
+
|
|
59
64
|
// Re-export types from navigation types
|
|
60
65
|
export type {
|
|
61
66
|
MobileNavConfig, NavigationItem, NavigationSidebarConfig,
|