starlight-mega-menu 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: Bug Report
3
+ description: Report a bug or unexpected behavior
4
+ labels: ["bug"]
5
+ ---
6
+
7
+ ## Describe the Bug
8
+
9
+ A clear and concise description of what the bug is.
10
+
11
+ ## Steps to Reproduce
12
+
13
+ 1. Go to '...'
14
+ 2. Click on '...'
15
+ 3. Scroll down to '...'
16
+ 4. See error
17
+
18
+ ## Expected Behavior
19
+
20
+ A clear and concise description of what you expected to happen.
21
+
22
+ ## Actual Behavior
23
+
24
+ A clear and concise description of what actually happened.
25
+
26
+ ## Environment
27
+
28
+ - OS: [e.g., macOS 14.0, Ubuntu 22.04]
29
+ - Browser: [e.g., Chrome 120, Firefox 121]
30
+ - Version/Commit: [e.g., commit SHA or tag]
31
+
32
+ ## Additional Context
33
+
34
+ Add any other context, screenshots, or log output about the problem here.
@@ -0,0 +1,2 @@
1
+ blank_issues_enabled: false
2
+ contact_links: []
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: Documentation
3
+ description: Suggest a documentation improvement or report missing docs
4
+ labels: ["documentation"]
5
+ ---
6
+
7
+ ## What Needs to Be Documented
8
+
9
+ Describe the topic, feature, or area that needs documentation.
10
+
11
+ ## Current State
12
+
13
+ Describe the current state of the documentation (missing, incomplete, outdated, unclear).
14
+
15
+ ## Suggested Improvement
16
+
17
+ Describe what the documentation should cover or how it should be improved.
18
+
19
+ ## Additional Context
20
+
21
+ Add any references, examples, or links that would help with the documentation.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: Feature Request
3
+ description: Suggest a new feature or improvement
4
+ labels: ["enhancement"]
5
+ ---
6
+
7
+ ## Feature Description
8
+
9
+ A clear and concise description of the feature you'd like.
10
+
11
+ ## Use Case / Motivation
12
+
13
+ Explain the problem this feature would solve or the value it would provide.
14
+
15
+ ## Proposed Solution
16
+
17
+ Describe how you'd like this feature to work.
18
+
19
+ ## Alternatives Considered
20
+
21
+ Describe any alternative solutions or features you've considered.
22
+
23
+ ## Additional Context
24
+
25
+ Add any other context, mockups, or references about the feature request here.
@@ -0,0 +1,17 @@
1
+ ## Summary
2
+
3
+ Brief description of the changes in this PR.
4
+
5
+ ## Related Issue
6
+
7
+ Closes #
8
+
9
+ ## Changes
10
+
11
+ -
12
+
13
+ ## Checklist
14
+
15
+ - [ ] Linked to a GitHub issue (required — CI will block merge without it)
16
+ - [ ] Tested locally
17
+ - [ ] Follows project conventions
@@ -0,0 +1,15 @@
1
+ name: Auto Merge
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, ready_for_review]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ auto-merge:
13
+ uses: robinmordasiewicz/f5xc-template/.github/workflows/auto-merge.yml@main
14
+ secrets:
15
+ REPO_ADMIN_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
@@ -0,0 +1,19 @@
1
+ name: Enforce Repository Settings
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 */6 * * *'
6
+ push:
7
+ branches: [main]
8
+ paths:
9
+ - '.github/config/**'
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ enforce:
17
+ uses: robinmordasiewicz/f5xc-template/.github/workflows/enforce-repo-settings.yml@main
18
+ secrets:
19
+ repo-admin-token: ${{ secrets.REPO_ADMIN_TOKEN }}
@@ -0,0 +1,72 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths-ignore:
7
+ - '.github/**'
8
+ - '.editorconfig'
9
+ - '.gitignore'
10
+ - 'CLAUDE.md'
11
+ - 'CONTRIBUTING.md'
12
+ - 'README.md'
13
+ - 'LICENSE'
14
+ - 'release.config.js'
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: write
19
+ issues: write
20
+ pull-requests: write
21
+ id-token: write
22
+
23
+ jobs:
24
+ release:
25
+ name: Semantic Release
26
+ runs-on: ubuntu-latest
27
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, 'chore(release):')"
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+ with:
32
+ fetch-depth: 0
33
+
34
+ - name: Setup Node.js
35
+ uses: actions/setup-node@v4
36
+ with:
37
+ node-version: 'lts/*'
38
+ registry-url: 'https://registry.npmjs.org'
39
+
40
+ - name: Install semantic-release
41
+ run: >
42
+ npm install --no-save
43
+ semantic-release
44
+ @semantic-release/commit-analyzer
45
+ @semantic-release/release-notes-generator
46
+ @semantic-release/npm
47
+ @semantic-release/github
48
+
49
+ - name: Run semantic-release
50
+ run: |
51
+ max_attempts=3
52
+ attempt=1
53
+ delay=10
54
+ while [ $attempt -le $max_attempts ]; do
55
+ echo "Attempt $attempt of $max_attempts"
56
+ if npx semantic-release; then
57
+ echo "semantic-release succeeded"
58
+ exit 0
59
+ fi
60
+ if [ $attempt -eq $max_attempts ]; then
61
+ echo "semantic-release failed after $max_attempts attempts"
62
+ exit 1
63
+ fi
64
+ echo "Retrying in ${delay}s..."
65
+ sleep $delay
66
+ delay=$((delay * 2))
67
+ attempt=$((attempt + 1))
68
+ done
69
+ env:
70
+ GITHUB_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
71
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
72
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,23 @@
1
+ name: Require Linked Issue
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, edited, reopened, synchronize]
6
+
7
+ permissions:
8
+ issues: read
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ check-linked-issue:
13
+ runs-on: ubuntu-latest
14
+ name: Check linked issues
15
+ steps:
16
+ - uses: nearform-actions/github-action-check-linked-issues@v1
17
+ with:
18
+ exclude-branches: "dependabot/**"
19
+ custom-message: >-
20
+ This PR must be linked to a GitHub issue. Use 'Closes #123'
21
+ in the PR description, or link an issue from the sidebar.
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/CLAUDE.md ADDED
@@ -0,0 +1,35 @@
1
+ # Claude Code Project Instructions
2
+
3
+ ## Repository Workflow
4
+
5
+ This repo enforces a strict governance workflow. Follow it exactly:
6
+
7
+ 1. **Create a GitHub issue** before making any changes
8
+ 2. **Create a feature branch** from `main` — never commit to `main` directly
9
+ 3. **Open a PR** that links to the issue using `Closes #N`
10
+ 4. **CI must pass** — the "Check linked issues" check blocks PRs without a linked issue
11
+ 5. **Merge** — squash merge preferred, branch auto-deletes after merge
12
+
13
+ ## Use the `/ship` Skill
14
+
15
+ When available, use `/ship` to handle the full workflow (issue creation, branch, commit, PR) in one step.
16
+
17
+ ## Branch Naming
18
+
19
+ Use the format `<prefix>/<issue-number>-short-description`:
20
+
21
+ - `feature/42-add-grid-layout`
22
+ - `fix/17-correct-animation`
23
+ - `docs/8-update-guide`
24
+
25
+ ## Rules
26
+
27
+ - Never push directly to `main`
28
+ - Never force push
29
+ - Every PR must link to an issue
30
+ - Fill out the PR template completely
31
+ - Follow conventional commit messages (`feat:`, `fix:`, `docs:`)
32
+
33
+ ## Reference
34
+
35
+ Read `CONTRIBUTING.md` for full governance details.
@@ -0,0 +1,85 @@
1
+ # Contributing
2
+
3
+ This document describes the workflow and rules that all contributors — human and AI — must follow.
4
+
5
+ ## Workflow Overview
6
+
7
+ Every change follows this path:
8
+
9
+ ```
10
+ Issue → Branch → PR (linked to issue) → CI passes → Merge → Branch auto-deleted
11
+ ```
12
+
13
+ No exceptions. PRs without a linked issue will be blocked by CI.
14
+
15
+ ## Step 1: Create an Issue
16
+
17
+ Every change starts with a GitHub issue. Use one of the provided templates:
18
+
19
+ - **Bug Report** — for bugs and unexpected behavior
20
+ - **Feature Request** — for new features and improvements
21
+ - **Documentation** — for docs improvements or missing content
22
+
23
+ Blank issues are disabled. Pick the template that best fits your change.
24
+
25
+ ## Step 2: Create a Feature Branch
26
+
27
+ Branch from `main` using one of these naming conventions:
28
+
29
+ | Prefix | Use for | Example |
30
+ |--------|---------|---------|
31
+ | `feature/` | New features | `feature/42-add-grid-layout` |
32
+ | `fix/` | Bug fixes | `fix/17-correct-animation-timing` |
33
+ | `docs/` | Documentation | `docs/8-update-setup-guide` |
34
+
35
+ Format: `<prefix>/<issue-number>-short-description`
36
+
37
+ ```bash
38
+ git checkout main
39
+ git pull origin main
40
+ git checkout -b feature/42-add-grid-layout
41
+ ```
42
+
43
+ ## Step 3: Make Changes and Commit
44
+
45
+ - Write small, focused commits
46
+ - Use conventional commit messages:
47
+ - `feat: add grid layout support`
48
+ - `fix: correct animation timing`
49
+ - `docs: update setup guide`
50
+
51
+ ## Step 4: Open a Pull Request
52
+
53
+ 1. Push your branch and open a PR against `main`
54
+ 2. **Link the issue** — use `Closes #42` in the PR description, or link from the sidebar
55
+ 3. Fill out the PR template (it loads automatically)
56
+ 4. The **"Check linked issues"** CI check will block merge if no issue is linked
57
+
58
+ ## Step 5: Review and Merge
59
+
60
+ - All CI checks must pass before merge
61
+ - Auto-merge is enabled — PRs merge automatically once all checks pass
62
+ - Squash merge is preferred
63
+ - The branch is automatically deleted after merge (`delete_branch_on_merge` is enabled)
64
+
65
+ ## Branch Protection Rules
66
+
67
+ The `main` branch is protected. The following rules are enforced:
68
+
69
+ - No direct pushes to `main` — all changes go through PRs
70
+ - No force pushes
71
+ - Required status check: **"Check linked issues"** must pass
72
+ - Admin enforcement enabled — these rules apply to everyone
73
+
74
+ ## AI Assistant Guidelines
75
+
76
+ If you are Claude Code, Copilot, or another AI coding assistant, follow these rules:
77
+
78
+ 1. **Always create a GitHub issue before writing code.** No issue = no work.
79
+ 2. **Always work on a feature branch.** Never commit directly to `main`.
80
+ 3. **Always link the PR to the issue.** Use `Closes #N` in the PR description.
81
+ 4. **Use the `/ship` skill** when available — it handles the full Issue → Branch → PR flow.
82
+ 5. **Never force push** or attempt to bypass branch protection.
83
+ 6. **Fill out the PR template checklist** completely.
84
+ 7. **Follow the branch naming convention**: `feature/<issue>-desc`, `fix/<issue>-desc`, `docs/<issue>-desc`.
85
+ 8. **Respect CODEOWNERS** — `@robinmordasiewicz` is the default reviewer.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Robin Mordasiewicz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ ---
2
+ import config from 'virtual:starlight/user-config';
3
+
4
+ import LanguageSelect from 'virtual:starlight/components/LanguageSelect';
5
+ import Search from 'virtual:starlight/components/Search';
6
+ import SiteTitle from 'virtual:starlight/components/SiteTitle';
7
+ import SocialIcons from 'virtual:starlight/components/SocialIcons';
8
+ import ThemeSelect from 'virtual:starlight/components/ThemeSelect';
9
+ import megaMenuConfig from 'virtual:starlight-mega-menu/config';
10
+ import MegaMenu from './MegaMenu.tsx';
11
+ import MegaMenuMobile from './MegaMenuMobile.tsx';
12
+
13
+ /**
14
+ * Render the `Search` component if Pagefind is enabled or the default search component has been overridden.
15
+ */
16
+ const shouldRenderSearch =
17
+ config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro';
18
+ ---
19
+
20
+ <div class="header sl-mega-menu-header">
21
+ <div class="title-wrapper sl-flex">
22
+ <SiteTitle />
23
+ </div>
24
+ <nav class="smm-desktop-nav">
25
+ <MegaMenu client:load config={megaMenuConfig} />
26
+ </nav>
27
+ <div class="sl-flex print:hidden">
28
+ {shouldRenderSearch && <Search />}
29
+ </div>
30
+ <div class="sl-hidden md:sl-flex print:hidden right-group">
31
+ <div class="sl-flex social-icons">
32
+ <SocialIcons />
33
+ </div>
34
+ <ThemeSelect />
35
+ <LanguageSelect />
36
+ </div>
37
+ <div class="smm-mobile-toggle">
38
+ <MegaMenuMobile client:load config={megaMenuConfig} />
39
+ </div>
40
+ </div>
41
+
42
+ <style>
43
+ @layer starlight.core {
44
+ .header {
45
+ display: flex;
46
+ gap: var(--sl-nav-gap);
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ height: 100%;
50
+ }
51
+
52
+ .title-wrapper {
53
+ /* Prevent long titles overflowing and covering the search and menu buttons on narrow viewports. */
54
+ overflow: clip;
55
+ /* Avoid clipping focus ring around link inside title wrapper. */
56
+ padding: 0.25rem;
57
+ margin: -0.25rem;
58
+ min-width: 0;
59
+ }
60
+
61
+ .right-group,
62
+ .social-icons {
63
+ gap: 1rem;
64
+ align-items: center;
65
+ }
66
+ .social-icons::after {
67
+ content: '';
68
+ height: 2rem;
69
+ border-inline-end: 1px solid var(--sl-color-gray-5);
70
+ }
71
+
72
+ /* Desktop nav is hidden on small screens */
73
+ .smm-desktop-nav {
74
+ display: none;
75
+ }
76
+
77
+ /* Mobile toggle is visible on small screens */
78
+ .smm-mobile-toggle {
79
+ display: flex;
80
+ align-items: center;
81
+ }
82
+
83
+ @media (min-width: 50rem) {
84
+ :global(:root[data-has-sidebar]) {
85
+ --__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
86
+ }
87
+ :global(:root:not([data-has-toc])) {
88
+ --__toc-width: 0rem;
89
+ }
90
+ .header {
91
+ --__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));
92
+ --__main-column-fr: calc(
93
+ (
94
+ 100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) -
95
+ (2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) -
96
+ var(--sl-content-width)
97
+ ) / 2
98
+ );
99
+ display: grid;
100
+ grid-template-columns:
101
+ /* 1 (site title) */
102
+ auto
103
+ /* 2 (mega-menu nav) */
104
+ 1fr
105
+ /* 3 (search box) */
106
+ auto
107
+ /* 4 (right items) */
108
+ auto;
109
+ align-content: center;
110
+ }
111
+
112
+ /* Show desktop nav, hide mobile toggle */
113
+ .smm-desktop-nav {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ min-width: 0;
118
+ }
119
+
120
+ .smm-mobile-toggle {
121
+ display: none;
122
+ }
123
+ }
124
+ }
125
+ </style>
@@ -0,0 +1,137 @@
1
+ import * as NavigationMenu from '@radix-ui/react-navigation-menu';
2
+ import type { MegaMenuConfig, MegaMenuPanel, MegaMenuCategory, MegaMenuLink as MegaMenuLinkType, MegaMenuFooter } from '../types';
3
+
4
+ function ChevronDown({ className }: { className?: string }) {
5
+ return (
6
+ <svg
7
+ className={className}
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ width="12"
10
+ height="12"
11
+ viewBox="0 0 12 12"
12
+ fill="none"
13
+ aria-hidden="true"
14
+ >
15
+ <path
16
+ d="M2.5 4.5L6 8L9.5 4.5"
17
+ stroke="currentColor"
18
+ strokeWidth="1.5"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ </svg>
23
+ );
24
+ }
25
+
26
+ function PanelContent({ panel }: { panel: MegaMenuPanel }) {
27
+ const layout = panel.layout ?? 'list';
28
+ const columns = panel.columns ?? 2;
29
+
30
+ return (
31
+ <div
32
+ className="smm-panel"
33
+ data-layout={layout}
34
+ style={layout === 'grid' ? { '--smm-columns': columns } as React.CSSProperties : undefined}
35
+ >
36
+ {panel.categories?.map((category) => (
37
+ <CategorySection key={category.title} category={category} />
38
+ ))}
39
+ {panel.footer && <PanelFooter footer={panel.footer} />}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ function CategorySection({ category }: { category: MegaMenuCategory }) {
45
+ return (
46
+ <div className="smm-category">
47
+ <h3 className="smm-category-title">{category.title}</h3>
48
+ <ul className="smm-category-list">
49
+ {category.items.map((item) => (
50
+ <li key={item.href}>
51
+ <LinkItem item={item} />
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ function LinkItem({ item }: { item: MegaMenuLinkType }) {
60
+ return (
61
+ <NavigationMenu.Link className="smm-menu-link" href={item.href}>
62
+ {item.icon && (
63
+ <span className="smm-link-icon" dangerouslySetInnerHTML={{ __html: item.icon }} />
64
+ )}
65
+ <span className="smm-link-text">
66
+ <span className="smm-link-label">{item.label}</span>
67
+ {item.description && (
68
+ <span className="smm-link-description">{item.description}</span>
69
+ )}
70
+ </span>
71
+ </NavigationMenu.Link>
72
+ );
73
+ }
74
+
75
+ function PanelFooter({ footer }: { footer: MegaMenuFooter }) {
76
+ return (
77
+ <div className="smm-panel-footer">
78
+ <NavigationMenu.Link className="smm-footer-link" href={footer.href}>
79
+ <span className="smm-footer-label">{footer.label}</span>
80
+ {footer.description && (
81
+ <span className="smm-footer-description">{footer.description}</span>
82
+ )}
83
+ <svg
84
+ className="smm-footer-arrow"
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ width="14"
87
+ height="14"
88
+ viewBox="0 0 14 14"
89
+ fill="none"
90
+ aria-hidden="true"
91
+ >
92
+ <path
93
+ d="M1.75 7H12.25M12.25 7L7.875 2.625M12.25 7L7.875 11.375"
94
+ stroke="currentColor"
95
+ strokeWidth="1.5"
96
+ strokeLinecap="round"
97
+ strokeLinejoin="round"
98
+ />
99
+ </svg>
100
+ </NavigationMenu.Link>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ export default function MegaMenu({ config }: { config: MegaMenuConfig }) {
106
+ return (
107
+ <NavigationMenu.Root className="smm-root" delayDuration={200} skipDelayDuration={300}>
108
+ <NavigationMenu.List className="smm-list">
109
+ {config.items.map((item) =>
110
+ item.content ? (
111
+ <NavigationMenu.Item key={item.label} value={item.label}>
112
+ <NavigationMenu.Trigger className="smm-trigger">
113
+ {item.label}
114
+ <ChevronDown className="smm-chevron" />
115
+ </NavigationMenu.Trigger>
116
+ <NavigationMenu.Content className="smm-content">
117
+ <PanelContent panel={item.content} />
118
+ </NavigationMenu.Content>
119
+ </NavigationMenu.Item>
120
+ ) : (
121
+ <NavigationMenu.Item key={item.label}>
122
+ <NavigationMenu.Link className="smm-link" href={item.href}>
123
+ {item.label}
124
+ </NavigationMenu.Link>
125
+ </NavigationMenu.Item>
126
+ )
127
+ )}
128
+ <NavigationMenu.Indicator className="smm-indicator">
129
+ <div className="smm-arrow" />
130
+ </NavigationMenu.Indicator>
131
+ </NavigationMenu.List>
132
+ <div className="smm-viewport-wrapper">
133
+ <NavigationMenu.Viewport className="smm-viewport" />
134
+ </div>
135
+ </NavigationMenu.Root>
136
+ );
137
+ }