doccupine 0.0.105 → 0.0.107

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.
Files changed (28) hide show
  1. package/dist/templates/components/Chat.d.ts +1 -1
  2. package/dist/templates/components/Chat.js +52 -40
  3. package/dist/templates/components/SearchModalContent.d.ts +1 -1
  4. package/dist/templates/components/SearchModalContent.js +27 -20
  5. package/dist/templates/components/layout/Accordion.d.ts +1 -1
  6. package/dist/templates/components/layout/Accordion.js +14 -4
  7. package/dist/templates/components/layout/Button.d.ts +1 -1
  8. package/dist/templates/components/layout/Button.js +5 -1
  9. package/dist/templates/components/layout/Code.d.ts +1 -1
  10. package/dist/templates/components/layout/Code.js +14 -4
  11. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  12. package/dist/templates/components/layout/DocsComponents.js +12 -6
  13. package/dist/templates/components/layout/Footer.d.ts +1 -1
  14. package/dist/templates/components/layout/Footer.js +2 -3
  15. package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
  16. package/dist/templates/components/layout/GlobalStyles.js +14 -0
  17. package/dist/templates/components/layout/Header.d.ts +1 -1
  18. package/dist/templates/components/layout/Header.js +0 -2
  19. package/dist/templates/components/layout/SectionBar.d.ts +1 -1
  20. package/dist/templates/components/layout/SectionBar.js +16 -4
  21. package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
  22. package/dist/templates/components/layout/SharedStyles.js +4 -17
  23. package/dist/templates/mdx/platform/navigation-settings.mdx.d.ts +1 -1
  24. package/dist/templates/mdx/platform/navigation-settings.mdx.js +47 -8
  25. package/dist/templates/mdx/update.mdx.d.ts +1 -1
  26. package/dist/templates/mdx/update.mdx.js +2 -2
  27. package/dist/templates/package.js +1 -1
  28. package/package.json +1 -1
@@ -167,11 +167,13 @@ const TabList = styled.div<{ theme: Theme }>\`
167
167
  /* Individual tab button. Active tab reads as part of the window: a light body
168
168
  fill (#ffffff) that echoes the code area, with a soft border. Inactive tabs
169
169
  are muted and transparent. resetButton strips native styling; focus-visible
170
- uses the brand primary ring for keyboard users. All colors stay fixed and
171
- swap purely via :root.dark, matching the no-re-render palette approach. */
170
+ draws an inset brand primary ring on a pseudo-element so the scrolling
171
+ TabList can't clip it. All colors stay fixed and swap purely via :root.dark,
172
+ matching the no-re-render palette approach. */
172
173
  const CodeTab = styled.button<{ theme: Theme; $active: boolean }>\`
173
174
  \${resetButton}
174
175
  flex: 0 0 auto;
176
+ position: relative;
175
177
  font-family: \${({ theme }) => theme.fonts.mono};
176
178
  font-size: 12px;
177
179
  line-height: 1;
@@ -195,8 +197,16 @@ const CodeTab = styled.button<{ theme: Theme; $active: boolean }>\`
195
197
  }
196
198
 
197
199
  &:focus-visible {
198
- outline: solid 2px \${({ theme }) => theme.colors.primary};
199
- outline-offset: 1px;
200
+ outline: none;
201
+ }
202
+
203
+ &:focus-visible::after {
204
+ content: "";
205
+ position: absolute;
206
+ inset: 2px;
207
+ border: solid 2px \${({ theme }) => theme.colors.primary};
208
+ border-radius: \${({ theme }) => theme.spacing.radius.xs};
209
+ pointer-events: none;
200
210
  }
201
211
 
202
212
  :root.dark & {
@@ -1 +1 @@
1
- export declare const docsComponentsTemplate = "\"use client\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n focusRing,\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.main<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]) {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $isOpen?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n display: flex;\n flex-direction: column;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledSidebarFooter = styled.div`\n padding: 22px 20px;\n position: sticky;\n border-top: 1px solid ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n margin: 0 -20px -20px;\n bottom: -20px;\n backdrop-filter: blur(10px);\n\n ${mq(\"lg\")} {\n padding: 16px 20px;\n }\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n ${focusRing};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n\n &:last-of-type {\n margin-bottom: auto;\n }\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) => theme.colors.accentStrong};\n display: inline-flex;\n align-items: center;\n gap: 8px;\n\n & svg {\n flex-shrink: 0;\n }\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\n// Shared appearance for every sidebar row - leaf links AND nested group\n// headers - so hover, active state, and the left rail read identically.\nconst sidebarRowStyles = css<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) => theme.colors.accentMuted};\n padding: 5px 0 5px 20px;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n flex-shrink: 0;\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${focusRing};\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n color: ${theme.colors.accentStrong};\n border-color: ${theme.colors.primary};\n font-weight: 600;\n `};\n`;\n\n// The collapse chevron points right when closed and rotates to point down\n// when the group is open.\nconst sidebarChevron = css<Props>`\n & .lucide-chevron-right {\n margin-left: auto;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: rotate(90deg);\n `}\n }\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n ${sidebarRowStyles};\n`;\n\n// Nested navigation group. The header shares the link appearance/hover; a\n// non-navigable group is a single toggle button, while a group that is also a\n// page pairs a link with a chevron toggle. Children live in\n// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto\n// (enabled by interpolate-size in GlobalStyles, same as the Accordion).\nexport const StyledSidebarGroupButton = styled.button<Props>`\n ${resetButton};\n ${sidebarRowStyles};\n ${sidebarChevron};\n width: 100%;\n text-align: left;\n`;\n\nexport const StyledSidebarGroupRow = styled.div<Props>`\n ${sidebarRowStyles};\n ${sidebarChevron};\n\n /* The row is a plain div, so it never matches :focus-visible itself. Ring the\n whole row when its inner link or chevron takes keyboard focus instead, to\n match the leaf-row focus treatment. */\n &:has(:focus-visible) {\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupLink = styled(Link)`\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n\n & svg {\n flex-shrink: 0;\n }\n\n /* Ring is drawn on the parent row via :has(:focus-visible). */\n &:focus-visible {\n outline: none;\n }\n`;\n\nexport const StyledSidebarGroupChevron = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n cursor: pointer;\n\n /* Ring is drawn on the parent row via :has(:focus-visible). */\n &:focus-visible {\n outline: none;\n }\n`;\n\nexport const StyledSidebarGroupContent = styled.ul<Props>`\n list-style: none;\n margin: 0;\n padding: 0;\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n height: auto;\n `}\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.surface};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 10px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n transform: scale(0.8);\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) => theme.colors.surface};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\nexport const StyledMissingComponent = styled.div`\n background: ${({ theme }) => theme.colors.error};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n color: ${({ theme }) => theme.colors.surface};\n font-weight: 600;\n display: flex;\n gap: 10px;\n align-items: center;\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
1
+ export declare const docsComponentsTemplate = "\"use client\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.main<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]) {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $isOpen?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n display: flex;\n flex-direction: column;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledSidebarFooter = styled.div`\n padding: 22px 20px;\n position: sticky;\n border-top: 1px solid ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n margin: 0 -20px -20px;\n bottom: -20px;\n backdrop-filter: blur(10px);\n\n ${mq(\"lg\")} {\n padding: 16px 20px;\n }\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n\n &:last-of-type {\n margin-bottom: auto;\n }\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) => theme.colors.accentStrong};\n display: inline-flex;\n align-items: center;\n gap: 8px;\n\n & svg {\n flex-shrink: 0;\n }\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\n// Shared appearance for every sidebar row - leaf links AND nested group\n// headers - so hover, active state, and the left rail read identically.\nconst sidebarRowStyles = css<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) => theme.colors.accentMuted};\n padding: 5px 0 5px 20px;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n flex-shrink: 0;\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n color: ${theme.colors.accentStrong};\n border-color: ${theme.colors.primary};\n font-weight: 600;\n `};\n`;\n\n// The collapse chevron points right when closed and rotates to point down\n// when the group is open.\nconst sidebarChevron = css<Props>`\n & .lucide-chevron-right {\n margin-left: auto;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: rotate(90deg);\n `}\n }\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n ${sidebarRowStyles};\n`;\n\n// Nested navigation group. The header shares the link appearance/hover; a\n// non-navigable group is a single toggle button, while a group that is also a\n// page pairs a link with a chevron toggle. Children live in\n// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto\n// (enabled by interpolate-size in GlobalStyles, same as the Accordion).\nexport const StyledSidebarGroupButton = styled.button<Props>`\n ${resetButton};\n ${sidebarRowStyles};\n ${sidebarChevron};\n width: 100%;\n text-align: left;\n\n /* Buttons aren't covered by the global a:focus-visible ring, so this\n nav-row toggle carries its own copy of it. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupRow = styled.div<Props>`\n ${sidebarRowStyles};\n ${sidebarChevron};\n\n /* The row is a plain div, so it never matches :focus-visible itself. Ring the\n whole row when its inner link or chevron takes keyboard focus instead, to\n match the leaf-row focus treatment. */\n &:has(:focus-visible) {\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupLink = styled(Link)`\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n\n & svg {\n flex-shrink: 0;\n }\n\n /* Ring is drawn on the parent row via :has(:focus-visible); suppress the\n global a:focus-visible glow so it isn't drawn twice. */\n &:focus-visible {\n outline: none;\n box-shadow: none;\n }\n`;\n\nexport const StyledSidebarGroupChevron = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n cursor: pointer;\n\n /* Ring is drawn on the parent row via :has(:focus-visible). */\n &:focus-visible {\n outline: none;\n }\n`;\n\nexport const StyledSidebarGroupContent = styled.ul<Props>`\n list-style: none;\n margin: 0;\n padding: 0;\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n height: auto;\n `}\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.surface};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 10px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n transform: scale(0.8);\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) => theme.colors.surface};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\nexport const StyledMissingComponent = styled.div`\n background: ${({ theme }) => theme.colors.error};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n color: ${({ theme }) => theme.colors.surface};\n font-weight: 600;\n display: flex;\n gap: 10px;\n align-items: center;\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
@@ -11,7 +11,6 @@ import {
11
11
  import Link from "next/link";
12
12
  import { mq, Theme } from "@/app/theme";
13
13
  import {
14
- focusRing,
15
14
  styledAnchor,
16
15
  styledTable,
17
16
  stylesLists,
@@ -253,7 +252,6 @@ export const StyledIndexSidebarLink = styled.a<{
253
252
  font-weight: \${({ $isActive }) => ($isActive ? "600" : "400")};
254
253
  text-decoration: none;
255
254
  transition: all 0.3s ease;
256
- \${focusRing};
257
255
 
258
256
  &:hover {
259
257
  color: \${({ theme }) => theme.colors.primary};
@@ -314,8 +312,6 @@ const sidebarRowStyles = css<Props>\`
314
312
  border-color: \${({ theme }) => theme.colors.primary};
315
313
  }
316
314
 
317
- \${focusRing};
318
-
319
315
  \${({ $isActive, theme }) =>
320
316
  $isActive &&
321
317
  css\`
@@ -355,6 +351,14 @@ export const StyledSidebarGroupButton = styled.button<Props>\`
355
351
  \${sidebarChevron};
356
352
  width: 100%;
357
353
  text-align: left;
354
+
355
+ /* Buttons aren't covered by the global a:focus-visible ring, so this
356
+ nav-row toggle carries its own copy of it. */
357
+ &:focus-visible {
358
+ outline: none;
359
+ border-radius: \${({ theme }) => theme.spacing.radius.xs};
360
+ box-shadow: 0 0 0 2px \${({ theme }) => theme.colors.primaryLight};
361
+ }
358
362
  \`;
359
363
 
360
364
  export const StyledSidebarGroupRow = styled.div<Props>\`
@@ -366,7 +370,7 @@ export const StyledSidebarGroupRow = styled.div<Props>\`
366
370
  match the leaf-row focus treatment. */
367
371
  &:has(:focus-visible) {
368
372
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
369
- box-shadow: 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
373
+ box-shadow: 0 0 0 2px \${({ theme }) => theme.colors.primaryLight};
370
374
  }
371
375
  \`;
372
376
 
@@ -383,9 +387,11 @@ export const StyledSidebarGroupLink = styled(Link)\`
383
387
  flex-shrink: 0;
384
388
  }
385
389
 
386
- /* Ring is drawn on the parent row via :has(:focus-visible). */
390
+ /* Ring is drawn on the parent row via :has(:focus-visible); suppress the
391
+ global a:focus-visible glow so it isn't drawn twice. */
387
392
  &:focus-visible {
388
393
  outline: none;
394
+ box-shadow: none;
389
395
  }
390
396
  \`;
391
397
 
@@ -1 +1 @@
1
- export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport { focusRing } from \"@/components/layout/SharedStyled\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n ${focusRing};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n /* The GitHub logo is an icon-only link - round its focus ring to match. */\n &:has(svg):focus-visible {\n border-radius: 100%;\n }\n\n & svg {\n width: 18px;\n height: 18px;\n }\n }\n`;\n\nconst StyledFooterFlex = styled.div`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 20px;\n\n ${mq(\"lg\")} {\n justify-content: space-between;\n }\n`;\n\nfunction Footer({ hideBranding }: { hideBranding?: boolean }) {\n const { isOpen } = useContext(ChatContext);\n\n if (hideBranding) return <Space $xs={80} $lg=\"none\" />;\n\n return (\n <StyledFooter $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledFooterInner>\n <StyledFooterFlex>\n <span>\n Powered by{\" \"}\n <a\n href=\"https://doccupine.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Doccupine\n </a>\n </span>\n <a\n href=\"https://github.com/doccupine/cli\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label=\"Doccupine on GitHub\"\n >\n <GitHubLogo />\n </a>\n </StyledFooterFlex>\n </StyledFooterInner>\n </StyledFooter>\n );\n}\n\nexport { Footer };\n";
1
+ export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n /* The GitHub logo is an icon-only link - round the global focus ring so it\n hugs the icon instead of drawing a rectangle. */\n &:has(svg):focus-visible {\n border-radius: 100%;\n }\n\n & svg {\n width: 18px;\n height: 18px;\n }\n }\n`;\n\nconst StyledFooterFlex = styled.div`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 20px;\n\n ${mq(\"lg\")} {\n justify-content: space-between;\n }\n`;\n\nfunction Footer({ hideBranding }: { hideBranding?: boolean }) {\n const { isOpen } = useContext(ChatContext);\n\n if (hideBranding) return <Space $xs={80} $lg=\"none\" />;\n\n return (\n <StyledFooter $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledFooterInner>\n <StyledFooterFlex>\n <span>\n Powered by{\" \"}\n <a\n href=\"https://doccupine.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Doccupine\n </a>\n </span>\n <a\n href=\"https://github.com/doccupine/cli\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label=\"Doccupine on GitHub\"\n >\n <GitHubLogo />\n </a>\n </StyledFooterFlex>\n </StyledFooterInner>\n </StyledFooter>\n );\n}\n\nexport { Footer };\n";
@@ -6,7 +6,6 @@ import { Space, styledSmall } from "cherry-styled-components";
6
6
  import { ChatContext } from "@/components/Chat";
7
7
  import { mq, Theme } from "@/app/theme";
8
8
  import { GitHubLogo } from "@/components/layout/Pictograms";
9
- import { focusRing } from "@/components/layout/SharedStyled";
10
9
  import linksData from "@/links.json";
11
10
 
12
11
  interface LinkProps {
@@ -61,13 +60,13 @@ const StyledFooterInner = styled.div<{ theme: Theme }>\`
61
60
  text-decoration: none;
62
61
  transition: all 0.3s ease;
63
62
  display: inline-flex;
64
- \${focusRing};
65
63
 
66
64
  &:hover {
67
65
  color: \${({ theme }) => theme.colors.primary};
68
66
  }
69
67
 
70
- /* The GitHub logo is an icon-only link - round its focus ring to match. */
68
+ /* The GitHub logo is an icon-only link - round the global focus ring so it
69
+ hugs the icon instead of drawing a rectangle. */
71
70
  &:has(svg):focus-visible {
72
71
  border-radius: 100%;
73
72
  }
@@ -1 +1 @@
1
- export declare const globalStylesTemplate = "\"use client\";\nimport { createGlobalStyle } from \"styled-components\";\nimport {\n colorsLight,\n colorsDark,\n shadowsLight,\n shadowsDark,\n} from \"@/app/theme\";\n\n// Build \"name: value;\" lines for every entry in a record. Used to emit\n// :root and :root.dark blocks from the resolved hex objects in theme.ts.\n// Custom theme.json overrides flow through automatically because they are\n// already merged into colorsLight / colorsDark.\nfunction toCssVars<T extends object>(prefix: string, record: T): string {\n return Object.entries(record)\n .map(([k, v]) => ` --${prefix}-${k}: ${v};`)\n .join(\"\\n\");\n}\n\nconst lightVars = [\n toCssVars(\"color\", colorsLight),\n toCssVars(\"shadow\", shadowsLight),\n // Semantic tokens \u2014 derived from the brand palette per mode. See the\n // SemanticColors interface in theme.ts for the intent of each.\n // These reference the palette via var() (not baked hex) so that any\n // runtime override of --color-primary* (e.g. the DemoTheme presets)\n // cascades into the semantic tokens automatically.\n ` --color-accent: var(--color-primaryDark);`,\n // accentStrong = accent shifted ~10% toward black (light mode) or white (dark\n // mode). color-mix in sRGB is not identical to polished's HSL darken/lighten\n // but the visual difference at 10% on a UI accent is imperceptible.\n ` --color-accentStrong: color-mix(in srgb, var(--color-primaryDark) 90%, black);`,\n ` --color-accentMuted: var(--color-primary);`,\n ` --color-surface: var(--color-light);`,\n].join(\"\\n\");\n\nconst darkVars = [\n toCssVars(\"color\", colorsDark),\n toCssVars(\"shadow\", shadowsDark),\n ` --color-accent: var(--color-primaryLight);`,\n ` --color-accentStrong: color-mix(in srgb, var(--color-primaryLight) 90%, white);`,\n ` --color-accentMuted: var(--color-grayDark);`,\n ` --color-surface: var(--color-dark);`,\n].join(\"\\n\");\n\nconst GlobalStyles = createGlobalStyle`\n:root {\n${lightVars}\n}\n\n:root.dark {\n${darkVars}\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n min-height: 100%;\n background-color: var(--color-light);\n scroll-padding-top: 80px;\n}\n\nhtml:has(:target) {\n scroll-behavior: smooth;\n}\n\nbody {\n font-family: \"Inter\", sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-text-size-adjust: 100%;\n -webkit-font-smoothing: antialiased;\n}\n\n:root {\n interpolate-size: allow-keywords;\n}\n\n* {\n box-sizing: border-box;\n min-width: 0;\n}\n\nhr {\n border: none;\n border-bottom: solid 1px var(--color-grayLight);\n margin: 10px 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace;\n}\n\npre,\ncode,\nkbd,\nsamp,\nblockquote,\np,\na,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nul li,\nol li {\n margin: 0;\n padding: 0;\n color: var(--color-dark);\n}\n\na {\n color: var(--color-primary);\n}\n\nol,\nul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\nfigure {\n margin: 0;\n}\n\nfieldset {\n appearance: none;\n border: none;\n}\n\nbutton,\ninput,\na,\nimg,\nsvg,\nsvg * {\n transition: all 0.3s ease;\n}\n\nstrong,\nb {\n font-weight: 700;\n}\n\n.full-width {\n width: 100%;\n}\n\n/* Mode-conditional visibility helpers. ThemeToggle uses these to swap Sun\n and Moon icons; Header uses them to swap light/dark logos. Pure CSS so\n the swap happens via the active <html> class without JS or re-render. */\n:root.dark .light-only {\n display: none !important;\n}\n:root:not(.dark) .dark-only {\n display: none !important;\n}\n`;\n\nexport { GlobalStyles };\n";
1
+ export declare const globalStylesTemplate = "\"use client\";\nimport { createGlobalStyle } from \"styled-components\";\nimport {\n colorsLight,\n colorsDark,\n shadowsLight,\n shadowsDark,\n} from \"@/app/theme\";\n\n// Build \"name: value;\" lines for every entry in a record. Used to emit\n// :root and :root.dark blocks from the resolved hex objects in theme.ts.\n// Custom theme.json overrides flow through automatically because they are\n// already merged into colorsLight / colorsDark.\nfunction toCssVars<T extends object>(prefix: string, record: T): string {\n return Object.entries(record)\n .map(([k, v]) => ` --${prefix}-${k}: ${v};`)\n .join(\"\\n\");\n}\n\nconst lightVars = [\n toCssVars(\"color\", colorsLight),\n toCssVars(\"shadow\", shadowsLight),\n // Semantic tokens \u2014 derived from the brand palette per mode. See the\n // SemanticColors interface in theme.ts for the intent of each.\n // These reference the palette via var() (not baked hex) so that any\n // runtime override of --color-primary* (e.g. the DemoTheme presets)\n // cascades into the semantic tokens automatically.\n ` --color-accent: var(--color-primaryDark);`,\n // accentStrong = accent shifted ~10% toward black (light mode) or white (dark\n // mode). color-mix in sRGB is not identical to polished's HSL darken/lighten\n // but the visual difference at 10% on a UI accent is imperceptible.\n ` --color-accentStrong: color-mix(in srgb, var(--color-primaryDark) 90%, black);`,\n ` --color-accentMuted: var(--color-primary);`,\n ` --color-surface: var(--color-light);`,\n].join(\"\\n\");\n\nconst darkVars = [\n toCssVars(\"color\", colorsDark),\n toCssVars(\"shadow\", shadowsDark),\n ` --color-accent: var(--color-primaryLight);`,\n ` --color-accentStrong: color-mix(in srgb, var(--color-primaryLight) 90%, white);`,\n ` --color-accentMuted: var(--color-grayDark);`,\n ` --color-surface: var(--color-dark);`,\n].join(\"\\n\");\n\nconst GlobalStyles = createGlobalStyle`\n:root {\n${lightVars}\n}\n\n:root.dark {\n${darkVars}\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n min-height: 100%;\n background-color: var(--color-light);\n scroll-padding-top: 80px;\n}\n\nhtml:has(:target) {\n scroll-behavior: smooth;\n}\n\nbody {\n font-family: \"Inter\", sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-text-size-adjust: 100%;\n -webkit-font-smoothing: antialiased;\n}\n\n:root {\n interpolate-size: allow-keywords;\n}\n\n* {\n box-sizing: border-box;\n min-width: 0;\n}\n\nhr {\n border: none;\n border-bottom: solid 1px var(--color-grayLight);\n margin: 10px 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace;\n}\n\npre,\ncode,\nkbd,\nsamp,\nblockquote,\np,\na,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nul li,\nol li {\n margin: 0;\n padding: 0;\n color: var(--color-dark);\n}\n\na {\n color: var(--color-primary);\n}\n\n/* Keyboard-focus ring, defined once globally so links don't each re-declare it.\n :focus-visible keeps it keyboard-only (no ring on mouse click); box-shadow\n gives a soft primaryLight glow that follows the element's border-radius. The\n 6px radius mirrors theme.spacing.radius.xs so the ring rounds on links that\n have no radius of their own. Cherry link-buttons (.button-link) are excluded\n via :where() so the exclusion adds no specificity - this keeps the selector\n at (0,1,1) so bespoke per-component :focus-visible rules (inline content\n links, the section bar, the sidebar group rows) still override it. */\na:focus-visible:where(:not(.button-link)) {\n outline: none;\n border-radius: 6px;\n box-shadow: 0 0 0 2px var(--color-primaryLight);\n}\n\nol,\nul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\nfigure {\n margin: 0;\n}\n\nfieldset {\n appearance: none;\n border: none;\n}\n\nbutton,\ninput,\na,\nimg,\nsvg,\nsvg * {\n transition: all 0.3s ease;\n}\n\nstrong,\nb {\n font-weight: 700;\n}\n\n.full-width {\n width: 100%;\n}\n\n/* Mode-conditional visibility helpers. ThemeToggle uses these to swap Sun\n and Moon icons; Header uses them to swap light/dark logos. Pure CSS so\n the swap happens via the active <html> class without JS or re-render. */\n:root.dark .light-only {\n display: none !important;\n}\n:root:not(.dark) .dark-only {\n display: none !important;\n}\n`;\n\nexport { GlobalStyles };\n";
@@ -118,6 +118,20 @@ a {
118
118
  color: var(--color-primary);
119
119
  }
120
120
 
121
+ /* Keyboard-focus ring, defined once globally so links don't each re-declare it.
122
+ :focus-visible keeps it keyboard-only (no ring on mouse click); box-shadow
123
+ gives a soft primaryLight glow that follows the element's border-radius. The
124
+ 6px radius mirrors theme.spacing.radius.xs so the ring rounds on links that
125
+ have no radius of their own. Cherry link-buttons (.button-link) are excluded
126
+ via :where() so the exclusion adds no specificity - this keeps the selector
127
+ at (0,1,1) so bespoke per-component :focus-visible rules (inline content
128
+ links, the section bar, the sidebar group rows) still override it. */
129
+ a:focus-visible:where(:not(.button-link)) {
130
+ outline: none;
131
+ border-radius: 6px;
132
+ box-shadow: 0 0 0 2px var(--color-primaryLight);
133
+ }
134
+
121
135
  ol,
122
136
  ul {
123
137
  list-style: none;
@@ -1 +1 @@
1
- export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { focusRing } from \"@/components/layout/SharedStyled\";\nimport { useOnClickOutside } from \"cherry-styled-components\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n min-width: max-content;\n ${focusRing};\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n <>\n {/* Both logos render; .light-only and .dark-only classes in\n GlobalStyles hide the inactive one based on the \"dark\" class\n on <html>. Avoids a JS-driven swap so no flash on first load. */}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"light-only\"\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"dark-only\"\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n </>\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>&#8984;K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
1
+ export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useOnClickOutside } from \"cherry-styled-components\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n min-width: max-content;\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n <>\n {/* Both logos render; .light-only and .dark-only classes in\n GlobalStyles hide the inactive one based on the \"dark\" class\n on <html>. Avoids a JS-driven swap so no flash on first load. */}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"light-only\"\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"dark-only\"\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n </>\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>&#8984;K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
@@ -4,7 +4,6 @@ import { useCallback, useContext, useRef, useState } from "react";
4
4
  import styled, { css } from "styled-components";
5
5
  import Link from "next/link";
6
6
  import { mq, Theme } from "@/app/theme";
7
- import { focusRing } from "@/components/layout/SharedStyled";
8
7
  import { useOnClickOutside } from "cherry-styled-components";
9
8
  import { Search } from "lucide-react";
10
9
  import { Logo } from "@/components/layout/Pictograms";
@@ -60,7 +59,6 @@ const StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>\`
60
59
  & .logo {
61
60
  display: flex;
62
61
  min-width: max-content;
63
- \${focusRing};
64
62
 
65
63
  & svg,
66
64
  & img {
@@ -1 +1 @@
1
- export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -10px;\n padding: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n padding: 0 10px;\n order: unset;\n width: 100%;\n margin: 0;\n justify-content: flex-end;\n }\n`;\n\nconst StyledSectionLink = styled(Link)<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n text-decoration: none;\n padding: 16px 10px;\n white-space: nowrap;\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"500\")};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.gray};\n border-bottom: solid 2px\n ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : \"transparent\"};\n transition: all 0.3s ease;\n min-width: fit-content;\n\n /* Inset ring (not the shared outset focusRing): the scrolling section bar\n clips overflow, so an outset glow gets cropped top/bottom. Drawing it inside\n the link's padding keeps the primaryLight focus look fully visible. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: inset 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nfunction SectionBar({ sections }: SectionBarProps) {\n const pathname = usePathname();\n const currentPath = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n\n const activeSection = sections.find((section) => {\n if (section.slug === \"\") return false;\n return (\n currentPath === section.slug || currentPath.startsWith(section.slug + \"/\")\n );\n });\n\n const activeSectionSlug = activeSection ? activeSection.slug : \"\";\n\n return (\n <StyledSectionBar>\n {sections.map((section) => (\n <StyledSectionLink\n key={section.slug}\n href={section.slug === \"\" ? \"/\" : `/${section.slug}`}\n $isActive={activeSectionSlug === section.slug}\n >\n {section.label}\n </StyledSectionLink>\n ))}\n </StyledSectionBar>\n );\n}\n\nexport { SectionBar };\n";
1
+ export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -10px;\n padding: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n padding: 0 10px;\n order: unset;\n width: 100%;\n margin: 0;\n justify-content: flex-end;\n }\n`;\n\nconst StyledSectionLink = styled(Link)<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n text-decoration: none;\n padding: 16px 10px;\n white-space: nowrap;\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"500\")};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.gray};\n border-bottom: solid 2px\n ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : \"transparent\"};\n transition: all 0.3s ease;\n min-width: fit-content;\n position: relative;\n\n /* The section bar scrolls horizontally, and overflow-x:auto makes it clip\n vertically too, so an edge-drawn ring gets shaved top and bottom. Draw the\n ring on an inset pseudo-element so it sits fully inside the link and can't\n be clipped on any side. */\n &:focus-visible {\n outline: none;\n /* Opt out of the global a:focus-visible ring; the ::after below draws it\n inset so the scroll container can't clip it. */\n box-shadow: none;\n }\n\n &:focus-visible::after {\n content: \"\";\n position: absolute;\n inset: 3px;\n border: solid 2px ${({ theme }) => theme.colors.primaryLight};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n pointer-events: none;\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nfunction SectionBar({ sections }: SectionBarProps) {\n const pathname = usePathname();\n const currentPath = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n\n const activeSection = sections.find((section) => {\n if (section.slug === \"\") return false;\n return (\n currentPath === section.slug || currentPath.startsWith(section.slug + \"/\")\n );\n });\n\n const activeSectionSlug = activeSection ? activeSection.slug : \"\";\n\n return (\n <StyledSectionBar>\n {sections.map((section) => (\n <StyledSectionLink\n key={section.slug}\n href={section.slug === \"\" ? \"/\" : `/${section.slug}`}\n $isActive={activeSectionSlug === section.slug}\n >\n {section.label}\n </StyledSectionLink>\n ))}\n </StyledSectionBar>\n );\n}\n\nexport { SectionBar };\n";
@@ -54,14 +54,26 @@ const StyledSectionLink = styled(Link)<{
54
54
  $isActive ? theme.colors.primary : "transparent"};
55
55
  transition: all 0.3s ease;
56
56
  min-width: fit-content;
57
+ position: relative;
57
58
 
58
- /* Inset ring (not the shared outset focusRing): the scrolling section bar
59
- clips overflow, so an outset glow gets cropped top/bottom. Drawing it inside
60
- the link's padding keeps the primaryLight focus look fully visible. */
59
+ /* The section bar scrolls horizontally, and overflow-x:auto makes it clip
60
+ vertically too, so an edge-drawn ring gets shaved top and bottom. Draw the
61
+ ring on an inset pseudo-element so it sits fully inside the link and can't
62
+ be clipped on any side. */
61
63
  &:focus-visible {
62
64
  outline: none;
65
+ /* Opt out of the global a:focus-visible ring; the ::after below draws it
66
+ inset so the scroll container can't clip it. */
67
+ box-shadow: none;
68
+ }
69
+
70
+ &:focus-visible::after {
71
+ content: "";
72
+ position: absolute;
73
+ inset: 3px;
74
+ border: solid 2px \${({ theme }) => theme.colors.primaryLight};
63
75
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
64
- box-shadow: inset 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
76
+ pointer-events: none;
65
77
  }
66
78
 
67
79
  &:hover {
@@ -1 +1 @@
1
- export declare const sharedStyledTemplate = "\"use client\";\nimport { styledSmall, styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport styled, { css } from \"styled-components\";\n\n/** Slim, theme-aware scrollbar for internal scroll areas (code blocks,\n modals, tables) so the chunky native bar doesn't stand out, especially\n in dark mode. */\nexport const thinScrollbar = css<{ theme: Theme }>`\n scrollbar-width: thin;\n scrollbar-color: ${({ theme }) => theme.colors.grayLight} transparent;\n`;\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\n/** Keyboard-focus ring for bare navigation links (header links, sidebar rows,\n footer links) that don't carry the full interactiveStyles treatment. Mirrors\n the primaryLight glow from interactiveStyles, but uses :focus-visible so the\n ring only shows for keyboard users - not on mouse click. Pair with an element\n that already has a transition for a smooth fade. The small border-radius\n rounds the ring, so don't apply this to elements that set their own radius. */\nexport const focusRing = css<{ theme?: Theme }>`\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n\n /* Same primaryLight focus glow as focusRing, but layered over the link's\n own box-shadow underline (same property) so the underline is preserved. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow:\n 0 2px 0 0 ${({ theme }) => theme.colors.primary},\n 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n ${thinScrollbar};\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.accent};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
1
+ export declare const sharedStyledTemplate = "\"use client\";\nimport { styledSmall, styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport styled, { css } from \"styled-components\";\n\n/** Slim, theme-aware scrollbar for internal scroll areas (code blocks,\n modals, tables) so the chunky native bar doesn't stand out, especially\n in dark mode. */\nexport const thinScrollbar = css<{ theme: Theme }>`\n scrollbar-width: thin;\n scrollbar-color: ${({ theme }) => theme.colors.grayLight} transparent;\n`;\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n\n /* Same primaryLight focus glow as the global a:focus-visible ring, but\n layered over the link's own box-shadow underline (same property) so the\n underline is preserved. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow:\n 0 2px 0 0 ${({ theme }) => theme.colors.primary},\n 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n ${thinScrollbar};\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.accent};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
@@ -30,20 +30,6 @@ export const interactiveStyles = css<{ theme: Theme }>\`
30
30
  }
31
31
  \`;
32
32
 
33
- /** Keyboard-focus ring for bare navigation links (header links, sidebar rows,
34
- footer links) that don't carry the full interactiveStyles treatment. Mirrors
35
- the primaryLight glow from interactiveStyles, but uses :focus-visible so the
36
- ring only shows for keyboard users - not on mouse click. Pair with an element
37
- that already has a transition for a smooth fade. The small border-radius
38
- rounds the ring, so don't apply this to elements that set their own radius. */
39
- export const focusRing = css<{ theme?: Theme }>\`
40
- &:focus-visible {
41
- outline: none;
42
- border-radius: \${({ theme }) => theme.spacing.radius.xs};
43
- box-shadow: 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
44
- }
45
- \`;
46
-
47
33
  export const styledAnchor = css<{ theme: Theme }>\`
48
34
  & a:not([class]):not(:has(img)) {
49
35
  color: inherit;
@@ -56,14 +42,15 @@ export const styledAnchor = css<{ theme: Theme }>\`
56
42
  box-shadow: 0 1px 0 0 \${({ theme }) => theme.colors.primary};
57
43
  }
58
44
 
59
- /* Same primaryLight focus glow as focusRing, but layered over the link's
60
- own box-shadow underline (same property) so the underline is preserved. */
45
+ /* Same primaryLight focus glow as the global a:focus-visible ring, but
46
+ layered over the link's own box-shadow underline (same property) so the
47
+ underline is preserved. */
61
48
  &:focus-visible {
62
49
  outline: none;
63
50
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
64
51
  box-shadow:
65
52
  0 2px 0 0 \${({ theme }) => theme.colors.primary},
66
- 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
53
+ 0 0 0 2px \${({ theme }) => theme.colors.primaryLight};
67
54
  }
68
55
  }
69
56
  \`;
@@ -1 +1 @@
1
- export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 2\nsection: \"Platform\"\n---\n\n# Navigation Settings\n\nThe Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.\n\n## Structure\n\nNavigation is organized into three levels:\n\n- **Section** - a top-level area of your site (e.g., \"Docs\", \"API Reference\"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.\n- **Category** - a group label within a section's sidebar (e.g., \"Getting Started\")\n- **Link** - an individual page entry within a category, defined by a slug and title\n\n## Drag-and-drop reordering\n\nYou can reorder items at every level by dragging their handles:\n\n- **Drag categories** between sections or within the same section to change their position\n- **Drag links** between categories or within the same category to reorder them\n\n## Managing sections\n\n- Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.\n- Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.\n- Delete a section from its edit modal using the delete button. The root section cannot be deleted.\n\n### The default section\n\nOne section should have an empty slug (`\"\"`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n### Frontmatter-based sections\n\nYou can also define sections purely through page frontmatter without using the Navigation Builder. Add a `section` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.\n\n## Managing categories\n\n- Click **Add Category** within a section to create a new group\n- Click the **edit** button on a category to open its edit modal, where you can rename it or delete it\n\n## Adding links\n\nWithin each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:\n\n- **Search** by file name to filter the list\n- **Select** one or more files using checkboxes\n- Click **Add** to insert them as links in the category\n\nTo remove a link, click the **delete** button next to it.\n\n## Regenerate from files\n\nThe toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (`category`, `categoryOrder`, and `order` fields). A confirmation modal appears before any existing manual navigation is replaced.\n\n<Callout type=\"warning\">\n Regenerating from files replaces your current navigation structure. This cannot be undone.\n</Callout>\n\n## Auto-generated vs. manual\n\nIf you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.\n\n## How it works\n\nWhen you save, the Navigation Builder writes two files to your repository as pending changes:\n\n- `navigation.json` - the category and link structure for each section\n- `sections.json` - the list of sections with their labels, slugs, and directories\n\n**Array format** for single-section sites (`navigation.json`):\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [{ \"slug\": \"getting-started\", \"title\": \"Quick Start\" }]\n }\n]\n```\n\n**Object format** for multi-section sites (`navigation.json`):\n\n```json\n{\n \"\": [\n { \"label\": \"General\", \"links\": [{ \"slug\": \"\", \"title\": \"Introduction\" }] }\n ],\n \"api\": [\n {\n \"label\": \"Auth\",\n \"links\": [{ \"slug\": \"api/auth\", \"title\": \"Authentication\" }]\n }\n ]\n}\n```\n\n**Sections** (`sections.json`):\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" }\n]\n```";
1
+ export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder.\"\ndate: \"2026-07-06\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 2\nsection: \"Platform\"\n---\n\n# Navigation Settings\n\nThe Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.\n\n## Structure\n\nNavigation is organized into these levels:\n\n- **Section** - a top-level area of your site (e.g., \"Docs\", \"API Reference\"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.\n- **Category** - a group label within a section's sidebar (e.g., \"Getting Started\"). Categories can have an optional icon.\n- **Link** - an individual page entry within a category, defined by a slug and title, with an optional icon.\n- **Group** - a link that holds its own nested links, rendered as a collapsible item in the sidebar. Groups can nest as deep as you need. See [Nested groups](#nested-groups).\n\n## Drag-and-drop reordering\n\nYou can reorder items at every level by dragging their handles:\n\n- **Drag categories** between sections or within the same section to change their position\n- **Drag links** between categories or within the same category to reorder them\n\n## Managing sections\n\n- Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.\n- Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.\n- Delete a section from its edit modal using the delete button. The root section cannot be deleted.\n\n### The default section\n\nOne section should have an empty slug (`\"\"`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n### Frontmatter-based sections\n\nYou can also define sections purely through page frontmatter without using the Navigation Builder. Add a `section` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.\n\n## Managing categories\n\n- Click **Add Category** within a section to create a new group\n- Click the **edit** button on a category to open its **Edit Category** modal, where you can rename it, pick an icon, or delete it\n\n## Adding links\n\nWithin each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:\n\n- **Search** by file name to filter the list\n- **Select** one or more files using checkboxes\n- Click **Add** to insert them as links in the category\n\n### Editing a link\n\n- **Double-click** a link's title to rename it inline.\n- Click the **edit** button on a link to open the **Edit Link** modal, where you can change its title, slug, and icon. Leaving the slug empty makes a leaf link point at the index page; for a group it makes the group a non-clickable label.\n- Click the **delete** button next to a link to remove it.\n\n## Icons\n\nYou can show a [Lucide](https://lucide.dev/icons) icon next to any category or link to make the sidebar easier to scan.\n\n- Set a **category icon** from the Edit Category modal.\n- Set a **link icon** from the Edit Link modal.\n\nBoth use a searchable icon picker, and icon names are stored in kebab-case (e.g. `rocket`, `book-open`, `settings`). You can also set icons from page frontmatter using the `navIcon` (link) and `categoryIcon` (category) fields, which the **Regenerate** button picks up.\n\n## Nested groups\n\nA link can hold its own child links, rendering as a collapsible group in the sidebar. Nesting can go as deep as you need.\n\nThere are two ways to create a group:\n\n- Click **Add group** (in a category or inside another group) to create a collapsible group. Give it a title in the Edit Link modal, then leave its slug empty to make it a plain, non-clickable label, or set a slug to make the group header link to its own page.\n- Click **Add pages inside** on an existing link to nest pages under it. The link keeps its own slug and becomes the group header.\n\nInside a group, use **Add files** to add pages and **Add group** to nest another group. Toggle the chevron on a group to collapse or expand it in the builder.\n\n## Regenerate from files\n\nThe toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (`category`, `categoryOrder`, `order`, `navIcon`, and `categoryIcon` fields). A confirmation modal appears before any existing manual navigation is replaced.\n\n<Callout type=\"warning\">\n Regenerating from files replaces your current navigation structure. This cannot be undone.\n</Callout>\n\n## Auto-generated vs. manual\n\nIf you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.\n\n## How it works\n\nWhen you save, the Navigation Builder writes two files to your repository as pending changes:\n\n- `navigation.json` - the category and link structure for each section\n- `sections.json` - the list of sections with their labels, slugs, and directories\n\nIcons are written as an `icon` field on a category or link, and nested groups as a `links` array on a link.\n\n**Array format** for single-section sites (`navigation.json`):\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"icon\": \"rocket\",\n \"links\": [\n {\n \"slug\": \"getting-started\",\n \"title\": \"Quick Start\",\n \"icon\": \"book-open\"\n },\n {\n \"title\": \"Guides\",\n \"icon\": \"compass\",\n \"links\": [{ \"slug\": \"guides/deploy\", \"title\": \"Deploying\" }]\n }\n ]\n }\n]\n```\n\n**Object format** for multi-section sites (`navigation.json`):\n\n```json\n{\n \"\": [\n { \"label\": \"General\", \"links\": [{ \"slug\": \"\", \"title\": \"Introduction\" }] }\n ],\n \"api\": [\n {\n \"label\": \"Auth\",\n \"links\": [{ \"slug\": \"api/auth\", \"title\": \"Authentication\" }]\n }\n ]\n}\n```\n\n**Sections** (`sections.json`):\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" }\n]\n```";
@@ -1,7 +1,7 @@
1
1
  export const platformNavigationSettingsMdxTemplate = `---
2
2
  title: "Navigation Settings"
3
3
  description: "Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder."
4
- date: "2026-02-24"
4
+ date: "2026-07-06"
5
5
  category: "Configuration"
6
6
  categoryOrder: 2
7
7
  order: 2
@@ -14,11 +14,12 @@ The Navigation Builder lets you define your sidebar structure through a visual,
14
14
 
15
15
  ## Structure
16
16
 
17
- Navigation is organized into three levels:
17
+ Navigation is organized into these levels:
18
18
 
19
19
  - **Section** - a top-level area of your site (e.g., "Docs", "API Reference"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.
20
- - **Category** - a group label within a section's sidebar (e.g., "Getting Started")
21
- - **Link** - an individual page entry within a category, defined by a slug and title
20
+ - **Category** - a group label within a section's sidebar (e.g., "Getting Started"). Categories can have an optional icon.
21
+ - **Link** - an individual page entry within a category, defined by a slug and title, with an optional icon.
22
+ - **Group** - a link that holds its own nested links, rendered as a collapsible item in the sidebar. Groups can nest as deep as you need. See [Nested groups](#nested-groups).
22
23
 
23
24
  ## Drag-and-drop reordering
24
25
 
@@ -44,7 +45,7 @@ You can also define sections purely through page frontmatter without using the N
44
45
  ## Managing categories
45
46
 
46
47
  - Click **Add Category** within a section to create a new group
47
- - Click the **edit** button on a category to open its edit modal, where you can rename it or delete it
48
+ - Click the **edit** button on a category to open its **Edit Category** modal, where you can rename it, pick an icon, or delete it
48
49
 
49
50
  ## Adding links
50
51
 
@@ -54,11 +55,35 @@ Within each category, click **Add files** to open a popover that lists available
54
55
  - **Select** one or more files using checkboxes
55
56
  - Click **Add** to insert them as links in the category
56
57
 
57
- To remove a link, click the **delete** button next to it.
58
+ ### Editing a link
59
+
60
+ - **Double-click** a link's title to rename it inline.
61
+ - Click the **edit** button on a link to open the **Edit Link** modal, where you can change its title, slug, and icon. Leaving the slug empty makes a leaf link point at the index page; for a group it makes the group a non-clickable label.
62
+ - Click the **delete** button next to a link to remove it.
63
+
64
+ ## Icons
65
+
66
+ You can show a [Lucide](https://lucide.dev/icons) icon next to any category or link to make the sidebar easier to scan.
67
+
68
+ - Set a **category icon** from the Edit Category modal.
69
+ - Set a **link icon** from the Edit Link modal.
70
+
71
+ Both use a searchable icon picker, and icon names are stored in kebab-case (e.g. \`rocket\`, \`book-open\`, \`settings\`). You can also set icons from page frontmatter using the \`navIcon\` (link) and \`categoryIcon\` (category) fields, which the **Regenerate** button picks up.
72
+
73
+ ## Nested groups
74
+
75
+ A link can hold its own child links, rendering as a collapsible group in the sidebar. Nesting can go as deep as you need.
76
+
77
+ There are two ways to create a group:
78
+
79
+ - Click **Add group** (in a category or inside another group) to create a collapsible group. Give it a title in the Edit Link modal, then leave its slug empty to make it a plain, non-clickable label, or set a slug to make the group header link to its own page.
80
+ - Click **Add pages inside** on an existing link to nest pages under it. The link keeps its own slug and becomes the group header.
81
+
82
+ Inside a group, use **Add files** to add pages and **Add group** to nest another group. Toggle the chevron on a group to collapse or expand it in the builder.
58
83
 
59
84
  ## Regenerate from files
60
85
 
61
- The toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (\`category\`, \`categoryOrder\`, and \`order\` fields). A confirmation modal appears before any existing manual navigation is replaced.
86
+ The toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (\`category\`, \`categoryOrder\`, \`order\`, \`navIcon\`, and \`categoryIcon\` fields). A confirmation modal appears before any existing manual navigation is replaced.
62
87
 
63
88
  <Callout type="warning">
64
89
  Regenerating from files replaces your current navigation structure. This cannot be undone.
@@ -75,13 +100,27 @@ When you save, the Navigation Builder writes two files to your repository as pen
75
100
  - \`navigation.json\` - the category and link structure for each section
76
101
  - \`sections.json\` - the list of sections with their labels, slugs, and directories
77
102
 
103
+ Icons are written as an \`icon\` field on a category or link, and nested groups as a \`links\` array on a link.
104
+
78
105
  **Array format** for single-section sites (\`navigation.json\`):
79
106
 
80
107
  \`\`\`json
81
108
  [
82
109
  {
83
110
  "label": "Getting Started",
84
- "links": [{ "slug": "getting-started", "title": "Quick Start" }]
111
+ "icon": "rocket",
112
+ "links": [
113
+ {
114
+ "slug": "getting-started",
115
+ "title": "Quick Start",
116
+ "icon": "book-open"
117
+ },
118
+ {
119
+ "title": "Guides",
120
+ "icon": "compass",
121
+ "links": [{ "slug": "guides/deploy", "title": "Deploying" }]
122
+ }
123
+ ]
85
124
  }
86
125
  ]
87
126
  \`\`\`
@@ -1 +1 @@
1
- export declare const updateMdxTemplate = "---\ntitle: \"Update\"\ndescription: \"Easily manage and present change history.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 12\n---\n\n# Update\n\nEasily manage and present change history.\n\nThe `Update` component helps you display release notes, version details, and changelogs in a standardized format.\n\nEach `Update` label is added to the \"On this page\" sidebar and gets its own anchor, so you can link directly to a specific entry.\n\n<Update label=\"Example\" description=\"v0.0.1\">\n ## Example entry\n\nYou can include anything here - images, code snippets, or a bullet list of modifications.\n\n![Demo Image](https://docs.doccupine.com/demo.png)\n\n### Key additions\n\n- Fully responsive layout\n- Individual anchor for each update\n- Automatic RSS feed entry generation\n\n</Update>\n\n## Update Usage\n\nYou can combine multiple `Update` components to build complete changelogs.\n\n```mdx\n<Update label=\"Example\" description=\"v0.0.1\">\n ## Example entry\n\nYou can include anything here - images, code snippets, or a bullet list of modifications.\n\n![Demo Image](https://docs.doccupine.com/demo.png)\n\n### Key additions\n\n- Fully responsive layout\n- Individual anchor for each update\n- Automatic RSS feed entry generation\n\n</Update>\n```\n\n## Properties\n\n<Field value=\"label\" type=\"string\" required>\n The label of the update. It also appears in the page's \"On this page\"\n navigation and acts as a deep-link anchor to this entry.\n</Field>\n\n<Field value=\"description\" type=\"string\" required>\n The description of the update.\n</Field>\n\n<Field value=\"children\" type=\"node\" required>\n The content of the update.\n</Field>";
1
+ export declare const updateMdxTemplate = "---\ntitle: \"Update\"\ndescription: \"Easily manage and present change history.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 12\n---\n\n# Update\n\nEasily manage and present change history.\n\nThe `Update` component helps you display release notes, version details, and changelogs in a standardized format.\n\nEach `Update` label is added to the \"On this page\" sidebar and gets its own anchor, so you can link directly to a specific entry.\n\n<Update label=\"v0.0.1\" description=\"Example\">\n ## Example entry\n\nYou can include anything here - images, code snippets, or a bullet list of modifications.\n\n![Demo Image](https://docs.doccupine.com/demo.png)\n\n### Key additions\n\n- Fully responsive layout\n- Individual anchor for each update\n- Automatic RSS feed entry generation\n\n</Update>\n\n## Update Usage\n\nYou can combine multiple `Update` components to build complete changelogs.\n\n```mdx\n<Update label=\"v0.0.1\" description=\"Example\">\n ## Example entry\n\nYou can include anything here - images, code snippets, or a bullet list of modifications.\n\n![Demo Image](https://docs.doccupine.com/demo.png)\n\n### Key additions\n\n- Fully responsive layout\n- Individual anchor for each update\n- Automatic RSS feed entry generation\n\n</Update>\n```\n\n## Properties\n\n<Field value=\"label\" type=\"string\" required>\n The label of the update. It also appears in the page's \"On this page\"\n navigation and acts as a deep-link anchor to this entry.\n</Field>\n\n<Field value=\"description\" type=\"string\" required>\n The description of the update.\n</Field>\n\n<Field value=\"children\" type=\"node\" required>\n The content of the update.\n</Field>";
@@ -15,7 +15,7 @@ The \`Update\` component helps you display release notes, version details, and c
15
15
 
16
16
  Each \`Update\` label is added to the "On this page" sidebar and gets its own anchor, so you can link directly to a specific entry.
17
17
 
18
- <Update label="Example" description="v0.0.1">
18
+ <Update label="v0.0.1" description="Example">
19
19
  ## Example entry
20
20
 
21
21
  You can include anything here - images, code snippets, or a bullet list of modifications.
@@ -35,7 +35,7 @@ You can include anything here - images, code snippets, or a bullet list of modif
35
35
  You can combine multiple \`Update\` components to build complete changelogs.
36
36
 
37
37
  \`\`\`mdx
38
- <Update label="Example" description="v0.0.1">
38
+ <Update label="v0.0.1" description="Example">
39
39
  ## Example entry
40
40
 
41
41
  You can include anything here - images, code snippets, or a bullet list of modifications.
@@ -17,7 +17,7 @@ export const packageJsonTemplate = JSON.stringify({
17
17
  "@mdx-js/react": "^3.1.1",
18
18
  "@modelcontextprotocol/sdk": "^1.29.0",
19
19
  "@posthog/react": "^1.10.3",
20
- "cherry-styled-components": "^0.2.8",
20
+ "cherry-styled-components": "^0.2.9",
21
21
  langchain: "^1.5.2",
22
22
  "lucide-react": "^1.23.0",
23
23
  minisearch: "^7.2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {