From c134ce9fa3a4db930f64f544f6cc370a2865ab67 Mon Sep 17 00:00:00 2001 From: Mateusz Bartosik Date: Fri, 27 Mar 2026 14:20:23 +0100 Subject: [PATCH 1/6] RDoc-3729 Add Samples to documentation --- docusaurus.config.ts | 10 + package-lock.json | 62 ++ package.json | 2 + samples/home.mdx | 13 + samples/tags/challenges-solutions.yml | 8 + samples/tags/feature.yml | 12 + samples/tags/tech-stack.yml | 16 + samples/the-ravens-library.mdx | 154 +++++ sidebarsSamples.js | 15 + sidebarsTemplates.js | 41 ++ src/components/Common/Button.tsx | 19 +- src/components/Common/Checkbox.tsx | 36 ++ src/components/Common/Drawer.tsx | 111 ++++ src/components/Common/Gallery.tsx | 187 ++++++ src/components/Common/Icon.tsx | 4 +- src/components/Common/LazyImage.tsx | 56 +- src/components/Common/Toggle.tsx | 35 ++ src/components/LanguageSwitcher.tsx | 17 +- src/components/MarkdownImageLightbox.tsx | 14 +- .../Samples/Hub/Partials/FilterCategory.tsx | 93 +++ .../Samples/Hub/Partials/LanguageTag.tsx | 36 ++ .../Samples/Hub/Partials/SampleCard.tsx | 173 ++++++ .../Hub/Partials/SamplesDecoration.tsx | 547 ++++++++++++++++++ .../Samples/Hub/Partials/SamplesFilter.tsx | 127 ++++ .../Samples/Hub/Partials/SamplesGrid.tsx | 77 +++ .../Samples/Hub/Partials/SamplesHeader.tsx | 26 + .../Samples/Hub/SamplesHomePage.tsx | 176 ++++++ .../Samples/Overview/Partials/ActionsCard.tsx | 39 ++ .../Overview/Partials/FeatureAccordion.tsx | 79 +++ .../Overview/Partials/RelatedResource.tsx | 80 +++ .../Partials/SampleMetadataColumn.tsx | 138 +++++ .../Samples/Overview/SampleLayout.tsx | 22 + src/components/Samples/index.ts | 15 + src/components/Samples/types.ts | 20 + src/components/languageConfig.ts | 23 + src/css/custom.css | 38 +- src/hooks/useBoolean.ts | 14 + src/plugins/recent-guides-plugin.ts | 6 +- src/plugins/recent-samples-plugin.ts | 206 +++++++ src/theme/DocSidebar/Desktop/index.tsx | 14 +- src/theme/DocSidebar/Mobile/index.tsx | 19 +- src/typescript/docMetadata.d.ts | 6 + src/typescript/pathUtils.ts | 7 + static/img/samples/library-of-ravens/01.webp | Bin 0 -> 52760 bytes static/img/samples/library-of-ravens/02.webp | Bin 0 -> 45368 bytes static/img/samples/library-of-ravens/03.webp | Bin 0 -> 36692 bytes .../img/samples/library-of-ravens/cover.webp | Bin 0 -> 21326 bytes templates/best-practices-samples.mdx | 281 +++++++++ templates/components-samples.mdx | 296 ++++++++++ templates/filtering-samples.mdx | 177 ++++++ templates/frames.mdx | 30 +- templates/gallery-example.mdx | 97 ++++ templates/introduction-samples.mdx | 116 ++++ templates/new-samples.mdx | 198 +++++++ templates/tags-samples.mdx | 180 ++++++ templates/themed-images.mdx | 4 +- 56 files changed, 4065 insertions(+), 107 deletions(-) create mode 100644 samples/home.mdx create mode 100644 samples/tags/challenges-solutions.yml create mode 100644 samples/tags/feature.yml create mode 100644 samples/tags/tech-stack.yml create mode 100644 samples/the-ravens-library.mdx create mode 100644 sidebarsSamples.js create mode 100644 src/components/Common/Checkbox.tsx create mode 100644 src/components/Common/Drawer.tsx create mode 100644 src/components/Common/Gallery.tsx create mode 100644 src/components/Common/Toggle.tsx create mode 100644 src/components/Samples/Hub/Partials/FilterCategory.tsx create mode 100644 src/components/Samples/Hub/Partials/LanguageTag.tsx create mode 100644 src/components/Samples/Hub/Partials/SampleCard.tsx create mode 100644 src/components/Samples/Hub/Partials/SamplesDecoration.tsx create mode 100644 src/components/Samples/Hub/Partials/SamplesFilter.tsx create mode 100644 src/components/Samples/Hub/Partials/SamplesGrid.tsx create mode 100644 src/components/Samples/Hub/Partials/SamplesHeader.tsx create mode 100644 src/components/Samples/Hub/SamplesHomePage.tsx create mode 100644 src/components/Samples/Overview/Partials/ActionsCard.tsx create mode 100644 src/components/Samples/Overview/Partials/FeatureAccordion.tsx create mode 100644 src/components/Samples/Overview/Partials/RelatedResource.tsx create mode 100644 src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx create mode 100644 src/components/Samples/Overview/SampleLayout.tsx create mode 100644 src/components/Samples/index.ts create mode 100644 src/components/Samples/types.ts create mode 100644 src/components/languageConfig.ts create mode 100644 src/hooks/useBoolean.ts create mode 100644 src/plugins/recent-samples-plugin.ts create mode 100644 static/img/samples/library-of-ravens/01.webp create mode 100644 static/img/samples/library-of-ravens/02.webp create mode 100644 static/img/samples/library-of-ravens/03.webp create mode 100644 static/img/samples/library-of-ravens/cover.webp create mode 100644 templates/best-practices-samples.mdx create mode 100644 templates/components-samples.mdx create mode 100644 templates/filtering-samples.mdx create mode 100644 templates/gallery-example.mdx create mode 100644 templates/introduction-samples.mdx create mode 100644 templates/new-samples.mdx create mode 100644 templates/tags-samples.mdx diff --git a/docusaurus.config.ts b/docusaurus.config.ts index a80074d39c..893b6f7e9c 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -123,6 +123,15 @@ const config: Config = { showLastUpdateTime: true, }, ], + [ + "content-docs", + { + id: "samples", + path: "samples", + routeBasePath: "samples", + sidebarPath: require.resolve("./sidebarsSamples.js"), + }, + ], [ "@docusaurus/plugin-ideal-image", { @@ -134,6 +143,7 @@ const config: Config = { }, ], require.resolve("./src/plugins/recent-guides-plugin"), + require.resolve("./src/plugins/recent-samples-plugin"), ], headTags: [ { diff --git a/package-lock.json b/package-lock.json index 35b8f6642c..e1abda799a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,10 +16,12 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", + "framer-motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-github-btn": "^1.4.0", "yet-another-react-lightbox": "^3.24.0" }, "devDependencies": { @@ -10942,6 +10944,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -11099,6 +11128,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-buttons": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/github-buttons/-/github-buttons-2.32.0.tgz", + "integrity": "sha512-DbKam2n7JGbccpbAoQ7UHhxH2XYlumCakTM4Ln7v8A9TzMaaEgKV6S7A+Suyx3EVBE1DPEqA6QSgyCoGJamymg==", + "license": "BSD-2-Clause" + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -15754,6 +15789,21 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -18356,6 +18406,18 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "node_modules/react-github-btn": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-github-btn/-/react-github-btn-1.4.0.tgz", + "integrity": "sha512-lV4FYClAfjWnBfv0iNlJUGhamDgIq6TayD0kPZED6VzHWdpcHmPfsYOZ/CFwLfPv4Zp+F4m8QKTj0oy2HjiGXg==", + "license": "BSD-2-Clause", + "dependencies": { + "github-buttons": "^2.22.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-helmet-async": { "name": "@slorber/react-helmet-async", "version": "1.3.0", diff --git a/package.json b/package.json index ad67343d53..423afef8f7 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,12 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", + "framer-motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-github-btn": "^1.4.0", "yet-another-react-lightbox": "^3.24.0" }, "devDependencies": { diff --git a/samples/home.mdx b/samples/home.mdx new file mode 100644 index 0000000000..e4a8af6fbc --- /dev/null +++ b/samples/home.mdx @@ -0,0 +1,13 @@ +--- +title: Samples +hide_title: true +slug: / +pagination_next: null +pagination_prev: null +wrapperClassName: samples-home-page +hide_table_of_contents: true +--- + +import { SamplesHomePage } from "@site/src/components/Samples"; + + diff --git a/samples/tags/challenges-solutions.yml b/samples/tags/challenges-solutions.yml new file mode 100644 index 0000000000..0284737f52 --- /dev/null +++ b/samples/tags/challenges-solutions.yml @@ -0,0 +1,8 @@ +cloud-tax: + label: "Cloud Tax" +gen-ai-data-enrichment: + label: "Gen AI Data Enrichment" +integration-patterns: + label: "Integration Patterns" +semantic-search: + label: "Semantic Search" diff --git a/samples/tags/feature.yml b/samples/tags/feature.yml new file mode 100644 index 0000000000..7d16c8e681 --- /dev/null +++ b/samples/tags/feature.yml @@ -0,0 +1,12 @@ +vector-search: + label: "Vector Search" +document-refresh: + label: "Document Refresh" +include: + label: "Include" +azure-storage-queues-etl: + label: "Azure Storage Queues ETL" +ai-agents: + label: "AI Agents" +full-text-search: + label: "Full-Text Search" diff --git a/samples/tags/tech-stack.yml b/samples/tags/tech-stack.yml new file mode 100644 index 0000000000..d186346d3c --- /dev/null +++ b/samples/tags/tech-stack.yml @@ -0,0 +1,16 @@ +csharp: + label: "C#" +nodejs: + label: "Node.js" +python: + label: "Python" +java: + label: "Java" +php: + label: "PHP" +aspire: + label: "Aspire" +azure-storage-queues: + label: "Azure Storage Queues" +azure-functions: + label: "Azure Functions" diff --git a/samples/the-ravens-library.mdx b/samples/the-ravens-library.mdx new file mode 100644 index 0000000000..1a9bec6b7d --- /dev/null +++ b/samples/the-ravens-library.mdx @@ -0,0 +1,154 @@ +--- +title: "The Library of Ravens" +description: "A simple library management application. Built with RavenDB, Aspire, Azure Storage Queues, and Azure Functions. " +challengesSolutionsTags: [cloud-tax, gen-ai-data-enrichment, integration-patterns, semantic-search] +featureTags: [azure-storage-queues-etl, document-refresh, include, vector-search] +techStackTags: [csharp, aspire, azure-storage-queues, azure-functions] +category: "Ecommerce" +license: "MIT License" +image: "/img/samples/library-of-ravens/cover.webp" +gallery: + - src: "/img/samples/library-of-ravens/01.webp" + alt: "The Library of Ravens - Main Interface" + - src: "/img/samples/library-of-ravens/02.webp" + alt: "The Library of Ravens - Author Profile" +--- + +import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples'; +import Gallery from '@site/src/components/Common/Gallery'; + +export const sampleDetails = ( + + } + relatedResources={ + <> + + + + } + /> +); + +}> + +## Overview + +The Library of Ravens **solves the "architecture bloat"** of managing separate databases by **consolidating full-text and vector search into a single database**. Instead of wrestling with data synchronization across multiple platforms, you get a unified search experience that keeps your infrastructure lean and your development cycle fast. + +The app demonstrates **robust integration patterns using Azure Storage Queues**. By leveraging RavenDB’s change tracking and ETL services, the system ensures that critical library updates are never lost, providing architectural resilience. + +Finally, the app **addresses the "cloud tax" of high egress fees** by utilizing ETags and native HTTP caching driven by RavenDB’s metadata. This approach significantly reduces the volume of data sent over the wire, slashing public cloud billing while providing a snappier, more responsive experience for the end user through efficient data reuse. + +Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azure Storage Queues](https://github.com/ravendb/samples-library/blob/main/azure.microsoft.com/en-us/products/storage/queues), and [Azure Functions](https://azure.microsoft.com/en-us/products/functions). + + +## Features used + + + RavenDB's Include feature allows loading related documents in a single request, eliminating the N+1 query problem. In the Library application, when loading a book, we simultaneously fetch the author and category documents without additional roundtrips to the database. + + Implementation example: + + ```csharp + // Load a book with its related author in one request + using var session = store.OpenAsyncSession(); + + var book = await session + .Include(b => b.AuthorId) + .Include(b => b.CategoryId) + .LoadAsync("books/1-A"); + + // These are already loaded - no additional DB calls + var author = await session.LoadAsync(book.AuthorId); + var category = await session.LoadAsync(book.CategoryId); + + // Example: Loading multiple books with their authors + var books = await session.Query() + .Include(b => b.AuthorId) + .Where(b => b.IsAvailable) + .ToListAsync(); + ``` + + + + Vector search enables semantic similarity matching, allowing users to find books based on meaning rather than just keywords. + + + + ETL to Azure Storage Queues ensures that all library updates are reliably propagated to downstream systems. + + + + Document Refresh keeps book metadata up-to-date by automatically fetching the latest information from external APIs. + + +## Technologies + +The following technologies were used to build this application: + +- [RavenDB](https://ravendb.net/) +- [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +- [Aspire](https://aspire.dev/) +- [Azure Functions](https://azure.microsoft.com/en-us/products/functions) +- [Azure Storage Queues](https://azure.microsoft.com/products/storage/queues) +- [SvelteKit](https://svelte.dev/) + +## Run locally + +If you want to run the application locally, please follow the steps: + +1. Check out the GIT repository +2. Install prerequisites: + 1. [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) + 2. [Aspire.dev](https://aspire.dev/get-started/install-cli/) + 3. [nodejs](https://nodejs.org/) +3. Request a [dev license](https://ravendb.net/license/request/dev-ai-agent-inside) and set it under `RAVEN_LICENSE` env variable +4. Get the app running by opening `/src/RavenDB.Samples.Library.sln` and starting the `Aspire` `AppHost` project + +## Community & Support + +If you spot a bug, have an idea or a question, please let us know by raising an issue or creating a pull request. + +We do use a [Discord server](https://discord.gg/ravendb). If you have any doubts, don't hesitate to reach out! + +## Contributing + +We encourage you to contribute! Please read our [CONTRIBUTING](https://github.com/ravendb/samples-library/blob/main/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. + +## License + +This project is licensed with the [MIT license](https://github.com/ravendb/samples-library/blob/main/LICENSE). + + diff --git a/sidebarsSamples.js b/sidebarsSamples.js new file mode 100644 index 0000000000..ee2905168e --- /dev/null +++ b/sidebarsSamples.js @@ -0,0 +1,15 @@ +// This file has to be in JavaScript, otherwise @docusaurus/plugin-content-docs doesn't work properly +export default { + guides: [ + { + type: "category", + label: "Samples", + link: { + type: "doc", + id: "home", + }, + collapsible: false, + items: [{ type: "autogenerated", dirName: "." }], + }, + ], +}; diff --git a/sidebarsTemplates.js b/sidebarsTemplates.js index 6b1cfd05f1..7cebde406e 100644 --- a/sidebarsTemplates.js +++ b/sidebarsTemplates.js @@ -41,11 +41,52 @@ export default { }, ], }, + { + type: "category", + label: "Samples authoring", + items: [ + { + type: "doc", + id: "introduction-samples", + label: "Introduction", + }, + { + type: "doc", + id: "new-samples", + label: "Adding new samples", + }, + { + type: "doc", + id: "components-samples", + label: "Components", + }, + { + type: "doc", + id: "filtering-samples", + label: "Filtering", + }, + { + type: "doc", + id: "tags-samples", + label: "Tags", + }, + { + type: "doc", + id: "best-practices-samples", + label: "Best practices", + }, + ], + }, { type: "doc", id: "frames", label: "Frames", }, + { + type: "doc", + id: "gallery-example", + label: "Gallery example", + }, { type: "doc", id: "icon-gallery", diff --git a/src/components/Common/Button.tsx b/src/components/Common/Button.tsx index c26afee97f..484dc9ea88 100644 --- a/src/components/Common/Button.tsx +++ b/src/components/Common/Button.tsx @@ -12,13 +12,14 @@ export interface ButtonProps extends React.ButtonHTMLAttributes = { default: "bg-primary !text-white dark:!text-black hover:bg-primary-darker", - secondary: "bg-gray-300 hover:bg-gray-400 dark:bg-secondary !text-black dark:hover:bg-secondary-darker", + secondary: + "bg-black/10 dark:bg-white/10 !text-black dark:!text-white hover:bg-black/20 dark:hover:bg-white/20 border border-black/10 dark:border-white/10", outline: "border !text-black border-black/25 !text-foreground hover:bg-black/5 dark:!text-white dark:border-white/25 dark:hover:bg-white/5", ghost: "hover:bg-muted !text-foreground", @@ -26,9 +27,8 @@ const variantClasses: Record = { }; const sizeClasses: Record, string> = { - sm: "h-8 px-3 text-xs", - md: "h-10 px-4 text-sm", - lg: "h-12 px-6 text-base", + xs: "py-2 px-3 text-xs", + sm: "py-2 px-3 text-sm", }; export default function Button({ @@ -36,12 +36,12 @@ export default function Button({ url, className = "", variant = "secondary", - size = "md", + size = "sm", iconName, ...props }: ButtonProps) { const baseClasses = clsx( - "inline-flex items-center justify-center rounded-md font-medium", + "cursor-pointer inline-flex items-center justify-center rounded-md font-medium leading-none", "!no-underline !transition-all", "disabled:opacity-50 disabled:pointer-events-none", variantClasses[variant], @@ -53,15 +53,14 @@ export default function Button({ const isExternal = !isInternalUrl(url); return ( - {children} {iconName && } - {isExternal && } + {children} {iconName && } ); } return ( ); } diff --git a/src/components/Common/Checkbox.tsx b/src/components/Common/Checkbox.tsx new file mode 100644 index 0000000000..d9962ef251 --- /dev/null +++ b/src/components/Common/Checkbox.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import clsx from "clsx"; +import { Icon } from "./Icon"; + +interface CheckboxProps { + checked: boolean; + onChange: () => void; + label?: string; + className?: string; +} + +export default function Checkbox({ checked, onChange, label, className }: CheckboxProps) { + return ( + + ); +} diff --git a/src/components/Common/Drawer.tsx b/src/components/Common/Drawer.tsx new file mode 100644 index 0000000000..c35578e715 --- /dev/null +++ b/src/components/Common/Drawer.tsx @@ -0,0 +1,111 @@ +import React, { useEffect } from "react"; +import { motion, AnimatePresence, PanInfo } from "framer-motion"; +import clsx from "clsx"; +import { Icon } from "./Icon"; + +interface DrawerProps { + open: boolean; + onClose: () => void; + children: React.ReactNode; + title?: string; + headerAction?: React.ReactNode; +} + +export default function Drawer({ open, onClose, children, title, headerAction }: DrawerProps) { + useEffect(() => { + if (open) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + + return () => { + document.body.style.overflow = ""; + }; + }, [open]); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && open) { + onClose(); + } + }; + + document.addEventListener("keydown", handleEscape); + return () => document.removeEventListener("keydown", handleEscape); + }, [open, onClose]); + + // eslint-disable-next-line no-undef + const handleDragEnd = (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { + const shouldClose = info.velocity.y > 500 || info.offset.y > 150; + if (shouldClose) { + onClose(); + } + }; + + return ( + + {open && ( + <> + + +
+
+
+ + {title && ( +
+

{title}

+
+ {headerAction} + +
+
+ )} + +
+ {children} +
+ + + )} + + ); +} diff --git a/src/components/Common/Gallery.tsx b/src/components/Common/Gallery.tsx new file mode 100644 index 0000000000..91b60d8f62 --- /dev/null +++ b/src/components/Common/Gallery.tsx @@ -0,0 +1,187 @@ +import React, { useRef, useState } from "react"; +import clsx from "clsx"; +import LazyImage from "./LazyImage"; + +export interface GalleryImage { + src: string; + alt?: string; +} + +export interface GalleryProps { + images: GalleryImage[]; + className?: string; +} + +export default function Gallery({ images, className }: GalleryProps) { + const thirdImageRef = useRef(null); + const [currentSlide, setCurrentSlide] = useState(0); + const carouselRef = useRef(null); + + if (!images || images.length === 0) { + return null; + } + + const visibleImages = images.slice(0, 3); + const remainingCount = Math.max(0, images.length - 3); + + const handleOverlayClick = () => { + const imgElement = thirdImageRef.current?.querySelector("img"); + if (imgElement) { + imgElement.click(); + } + }; + + const handleScroll = () => { + if (carouselRef.current) { + const scrollLeft = carouselRef.current.scrollLeft; + const slideWidth = carouselRef.current.offsetWidth; + const newSlide = Math.round(scrollLeft / slideWidth); + setCurrentSlide(newSlide); + } + }; + + return ( + <> +
+
+ {images.map((image, index) => ( +
+ +
+ ))} +
+ {images.length > 1 && ( +
+ {images.map((_, index) => ( +
+ )} +
+ +
+ {visibleImages[0] && ( +
= 3 && "flex-[592]" + )} + > + +
+ )} + {visibleImages.length === 2 && visibleImages[1] && ( +
+ +
+ )} + {visibleImages.length >= 3 && ( +
+ {visibleImages[1] && ( +
+ +
+ )} + + {visibleImages[2] && ( +
+ + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ )} +
+ )} +
+ + {images.length > 3 && ( +
+ {images.slice(3).map((img, index) => ( + + ))} +
+ )} + + ); +} diff --git a/src/components/Common/Icon.tsx b/src/components/Common/Icon.tsx index d7f7f7fc2b..6bc6b28118 100644 --- a/src/components/Common/Icon.tsx +++ b/src/components/Common/Icon.tsx @@ -2,7 +2,7 @@ import React from "react"; import { IconName } from "../../typescript/iconName"; import clsx from "clsx"; -export type IconSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; +export type IconSize = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; export interface IconProps { icon: IconName; @@ -34,6 +34,8 @@ function getSvg(base64String: string, sizeClass: string): string { function getSizeClass(size?: IconSize): `w-${number} h-${number}` { switch (size) { + case "2xs": + return "w-3 h-3"; case "xs": return "w-4 h-4"; case "sm": diff --git a/src/components/Common/LazyImage.tsx b/src/components/Common/LazyImage.tsx index e083d5d20d..55701063ba 100644 --- a/src/components/Common/LazyImage.tsx +++ b/src/components/Common/LazyImage.tsx @@ -1,21 +1,29 @@ import React, { useState, useEffect, useRef } from "react"; import clsx from "clsx"; -import ThemedImage, { Props as ThemedImageProps } from "@theme/ThemedImage"; export interface LazyImageProps extends React.ImgHTMLAttributes { - imgSrc?: string | { light: string; dark: string }; + imgSrc?: string; minContentHeight?: number; + isRounded?: boolean; } // @docusaurus/plugin-ideal-image transforms image imports into objects like // { src: { src: "/img/file.hash.png" } } instead of plain URL strings. // Extract the URL string from such objects. function toUrl(value: unknown): string | undefined { - if (typeof value === "string") return value; - if (!value || typeof value !== "object") return undefined; + if (typeof value === "string") { + return value; + } + if (!value || typeof value !== "object") { + return undefined; + } const { src } = value as Record; - if (typeof src === "string") return src; - if (src && typeof src === "object") return (src as Record).src as string; + if (typeof src === "string") { + return src; + } + if (src && typeof src === "object") { + return (src as Record).src as string; + } return undefined; } @@ -26,6 +34,7 @@ export default function LazyImage({ className, style, minContentHeight = 100, + isRounded = true, ...props }: LazyImageProps) { const [isLoaded, setIsLoaded] = useState(false); @@ -38,21 +47,29 @@ export default function LazyImage({ } }, []); - const sources = getSources({ imgSrc, src }); + const imageSrc = src || toUrl(imgSrc); return ( - {!isLoaded && ); } - -function getSources({ imgSrc, src }: Pick): ThemedImageProps["sources"] { - if (src) { - return { light: src, dark: src }; - } - - if (imgSrc && typeof imgSrc === "object" && "light" in imgSrc && "dark" in imgSrc) { - return imgSrc; - } - - const resolved = toUrl(imgSrc); - if (resolved) { - return { light: resolved, dark: resolved }; - } - - return imgSrc; -} diff --git a/src/components/Common/Toggle.tsx b/src/components/Common/Toggle.tsx new file mode 100644 index 0000000000..ef157f86ca --- /dev/null +++ b/src/components/Common/Toggle.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import clsx from "clsx"; + +interface ToggleOption { + value: T; + label: string; +} + +interface ToggleProps { + options: ToggleOption[]; + value: T; + onChange: (value: T) => void; + className?: string; +} + +export default function Toggle({ options, value, onChange, className }: ToggleProps) { + return ( +
+ {options.map((option) => ( + + ))} +
+ ); +} diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 3ac46a6b19..7fefef3f7a 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -1,21 +1,8 @@ import React, { useEffect } from "react"; import { useLanguage, type DocsLanguage } from "./LanguageStore"; +import { languageConfig } from "./languageConfig"; import clsx from "clsx"; -interface LanguageOption { - label: string; - value: DocsLanguage; - brand: string; -} - -const languageOptions: LanguageOption[] = [ - { label: "C#", value: "csharp", brand: "#9179E4" }, - { label: "Java", value: "java", brand: "#f89820" }, - { label: "Python", value: "python", brand: "#fbcb24" }, - { label: "PHP", value: "php", brand: "#8993be" }, - { label: "Node.js", value: "nodejs", brand: "#3c873a" }, -]; - type LanguageSwitcherProps = { supportedLanguages: DocsLanguage[]; flush?: boolean; @@ -35,7 +22,7 @@ export default function LanguageSwitcher({ supportedLanguages, flush = false }: return (
- {languageOptions + {languageConfig .filter((lang) => supportedLanguages.includes(lang.value)) .map((lang) => { const isActive = language === lang.value; diff --git a/src/components/MarkdownImageLightbox.tsx b/src/components/MarkdownImageLightbox.tsx index 7bbdcc1fa3..48d48a0de9 100644 --- a/src/components/MarkdownImageLightbox.tsx +++ b/src/components/MarkdownImageLightbox.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useState } from "react"; +import { useLocation } from "@docusaurus/router"; import Lightbox from "yet-another-react-lightbox"; -import "yet-another-react-lightbox/styles.css"; import Captions from "yet-another-react-lightbox/plugins/captions"; -import { useLocation } from "@docusaurus/router"; -import { Share } from "yet-another-react-lightbox/plugins"; +import Download from "yet-another-react-lightbox/plugins/download"; +import Zoom from "yet-another-react-lightbox/plugins/zoom"; +import "yet-another-react-lightbox/styles.css"; +import "yet-another-react-lightbox/plugins/captions.css"; type Slide = { src: string; description?: string }; type CandidateElement = HTMLDivElement | HTMLImageElement; @@ -132,11 +134,11 @@ export default function MarkdownImageLightbox() { close={() => setOpen(false)} index={currentIndex} slides={slides} - plugins={[Share, Captions]} + plugins={[Download, Captions, Zoom]} captions={{ - descriptionTextAlign: "center", + descriptionTextAlign: "start", descriptionMaxLines: 2, - showToggle: false, + showToggle: true, }} /> ); diff --git a/src/components/Samples/Hub/Partials/FilterCategory.tsx b/src/components/Samples/Hub/Partials/FilterCategory.tsx new file mode 100644 index 0000000000..f4d2266023 --- /dev/null +++ b/src/components/Samples/Hub/Partials/FilterCategory.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { Icon } from "@site/src/components/Common/Icon"; +import Checkbox from "@site/src/components/Common/Checkbox"; +import useBoolean from "@site/src/hooks/useBoolean"; + +interface FilterTag { + key: string; + label: string; +} + +interface FilterCategoryProps { + name: string; + label: string; + tags: FilterTag[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + isExpanded: boolean; + onToggleExpanded: () => void; +} + +export default function FilterCategory({ + label, + tags, + selectedTags, + onTagToggle, + isExpanded, + onToggleExpanded, +}: FilterCategoryProps) { + const { value: isTagsExpanded, toggle: toggleTagsExpanded } = useBoolean(false); + + const visibleTags = isTagsExpanded ? tags : tags.slice(0, 5); + const hiddenCount = Math.max(0, tags.length - 5); + + return ( +
+ + + + {isExpanded && ( + +
+ {visibleTags.map((tag) => ( + onTagToggle(tag.key)} + label={tag.label} + /> + ))} + + {hiddenCount > 0 && ( + + )} +
+
+ )} +
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/LanguageTag.tsx b/src/components/Samples/Hub/Partials/LanguageTag.tsx new file mode 100644 index 0000000000..409ffa5f5c --- /dev/null +++ b/src/components/Samples/Hub/Partials/LanguageTag.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import Tag from "@site/src/theme/Tag"; +import { getLanguageConfig } from "../../../languageConfig"; + +export interface LanguageTagProps { + languageKey: string; + className?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export default function LanguageTag({ languageKey, className, onClick }: LanguageTagProps) { + const config = getLanguageConfig(languageKey); + + if (!config) { + return ( + + {languageKey} + + ); + } + + return ( + + {config.label} + + ); +} diff --git a/src/components/Samples/Hub/Partials/SampleCard.tsx b/src/components/Samples/Hub/Partials/SampleCard.tsx new file mode 100644 index 0000000000..177d320474 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SampleCard.tsx @@ -0,0 +1,173 @@ +import React, { ReactNode } from "react"; +import Link from "@docusaurus/Link"; +import Heading from "@theme/Heading"; +import LazyImage from "@site/src/components/Common/LazyImage"; +import clsx from "clsx"; +import Tag from "@site/src/theme/Tag"; +import LanguageTag from "@site/src/components/Samples/Hub/Partials/LanguageTag"; + +interface TagWithCategory { + label: string; + key: string; + category?: string; +} + +export interface SampleCardProps { + title: string; + description: ReactNode; + imgSrc?: string; + imgAlt?: string; + imgWidth?: number; + imgHeight?: number; + url: string; + tags?: TagWithCategory[]; + animationDelay?: number; + onTagClick?: (tagKey: string) => void; + selectedTags?: Set; +} + +export default function SampleCard({ + title, + description, + imgSrc, + imgAlt = "", + url, + tags = [], + animationDelay = 0, + onTagClick, + selectedTags, +}: SampleCardProps) { + const hasImage = Boolean(imgSrc); + + const challengesSolutionsTags = tags.filter((t) => t.category === "challenges-solutions"); + const featureTags = tags.filter((t) => t.category === "feature"); + const techStackTags = tags.filter((t) => t.category === "tech-stack"); + + const languageTags = techStackTags.filter((t) => ["csharp", "java", "python", "php", "nodejs"].includes(t.key)); + + const handleTagClick = (e: React.MouseEvent, tag: TagWithCategory) => { + e.preventDefault(); + e.stopPropagation(); + onTagClick?.(tag.key); + }; + + const isTagSelected = (tag: TagWithCategory) => { + if (!selectedTags || selectedTags.size === 0) { + return true; + } + return selectedTags.has(tag.key); + }; + + return ( +
+
+ {hasImage && ( + <> + + {languageTags.length > 0 && ( +
+ {languageTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx(!isTagSelected(tag) && "opacity-50")} + /> + ))} +
+ )} + + )} +
+
+ +
+
+ + {title} + +
+ +

{description}

+ +
+ {challengesSolutionsTags.length > 0 && ( +
+ Challenges & Solutions +
+ {challengesSolutionsTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx( + onTagClick && "cursor-pointer", + !isTagSelected(tag) && "opacity-50" + )} + > + {tag.label} + + ))} +
+
+ )} + + {featureTags.length > 0 && ( +
+ Features +
+ {featureTags.map((tag) => ( + handleTagClick(e, tag) : undefined} + className={clsx( + onTagClick && "cursor-pointer", + !isTagSelected(tag) && "opacity-50" + )} + > + {tag.label} + + ))} +
+
+ )} +
+
+
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesDecoration.tsx b/src/components/Samples/Hub/Partials/SamplesDecoration.tsx new file mode 100644 index 0000000000..333f58b225 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesDecoration.tsx @@ -0,0 +1,547 @@ +import { useState, useEffect } from "react"; + +export default function SamplesDecoration() { + const [colors, setColors] = useState({ + lightest: "#86BAF2", + darkest: "#2382E7", + lighter: "#5FA4ED", + }); + + useEffect(() => { + const getColorValue = (varName: string, fallback: string): string => { + // eslint-disable-next-line no-undef + const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); + return value || fallback; + }; + + const updateColors = () => { + setColors({ + lightest: getColorValue("--ifm-color-primary-lightest", "#86BAF2"), + darkest: getColorValue("--ifm-color-primary-darkest", "#2382E7"), + lighter: getColorValue("--ifm-color-primary-lighter", "#5FA4ED"), + }); + }; + + updateColors(); + + // eslint-disable-next-line no-undef + const observer = new MutationObserver(updateColors); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); + + return () => observer.disconnect(); + }, []); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesFilter.tsx b/src/components/Samples/Hub/Partials/SamplesFilter.tsx new file mode 100644 index 0000000000..067527128e --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesFilter.tsx @@ -0,0 +1,127 @@ +import React, { useState } from "react"; +import Heading from "@theme/Heading"; +import clsx from "clsx"; +import Toggle from "@site/src/components/Common/Toggle"; +import FilterCategory from "./FilterCategory"; +import Button from "@site/src/components/Common/Button"; + +interface FilterTag { + key: string; + label: string; +} + +interface FilterCategoryData { + name: string; + label: string; + tags: FilterTag[]; +} + +interface SamplesFilterProps { + categories: FilterCategoryData[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + matchLogic: "any" | "all"; + onMatchLogicChange: (logic: "any" | "all") => void; + onClearFilters?: () => void; + showHeader?: boolean; +} + +export default function SamplesFilter({ + categories, + selectedTags, + onTagToggle, + matchLogic, + onMatchLogicChange, + onClearFilters, + showHeader = true, +}: SamplesFilterProps) { + const [expandedCategories, setExpandedCategories] = useState>(new Set(categories.map((c) => c.name))); + const [searchQuery, setSearchQuery] = useState(""); + + const toggleCategory = (categoryName: string) => { + const newExpanded = new Set(expandedCategories); + if (newExpanded.has(categoryName)) { + newExpanded.delete(categoryName); + } else { + newExpanded.add(categoryName); + } + setExpandedCategories(newExpanded); + }; + + const filteredCategories = categories + .map((category) => ({ + ...category, + tags: category.tags.filter((tag) => tag.label.toLowerCase().includes(searchQuery.toLowerCase())), + })) + .filter((category) => category.tags.length > 0); + + const handleClearAll = () => { + setSearchQuery(""); + onMatchLogicChange("any"); + onClearFilters?.(); + }; + + const hasActiveFilters = selectedTags.size > 0 || searchQuery.length > 0 || matchLogic !== "any"; + + return ( +
+ {showHeader && ( +
+ + Filters + + {hasActiveFilters && ( + + )} +
+ )} +
+ setSearchQuery(e.target.value)} + className={clsx( + "px-3 py-2 rounded-full", + "border border-black/10 dark:border-white/10", + "text-sm text-black dark:text-white", + "placeholder-black/30 dark:placeholder-white/30" + )} + /> +
+ +
+ + Match logic + + +
+ + {filteredCategories.length > 0 ? ( + filteredCategories.map((category) => ( + toggleCategory(category.name)} + /> + )) + ) : ( +
Nothing found
+ )} +
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesGrid.tsx b/src/components/Samples/Hub/Partials/SamplesGrid.tsx new file mode 100644 index 0000000000..1739b0feee --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesGrid.tsx @@ -0,0 +1,77 @@ +import React, { useMemo } from "react"; +import clsx from "clsx"; +import { SampleCard } from "@site/src/components/Samples"; +import Heading from "@theme/Heading"; +import type { Sample } from "../../types"; + +interface SamplesGridProps { + samples: Sample[]; + selectedTags: Set; + matchLogic: "any" | "all"; + onTagClick?: (tagKey: string) => void; +} + +export default function SamplesGrid({ samples, selectedTags, matchLogic, onTagClick }: SamplesGridProps) { + const filteredSamples = useMemo(() => { + if (selectedTags.size === 0) { + return samples; + } + + return samples.filter((sample) => { + const sampleTagKeys = new Set(sample.tags.map((t) => t.key)); + + if (matchLogic === "any") { + return Array.from(selectedTags).some((tag) => sampleTagKeys.has(tag)); + } else { + return Array.from(selectedTags).every((tag) => sampleTagKeys.has(tag)); + } + }); + }, [samples, selectedTags, matchLogic]); + + const sortedSamples = useMemo(() => { + return filteredSamples; + }, [filteredSamples]); + + if (sortedSamples.length === 0) { + return ( +
+

No samples found matching your filters.

+
+ ); + } + + return ( +
+
+ + Samples + + + {sortedSamples.length} + +
+
+ {sortedSamples.map((sample, index) => ( + + ))} +
+
+ ); +} diff --git a/src/components/Samples/Hub/Partials/SamplesHeader.tsx b/src/components/Samples/Hub/Partials/SamplesHeader.tsx new file mode 100644 index 0000000000..cbe3fa2452 --- /dev/null +++ b/src/components/Samples/Hub/Partials/SamplesHeader.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import Heading from "@theme/Heading"; +import clsx from "clsx"; +import SamplesDecoration from "./SamplesDecoration"; + +export default function SamplesHeader() { + return ( +
+
+ + Explore Samples + +

Production-ready code samples, architecture patterns, and starter kits.

+
+ +
+ ); +} diff --git a/src/components/Samples/Hub/SamplesHomePage.tsx b/src/components/Samples/Hub/SamplesHomePage.tsx new file mode 100644 index 0000000000..aa57830293 --- /dev/null +++ b/src/components/Samples/Hub/SamplesHomePage.tsx @@ -0,0 +1,176 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { SamplesHeader, SamplesFilter, SamplesGrid } from "@site/src/components/Samples"; +import { usePluginData } from "@docusaurus/useGlobalData"; +import { useHistory, useLocation } from "@docusaurus/router"; +import type { Tag, PluginData } from "../types"; +import Drawer from "@site/src/components/Common/Drawer"; +import useBoolean from "@site/src/hooks/useBoolean"; +import Button from "@site/src/components/Common/Button"; +import clsx from "clsx"; + +export default function SamplesHomePage() { + const pluginData = usePluginData("recent-samples-plugin") as PluginData | undefined; + const history = useHistory(); + const location = useLocation(); + + const [selectedTags, setSelectedTags] = useState>(() => { + // eslint-disable-next-line no-undef + const params = new URLSearchParams(location.search); + const tagsParam = params.get("tags"); + return tagsParam ? new Set(tagsParam.split(",")) : new Set(); + }); + + const [matchLogic, setMatchLogic] = useState<"any" | "all">(() => { + // eslint-disable-next-line no-undef + const params = new URLSearchParams(location.search); + const logicParam = params.get("match"); + return logicParam === "all" ? "all" : "any"; + }); + + const samples = pluginData?.samples || []; + const allTags = pluginData?.tags || []; + + const categories = useMemo(() => { + const categoryMap: Record = {}; + + allTags.forEach((tag) => { + const category = tag.category || "other"; + if (!categoryMap[category]) { + categoryMap[category] = { + name: category, + label: + category === "tech-stack" + ? "Tech Stack" + : category === "challenges-solutions" + ? "Challenges & Solutions" + : category === "feature" + ? "Features" + : "Other", + tags: [], + }; + } + categoryMap[category].tags.push({ + key: tag.key, + label: tag.label, + count: tag.count, + }); + }); + + return Object.values(categoryMap); + }, [allTags]); + + useEffect(() => { + // eslint-disable-next-line no-undef + const params = new URLSearchParams(); + + if (selectedTags.size > 0) { + params.set("tags", Array.from(selectedTags).join(",")); + } + + if (matchLogic !== "any") { + params.set("match", matchLogic); + } + + const newSearch = params.toString(); + const currentSearch = location.search.replace(/^\?/, ""); + + if (newSearch !== currentSearch) { + history.replace({ + pathname: location.pathname, + search: newSearch ? `?${newSearch}` : "", + }); + } + }, [selectedTags, matchLogic, history, location.pathname, location.search]); + + const handleTagToggle = (tagKey: string) => { + const newSelected = new Set(selectedTags); + if (newSelected.has(tagKey)) { + newSelected.delete(tagKey); + } else { + newSelected.add(tagKey); + } + setSelectedTags(newSelected); + }; + + const handleTagClick = (tagKey: string) => { + setSelectedTags(new Set([tagKey])); + }; + + const { value: isFilterDrawerOpen, setTrue: openFilterDrawer, setFalse: closeFilterDrawer } = useBoolean(false); + + const hasActiveFilters = selectedTags.size > 0 || matchLogic !== "any"; + + return ( + <> + +
+ +
+ +
+
+ setSelectedTags(new Set())} + /> +
+ + { + setSelectedTags(new Set()); + setMatchLogic("any"); + }} + variant="secondary" + size="xs" + > + Clear all + + ) + } + > +
+ setSelectedTags(new Set())} + showHeader={false} + /> + +
+
+ +
+ s.id !== "home")} + selectedTags={selectedTags} + matchLogic={matchLogic} + onTagClick={handleTagClick} + /> +
+
+ + ); +} diff --git a/src/components/Samples/Overview/Partials/ActionsCard.tsx b/src/components/Samples/Overview/Partials/ActionsCard.tsx new file mode 100644 index 0000000000..99f21f3327 --- /dev/null +++ b/src/components/Samples/Overview/Partials/ActionsCard.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import clsx from "clsx"; +import Button from "@site/src/components/Common/Button"; + +export interface ActionsCardProps { + className?: string; + githubUser?: string; + githubRepo?: string; + demoUrl?: string; +} + +export default function ActionsCard({ className, githubUser, githubRepo, demoUrl }: ActionsCardProps) { + const githubUrl = githubUser && githubRepo ? `https://github.com/${githubUser}/${githubRepo}` : undefined; + + return ( +
+
+ {githubUrl && ( + + )} + {demoUrl && ( + + )} +
+
+ ); +} diff --git a/src/components/Samples/Overview/Partials/FeatureAccordion.tsx b/src/components/Samples/Overview/Partials/FeatureAccordion.tsx new file mode 100644 index 0000000000..81f317eec8 --- /dev/null +++ b/src/components/Samples/Overview/Partials/FeatureAccordion.tsx @@ -0,0 +1,79 @@ +import React, { ReactNode } from "react"; +import clsx from "clsx"; +import { motion, AnimatePresence } from "framer-motion"; +import { Icon } from "@site/src/components/Common/Icon"; +import { IconName } from "@site/src/typescript/iconName"; +import useBoolean from "@site/src/hooks/useBoolean"; + +export interface FeatureAccordionProps { + className?: string; + title: string; + description: string; + icon?: IconName; + children?: ReactNode; + defaultExpanded?: boolean; +} + +export default function FeatureAccordion({ + className, + title, + description, + icon = "link", + children, + defaultExpanded = false, +}: FeatureAccordionProps) { + const { value: isExpanded, toggle } = useBoolean(defaultExpanded); + + return ( +
+ + + {isExpanded && children && ( + +
+ {children} +
+
+ )} +
+
+ ); +} diff --git a/src/components/Samples/Overview/Partials/RelatedResource.tsx b/src/components/Samples/Overview/Partials/RelatedResource.tsx new file mode 100644 index 0000000000..23af7754ac --- /dev/null +++ b/src/components/Samples/Overview/Partials/RelatedResource.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import clsx from "clsx"; +import { Icon } from "@site/src/components/Common/Icon"; +import { IconName } from "@site/src/typescript/iconName"; +import Link from "@docusaurus/Link"; +import { useLatestVersion } from "@site/src/hooks/useLatestVersion"; + +type ResourceType = "guide" | "documentation"; + +type DocumentationType = "docs" | "cloud"; + +export interface RelatedResourceProps { + className?: string; + type: ResourceType; + documentationType?: DocumentationType; + subtitle: string; + articleKey: string; +} + +const TYPE_CONFIG: Record = { + guide: { + title: "Guide", + icon: "guides", + }, + documentation: { + title: "Documentation", + icon: "database", + }, +}; + +export default function RelatedResource({ + className, + type, + documentationType = "docs", + subtitle, + articleKey, +}: RelatedResourceProps) { + const latestVersion = useLatestVersion() as string; + const config = TYPE_CONFIG[type]; + + const url = React.useMemo(() => { + if (type === "guide") { + return `/guides/${articleKey}`; + } + + const basePath = documentationType === "cloud" ? "/cloud" : `/${latestVersion}`; + return `${basePath}/${articleKey}`; + }, [type, documentationType, articleKey, latestVersion]); + + const icon = type === "documentation" && documentationType === "cloud" ? "cloud" : config.icon; + + return ( + +
+ +
+
+

{config.title}

+

{subtitle}

+
+ + ); +} diff --git a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx new file mode 100644 index 0000000000..dbd4379899 --- /dev/null +++ b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx @@ -0,0 +1,138 @@ +import React, { ReactNode, useMemo } from "react"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; +import { usePluginData } from "@docusaurus/useGlobalData"; +import clsx from "clsx"; +import Heading from "@theme/Heading"; +import Tag from "@site/src/theme/Tag"; +import type { PluginData } from "@site/src/components/Samples/types"; + +export interface SampleMetadataColumnProps { + className?: string; + actionsCard?: ReactNode; + relatedResources?: ReactNode; +} + +interface TagData { + [key: string]: { + label: string; + }; +} + +function createFilterUrl(tagKey: string): string { + return `/samples?tags=${tagKey}`; +} + +function getTagsWithLabels(tagKeys: string[] | undefined, tagData: TagData) { + if (!tagKeys || tagKeys.length === 0) { + return []; + } + + return tagKeys.map((key) => ({ + key, + label: tagData[key]?.label || key, + })); +} + +interface TagSectionProps { + title: string; + tags: Array<{ key: string; label: string }>; +} + +function TagSection({ title, tags }: TagSectionProps) { + if (tags.length === 0) { + return null; + } + + return ( +
+ + {title} + +
+ {tags.map((tag) => ( + + {tag.label} + + ))} +
+
+ ); +} + +export default function SampleMetadataColumn({ className, actionsCard, relatedResources }: SampleMetadataColumnProps) { + const { frontMatter } = useDoc(); + const pluginData = usePluginData("recent-samples-plugin") as PluginData | undefined; + + const { challengesSolutionsTagsData, featureTagsData, techStackTagsData } = useMemo(() => { + const allTags = pluginData?.tags || []; + + const challengesSolutionsTags: TagData = {}; + const featureTags: TagData = {}; + const techStackTags: TagData = {}; + + allTags.forEach((tag) => { + const tagEntry = { label: tag.label }; + if (tag.category === "challenges-solutions") { + challengesSolutionsTags[tag.key] = tagEntry; + } else if (tag.category === "feature") { + featureTags[tag.key] = tagEntry; + } else if (tag.category === "tech-stack") { + techStackTags[tag.key] = tagEntry; + } + }); + + return { + challengesSolutionsTagsData: challengesSolutionsTags, + featureTagsData: featureTags, + techStackTagsData: techStackTags, + }; + }, [pluginData]); + + const challengesSolutionsTagKeys = ((frontMatter as any).challengesSolutionsTags || + (frontMatter as any).challengesSolutionsTags) as string[]; + const featureTagKeys = (frontMatter as any).featureTags as string[]; + const techStackTagKeys = (frontMatter as any).techStackTags as string[]; + const category = (frontMatter as any).category as string; + const license = (frontMatter as any).license as string; + + const challengesSolutionsTags = getTagsWithLabels(challengesSolutionsTagKeys, challengesSolutionsTagsData); + const featureTags = getTagsWithLabels(featureTagKeys, featureTagsData); + const techStackTags = getTagsWithLabels(techStackTagKeys, techStackTagsData); + + return ( +
+ {actionsCard} + + + + + + {category && ( +
+ + Category + +

{category}

+
+ )} + + {license && ( +
+ + License + +

{license}

+
+ )} + + {relatedResources && ( +
+ + Related Resources + +
{relatedResources}
+
+ )} +
+ ); +} diff --git a/src/components/Samples/Overview/SampleLayout.tsx b/src/components/Samples/Overview/SampleLayout.tsx new file mode 100644 index 0000000000..16c6c477e3 --- /dev/null +++ b/src/components/Samples/Overview/SampleLayout.tsx @@ -0,0 +1,22 @@ +import React, { ReactNode } from "react"; + +interface SampleLayoutProps { + children: ReactNode; + details: ReactNode; + gallery?: ReactNode; +} + +export default function SampleLayout({ children, details, gallery }: SampleLayoutProps) { + return ( +
+
+
{gallery}
+ {children} +
+
+
{gallery}
+ {details} +
+
+ ); +} diff --git a/src/components/Samples/index.ts b/src/components/Samples/index.ts new file mode 100644 index 0000000000..10837552e1 --- /dev/null +++ b/src/components/Samples/index.ts @@ -0,0 +1,15 @@ +// Sample overview page components +export { default as ActionsCard } from "./Overview/Partials/ActionsCard"; +export { default as RelatedResource } from "./Overview/Partials/RelatedResource"; +export { default as FeatureAccordion } from "./Overview/Partials/FeatureAccordion"; +export { default as SampleMetadataColumn } from "./Overview/Partials/SampleMetadataColumn"; +export { default as SampleLayout } from "./Overview/SampleLayout"; + +// Samples hub components +export { default as SampleCard } from "./Hub/Partials/SampleCard"; +export { default as SamplesGrid } from "./Hub/Partials/SamplesGrid"; +export { default as SamplesFilter } from "./Hub/Partials/SamplesFilter"; +export { default as FilterCategory } from "./Hub/Partials/FilterCategory"; +export { default as SamplesHeader } from "./Hub/Partials/SamplesHeader"; +export { default as SamplesDecoration } from "./Hub/Partials/SamplesDecoration"; +export { default as SamplesHomePage } from "./Hub/SamplesHomePage"; diff --git a/src/components/Samples/types.ts b/src/components/Samples/types.ts new file mode 100644 index 0000000000..b2e4925b9b --- /dev/null +++ b/src/components/Samples/types.ts @@ -0,0 +1,20 @@ +export interface Tag { + key: string; + label: string; + category?: string; + count?: number; +} + +export interface Sample { + id: string; + title: string; + description?: string; + permalink: string; + image?: string | { light: string; dark: string }; + tags: Array<{ label: string; key: string; category?: string }>; +} + +export interface PluginData { + samples: Sample[]; + tags: Tag[]; +} diff --git a/src/components/languageConfig.ts b/src/components/languageConfig.ts new file mode 100644 index 0000000000..96b08f23d2 --- /dev/null +++ b/src/components/languageConfig.ts @@ -0,0 +1,23 @@ +import type { DocsLanguage } from "./LanguageStore"; + +export interface LanguageConfig { + label: string; + value: DocsLanguage; + brand: string; +} + +export const languageConfig: LanguageConfig[] = [ + { label: "C#", value: "csharp", brand: "#9179E4" }, + { label: "Java", value: "java", brand: "#f89820" }, + { label: "Python", value: "python", brand: "#fbcb24" }, + { label: "PHP", value: "php", brand: "#8993be" }, + { label: "Node.js", value: "nodejs", brand: "#3c873a" }, +]; + +export function getLanguageConfig(languageKey: string): LanguageConfig | undefined { + return languageConfig.find((lang) => lang.value === languageKey); +} + +export function getLanguageBrandColor(languageKey: string): string | undefined { + return getLanguageConfig(languageKey)?.brand; +} diff --git a/src/css/custom.css b/src/css/custom.css index eac7e0a529..d1514d0d43 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -10,6 +10,11 @@ } } +/* Hide TOC column for sample pages */ +.plugin-id-samples .col.col--3 { + display: none !important; +} + /* Custom animations for layout switcher */ @keyframes slide-in-from-bottom { from { @@ -252,16 +257,6 @@ pre[class*="language-"] { display: none; } -/* Custom styling for lightbox */ -.yarl__flex_center { - @apply !p-[64px]; - flex-direction: column; -} - -.yarl__slide_description_container { - @apply mt-4 text-white; -} - /* Custom border for alerts */ .alert { @apply border; @@ -381,7 +376,7 @@ a.breadcrumbs__link:hover { } /* Custom border-radius for document images */ -.theme-doc-markdown img { +.plugin-id-default .theme-doc-markdown img { @apply rounded-md; } @@ -595,7 +590,8 @@ hr { /* Home Page full width override */ .guides-home-page [class*="docItemCol"], .cloud-home-page [class*="docItemCol"], -.docs-home-page [class*="docItemCol"] { +.docs-home-page [class*="docItemCol"], +.plugin-id-samples [class*="docItemCol"] { max-width: 100% !important; } @@ -649,6 +645,16 @@ a { animation: skeleton-loading 2000ms infinite linear; } +/* Hide scrollbar utility */ +.scrollbar-none { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.scrollbar-none::-webkit-scrollbar { + display: none; +} + [data-theme="dark"] .skeleton { background-image: linear-gradient( 45deg, @@ -711,7 +717,8 @@ a { /* Disable navigation for Docs */ .plugin-id-default .pagination-nav, -.plugin-id-cloud .pagination-nav { +.plugin-id-cloud .pagination-nav, +.plugin-id-samples .pagination-nav { display: none; } @@ -778,3 +785,8 @@ a { font-size: 0.75rem; line-height: 1.2; } + +/* Hide TOC for Samples */ +.plugin-id-samples [class*="tocCollapsibleButton"] { + display: none; +} diff --git a/src/hooks/useBoolean.ts b/src/hooks/useBoolean.ts new file mode 100644 index 0000000000..7ecfd51a9a --- /dev/null +++ b/src/hooks/useBoolean.ts @@ -0,0 +1,14 @@ +import { useCallback, useState } from "react"; + +const useBoolean = (initial: boolean) => { + const [value, setValue] = useState(initial); + return { + value, + setValue, + toggle: useCallback(() => setValue((value: boolean) => !value), []), + setTrue: useCallback(() => setValue(true), []), + setFalse: useCallback(() => setValue(false), []), + }; +}; + +export default useBoolean; diff --git a/src/plugins/recent-guides-plugin.ts b/src/plugins/recent-guides-plugin.ts index 49a307ca17..46f4b5384e 100644 --- a/src/plugins/recent-guides-plugin.ts +++ b/src/plugins/recent-guides-plugin.ts @@ -40,7 +40,7 @@ function getFiles(dir: string, files: string[] = []) { return files; } -const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options) { +export default function recentGuidesPlugin(context, _options): Plugin { return { name: "recent-guides-plugin", async loadContent() { @@ -160,6 +160,4 @@ const recentGuidesPlugin: Plugin = function recentGuidesPlugin(context, _options setGlobalData(content); }, }; -}; - -export default recentGuidesPlugin; +} diff --git a/src/plugins/recent-samples-plugin.ts b/src/plugins/recent-samples-plugin.ts new file mode 100644 index 0000000000..4adce18777 --- /dev/null +++ b/src/plugins/recent-samples-plugin.ts @@ -0,0 +1,206 @@ +import type { Plugin } from "@docusaurus/types"; +import path from "path"; +import fs from "fs"; +import matter from "gray-matter"; +import type { IconName } from "../typescript/iconName"; +const yaml = require("js-yaml"); + +interface TagDefinition { + label: string; + description?: string; +} + +interface TagsByCategory { + [category: string]: { + [tagKey: string]: TagDefinition; + }; +} + +export interface Sample { + id: string; + title: string; + permalink: string; + tags: { label: string; key: string; category: string }[]; + lastUpdatedAt: number; + description?: string; + image?: string | { light: string; dark: string }; + icon?: IconName; + externalUrl?: string; +} + +export interface PluginData { + samples: Sample[]; + tags: Array<{ + label: string; + key: string; + count: number; + category: string; + }>; +} + +function getFiles(dir: string, files: string[] = []) { + const fileList = fs.readdirSync(dir); + for (const file of fileList) { + const name = path.join(dir, file); + if (fs.statSync(name).isDirectory()) { + getFiles(name, files); + } else { + files.push(name); + } + } + return files; +} + +export default function recentSamplesPlugin(context, _options): Plugin { + return { + name: "recent-samples-plugin", + async loadContent() { + const samplesDir = path.join(context.siteDir, "samples"); + + if (!fs.existsSync(samplesDir)) { + return []; + } + + const tagsByCategory: TagsByCategory = {}; + const tagsDir = path.join(samplesDir, "tags"); + + if (fs.existsSync(tagsDir)) { + const categoryFiles = fs.readdirSync(tagsDir).filter((f) => f.endsWith(".yml")); + for (const file of categoryFiles) { + const category = path.basename(file, ".yml"); + const filePath = path.join(tagsDir, file); + try { + const fileContent = fs.readFileSync(filePath, "utf8"); + tagsByCategory[category] = (yaml.load(fileContent) as any) || {}; + } catch (e) { + // eslint-disable-next-line no-console + console.error(`Failed to load tags/${file}`, e); + } + } + } + + const tagCounts: Record = {}; + + const files = getFiles(samplesDir) + .filter((f) => /\.(md|mdx)$/.test(f)) + .filter((f) => { + const relativePath = path.relative(samplesDir, f); + const normalized = relativePath.split(path.sep).join("/"); + return normalized !== "home.mdx"; + }); + + const samples = files.map((filePath) => { + const fileContent = fs.readFileSync(filePath, "utf-8"); + const { data } = matter(fileContent); + const stats = fs.statSync(filePath); + + const relativePath = path.relative(samplesDir, filePath); + const relativePathNormalized = relativePath.split(path.sep).join("/"); + const baseName = relativePathNormalized.replace(/\.(md|mdx)$/, ""); + + const slug = baseName.endsWith("/index") ? baseName.replace(/\/index$/, "") : baseName; + const permalink = `/samples/${slug === "index" ? "" : slug}`; + + const externalUrl: string | undefined = (data as any).externalUrl || (data as any).external_url; + const frontmatterDate: unknown = (data as any).publishedAt; + + let lastUpdatedAt: number; + if (frontmatterDate) { + let millis: number | null = null; + if (typeof frontmatterDate === "string") { + const parsed = Date.parse(frontmatterDate); + if (!Number.isNaN(parsed)) { + millis = parsed; + } + } else if (frontmatterDate instanceof Date) { + millis = frontmatterDate.getTime(); + } else if (typeof frontmatterDate === "number") { + millis = frontmatterDate > 1e12 ? frontmatterDate : frontmatterDate * 1000; + } + + lastUpdatedAt = millis ? Math.floor(millis / 1000) : Math.floor(stats.mtimeMs / 1000); + } else { + lastUpdatedAt = Math.floor(stats.mtimeMs / 1000); + } + + const allTagsArray: Array<{ key: string; category: string }> = []; + + const challengesSolutionsTags = data.challengesSolutionsTags; + + if (Array.isArray(challengesSolutionsTags)) { + challengesSolutionsTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "challenges-solutions" }); + }); + } + + const featureTags = data.featureTags || data.feature_tags || []; + if (Array.isArray(featureTags)) { + featureTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "feature" }); + }); + } + + const techStackTags = data.techStackTags || data.tech_stack_tags || []; + if (Array.isArray(techStackTags)) { + techStackTags.forEach((tag: string) => { + allTagsArray.push({ key: tag, category: "tech-stack" }); + }); + } + + allTagsArray.forEach(({ key }) => { + tagCounts[key] = (tagCounts[key] || 0) + 1; + }); + + const formattedTags = allTagsArray.map(({ key, category }) => { + const categoryTags = tagsByCategory[category] || {}; + const definedTag = categoryTags[key]; + + return { + label: definedTag?.label || key, + key, + category, + }; + }); + + return { + id: path.basename(filePath, path.extname(filePath)), + title: data.title || path.basename(filePath, path.extname(filePath)), + permalink: data.slug || permalink, + tags: formattedTags, + lastUpdatedAt, + description: data.description, + image: data.image, + icon: data.icon, + externalUrl, + }; + }); + + const allTags: Array<{ + label: string; + key: string; + count: number; + category: string; + }> = []; + + Object.entries(tagsByCategory).forEach(([category, tags]) => { + Object.entries(tags).forEach(([key, value]) => { + allTags.push({ + label: value.label, + key, + count: tagCounts[key] || 0, + category, + }); + }); + }); + + return { + samples: samples.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt), + tags: allTags, + }; + }, + async contentLoaded({ content, actions }) { + const { setGlobalData } = actions; + setGlobalData(content); + }, + }; +} diff --git a/src/theme/DocSidebar/Desktop/index.tsx b/src/theme/DocSidebar/Desktop/index.tsx index 227d09dcf4..bf55cb4632 100644 --- a/src/theme/DocSidebar/Desktop/index.tsx +++ b/src/theme/DocSidebar/Desktop/index.tsx @@ -30,6 +30,8 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { const pathType = getPathType(path); const landingPagePath = getLandingPagePath(pathType, versionLabel); + const shouldDisplayContent = pathType !== PathType.Guides && pathType !== PathType.Samples; + return (
)} + {pathType !== PathType.Samples && ( + + Samples + + Switch + + + )} {pathType !== PathType.Documentation && ( RavenDB Docs @@ -83,9 +93,9 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { )}
- {pathType !== PathType.Guides &&
} + {shouldDisplayContent &&
} {pathType === PathType.Documentation && } - {pathType !== PathType.Guides && } + {shouldDisplayContent && } {hideable && }
); diff --git a/src/theme/DocSidebar/Mobile/index.tsx b/src/theme/DocSidebar/Mobile/index.tsx index efc62ea098..f9fd75907d 100644 --- a/src/theme/DocSidebar/Mobile/index.tsx +++ b/src/theme/DocSidebar/Mobile/index.tsx @@ -22,6 +22,8 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { const pathType = getPathType(path); const landingPagePath = getLandingPagePath(pathType, versionLabel); + const shouldDisplayContent = pathType !== PathType.Guides && pathType !== PathType.Samples; + return (
  • @@ -47,6 +49,14 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { )} + {pathType !== PathType.Samples && ( + + Samples + + Switch + + + )} {pathType !== PathType.Cloud && ( RavenDB Cloud Docs @@ -59,7 +69,7 @@ function DocSidebarMobileSecondaryMenu({ sidebar, path }: DocSidebarProps) { Community - {pathType !== PathType.Cloud && pathType !== PathType.Guides && ( + {pathType === PathType.Documentation && (
  • )} - {pathType !== PathType.Guides && ( + {shouldDisplayContent && (

  • )} - {pathType !== PathType.Cloud && pathType !== PathType.Guides && } - {pathType !== PathType.Guides && ( + {pathType === PathType.Documentation && } + {shouldDisplayContent && ( { - // Mobile sidebar should only be closed if the category has a link if (item.type === "category" && item.href) { mobileSidebar.toggle(); } diff --git a/src/typescript/docMetadata.d.ts b/src/typescript/docMetadata.d.ts index d40e3f782d..645fb8aafd 100644 --- a/src/typescript/docMetadata.d.ts +++ b/src/typescript/docMetadata.d.ts @@ -3,6 +3,11 @@ import { DocsLanguage } from "@site/src/components/LanguageStore"; import { SeeAlsoItemType } from "@site/src/components/SeeAlso/types"; import { IconName } from "@site/src/typescript/iconName"; +export interface GalleryImage { + src: string; + alt?: string; +} + export interface CustomDocFrontMatter extends DocFrontMatter { supported_languages?: DocsLanguage[]; see_also?: SeeAlsoItemType[]; @@ -12,6 +17,7 @@ export interface CustomDocFrontMatter extends DocFrontMatter { publishedAt?: string; proficiencyLevel?: string; keywords?: string[]; + gallery?: GalleryImage[]; } type CustomDocContextValue = Omit & { diff --git a/src/typescript/pathUtils.ts b/src/typescript/pathUtils.ts index 438fe3e043..48abb5d651 100644 --- a/src/typescript/pathUtils.ts +++ b/src/typescript/pathUtils.ts @@ -3,6 +3,7 @@ export const PathType = { Guides: "GUIDES", Documentation: "DOCUMENTATION", Templates: "TEMPLATES", + Samples: "SAMPLES", } as const; export type PathTypeValue = (typeof PathType)[keyof typeof PathType]; @@ -14,6 +15,9 @@ export function getPathType(path: string): PathTypeValue { if (path.includes("/guides")) { return PathType.Guides; } + if (path.includes("/samples")) { + return PathType.Samples; + } if (path.includes("/templates")) { return PathType.Templates; } @@ -27,6 +31,9 @@ export function getLandingPagePath(pathType: PathTypeValue, versionLabel: string if (pathType === PathType.Guides) { return "/guides"; } + if (pathType === PathType.Samples) { + return "/samples"; + } if (pathType === PathType.Templates) { return "/templates"; } diff --git a/static/img/samples/library-of-ravens/01.webp b/static/img/samples/library-of-ravens/01.webp new file mode 100644 index 0000000000000000000000000000000000000000..83addb6c52e888911638a6b180229af54b56ac35 GIT binary patch literal 52760 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�@r+-tbRH zo}#lM|JY3)sNFcPzsuqa&yMO1Ko{Svwwn`zzFZiau{$Z5-e{IrhkLk&{=cC;wfg+Z zE+Vg*0;QVEKbLMk*C0u|K?QgK17Y<> zLQAQOdVWpI@&&pAdXBx2rVS8i0&_0Ax00N>W?a=!N&Ml}e0+89QO+G_WXUv{Em`s5 zXba$QuI75>5`{YkzyFLAN~|=p?>;vNlwG?Q1ve~y--StqR>hVR9FTL?Bt(Y7Lfz!o zTjGOdGP981UFa)H|E=HNU=V7lLP2Yg4>FNZMwS+BhH77z+2f{KcFc6&_Kp*}^3qD- zyPF3{gl`x+M}>n6c8>4wfzSuc0KaZQdS?FTJeIDq(-a47`q53UciWl^?P#%<&rv%b zC`X!<&uY27ZY}KA9z=SWH!=ipiwJDVmiN4_3@#6=S9@P3Xu+72;9isg`oT@&c<|c% z3qIf(sy-MP%{qN2SLjgvQuUND299~!Wxmxddj*;E5yildTo7FH;tSV^4$cY9@Dbx( zC~Yf?iI+oFGDAU8U3XM7RidR0Czhn(S#=ACuG{}vkcd=mZ`F?5bN*xolRv#U+3ffPIH^4&{c^1r= zpYF*pV?AgELk0Cz3f5UMaGvU|`p%PYx{J>LK_ z|2~yuob1u&&GNyG1}PSJqwy(g7f+QqG+JtW{@tXdpo+@Wrqj}{Nfu#7YtJJy6r?AC z1W89v5)hP%;dsJPrXwv^2@8zR_j(Q zOuKl(m+>SQVnC!+W?1BFf_an0mM)miX;MEQ+p<#tGwex!A?Ju>bxNqr1s`Q?40#V( uhu7n{7&~g2sN`^yX0BWa2_r3&<6&qJlFe6POeG%f7L-88KmZM(fB*nl59+7@ literal 0 HcmV?d00001 diff --git a/static/img/samples/library-of-ravens/03.webp b/static/img/samples/library-of-ravens/03.webp new file mode 100644 index 0000000000000000000000000000000000000000..2122e3a367c2dfbdc9e68b0cf8b48dccaf5997ca GIT binary patch literal 36692 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 literal 0 HcmV?d00001 diff --git a/templates/best-practices-samples.mdx b/templates/best-practices-samples.mdx new file mode 100644 index 0000000000..d1736505de --- /dev/null +++ b/templates/best-practices-samples.mdx @@ -0,0 +1,281 @@ +--- +title: "Samples: Best practices" +hide_table_of_contents: false +sidebar_label: Best practices +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + + + + + +Guidelines and recommendations for creating high-quality sample documentation. + +## Content Guidelines + +### Tag Selection + +**Challenges & Solutions Tags:** +- Focus on primary business problems +- Be specific: `semantic-search` not just `search` + +**Feature Tags:** +- Select key RavenDB features +- Prioritize features with code examples +- Include both basic and advanced features + +**Tech Stack Tags:** +- List all major technologies +- Include language, framework, and cloud services +- Order by importance: language first, then frameworks + +**Example:** + +```yaml +challengesSolutionsTags: [semantic-search, integration-patterns] +featureTags: [vector-search, include, document-refresh, azure-storage-queues-etl] +techStackTags: [csharp, aspire, azure-storage-queues, azure-functions] +``` + +### Images + +**Cover Image:** +- Recommended size: 1200x630px +- Format: WebP for best compression +- Show the app in action, not just a logo +- Use high-quality screenshots +- Ensure text is readable + +**Gallery Screenshots:** +- At least 2 images showing key features +- Consistent size and aspect ratio +- Add descriptive alt text +- Show different parts of the app +- Highlight unique features + +**File organization:** + +``` +static/img/samples/{sample-name}/ +├── cover.webp # Main cover image +├── 01.webp # Gallery image 1 +├── 02.webp # Gallery image 2 +└── 03.webp # Gallery image 3 +``` + +## Structure Guidelines + +### Features Section + +Use `FeatureAccordion` components for each major feature: + +**Guidelines:** +- 3-5 accordions per sample +- Order by importance + +**Template:** + +```mdx +## Features used + + + [Detailed explanation of how and why this feature is used] + + Implementation example: + + // Code showing the feature in action + + [Additional context or benefits] + +``` + +### Related Resources + +**Guidelines:** +- Link to 2-3 related resources +- Mix guides and documentation + +**Priority order:** +1. Related guides (how-to articles) +2. Feature documentation (RavenDB docs) +3. Cloud documentation (if applicable) + +## Component Usage + +### ActionsCard + +**Always provide:** +- GitHub repository link (if code is public) +- Demo URL (if available) + +**Don't:** +- Link to private repositories +- Use broken or outdated demo URLs +- Include download links (use GitHub releases instead) + +### FeatureAccordion + +**Do:** +- Use descriptive icons matching the feature +- Keep descriptions to one sentence + +**Don't:** +- Leave children empty (omit the accordion instead) +- Include broken code examples + +### RelatedResource + +**Do:** +- Verify links are correct and current +- Use descriptive subtitles + +**Don't:** +- Link to deprecated documentation +- Use vague subtitles like "Related Article" +- Link to external sites without context +- Duplicate links + +## Tag Management + +### Adding New Tags + +**Before adding a tag:** +1. Check if a similar tag exists +2. Verify it fits the category (challenges-solutions/feature/tech-stack) +3. Ensure it will be used by multiple samples +4. Use consistent naming conventions + +**Naming conventions:** +- Use kebab-case: `azure-storage-queues` not `AzureStorageQueues` +- Be specific: `azure-storage-queues-etl` not `etl` +- Avoid abbreviations: `semantic-search` not `sem-search` +- Use full names: `csharp` not `cs` + +### Tag Maintenance + +**Periodically review:** +- Unused tags (count = 0) +- Duplicate or similar tags +- Outdated technology tags +- Inconsistent naming + +## Common Mistakes + +### ❌ Missing Required Frontmatter + +```yaml +--- +title: "My Sample" +# Missing description, tags +--- +``` + +### ✅ Complete Frontmatter + +```yaml +--- +title: "My Sample" +description: "Complete description" +challengesSolutionsTags: [semantic-search] +featureTags: [vector-search] +techStackTags: [csharp] +--- +``` + +### ❌ Adding H1 Heading + +```mdx +--- +title: "My Sample" +--- + +# My Sample + +## Overview +``` + +### ✅ Start with H2 + +```mdx +--- +title: "My Sample" +--- + +## Overview +``` + +### ❌ Undefined Tags + +```yaml +challengesSolutionsTags: [nonexistent-tag] # Tag doesn't exist in YAML +``` + +### ✅ Valid Tags + +```yaml +challengesSolutionsTags: [semantic-search] # Tag exists in samples/tags/challenges-solutions.yml +``` + +### ❌ Empty FeatureAccordion + +```tsx + + {/* Empty - don't do this */} + +``` + +### ✅ Complete FeatureAccordion + +```tsx + + Detailed explanation with code examples... + +``` + +## Testing Checklist + +Before publishing a sample: + +- [ ] All frontmatter fields are present and valid +- [ ] All tags exist in their respective YAML files +- [ ] Cover image loads correctly +- [ ] Gallery images load and have alt text +- [ ] GitHub repository link works +- [ ] Demo URL works (if provided) +- [ ] All related resource links work +- [ ] Code examples are syntactically correct +- [ ] Sample appears in hub page grid +- [ ] Filtering works with all tags +- [ ] Metadata sidebar displays correctly +- [ ] No console errors or warnings + +## Performance Tips + +**Images:** +- Use WebP format for smaller file sizes +- Optimize images before committing +- Use appropriate dimensions (don't serve 4K images) + +**Code Examples:** +- Keep examples concise +- Use syntax highlighting + +**Content:** +- Keep descriptions brief +- Link to external resources instead of duplicating content + diff --git a/templates/components-samples.mdx b/templates/components-samples.mdx new file mode 100644 index 0000000000..47e8b8719d --- /dev/null +++ b/templates/components-samples.mdx @@ -0,0 +1,296 @@ +--- +title: "Samples: Components" +hide_table_of_contents: false +sidebar_label: Components +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + + + + + +This guide documents all React components used in the Samples system. + +## Import Statement + +All sample components are exported from a single entry point: + +```tsx +import { + // Layout + SampleLayout, + + // Metadata sidebar + SampleMetadataColumn, + ActionsCard, + RelatedResource, + + // Content + FeatureAccordion, + + // Hub (not imported in sample pages) + SamplesHomePage, + SampleCard, + SamplesGrid, + SamplesFilter, +} from '@site/src/components/Samples'; +``` + +## Layout Components + +### SampleLayout + +Two-column responsive layout for sample detail pages. + +**Location:** `src/components/Samples/Overview/SampleLayout.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `children` | `ReactNode` | ✅ | Main content area | +| `details` | `ReactNode` | ✅ | Sidebar content (usually `SampleMetadataColumn`) | + +**Usage:** + +```tsx +export const sampleDetails = ( + +); + + + {/* Main content */} + +``` +**Behavior:** +- Desktop (lg+): Two columns, sidebar on right (300px fixed width) +- Mobile: Stacked, sidebar appears above content + +--- + +## Metadata Sidebar Components + +### SampleMetadataColumn + +Container for the metadata sidebar, displays tags, category, license, and related resources. + +**Location:** `src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `className` | `string` | ❌ | Additional CSS classes | +| `actionsCard` | `ReactNode` | ❌ | Usually an `ActionsCard` component | +| `relatedResources` | `ReactNode` | ❌ | Usually multiple `RelatedResource` components | + +**Usage:** + +```tsx +} + relatedResources={ + <> + + + + } +/> +``` +**Auto-populated from frontmatter:** +- Challenges & Solutions tags (`challengesSolutionsTags`) +- Feature tags (`featureTags`) +- Tech stack tags (`techStackTags`) +- Category (`category`) +- License (`license`) + +--- + +### ActionsCard + +Displays GitHub repository and demo links. + +**Location:** `src/components/Samples/Overview/Partials/ActionsCard.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `className` | `string` | ❌ | Additional CSS classes | +| `githubUser` | `string` | ❌ | GitHub username or organization | +| `githubRepo` | `string` | ❌ | GitHub repository name | +| `demoUrl` | `string` | ❌ | URL to live demo | + +**Usage:** + +```tsx + +``` + +**Rendered buttons:** +- **Browse code** (if `githubUser` and `githubRepo` provided) - Links to `https://github.com/{user}/{repo}` +- **View demo** (if `demoUrl` provided) - Links to demo URL + +--- + +### RelatedResource + +Links to related guides or documentation. + +**Location:** `src/components/Samples/Overview/Partials/RelatedResource.tsx` + +**Props:** + +| Prop | Type | Required | Description | +|---|---|---|---| +| `className` | `string` | ❌ | Additional CSS classes | +| `type` | `"guide"` \| `"documentation"` | ✅ | Resource type | +| `documentationType` | `"docs"` \| `"cloud"` | ❌ | Documentation section (only for `type="documentation"`) | +| `subtitle` | `string` | ✅ | Display text | +| `articleKey` | `string` | ✅ | Path to article (without version prefix) | + +**Usage:** + +```tsx +{/* Link to guide */} + + +{/* Link to docs */} + + +{/* Link to cloud docs */} + +``` + +**Auto-versioning:** +- For `type="documentation"` with `documentationType="docs"`, the current version is automatically prepended +- Example: `ai-integration/vector-search/overview` → `/7.2/ai-integration/vector-search/overview` + +**Icons:** +- `guide` → Guides icon +- `documentation` (docs) → Database icon +- `documentation` (cloud) → Cloud icon + +--- + +## Content Components + +### FeatureAccordion + +Expandable accordion for describing RavenDB features used in the sample. + +**Location:** `src/components/Samples/Overview/Partials/FeatureAccordion.tsx` + +**Props:** + +| Prop | Type | Required | Default | Description | +|---|---|---|---|---| +| `className` | `string` | ❌ | - | Additional CSS classes | +| `title` | `string` | ✅ | - | Feature name | +| `description` | `string` | ✅ | - | Short description (visible when collapsed) | +| `icon` | `IconName` | ❌ | `"link"` | Icon name from icon gallery | +| `children` | `ReactNode` | ❌ | - | Detailed content (visible when expanded) | +| `defaultExpanded` | `boolean` | ❌ | `false` | Initial expanded state | + +**Usage:** + +```tsx + + Detailed explanation of how vector search is used... + + // Code example + +``` + +**Behavior:** +- Click header to expand/collapse +- Smooth animation with Framer Motion +- Chevron icon rotates on expand/collapse + +--- + +## Hub Components + +These components power the `/samples` hub page. They are typically not used in individual sample pages. + +### SamplesHomePage + +Main hub page component with filters and grid. + +**Location:** `src/components/Samples/Hub/SamplesHomePage.tsx` + +**Usage:** + +```mdx +--- +title: Samples +slug: / +--- + +import { SamplesHomePage } from "@site/src/components/Samples"; + + +``` +**Features:** +- URL-based filter state (`?tags=vector-search,include&match=all`) +- Responsive filter sidebar +- Sample grid with cards +- Match logic toggle (Any/All) + +--- + +### SampleCard + +Individual sample card in the grid. + +**Location:** `src/components/Samples/Hub/Partials/SampleCard.tsx` + +**Auto-rendered by `SamplesGrid`** - not typically used directly. + +--- + +### SamplesFilter + +Filter sidebar with tag categories. + +**Location:** `src/components/Samples/Hub/Partials/SamplesFilter.tsx` + +**Auto-rendered by `SamplesHomePage`** - not typically used directly. + + + All components are re-exported from `src/components/Samples/index.ts` for convenient importing. + + diff --git a/templates/filtering-samples.mdx b/templates/filtering-samples.mdx new file mode 100644 index 0000000000..71ec3f70d1 --- /dev/null +++ b/templates/filtering-samples.mdx @@ -0,0 +1,177 @@ +--- +title: "Samples: Filtering" +hide_table_of_contents: false +sidebar_label: Filtering +see_also: + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + + + + + +The Samples hub page features a powerful filtering system with URL-based state, multi-category tag selection, and flexible match logic. + +## Features + +### 1. Multi-Category Filtering + +Tags are organized into three categories: +- **Challenges & Solutions** - Business problems solved +- **Features** - RavenDB features demonstrated +- **Tech Stack** - Technologies used + +Users can select tags from any or all categories simultaneously. + +### 2. Match Logic Toggle + +Two matching modes: +- **Any** (OR logic) - Show samples matching *any* selected tag +- **All** (AND logic) - Show samples matching *all* selected tags + +### 3. URL-Based State + +Filter state is stored in URL query parameters for: +- Shareable filtered views +- Browser back/forward navigation +- Bookmarkable searches + +**Example URLs:** +``` +/samples?tags=vector-search +/samples?tags=vector-search,include&match=all +/samples?tags=csharp,aspire +``` + +### 4. Tag Search + +Filter sidebar includes a search box to quickly find tags by name. + +### 5. Expandable Categories + +Each category can be expanded/collapsed independently. Categories with many tags show only the first 5 by default with a "More" button. + +## User Flows + +### Flow 1: Browse and Filter + +1. User visits `/samples` +2. Sees all samples in grid +3. Clicks "Vector Search" tag in filter sidebar +4. URL updates to `/samples?tags=vector-search` +5. Grid filters to show only samples with vector search +6. User clicks "Include" tag +7. URL updates to `/samples?tags=vector-search,include` +8. Grid shows samples with *either* tag (Any mode) + +### Flow 2: Refine with AND Logic + +1. User has selected `vector-search` and `include` tags +2. Switches match logic from "Any" to "All" +3. URL updates to `/samples?tags=vector-search,include&match=all` +4. Grid shows only samples with *both* tags + +### Flow 3: Quick Filter from Card + +1. User sees a sample card with "C#" language badge +2. Clicks the "C#" badge +3. All filters clear, only "C#" is selected +4. URL updates to `/samples?tags=csharp` +5. Grid shows all C# samples + +### Flow 4: Share Filtered View + +1. User has filtered to `csharp,aspire` with "All" logic +2. Copies URL: `/samples?tags=csharp,aspire&match=all` +3. Shares URL with colleague +4. Colleague opens URL and sees the same filtered view + +## UI Components + +### SamplesFilter + +**Features:** +- Search box for filtering tags by name +- Match logic toggle (Any/All) +- Clear filters button +- Expandable categories +- Tag checkboxes with counts + +**Props:** + +```typescript +interface SamplesFilterProps { + categories: FilterCategoryData[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + matchLogic: "any" | "all"; + onMatchLogicChange: (logic: "any" | "all") => void; + onClearFilters?: () => void; +} +``` + +### FilterCategory + +**Features:** +- Collapsible category header +- Shows first 5 tags by default +- "More" button to expand remaining tags +- Checkbox for each tag with label + +**Props:** + +```typescript +interface FilterCategoryProps { + name: string; + label: string; + tags: FilterTag[]; + selectedTags: Set; + onTagToggle: (tagKey: string) => void; + isExpanded: boolean; + onToggleExpanded: () => void; +} +``` + +### SamplesGrid + +**Features:** +- Displays filtered samples +- Shows sample count badge +- Responsive grid (1 column mobile, 2 columns desktop) +- Staggered animation on load + +**Props:** + +```typescript +interface SamplesGridProps { + samples: Sample[]; + selectedTags: Set; + matchLogic: "any" | "all"; + onTagClick?: (tagKey: string) => void; +} +``` + +## Styling + +### Active Tag State + +Selected tags are highlighted in the filter sidebar: + +```tsx + onTagToggle(tag.key)} + label={tag.label} +/> +``` diff --git a/templates/frames.mdx b/templates/frames.mdx index b1bbadf41f..7724d4f703 100644 --- a/templates/frames.mdx +++ b/templates/frames.mdx @@ -26,15 +26,13 @@ import ContentFrame from '@site/src/components/ContentFrame'; ``` ### Basic usage -````mdx +```mdx Text, lists, images, and even code blocks. - ```ts - console.log('Hello world!') - ``` + // Code example -```` +``` #### Live example @@ -167,41 +165,31 @@ You can combine `Panel` and `ContentFrame`: ### Panels with code and tabs -````mdx +```mdx import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - ```csharp - // sample code - Console.WriteLine("Hello"); - ``` + // Code example 1 - ```csharp - await Console.Out.WriteLineAsync("Hello"); - ``` + // Code example 2 -```` +``` #### Live example - ```csharp - // sample code - Console.WriteLine("Hello"); - ``` + // Code example 1 - ```csharp - await Console.Out.WriteLineAsync("Hello"); - ``` + // Code example 2 diff --git a/templates/gallery-example.mdx b/templates/gallery-example.mdx new file mode 100644 index 0000000000..fb362c7751 --- /dev/null +++ b/templates/gallery-example.mdx @@ -0,0 +1,97 @@ +--- +title: "Gallery Component Example" +description: "Example of using the Gallery component with multiple images" +gallery: + - src: "/img/samples/library-of-ravens/01.webp" + alt: "First example image" + - src: "/img/samples/library-of-ravens/02.webp" + alt: "Second example image" + - src: "/img/samples/library-of-ravens/03.webp" + alt: "Third example image" + - src: "/img/discord.webp" + alt: "Fourth example image" + - src: "/img/webinar.webp" + alt: "Fifth example image" +--- + +import Gallery from '@site/src/components/Common/Gallery'; + +# Gallery Component Example + +This page demonstrates how to use the Gallery component with images defined in frontmatter. + +## Basic Usage + +The Gallery component provides responsive layouts optimized for each device: + +### Desktop (≥ lg breakpoint) +- One large image on the left (taking ~2/3 of the width) +- Two smaller images stacked on the right (taking ~1/3 of the width) +- If there are more than 3 images, a "+x" overlay appears on the third image + +### Mobile (< lg breakpoint) +- Single-image carousel with swipe navigation +- All images accessible via horizontal scroll +- Dot indicators showing current position +- Tap dots to jump to specific images +- Images display in their natural aspect ratio + +### Example Gallery + + + +## How to Use + +### 1. Define images in frontmatter + +```yaml +--- +title: "Your Page Title" +gallery: + - src: "/img/path/to/image1.png" + alt: "Description of first image" + - src: "/img/path/to/image2.png" + alt: "Description of second image" + - src: "/img/path/to/image3.png" + alt: "Description of third image" + - src: "/img/path/to/image4.png" + alt: "Description of fourth image" +--- +``` + +### 2. Import and use the Gallery component + +#### Standalone Usage +```mdx +import Gallery from '@site/src/components/Common/Gallery'; + + +``` + +#### With SampleLayout +```mdx +import { SampleLayout } from '@site/src/components/Samples'; +import Gallery from '@site/src/components/Common/Gallery'; + +} +> + ## Overview + ... + +``` + +When using the `gallery` prop with `SampleLayout`: +- **Mobile**: Gallery appears at the top, right after the title and above the ActionsCard +- **Desktop**: Gallery appears in the main content area before the Overview section + + +## Customization + +You can pass a custom className to adjust the gallery styling: + +```mdx + +``` + diff --git a/templates/introduction-samples.mdx b/templates/introduction-samples.mdx new file mode 100644 index 0000000000..93ddb4c819 --- /dev/null +++ b/templates/introduction-samples.mdx @@ -0,0 +1,116 @@ +--- +title: "Samples: Introduction" +hide_table_of_contents: false +sidebar_label: Introduction +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + + + + + +The Samples system provides a comprehensive platform for showcasing production-ready code samples, architecture patterns, and starter kits. It features a filterable hub page, detailed sample pages with metadata, and a plugin-based architecture for automatic indexing. + +## Architecture Overview + +The Samples system consists of three main parts: + +### 1. Samples Hub (`/samples`) + +A filterable, searchable landing page that displays all available samples with: +- **Filter sidebar** with three tag categories (Challenges & Solutions, Features, Tech Stack) +- **Sample cards** showing title, description, image, and tags +- **Match logic toggle** (Any/All) for flexible filtering +- **URL-based state** for shareable filtered views + +### 2. Sample Detail Pages + +Individual sample pages with a two-column layout: +- **Main content area** for documentation, code examples, and feature descriptions +- **Metadata sidebar** with actions, tags, and related resources + +### 3. Plugin System + +The `recent-samples-plugin` automatically: +- Scans the `samples/` directory for `.mdx` files +- Loads tag definitions from `samples/tags/` (organized by category) +- Indexes samples with their metadata and tags +- Exposes data via Docusaurus global plugin data + +## Key Components + +| Component | Purpose | Location | +|---|---|---| +| `SamplesHomePage` | Main hub page with filters and grid | `src/components/Samples/Hub/SamplesHomePage.tsx` | +| `SampleCard` | Individual sample card in the grid | `src/components/Samples/Hub/Partials/SampleCard.tsx` | +| `SamplesFilter` | Filter sidebar with categories | `src/components/Samples/Hub/Partials/SamplesFilter.tsx` | +| `SampleLayout` | Two-column layout for detail pages | `src/components/Samples/Overview/SampleLayout.tsx` | +| `SampleMetadataColumn` | Sidebar with tags and metadata | `src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx` | +| `ActionsCard` | GitHub and demo links | `src/components/Samples/Overview/Partials/ActionsCard.tsx` | +| `RelatedResource` | Links to guides/docs | `src/components/Samples/Overview/Partials/RelatedResource.tsx` | +| `FeatureAccordion` | Expandable feature descriptions | `src/components/Samples/Overview/Partials/FeatureAccordion.tsx` | + +## Tag System + +Samples use a **three-category tag system**: + +1. **Challenges & Solutions** (`samples/tags/challenges-solutions.yml`) - What business problems the sample solves + - Examples: `cloud-tax`, `semantic-search`, `integration-patterns` + +2. **Features** (`samples/tags/feature.yml`) - Which RavenDB features are demonstrated + - Examples: `vector-search`, `document-refresh`, `include`, `ai-agents` + +3. **Tech Stack** (`samples/tags/tech-stack.yml`) - Technologies and frameworks used + - Examples: `csharp`, `nodejs`, `aspire`, `azure-functions` + + + Unlike Guides which use a single `tags.yml` file, Samples organize tags into separate category files. This enables the filter UI to group tags logically and allows for "Any/All" matching within categories. + + +## File Structure + +``` +samples/ +├── home.mdx # Hub page (uses SamplesHomePage component) +├── the-ravens-library.mdx # Example sample +├── tags/ +│ ├── challenges-solutions.yml # Challenges & solutions tags +│ ├── feature.yml # RavenDB feature tags +│ └── tech-stack.yml # Technology stack tags +└── ... # Additional sample files + +src/components/Samples/ +├── Hub/ # Hub page components +│ ├── SamplesHomePage.tsx +│ └── Partials/ +│ ├── SampleCard.tsx +│ ├── SamplesGrid.tsx +│ ├── SamplesFilter.tsx +│ ├── FilterCategory.tsx +│ ├── SamplesHeader.tsx +│ └── SamplesDecoration.tsx +├── Overview/ # Detail page components +│ ├── SampleLayout.tsx +│ └── Partials/ +│ ├── SampleMetadataColumn.tsx +│ ├── ActionsCard.tsx +│ ├── RelatedResource.tsx +│ └── FeatureAccordion.tsx +├── index.ts # Component exports +└── types.ts # TypeScript types + +src/plugins/ +└── recent-samples-plugin.ts # Indexing plugin +``` + diff --git a/templates/new-samples.mdx b/templates/new-samples.mdx new file mode 100644 index 0000000000..dc91b4787d --- /dev/null +++ b/templates/new-samples.mdx @@ -0,0 +1,198 @@ +--- +title: "Samples: Adding new samples" +hide_table_of_contents: false +sidebar_label: Adding new samples +see_also: + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample tags" + link: "/templates/tags-samples" + source: "docs" + path: "Templates > Samples" + - title: "Sample components" + link: "/templates/components-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; +import Link from "@docusaurus/Link"; + + + + + +This guide explains how to create new sample pages in the Samples section. + +## Create the file + +Place the file under the `samples` directory: + +``` +samples/my-new-sample.mdx +``` + +## Frontmatter Schema + + + +```mdx +--- +title: "My Sample Application" +description: "A production-ready example demonstrating RavenDB features." +challengesSolutionsTags: [semantic-search, integration-patterns] +featureTags: [vector-search, include, document-refresh] +techStackTags: [csharp, aspire, azure-functions] +category: "E-commerce" +license: "MIT License" +image: "/img/samples/my-sample/cover.webp" +gallery: + - src: "/img/samples/my-sample/screenshot-1.webp" + alt: "Main interface" + - src: "/img/samples/my-sample/screenshot-2.webp" + alt: "Admin dashboard" +--- +``` + + + +### Frontmatter Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| `title` | `string` | ✅ | Sample name displayed in cards and page header | +| `description` | `string` | ✅ | Short summary shown in sample cards | +| `challengesSolutionsTags` | `string[]` | ✅ | Challenges & solutions tags from `samples/tags/challenges-solutions.yml` | +| `featureTags` | `string[]` | ✅ | RavenDB feature tags from `samples/tags/feature.yml` | +| `techStackTags` | `string[]` | ✅ | Technology tags from `samples/tags/tech-stack.yml` | +| `category` | `string` | ❌ | Business category (e.g., "E-commerce", "Healthcare") | +| `license` | `string` | ❌ | License type (e.g., "MIT License", "Apache 2.0") | +| `image` | `string` or `object` | ❌ | Cover image path or themed image object | +| `gallery` | `array` | ❌ | Array of screenshot objects with `src` and `alt` | + + + All tag keys must exist in their respective YAML files (`samples/tags/challenges-solutions.yml`, `feature.yml`, `tech-stack.yml`). Using undefined tags will cause them to be ignored in the UI. + + +## Page Structure + +### Basic Layout + + + +```mdx +--- +title: "My Sample" +description: "Sample description" +challengesSolutionsTags: [semantic-search] +featureTags: [vector-search] +techStackTags: [csharp] +--- + +import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples'; +import Gallery from '@site/src/components/Common/Gallery'; + +export const sampleDetails = ( + + } + relatedResources={ + <> + + + + } + /> +); + +} +> + +## Overview + +Describe what the sample does and what problems it solves... + +## Features used + + + Detailed explanation of how vector search is used in this sample... + + // Code example + + + +``` + + + + + The `gallery` prop on `SampleLayout` provides optimal responsive behavior: + + - **Mobile**: Gallery appears at the top, right after the page title and above the ActionsCard + - **Desktop**: Gallery appears in the main content area, before the Overview section + + **Usage:** + ```mdx + } + > + ## Overview + ... + + ``` + + The `gallery` prop is **optional**. If your sample doesn't have screenshots, simply omit it. + + + + **Do NOT add a `#` heading in your markdown content.** + + The `title` frontmatter is automatically rendered as the page heading. Adding a `#` heading breaks the layout. + + ✅ Correct: + ```mdx + --- + title: "My Sample" + --- + + ## Overview + + Content starts here... + ``` + + ❌ Wrong: + ```mdx + --- + title: "My Sample" + --- + + # My Sample + + ## Overview + ``` + + diff --git a/templates/tags-samples.mdx b/templates/tags-samples.mdx new file mode 100644 index 0000000000..f4b6192fef --- /dev/null +++ b/templates/tags-samples.mdx @@ -0,0 +1,180 @@ +--- +title: "Samples: Tags" +hide_table_of_contents: false +sidebar_label: Tags +see_also: + - title: "Adding new samples" + link: "/templates/new-samples" + source: "docs" + path: "Templates > Samples" + - title: "Samples Introduction" + link: "/templates/introduction-samples" + source: "docs" + path: "Templates > Samples" +--- + +import Admonition from "@theme/Admonition"; +import Panel from "@site/src/components/Panel"; + + + + + +Samples use a **three-category tag system** to enable powerful filtering and discovery. Unlike Guides which use a single flat tag list, Samples organize tags into separate category files. + +## Tag Categories + +### 1. Challenges & Solutions Tags (`samples/tags/challenges-solutions.yml`) + +These tags describe **what business problems** the sample solves. + +**Location:** `samples/tags/challenges-solutions.yml` + +**Some of the existing tags:** +- `cloud-tax` +- `gen-ai-data-enrichment` +- `integration-patterns` +- `semantic-search` + +**Example usage in frontmatter:** +```yaml +challengesSolutionsTags: [semantic-search, integration-patterns] +``` + +### 2. Feature Tags (`samples/tags/feature.yml`) + +These tags indicate **which RavenDB features** are demonstrated. + +**Location:** `samples/tags/feature.yml` + +**Some of the existing tags:** +- `vector-search` +- `document-refresh` +- `include` +- `azure-storage-queues-etl` +- `ai-agents` +- `full-text-search` + +**Example usage in frontmatter:** +```yaml +featureTags: [vector-search, include, document-refresh] +``` + +### 3. Tech Stack Tags (`samples/tags/tech-stack.yml`) + +These tags specify **technologies and frameworks** used in the sample. + +**Location:** `samples/tags/tech-stack.yml` + +**Some of the existing tags:** +- `csharp` +- `nodejs` +- `python` +- `java` +- `php` +- `aspire` +- `azure-storage-queues` +- `azure-functions` + +**Example usage in frontmatter:** +```yaml +techStackTags: [csharp, aspire, azure-functions] +``` + +## Adding New Tags + +### Step 1: Choose the correct category + +Determine which category your tag belongs to: +- **Challenges & Solutions** - Business problem or use case +- **Feature** - RavenDB feature or capability +- **Tech Stack** - Programming language, framework, or service + +### Step 2: Edit the appropriate YAML file + + + +Edit `samples/tags/challenges-solutions.yml`: + +```yaml +cloud-tax: + label: "Cloud Tax" +gen-ai-data-enrichment: + label: "Gen AI Data Enrichment" +# Add your new tag: +real-time-analytics: + label: "Real-Time Analytics" +``` + + + + + +Edit `samples/tags/feature.yml`: + +```yaml +vector-search: + label: "Vector Search" +document-refresh: + label: "Document Refresh" +# Add your new tag: +time-series: + label: "Time Series" +``` + + + + + +Edit `samples/tags/tech-stack.yml`: + +```yaml +csharp: + label: "C#" +nodejs: + label: "Node.js" +# Add your new tag: +nextjs: + label: "Next.js" +``` + + + +### Step 3: Use the tag in a sample + +Reference the tag key in your sample's frontmatter: + +```mdx +--- +title: "My Sample" +challengesSolutionsTags: [real-time-analytics] +featureTags: [time-series] +techStackTags: [nextjs] +--- +``` + + + Each tag entry requires only a `label` field. The key (e.g., `cloud-tax`) is used in frontmatter, while the label (e.g., "Cloud Tax") is displayed in the UI. + + +## Tag Guidelines + + + - **Be specific**: Use `azure-storage-queues-etl` instead of just `etl` + - **Use kebab-case**: `gen-ai-data-enrichment` not `GenAIDataEnrichment` + - **Keep labels concise**: "Vector Search" not "Vector Search Functionality" + - **Avoid duplicates**: Check existing tags before adding new ones + - **Stay consistent**: Follow naming patterns of existing tags + + + + The following tech stack tags are treated specially as "language tags" and displayed with colored badges: + - `csharp` + - `java` + - `python` + - `php` + - `nodejs` + + These should only be used for the primary programming language of the sample. + + diff --git a/templates/themed-images.mdx b/templates/themed-images.mdx index 407d00bcbb..b18aed4993 100644 --- a/templates/themed-images.mdx +++ b/templates/themed-images.mdx @@ -30,7 +30,7 @@ import ThemedImage from '@theme/ThemedImage'; ### Basic usage -````mdx +```mdx import ThemedImage from '@theme/ThemedImage'; import lightImage from '@site/static/img/templates/light-image.png'; import darkImage from '@site/static/img/templates/dark-image.png'; @@ -42,7 +42,7 @@ import darkImage from '@site/static/img/templates/dark-image.png'; dark: darkImage, }} /> -```` +``` #### Live example From 06a23e0f9c70f2dbdede5746c38e37eebda4bda7 Mon Sep 17 00:00:00 2001 From: Mateusz Bartosik Date: Mon, 30 Mar 2026 16:36:33 +0200 Subject: [PATCH 2/6] RDoc-3729 Review fixes --- cloud/home.mdx | 2 +- docs/home.mdx | 2 +- guides/home.mdx | 2 +- package-lock.json | 47 ++++---- package.json | 3 +- samples/home.mdx | 2 +- samples/the-ravens-library.mdx | 2 +- src/components/Common/CardWithImage.tsx | 2 +- .../Common/CardWithImageHorizontal.tsx | 2 +- src/components/Common/Checkbox.tsx | 4 +- src/components/Common/Drawer.tsx | 15 +-- src/components/Common/LazyImage.tsx | 4 + .../Samples/Hub/Partials/FilterCategory.tsx | 16 ++- .../Samples/Hub/Partials/SampleCard.tsx | 2 +- .../Hub/Partials/SamplesDecoration.tsx | 18 +-- .../Samples/Hub/Partials/SamplesFilter.tsx | 19 +++- .../Samples/Hub/SamplesHomePage.tsx | 24 ++-- .../Overview/Partials/FeatureAccordion.tsx | 2 +- .../Partials/SampleMetadataColumn.tsx | 11 +- src/components/Samples/types.ts | 2 +- src/css/custom.css | 14 ++- src/plugins/recent-guides-plugin.ts | 2 +- src/plugins/recent-samples-plugin.ts | 8 +- src/theme/DocItem/BannerImage/index.tsx | 2 +- src/theme/DocItem/index.tsx | 10 ++ src/typescript/docMetadata.d.ts | 7 +- templates/authors.mdx | 4 - templates/best-practices-samples.mdx | 4 - templates/components-samples.mdx | 4 - templates/featured-guides.mdx | 4 - templates/filtering-samples.mdx | 4 - templates/frames.mdx | 4 - templates/home.mdx | 4 - templates/icon-gallery.mdx | 4 - templates/introduction-samples.mdx | 4 - templates/introduction.mdx | 4 - templates/new-guides.mdx | 4 - templates/new-samples.mdx | 6 +- templates/see-also.mdx | 4 - templates/tags-samples.mdx | 4 - templates/tags.mdx | 4 - templates/themed-images.mdx | 105 ------------------ versioned_docs/version-1.0/home.mdx | 2 +- versioned_docs/version-2.0/home.mdx | 2 +- versioned_docs/version-2.5/home.mdx | 2 +- versioned_docs/version-3.0/home.mdx | 2 +- versioned_docs/version-3.5/home.mdx | 2 +- versioned_docs/version-4.0/home.mdx | 2 +- versioned_docs/version-4.1/home.mdx | 2 +- versioned_docs/version-4.2/home.mdx | 2 +- versioned_docs/version-5.0/home.mdx | 2 +- versioned_docs/version-5.1/home.mdx | 2 +- versioned_docs/version-5.2/home.mdx | 2 +- versioned_docs/version-5.3/home.mdx | 2 +- versioned_docs/version-5.4/home.mdx | 2 +- versioned_docs/version-6.0/home.mdx | 2 +- versioned_docs/version-6.2/home.mdx | 2 +- versioned_docs/version-7.0/home.mdx | 2 +- versioned_docs/version-7.1/home.mdx | 2 +- 59 files changed, 157 insertions(+), 268 deletions(-) diff --git a/cloud/home.mdx b/cloud/home.mdx index 0f3454e3ed..3dc1b64666 100644 --- a/cloud/home.mdx +++ b/cloud/home.mdx @@ -2,7 +2,7 @@ slug: / pagination_next: null pagination_prev: null -wrapperClassName: cloud-home-page +wrapperClassName: cloudHomePage hide_table_of_contents: true --- diff --git a/docs/home.mdx b/docs/home.mdx index 09a567c382..70c13740be 100644 --- a/docs/home.mdx +++ b/docs/home.mdx @@ -3,7 +3,7 @@ slug: / title: "Guides, API Reference & Tutorials" pagination_next: null pagination_prev: null -wrapperClassName: docs-home-page +wrapperClassName: docsHomePage hide_table_of_contents: true description: "Official RavenDB documentation. Guides for installation, client APIs, indexing, querying, AI integration, clustering, security, and the Studio UI across all supported versions." keywords: diff --git a/guides/home.mdx b/guides/home.mdx index f7c0941f64..2dbe87d3cb 100644 --- a/guides/home.mdx +++ b/guides/home.mdx @@ -4,7 +4,7 @@ hide_title: true slug: / pagination_next: null pagination_prev: null -wrapperClassName: guides-home-page +wrapperClassName: guidesHomePage hide_table_of_contents: true --- diff --git a/package-lock.json b/package-lock.json index e1abda799a..49a0bc52e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,11 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", - "framer-motion": "^12.38.0", + "motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-github-btn": "^1.4.0", "yet-another-react-lightbox": "^3.24.0" }, "devDependencies": { @@ -11128,12 +11127,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-buttons": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/github-buttons/-/github-buttons-2.32.0.tgz", - "integrity": "sha512-DbKam2n7JGbccpbAoQ7UHhxH2XYlumCakTM4Ln7v8A9TzMaaEgKV6S7A+Suyx3EVBE1DPEqA6QSgyCoGJamymg==", - "license": "BSD-2-Clause" - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -15789,6 +15782,32 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.38.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/motion-dom": { "version": "12.38.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", @@ -18406,18 +18425,6 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, - "node_modules/react-github-btn": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-github-btn/-/react-github-btn-1.4.0.tgz", - "integrity": "sha512-lV4FYClAfjWnBfv0iNlJUGhamDgIq6TayD0kPZED6VzHWdpcHmPfsYOZ/CFwLfPv4Zp+F4m8QKTj0oy2HjiGXg==", - "license": "BSD-2-Clause", - "dependencies": { - "github-buttons": "^2.22.0" - }, - "peerDependencies": { - "react": ">=16.3.0" - } - }, "node_modules/react-helmet-async": { "name": "@slorber/react-helmet-async", "version": "1.3.0", diff --git a/package.json b/package.json index 423afef8f7..9d7ef40ce4 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,11 @@ "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.21", "clsx": "^2.1.1", - "framer-motion": "^12.38.0", + "motion": "^12.38.0", "prettier": "^3.6.2", "prism-react-renderer": "^2.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-github-btn": "^1.4.0", "yet-another-react-lightbox": "^3.24.0" }, "devDependencies": { diff --git a/samples/home.mdx b/samples/home.mdx index e4a8af6fbc..7c02632340 100644 --- a/samples/home.mdx +++ b/samples/home.mdx @@ -4,7 +4,7 @@ hide_title: true slug: / pagination_next: null pagination_prev: null -wrapperClassName: samples-home-page +wrapperClassName: samplesHomePage hide_table_of_contents: true --- diff --git a/samples/the-ravens-library.mdx b/samples/the-ravens-library.mdx index 1a9bec6b7d..a5c4c81a0f 100644 --- a/samples/the-ravens-library.mdx +++ b/samples/the-ravens-library.mdx @@ -55,7 +55,7 @@ The app demonstrates **robust integration patterns using Azure Storage Queues**. Finally, the app **addresses the "cloud tax" of high egress fees** by utilizing ETags and native HTTP caching driven by RavenDB’s metadata. This approach significantly reduces the volume of data sent over the wire, slashing public cloud billing while providing a snappier, more responsive experience for the end user through efficient data reuse. -Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azure Storage Queues](https://github.com/ravendb/samples-library/blob/main/azure.microsoft.com/en-us/products/storage/queues), and [Azure Functions](https://azure.microsoft.com/en-us/products/functions). +Built with [RavenDB](https://ravendb.net/), [Aspire](https://aspire.dev/), [Azure Storage Queues](https://azure.microsoft.com/en-us/products/storage/queues), and [Azure Functions](https://azure.microsoft.com/en-us/products/functions). ## Features used diff --git a/src/components/Common/CardWithImage.tsx b/src/components/Common/CardWithImage.tsx index 79886c4da3..0d59eda791 100644 --- a/src/components/Common/CardWithImage.tsx +++ b/src/components/Common/CardWithImage.tsx @@ -13,7 +13,7 @@ import Tag from "@site/src/theme/Tag"; export interface CardWithImageProps { title: string; description: ReactNode; - imgSrc?: string | { light: string; dark: string }; + imgSrc?: string; imgAlt?: string; imgWidth?: number; imgHeight?: number; diff --git a/src/components/Common/CardWithImageHorizontal.tsx b/src/components/Common/CardWithImageHorizontal.tsx index 3e4a52817e..3d24dd6521 100644 --- a/src/components/Common/CardWithImageHorizontal.tsx +++ b/src/components/Common/CardWithImageHorizontal.tsx @@ -10,7 +10,7 @@ import { clsx } from "clsx"; export interface CardWithImageHorizontalProps { title: string; description: ReactNode; - imgSrc: string | { light: string; dark: string }; + imgSrc: string; imgAlt?: string; url: string; iconName?: IconName; diff --git a/src/components/Common/Checkbox.tsx b/src/components/Common/Checkbox.tsx index d9962ef251..7f1e3a59a8 100644 --- a/src/components/Common/Checkbox.tsx +++ b/src/components/Common/Checkbox.tsx @@ -1,10 +1,10 @@ -import React from "react"; +import React, { ChangeEvent } from "react"; import clsx from "clsx"; import { Icon } from "./Icon"; interface CheckboxProps { checked: boolean; - onChange: () => void; + onChange: (x: ChangeEvent) => void; label?: string; className?: string; } diff --git a/src/components/Common/Drawer.tsx b/src/components/Common/Drawer.tsx index c35578e715..925ab67b8c 100644 --- a/src/components/Common/Drawer.tsx +++ b/src/components/Common/Drawer.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import { motion, AnimatePresence, PanInfo } from "framer-motion"; +import { motion, AnimatePresence, type PanInfo } from "motion/react"; import clsx from "clsx"; import { Icon } from "./Icon"; @@ -12,18 +12,6 @@ interface DrawerProps { } export default function Drawer({ open, onClose, children, title, headerAction }: DrawerProps) { - useEffect(() => { - if (open) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = ""; - } - - return () => { - document.body.style.overflow = ""; - }; - }, [open]); - useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && open) { @@ -49,6 +37,7 @@ export default function Drawer({ open, onClose, children, title, headerAction }: <> { + if (!isTagsExpanded && tags.length > 5) { + const hiddenTags = tags.slice(5); + const hasSelectedHiddenTag = hiddenTags.some((tag) => selectedTags.has(tag.key)); + if (hasSelectedHiddenTag) { + expandTags(); + } + } + }, [selectedTags, tags, isTagsExpanded, expandTags]); + return (

    - - {isExpanded && children && ( - -
    + {children && ( +
    +
    +
    {children}
    - - )} - +
    +
    + )}
    ); } diff --git a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx index 7093a954cd..caf470336a 100644 --- a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx +++ b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx @@ -88,11 +88,12 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe }; }, [pluginData]); - const challengesSolutionsTagKeys = frontMatter.challengesSolutionsTags; - const featureTagKeys = frontMatter.featureTags; - const techStackTagKeys = frontMatter.techStackTags; + const challengesSolutionsTagKeys = frontMatter.challenges_solutions_tags; + const featureTagKeys = frontMatter.feature_tags; + const techStackTagKeys = frontMatter.tech_stack_tags; const category = frontMatter.category; const license = frontMatter.license; + const licenseUrl = frontMatter.license_url; const challengesSolutionsTags = getTagsWithLabels(challengesSolutionsTagKeys, challengesSolutionsTagsData); const featureTags = getTagsWithLabels(featureTagKeys, featureTagsData); @@ -120,7 +121,19 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe License -

    {license}

    +

    + {licenseUrl ? ( + + {license} + + ) : ( + license + )} +

    )} diff --git a/src/components/Samples/types.ts b/src/components/Samples/types.ts index 275acbeb52..a49af9fc8b 100644 --- a/src/components/Samples/types.ts +++ b/src/components/Samples/types.ts @@ -11,6 +11,7 @@ export interface Sample { description?: string; permalink: string; image?: string; + img_alt?: string; tags: Array<{ label: string; key: string; category?: string }>; } diff --git a/src/css/custom.css b/src/css/custom.css index 3a274dedff..2accb7f573 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -21,27 +21,6 @@ body:has([data-drawer-open="true"]) { } /* Custom animations for layout switcher */ -@keyframes slide-in-from-bottom { - from { - opacity: 0; - transform: translateY(16px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slide-in-from-left { - from { - opacity: 0; - transform: translateX(-16px); - } - to { - opacity: 1; - transform: translateX(0); - } -} @keyframes fade-in { from { @@ -58,14 +37,7 @@ body:has([data-drawer-open="true"]) { .fade-in { animation-name: fade-in; -} - -.slide-in-from-bottom-4 { - animation-name: slide-in-from-bottom; -} - -.slide-in-from-left-4 { - animation-name: slide-in-from-left; + animation-duration: 400ms; } /* Custom selection */ diff --git a/src/pages/guides/all.tsx b/src/pages/guides/all.tsx index 58af25a19b..126aa9da58 100644 --- a/src/pages/guides/all.tsx +++ b/src/pages/guides/all.tsx @@ -72,7 +72,7 @@ function AllGuidesPageContent(): ReactNode { "animate-in fade-in" )} > - {sortedGuides.map((guide, index) => { + {sortedGuides.map((guide) => { const formattedDate = guide.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { month: "short", @@ -85,19 +85,18 @@ function AllGuidesPageContent(): ReactNode { key={guide.id} title={guide.title} description={guide.description} - url={guide.externalUrl || guide.permalink} + url={guide.external_url || guide.permalink} imgSrc={guide.image} imgIcon={guide.icon} tags={guide.tags} date={formattedDate} - animationDelay={index * 50} /> ); })}
) : (
- {sortedGuides.map((guide, index) => { + {sortedGuides.map((guide) => { const formattedDate = guide.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { month: "short", @@ -106,18 +105,10 @@ function AllGuidesPageContent(): ReactNode { }) : undefined; return ( -
+
diff --git a/src/plugins/recent-guides-plugin.ts b/src/plugins/recent-guides-plugin.ts index a4c7c4d9f2..a6d520254c 100644 --- a/src/plugins/recent-guides-plugin.ts +++ b/src/plugins/recent-guides-plugin.ts @@ -13,8 +13,9 @@ export interface Guide { lastUpdatedAt: number; description?: string; image?: string; + img_alt?: string; icon?: IconName; - externalUrl?: string; + external_url?: string; } export interface PluginData { @@ -57,7 +58,6 @@ export default function recentGuidesPlugin(context, _options): Plugin { const fileContent = fs.readFileSync(tagsYmlPath, "utf8"); predefinedTags = (yaml.load(fileContent) as any) || {}; } catch (e) { - // eslint-disable-next-line no-console console.error("Failed to load tags.yml", e); } } @@ -85,9 +85,9 @@ export default function recentGuidesPlugin(context, _options): Plugin { const slug = baseName.endsWith("/index") ? baseName.replace(/\/index$/, "") : baseName; const permalink = `/guides/${slug === "index" ? "" : slug}`; - const externalUrl: string | undefined = (data as any).externalUrl || (data as any).external_url; + const externalUrl: string | undefined = (data as any).external_url; - const frontmatterDate: unknown = (data as any).publishedAt; + const frontmatterDate: unknown = (data as any).published_at; let lastUpdatedAt: number; if (frontmatterDate) { @@ -133,14 +133,15 @@ export default function recentGuidesPlugin(context, _options): Plugin { return { id: path.basename(filePath, path.extname(filePath)), - title: data.title || path.basename(filePath, path.extname(filePath)), + title: data.title, permalink: data.slug || permalink, tags: formattedTags, lastUpdatedAt, description: data.description, image: data.image, + img_alt: data.img_alt, icon: data.icon, - externalUrl, + external_url: externalUrl, }; }); diff --git a/src/plugins/recent-samples-plugin.ts b/src/plugins/recent-samples-plugin.ts index 2cb68087bb..7f9d06a2f4 100644 --- a/src/plugins/recent-samples-plugin.ts +++ b/src/plugins/recent-samples-plugin.ts @@ -2,7 +2,6 @@ import type { Plugin } from "@docusaurus/types"; import path from "path"; import fs from "fs"; import matter from "gray-matter"; -import type { IconName } from "../typescript/iconName"; const yaml = require("js-yaml"); interface TagDefinition { @@ -21,11 +20,9 @@ export interface Sample { title: string; permalink: string; tags: { label: string; key: string; category: string }[]; - lastUpdatedAt: number; description?: string; image?: string; - icon?: IconName; - externalUrl?: string; + imgAlt?: string; } export interface PluginData { @@ -73,7 +70,6 @@ export default function recentSamplesPlugin(context, _options): Plugin { const fileContent = fs.readFileSync(filePath, "utf8"); tagsByCategory[category] = (yaml.load(fileContent) as any) || {}; } catch (e) { - // eslint-disable-next-line no-console console.error(`Failed to load tags/${file}`, e); } } @@ -92,7 +88,6 @@ export default function recentSamplesPlugin(context, _options): Plugin { const samples = files.map((filePath) => { const fileContent = fs.readFileSync(filePath, "utf-8"); const { data } = matter(fileContent); - const stats = fs.statSync(filePath); const relativePath = path.relative(samplesDir, filePath); const relativePathNormalized = relativePath.split(path.sep).join("/"); @@ -101,31 +96,9 @@ export default function recentSamplesPlugin(context, _options): Plugin { const slug = baseName.endsWith("/index") ? baseName.replace(/\/index$/, "") : baseName; const permalink = `/samples/${slug === "index" ? "" : slug}`; - const externalUrl: string | undefined = (data as any).externalUrl || (data as any).external_url; - const frontmatterDate: unknown = (data as any).publishedAt; - - let lastUpdatedAt: number; - if (frontmatterDate) { - let millis: number | null = null; - if (typeof frontmatterDate === "string") { - const parsed = Date.parse(frontmatterDate); - if (!Number.isNaN(parsed)) { - millis = parsed; - } - } else if (frontmatterDate instanceof Date) { - millis = frontmatterDate.getTime(); - } else if (typeof frontmatterDate === "number") { - millis = frontmatterDate > 1e12 ? frontmatterDate : frontmatterDate * 1000; - } - - lastUpdatedAt = millis ? Math.floor(millis / 1000) : Math.floor(stats.mtimeMs / 1000); - } else { - lastUpdatedAt = Math.floor(stats.mtimeMs / 1000); - } - const allTagsArray: Array<{ key: string; category: string }> = []; - const challengesSolutionsTags = data.challengesSolutionsTags; + const challengesSolutionsTags = data.challenges_solutions_tags; if (Array.isArray(challengesSolutionsTags)) { challengesSolutionsTags.forEach((tag: string) => { @@ -133,14 +106,14 @@ export default function recentSamplesPlugin(context, _options): Plugin { }); } - const featureTags = data.featureTags; + const featureTags = data.feature_tags; if (Array.isArray(featureTags)) { featureTags.forEach((tag: string) => { allTagsArray.push({ key: tag, category: "feature" }); }); } - const techStackTags = data.techStackTags; + const techStackTags = data.tech_stack_tags; if (Array.isArray(techStackTags)) { techStackTags.forEach((tag: string) => { allTagsArray.push({ key: tag, category: "tech-stack" }); @@ -164,14 +137,12 @@ export default function recentSamplesPlugin(context, _options): Plugin { return { id: path.basename(filePath, path.extname(filePath)), - title: data.title || path.basename(filePath, path.extname(filePath)), + title: data.title, permalink: data.slug || permalink, tags: formattedTags, - lastUpdatedAt, description: data.description, image: data.image, - icon: data.icon, - externalUrl, + img_alt: data.img_alt, }; }); @@ -194,7 +165,7 @@ export default function recentSamplesPlugin(context, _options): Plugin { }); return { - samples: samples.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt), + samples: samples, tags: allTags, }; }, diff --git a/src/theme/DocItem/Authors/index.tsx b/src/theme/DocItem/Authors/index.tsx index 1bba0891f8..19ded05074 100644 --- a/src/theme/DocItem/Authors/index.tsx +++ b/src/theme/DocItem/Authors/index.tsx @@ -18,7 +18,6 @@ type Author = { function getAuthorData(authorKey: string): Author | null { const authorInfo = authorsData[authorKey]; if (!authorInfo) { - // eslint-disable-next-line no-console console.warn(`No author data found for key '${authorKey}' in authors.json`); return null; } diff --git a/src/theme/DocItem/Metadata/DocPageMetadata.tsx b/src/theme/DocItem/Metadata/DocPageMetadata.tsx index 0d9a4c0d65..9dda519921 100644 --- a/src/theme/DocItem/Metadata/DocPageMetadata.tsx +++ b/src/theme/DocItem/Metadata/DocPageMetadata.tsx @@ -17,11 +17,16 @@ export interface DocPageMetadataProps { ogImageUrl: string; // Shared (optional) lastUpdatedAt?: number; + keywords?: string[]; // Guide-only (optional) proficiencyLevel?: string; authorKey?: string; publishedAt?: string; - keywords?: string[]; + // Sample-only (optional) + schemaType?: "TechArticle" | "SoftwareSourceCode"; + repositoryUrl?: string; + licenseUrl?: string; + languages?: string[]; } export default function DocPageMetadata({ @@ -34,9 +39,46 @@ export default function DocPageMetadata({ authorKey, publishedAt, keywords, + schemaType = "TechArticle", + repositoryUrl, + licenseUrl, + languages, }: DocPageMetadataProps): ReactNode { const authorInfo = authorKey ? authorsData[authorKey as keyof typeof authorsData] : null; + // Generate SoftwareSourceCode schema for samples + if (schemaType === "SoftwareSourceCode") { + const softwareSourceCodeJsonLd = JSON.stringify({ + "@context": "https://schema.org", + "@type": "SoftwareSourceCode", + name: title, + ...(description ? { description } : {}), + url: canonicalUrl, + ...(repositoryUrl ? { codeRepository: repositoryUrl } : {}), + ...(licenseUrl ? { license: licenseUrl } : {}), + ...(languages?.length ? { programmingLanguage: languages } : {}), + ...(keywords?.length ? { keywords } : {}), + runtimePlatform: "RavenDB", + publisher: { + "@type": "Organization", + name: "RavenDB", + url: "https://ravendb.net", + }, + isPartOf: { + "@type": "CollectionPage", + "@id": `${canonicalUrl.split("/samples/")[0]}/samples`, + name: "RavenDB Code Samples", + }, + }); + + return ( + + + + ); + } + + // Generate TechArticle schema for guides and docs const techArticleJsonLd = JSON.stringify({ "@context": "https://schema.org", "@type": "TechArticle", diff --git a/src/theme/DocItem/Metadata/index.tsx b/src/theme/DocItem/Metadata/index.tsx index 55db8ac27f..893e9a8307 100644 --- a/src/theme/DocItem/Metadata/index.tsx +++ b/src/theme/DocItem/Metadata/index.tsx @@ -21,10 +21,12 @@ export default function MetadataWrapper(props: Props): ReactNode { const isCloud = source?.startsWith("@site/cloud/") || source?.startsWith("cloud/") || false; const isTemplate = source?.startsWith("@site/templates/") || source?.startsWith("templates/") || false; const isDocumentationPage = !isGuide && !isCloud && !isTemplate; + const isSample = source?.startsWith("@site/samples/") || source?.startsWith("samples/") || false; - // Exclude landing pages (e.g. guides/home.mdx) from guide-specific metadata + // Exclude landing pages (e.g. guides/home.mdx, samples/home.mdx) from type-specific metadata const fileName = source?.split("/").pop(); const isGuidePage = isGuide && fileName !== "home.mdx"; + const isSamplePage = isSample && fileName !== "home.mdx"; // Strip trailing slash from base URL to avoid double slashes const baseUrl = (siteConfig.url as string).replace(/\/$/, ""); @@ -76,6 +78,23 @@ export default function MetadataWrapper(props: Props): ReactNode { lastUpdatedAt={metadata.lastUpdatedAt} /> )} + {isSamplePage && ( + tag.replace(/-/g, " "))} + /> + )} ); @@ -105,19 +124,19 @@ function ValidatedGuideDocPageMetadata({ if (!title) { throw new Error(`Guide "${permalink}" is missing a required "title" in frontmatter.`); } - if (!frontMatter.proficiencyLevel) { - throw new Error(`Guide "${permalink}" is missing a required "proficiencyLevel" in frontmatter.`); + if (!frontMatter.proficiency_level) { + throw new Error(`Guide "${permalink}" is missing a required "proficiency_level" in frontmatter.`); } return ( diff --git a/src/theme/DocTagDocListPage/index.tsx b/src/theme/DocTagDocListPage/index.tsx index 751d4e873e..0ad9f15d6c 100644 --- a/src/theme/DocTagDocListPage/index.tsx +++ b/src/theme/DocTagDocListPage/index.tsx @@ -87,7 +87,7 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { "animate-in fade-in" )} > - {sortedItems.map((doc, index) => { + {sortedItems.map((doc) => { const guide = guides.find((g: Guide) => g.permalink === doc.permalink); const formattedDate = guide?.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { @@ -101,19 +101,18 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { key={doc.id} title={doc.title} description={doc.description} - url={guide?.externalUrl || doc.permalink} + url={guide?.external_url || doc.permalink} imgSrc={guide?.image} imgIcon={guide?.icon} tags={guide?.tags} date={formattedDate} - animationDelay={index * 50} /> ); })}
) : (
- {sortedItems.map((doc, index) => { + {sortedItems.map((doc) => { const guide = guides.find((g: Guide) => g.permalink === doc.permalink); const formattedDate = guide?.lastUpdatedAt ? new Date(guide.lastUpdatedAt * 1000).toLocaleDateString("en-US", { @@ -123,18 +122,10 @@ function DocTagDocListPageContent({ tag }: Props): ReactNode { }) : undefined; return ( -
+
diff --git a/src/typescript/docMetadata.d.ts b/src/typescript/docMetadata.d.ts index f9b28a0820..1fe5bc4fb1 100644 --- a/src/typescript/docMetadata.d.ts +++ b/src/typescript/docMetadata.d.ts @@ -14,15 +14,20 @@ export interface CustomDocFrontMatter extends DocFrontMatter { author?: string; icon?: IconName; image?: string; - publishedAt?: string; - proficiencyLevel?: string; + img_alt?: string; + published_at?: string; + proficiency_level?: string; keywords?: string[]; gallery?: GalleryImage[]; - challengesSolutionsTags?: string[]; - featureTags?: string[]; - techStackTags?: string[]; + challenges_solutions_tags?: string[]; + feature_tags?: string[]; + tech_stack_tags?: string[]; category?: string; license?: string; + license_url?: string; + repository_url?: string; + languages?: string[]; + external_url?: string; } type CustomDocContextValue = Omit & { diff --git a/static/llms.txt b/static/llms.txt index c5eb0d8c5f..652c498398 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -149,6 +149,11 @@ Documentation is versioned. The current version is 7.2. Use the `/7.2/` URL pref - [Data Migration](https://docs.ravendb.net/7.2/migration/server/data-migration): Migrate data between RavenDB versions - [Breaking Changes](https://docs.ravendb.net/7.2/migration/server/server-breaking-changes): Server-side breaking changes by version +## Sample Applications + +- [Samples Home](https://docs.ravendb.net/samples/): Browse production-ready code samples, architecture patterns, and starter kits built with RavenDB +- [The Library of Ravens](https://docs.ravendb.net/samples/the-ravens-library): Library management app demonstrating vector search, Azure Storage Queues ETL, Include for N+1 elimination, and Document Refresh — built with C#, Aspire, and Azure Functions + ## Cloud - [RavenDB Cloud Documentation](https://docs.ravendb.net/cloud/): Cloud service portal documentation diff --git a/templates/best-practices-samples.mdx b/templates/best-practices-samples.mdx index aa256cd2af..9340b8cf85 100644 --- a/templates/best-practices-samples.mdx +++ b/templates/best-practices-samples.mdx @@ -39,9 +39,9 @@ Guidelines and recommendations for creating high-quality sample documentation. **Example:** ```yaml -challengesSolutionsTags: [semantic-search, integration-patterns] -featureTags: [vector-search, include, document-refresh, azure-storage-queues-etl] -techStackTags: [csharp, aspire, azure-storage-queues, azure-functions] +challenges_solutions_tags: [semantic-search, integration-patterns] +feature_tags: [vector-search, include, document-refresh, azure-storage-queues-etl] +tech_stack_tags: [csharp, aspire, azure-storage-queues, azure-functions] ``` ### Images @@ -187,9 +187,13 @@ title: "My Sample" --- title: "My Sample" description: "Complete description" -challengesSolutionsTags: [semantic-search] -featureTags: [vector-search] -techStackTags: [csharp] +challenges_solutions_tags: [semantic-search] +feature_tags: [vector-search] +tech_stack_tags: [csharp] +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +languages: ["C#"] --- ``` @@ -218,13 +222,13 @@ title: "My Sample" ### ❌ Undefined Tags ```yaml -challengesSolutionsTags: [nonexistent-tag] # Tag doesn't exist in YAML +challenges_solutions_tags: [nonexistent-tag] # Tag doesn't exist in YAML ``` ### ✅ Valid Tags ```yaml -challengesSolutionsTags: [semantic-search] # Tag exists in samples/tags/challenges-solutions.yml +challenges_solutions_tags: [semantic-search] # Tag exists in samples/tags/challenges-solutions.yml ``` ### ❌ Empty FeatureAccordion @@ -248,6 +252,7 @@ challengesSolutionsTags: [semantic-search] # Tag exists in samples/tags/challen Before publishing a sample: - [ ] All frontmatter fields are present and valid +- [ ] `repository_url` and `languages` are set (feeds `SoftwareSourceCode` JSON-LD) - [ ] All tags exist in their respective YAML files - [ ] Cover image loads correctly - [ ] Gallery images load and have alt text diff --git a/templates/components-samples.mdx b/templates/components-samples.mdx index 7f8b9f2112..a226eb0f38 100644 --- a/templates/components-samples.mdx +++ b/templates/components-samples.mdx @@ -105,11 +105,11 @@ Container for the metadata sidebar, displays tags, category, license, and relate /> ``` **Auto-populated from frontmatter:** -- Challenges & Solutions tags (`challengesSolutionsTags`) -- Feature tags (`featureTags`) -- Tech stack tags (`techStackTags`) -- Category (`category`) -- License (`license`) +- Challenges & Solutions tags (`challenges_solutions_tags`) +- Feature tags (`feature_tags`) +- Tech stack tags (`tech_stack_tags`) +- Category (`category`) - optional, displayed in sidebar +- License (`license`) - optional, displayed in sidebar --- diff --git a/templates/featured-guides.mdx b/templates/featured-guides.mdx index b46be0d8ab..59d715022a 100644 --- a/templates/featured-guides.mdx +++ b/templates/featured-guides.mdx @@ -46,7 +46,7 @@ import FeaturedGuides from "@site/src/components/Guides/FeaturedGuides"; ```mdx --- title: "Example Guide" - publishedAt: 2026-02-27 + published_at: 2026-02-27 author: "Author Name" tags: [ai, demo] image: "/img/guides-ai-agents.webp" @@ -67,5 +67,5 @@ import FeaturedGuides from "@site/src/components/Guides/FeaturedGuides"; section will update automatically. - If you omit the `guidesTitles` prop, `FeaturedGuides` will fall back to showing the two most recently updated guides based on file timestamps or `publishedAt`. + If you omit the `guidesTitles` prop, `FeaturedGuides` will fall back to showing the two most recently updated guides based on file timestamps or `published_at`. diff --git a/templates/new-guides.mdx b/templates/new-guides.mdx index 77d580d250..9494184ae2 100644 --- a/templates/new-guides.mdx +++ b/templates/new-guides.mdx @@ -52,7 +52,7 @@ Intro paragraph... **Fields explained** - `title` – main page title displayed in the header. -- `publishedAt` – publication date (YYYY-MM-DD format). Displayed in the article header. +- `published_at` – publication date (YYYY-MM-DD format). Displayed in the article header. - `author` – name of the guide author (single author only). Displayed in the article header below the title. Must match exactly with a key in `docs/authors.json`. - `tags` – keys from `guides/tags.yml` (see Tags). Displayed below the title. - `image` – banner image displayed at the top of the article; can be a string or a themed image object (see Themed Images template). If not provided, a gradient background with icon is shown. @@ -100,11 +100,11 @@ Use this pattern when the actual content lives elsewhere but you still want it l ```mdx --- title: "Begin analysis with OLAP ETL" -publishedAt: 2025-12-23 +published_at: 2025-12-23 author: "Author Name" tags: [integration, getting-started] description: "Short description shown in cards and lists." -externalUrl: "https://ravendb.net/articles/begin-analysis-with-olap-etl" +external_url: "https://ravendb.net/articles/begin-analysis-with-olap-etl" image: "https://ravendb.net/wp-content/uploads/2025/12/OLAP-article-image.svg" --- diff --git a/templates/new-samples.mdx b/templates/new-samples.mdx index eb6eeb5577..2c060fde3f 100644 --- a/templates/new-samples.mdx +++ b/templates/new-samples.mdx @@ -39,12 +39,16 @@ samples/my-new-sample.mdx --- title: "My Sample Application" description: "A production-ready example demonstrating RavenDB features." -challengesSolutionsTags: [semantic-search, integration-patterns] -featureTags: [vector-search, include, document-refresh] -techStackTags: [csharp, aspire, azure-functions] -category: "E-commerce" -license: "MIT License" +challenges_solutions_tags: [semantic-search, integration-patterns] +feature_tags: [vector-search, include, document-refresh] +tech_stack_tags: [csharp, aspire, azure-functions] image: "/img/samples/my-sample/cover.webp" +img_alt: "My Sample Application Screenshot" +category: "Ecommerce" +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +languages: ["C#"] gallery: - src: "/img/samples/my-sample/screenshot-1.webp" alt: "Main interface" @@ -61,16 +65,20 @@ gallery: |---|---|---|---| | `title` | `string` | ✅ | Sample name displayed in cards and page header | | `description` | `string` | ✅ | Short summary shown in sample cards | -| `challengesSolutionsTags` | `string[]` | ✅ | Challenges & solutions tags from `samples/tags/challenges-solutions.yml` | -| `featureTags` | `string[]` | ✅ | RavenDB feature tags from `samples/tags/feature.yml` | -| `techStackTags` | `string[]` | ✅ | Technology tags from `samples/tags/tech-stack.yml` | -| `category` | `string` | ❌ | Business category (e.g., "E-commerce", "Healthcare") | -| `license` | `string` | ❌ | License type (e.g., "MIT License", "Apache 2.0") | -| `image` | `string` | ❌ | Cover image path | -| `gallery` | `array` | ❌ | Array of screenshot objects with `src` and `alt` | +| `challenges_solutions_tags` | `string[]` | ✅ | Challenges & solutions tags from `samples/tags/challenges-solutions.yml` (kebab-case) | +| `feature_tags` | `string[]` | ✅ | RavenDB feature tags from `samples/tags/feature.yml` (kebab-case) | +| `tech_stack_tags` | `string[]` | ✅ | Technology tags from `samples/tags/tech-stack.yml` (kebab-case) | +| `image` | `string` | ❌ | Cover image path (shown in sample cards) | +| `img_alt` | `string` | ❌ | Alt text for the cover image | +| `category` | `string` | ❌ | Business category (e.g., "Ecommerce", "Healthcare") - displayed in metadata sidebar | +| `license` | `string` | ❌ | License type (e.g., "MIT License", "Apache 2.0") - displayed in metadata sidebar | +| `license_url` | `string` | ❌ | License URL (e.g., `https://opensource.org/licenses/MIT`) - links the sidebar label and feeds JSON-LD | +| `repository_url` | `string` | ❌ | GitHub repository URL - used in `SoftwareSourceCode` JSON-LD for SEO | +| `languages` | `string[]` | ❌ | Programming languages (e.g., `["C#"]`) - used in `SoftwareSourceCode` JSON-LD for SEO | +| `gallery` | `array` | ❌ | Array of screenshot objects with `src` and `alt` properties | - All tag keys must exist in their respective YAML files (`samples/tags/challenges-solutions.yml`, `feature.yml`, `tech-stack.yml`). Using undefined tags will cause them to be ignored in the UI. + All tag keys must exist in their respective YAML files (`samples/tags/challenges-solutions.yml`, `feature.yml`, `tech-stack.yml`). Using undefined tags will cause them to display with their raw key value instead of a formatted label. ## Page Structure @@ -83,9 +91,13 @@ gallery: --- title: "My Sample" description: "Sample description" -challengesSolutionsTags: [semantic-search] -featureTags: [vector-search] -techStackTags: [csharp] +challenges_solutions_tags: [semantic-search] +feature_tags: [vector-search] +tech_stack_tags: [csharp] +license: "MIT License" +license_url: "https://opensource.org/licenses/MIT" +repository_url: "https://github.com/ravendb/my-sample-repo" +languages: ["C#"] --- import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples'; diff --git a/templates/tags-samples.mdx b/templates/tags-samples.mdx index 5e446d82ca..5d9bfcf1fe 100644 --- a/templates/tags-samples.mdx +++ b/templates/tags-samples.mdx @@ -34,7 +34,7 @@ These tags describe **what business problems** the sample solves. **Example usage in frontmatter:** ```yaml -challengesSolutionsTags: [semantic-search, integration-patterns] +challenges_solutions_tags: [semantic-search, integration-patterns] ``` ### 2. Feature Tags (`samples/tags/feature.yml`) @@ -53,7 +53,7 @@ These tags indicate **which RavenDB features** are demonstrated. **Example usage in frontmatter:** ```yaml -featureTags: [vector-search, include, document-refresh] +feature_tags: [vector-search, include, document-refresh] ``` ### 3. Tech Stack Tags (`samples/tags/tech-stack.yml`) @@ -74,7 +74,7 @@ These tags specify **technologies and frameworks** used in the sample. **Example usage in frontmatter:** ```yaml -techStackTags: [csharp, aspire, azure-functions] +tech_stack_tags: [csharp, aspire, azure-functions] ``` ## Adding New Tags @@ -143,9 +143,9 @@ Reference the tag key in your sample's frontmatter: ```mdx --- title: "My Sample" -challengesSolutionsTags: [real-time-analytics] -featureTags: [time-series] -techStackTags: [nextjs] +challenges_solutions_tags: [real-time-analytics] +feature_tags: [time-series] +tech_stack_tags: [nextjs] --- ``` From b3b9c13a9787b00956517e95f6f83d77578a1dec Mon Sep 17 00:00:00 2001 From: Mateusz Bartosik Date: Thu, 2 Apr 2026 14:09:00 +0200 Subject: [PATCH 5/6] RDoc-3729 Rebase fixes --- CLAUDE.md | 11 +- samples/the-ravens-library.mdx | 46 ++--- src/components/Common/CardWithImage.tsx | 7 +- .../Samples/Hub/Partials/FilterCategory.tsx | 6 +- .../Samples/Hub/Partials/SamplesGrid.tsx | 10 +- .../Samples/Hub/SamplesHomePage.tsx | 17 +- .../Samples/Overview/Partials/ActionsCard.tsx | 7 +- .../Overview/Partials/RelatedResource.tsx | 24 ++- .../Partials/SampleMetadataColumn.tsx | 34 ++-- .../Samples/Overview/SampleLayout.tsx | 12 +- src/typescript/docMetadata.d.ts | 10 ++ templates/best-practices-samples.mdx | 32 ++-- templates/components-samples.mdx | 158 ++++++------------ templates/new-samples.mdx | 80 ++++----- 14 files changed, 191 insertions(+), 263 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6a3d94ee92..c7bc21e658 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -208,18 +208,27 @@ category: "Ecommerce" license: "MIT License" license_url: "https://opensource.org/licenses/MIT" repository_url: "https://github.com/ravendb/sample-repo" +demo_url: "https://demo.example.com" languages: ["C#"] gallery: - src: "/img/samples/my-sample/screenshot-1.webp" alt: "Main interface" - src: "/img/samples/my-sample/screenshot-2.webp" alt: "Admin dashboard" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" --- ``` **Required fields**: `title`, `description`, `challenges_solutions_tags`, `feature_tags`, `tech_stack_tags` -**Optional fields**: `image`, `img_alt`, `category`, `license`, `license_url`, `repository_url`, `languages`, `gallery` +**Optional fields**: `image`, `img_alt`, `category`, `license`, `license_url`, `repository_url`, `demo_url`, `languages`, `gallery`, `related_resources` **SEO**: `repository_url` and `languages` feed `SoftwareSourceCode` JSON-LD schema for better search visibility. diff --git a/samples/the-ravens-library.mdx b/samples/the-ravens-library.mdx index 28da33261b..208ef1c08a 100644 --- a/samples/the-ravens-library.mdx +++ b/samples/the-ravens-library.mdx @@ -18,39 +18,23 @@ gallery: alt: "The Library of Ravens - Author Profile" - src: "/img/samples/library-of-ravens/03.webp" alt: "The Library of Ravens - User Profile" +related_resources: + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: documentation + documentation_type: docs + subtitle: "Azure Storage Queues ETL" + article_key: "server/ongoing-tasks/etl/queue-etl/azure-queue" + - type: video + subtitle: "Learn How to Build a Modern .NET App with ease: Azure Functions and Aspire with RavenDB" + url: "https://www.youtube.com/watch?v=TEvBGNMSq9g" --- -import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples'; -import Gallery from '@site/src/components/Common/Gallery'; - -export const sampleDetails = ( - - } - relatedResources={ - <> - - - - } - /> -); - -}> +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + ## Overview diff --git a/src/components/Common/CardWithImage.tsx b/src/components/Common/CardWithImage.tsx index cc273ee2ee..64634d3dcf 100644 --- a/src/components/Common/CardWithImage.tsx +++ b/src/components/Common/CardWithImage.tsx @@ -104,7 +104,12 @@ export default function CardWithImage({ {hasTags && (
{visibleTags.map((tag) => ( - + {tag.label} ))} diff --git a/src/components/Samples/Hub/Partials/FilterCategory.tsx b/src/components/Samples/Hub/Partials/FilterCategory.tsx index ea76741a89..f586431b35 100644 --- a/src/components/Samples/Hub/Partials/FilterCategory.tsx +++ b/src/components/Samples/Hub/Partials/FilterCategory.tsx @@ -27,11 +27,7 @@ export default function FilterCategory({ isExpanded, onToggleExpanded, }: FilterCategoryProps) { - const { - value: isTagsExpanded, - setTrue: expandTags, - setFalse: collapseTags, - } = useBoolean(false); + const { value: isTagsExpanded, setTrue: expandTags, setFalse: collapseTags } = useBoolean(false); const [manuallyCollapsed, setManuallyCollapsed] = React.useState(false); const visibleTags = isTagsExpanded ? tags : tags.slice(0, 5); diff --git a/src/components/Samples/Hub/Partials/SamplesGrid.tsx b/src/components/Samples/Hub/Partials/SamplesGrid.tsx index 6b64721f0d..ece052a973 100644 --- a/src/components/Samples/Hub/Partials/SamplesGrid.tsx +++ b/src/components/Samples/Hub/Partials/SamplesGrid.tsx @@ -28,11 +28,7 @@ export default function SamplesGrid({ samples, selectedTags, matchLogic, onTagCl }); }, [samples, selectedTags, matchLogic]); - const sortedSamples = useMemo(() => { - return filteredSamples; - }, [filteredSamples]); - - if (sortedSamples.length === 0) { + if (filteredSamples.length === 0) { return (

No samples found matching your filters.

@@ -54,11 +50,11 @@ export default function SamplesGrid({ samples, selectedTags, matchLogic, onTagCl "dark:bg-white/20 dark:text-white" )} > - {sortedSamples.length} + {filteredSamples.length}
- {sortedSamples.map((sample) => ( + {filteredSamples.map((sample) => ( { - const newSelected = new Set(selectedTags); - if (newSelected.has(tagKey)) { - newSelected.delete(tagKey); - } else { - newSelected.add(tagKey); - } - setSelectedTags(newSelected); - }; - const { value: isFilterDrawerOpen, setTrue: openFilterDrawer, setFalse: closeFilterDrawer } = useBoolean(false); const hasActiveFilters = selectedTags.size > 0 || matchLogic !== "any"; @@ -116,8 +106,7 @@ export default function SamplesHomePage() { "@type": "CollectionPage", "@id": samplesUrl, name: "RavenDB Code Samples", - description: - "Production-ready code samples, architecture patterns, and starter kits built with RavenDB.", + description: "Production-ready code samples, architecture patterns, and starter kits built with RavenDB.", url: samplesUrl, isPartOf: { "@type": "WebSite", "@id": `${siteUrl}/` }, breadcrumb: { @@ -218,10 +207,10 @@ export default function SamplesHomePage() {
s.id !== "home")} + samples={filteredSamples} selectedTags={selectedTags} matchLogic={matchLogic} - onTagClick={handleTagClick} + onTagClick={handleTagToggle} />
diff --git a/src/components/Samples/Overview/Partials/ActionsCard.tsx b/src/components/Samples/Overview/Partials/ActionsCard.tsx index 99f21f3327..3860d5ce6f 100644 --- a/src/components/Samples/Overview/Partials/ActionsCard.tsx +++ b/src/components/Samples/Overview/Partials/ActionsCard.tsx @@ -4,14 +4,11 @@ import Button from "@site/src/components/Common/Button"; export interface ActionsCardProps { className?: string; - githubUser?: string; - githubRepo?: string; + githubUrl?: string; demoUrl?: string; } -export default function ActionsCard({ className, githubUser, githubRepo, demoUrl }: ActionsCardProps) { - const githubUrl = githubUser && githubRepo ? `https://github.com/${githubUser}/${githubRepo}` : undefined; - +export default function ActionsCard({ className, githubUrl, demoUrl }: ActionsCardProps) { return (
= { @@ -26,6 +27,10 @@ const TYPE_CONFIG: Record = { title: "Documentation", icon: "database", }, + video: { + title: "Video Walkthrough", + icon: "play", + }, }; export default function RelatedResource({ @@ -34,18 +39,23 @@ export default function RelatedResource({ documentationType = "docs", subtitle, articleKey, + externalUrl, }: RelatedResourceProps) { const latestVersion = useLatestVersion() as string; const config = TYPE_CONFIG[type]; const url = React.useMemo(() => { + if (externalUrl) { + return externalUrl; + } + if (type === "guide") { return `/guides/${articleKey}`; } const basePath = documentationType === "cloud" ? "/cloud" : `/${latestVersion}`; return `${basePath}/${articleKey}`; - }, [type, documentationType, articleKey, latestVersion]); + }, [type, documentationType, articleKey, externalUrl, latestVersion]); const icon = type === "documentation" && documentationType === "cloud" ? "cloud" : config.icon; @@ -72,8 +82,12 @@ export default function RelatedResource({
-

{config.title}

-

{subtitle}

+

+ {config.title} +

+

+ {subtitle} +

); diff --git a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx index caf470336a..46e6cfd1bd 100644 --- a/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx +++ b/src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx @@ -1,15 +1,15 @@ -import React, { ReactNode, useMemo } from "react"; +import React, { useMemo } from "react"; import { useDoc } from "@docusaurus/plugin-content-docs/client"; import { usePluginData } from "@docusaurus/useGlobalData"; import clsx from "clsx"; import Heading from "@theme/Heading"; import Tag from "@site/src/theme/Tag"; import type { PluginData } from "@site/src/components/Samples/types"; +import ActionsCard from "./ActionsCard"; +import RelatedResource from "./RelatedResource"; export interface SampleMetadataColumnProps { className?: string; - actionsCard?: ReactNode; - relatedResources?: ReactNode; } interface TagData { @@ -59,7 +59,7 @@ function TagSection({ title, tags }: TagSectionProps) { ); } -export default function SampleMetadataColumn({ className, actionsCard, relatedResources }: SampleMetadataColumnProps) { +export default function SampleMetadataColumn({ className }: SampleMetadataColumnProps) { const { frontMatter } = useDoc(); const pluginData = usePluginData("recent-samples-plugin") as PluginData | undefined; @@ -94,6 +94,9 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe const category = frontMatter.category; const license = frontMatter.license; const licenseUrl = frontMatter.license_url; + const repositoryUrl = frontMatter.repository_url; + const demoUrl = frontMatter.demo_url; + const relatedResourceItems = frontMatter.related_resources; const challengesSolutionsTags = getTagsWithLabels(challengesSolutionsTagKeys, challengesSolutionsTagsData); const featureTags = getTagsWithLabels(featureTagKeys, featureTagsData); @@ -101,7 +104,7 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe return (
- {actionsCard} + {(repositoryUrl || demoUrl) && } @@ -123,11 +126,7 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe

{licenseUrl ? ( - + {license} ) : ( @@ -137,12 +136,23 @@ export default function SampleMetadataColumn({ className, actionsCard, relatedRe

)} - {relatedResources && ( + {relatedResourceItems && relatedResourceItems.length > 0 && (
Related Resources -
{relatedResources}
+
+ {relatedResourceItems.map((resource, index) => ( + + ))} +
)}
diff --git a/src/components/Samples/Overview/SampleLayout.tsx b/src/components/Samples/Overview/SampleLayout.tsx index 16c6c477e3..5801fe01da 100644 --- a/src/components/Samples/Overview/SampleLayout.tsx +++ b/src/components/Samples/Overview/SampleLayout.tsx @@ -1,12 +1,16 @@ import React, { ReactNode } from "react"; +import SampleMetadataColumn from "./Partials/SampleMetadataColumn"; +import Gallery from "@site/src/components/Common/Gallery"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; interface SampleLayoutProps { children: ReactNode; - details: ReactNode; - gallery?: ReactNode; } -export default function SampleLayout({ children, details, gallery }: SampleLayoutProps) { +export default function SampleLayout({ children }: SampleLayoutProps) { + const { frontMatter } = useDoc(); + const gallery = frontMatter.gallery?.length ? : null; + return (
@@ -15,7 +19,7 @@ export default function SampleLayout({ children, details, gallery }: SampleLayou
{gallery}
- {details} +
); diff --git a/src/typescript/docMetadata.d.ts b/src/typescript/docMetadata.d.ts index 1fe5bc4fb1..f6219efc5d 100644 --- a/src/typescript/docMetadata.d.ts +++ b/src/typescript/docMetadata.d.ts @@ -8,6 +8,14 @@ export interface GalleryImage { alt?: string; } +export interface RelatedResourceFrontMatter { + type: "guide" | "documentation" | "video"; + documentation_type?: "docs" | "cloud"; + subtitle: string; + article_key?: string; + url?: string; +} + export interface CustomDocFrontMatter extends DocFrontMatter { supported_languages?: DocsLanguage[]; see_also?: SeeAlsoItemType[]; @@ -26,8 +34,10 @@ export interface CustomDocFrontMatter extends DocFrontMatter { license?: string; license_url?: string; repository_url?: string; + demo_url?: string; languages?: string[]; external_url?: string; + related_resources?: RelatedResourceFrontMatter[]; } type CustomDocContextValue = Omit & { diff --git a/templates/best-practices-samples.mdx b/templates/best-practices-samples.mdx index 9340b8cf85..1cb1d679ae 100644 --- a/templates/best-practices-samples.mdx +++ b/templates/best-practices-samples.mdx @@ -113,16 +113,20 @@ Use `FeatureAccordion` components for each major feature: ## Component Usage -### ActionsCard +### Sidebar (frontmatter-driven) -**Always provide:** -- GitHub repository link (if code is public) -- Demo URL (if available) +The metadata sidebar is fully driven by frontmatter — no JSX props needed. -**Don't:** -- Link to private repositories -- Use broken or outdated demo URLs -- Include download links (use GitHub releases instead) +**Actions (repository_url / demo_url):** +- Always provide `repository_url` if the code is public +- Add `demo_url` only if a live demo is available and maintained +- Don't link to private repositories or broken demo URLs + +**Related resources (related_resources):** +- Link to 2–3 related resources +- Mix guides and documentation +- Verify links are correct and current; use descriptive `subtitle` values +- Don't link to deprecated documentation, use vague subtitles like "Related Article", or duplicate links ### FeatureAccordion @@ -134,18 +138,6 @@ Use `FeatureAccordion` components for each major feature: - Leave children empty (omit the accordion instead) - Include broken code examples -### RelatedResource - -**Do:** -- Verify links are correct and current -- Use descriptive subtitles - -**Don't:** -- Link to deprecated documentation -- Use vague subtitles like "Related Article" -- Link to external sites without context -- Duplicate links - ## Tag Management ### Adding New Tags diff --git a/templates/components-samples.mdx b/templates/components-samples.mdx index a226eb0f38..f1cc582992 100644 --- a/templates/components-samples.mdx +++ b/templates/components-samples.mdx @@ -24,17 +24,12 @@ All sample components are exported from a single entry point: ```tsx import { - // Layout + // Layout (includes sidebar and gallery automatically) SampleLayout, - - // Metadata sidebar - SampleMetadataColumn, - ActionsCard, - RelatedResource, - + // Content FeatureAccordion, - + // Hub (not imported in sample pages) SamplesHomePage, SampleCard, @@ -47,7 +42,7 @@ import { ### SampleLayout -Two-column responsive layout for sample detail pages. +Two-column responsive layout for sample detail pages. Renders the metadata sidebar and gallery automatically from frontmatter — no props needed beyond `children`. **Location:** `src/components/Samples/Overview/SampleLayout.tsx` @@ -56,22 +51,22 @@ Two-column responsive layout for sample detail pages. | Prop | Type | Required | Description | |---|---|---|---| | `children` | `ReactNode` | ✅ | Main content area | -| `details` | `ReactNode` | ✅ | Sidebar content (usually `SampleMetadataColumn`) | **Usage:** ```tsx -export const sampleDetails = ( - -); - - + {/* Main content */} ``` + +**Auto-rendered from frontmatter:** +- Sidebar — `SampleMetadataColumn` (tags, actions, license, related resources) +- Gallery — `gallery` field; omitted automatically when the field is absent + **Behavior:** -- Desktop (lg+): Two columns, sidebar on right (300px fixed width) -- Mobile: Stacked, sidebar appears above content +- Desktop (lg+): Two columns, sidebar on right (300px fixed width); gallery above content +- Mobile: Stacked, sidebar and gallery appear above content --- @@ -79,7 +74,7 @@ export const sampleDetails = ( ### SampleMetadataColumn -Container for the metadata sidebar, displays tags, category, license, and related resources. +Container for the metadata sidebar. Reads all data from frontmatter — no props required. **Location:** `src/components/Samples/Overview/Partials/SampleMetadataColumn.tsx` @@ -88,114 +83,55 @@ Container for the metadata sidebar, displays tags, category, license, and relate | Prop | Type | Required | Description | |---|---|---|---| | `className` | `string` | ❌ | Additional CSS classes | -| `actionsCard` | `ReactNode` | ❌ | Usually an `ActionsCard` component | -| `relatedResources` | `ReactNode` | ❌ | Usually multiple `RelatedResource` components | - -**Usage:** - -```tsx -} - relatedResources={ - <> - - - - } -/> -``` -**Auto-populated from frontmatter:** -- Challenges & Solutions tags (`challenges_solutions_tags`) -- Feature tags (`feature_tags`) -- Tech stack tags (`tech_stack_tags`) -- Category (`category`) - optional, displayed in sidebar -- License (`license`) - optional, displayed in sidebar - ---- - -### ActionsCard - -Displays GitHub repository and demo links. - -**Location:** `src/components/Samples/Overview/Partials/ActionsCard.tsx` - -**Props:** - -| Prop | Type | Required | Description | -|---|---|---|---| -| `className` | `string` | ❌ | Additional CSS classes | -| `githubUser` | `string` | ❌ | GitHub username or organization | -| `githubRepo` | `string` | ❌ | GitHub repository name | -| `demoUrl` | `string` | ❌ | URL to live demo | **Usage:** ```tsx - + ``` -**Rendered buttons:** -- **Browse code** (if `githubUser` and `githubRepo` provided) - Links to `https://github.com/{user}/{repo}` -- **View demo** (if `demoUrl` provided) - Links to demo URL +**Populated from frontmatter:** +- `repository_url` — renders "Browse code" button +- `demo_url` — renders "View demo" button +- `challenges_solutions_tags` — Challenges & Solutions tag pills +- `feature_tags` — Feature tag pills +- `tech_stack_tags` — Tech stack tag pills +- `category` — optional, displayed in sidebar +- `license` / `license_url` — optional, displayed in sidebar +- `related_resources` — renders linked resource items (see schema below) ---- +**`related_resources` item schema (frontmatter):** -### RelatedResource - -Links to related guides or documentation. - -**Location:** `src/components/Samples/Overview/Partials/RelatedResource.tsx` - -**Props:** - -| Prop | Type | Required | Description | +| Field | Type | Required | Description | |---|---|---|---| -| `className` | `string` | ❌ | Additional CSS classes | | `type` | `"guide"` \| `"documentation"` | ✅ | Resource type | -| `documentationType` | `"docs"` \| `"cloud"` | ❌ | Documentation section (only for `type="documentation"`) | +| `documentation_type` | `"docs"` \| `"cloud"` | ❌ | Documentation section | | `subtitle` | `string` | ✅ | Display text | -| `articleKey` | `string` | ✅ | Path to article (without version prefix) | - -**Usage:** - -```tsx -{/* Link to guide */} - - -{/* Link to docs */} - - -{/* Link to cloud docs */} - +| `article_key` | `string` | ✅ | Article path without version prefix | + +**Example frontmatter:** + +```yaml +repository_url: "https://github.com/ravendb/my-sample-repo" +demo_url: "https://demo.example.com" +related_resources: + - type: guide + subtitle: "Bookkeeping with RavenDB & ETLs" + article_key: "bookkeeping-with-ravendb" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" + - type: documentation + documentation_type: cloud + subtitle: "Cloud Backup" + article_key: "portal-backup" ``` -**Auto-versioning:** -- For `type="documentation"` with `documentationType="docs"`, the current version is automatically prepended +**Auto-versioning for docs links:** +- `documentation_type: docs` → current version is automatically prepended - Example: `ai-integration/vector-search/overview` → `/7.2/ai-integration/vector-search/overview` -**Icons:** -- `guide` → Guides icon -- `documentation` (docs) → Database icon -- `documentation` (cloud) → Cloud icon - --- ## Content Components diff --git a/templates/new-samples.mdx b/templates/new-samples.mdx index 2c060fde3f..7c397d3294 100644 --- a/templates/new-samples.mdx +++ b/templates/new-samples.mdx @@ -48,12 +48,21 @@ category: "Ecommerce" license: "MIT License" license_url: "https://opensource.org/licenses/MIT" repository_url: "https://github.com/ravendb/my-sample-repo" +demo_url: "https://demo.example.com" languages: ["C#"] gallery: - src: "/img/samples/my-sample/screenshot-1.webp" alt: "Main interface" - src: "/img/samples/my-sample/screenshot-2.webp" alt: "Admin dashboard" +related_resources: + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" --- ``` @@ -73,9 +82,20 @@ gallery: | `category` | `string` | ❌ | Business category (e.g., "Ecommerce", "Healthcare") - displayed in metadata sidebar | | `license` | `string` | ❌ | License type (e.g., "MIT License", "Apache 2.0") - displayed in metadata sidebar | | `license_url` | `string` | ❌ | License URL (e.g., `https://opensource.org/licenses/MIT`) - links the sidebar label and feeds JSON-LD | -| `repository_url` | `string` | ❌ | GitHub repository URL - used in `SoftwareSourceCode` JSON-LD for SEO | +| `repository_url` | `string` | ❌ | GitHub repository URL - renders "Browse code" button; used in `SoftwareSourceCode` JSON-LD for SEO | +| `demo_url` | `string` | ❌ | Live demo URL - renders "View demo" button in the sidebar | | `languages` | `string[]` | ❌ | Programming languages (e.g., `["C#"]`) - used in `SoftwareSourceCode` JSON-LD for SEO | | `gallery` | `array` | ❌ | Array of screenshot objects with `src` and `alt` properties | +| `related_resources` | `array` | ❌ | Related guides and docs shown in the sidebar — see schema below | + +Each `related_resources` item accepts: + +| Field | Type | Required | Description | +|---|---|---|---| +| `type` | `"guide"` \| `"documentation"` | ✅ | Resource type | +| `documentation_type` | `"docs"` \| `"cloud"` | ❌ | Documentation section (only for `type: documentation`) | +| `subtitle` | `string` | ✅ | Display text | +| `article_key` | `string` | ✅ | Article path without version prefix (e.g., `ai-integration/vector-search/overview`) | All tag keys must exist in their respective YAML files (`samples/tags/challenges-solutions.yml`, `feature.yml`, `tech-stack.yml`). Using undefined tags will cause them to display with their raw key value instead of a formatted label. @@ -98,42 +118,19 @@ license: "MIT License" license_url: "https://opensource.org/licenses/MIT" repository_url: "https://github.com/ravendb/my-sample-repo" languages: ["C#"] +related_resources: + - type: guide + subtitle: "Related Guide Title" + article_key: "guide-slug" + - type: documentation + documentation_type: docs + subtitle: "Vector Search Overview" + article_key: "ai-integration/vector-search/overview" --- -import { ActionsCard, RelatedResource, FeatureAccordion, SampleMetadataColumn, SampleLayout } from '@site/src/components/Samples'; -import Gallery from '@site/src/components/Common/Gallery'; - -export const sampleDetails = ( - - } - relatedResources={ - <> - - - - } - /> -); - -} -> +import { FeatureAccordion, SampleLayout } from '@site/src/components/Samples'; + + ## Overview @@ -162,18 +159,7 @@ Describe what the sample does and what problems it solves... - **Mobile**: Gallery appears at the top, right after the page title and above the ActionsCard - **Desktop**: Gallery appears in the main content area, before the Overview section - **Usage:** - ```mdx - } - > - ## Overview - ... - - ``` - - The `gallery` prop is **optional**. If your sample doesn't have screenshots, simply omit it. + `SampleLayout` reads `gallery` from the `gallery` frontmatter field automatically. If the field is absent or empty, no gallery is rendered. From d764d27d96ca55b71ab1b2d87a206b45b053289f Mon Sep 17 00:00:00 2001 From: Mateusz Bartosik Date: Tue, 14 Apr 2026 13:36:26 +0200 Subject: [PATCH 6/6] RDoc-3729 Review fixes --- .../Samples/Hub/Partials/FilterCategory.tsx | 24 +++++++------------ .../Samples/Hub/Partials/SamplesFilter.tsx | 24 +++++++------------ 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/components/Samples/Hub/Partials/FilterCategory.tsx b/src/components/Samples/Hub/Partials/FilterCategory.tsx index f586431b35..52e559d8b3 100644 --- a/src/components/Samples/Hub/Partials/FilterCategory.tsx +++ b/src/components/Samples/Hub/Partials/FilterCategory.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { motion, AnimatePresence } from "motion/react"; import { Icon } from "@site/src/components/Common/Icon"; import Checkbox from "@site/src/components/Common/Checkbox"; @@ -30,21 +30,13 @@ export default function FilterCategory({ const { value: isTagsExpanded, setTrue: expandTags, setFalse: collapseTags } = useBoolean(false); const [manuallyCollapsed, setManuallyCollapsed] = React.useState(false); - const visibleTags = isTagsExpanded ? tags : tags.slice(0, 5); + const hasSelectedHiddenTag = tags.length > 5 && tags.slice(5).some((tag) => selectedTags.has(tag.key)); + const showAllTags = isTagsExpanded || (!manuallyCollapsed && hasSelectedHiddenTag); + const visibleTags = showAllTags ? tags : tags.slice(0, 5); const hiddenCount = Math.max(0, tags.length - 5); - useEffect(() => { - if (!isTagsExpanded && !manuallyCollapsed && tags.length > 5) { - const hiddenTags = tags.slice(5); - const hasSelectedHiddenTag = hiddenTags.some((tag) => selectedTags.has(tag.key)); - if (hasSelectedHiddenTag) { - expandTags(); - } - } - }, [selectedTags, tags, isTagsExpanded, expandTags, manuallyCollapsed]); - const handleToggleTagsExpanded = () => { - if (isTagsExpanded) { + if (showAllTags) { setManuallyCollapsed(true); collapseTags(); } else { @@ -93,13 +85,13 @@ export default function FilterCategory({ className="flex items-center gap-2 text-xs hover:text-black dark:hover:text-white cursor-pointer mt-1" > - {isTagsExpanded ? "Less" : "More "} - {!isTagsExpanded && ( + {showAllTags ? "Less" : "More "} + {!showAllTags && ( ({hiddenCount}) )} diff --git a/src/components/Samples/Hub/Partials/SamplesFilter.tsx b/src/components/Samples/Hub/Partials/SamplesFilter.tsx index a9c3ad66cf..e96275a0e5 100644 --- a/src/components/Samples/Hub/Partials/SamplesFilter.tsx +++ b/src/components/Samples/Hub/Partials/SamplesFilter.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useMemo } from "react"; import Heading from "@theme/Heading"; import clsx from "clsx"; import Toggle from "@site/src/components/Common/Toggle"; @@ -39,28 +39,22 @@ export default function SamplesFilter({ const [manuallyCollapsed, setManuallyCollapsed] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(""); - useEffect(() => { - const categoriesToExpand = new Set(expandedCategories); - let hasChanges = false; - + const effectiveExpanded = useMemo(() => { + const result = new Set(expandedCategories); categories.forEach((category) => { const hasSelectedTag = category.tags.some((tag) => selectedTags.has(tag.key)); - if (hasSelectedTag && !categoriesToExpand.has(category.name) && !manuallyCollapsed.has(category.name)) { - categoriesToExpand.add(category.name); - hasChanges = true; + if (hasSelectedTag && !manuallyCollapsed.has(category.name)) { + result.add(category.name); } }); - - if (hasChanges) { - setExpandedCategories(categoriesToExpand); - } - }, [selectedTags, categories, manuallyCollapsed, expandedCategories]); + return result; + }, [expandedCategories, categories, selectedTags, manuallyCollapsed]); const toggleCategory = (categoryName: string) => { const newExpanded = new Set(expandedCategories); const newManuallyCollapsed = new Set(manuallyCollapsed); - if (newExpanded.has(categoryName)) { + if (effectiveExpanded.has(categoryName)) { newExpanded.delete(categoryName); newManuallyCollapsed.add(categoryName); } else { @@ -138,7 +132,7 @@ export default function SamplesFilter({ tags={category.tags} selectedTags={selectedTags} onTagToggle={onTagToggle} - isExpanded={expandedCategories.has(category.name)} + isExpanded={effectiveExpanded.has(category.name)} onToggleExpanded={() => toggleCategory(category.name)} /> ))