Skip to content

Commit 8c07f2a

Browse files
committed
feat: page menu button docs done
1 parent b9db994 commit 8c07f2a

6 files changed

Lines changed: 193 additions & 73 deletions

File tree

apps/site/pages/components/open.md

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
title: Page Menu Button Component (Open/Copy)
3+
---
4+
import {
5+
PageMenuButton,
6+
CopyAsMarkdownButton,
7+
OpenAsMarkdownAnchor,
8+
OpenInClaudeAnchor,
9+
OpenInChatGPTAnchor,
10+
OpenInCursorAnchor,
11+
OpenInSciraAnchor,
12+
OpenInVoAnchor,
13+
OpenInT3Anchor
14+
} from "@combostrap/interact/components/PageMenuButton";
15+
import ChevronDown from "bootstrap-icons/icons/chevron-down.svg";
16+
import {Button} from "@/components/ui/button";
17+
18+
The `PageMenuButton` component is a component that permits to:
19+
20+
* show or copy an alternate version of your pages such as [Markdown](../reference/ai.md#markdown-format-for-all-pages)
21+
* open your page in a [AI chatbot](../reference/ai.md)
22+
23+
This component is used by default in the [Hero partial](../reference/layout.md#partials).
24+
25+
## Variants
26+
27+
### Default button
28+
29+
In the default button, clicking on it open a menu of actions.
30+
31+
```jsx
32+
<PageMenuButton render={<Button type="button" variant="outline">Open As / Open In <ChevronDown className="size-4"/></Button>}>
33+
<CopyAsMarkdownButton>Copy as Markdown</CopyAsMarkdownButton>
34+
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
35+
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
36+
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
37+
<OpenInCursorAnchor>Open in Cursor</OpenInCursorAnchor>
38+
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
39+
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
40+
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
41+
</PageMenuButton>
42+
```
43+
Output:
44+
<PageMenuButton
45+
render={<Button type="button" variant="outline">Open As / Open In <ChevronDown className="size-4"/></Button>}>
46+
<CopyAsMarkdownButton>Copy as Markdown</CopyAsMarkdownButton>
47+
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
48+
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
49+
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
50+
<OpenInCursorAnchor>Open in Cursor</OpenInCursorAnchor>
51+
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
52+
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
53+
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
54+
</PageMenuButton>
55+
56+
### Split button
57+
58+
In the split button, the left side permits an action while the right side opens the menu.
59+
60+
```jsx
61+
<PageMenuButton variant="split" render={<CopyAsMarkdownButton>Copy As Markdown</CopyAsMarkdownButton>}>
62+
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
63+
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
64+
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
65+
<OpenInCursorAnchor>Open in Cursor</OpenInCursorAnchor>
66+
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
67+
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
68+
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
69+
</PageMenuButton>
70+
```
71+
Output:
72+
<PageMenuButton variant="split" render={<CopyAsMarkdownButton>Copy As Markdown</CopyAsMarkdownButton>}>
73+
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
74+
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
75+
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
76+
<OpenInCursorAnchor>Open in Cursor</OpenInCursorAnchor>
77+
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
78+
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
79+
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
80+
</PageMenuButton>
81+
82+
83+
## How it works
84+
85+
For each open button variant, you can pass:
86+
87+
* a `render` element to customize the output of the trigger element
88+
* `children` that represents each menu item. Each menu item should be an anchor or a button that have as children:
89+
* a svg icon
90+
* a span element for the label
91+
92+
An action component to be used as trigger or menu item should accepts the `hideSvg` to delete the icon if `true`.
93+
94+
### Page Button Props
95+
96+
| Prop | Description |
97+
|-----------|------------------------------------------------|
98+
| `render` | A anchor or button element to render as button |
99+
| `query` | The query to send to the chatbot |
100+
| `variant` | The variant (`default` or `split`) |

apps/site/pages/reference/ai.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
title: AI Agent Support
33
---
44

5-
## Markdown format for all pages
5+
6+
## Features
7+
8+
### Markdown format for all pages
69

710
All [pages](page.md) gets by default a Markdown version.
811

@@ -18,9 +21,17 @@ Every HTML page announces this alternate version in a link head element:
1821
Example:
1922

2023
```html
24+
2125
<link rel="alternate" type="text/markdown" href="/index.md">
2226
```
2327

24-
This alternate version is then used by the [Open Component](../components/open.md)
28+
This alternate version is then used by the [Open Component](../components/page-menu-button.mdx)
2529

2630
The [static server generation](build.md#static-server-generation) generates also the Markdown version
31+
32+
### Page Button Component
33+
34+
The [page button component](../components/page-menu-button.mdx) is a button that permits to:
35+
36+
* copy the page as Markdown
37+
* open the page in a chatbot

apps/site/pages/reference/layout.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Default layouts are composed of the following partials:
5454
* Head - the head tag
5555
* Body - the body tag
5656
* Header - the page header
57+
* Hero - the page content header
5758
* Aside - the sidebar
5859
* Toc - the toc
5960
* Footer - the page footer

src/resources/components/interact/Open.tsx renamed to src/resources/components/interact/PageMenuButton.tsx

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,15 @@ import {Button} from "@/components/ui/button";
2121
import {cn} from "@/lib/utils";
2222

2323

24-
export type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>
25-
& ComponentProps<typeof DropdownMenuTrigger>;
26-
2724
type OpenUiElement = {
2825
hideSvg?: boolean
2926
}
3027
type OpenUiAnchorElement = React.AnchorHTMLAttributes<HTMLAnchorElement> & OpenUiElement
3128
export type OpenUiInExternalAnchor = OpenUiAnchorElement & {
3229
query?: string
3330
}
34-
31+
export type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>
32+
& ComponentProps<typeof DropdownMenuTrigger>
3533
export const OpenInContent = ({className, ...props}: OpenInContentProps) => {
3634
if (className == null) {
3735
className = "w-full"
@@ -57,7 +55,7 @@ export type OpenContextProps = {
5755
children: React.ReactNode,
5856
};
5957

60-
export const OpenContext = ({query: localQuery, children}: OpenContextProps) => {
58+
export const PageButtonContext = ({query: localQuery, children}: OpenContextProps) => {
6159
const [query, setQuery] = useState(localQuery);
6260

6361
// Client component runs also on the server
@@ -282,7 +280,12 @@ export const OpenInT3Anchor = ({query: localQuery, hideSvg = false, children, ..
282280
</a>
283281
);
284282
};
285-
export const OpenInCursor = ({query: localQuery, hideSvg = false, children, ...props}: OpenUiInExternalAnchor) => {
283+
export const OpenInCursorAnchor = ({
284+
query: localQuery,
285+
hideSvg = false,
286+
children,
287+
...props
288+
}: OpenUiInExternalAnchor) => {
286289
const {query} = useOpenInContext();
287290
let finalQuery = query;
288291
if (!finalQuery) {
@@ -394,21 +397,53 @@ export const OpenAsPdfAnchor = ({children, href, hideSvg = false, ...props}: Ope
394397
};
395398

396399

397-
export function OpenButton({children, render, ...props}: OpenInContentProps) {
398-
let triggerRender = render;
399-
if (triggerRender == null) {
400-
triggerRender =
401-
<Button type="button" variant="outline">Open As / Open In <ChevronDown className="size-4"/></Button>
400+
// noinspection JSUnusedGlobalSymbols
401+
export function PageMenuButton({children, variant = "default", query, render, ...props}: OpenInContentProps &
402+
{
403+
render?: RenderNodeType,
404+
variant?: 'default' | 'split',
405+
query?: string,
406+
}) {
407+
408+
409+
if (variant == "default") {
410+
if (render == null) {
411+
render = (
412+
<Button type="button" variant="outline">
413+
Copy / Open <ChevronDown className="size-4"/>
414+
</Button>
415+
)
416+
}
417+
return (
418+
<PageButtonContext query={query}>
419+
<PageMenuDropDown render={render as any} {...props}>
420+
{children}
421+
</PageMenuDropDown>
422+
</PageButtonContext>
423+
)
402424
}
425+
if (render == null) {
426+
render = <CopyAsMarkdownButton/>
427+
}
428+
429+
let styledButton = React.cloneElement(render, {
430+
className: cn("group/button inline-flex shrink-0 px-2 items-center justify-center rounded-lg no-underline h-8 text-black border rounded-r-none border-r-0", render.props.className),
431+
hideSvg: true
432+
} as any)
433+
434+
let leftRender = (<Button type="button" variant={"outline"} className={"rounded-l-none px-2"}>
435+
<ChevronDown className="size-4"/>
436+
</Button>)
437+
403438
return (
404-
<OpenContext>
405-
<DropdownMenu>
406-
<DropdownMenuTrigger render={triggerRender}/>
407-
<OpenInContent {...props}>
439+
<PageButtonContext query={query}>
440+
<div className="flex">
441+
{styledButton}
442+
<PageMenuDropDown render={leftRender}>
408443
{children}
409-
</OpenInContent>
410-
</DropdownMenu>
411-
</OpenContext>
444+
</PageMenuDropDown>
445+
</div>
446+
</PageButtonContext>
412447
)
413448
}
414449

@@ -472,8 +507,9 @@ export function CopyAsMarkdownButton({
472507
)
473508
}
474509

475-
export function OpenSplitButtonMenu({children}: {
510+
function PageMenuDropDown({children, render}: {
476511
children: React.ReactNode,
512+
render?: RenderNodeType;
477513
}) {
478514
/**
479515
* Children are expected to be an anchor or a button with a svg and a span
@@ -522,41 +558,24 @@ export function OpenSplitButtonMenu({children}: {
522558
</DropdownMenuItem>
523559
)
524560
})
525-
let triggerRender = (
526-
<Button type="button" variant={"outline"} className={"rounded-l-none px-2"}>
527-
<ChevronDown className="size-4"/>
528-
</Button>);
561+
if (render == null) {
562+
render = (
563+
<Button type="button" variant={"outline"}>
564+
<ChevronDown className="size-4"/>
565+
</Button>
566+
);
567+
}
529568
return (
530569
<DropdownMenu>
531-
<DropdownMenuTrigger render={triggerRender}/>
570+
<DropdownMenuTrigger render={render}/>
532571
<OpenInContent>
533572
{styledChildren}
534573
</OpenInContent>
535574
</DropdownMenu>
536575
)
537576
}
538577

539-
export function OpenSplitButton({children, render}: {
540-
children: React.ReactNode,
541-
render?: React.ReactElement<
542-
React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }
543-
>;
544-
}) {
545-
if (render == null) {
546-
render = <CopyAsMarkdownButton/>
547-
}
548-
549-
let styledButton = React.cloneElement(render, {
550-
className: cn("group/button inline-flex shrink-0 px-2 items-center justify-center rounded-lg no-underline h-8 text-black border rounded-r-none border-r-0", render.props.className),
551-
hideSvg: true
552-
} as any)
578+
type RenderNodeType = React.ReactElement<
579+
React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }
580+
>
553581

554-
return (
555-
<OpenContext>
556-
<div className="flex">
557-
{styledButton}
558-
{children}
559-
</div>
560-
</OpenContext>
561-
)
562-
}

src/resources/components/partials/Hero.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
OpenAsMarkdownAnchor,
66
OpenInChatGPTAnchor,
77
OpenInClaudeAnchor,
8-
OpenInCursor,
8+
OpenInCursorAnchor,
99
OpenInSciraAnchor,
1010
OpenInT3Anchor,
1111
OpenInVoAnchor,
12-
OpenSplitButton, OpenSplitButtonMenu
13-
} from "@/components/interact/Open";
12+
PageMenuButton
13+
} from "@/components/interact/PageMenuButton";
1414

1515

1616
export type HeroProps = React.HTMLAttributes<HTMLElement> & LayoutProps
@@ -30,17 +30,16 @@ export default function Hero({page, context, ...htmlProps}: HeroProps) {
3030
{title &&
3131
<h1 className="h1 outline-heading-cs heading-cs heading-h1-cs">{title}</h1>
3232
}
33-
<OpenSplitButton render={<CopyAsMarkdownButton>Copy As Markdown</CopyAsMarkdownButton>}>
34-
<OpenSplitButtonMenu>
35-
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
36-
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
37-
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
38-
<OpenInCursor>Open in Cursor</OpenInCursor>
39-
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
40-
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
41-
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
42-
</OpenSplitButtonMenu>
43-
</OpenSplitButton>
33+
<PageMenuButton>
34+
<CopyAsMarkdownButton>Copy As Markdown</CopyAsMarkdownButton>
35+
<OpenAsMarkdownAnchor>Open as Markdown</OpenAsMarkdownAnchor>
36+
<OpenInClaudeAnchor>Open in Claude</OpenInClaudeAnchor>
37+
<OpenInChatGPTAnchor>Open in ChatGpt</OpenInChatGPTAnchor>
38+
<OpenInCursorAnchor>Open in Cursor</OpenInCursorAnchor>
39+
<OpenInSciraAnchor>Open in Scira</OpenInSciraAnchor>
40+
<OpenInT3Anchor>Open in T3</OpenInT3Anchor>
41+
<OpenInVoAnchor>Open in Vo</OpenInVoAnchor>
42+
</PageMenuButton>
4443

4544
</header>
4645
)

0 commit comments

Comments
 (0)