diff --git a/docs/myst.yml b/docs/myst.yml index 7b4c31b99..f6d8b2b4a 100644 --- a/docs/myst.yml +++ b/docs/myst.yml @@ -42,3 +42,8 @@ site: folders: true logo: _static/myst-logo-light.svg logo_dark: _static/myst-logo-dark.svg + navbar_icons: + - icon: github + url: https://github.com/jupyter-book/myst-theme + - icon: discord + url: https://discord.gg/QTnAx2yq diff --git a/docs/ui.md b/docs/ui.md index afa7a8ee0..5707ce23b 100644 --- a/docs/ui.md +++ b/docs/ui.md @@ -71,6 +71,48 @@ site: - If the `.md` file it points to is empty, the footer will not be visible - If not configured, falls back to the default "Made with MyST" footer +## Navbar Icons + +Display icon links in the navigation bar for social media, GitHub, and other external resources. + +### Configuration + +Add icons to your site's navbar through `site.options.navbar_icons`: + +```yaml +site: + options: + navbar_icons: + - icon: github + url: https://github.com/your-org/your-repo + - icon: discord + url: https://discord.gg/your-server + - icon: bluesky + url: https://bsky.app/profile/your-profile +``` + +Each icon requires: + +- `icon`: The icon name (see supported icons below) +- `url`: The external URL to link to +- `title` (optional): Custom tooltip text + +### Supported Icons + +| Icon name | Description | +|-----------|-------------| +| `github` | GitHub | +| `twitter` or `x` | X (Twitter) | +| `bluesky` | Bluesky | +| `discord` | Discord | +| `mastodon` | Mastodon | +| `linkedin` | LinkedIn | +| `youtube` | YouTube | +| `slack` | Slack | +| `email` | Email | +| `rss` | RSS Feed | +| `link` or `website` | Generic external link | + ## Hiding Elements Control the visibility of various page elements. All options can be set site-wide or per-page. diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 99d4d2132..3a6ac6d24 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -73,6 +73,12 @@ export type PageLoader = { dependencies?: Dependency[]; }; +export type NavbarIcon = { + icon: string; + url: string; + title?: string; +}; + export type CommonTemplateOptions = { favicon?: string; logo?: string; @@ -89,4 +95,5 @@ export type CommonTemplateOptions = { hide_toc?: boolean; hide_outline?: boolean; outline_maxdepth?: number; + navbar_icons?: NavbarIcon[]; }; diff --git a/packages/site/src/components/Navigation/NavbarIcons.tsx b/packages/site/src/components/Navigation/NavbarIcons.tsx new file mode 100644 index 000000000..133a20ea1 --- /dev/null +++ b/packages/site/src/components/Navigation/NavbarIcons.tsx @@ -0,0 +1,84 @@ +/** + * Navbar icon links for social media and external resources. + * Configured via site.options.navbar_icons in myst.yml. + */ +import type { NavbarIcon } from '@myst-theme/common'; +import { + GithubIcon, + XIcon, + BlueskyIcon, + DiscordIcon, + MastodonIcon, + LinkedinIcon, + YoutubeIcon, + SlackIcon, + EmailIcon, + WebsiteIcon, +} from '@scienceicons/react/24/solid'; +import { RssIcon } from '@heroicons/react/24/solid'; + +// Maps icon name strings to icon components +const ICON_MAP: Record> = { + github: GithubIcon, + twitter: XIcon, + x: XIcon, + bluesky: BlueskyIcon, + discord: DiscordIcon, + mastodon: MastodonIcon, + linkedin: LinkedinIcon, + youtube: YoutubeIcon, + slack: SlackIcon, + email: EmailIcon, + rss: RssIcon, + link: WebsiteIcon, + website: WebsiteIcon, +}; + +// Default tooltip text for each icon type +const DEFAULT_TITLES: Record = { + github: 'GitHub', + twitter: 'X (Twitter)', + x: 'X (Twitter)', + bluesky: 'Bluesky', + discord: 'Discord', + mastodon: 'Mastodon', + linkedin: 'LinkedIn', + youtube: 'YouTube', + slack: 'Slack', + email: 'Email', + rss: 'RSS Feed', + link: 'Website', + website: 'Website', +}; + +/** Single icon link that opens in a new tab */ +export function NavbarIconLink({ icon, url, title }: NavbarIcon) { + const IconComponent = ICON_MAP[icon.toLowerCase()] ?? WebsiteIcon; + const displayTitle = title ?? DEFAULT_TITLES[icon.toLowerCase()] ?? icon; + + return ( + + + {displayTitle} + + ); +} + +/** Container for multiple navbar icon links */ +export function NavbarIcons({ icons, className }: { icons?: NavbarIcon[]; className?: string }) { + if (!icons || icons.length === 0) return null; + + return ( +
+ {icons.map((iconConfig, index) => ( + + ))} +
+ ); +} diff --git a/packages/site/src/components/Navigation/PrimarySidebar.tsx b/packages/site/src/components/Navigation/PrimarySidebar.tsx index 8560aba0c..488920832 100644 --- a/packages/site/src/components/Navigation/PrimarySidebar.tsx +++ b/packages/site/src/components/Navigation/PrimarySidebar.tsx @@ -17,6 +17,7 @@ import { ExternalOrInternalLink } from './Link.js'; import type { SiteManifest, SiteNavItem } from 'myst-config'; import * as Collapsible from '@radix-ui/react-collapsible'; import { ChevronRightIcon } from '@heroicons/react/24/solid'; +import { NavbarIcons } from './NavbarIcons.js'; export function SidebarNavItem({ item }: { item: SiteNavItem }) { const baseurl = useBaseurl(); @@ -152,6 +153,7 @@ export const PrimarySidebar = ({ const footerRef = useRef(null); const [open] = useNavOpen(); const config = useSiteManifest(); + const { navbar_icons } = config?.options ?? {}; useEffect(() => { setTimeout(() => { @@ -191,6 +193,7 @@ export const PrimarySidebar = ({ )} >
+ {/* Site navigation links (mobile only) */} {nav && ( )} - {nav && headings &&
} + {/* Social/external icon links (mobile only) */} + {navbar_icons && navbar_icons.length > 0 && ( +
+ +
+ )} + {/* Divider between nav/icons and table of contents */} + {(nav || (navbar_icons && navbar_icons.length > 0)) && headings && ( +
+ )} + {/* Page table of contents */} {headings && (