@@ -7,7 +7,7 @@ import { useSetAtom } from "jotai";
77import { Check , ChevronDown , Copy } from "lucide-react" ;
88import type { ParamValue } from "next/dist/server/request/params" ;
99import { useParams } from "next/navigation" ;
10- import { useState } from "react" ;
10+ import { Fragment , useState } from "react" ;
1111import { capturePosthogEventInternal } from "@/components/analytics/posthog" ;
1212import { useIsAskAiEnabled } from "@/state/search" ;
1313import { searchPanelOpenAtom , useSetPageContext } from "@/state/search-panel" ;
@@ -17,11 +17,13 @@ import { OpenAISearchOption, Separator } from "./PageActionsDropdownOptions";
1717export function PageActionsDropdown ( {
1818 markdownPromise,
1919 pageActionOptions,
20- lang
20+ lang,
21+ style = "default"
2122} : {
2223 markdownPromise ?: Promise < { content : string ; contentType : "markdown" | "mdx" } | undefined > ;
2324 pageActionOptions : FernDropdown . PageActionOption [ ] ;
2425 lang : string ;
26+ style ?: "default" | "toolbar" ;
2527} ) {
2628 const [ showCopied , setShowCopied ] = useState < boolean > ( false ) ;
2729 const { domain, slug } = useParams ( ) ;
@@ -40,51 +42,207 @@ export function PageActionsDropdown({
4042 return null ;
4143 }
4244
43- const handleValueChange = async ( value : string ) => {
44- if ( value === "copy-page" ) {
45- window . focus ( ) ;
45+ const handleCopyPage = async ( ) => {
46+ window . focus ( ) ;
4647
47- if ( markdownPromise ) {
48- try {
49- const markdownResult = await markdownPromise ;
50- const markdown = markdownResult ?. content ;
48+ if ( markdownPromise ) {
49+ try {
50+ const markdownResult = await markdownPromise ;
51+ const markdown = markdownResult ?. content ;
5152
52- if ( markdown ) {
53- await navigator . clipboard . writeText ( markdown ) ;
54- capturePosthogEventInternal ( "page_actions_dropdown" , {
55- type : "copy-option" ,
56- page_location : window . location . pathname
57- } ) ;
53+ if ( markdown ) {
54+ await navigator . clipboard . writeText ( markdown ) ;
55+ capturePosthogEventInternal ( "page_actions_dropdown" , {
56+ type : "copy-option" ,
57+ page_location : window . location . pathname
58+ } ) ;
5859
59- setShowCopied ( true ) ;
60+ setShowCopied ( true ) ;
6061
61- setTimeout ( ( ) => {
62- setShowCopied ( false ) ;
63- } , 2000 ) ;
64- }
65- } catch ( error ) {
66- console . error ( "Failed to copy to clipboard:" , error ) ;
62+ setTimeout ( ( ) => {
63+ setShowCopied ( false ) ;
64+ } , 2000 ) ;
6765 }
66+ } catch ( error ) {
67+ console . error ( "Failed to copy to clipboard:" , error ) ;
6868 }
69+ }
70+ } ;
71+
72+ const handleAskAI = ( ) => {
73+ const pageContext = {
74+ title : document . title ,
75+ url : constructPageUrl ( domain , slug )
76+ } ;
77+ setPageContext ( pageContext ) ;
78+ setSearchPanelState ( true ) ;
79+ capturePosthogEventInternal ( "page_actions_dropdown" , {
80+ type : "ai-search" ,
81+ page_location : window . location . pathname
82+ } ) ;
83+ } ;
84+
85+ const handleValueChange = async ( value : string ) => {
86+ if ( value === "copy-page" ) {
87+ await handleCopyPage ( ) ;
6988 } else if ( value === "open-ai-search" ) {
70- const pageContext = {
71- title : document . title ,
72- url : constructPageUrl ( domain , slug )
73- } ;
74- setPageContext ( pageContext ) ;
75- setSearchPanelState ( true ) ;
89+ handleAskAI ( ) ;
90+ } else if ( value === "view-as-markdown" ) {
7691 capturePosthogEventInternal ( "page_actions_dropdown" , {
77- type : "ai-search " ,
92+ type : "markdown " ,
7893 page_location : window . location . pathname
7994 } ) ;
80- } else if ( value === "view-as-markdown" ) {
95+ } else {
8196 capturePosthogEventInternal ( "page_actions_dropdown" , {
82- type : "markdown" ,
97+ type : value ,
8398 page_location : window . location . pathname
8499 } ) ;
85100 }
86101 } ;
87102
103+ if ( style === "toolbar" ) {
104+ const renderInlineLink = ( option : FernDropdown . PageActionOption ) => {
105+ if ( option . type === "separator" ) {
106+ return null ;
107+ }
108+
109+ const { value, label, href } = option ;
110+
111+ if ( value === "copy-page" ) {
112+ return (
113+ < button
114+ key = { value }
115+ onClick = { ( ) => {
116+ capturePosthogEventInternal ( "page_actions_dropdown" , {
117+ type : "copy-button" ,
118+ page_location : window . location . pathname
119+ } ) ;
120+ void handleCopyPage ( ) ;
121+ } }
122+ className = "hover:underline text-(color:--grayscale-a11) whitespace-nowrap"
123+ >
124+ { showCopied ? t ( lang ) . buttons . copied : t ( lang ) . buttons . copyPage }
125+ </ button >
126+ ) ;
127+ }
128+
129+ if ( value === "open-ai-search" ) {
130+ return (
131+ < button
132+ key = { value }
133+ onClick = { handleAskAI }
134+ className = "hover:underline text-(color:--grayscale-a11) whitespace-nowrap"
135+ >
136+ { label }
137+ </ button >
138+ ) ;
139+ }
140+
141+ if ( value === "view-as-markdown" && href ) {
142+ return (
143+ < a
144+ key = { value }
145+ href = { href }
146+ target = "_blank"
147+ rel = "noopener noreferrer"
148+ onClick = { ( ) => {
149+ capturePosthogEventInternal ( "page_actions_dropdown" , {
150+ type : "markdown" ,
151+ page_location : window . location . pathname
152+ } ) ;
153+ } }
154+ className = "hover:underline text-(color:--grayscale-a11) whitespace-nowrap"
155+ >
156+ { label }
157+ </ a >
158+ ) ;
159+ }
160+
161+ if ( href ) {
162+ return (
163+ < a
164+ key = { value }
165+ href = { href }
166+ target = "_blank"
167+ rel = "noopener noreferrer"
168+ onClick = { ( ) => {
169+ capturePosthogEventInternal ( "page_actions_dropdown" , {
170+ type : value ,
171+ page_location : window . location . pathname
172+ } ) ;
173+ } }
174+ className = "hover:underline text-(color:--grayscale-a11) whitespace-nowrap"
175+ >
176+ { label }
177+ </ a >
178+ ) ;
179+ }
180+
181+ return (
182+ < button
183+ key = { value }
184+ onClick = { ( ) => {
185+ capturePosthogEventInternal ( "page_actions_dropdown" , {
186+ type : value ,
187+ page_location : window . location . pathname
188+ } ) ;
189+ } }
190+ className = "hover:underline text-(color:--grayscale-a11) whitespace-nowrap"
191+ >
192+ { label }
193+ </ button >
194+ ) ;
195+ } ;
196+
197+ const allOptions = isAskAiEnabled ? [ OpenAISearchOption ( { lang } ) , ...pageActionOptions ] : pageActionOptions ;
198+ const items = allOptions . filter ( ( option ) => option . type !== "separator" ) ;
199+
200+ if ( items . length === 0 ) {
201+ return null ;
202+ }
203+
204+ const MAX_VISIBLE = 3 ;
205+ const visibleItems = items . slice ( 0 , MAX_VISIBLE ) ;
206+ const overflowItems = items . slice ( MAX_VISIBLE ) ;
207+
208+ return (
209+ < div className = "fern-page-actions flex flex-wrap items-center text-sm" >
210+ { visibleItems . map ( ( item , i ) => (
211+ < Fragment key = { item . value } >
212+ { i > 0 && (
213+ < span aria-hidden = "true" className = "px-1 text-(color:--grayscale-a8)" >
214+ |
215+ </ span >
216+ ) }
217+ { renderInlineLink ( item ) }
218+ </ Fragment >
219+ ) ) }
220+ { overflowItems . length > 0 && (
221+ < >
222+ { visibleItems . length > 0 && (
223+ < span aria-hidden = "true" className = "px-1 text-(color:--grayscale-a8)" >
224+ |
225+ </ span >
226+ ) }
227+ < FernDropdown
228+ options = { overflowItems }
229+ onValueChange = { ( value ) => void handleValueChange ( value ) }
230+ dropdownMenuElement = { < a target = "_blank" rel = "noopener noreferrer" /> }
231+ lang = { lang }
232+ >
233+ < button
234+ aria-label = { t ( lang ) . buttons . moreActions }
235+ className = "px-1 text-(color:--grayscale-a11)"
236+ >
237+ …
238+ </ button >
239+ </ FernDropdown >
240+ </ >
241+ ) }
242+ </ div >
243+ ) ;
244+ }
245+
88246 return (
89247 < div className = "fern-page-actions" >
90248 < FernButton
0 commit comments