doccupine 0.0.87 → 0.0.89
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/README.md +15 -2
- package/dist/index.js +177 -4
- package/dist/lib/layout.js +38 -24
- package/dist/lib/metadata.d.ts +30 -0
- package/dist/lib/metadata.js +98 -1
- package/dist/lib/structures.js +0 -2
- package/dist/lib/types.d.ts +1 -0
- package/dist/templates/app/robots.d.ts +1 -1
- package/dist/templates/app/robots.js +28 -1
- package/dist/templates/app/sitemap.d.ts +7 -0
- package/dist/templates/app/sitemap.js +56 -0
- package/dist/templates/app/theme.d.ts +1 -1
- package/dist/templates/app/theme.js +84 -19
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +26 -27
- package/dist/templates/components/SearchModalContent.d.ts +1 -1
- package/dist/templates/components/SearchModalContent.js +12 -6
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +3 -1
- package/dist/templates/components/layout/Accordion.d.ts +1 -1
- package/dist/templates/components/layout/Accordion.js +2 -1
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +4 -6
- package/dist/templates/components/layout/Button.d.ts +1 -1
- package/dist/templates/components/layout/Button.js +10 -0
- package/dist/templates/components/layout/Callout.d.ts +1 -1
- package/dist/templates/components/layout/Callout.js +75 -20
- package/dist/templates/components/layout/Card.d.ts +1 -1
- package/dist/templates/components/layout/Card.js +2 -1
- package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/CherryThemeProvider.js +6 -12
- package/dist/templates/components/layout/ClientThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/ClientThemeProvider.js +45 -40
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +223 -255
- package/dist/templates/components/layout/ColorSwatch.d.ts +1 -1
- package/dist/templates/components/layout/ColorSwatch.js +2 -2
- package/dist/templates/components/layout/Columns.d.ts +1 -1
- package/dist/templates/components/layout/Columns.js +1 -1
- package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
- package/dist/templates/components/layout/DemoTheme.js +65 -167
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +13 -19
- package/dist/templates/components/layout/Field.d.ts +1 -1
- package/dist/templates/components/layout/Field.js +6 -4
- package/dist/templates/components/layout/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +1 -2
- package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
- package/dist/templates/components/layout/GlobalStyles.js +63 -10
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +14 -11
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +4 -5
- package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
- package/dist/templates/components/layout/StaticLinks.js +4 -6
- package/dist/templates/components/layout/Steps.d.ts +1 -1
- package/dist/templates/components/layout/Steps.js +3 -3
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +5 -2
- package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
- package/dist/templates/components/layout/ThemeToggle.js +17 -19
- package/dist/templates/components/layout/Typography.d.ts +1 -1
- package/dist/templates/components/layout/Typography.js +1 -1
- package/dist/templates/components/layout/Update.d.ts +1 -1
- package/dist/templates/components/layout/Update.js +4 -3
- package/dist/templates/env.example.d.ts +1 -1
- package/dist/templates/env.example.js +5 -1
- package/dist/templates/mdx/deployment-and-hosting.mdx.d.ts +1 -1
- package/dist/templates/mdx/deployment-and-hosting.mdx.js +19 -0
- package/dist/templates/mdx/globals.mdx.d.ts +1 -1
- package/dist/templates/mdx/globals.mdx.js +3 -1
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +5 -1
- package/dist/templates/package.js +17 -18
- package/dist/templates/proxy.js +14 -20
- package/dist/templates/utils/config.d.ts +1 -1
- package/dist/templates/utils/config.js +1 -0
- package/package.json +8 -8
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const searchModalContentTemplate = `"use client";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import styled, { css, keyframes } from "styled-components";
|
|
4
|
-
import { rgba } from "polished";
|
|
5
4
|
import { Search } from "lucide-react";
|
|
6
5
|
import { mq, Theme } from "@/app/theme";
|
|
7
6
|
import { Spinner } from "@/components/Spinner";
|
|
@@ -61,8 +60,9 @@ const StyledBackdrop = styled.div<{ theme: Theme; $isClosing: boolean }>\`
|
|
|
61
60
|
position: fixed;
|
|
62
61
|
inset: 0;
|
|
63
62
|
z-index: 9999;
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
/* Backdrop is intentionally near-black in both modes — covers everything
|
|
64
|
+
behind the modal regardless of the user's theme. */
|
|
65
|
+
background: rgba(0, 0, 0, 0.5);
|
|
66
66
|
backdrop-filter: blur(4px);
|
|
67
67
|
-webkit-backdrop-filter: blur(4px);
|
|
68
68
|
display: flex;
|
|
@@ -148,11 +148,16 @@ const StyledResultItem = styled.li<{ theme: Theme; $isActive: boolean }>\`
|
|
|
148
148
|
\${({ $isActive, theme }) =>
|
|
149
149
|
$isActive &&
|
|
150
150
|
css\`
|
|
151
|
-
background:
|
|
151
|
+
background: color-mix(
|
|
152
|
+
in srgb,
|
|
153
|
+
\${theme.colors.primaryLight} 20%,
|
|
154
|
+
transparent
|
|
155
|
+
);
|
|
152
156
|
\`}
|
|
153
157
|
|
|
154
158
|
&:hover {
|
|
155
|
-
background: \${({ theme }) =>
|
|
159
|
+
background: \${({ theme }) =>
|
|
160
|
+
\`color-mix(in srgb, \${theme.colors.primaryLight} 15%, transparent)\`};
|
|
156
161
|
}
|
|
157
162
|
\`;
|
|
158
163
|
|
|
@@ -181,7 +186,8 @@ const StyledSnippet = styled.span<{ theme: Theme }>\`
|
|
|
181
186
|
white-space: nowrap;
|
|
182
187
|
|
|
183
188
|
& mark {
|
|
184
|
-
background: \${({ theme }) =>
|
|
189
|
+
background: \${({ theme }) =>
|
|
190
|
+
\`color-mix(in srgb, \${theme.colors.primaryLight} 35%, transparent)\`};
|
|
185
191
|
color: inherit;
|
|
186
192
|
border-radius: 4px;
|
|
187
193
|
padding: 0 1px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sideBarTemplate = "\"use client\";\nimport { useContext, useState, Suspense } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Flex, Space } from \"cherry-styled-components\";\nimport {\n DocsSidebar,\n SectionBarContext,\n StyledSidebar,\n StyledSidebarList,\n StyledSidebarListItem,\n StyledStrong,\n StyledSidebarListItemLink,\n StyledSidebarFooter,\n StyleMobileBar,\n StyledMobileBurger,\n} from \"@/components/layout/DocsComponents\";\nimport {\n ToggleTheme,\n ToggleThemeLoading,\n} from \"@/components/layout/ThemeToggle\";\nimport { useLockBodyScroll } from \"@/components/LockBodyScroll\";\n\ntype NavItem = {\n label: string;\n links: NavItemLink[];\n};\n\ntype NavItemLink = {\n slug: string;\n title: string;\n};\n\ninterface SideBarProps {\n result: NavItem[];\n}\n\nfunction SideBar({ result }: SideBarProps) {\n const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\n const pathname = usePathname();\n\n useLockBodyScroll(isMobileMenuOpen);\n\n return (\n <DocsSidebar>\n <StyleMobileBar\n onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}\n $isActive={isMobileMenuOpen}\n aria-label={isMobileMenuOpen ? \"Close navigation menu\" : \"Open navigation menu\"}\n aria-expanded={isMobileMenuOpen}\n >\n <StyledMobileBurger $isActive={isMobileMenuOpen} />\n </StyleMobileBar>\n\n <StyledSidebar\n $isActive={isMobileMenuOpen}\n $hasSectionBar={hasSectionBar}\n >\n {result &&\n result.map((item: NavItem, index: number) => {\n return (\n <StyledSidebarList key={index}>\n <StyledSidebarListItem>\n <StyledStrong>{item.label}</StyledStrong>{\" \"}\n </StyledSidebarListItem>\n <li>\n <Space $size={20} />\n </li>\n {item.links &&\n item.links.map((link: NavItemLink, indexChild: number) => {\n return (\n <StyledSidebarListItem key={indexChild}>\n <StyledSidebarListItemLink\n href={`/${link.slug}`}\n $isActive={pathname === `/${link.slug}`}\n onClick={() => setIsMobileMenuOpen(false)}\n >\n {link.title}\n </StyledSidebarListItemLink>\n </StyledSidebarListItem>\n );\n })}\n <li aria-hidden=\"true\">\n <Space $size={20} />\n </li>\n </StyledSidebarList>\n );\n })}\n <StyledSidebarFooter>\n <Flex $xsJustifyContent=\"flex-start\" $lgJustifyContent=\"flex-end\">\n <Suspense fallback={<ToggleThemeLoading />}>\n <ToggleTheme />\n </Suspense>\n </Flex>\n </StyledSidebarFooter>\n </StyledSidebar>\n </DocsSidebar>\n );\n}\n\nexport { SideBar };\n";
|
|
1
|
+
export declare const sideBarTemplate = "\"use client\";\nimport { useContext, useState, Suspense } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Flex, Space } from \"cherry-styled-components\";\nimport {\n DocsSidebar,\n SectionBarContext,\n StyledSidebar,\n StyledSidebarList,\n StyledSidebarListItem,\n StyledStrong,\n StyledSidebarListItemLink,\n StyledSidebarFooter,\n StyleMobileBar,\n StyledMobileBurger,\n} from \"@/components/layout/DocsComponents\";\nimport {\n ToggleTheme,\n ToggleThemeLoading,\n} from \"@/components/layout/ThemeToggle\";\nimport { useLockBodyScroll } from \"@/components/LockBodyScroll\";\n\ntype NavItem = {\n label: string;\n links: NavItemLink[];\n};\n\ntype NavItemLink = {\n slug: string;\n title: string;\n};\n\ninterface SideBarProps {\n result: NavItem[];\n}\n\nfunction SideBar({ result }: SideBarProps) {\n const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\n const pathname = usePathname();\n\n useLockBodyScroll(isMobileMenuOpen);\n\n return (\n <DocsSidebar>\n <StyleMobileBar\n onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}\n $isActive={isMobileMenuOpen}\n aria-label={\n isMobileMenuOpen ? \"Close navigation menu\" : \"Open navigation menu\"\n }\n aria-expanded={isMobileMenuOpen}\n >\n <StyledMobileBurger $isActive={isMobileMenuOpen} />\n </StyleMobileBar>\n\n <StyledSidebar\n $isActive={isMobileMenuOpen}\n $hasSectionBar={hasSectionBar}\n >\n {result &&\n result.map((item: NavItem, index: number) => {\n return (\n <StyledSidebarList key={index}>\n <StyledSidebarListItem>\n <StyledStrong>{item.label}</StyledStrong>{\" \"}\n </StyledSidebarListItem>\n <li>\n <Space $size={20} />\n </li>\n {item.links &&\n item.links.map((link: NavItemLink, indexChild: number) => {\n return (\n <StyledSidebarListItem key={indexChild}>\n <StyledSidebarListItemLink\n href={`/${link.slug}`}\n $isActive={pathname === `/${link.slug}`}\n onClick={() => setIsMobileMenuOpen(false)}\n >\n {link.title}\n </StyledSidebarListItemLink>\n </StyledSidebarListItem>\n );\n })}\n <li aria-hidden=\"true\">\n <Space $size={20} />\n </li>\n </StyledSidebarList>\n );\n })}\n <StyledSidebarFooter>\n <Flex $xsJustifyContent=\"flex-start\" $lgJustifyContent=\"flex-end\">\n <Suspense fallback={<ToggleThemeLoading />}>\n <ToggleTheme />\n </Suspense>\n </Flex>\n </StyledSidebarFooter>\n </StyledSidebar>\n </DocsSidebar>\n );\n}\n\nexport { SideBar };\n";
|
|
@@ -46,7 +46,9 @@ function SideBar({ result }: SideBarProps) {
|
|
|
46
46
|
<StyleMobileBar
|
|
47
47
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
48
48
|
$isActive={isMobileMenuOpen}
|
|
49
|
-
aria-label={
|
|
49
|
+
aria-label={
|
|
50
|
+
isMobileMenuOpen ? "Close navigation menu" : "Open navigation menu"
|
|
51
|
+
}
|
|
50
52
|
aria-expanded={isMobileMenuOpen}
|
|
51
53
|
>
|
|
52
54
|
<StyledMobileBurger $isActive={isMobileMenuOpen} />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText
|
|
1
|
+
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\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\ninterface 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\n onClick={() => setIsOpen(!isOpen)}\n $isOpen={isOpen}\n role=\"button\"\n aria-expanded={isOpen}\n >\n {title} <Icon name=\"ChevronDown\" />\n </StyledAccordionTitle>\n <StyledAccordionContent $isOpen={isOpen}>\n {children}\n </StyledAccordionContent>\n </StyledAccordion>\n );\n}\n\nexport { Accordion };\n";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export const accordionTemplate = `"use client";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import styled, { css } from "styled-components";
|
|
4
|
-
import { styledText
|
|
4
|
+
import { styledText } from "cherry-styled-components";
|
|
5
|
+
import { Theme } from "@/app/theme";
|
|
5
6
|
import { Icon } from "@/components/layout/Icon";
|
|
6
7
|
|
|
7
8
|
const StyledAccordion = styled.div<{ theme: Theme }>\`
|
|
@@ -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 {
|
|
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 { resetButton, Textarea } from \"cherry-styled-components\";\nimport { SectionBarContext } from \"@/components/layout/DocsComponents\";\nimport { StyledSmallButton } from \"@/components/layout/SharedStyled\";\n\ninterface ActionBarProps {\n children: React.ReactNode;\n content: string;\n}\n\nconst StyledActionBar = styled.div<{\n theme: Theme;\n $isChatOpen?: boolean;\n}>`\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n left: 0;\n padding: 12px 0;\n display: flex;\n justify-content: space-between;\n width: 100%;\n max-width: 640px;\n margin: auto;\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 12px 0;\n }\n`;\n\nconst StyledActionBarContent = styled.div`\n margin: auto 0;\n`;\n\nconst StyledCopyButton = styled(StyledSmallButton)<{\n theme: Theme;\n $copied: boolean;\n}>`\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\n & svg.lucide {\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\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 }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\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 }) => theme.colors.accent};\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.accent};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nconst StyledContent = styled.div<{\n theme: Theme;\n $hasSectionBar?: boolean;\n}>`\n padding-top: 20px;\n transition: all 0.3s ease;\n\n & textarea {\n max-width: 640px;\n margin: auto;\n width: 100%;\n height: 100%;\n min-height: calc(\n 100vh - ${({ $hasSectionBar }) => ($hasSectionBar ? 202 : 160)}px\n );\n\n ${mq(\"lg\")} {\n min-height: calc(100vh - 159px);\n }\n }\n`;\n\nfunction ActionBar({ children, content }: ActionBarProps) {\n const [isView, setIsView] = useState(true);\n const [copied, setCopied] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\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>\n <StyledCopyButton onClick={handleCopyContent} $copied={copied}>\n {copied ? (\n <>\n <Icon name=\"check\" size={16} />\n Copied!\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={16} />\n Copy content\n </>\n )}\n </StyledCopyButton>\n <StyledActionBarContent>\n <StyledToggle\n onClick={() => setIsView(!isView)}\n aria-label=\"Toggle View\"\n $isActive={isView}\n >\n <Icon name=\"Eye\" />\n <Icon name=\"CodeXml\" />\n </StyledToggle>\n </StyledActionBarContent>\n </StyledActionBar>\n {isView && (\n <StyledContent $hasSectionBar={hasSectionBar}>{children}</StyledContent>\n )}\n {!isView && (\n <StyledContent $hasSectionBar={hasSectionBar}>\n <Textarea defaultValue={content} $fullWidth />\n </StyledContent>\n )}\n </>\n );\n}\n\nexport { ActionBar };\n";
|
|
@@ -3,7 +3,6 @@ import { useContext, useState } from "react";
|
|
|
3
3
|
import styled, { css } from "styled-components";
|
|
4
4
|
import { Icon } from "@/components/layout/Icon";
|
|
5
5
|
import { mq, Theme } from "@/app/theme";
|
|
6
|
-
import { rgba } from "polished";
|
|
7
6
|
import { resetButton, Textarea } from "cherry-styled-components";
|
|
8
7
|
import { SectionBarContext } from "@/components/layout/DocsComponents";
|
|
9
8
|
import { StyledSmallButton } from "@/components/layout/SharedStyled";
|
|
@@ -72,7 +71,8 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
|
72
71
|
width: 24px;
|
|
73
72
|
height: 24px;
|
|
74
73
|
border-radius: 50%;
|
|
75
|
-
background: \${({ theme }) =>
|
|
74
|
+
background: \${({ theme }) =>
|
|
75
|
+
\`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
|
|
76
76
|
transition: all 0.3s ease;
|
|
77
77
|
z-index: 1;
|
|
78
78
|
\${({ $isActive }) =>
|
|
@@ -106,12 +106,10 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
|
106
106
|
|
|
107
107
|
&:hover {
|
|
108
108
|
transform: scale(1.05);
|
|
109
|
-
color: \${({ theme }) =>
|
|
110
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
109
|
+
color: \${({ theme }) => theme.colors.accent};
|
|
111
110
|
|
|
112
111
|
& svg[stroke] {
|
|
113
|
-
stroke: \${({ theme }) =>
|
|
114
|
-
theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
|
|
112
|
+
stroke: \${({ theme }) => theme.colors.accent};
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
|
|
1
|
+
export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\n// Cherry's buttonStyles picks filled-button text via `isDark ? colors.dark : colors.light`.\n// Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so\n// the fallback resolves to --color-light (black in dark mode). Re-pin to `surface`,\n// which resolves to white in both modes, for the filled, non-disabled case.\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
|
|
@@ -20,10 +20,17 @@ interface LinkButtonProps extends ButtonProps {
|
|
|
20
20
|
theme?: typeof localTheme;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Cherry's buttonStyles picks filled-button text via \`isDark ? colors.dark : colors.light\`.
|
|
24
|
+
// Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so
|
|
25
|
+
// the fallback resolves to --color-light (black in dark mode). Re-pin to \`surface\`,
|
|
26
|
+
// which resolves to white in both modes, for the filled, non-disabled case.
|
|
23
27
|
const StyledLinkButton = styled(Link)<LinkButtonProps>\`
|
|
24
28
|
\${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>
|
|
25
29
|
buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}
|
|
26
30
|
|
|
31
|
+
\${({ theme, $outline, disabled }) =>
|
|
32
|
+
!disabled && !$outline && \`color: \${theme.colors.surface};\`}
|
|
33
|
+
|
|
27
34
|
& p {
|
|
28
35
|
color: inherit !important;
|
|
29
36
|
}
|
|
@@ -39,6 +46,9 @@ const ButtonBase = styled.button<ButtonProps>\`
|
|
|
39
46
|
\${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>
|
|
40
47
|
buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}
|
|
41
48
|
|
|
49
|
+
\${({ theme, $outline, disabled }) =>
|
|
50
|
+
!disabled && !$outline && \`color: \${theme.colors.surface};\`}
|
|
51
|
+
|
|
42
52
|
& p {
|
|
43
53
|
color: inherit !important;
|
|
44
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const calloutTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\ntype CalloutType = \"note\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\nconst StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>`\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 }) => styledSmall(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n\n & svg {\n vertical-align: middle;\n min-width: min-content;\n margin: 3px 10px 0 0;\n }\n\n ${({
|
|
1
|
+
export declare const calloutTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\ntype CalloutType = \"note\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\nconst StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>`\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 }) => styledSmall(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n\n & svg {\n vertical-align: middle;\n min-width: min-content;\n margin: 3px 10px 0 0;\n }\n\n /* Callout tints are alert-semantic (info-blue, warning-amber, danger-red,\n etc.) and intentionally independent of theme.json. Light-mode values are\n defined by default; dark-mode overrides live in :root.dark & blocks so\n the swap happens via the active <html> class with no re-render. */\n ${({ $type }) =>\n $type === \"note\" &&\n css`\n border-color: #0ea5e933;\n background: #f0f9ff80;\n\n & svg.lucide,\n & p {\n color: #0c4a6e;\n }\n\n :root.dark & {\n border-color: #0ea5e94d;\n background: #0ea5e91a;\n\n & svg.lucide,\n & p {\n color: #bae6fd;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"info\" &&\n css`\n border-color: #71717a33;\n background: #fafafa80;\n\n & svg.lucide,\n & .lucide,\n & p {\n color: #18181b;\n }\n\n :root.dark & {\n border-color: #71717a4d;\n background: #71717a1a;\n\n & svg.lucide,\n & .lucide,\n & p {\n color: #e4e4e7;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"warning\" &&\n css`\n border-color: #f59e0b33;\n background: #fffbeb80;\n\n & svg.lucide,\n & p {\n color: #78350f;\n }\n\n :root.dark & {\n border-color: #f59e0b4d;\n background: #f59e0b1a;\n\n & svg.lucide,\n & p {\n color: #fde68a;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"danger\" &&\n css`\n border-color: #ef444433;\n background: #fef2f280;\n\n & svg.lucide,\n & p {\n color: #7f1d1d;\n }\n\n :root.dark & {\n border-color: #ef44444d;\n background: #ef44441a;\n\n & svg.lucide,\n & p {\n color: #fecaca;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"success\" &&\n css`\n border-color: #10b98133;\n background: #ecfdf580;\n\n & svg.lucide,\n & p {\n color: #064e3b;\n }\n\n :root.dark & {\n border-color: #10b9814d;\n background: #10b9811a;\n\n & svg.lucide,\n & p {\n color: #a7f3d0;\n }\n }\n `}\n`;\n\nconst StyledChildren = styled.span`\n display: flex;\n flex-direction: column;\n gap: 10px;\n`;\n\ninterface CalloutProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n icon?: IconProps;\n type?: CalloutType;\n}\n\nfunction Callout({ children, type, icon }: CalloutProps) {\n const iconType =\n type === \"note\"\n ? \"CircleAlert\"\n : type === \"info\"\n ? \"Info\"\n : type === \"warning\"\n ? \"TriangleAlert\"\n : type === \"danger\"\n ? \"OctagonAlert\"\n : type === \"success\"\n ? \"Check\"\n : (icon as IconProps);\n return (\n <StyledCallout $type={type}>\n <Icon name={iconType} size={16} />\n <StyledChildren>{children}</StyledChildren>\n </StyledCallout>\n );\n}\n\nexport { Callout };\n";
|
|
@@ -22,64 +22,119 @@ const StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>\`
|
|
|
22
22
|
margin: 3px 10px 0 0;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/* Callout tints are alert-semantic (info-blue, warning-amber, danger-red,
|
|
26
|
+
etc.) and intentionally independent of theme.json. Light-mode values are
|
|
27
|
+
defined by default; dark-mode overrides live in :root.dark & blocks so
|
|
28
|
+
the swap happens via the active <html> class with no re-render. */
|
|
29
|
+
\${({ $type }) =>
|
|
26
30
|
$type === "note" &&
|
|
27
31
|
css\`
|
|
28
|
-
border-color:
|
|
29
|
-
background:
|
|
32
|
+
border-color: #0ea5e933;
|
|
33
|
+
background: #f0f9ff80;
|
|
30
34
|
|
|
31
35
|
& svg.lucide,
|
|
32
36
|
& p {
|
|
33
|
-
color:
|
|
37
|
+
color: #0c4a6e;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:root.dark & {
|
|
41
|
+
border-color: #0ea5e94d;
|
|
42
|
+
background: #0ea5e91a;
|
|
43
|
+
|
|
44
|
+
& svg.lucide,
|
|
45
|
+
& p {
|
|
46
|
+
color: #bae6fd;
|
|
47
|
+
}
|
|
34
48
|
}
|
|
35
49
|
\`}
|
|
36
50
|
|
|
37
|
-
\${({
|
|
51
|
+
\${({ $type }) =>
|
|
38
52
|
$type === "info" &&
|
|
39
53
|
css\`
|
|
40
|
-
border-color:
|
|
41
|
-
background:
|
|
54
|
+
border-color: #71717a33;
|
|
55
|
+
background: #fafafa80;
|
|
42
56
|
|
|
43
57
|
& svg.lucide,
|
|
44
58
|
& .lucide,
|
|
45
59
|
& p {
|
|
46
|
-
color:
|
|
60
|
+
color: #18181b;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:root.dark & {
|
|
64
|
+
border-color: #71717a4d;
|
|
65
|
+
background: #71717a1a;
|
|
66
|
+
|
|
67
|
+
& svg.lucide,
|
|
68
|
+
& .lucide,
|
|
69
|
+
& p {
|
|
70
|
+
color: #e4e4e7;
|
|
71
|
+
}
|
|
47
72
|
}
|
|
48
73
|
\`}
|
|
49
74
|
|
|
50
|
-
\${({
|
|
75
|
+
\${({ $type }) =>
|
|
51
76
|
$type === "warning" &&
|
|
52
77
|
css\`
|
|
53
|
-
border-color:
|
|
54
|
-
background:
|
|
78
|
+
border-color: #f59e0b33;
|
|
79
|
+
background: #fffbeb80;
|
|
55
80
|
|
|
56
81
|
& svg.lucide,
|
|
57
82
|
& p {
|
|
58
|
-
color:
|
|
83
|
+
color: #78350f;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:root.dark & {
|
|
87
|
+
border-color: #f59e0b4d;
|
|
88
|
+
background: #f59e0b1a;
|
|
89
|
+
|
|
90
|
+
& svg.lucide,
|
|
91
|
+
& p {
|
|
92
|
+
color: #fde68a;
|
|
93
|
+
}
|
|
59
94
|
}
|
|
60
95
|
\`}
|
|
61
96
|
|
|
62
|
-
\${({
|
|
97
|
+
\${({ $type }) =>
|
|
63
98
|
$type === "danger" &&
|
|
64
99
|
css\`
|
|
65
|
-
border-color:
|
|
66
|
-
background:
|
|
100
|
+
border-color: #ef444433;
|
|
101
|
+
background: #fef2f280;
|
|
67
102
|
|
|
68
103
|
& svg.lucide,
|
|
69
104
|
& p {
|
|
70
|
-
color:
|
|
105
|
+
color: #7f1d1d;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:root.dark & {
|
|
109
|
+
border-color: #ef44444d;
|
|
110
|
+
background: #ef44441a;
|
|
111
|
+
|
|
112
|
+
& svg.lucide,
|
|
113
|
+
& p {
|
|
114
|
+
color: #fecaca;
|
|
115
|
+
}
|
|
71
116
|
}
|
|
72
117
|
\`}
|
|
73
118
|
|
|
74
|
-
\${({
|
|
119
|
+
\${({ $type }) =>
|
|
75
120
|
$type === "success" &&
|
|
76
121
|
css\`
|
|
77
|
-
border-color:
|
|
78
|
-
background:
|
|
122
|
+
border-color: #10b98133;
|
|
123
|
+
background: #ecfdf580;
|
|
79
124
|
|
|
80
125
|
& svg.lucide,
|
|
81
126
|
& p {
|
|
82
|
-
color:
|
|
127
|
+
color: #064e3b;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:root.dark & {
|
|
131
|
+
border-color: #10b9814d;
|
|
132
|
+
background: #10b9811a;
|
|
133
|
+
|
|
134
|
+
& svg.lucide,
|
|
135
|
+
& p {
|
|
136
|
+
color: #a7f3d0;
|
|
137
|
+
}
|
|
83
138
|
}
|
|
84
139
|
\`}
|
|
85
140
|
\`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const cardTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { styledText
|
|
1
|
+
export declare const cardTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\n\nconst cardStyles = css<{ 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 color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nconst StyledCard = styled.div<{ theme: Theme }>`\n ${cardStyles}\n`;\n\nconst StyledCardLink = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n ${cardStyles}\n text-decoration: none;\n`;\n\nconst StyledCardTitle = styled.p<{ theme: Theme }>`\n font-weight: bold;\n margin: 5px 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n`;\n\ninterface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n icon?: IconProps;\n href?: string;\n}\n\nfunction Card({ children, title, icon, href }: CardProps) {\n const theme = useTheme() as Theme;\n\n const content = (\n <>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n <StyledCardTitle>{title}</StyledCardTitle>\n {children}\n </>\n );\n\n if (href) {\n return <StyledCardLink href={href}>{content}</StyledCardLink>;\n }\n\n return <StyledCard>{content}</StyledCard>;\n}\n\nexport { Card };\n";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export const cardTemplate = `"use client";
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import styled, { css, useTheme } from "styled-components";
|
|
4
|
-
import { styledText
|
|
4
|
+
import { styledText } from "cherry-styled-components";
|
|
5
|
+
import { Theme } from "@/app/theme";
|
|
5
6
|
import { Icon, IconProps } from "@/components/layout/Icon";
|
|
6
7
|
import { interactiveStyles } from "@/components/layout/SharedStyled";
|
|
7
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const cherryThemeProviderTemplate = "import React from \"react\";\nimport {
|
|
1
|
+
export declare const cherryThemeProviderTemplate = "import React from \"react\";\nimport { Theme } from \"@/app/theme\";\nimport { ClientThemeProvider } from \"@/components/layout/ClientThemeProvider\";\n\n// Light vs dark is decided by the \"dark\" class on <html>, set before paint\n// by the theme-init blocking script in the root layout. The theme object\n// itself references CSS variables that flip per :root vs :root.dark, so\n// no per-request server data is needed and pages stay statically renderable.\nfunction CherryThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n return <ClientThemeProvider theme={theme}>{children}</ClientThemeProvider>;\n}\n\nexport { CherryThemeProvider };\n";
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
export const cherryThemeProviderTemplate = `import React from "react";
|
|
2
|
-
import { cookies } from "next/headers";
|
|
3
2
|
import { Theme } from "@/app/theme";
|
|
4
3
|
import { ClientThemeProvider } from "@/components/layout/ClientThemeProvider";
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
// Light vs dark is decided by the "dark" class on <html>, set before paint
|
|
6
|
+
// by the theme-init blocking script in the root layout. The theme object
|
|
7
|
+
// itself references CSS variables that flip per :root vs :root.dark, so
|
|
8
|
+
// no per-request server data is needed and pages stay statically renderable.
|
|
9
|
+
function CherryThemeProvider({
|
|
7
10
|
children,
|
|
8
11
|
theme,
|
|
9
|
-
themeDark,
|
|
10
12
|
}: {
|
|
11
13
|
children: React.ReactNode;
|
|
12
14
|
theme: Theme;
|
|
13
|
-
themeDark?: Theme;
|
|
14
15
|
}) {
|
|
15
|
-
|
|
16
|
-
const cookieTheme = cookieStore.get("theme")?.value;
|
|
17
|
-
const useDark = cookieTheme === "dark";
|
|
18
|
-
const currentTheme = useDark && themeDark ? themeDark : theme;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<ClientThemeProvider theme={currentTheme}>{children}</ClientThemeProvider>
|
|
22
|
-
);
|
|
16
|
+
return <ClientThemeProvider theme={theme}>{children}</ClientThemeProvider>;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
19
|
export { CherryThemeProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, {
|
|
1
|
+
export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useCallback, useContext, useEffect, useState } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeMode = \"light\" | \"dark\";\n\ntype ThemeContextValue = {\n /** The styled-components theme object \u2014 same reference in both modes;\n the values it points to are CSS variables that flip per active class. */\n theme: Theme;\n /** Active mode, derived from the \"dark\" class on <html>. */\n mode: ThemeMode;\n /** Update the active mode: flips the class and persists the cookie. */\n setMode: (m: ThemeMode) => void;\n};\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction readMode(): ThemeMode {\n if (typeof document === \"undefined\") return \"light\";\n return document.documentElement.classList.contains(\"dark\") ? \"dark\" : \"light\";\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n // Mode lives in React state so consumers (e.g. ThemeToggle) re-render when\n // the user toggles. The visual swap itself is driven entirely by the\n // \"dark\" class on <html> + CSS variables \u2014 React doesn't drive paint.\n const [mode, setModeState] = useState<ThemeMode>(\"light\");\n\n useEffect(() => {\n // Sync with the class set by the theme-init blocking script. Also\n // backfill the cookie if the script didn't get to write one (rare).\n setModeState(readMode());\n try {\n const has = document.cookie\n .split(\";\")\n .some((c) => c.trim().startsWith(\"theme=\"));\n if (!has) {\n const m = readMode();\n document.cookie = `theme=${m};path=/;max-age=31536000;SameSite=Lax`;\n }\n } catch {}\n }, []);\n\n const setMode = useCallback((next: ThemeMode) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n document.documentElement.style.colorScheme = next;\n try {\n document.cookie = `theme=${next};path=/;max-age=31536000;SameSite=Lax`;\n } catch {}\n }\n setModeState(next);\n }, []);\n\n return (\n <ThemeContext.Provider value={{ theme, mode, setMode }}>\n <StyledThemeProvider theme={theme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
|
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
export const clientThemeProviderTemplate = `"use client";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useCallback, useContext, useEffect, useState } from "react";
|
|
3
3
|
import { ThemeProvider as StyledThemeProvider } from "styled-components";
|
|
4
4
|
import { Theme } from "@/app/theme";
|
|
5
|
-
import { useRouter } from "next/navigation";
|
|
6
5
|
import { GlobalStyles } from "@/components/layout/GlobalStyles";
|
|
7
6
|
|
|
8
|
-
type
|
|
7
|
+
type ThemeMode = "light" | "dark";
|
|
8
|
+
|
|
9
|
+
type ThemeContextValue = {
|
|
10
|
+
/** The styled-components theme object — same reference in both modes;
|
|
11
|
+
the values it points to are CSS variables that flip per active class. */
|
|
9
12
|
theme: Theme;
|
|
10
|
-
|
|
13
|
+
/** Active mode, derived from the "dark" class on <html>. */
|
|
14
|
+
mode: ThemeMode;
|
|
15
|
+
/** Update the active mode: flips the class and persists the cookie. */
|
|
16
|
+
setMode: (m: ThemeMode) => void;
|
|
11
17
|
};
|
|
12
18
|
|
|
13
|
-
const
|
|
14
|
-
React.createContext<ThemeOverrideContextValue | null>(null);
|
|
19
|
+
const ThemeContext = React.createContext<ThemeContextValue | null>(null);
|
|
15
20
|
|
|
16
21
|
function useThemeOverride() {
|
|
17
|
-
const ctx = useContext(
|
|
22
|
+
const ctx = useContext(ThemeContext);
|
|
18
23
|
if (!ctx) {
|
|
19
24
|
throw new Error("useThemeOverride must be used within ClientThemeProvider");
|
|
20
25
|
}
|
|
21
26
|
return ctx;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
function readMode(): ThemeMode {
|
|
30
|
+
if (typeof document === "undefined") return "light";
|
|
31
|
+
return document.documentElement.classList.contains("dark") ? "dark" : "light";
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
function ClientThemeProvider({
|
|
25
35
|
children,
|
|
26
36
|
theme,
|
|
@@ -28,49 +38,44 @@ function ClientThemeProvider({
|
|
|
28
38
|
children: React.ReactNode;
|
|
29
39
|
theme: Theme;
|
|
30
40
|
}) {
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
// Mode lives in React state so consumers (e.g. ThemeToggle) re-render when
|
|
42
|
+
// the user toggles. The visual swap itself is driven entirely by the
|
|
43
|
+
// "dark" class on <html> + CSS variables — React doesn't drive paint.
|
|
44
|
+
const [mode, setModeState] = useState<ThemeMode>("light");
|
|
45
|
+
|
|
33
46
|
useEffect(() => {
|
|
47
|
+
// Sync with the class set by the theme-init blocking script. Also
|
|
48
|
+
// backfill the cookie if the script didn't get to write one (rare).
|
|
49
|
+
setModeState(readMode());
|
|
34
50
|
try {
|
|
35
|
-
const
|
|
51
|
+
const has = document.cookie
|
|
36
52
|
.split(";")
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Fallback: blocking script should have set this, but handle edge cases
|
|
42
|
-
const prefersDark =
|
|
43
|
-
window.matchMedia &&
|
|
44
|
-
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
45
|
-
const shouldBe = prefersDark ? "dark" : "light";
|
|
46
|
-
document.cookie = \`theme=\${shouldBe};path=/;max-age=31536000;SameSite=Lax\`;
|
|
47
|
-
router.refresh();
|
|
48
|
-
} else if (theme.isDark !== (cookieValue === "dark")) {
|
|
49
|
-
// Server-rendered theme doesn't match cookie (e.g., cookie was set
|
|
50
|
-
// by the blocking script after SSR already committed to light theme)
|
|
51
|
-
router.refresh();
|
|
52
|
-
} else {
|
|
53
|
-
// Theme matches cookie - remove the flash-prevention style injected
|
|
54
|
-
// by the blocking script so styled-components takes over
|
|
55
|
-
const el = document.getElementById("__theme-init");
|
|
56
|
-
if (el) el.remove();
|
|
53
|
+
.some((c) => c.trim().startsWith("theme="));
|
|
54
|
+
if (!has) {
|
|
55
|
+
const m = readMode();
|
|
56
|
+
document.cookie = \`theme=\${m};path=/;max-age=31536000;SameSite=Lax\`;
|
|
57
57
|
}
|
|
58
58
|
} catch {}
|
|
59
|
-
}, [
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const setMode = useCallback((next: ThemeMode) => {
|
|
62
|
+
if (typeof document !== "undefined") {
|
|
63
|
+
document.documentElement.classList.toggle("dark", next === "dark");
|
|
64
|
+
document.documentElement.style.colorScheme = next;
|
|
65
|
+
try {
|
|
66
|
+
document.cookie = \`theme=\${next};path=/;max-age=31536000;SameSite=Lax\`;
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
setModeState(next);
|
|
70
|
+
}, []);
|
|
60
71
|
|
|
61
|
-
const effectiveTheme = useMemo(
|
|
62
|
-
() => overrideTheme ?? theme,
|
|
63
|
-
[overrideTheme, theme],
|
|
64
|
-
);
|
|
65
72
|
return (
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
>
|
|
69
|
-
<StyledThemeProvider theme={effectiveTheme}>
|
|
73
|
+
<ThemeContext.Provider value={{ theme, mode, setMode }}>
|
|
74
|
+
<StyledThemeProvider theme={theme}>
|
|
70
75
|
<GlobalStyles />
|
|
71
76
|
{children}
|
|
72
77
|
</StyledThemeProvider>
|
|
73
|
-
</
|
|
78
|
+
</ThemeContext.Provider>
|
|
74
79
|
);
|
|
75
80
|
}
|
|
76
81
|
|