Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Curated code themes. Core exports `CodeThemeOption`, `CODE_THEME_OPTIONS`
(`follow` plus the Catppuccin flavours, Nord and the Kolektiv palettes) and
`codeThemeOptions(config)`, which restricts the list to the themes a site
offers (so `themeFamilies` applies) and appends any `config.themes`.
`ThemePicker` renders site themes grouped by family — one `menu-title` per
family, Catppuccin shown by flavour (for example `Mocha`) — with the curated
code themes below `follow`.
- `RepoConfig.remotes` (`FooterLink[]`). When supplied, `defineDocsChrome`
derives `scm` from it, deduplicated by `href` and order-preserving;
otherwise it derives a single `{ label: 'Source', href: repo.url }`. An
explicit `scm` still wins. `deriveScm(repo)` is exported.

### Changed

- `ScmMenu` renders a single icon link when the deduplicated `scm` list has one
entry (previously it always opened a popover); several links keep the popover
menu and zero links render nothing. `defineDocsChrome` no longer emits the
duplicate `Source` / `yuri.capital` pair for `repo.url`.

## [0.0.1-SNAPSHOT.5] - 2026-09-14

### Added
Expand Down
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,22 @@ Config:
- `resolveNavbar(config, override?)` — navbar flags + links, layered over
`DEFAULT_NAVBAR`
- `resolveSwitchers(config)` — implicit `lang` plus every generic switcher
- `deriveScm(repo)` — `remotes` (deduped) or a single `Source` link for `url`
- `visibilityCss(config)` — `[data-lang-panel]` / `[data-<id>-panel]` /
`[data-framework-panel]` rules
- `RESERVED_SWITCHER_IDS`, `DEFAULT_CODE_THEME`, `DEFAULT_THEME_FAMILY`,
`DEFAULT_BUILT_BY`, `DEFAULT_NAVBAR`

Themes:

- Types `ChromeTheme`, `ThemeFamily`, `ThemeScheme`, `ChromeThemeGroup`
- Types `ChromeTheme`, `ThemeFamily`, `ThemeScheme`, `ChromeThemeGroup`,
`CodeThemeOption`
- `allChromeThemes()` — every built-in palette + daisyUI light/dark
- `themesByFamily(themes?)` — `Record<family, ChromeTheme[]>`
- `groupChromeThemes(themes?, order?)` — ordered `{ family, label, themes }[]`
- `siteThemes(config)` — built-ins filtered by `themeFamilies` + `config.themes`
- `CODE_THEME_OPTIONS` — curated code themes (`follow` + Kolektiv palettes)
- `codeThemeOptions(config?)` — curated code themes available for a site
- `resolveTheme(id, themes?)` — resolve an id or alias
- `shikiThemeForChrome(id, themes?)` — Shiki registration for a theme
- `themeCssFor(themes)` — daisyUI CSS for the supplied themes
Expand Down Expand Up @@ -246,11 +250,11 @@ Every component lives at `@kolektiv/common-docs-chrome/astro/<Name>.astro`:
| `Navbar.astro` | Sticky navbar with the current page label and controls |
| `Sidebar.astro` | Categorised nav, optional per-item icons, active highlight |
| `Footer.astro` | Footer with links and the built-by mark |
| `ThemePicker.astro` | Site + code theme popover grouped by family |
| `ThemePicker.astro` | Site themes grouped by family + curated code themes |
| `LangToggle.astro` | Language switch (renders only when `langs` is set) |
| `Switcher.astro` | Generic segmented switcher (renders a `SwitcherConfig`) |
| `FrameworkPicker.astro` | Framework switch (renders only when `frameworks` is set) |
| `ScmMenu.astro` | Source-control menu (renders when `scm`/`repo` is set) |
| `ScmMenu.astro` | Source-control link (one) or popover menu (many) |
| `Mark.astro` | Logo/mark image, with the `@kolektiv/brand-core` icon mark as fallback |
| `BuiltByMark.astro` | Always-on "Built by Kolektiv Computing" mark |
| `SearchDialog.astro` | Client-side search over the configured nav |
Expand All @@ -269,7 +273,7 @@ component names to import paths).
| `base` | `string` | `/` | Astro base path. |
| `logo` | `string` | — | Logo/wordmark URL for the navbar. |
| `mark` | `string` | — | Icon URL; falls back to the `@kolektiv/brand-core` icon mark. |
| `repo` | `RepoConfig` | — | `{ url, branch?, editBaseUrl? }`. |
| `repo` | `RepoConfig` | — | `{ url, branch?, editBaseUrl?, remotes? }`. |
| `nav` | `NavSection[]` | `[]` | Sidebar/nav model. |
| `defaultTheme` | `string` | — | Required. Applied on first visit + SSR. |
| `defaultCodeTheme` | `string` | `follow` | A theme id or `follow`. |
Expand All @@ -282,7 +286,7 @@ component names to import paths).
| `defaultFramework` | `string` | first `frameworks` | SSR default. |
| `switchers` | `SwitcherConfig[]` | `[]` | Extra generic switchers (see below). |
| `navbar` | `NavbarConfig` | all shown | Toggle built-in controls and add navbar links (see below). |
| `scm` | `FooterLink[]` | derived from `repo` | Source-control menu links. |
| `scm` | `FooterLink[]` | `repo.remotes`, else one `Source` link for `repo.url` | Source-control links; deduplicated by `href`. |
| `footer.links` | `FooterLink[]` | `[]` | Footer link column. |
| `footer.copyright` | `string` | `© {year} Kolektiv Computing` | `{year}` is replaced. |
| `footer.tagline` | `string` | — | Blurb next to the built-by mark. |
Expand Down Expand Up @@ -346,7 +350,34 @@ selected by `data-code-theme`:
- `follow` (default) tracks the site theme.
- Any theme id pins code blocks to that theme.

`ThemePicker` offers `Follow site theme` plus every registered theme.
`ThemePicker` groups the site themes by family (one `menu-title` heading per
family, ordered by `themeFamilies`) and offers a **curated** code-theme list —
`Follow site theme`, the Catppuccin flavours (shown by flavour, e.g. `Mocha`),
Nord, and the Kolektiv palettes. The curated list is restricted to the themes
that exist for the site, then any `config.themes` are appended, so custom site
themes appear in both lists. `codeThemeOptions(config)` returns that list and
`CODE_THEME_OPTIONS` is the curated constant.

## Source-control links

`ScmMenu` renders the deduplicated `scm` list. With **one** link it is a single
icon link (`title` / `aria-label` = the link label; external links get
`target`/`rel`); with **several** it opens the popover menu; with **none** it
renders nothing.

By default `scm` is derived from `repo`: `repo.remotes` when supplied
(deduplicated by `href`, order preserved), otherwise a single
`{ label: 'Source', href: repo.url }`. An explicit `config.scm` always wins.

```ts
repo: {
url: 'https://github.com/KolektivComputer/keel',
remotes: [
{ label: 'GitHub', href: 'https://github.com/KolektivComputer/keel' },
{ label: 'yuri.capital', href: 'https://yuri.capital/keel' },
],
},
```

## Generic switchers

Expand Down
118 changes: 73 additions & 45 deletions src/astro/ScmMenu.astro
Original file line number Diff line number Diff line change
@@ -1,67 +1,95 @@
---
import { isExternal, type DocsChromeConfig } from '@kolektiv/common-docs-chrome';
import { isExternal, type DocsChromeConfig, type FooterLink } from '@kolektiv/common-docs-chrome';

interface Props {
config: DocsChromeConfig;
}

const { config } = Astro.props;
const links: { label: string; href: string }[] = [];
const links: FooterLink[] = [];
for (const link of config.scm ?? []) {
if (!links.some((existing) => existing.href === link.href)) links.push(link);
}
const single = links.length === 1 ? links[0] : undefined;
const id = 'kdc-scm-pop';
const anchor = '--kdc-scm';
---

{
links.length > 0 && (
<>
<button
type="button"
class="btn btn-ghost btn-square btn-sm min-h-11 min-w-11"
popovertarget={id}
style={`anchor-name:${anchor}`}
aria-label="Source code"
title="Source"
>
<svg
class="size-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
aria-hidden="true"
{single ? (
<a
href={single.href}
target={isExternal(single.href) ? '_blank' : undefined}
rel={isExternal(single.href) ? 'noreferrer' : undefined}
class="btn btn-ghost btn-square btn-sm min-h-11 min-w-11"
aria-label={single.label}
title={single.label}
>
<path d="M6 3v12" stroke-linecap="round"></path>
<circle cx="6" cy="18" r="3"></circle>
<circle cx="18" cy="6" r="3"></circle>
<path d="M18 9v3.5a2.5 2.5 0 0 1-2.5 2.5H9" stroke-linecap="round"></path>
</svg>
</button>
<ul
popover
id={id}
class="dropdown dropdown-end dropdown-bottom menu menu-sm w-64 rounded-box border-2 border-base-300 bg-base-100 p-2 shadow-lg"
style={`position-anchor:${anchor}`}
>
<li class="menu-title">Source</li>
{links.map((link) => (
<li>
<a
href={link.href}
target={isExternal(link.href) ? '_blank' : undefined}
rel={isExternal(link.href) ? 'noreferrer' : undefined}
class="flex flex-col items-start gap-0.5 py-2"
<svg
class="size-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
aria-hidden="true"
>
<path d="M6 3v12" stroke-linecap="round" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="6" r="3" />
<path d="M18 9v3.5a2.5 2.5 0 0 1-2.5 2.5H9" stroke-linecap="round" />
</svg>
</a>
) : (
<>
<button
type="button"
class="btn btn-ghost btn-square btn-sm min-h-11 min-w-11"
popovertarget={id}
style={`anchor-name:${anchor}`}
aria-label="Source code"
title="Source"
>
<svg
class="size-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
aria-hidden="true"
>
<span class="font-medium">{link.label}</span>
<span class="font-mono text-[0.65rem] tracking-wide text-base-content/55">
{link.href}
</span>
</a>
</li>
))}
</ul>
<path d="M6 3v12" stroke-linecap="round" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="6" r="3" />
<path d="M18 9v3.5a2.5 2.5 0 0 1-2.5 2.5H9" stroke-linecap="round" />
</svg>
</button>
<ul
popover
id={id}
class="dropdown dropdown-end dropdown-bottom menu menu-sm w-64 rounded-box border-2 border-base-300 bg-base-100 p-2 shadow-lg"
style={`position-anchor:${anchor}`}
>
<li class="menu-title">Source</li>
{links.map((link) => (
<li>
<a
href={link.href}
target={isExternal(link.href) ? '_blank' : undefined}
rel={isExternal(link.href) ? 'noreferrer' : undefined}
class="flex flex-col items-start gap-0.5 py-2"
>
<span class="font-medium">{link.label}</span>
<span class="font-mono text-[0.65rem] tracking-wide text-base-content/55">
{link.href}
</span>
</a>
</li>
))}
</ul>
</>
)}
</>
)
}
Loading
Loading