From 396c315266c473ff89333065f5ef14c7b3c74694 Mon Sep 17 00:00:00 2001 From: patcapulong Date: Sat, 21 Feb 2026 00:47:29 -0800 Subject: [PATCH 1/5] docs: clarify React hooks work in MDX, useEffect is not the issue Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index a7ae0235..e583f6cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ Grid is an API for global payments across fiat, stablecoins, and Bitcoin. - Use snippets from `mintlify/snippets/` instead of duplicating content across use cases - Follow writing standards in `mintlify/CLAUDE.md` for all documentation content - MDX files require frontmatter with `title` and `description` -- Do not use `React.useEffect` in MDX — it breaks Mintlify's acorn parser. `React.useState` is fine. +- React hooks (`useState`, `useEffect`, etc.) work in MDX. If the acorn parser throws, it's usually a JS expression issue (e.g., template literals in JSX curlies), not the hook itself. ## Commands From adbe22700978953863c8b2a22050be37a18c7ca7 Mon Sep 17 00:00:00 2001 From: patcapulong Date: Mon, 23 Feb 2026 16:49:34 -0800 Subject: [PATCH 2/5] Add scroll affordance to horizontal tab lists Horizontal tab lists (e.g. account type tabs on API reference pages) overflow but give no visual hint with scrollbars hidden. This adds gradient fade masks on the edges that appear/disappear based on scroll position, drag-to-scroll support for mouse users, and a grab cursor to signal the interaction. Co-authored-by: Cursor --- mintlify/docs.json | 2 +- mintlify/scroll-fade.js | 116 ++++++++++++++++++++++++++++++++++++++++ mintlify/style.css | 36 +++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 mintlify/scroll-fade.js diff --git a/mintlify/docs.json b/mintlify/docs.json index 97723499..ed4adf87 100644 --- a/mintlify/docs.json +++ b/mintlify/docs.json @@ -298,7 +298,7 @@ }, "footer": {}, "head": { - "raw": "", + "raw": "", "links": [ { "rel": "preload", diff --git a/mintlify/scroll-fade.js b/mintlify/scroll-fade.js new file mode 100644 index 00000000..5b363aa6 --- /dev/null +++ b/mintlify/scroll-fade.js @@ -0,0 +1,116 @@ +(function () { + var EDGE_THRESHOLD = 2; + var DRAG_THRESHOLD = 5; + var DEBOUNCE_MS = 100; + var FADE_SELECTOR = '[data-component-part="tabs-list"]'; + + function updateFadeClasses(el) { + var scrollLeft = el.scrollLeft; + var scrollWidth = el.scrollWidth; + var clientWidth = el.clientWidth; + var canScrollLeft = scrollLeft > EDGE_THRESHOLD; + var canScrollRight = scrollLeft < scrollWidth - clientWidth - EDGE_THRESHOLD; + var overflows = scrollWidth > clientWidth + EDGE_THRESHOLD; + + el.classList.toggle("is-scrollable", overflows); + el.classList.toggle("scroll-fade-both", canScrollLeft && canScrollRight); + el.classList.toggle("scroll-fade-left", canScrollLeft && !canScrollRight); + el.classList.toggle("scroll-fade-right", !canScrollLeft && canScrollRight); + + if (!canScrollLeft && !canScrollRight) { + el.classList.remove( + "scroll-fade-left", + "scroll-fade-right", + "scroll-fade-both" + ); + } + } + + function initDragScroll(el) { + var isDown = false; + var startX = 0; + var startScrollLeft = 0; + var hasDragged = false; + + el.addEventListener("mousedown", function (e) { + if (e.button !== 0) return; + isDown = true; + hasDragged = false; + startX = e.pageX; + startScrollLeft = el.scrollLeft; + }); + + el.addEventListener("mousemove", function (e) { + if (!isDown) return; + var dx = e.pageX - startX; + if (!hasDragged && Math.abs(dx) < DRAG_THRESHOLD) return; + + if (!hasDragged) { + hasDragged = true; + el.classList.add("is-dragging"); + } + + e.preventDefault(); + el.scrollLeft = startScrollLeft - dx; + }); + + function endDrag() { + if (!isDown) return; + isDown = false; + if (hasDragged) { + el.classList.remove("is-dragging"); + // Suppress the click that follows mouseup after a drag + el.addEventListener( + "click", + function (e) { + e.stopPropagation(); + e.preventDefault(); + }, + { once: true, capture: true } + ); + } + hasDragged = false; + } + + el.addEventListener("mouseup", endDrag); + el.addEventListener("mouseleave", endDrag); + } + + function initTabList(el) { + if (el.dataset.scrollFadeInit) return; + el.dataset.scrollFadeInit = "true"; + + updateFadeClasses(el); + el.addEventListener("scroll", function () { updateFadeClasses(el); }, { + passive: true, + }); + + var ro = new ResizeObserver(function () { updateFadeClasses(el); }); + ro.observe(el); + + initDragScroll(el); + } + + function initAll() { + var els = document.querySelectorAll(FADE_SELECTOR); + for (var i = 0; i < els.length; i++) { + initTabList(els[i]); + } + } + + // Debounced version for MutationObserver + var timer = null; + function debouncedInitAll() { + if (timer) clearTimeout(timer); + timer = setTimeout(initAll, DEBOUNCE_MS); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initAll); + } else { + initAll(); + } + + var observer = new MutationObserver(debouncedInitAll); + observer.observe(document.body, { childList: true, subtree: true }); +})(); diff --git a/mintlify/style.css b/mintlify/style.css index 0c57e3b5..adebb946 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -2564,6 +2564,42 @@ html.dark [data-component-part="tab-button"][data-active="true"] { border-color: var(--ls-gray-100) !important; } +/* --- Horizontal scroll affordance for tab lists --- */ + +[data-component-part="tabs-list"] { + --fade-size: 32px; + user-select: none; + -webkit-user-select: none; +} + +[data-component-part="tabs-list"].is-scrollable { + cursor: grab; +} + +[data-component-part="tabs-list"].is-dragging { + cursor: grabbing !important; + user-select: none; +} + +[data-component-part="tab-button"] { + cursor: pointer !important; +} + +[data-component-part="tabs-list"].scroll-fade-right { + mask-image: linear-gradient(to right, black calc(100% - var(--fade-size)), transparent); + -webkit-mask-image: linear-gradient(to right, black calc(100% - var(--fade-size)), transparent); +} + +[data-component-part="tabs-list"].scroll-fade-left { + mask-image: linear-gradient(to left, black calc(100% - var(--fade-size)), transparent); + -webkit-mask-image: linear-gradient(to left, black calc(100% - var(--fade-size)), transparent); +} + +[data-component-part="tabs-list"].scroll-fade-both { + mask-image: linear-gradient(to right, transparent, black var(--fade-size), black calc(100% - var(--fade-size)), transparent); + -webkit-mask-image: linear-gradient(to right, transparent, black var(--fade-size), black calc(100% - var(--fade-size)), transparent); +} + /* =========================================== Tables =========================================== */ From ca79123fda789ec5233a41ab3edda9ee6bf93729 Mon Sep 17 00:00:00 2001 From: patcapulong Date: Tue, 24 Feb 2026 00:53:26 -0800 Subject: [PATCH 3/5] Replace horizontal scroll tabs with wrapping pill-style tabs Switch from scroll affordance (fade gradients + drag-to-scroll) to flex-wrap with pill-shaped tab buttons for oneOf schema variants. Handles 20+ account types gracefully across viewports. Co-Authored-By: Claude Opus 4.6 (1M context) --- mintlify/style.css | 73 ++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/mintlify/style.css b/mintlify/style.css index adebb946..111aa335 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -2536,68 +2536,59 @@ html.dark #content ul:has([data-component-part="tab-button"]) { /* Inactive tab */ [data-component-part="tab-button"][data-active="false"] { color: var(--ls-gray-500) !important; - border-color: transparent !important; -} - -[data-component-part="tab-button"][data-active="false"]:hover { - color: var(--ls-gray-700) !important; - border-color: var(--ls-gray-300) !important; } html.dark [data-component-part="tab-button"][data-active="false"] { color: var(--ls-gray-600) !important; } -html.dark [data-component-part="tab-button"][data-active="false"]:hover { - color: var(--ls-gray-400) !important; - border-color: var(--ls-gray-700) !important; -} - -/* Active tab */ -[data-component-part="tab-button"][data-active="true"] { - color: var(--ls-gray-950) !important; - border-color: var(--ls-gray-950) !important; -} +/* --- Wrap overflowing tab lists instead of horizontal scroll --- */ -html.dark [data-component-part="tab-button"][data-active="true"] { - color: var(--ls-white) !important; - border-color: var(--ls-gray-100) !important; +[data-component-part="tabs-list"] { + flex-wrap: wrap !important; + overflow-x: visible !important; + gap: 4px !important; + border-bottom: none !important; + padding: 16px 0 0 0 !important; } -/* --- Horizontal scroll affordance for tab lists --- */ - -[data-component-part="tabs-list"] { - --fade-size: 32px; - user-select: none; - -webkit-user-select: none; +/* Pill-style tab buttons */ +[data-component-part="tab-button"] { + border-bottom: none !important; + border-radius: 6px !important; + padding: 4px 14px !important; + margin-bottom: 0 !important; } -[data-component-part="tabs-list"].is-scrollable { - cursor: grab; +[data-component-part="tab-button"][data-active="false"] { + border-color: transparent !important; + background: var(--ls-black-02) !important; } -[data-component-part="tabs-list"].is-dragging { - cursor: grabbing !important; - user-select: none; +html.dark [data-component-part="tab-button"][data-active="false"] { + background: var(--ls-white-02) !important; } -[data-component-part="tab-button"] { - cursor: pointer !important; +[data-component-part="tab-button"][data-active="false"]:hover { + border-color: transparent !important; + background: var(--ls-gray-075) !important; } -[data-component-part="tabs-list"].scroll-fade-right { - mask-image: linear-gradient(to right, black calc(100% - var(--fade-size)), transparent); - -webkit-mask-image: linear-gradient(to right, black calc(100% - var(--fade-size)), transparent); +html.dark [data-component-part="tab-button"][data-active="false"]:hover { + border-color: transparent !important; + background: var(--ls-white-06) !important; } -[data-component-part="tabs-list"].scroll-fade-left { - mask-image: linear-gradient(to left, black calc(100% - var(--fade-size)), transparent); - -webkit-mask-image: linear-gradient(to left, black calc(100% - var(--fade-size)), transparent); +[data-component-part="tab-button"][data-active="true"] { + border-color: transparent !important; + background: var(--ls-gray-100) !important; + color: var(--ls-gray-950) !important; } -[data-component-part="tabs-list"].scroll-fade-both { - mask-image: linear-gradient(to right, transparent, black var(--fade-size), black calc(100% - var(--fade-size)), transparent); - -webkit-mask-image: linear-gradient(to right, transparent, black var(--fade-size), black calc(100% - var(--fade-size)), transparent); +html.dark [data-component-part="tab-button"][data-active="true"] { + border-color: transparent !important; + background: var(--ls-white-10) !important; + color: var(--ls-white) !important; } /* =========================================== From 4151b51bda6c62b5d054d7d899f2288224f7fc54 Mon Sep 17 00:00:00 2001 From: patcapulong Date: Tue, 24 Feb 2026 10:05:42 -0800 Subject: [PATCH 4/5] Remove unused scroll-fade.js and its script tag The scroll-fade approach (drag-to-scroll + fade gradients) was superseded by flex-wrap pill-style tabs. The JS file was dead code loading on every page. Co-Authored-By: Claude Opus 4.6 (1M context) --- mintlify/docs.json | 2 +- mintlify/scroll-fade.js | 116 ---------------------------------------- 2 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 mintlify/scroll-fade.js diff --git a/mintlify/docs.json b/mintlify/docs.json index ed4adf87..97723499 100644 --- a/mintlify/docs.json +++ b/mintlify/docs.json @@ -298,7 +298,7 @@ }, "footer": {}, "head": { - "raw": "", + "raw": "", "links": [ { "rel": "preload", diff --git a/mintlify/scroll-fade.js b/mintlify/scroll-fade.js deleted file mode 100644 index 5b363aa6..00000000 --- a/mintlify/scroll-fade.js +++ /dev/null @@ -1,116 +0,0 @@ -(function () { - var EDGE_THRESHOLD = 2; - var DRAG_THRESHOLD = 5; - var DEBOUNCE_MS = 100; - var FADE_SELECTOR = '[data-component-part="tabs-list"]'; - - function updateFadeClasses(el) { - var scrollLeft = el.scrollLeft; - var scrollWidth = el.scrollWidth; - var clientWidth = el.clientWidth; - var canScrollLeft = scrollLeft > EDGE_THRESHOLD; - var canScrollRight = scrollLeft < scrollWidth - clientWidth - EDGE_THRESHOLD; - var overflows = scrollWidth > clientWidth + EDGE_THRESHOLD; - - el.classList.toggle("is-scrollable", overflows); - el.classList.toggle("scroll-fade-both", canScrollLeft && canScrollRight); - el.classList.toggle("scroll-fade-left", canScrollLeft && !canScrollRight); - el.classList.toggle("scroll-fade-right", !canScrollLeft && canScrollRight); - - if (!canScrollLeft && !canScrollRight) { - el.classList.remove( - "scroll-fade-left", - "scroll-fade-right", - "scroll-fade-both" - ); - } - } - - function initDragScroll(el) { - var isDown = false; - var startX = 0; - var startScrollLeft = 0; - var hasDragged = false; - - el.addEventListener("mousedown", function (e) { - if (e.button !== 0) return; - isDown = true; - hasDragged = false; - startX = e.pageX; - startScrollLeft = el.scrollLeft; - }); - - el.addEventListener("mousemove", function (e) { - if (!isDown) return; - var dx = e.pageX - startX; - if (!hasDragged && Math.abs(dx) < DRAG_THRESHOLD) return; - - if (!hasDragged) { - hasDragged = true; - el.classList.add("is-dragging"); - } - - e.preventDefault(); - el.scrollLeft = startScrollLeft - dx; - }); - - function endDrag() { - if (!isDown) return; - isDown = false; - if (hasDragged) { - el.classList.remove("is-dragging"); - // Suppress the click that follows mouseup after a drag - el.addEventListener( - "click", - function (e) { - e.stopPropagation(); - e.preventDefault(); - }, - { once: true, capture: true } - ); - } - hasDragged = false; - } - - el.addEventListener("mouseup", endDrag); - el.addEventListener("mouseleave", endDrag); - } - - function initTabList(el) { - if (el.dataset.scrollFadeInit) return; - el.dataset.scrollFadeInit = "true"; - - updateFadeClasses(el); - el.addEventListener("scroll", function () { updateFadeClasses(el); }, { - passive: true, - }); - - var ro = new ResizeObserver(function () { updateFadeClasses(el); }); - ro.observe(el); - - initDragScroll(el); - } - - function initAll() { - var els = document.querySelectorAll(FADE_SELECTOR); - for (var i = 0; i < els.length; i++) { - initTabList(els[i]); - } - } - - // Debounced version for MutationObserver - var timer = null; - function debouncedInitAll() { - if (timer) clearTimeout(timer); - timer = setTimeout(initAll, DEBOUNCE_MS); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", initAll); - } else { - initAll(); - } - - var observer = new MutationObserver(debouncedInitAll); - observer.observe(document.body, { childList: true, subtree: true }); -})(); From fc0f0bb67c8107278e00cc5cac84689368c6a383 Mon Sep 17 00:00:00 2001 From: patcapulong Date: Tue, 24 Feb 2026 10:24:13 -0800 Subject: [PATCH 5/5] Refine pill tab hover, active states, and scoping - Scope pill-style tabs to API reference pages only - Add general tab hover/active states for light and dark mode - Bump hover backgrounds for visibility (5% black / 8% white) - Active pill: white bg with 0.5px border at 10% alpha - Add transparent border baseline to prevent layout shift - Add 150ms ease transition on bg, border, and color - Bump pill gap from 4px to 6px Co-Authored-By: Claude Opus 4.6 (1M context) --- mintlify/style.css | 56 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/mintlify/style.css b/mintlify/style.css index 111aa335..3dd6aba7 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -2536,57 +2536,79 @@ html.dark #content ul:has([data-component-part="tab-button"]) { /* Inactive tab */ [data-component-part="tab-button"][data-active="false"] { color: var(--ls-gray-500) !important; + border-color: transparent !important; +} + +[data-component-part="tab-button"][data-active="false"]:hover { + color: var(--ls-gray-700) !important; + border-color: var(--ls-gray-300) !important; } html.dark [data-component-part="tab-button"][data-active="false"] { color: var(--ls-gray-600) !important; } -/* --- Wrap overflowing tab lists instead of horizontal scroll --- */ +html.dark [data-component-part="tab-button"][data-active="false"]:hover { + color: var(--ls-gray-400) !important; + border-color: var(--ls-gray-700) !important; +} + +/* Active tab */ +[data-component-part="tab-button"][data-active="true"] { + color: var(--ls-gray-950) !important; + border-color: var(--ls-gray-950) !important; +} + +html.dark [data-component-part="tab-button"][data-active="true"] { + color: var(--ls-white) !important; + border-color: var(--ls-gray-100) !important; +} -[data-component-part="tabs-list"] { +/* --- API Reference: Wrap overflowing tab lists with pill-style buttons --- */ + +#api-playground-2-operation-page [data-component-part="tabs-list"] { flex-wrap: wrap !important; overflow-x: visible !important; - gap: 4px !important; + gap: 6px !important; border-bottom: none !important; padding: 16px 0 0 0 !important; } -/* Pill-style tab buttons */ -[data-component-part="tab-button"] { - border-bottom: none !important; +#api-playground-2-operation-page [data-component-part="tab-button"] { + border: 0.5px solid transparent !important; border-radius: 6px !important; padding: 4px 14px !important; margin-bottom: 0 !important; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease !important; } -[data-component-part="tab-button"][data-active="false"] { +#api-playground-2-operation-page [data-component-part="tab-button"][data-active="false"] { border-color: transparent !important; background: var(--ls-black-02) !important; } -html.dark [data-component-part="tab-button"][data-active="false"] { +html.dark #api-playground-2-operation-page [data-component-part="tab-button"][data-active="false"] { background: var(--ls-white-02) !important; } -[data-component-part="tab-button"][data-active="false"]:hover { +#api-playground-2-operation-page [data-component-part="tab-button"][data-active="false"]:hover { border-color: transparent !important; - background: var(--ls-gray-075) !important; + background: rgba(0, 0, 0, 0.05) !important; } -html.dark [data-component-part="tab-button"][data-active="false"]:hover { +html.dark #api-playground-2-operation-page [data-component-part="tab-button"][data-active="false"]:hover { border-color: transparent !important; - background: var(--ls-white-06) !important; + background: rgba(255, 255, 255, 0.08) !important; } -[data-component-part="tab-button"][data-active="true"] { - border-color: transparent !important; - background: var(--ls-gray-100) !important; +#api-playground-2-operation-page [data-component-part="tab-button"][data-active="true"] { + border: 0.5px solid rgba(0, 0, 0, 0.1) !important; + background: var(--ls-white) !important; color: var(--ls-gray-950) !important; } -html.dark [data-component-part="tab-button"][data-active="true"] { - border-color: transparent !important; +html.dark #api-playground-2-operation-page [data-component-part="tab-button"][data-active="true"] { + border: 0.5px solid rgba(255, 255, 255, 0.1) !important; background: var(--ls-white-10) !important; color: var(--ls-white) !important; }