doccupine 0.0.95 → 0.0.97
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.
- package/dist/index.js +4 -1
- package/dist/lib/layout.js +30 -10
- package/dist/lib/structures.d.ts +1 -0
- package/dist/lib/structures.js +9 -6
- package/dist/templates/app/theme.d.ts +1 -1
- package/dist/templates/app/theme.js +36 -54
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +4 -0
- package/dist/templates/components/SearchDocs.d.ts +1 -1
- package/dist/templates/components/SearchDocs.js +26 -17
- package/dist/templates/components/SearchModalContent.d.ts +1 -1
- package/dist/templates/components/SearchModalContent.js +2 -4
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +3 -9
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +22 -27
- package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/CherryThemeProvider.js +26 -6
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +25 -38
- package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
- package/dist/templates/components/layout/DemoTheme.js +45 -14
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +1 -1
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +9 -0
- package/dist/templates/components/layout/SiteGate.d.ts +1 -1
- package/dist/templates/components/layout/SiteGate.js +3 -9
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +2 -0
- package/dist/templates/package.js +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export const demoThemeTemplate = `"use client";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { useTheme } from "styled-components";
|
|
4
|
+
import { ThemeContext } from "cherry-styled-components";
|
|
5
|
+
import {
|
|
6
|
+
buildColors,
|
|
7
|
+
colorsLight,
|
|
8
|
+
colorsDark,
|
|
9
|
+
theme,
|
|
10
|
+
themeDark,
|
|
11
|
+
Theme,
|
|
12
|
+
} from "@/app/theme";
|
|
2
13
|
import { Button } from "@/components/layout/Button";
|
|
3
|
-
import { useThemeOverride } from "@/components/layout/ClientThemeProvider";
|
|
4
14
|
import { Columns } from "@/components/layout/Columns";
|
|
5
15
|
|
|
6
16
|
interface DemoThemeProps {
|
|
@@ -9,11 +19,13 @@ interface DemoThemeProps {
|
|
|
9
19
|
|
|
10
20
|
type Palette = Record<string, string>;
|
|
11
21
|
|
|
12
|
-
// Each preset is light + dark color overrides
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
22
|
+
// Each preset is light + dark color overrides, mirroring what a user editing
|
|
23
|
+
// theme.json would see. Applying one does two things: swaps in a theme object
|
|
24
|
+
// rebuilt with the overridden palette (Cherry's setTheme, which also persists
|
|
25
|
+
// the mode), and mirrors the overrides onto the --color-* CSS custom
|
|
26
|
+
// properties on <html> for the plain-CSS consumers that read variables
|
|
27
|
+
// (e.g. Callout's and Code's :root.dark blocks). Reset clears both so the
|
|
28
|
+
// defaults take over again.
|
|
17
29
|
const PRESETS: Record<
|
|
18
30
|
DemoThemeProps["variant"],
|
|
19
31
|
{ light: Palette; dark: Palette; label: string }
|
|
@@ -61,28 +73,47 @@ const PRESETS: Record<
|
|
|
61
73
|
|
|
62
74
|
const PRESET_KEYS = ["primaryLight", "primary", "primaryDark"] as const;
|
|
63
75
|
|
|
64
|
-
function
|
|
76
|
+
function applyPresetVars(palette: Palette) {
|
|
65
77
|
const root = document.documentElement;
|
|
66
78
|
for (const key of PRESET_KEYS) {
|
|
67
79
|
if (palette[key]) root.style.setProperty(\`--color-\${key}\`, palette[key]);
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
function
|
|
83
|
+
function clearPresetVars() {
|
|
72
84
|
const root = document.documentElement;
|
|
73
85
|
for (const key of PRESET_KEYS) {
|
|
74
86
|
root.style.removeProperty(\`--color-\${key}\`);
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
// Rebuilds a mode's theme with the preset palette merged in, so the semantic
|
|
91
|
+
// tokens (accent, accentStrong, …) derive from the preset colors exactly like
|
|
92
|
+
// they would from a theme.json override.
|
|
93
|
+
function presetTheme(palette: Palette, dark: boolean): Theme {
|
|
94
|
+
const base = dark ? themeDark : theme;
|
|
95
|
+
const paletteBase = dark ? colorsDark : colorsLight;
|
|
96
|
+
return {
|
|
97
|
+
...base,
|
|
98
|
+
colors: buildColors({ ...paletteBase, ...palette }, dark),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
78
102
|
function DemoTheme({ variant }: DemoThemeProps) {
|
|
79
|
-
const {
|
|
103
|
+
const { setTheme } = useContext(ThemeContext);
|
|
104
|
+
const activeTheme = useTheme() as Theme;
|
|
80
105
|
const preset = PRESETS[variant];
|
|
81
106
|
|
|
82
107
|
if (!preset) {
|
|
83
108
|
return (
|
|
84
109
|
<Columns cols={2}>
|
|
85
|
-
<Button
|
|
110
|
+
<Button
|
|
111
|
+
onClick={() => {
|
|
112
|
+
clearPresetVars();
|
|
113
|
+
setTheme(activeTheme.isDark ? themeDark : theme);
|
|
114
|
+
}}
|
|
115
|
+
fullWidth
|
|
116
|
+
>
|
|
86
117
|
Reset to Default
|
|
87
118
|
</Button>
|
|
88
119
|
</Columns>
|
|
@@ -93,8 +124,8 @@ function DemoTheme({ variant }: DemoThemeProps) {
|
|
|
93
124
|
<Columns cols={2}>
|
|
94
125
|
<Button
|
|
95
126
|
onClick={() => {
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
applyPresetVars(preset.light);
|
|
128
|
+
setTheme(presetTheme(preset.light, false));
|
|
98
129
|
}}
|
|
99
130
|
icon="sun"
|
|
100
131
|
fullWidth
|
|
@@ -104,8 +135,8 @@ function DemoTheme({ variant }: DemoThemeProps) {
|
|
|
104
135
|
<Button
|
|
105
136
|
outline
|
|
106
137
|
onClick={() => {
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
applyPresetVars(preset.dark);
|
|
139
|
+
setTheme(presetTheme(preset.dark, true));
|
|
109
140
|
}}
|
|
110
141
|
icon="moon-star"
|
|
111
142
|
fullWidth
|
|
@@ -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 { useOnClickOutside } from \"
|
|
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\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>⌘K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
|
|
@@ -4,7 +4,7 @@ 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 { useOnClickOutside } from "
|
|
7
|
+
import { useOnClickOutside } from "cherry-styled-components";
|
|
8
8
|
import { Search } from "lucide-react";
|
|
9
9
|
import { Logo } from "@/components/layout/Pictograms";
|
|
10
10
|
import { ChatContext, ChatButtonCTA } from "@/components/Chat";
|
|
@@ -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\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`;\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 }\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`;\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";
|
|
@@ -3,6 +3,14 @@ import { styledSmall, styledText } from "cherry-styled-components";
|
|
|
3
3
|
import { mq, Theme } from "@/app/theme";
|
|
4
4
|
import styled, { css } from "styled-components";
|
|
5
5
|
|
|
6
|
+
/** Slim, theme-aware scrollbar for internal scroll areas (code blocks,
|
|
7
|
+
modals, tables) so the chunky native bar doesn't stand out, especially
|
|
8
|
+
in dark mode. */
|
|
9
|
+
export const thinScrollbar = css<{ theme: Theme }>\`
|
|
10
|
+
scrollbar-width: thin;
|
|
11
|
+
scrollbar-color: \${({ theme }) => theme.colors.grayLight} transparent;
|
|
12
|
+
\`;
|
|
13
|
+
|
|
6
14
|
export const interactiveStyles = css<{ theme: Theme }>\`
|
|
7
15
|
transition: all 0.3s ease;
|
|
8
16
|
border: solid 1px transparent;
|
|
@@ -113,6 +121,7 @@ export const styledTable = css<{ theme: Theme }>\`
|
|
|
113
121
|
& .table-wrapper {
|
|
114
122
|
overflow-x: auto;
|
|
115
123
|
width: 100%;
|
|
124
|
+
\${thinScrollbar};
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
& table {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const siteGateComponentTemplate = "\"use client\";\nimport {
|
|
1
|
+
export declare const siteGateComponentTemplate = "\"use client\";\nimport { useState, type FormEvent } from \"react\";\nimport styled from \"styled-components\";\nimport { Input, ThemeToggle } from \"cherry-styled-components\";\nimport { Lock } from \"lucide-react\";\nimport { Theme } from \"@/app/theme\";\nimport { Button } from \"@/components/layout/Button\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { config } from \"@/utils/config\";\n\nconst StyledWrapper = styled.div<{ theme: Theme }>`\n min-height: 100dvh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: ${({ theme }) => theme.colors.light};\n`;\n\nconst StyledInner = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 20px;\n width: 100%;\n max-width: 380px;\n`;\n\nconst StyledFooter = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n`;\n\nconst StyledBranding = styled.p<{ theme: Theme }>`\n margin: 0;\n font-size: 14px;\n color: ${({ theme }) => theme.colors.grayDark};\n\n & a {\n color: ${({ theme }) => theme.colors.accent};\n font-weight: 700;\n text-decoration: none;\n transition: all 0.3s ease;\n }\n\n & a:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nconst StyledCard = styled.div<{ theme: Theme }>`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 20px;\n width: 100%;\n padding: 40px 32px;\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n text-align: center;\n`;\n\nconst StyledIcon = styled.div<{ theme: Theme }>`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n color: ${({ theme }) => theme.colors.accent};\n background: ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n width: 22px;\n height: 22px;\n }\n`;\n\nconst StyledTitle = styled.h1<{ theme: Theme }>`\n margin: 0;\n font-size: 22px;\n color: ${({ theme }) => theme.colors.accent};\n`;\n\nconst StyledLede = styled.p<{ theme: Theme }>`\n margin: 0;\n font-size: 15px;\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nconst StyledForm = styled.form`\n display: flex;\n flex-direction: column;\n gap: 20px;\n width: 100%;\n`;\n\nconst StyledAlert = styled.div`\n width: 100%;\n text-align: left;\n`;\n\nexport function SiteGate({ hideBranding }: { hideBranding?: boolean }) {\n const [password, setPassword] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"error\">(\"idle\");\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!password || status === \"loading\") return;\n\n setStatus(\"loading\");\n try {\n const res = await fetch(\"/api/gate\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ password }),\n });\n if (res.ok) {\n window.location.reload();\n return;\n }\n setStatus(\"error\");\n } catch {\n setStatus(\"error\");\n }\n }\n\n return (\n <StyledWrapper>\n <StyledInner>\n <StyledCard>\n <StyledIcon>\n <Lock />\n </StyledIcon>\n <StyledTitle>{config.name || \"Documentation\"}</StyledTitle>\n <StyledLede>This site is password protected.</StyledLede>\n <StyledForm onSubmit={handleSubmit} noValidate>\n <Input\n type=\"password\"\n name=\"password\"\n autoComplete=\"current-password\"\n placeholder=\"Enter password\"\n aria-label=\"Password\"\n autoFocus\n value={password}\n $error={status === \"error\"}\n $fullWidth\n onChange={(e) => {\n setPassword(e.target.value);\n if (status === \"error\") setStatus(\"idle\");\n }}\n />\n <Button\n type=\"submit\"\n fullWidth\n disabled={status === \"loading\" || !password}\n >\n {status === \"loading\" ? \"Unlocking...\" : \"Enter\"}\n </Button>\n </StyledForm>\n {status === \"error\" && (\n <StyledAlert>\n <Callout type=\"danger\">\n <p>Incorrect password. Try again.</p>\n </Callout>\n </StyledAlert>\n )}\n </StyledCard>\n <StyledFooter>\n <ThemeToggle />\n {!hideBranding && (\n <StyledBranding>\n Powered by <a href=\"https://doccupine.com\">Doccupine</a>\n </StyledBranding>\n )}\n </StyledFooter>\n </StyledInner>\n </StyledWrapper>\n );\n}\n";
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
export const siteGateComponentTemplate = `"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { useState, type FormEvent } from "react";
|
|
3
3
|
import styled from "styled-components";
|
|
4
|
-
import { Input } from "cherry-styled-components";
|
|
4
|
+
import { Input, ThemeToggle } from "cherry-styled-components";
|
|
5
5
|
import { Lock } from "lucide-react";
|
|
6
6
|
import { Theme } from "@/app/theme";
|
|
7
7
|
import { Button } from "@/components/layout/Button";
|
|
8
8
|
import { Callout } from "@/components/layout/Callout";
|
|
9
|
-
import {
|
|
10
|
-
ToggleTheme,
|
|
11
|
-
ToggleThemeLoading,
|
|
12
|
-
} from "@/components/layout/ThemeToggle";
|
|
13
9
|
import { config } from "@/utils/config";
|
|
14
10
|
|
|
15
11
|
const StyledWrapper = styled.div<{ theme: Theme }>\`
|
|
@@ -175,9 +171,7 @@ export function SiteGate({ hideBranding }: { hideBranding?: boolean }) {
|
|
|
175
171
|
)}
|
|
176
172
|
</StyledCard>
|
|
177
173
|
<StyledFooter>
|
|
178
|
-
<
|
|
179
|
-
<ToggleTheme />
|
|
180
|
-
</Suspense>
|
|
174
|
+
<ThemeToggle />
|
|
181
175
|
{!hideBranding && (
|
|
182
176
|
<StyledBranding>
|
|
183
177
|
Powered by <a href="https://doccupine.com">Doccupine</a>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\ninterface TabContentProps {\n title: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: color-mix(\n in srgb,\n ${theme.colors.primaryLight} 10%,\n transparent\n );\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
|
|
1
|
+
export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\ninterface TabContentProps {\n title: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n ${thinScrollbar};\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: color-mix(\n in srgb,\n ${theme.colors.primaryLight} 10%,\n transparent\n );\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
|
|
@@ -3,6 +3,7 @@ import { Theme } from "@/app/theme";
|
|
|
3
3
|
import { styledText } from "cherry-styled-components";
|
|
4
4
|
import React, { useState, ReactNode } from "react";
|
|
5
5
|
import styled, { css } from "styled-components";
|
|
6
|
+
import { thinScrollbar } from "@/components/layout/SharedStyled";
|
|
6
7
|
interface TabContentProps {
|
|
7
8
|
title: string;
|
|
8
9
|
children: ReactNode;
|
|
@@ -23,6 +24,7 @@ const TabsList = styled.div<{ theme: Theme }>\`
|
|
|
23
24
|
background-color: \${({ theme }) => theme.colors.light};
|
|
24
25
|
border: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
25
26
|
overflow-x: auto;
|
|
27
|
+
\${thinScrollbar};
|
|
26
28
|
\`;
|
|
27
29
|
const TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
28
30
|
flex: 1;
|
|
@@ -17,13 +17,13 @@ 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.
|
|
20
|
+
"cherry-styled-components": "^0.2.5",
|
|
21
21
|
langchain: "^1.5.2",
|
|
22
22
|
"lucide-react": "^1.23.0",
|
|
23
23
|
minisearch: "^7.2.0",
|
|
24
24
|
next: "16.2.10",
|
|
25
25
|
"next-mdx-remote": "^6.0.0",
|
|
26
|
-
"posthog-js": "^1.396.
|
|
26
|
+
"posthog-js": "^1.396.6",
|
|
27
27
|
"posthog-node": "^5.39.4",
|
|
28
28
|
react: "19.2.7",
|
|
29
29
|
"react-dom": "19.2.7",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doccupine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.97",
|
|
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": {
|