doccupine 0.0.33 → 0.0.35
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 +5 -0
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +6 -8
- package/dist/templates/components/DocsSideBar.d.ts +1 -1
- package/dist/templates/components/DocsSideBar.js +3 -1
- package/dist/templates/components/layout/Accordion.d.ts +1 -1
- package/dist/templates/components/layout/Accordion.js +2 -4
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +7 -8
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +4 -6
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +2 -0
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +30 -42
- package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
- package/dist/templates/components/layout/StaticLinks.js +3 -5
- package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
- package/dist/templates/components/layout/ThemeToggle.js +7 -8
- package/dist/templates/eslint.config.d.ts +1 -0
- package/dist/templates/eslint.config.js +6 -0
- package/dist/templates/mdx/media-and-assets.mdx.d.ts +1 -1
- package/dist/templates/mdx/media-and-assets.mdx.js +2 -2
- package/dist/templates/package.js +5 -6
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import chalk from "chalk";
|
|
|
9
9
|
import prompts from "prompts";
|
|
10
10
|
import { envExampleTemplate } from "./templates/env.example.js";
|
|
11
11
|
import { gitignoreTemplate } from "./templates/gitignore.js";
|
|
12
|
+
import { eslintConfigTeamplate } from "./templates/eslint.config.js";
|
|
12
13
|
import { nextConfigTemplate } from "./templates/next.config.js";
|
|
13
14
|
import { packageJsonTemplate } from "./templates/package.js";
|
|
14
15
|
import { proxyTemplate } from "./templates/proxy.js";
|
|
@@ -196,6 +197,7 @@ class MDXToNextJSGenerator {
|
|
|
196
197
|
".env.example": this.generateEnvExample(),
|
|
197
198
|
".gitignore": this.generateGitIgnore(),
|
|
198
199
|
"config.json": this.generateConfig(),
|
|
200
|
+
"eslint.config.mjs": this.generateESLintConfig(),
|
|
199
201
|
"links.json": this.generateLinksConfig(),
|
|
200
202
|
"navigation.json": this.generateNavigationConfig(),
|
|
201
203
|
"next.config.ts": this.generateNextConfig(),
|
|
@@ -625,6 +627,9 @@ class MDXToNextJSGenerator {
|
|
|
625
627
|
generateConfig() {
|
|
626
628
|
return `{}`;
|
|
627
629
|
}
|
|
630
|
+
generateESLintConfig() {
|
|
631
|
+
return eslintConfigTeamplate;
|
|
632
|
+
}
|
|
628
633
|
generateLinksConfig() {
|
|
629
634
|
return `[]`;
|
|
630
635
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const chatTemplate = "\"use client\";\nimport React, {\n createContext,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Button } from \"cherry-styled-components/src/lib\";\nimport { ArrowUp, LoaderPinwheel, Sparkles, X } from \"lucide-react\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport { MDXRemote, MDXRemoteSerializeResult } from \"next-mdx-remote\";\nimport { serialize } from \"next-mdx-remote/serialize\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport links from \"@/links.json\";\n\nconst styledText = css<{ theme: Theme }>`\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: ${({ theme }) => theme.lineHeights.small.lg};\n }\n`;\n\nconst StyledChat = styled.div<{ theme: Theme; $isVisible: boolean }>`\n margin: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 100%;\n height: calc(100vh - 90px);\n overflow-y: scroll;\n overflow-x: hidden;\n z-index: 1000;\n width: 100%;\n padding: 0 20px;\n transition: all 0.3s ease;\n transform: translateX(0);\n background: ${({ theme }) => theme.colors.light};\n\n ${({ $isVisible }) =>\n !$isVisible &&\n css`\n transform: translateX(100%);\n `}\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n`;\n\nconst loadingAnimation = keyframes`\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n`;\n\nconst rotateGradient = keyframes`\n 0% {\n --gradient-angle: 0deg;\n }\n 100% {\n --gradient-angle: 360deg;\n }\n`;\n\nconst pulseGlow = keyframes`\n 0%, 100% {\n opacity: 0.5;\n filter: blur(16px);\n }\n 50% {\n opacity: 1;\n filter: blur(22px);\n }\n`;\n\nconst sparkleFloat = keyframes`\n 0%, 100% {\n opacity: 0;\n transform: translateY(0) scale(0);\n }\n 50% {\n opacity: 0.9;\n transform: translateY(-20px) scale(1);\n }\n`;\n\nconst shimmer = keyframes`\n 0% {\n background-position: 0% center;\n }\n 50% {\n background-position: 100% center;\n }\n 100% {\n background-position: 0% center;\n }\n`;\n\nconst StyledRainbowInputWrapper = styled.div<{\n theme: Theme;\n $isActive: boolean;\n}>`\n @property --gradient-angle {\n syntax: \"<angle>\";\n initial-value: 0deg;\n inherits: false;\n }\n\n position: relative;\n flex: 1;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 14px;\n background: conic-gradient(\n from var(--gradient-angle),\n #cc5555,\n #d9a745,\n #3ab0cc,\n #cc7fc2,\n #4380cc,\n #4c1fa3,\n #cc5555\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation: ${rotateGradient} 3s linear infinite;\n z-index: 0;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n inset: -10px;\n border-radius: 20px;\n background: conic-gradient(\n from var(--gradient-angle),\n ${rgba(\"#ff6b6b\", 0.4)},\n ${rgba(\"#feca57\", 0.4)},\n ${rgba(\"#48dbfb\", 0.4)},\n ${rgba(\"#ff9ff3\", 0.4)},\n ${rgba(\"#54a0ff\", 0.4)},\n ${rgba(\"#5f27cd\", 0.4)},\n ${rgba(\"#ff6b6b\", 0.4)}\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation:\n ${rotateGradient} 3s linear infinite,\n ${pulseGlow} 2s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n &:hover::before,\n &:focus-within::before {\n opacity: 1;\n }\n\n &:hover::after,\n &:focus-within::after {\n opacity: 1;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n opacity: 1;\n }\n &::after {\n opacity: 1;\n }\n `}\n`;\n\nconst StyledSparkleContainer = styled.div<{ $isActive: boolean }>`\n position: absolute;\n inset: -30px;\n pointer-events: none;\n overflow: hidden;\n border-radius: 30px;\n z-index: -2;\n opacity: 0;\n transition: opacity 0.4s ease;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n `}\n`;\n\nconst StyledSparkle = styled.div<{\n $color: string;\n $left: number;\n $top: number;\n $delay: number;\n}>`\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: ${({ $color }) => $color};\n box-shadow: 0 0 6px ${({ $color }) => $color};\n left: ${({ $left }) => $left}%;\n top: ${({ $top }) => $top}%;\n animation: ${sparkleFloat} 2s ease-in-out infinite;\n animation-delay: ${({ $delay }) => $delay}s;\n`;\n\nconst StyledRainbowInput = styled.input<{ theme: Theme }>`\n position: relative;\n z-index: 1;\n width: 100%;\n background: ${({ theme }) => theme.colors.light};\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\n border-radius: 12px;\n padding: 14px 18px;\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n font-family: inherit;\n color: ${({ theme }) => theme.colors.dark};\n outline: none;\n transition:\n border-color 0.3s ease,\n box-shadow 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n }\n\n &::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.4)};\n transition: color 0.3s ease;\n }\n\n &:focus::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.6)};\n }\n\n &:focus {\n border-color: transparent;\n }\n`;\n\nconst StyledRainbowButton = styled(Button)<{\n theme: Theme;\n $hasContent: boolean;\n}>`\n padding-top: 10px;\n padding-bottom: 10px;\n position: relative;\n overflow: hidden;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n\n &::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 135deg,\n #ff6b6b,\n #feca57,\n #48dbfb,\n #ff9ff3,\n #54a0ff\n );\n background-size: 300% 300%;\n opacity: 0;\n transition: opacity 0.3s ease;\n z-index: 0;\n animation: ${shimmer} 3s linear infinite;\n width: 200%;\n }\n\n ${({ $hasContent }) =>\n $hasContent &&\n css`\n &::before {\n opacity: 1;\n }\n `}\n\n &:hover::before {\n opacity: 1;\n }\n\n &:hover {\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n\n & svg {\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:disabled,\n &:disabled:hover {\n background: ${({ theme }) => theme.colors.primaryDark};\n transform: none;\n box-shadow: none;\n\n &::before {\n opacity: 0;\n }\n }\n`;\n\nconst StyledChatForm = styled.form<{ theme: Theme; $isVisible: boolean }>`\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n background: ${({ theme }) => theme.colors.light};\n padding: 20px;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 1000;\n width: 100%;\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n transform: translateX(100%);\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n\n ${({ $isVisible }) =>\n $isVisible &&\n css`\n transform: translateX(0);\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedForm = styled.form<{\n theme: Theme;\n $hide: boolean;\n $hasLinks: boolean;\n}>`\n transition: all 0.3s ease;\n position: fixed;\n bottom: 20px;\n left: 20px;\n width: calc(100% - 115px);\n z-index: 998;\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%) translateY(0);\n bottom: initial;\n position: absolute;\n top: 90px;\n width: calc(100% - 320px * 2 - 40px);\n opacity: 1;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n top: 164px;\n `}\n }\n\n ${({ $hide }) =>\n $hide &&\n css`\n transform: translateX(-100px);\n\n ${mq(\"lg\")} {\n opacity: 0;\n transform: translateX(-50%) translateY(-20px);\n }\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedInner = styled.div`\n margin: auto;\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n\n ${mq(\"lg\")} {\n max-width: 640px;\n }\n`;\n\nconst StyledError = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.error};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n`;\n\nconst loadingDotAnimation = keyframes`\n 0% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n`;\n\nconst StyledLoading = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n margin: 20px 0;\n width: 100%;\n font-weight: 600;\n ${styledText};\n color: ${({ theme }) => theme.colors.dark};\n\n & span {\n &:nth-child(1) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n }\n &:nth-child(2) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.2s;\n }\n &:nth-child(3) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.4s;\n }\n }\n`;\n\nconst StyledAnswer = styled.div<{ theme: Theme; $isAnswer: boolean }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n\n & p {\n ${styledText};\n }\n\n ${({ $isAnswer }) =>\n $isAnswer &&\n css`\n background: transparent;\n color: ${({ theme }) => theme.colors.dark};\n padding: 0;\n `}\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};A\n white-space: pre;\n }\n\n ${stylesLists};\n ${styledTable};\n\n & pre,\n & .hljs {\n margin: 10px 0;\n }\n\n & .code-wrapper pre {\n margin: 0;\n ${styledText};\n }\n\n & > *:first-child {\n margin-top: 0;\n }\n\n & > *:last-child {\n margin-bottom: 0;\n\n & > *:last-child {\n margin-bottom: 0;\n }\n }\n\n & ul,\n & ol {\n & li {\n ${styledText};\n }\n }\n\n & ol {\n & > li {\n padding-left: 20px;\n\n &::before {\n position: absolute;\n top: 0;\n left: 0;\n }\n }\n }\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n margin: 10px 0;\n display: block;\n }\n\n & h1,\n & h2,\n & h3,\n & h4,\n & h5,\n & h6 {\n margin: 10px 0;\n padding: 0;\n }\n`;\n\nconst StyledChatTitle = styled.div<{ theme: Theme }>`\n display: flex;\n flex-wrap: nowrap;\n justify-content: space-between;\n position: sticky;\n margin: 0 -20px;\n padding: 25px 20px;\n height: 73px;\n top: 0;\n background: ${({ theme }) => theme.colors.light};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n z-index: 1000;\n`;\n\nconst StyledChatTitleIconWrapper = styled.span<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 5px;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nconst StyledChatCloseButton = styled.button<{ theme: Theme }>`\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n color: ${({ theme }) => theme.colors.primary};\n\n @media (hover: hover) {\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n }\n`;\n\ntype Source = { id: string; path: string; score: number };\ntype Answer = {\n text: string;\n answer?: boolean;\n mdx?: MDXRemoteSerializeResult;\n};\n\nconst SPARKLE_COLORS = [\n \"#ff6b6b\",\n \"#feca57\",\n \"#48dbfb\",\n \"#ff9ff3\",\n \"#54a0ff\",\n \"#5f27cd\",\n];\n\n// Deterministic sparkle positions to avoid hydration mismatch\nconst SPARKLE_POSITIONS = [\n { left: 8, top: 35 },\n { left: 17, top: 55 },\n { left: 26, top: 28 },\n { left: 35, top: 68 },\n { left: 44, top: 42 },\n { left: 53, top: 75 },\n { left: 62, top: 32 },\n { left: 71, top: 58 },\n { left: 80, top: 45 },\n { left: 89, top: 65 },\n];\n\ninterface RainbowInputProps {\n id?: string;\n value: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n autoComplete?: string;\n}\n\nfunction RainbowInput({\n id,\n value,\n onChange,\n placeholder,\n autoComplete,\n}: RainbowInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const isActive = isFocused || isHovered;\n\n const sparkles = SPARKLE_POSITIONS.map((pos, i) => ({\n color: SPARKLE_COLORS[i % SPARKLE_COLORS.length],\n left: pos.left,\n top: pos.top,\n delay: i * 0.12,\n }));\n\n return (\n <StyledRainbowInputWrapper\n $isActive={isActive}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <StyledSparkleContainer $isActive={isActive}>\n {sparkles.map((sparkle, i) => (\n <StyledSparkle\n key={i}\n $color={sparkle.color}\n $left={sparkle.left}\n $top={sparkle.top}\n $delay={sparkle.delay}\n />\n ))}\n </StyledSparkleContainer>\n <StyledRainbowInput\n id={id}\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n autoComplete={autoComplete}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n />\n </StyledRainbowInputWrapper>\n );\n}\n\nfunction Chat() {\n const [question, setQuestion] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [answer, setAnswer] = useState<Answer[]>([]);\n const endRef = useRef<HTMLDivElement | null>(null);\n const mdxComponents = useMDXComponents({});\n const { isOpen, setIsOpen } = useContext(ChatContext);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"end\" });\n }, [answer]);\n\n useEffect(() => {\n if (answer?.length > 0) {\n const el = document.getElementById(\n \"chat-bottom-input\",\n ) as HTMLInputElement | null;\n el?.focus();\n }\n }, [answer]);\n\n async function ask(e: React.FormEvent) {\n e.preventDefault();\n if (loading || question.trim() === \"\") return;\n const currentQuestion = question;\n setQuestion(\"\");\n setIsOpen(true);\n setLoading(true);\n setError(null);\n\n const mergedQuestions =\n answer.length > 0\n ? [...answer, { text: currentQuestion, answer: false }]\n : [{ text: currentQuestion, answer: false }];\n\n setAnswer(mergedQuestions);\n\n try {\n const res = await fetch(\"/api/rag\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ question: currentQuestion }),\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n throw new Error(errorData.error || \"Request failed\");\n }\n\n const reader = res.body?.getReader();\n const decoder = new TextDecoder();\n let streamedContent = \"\";\n let metadata: any = null;\n\n if (!reader) {\n throw new Error(\"Failed to get response reader\");\n }\n\n // Add a placeholder for the streaming answer\n const streamingAnswerIndex = mergedQuestions.length;\n setAnswer([...mergedQuestions, { text: \"\", answer: true }]);\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value);\n const lines = chunk.split(\"\\n\");\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6));\n\n if (data.type === \"metadata\") {\n metadata = data.data;\n } else if (data.type === \"content\") {\n streamedContent += data.data;\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n };\n return newAnswers;\n });\n } else if (data.type === \"error\") {\n throw new Error(data.data);\n } else if (data.type === \"done\") {\n // Finalize with MDX serialization\n let mdxSource: MDXRemoteSerializeResult | null = null;\n try {\n mdxSource = await serialize(streamedContent, {\n parseFrontmatter: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n rehypePlugins: [rehypeHighlight],\n format: \"md\",\n development: false,\n },\n });\n } catch (mdxError: any) {\n console.error(\"MDX serialization error:\", mdxError);\n }\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n mdx: mdxSource || undefined,\n };\n return newAnswers;\n });\n }\n } catch (parseError) {\n console.error(\"Failed to parse SSE data:\", parseError);\n }\n }\n }\n }\n } catch (err: any) {\n setError(err.message || \"Unknown error\");\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <>\n <StyledChatFixedForm\n onSubmit={ask}\n $hide={answer?.length > 0}\n $hasLinks={links.length > 0}\n >\n <StyledChatFixedInner>\n <RainbowInput\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading}\n $hasContent={question.trim().length > 0}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatFixedInner>\n </StyledChatFixedForm>\n\n <StyledChat $isVisible={isOpen}>\n <StyledChatTitle>\n <StyledChatTitleIconWrapper>\n <Sparkles />\n <h3>AI Assistant</h3>\n </StyledChatTitleIconWrapper>\n <StyledChatCloseButton\n onClick={() => {\n setAnswer([]);\n setIsOpen(false);\n }}\n >\n <X />\n </StyledChatCloseButton>\n </StyledChatTitle>\n {answer &&\n answer.map((a, i) => (\n <StyledAnswer key={i} $isAnswer={a.answer ?? false}>\n {a.answer && a.mdx ? (\n <MDXRemote {...a.mdx} components={mdxComponents} />\n ) : (\n a.text\n )}\n </StyledAnswer>\n ))}\n {loading && (\n <StyledLoading>\n Answering<span>.</span>\n <span>.</span>\n <span>.</span>\n </StyledLoading>\n )}\n {error && (\n <StyledError>\n <strong>Error:</strong> {error}\n </StyledError>\n )}\n <div ref={endRef} />\n </StyledChat>\n\n <StyledChatForm onSubmit={ask} $isVisible={isOpen}>\n <RainbowInput\n id=\"chat-bottom-input\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading || question.trim() === \"\"}\n $hasContent={question.trim().length > 0}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatForm>\n </>\n );\n}\n\nconst ChatContext = createContext<{\n isOpen: boolean;\n setIsOpen: (isOpen: boolean) => void;\n isChatActive: boolean;\n}>({\n isOpen: false,\n setIsOpen: () => {},\n isChatActive: false,\n});\n\ninterface ChatContextProviderProps {\n children: React.ReactNode;\n isChatActive: boolean;\n}\n\nconst ChtProvider = ({ children, isChatActive }: ChatContextProviderProps) => {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <ChatContext.Provider\n value={{\n isOpen,\n setIsOpen,\n isChatActive,\n }}\n >\n {children}\n </ChatContext.Provider>\n );\n};\n\nexport { Chat, ChtProvider, ChatContext };\n";
|
|
1
|
+
export declare const chatTemplate = "\"use client\";\nimport React, {\n createContext,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Button } from \"cherry-styled-components/src/lib\";\nimport { ArrowUp, LoaderPinwheel, Sparkles, X } from \"lucide-react\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport { MDXRemote, MDXRemoteSerializeResult } from \"next-mdx-remote\";\nimport { serialize } from \"next-mdx-remote/serialize\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport links from \"@/links.json\";\n\nconst styledText = css<{ theme: Theme }>`\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: ${({ theme }) => theme.lineHeights.small.lg};\n }\n`;\n\nconst StyledChat = styled.div<{ theme: Theme; $isVisible: boolean }>`\n margin: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 100%;\n height: calc(100vh - 90px);\n overflow-y: scroll;\n overflow-x: hidden;\n z-index: 1000;\n width: 100%;\n padding: 0 20px;\n transition: all 0.3s ease;\n transform: translateX(0);\n background: ${({ theme }) => theme.colors.light};\n\n ${({ $isVisible }) =>\n !$isVisible &&\n css`\n transform: translateX(100%);\n `}\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n`;\n\nconst loadingAnimation = keyframes`\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n`;\n\nconst rotateGradient = keyframes`\n 0% {\n --gradient-angle: 0deg;\n }\n 100% {\n --gradient-angle: 360deg;\n }\n`;\n\nconst pulseGlow = keyframes`\n 0%, 100% {\n opacity: 0.5;\n filter: blur(16px);\n }\n 50% {\n opacity: 1;\n filter: blur(22px);\n }\n`;\n\nconst sparkleFloat = keyframes`\n 0%, 100% {\n opacity: 0;\n transform: translateY(0) scale(0);\n }\n 50% {\n opacity: 0.9;\n transform: translateY(-20px) scale(1);\n }\n`;\n\nconst shimmer = keyframes`\n 0% {\n background-position: 0% center;\n }\n 50% {\n background-position: 100% center;\n }\n 100% {\n background-position: 0% center;\n }\n`;\n\nconst StyledRainbowInputWrapper = styled.div<{\n theme: Theme;\n $isActive: boolean;\n}>`\n @property --gradient-angle {\n syntax: \"<angle>\";\n initial-value: 0deg;\n inherits: false;\n }\n\n position: relative;\n flex: 1;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 14px;\n background: conic-gradient(\n from var(--gradient-angle),\n #cc5555,\n #d9a745,\n #3ab0cc,\n #cc7fc2,\n #4380cc,\n #4c1fa3,\n #cc5555\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation: ${rotateGradient} 3s linear infinite;\n z-index: 0;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n inset: -10px;\n border-radius: 20px;\n background: conic-gradient(\n from var(--gradient-angle),\n ${rgba(\"#ff6b6b\", 0.4)},\n ${rgba(\"#feca57\", 0.4)},\n ${rgba(\"#48dbfb\", 0.4)},\n ${rgba(\"#ff9ff3\", 0.4)},\n ${rgba(\"#54a0ff\", 0.4)},\n ${rgba(\"#5f27cd\", 0.4)},\n ${rgba(\"#ff6b6b\", 0.4)}\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation:\n ${rotateGradient} 3s linear infinite,\n ${pulseGlow} 2s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n &:hover::before,\n &:focus-within::before {\n opacity: 1;\n }\n\n &:hover::after,\n &:focus-within::after {\n opacity: 1;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n opacity: 1;\n }\n &::after {\n opacity: 1;\n }\n `}\n`;\n\nconst StyledSparkleContainer = styled.div<{ $isActive: boolean }>`\n position: absolute;\n inset: -30px;\n pointer-events: none;\n overflow: hidden;\n border-radius: 30px;\n z-index: -2;\n opacity: 0;\n transition: opacity 0.4s ease;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n `}\n`;\n\nconst StyledSparkle = styled.div<{\n $color: string;\n $left: number;\n $top: number;\n $delay: number;\n}>`\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: ${({ $color }) => $color};\n box-shadow: 0 0 6px ${({ $color }) => $color};\n left: ${({ $left }) => $left}%;\n top: ${({ $top }) => $top}%;\n animation: ${sparkleFloat} 2s ease-in-out infinite;\n animation-delay: ${({ $delay }) => $delay}s;\n`;\n\nconst StyledRainbowInput = styled.input<{ theme: Theme }>`\n position: relative;\n z-index: 1;\n width: 100%;\n background: ${({ theme }) => theme.colors.light};\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\n border-radius: 12px;\n padding: 14px 18px;\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n font-family: inherit;\n color: ${({ theme }) => theme.colors.dark};\n outline: none;\n transition:\n border-color 0.3s ease,\n box-shadow 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n }\n\n &::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.4)};\n transition: color 0.3s ease;\n }\n\n &:focus::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.6)};\n }\n\n &:focus {\n border-color: transparent;\n }\n`;\n\nconst StyledRainbowButton = styled(Button)<{\n theme: Theme;\n $hasContent: boolean;\n}>`\n padding-top: 10px;\n padding-bottom: 10px;\n position: relative;\n overflow: hidden;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n\n &::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 135deg,\n #ff6b6b,\n #feca57,\n #48dbfb,\n #ff9ff3,\n #54a0ff\n );\n background-size: 300% 300%;\n opacity: 0;\n transition: opacity 0.3s ease;\n z-index: 0;\n animation: ${shimmer} 3s linear infinite;\n width: 200%;\n }\n\n ${({ $hasContent }) =>\n $hasContent &&\n css`\n &::before {\n opacity: 1;\n }\n `}\n\n &:hover::before {\n opacity: 1;\n }\n\n &:hover {\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n\n & svg {\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:disabled,\n &:disabled:hover {\n background: ${({ theme }) => theme.colors.primaryDark};\n transform: none;\n box-shadow: none;\n\n &::before {\n opacity: 0;\n }\n }\n`;\n\nconst StyledChatForm = styled.form<{ theme: Theme; $isVisible: boolean }>`\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n background: ${({ theme }) => theme.colors.light};\n padding: 20px;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 1000;\n width: 100%;\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n transform: translateX(100%);\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n\n ${({ $isVisible }) =>\n $isVisible &&\n css`\n transform: translateX(0);\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedForm = styled.form<{\n theme: Theme;\n $hide: boolean;\n $hasLinks: boolean;\n}>`\n transition: all 0.3s ease;\n position: fixed;\n bottom: 20px;\n left: 20px;\n width: calc(100% - 115px);\n z-index: 998;\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%) translateY(0);\n bottom: initial;\n position: absolute;\n top: 90px;\n width: calc(100% - 320px * 2 - 40px);\n opacity: 1;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n top: 164px;\n `}\n }\n\n ${({ $hide }) =>\n $hide &&\n css`\n transform: translateX(-100px);\n\n ${mq(\"lg\")} {\n opacity: 0;\n transform: translateX(-50%) translateY(-20px);\n }\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedInner = styled.div`\n margin: auto;\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n\n ${mq(\"lg\")} {\n max-width: 640px;\n }\n`;\n\nconst StyledError = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.error};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n`;\n\nconst loadingDotAnimation = keyframes`\n 0% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n`;\n\nconst StyledLoading = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n margin: 20px 0;\n width: 100%;\n font-weight: 600;\n ${styledText};\n color: ${({ theme }) => theme.colors.dark};\n\n & span {\n &:nth-child(1) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n }\n &:nth-child(2) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.2s;\n }\n &:nth-child(3) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.4s;\n }\n }\n`;\n\nconst StyledAnswer = styled.div<{ theme: Theme; $isAnswer: boolean }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n\n & p {\n ${styledText};\n }\n\n ${({ $isAnswer }) =>\n $isAnswer &&\n css`\n background: transparent;\n color: ${({ theme }) => theme.colors.dark};\n padding: 0;\n `}\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};A\n white-space: pre;\n }\n\n ${stylesLists};\n ${styledTable};\n\n & pre,\n & .hljs {\n margin: 10px 0;\n }\n\n & .code-wrapper pre {\n margin: 0;\n ${styledText};\n }\n\n & > *:first-child {\n margin-top: 0;\n }\n\n & > *:last-child {\n margin-bottom: 0;\n\n & > *:last-child {\n margin-bottom: 0;\n }\n }\n\n & ul,\n & ol {\n & li {\n ${styledText};\n }\n }\n\n & ol {\n & > li {\n padding-left: 20px;\n\n &::before {\n position: absolute;\n top: 0;\n left: 0;\n }\n }\n }\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n margin: 10px 0;\n display: block;\n }\n\n & h1,\n & h2,\n & h3,\n & h4,\n & h5,\n & h6 {\n margin: 10px 0;\n padding: 0;\n }\n`;\n\nconst StyledChatTitle = styled.div<{ theme: Theme }>`\n display: flex;\n flex-wrap: nowrap;\n justify-content: space-between;\n position: sticky;\n margin: 0 -20px;\n padding: 25px 20px;\n height: 73px;\n top: 0;\n background: ${({ theme }) => theme.colors.light};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n z-index: 1000;\n`;\n\nconst StyledChatTitleIconWrapper = styled.span<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 5px;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nconst StyledChatCloseButton = styled.button<{ theme: Theme }>`\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n color: ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\ntype Source = { id: string; path: string; score: number };\ntype Answer = {\n text: string;\n answer?: boolean;\n mdx?: MDXRemoteSerializeResult;\n};\n\nconst SPARKLE_COLORS = [\n \"#ff6b6b\",\n \"#feca57\",\n \"#48dbfb\",\n \"#ff9ff3\",\n \"#54a0ff\",\n \"#5f27cd\",\n];\n\n// Deterministic sparkle positions to avoid hydration mismatch\nconst SPARKLE_POSITIONS = [\n { left: 8, top: 35 },\n { left: 17, top: 55 },\n { left: 26, top: 28 },\n { left: 35, top: 68 },\n { left: 44, top: 42 },\n { left: 53, top: 75 },\n { left: 62, top: 32 },\n { left: 71, top: 58 },\n { left: 80, top: 45 },\n { left: 89, top: 65 },\n];\n\ninterface RainbowInputProps {\n id?: string;\n value: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n autoComplete?: string;\n}\n\nfunction RainbowInput({\n id,\n value,\n onChange,\n placeholder,\n autoComplete,\n}: RainbowInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const isActive = isFocused || isHovered;\n\n const sparkles = SPARKLE_POSITIONS.map((pos, i) => ({\n color: SPARKLE_COLORS[i % SPARKLE_COLORS.length],\n left: pos.left,\n top: pos.top,\n delay: i * 0.12,\n }));\n\n return (\n <StyledRainbowInputWrapper\n $isActive={isActive}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <StyledSparkleContainer $isActive={isActive}>\n {sparkles.map((sparkle, i) => (\n <StyledSparkle\n key={i}\n $color={sparkle.color}\n $left={sparkle.left}\n $top={sparkle.top}\n $delay={sparkle.delay}\n />\n ))}\n </StyledSparkleContainer>\n <StyledRainbowInput\n id={id}\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n autoComplete={autoComplete}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n />\n </StyledRainbowInputWrapper>\n );\n}\n\nfunction Chat() {\n const [question, setQuestion] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [answer, setAnswer] = useState<Answer[]>([]);\n const endRef = useRef<HTMLDivElement | null>(null);\n const mdxComponents = useMDXComponents({});\n const { isOpen, setIsOpen } = useContext(ChatContext);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"end\" });\n }, [answer]);\n\n useEffect(() => {\n if (answer?.length > 0) {\n const el = document.getElementById(\n \"chat-bottom-input\",\n ) as HTMLInputElement | null;\n el?.focus();\n }\n }, [answer]);\n\n async function ask(e: React.FormEvent) {\n e.preventDefault();\n if (loading || question.trim() === \"\") return;\n const currentQuestion = question;\n setQuestion(\"\");\n setIsOpen(true);\n setLoading(true);\n setError(null);\n\n const mergedQuestions =\n answer.length > 0\n ? [...answer, { text: currentQuestion, answer: false }]\n : [{ text: currentQuestion, answer: false }];\n\n setAnswer(mergedQuestions);\n\n try {\n const res = await fetch(\"/api/rag\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ question: currentQuestion }),\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n throw new Error(errorData.error || \"Request failed\");\n }\n\n const reader = res.body?.getReader();\n const decoder = new TextDecoder();\n let streamedContent = \"\";\n let metadata: any = null;\n\n if (!reader) {\n throw new Error(\"Failed to get response reader\");\n }\n\n // Add a placeholder for the streaming answer\n const streamingAnswerIndex = mergedQuestions.length;\n setAnswer([...mergedQuestions, { text: \"\", answer: true }]);\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value);\n const lines = chunk.split(\"\\n\");\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6));\n\n if (data.type === \"metadata\") {\n metadata = data.data;\n } else if (data.type === \"content\") {\n streamedContent += data.data;\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n };\n return newAnswers;\n });\n } else if (data.type === \"error\") {\n throw new Error(data.data);\n } else if (data.type === \"done\") {\n // Finalize with MDX serialization\n let mdxSource: MDXRemoteSerializeResult | null = null;\n try {\n mdxSource = await serialize(streamedContent, {\n parseFrontmatter: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n rehypePlugins: [rehypeHighlight],\n format: \"md\",\n development: false,\n },\n });\n } catch (mdxError: any) {\n console.error(\"MDX serialization error:\", mdxError);\n }\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n mdx: mdxSource || undefined,\n };\n return newAnswers;\n });\n }\n } catch (parseError) {\n console.error(\"Failed to parse SSE data:\", parseError);\n }\n }\n }\n }\n } catch (err: any) {\n setError(err.message || \"Unknown error\");\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <>\n <StyledChatFixedForm\n onSubmit={ask}\n $hide={answer?.length > 0}\n $hasLinks={links.length > 0}\n >\n <StyledChatFixedInner>\n <RainbowInput\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading}\n $hasContent={question.trim().length > 0}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatFixedInner>\n </StyledChatFixedForm>\n\n <StyledChat $isVisible={isOpen}>\n <StyledChatTitle>\n <StyledChatTitleIconWrapper>\n <Sparkles />\n <h3>AI Assistant</h3>\n </StyledChatTitleIconWrapper>\n <StyledChatCloseButton\n onClick={() => {\n setAnswer([]);\n setIsOpen(false);\n }}\n >\n <X />\n </StyledChatCloseButton>\n </StyledChatTitle>\n {answer &&\n answer.map((a, i) => (\n <StyledAnswer key={i} $isAnswer={a.answer ?? false}>\n {a.answer && a.mdx ? (\n <MDXRemote {...a.mdx} components={mdxComponents} />\n ) : (\n a.text\n )}\n </StyledAnswer>\n ))}\n {loading && (\n <StyledLoading>\n Answering<span>.</span>\n <span>.</span>\n <span>.</span>\n </StyledLoading>\n )}\n {error && (\n <StyledError>\n <strong>Error:</strong> {error}\n </StyledError>\n )}\n <div ref={endRef} />\n </StyledChat>\n\n <StyledChatForm onSubmit={ask} $isVisible={isOpen}>\n <RainbowInput\n id=\"chat-bottom-input\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading || question.trim() === \"\"}\n $hasContent={question.trim().length > 0}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatForm>\n </>\n );\n}\n\nconst ChatContext = createContext<{\n isOpen: boolean;\n setIsOpen: (isOpen: boolean) => void;\n isChatActive: boolean;\n}>({\n isOpen: false,\n setIsOpen: () => {},\n isChatActive: false,\n});\n\ninterface ChatContextProviderProps {\n children: React.ReactNode;\n isChatActive: boolean;\n}\n\nconst ChtProvider = ({ children, isChatActive }: ChatContextProviderProps) => {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <ChatContext.Provider\n value={{\n isOpen,\n setIsOpen,\n isChatActive,\n }}\n >\n {children}\n </ChatContext.Provider>\n );\n};\n\nexport { Chat, ChtProvider, ChatContext };\n";
|
|
@@ -587,15 +587,13 @@ const StyledChatCloseButton = styled.button<{ theme: Theme }>\`
|
|
|
587
587
|
margin: 0;
|
|
588
588
|
color: \${({ theme }) => theme.colors.primary};
|
|
589
589
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
590
|
+
&:hover {
|
|
591
|
+
color: \${({ theme }) => theme.colors.primaryDark};
|
|
592
|
+
transform: scale(1.05);
|
|
593
|
+
}
|
|
595
594
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
595
|
+
&:active {
|
|
596
|
+
transform: scale(0.95);
|
|
599
597
|
}
|
|
600
598
|
\`;
|
|
601
599
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components/src/lib\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter(Boolean) as HTMLElement[];\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= 0) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n handleScroll
|
|
1
|
+
export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components/src/lib\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter(Boolean) as HTMLElement[];\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= 0) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n element.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </li>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
|
|
@@ -67,7 +67,8 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
67
67
|
|
|
68
68
|
useEffect(() => {
|
|
69
69
|
if (headings.length === 0) return;
|
|
70
|
-
|
|
70
|
+
// Run initial scroll check on next frame to avoid synchronous setState in effect
|
|
71
|
+
const rafId = requestAnimationFrame(handleScroll);
|
|
71
72
|
let timeoutId: NodeJS.Timeout;
|
|
72
73
|
const throttledHandleScroll = () => {
|
|
73
74
|
clearTimeout(timeoutId);
|
|
@@ -78,6 +79,7 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
78
79
|
return () => {
|
|
79
80
|
window.removeEventListener("scroll", throttledHandleScroll);
|
|
80
81
|
window.removeEventListener("resize", handleScroll);
|
|
82
|
+
cancelAnimationFrame(rafId);
|
|
81
83
|
clearTimeout(timeoutId);
|
|
82
84
|
};
|
|
83
85
|
}, [handleScroll, headings]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components/src/lib\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n
|
|
1
|
+
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components/src/lib\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n }\n\n & .lucide-chevron-down {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n right: 0;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: translateY(-50%) rotate(180deg);\n `}\n }\n`;\n\nconst StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n margin: 20px 0 0;\n height: auto;\n `}\n`;\n\nexport interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n}\n\nfunction Accordion({ children, title }: AccordionProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <StyledAccordion>\n <StyledAccordionTitle onClick={() => setIsOpen(!isOpen)} $isOpen={isOpen}>\n {title} <Icon name=\"ChevronDown\" />\n </StyledAccordionTitle>\n <StyledAccordionContent $isOpen={isOpen}>\n {children}\n </StyledAccordionContent>\n </StyledAccordion>\n );\n}\n\nexport { Accordion };\n";
|
|
@@ -23,10 +23,8 @@ const StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>\`
|
|
|
23
23
|
transition: color 0.3s ease;
|
|
24
24
|
position: relative;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
color: \${({ theme }) => theme.colors.primaryDark};
|
|
29
|
-
}
|
|
26
|
+
&:hover {
|
|
27
|
+
color: \${({ theme }) => theme.colors.primaryDark};
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
& .lucide-chevron-down {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const actionBarTemplate = "\"use client\";\nimport { useContext, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { rgba } from \"polished\";\nimport { resetButton, Textarea } from \"cherry-styled-components/src/lib\";\nimport { ChatContext } from \"@/components/Chat\";\nimport links from \"@/links.json\";\n\ninterface ActionBarProps {\n children: React.ReactNode;\n content: string;\n}\n\nconst StyledActionBar = styled.div<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n position: absolute;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n left: 0;\n padding: 70px 20px 20px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 49px;\n `}\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%);\n max-width: calc(100vw - 640px);\n width: 100%;\n padding: 0 20px 20px 20px;\n margin: 0;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 73px;\n `}\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledActionBarContent = styled.div`\n margin: auto 0;\n`;\n\nconst StyledCopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: transparent;\n border: solid 1px\n ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.grayLight};\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\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: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:hover {\n border-color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\nconst StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 32px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ $isActive }) =>\n !$isActive &&\n css`\n transform: translateX(24px);\n `}\n }\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-eye {\n transform: translateX(1px);\n }\n\n & .lucide-code-xml {\n transform: translateX(-1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n
|
|
1
|
+
export declare const actionBarTemplate = "\"use client\";\nimport { useContext, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { rgba } from \"polished\";\nimport { resetButton, Textarea } from \"cherry-styled-components/src/lib\";\nimport { ChatContext } from \"@/components/Chat\";\nimport links from \"@/links.json\";\n\ninterface ActionBarProps {\n children: React.ReactNode;\n content: string;\n}\n\nconst StyledActionBar = styled.div<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n position: absolute;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n left: 0;\n padding: 70px 20px 20px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 49px;\n `}\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%);\n max-width: calc(100vw - 640px);\n width: 100%;\n padding: 0 20px 20px 20px;\n margin: 0;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 73px;\n `}\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledActionBarContent = styled.div`\n margin: auto 0;\n`;\n\nconst StyledCopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: transparent;\n border: solid 1px\n ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.grayLight};\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\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: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:hover {\n border-color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\nconst StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 32px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ $isActive }) =>\n !$isActive &&\n css`\n transform: translateX(24px);\n `}\n }\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-eye {\n transform: translateX(1px);\n }\n\n & .lucide-code-xml {\n transform: translateX(-1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nconst StyledContent = styled.div<{\n theme: Theme;\n $isChatActive?: boolean;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding-top: 140px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n padding-top: calc(49px + 140px);\n `}\n\n ${mq(\"lg\")} {\n padding-top: 70px;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n padding-top: calc(73px + 70px);\n `}\n\n ${({ $isChatActive }) =>\n $isChatActive &&\n css`\n padding-top: 140px;\n `}\n\n ${({ $isChatOpen, $isChatActive }) =>\n $isChatOpen &&\n $isChatActive &&\n css`\n padding-top: 70px;\n `}\n\n ${({ $isChatActive, $hasLinks }) =>\n $isChatActive &&\n $hasLinks &&\n css`\n padding-top: calc(73px + 140px);\n `}\n }\n\n & textarea {\n max-width: 640px;\n margin: auto;\n width: 100%;\n height: 100%;\n min-height: calc(100vh - 180px);\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n min-height: calc(100vh - 229px);\n `}\n\n ${({ $isChatOpen, $isChatActive, $hasLinks }) =>\n !$isChatOpen &&\n $isChatActive &&\n css`\n min-height: calc(100vh - 250px);\n\n ${$hasLinks &&\n css`\n min-height: calc(100vh - 299px);\n `}\n `}\n\n ${mq(\"lg\")} {\n min-height: calc(100vh - 110px);\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n min-height: calc(100vh - 183px);\n `}\n\n ${({ $isChatOpen, $isChatActive, $hasLinks }) =>\n !$isChatOpen &&\n $isChatActive &&\n css`\n min-height: calc(100vh - 180px);\n\n ${$hasLinks &&\n css`\n min-height: calc(100vh - 253px);\n `}\n `}\n }\n }\n`;\n\nfunction ActionBar({ children, content }: ActionBarProps) {\n const [isView, setIsView] = useState(true);\n const [copied, setCopied] = useState(false);\n const { isOpen, isChatActive } = useContext(ChatContext);\n\n const handleCopyContent = async () => {\n try {\n await navigator.clipboard.writeText(content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy:\", err);\n }\n };\n\n return (\n <>\n <StyledActionBar $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledCopyButton onClick={handleCopyContent} $copied={copied}>\n {copied ? (\n <>\n <Icon name=\"check\" size={16} />\n <span>Copied!</span>\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={16} />\n <span>Copy Content</span>\n </>\n )}\n </StyledCopyButton>\n <StyledActionBarContent>\n <StyledToggle\n onClick={() => setIsView(!isView)}\n aria-label=\"Toggle Theme\"\n $isActive={isView}\n >\n <Icon name=\"Eye\" />\n <Icon name=\"CodeXml\" />\n </StyledToggle>\n </StyledActionBarContent>\n </StyledActionBar>\n {isView && (\n <StyledContent\n $isChatActive={isChatActive}\n $isChatOpen={isOpen}\n $hasLinks={links.length > 0}\n >\n {children}\n </StyledContent>\n )}\n {!isView && (\n <StyledContent\n $isChatActive={isChatActive}\n $isChatOpen={isOpen}\n $hasLinks={links.length > 0}\n >\n <Textarea defaultValue={content} $fullWidth />\n </StyledContent>\n )}\n </>\n );\n}\n\nexport { ActionBar };\n";
|
|
@@ -145,15 +145,14 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
|
145
145
|
stroke: \${({ theme }) => theme.colors.primary};
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
&:hover {
|
|
149
|
+
transform: scale(1.05);
|
|
150
|
+
color: \${({ theme }) =>
|
|
151
|
+
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
152
|
+
|
|
153
|
+
& svg[stroke] {
|
|
154
|
+
stroke: \${({ theme }) =>
|
|
152
155
|
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
153
|
-
Toggle & svg[stroke] {
|
|
154
|
-
stroke: \${({ theme }) =>
|
|
155
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
156
|
-
}
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsComponentsTemplate = "\"use client\";\nimport { darken, lighten, rgba } from \"polished\";\nimport React, { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components/src/lib\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.div<{ 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: 20px 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 20px 340px 80px 340px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 440px 80px 340px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n }\n\n & pre {\n max-width: 100%;\n }\n\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 }) => rgba(theme.colors.primaryLight, 0.2)};\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}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(100svh - 70px);\n width: 100%;\n z-index: 99;\n top: 70px;\n height: 100%;\n padding: 20px 20px 80px 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 border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${mq(\"lg\")} {\n transition: none;\n max-height: 100svh;\n width: 220px;\n background: transparent;\n padding: 90px 40px 40px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};\n top: 0;\n width: 320px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\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: 320px;\n height: 100vh;\n overflow-y: auto;\n z-index: 1;\n padding: 25px 40px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\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 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.primaryDark};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n
|
|
1
|
+
export declare const docsComponentsTemplate = "\"use client\";\nimport { darken, lighten, rgba } from \"polished\";\nimport React, { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components/src/lib\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.div<{ 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: 20px 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 20px 340px 80px 340px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 440px 80px 340px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n }\n\n & pre {\n max-width: 100%;\n }\n\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 }) => rgba(theme.colors.primaryLight, 0.2)};\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}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(100svh - 70px);\n width: 100%;\n z-index: 99;\n top: 70px;\n height: 100%;\n padding: 20px 20px 80px 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 border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${mq(\"lg\")} {\n transition: none;\n max-height: 100svh;\n width: 220px;\n background: transparent;\n padding: 90px 40px 40px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};\n top: 0;\n width: 320px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\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: 320px;\n height: 100vh;\n overflow-y: auto;\n z-index: 1;\n padding: 25px 40px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\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 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.primaryDark};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n `\n\t\t\tcolor: ${theme.isDark ? lighten(0.1, theme.colors.primaryLight) : darken(0.1, theme.colors.primaryDark)};\n\t\t\tborder-color: ${theme.colors.primary};\n\t\t\tfont-weight: 600;\n\t`};\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 }) =>\n theme.isDark\n ? rgba(theme.colors.grayLight, 0.7)\n : rgba(theme.colors.light, 0.7)};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 20px;\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\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 }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\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\nfunction DocsWrapper({ children }: DocsProps) {\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 { DocsWrapper, DocsSidebar, DocsContainer };\n";
|
|
@@ -211,12 +211,10 @@ export const StyledSidebarListItemLink = styled(Link)<Props>\`
|
|
|
211
211
|
transition: all 0.3s ease;
|
|
212
212
|
border-left: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
border-color: \${({ theme }) => theme.colors.primary};
|
|
219
|
-
}
|
|
214
|
+
&:hover {
|
|
215
|
+
color: \${({ theme }) =>
|
|
216
|
+
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
217
|
+
border-color: \${({ theme }) => theme.colors.primary};
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
\${({ $isActive, theme }) =>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const headerTemplate = "\"use client\";\nimport { Flex, MaxWidth } from \"cherry-styled-components/src/lib\";\nimport { useCallback, useRef, useState, Suspense } from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport Link from \"next/link\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n ToggleTheme,\n ToggleThemeLoading,\n} from \"@/components/layout/ThemeToggle\";\nimport { useOnClickOutside } from \"@/components/ClickOutside\";\nimport { Logo } from \"@/components/layout/Pictograms\";\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 }>`\n position: fixed;\n top: 0;\n padding: 20px;\n margin: 0;\n z-index: 1000;\n width: 100%;\n\n ${mq(\"lg\")} {\n width: 320px;\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\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 }) => rgba(theme.colors.primaryLight, 0.05)};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n\n & svg,\n & img {\n margin: auto;\n width: 100%;\n max-width: max-content;\n height: auto;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nfunction Header() {\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 theme = useTheme() as Theme;\n\n return (\n <>\n <StyledHeader>\n <MaxWidth $size={1000}>\n <Flex $justifyContent=\"space-between\" $wrap=\"nowrap\">\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {(customThemeJson as any).logo ? (\n theme.isDark ? (\n <img\n src={(themeJson as any).logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n ) : (\n <img\n src={(themeJson as any).logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n )\n ) : (\n <Logo />\n )}\n </Link>\n <Suspense fallback={<ToggleThemeLoading />}>\n <ToggleTheme />\n </Suspense>\n </Flex>\n </MaxWidth>\n </StyledHeader>\n </>\n );\n}\n\nexport { Header };\n";
|
|
1
|
+
export declare const headerTemplate = "\"use client\";\nimport { Flex, MaxWidth } from \"cherry-styled-components/src/lib\";\nimport { useCallback, useRef, useState, Suspense } from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport Link from \"next/link\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n ToggleTheme,\n ToggleThemeLoading,\n} from \"@/components/layout/ThemeToggle\";\nimport { useOnClickOutside } from \"@/components/ClickOutside\";\nimport { Logo } from \"@/components/layout/Pictograms\";\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 }>`\n position: fixed;\n top: 0;\n padding: 20px;\n margin: 0;\n z-index: 1000;\n width: 100%;\n\n ${mq(\"lg\")} {\n width: 320px;\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\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 }) => rgba(theme.colors.primaryLight, 0.05)};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n\n & svg,\n & img {\n margin: auto;\n width: 100%;\n max-width: max-content;\n height: auto;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nfunction Header() {\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 theme = useTheme() as Theme;\n\n return (\n <>\n <StyledHeader>\n <MaxWidth $size={1000}>\n <Flex $justifyContent=\"space-between\" $wrap=\"nowrap\">\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {(customThemeJson as any).logo ? (\n theme.isDark ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={(themeJson as any).logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={(themeJson as any).logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n )\n ) : (\n <Logo />\n )}\n </Link>\n <Suspense fallback={<ToggleThemeLoading />}>\n <ToggleTheme />\n </Suspense>\n </Flex>\n </MaxWidth>\n </StyledHeader>\n </>\n );\n}\n\nexport { Header };\n";
|
|
@@ -93,6 +93,7 @@ function Header() {
|
|
|
93
93
|
<Link href="/" className="logo" aria-label="Logo">
|
|
94
94
|
{(customThemeJson as any).logo ? (
|
|
95
95
|
theme.isDark ? (
|
|
96
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
96
97
|
<img
|
|
97
98
|
src={(themeJson as any).logo.dark}
|
|
98
99
|
alt="Logo"
|
|
@@ -100,6 +101,7 @@ function Header() {
|
|
|
100
101
|
height="100"
|
|
101
102
|
/>
|
|
102
103
|
) : (
|
|
104
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
103
105
|
<img
|
|
104
106
|
src={(themeJson as any).logo.light}
|
|
105
107
|
alt="Logo"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sharedStyledTemplate = "\"use client\";\nimport {\n mq,\n resetButton,\n styledH3,\n styledSmall,\n styledText,\n Theme,\n} from \"cherry-styled-components/src/lib\";\nimport { rgba } from \"polished\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { StyledH1 } from \"@/components/layout/Typography\";\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 @media (hover: hover) {\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\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 StyledPriceHeader = styled.div<{ theme: Theme }>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 20px;\n display: flex;\n gap: 20px;\n\n & strong {\n margin: auto 0;\n color: ${({ theme }) => theme.colors.dark};\n }\n`;\n\nexport const StyledPrice = styled.span<{\n theme: Theme;\n $textAlign?: \"left\" | \"center\" | \"right\";\n}>`\n color: ${({ theme }) => theme.colors.dark};\n display: block;\n font-weight: 800;\n ${({ theme }) => styledH3(theme)}\n ${({ $textAlign }) =>\n $textAlign &&\n css`\n text-align: ${$textAlign};\n `}\n`;\n\nexport const StyledSmall = styled.small<{\n theme: Theme;\n $textAlign?: \"left\" | \"center\" | \"right\";\n}>`\n ${({ theme }) => styledSmall(theme)};\n margin: auto 0;\n display: block;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ $textAlign }) =>\n $textAlign &&\n css`\n text-align: ${$textAlign};\n `}\n`;\n\nexport const StyledMinHeight = styled.div`\n min-height: calc(100svh - 100px);\n\n ${mq(\"lg\")} {\n min-height: calc(100svh - 120px);\n }\n`;\n\nexport const StyledDataUserAvatar = styled.span<{ theme: Theme }>`\n display: inline-flex;\n justify-content: center;\n min-width: 50px;\n min-height: 50px;\n border-radius: 50%;\n border: solid 2px ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) => theme.colors.light};\n position: relative;\n overflow: hidden;\n color: ${({ theme }) => theme.colors.primary};\n\n & img {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n }\n\n & svg {\n pointer-events: none;\n margin: auto;\n transition: none;\n }\n`;\n\nexport const StyledDataArrowButton = styled.button<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${resetButton};\n display: flex;\n gap: 5px;\n min-width: fit-content;\n\n & .avatar,\n & .clickable {\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n transition: all 0.3s ease;\n }\n\n & svg {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n @media (hover: hover) {\n &:hover {\n & .lucide-chevron-down {\n & path {\n stroke: ${({ theme }) => theme.colors.primaryDark};\n }\n }\n\n & .avatar,\n & .clickable {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n\n &:focus {\n & .avatar,\n & .clickable {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n &:active {\n & .avatar,\n & .clickable {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n & svg {\n margin: auto 0;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n & .lucide-chevron-down {\n transform: rotate(180deg);\n }\n `}\n`;\n\nexport const StyledDataSpan = styled.span<{ theme: Theme }>`\n display: block;\n font-weight: 500;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n font-family: ${({ theme }) => theme.fonts.text};\n`;\n\nexport const StyledDataDropdownHoverWrapper = styled.span<{\n theme: Theme;\n $align?: \"center\";\n}>`\n position: relative;\n display: block;\n padding: 10px 0;\n margin: -10px 0;\n\n &:hover {\n & ul {\n pointer-events: all;\n opacity: 1;\n transform: ${({ $align }) =>\n $align === \"center\"\n ? \"translateX(-50%) translateY(0)\"\n : \"translateY(0)\"};\n }\n\n & .lucide-chevron-down {\n transform: rotate(180deg);\n }\n }\n\n &:active {\n & ul {\n opacity: 0;\n }\n }\n`;\n\nexport const StyledDataDropdownWrapper = styled.span<{\n theme: Theme;\n $isAbsolute?: boolean;\n $marginAuto?: boolean;\n}>`\n position: relative;\n display: flex;\n padding: 10px 0;\n margin: -10px 0;\n\n & span {\n display: flex;\n margin: auto 0;\n }\n\n ${({ $isAbsolute }) =>\n $isAbsolute &&\n css`\n margin: 0;\n padding: 0;\n `}\n\n ${({ $marginAuto }) => $marginAuto && `margin: auto 0;`}\n`;\n\nexport const StyledDataDropdown = styled.ul<{\n theme: Theme;\n $isActive?: boolean;\n $align?: \"left\" | \"center\";\n $isTop?: boolean;\n $isAbsolute?: boolean;\n $minWidth?: string;\n}>`\n position: absolute;\n display: block;\n top: 100%;\n right: 0;\n list-style: none;\n transition: all 0.3s ease;\n min-width: ${({ $minWidth }) => ($minWidth ? $minWidth : \"200px\")};\n text-align: left;\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n overflow: hidden;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-10px);\n z-index: 999;\n\n ${({ $isAbsolute }) =>\n $isAbsolute &&\n css`\n top: calc(100% + 56px);\n right: 20px;\n `}\n\n ${({ $isTop }) => $isTop && `top: initial; bottom: 100%;`}\n\n ${({ $align }) => $align === \"left\" && `right: initial; left: 0;`}\n ${({ $align }) =>\n $align === \"center\" &&\n `right: initial; left: 50%; transform: translateX(-50%) translateY(-10px);`}\n\n ${({ $isActive, $align }) =>\n $isActive &&\n css`\n pointer-events: all;\n opacity: 1;\n transform: translateY(0);\n\n ${$align === \"center\" &&\n `right: initial; left: 50%; transform: translateX(-50%) translateY(0);`}\n `}\n`;\n\nexport const StyledDataDropdownItem = styled.li<{ theme: Theme }>`\n display: flex;\n width: 100%;\n text-align: left;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:last-of-type,\n &:only-of-type {\n border-bottom: none;\n }\n\n & form {\n width: 100%;\n margin: 0;\n padding: 0;\n }\n\n & a,\n & button,\n & .logout-button {\n ${resetButton};\n width: 100%;\n text-align: left;\n padding: 12px 20px;\n font-weight: 600;\n font-family: inherit;\n color: ${({ theme }) => theme.colors.primary};\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n text-decoration: none;\n transition: all 0.3s ease;\n border-radius: 0;\n background: transparent;\n display: flex;\n gap: 10px;\n height: auto;\n min-height: 45px;\n\n @media (hover: hover) {\n &:hover {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.1,\n )};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active,\n &:focus {\n box-shadow: none;\n }\n\n * {\n margin: auto 0;\n }\n\n &:focus {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.15,\n )};\n }\n\n &:active {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.2,\n )};\n }\n }\n\n & button {\n border: none;\n\n & svg {\n margin: -2px -5px;\n }\n }\n`;\n\nexport const StyledDataDropdownButton = styled.button<{ theme: Theme }>`\n ${resetButton};\n`;\n\nexport const StyledDataDropdownSelectWrapper = styled.span<{ theme: Theme }>`\n padding: 5px;\n display: block;\n width: 100%;\n\n & select {\n width: 100%;\n }\n`;\n\nexport const StyledDataTagButton = styled.button<{ theme: Theme }>`\n ${resetButton};\n position: absolute;\n top: 0;\n width: 24px;\n height: 100%;\n right: 0;\n border-left: solid 1px\n ${({ theme }) =>\n rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};\n transition: all 0.3s ease;\n\n @media (hover: hover) {\n &:hover {\n background: ${({ theme }) =>\n rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};\n }\n }\n\n & svg {\n width: 16px;\n height: 100%;\n vertical-align: middle;\n\n & path {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n }\n }\n`;\n\nexport const StyledDataTag = styled.span<{\n theme: Theme;\n $color?: \"default\" | \"warning\";\n $capitalize?: boolean;\n}>`\n display: inline-block;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n background: ${({ theme, $color }) =>\n $color === \"warning\" ? theme.colors.warning : theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n font-size: ${({ theme }) => theme.fontSizes.small.xs};\n position: relative;\n font-weight: 500;\n padding: 5px 10px;\n margin: auto 0;\n overflow: hidden;\n ${({ $capitalize }) =>\n $capitalize &&\n css`\n text-transform: capitalize;\n `}\n\n &:has(button) {\n padding: 5px 35px 5px 10px;\n }\n`;\n\nexport const StyledIllustration = styled.div<{ theme: Theme }>`\n text-align: center;\n\n & svg {\n height: auto;\n width: 250px;\n margin: auto;\n }\n`;\n\nexport const StyledIllustrationText = styled.p<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledSmall(theme)};\n`;\n\nexport const StyledDataText = styled.div<{ theme: Theme; $gray?: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme, $gray }) =>\n $gray ? theme.colors.grayDark : theme.colors.dark};\n`;\n\nexport const StyledDataHeader = styled.h1<{ theme: Theme }>`\n ${({ theme }) => styledH3(theme)};\n font-weight: 900;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nexport const StyledMobileOnly = styled.em<{ theme: Theme }>`\n font-style: normal;\n display: inline;\n\n ${mq(\"lg\")} {\n display: none;\n }\n`;\n\nexport const StyledDesktopOnly = styled.em<{ theme: Theme }>`\n font-style: normal;\n display: none;\n\n ${mq(\"lg\")} {\n display: inline;\n }\n`;\n\nexport const StyledAlignMiddle = styled.div<{ theme: Theme }>`\n margin: auto 0;\n`;\n\nexport const StyledLoadingOverlay = styled.div<{\n theme: Theme;\n $isActive?: boolean;\n}>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: ${({ theme }) => theme.colors.light};\n z-index: 1000;\n display: flex;\n transition: all 0.3s ease;\n opacity: 0;\n pointer-events: none;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n pointer-events: all;\n `}\n\n & > * {\n margin: auto;\n }\n`;\n\nexport const StyledFlex1 = styled.div`\n flex: 1;\n`;\n\nexport const StyledIconCircle = styled.span<{ theme: Theme }>`\n border-radius: 50%;\n width: 40px;\n height: 40px;\n min-width: 40px;\n min-height: 40px;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n display: inline-flex;\n color: ${({ theme }) => theme.colors.primary};\n vertical-align: middle;\n\n & svg {\n margin: auto;\n }\n`;\n\nexport const StyledStrong = styled.span<{ theme: Theme }>`\n display: block;\n margin: auto 0;\n color: ${({ theme }) => theme.colors.primary};\n font-weight: 700;\n`;\n\nexport const StyledTextMiddle = styled.span<{ theme: Theme }>`\n display: block;\n margin: auto 0;\n color: ${({ theme }) => theme.colors.dark};\n text-align: left;\n`;\n\nexport const StyledPanelWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n z-index: 100;\n\n & input {\n position: relative;\n z-index: 10;\n }\n`;\n\nexport const StyledPanel = styled.div<{ theme: Theme; $absolute?: boolean }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 2px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n margin: ${({ $absolute }) => ($absolute ? \"-10px 0 0 0\" : \"0\")};\n ${({ $absolute }) =>\n $absolute &&\n css`\n position: absolute;\n width: 100%;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n `}\n max-height: calc(63px * 3);\n overflow-y: auto;\n`;\n\nexport const StyledPanelLabel = styled.div<{ theme: Theme; $moveUp?: boolean }>`\n font-size: ${({ theme }) => theme.fontSizes.small.xs};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.gray};\n padding: ${({ $moveUp }) => ($moveUp ? \"20px 15px 10px\" : \"20px 15px\")};\n`;\n\nexport const StyledPanelContent = styled.div<{ theme: Theme }>`\n display: flex;\n padding: 15px;\n gap: 10px;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n width: 100%;\n text-align: left;\n font-family: ${({ theme }) => theme.fonts.text};\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n font-weight: 500;\n color: ${({ theme }) => theme.colors.primary};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n line-height: ${({ theme }) => theme.lineHeights.text.lg};\n }\n\n &:last-of-type {\n border-bottom: none;\n }\n`;\n\nexport const StyledPanelButton = styled(Link)<{ theme: Theme }>`\n ${resetButton};\n display: flex;\n padding: 15px;\n gap: 10px;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n width: 100%;\n text-align: left;\n font-family: ${({ theme }) => theme.fonts.text};\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n font-weight: 500;\n color: ${({ theme }) => theme.colors.primary};\n text-decoration: none;\n transition: all 0.3s ease;\n\n @media (hover: hover) {\n &:hover {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n\n & .link {\n border-color: ${({ theme }) => theme.colors.primaryDark};\n }\n }\n }\n\n &:focus {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.15)};\n\n & .link {\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n\n &:active {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n\n & .link {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n line-height: ${({ theme }) => theme.lineHeights.text.lg};\n }\n\n &:last-of-type {\n border-bottom: none;\n }\n\n & .link {\n transition: all 0.3s ease;\n pointer-events: none;\n }\n`;\n\nexport const StyledPanelSpan = styled.span<{ theme: Theme }>`\n margin: auto 0;\n display: flex;\n flex: 1;\n line-height: 1.5;\n`;\n\nexport const editableContent = css<{ theme: Theme }>`\n border: dotted 1px transparent;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n transition: all 0.3s ease;\n outline: none;\n cursor: text;\n\n &:hover {\n border: dotted 1px ${({ theme }) => theme.colors.gray};\n }\n\n &:focus {\n border: dotted 1px ${({ theme }) => theme.colors.info};\n }\n`;\n\nexport const StyledDataEditableText = styled.div<{\n theme: Theme;\n $gray?: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme, $gray }) =>\n $gray ? theme.colors.grayDark : theme.colors.dark};\n\n &[contenteditable=\"true\"] {\n ${editableContent};\n }\n`;\n\nexport const StyledSmallButtonWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n\n & .hidden-button {\n opacity: 0;\n pointer-events: none;\n transform: translateY(10px);\n }\n\n @media (hover: hover) {\n &:hover {\n & .hidden-button {\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n }\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{\n theme: Theme;\n}>`\n ${resetButton};\n display: flex;\n gap: 5px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n color: ${({ theme }) => theme.colors.primary};\n background: ${({ theme }) => theme.colors.light};\n margin: auto 0;\n min-width: fit-content;\n ${interactiveStyles}\n\n &.hidden-button {\n position: absolute;\n bottom: 1px;\n right: 1px;\n z-index: 98;\n }\n`;\n\nexport const StyledTitle = styled(StyledH1)<{ theme: Theme }>`\n display: block;\n\n &[contenteditable=\"true\"] {\n ${editableContent};\n }\n`;\n\nexport const StyledImage = styled.img<{ theme: Theme; $maxWidth?: string }>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n max-width: ${({ $maxWidth }) => ($maxWidth ? $maxWidth : \"100%\")};\n width: 100%;\n height: auto;\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\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 {\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 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";
|
|
1
|
+
export declare const sharedStyledTemplate = "\"use client\";\nimport {\n mq,\n resetButton,\n styledH3,\n styledSmall,\n styledText,\n Theme,\n} from \"cherry-styled-components/src/lib\";\nimport { rgba } from \"polished\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { StyledH1 } from \"@/components/layout/Typography\";\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 StyledPriceHeader = styled.div<{ theme: Theme }>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 20px;\n display: flex;\n gap: 20px;\n\n & strong {\n margin: auto 0;\n color: ${({ theme }) => theme.colors.dark};\n }\n`;\n\nexport const StyledPrice = styled.span<{\n theme: Theme;\n $textAlign?: \"left\" | \"center\" | \"right\";\n}>`\n color: ${({ theme }) => theme.colors.dark};\n display: block;\n font-weight: 800;\n ${({ theme }) => styledH3(theme)}\n ${({ $textAlign }) =>\n $textAlign &&\n css`\n text-align: ${$textAlign};\n `}\n`;\n\nexport const StyledSmall = styled.small<{\n theme: Theme;\n $textAlign?: \"left\" | \"center\" | \"right\";\n}>`\n ${({ theme }) => styledSmall(theme)};\n margin: auto 0;\n display: block;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ $textAlign }) =>\n $textAlign &&\n css`\n text-align: ${$textAlign};\n `}\n`;\n\nexport const StyledMinHeight = styled.div`\n min-height: calc(100svh - 100px);\n\n ${mq(\"lg\")} {\n min-height: calc(100svh - 120px);\n }\n`;\n\nexport const StyledDataUserAvatar = styled.span<{ theme: Theme }>`\n display: inline-flex;\n justify-content: center;\n min-width: 50px;\n min-height: 50px;\n border-radius: 50%;\n border: solid 2px ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) => theme.colors.light};\n position: relative;\n overflow: hidden;\n color: ${({ theme }) => theme.colors.primary};\n\n & img {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n }\n\n & svg {\n pointer-events: none;\n margin: auto;\n transition: none;\n }\n`;\n\nexport const StyledDataArrowButton = styled.button<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${resetButton};\n display: flex;\n gap: 5px;\n min-width: fit-content;\n\n & .avatar,\n & .clickable {\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n transition: all 0.3s ease;\n }\n\n & svg {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n & .lucide-chevron-down {\n & path {\n stroke: ${({ theme }) => theme.colors.primaryDark};\n }\n }\n\n & .avatar,\n & .clickable {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n\n &:focus {\n & .avatar,\n & .clickable {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n &:active {\n & .avatar,\n & .clickable {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n & svg {\n margin: auto 0;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n & .lucide-chevron-down {\n transform: rotate(180deg);\n }\n `}\n`;\n\nexport const StyledDataSpan = styled.span<{ theme: Theme }>`\n display: block;\n font-weight: 500;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n font-family: ${({ theme }) => theme.fonts.text};\n`;\n\nexport const StyledDataDropdownHoverWrapper = styled.span<{\n theme: Theme;\n $align?: \"center\";\n}>`\n position: relative;\n display: block;\n padding: 10px 0;\n margin: -10px 0;\n\n &:hover {\n & ul {\n pointer-events: all;\n opacity: 1;\n transform: ${({ $align }) =>\n $align === \"center\"\n ? \"translateX(-50%) translateY(0)\"\n : \"translateY(0)\"};\n }\n\n & .lucide-chevron-down {\n transform: rotate(180deg);\n }\n }\n\n &:active {\n & ul {\n opacity: 0;\n }\n }\n`;\n\nexport const StyledDataDropdownWrapper = styled.span<{\n theme: Theme;\n $isAbsolute?: boolean;\n $marginAuto?: boolean;\n}>`\n position: relative;\n display: flex;\n padding: 10px 0;\n margin: -10px 0;\n\n & span {\n display: flex;\n margin: auto 0;\n }\n\n ${({ $isAbsolute }) =>\n $isAbsolute &&\n css`\n margin: 0;\n padding: 0;\n `}\n\n ${({ $marginAuto }) => $marginAuto && `margin: auto 0;`}\n`;\n\nexport const StyledDataDropdown = styled.ul<{\n theme: Theme;\n $isActive?: boolean;\n $align?: \"left\" | \"center\";\n $isTop?: boolean;\n $isAbsolute?: boolean;\n $minWidth?: string;\n}>`\n position: absolute;\n display: block;\n top: 100%;\n right: 0;\n list-style: none;\n transition: all 0.3s ease;\n min-width: ${({ $minWidth }) => ($minWidth ? $minWidth : \"200px\")};\n text-align: left;\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n overflow: hidden;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-10px);\n z-index: 999;\n\n ${({ $isAbsolute }) =>\n $isAbsolute &&\n css`\n top: calc(100% + 56px);\n right: 20px;\n `}\n\n ${({ $isTop }) => $isTop && `top: initial; bottom: 100%;`}\n\n ${({ $align }) => $align === \"left\" && `right: initial; left: 0;`}\n ${({ $align }) =>\n $align === \"center\" &&\n `right: initial; left: 50%; transform: translateX(-50%) translateY(-10px);`}\n\n ${({ $isActive, $align }) =>\n $isActive &&\n css`\n pointer-events: all;\n opacity: 1;\n transform: translateY(0);\n\n ${$align === \"center\" &&\n `right: initial; left: 50%; transform: translateX(-50%) translateY(0);`}\n `}\n`;\n\nexport const StyledDataDropdownItem = styled.li<{ theme: Theme }>`\n display: flex;\n width: 100%;\n text-align: left;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:last-of-type,\n &:only-of-type {\n border-bottom: none;\n }\n\n & form {\n width: 100%;\n margin: 0;\n padding: 0;\n }\n\n & a,\n & button,\n & .logout-button {\n ${resetButton};\n width: 100%;\n text-align: left;\n padding: 12px 20px;\n font-weight: 600;\n font-family: inherit;\n color: ${({ theme }) => theme.colors.primary};\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n text-decoration: none;\n transition: all 0.3s ease;\n border-radius: 0;\n background: transparent;\n display: flex;\n gap: 10px;\n height: auto;\n min-height: 45px;\n\n &:hover {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.1,\n )};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n\n &:active,\n &:focus {\n box-shadow: none;\n }\n\n * {\n margin: auto 0;\n }\n\n &:focus {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.15,\n )};\n }\n\n &:active {\n background-color: ${({ theme }) =>\n rgba(\n theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,\n 0.2,\n )};\n }\n }\n\n & button {\n border: none;\n\n & svg {\n margin: -2px -5px;\n }\n }\n`;\n\nexport const StyledDataDropdownButton = styled.button<{ theme: Theme }>`\n ${resetButton};\n`;\n\nexport const StyledDataDropdownSelectWrapper = styled.span<{ theme: Theme }>`\n padding: 5px;\n display: block;\n width: 100%;\n\n & select {\n width: 100%;\n }\n`;\n\nexport const StyledDataTagButton = styled.button<{ theme: Theme }>`\n ${resetButton};\n position: absolute;\n top: 0;\n width: 24px;\n height: 100%;\n right: 0;\n border-left: solid 1px\n ${({ theme }) =>\n rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};\n transition: all 0.3s ease;\n\n &:hover {\n background: ${({ theme }) =>\n rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};\n }\n\n & svg {\n width: 16px;\n height: 100%;\n vertical-align: middle;\n\n & path {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n }\n }\n`;\n\nexport const StyledDataTag = styled.span<{\n theme: Theme;\n $color?: \"default\" | \"warning\";\n $capitalize?: boolean;\n}>`\n display: inline-block;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n background: ${({ theme, $color }) =>\n $color === \"warning\" ? theme.colors.warning : theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n font-size: ${({ theme }) => theme.fontSizes.small.xs};\n position: relative;\n font-weight: 500;\n padding: 5px 10px;\n margin: auto 0;\n overflow: hidden;\n ${({ $capitalize }) =>\n $capitalize &&\n css`\n text-transform: capitalize;\n `}\n\n &:has(button) {\n padding: 5px 35px 5px 10px;\n }\n`;\n\nexport const StyledIllustration = styled.div<{ theme: Theme }>`\n text-align: center;\n\n & svg {\n height: auto;\n width: 250px;\n margin: auto;\n }\n`;\n\nexport const StyledIllustrationText = styled.p<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledSmall(theme)};\n`;\n\nexport const StyledDataText = styled.div<{ theme: Theme; $gray?: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme, $gray }) =>\n $gray ? theme.colors.grayDark : theme.colors.dark};\n`;\n\nexport const StyledDataHeader = styled.h1<{ theme: Theme }>`\n ${({ theme }) => styledH3(theme)};\n font-weight: 900;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nexport const StyledMobileOnly = styled.em<{ theme: Theme }>`\n font-style: normal;\n display: inline;\n\n ${mq(\"lg\")} {\n display: none;\n }\n`;\n\nexport const StyledDesktopOnly = styled.em<{ theme: Theme }>`\n font-style: normal;\n display: none;\n\n ${mq(\"lg\")} {\n display: inline;\n }\n`;\n\nexport const StyledAlignMiddle = styled.div<{ theme: Theme }>`\n margin: auto 0;\n`;\n\nexport const StyledLoadingOverlay = styled.div<{\n theme: Theme;\n $isActive?: boolean;\n}>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: ${({ theme }) => theme.colors.light};\n z-index: 1000;\n display: flex;\n transition: all 0.3s ease;\n opacity: 0;\n pointer-events: none;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n pointer-events: all;\n `}\n\n & > * {\n margin: auto;\n }\n`;\n\nexport const StyledFlex1 = styled.div`\n flex: 1;\n`;\n\nexport const StyledIconCircle = styled.span<{ theme: Theme }>`\n border-radius: 50%;\n width: 40px;\n height: 40px;\n min-width: 40px;\n min-height: 40px;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n display: inline-flex;\n color: ${({ theme }) => theme.colors.primary};\n vertical-align: middle;\n\n & svg {\n margin: auto;\n }\n`;\n\nexport const StyledStrong = styled.span<{ theme: Theme }>`\n display: block;\n margin: auto 0;\n color: ${({ theme }) => theme.colors.primary};\n font-weight: 700;\n`;\n\nexport const StyledTextMiddle = styled.span<{ theme: Theme }>`\n display: block;\n margin: auto 0;\n color: ${({ theme }) => theme.colors.dark};\n text-align: left;\n`;\n\nexport const StyledPanelWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n z-index: 100;\n\n & input {\n position: relative;\n z-index: 10;\n }\n`;\n\nexport const StyledPanel = styled.div<{ theme: Theme; $absolute?: boolean }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 2px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n margin: ${({ $absolute }) => ($absolute ? \"-10px 0 0 0\" : \"0\")};\n ${({ $absolute }) =>\n $absolute &&\n css`\n position: absolute;\n width: 100%;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n `}\n max-height: calc(63px * 3);\n overflow-y: auto;\n`;\n\nexport const StyledPanelLabel = styled.div<{ theme: Theme; $moveUp?: boolean }>`\n font-size: ${({ theme }) => theme.fontSizes.small.xs};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.gray};\n padding: ${({ $moveUp }) => ($moveUp ? \"20px 15px 10px\" : \"20px 15px\")};\n`;\n\nexport const StyledPanelContent = styled.div<{ theme: Theme }>`\n display: flex;\n padding: 15px;\n gap: 10px;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n width: 100%;\n text-align: left;\n font-family: ${({ theme }) => theme.fonts.text};\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n font-weight: 500;\n color: ${({ theme }) => theme.colors.primary};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n line-height: ${({ theme }) => theme.lineHeights.text.lg};\n }\n\n &:last-of-type {\n border-bottom: none;\n }\n`;\n\nexport const StyledPanelButton = styled(Link)<{ theme: Theme }>`\n ${resetButton};\n display: flex;\n padding: 15px;\n gap: 10px;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n width: 100%;\n text-align: left;\n font-family: ${({ theme }) => theme.fonts.text};\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n font-weight: 500;\n color: ${({ theme }) => theme.colors.primary};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n\n & .link {\n border-color: ${({ theme }) => theme.colors.primaryDark};\n }\n }\n\n &:focus {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.15)};\n\n & .link {\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n\n &:active {\n background-color: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n\n & .link {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n line-height: ${({ theme }) => theme.lineHeights.text.lg};\n }\n\n &:last-of-type {\n border-bottom: none;\n }\n\n & .link {\n transition: all 0.3s ease;\n pointer-events: none;\n }\n`;\n\nexport const StyledPanelSpan = styled.span<{ theme: Theme }>`\n margin: auto 0;\n display: flex;\n flex: 1;\n line-height: 1.5;\n`;\n\nexport const editableContent = css<{ theme: Theme }>`\n border: dotted 1px transparent;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n transition: all 0.3s ease;\n outline: none;\n cursor: text;\n\n &:hover {\n border: dotted 1px ${({ theme }) => theme.colors.gray};\n }\n\n &:focus {\n border: dotted 1px ${({ theme }) => theme.colors.info};\n }\n`;\n\nexport const StyledDataEditableText = styled.div<{\n theme: Theme;\n $gray?: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme, $gray }) =>\n $gray ? theme.colors.grayDark : theme.colors.dark};\n\n &[contenteditable=\"true\"] {\n ${editableContent};\n }\n`;\n\nexport const StyledSmallButtonWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n\n & .hidden-button {\n opacity: 0;\n pointer-events: none;\n transform: translateY(10px);\n }\n\n &:hover {\n & .hidden-button {\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{\n theme: Theme;\n}>`\n ${resetButton};\n display: flex;\n gap: 5px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n color: ${({ theme }) => theme.colors.primary};\n background: ${({ theme }) => theme.colors.light};\n margin: auto 0;\n min-width: fit-content;\n ${interactiveStyles}\n\n &.hidden-button {\n position: absolute;\n bottom: 1px;\n right: 1px;\n z-index: 98;\n }\n`;\n\nexport const StyledTitle = styled(StyledH1)<{ theme: Theme }>`\n display: block;\n\n &[contenteditable=\"true\"] {\n ${editableContent};\n }\n`;\n\nexport const StyledImage = styled.img<{ theme: Theme; $maxWidth?: string }>`\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n max-width: ${({ $maxWidth }) => ($maxWidth ? $maxWidth : \"100%\")};\n width: 100%;\n height: auto;\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\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 {\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 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";
|
|
@@ -17,10 +17,8 @@ export const interactiveStyles = css<{ theme: Theme }>\`
|
|
|
17
17
|
border: solid 1px transparent;
|
|
18
18
|
box-shadow: 0 0 0 0px \${({ theme }) => theme.colors.primary};
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
border-color: \${({ theme }) => theme.colors.primary};
|
|
23
|
-
}
|
|
20
|
+
&:hover {
|
|
21
|
+
border-color: \${({ theme }) => theme.colors.primary};
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
&:focus {
|
|
@@ -133,18 +131,16 @@ export const StyledDataArrowButton = styled.button<{
|
|
|
133
131
|
color: \${({ theme }) => theme.colors.primary};
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
&
|
|
139
|
-
|
|
140
|
-
stroke: \${({ theme }) => theme.colors.primaryDark};
|
|
141
|
-
}
|
|
134
|
+
&:hover {
|
|
135
|
+
& .lucide-chevron-down {
|
|
136
|
+
& path {
|
|
137
|
+
stroke: \${({ theme }) => theme.colors.primaryDark};
|
|
142
138
|
}
|
|
139
|
+
}
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
141
|
+
& .avatar,
|
|
142
|
+
& .clickable {
|
|
143
|
+
border-color: \${({ theme }) => theme.colors.primary};
|
|
148
144
|
}
|
|
149
145
|
}
|
|
150
146
|
|
|
@@ -330,16 +326,14 @@ export const StyledDataDropdownItem = styled.li<{ theme: Theme }>\`
|
|
|
330
326
|
height: auto;
|
|
331
327
|
min-height: 45px;
|
|
332
328
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
342
|
-
}
|
|
329
|
+
&:hover {
|
|
330
|
+
background-color: \${({ theme }) =>
|
|
331
|
+
rgba(
|
|
332
|
+
theme.isDark ? theme.colors.primaryDark : theme.colors.primaryLight,
|
|
333
|
+
0.1,
|
|
334
|
+
)};
|
|
335
|
+
color: \${({ theme }) =>
|
|
336
|
+
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
343
337
|
}
|
|
344
338
|
|
|
345
339
|
&:active,
|
|
@@ -403,11 +397,9 @@ export const StyledDataTagButton = styled.button<{ theme: Theme }>\`
|
|
|
403
397
|
rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};
|
|
404
398
|
transition: all 0.3s ease;
|
|
405
399
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};
|
|
410
|
-
}
|
|
400
|
+
&:hover {
|
|
401
|
+
background: \${({ theme }) =>
|
|
402
|
+
rgba(theme.isDark ? theme.colors.dark : theme.colors.light, 0.2)};
|
|
411
403
|
}
|
|
412
404
|
|
|
413
405
|
& svg {
|
|
@@ -636,13 +628,11 @@ export const StyledPanelButton = styled(Link)<{ theme: Theme }>\`
|
|
|
636
628
|
text-decoration: none;
|
|
637
629
|
transition: all 0.3s ease;
|
|
638
630
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
background-color: \${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};
|
|
631
|
+
&:hover {
|
|
632
|
+
background-color: \${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};
|
|
642
633
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
634
|
+
& .link {
|
|
635
|
+
border-color: \${({ theme }) => theme.colors.primaryDark};
|
|
646
636
|
}
|
|
647
637
|
}
|
|
648
638
|
|
|
@@ -723,13 +713,11 @@ export const StyledSmallButtonWrapper = styled.div<{ theme: Theme }>\`
|
|
|
723
713
|
transform: translateY(10px);
|
|
724
714
|
}
|
|
725
715
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
transform: translateY(0);
|
|
732
|
-
}
|
|
716
|
+
&:hover {
|
|
717
|
+
& .hidden-button {
|
|
718
|
+
opacity: 1;
|
|
719
|
+
pointer-events: all;
|
|
720
|
+
transform: translateY(0);
|
|
733
721
|
}
|
|
734
722
|
}
|
|
735
723
|
\`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const staticLinksTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\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 StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: fixed;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n top: 70px;\n padding: 10px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n z-index: 999;\n transition: all 0.3s ease;\n margin: auto;\n background: ${({ theme }) => theme.colors.light};\n overflow-x: auto;\n left: 50%;\n transform: translateX(-50%);\n\n ${mq(\"lg\")} {\n padding: 20px;\n height: 73px;\n top: 0;\n max-width: calc(100vw - 640px);\n width: 100%;\n margin: auto;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: nowrap;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n
|
|
1
|
+
export declare const staticLinksTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\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 StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: fixed;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n top: 70px;\n padding: 10px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n z-index: 999;\n transition: all 0.3s ease;\n margin: auto;\n background: ${({ theme }) => theme.colors.light};\n overflow-x: auto;\n left: 50%;\n transform: translateX(-50%);\n\n ${mq(\"lg\")} {\n padding: 20px;\n height: 73px;\n top: 0;\n max-width: calc(100vw - 640px);\n width: 100%;\n margin: auto;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: nowrap;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nconst StyledEmpty = styled.div`\n width: 1px;\n max-width: 1px;\n min-width: 1px;\n overflow: hidden;\n text-indent: -9999px;\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n <StyledEmpty />\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
|
|
@@ -91,11 +91,9 @@ const StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>\`
|
|
|
91
91
|
transform: translateY(-50%);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
98
|
-
}
|
|
94
|
+
&:hover {
|
|
95
|
+
color: \${({ theme }) =>
|
|
96
|
+
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
99
97
|
}
|
|
100
98
|
\`;
|
|
101
99
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const themeToggleTemplate = "\"use client\";\nimport { Theme, resetButton } from \"cherry-styled-components/src/lib\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { theme as themeLight, themeDark } from \"@/app/theme\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 30px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ theme }) =>\n theme.isDark &&\n css`\n transform: translateX(27px);\n `}\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n
|
|
1
|
+
export declare const themeToggleTemplate = "\"use client\";\nimport { Theme, resetButton } from \"cherry-styled-components/src/lib\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { theme as themeLight, themeDark } from \"@/app/theme\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 30px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ theme }) =>\n theme.isDark &&\n css`\n transform: translateX(27px);\n `}\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nfunction ToggleTheme({ $hidden }: { $hidden?: boolean }) {\n const { setTheme } = useThemeOverride();\n const theme = useTheme() as Theme;\n\n return (\n <StyledThemeToggle\n onClick={async () => {\n const nextTheme = theme.isDark ? \"light\" : \"dark\";\n setTheme(nextTheme === \"light\" ? themeLight : themeDark);\n await fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: nextTheme }),\n });\n }}\n $hidden={$hidden}\n aria-label=\"Toggle Theme\"\n >\n <Icon name=\"Sun\" className=\"light\" />\n <Icon name=\"MoonStar\" className=\"dark\" />\n </StyledThemeToggle>\n );\n}\n\nfunction ToggleThemeLoading() {\n return (\n <StyledThemeToggle $hidden aria-label=\"Toggle Theme\">\n <Icon name=\"MoonStar\" className=\"dark\" />\n <Icon name=\"Sun\" className=\"light\" />\n </StyledThemeToggle>\n );\n}\n\nexport { ToggleTheme, ToggleThemeLoading };\n";
|
|
@@ -59,15 +59,14 @@ const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
|
|
|
59
59
|
stroke: \${({ theme }) => theme.colors.primary};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
&:hover {
|
|
63
|
+
transform: scale(1.05);
|
|
64
|
+
color: \${({ theme }) =>
|
|
65
|
+
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
66
|
+
|
|
67
|
+
& svg[stroke] {
|
|
68
|
+
stroke: \${({ theme }) =>
|
|
66
69
|
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
67
|
-
Toggle & svg[stroke] {
|
|
68
|
-
stroke: \${({ theme }) =>
|
|
69
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
70
|
-
}
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const eslintConfigTeamplate = "import nextConfig from \"eslint-config-next\";\n\nconst config = [...nextConfig];\n\nexport default config;\n";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and
|
|
1
|
+
export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and assets\"\ndescription: \"Serve static files like images, favicons, fonts, and Open Graph previews from the public directory.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 5\n---\n# Media and assets\nDoccupine watches a `public` directory in your project root (the same folder where you execute `npx doccupine`) and copies its contents into the generated Next.js `public` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.\n\n## The public directory\nCreate a `public` folder at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.\n\n```text\nmy-docs/\n\u251C\u2500\u2500 public/\n\u2502 \u251C\u2500\u2500 favicon.ico\n\u2502 \u251C\u2500\u2500 og-image.png\n\u2502 \u251C\u2500\u2500 logo.svg\n\u2502 \u2514\u2500\u2500 fonts/\n\u2502 \u2514\u2500\u2500 custom-font.woff2\n\u251C\u2500\u2500 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\nFiles in the `public` directory are available at the root of your deployed domain. The path inside `public` maps directly to the URL path.\n\n| File | URL |\n|-------------------------------|--------------------------------------------|\n| `public/favicon.ico` | `https://your-domain.com/favicon.ico` |\n| `public/og-image.png` | `https://your-domain.com/og-image.png` |\n| `public/logo.svg` | `https://your-domain.com/logo.svg` |\n| `public/fonts/custom-font.woff2` | `https://your-domain.com/fonts/custom-font.woff2` |\n\n## Common use cases\n\n### Favicon\nDrop a `favicon.ico` into the `public` folder. Browsers pick it up automatically from the root path.\n\n```text\npublic/favicon.ico \u2192 https://your-domain.com/favicon.ico\n```\n\n### Open Graph preview image\nAdd an image for link previews on social media. Reference it in your `config.json` so Doccupine sets the correct meta tags.\n\n```text\npublic/og-image.png \u2192 https://your-domain.com/og-image.png\n```\n\n### Custom fonts\nPlace font files in `public` and reference them from your `fonts.json`. See the **Fonts** page for full configuration details.\n\n```text\npublic/fonts/custom-font.woff2 \u2192 https://your-domain.com/fonts/custom-font.woff2\n```\n\n### Images and other media\nAny image or file you want to reference in your MDX pages can live in `public`. Use a root-relative path in your content:\n\n```mdx\n\n```\n\n## Live syncing\nDoccupine watches the `public` directory for changes while running. When you add, update, or remove a file, the generated site is updated automatically \u2014 no restart required.\n\n## Tips\n- **Keep it flat**: For a small number of files, placing them directly in `public` keeps paths short and simple.\n- **Use subfolders for organization**: For larger projects, group assets into folders like `public/fonts`, `public/images`, or `public/icons`.\n- **Mind file size**: Large files increase deployment size and load times. Optimize images before adding them.\n- **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., `og-image.png`) for predictable URLs.";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const mediaAndAssetsMdxTemplate = `---
|
|
2
|
-
title: "Media and
|
|
2
|
+
title: "Media and assets"
|
|
3
3
|
description: "Serve static files like images, favicons, fonts, and Open Graph previews from the public directory."
|
|
4
4
|
date: "2025-01-15"
|
|
5
5
|
category: "Configuration"
|
|
6
6
|
categoryOrder: 3
|
|
7
7
|
order: 5
|
|
8
8
|
---
|
|
9
|
-
# Media and
|
|
9
|
+
# Media and assets
|
|
10
10
|
Doccupine watches a \`public\` directory in your project root (the same folder where you execute \`npx doccupine\`) and copies its contents into the generated Next.js \`public\` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.
|
|
11
11
|
|
|
12
12
|
## The public directory
|
|
@@ -6,16 +6,15 @@ export const packageJsonTemplate = JSON.stringify({
|
|
|
6
6
|
dev: "next dev",
|
|
7
7
|
build: "next build",
|
|
8
8
|
start: "next start",
|
|
9
|
-
lint: "
|
|
9
|
+
lint: "eslint .",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
"@langchain/anthropic": "^1.3.
|
|
12
|
+
"@langchain/anthropic": "^1.3.15",
|
|
13
13
|
"@langchain/google-genai": "^2.1.15",
|
|
14
|
-
"@langchain/openai": "^1.2.
|
|
14
|
+
"@langchain/openai": "^1.2.5",
|
|
15
15
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
16
|
-
langchain: "^1.2.
|
|
16
|
+
langchain: "^1.2.18",
|
|
17
17
|
next: "16.1.6",
|
|
18
|
-
openai: "^6.17.0",
|
|
19
18
|
react: "19.2.4",
|
|
20
19
|
"react-dom": "19.2.4",
|
|
21
20
|
},
|
|
@@ -25,7 +24,7 @@ export const packageJsonTemplate = JSON.stringify({
|
|
|
25
24
|
"@types/react": "^19",
|
|
26
25
|
"@types/react-dom": "^19",
|
|
27
26
|
"baseline-browser-mapping": "^2.9.19",
|
|
28
|
-
"cherry-styled-components": "^0.1.
|
|
27
|
+
"cherry-styled-components": "^0.1.6",
|
|
29
28
|
eslint: "^9",
|
|
30
29
|
"eslint-config-next": "16.1.6",
|
|
31
30
|
"lucide-react": "^0.563.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doccupine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "Document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
"commander": "^14.0.3",
|
|
37
37
|
"fs-extra": "^11.3.3",
|
|
38
38
|
"gray-matter": "^4.0.3",
|
|
39
|
-
"
|
|
39
|
+
"next": "^16.1.6",
|
|
40
|
+
"prompts": "^2.4.2",
|
|
41
|
+
"react": "^19.2.4",
|
|
42
|
+
"react-dom": "^19.2.4"
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@types/chokidar": "^2.1.7",
|