Docs Embed: Make trademark optional (#4079)

Co-authored-by: Greg Bergé <berge.greg@gmail.com>
This commit is contained in:
Zeno Kapitein
2026-03-04 14:53:42 +01:00
committed by GitHub
parent b7fb9e57ca
commit 370b731972
11 changed files with 76 additions and 18 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@gitbook/embed": minor
"gitbook": patch
---
Docs Embed: Make trademark optional
+13
View File
@@ -269,6 +269,19 @@ Notes:
- The close button is rendered in the same sidebar area as tabs/actions.
- If your UI hides that sidebar or doesn't render it, the button will not be visible.
### `trademark`
Available in: Standalone script, NPM package, React components
Display GitBook branding in the embed. Defaults to true.
- **Type**: `boolean`
- **Default**: `true`
```javascript
trademark: true
```
### `actions`
Available in: Standalone script, NPM package, React components
@@ -69,6 +69,7 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien
greeting: { title: '', subtitle: '' },
suggestions: [],
tools: [],
trademark: true,
};
return {
+5
View File
@@ -68,6 +68,11 @@ export type GitBookEmbeddableConfiguration = {
/** Tools to be provided to the assistant. */
tools: GitBookToolDefinition[];
/**
* Display GitBook branding in the embed.
*/
trademark?: boolean;
/**
* Display a close button inside the assistant.
*/
+3 -1
View File
@@ -25,6 +25,7 @@ export function GitBookFrame(props: GitBookFrameProps) {
suggestions = [],
tools = [],
tabs = ['assistant', 'docs'],
trademark = true,
closeButton = false,
} = props;
@@ -48,8 +49,9 @@ export function GitBookFrame(props: GitBookFrameProps) {
suggestions,
tools,
closeButton,
trademark,
});
}, [gitbookFrame, actions, greeting, suggestions, tools, tabs, closeButton]);
}, [gitbookFrame, actions, greeting, suggestions, tools, tabs, closeButton, trademark]);
return (
<iframe
+14
View File
@@ -65,6 +65,7 @@ let frameConfiguration: GitBookEmbeddableConfiguration & StandaloneConfiguration
suggestions: [],
tools: [],
tabs: ['assistant', 'docs'],
trademark: true,
};
const widgetButton = document.createElement('button');
@@ -156,6 +157,19 @@ const GitBook = (...args: StandaloneCalls) => {
break;
case 'configure': {
const settings = args[1];
// If trademark is disabled, change the (branded) icon to the sparkle icon
if (
settings.trademark === false &&
!settings.button?.icon &&
frameConfiguration.button.icon === 'assistant'
) {
settings.button = {
label: frameConfiguration.button.label,
icon: 'sparkle',
};
}
frameConfiguration = {
...frameConfiguration,
...settings,
@@ -111,6 +111,7 @@ export function AIChat() {
chatController={chatController}
chat={chat}
suggestions={config.suggestions}
trademark={config.trademark}
/>
</EmbeddableFrameBody>
</EmbeddableFrameMain>
@@ -199,13 +200,13 @@ export function AIChatBody(props: {
chat: AIChatState;
welcomeMessage?: string;
suggestions?: string[];
trademark?: boolean;
greeting?: {
title: string;
subtitle: string;
};
}) {
const { chatController, chat, suggestions, greeting } = props;
const { trademark } = useAI().config;
const { chatController, chat, suggestions, greeting, trademark } = props;
const language = useLanguage();
const now = useNow(60 * 60 * 1000); // Refresh every hour for greeting
@@ -41,9 +41,9 @@ type EmbeddableAIChatProps = {
export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
const { baseURL, siteTitle } = props;
const chat = useAIChatState();
const { config } = useAI();
const { config: siteConfig } = useAI();
const chatController = useAIChatController();
const configuration = useEmbeddableConfiguration();
const embedConfig = useEmbeddableConfiguration();
const language = useLanguage();
React.useEffect(() => {
@@ -65,7 +65,8 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
}, [trackEvent]);
const tabsRef = React.useRef<HTMLDivElement>(null);
const hasDocsTab = configuration.tabs.includes('docs');
const hasDocsTab = embedConfig.tabs.includes('docs');
const trademark = siteConfig.trademark && (embedConfig.trademark ?? true);
const currentLinkContext = use(LinkContext);
const linkContext: LinkContextType = useMemo(
() =>
@@ -95,14 +96,11 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
<EmbeddableFrameMain data-testid="ai-chat">
<EmbeddableFrameHeader>
{!tabsRef.current ? (
<AIChatDynamicIcon
className="animate-blur-in-slow"
trademark={config.trademark}
/>
<AIChatDynamicIcon className="animate-blur-in-slow" trademark={trademark} />
) : null}
<EmbeddableFrameHeaderMain>
<EmbeddableFrameTitle>
{getAIChatName(language, config.trademark)}
{getAIChatName(language, trademark)}
</EmbeddableFrameTitle>
<AIChatSubtitle chat={chat} />
</EmbeddableFrameHeaderMain>
@@ -115,8 +113,9 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
<AIChatBody
chatController={chatController}
chat={chat}
suggestions={configuration.suggestions}
greeting={configuration.greeting}
suggestions={embedConfig.suggestions}
greeting={embedConfig.greeting}
trademark={trademark}
/>
</LinkContext>
</EmbeddableFrameBody>
@@ -17,6 +17,7 @@ const embeddableConfiguration = createStore<GitBookEmbeddableConfiguration>(() =
greeting: { title: '', subtitle: '' },
suggestions: [],
tools: [],
trademark: true,
}));
// biome-ignore lint/suspicious/noExplicitAny: expected
@@ -12,6 +12,7 @@ import { SpaceLayoutServerContext } from '../SpaceLayout';
import { Trademark } from '../TableOfContents/Trademark';
import { NavigationLoader } from '../primitives/NavigationLoader';
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';
import { IfEmbeddableTrademark } from './EmbeddableTrademark';
type EmbeddableRootLayoutProps = {
context: GitBookSiteContext;
@@ -58,11 +59,13 @@ export async function EmbeddableRootLayout({
<div className="fixed inset-0 flex flex-col">
{children}
{context.customization.trademark.enabled ? (
<Trademark
className="rounded-none! border-x-0 border-t border-b-0 bg-tint-solid/1 depth-flat:bg-tint-solid/1 px-4 py-2.5 text-tint/8"
context={context}
placement={SiteInsightsTrademarkPlacement.Embed}
/>
<IfEmbeddableTrademark>
<Trademark
className="rounded-none! border-x-0 border-t border-b-0 bg-tint-solid/1 depth-flat:bg-tint-solid/1 px-4 py-2.5 text-tint/8"
context={context}
placement={SiteInsightsTrademarkPlacement.Embed}
/>
</IfEmbeddableTrademark>
) : null}
</div>
<EmbeddableIframeAPI
@@ -0,0 +1,13 @@
'use client';
import type React from 'react';
import { useEmbeddableConfiguration } from './EmbeddableIframeAPI';
export function IfEmbeddableTrademark(props: {
children: React.ReactNode;
}) {
const { children } = props;
const embedTrademarkEnabled = useEmbeddableConfiguration((state) => state.trademark ?? true);
return embedTrademarkEnabled ? children : null;
}