& {
+ className?: string[];
+ raw?: string;
+ };
+ children?: Node[];
+ value?: string;
+ raw?: string; // For internal use in processing
+}
+
+interface TextNode extends Node {
+ type: 'text';
+ value: string;
+}
+
// custom components imports
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell } from "@/components/ui/table";
import Pre from "@/components/markdown/PreMdx";
import Note from "@/components/markdown/NoteMdx";
import { Stepper, StepperItem } from "@/components/markdown/StepperMdx";
@@ -27,6 +47,7 @@ import CardGroup from "@/components/markdown/CardGroupMdx";
import Kbd from "@/components/markdown/KeyboardMdx";
import { Release, Changes } from "@/components/markdown/ReleaseMdx";
import { File, Files, Folder } from "@/components/markdown/FileTreeMdx";
+import AccordionGroup from "@/components/markdown/AccordionGroupMdx";
// add custom components
const components = {
@@ -34,20 +55,22 @@ const components = {
TabsContent,
TabsList,
TabsTrigger,
- pre: Pre,
- Note,
- Stepper,
- StepperItem,
- img: Image,
- a: Link,
- Outlet,
Youtube,
Tooltip,
Card,
Button,
Accordion,
+ AccordionGroup,
CardGroup,
Kbd,
+ // Table Components
+ table: Table,
+ thead: TableHeader,
+ tbody: TableBody,
+ tfoot: TableFooter,
+ tr: TableRow,
+ th: TableHead,
+ td: TableCell,
// Release Note Components
Release,
Changes,
@@ -55,6 +78,56 @@ const components = {
File,
Files,
Folder,
+ pre: Pre,
+ Note,
+ Stepper,
+ StepperItem,
+ img: Image,
+ a: Link,
+ Outlet,
+};
+
+// helper function to handle rehype code titles, since by default we can't inject into the className of rehype-code-titles
+const handleCodeTitles = () => (tree: Node) => {
+ visit(tree, "element", (node: Element, index: number | null, parent: Parent | null) => {
+ // Ensure the visited node is valid
+ if (!parent || index === null || node.tagName !== 'div') {
+ return;
+ }
+
+ // Check if this is the title div from rehype-code-titles
+ const isTitleDiv = node.properties?.className?.includes('rehype-code-title');
+ if (!isTitleDiv) {
+ return;
+ }
+
+ // Find the next element, skipping over other nodes like whitespace text
+ let nextElement = null;
+ for (let i = index + 1; i < parent.children.length; i++) {
+ const sibling = parent.children[i];
+ if (sibling.type === 'element') {
+ nextElement = sibling as Element;
+ break;
+ }
+ }
+
+ // If the next element is a , move the title to it
+ if (nextElement && nextElement.tagName === 'pre') {
+ const titleNode = node.children?.[0] as TextNode;
+ if (titleNode && titleNode.type === 'text') {
+ if (!nextElement.properties) {
+ nextElement.properties = {};
+ }
+ nextElement.properties['data-title'] = titleNode.value;
+
+ // Remove the original title div
+ parent.children.splice(index, 1);
+
+ // Return the same index to continue visiting from the correct position
+ return index;
+ }
+ }
+ });
};
// can be used for other pages like blogs, Guides etc
@@ -67,6 +140,7 @@ async function parseMdx(rawMdx: string) {
rehypePlugins: [
preProcess,
rehypeCodeTitles,
+ handleCodeTitles,
rehypePrism,
rehypeSlug,
rehypeAutolinkHeadings,
@@ -139,11 +213,11 @@ function justGetFrontmatterFromMD(rawMd: string): Frontmatter {
}
export async function getAllChilds(pathString: string) {
- const items = pathString.split("/").filter((it) => it != "");
+ const items = pathString.split("/").filter((it) => it !== "");
let page_routes_copy = ROUTES;
let prevHref = "";
- for (let it of items) {
+ for (const it of items) {
const found = page_routes_copy.find((innerIt) => innerIt.href == `/${it}`);
if (!found) break;
prevHref += found.href;
@@ -170,20 +244,28 @@ export async function getAllChilds(pathString: string) {
}
// for copying the code in pre
-const preProcess = () => (tree: any) => {
- visit(tree, (node) => {
- if (node?.type === "element" && node?.tagName === "pre") {
- const [codeEl] = node.children;
- if (codeEl.tagName !== "code") return;
- node.raw = codeEl.children?.[0].value;
+const preProcess = () => (tree: Node) => {
+ visit(tree, (node: Node) => {
+ const element = node as Element;
+ if (element?.type === "element" && element?.tagName === "pre" && element.children) {
+ const [codeEl] = element.children as Element[];
+ if (codeEl.tagName !== "code" || !codeEl.children?.[0]) return;
+
+ const textNode = codeEl.children[0] as TextNode;
+ if (textNode.type === 'text' && textNode.value) {
+ element.raw = textNode.value;
+ }
}
});
};
-const postProcess = () => (tree: any) => {
- visit(tree, "element", (node) => {
- if (node?.type === "element" && node?.tagName === "pre") {
- node.properties["raw"] = node.raw;
+const postProcess = () => (tree: Node) => {
+ visit(tree, "element", (node: Node) => {
+ const element = node as Element;
+ if (element?.type === "element" && element?.tagName === "pre") {
+ if (element.properties && element.raw) {
+ element.properties.raw = element.raw;
+ }
}
});
};
diff --git a/package.json b/package.json
index 5f24f90..3b18768 100644
--- a/package.json
+++ b/package.json
@@ -1,59 +1,68 @@
{
"name": "docubook",
- "version": "1.13.6",
+ "version": "2.0.0-beta.3",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "eslint ."
},
"dependencies": {
- "@radix-ui/react-accordion": "^1.2.0",
- "@radix-ui/react-avatar": "^1.1.0",
- "@radix-ui/react-collapsible": "^1.1.0",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.1",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-scroll-area": "^1.2.0",
- "@radix-ui/react-separator": "^1.0.3",
- "@radix-ui/react-slot": "^1.1.0",
- "@radix-ui/react-tabs": "^1.1.0",
- "@radix-ui/react-toggle": "^1.1.2",
- "@radix-ui/react-toggle-group": "^1.1.2",
- "class-variance-authority": "^0.7.0",
+ "@docsearch/css": "^3.9.0",
+ "@docsearch/react": "^3.9.0",
+ "@radix-ui/react-accordion": "^1.2.12",
+ "@radix-ui/react-avatar": "^1.1.11",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "algoliasearch": "^5.46.3",
+ "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "cmdk": "1.0.0",
- "framer-motion": "^12.4.1",
- "geist": "^1.3.1",
+ "cmdk": "^1.1.1",
+ "framer-motion": "^12.26.2",
+ "geist": "^1.5.1",
"gray-matter": "^4.0.3",
"lucide-react": "^0.511.0",
- "next": "^14.2.6",
+ "next": "^16.1.6",
"next-mdx-remote": "^5.0.0",
- "next-themes": "^0.3.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
+ "next-themes": "^0.4.4",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0",
- "rehype-code-titles": "^1.2.0",
- "rehype-prism-plus": "^2.0.0",
+ "rehype-code-titles": "^1.2.1",
+ "rehype-prism-plus": "^2.0.1",
"rehype-slug": "^6.0.0",
- "remark-gfm": "^4.0.0",
- "sonner": "^1.4.3",
- "tailwind-merge": "^2.5.2",
+ "remark-gfm": "^4.0.1",
+ "sonner": "^1.7.4",
+ "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
- "@tailwindcss/typography": "^0.5.14",
- "@types/node": "^20",
- "@types/react": "^18",
- "@types/react-dom": "^18",
- "autoprefixer": "^10.4.20",
- "eslint": "^8",
- "eslint-config-next": "^14.2.6",
- "postcss": "^8",
- "tailwindcss": "^3.4.10",
- "typescript": "^5"
+ "@tailwindcss/postcss": "^4.1.18",
+ "@tailwindcss/typography": "^0.5.19",
+ "@types/node": "^20.19.30",
+ "@types/react": "19.2.8",
+ "@types/react-dom": "19.2.3",
+ "autoprefixer": "^10.4.23",
+ "eslint": "^9.39.2",
+ "eslint-config-next": "16.1.3",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.18",
+ "typescript": "^5.9.3"
},
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
-}
+ "overrides": {
+ "@types/react": "19.2.8",
+ "@types/react-dom": "19.2.3"
+ },
+ "packageManager": "bun@1.3.8"
+}
\ No newline at end of file
diff --git a/postcss.config.js b/postcss.config.cjs
similarity index 65%
rename from postcss.config.js
rename to postcss.config.cjs
index 12a703d..b4bee66 100644
--- a/postcss.config.js
+++ b/postcss.config.cjs
@@ -1,6 +1,6 @@
module.exports = {
plugins: {
- tailwindcss: {},
+ '@tailwindcss/postcss': {},
autoprefixer: {},
},
};
diff --git a/styles/algolia.css b/styles/algolia.css
new file mode 100644
index 0000000..77b0fad
--- /dev/null
+++ b/styles/algolia.css
@@ -0,0 +1,162 @@
+/*
+================================================================================
+ DocSearch Component Styling (Refactored Version)
+================================================================================
+*/
+
+/* -- LANGKAH 1: Definisi Variabel Global --
+ Variabel tema DocSearch sekarang didefinisikan secara global di :root.
+ Ini menyederhanakan pewarisan tema dan memastikan konsistensi.
+ Mode gelap secara otomatis menimpa variabel ini karena .dark di globals.css.
+*/
+:root {
+ --docsearch-primary-color: hsl(var(--primary));
+ --docsearch-text-color: hsl(var(--muted-foreground));
+ --docsearch-spacing: 12px;
+ --docsearch-icon-stroke-width: 1.4;
+ --docsearch-highlight-color: var(--docsearch-primary-color);
+ --docsearch-muted-color: hsl(var(--muted-foreground));
+ --docsearch-container-background: rgba(0, 0, 0, 0.7);
+ --docsearch-logo-color: hsl(var(--primary-foreground));
+
+ /* Modal */
+ --docsearch-modal-width: 560px;
+ --docsearch-modal-height: 600px;
+ --docsearch-modal-background: hsl(var(--background));
+ --docsearch-modal-shadow: 0 0 0 1px hsl(var(--border)), 0 8px 20px rgba(0, 0, 0, 0.2);
+
+ /* SearchBox */
+ --docsearch-searchbox-height: 56px;
+ --docsearch-searchbox-background: hsl(var(--input));
+ --docsearch-searchbox-focus-background: hsl(var(--card));
+ --docsearch-searchbox-shadow: none;
+
+ /* Hit (Hasil Pencarian) */
+ --docsearch-hit-height: 56px;
+ --docsearch-hit-color: hsl(var(--foreground));
+ --docsearch-hit-active-color: hsl(var(--primary-foreground));
+ --docsearch-hit-background: hsl(var(--card));
+ --docsearch-hit-shadow: none;
+
+ /* Keys */
+ --docsearch-key-gradient: none;
+ --docsearch-key-shadow: none;
+ --docsearch-key-pressed-shadow: none;
+
+ /* Footer */
+ --docsearch-footer-height: 44px;
+ --docsearch-footer-background: hsl(var(--background));
+ --docsearch-footer-shadow: none;
+}
+
+/* -- LANGKAH 2: Gaya untuk Tombol Awal --
+ Gaya ini spesifik untuk tombol yang ada di Navbar,
+ yang dibungkus oleh .
+*/
+.docsearch .DocSearch-Button {
+ background-color: hsl(var(--secondary));
+ border: 1px solid hsl(var(--border));
+ border-radius: 9999px;
+ width: 160px;
+ height: 40px;
+ color: hsl(var(--muted-foreground));
+ transition: width 0.3s ease;
+ margin: 0;
+}
+
+.docsearch .DocSearch-Button:hover {
+ border-color: var(--docsearch-primary-color);
+ box-shadow: none;
+}
+
+.docsearch .DocSearch-Search-Icon {
+ color: var(--docsearch-muted-color);
+ width: 1rem;
+ height: 1rem;
+}
+
+.docsearch .DocSearch-Button-Placeholder {
+ font-style: normal;
+ margin-left: 0.25rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--docsearch-muted-color);
+}
+
+.docsearch .DocSearch-Button-Key {
+ background: var(--docsearch-primary-color);
+ color: var(--docsearch-logo-color); /* Menggunakan variabel yg relevan */
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ height: 24px;
+ padding: 0 6px;
+ border: none;
+ box-shadow: none;
+ top: 0;
+}
+
+/* -- LANGKAH 3: Gaya untuk Modal dan Isinya --
+ Gaya ini menargetkan elemen-elemen modal yang dirender terpisah.
+ Karena variabel sudah global, kita hanya perlu menata elemennya.
+*/
+.DocSearch-Container .DocSearch-Modal {
+ backdrop-filter: blur(8px);
+}
+
+.DocSearch-Form {
+ border: 1px solid hsl(var(--border));
+ background-color: transparent;
+}
+
+.DocSearch-Input {
+ font-size: 15px !important;
+}
+
+.DocSearch-Footer {
+ border-top: 1px solid hsl(var(--border));
+}
+
+/* Gaya untuk tombol keyboard di footer */
+.DocSearch-Footer--commands kbd {
+ background-color: hsl(var(--secondary));
+ border: 1px solid hsl(var(--border));
+ border-bottom-width: 2px;
+ border-radius: 6px;
+ color: var(--docsearch-muted-color);
+ padding: 4px 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Menghilangkan gaya default dari ikon di dalam tombol footer */
+.DocSearch-Commands-Key {
+ background: none;
+ color: hsl(var(--muted-foreground));
+ border: 1px solid hsl(var(--border));
+ box-shadow: none;
+ padding: 2px 4px;
+ margin-right: 0.4em;
+ height: 20px;
+ width: 32px;
+ border-radius: 6px;
+}
+
+/* -- LANGKAH 4: Gaya Responsif --
+ Tidak ada perubahan, hanya mempertahankan fungsionalitas mobile.
+*/
+@media (max-width: 768px) {
+ .docsearch .DocSearch-Button {
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ justify-content: center;
+ background: none;
+ border: none;
+ }
+ .docsearch .DocSearch-Button-Placeholder,
+ .docsearch .DocSearch-Button-Key {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/styles/globals.css b/styles/globals.css
index e9ced65..6565c7c 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -1,10 +1,131 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import 'tailwindcss';
+@plugin '@tailwindcss/typography';
-@import url("../styles/syntax.css");
-/* ocean */
+@custom-variant dark (&:is(.dark *));
+
+@utility container {
+ margin-inline: auto;
+ padding-inline: 2rem;
+
+ @media (width >=--theme(--breakpoint-sm)) {
+ max-width: none;
+ }
+
+ @media (width >=1440px) {
+ max-width: 1440px;
+ }
+}
+
+@theme {
+ --color-border: hsl(var(--border));
+ --color-input: hsl(var(--input));
+ --color-ring: hsl(var(--ring));
+ --color-background: hsl(var(--background));
+ --color-foreground: hsl(var(--foreground));
+
+ --color-primary: hsl(var(--primary));
+ --color-primary-foreground: hsl(var(--primary-foreground));
+
+ --color-secondary: hsl(var(--secondary));
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
+
+ --color-destructive: hsl(var(--destructive));
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
+
+ --color-muted: hsl(var(--muted));
+ --color-muted-foreground: hsl(var(--muted-foreground));
+
+ --color-accent: hsl(var(--accent));
+ --color-accent-foreground: hsl(var(--accent-foreground));
+
+ --color-popover: hsl(var(--popover));
+ --color-popover-foreground: hsl(var(--popover-foreground));
+
+ --color-card: hsl(var(--card));
+ --color-card-foreground: hsl(var(--card-foreground));
+
+ --color-sidebar: hsl(var(--sidebar-background));
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
+ --color-sidebar-border: hsl(var(--sidebar-border));
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
+
+ --radius-lg: var(--radius);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-sm: calc(var(--radius) - 4px);
+
+ --font-code: var(--font-geist-mono);
+ --font-regular: var(--font-geist-sans);
+
+ --animate-accordion-down: accordion-down 0.2s ease-out;
+ --animate-accordion-up: accordion-up 0.2s ease-out;
+ --animate-shiny-text: shiny-text 8s infinite;
+
+ @keyframes accordion-down {
+ from {
+ height: 0;
+ }
+
+ to {
+ height: var(--radix-accordion-content-height);
+ }
+ }
+
+ @keyframes accordion-up {
+ from {
+ height: var(--radix-accordion-content-height);
+ }
+
+ to {
+ height: 0;
+ }
+ }
+
+ @keyframes shiny-text {
+
+ 0%,
+ 90%,
+ 100% {
+ background-position: calc(-100% - var(--shiny-width)) 0;
+ }
+
+ 30%,
+ 60% {
+ background-position: calc(100% + var(--shiny-width)) 0;
+ }
+ }
+}
+
+/*
+ The default border color has changed to `currentcolor` in Tailwind CSS v4,
+ so we've added these compatibility styles to make sure everything still
+ looks the same as it did with Tailwind CSS v3.
+
+ If we ever want to remove these styles, we need to add an explicit border
+ color utility to any element that depends on these defaults.
+*/
+@layer base {
+
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-gray-200, currentcolor);
+ }
+}
+
+@utility animate-shine {
+ --animate-shine: shine var(--duration) infinite linear;
+ animation: var(--animate-shine);
+ background-size: 200% 200%;
+}
+
+/* Modern Blue Theme */
@layer base {
:root {
--background: 210 50% 95%;
@@ -21,17 +142,18 @@
--muted-foreground: 220 20% 40%;
--accent: 132 86% 32%;
--accent-foreground: 0 0% 100%;
- --destructive: 0 65% 55%;
- --destructive-foreground: 220 20% 95%;
- --border: 220 15% 90%;
- --input: 220 15% 90%;
- --ring: 132 86% 42%;
+ --destructive: 0 85% 60%;
+ --destructive-foreground: 0 0% 100%;
+ --border: 210 20% 85%;
+ --input: 210 20% 85%;
+ --ring: 210 81% 56%;
--radius: 0.5rem;
- --chart-1: 210 60% 50%;
- --chart-2: 220 40% 65%;
- --chart-3: 132 86% 42%;
- --chart-4: 200 60% 55%;
- --chart-5: 240 30% 40%;
+ --chart-1: 210 81% 56%;
+ --chart-2: 200 100% 40%;
+ --chart-3: 220 76% 60%;
+ --chart-4: 190 90% 50%;
+ --chart-5: 230 86% 45%;
+ --line-number-color: rgba(0, 0, 0, 0.05);
}
.dark {
@@ -72,82 +194,66 @@
}
}
-.prose {
- margin: 0 !important;
-}
+@layer utilities {
+ .prose {
+ margin: 0 !important;
+ }
-pre {
- padding: 2px 0 !important;
- width: inherit !important;
- overflow-x: auto;
-}
+ pre {
+ padding: 2px 0 !important;
+ width: inherit !important;
+ overflow-x: auto;
+ }
-pre>code {
- display: grid;
- max-width: inherit !important;
- padding: 14px 0 !important;
-}
+ pre>code {
+ display: grid;
+ max-width: inherit !important;
+ padding: 14px 0 !important;
+ border: 0 !important;
+ }
-.code-line {
- padding: 0.75px 16px;
- @apply border-l-2 border-transparent
-}
+ .code-line {
+ padding: 0.75px 16px;
+ @apply border-l-2 border-transparent;
+ }
-.line-number::before {
- display: inline-block;
- width: 1rem;
- margin-right: 22px;
- margin-left: -2px;
- color: rgb(110, 110, 110);
- content: attr(line);
- font-size: 13.5px;
- text-align: right;
-}
+ .line-number::before {
+ display: inline-block;
+ width: 1rem;
+ margin-right: 22px;
+ margin-left: -2px;
+ color: rgb(110, 110, 110);
+ content: attr(line);
+ font-size: 13.5px;
+ text-align: right;
+ }
-.highlight-line {
- @apply bg-primary/5 border-l-2 border-primary/30;
-}
+ .highlight-line {
+ @apply bg-primary/5 border-l-2 border-primary/30;
+ }
-.rehype-code-title {
- @apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code;
-}
+ .rehype-code-title {
+ @apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code;
+ }
-.highlight-comp>code {
- background-color: transparent !important;
-}
-
-.line-clamp-3 {
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.line-clamp-2 {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
+ .highlight-comp>code {
+ background-color: transparent !important;
+ }
}
@layer utilities {
- .animate-shine {
- --animate-shine: shine var(--duration) infinite linear;
- animation: var(--animate-shine);
- background-size: 200% 200%;
+
+ @keyframes shine {
+ 0% {
+ background-position: 0% 0%;
}
- @keyframes shine {
- 0% {
- background-position: 0% 0%;
- }
- 50% {
- background-position: 100% 100%;
- }
- 100% {
- background-position: 0% 0%;
- }
+ 50% {
+ background-position: 100% 100%;
+ }
+
+ 100% {
+ background-position: 0% 0%;
}
}
+}
\ No newline at end of file
diff --git a/styles/syntax.css b/styles/syntax.css
index 569e549..8fae5fb 100644
--- a/styles/syntax.css
+++ b/styles/syntax.css
@@ -1,23 +1,27 @@
/* ocean with green variant */
/* Light Mode */
.keyword {
- color: #1EAB18; /* Slightly darker green */
- /* Dark Lime */
+ color: #1EAB18;
+ /* Slightly darker green */
+ /* Dark Lime */
}
.function {
- color: #39D833; /* Brighter lime green */
- /* Bright Lime */
+ color: #39D833;
+ /* Brighter lime green */
+ /* Bright Lime */
}
.punctuation {
- color: #357C30; /* Muted green-gray */
- /* Sage Green */
+ color: #357C30;
+ /* Muted green-gray */
+ /* Sage Green */
}
.comment {
- color: #5F935B; /* Muted green */
- /* Olive Green */
+ color: #5F935B;
+ /* Muted green */
+ /* Olive Green */
}
.string,
@@ -25,34 +29,40 @@
.annotation,
.boolean,
.number {
- color: #2E8F2A; /* Darker green */
- /* Dark Forest Green */
+ color: #2E8F2A;
+ /* Darker green */
+ /* Dark Forest Green */
}
.tag {
- color: #1FC01B; /* Original vibrant green */
- /* Vibrant Green */
+ color: #1FC01B;
+ /* Original vibrant green */
+ /* Vibrant Green */
}
.attr-name {
- color: #4FE34A; /* Light and bright green */
- /* Electric Green */
+ color: #4FE34A;
+ /* Light and bright green */
+ /* Electric Green */
}
.attr-value {
- color: #1EAB18; /* Slightly darker green */
- /* Dark Lime */
+ color: #1EAB18;
+ /* Slightly darker green */
+ /* Dark Lime */
}
/* Dark Mode */
.dark .keyword {
- color: #8CFF7D; /* Soft light green */
- /* Soft Mint */
+ color: #8CFF7D;
+ /* Soft light green */
+ /* Soft Mint */
}
.dark .function {
- color: #A0FF93; /* Light lime green */
- /* Minty Green */
+ color: #A0FF93;
+ /* Light lime green */
+ /* Minty Green */
}
.dark .string,
@@ -60,33 +70,52 @@
.dark .annotation,
.dark .boolean,
.dark .number {
- color: #72FF73; /* Light green */
- /* Spring Green */
+ color: #72FF73;
+ /* Light green */
+ /* Spring Green */
}
.dark .tag {
- color: #7FFF7A; /* Vibrant green */
- /* Neon Green */
+ color: #7FFF7A;
+ /* Vibrant green */
+ /* Neon Green */
}
.dark .attr-name {
- color: #B2FFA3; /* Soft pastel green */
- /* Mint Green */
+ color: #B2FFA3;
+ /* Soft pastel green */
+ /* Mint Green */
}
.dark .attr-value {
- color: #8CFF7D; /* Soft light green */
- /* Soft Mint */
+ color: #1EAB18;
+ /* Slightly darker green */
+ /* Dark Lime */
+}
+
+.dark .comment {
+ color: #9ca3af;
+ /* Lighter gray for dark mode */
+}
+
+.dark .punctuation {
+ color: #9ca3af;
+ /* Lighter gray for dark mode */
}
.youtube {
position: relative;
- padding-bottom: 56.25%; /* Rasio aspek 16:9 */
+ padding-bottom: 56.25%;
+ /* Rasio aspek 16:9 */
height: 0;
overflow: hidden;
- background: #000; /* Latar belakang hitam untuk memadukan player */
- border-radius: 8px; /* Sudut melengkung */
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); /* Bayangan lembut */
+ background: #000;
+ /* Latar belakang hitam untuk memadukan player */
+ border-radius: 8px;
+ /* Sudut melengkung */
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+ /* Bayangan lembut */
+ margin: 24px 0;
}
.youtube iframe {
@@ -96,5 +125,95 @@
width: 100%;
height: 100%;
border: none;
- border-radius: 8px; /* Sudut melengkung pada iframe */
+ border-radius: 8px;
+ /* Sudut melengkung pada iframe */
}
+
+/* ======================================================================== */
+/* Custom styling for code blocks */
+/* ======================================================================== */
+
+.code-block-container {
+ position: relative;
+ margin: 1.5rem 0;
+ border: 1px solid hsl(var(--border));
+ overflow: hidden;
+ font-size: 0.875rem;
+ border-radius: 0.75rem;
+}
+
+.code-block-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ background-color: hsl(var(--muted));
+ padding: 0.5rem 1rem;
+ border-bottom: 1px solid hsl(var(--border));
+ color: hsl(var(--muted-foreground));
+ font-family: monospace;
+ font-size: 0.8rem;
+}
+
+.code-block-actions {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.75rem;
+ z-index: 10;
+}
+
+.code-block-actions button {
+ color: hsl(var(--muted-foreground));
+ transition: color 0.2s ease-in-out;
+}
+
+.code-block-actions button:hover {
+ color: hsl(var(--foreground));
+}
+
+
+.code-block-body pre[class*="language-"] {
+ margin: 0 !important;
+ padding: 0 !important;
+ background: transparent !important;
+}
+
+.line-numbers-wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 3rem;
+ padding-top: 1rem;
+ text-align: right;
+ color: var(--line-number-color);
+ user-select: none;
+}
+
+.line-highlight {
+ position: absolute;
+ left: 0;
+ right: 0;
+ background: hsl(var(--primary) / 0.1);
+ border-left: 2px solid hsl(var(--primary));
+ pointer-events: none;
+}
+
+.code-block-body pre[data-line-numbers="true"] .line-highlight {
+ padding-left: 3.5rem;
+}
+
+.code-block-body::-webkit-scrollbar {
+ height: 8px;
+}
+
+.code-block-body::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.code-block-body::-webkit-scrollbar-thumb {
+ background: hsl(var(--border));
+ border-radius: 4px;
+}
+
+.code-block-body::-webkit-scrollbar-thumb:hover {
+ background: hsl(var(--muted));
+}
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 56783f3..8e6fa3b 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,111 +1,113 @@
import type { Config } from "tailwindcss";
+import tailwindAnimate from "tailwindcss-animate";
+import typography from "@tailwindcss/typography";
const config = {
- darkMode: ["class"],
- content: [
- "./pages/**/*.{ts,tsx}",
- "./components/**/*.{ts,tsx}",
- "./app/**/*.{ts,tsx}",
- "./src/**/*.{ts,tsx}",
- ],
- prefix: "",
- theme: {
- container: {
- center: true,
- padding: '2rem',
- screens: {
- '2xl': '1440px'
- }
- },
- extend: {
- colors: {
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- sidebar: {
- DEFAULT: 'hsl(var(--sidebar-background))',
- foreground: 'hsl(var(--sidebar-foreground))',
- primary: 'hsl(var(--sidebar-primary))',
- 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
- accent: 'hsl(var(--sidebar-accent))',
- 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
- border: 'hsl(var(--sidebar-border))',
- ring: 'hsl(var(--sidebar-ring))'
- }
- },
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- fontFamily: {
- code: ["var(--font-geist-mono)"],
- regular: ["var(--font-geist-sans)"]
- },
- keyframes: {
- 'accordion-down': {
- from: {
- height: '0'
- },
- to: {
- height: 'var(--radix-accordion-content-height)'
- }
- },
- 'accordion-up': {
- from: {
- height: 'var(--radix-accordion-content-height)'
- },
- to: {
- height: '0'
- }
- },
- 'shiny-text': {
- '0%, 90%, 100%': {
- 'background-position': 'calc(-100% - var(--shiny-width)) 0'
- },
- '30%, 60%': {
- 'background-position': 'calc(100% + var(--shiny-width)) 0'
- }
- }
- },
- animation: {
- 'accordion-down': 'accordion-down 0.2s ease-out',
- 'accordion-up': 'accordion-up 0.2s ease-out',
- 'shiny-text': 'shiny-text 8s infinite'
- }
- }
- },
- plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
+ darkMode: "class",
+ content: [
+ "./pages/**/*.{ts,tsx}",
+ "./components/**/*.{ts,tsx}",
+ "./app/**/*.{ts,tsx}",
+ "./src/**/*.{ts,tsx}",
+ ],
+ prefix: "",
+ theme: {
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1440px'
+ }
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))'
+ }
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ fontFamily: {
+ code: ["var(--font-geist-mono)"],
+ regular: ["var(--font-geist-sans)"]
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0'
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)'
+ }
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)'
+ },
+ to: {
+ height: '0'
+ }
+ },
+ 'shiny-text': {
+ '0%, 90%, 100%': {
+ 'background-position': 'calc(-100% - var(--shiny-width)) 0'
+ },
+ '30%, 60%': {
+ 'background-position': 'calc(100% + var(--shiny-width)) 0'
+ }
+ }
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ 'shiny-text': 'shiny-text 8s infinite'
+ }
+ }
+ },
+ plugins: [tailwindAnimate, typography],
} satisfies Config;
export default config;
diff --git a/tsconfig.json b/tsconfig.json
index e7ff90f..9e9bbf7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,10 @@
{
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -10,7 +14,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -18,9 +22,20 @@
}
],
"paths": {
- "@/*": ["./*"]
- }
+ "@/*": [
+ "./*"
+ ]
+ },
+ "target": "ES2017"
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}