Compare commits

..

91 Commits

Author SHA1 Message Date
amarantha-k
9af8e7849e Update logo image for dark mode 2026-02-12 11:18:09 -08:00
mDuo13
c62d5090d0 Fix typo causing Bitget logo to not apear 2026-02-11 04:03:45 -08:00
amarantha-k
19ec38e7d8 Update number of wallets to 7 2026-02-10 20:34:49 -08:00
amarantha-k
f119214cea replace corrupted image files 2026-02-10 20:24:23 -08:00
amarantha-k
69d4113536 Add missing image files 2026-02-10 16:37:14 -08:00
amarantha-k
f5988554db Add Bitget wallet to XRP and Uses pages 2026-02-10 16:23:43 -08:00
Amarantha Kulkarni
eecdaccd55 Merge pull request #3478 from TowoLabs/update-towo-labs-bifrost-wallet
Update Towo Labs to Bifrost Wallet in wallets list
2026-02-09 15:14:07 -08:00
oeggert
260f8afd8b Merge pull request #3483 from XRPLF/lending-updates
Lending Cover Clawback
2026-02-05 12:08:17 -08:00
Oliver Eggert
f1eab6f8ae add claw back cover tutorial 2026-02-04 17:50:36 -08:00
Oliver Eggert
934d7c3ff8 run standard js linter 2026-02-04 15:51:42 -08:00
Oliver Eggert
883a6a1d29 add coverClawback code 2026-02-04 15:42:22 -08:00
Erik Frisk
c5f38c1a07 Add Bifrost Wallet to use cases page 2026-02-04 15:50:01 +01:00
Erik Frisk
6a11249b3d Remove unused bitfrost logo 2026-02-03 10:25:50 +01:00
Erik Frisk
f2200e2a51 Update Towo Labs to Bifrost Wallet in wallets list 2026-02-03 10:02:13 +01:00
oeggert
ff6e5932f6 Merge pull request #3459 from XRPLF/support-disabled-amendments
Add support for inactive amendments in tracker
2026-02-02 15:43:35 -08:00
oeggert
776a4f290e Update @theme/components/Amendments.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-02-02 15:42:55 -08:00
oeggert
821ada37ba Update @l10n/ja/translations.yaml
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-02-02 15:42:20 -08:00
oeggert
8d83ddcf63 Merge pull request #3261 from XRPLF/sav-concept-and-reference-docs
Move Single Asset Vault docs from opensource
2026-01-29 15:13:16 -08:00
oeggert
61e700ed2b Merge pull request #3251 from XRPLF/sav-doc-updates
update mpt docs for permissioneddomains
2026-01-29 14:32:28 -08:00
Oliver Eggert
1c35d320d9 update to latest master 2026-01-29 14:11:33 -08:00
Maria Shodunke
63bf0b61ec Update with review comments 2026-01-29 21:48:35 +00:00
oeggert
0324ff52b6 Merge pull request #3473 from XRPLF/fixBatchInnerSigs
add fixbatchinnersigs to known amendments
2026-01-29 12:51:22 -08:00
Oliver Eggert
fdb839295d add fixbatchinnersigs to known amendments 2026-01-29 11:44:14 -08:00
Maria Shodunke
22eaf502a5 Apply suggestions from code review
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-01-29 19:40:55 +00:00
oeggert
104977125b Merge pull request #3418 from XRPLF/ledger-entry-updates
ledger_entry updates
2026-01-29 10:39:19 -08:00
Maria Shodunke
8a1fb62712 Add more information for known amendments + use amendment disclaimer tag 2026-01-29 17:47:17 +00:00
Maria Shodunke
c59e930061 Update Lending Protocol link to concept doc 2026-01-29 17:43:39 +00:00
Maria Shodunke
2a088dfcba Add Tutorials for SAV 2026-01-29 17:43:36 +00:00
Maria Shodunke
3d877de05a Add data field format to reference documentation 2026-01-29 17:41:54 +00:00
Maria Shodunke
8e8e2fc676 Move Single Asset Vault docs from opensource 2026-01-29 17:41:52 +00:00
Oliver Eggert
ade482a349 add loan and loanbroker, also clean up tables and ordering 2026-01-29 09:25:20 -08:00
oeggert
d6c68d6a2d Merge branch 'master' into ledger-entry-updates 2026-01-29 08:47:21 -08:00
oeggert
5d18b40746 Merge pull request #3469 from XRPLF/migrate-lending-docs
migrate lending protocol docs from opensource
2026-01-29 08:39:28 -08:00
Oliver Eggert
a5f8580e0a add ledger entry ID common link 2026-01-28 23:29:54 -08:00
Oliver Eggert
794f588008 fix links to sav pages 2026-01-28 17:19:07 -08:00
Oliver Eggert
799a51f528 add loan and loanbroker ledger entries 2026-01-28 17:07:08 -08:00
Oliver Eggert
fef973d443 update source code links per reviewer suggestion 2026-01-28 15:12:18 -08:00
Oliver Eggert
04c33adeb8 add reviewer suggestion 2026-01-28 14:46:29 -08:00
Rome Reginelli
6611b82f5b Merge pull request #3463 from alloynetworks/patch-1
Change current full history size
2026-01-28 13:29:41 -08:00
Rome Reginelli
1a5762b36f Merge pull request #3438 from XRPLF/dependabot/npm_and_yarn/multi-dcb5c27ca1
Bump react-router and react-router-dom
2026-01-28 13:29:10 -08:00
Rome Reginelli
92b0d8b9d3 Merge pull request #3458 from XRPLF/dependabot/npm_and_yarn/lodash-4.17.23
Bump lodash from 4.17.21 to 4.17.23
2026-01-28 13:28:00 -08:00
Rome Reginelli
b3ff5bf1a4 Merge pull request #3452 from XRPLF/dependabot/npm_and_yarn/diff-4.0.4
Bump diff from 4.0.2 to 4.0.4
2026-01-28 13:26:31 -08:00
oeggert
695007d3db Merge pull request #3470 from XRPLF/release-notes-3.1
3.1 release notes
2026-01-28 13:17:45 -08:00
oeggert
f9aebc83b9 Update blog/2026/rippled-3.1.0.md
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-28 13:17:12 -08:00
Oliver Eggert
fb94fed151 add reviewer suggestions 2026-01-28 12:10:45 -08:00
Oliver Eggert
778c676664 3.1 release notes 2026-01-28 11:27:41 -08:00
Aria Keshmiri
2b6971a89d Merge pull request #3464 from XRPLF/events-updates-2026-01-23
Events updates 2026 01 23
2026-01-28 10:45:00 -08:00
Maria Shodunke
47fb4632bd Merge pull request #3455 from XRPLF/clio-2.7.0-release
Clio 2.7.0 release notes
2026-01-28 09:28:50 +00:00
Oliver Eggert
e32c12a359 migrate lending protocol docs from opensource 2026-01-28 01:02:52 -08:00
Maria Shodunke
ab14511bb4 Update publish date 2026-01-27 10:38:03 +00:00
Maria Shodunke
75f861cfed Fix duplicate test fix note 2026-01-27 10:35:32 +00:00
akcodez
b60c72cdf3 rm extra file 2026-01-26 19:55:44 -08:00
Aria Keshmiri
f73ebc41bd Update community/index.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:54:18 -08:00
Aria Keshmiri
ab7d6a09e9 Update community/index.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:54:11 -08:00
Aria Keshmiri
1c6ade3aba Update community/index.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:54:05 -08:00
Aria Keshmiri
d9d884543b Update community/events.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:53:58 -08:00
Aria Keshmiri
6d2259e30a Update community/events.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:53:53 -08:00
Aria Keshmiri
900a4f01ba Update community/events.page.tsx
Co-authored-by: Rome Reginelli <rome@ripple.com>
2026-01-26 19:53:46 -08:00
akcodez
94e4173441 update casing 2026-01-26 12:09:37 -08:00
akcodez
9dffd66faf update 2026-01-26 11:10:34 -08:00
akcodez
ec6bbff42f add xro community night denver to community page 2026-01-26 10:50:32 -08:00
akcodez
1a0310bf90 update herog 2026-01-26 10:43:48 -08:00
Alloy Networks
d2c2b91b0a Change current full history size 2026-01-25 18:13:48 +05:30
akcodez
baf0f4e819 new events 2026-01-23 11:24:02 -08:00
Oliver Eggert
db9dd303ae remove permission delegation from obsolete table 2026-01-21 17:50:18 -08:00
Oliver Eggert
e181ee6e0f change language from obsolete to disabled 2026-01-21 17:47:54 -08:00
Oliver Eggert
af79cb6cf2 add support for inactive amendments in tracker 2026-01-21 17:18:25 -08:00
dependabot[bot]
98bea864bc Bump lodash from 4.17.21 to 4.17.23
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-21 23:08:18 +00:00
Maria Shodunke
804e51b6b1 Clio 2.7.0 release notes 2026-01-21 18:14:56 +00:00
dependabot[bot]
ca245d72ee Bump diff from 4.0.2 to 4.0.4
Bumps [diff](https://github.com/kpdecker/jsdiff) from 4.0.2 to 4.0.4.
- [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md)
- [Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.2...v4.0.4)

---
updated-dependencies:
- dependency-name: diff
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 18:48:28 +00:00
Oliver Eggert
b0f04a34ed address reviewer comment 2026-01-16 20:50:52 -08:00
dependabot[bot]
ba4ac4c923 Bump react-router and react-router-dom
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) and [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom). These dependencies needed to be updated together.

Updates `react-router` from 6.30.1 to 6.30.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@6.30.3/packages/react-router)

Updates `react-router-dom` from 6.30.1 to 6.30.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.30.3/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 6.30.3
  dependency-type: indirect
- dependency-name: react-router-dom
  dependency-version: 6.30.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 10:13:21 +00:00
oeggert
a5475869c5 Update docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2025-12-19 14:53:17 -08:00
oeggert
053c4bb5a2 Update docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2025-12-19 14:52:05 -08:00
oeggert
9ceb186fb4 Update docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2025-12-19 14:51:50 -08:00
oeggert
18542eb915 Update docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2025-12-19 14:51:03 -08:00
oeggert
d8655b4a0c Update docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2025-12-19 14:50:52 -08:00
Oliver Eggert
3b276c6f19 update error messages 2025-12-17 21:56:34 -08:00
Oliver Eggert
898e698bec alphabetize entries 2025-12-17 21:14:02 -08:00
Oliver Eggert
e5049e53f9 add xchainownedcreateaccountclaimid entry 2025-12-17 20:54:00 -08:00
Oliver Eggert
afd636e69d add xchainownedclaimid entry 2025-12-17 18:50:58 -08:00
Oliver Eggert
a5b914caee remove unnecessary index field from bridge table 2025-12-17 18:06:00 -08:00
Oliver Eggert
df7cd95784 fix bridge entry 2025-12-17 17:53:43 -08:00
Oliver Eggert
5a9357553c add signerlist entry 2025-12-17 12:37:03 -08:00
Oliver Eggert
21d27c36bb add permissioneddomain entry 2025-12-17 12:20:39 -08:00
Oliver Eggert
d153a017b2 remove oracle not enabled status 2025-12-17 10:21:25 -08:00
Oliver Eggert
20a873ae55 fix credential entry and add example 2025-12-17 10:19:43 -08:00
Oliver Eggert
e54a5a4ea6 add negativeunl entry 2025-12-16 21:12:51 -08:00
Oliver Eggert
8f05a58f12 add nftokenoffer 2025-12-10 16:38:06 -08:00
Oliver Eggert
d8849f03f9 update amendments, feesettings, and ledgerhash to mainnet queries 2025-12-10 14:57:05 -08:00
Oliver Eggert
4b8b714e5b add amendments, did, fee, and hashes options 2025-12-10 14:24:50 -08:00
449 changed files with 12117 additions and 78364 deletions

3
.gitignore vendored
View File

@@ -8,8 +8,7 @@ yarn-error.log
*.iml
.venv/
_code-samples/*/js/package-lock.json
*.css.map
_code-samples/*/js/*[Ss]etup.json
# PHP
composer.lock
.cursor/

View File

@@ -155,6 +155,8 @@ amendment.table.status: ステータス
amendment.status.enabled: 有効
amendment.status.eta: 予定
amendment.status.openForVoting: 投票中
amendment.status.inactive: 無効
amendment.status.inactiveButton: 詳細を取得する
# index.page.tsx
home.hero.h1part1: ビジネスのための

View File

@@ -23,6 +23,7 @@ type AmendmentsCachePayload = {
// API data caching
const amendmentsEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/vote/main/'
const amendmentsInfoEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/info/main/'
const amendmentsCacheKey = 'xrpl.amendments.mainnet.cache'
const amendmentsTTL = 15 * 60 * 1000 // 15 minutes in milliseconds
@@ -180,6 +181,8 @@ function AmendmentBadge(props: { amendment: Amendment }) {
const enabledLabel = translate("amendment.status.enabled", "Enabled")
const votingLabel = translate("amendment.status.openForVoting", "Open for Voting")
const etaLabel = translate("amendment.status.eta", "Expected")
const inactiveLabel = translate("amendment.status.inactive", "Inactive")
const inactiveButton = translate("amendment.status.inactiveButton", "Get details")
React.useEffect(() => {
const amendment = props.amendment
@@ -202,10 +205,16 @@ function AmendmentBadge(props: { amendment: Amendment }) {
else if (amendment.consensus) {
setStatus(`${votingLabel}: ${amendment.consensus}`)
setColor('80d0e0')
setHref(undefined) // No link for voting amendments
setHref(undefined)
}
}, [props.amendment, enabledLabel, etaLabel, votingLabel])
// Fallback: amendment is inactive
else {
setStatus(`${inactiveLabel}: ${inactiveButton}`)
setColor('lightgrey')
setHref(`/resources/known-amendments#${amendment.name.toLowerCase()}`)
}
}, [props.amendment, enabledLabel, etaLabel, votingLabel, inactiveLabel])
// Split the status at the colon to create two-color badge
const parts = status.split(':')
const label = shieldsIoEscape(parts[0])
@@ -257,15 +266,32 @@ export function AmendmentDisclaimer(props: {
const response = await fetch(amendmentsEndpoint)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data: AmendmentsResponse = await response.json()
writeAmendmentsCache(data.amendments)
const found = data.amendments.find(a => a.name === props.name)
// 3. If not found in live data, try the info endpoint.
if (!found) {
throw new Error(`Couldn't find ${props.name} amendment in status table.`)
const infoResponse = await fetch(amendmentsInfoEndpoint)
if (!infoResponse.ok) {
throw new Error(`HTTP error from info endpoint! Status: ${infoResponse.status}`)
}
const infoData: AmendmentsResponse = await infoResponse.json()
const foundInInfo = infoData.amendments.find(a => a.name === props.name)
if (!foundInInfo) {
throw new Error(`Couldn't find ${props.name} amendment in status tables.`)
}
setStatus(foundInInfo)
return
}
setStatus(found)
@@ -389,6 +415,8 @@ export function Badge(props: {
"更新": "blue", // ja: updated in
"in development": "lightgrey",
"開発中": "lightgrey", // ja: in development
"inactive": "lightgrey",
"無効": "lightgrey" // ja: inactive
}
let childstrings = ""

View File

@@ -1,152 +1,449 @@
import * as React from "react";
import { useSearchDialog } from "@redocly/theme/core/hooks";
import { SearchDialog } from "@redocly/theme/components/Search/SearchDialog";
import { useThemeConfig, useThemeHooks } from "@redocly/theme/core/hooks";
import { LanguagePicker } from "@redocly/theme/components/LanguagePicker/LanguagePicker";
import { slugify } from "../../helpers";
import { Link } from "@redocly/theme/components/Link/Link";
import { ColorModeSwitcher } from "@redocly/theme/components/ColorModeSwitcher/ColorModeSwitcher";
import { Search } from "@redocly/theme/components/Search/Search";
import arrowUpRight from "../../../static/img/icons/arrow-up-right-custom.svg";
import moment from "moment-timezone";
// Import from modular components
import { AlertBanner } from "./components/AlertBanner";
import { NavLogo } from "./components/NavLogo";
import { NavItems } from "./components/NavItems";
import { NavControls, HamburgerButton } from "./controls";
import { DevelopSubmenu, UseCasesSubmenu, CommunitySubmenu, NetworkSubmenu } from "./submenus";
import { MobileMenu } from "./mobile-menu";
import { alertBanner } from "./constants/navigation";
// @ts-ignore
// Re-export AlertBanner for backwards compatibility
export { AlertBanner } from "./components/AlertBanner";
const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Props interface for Navbar (extensible for future use)
interface NavbarProps {
className?: string;
}
export function AlertBanner({ message, button, link, show }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef(null);
const [displayDate, setDisplayDate] = React.useState("JUNE 10-12");
/**
* Main Navbar Component.
* Renders the complete navigation bar including:
* - Alert banner (when enabled)
* - Logo
* - Navigation items with desktop submenus
* - Control buttons (search, theme toggle, language)
* - Mobile menu
*/
export function Navbar(_props: NavbarProps = {}) {
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
const [activeSubmenu, setActiveSubmenu] = React.useState<string | null>(null);
const [closingSubmenu, setClosingSubmenu] = React.useState<string | null>(null);
const submenuTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
const closingTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
// Use Redocly's search dialog hook - shared across navbar and mobile menu
const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose } = useSearchDialog();
const handleHamburgerClick = () => {
setMobileMenuOpen(true);
};
const handleMobileMenuClose = () => {
setMobileMenuOpen(false);
};
const handleSubmenuMouseEnter = (itemLabel: string) => {
// Clear any pending close/closing timeouts
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
submenuTimeoutRef.current = null;
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
closingTimeoutRef.current = null;
}
// Cancel closing state and activate the new submenu
setClosingSubmenu(null);
setActiveSubmenu(itemLabel);
};
const handleSubmenuMouseLeave = () => {
submenuTimeoutRef.current = setTimeout(() => {
// Start closing animation
const currentSubmenu = activeSubmenu;
if (currentSubmenu) {
setClosingSubmenu(currentSubmenu);
setActiveSubmenu(null);
// After animation completes (300ms), clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350); // Slightly longer than animation to ensure completion
}
}, 150);
};
const handleSubmenuClose = () => {
// Close submenu immediately (for keyboard navigation)
if (activeSubmenu) {
setClosingSubmenu(activeSubmenu);
setActiveSubmenu(null);
// After animation completes, clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350);
}
};
// Handle scroll lock when submenu is open or closing
React.useEffect(() => {
if (activeSubmenu || closingSubmenu) {
document.body.classList.add('bds-submenu-open');
} else {
document.body.classList.remove('bds-submenu-open');
}
return () => {
document.body.classList.remove('bds-submenu-open');
const calculateCountdown = () => {
// Calculate days until June 11, 2025 8AM Singapore time
// This will automatically adjust for the user's timezone
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
// Show countdown if event is in the future, otherwise show the provided date
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
// Check if it's today
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
}, [activeSubmenu, closingSubmenu]);
// Calculate immediately
calculateCountdown();
// Update every hour
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
// Attach the event listener
banner.addEventListener("mouseenter", handleMouseEnter);
// Clean up the event listener on unmount
return () => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
}
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
const navbarClasses = [
"bds-navbar",
alertBanner.show ? "bds-navbar--with-banner" : ""
].filter(Boolean).join(" ");
if (show) {
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label="Get Tickets for the APEX 2025 Event"
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img
className="button-icon"
src={arrowUpRight}
alt="Get Tickets Icon"
/>
</div>
</a>
);
}
return null;
}
export function Navbar(props) {
// const [isOpen, setIsOpen] = useMobileMenu(false);
const themeConfig = useThemeConfig();
const { useL10n } = useThemeHooks();
const { changeLanguage } = useL10n();
const menu = themeConfig.navbar?.items;
const logo = themeConfig.logo;
const { href, altText, items } = props;
const pathPrefix = "";
const navItems = menu.map((item, index) => {
if (item.type === "group") {
return (
<NavDropdown
key={index}
label={item.label}
labelTranslationKey={item.labelTranslationKey}
items={item.items}
pathPrefix={pathPrefix}
/>
);
} else {
return (
<NavItem key={index}>
<Link to={item.link} className="nav-link">
{item.label}
</Link>
</NavItem>
);
}
});
React.useEffect(() => {
// Turns out jQuery is necessary for firing events on Bootstrap v4
// dropdowns. These events set classes so that the search bar and other
// submenus collapse on mobile when you expand one submenu.
const dds = $("#topnav-pages .dropdown");
const top_main_nav = document.querySelector("#top-main-nav");
dds.on("show.bs.dropdown", (evt) => {
top_main_nav.classList.add("submenu-expanded");
});
dds.on("hidden.bs.dropdown", (evt) => {
top_main_nav.classList.remove("submenu-expanded");
});
// Close navbar on .dropdown-item click
const toggleNavbar = () => {
const navbarToggler = document.querySelector(".navbar-toggler");
const isNavbarCollapsed =
navbarToggler.getAttribute("aria-expanded") === "true";
if (isNavbarCollapsed) {
navbarToggler?.click(); // Simulate click to toggle navbar
}
};
const dropdownItems = document.querySelectorAll(".dropdown-item");
dropdownItems.forEach((item) => {
item.addEventListener("click", toggleNavbar);
});
// Cleanup function to remove event listeners
return () => {
dropdownItems.forEach((item) => {
item.removeEventListener("click", toggleNavbar);
});
};
}, []);
return (
<>
<AlertBanner {...alertBanner} />
{/* Backdrop blur overlay when submenu is open or closing */}
<div
className={`bds-submenu-backdrop ${activeSubmenu || closingSubmenu ? 'bds-submenu-backdrop--active' : ''}`}
onClick={() => setActiveSubmenu(null)}
/>
<header
className={navbarClasses}
onMouseLeave={handleSubmenuMouseLeave}
>
<div className="bds-navbar__content">
<NavLogo />
<NavItems activeSubmenu={activeSubmenu} onSubmenuEnter={handleSubmenuMouseEnter} onSubmenuClose={handleSubmenuClose} />
<NavControls onSearch={onSearchOpen} />
<HamburgerButton onClick={handleHamburgerClick} />
</div>
{/* Submenus positioned relative to navbar */}
<div onMouseEnter={() => activeSubmenu && handleSubmenuMouseEnter(activeSubmenu)}>
<DevelopSubmenu isActive={activeSubmenu === 'Develop'} isClosing={closingSubmenu === 'Develop'} onClose={handleSubmenuClose} />
<UseCasesSubmenu isActive={activeSubmenu === 'Use Cases'} isClosing={closingSubmenu === 'Use Cases'} onClose={handleSubmenuClose} />
<CommunitySubmenu isActive={activeSubmenu === 'Community'} isClosing={closingSubmenu === 'Community'} onClose={handleSubmenuClose} />
<NetworkSubmenu isActive={activeSubmenu === 'Network'} isClosing={closingSubmenu === 'Network'} onClose={handleSubmenuClose} />
</div>
</header>
<MobileMenu isOpen={mobileMenuOpen} onClose={handleMobileMenuClose} onSearch={onSearchOpen} />
{/* Render SearchDialog when open - this is the actual search modal */}
{isSearchOpen && <SearchDialog onClose={onSearchClose} />}
<NavWrapper belowAlertBanner={alertBanner.show}>
<LogoBlock to={href} img={logo} alt={altText} />
<NavControls>
<MobileMenuIcon />
</NavControls>
<TopNavCollapsible>
<NavItems>
{navItems}
<div id="topnav-search" className="nav-item search">
<Search className="topnav-search" />
</div>
<div id="topnav-language" className="nav-item">
<LanguagePicker
onChangeLanguage={changeLanguage}
onlyIcon
alignment="end"
/>
</div>
<div id="topnav-theme" className="nav-item">
<ColorModeSwitcher />
</div>
</NavItems>
</TopNavCollapsible>
</NavWrapper>
</>
);
}
export function TopNavCollapsible({ children }) {
return (
<div
className="collapse navbar-collapse justify-content-between"
id="top-main-nav"
>
{children}
</div>
);
}
export function NavDropdown(props) {
const { label, items, pathPrefix, labelTranslationKey } = props;
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const dropdownGroups = items.map((item, index) => {
if (item.items) {
const groupLinks = item.items.map((item2, index2) => {
const cls2 = item2.external
? "dropdown-item external-link"
: "dropdown-item";
let item2_href = item2.link;
if (item2_href && !item2_href.match(/^https?:/)) {
item2_href = pathPrefix + item2_href;
}
//conditional specific for brand kit
if (item2.link === "/XRPL_Brand_Kit.zip") {
return (
<a target="_blank" key={index2} href="/XRPL_Brand_Kit.zip" className={cls2}>
{translate(item2.labelTranslationKey, item2.label)}
</a>
);
}
return (
<Link key={index2} className={cls2} to={item2_href}>
{translate(item2.labelTranslationKey, item2.label)}
</Link>
);
});
const clnm = "navcol col-for-" + slugify(item.label);
return (
<div key={index} className={clnm}>
<h5 className="dropdown-item">
{translate(item.labelTranslationKey, item.label)}
</h5>
{groupLinks}
</div>
);
} else if (item.icon) {
const hero_id = "dropdown-hero-for-" + slugify(label);
const img_alt = item.label + " icon";
let hero_href = item.link;
if (hero_href && !hero_href.match(/^https?:/)) {
hero_href = pathPrefix + hero_href;
}
const splitlabel = item.label.split(" || ");
let splittranslationkey = ["", ""];
if (item.labelTranslationKey) {
splittranslationkey = item.labelTranslationKey.split(" || ");
}
const newlabel = translate(splittranslationkey[0], splitlabel[0]);
const description = translate(splittranslationkey[1], splitlabel[1]); // splitlabel[1] might be undefined, that's ok
return (
<Link
key={index}
className="dropdown-item dropdown-hero"
id={hero_id}
to={hero_href}
>
<img id={item.hero} alt={img_alt} src={item.icon} />
<div className="dropdown-hero-text">
<h4>{newlabel}</h4>
<p>{description}</p>
</div>
</Link>
);
} else {
const cls = item.external
? "dropdown-item ungrouped external-link"
: "dropdown-item ungrouped";
let item_href = item.link;
if (item_href && !item_href.match(/^https?:/)) {
item_href = pathPrefix + item_href;
}
return (
<Link key={index} className={cls} to={item_href}>
{translate(item.labelTranslationKey, item.label)}
</Link>
);
}
});
const toggler_id = "topnav_" + slugify(label);
const dd_id = "topnav_dd_" + slugify(label);
return (
<li className="nav-item dropdown">
<a
className="nav-link dropdown-toggle"
href="#"
id={toggler_id}
role="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span>{translate(labelTranslationKey, label)}</span>
</a>
<div className="dropdown-menu" aria-labelledby={toggler_id} id={dd_id}>
{dropdownGroups}
</div>
</li>
);
}
export function NavWrapper(props) {
return (
<nav
className="top-nav navbar navbar-expand-lg navbar-dark fixed-top"
style={props.belowAlertBanner ? { marginTop: "52px" } : {}}
>
{props.children}
</nav>
);
}
export function NavControls(props) {
return (
<button
className="navbar-toggler collapsed"
type="button"
data-toggle="collapse"
data-target="#top-main-nav"
aria-controls="navbarHolder"
aria-expanded="false"
aria-label="Toggle navigation"
>
{props.children}
</button>
);
}
export function MobileMenuIcon() {
return (
<span className="navbar-toggler-icon">
<div></div>
</span>
);
}
export function GetStartedButton() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<Link
className="btn btn-primary"
to={"/docs/tutorials"}
style={{ height: "38px", paddingTop: "11px" }}
>
{translate("Get Started")}
</Link>
);
}
export function NavItems(props) {
return (
<ul className="nav navbar-nav" id="topnav-pages">
{props.children}
</ul>
);
}
export function NavItem(props) {
return <li className="nav-item">{props.children}</li>;
}
export function LogoBlock(props) {
const { to, img, altText } = props;
return (
<Link className="navbar-brand" to="/">
<img className="logo" alt={"XRP LEDGER"} height="40" src="data:," />
</Link>
);
}
export class ThemeToggle extends React.Component {
auto_update_theme() {
const upc = window.localStorage.getItem("user-prefers-color");
let theme = "dark"; // Default to dark theme
if (!upc) {
// User hasn't saved a preference specifically for this site; check
// the browser-level preferences.
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: light)").matches
) {
theme = "light";
}
} else {
// Follow user's saved setting.
theme = upc == "light" ? "light" : "dark";
}
const disable_theme = theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(theme);
document.documentElement.classList.remove(disable_theme);
}
user_choose_theme() {
const new_theme = document.documentElement.classList.contains("dark")
? "light"
: "dark";
window.localStorage.setItem("user-prefers-color", new_theme);
document.body.style.transition = "background-color .2s ease";
const disable_theme = new_theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(new_theme);
document.documentElement.classList.remove(disable_theme);
}
render() {
return (
<div className="nav-item" id="topnav-theme">
<form className="form-inline">
<div
className="custom-control custom-theme-toggle form-inline-item"
title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Toggle Dark Mode"
>
<input
type="checkbox"
className="custom-control-input"
id="css-toggle-btn"
onClick={this.user_choose_theme}
/>
<label className="custom-control-label" htmlFor="css-toggle-btn">
<span className="d-lg-none">Light/Dark Theme</span>
</label>
</div>
</form>
</div>
);
}
componentDidMount() {
this.auto_update_theme();
}
}

View File

@@ -1,82 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import moment from "moment-timezone";
import { arrowUpRight } from "../constants/icons";
interface AlertBannerProps {
message: string;
button: string;
link: string;
show: boolean;
}
/**
* Alert Banner Component.
* Displays a promotional banner at the top of the page.
*/
export function AlertBanner({ message, button, link, show }: AlertBannerProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef<HTMLAnchorElement>(null);
// Use null initial state to avoid hydration mismatch - server and client both render null initially
const [displayDate, setDisplayDate] = React.useState<string | null>(null);
React.useEffect(() => {
const calculateCountdown = () => {
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
calculateCountdown();
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
banner.addEventListener("mouseenter", handleMouseEnter);
return () => {
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
if (!show) return null;
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label={translate("Get Tickets for the APEX 2025 Event")}
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate ?? translate("JUNE 10-12")}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img className="button-icon" src={arrowUpRight} alt="" />
</div>
</a>
);
}

View File

@@ -1,114 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { navItems } from "../constants/navigation";
interface NavItemsProps {
activeSubmenu: string | null;
onSubmenuEnter: (itemLabel: string) => void;
onSubmenuClose?: () => void;
}
/**
* Nav Items Component.
* Centered navigation links with submenu support.
* ARIA compliant with full keyboard navigation support.
*/
export function NavItems({ activeSubmenu, onSubmenuEnter, onSubmenuClose }: NavItemsProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [activeItem, setActiveItem] = React.useState<string | null>(null);
const handleMouseEnter = (itemLabel: string, hasSubmenu: boolean) => {
setActiveItem(itemLabel);
if (hasSubmenu) {
onSubmenuEnter(itemLabel);
}
};
const handleMouseLeave = (hasSubmenu: boolean) => {
if (!hasSubmenu) {
setActiveItem(null);
}
// Don't close submenu on leave - let the parent Navbar handle that
};
const handleKeyDown = (event: React.KeyboardEvent, itemLabel: string) => {
switch (event.key) {
case 'Enter':
case ' ':
event.preventDefault();
// Toggle submenu on Enter/Space
if (activeSubmenu === itemLabel) {
onSubmenuClose?.();
} else {
onSubmenuEnter(itemLabel);
}
break;
case 'Escape':
event.preventDefault();
onSubmenuClose?.();
break;
case 'Tab':
// If submenu is open and Tab is pressed (without Shift), move focus into submenu
if (activeSubmenu === itemLabel && !event.shiftKey) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
case 'ArrowDown':
// If submenu is open, move focus into submenu
if (activeSubmenu === itemLabel) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
}
};
// Sync activeItem with activeSubmenu state
React.useEffect(() => {
if (!activeSubmenu) {
setActiveItem(null);
}
}, [activeSubmenu]);
return (
<nav className="bds-navbar__items" aria-label={translate("Main navigation")}>
{navItems.map((item) => (
item.hasSubmenu ? (
<button
key={item.label}
type="button"
className={`bds-navbar__item ${activeItem === item.label || activeSubmenu === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, true)}
onMouseLeave={() => handleMouseLeave(true)}
onKeyDown={(e) => handleKeyDown(e, item.label)}
aria-expanded={activeSubmenu === item.label}
aria-haspopup="menu"
>
{translate(item.labelTranslationKey, item.label)}
</button>
) : (
<BdsLink
key={item.label}
href={item.href}
className={`bds-navbar__item ${activeItem === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, false)}
onMouseLeave={() => handleMouseLeave(false)}
variant="inline"
>
{translate(item.labelTranslationKey, item.label)}
</BdsLink>
)
))}
</nav>
);
}

View File

@@ -1,34 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { xrpSymbolBlack, xrpLogotypeBlack, xrpLedgerNav } from "../constants/icons";
/**
* Nav Logo Component.
* Shows symbol on desktop/mobile, full logotype on tablet.
* On desktop hover, the "XRP LEDGER" text animates out to the right.
*/
export function NavLogo() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} variant="inline">
<img
src={xrpSymbolBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-symbol"
/>
<img
src={xrpLedgerNav}
alt=""
className="bds-navbar__logo-text"
/>
<img
src={xrpLogotypeBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-full"
/>
</BdsLink>
);
}

View File

@@ -1,5 +0,0 @@
// Re-export navbar components
export { AlertBanner } from './AlertBanner';
export { NavLogo } from './NavLogo';
export { NavItems } from './NavItems';

View File

@@ -1,83 +0,0 @@
// Navbar icon imports
// Main navbar icons
export { default as xrpSymbolBlack } from "../../../../static/img/navbar/xrp-symbol-black.svg";
export { default as xrpLogotypeBlack } from "../../../../static/img/navbar/xrp-logotype-black.svg";
export { default as xrpLedgerNav } from "../../../../static/img/navbar/xrp-ledger-nav.svg";
export { default as searchIcon } from "../../../../static/img/navbar/search-icon.svg";
export { default as modeToggleIcon } from "../../../../static/img/navbar/mode-toggle.svg";
export { default as globeIcon } from "../../../../static/img/navbar/globe-icon.svg";
export { default as chevronDown } from "../../../../static/img/navbar/chevron-down.svg";
export { default as hamburgerIcon } from "../../../../static/img/navbar/hamburger-icon.svg";
export { default as arrowUpRight } from "../../../../static/img/icons/arrow-up-right-custom.svg";
// Wallet icons for submenu
export { default as greenWallet } from "../../../../static/img/navbar/green-wallet.svg";
export { default as lilacWallet } from "../../../../static/img/navbar/lilac-wallet.svg";
export { default as yellowWallet } from "../../../../static/img/navbar/yellow-wallet.svg";
export { default as pinkWallet } from "../../../../static/img/navbar/pink-wallet.svg";
export { default as blueWallet } from "../../../../static/img/navbar/blue-wallet.svg";
// Develop submenu icons
export { default as devHomeIcon } from "../../../../static/img/navbar/dev_home.svg";
export { default as learnIcon } from "../../../../static/img/navbar/learn.svg";
export { default as codeSamplesIcon } from "../../../../static/img/navbar/code_samples.svg";
export { default as docsIcon } from "../../../../static/img/navbar/docs.svg";
export { default as clientLibIcon } from "../../../../static/img/navbar/client_lib.svg";
// Use Cases submenu icons
export { default as paymentsIcon } from "../../../../static/img/navbar/payments.svg";
export { default as tokenizationIcon } from "../../../../static/img/navbar/tokenization.svg";
export { default as creditIcon } from "../../../../static/img/navbar/credit.svg";
export { default as tradingIcon } from "../../../../static/img/navbar/trading.svg";
// Community submenu icons
export { default as communityIcon } from "../../../../static/img/navbar/community.svg";
// Network submenu icons
export { default as insightsIcon } from "../../../../static/img/navbar/insights.svg";
export { default as resourcesIcon } from "../../../../static/img/navbar/resources.svg";
// Network submenu pattern images (used for both light and dark mode)
export { default as resourcesIconPattern } from "../../../../static/img/navbar/resources-icon.svg";
export { default as insightsIconPattern } from "../../../../static/img/navbar/insights-icon.svg";
// Wallet icon mapping for dynamic icon lookup
import greenWallet from "../../../../static/img/navbar/green-wallet.svg";
import lilacWallet from "../../../../static/img/navbar/lilac-wallet.svg";
import yellowWallet from "../../../../static/img/navbar/yellow-wallet.svg";
import pinkWallet from "../../../../static/img/navbar/pink-wallet.svg";
import blueWallet from "../../../../static/img/navbar/blue-wallet.svg";
import devHomeIcon from "../../../../static/img/navbar/dev_home.svg";
import learnIcon from "../../../../static/img/navbar/learn.svg";
import codeSamplesIcon from "../../../../static/img/navbar/code_samples.svg";
import docsIcon from "../../../../static/img/navbar/docs.svg";
import clientLibIcon from "../../../../static/img/navbar/client_lib.svg";
import paymentsIcon from "../../../../static/img/navbar/payments.svg";
import tokenizationIcon from "../../../../static/img/navbar/tokenization.svg";
import creditIcon from "../../../../static/img/navbar/credit.svg";
import tradingIcon from "../../../../static/img/navbar/trading.svg";
import communityIcon from "../../../../static/img/navbar/community.svg";
import insightsIcon from "../../../../static/img/navbar/insights.svg";
import resourcesIcon from "../../../../static/img/navbar/resources.svg";
export const walletIcons: Record<string, string> = {
green: greenWallet,
lilac: lilacWallet,
yellow: yellowWallet,
pink: pinkWallet,
blue: blueWallet,
dev_home: devHomeIcon,
learn: learnIcon,
code_samples: codeSamplesIcon,
docs: docsIcon,
client_lib: clientLibIcon,
payments: paymentsIcon,
tokenization: tokenizationIcon,
credit: creditIcon,
trading: tradingIcon,
community: communityIcon,
insights: insightsIcon,
resources: resourcesIcon,
};

View File

@@ -1,4 +0,0 @@
// Re-export all constants
export * from './icons';
export * from './navigation';

View File

@@ -1,160 +0,0 @@
import type { NavItem, SubmenuItemBase, SubmenuItemWithChildren, SubmenuItem, NetworkSubmenuSection } from '../types';
// Alert Banner Configuration
export const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Main navigation items
export const navItems: NavItem[] = [
{ label: "Develop", labelTranslationKey: "navbar.develop", href: "/develop", hasSubmenu: true },
{ label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/use-cases", hasSubmenu: true },
{ label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true },
{ label: "Network", labelTranslationKey: "navbar.network", href: "/resources", hasSubmenu: true },
];
// Develop submenu data structure
export const developSubmenuData: {
left: SubmenuItemBase[];
right: SubmenuItemWithChildren[];
} = {
left: [
{ label: "Developer's Home", href: "/develop", icon: "dev_home" },
{ label: "Learn", href: "https://learn.xrpl.org", icon: "learn" },
{ label: "Code Samples", href: "/resources/code-samples", icon: "code_samples" },
],
right: [
{
label: "Docs",
href: "/docs",
icon: "docs",
children: [
{ label: "API Reference", href: "/references" },
{ label: "Tutorials", href: "/docs/tutorials" },
{ label: "Concepts", href: "/concepts" },
{ label: "Infrastructure", href: "/docs/infrastructure" },
],
},
{
label: "Client Libraries",
href: "#",
icon: "client_lib",
children: [
{ label: "JavaScript", href: "#" },
{ label: "Python", href: "#" },
{ label: "PHP", href: "#" },
{ label: "Go", href: "#" },
],
},
],
};
// Use Cases submenu data structure
export const useCasesSubmenuData: {
left: SubmenuItemWithChildren[];
right: SubmenuItemWithChildren[];
} = {
left: [
{
label: "Payments",
href: "/use-cases/payments",
icon: "payments",
children: [
{ label: "Direct XRP Payments", href: "/use-cases/payments/direct-xrp-payments" },
{ label: "Cross-currency Payments", href: "/use-cases/payments/cross-currency-payments" },
{ label: "Escrow", href: "/use-cases/payments/escrow" },
{ label: "Checks", href: "/use-cases/payments/checks" },
],
},
{
label: "Tokenization",
href: "/use-cases/tokenization",
icon: "tokenization",
children: [
{ label: "Stablecoin", href: "/use-cases/tokenization/stablecoin" },
{ label: "NFT", href: "/use-cases/tokenization/nft" },
],
},
],
right: [
{
label: "Credit",
href: "/use-cases/credit",
icon: "credit",
children: [
{ label: "Lending", href: "/use-cases/credit/lending" },
{ label: "Collateralization", href: "/use-cases/credit/collateralization" },
{ label: "Sustainability", href: "/use-cases/credit/sustainability" },
],
},
{
label: "Trading",
href: "/use-cases/trading",
icon: "trading",
children: [
{ label: "DEX", href: "/use-cases/trading/dex" },
{ label: "Permissioned Trading", href: "/use-cases/trading/permissioned-trading" },
{ label: "AMM", href: "/use-cases/trading/amm" },
],
},
],
};
// Community submenu data structure
export const communitySubmenuData: {
left: SubmenuItem[];
right: SubmenuItem[];
} = {
left: [
{
label: "Community",
href: "/community",
icon: "community",
children: [
{ label: "Events", href: "/community/events" },
{ label: "Blog", href: "/blog" },
],
},
{ label: "Funding", href: "/community/developer-funding", icon: "code_samples" },
],
right: [
{
label: "Contribute",
href: "/community/contribute",
icon: "client_lib",
children: [
{ label: "Bug Bounty", href: "/blog/2020/rippled-1.5.0#bug-bounties-and-responsible-disclosures" },
{ label: "Research", href: "https://xls.xrpl.org/" },
],
},
{ label: "Ecosystem Map", href: "/about/uses", icon: "learn" },
],
};
// Network submenu data
export const networkSubmenuData: NetworkSubmenuSection[] = [
{
label: "Resources",
href: "/resources",
icon: "resources",
children: [
{ label: "About", href: "/about/history" },
{ label: "XRPL Brand Kit", href: "/community/brand-kit" },
],
patternColor: 'lilac',
},
{
label: "Insights",
href: "/insights",
icon: "insights",
children: [
{ label: "Explorer", href: "https://livenet.xrpl.org" },
{ label: "Amendment Voting Status", href: "https://xrpl.org/resources/known-amendments" },
],
patternColor: 'green',
},
];

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { hamburgerIcon } from "../constants/icons";
interface HamburgerButtonProps {
onClick?: () => void;
}
/**
* Hamburger Menu Button Component.
* Mobile-only button that opens the mobile menu.
*/
export function HamburgerButton({ onClick }: HamburgerButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={hamburgerIcon} ariaLabel={translate("Open menu")} className="bds-navbar__hamburger" onClick={onClick} />;
}

View File

@@ -1,28 +0,0 @@
interface IconButtonProps {
/** The icon image source */
icon: string;
/** Accessible label for the button */
ariaLabel: string;
/** Optional click handler */
onClick?: () => void;
/** CSS class name for styling variants */
className?: string;
}
/**
* Unified Icon Button component.
* Used for search, mode toggle, hamburger menu, and other icon-only buttons.
*/
export function IconButton({ icon, ariaLabel, onClick, className = "bds-navbar__icon" }: IconButtonProps) {
return (
<button
type="button"
className={className}
aria-label={ariaLabel}
onClick={onClick}
>
<img src={icon} alt="" />
</button>
);
}

View File

@@ -1,85 +0,0 @@
import * as React from "react";
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
interface LanguageDropdownProps {
isOpen: boolean;
onClose: () => void;
}
/**
* Language Dropdown Component.
* Displays available language options in a dropdown menu.
* Based on Figma design: dark background with rounded corners.
*/
export function LanguageDropdown({ isOpen, onClose }: LanguageDropdownProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const dropdownRef = React.useRef<HTMLDivElement>(null);
// Handle clicking outside to close
React.useEffect(() => {
if (!isOpen) return;
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
// Check if click was on the language pill (parent trigger)
const target = event.target as HTMLElement;
if (!target.closest('.bds-navbar__lang-pill')) {
onClose();
}
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscape);
};
}, [isOpen, onClose]);
if (!isOpen || locales.length < 2) {
return null;
}
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
onClose();
};
return (
<div
ref={dropdownRef}
className="bds-lang-dropdown"
role="menu"
aria-label={translate("Language selection")}
>
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
aria-current={isActive ? 'true' : undefined}
>
{locale.name || locale.code}
</button>
);
})}
</div>
);
}

View File

@@ -1,53 +0,0 @@
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
import { globeIcon, chevronDown } from "../constants/icons";
interface LanguagePillProps {
onClick?: () => void;
isOpen?: boolean;
}
/**
* Get short display name for a locale code.
* e.g., "en-US" -> "En", "ja" -> "日本語"
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
// Map locale codes to short display names
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
/**
* Language Pill Button Component.
* Shows current language and opens language selector.
*/
export function LanguagePill({ onClick, isOpen }: LanguagePillProps) {
const { currentLocale } = useLanguagePicker();
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const displayName = getLocaleShortName(currentLocale?.code);
return (
<button
type="button"
className={`bds-navbar__lang-pill ${isOpen ? 'bds-navbar__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isOpen}
aria-haspopup="menu"
onClick={onClick}
>
<img src={globeIcon} alt="" className="bds-navbar__lang-pill-icon" />
<span className="bds-navbar__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-navbar__lang-pill-chevron" />
</span>
</button>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { modeToggleIcon } from "../constants/icons";
interface ModeToggleButtonProps {
onClick?: () => void;
}
/**
* Mode Toggle Button Component.
* Icon button that toggles between light and dark mode.
*/
export function ModeToggleButton({ onClick }: ModeToggleButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={modeToggleIcon} ariaLabel={translate("Toggle color mode")} onClick={onClick} />;
}

View File

@@ -1,46 +0,0 @@
import * as React from "react";
import { SearchButton } from "./SearchButton";
import { ModeToggleButton } from "./ModeToggleButton";
import { LanguagePill } from "./LanguagePill";
import { LanguageDropdown } from "./LanguageDropdown";
interface NavControlsProps {
onSearch?: () => void;
}
/**
* Nav Controls Component.
* Right side of the navbar containing search, mode toggle, and language selector.
*/
export function NavControls({ onSearch }: NavControlsProps) {
const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = React.useState(false);
const handleModeToggle = () => {
// Toggle between light and dark theme
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const handleLanguageClick = () => {
setIsLanguageDropdownOpen(!isLanguageDropdownOpen);
};
const handleLanguageDropdownClose = () => {
setIsLanguageDropdownOpen(false);
};
return (
<div className="bds-navbar__controls">
<SearchButton onClick={onSearch} />
<ModeToggleButton onClick={handleModeToggle} />
<div className="bds-navbar__lang-wrapper">
<LanguagePill onClick={handleLanguageClick} isOpen={isLanguageDropdownOpen} />
<LanguageDropdown isOpen={isLanguageDropdownOpen} onClose={handleLanguageDropdownClose} />
</div>
</div>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { searchIcon } from "../constants/icons";
interface SearchButtonProps {
onClick?: () => void;
}
/**
* Search Button Component.
* Icon button that triggers the search modal.
*/
export function SearchButton({ onClick }: SearchButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={searchIcon} ariaLabel={translate("Search")} onClick={onClick} />;
}

View File

@@ -1,11 +0,0 @@
// Unified base component
export { IconButton } from './IconButton';
// Specific button implementations (use IconButton internally)
export { NavControls } from './NavControls';
export { SearchButton } from './SearchButton';
export { ModeToggleButton } from './ModeToggleButton';
export { LanguagePill } from './LanguagePill';
export { LanguageDropdown } from './LanguageDropdown';
export { HamburgerButton } from './HamburgerButton';

View File

@@ -1,30 +0,0 @@
import * as React from "react";
interface ChevronIconProps {
expanded: boolean;
}
/**
* Chevron Icon Component for Mobile Accordion
*/
export function ChevronIcon({ expanded }: ChevronIconProps) {
return (
<svg
className={`bds-mobile-menu__chevron ${expanded ? 'bds-mobile-menu__chevron--expanded' : ''}`}
width="13"
height="8"
viewBox="0 0 13 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 1L6.5 6.5L12 1"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,14 +0,0 @@
import * as React from "react";
/**
* Close Icon Component for Mobile Menu
*/
export function CloseIcon() {
return (
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="7" y1="7" x2="21" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
<line x1="21" y1="7" x2="7" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}

View File

@@ -1,56 +0,0 @@
interface ArrowIconProps {
className?: string;
color?: string;
/**
* When true, the horizontal line has a class for CSS animation (parent links).
* When false, the full arrow is shown without animation class (child links).
*/
animated?: boolean;
}
/**
* Unified Arrow Icon component.
* - For parent links (animated=true): horizontal line animates away on hover
* - For child links (animated=false): full static arrow
*/
export function ArrowIcon({ className, color = "currentColor", animated = true }: ArrowIconProps) {
return (
<svg
className={className}
width="15"
height="14"
viewBox="0 0 26 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
{/* Chevron part */}
<path
d="M14.0019 1.00191L24.0015 11.0015L14.0019 21.001"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
/>
{/* Horizontal line */}
<path
d="M23.999 10.999H0"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
className={animated ? "arrow-horizontal" : undefined}
/>
</svg>
);
}
// Backwards-compatible aliases
export const SubmenuArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={true} />
);
export const SubmenuChildArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={false} />
);

View File

@@ -1,6 +0,0 @@
// Re-export all icon components
// Unified arrow icon with backwards-compatible aliases
export { ArrowIcon, SubmenuArrow, SubmenuChildArrow } from './SubmenuArrow';
export { CloseIcon } from './CloseIcon';
export { ChevronIcon } from './ChevronIcon';

View File

@@ -1,13 +0,0 @@
// Main Navbar component
export { Navbar } from './Navbar';
// Re-export types
export * from './types';
// Re-export components for advanced usage
export * from './components';
export * from './controls';
export * from './submenus';
export * from './mobile-menu';
export * from './icons';

View File

@@ -1,215 +0,0 @@
import * as React from "react";
import { useThemeHooks, useLanguagePicker } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { CloseIcon, ChevronIcon } from "../icons";
import { xrpSymbolBlack, globeIcon, chevronDown, modeToggleIcon, searchIcon } from "../constants/icons";
import { navItems } from "../constants/navigation";
import { MobileMenuContent, type MobileMenuKey } from "./MobileMenuContent";
interface MobileMenuProps {
isOpen: boolean;
onClose: () => void;
onSearch?: () => void;
}
/**
* Mobile Menu Component.
* Full-screen slide-out menu for mobile devices.
*/
export function MobileMenu({ isOpen, onClose, onSearch }: MobileMenuProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [expandedItem, setExpandedItem] = React.useState<string | null>("Develop");
// Handle body scroll lock
React.useEffect(() => {
if (isOpen) {
document.body.classList.add('bds-mobile-menu-open');
} else {
document.body.classList.remove('bds-mobile-menu-open');
}
return () => {
document.body.classList.remove('bds-mobile-menu-open');
};
}, [isOpen]);
const toggleAccordion = (item: string) => {
setExpandedItem(expandedItem === item ? null : item);
};
const handleSearch = () => {
if (onSearch) {
onSearch();
}
onClose();
};
const handleModeToggle = () => {
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const renderAccordionContent = (label: string) => {
// All nav items with submenus use the unified MobileMenuContent
const validKeys: MobileMenuKey[] = ['Develop', 'Use Cases', 'Community', 'Network'];
if (validKeys.includes(label as MobileMenuKey)) {
return <MobileMenuContent menuKey={label as MobileMenuKey} />;
}
return null;
};
return (
<div className={`bds-mobile-menu ${isOpen ? 'bds-mobile-menu--open' : ''}`}>
{/* Header */}
<div className="bds-mobile-menu__header">
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} onClick={onClose} variant="inline">
<img src={xrpSymbolBlack} alt={translate("XRP Ledger")} className="bds-navbar__logo-symbol" style={{ width: 33, height: 28 }} />
</BdsLink>
<button
type="button"
className="bds-mobile-menu__close"
aria-label={translate("Close menu")}
onClick={onClose}
>
<CloseIcon />
</button>
</div>
{/* Content */}
<div className="bds-mobile-menu__content">
<div className="bds-mobile-menu__accordion">
{navItems.map((item) => (
<React.Fragment key={item.label}>
<button
type="button"
className="bds-mobile-menu__accordion-header"
onClick={() => item.hasSubmenu ? toggleAccordion(item.label) : null}
aria-expanded={expandedItem === item.label}
>
{item.hasSubmenu ? (
<>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={expandedItem === item.label} />
</>
) : (
<BdsLink
href={item.href}
onClick={onClose}
variant="inline"
style={{
display: 'flex',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
color: 'inherit',
textDecoration: 'none'
}}
>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={false} />
</BdsLink>
)}
</button>
{item.hasSubmenu && (
<div
className={`bds-mobile-menu__accordion-content ${
expandedItem === item.label ? 'bds-mobile-menu__accordion-content--expanded' : ''
}`}
>
{renderAccordionContent(item.label)}
</div>
)}
</React.Fragment>
))}
</div>
</div>
{/* Footer */}
<MobileMenuFooter
onModeToggle={handleModeToggle}
onSearch={handleSearch}
/>
</div>
);
}
interface MobileMenuFooterProps {
onModeToggle: () => void;
onSearch: () => void;
}
/**
* Get short display name for a locale code.
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
function MobileMenuFooter({ onModeToggle, onSearch }: MobileMenuFooterProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const [isLangOpen, setIsLangOpen] = React.useState(false);
const displayName = getLocaleShortName(currentLocale?.code);
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
setIsLangOpen(false);
};
return (
<div className="bds-mobile-menu__footer">
<div className="bds-mobile-menu__lang-wrapper">
<button
type="button"
className={`bds-mobile-menu__lang-pill ${isLangOpen ? 'bds-mobile-menu__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isLangOpen}
onClick={() => setIsLangOpen(!isLangOpen)}
>
<img src={globeIcon} alt="" className="bds-mobile-menu__lang-pill-icon" />
<span className="bds-mobile-menu__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-mobile-menu__lang-pill-chevron" />
</span>
</button>
{isLangOpen && locales.length >= 2 && (
<div className="bds-lang-dropdown bds-lang-dropdown--mobile" role="menu">
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
>
{locale.name || locale.code}
</button>
);
})}
</div>
)}
</div>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Toggle color mode")} onClick={onModeToggle}>
<img src={modeToggleIcon} alt="" />
</button>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Search")} onClick={onSearch}>
<img src={searchIcon} alt="" />
</button>
</div>
);
}

View File

@@ -1,47 +0,0 @@
import { MobileMenuSection } from "./MobileMenuSection";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type MobileMenuKey = 'Develop' | 'Use Cases' | 'Community' | 'Network';
interface MobileMenuContentProps {
/** Which menu section to render */
menuKey: MobileMenuKey;
}
/** Get flattened menu items based on menu key */
function getMenuItems(menuKey: MobileMenuKey): (SubmenuItem | SubmenuItemWithChildren | NetworkSubmenuSection)[] {
switch (menuKey) {
case 'Develop':
return [...developSubmenuData.left, ...developSubmenuData.right];
case 'Use Cases':
return [...useCasesSubmenuData.left, ...useCasesSubmenuData.right];
case 'Community':
return [...communitySubmenuData.left, ...communitySubmenuData.right];
case 'Network':
return networkSubmenuData;
}
}
/**
* Unified Mobile Menu Content component.
* Renders accordion content for any menu section.
*/
export function MobileMenuContent({ menuKey }: MobileMenuContentProps) {
const items = getMenuItems(menuKey);
return (
<div className="bds-mobile-menu__tier-list">
{items.map((item) => (
<MobileMenuSection key={item.label} item={item} />
))}
</div>
);
}
// Backwards-compatible named exports
export const MobileMenuDevelopContent = () => <MobileMenuContent menuKey="Develop" />;
export const MobileMenuUseCasesContent = () => <MobileMenuContent menuKey="Use Cases" />;
export const MobileMenuCommunityContent = () => <MobileMenuContent menuKey="Community" />;
export const MobileMenuNetworkContent = () => <MobileMenuContent menuKey="Network" />;

View File

@@ -1,54 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuArrow, SubmenuChildArrow } from "../icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem } from "../types";
interface MobileMenuSectionProps {
item: SubmenuItem;
}
/**
* Reusable mobile menu section component.
* Renders a parent link with icon, and optionally child links.
*/
export function MobileMenuSection({ item }: MobileMenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item);
return (
<React.Fragment>
<a href={item.href} className="bds-mobile-menu__tier1 bds-mobile-menu__parent-link">
<span className="bds-mobile-menu__icon">
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-mobile-menu__link bds-mobile-menu__link--bold">
{translate(item.label)}
<span className="bds-mobile-menu__arrow">
<SubmenuArrow />
</span>
</span>
</a>
{itemHasChildren && (
<div className="bds-mobile-menu__tier2">
{item.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-mobile-menu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-mobile-menu__sublink-arrow">
<SubmenuChildArrow />
</span>
</a>
))}
</div>
)}
</React.Fragment>
);
}

View File

@@ -1,16 +0,0 @@
// Main mobile menu component
export { MobileMenu } from './MobileMenu';
// Unified content component with backwards-compatible aliases
export {
MobileMenuContent,
MobileMenuDevelopContent,
MobileMenuUseCasesContent,
MobileMenuCommunityContent,
MobileMenuNetworkContent,
type MobileMenuKey
} from './MobileMenuContent';
// Reusable section component
export { MobileMenuSection } from './MobileMenuSection';

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface CommunitySubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Community Submenu Component.
* Wrapper for unified Submenu component with 'community' variant.
*/
export function CommunitySubmenu({ isActive, isClosing, onClose }: CommunitySubmenuProps) {
return <Submenu variant="community" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface DevelopSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Develop Submenu Component.
* Wrapper for unified Submenu component with 'develop' variant.
*/
export function DevelopSubmenu({ isActive, isClosing, onClose }: DevelopSubmenuProps) {
return <Submenu variant="develop" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface NetworkSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Network Submenu Component.
* Wrapper for unified Submenu component with 'network' variant.
*/
export function NetworkSubmenu({ isActive, isClosing, onClose }: NetworkSubmenuProps) {
return <Submenu variant="network" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,277 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuSection } from "./SubmenuSection";
import { ArrowIcon } from "../icons";
import { walletIcons, resourcesIconPattern, insightsIconPattern } from "../constants/icons";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type SubmenuVariant = 'develop' | 'use-cases' | 'community' | 'network';
interface SubmenuProps {
/** Which submenu variant to render */
variant: SubmenuVariant;
/** Whether this submenu is currently active (visible) */
isActive: boolean;
/** Whether this submenu is in closing animation */
isClosing: boolean;
/** Callback when submenu should close (e.g., Escape key) */
onClose?: () => void;
}
/** Get submenu data based on variant */
function getSubmenuData(variant: SubmenuVariant) {
switch (variant) {
case 'develop': return developSubmenuData;
case 'use-cases': return useCasesSubmenuData;
case 'community': return communitySubmenuData;
case 'network': return networkSubmenuData;
}
}
/** Get CSS modifier class for variant */
function getVariantClass(variant: SubmenuVariant): string {
if (variant === 'develop') return '';
return `bds-submenu--${variant}`;
}
/**
* Get all focusable elements within a container
*/
function getFocusableElements(container: HTMLElement | null): HTMLElement[] {
if (!container) return [];
return Array.from(
container.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])')
);
}
/**
* Find the next nav item button after the current expanded one
*/
function getNextNavItem(): HTMLElement | null {
const navItems = document.querySelectorAll<HTMLElement>('.bds-navbar__item');
const currentIndex = Array.from(navItems).findIndex(item =>
item.getAttribute('aria-expanded') === 'true'
);
if (currentIndex >= 0 && currentIndex < navItems.length - 1) {
return navItems[currentIndex + 1];
}
// If at the last nav item, go to the first control button (search, etc.)
const controls = document.querySelector<HTMLElement>('.bds-navbar__controls button, .bds-navbar__controls a');
return controls;
}
/**
* Unified Submenu component.
* Handles all submenu variants (develop, use-cases, community, network).
* ARIA compliant with full keyboard navigation support.
*/
export function Submenu({ variant, isActive, isClosing, onClose }: SubmenuProps) {
const submenuRef = React.useRef<HTMLDivElement>(null);
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
// Network submenu needs special handling for theme-aware patterns
if (variant === 'network') {
return <NetworkSubmenuContent isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}
const data = getSubmenuData(variant);
const classNames = [
'bds-submenu',
getVariantClass(variant),
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
// Standard two-column layout
const leftItems = 'left' in data ? data.left : [];
const rightItems = 'right' in data ? data.right : [];
return (
<div
ref={submenuRef}
className={classNames}
role="menu"
aria-hidden={!isActive}
>
<div className="bds-submenu__left">
{leftItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
<div className="bds-submenu__right">
{rightItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
</div>
);
}
/** Network submenu with pattern images (same for light and dark mode) */
function NetworkSubmenuContent({ isActive, isClosing, onClose }: { isActive: boolean; isClosing: boolean; onClose?: () => void }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
// Use same pattern images for both light and dark mode
const patternImages = {
lilac: resourcesIconPattern,
green: insightsIconPattern,
};
const classNames = [
'bds-submenu',
'bds-submenu--network',
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
return (
<div className={classNames} role="menu" aria-hidden={!isActive}>
{networkSubmenuData.map((section: NetworkSubmenuSection) => (
<div key={section.label} className="bds-submenu__section">
<a href={section.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={walletIcons[section.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(section.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
<div className="bds-submenu__network-content">
<div className="bds-submenu__tier2">
{section.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
<div className="bds-submenu__pattern-container">
<img src={patternImages[section.patternColor]} alt="" className="bds-submenu__pattern" />
</div>
</div>
</div>
))}
</div>
);
}

View File

@@ -1,70 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { ArrowIcon } from "../icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem, type SubmenuItemWithChildren, type SubmenuItemBase } from "../types";
interface SubmenuSectionProps {
/** The menu item data */
item: SubmenuItem | SubmenuItemWithChildren | SubmenuItemBase;
/** Whether to render children links (default: true) */
showChildren?: boolean;
}
/**
* Unified submenu section component.
* Renders a parent link with icon, and optionally child links if the item has them.
*
* Usage:
* - For items that may or may not have children: <SubmenuSection item={item} />
* - For parent-only rendering: <SubmenuSection item={item} showChildren={false} />
*/
export function SubmenuSection({ item, showChildren = true }: SubmenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item as SubmenuItem);
const shouldShowChildren = showChildren && itemHasChildren;
return (
<div className="bds-submenu__section">
<a href={item.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(item.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
{shouldShowChildren && (
<div className="bds-submenu__tier2">
{(item as SubmenuItemWithChildren).children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
)}
</div>
);
}
// Backwards-compatible aliases (all use the unified SubmenuSection)
export const SubmenuParentOnly = ({ item }: { item: SubmenuItemBase }) => (
<SubmenuSection item={item} showChildren={false} />
);
export const SubmenuWithChildren = ({ item }: { item: SubmenuItemWithChildren }) => (
<SubmenuSection item={item} showChildren={true} />
);

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface UseCasesSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Use Cases Submenu Component.
* Wrapper for unified Submenu component with 'use-cases' variant.
*/
export function UseCasesSubmenu({ isActive, isClosing, onClose }: UseCasesSubmenuProps) {
return <Submenu variant="use-cases" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,12 +0,0 @@
// Unified submenu component
export { Submenu, type SubmenuVariant } from './Submenu';
// Variant-specific wrappers (all use Submenu internally)
export { DevelopSubmenu } from './DevelopSubmenu';
export { UseCasesSubmenu } from './UseCasesSubmenu';
export { CommunitySubmenu } from './CommunitySubmenu';
export { NetworkSubmenu } from './NetworkSubmenu';
// Reusable submenu section component
export { SubmenuSection, SubmenuParentOnly, SubmenuWithChildren } from './SubmenuSection';

View File

@@ -1,42 +0,0 @@
// Types for submenu data structures
export interface SubmenuChild {
label: string;
href: string;
active?: boolean;
}
export interface SubmenuItemBase {
label: string;
href: string;
icon: string;
}
export interface SubmenuItemWithChildren extends SubmenuItemBase {
children: SubmenuChild[];
}
export type SubmenuItem = SubmenuItemBase | SubmenuItemWithChildren;
// Network submenu section with decorative images
export interface NetworkSubmenuSection {
label: string;
href: string;
icon: string;
children: SubmenuChild[];
patternColor: 'lilac' | 'green';
}
// Nav item type
export interface NavItem {
label: string;
labelTranslationKey: string;
href: string;
hasSubmenu: boolean;
}
// Type guard to check if item has children
export function hasChildren(item: SubmenuItem): item is SubmenuItemWithChildren {
return 'children' in item && Array.isArray((item as SubmenuItemWithChildren).children);
}

View File

@@ -25,7 +25,7 @@ export function XRPLCard(props: XRPLCardProps) {
<p className="card-text">{props.body}</p>
)}
</div>
{/* <div className="card-footer">&nbsp;</div> */}
<div className="card-footer">&nbsp;</div>
</Link>
)
}

View File

@@ -1,178 +0,0 @@
# Color System Migration Summary
**Date:** October 21, 2025
**Source:** [XRPL.org Design Tokens - Figma](https://figma.com/design/zRyhXG4hRP3Lk3B6Owr3eo/XRPL.org-Design-Tokens)
## Migration Strategy: Clean Migration
The old 10-level color scale (100-1000) has been completely migrated to a new 5-level scale (100-500). All references in the codebase have been updated, and backward compatibility aliases have been removed for a clean implementation.
**Mapping Applied:**
```
Old System → New System
100 → 100 (lightest)
200 → 100
300 → 200
400 → 200
500 → 300 (mid-tone, default)
600 → 300
700 → 400
800 → 400
900 → 500 (darkest)
1000 → 500
```
**Migration Approach:**
1. All color usages (600-1000) were found and replaced with their new equivalents (300-500)
2. Backward compatibility aliases were removed from `_colors.scss`
3. Only 100-500 design tokens remain for each color family
## Color Families Updated
### Primary Colors
#### Gray (Neutral) ⏸️ NOT UPDATED
- **Status:** Original values retained - design tokens not ready
- **Current values:** #FCFCFD, #F5F5F7, #E0E0E1, #C1C1C2, #A2A2A4, #838386, #454549, #343437, #232325, #111112
- **Note:** Gray/Neutral design tokens will be migrated in a future update
#### Green ✅
- **New Design Tokens:** #EAFCF1, #70EE97, #21E46B, #0DAA3E, #078139
- **Variables:** `$green-100` through `$green-500` only
- **Migrated:** 14 file references updated
- **Special:** `$apex-2023-green` (#00FF76) retained
#### Lilac (Primary) ✅ *replaces blue-purple*
- **New Design Tokens:** #F2EDFF, #D9CAFF, #C0A7FF, #7649E3, #5429A1
- **Variables:** `$lilac-100` through `$lilac-500` only
- **Legacy aliases:** `$blue-purple-100` through `$blue-purple-500` map to lilac (600-900 removed)
- **Migrated:** 16 file references updated
- **Note:** This is a new color name in the design system
### Secondary Colors
#### Red ✅ *NEW*
- **New Design Tokens:** #FDECE7, #F27A66, #F0643A, #DA4518, #A22514
- **Variables:** `$red-100` through `$red-500` only
- **Note:** This is a completely new color family added to the design system
#### Pink ✅ *replaces magenta*
- **New Design Tokens:** #FDF1F4, #F2B5C3, #F18DA5, #FF577F, #DC466F
- **Variables:** `$pink-100` through `$pink-500` only
- **Legacy aliases:** `$magenta-100` through `$magenta-500` map to pink (600-900 removed)
- **Migrated:** 7 file references updated
#### Blue ✅
- **New Design Tokens:** #EDF4FF, #93BFF1, #428CFF, #0179E7, #0A4DC0
- **Variables:** `$blue-100` through `$blue-500` only
- **Migrated:** 8 file references updated
- **Special:** `$accent-blue-90` (#001133) retained
#### Yellow ✅
- **New Design Tokens:** #F3F1EB, #E6F1A7, #DBF15E, #E1DB26, #D4C02D
- **Variables:** `$yellow-100` through `$yellow-500` only
- **Migrated:** 11 file references updated
## Colors Retained (No Design Token Replacement)
### Orange
- **Status:** Legacy values retained
- **Values:** #FFEEE5, #FFCCB2, #FFAA80, #FF884B, #FF6719, #E54D00, #B23C00, #802B00, #4C1A00
- **Reason:** No corresponding design token in new system
### Red-purple
- **Status:** Legacy values retained
- **Values:** #FBE5FF, #F2B2FF, #EA80FF, #E24CFF, #D919FF, #C000E5, #9500B2, #6B0080, #40004C
- **Reason:** No corresponding design token in new system
### Special Event Colors
- `$apex-2023-green: #00FF76`
- `$token-2049-purple: #410bb9`
- `$accent-blue-90: #001133`
## Bootstrap & Component Colors
All Bootstrap theme variables remain functional:
- `$primary``$purple` (now `$lilac-400`)
- `$secondary``$gray-200`
- `$success``$green-500`
- `$info``$blue-500`
- `$warning``$yellow-500`
- `$danger``$magenta-500` (now `$pink-500`)
## Breaking Changes
**Removed Variables:**
- All color variables from 600-1000 have been removed for: Green, Blue, Lilac, Pink, Red, Yellow
- `$blue-purple-600` through `$blue-purple-900` removed (use 100-500)
- `$magenta-600` through `$magenta-900` removed (use 100-500)
**No Impact:**
- All usages in the codebase have been updated
- Legacy color name aliases maintained (100-500 only):
- `$blue-purple-100` through `$blue-purple-500` → maps to `$lilac-*`
- `$magenta-100` through `$magenta-500` → maps to `$pink-*`
## Color Name Changes
| Old Name | New Name | Reason |
|----------|----------|--------|
| `blue-purple-*` | `lilac-*` | Design system rebranding |
| `magenta-*` | `pink-*` | Design system rebranding |
| N/A | `red-*` | New color family added |
## Usage Recommendations
### Current Best Practices
Use the new 5-level design tokens (100-500):
```scss
// Primary colors
color: $gray-300; // Gray (not yet migrated - still uses old values)
color: $green-300; // Default green
color: $lilac-400; // Primary purple
// Secondary colors
color: $red-300; // Default red
color: $pink-300; // Default pink
color: $blue-300; // Default blue
color: $yellow-300; // Default yellow
```
### Legacy Aliases Still Available
```scss
// These legacy names work (100-500 only)
color: $blue-purple-400; // Same as $lilac-400
color: $magenta-300; // Same as $pink-300
```
## Files Modified
- `styles/_colors.scss` - Complete color system update
## Validation Status
✅ All SCSS variables resolve correctly
✅ No linter errors
✅ Bootstrap theme colors functional
✅ All old color references (600-1000) removed from codebase
✅ Special event colors preserved
⏸️ Gray/Neutral colors - pending future update
## Migration Statistics
**Files Updated:** 11 SCSS files
- `styles/_colors.scss` - Color definitions cleaned up
- `styles/light/_light-theme.scss` - 11 color references updated
- `styles/_status-labels.scss` - 39 color references updated
- `styles/_diagrams.scss` - 6 color references updated
- `styles/_code-tabs.scss` - 2 color references updated
- `styles/_content.scss` - 1 color reference updated
- `styles/_buttons.scss` - 7 color references updated
- `styles/_pages.scss` - 3 color references updated
- `styles/_blog.scss` - 2 color references updated
- `styles/_feedback.scss` - 2 color references updated
- `styles/_rpc-tool.scss` - 1 color reference updated
- `styles/_landings.scss` - 1 color reference updated
**Total Color References Updated:** 75+ instances

View File

@@ -1,154 +0,0 @@
# CSS Optimization - Implementation Summary
## ✅ Successfully Completed
The CSS build pipeline has been modernized with industry-standard optimization tools, resulting in significant performance improvements.
## Results
### Bundle Size Improvements
\`\`\`
=== CSS Bundle Comparison ===
Master (Bootstrap 4):
Uncompressed: 405.09 KB
Gzipped: 63.44 KB
This Branch BEFORE Optimization (Bootstrap 5):
Uncompressed: 486.64 KB
Gzipped: 71.14 KB
This Branch AFTER Optimization (Bootstrap 5 + PurgeCSS):
Uncompressed: 280.92 KB ✅ 42% smaller
Gzipped: 43.32 KB ✅ 39% smaller (network transfer)
\`\`\`
### Key Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Network Transfer (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Uncompressed Size** | 486.64 KB | 280.92 KB | **42% smaller** |
| **CSS Selectors** | 5,423 | 2,681 | **51% fewer** |
| **DevTools Filter Performance** | ~60 seconds | <1 second | **98% faster** |
### Real-World Impact
- **Page Load:** 40% faster CSS download on 3G connections
- **Developer Experience:** DevTools CSS filtering is now instant (<1s vs 60s)
- **Bandwidth Savings:** ~28 KB less per page load
- **Maintainability:** Modern tooling with source maps in development
## What Was Implemented
### 1. Modern Build Pipeline
- **Upgraded Sass** from 1.26.10 (2020) 1.93.2 (latest)
- **Added PostCSS** with optimization plugins:
- **PurgeCSS** - Removes unused CSS selectors
- **Autoprefixer** - Browser compatibility
- **cssnano** - Advanced minification
### 2. Build Scripts
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css-dev": "Development build with source maps",
"build-css-watch": "Watch mode for continuous compilation",
"analyze-css": "Bundle analysis tool"
}
}
```
### 3. PurgeCSS Configuration
- Scans all `.tsx`, `.md`, `.yaml`, `.html` files for class names
- Intelligent safelist for dynamically-added classes
- Preserves Bootstrap JS components, CodeMirror, custom tools
- Only runs in production (dev builds are fast)
### 4. CSS Analysis Tool
Created `scripts/analyze-css.js` to monitor:
- Bundle size and composition
- Bootstrap component usage
- Optimization opportunities
- Before/after comparisons
## Files Created/Modified
### New Files
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - CSS bundle analysis tool
- `CSS-OPTIMIZATION.md` - Comprehensive optimization guide
- `CSS-OPTIMIZATION-SUMMARY.md` - This summary
### Modified Files
- `package.json` - Updated dependencies and build scripts
- `styles/README.md` - Updated build documentation
### Configuration Files
All configuration files include extensive inline documentation explaining decisions and patterns.
## Usage
### For Production
```bash
npm run build-css # Full optimization
npm run analyze-css # Check results
```
### For Development
```bash
npm run build-css:dev # Fast build with source maps
npm run build-css:watch # Auto-rebuild on changes
```
## Backward Compatibility
**No breaking changes** - All existing styles are preserved
Visual appearance is identical
All Bootstrap components still work
Dynamic classes are safelisted
## Documentation
- **`styles/README.md`** - Build process and troubleshooting
- **`CSS-OPTIMIZATION.md`** - Detailed implementation guide
- **`postcss.config.cjs`** - Inline configuration documentation
## Maintenance
### Adding New Styles
1. Create `_component.scss` file
2. Import in `xrpl.scss`
3. Add dynamic classes to safelist if needed
4. Test: `npm run build-css:dev` and `npm run build-css`
5. Analyze: `npm run analyze-css`
### Troubleshooting Missing Styles
If styles are missing in production:
1. Check if class is added dynamically
2. Add pattern to safelist in `postcss.config.cjs`
3. Rebuild with `npm run build-css`
## Next Steps (Optional Future Optimizations)
1. **Code Splitting** - Separate vendor CSS from custom styles
2. **Critical CSS** - Extract above-the-fold styles
3. **Bootstrap Customization** - Import only needed components
4. **CSS Modules** - Component-scoped styles for React pages
## Conclusion
The CSS optimization is complete and working perfectly. The bundle size has been reduced by 42% (uncompressed) and 39% (gzipped), resulting in faster page loads and dramatically improved developer experience.
**Status: ✅ Ready for Production**
---
*Last Updated: October 2025*

View File

@@ -1,381 +0,0 @@
# CSS Optimization Guide
## Overview
This document describes the CSS optimization implementation for the XRPL Dev Portal, including the rationale, implementation details, performance improvements, and maintenance guidelines.
## The Problem
### Before Optimization
The dev portal was serving a **486 KB** minified CSS bundle that included:
- **Entire Bootstrap 5.3.8 framework** (~200+ KB)
- Thousands of unused CSS selectors
- No tree-shaking or dead code elimination
- All styles loaded on every page, regardless of usage
- **1-minute lag** in Chrome DevTools when filtering CSS
#### Impact
- **Developer Experience:** DevTools filter took 60+ seconds to respond
- **Page Performance:** 486 KB CSS downloaded on every page load
- **Build Process:** Outdated Sass 1.26.10 (from 2020)
- **Debugging:** No source maps, even in development
### Analysis Results
Initial analysis showed:
```
Bundle Size: 486.64 KB
Total Selectors: 5,423
Unique Selectors: 4,678
Bootstrap Component Usage:
- Pagination: 998 usages
- Cards: 428 usages
- Grid System: 253 usages
- ...but also...
- Toast: 8 usages
- Spinner: 8 usages
- Accordion: 0 usages (unused!)
```
## The Solution
### Modern Build Pipeline
Implemented a three-stage optimization pipeline:
```
SCSS → Sass Compiler → PostCSS → Optimized CSS
├─ PurgeCSS (removes unused)
├─ Autoprefixer (adds vendor prefixes)
└─ cssnano (minifies)
```
### Key Technologies
1. **Sass (latest)** - Modern SCSS compilation with better performance
2. **PostCSS** - Industry-standard CSS processing
3. **PurgeCSS** - Intelligent unused CSS removal
4. **Autoprefixer** - Browser compatibility
5. **cssnano** - Advanced minification
## Implementation
### 1. Dependency Upgrades
```json
{
"devDependencies": {
"sass": "^1.93.2", // was 1.26.10
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"@fullhuman/postcss-purgecss": "^7.0.2",
"autoprefixer": "^10.4.21",
"cssnano": "^7.1.1"
}
}
```
### 2. Build Scripts
Created separate development and production builds:
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css:dev": "Development build with source maps",
"build-css:watch": "Watch mode for continuous compilation",
"analyze-css": "node scripts/analyze-css.js"
}
}
```
**Production Build:**
- ✅ Full PurgeCSS optimization
- ✅ Minified and compressed
- ✅ Autoprefixed
- ❌ No source maps
**Development Build:**
- ✅ Source maps for debugging
- ✅ Autoprefixed
- ❌ No PurgeCSS (faster builds)
- ❌ Not minified (readable)
### 3. PurgeCSS Configuration
Created `postcss.config.cjs` with intelligent safelist:
```javascript
// Content paths - scan these for class names
content: [
'./**/*.tsx',
'./**/*.md',
'./**/*.yaml',
'./**/*.html',
'./static/js/**/*.js',
]
// Safelist - preserve these classes
safelist: {
standard: [
'html', 'body', 'light', 'dark',
/^show$/, /^active$/, /^disabled$/,
],
deep: [
/dropdown-menu/, /modal-backdrop/,
/cm-/, /CodeMirror/, // Third-party
/rpc-tool/, /websocket/, // Custom components
],
}
```
**Safelist Strategy:**
- **Standard:** State classes added by JavaScript
- **Deep:** Component patterns (keeps parent and children)
- **Greedy:** Attribute-based matching
### 4. Analysis Tool
Created `scripts/analyze-css.js` to track optimization:
- Bundle size metrics
- Selector counts
- Bootstrap component usage
- Custom pattern detection
- Optimization recommendations
## Results
### Performance Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Bundle Size (Uncompressed)** | 486.64 KB | 280.92 KB | **42% smaller** |
| **Bundle Size (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Total Selectors** | 5,423 | 2,681 | **51% fewer** |
| **Unique Selectors** | 4,678 | 2,167 | **54% fewer** |
| **DevTools Filter** | ~60 seconds | <1 second | **98% faster** |
| **Download Time (3G)** | ~2.0s | ~1.2s | **40% faster** |
**Note:** Gzipped size is what actually gets transmitted over the network, representing the real-world bandwidth savings.
### Bootstrap Component Optimization
| Component | Before | After | Reduction |
|-----------|--------|-------|-----------|
| Pagination | 998 | 831 | 17% |
| Cards | 428 | 306 | 29% |
| Grid System | 253 | 94 | 63% |
| Badge | 253 | 0 | 100% (unused) |
| Navbar | 171 | 78 | 54% |
| Buttons | 145 | 77 | 47% |
| Forms | 179 | 70 | 61% |
### Developer Experience
**Before:**
```
Build time: 5-10 seconds
DevTools CSS filter: 60 seconds
Debugging: No source maps
```
**After:**
```
Production build: 8-12 seconds
Development build: 3-5 seconds (no PurgeCSS)
DevTools CSS filter: <1 second
Debugging: Source maps in dev mode
```
## Maintenance
### Adding New Styles
When adding new component styles:
1. **Create the SCSS file:**
```scss
// styles/_my-component.scss
.my-component {
// styles here
}
```
2. **Import in xrpl.scss:**
```scss
@import "_my-component.scss";
```
3. **If using dynamic classes, update safelist:**
```javascript
// postcss.config.cjs
deep: [
/my-component/, // Keeps all .my-component-* classes
]
```
4. **Test both builds:**
```bash
npm run build-css:dev # Test development build
npm run build-css # Test production build
npm run analyze-css # Check bundle size impact
```
### Troubleshooting Missing Styles
If styles are missing after a production build:
1. **Identify the missing class:**
```bash
# Search for class usage in codebase
grep -r "missing-class" .
```
2. **Check if it's dynamically added:**
- Bootstrap JavaScript components
- React state-based classes
- Third-party library classes
3. **Add to PurgeCSS safelist:**
```javascript
// postcss.config.cjs
safelist: {
deep: [
/missing-class/, // Preserve this pattern
],
}
```
4. **Rebuild and verify:**
```bash
npm run build-css
npm run analyze-css
```
### Monitoring Bundle Size
Run the analysis tool regularly:
```bash
npm run analyze-css
```
**Watch for:**
- Bundle size > 350 KB (indicates regression)
- Components with 0 usages (can be removed from Bootstrap import)
- Significant selector count increases
### Future Optimizations
Potential next steps for further optimization:
1. **Code Splitting**
- Split vendor CSS (Bootstrap) from custom styles
- Lazy-load page-specific styles
- Critical CSS extraction
2. **Bootstrap Customization**
- Import only needed Bootstrap components
- Remove unused variables and mixins
- Custom Bootstrap build
3. **Component-Level CSS**
- CSS Modules for page components
- CSS-in-JS for dynamic styles
- Scoped styles per route
4. **Advanced Compression**
- Brotli compression (88% ratio vs 76% gzip)
- CSS splitting by media queries
- HTTP/2 server push for critical CSS
## Migration Notes
### Breaking Changes
**None** - This optimization is backward-compatible. All existing classes and styles are preserved.
### Testing Checklist
When testing the optimization:
- [ ] Homepage loads correctly
- [ ] Documentation pages display properly
- [ ] Blog posts render correctly
- [ ] Dev tools (RPC tool, WebSocket tool) function
- [ ] Navigation menus work
- [ ] Dropdowns and modals open correctly
- [ ] Forms are styled properly
- [ ] Code syntax highlighting works
- [ ] Print styles work
- [ ] Light/dark theme switching works
### Rollback Procedure
If issues are found:
1. **Temporarily revert to old build:**
```bash
# In package.json, change build-css to:
"build-css": "sass --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map"
```
2. **Rebuild:**
```bash
npm run build-css
```
3. **Report the issue** with:
- Missing class names
- Page where issue appears
- Expected vs actual behavior
## Resources
### Documentation
- [PurgeCSS Documentation](https://purgecss.com/)
- [PostCSS Documentation](https://postcss.org/)
- [Sass Documentation](https://sass-lang.com/)
- [Bootstrap Customization](https://getbootstrap.com/docs/5.3/customize/sass/)
### Tools
- `npm run build-css` - Production build
- `npm run build-css:dev` - Development build
- `npm run build-css:watch` - Watch mode
- `npm run analyze-css` - Bundle analysis
### Files
- `styles/README.md` - Build process documentation
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - Bundle analysis tool
- `package.json` - Build scripts
## Conclusion
This optimization reduces the CSS bundle by 42% (486 KB 281 KB), dramatically improving both developer experience and end-user performance. The implementation uses industry-standard tools and maintains full backward compatibility while providing a foundation for future optimizations.
**Key Takeaways:**
- 42% smaller uncompressed CSS bundle (486 KB 281 KB)
- 39% smaller gzipped bundle (71 KB 43 KB network transfer)
- 98% faster DevTools filtering (60s <1s)
- Modern build tooling (Sass + PostCSS + PurgeCSS)
- Source maps in development mode
- Backward compatible - no breaking changes
- Well documented and maintainable
---
*Last updated: October 2025*
*Contributors: CSS Optimization Initiative*

View File

@@ -92,7 +92,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title subhead-sm-r" id="send-xrp-modal-label">Send XRP</h1>
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -92,7 +92,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title subhead-sm-r" id="send-xrp-modal-label">Send XRP</h1>
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -0,0 +1,3 @@
# Lending Protocol Examples
Code samples showing how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.

View File

@@ -0,0 +1,394 @@
# Lending Protocol Examples (JavaScript)
This directory contains JavaScript examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
## Setup
Install dependencies before running any examples:
```sh
npm i
```
---
## Create a Loan Broker
```sh
node createLoanBroker.js
```
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
```sh
Loan broker/vault owner address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
Vault ID: 33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68
=== Preparing LoanBrokerSet transaction ===
{
"TransactionType": "LoanBrokerSet",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"VaultID": "33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68",
"ManagementFeeRate": 1000
}
=== Submitting LoanBrokerSet transaction ===
Loan broker created successfully!
=== Loan Broker Information ===
LoanBroker ID: 0AA13C8A8E95D8F2D9EF1FA1B15EF4668EF779A678D1D24D099C532E126E8BBF
LoanBroker Psuedo-Account Address: rfhftuQGpqUVRcERZbY9htJshijKur7dS4
```
---
## Claw Back First-loss Capital
```sh
node coverClawback.js
```
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback:
```sh
Loan broker address: r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU
MPT issuer address: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3
LoanBrokerID: 655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442
MPT ID: 00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E
=== Cover Available ===
0 TSTUSD
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"TransactionType": "LoanBrokerCoverDeposit",
"Account": "r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU",
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
"Amount": {
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Available After Deposit ===
1000 TSTUSD
=== Verifying Asset Issuer ===
MPT issuer account verified: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3. Proceeding to clawback.
=== Preparing LoanBrokerCoverClawback transaction ===
{
"TransactionType": "LoanBrokerCoverClawback",
"Account": "rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3",
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
"Amount": {
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverClawback transaction ===
Successfully clawed back 1000 TSTUSD!
=== Final Cover Available After Clawback ===
0 TSTUSD
```
---
## Deposit and Withdraw First-loss Capital
```sh
node coverDepositAndWithdraw.js
```
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal:
```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"TransactionType": "LoanBrokerCoverDeposit",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Balance ===
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
Cover balance after deposit: 2000 TSTUSD
=== Preparing LoanBrokerCoverWithdraw transaction ===
{
"TransactionType": "LoanBrokerCoverWithdraw",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverWithdraw transaction ===
Cover withdraw successful!
=== Updated Cover Balance ===
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
Cover balance after withdraw: 1000 TSTUSD
```
---
## Create a Loan
```sh
node createLoan.js
```
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information:
```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
Borrower address: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15
=== Preparing LoanSet transaction ===
{
"TransactionType": "LoanSet",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"PrincipalRequested": 1000,
"InterestRate": 500,
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800,
"LoanOriginationFee": 100,
"LoanServiceFee": 10,
"Flags": 0,
"Sequence": 3212122,
"LastLedgerSequence": 3212233,
"Fee": "2"
}
=== Adding loan broker signature ===
TxnSignature: 44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400
SigningPubKey: ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C
Signed loanSetTx for borrower to sign over:
{
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"Fee": "2",
"Flags": 0,
"GracePeriod": 604800,
"InterestRate": 500,
"LastLedgerSequence": 3212233,
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 3212122,
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C",
"TransactionType": "LoanSet",
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400"
}
=== Adding borrower signature ===
Borrower TxnSignature: 2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F
Borrower SigningPubKey: ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA
Fully signed LoanSet transaction:
{
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"CounterpartySignature": {
"SigningPubKey": "ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA",
"TxnSignature": "2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F"
},
"Fee": "2",
"Flags": 0,
"GracePeriod": 604800,
"InterestRate": 500,
"LastLedgerSequence": 3212233,
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 3212122,
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C",
"TransactionType": "LoanSet",
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400"
}
=== Submitting signed LoanSet transaction ===
Loan created successfully!
=== Loan Information ===
{
"Borrower": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"GracePeriod": 604800,
"InterestRate": 500,
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
"LoanOriginationFee": "100",
"LoanSequence": 3,
"LoanServiceFee": "10",
"NextPaymentDueDate": 825408182,
"PaymentInterval": 2592000,
"PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000",
"StartDate": 822816182,
"TotalValueOutstanding": "1003"
}
```
---
## Manage a Loan
```sh
node loanManage.js
```
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
LoanID: D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C
=== Loan Status ===
Total Amount Owed: 1001 TSTUSD.
Payment Due Date: 2/25/2026, 11:58:20 PM
=== Preparing LoanManage transaction to impair loan ===
{
"TransactionType": "LoanManage",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
"Flags": 131072
}
=== Submitting LoanManage impairment transaction ===
Loan impaired successfully!
New Payment Due Date: 1/27/2026, 12:05:02 AM
Grace Period: 60 seconds
=== Countdown until loan can be defaulted ===
Grace period expired. Loan can now be defaulted.
=== Preparing LoanManage transaction to default loan ===
{
"TransactionType": "LoanManage",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
"Flags": 65536
}
=== Submitting LoanManage default transaction ===
Loan defaulted successfully!
=== Checking final loan status ===
Final loan flags (parsed): {"tfLoanDefault":true,"tfLoanImpair":true}
```
## Pay a Loan
```sh
node loanPay.js
```
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry:
```sh
Borrower address: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA
LoanID: 8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
=== Loan Status ===
Amount Owed: 1001 TSTUSD
Loan Service Fee: 10 TSTUSD
Total Payment Due (including fees): 1011 TSTUSD
=== Preparing LoanPay transaction ===
{
"TransactionType": "LoanPay",
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E",
"Amount": {
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
"value": "1011"
}
}
=== Submitting LoanPay transaction ===
Loan paid successfully!
=== Loan Status After Payment ===
Outstanding Loan Balance: Loan fully paid off!
=== Preparing LoanDelete transaction ===
{
"TransactionType": "LoanDelete",
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E"
}
=== Submitting LoanDelete transaction ===
Loan deleted successfully!
=== Verifying Loan Deletion ===
Loan has been successfully removed from the XRP Ledger!
```

View File

@@ -0,0 +1,143 @@
// IMPORTANT: This example deposits and claws back first-loss capital from a
// preconfigured LoanBroker entry. The first-loss capital is an MPT
// with clawback enabled.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const mptIssuer = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const loanBrokerID = setupData.loanBrokerID
const mptID = setupData.mptID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`MPT issuer address: ${mptIssuer.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
console.log(`MPT ID: ${mptID}`)
// Check cover available ----------------------
console.log(`\n=== Cover Available ===\n`)
const coverInfo = await client.request({
command: 'ledger_entry',
index: loanBrokerID,
ledger_index: 'validated'
})
let currentCoverAvailable = coverInfo.result.node.CoverAvailable || '0'
console.log(`${currentCoverAvailable} TSTUSD`)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
const coverDepositTx = {
TransactionType: 'LoanBrokerCoverDeposit',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '1000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverDepositTx)
console.log(JSON.stringify(coverDepositTx, null, 2))
// Sign, submit, and wait for deposit validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
const depositResponse = await client.submitAndWait(coverDepositTx, {
wallet: loanBroker,
autofill: true
})
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = depositResponse.result.meta.TransactionResult
console.error('Error: Unable to deposit cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover deposit successful!')
// Extract updated cover available after deposit ----------------------
console.log(`\n=== Cover Available After Deposit ===\n`)
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
currentCoverAvailable = loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable
console.log(`${currentCoverAvailable} TSTUSD`)
// Verify issuer of cover asset matches ----------------------
// Only the issuer of the asset can submit clawback transactions.
// The asset must also have clawback enabled.
console.log(`\n=== Verifying Asset Issuer ===\n`)
const assetIssuerInfo = await client.request({
command: 'ledger_entry',
mpt_issuance: mptID,
ledger_index: 'validated'
})
if (assetIssuerInfo.result.node.Issuer !== mptIssuer.address) {
console.error(`Error: ${assetIssuerInfo.result.node.Issuer} does not match account (${mptIssuer.address}) attempting clawback!`)
await client.disconnect()
process.exit(1)
}
console.log(`MPT issuer account verified: ${mptIssuer.address}. Proceeding to clawback.`)
// Prepare LoanBrokerCoverClawback transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverClawback transaction ===\n`)
const coverClawbackTx = {
TransactionType: 'LoanBrokerCoverClawback',
Account: mptIssuer.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: currentCoverAvailable
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverClawbackTx)
console.log(JSON.stringify(coverClawbackTx, null, 2))
// Sign, submit, and wait for clawback validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverClawback transaction ===\n`)
const clawbackResponse = await client.submitAndWait(coverClawbackTx, {
wallet: mptIssuer,
autofill: true
})
if (clawbackResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = clawbackResponse.result.meta.TransactionResult
console.error('Error: Unable to clawback cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log(`Successfully clawed back ${currentCoverAvailable} TSTUSD!`)
// Extract final cover available ----------------------
console.log(`\n=== Final Cover Available After Clawback ===\n`)
loanBrokerNode = clawbackResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable || '0'} TSTUSD`)
await client.disconnect()

View File

@@ -0,0 +1,108 @@
// IMPORTANT: This example deposits and withdraws first-loss capital from a
// preconfigured LoanBroker entry.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const loanBrokerID = setupData.loanBrokerID
const mptID = setupData.mptID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
console.log(`MPT ID: ${mptID}`)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
const coverDepositTx = {
TransactionType: 'LoanBrokerCoverDeposit',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '2000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverDepositTx)
console.log(JSON.stringify(coverDepositTx, null, 2))
// Sign, submit, and wait for deposit validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
const depositResponse = await client.submitAndWait(coverDepositTx, {
wallet: loanBroker,
autofill: true
})
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = depositResponse.result.meta.TransactionResult
console.error('Error: Unable to deposit cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover deposit successful!')
// Extract cover balance from the transaction result
console.log(`\n=== Cover Balance ===\n`)
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
// First-loss capital is stored in the LoanBroker's pseudo-account.
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
console.log(`Cover balance after deposit: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
console.log(`\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n`)
const coverWithdrawTx = {
TransactionType: 'LoanBrokerCoverWithdraw',
Account: loanBroker.address,
LoanBrokerID: loanBrokerID,
Amount: {
mpt_issuance_id: mptID,
value: '1000'
}
}
// Validate the transaction structure before submitting
xrpl.validate(coverWithdrawTx)
console.log(JSON.stringify(coverWithdrawTx, null, 2))
// Sign, submit, and wait for withdraw validation ----------------------
console.log(`\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n`)
const withdrawResponse = await client.submitAndWait(coverWithdrawTx, {
wallet: loanBroker,
autofill: true
})
if (withdrawResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = withdrawResponse.result.meta.TransactionResult
console.error('Error: Unable to withdraw cover:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Cover withdraw successful!')
// Extract updated cover balance from the transaction result
console.log(`\n=== Updated Cover Balance ===\n`)
loanBrokerNode = withdrawResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
console.log(`Cover balance after withdraw: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
await client.disconnect()

View File

@@ -0,0 +1,130 @@
// IMPORTANT: This example creates a loan using a preconfigured
// loan broker, borrower, and private vault.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanBrokerID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
const loanBrokerID = setupData.loanBrokerID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`Borrower address: ${borrower.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)
// Prepare LoanSet transaction ----------------------
// Account and Counterparty accounts can be swapped, but determines signing order.
// Account signs first, Counterparty signs second.
console.log(`\n=== Preparing LoanSet transaction ===\n`)
// Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {}
const loanSetTx = await client.autofill({
TransactionType: 'LoanSet',
Account: loanBroker.address,
Counterparty: borrower.address,
LoanBrokerID: loanBrokerID,
PrincipalRequested: 1000,
InterestRate: 500,
PaymentTotal: 12,
PaymentInterval: 2592000,
GracePeriod: 604800,
LoanOriginationFee: 100,
LoanServiceFee: 10
})
console.log(JSON.stringify(loanSetTx, null, 2))
// Loan broker signs first
console.log(`\n=== Adding loan broker signature ===\n`)
const loanBrokerSignature = await client.request({
command: 'sign',
tx_json: loanSetTx,
secret: loanBroker.seed
})
const loanBrokerSignatureResult = loanBrokerSignature.result.tx_json
console.log(`TxnSignature: ${loanBrokerSignatureResult.TxnSignature}`)
console.log(`SigningPubKey: ${loanBrokerSignatureResult.SigningPubKey}\n`)
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignatureResult, null, 2)}`)
// Borrower signs second
console.log(`\n=== Adding borrower signature ===\n`)
const borrowerSignature = await client.request({
command: 'sign',
tx_json: loanBrokerSignatureResult,
secret: borrower.seed,
signature_target: 'CounterpartySignature'
})
const borrowerSignatureResult = borrowerSignature.result.tx_json
console.log(`Borrower TxnSignature: ${borrowerSignatureResult.CounterpartySignature.TxnSignature}`)
console.log(`Borrower SigningPubKey: ${borrowerSignatureResult.CounterpartySignature.SigningPubKey}`)
// Validate the transaction structure before submitting.
xrpl.validate(borrowerSignatureResult)
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(borrowerSignatureResult, null, 2)}`)
// Submit and wait for validation ----------------------
console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
// Submit the transaction
const submitResult = await client.submit(borrowerSignatureResult)
const txHash = submitResult.result.tx_json.hash
// Helper function to check tx hash is validated
async function validateTx (hash, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const tx = await client.request({ command: 'tx', transaction: hash })
if (tx.result.validated) {
return tx
}
} catch (error) {
// Transaction not validated yet, check again
}
}
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
await client.disconnect()
process.exit(1)
}
// Validate the transaction
const submitResponse = await validateTx(txHash)
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan created successfully!')
// Extract loan information from the transaction result.
console.log(`\n=== Loan Information ===\n`)
const loanNode = submitResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
)
console.log(JSON.stringify(loanNode.CreatedNode.NewFields, null, 2))
await client.disconnect()

View File

@@ -0,0 +1,66 @@
// IMPORTANT: This example creates a loan broker using an existing account
// that has already created a PRIVATE vault.
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
// and loanBroker values with your own.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and VaultID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const vaultID = setupData.vaultID
console.log(`\nLoan broker/vault owner address: ${loanBroker.address}`)
console.log(`Vault ID: ${vaultID}`)
// Prepare LoanBrokerSet transaction ----------------------
console.log(`\n=== Preparing LoanBrokerSet transaction ===\n`)
const loanBrokerSetTx = {
TransactionType: 'LoanBrokerSet',
Account: loanBroker.address,
VaultID: vaultID,
ManagementFeeRate: 1000
}
// Validate the transaction structure before submitting
xrpl.validate(loanBrokerSetTx)
console.log(JSON.stringify(loanBrokerSetTx, null, 2))
// Submit, sign, and wait for validation ----------------------
console.log(`\n=== Submitting LoanBrokerSet transaction ===\n`)
const submitResponse = await client.submitAndWait(loanBrokerSetTx, {
wallet: loanBroker,
autofill: true
})
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan broker:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan broker created successfully!')
// Extract loan broker information from the transaction result
console.log(`\n=== Loan Broker Information ===\n`)
const loanBrokerNode = submitResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
)
console.log(`LoanBroker ID: ${loanBrokerNode.CreatedNode.LedgerIndex}`)
console.log(`LoanBroker Psuedo-Account Address: ${loanBrokerNode.CreatedNode.NewFields.Account}`)
await client.disconnect()

View File

@@ -0,0 +1,375 @@
import xrpl from 'xrpl'
import fs from 'fs'
// Setup script for lending protocol tutorials
process.stdout.write('Setting up tutorial: 0/6\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// Create and fund wallets
const [
{ wallet: loanBroker },
{ wallet: borrower },
{ wallet: depositor },
{ wallet: credentialIssuer }
] = await Promise.all([
client.fundWallet(),
client.fundWallet(),
client.fundWallet(),
client.fundWallet()
])
process.stdout.write('Setting up tutorial: 1/6\r')
// Issue MPT with depositor
// Create tickets for later use with loanBroker
// Set up credentials and domain with credentialIssuer
const credentialType = xrpl.convertStringToHex('KYC-Verified')
const mptData = {
ticker: 'TSTUSD',
name: 'Test USD MPT',
desc: 'A sample non-yield-bearing stablecoin backed by U.S. Treasuries.',
icon: 'https://example.org/tstusd-icon.png',
asset_class: 'rwa',
asset_subclass: 'stablecoin',
issuer_name: 'Example Treasury Reserve Co.',
uris: [
{
uri: 'https://exampletreasury.com/tstusd',
category: 'website',
title: 'Product Page'
},
{
uri: 'https://exampletreasury.com/tstusd/reserve',
category: 'docs',
title: 'Reserve Attestation'
}
],
additional_info: {
reserve_type: 'U.S. Treasury Bills',
custody_provider: 'Example Custodian Bank',
audit_frequency: 'Monthly',
last_audit_date: '2026-01-15',
pegged_currency: 'USD'
}
}
const [ticketCreateResponse, mptIssuanceResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: loanBroker.address,
TicketCount: 2
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'MPTokenIssuanceCreate',
Account: depositor.address,
MaximumAmount: '100000000',
TransferFee: 0,
Flags:
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanClawback |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTrade,
MPTokenMetadata: xrpl.encodeMPTokenMetadata(mptData)
}, { wallet: depositor, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
Account: credentialIssuer.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: loanBroker.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: borrower.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: depositor.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'PermissionedDomainSet',
Account: credentialIssuer.address,
AcceptedCredentials: [
{
Credential: {
Issuer: credentialIssuer.address,
CredentialType: credentialType
}
}
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet: credentialIssuer, autofill: true })
])
// Extract ticket sequence numbers
const tickets = ticketCreateResponse.result.meta.AffectedNodes
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
.map(node => node.CreatedNode.NewFields.TicketSequence)
// Extract MPT issuance ID
const mptID = mptIssuanceResponse.result.meta.mpt_issuance_id
// Get domain ID
const credentialIssuerObjects = await client.request({
command: 'account_objects',
account: credentialIssuer.address,
ledger_index: 'validated'
})
const domainID = credentialIssuerObjects.result.account_objects.find(node =>
node.LedgerEntryType === 'PermissionedDomain'
).index
process.stdout.write('Setting up tutorial: 2/6\r')
// Accept credentials and authorize MPT for each account
await Promise.all([
...([loanBroker, borrower].map(wallet =>
client.submitAndWait({
TransactionType: 'Batch',
Account: wallet.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: wallet.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: wallet.address,
MPTokenIssuanceID: mptID,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet, autofill: true })
)),
// Depositor only needs to accept credentials
client.submitAndWait({
TransactionType: 'CredentialAccept',
Account: depositor.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType
}, { wallet: depositor, autofill: true })
])
process.stdout.write('Setting up tutorial: 3/6\r')
// Create private vault and distribute MPT to accounts
const [vaultCreateResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'VaultCreate',
Account: loanBroker.address,
Asset: {
mpt_issuance_id: mptID
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainID
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: loanBroker.address,
Amount: {
mpt_issuance_id: mptID,
value: '5000'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: borrower.address,
Amount: {
mpt_issuance_id: mptID,
value: '2500'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet: depositor, autofill: true })
])
const vaultID = vaultCreateResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Vault'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 4/6\r')
// Create LoanBroker and deposit MPT into vault
const [loanBrokerSetResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'LoanBrokerSet',
Account: loanBroker.address,
VaultID: vaultID
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'VaultDeposit',
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: mptID,
value: '50000000'
}
}, { wallet: depositor, autofill: true })
])
const loanBrokerID = loanBrokerSetResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 5/6\r')
// Create 2 identical loans with complete repayment due in 30 days
// Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {}
// Helper function to create and sign a LoanSet transaction
async function createSignedLoanSetTx (ticketSequence) {
const loanSetTx = await client.autofill({
TransactionType: 'LoanSet',
Account: loanBroker.address,
Counterparty: borrower.address,
LoanBrokerID: loanBrokerID,
PrincipalRequested: 1000,
InterestRate: 500,
PaymentTotal: 1,
PaymentInterval: 2592000,
LoanOriginationFee: 100,
LoanServiceFee: 10,
Sequence: 0,
TicketSequence: ticketSequence
})
const loanBrokerSignature = await client.request({
command: 'sign',
tx_json: loanSetTx,
secret: loanBroker.seed
})
const borrowerSignature = await client.request({
command: 'sign',
tx_json: loanBrokerSignature.result.tx_json,
secret: borrower.seed,
signature_target: 'CounterpartySignature'
})
return borrowerSignature.result.tx_json
}
// Create and submit both loans
const [signedLoan1, signedLoan2] = await Promise.all([
createSignedLoanSetTx(tickets[0]),
createSignedLoanSetTx(tickets[1])
])
const [submitLoan1, submitLoan2] = await Promise.all([
client.submit(signedLoan1),
client.submit(signedLoan2)
])
const hash1 = submitLoan1.result.tx_json.hash
const hash2 = submitLoan2.result.tx_json.hash
// Helper function to check tx hash is validated
async function validateTx (hash, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const tx = await client.request({ command: 'tx', transaction: hash })
if (tx.result.validated) {
return tx
}
} catch (error) {
// Transaction not validated yet, check again
}
}
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
await client.disconnect()
process.exit(1)
}
const [submitResponse1, submitResponse2] = await Promise.all([
validateTx(hash1),
validateTx(hash2)
])
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
).CreatedNode.LedgerIndex
const loanID2 = submitResponse2.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 6/6\r')
// Write setup data to JSON file
const setupData = {
description: 'This file is auto-generated by lendingSetup.js. It stores XRPL account info for use in lending protocol tutorials.',
loanBroker: {
address: loanBroker.address,
seed: loanBroker.seed
},
borrower: {
address: borrower.address,
seed: borrower.seed
},
depositor: {
address: depositor.address,
seed: depositor.seed
},
credentialIssuer: {
address: credentialIssuer.address,
seed: credentialIssuer.seed
},
domainID,
mptID,
vaultID,
loanBrokerID,
loanID1,
loanID2
}
fs.writeFileSync('lendingSetup.json', JSON.stringify(setupData, null, 2))
process.stdout.write('Setting up tutorial: Complete!\n')
await client.disconnect()

View File

@@ -0,0 +1,144 @@
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
// After the 60 seconds pass, this example defaults the loan.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
const loanID = setupData.loanID1
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`LoanID: ${loanID}`)
// Check loan status before impairment ----------------------
console.log(`\n=== Loan Status ===\n`)
const loanStatus = await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
console.log(`Total Amount Owed: ${loanStatus.result.node.TotalValueOutstanding} TSTUSD.`)
// Convert Ripple Epoch timestamp to local date and time
let nextPaymentDueDate = loanStatus.result.node.NextPaymentDueDate
let paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
console.log(`Payment Due Date: ${paymentDue.toLocaleString()}`)
// Prepare LoanManage transaction to impair the loan ----------------------
console.log(`\n=== Preparing LoanManage transaction to impair loan ===\n`)
const loanManageImpair = {
TransactionType: 'LoanManage',
Account: loanBroker.address,
LoanID: loanID,
Flags: xrpl.LoanManageFlags.tfLoanImpair
}
// Validate the impairment transaction before submitting
xrpl.validate(loanManageImpair)
console.log(JSON.stringify(loanManageImpair, null, 2))
// Sign, submit, and wait for impairment validation ----------------------
console.log(`\n=== Submitting LoanManage impairment transaction ===\n`)
const impairResponse = await client.submitAndWait(loanManageImpair, {
wallet: loanBroker,
autofill: true
})
if (impairResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = impairResponse.result.meta.TransactionResult
console.error('Error: Unable to impair loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan impaired successfully!')
// Extract loan impairment info from transaction results ----------------------
let loanNode = impairResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
// Check grace period and next payment due date
const gracePeriod = loanNode.ModifiedNode.FinalFields.GracePeriod
nextPaymentDueDate = loanNode.ModifiedNode.FinalFields.NextPaymentDueDate
const defaultTime = nextPaymentDueDate + gracePeriod
paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
console.log(`New Payment Due Date: ${paymentDue.toLocaleString()}`)
console.log(`Grace Period: ${gracePeriod} seconds`)
// Convert current time to Ripple Epoch timestamp
const currentTime = Math.floor(Date.now() / 1000) - 946684800
let secondsUntilDefault = defaultTime - currentTime
// Countdown until loan can be defaulted ----------------------
console.log(`\n=== Countdown until loan can be defaulted ===\n`)
await new Promise((resolve) => {
const countdown = setInterval(() => {
if (secondsUntilDefault <= 0) {
clearInterval(countdown)
process.stdout.write('\rGrace period expired. Loan can now be defaulted.\n')
resolve()
} else {
process.stdout.write(`\r${secondsUntilDefault} seconds...`)
secondsUntilDefault--
}
}, 1000)
})
// Prepare LoanManage transaction to default the loan ----------------------
console.log(`\n=== Preparing LoanManage transaction to default loan ===\n`)
const loanManageDefault = {
TransactionType: 'LoanManage',
Account: loanBroker.address,
LoanID: loanID,
Flags: xrpl.LoanManageFlags.tfLoanDefault
}
// Validate the default transaction before submitting
xrpl.validate(loanManageDefault)
console.log(JSON.stringify(loanManageDefault, null, 2))
// Sign, submit, and wait for default validation ----------------------
console.log(`\n=== Submitting LoanManage default transaction ===\n`)
const defaultResponse = await client.submitAndWait(loanManageDefault, {
wallet: loanBroker,
autofill: true
})
if (defaultResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = defaultResponse.result.meta.TransactionResult
console.error('Error: Unable to default loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan defaulted successfully!')
// Verify loan default status from transaction results ----------------------
console.log(`\n=== Checking final loan status ===\n`)
loanNode = defaultResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
const loanFlags = loanNode.ModifiedNode.FinalFields.Flags
console.log(`Final loan flags (parsed): ${JSON.stringify(xrpl.parseTransactionFlags({
TransactionType: 'LoanManage',
Flags: loanFlags
}))}`)
await client.disconnect()

View File

@@ -0,0 +1,134 @@
// IMPORTANT: This example pays off an existing loan and then deletes it.
import fs from 'fs'
import { execSync } from 'child_process'
import xrpl from 'xrpl'
// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// This step checks for the necessary setup data to run the lending protocol tutorials.
// If missing, lendingSetup.js will generate the data.
if (!fs.existsSync('lendingSetup.json')) {
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
execSync('node lendingSetup.js', { stdio: 'inherit' })
}
// Load preconfigured accounts and LoanID.
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
// You can replace these values with your own
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
const loanID = setupData.loanID2
const mptID = setupData.mptID
console.log(`\nBorrower address: ${borrower.address}`)
console.log(`LoanID: ${loanID}`)
console.log(`MPT ID: ${mptID}`)
// Check initial loan status ----------------------
console.log(`\n=== Loan Status ===\n`)
const loanStatus = await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
const totalValueOutstanding = loanStatus.result.node.TotalValueOutstanding
const loanServiceFee = loanStatus.result.node.LoanServiceFee
const totalPayment = (BigInt(totalValueOutstanding) + BigInt(loanServiceFee)).toString()
console.log(`Amount Owed: ${totalValueOutstanding} TSTUSD`)
console.log(`Loan Service Fee: ${loanServiceFee} TSTUSD`)
console.log(`Total Payment Due (including fees): ${totalPayment} TSTUSD`)
// Prepare LoanPay transaction ----------------------
console.log(`\n=== Preparing LoanPay transaction ===\n`)
const loanPayTx = {
TransactionType: 'LoanPay',
Account: borrower.address,
LoanID: loanID,
Amount: {
mpt_issuance_id: mptID,
value: totalPayment
}
}
// Validate the transaction structure before submitting
xrpl.validate(loanPayTx)
console.log(JSON.stringify(loanPayTx, null, 2))
// Sign, submit, and wait for payment validation ----------------------
console.log(`\n=== Submitting LoanPay transaction ===\n`)
const payResponse = await client.submitAndWait(loanPayTx, {
wallet: borrower,
autofill: true
})
if (payResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = payResponse.result.meta.TransactionResult
console.error('Error: Unable to pay loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan paid successfully!')
// Extract updated loan info from transaction results ----------------------
console.log(`\n=== Loan Status After Payment ===\n`)
const loanNode = payResponse.result.meta.AffectedNodes.find(node =>
node.ModifiedNode?.LedgerEntryType === 'Loan'
)
const finalBalance = loanNode.ModifiedNode.FinalFields.TotalValueOutstanding
? `${loanNode.ModifiedNode.FinalFields.TotalValueOutstanding} TSTUSD`
: 'Loan fully paid off!'
console.log(`Outstanding Loan Balance: ${finalBalance}`)
// Prepare LoanDelete transaction ----------------------
// Either the loan broker or borrower can submit this transaction.
console.log(`\n=== Preparing LoanDelete transaction ===\n`)
const loanDeleteTx = {
TransactionType: 'LoanDelete',
Account: borrower.address,
LoanID: loanID
}
// Validate the transaction structure before submitting
xrpl.validate(loanDeleteTx)
console.log(JSON.stringify(loanDeleteTx, null, 2))
// Sign, submit, and wait for deletion validation ----------------------
console.log(`\n=== Submitting LoanDelete transaction ===\n`)
const deleteResponse = await client.submitAndWait(loanDeleteTx, {
wallet: borrower,
autofill: true
})
if (deleteResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = deleteResponse.result.meta.TransactionResult
console.error('Error: Unable to delete loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan deleted successfully!')
// Verify loan deletion ----------------------
console.log(`\n=== Verifying Loan Deletion ===\n`)
try {
await client.request({
command: 'ledger_entry',
index: loanID,
ledger_index: 'validated'
})
console.log('Warning: Loan still exists in the ledger.')
} catch (error) {
if (error.data.error === 'entryNotFound') {
console.log('Loan has been successfully removed from the XRP Ledger!')
} else {
console.error('Error checking loan status:', error)
}
}
await client.disconnect()

View File

@@ -0,0 +1,8 @@
{
"name": "lending-protocol-examples",
"description": "Example code for creating and managing loans.",
"dependencies": {
"xrpl": "^4.5.0"
},
"type": "module"
}

View File

@@ -0,0 +1,3 @@
# Single Asset Vault Examples
Shows how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.

View File

@@ -0,0 +1,187 @@
# Single Asset Vault Examples (JavaScript)
This directory contains JavaScript examples demonstrating how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
## Setup
Install dependencies before running any examples:
```sh
npm i
```
---
## Create a Vault
```sh
node createVault.js
```
The script should output the VaultCreate transaction, vault ID, and complete vault information:
```sh
Vault owner address: rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms
MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Permissioned domain ID: 3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1
=== VaultCreate transaction ===
{
"TransactionType": "VaultCreate",
"Account": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
"Asset": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
},
"Flags": 65536,
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
"Data": "50726976617465207661756C74",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"AssetsMaximum": "0",
"WithdrawalPolicy": 1
}
=== Submitting VaultCreate transaction... ===
Vault created successfully!
Vault ID: 9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29
Vault pseudo-account address: rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj
Share MPT issuance ID: 000000012DF200D67FF9DA7686FF8B6F32097337D7765211
=== Getting vault_info... ===
{
"api_version": 2,
"id": 12,
"result": {
"ledger_hash": "73B53C0608A9C87C2B97314F0BAD109F236C4A95FB53FE4E8CEAEFE826A1E7AB",
"ledger_index": 597229,
"validated": true,
"vault": {
"Account": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
"Asset": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
},
"Data": "50726976617465207661756C74",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
"OwnerNode": "0",
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
"PreviousTxnLgrSeq": 597229,
"Sequence": 597228,
"ShareMPTID": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211",
"WithdrawalPolicy": 1,
"index": "9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29",
"shares": {
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
"Flags": 60,
"Issuer": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
"PreviousTxnLgrSeq": 597229,
"Sequence": 1,
"index": "4C3CC0AF1FE27EBE364F02AFF889D73D1F6F7CB5ED6126D1CD605E8952E18302",
"mpt_issuance_id": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211"
}
}
},
"type": "response"
}
```
---
## Deposit into a Vault
```sh
node deposit.js
```
The script should output the vault state before and after the deposit, along with the depositor's share balance:
```sh
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
=== Getting initial vault state... ===
- Total vault value: 1
- Available assets: 1
=== Checking depositor's balance... ===
Balance: 9937
=== VaultDeposit transaction ===
{
"TransactionType": "VaultDeposit",
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
"Amount": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
"value": "1"
}
}
=== Submitting VaultDeposit transaction... ===
Deposit successful!
=== Vault state after deposit ===
- Total vault value: 2
- Available assets: 2
=== Depositor's share balance ==
Shares held: 2
```
---
## Withdraw from a Vault
```sh
node withdraw.js
```
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
```sh
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
=== Getting initial vault state... ===
Initial vault state:
Assets Total: 2
Assets Available: 2
=== Checking depositor's share balance... ===
Shares held: 2
=== Preparing VaultWithdraw transaction ===
{
"TransactionType": "VaultWithdraw",
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
"Amount": {
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
"value": "1"
}
}
=== Submitting VaultWithdraw transaction... ===
Withdrawal successful!
=== Vault state after withdrawal ===
Assets Total: 1
Assets Available: 1
=== Depositor's share balance ==
Shares held: 1
=== Depositor's asset balance ==
Balance: 9937
```

View File

@@ -0,0 +1,111 @@
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// Create and fund vault owner account
const { wallet: vaultOwner } = await client.fundWallet()
// You can replace these values with your own
const mptIssuanceId = setupData.mptIssuanceId
const domainId = setupData.domainId
console.log(`Vault owner address: ${vaultOwner.address}`)
console.log(`MPT issuance ID: ${mptIssuanceId}`)
console.log(`Permissioned domain ID: ${domainId}\n`)
// Prepare VaultCreate transaction ----------------------
console.log(`\n=== VaultCreate transaction ===`)
const vaultCreateTx = {
TransactionType: "VaultCreate",
Account: vaultOwner.address,
Asset: { mpt_issuance_id: mptIssuanceId },
Flags: xrpl.VaultCreateFlags.tfVaultPrivate, // Omit tfVaultPrivate flag for public vaults
// To make vault shares non-transferable add the tfVaultShareNonTransferable flag:
// Flags: xrpl.VaultCreateFlags.tfVaultPrivate | xrpl.VaultCreateFlags.tfVaultShareNonTransferable
DomainID: domainId, // Omit for public vaults
// Convert Vault data to a string (without excess whitespace), then string to hex.
Data: xrpl.convertStringToHex(JSON.stringify(
{ n: "LATAM Fund II", w: "examplefund.com" })
),
// Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
// See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: "SHARE1",
name: "Vault shares",
desc: "Proportional ownership shares of the vault.",
icon: "example.com/asset-icon.png",
asset_class: "defi",
issuer_name: "Asset Issuer Name",
uris: [
{
uri: "example.com/asset",
category: "website",
title: "Asset Website",
},
{
uri: "example.com/docs",
category: "docs",
title: "Docs",
},
],
additional_info: {
example_info: "test",
},
}),
AssetsMaximum: "0", // No cap
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe,
};
// Validate the transaction structure before submitting
xrpl.validate(vaultCreateTx)
console.log(JSON.stringify(vaultCreateTx, null, 2))
// Submit, sign, and wait for validation ----------------------
console.log("\n=== Submitting VaultCreate transaction... ===")
const submit_response = await client.submitAndWait(vaultCreateTx, {
wallet: vaultOwner,
autofill: true,
})
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = submit_response.result.meta.TransactionResult;
console.error("Error: Unable to create vault:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Vault created successfully!")
// Extract vault information from the transaction result
const affectedNodes = submit_response.result.meta.AffectedNodes || []
const vaultNode = affectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === "Vault"
)
if (vaultNode) {
console.log(`\nVault ID: ${vaultNode.CreatedNode.LedgerIndex}`)
console.log(`Vault pseudo-account address: ${vaultNode.CreatedNode.NewFields.Account}`)
console.log(`Share MPT issuance ID: ${vaultNode.CreatedNode.NewFields.ShareMPTID}`)
}
// Call vault_info method to retrieve the vault's information
console.log("\n=== Getting vault_info... ===")
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vault_info_response = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(JSON.stringify(vault_info_response, null, 2))
await client.disconnect()

View File

@@ -0,0 +1,145 @@
// IMPORTANT: This example deposits into an existing PRIVATE vault.
// The depositor account used has valid credentials in the vault's Permissioned Domain.
// Without valid credentials, the VaultDeposit transaction will fail.
// If you want to deposit into a public vault, you can replace the vaultID and shareMPTIssuanceId
// values with your own.
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// You can replace these values with your own
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const vaultID = setupData.vaultID
const assetMPTIssuanceId = setupData.mptIssuanceId
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
console.log(`Depositor address: ${depositor.address}`)
console.log(`Vault ID: ${vaultID}`)
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
const depositAmount = 1
// Get initial vault state ----------------------
console.log("\n=== Getting initial vault state... ===")
const initialVaultInfo = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(` - Total vault value: ${initialVaultInfo.result.vault.AssetsTotal}`)
console.log(` - Available assets: ${initialVaultInfo.result.vault.AssetsAvailable}`)
// Check depositor's asset balance ----------------------
console.log("\n=== Checking depositor's balance... ===")
try {
// Use ledger_entry to get specific MPT issuance balance
const ledgerEntryResult = await client.request({
command: "ledger_entry",
mptoken: {
mpt_issuance_id: assetMPTIssuanceId,
account: depositor.address
},
ledger_index: "validated"
})
const balance = ledgerEntryResult.result.node?.MPTAmount
console.log(`Balance: ${balance}`)
// Check if balance is sufficient
if (balance < depositAmount) {
console.error(`Error: Insufficient balance! Have ${balance}, need ${depositAmount}`)
await client.disconnect()
process.exit(1)
}
} catch (error) {
if (error.data?.error === 'entryNotFound') {
console.log(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
}
await client.disconnect()
process.exit(1)
}
// Prepare VaultDeposit transaction ----------------------
console.log(`\n=== VaultDeposit transaction ===`)
const vaultDepositTx = {
TransactionType: "VaultDeposit",
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: assetMPTIssuanceId,
value: depositAmount.toString()
}
}
// Validate the transaction structure before submitting
xrpl.validate(vaultDepositTx)
console.log(JSON.stringify(vaultDepositTx, null, 2))
// Submit VaultDeposit transaction ----------------------
console.log("\n=== Submitting VaultDeposit transaction... ===")
const depositResult = await client.submitAndWait(vaultDepositTx, {
wallet: depositor,
autofill: true,
})
if (depositResult.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = depositResult.result.meta.TransactionResult
console.error("Error: Unable to deposit:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Deposit successful!")
// Extract vault state from transaction metadata ----------------------
console.log("\n=== Vault state after deposit ===")
const affectedNodes = depositResult.result.meta.AffectedNodes
const vaultNode = affectedNodes.find(
(node) => {
return (
node.ModifiedNode &&
node.ModifiedNode.LedgerEntryType === "Vault" &&
node.ModifiedNode.LedgerIndex === vaultID
)
}
)
if (vaultNode) {
const vaultFields = vaultNode.ModifiedNode.FinalFields
console.log(` - Total vault value: ${vaultFields.AssetsTotal}`)
console.log(` - Available assets: ${vaultFields.AssetsAvailable}`)
}
// Get the depositor's share balance ----------------------
console.log("\n=== Depositor's share balance ==")
const depositorShareNode = affectedNodes.find((node) => {
const shareNode = node.ModifiedNode || node.CreatedNode
const fields = shareNode?.FinalFields || shareNode?.NewFields
return (
shareNode &&
shareNode.LedgerEntryType === "MPToken" &&
fields?.Account === depositor.address &&
fields?.MPTokenIssuanceID === shareMPTIssuanceId
)
})
if (depositorShareNode) {
const shareNode = depositorShareNode.ModifiedNode || depositorShareNode.CreatedNode
const shareFields = shareNode.FinalFields || shareNode.NewFields
console.log(`Shares held: ${shareFields.MPTAmount}`)
}
await client.disconnect()

View File

@@ -0,0 +1,8 @@
{
"name": "vault-examples",
"description": "Example code for creating and managing vaults",
"dependencies": {
"xrpl": "^4.5.0"
},
"type": "module"
}

View File

@@ -0,0 +1,264 @@
import xrpl from 'xrpl'
import fs from 'fs'
// Setup script for vault tutorials
process.stdout.write('Setting up tutorial: 0/7\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
// Create and fund all wallets
const [
{ wallet: mptIssuer },
{ wallet: domainOwner },
{ wallet: depositor },
{ wallet: vaultOwner }
] = await Promise.all([
client.fundWallet(),
client.fundWallet(),
client.fundWallet(),
client.fundWallet()
])
// Step 1: Create MPT issuance
process.stdout.write('Setting up tutorial: 1/7\r')
const mptCreateResult = await client.submitAndWait(
{
TransactionType: 'MPTokenIssuanceCreate',
Account: mptIssuer.address,
Flags:
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
AssetScale: 2,
TransferFee: 0,
MaximumAmount: '1000000000000',
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: 'USTST',
name: 'USTST Stablecoin',
desc: 'A test stablecoin token',
icon: 'example.org/ustst-icon.png',
asset_class: 'rwa',
asset_subclass: 'stablecoin',
issuer_name: 'Test Stablecoin Inc',
uris: [
{
uri: 'example.org/ustst',
category: 'website',
title: 'USTST Official Website'
},
{
uri: 'example.org/ustst/reserves',
category: 'attestation',
title: 'Reserve Attestation Reports'
},
{
uri: 'example.org/ustst/docs',
category: 'docs',
title: 'USTST Documentation'
}
],
additional_info: {
backing: 'USD',
reserve_ratio: '1:1'
}
})
},
{ wallet: mptIssuer, autofill: true }
)
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
// Step 2: Create Permissioned Domain
process.stdout.write('Setting up tutorial: 2/7\r')
const credType = 'VaultAccess'
const domainResult = await client.submitAndWait(
{
TransactionType: 'PermissionedDomainSet',
Account: domainOwner.address,
AcceptedCredentials: [
{
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType)
}
}
]
},
{ wallet: domainOwner, autofill: true }
)
const domainId = domainResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
).CreatedNode.LedgerIndex
// Step 3: Create depositor account with credentials and MPT balance
process.stdout.write('Setting up tutorial: 3/7\r')
await Promise.all([
client.submitAndWait(
{
TransactionType: 'CredentialCreate',
Account: domainOwner.address,
Subject: depositor.address,
CredentialType: xrpl.convertStringToHex(credType)
},
{ wallet: domainOwner, autofill: true }
),
client.submitAndWait(
{
TransactionType: 'Batch',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: depositor.address,
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: depositor.address,
MPTokenIssuanceID: mptIssuanceId,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
},
{ wallet: depositor, autofill: true }
)
])
process.stdout.write('Setting up tutorial: 4/7\r')
const paymentResult = await client.submitAndWait(
{
TransactionType: 'Payment',
Account: mptIssuer.address,
Destination: depositor.address,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '10000'
}
},
{ wallet: mptIssuer, autofill: true }
)
if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error('\nPayment failed:', paymentResult.result.meta.TransactionResult)
await client.disconnect()
process.exit(1)
}
// Step 5: Create a vault for deposit/withdraw examples
process.stdout.write('Setting up tutorial: 5/7\r')
const vaultCreateResult = await client.submitAndWait(
{
TransactionType: 'VaultCreate',
Account: vaultOwner.address,
Asset: {
mpt_issuance_id: mptIssuanceId
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainId,
Data: xrpl.convertStringToHex(
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
),
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: 'SHARE1',
name: 'Vault Shares',
desc: 'Proportional ownership shares of the vault',
icon: 'example.com/vault-shares-icon.png',
asset_class: 'defi',
issuer_name: 'Vault Owner',
uris: [
{
uri: 'example.com/asset',
category: 'website',
title: 'Asset Website'
},
{
uri: 'example.com/docs',
category: 'docs',
title: 'Docs'
}
],
additional_info: {
example_info: 'test'
}
}),
AssetsMaximum: '0',
WithdrawalPolicy:
xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
},
{ wallet: vaultOwner, autofill: true }
)
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
)
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
// Step 6: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 6/7\r')
const initialDepositResult = await client.submitAndWait(
{
TransactionType: 'VaultDeposit',
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '1000'
}
},
{ wallet: depositor, autofill: true }
)
if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error('\nInitial deposit failed:', initialDepositResult.result.meta.TransactionResult)
await client.disconnect()
process.exit(1)
}
// Step 7: Save setup data to file
process.stdout.write('Setting up tutorial: 7/7\r')
const setupData = {
mptIssuer: {
address: mptIssuer.address,
seed: mptIssuer.seed
},
mptIssuanceId,
domainOwner: {
address: domainOwner.address,
seed: domainOwner.seed
},
domainId,
credentialType: credType,
depositor: {
address: depositor.address,
seed: depositor.seed
},
vaultOwner: {
address: vaultOwner.address,
seed: vaultOwner.seed
},
vaultID,
vaultShareMPTIssuanceId
}
fs.writeFileSync('vaultSetup.json', JSON.stringify(setupData, null, 2))
process.stdout.write('Setting up tutorial: Complete!\n')
await client.disconnect()

View File

@@ -0,0 +1,159 @@
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()
// You can replace these values with your own
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
const vaultID = setupData.vaultID
const assetMPTIssuanceId = setupData.mptIssuanceId
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
console.log(`Depositor address: ${depositor.address}`)
console.log(`Vault ID: ${vaultID}`)
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
const withdrawAmount = "1"
// Get initial vault state ----------------------
console.log("\n=== Getting initial vault state... ===")
const initialVaultInfo = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(`Initial vault state:`)
console.log(` Assets Total: ${initialVaultInfo.result.vault.AssetsTotal}`)
console.log(` Assets Available: ${initialVaultInfo.result.vault.AssetsAvailable}`)
// Check depositor's share balance ----------------------
console.log("\n=== Checking depositor's share balance... ===")
try {
const shareBalanceResult = await client.request({
command: "ledger_entry",
mptoken: {
mpt_issuance_id: shareMPTIssuanceId,
account: depositor.address
},
ledger_index: "validated"
})
const shareBalance = shareBalanceResult.result.node.MPTAmount
console.log(`Shares held: ${shareBalance}`)
} catch (error) {
if (error.data?.error === 'entryNotFound') {
console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`)
}
await client.disconnect()
process.exit(1)
}
// Prepare VaultWithdraw transaction ----------------------
console.log(`\n=== Preparing VaultWithdraw transaction ===`)
const vaultWithdrawTx = {
TransactionType: "VaultWithdraw",
Account: depositor.address,
VaultID: vaultID,
Amount: {
mpt_issuance_id: assetMPTIssuanceId,
value: withdrawAmount
},
// Optional: Add Destination field to send assets to a different account
// Destination: "rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
}
// Validate the transaction structure before submitting
xrpl.validate(vaultWithdrawTx)
console.log(JSON.stringify(vaultWithdrawTx, null, 2))
// Submit VaultWithdraw transaction ----------------------
console.log("\n=== Submitting VaultWithdraw transaction... ===")
const withdrawResult = await client.submitAndWait(vaultWithdrawTx, {
wallet: depositor,
autofill: true,
})
if (withdrawResult.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = withdrawResult.result.meta.TransactionResult
console.error("Error: Unable to withdraw from vault:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Withdrawal successful!")
// Extract vault state from transaction metadata ----------------------
console.log("\n=== Vault state after withdrawal ===")
const affectedNodes = withdrawResult.result.meta.AffectedNodes
const vaultNode = affectedNodes.find(
(node) => {
const modifiedNode = node.ModifiedNode || node.DeletedNode
return (
modifiedNode &&
modifiedNode.LedgerEntryType === "Vault" &&
modifiedNode.LedgerIndex === vaultID
)
}
)
if (vaultNode) {
if (vaultNode.DeletedNode) {
console.log(` Vault empty (all assets withdrawn)`)
} else {
const vaultFields = vaultNode.ModifiedNode.FinalFields
console.log(` Assets Total: ${vaultFields.AssetsTotal}`)
console.log(` Assets Available: ${vaultFields.AssetsAvailable}`)
}
}
// Get the depositor's share balance ----------------------
console.log("\n=== Depositor's share balance ==")
const depositorShareNode = affectedNodes.find((node) => {
const modifiedNode = node.ModifiedNode || node.DeletedNode
return (
modifiedNode &&
modifiedNode.LedgerEntryType === "MPToken" &&
modifiedNode.FinalFields?.Account === depositor.address &&
modifiedNode.FinalFields?.MPTokenIssuanceID === shareMPTIssuanceId
)
})
if (depositorShareNode) {
if (depositorShareNode.DeletedNode) {
console.log(`No more shares held (redeemed all shares)`)
} else {
const shareFields = depositorShareNode.ModifiedNode.FinalFields
console.log(`Shares held: ${shareFields.MPTAmount}`)
}
}
// Get the depositor's asset balance ----------------------
console.log("\n=== Depositor's asset balance ==")
const depositorAssetNode = affectedNodes.find((node) => {
const assetNode = node.ModifiedNode || node.CreatedNode
const fields = assetNode?.FinalFields || assetNode?.NewFields
return (
assetNode &&
assetNode.LedgerEntryType === "MPToken" &&
fields?.Account === depositor.address &&
fields?.MPTokenIssuanceID === assetMPTIssuanceId
)
})
if (depositorAssetNode) {
const assetNode = depositorAssetNode.ModifiedNode || depositorAssetNode.CreatedNode
const assetFields = assetNode.FinalFields || assetNode.NewFields
console.log(`Balance: ${assetFields.MPTAmount}`)
}
await client.disconnect()

View File

@@ -1,488 +0,0 @@
import * as React from 'react';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
title: 'BDS Button Component Showcase',
description: 'Interactive showcase of the Brand Design System Button component with all states and variants.',
},
};
export default function ButtonShowcase() {
const [clickCount, setClickCount] = React.useState(0);
const handleClick = () => {
setClickCount((prev) => prev + 1);
};
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">BDS Button Component</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
A scalable button component following the XRPL Brand Design System. This showcase demonstrates all states,
responsive behavior, and accessibility features of the Primary button variant.
</p>
</section>
{/* Basic Usage */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Basic Usage</h2>
<h6 className="eyebrow mb-3">Primary Variant</h6>
</div>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Get Started
</Button>
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Submit Form
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Continue
</Button>
</div>
{clickCount > 0 && (
<p className="mt-4 text-muted">Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}</p>
)}
</section>
{/* States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Default state when button is ready for interaction.</p>
<Button variant="primary" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Button cannot be interacted with.</p>
<Button variant="primary" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons below or use Tab to focus them. Notice the background color change and icon swap.
</p>
<div className="d-flex flex-wrap">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Black Color Variant */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Color Variant</h2>
<h6 className="eyebrow mb-3">Color Theme</h6>
</div>
<p className="mb-4 text-muted">
Primary buttons can use a black color theme for dark backgrounds or alternative styling needs.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" color="black" onClick={handleClick} className="me-4 mb-4">
Black Primary
</Button>
<Button variant="primary" color="black" onClick={handleClick} className="me-4 mb-4">
Dark Button
</Button>
<Button variant="primary" color="black" onClick={handleClick} className="mb-4">
Get Started
</Button>
</div>
</section>
{/* Black Variant States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Variant States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black background with white text.</p>
<Button variant="primary" color="black" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="primary" color="black" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the background darkens slightly on hover.
</p>
<div className="d-flex flex-wrap">
<Button variant="primary" color="black" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="primary" color="black" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Green vs Black Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Green vs Black Comparison</h2>
<h6 className="eyebrow mb-3">Color Themes</h6>
</div>
<p className="mb-4 text-muted">Compare the green (default) and black color themes side by side.</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" color="green" onClick={handleClick} className="me-4 mb-4">
Green Primary
</Button>
<Button variant="primary" color="black" onClick={handleClick} className="mb-4">
Black Primary
</Button>
</div>
</section>
{/* Link Buttons */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Link Buttons</h2>
<h6 className="eyebrow mb-3">Navigation</h6>
</div>
<p className="mb-4 text-muted">
Buttons can function as links by passing an <code>href</code> prop. They render as anchor elements wrapped in a Redocly Link component for routing support.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" href="/docs" className="me-4 mb-4">
View Documentation
</Button>
<Button variant="primary" href="https://xrpl.org" target="_blank" className="me-4 mb-4">
Visit XRPL.org
</Button>
<Button variant="primary" color="black" href="/about" className="mb-4">
About Us
</Button>
</div>
</section>
{/* Without Icon */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Without Icon</h2>
<h6 className="eyebrow mb-3">Icon Control</h6>
</div>
<p className="mb-4 text-muted">Buttons can be rendered without the arrow icon when needed.</p>
<div className="d-flex flex-wrap">
<Button variant="primary" showIcon={false} onClick={handleClick} className="me-4 mb-4">
No Icon Button
</Button>
<Button variant="primary" showIcon={true} onClick={handleClick} className="mb-4">
With Icon Button
</Button>
</div>
</section>
{/* Button Types */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button Types</h2>
<h6 className="eyebrow mb-3">Form Integration</h6>
</div>
<p className="mb-4 text-muted">Different button types for form submission and actions.</p>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
className="d-flex flex-wrap"
>
<Button variant="primary" type="submit" className="me-4 mb-4">
Submit Button
</Button>
<Button variant="primary" type="reset" className="me-4 mb-4">
Reset Button
</Button>
<Button variant="primary" type="button" onClick={handleClick} className="mb-4">
Regular Button
</Button>
</form>
</section>
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-4 text-muted">
Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes:
</p>
<ul className="mb-4">
<li>
<strong>Desktop (1024px):</strong> Padding: 8px 19px 8px 20px, Gap: 16px
</li>
<li>
<strong>Tablet/Mobile (1023px):</strong> Padding: 8px 15px 8px 16px, Gap: 16px
</li>
<li>
<strong>Hover/Focus:</strong> Gap increases (22px desktop, 21px mobile) with adjusted padding to maintain
button width
</li>
</ul>
<div className="d-flex flex-wrap">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Responsive Button
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Long Button Label Example
</Button>
</div>
</section>
{/* Accessibility */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Accessibility Features</h2>
<h6 className="eyebrow mb-3">WCAG Compliance</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px black border</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Color Contrast</h5>
<ul>
<li>
<strong>Enabled:</strong> Black text (#141414) on Green 300 (#21E46B) = 9.06:1 (AAA)
</li>
<li>
<strong>Hover:</strong> Black text (#141414) on Green 200 (#70EE97) = 10.23:1 (AAA)
</li>
<li>
<strong>Disabled:</strong> Gray 500 (#838386) on Gray 200 (#E0E0E1) = 2.12:1 (acceptable for disabled
state)
</li>
</ul>
</div>
</section>
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic usage (green theme - default)
<Button variant="primary" onClick={handleClick}>
Get Started
</Button>
// Black color theme
<Button variant="primary" color="black" onClick={handleClick}>
Dark Button
</Button>
// Disabled state
<Button variant="primary" disabled>
Submit
</Button>
// Without icon
<Button variant="primary" showIcon={false}>
Continue
</Button>
// Form integration
<Button variant="primary" type="submit">
Submit Form
</Button>
// Link button (internal navigation)
<Button variant="primary" href="/docs">
View Documentation
</Button>
// Link button (external, opens in new tab)
<Button variant="primary" href="https://xrpl.org" target="_blank">
Visit XRPL.org
</Button>`}</code>
</pre>
</div>
</section>
{/* Design Specifications */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Design Specifications</h2>
<h6 className="eyebrow mb-3">Visual Details</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 16px</li>
<li>Weight: 400</li>
<li>Line Height: 23.2px</li>
<li>Letter Spacing: 0px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded)</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Min Height: 40px (touch target)</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#21E46B (Green 300)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#70EE97 (Green 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#70EE97 (Green 200)</div>
<div style={{ padding: '12px' }}>2px solid #141414</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>#21E46B (Green 300)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#838386 (Gray 500)</div>
<div style={{ padding: '12px' }}>#E0E0E1 (Gray 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.8) (80% Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.8) (80% Black)</div>
<div style={{ padding: '12px' }}>2px solid #141414</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#FFFFFF (White)</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#838386 (Gray 500)</div>
<div style={{ padding: '12px' }}>#E0E0E1 (Gray 200)</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,528 +0,0 @@
import * as React from 'react';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
title: 'BDS Secondary Button Component Showcase',
description: 'Interactive showcase of the Brand Design System Secondary Button component with all states and variants.',
},
};
export default function ButtonShowcaseSecondary() {
const [clickCount, setClickCount] = React.useState(0);
const handleClick = () => {
setClickCount((prev) => prev + 1);
};
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">BDS Secondary Button</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
The Secondary button is an outline-style button used for secondary actions. It features a transparent
background with a green stroke/border, providing visual hierarchy below the Primary button while maintaining
brand consistency.
</p>
</section>
{/* Basic Usage */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Basic Usage</h2>
<h6 className="eyebrow mb-3">Secondary Variant</h6>
</div>
<div className="d-flex flex-wrap align-items-center">
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
View Details
</Button>
<Button variant="secondary" onClick={handleClick} className="mb-4">
Explore
</Button>
</div>
{clickCount > 0 && (
<p className="mt-4 text-muted">
Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}
</p>
)}
</section>
{/* Primary vs Secondary Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Primary vs Secondary</h2>
<h6 className="eyebrow mb-3">Visual Hierarchy</h6>
</div>
<p className="mb-4 text-muted">
Use Primary for main actions and Secondary for supporting actions to create clear visual hierarchy.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Get Started
</Button>
<Button variant="secondary" onClick={handleClick} className="mb-4">
Learn More
</Button>
</div>
</section>
{/* States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Default outline style with green border and text.</p>
<Button variant="secondary" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Gray border and text indicate non-interactive state.</p>
<Button variant="secondary" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the light green background fill and darker green
border on hover/focus.
</p>
<div className="d-flex flex-wrap">
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="secondary" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Black Color Variant */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Color Variant</h2>
<h6 className="eyebrow mb-3">Color Theme</h6>
</div>
<p className="mb-4 text-muted">
Secondary buttons can use a black color theme with black text and border instead of green.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="secondary" color="black" onClick={handleClick} className="me-4 mb-4">
Black Secondary
</Button>
<Button variant="secondary" color="black" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="secondary" color="black" onClick={handleClick} className="mb-4">
View Details
</Button>
</div>
</section>
{/* Black Variant States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Variant States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black border and text with transparent background.</p>
<Button variant="secondary" color="black" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="secondary" color="black" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the subtle black background fill on hover.
</p>
<div className="d-flex flex-wrap">
<Button variant="secondary" color="black" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="secondary" color="black" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Green vs Black Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Green vs Black Comparison</h2>
<h6 className="eyebrow mb-3">Color Themes</h6>
</div>
<p className="mb-4 text-muted">Compare the green (default) and black color themes side by side.</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="secondary" color="green" onClick={handleClick} className="me-4 mb-4">
Green Secondary
</Button>
<Button variant="secondary" color="black" onClick={handleClick} className="mb-4">
Black Secondary
</Button>
</div>
</section>
{/* Without Icon */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Without Icon</h2>
<h6 className="eyebrow mb-3">Icon Control</h6>
</div>
<p className="mb-4 text-muted">Secondary buttons can also be rendered without the arrow icon.</p>
<div className="d-flex flex-wrap">
<Button variant="secondary" showIcon={false} onClick={handleClick} className="me-4 mb-4">
No Icon Button
</Button>
<Button variant="secondary" showIcon={true} onClick={handleClick} className="mb-4">
With Icon Button
</Button>
</div>
</section>
{/* Button Types */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button Types</h2>
<h6 className="eyebrow mb-3">Form Integration</h6>
</div>
<p className="mb-4 text-muted">Secondary buttons can be used for form actions like cancel or reset.</p>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
className="d-flex flex-wrap"
>
<Button variant="primary" type="submit" className="me-4 mb-4">
Submit
</Button>
<Button variant="secondary" type="reset" className="me-4 mb-4">
Reset
</Button>
<Button variant="secondary" type="button" onClick={() => alert('Cancelled!')} className="mb-4">
Cancel
</Button>
</form>
</section>
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-4 text-muted">
Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes:
</p>
<ul className="mb-4">
<li>
<strong>Desktop (1024px):</strong> Padding: 6px 17px 6px 18px (compensates for 2px border)
</li>
<li>
<strong>Tablet/Mobile (1023px):</strong> Padding: 6px 13px 6px 14px
</li>
<li>
<strong>Hover/Focus:</strong> Gap increases (22px desktop, 21px mobile) with adjusted padding
</li>
</ul>
<div className="d-flex flex-wrap">
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
Responsive Button
</Button>
<Button variant="secondary" onClick={handleClick} className="mb-4">
Long Button Label Example
</Button>
</div>
</section>
{/* Accessibility */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Accessibility Features</h2>
<h6 className="eyebrow mb-3">WCAG Compliance</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px black outline (additional to green border)</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Color Contrast</h5>
<ul>
<li>
<strong>Enabled:</strong> Green 400 (#0DAA3E) on White = 4.52:1 (AA for large text)
</li>
<li>
<strong>Hover:</strong> Green 500 (#078139) on Green 100 (#EAFCF1) = 4.87:1 (AA)
</li>
<li>
<strong>Disabled:</strong> Gray 400 (#A2A2A4) on White = reduced contrast (acceptable for disabled state)
</li>
</ul>
</div>
</section>
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic secondary button (green theme - default)
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
// Black color theme
<Button variant="secondary" color="black" onClick={handleClick}>
Learn More
</Button>
// Disabled state
<Button variant="secondary" disabled>
Unavailable
</Button>
// Without icon
<Button variant="secondary" showIcon={false}>
Cancel
</Button>
// Primary + Secondary pairing
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="secondary" type="button">
Cancel
</Button>`}</code>
</pre>
</div>
</section>
{/* Design Specifications */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Design Specifications</h2>
<h6 className="eyebrow mb-3">Visual Details</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 16px</li>
<li>Weight: 400</li>
<li>Line Height: 23.2px</li>
<li>Letter Spacing: 0px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded)</li>
<li>Border Width: 2px solid</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Max Height: 40px</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #0DAA3E (Green 400)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>#EAFCF1 (Green 100)</div>
<div style={{ padding: '12px' }}>2px #078139 (Green 500)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>#EAFCF1 (Green 100)</div>
<div style={{ padding: '12px' }}>2px #078139 + 2px #141414 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #0DAA3E (Green 400)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #A2A2A4 (Gray 400)</div>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Border</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.15) (15% Black)</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>rgba(20, 20, 20, 0.15) (15% Black)</div>
<div style={{ padding: '12px' }}>2px #141414 + 2px #141414 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #141414 (Neutral Black)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>2px #A2A2A4 (Gray 400)</div>
</div>
</div>
</div>
</section>
{/* Key Differences from Primary */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Key Differences from Primary</h2>
<h6 className="eyebrow mb-3">Comparison</h6>
</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Aspect</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Primary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Secondary</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Enabled)</div>
<div style={{ padding: '12px' }}>Green 300 (#21E46B)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Hover)</div>
<div style={{ padding: '12px' }}>Green 200 (#70EE97)</div>
<div style={{ padding: '12px' }}>Green 100 (#EAFCF1)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Border (Enabled)</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>2px Green 400</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Color (Enabled)</div>
<div style={{ padding: '12px' }}>Black (#141414)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Disabled Background</div>
<div style={{ padding: '12px' }}>Gray 200 (#E0E0E1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Arrow Icon</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,561 +0,0 @@
import * as React from 'react';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
title: 'BDS Tertiary Button Component Showcase',
description: 'Interactive showcase of the Brand Design System Tertiary Button component with all states and variants.',
},
};
export default function ButtonShowcaseTertiary() {
const [clickCount, setClickCount] = React.useState(0);
const handleClick = () => {
setClickCount((prev) => prev + 1);
};
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">BDS Tertiary Button</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
The Tertiary button is a text-only button style used for low-emphasis or contextual actions. It features no
background fill or border, appearing as a simple text link with optional arrow icon. This variant provides the
lowest visual emphasis while maintaining brand consistency through green text colors.
</p>
</section>
{/* Basic Usage */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Basic Usage</h2>
<h6 className="eyebrow mb-3">Tertiary Variant</h6>
</div>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
View Details
</Button>
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Read More
</Button>
</div>
{clickCount > 0 && (
<p className="mt-4 text-muted">
Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}
</p>
)}
</section>
{/* Primary vs Secondary vs Tertiary Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Primary vs Secondary vs Tertiary</h2>
<h6 className="eyebrow mb-3">Visual Hierarchy</h6>
</div>
<p className="mb-4 text-muted">
Use Primary for main actions, Secondary for supporting actions, and Tertiary for low-emphasis or contextual
actions to create clear visual hierarchy.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Get Started
</Button>
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
View Details
</Button>
</div>
</section>
{/* States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Text-only style with green text color, no background or border.</p>
<Button variant="tertiary" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Gray text indicates non-interactive state. Icon is hidden.</p>
<Button variant="tertiary" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the underline appears and text color darkens to
Green 500. The focus state adds a green outline around the text.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Black Color Variant */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Color Variant</h2>
<h6 className="eyebrow mb-3">Color Theme</h6>
</div>
<p className="mb-4 text-muted">
Tertiary buttons can use a black color theme with black text instead of green.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
Black Tertiary
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
View Details
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Learn More
</Button>
</div>
</section>
{/* Black Variant States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Variant States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black text with transparent background.</p>
<Button variant="tertiary" color="black" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="tertiary" color="black" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the underline appears on hover/focus.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Green vs Black Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Green vs Black Comparison</h2>
<h6 className="eyebrow mb-3">Color Themes</h6>
</div>
<p className="mb-4 text-muted">Compare the green (default) and black color themes side by side.</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" color="green" onClick={handleClick} className="me-4 mb-4">
Green Tertiary
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Black Tertiary
</Button>
</div>
</section>
{/* Without Icon */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Without Icon</h2>
<h6 className="eyebrow mb-3">Icon Control</h6>
</div>
<p className="mb-4 text-muted">Tertiary buttons can also be rendered without the arrow icon.</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" showIcon={false} onClick={handleClick} className="me-4 mb-4">
No Icon Button
</Button>
<Button variant="tertiary" showIcon={true} onClick={handleClick} className="mb-4">
With Icon Button
</Button>
</div>
</section>
{/* Button Types */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button Types</h2>
<h6 className="eyebrow mb-3">Form Integration</h6>
</div>
<p className="mb-4 text-muted">Tertiary buttons can be used for form actions like cancel or reset.</p>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
className="d-flex flex-wrap"
>
<Button variant="primary" type="submit" className="me-4 mb-4">
Submit
</Button>
<Button variant="tertiary" type="reset" className="me-4 mb-4">
Reset
</Button>
<Button variant="tertiary" type="button" onClick={() => alert('Cancelled!')} className="mb-4">
Cancel
</Button>
</form>
</section>
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-4 text-muted">
Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes:
</p>
<ul className="mb-4">
<li>
<strong>Desktop (1024px):</strong> Padding: 8px 20px, Gap: 16px
</li>
<li>
<strong>Tablet/Mobile (1023px):</strong> Padding: 8px 16px, Gap: 16px
</li>
<li>
<strong>Hover/Focus:</strong> Gap increases (22px desktop, 21px mobile) with adjusted padding
</li>
</ul>
<div className="d-flex flex-wrap">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Responsive Button
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Long Button Label Example
</Button>
</div>
</section>
{/* Accessibility */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Accessibility Features</h2>
<h6 className="eyebrow mb-3">WCAG Compliance</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px green outline (Green 500)</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Color Contrast</h5>
<ul>
<li>
<strong>Enabled:</strong> Green 400 (#0DAA3E) on White = 4.52:1 (AA for large text)
</li>
<li>
<strong>Hover/Focus:</strong> Green 500 (#078139) on White = 5.12:1 (AA)
</li>
<li>
<strong>Disabled:</strong> Gray 400 (#A2A2A4) on White = reduced contrast (acceptable for disabled state)
</li>
</ul>
</div>
</section>
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic tertiary button (green theme - default)
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
// Black color theme
<Button variant="tertiary" color="black" onClick={handleClick}>
View Details
</Button>
// Disabled state
<Button variant="tertiary" disabled>
Unavailable
</Button>
// Without icon
<Button variant="tertiary" showIcon={false}>
Cancel
</Button>
// Primary + Secondary + Tertiary pairing
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="secondary" type="button">
Learn More
</Button>
<Button variant="tertiary" type="button">
Cancel
</Button>`}</code>
</pre>
</div>
</section>
{/* Design Specifications */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Design Specifications</h2>
<h6 className="eyebrow mb-3">Visual Details</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 18px (Body R token - different from Primary/Secondary)</li>
<li>Weight: 400</li>
<li>Line Height: 26.1px</li>
<li>Letter Spacing: -0.5px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded - inherited but not visually apparent)</li>
<li>Border: None</li>
<li>Background: Transparent</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Max Height: 40px</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Green 500 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Black outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
</section>
{/* Key Differences from Primary/Secondary */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Key Differences from Primary/Secondary</h2>
<h6 className="eyebrow mb-3">Comparison</h6>
</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Aspect</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Primary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Secondary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Tertiary</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Enabled)</div>
<div style={{ padding: '12px' }}>Green 300 (#21E46B)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Hover)</div>
<div style={{ padding: '12px' }}>Green 200 (#70EE97)</div>
<div style={{ padding: '12px' }}>Green 100 (#EAFCF1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Border (Enabled)</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>2px Green 400</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Color (Enabled)</div>
<div style={{ padding: '12px' }}>Black (#141414)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Decoration</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>Underline (hover/focus/active)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Typography Token</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Body R (18px)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus Indicator</div>
<div style={{ padding: '12px' }}>2px Black border</div>
<div style={{ padding: '12px' }}>2px Black outline</div>
<div style={{ padding: '12px' }}>2px Green 500 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Disabled Background</div>
<div style={{ padding: '12px' }}>Gray 200 (#E0E0E1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Arrow Icon</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,636 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CalloutMediaBanner } from "shared/patterns/CalloutMediaBanner";
export const frontmatter = {
seo: {
title: 'CalloutMediaBanner Component Showcase',
description: "A comprehensive showcase of the CalloutMediaBanner component variants, responsive behavior, and usage examples in the XRPL.org Design System.",
}
};
export default function CalloutMediaBannerShowcase() {
const handleClick = (message: string) => {
console.log(`CalloutMediaBanner button clicked: ${message}`);
};
// Sample background images (placeholders)
// To load an image from the `public` folder in Next.js (or Create React App), use the path relative to the `public` directory, starting with a slash.
// For example, if you have `/public/backgrounds/Callout.jpg`, use:
const sampleBackgroundImage = "/img/backgrounds/callout.jpg";
const sampleLightBackgroundImage = "/img/backgrounds/callout-light.jpg";
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="my-5 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">CalloutMediaBanner Component</h1>
<p className="longform">
A full-width banner component featuring a heading, subheading, and optional action buttons.
Supports 5 color variants or a custom background image. Spans 100% of grid width and adapts
responsively across mobile, tablet, and desktop viewports.
</p>
</div>
</section>
<CalloutMediaBanner
variant="green"
heading="The Compliant Ledger Protocol"
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Get Started", onClick: () => handleClick('responsive-demo-primary') },
{ label: "Learn More", onClick: () => handleClick('responsive-demo-tertiary') }
]}
/>
{/* Responsive Behavior */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Responsive Behavior</h2>
<p className="mb-6">
CalloutMediaBanner automatically adapts its spacing and typography based on viewport width.
Resize your browser to see the responsive changes.
</p>
<div className="d-flex flex-column gap-4 mb-6">
<div className="d-flex flex-row gap-4 align-items-start" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Desktop (1024px)</h6>
<ul className="mb-0">
<li><strong>Width:</strong> 100% of container</li>
<li><strong>Padding:</strong> 40px</li>
<li><strong>Content gap:</strong> 80px</li>
<li><strong>Heading:</strong> 40px font size</li>
<li><strong>Subheading:</strong> 32px font size</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Tablet (768px1023px)</h6>
<ul className="mb-0">
<li><strong>Width:</strong> 100% of container</li>
<li><strong>Padding:</strong> 32px</li>
<li><strong>Content gap:</strong> 64px</li>
<li><strong>Heading:</strong> 36px font size</li>
<li><strong>Subheading:</strong> 28px font size</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Mobile (&lt;768px)</h6>
<ul className="mb-0">
<li><strong>Width:</strong> 100% of container</li>
<li><strong>Padding:</strong> 24px</li>
<li><strong>Content gap:</strong> 48px</li>
<li><strong>Heading:</strong> 32px font size</li>
<li><strong>Subheading:</strong> 24px font size</li>
</ul>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Variants Section Header */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Variants</h2>
<p className="mb-6">
CalloutMediaBanner comes in 5 color variants to support different visual hierarchies and use cases.
Color variants are only applied when no background image is provided.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Default Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Default</strong> - <code>variant="default"</code>
<br />
<small className="text-muted">White background, black text. General purpose, clean presentation.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="default"
heading="Build on XRPL"
subheading="Start building your next decentralized application on the XRP Ledger."
buttons={[
{ label: "Start Building", href: "#start" }
]}
/>
</div>
{/* Light Gray Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Light Gray</strong> - <code>variant="light-gray"</code>
<br />
<small className="text-muted">Subtle gray background for softer contrast.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="light-gray"
heading="Developer Resources"
subheading="Access comprehensive documentation, tutorials, and code samples."
buttons={[
{ label: "View Docs", href: "#docs" },
{ label: "Browse Tutorials", href: "#tutorials" }
]}
/>
</div>
{/* Lilac Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Lilac</strong> - <code>variant="lilac"</code>
<br />
<small className="text-muted">Distinctive purple tone for special announcements.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="lilac"
heading="New Feature Release"
subheading="Discover the latest enhancements and capabilities added to the XRP Ledger."
buttons={[
{ label: "Learn More", href: "#features" }
]}
/>
</div>
{/* Green Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Green</strong> - <code>variant="green"</code>
<br />
<small className="text-muted">Brand green for featured content and primary calls-to-action.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="green"
heading="The Compliant Ledger Protocol"
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Get Started", href: "#get-started" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* Gray Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Gray</strong> - <code>variant="gray"</code>
<br />
<small className="text-muted">Medium gray for neutral, secondary content.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="gray"
heading="Join the Community"
subheading="Connect with developers building on XRPL."
buttons={[
{ label: "Join Discord", href: "#discord" },
{ label: "View Events", href: "#events" }
]}
/>
</div>
{/* Background Image Variant Section Header */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Background Image Variant</h2>
<p className="mb-4">
When <code>backgroundImage</code> is provided, it overrides the <code>variant</code> prop.
The component automatically adds a gradient overlay to ensure text remains readable.
You can also specify <code>textColor</code> to fix the text color across both light and dark modes.
</p>
<div className="p-4 mb-6" style={{ backgroundColor: 'rgba(114, 119, 126, 0.1)', borderRadius: '8px' }}>
<h6 className="mb-3">Image Priority Logic</h6>
<ul className="mb-0">
<li><strong>If backgroundImage is provided:</strong> Image is used, variant color is ignored</li>
<li><strong>If only variant is provided:</strong> Solid color background is applied</li>
<li><strong>If neither:</strong> Defaults to white background (default variant)</li>
<li><strong>Text color:</strong> Defaults to white, or set to black via <code>textColor="black"</code></li>
<li><strong>Fixed text color:</strong> Text color remains consistent across light and dark modes</li>
<li><strong>Overlay gradient:</strong> Dark overlay for white text, light overlay for black text</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* White Text Example */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>White Text (Default)</strong> - <code>textColor="white"</code>
<br />
<small className="text-muted">Best for dark or colorful images. Includes dark overlay gradient.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
backgroundImage={sampleBackgroundImage}
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
buttons={[
{ label: "Start Building", onClick: () => handleClick('image-white-primary') }
]}
/>
</div>
{/* Black Text Example */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Black Text</strong> - <code>textColor="black"</code>
<br />
<small className="text-muted">Best for light or bright images. Includes light overlay gradient. Text remains black in both light and dark modes.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
backgroundImage={sampleLightBackgroundImage}
textColor="black"
heading="Build the Future of Finance"
subheading="Create powerful decentralized applications with XRPL's fast, efficient, and sustainable blockchain technology."
buttons={[
{ label: "Start Building", onClick: () => handleClick('image-black-primary') },
{ label: "Explore Features", onClick: () => handleClick('image-black-tertiary') }
]}
/>
</div>
<PageGrid className="mb-5">
<PageGridRow>
<PageGridCol span={12}>
<p className="text-muted small">
<em>Note: The image variant includes an automatic gradient overlay. White text gets a dark overlay, black text gets a light overlay. Text colors remain fixed across both light and dark modes.</em>
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Button Variations Section Header */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Variations</h2>
<p className="mb-6">
The component supports flexible button configurations. You can include a primary button, tertiary button, both, or neither.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Both Buttons */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Primary + Tertiary Buttons</strong>
<br />
<small className="text-muted">Most common configuration with primary and secondary actions.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="default"
heading="Complete Feature Set"
subheading="Access all the tools you need to build on XRPL."
buttons={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* Primary Only */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Primary Button Only</strong>
<br />
<small className="text-muted">Single, focused call-to-action.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="light-gray"
heading="Simple Call-to-Action"
subheading="Focus user attention on a single primary action."
buttons={[
{ label: "Take Action", href: "#action" }
]}
/>
</div>
{/* Tertiary Only */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>No buttons</strong>
<br />
<small className="text-muted">Informational banner without call-to-action.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="lilac"
heading="The Compliant Ledger Protocol"
subheading="A decentralized public Layer 1 blockchain for creating, transferring, and exchanging digital assets with a focus on compliance."
/>
</div>
{/* No Buttons */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>No heading, buttons</strong>
<br />
<small className="text-muted">Alternative informational banner without call-to-action.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CalloutMediaBanner
variant="green"
subheading="Important information or announcement without requiring user action."
buttons={[
{ label: "Take Action", href: "#action" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* Color Token Reference */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Token Reference</h2>
<p className="mb-4">All colors are mapped from <code>styles/_colors.scss</code>. The component uses <code>html.light</code> and <code>html.dark</code> selectors for mode-specific styles.</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Light Mode</h6>
<div className="d-flex flex-column gap-3">
<div>
<strong className="d-block mb-2">Default Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$white</code> <small className="text-muted">#FFFFFF</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Light Gray Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#E6EAF0', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$gray-200</code> <small className="text-muted">#E6EAF0</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Lilac Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#C0A7FF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$lilac-300</code> <small className="text-muted">#C0A7FF</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Green Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$green-200</code> <small className="text-muted">#70EE97</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Gray Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$gray-300</code> <small className="text-muted">#CAD4DF</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Text Color</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#141414', borderRadius: '4px', flexShrink: 0 }}></div>
<div><code>$black</code> <small className="text-muted">#141414</small></div>
</div>
</div>
</div>
</div>
{/* Dark Mode Colors */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Dark Mode <code className="small">(html.dark)</code></h6>
<div className="d-flex flex-column gap-3">
<div>
<strong className="d-block mb-2">Default Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#232325', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$gray-800</code> <small className="text-muted">#232325 + white text</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Light Gray Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#343437', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$gray-700</code> <small className="text-muted">#343437 + white text</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Lilac Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#7649E3', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$lilac-400</code> <small className="text-muted">#7649E3 + white text</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Green Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$green-300</code> <small className="text-muted">#21E46B + black text</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Gray Variant</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#454549', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$gray-600</code> <small className="text-muted">#454549 + white text</small></div>
</div>
</div>
<div>
<strong className="d-block mb-2">Image Variant Text</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$white</code> <small className="text-muted">Always white for readability</small></div>
</div>
</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Component API */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* variant */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>variant</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'default' | 'light-gray' | 'lilac' | 'green' | 'gray'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'default'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Color variant (ignored if backgroundImage is provided)</div>
</div>
{/* backgroundImage */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>backgroundImage</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Background image URL - overrides variant color</div>
</div>
{/* textColor */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>textColor</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'white' | 'black'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'white'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Text color for image variant - fixed across light/dark modes (only used when backgroundImage is provided)</div>
</div>
{/* heading */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>heading</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Main heading text</div>
</div>
{/* subheading */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>subheading</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Subheading/description text</div>
</div>
{/* buttons */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttons</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`Array<{ label, href?, onClick?, forceColor? }>`}</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Button configurations (1-2 buttons supported)</div>
</div>
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Design References */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design:</strong>{' '}
<a href="https://www.figma.com/design/i4OuOX6QSBauMaJE4iY4kV/Callout---Media-Banner?node-id=1-2&m=dev" target="_blank" rel="noopener noreferrer">
Callout - Media Banner (Figma)
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/CalloutMediaBanner/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,691 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardIcon } from "shared/components/CardIcon";
export const frontmatter = {
seo: {
title: 'CardIcon Component Showcase',
description: "A comprehensive showcase of the CardIcon component variants, states, and responsive sizing in the XRPL.org Design System.",
}
};
export default function CardIconShowcase() {
const handleClick = (message: string) => {
console.log(`CardIcon clicked: ${message}`);
};
// Sample icon SVG (black version for light backgrounds)
const cardIconSvg = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='53' height='38' viewBox='0 0 53 38' fill='none'%3E%3Cpath d='M38.6603 0.0618191C35.7826 0.289503 33.3694 1.32168 31.5728 3.09764C29.7228 4.92673 28.8397 7.15805 28.8397 9.98896C28.8397 14.2239 30.5831 17.1839 34.4732 19.529C35.4629 20.121 36.8104 20.7661 39.1399 21.768C42.3144 23.1265 43.4944 23.7716 44.2481 24.5761C45.1769 25.5703 45.4357 27.1565 44.8495 28.3709C44.7353 28.6062 44.4384 29.0008 44.172 29.2664C43.2737 30.1696 41.8577 30.6477 40.0991 30.6477C37.1301 30.6477 34.9148 29.4334 33.1334 26.8074C32.8898 26.4583 32.669 26.1699 32.6385 26.1699C32.57 26.1699 26.7767 29.5017 26.6549 29.6156C26.5787 29.6839 26.6396 29.8433 26.9365 30.329C29.2508 34.2148 32.8669 36.4917 37.7544 37.1444C39.0333 37.319 41.4314 37.3114 42.657 37.1444C45.7326 36.7118 48.0393 35.6948 49.8283 33.9644C51.7315 32.1353 52.6679 29.7674 52.6679 26.7998C52.6679 24.9024 52.3558 23.4225 51.6478 21.9577C51.1605 20.9559 50.6733 20.2804 49.8359 19.4304C48.2296 17.8062 46.1513 16.5767 42.0023 14.8007C38.8658 13.4574 37.8153 12.8806 37.1225 12.1444C36.4602 11.4386 36.1785 10.6113 36.2394 9.57912C36.2927 8.75945 36.5211 8.20541 37.0235 7.66656C37.7468 6.88483 38.5842 6.55848 39.8783 6.56607C41.3476 6.56607 42.2992 6.94555 43.2661 7.91701C43.6086 8.25095 44.0502 8.78981 44.2557 9.11616C44.4917 9.48805 44.6668 9.69297 44.7277 9.6702C44.9256 9.58671 50.4602 6.01962 50.4602 5.9665C50.4602 5.93614 50.1785 5.49594 49.8359 4.97985C49.1051 3.88696 47.7881 2.52083 46.8821 1.92126C45.2073 0.813185 43.4183 0.243967 41.0583 0.0694065C39.9012 -0.0216694 39.7489 -0.0216694 38.6603 0.0618191Z' fill='black'/%3E%3Cpath d='M14.9592 13.8528L14.9364 27.2711L14.7689 27.901C14.5481 28.7283 14.2893 29.2216 13.8325 29.677C13.193 30.3145 12.3708 30.5802 11.0005 30.5877C9.04403 30.5953 7.87166 29.7681 6.50896 27.4457C6.28819 27.0814 6.09026 26.7854 6.06742 26.793C6.03697 26.8081 4.65905 27.6354 3.00706 28.6296L0 30.4511L0.228385 30.9065C1.59108 33.616 3.95105 35.6652 6.79064 36.6063C9.79009 37.6005 13.6422 37.5094 16.4665 36.3786C19.8542 35.0125 21.8412 32.1891 22.3665 27.9921C22.4121 27.5671 22.4426 22.8236 22.4426 13.8983V0.442009H18.7123H14.9896L14.9592 13.8528Z' fill='black'/%3E%3C/svg%3E";
// Use the same icon for all examples
const jsIconBlack = cardIconSvg;
const jsIconWhite = cardIconSvg;
const pythonIcon = cardIconSvg;
const goIcon = cardIconSvg;
const rustIcon = cardIconSvg;
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">CardIcon Component</h1>
<p className="longform">
A clickable card component featuring an icon (top-left) and label text with arrow (bottom).
Supports two color variants (Neutral and Green), five interaction states, and responsive
sizing that adapts at breakpoints. Full card is clickable.
</p>
</div>
</section>
{/* Responsive Sizing */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Responsive Sizing</h2>
<p className="mb-6">
CardIcon automatically adapts its dimensions based on viewport width. Resize your browser to see the changes.
</p>
<div className="d-flex flex-column gap-4 mb-6">
<div className="d-flex flex-row gap-4 align-items-start" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">LG Breakpoint (992px)</h6>
<ul className="mb-0">
<li><strong>Column width:</strong> 4 columns</li>
<li><strong>Card height:</strong> 144px</li>
<li><strong>Icon bounding box:</strong> 64×64 (1:1 ratio)</li>
<li><strong>Padding:</strong> 16px</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">MD Breakpoint (576px991px)</h6>
<ul className="mb-0">
<li><strong>Column width:</strong> 4 columns</li>
<li><strong>Card height:</strong> 140px</li>
<li><strong>Icon bounding box:</strong> 60×60 (1:1 ratio)</li>
<li><strong>Padding:</strong> 12px</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">SM Breakpoint (&lt;576px)</h6>
<ul className="mb-0">
<li><strong>Column width:</strong> 4 columns</li>
<li><strong>Card height:</strong> 136px</li>
<li><strong>Icon bounding box:</strong> 56×56 (1:1 ratio)</li>
<li><strong>Padding:</strong> 8px</li>
</ul>
</div>
</div>
</div>
<div className="p-4 mb-6" style={{ backgroundColor: 'rgba(114, 119, 126, 0.1)', borderRadius: '8px' }}>
<h6 className="mb-3">Icon Requirements</h6>
<ul className="mb-0">
<li><strong>Bounding box:</strong> 1:1 ratio (square)</li>
<li><strong>Icon padding:</strong> At least 4px padding within bounding box</li>
<li><strong>Icon color:</strong> Must be black or white (depending on background)</li>
<li><strong>Full card clickable:</strong> Entire card area is interactive</li>
</ul>
</div>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Get Started with Javascript"
onClick={() => handleClick('responsive-demo')}
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Get Started with Javascript"
onClick={() => handleClick('responsive-demo-green')}
/>
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Variants */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Variants</h2>
<p className="mb-6">
CardIcon comes in two color variants to support different visual hierarchies and use cases.
</p>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Get Started with Javascript"
onClick={() => handleClick('neutral')}
/>
<div className="mt-3 text-center">
<strong>Neutral</strong>
<br />
<small className="text-muted">General purpose, subtle presentation</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Get Started with Javascript"
onClick={() => handleClick('green')}
/>
<div className="mt-3 text-center">
<strong>Green</strong>
<br />
<small className="text-muted">Featured, primary highlights</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Interaction States - Neutral */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interaction States: Neutral Variant</h2>
<p className="mb-4">
Hover over and interact with the cards below to see the different states.
Use Tab key to see focus states.
</p>
<PageGridRow>
{/* Default */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Default State"
onClick={() => handleClick('neutral-default')}
/>
<div className="mt-3 text-center">
<strong>Default</strong>
<br />
<code className="small">$gray-200</code>
</div>
</div>
</PageGridCol>
{/* Hover */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Hover to see"
onClick={() => handleClick('neutral-hover')}
/>
<div className="mt-3 text-center">
<strong>Hover</strong>
<br />
<code className="small">$gray-300</code>
</div>
</div>
</PageGridCol>
{/* Focus */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Tab to see focus"
onClick={() => handleClick('neutral-focus')}
/>
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">+ black border</code>
</div>
</div>
</PageGridCol>
{/* Pressed */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Click to see"
onClick={() => handleClick('neutral-pressed')}
/>
<div className="mt-3 text-center">
<strong>Pressed</strong>
<br />
<code className="small">$gray-400</code>
</div>
</div>
</PageGridCol>
{/* Disabled */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="neutral"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Disabled State"
disabled
/>
<div className="mt-3 text-center">
<strong>Disabled</strong>
<br />
<code className="small">$gray-100</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Interaction States - Green */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interaction States: Green Variant</h2>
<p className="mb-4">
The green variant follows the same interaction pattern but uses the brand green color palette.
</p>
<PageGridRow>
{/* Default */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Default State"
onClick={() => handleClick('green-default')}
/>
<div className="mt-3 text-center">
<strong>Default</strong>
<br />
<code className="small">$green-200</code>
</div>
</div>
</PageGridCol>
{/* Hover */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Hover to see"
onClick={() => handleClick('green-hover')}
/>
<div className="mt-3 text-center">
<strong>Hover</strong>
<br />
<code className="small">$green-300</code>
</div>
</div>
</PageGridCol>
{/* Focus */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Tab to see focus"
onClick={() => handleClick('green-focus')}
/>
<div className="mt-3 text-center">
<strong>Focused</strong>
<br />
<code className="small">+ black border</code>
</div>
</div>
</PageGridCol>
{/* Pressed */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Click to see"
onClick={() => handleClick('green-pressed')}
/>
<div className="mt-3 text-center">
<strong>Pressed</strong>
<br />
<code className="small">$green-400</code>
</div>
</div>
</PageGridCol>
{/* Disabled */}
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<div className="d-flex flex-column align-items-center">
<CardIcon
variant="green"
icon={jsIconBlack}
iconAlt="JavaScript"
label="Disabled State"
disabled
/>
<div className="mt-3 text-center">
<strong>Disabled</strong>
<br />
<code className="small">$green-100</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Token Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Token Reference</h2>
<p className="mb-4">All colors are mapped from <code>styles/_colors.scss</code>. The component uses <code>html.dark</code> selector for dark mode styles.</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Light Mode</h6>
<div className="mb-4">
<strong className="d-block mb-2">Neutral Variant</strong>
<div className="d-flex flex-column gap-2">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#E6EAF0', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Default: $gray-200</code> <small className="text-muted">#E6EAF0</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover/Focus: $gray-300</code> <small className="text-muted">#CAD4DF</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $gray-400</code> <small className="text-muted">#8A919A</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Disabled: $gray-100</code> <small className="text-muted">#F0F3F7</small></div>
</div>
</div>
</div>
<div>
<strong className="d-block mb-2">Green Variant</strong>
<div className="d-flex flex-column gap-2">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Default: $green-200</code> <small className="text-muted">#70EE97</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover/Focus: $green-300</code> <small className="text-muted">#21E46B</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#EAFCF1', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Disabled: $green-100</code> <small className="text-muted">#EAFCF1</small></div>
</div>
</div>
</div>
<div className="mt-4">
<strong className="d-block mb-2">Focus Border</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#000000', borderRadius: '4px', flexShrink: 0 }}></div>
<div><code>$black</code> <small className="text-muted">#000000</small></div>
</div>
</div>
</div>
{/* Dark Mode Colors */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Dark Mode <code className="small">(html.dark)</code></h6>
<div className="mb-4">
<strong className="d-block mb-2">Neutral Variant</strong>
<div className="d-flex flex-column gap-2">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#72777E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Default: $gray-500</code> <small className="text-muted">#72777E + white text</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover/Focus: $gray-400</code> <small className="text-muted">#8A919A + white text</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.7)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: 70% $gray-500</code></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.3)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Disabled: 30% opacity</code></div>
</div>
</div>
</div>
<div>
<strong className="d-block mb-2">Green Variant</strong>
<div className="d-flex flex-column gap-2">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Default: $green-300</code> <small className="text-muted">#21E46B + black text</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Hover/Focus: $green-200</code> <small className="text-muted">#70EE97 + black text</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Pressed: $green-400</code> <small className="text-muted">#0DAA3E</small></div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: 'rgba(114,119,126,0.3)', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>Disabled: 30% $gray-500</code> <small className="text-muted">+ white text</small></div>
</div>
</div>
</div>
<div className="mt-4">
<strong className="d-block mb-2">Focus Border</strong>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '32px', height: '32px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div><code>$white</code> <small className="text-muted">#FFFFFF</small></div>
</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Real-World Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Real-World Examples</h2>
<div className="d-flex flex-column gap-8 mb-10">
{/* Language Tutorial Grid */}
<div>
<h6 className="mb-4">Language Tutorial Cards</h6>
<p className="mb-4 text-muted">Use CardIcon for quick-access language tutorials in documentation.</p>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={jsIconBlack} iconAlt="JavaScript" label="JavaScript Tutorial" href="#javascript" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={pythonIcon} iconAlt="Python" label="Python Tutorial" href="#python" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={goIcon} iconAlt="Go" label="Go Tutorial" href="#go" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={rustIcon} iconAlt="Rust" label="Rust Tutorial" href="#rust" />
</PageGridCol>
</PageGridRow>
</div>
{/* Featured Tutorials */}
<div>
<h6 className="mb-4">Featured Content</h6>
<p className="mb-4 text-muted">Use green variant to highlight featured or recommended content.</p>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="green" icon={jsIconBlack} iconAlt="JavaScript" label="Quick Start Guide" onClick={() => handleClick('featured-quickstart')} />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="green" icon={pythonIcon} iconAlt="Python" label="Build Your First App" onClick={() => handleClick('featured-first-app')} />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={goIcon} iconAlt="Go" label="Advanced Topics" onClick={() => handleClick('advanced')} />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={rustIcon} iconAlt="Rust" label="API Reference" onClick={() => handleClick('api-ref')} />
</PageGridCol>
</PageGridRow>
</div>
{/* With Links */}
<div>
<h6 className="mb-4">Linked Cards</h6>
<p className="mb-4 text-muted">Use href prop to navigate to other pages. Cards render as anchor elements.</p>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={jsIconBlack} iconAlt="JavaScript" label="View Documentation" href="#documentation" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="green" icon={pythonIcon} iconAlt="Python" label="Get Started Now" href="#get-started" />
</PageGridCol>
</PageGridRow>
</div>
{/* Disabled States */}
<div>
<h6 className="mb-4">Coming Soon / Unavailable</h6>
<p className="mb-4 text-muted">Use disabled state for content that's not yet available.</p>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="neutral" icon={jsIconBlack} iconAlt="Coming Soon" label="Coming Soon" disabled />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
<CardIcon variant="green" icon={pythonIcon} iconAlt="Unavailable" label="Currently Unavailable" disabled />
</PageGridCol>
</PageGridRow>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* variant */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>variant</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'neutral' | 'green'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'neutral'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Color variant of the card</div>
</div>
{/* icon */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>icon</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Icon image source (URL or path). Must be black or white.</div>
</div>
{/* iconAlt */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>iconAlt</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Alt text for the icon image</div>
</div>
{/* label */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>label</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card label text displayed at bottom</div>
</div>
{/* onClick */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>onClick</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>() =&gt; void</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Click handler - renders as button</div>
</div>
{/* href */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>href</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Link destination - renders as anchor</div>
</div>
{/* disabled */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>disabled</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>boolean</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>false</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Disabled state - prevents interaction</div>
</div>
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '100px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Design References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Light Mode:</strong>{' '}
<a href="https://www.figma.com/design/GypElq0Tas4ZwgPyBe4Ymi/Card---Icon?node-id=2028-612&m=dev" target="_blank" rel="noopener noreferrer">
Figma - Light Mode Design
</a>
</div>
<div>
<strong>Dark Mode:</strong>{' '}
<a href="https://www.figma.com/design/GypElq0Tas4ZwgPyBe4Ymi/Card---Icon?node-id=2072-188&m=dev" target="_blank" rel="noopener noreferrer">
Figma - Dark Mode Design
</a>
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/components/CardIcon/CardIcon.md</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,782 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardImage } from "shared/components/CardImage";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardImage Component Showcase',
description: "A comprehensive showcase of all CardImage component variants, states, and responsive behavior in the XRPL.org Design System.",
}
};
// Sample image URL for demonstration (1:1 ratio image)
const SAMPLE_IMAGE = "/img/cards/card-image-showcase.png";
// Image from Figma Image Scaling spec (node 4171-104)
const IMAGE_SCALING_DEMO = "/img/cards/card-image-scaling-demo.png";
export default function CardImageShowcase() {
const [clickedCard, setClickedCard] = React.useState<string | null>(null);
const handleCardClick = (cardName: string) => {
setClickedCard(cardName);
setTimeout(() => setClickedCard(null), 1500);
};
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">CardImage Component</h1>
<p className="longform">
A responsive card component displaying an image, title, subtitle, and CTA button.
Features three responsive size variants (LG/MD/SM) that adapt to viewport width,
with card hover triggering the button's hover animation.
</p>
</div>
</section>
{/* Design Constraints */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design Constraints</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Image</h6>
<ul className="mb-0">
<li><strong>Aspect Ratio:</strong> 1:1 (square)</li>
<li>Scales with card width</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Title</h6>
<ul className="mb-0">
<li><strong>Lines:</strong> 1 line only</li>
<li>Truncated with ellipsis</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Subtitle</h6>
<ul className="mb-0">
<li><strong>Lines:</strong> Max 3 lines</li>
<li>Truncated with ellipsis</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Button</h6>
<ul className="mb-0">
<li><strong>Position:</strong> Locked to bottom</li>
<li>30px margin from card bottom</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Basic Showcase */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Basic Usage</h2>
<p className="mb-6">
The CardImage component displays an image (1:1 ratio), title (1 line only), subtitle (max 3 lines), and a primary button locked to the bottom.
Hover over the card to see the button animation trigger.
</p>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<h6 className="mb-3">With Link (href)</h6>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Documentation illustration"
title="Documentation"
subtitle="Access everything you need to get started working with the XRPL."
buttonLabel="Get Started"
href="#"
/>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<h6 className="mb-3">With Click Handler</h6>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Feature illustration"
title="Developer Tools"
subtitle="Build powerful applications with our comprehensive SDK and API documentation."
buttonLabel="Learn More"
onClick={() => handleCardClick('click-handler')}
/>
{clickedCard === 'click-handler' && (
<p className="mt-2 text-success">✓ Card clicked!</p>
)}
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Interactive States */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interactive States</h2>
<p className="mb-6">
Hover, focus, and press the cards below to see the state transitions.
Notice how hovering the card triggers the button's hover animation.
</p>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Default / Hover</small>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Default state"
title="Default State"
subtitle="Hover over this card to see the button animation. The entire card triggers the button's hover effect."
buttonLabel="Medium Link"
onClick={() => handleCardClick('default')}
/>
{clickedCard === 'default' && (
<p className="mt-2 text-success"> Card clicked!</p>
)}
</div>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Disabled</small>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Disabled state"
title="Disabled State"
subtitle="This card is disabled. The button shows disabled styling and interactions are blocked."
buttonLabel="Unavailable"
disabled
/>
</div>
</PageGridCol>
</PageGridRow>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Responsive Grid Demo */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Responsive Grid Layout</h2>
<p className="mb-6">
CardImage is designed to work with the PageGrid system. Resize your browser to see
the responsive behavior: 4-column on desktop (LG), 2-column on tablet (MD), 1-column on mobile (SM).
</p>
</PageGridCol>
</PageGridRow>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Card 1"
title="Documentation"
subtitle="Access everything you need to get started working with the XRPL."
buttonLabel="Get Started"
href="#"
/>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Card 2"
title="Tutorials"
subtitle="Step-by-step guides to help you build on the XRP Ledger."
buttonLabel="View Tutorials"
href="#"
/>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Card 3"
title="API Reference"
subtitle="Comprehensive API documentation for all XRPL methods."
buttonLabel="Explore API"
href="#"
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Palette - Light Mode */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Palette</h2>
<p className="mb-6">
All colors are mapped from <code>styles/_colors.scss</code>.
The site defaults to <strong>dark mode</strong>. Light mode is activated via <code>html.light</code>.
</p>
{/* Light Mode Colors */}
<h5 className="mb-4">Light Mode (Default for this component)</h5>
<div className="d-flex flex-column gap-3 mb-6">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #CAD4DF' }}></div>
<div>
<strong>Card Background:</strong> <code>$white</code>
<br />
<small className="text-muted">#FFFFFF</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Card Border:</strong> <code>$gray-300</code>
<br />
<small className="text-muted">#CAD4DF</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Image Container:</strong> <code>$gray-100</code>
<br />
<small className="text-muted">#F0F3F7</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#141414', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Text Color:</strong> <code>#141414</code> (Neutral Black)
<br />
<small className="text-muted">Title and Subtitle</small>
</div>
</div>
</div>
<Divider color="gray" className="my-6" />
{/* Dark Mode Colors */}
<h5 className="mb-4">Dark Mode (<code>html.dark</code>)</h5>
<div className="d-flex flex-column gap-3 mb-6">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#111112', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Card Background:</strong> <code>$gray-900</code>
<br />
<small className="text-muted">#111112</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#72777E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Image Container:</strong> <code>$gray-500</code>
<br />
<small className="text-muted">#72777E</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Title:</strong> <code>$white</code>
<br />
<small className="text-muted">#FFFFFF</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#E6EAF0', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Subtitle:</strong> <code>$gray-200</code>
<br />
<small className="text-muted">#E6EAF0</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: 'rgba(114, 119, 126, 0.15)', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Hover Overlay:</strong> 15% black
<br />
<small className="text-muted">rgba(114, 119, 126, 0.15)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: 'rgba(114, 119, 126, 0.45)', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Pressed Overlay:</strong> 45% black
<br />
<small className="text-muted">rgba(114, 119, 126, 0.45)</small>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Dimensions */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Responsive Dimensions</h2>
<div className="mb-6">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '180px', flexShrink: 0 }}><strong>Variant</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Breakpoint</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Grid Columns</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Card Height</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Image Ratio</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}><strong>LG (Large)</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>992px</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>4-column width</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>620px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>1:1</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}><strong>MD (Medium)</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>576px - 991px</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>2-column width</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>560px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>1:1</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}><strong>SM (Small)</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>&lt;576px</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>1-column width</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>536px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>1:1</code></div>
</div>
</div>
<Divider color="gray" className="my-6" />
<h5 className="mb-4">Spacing Tokens</h5>
<div className="mb-6">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '220px', flexShrink: 0 }}><strong>Property</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Value</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '220px', flexShrink: 0 }}>Image-to-content gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>24px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '220px', flexShrink: 0 }}>Title-to-subtitle gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>12px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '220px', flexShrink: 0 }}>Content horizontal padding</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '220px', flexShrink: 0 }}>Button margin-bottom</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>30px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '220px', flexShrink: 0 }}>Border radius</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>16px</code></div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Typography */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Typography</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Title (<code>.sh-md-l</code>)</h6>
<ul className="mb-0">
<li><strong>Font Size:</strong> 28px</li>
<li><strong>Font Weight:</strong> 300 (light)</li>
<li><strong>Line Height:</strong> 35px</li>
<li><strong>Letter Spacing:</strong> -0.5px</li>
<li><strong>Lines:</strong> 1 (truncated)</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Subtitle (<code>.body-l</code>)</h6>
<ul className="mb-0">
<li><strong>Font Size:</strong> 18px</li>
<li><strong>Font Weight:</strong> 300 (light)</li>
<li><strong>Line Height:</strong> 26.1px</li>
<li><strong>Letter Spacing:</strong> -0.5px</li>
<li><strong>Lines:</strong> Max 3 (truncated)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Image Scaling Animation */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Image Scaling Animation</h2>
<p className="mb-6">
On hover, focus, and pressed states, the image inside the card scales up by <strong>10%</strong> while
the image container remains fixed. This creates a subtle zoom effect that enhances interactivity without
disrupting the card layout.
</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Container Behavior</h6>
<ul className="mb-0">
<li><strong>Image box:</strong> Does NOT increase</li>
<li><strong>Overflow:</strong> Hidden (clips scaled content)</li>
<li><strong>Background:</strong> Remains visible at edges</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Image Behavior</h6>
<ul className="mb-0">
<li><strong>Scale:</strong> 110% (1.1x) on interaction</li>
<li><strong>Transform origin:</strong> Center</li>
<li><strong>Transition:</strong> 150ms cubic-bezier</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Trigger States</h6>
<ul className="mb-0">
<li>Hover (mouse over card)</li>
<li>Focus (keyboard navigation)</li>
<li>Pressed (active click)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Hover to see image zoom (fullBleed)</small>
<CardImage
image={IMAGE_SCALING_DEMO}
imageAlt="3D metallic cubes illustration"
title="Documentation"
subtitle="Access everything you need to get started working with the XRPL. Line 3"
buttonLabel="Medium Link"
onClick={() => handleCardClick('image-scale')}
fullBleed
/>
{clickedCard === 'image-scale' && (
<p className="mt-2 text-success"> Card clicked!</p>
)}
</div>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Custom backgroundColor</small>
<CardImage
image={SAMPLE_IMAGE}
imageAlt="Sample illustration"
title="Custom Background"
subtitle="This card has a custom background color set via the backgroundColor prop."
buttonLabel="Medium Link"
onClick={() => handleCardClick('custom-bg')}
backgroundColor="#1a1a2e"
/>
{clickedCard === 'custom-bg' && (
<p className="mt-2 text-success"> Card clicked!</p>
)}
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Animation Details */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Animation Specifications</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Timing</h6>
<ul className="mb-0">
<li><strong>Duration:</strong> 150ms</li>
<li><strong>Easing:</strong> <code>cubic-bezier(0.98, 0.12, 0.12, 0.98)</code></li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Card Hover Button Animation</h6>
<ul className="mb-0">
<li>Button background fills bottom top</li>
<li>Arrow icon line shrinks</li>
<li>Gap between label and icon increases</li>
<li>Padding adjusts for smooth transition</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">State Flow</h6>
<ul className="mb-0">
<li>Default Hover Pressed</li>
<li>Card hover triggers button hover</li>
<li>Focus ring on keyboard navigation</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* image */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>image</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Image source URL</div>
</div>
<Divider weight="thin" color="gray" />
{/* imageAlt */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>imageAlt</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Alt text for the image</div>
</div>
<Divider weight="thin" color="gray" />
{/* title */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>title</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card title (1 line only)</div>
</div>
<Divider weight="thin" color="gray" />
{/* subtitle */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>subtitle</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card subtitle (max 3 lines)</div>
</div>
<Divider weight="thin" color="gray" />
{/* buttonLabel */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>buttonLabel</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Button label text</div>
</div>
<Divider weight="thin" color="gray" />
{/* href */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>href</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Link destination (makes card clickable)</div>
</div>
<Divider weight="thin" color="gray" />
{/* onClick */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>onClick</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>() =&gt; void</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Click handler for button</div>
</div>
<Divider weight="thin" color="gray" />
{/* disabled */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>disabled</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>boolean</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>false</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Disabled state</div>
</div>
<Divider weight="thin" color="gray" />
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Usage Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Usage Examples</h2>
<div className="d-flex flex-column gap-6">
{/* Basic Usage */}
<div className="card p-4">
<h6 className="mb-3">Basic Usage with Link</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`import { CardImage } from 'shared/components/CardImage';
<CardImage
image="/images/docs-hero.png"
imageAlt="Documentation illustration"
title="Documentation"
subtitle="Access everything you need to get started..."
buttonLabel="Get Started"
href="/docs"
/>`}
</pre>
</div>
{/* With Click Handler */}
<div className="card p-4">
<h6 className="mb-3">With Click Handler</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`<CardImage
image="/images/feature.png"
imageAlt="Feature illustration"
title="New Feature"
subtitle="Learn about our latest feature..."
buttonLabel="Learn More"
onClick={() => console.log('clicked')}
/>`}
</pre>
</div>
{/* In PageGrid */}
<div className="card p-4">
<h6 className="mb-3">With PageGrid (Responsive 4-Column)</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { CardImage } from 'shared/components/CardImage';
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image="/images/card1.png"
imageAlt="Card 1"
title="Documentation"
subtitle="Access everything you need..."
buttonLabel="Get Started"
href="/docs"
/>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image="/images/card2.png"
imageAlt="Card 2"
title="Tutorials"
subtitle="Step-by-step guides..."
buttonLabel="View Tutorials"
href="/tutorials"
/>
</PageGridCol>
<PageGridCol span={{ base: 12, md: 6, lg: 4 }}>
<CardImage
image="/images/card3.png"
imageAlt="Card 3"
title="API Reference"
subtitle="Comprehensive API docs..."
buttonLabel="Explore API"
href="/api"
/>
</PageGridCol>
</PageGridRow>
</PageGrid>`}
</pre>
</div>
{/* Disabled State */}
<div className="card p-4">
<h6 className="mb-3">Disabled State</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`<CardImage
image="/images/coming-soon.png"
imageAlt="Coming soon"
title="Coming Soon"
subtitle="This feature is not yet available..."
buttonLabel="Unavailable"
disabled
/>`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Figma References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Figma References</h2>
<ul>
<li>
<a href="https://www.figma.com/design/3KewCK6ylLtHm9Yd3eSZqs/Card---Image?node-id=4139-185&m=dev" target="_blank" rel="noopener noreferrer">
Light Mode Design States
</a>
</li>
<li>
<a href="https://www.figma.com/design/3KewCK6ylLtHm9Yd3eSZqs/Card---Image?node-id=4139-245&m=dev" target="_blank" rel="noopener noreferrer">
Dark Mode Design States
</a>
</li>
<li>
<a href="https://www.figma.com/design/3KewCK6ylLtHm9Yd3eSZqs/Card---Image?node-id=4171-104&m=dev" target="_blank" rel="noopener noreferrer">
Image Scaling Animation Spec
</a>
</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,630 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardOffgrid } from "shared/components/CardOffgrid";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardOffgrid Component Showcase',
description: "A comprehensive showcase of all CardOffgrid component variants, states, and interactions in the XRPL.org Design System.",
}
};
// Sample icon component for demonstration
const SampleIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34 8L58 20V44L34 56L10 44V20L34 8Z" stroke="currentColor" strokeWidth="2" fill="none"/>
<path d="M34 8V32M34 32L58 20M34 32L10 20" stroke="currentColor" strokeWidth="2"/>
<path d="M34 32V56" stroke="currentColor" strokeWidth="2"/>
<circle cx="34" cy="32" r="6" fill="currentColor"/>
</svg>
);
// Alternative icon for variety
const MetadataIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 18C14 15.7909 15.7909 14 18 14H50C52.2091 14 54 15.7909 54 18V50C54 52.2091 52.2091 54 50 54H18C15.7909 54 14 52.2091 14 50V18Z" stroke="currentColor" strokeWidth="2"/>
<path d="M22 26H46M22 34H46M22 42H34" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
);
// Chain icon
const ChainIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28 34H40M24 28C24 25.7909 25.7909 24 28 24H32C34.2091 24 36 25.7909 36 28V40C36 42.2091 34.2091 44 32 44H28C25.7909 44 24 42.2091 24 40V28Z" stroke="currentColor" strokeWidth="2"/>
<path d="M32 28C32 25.7909 33.7909 24 36 24H40C42.2091 24 44 25.7909 44 28V40C44 42.2091 42.2091 44 40 44H36C33.7909 44 32 42.2091 32 40V28Z" stroke="currentColor" strokeWidth="2"/>
</svg>
);
export default function CardOffgridShowcase() {
const [clickedCard, setClickedCard] = React.useState<string | null>(null);
const handleCardClick = (cardName: string) => {
setClickedCard(cardName);
setTimeout(() => setClickedCard(null), 1500);
};
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">CardOffgrid Component</h1>
<p className="longform">
A versatile card component for displaying feature highlights with an icon, title, and description.
Supports neutral and green color variants with interactive states and bottom-to-top gradient hover animation.
</p>
</div>
</section>
{/* Variant Showcase */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Variants</h2>
<p className="mb-6">CardOffgrid supports two color variants: <strong>neutral</strong> (default) and <strong>green</strong>.</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div>
<h6 className="mb-3">Neutral Variant (Default)</h6>
<CardOffgrid
variant="neutral"
icon={<SampleIcon />}
title={"Onchain\nMetadata"}
description="Easily store key asset information or link to off-chain data using simple APIs, giving token holders transparency."
onClick={() => handleCardClick('neutral')}
/>
{clickedCard === 'neutral' && (
<p className="mt-2 text-success"> Card clicked!</p>
)}
</div>
<div>
<h6 className="mb-3">Green Variant</h6>
<CardOffgrid
variant="green"
icon={<SampleIcon />}
title={"Onchain\nMetadata"}
description="Easily store key asset information or link to off-chain data using simple APIs, giving token holders transparency."
onClick={() => handleCardClick('green')}
/>
{clickedCard === 'green' && (
<p className="mt-2 text-success"> Card clicked!</p>
)}
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Interactive States */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Interactive States</h2>
<p className="mb-6">Hover, focus, and press the cards below to see the state transitions.</p>
{/* Neutral States */}
<h5 className="mb-4">Neutral Variant States</h5>
<div className="d-flex flex-row gap-4 mb-8" style={{ flexWrap: 'wrap' }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Default</small>
<CardOffgrid
variant="neutral"
icon={<MetadataIcon />}
title={"Token\nManagement"}
description="Create and manage fungible and non-fungible tokens with built-in compliance features."
onClick={() => handleCardClick('neutral-default')}
/>
</div>
<div className="text-center">
<small className="d-block mb-2 text-muted">Disabled</small>
<CardOffgrid
variant="neutral"
icon={<MetadataIcon />}
title={"Token\nManagement"}
description="Create and manage fungible and non-fungible tokens with built-in compliance features."
disabled
/>
</div>
</div>
{/* Green States */}
<h5 className="mb-4">Green Variant States</h5>
<div className="d-flex flex-row gap-4 mb-6" style={{ flexWrap: 'wrap' }}>
<div className="text-center">
<small className="d-block mb-2 text-muted">Default</small>
<CardOffgrid
variant="green"
icon={<ChainIcon />}
title={"Cross-Chain\nBridges"}
description="Connect XRPL with other blockchain networks through secure and efficient bridge protocols."
onClick={() => handleCardClick('green-default')}
/>
</div>
<div className="text-center">
<small className="d-block mb-2 text-muted">Disabled</small>
<CardOffgrid
variant="green"
icon={<ChainIcon />}
title={"Cross-Chain\nBridges"}
description="Connect XRPL with other blockchain networks through secure and efficient bridge protocols."
disabled
/>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Palette */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Palette</h2>
<p className="mb-6">
All colors are mapped from <code>styles/_colors.scss</code>.
The site defaults to <strong>dark mode</strong>. Light mode is activated via <code>html.light</code>.
</p>
{/* Dark Mode Colors */}
<h5 className="mb-4">Dark Mode (Default)</h5>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Neutral Colors - Dark */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Neutral Variant</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#72777E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Default:</strong> <code>$gray-500</code>
<br />
<small className="text-muted">#72777E (white text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Hover/Focus:</strong> <code>$gray-400</code>
<br />
<small className="text-muted">#8A919A (white text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: 'rgba(114, 119, 126, 0.7)', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Pressed:</strong> <code>rgba($gray-500, 0.7)</code>
<br />
<small className="text-muted">70% opacity</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#72777E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444', opacity: 0.3 }}></div>
<div>
<strong>Disabled:</strong> <code>$gray-500 @ 30%</code>
<br />
<small className="text-muted">opacity: 0.3</small>
</div>
</div>
</div>
</div>
{/* Green Colors - Dark */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Green Variant</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Default:</strong> <code>$green-300</code>
<br />
<small className="text-muted">#21E46B (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Hover/Focus:</strong> <code>$green-200</code>
<br />
<small className="text-muted">#70EE97 (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444' }}></div>
<div>
<strong>Pressed:</strong> <code>$green-400</code>
<br />
<small className="text-muted">#0DAA3E (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#72777E', borderRadius: '4px', flexShrink: 0, border: '1px solid #444', opacity: 0.3 }}></div>
<div>
<strong>Disabled:</strong> <code>$gray-500 @ 30%</code>
<br />
<small className="text-muted">opacity: 0.3 (white text)</small>
</div>
</div>
</div>
</div>
</div>
<Divider color="gray" className="my-6" />
{/* Light Mode Colors */}
<h5 className="mb-4">Light Mode (<code>html.light</code>)</h5>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Neutral Colors - Light */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Neutral Variant</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#E6EAF0', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Default:</strong> <code>$gray-200</code>
<br />
<small className="text-muted">#E6EAF0 (dark text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#CAD4DF', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Hover/Focus:</strong> <code>$gray-300</code>
<br />
<small className="text-muted">#CAD4DF (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#8A919A', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Pressed:</strong> <code>$gray-400</code>
<br />
<small className="text-muted">#8A919A (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Disabled:</strong> <code>$gray-100</code>
<br />
<small className="text-muted">#F0F3F7 (gray text)</small>
</div>
</div>
</div>
</div>
{/* Green Colors - Light */}
<div style={{ flex: '1 1 400px', minWidth: '320px' }}>
<h6 className="mb-4">Green Variant</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#70EE97', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Default:</strong> <code>$green-200</code>
<br />
<small className="text-muted">#70EE97 (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Hover/Focus:</strong> <code>$green-300</code>
<br />
<small className="text-muted">#21E46B (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#0DAA3E', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Pressed:</strong> <code>$green-400</code>
<br />
<small className="text-muted">#0DAA3E (black text)</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '60px', height: '40px', backgroundColor: '#F0F3F7', borderRadius: '4px', flexShrink: 0, border: '1px solid #ccc' }}></div>
<div>
<strong>Disabled:</strong> <code>$gray-100</code>
<br />
<small className="text-muted">#F0F3F7 (gray text)</small>
</div>
</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Animation Details */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Animation Specifications</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Timing</h6>
<ul className="mb-0">
<li><strong>Duration:</strong> 200ms</li>
<li><strong>Easing:</strong> <code>cubic-bezier(0.98, 0.12, 0.12, 0.98)</code></li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Hover Effect ("Window Shade")</h6>
<ul className="mb-0">
<li><strong>Hover in:</strong> Shade rises up (bottom top)</li>
<li><strong>Hover out:</strong> Shade falls down (top bottom)</li>
<li>Darker pressed state on click</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">State Flow</h6>
<ul className="mb-0">
<li>Default Hover Pressed</li>
<li>Full card area is clickable</li>
<li>Focus ring on keyboard navigation</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Dimensions */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Dimensions</h2>
<div className="mb-6">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '180px', flexShrink: 0 }}><strong>Property</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Value</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Card Width</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>400px</code> (full-width on mobile)</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Card Height</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>480px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Padding</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>24px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Icon Container</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>84px × 84px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Icon Size</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>~68px × 68px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '180px', flexShrink: 0 }}>Content Gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>40px</code> (between title and description)</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Typography */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Typography</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Title</h6>
<ul className="mb-0">
<li><strong>Font Size:</strong> 32px</li>
<li><strong>Font Weight:</strong> 300 (light)</li>
<li><strong>Line Height:</strong> 40px</li>
<li><strong>Letter Spacing:</strong> -1px</li>
</ul>
</div>
<div style={{ flex: '1 1 300px' }}>
<h6 className="mb-3">Description</h6>
<ul className="mb-0">
<li><strong>Font Size:</strong> 18px</li>
<li><strong>Font Weight:</strong> 300 (light)</li>
<li><strong>Line Height:</strong> 26.1px</li>
<li><strong>Letter Spacing:</strong> -0.5px</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* variant */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>variant</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'neutral' | 'green'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'neutral'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Color variant of the card</div>
</div>
<Divider weight="thin" color="gray" />
{/* icon */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>icon</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ReactNode | string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Icon element or image URL</div>
</div>
<Divider weight="thin" color="gray" />
{/* title */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>title</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card title (use \n for line breaks)</div>
</div>
<Divider weight="thin" color="gray" />
{/* description */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>description</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card description text</div>
</div>
<Divider weight="thin" color="gray" />
{/* onClick */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>onClick</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>() =&gt; void</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Click handler (renders as button)</div>
</div>
<Divider weight="thin" color="gray" />
{/* href */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>href</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Link destination (renders as anchor)</div>
</div>
<Divider weight="thin" color="gray" />
{/* disabled */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>disabled</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>boolean</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>false</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Disabled state</div>
</div>
<Divider weight="thin" color="gray" />
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Usage Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Usage Examples</h2>
<div className="d-flex flex-column gap-6">
{/* Basic Usage */}
<div className="card p-4">
<h6 className="mb-3">Basic Usage</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`import { CardOffgrid } from 'shared/components/CardOffgrid';
<CardOffgrid
variant="neutral"
icon={<MyIcon />}
title="Onchain\\nMetadata"
description="Easily store key asset information..."
onClick={() => console.log('clicked')}
/>`}
</pre>
</div>
{/* With Link */}
<div className="card p-4">
<h6 className="mb-3">With Link</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`<CardOffgrid
variant="green"
icon="/icons/metadata.svg"
title="Learn More"
description="Click to navigate to documentation..."
href="/docs/metadata"
/>`}
</pre>
</div>
{/* Disabled State */}
<div className="card p-4">
<h6 className="mb-3">Disabled State</h6>
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`<CardOffgrid
variant="neutral"
icon={<MyIcon />}
title="Coming Soon"
description="This feature is not yet available..."
disabled
/>`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Figma References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Figma References</h2>
<ul>
<li>
<a href="https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-1963&m=dev" target="_blank" rel="noopener noreferrer">
Light Mode Color States
</a>
</li>
<li>
<a href="https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-2321&m=dev" target="_blank" rel="noopener noreferrer">
Dark Mode Color States
</a>
</li>
<li>
<a href="https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8007-1096&m=dev" target="_blank" rel="noopener noreferrer">
Animation Specifications
</a>
</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,279 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardStats, CardStatsCardConfig } from "shared/patterns/CardStats";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardStats Pattern Showcase',
description: "A comprehensive showcase of the CardStats pattern component demonstrating different configurations and color variants in the XRPL.org Design System.",
}
};
// Sample cards data matching Figma design (node 32051:2839)
const sampleCards: CardStatsCardConfig[] = [
{
statistic: "12",
superscript: "+",
label: "Continuous uptime years",
variant: "lilac",
primaryButton: { label: "Learn More", href: "#uptime" },
},
{
statistic: "6M",
superscript: "2",
label: "Active wallets",
variant: "light-gray",
primaryButton: { label: "Explore", href: "#wallets" },
},
{
statistic: "$1T",
superscript: "+",
label: "Value transferred",
variant: "green",
primaryButton: { label: "View Stats", href: "#value" },
},
{
statistic: "3-5s",
label: "Transaction finality",
variant: "green",
primaryButton: { label: "Learn More", href: "#speed" },
},
{
statistic: "70",
superscript: "+",
label: "Ecosystem partners",
variant: "dark-gray",
primaryButton: { label: "Meet Partners", href: "#partners" },
},
{
statistic: "100K",
superscript: "+",
label: "Developer community",
variant: "lilac",
primaryButton: { label: "Join Us", href: "#community" },
},
];
export default function CardStatsShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CardStats Pattern</h1>
<p className="longform">
A section pattern that displays a heading, optional description, and a responsive
grid of CardStat components. Designed for showcasing key statistics and metrics.
</p>
</div>
</section>
{/* Design Tokens Info */}
<PageGrid className="py-10">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design Specifications</h2>
<div className="d-flex flex-wrap gap-6">
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Typography</h6>
<ul className="mb-0">
<li><strong>Heading:</strong> heading-md (Tobias Light)</li>
<li><strong>Description:</strong> body-l (Booton Light)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Grid Layout</h6>
<ul className="mb-0">
<li><strong>Mobile:</strong> 2 columns</li>
<li><strong>Tablet:</strong> 2 columns</li>
<li><strong>Desktop:</strong> 3 columns</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Color Variants</h6>
<ul className="mb-0">
<li><strong>Lilac:</strong> #C0A7FF</li>
<li><strong>Green:</strong> #21E46B</li>
<li><strong>Light Gray:</strong> #E6EAF0</li>
<li><strong>Dark Gray:</strong> #CAD4DF</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* Full Example - 6 Cards with Heading and Description */}
<CardStats
heading="Blockchain Trusted at Scale"
description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
cards={sampleCards}
/>
<Divider />
{/* Heading Only - No Description */}
<CardStats
heading="XRPL Network Statistics"
cards={[
{
statistic: "12",
superscript: "+",
label: "Continuous uptime years",
variant: "lilac",
primaryButton: { label: "Learn More", href: "#uptime" },
span: { base: 4, md: 4, lg: 6 },
},
{
statistic: "6M",
superscript: "2",
label: "Active wallets",
variant: "light-gray",
primaryButton: { label: "Explore", href: "#wallets" },
span: { base: 4, md: 4, lg: 6 },
},
{
statistic: "$1T",
superscript: "+",
label: "Value transferred",
variant: "green",
primaryButton: { label: "View Stats", href: "#value" },
span: { base: 4, md: 8, lg: 12 },
}]}
/>
<Divider />
{/* 4 Cards Example */}
<CardStats
heading="Why Build on XRPL?"
description="The XRP Ledger provides enterprise-grade infrastructure for building the future of finance."
cards={sampleCards.slice(0, 4)}
/>
<Divider />
{/* Two Buttons Example */}
<PageGrid className="py-10">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-4">Two Button Cards</h2>
<p className="mb-8">Cards can include both primary and secondary buttons for multiple CTAs.</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CardStats
heading="Get Started with XRPL"
description="Explore the XRP Ledger ecosystem with comprehensive documentation and developer resources."
cards={[
{
statistic: "12",
superscript: "+",
label: "Continuous uptime years",
variant: "lilac",
primaryButton: { label: "Learn More", href: "#learn" },
secondaryButton: { label: "View Docs", href: "#docs" },
},
{
statistic: "6M",
superscript: "+",
label: "Active wallets",
variant: "green",
primaryButton: { label: "Get Started", href: "#start" },
secondaryButton: { label: "Explore", href: "#explore" },
},
{
statistic: "$1T",
superscript: "+",
label: "Value transferred",
variant: "light-gray",
primaryButton: { label: "View Stats", href: "#stats" },
secondaryButton: { label: "Learn More", href: "#about" },
},
]}
/>
<Divider />
{/* Code Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-8">Code Examples</h2>
<h5 className="mb-4">Basic Usage</h5>
<div className="p-4 mb-8 br-4" style={{ backgroundColor: '#f5f5f7', fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`import { CardStats } from 'shared/patterns/CardStats';
<CardStats
heading="Blockchain Trusted at Scale"
description="Optional description text here."
cards={[
{
statistic: "12",
superscript: "+",
label: "Continuous uptime years",
variant: "lilac",
primaryButton: { label: "Learn More", href: "/docs" }
},
{
statistic: "6M",
label: "Active wallets",
variant: "green"
},
// ... more cards
]}
/>`}</pre>
</div>
<h5 className="mb-4">Without Description</h5>
<div className="p-4 mb-8 br-4" style={{ backgroundColor: '#f5f5f7', fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`<CardStats
heading="XRPL Network Statistics"
cards={statsCards}
/>`}</pre>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* Design References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design:</strong>{' '}
<a href="https://www.figma.com/design/drnQQXnK9Q67MTPPKQsY9l/Section-Cards---Stats?node-id=32051-2839&m=dev" target="_blank" rel="noopener noreferrer">
Section Cards - Stats (Figma)
</a>
</div>
<div>
<strong>Pattern Location:</strong>{' '}
<code>shared/patterns/CardStats/</code>
</div>
<div>
<strong>Component Used:</strong>{' '}
<code>shared/components/CardStat/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,175 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardsFeatured } from "shared/patterns/CardsFeatured";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardsFeatured Pattern Showcase',
description: "A comprehensive showcase of the CardsFeatured pattern component demonstrating light and dark mode variations in the XRPL.org Design System.",
}
};
// Sample image URL for demonstration
const SAMPLE_IMAGE = "/img/cards/card-image-showcase.png";
// Sample cards data - 6 cards for full showcase
const sampleCards = [
{
image: SAMPLE_IMAGE,
imageAlt: "Documentation illustration",
title: "Documentation",
subtitle: "Access everything you need to get started working with the XRPL.",
buttonLabel: "Get Started",
href: "#docs",
},
{
image: SAMPLE_IMAGE,
imageAlt: "Tutorials illustration",
title: "Tutorials",
subtitle: "Step-by-step guides to help you build on the XRP Ledger.",
buttonLabel: "View Tutorials",
href: "#tutorials",
},
{
image: SAMPLE_IMAGE,
imageAlt: "API Reference illustration",
title: "API Reference",
subtitle: "Comprehensive API documentation for all XRPL methods.",
buttonLabel: "Explore API",
href: "#api",
},
{
image: SAMPLE_IMAGE,
imageAlt: "Use Cases illustration",
title: "Use Cases",
subtitle: "Explore real-world applications built on the XRP Ledger.",
buttonLabel: "View Use Cases",
href: "#use-cases",
},
{
image: SAMPLE_IMAGE,
imageAlt: "Community illustration",
title: "Community",
subtitle: "Join the global community of XRPL developers and enthusiasts.",
buttonLabel: "Join Community",
href: "#community",
},
{
image: SAMPLE_IMAGE,
imageAlt: "Resources illustration",
title: "Resources",
subtitle: "Tools, libraries, and resources to accelerate your development.",
buttonLabel: "Browse Resources",
href: "#resources",
},
];
export default function CardsFeaturedShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CardsFeatured Pattern</h1>
<p className="longform">
A section pattern that displays a heading, description, and a responsive grid
of CardImage components. Follows the "Logo Rectangle Grid" design from Figma.
</p>
</div>
</section>
{/* Design Specifications */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design Specifications</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Typography</h6>
<ul className="mb-0">
<li><strong>Heading:</strong> heading-md (Tobias Light)</li>
<li><strong>Description:</strong> body-l (Booton Light)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Header Gap</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 16px</li>
<li><strong>Tablet:</strong> 8px</li>
<li><strong>Mobile:</strong> 8px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Cards Column Gap</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 8px</li>
<li><strong>Tablet:</strong> 8px</li>
<li><strong>Mobile:</strong> 48px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Cards Row Gap (Vertical)</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 56px</li>
<li><strong>Tablet:</strong> 52px</li>
<li><strong>Mobile:</strong> 48px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Section Padding (Vertical)</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 80px</li>
<li><strong>Tablet:</strong> 64px</li>
<li><strong>Mobile:</strong> 48px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Grid</h6>
<ul className="mb-0">
<li><strong>Mobile:</strong> 1 column</li>
<li><strong>Tablet:</strong> 2 columns</li>
<li><strong>Desktop:</strong> 3 columns</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Colors</h6>
<ul className="mb-0">
<li><strong>Light:</strong> $black (#141414)</li>
<li><strong>Dark:</strong> $white (#FFFFFF)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* 6 Cards Example - Full Showcase */}
<section>
<CardsFeatured
heading="Trusted by Leaders in Real-World Asset Tokenization"
description="Powering institutions and builders who are bringing real world assets on chain at global scale."
cards={sampleCards}
/>
</section>
<Divider />
{/* 3 Cards Example */}
<section>
<CardsFeatured
heading="Developer Resources"
description="Everything you need to start building on the XRP Ledger."
cards={sampleCards.slice(0, 3)}
/>
</section>
<Divider />
</div>
</div>
);
}

View File

@@ -1,291 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardsTwoColumn } from "shared/patterns/CardsTwoColumn";
import { TextCard } from "shared/patterns/CardsTwoColumn";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardsTwoColumn Pattern Showcase',
description: "A comprehensive showcase of the CardsTwoColumn pattern component demonstrating different color combinations and arrangements in the XRPL.org Design System.",
}
};
export default function CardsTwoColumnShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CardsTwoColumn Pattern</h1>
<p className="longform">
A section pattern with a header (title + description) and a 2x2 grid of TextCard components.
Features 4 color variants and responsive behavior across all breakpoints.
</p>
</div>
</section>
{/* Design Specifications */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design Specifications</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Section Typography</h6>
<ul className="mb-0">
<li><strong>Title:</strong> heading-md (Tobias Light)</li>
<li><strong>Description:</strong> body-l (Booton Light, muted)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Card Typography</h6>
<ul className="mb-0">
<li><strong>Title:</strong> heading-lg (Tobias Light)</li>
<li><strong>Description:</strong> body-l (Booton Light)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Card Heights</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 340px</li>
<li><strong>Tablet:</strong> 309px</li>
<li><strong>Mobile:</strong> 274px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Section Padding</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 40px vertical, 32px horizontal</li>
<li><strong>Tablet:</strong> 32px vertical, 24px horizontal</li>
<li><strong>Mobile:</strong> 24px vertical, 16px horizontal</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Gap Between Header & Cards</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 40px</li>
<li><strong>Tablet:</strong> 32px</li>
<li><strong>Mobile:</strong> 24px</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Grid Layout</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 2×2 grid (8px gap)</li>
<li><strong>Tablet:</strong> 1 column stacked (8px gap)</li>
<li><strong>Mobile:</strong> 1 column stacked (8px gap)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* Full Pattern Example */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Full Pattern Example</h2>
<p className="mb-6">
The CardsTwoColumn pattern includes a header section (title + description) and a 2×2 grid of TextCards.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CardsTwoColumn
title="The Future of Finance is Already Onchain"
description="XRP Ledger isn't about bold predictions. It's about delivering value now. Institutions, developers, and enterprises are already building on XRPL."
secondaryDescription="On XRPL, you're not waiting for the future. You're building it."
cards={[
{
title: "Institutions",
description: "Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility.",
href: "#institutions",
color: "lilac"
},
{
title: "Developers",
description: "Build decentralized applications with comprehensive documentation, tutorials, and developer tools.",
href: "#developers",
color: "neutral-light"
},
{
title: "Enterprise",
description: "Scale your business with enterprise-grade blockchain solutions and dedicated support.",
href: "#enterprise",
color: "neutral-dark"
},
{
title: "Community",
description: "Join the global community of XRPL developers, validators, and enthusiasts.",
href: "#community",
color: "green"
}
]}
/>
<Divider />
{/* Color Variants Section */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">All 6 Color Variants</h2>
<p className="mb-6">
TextCard supports 6 color variants with hover and pressed states. Hover over cards to see the window shade animation.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* All Color Variants - Standalone TextCards */}
<PageGrid className="pb-26">
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-wrap gap-3">
<TextCard
title="Green"
description="Default: $green-200 → Hover: $green-300 → Pressed: $green-400"
color="green"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Neutral Light"
description="Default: $gray-200 → Hover: $gray-300 → Pressed: $gray-400"
color="neutral-light"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Neutral Dark"
description="Default: $gray-300 → Hover: $gray-400 → Pressed: $gray-500"
color="neutral-dark"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Lilac"
description="Default: $lilac-200 → Hover: $lilac-300 → Pressed: $lilac-400"
color="lilac"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Yellow"
description="Default: $yellow-100 → Hover: $yellow-200 → Pressed: $yellow-300"
color="yellow"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Blue"
description="Default: $blue-100 → Hover: $blue-200 → Pressed: $blue-300"
color="blue"
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* Mixed Colors in Pattern */}
<CardsTwoColumn
title="All 6 Colors in Pattern"
description="The CardsTwoColumn pattern accepts exactly 4 cards. Here we show various color combinations including the new blue variant."
cards={[
{ title: "Lilac", description: "Primary accent color for highlights.", color: "lilac" },
{ title: "Blue", description: "Secondary accent for cool tones.", color: "blue" },
{ title: "Green", description: "Brand color for positive actions.", color: "green" },
{ title: "Yellow", description: "Secondary accent for warm tones.", color: "yellow" }
]}
/>
<Divider />
{/* Alternative Color Combo */}
<CardsTwoColumn
title="Alternative Color Arrangement"
description="Different colors can be used to create visual hierarchy and distinguish between content types."
cards={[
{ title: "Documentation", description: "Comprehensive guides and API references.", color: "neutral-dark" },
{ title: "Tutorials", description: "Step-by-step learning resources.", color: "green" },
{ title: "Use Cases", description: "Real-world applications and success stories.", color: "yellow" },
{ title: "Resources", description: "Tools and libraries for development.", color: "lilac" }
]}
/>
<Divider />
{/* Disabled State Section */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Disabled State</h2>
<p className="mb-6">
TextCards can be disabled. In light mode, disabled cards have a $gray-100 background with $gray-500 text.
In dark mode, disabled cards have a $gray-500 background with 30% opacity. Toggle dark mode to see the difference.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="pb-26">
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-wrap gap-3">
<TextCard
title="Disabled Green"
description="This card is disabled and cannot be interacted with."
color="green"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Disabled Neutral Light"
description="This card is disabled and cannot be interacted with."
color="neutral-light"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Disabled Neutral Dark"
description="This card is disabled and cannot be interacted with."
color="neutral-dark"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Disabled Lilac"
description="This card is disabled and cannot be interacted with."
color="lilac"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Disabled Yellow"
description="This card is disabled and cannot be interacted with."
color="yellow"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
<TextCard
title="Disabled Blue"
description="This card is disabled and cannot be interacted with."
color="blue"
disabled
style={{ flex: '1 1 300px', minWidth: 280 }}
/>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
</div>
</div>
);
}

View File

@@ -1,686 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CarouselCardList } from "shared/patterns/CarouselCardList";
import { CarouselButton } from "shared/components/CarouselButton";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CarouselCardList Pattern Showcase',
description: "A comprehensive showcase of the CarouselCardList pattern component demonstrating horizontal scrolling, navigation buttons, and color variants in the XRPL.org Design System.",
}
};
// Sample icon components for demonstration
const TokenIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="34" cy="34" r="20" stroke="currentColor" strokeWidth="2" fill="none"/>
<path d="M34 22V46M26 34H42" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
);
const WalletIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="14" y="20" width="40" height="28" rx="4" stroke="currentColor" strokeWidth="2" fill="none"/>
<circle cx="46" cy="34" r="4" fill="currentColor"/>
<path d="M14 28H54" stroke="currentColor" strokeWidth="2"/>
</svg>
);
const ChartIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 50L26 38L34 46L54 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M46 18H54V26" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
const ShieldIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34 12L52 20V32C52 44 44 52 34 56C24 52 16 44 16 32V20L34 12Z" stroke="currentColor" strokeWidth="2" fill="none"/>
<path d="M26 34L32 40L42 28" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
const GlobeIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="34" cy="34" r="20" stroke="currentColor" strokeWidth="2" fill="none"/>
<ellipse cx="34" cy="34" rx="10" ry="20" stroke="currentColor" strokeWidth="2" fill="none"/>
<path d="M14 34H54" stroke="currentColor" strokeWidth="2"/>
</svg>
);
const CodeIcon = () => (
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26 24L14 34L26 44" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M42 24L54 34L42 44" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M38 18L30 50" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
);
// Sample cards data for neutral variant
const neutralCards = [
{
icon: <TokenIcon />,
title: "Native\nTokenization",
description: "Issue and manage digital assets directly on the ledger without smart contracts.",
href: "#tokenization",
},
{
icon: <WalletIcon />,
title: "Low Cost\nTransactions",
description: "Transaction costs are a fraction of a cent, making microtransactions viable.",
href: "#low-cost",
},
{
icon: <ChartIcon />,
title: "Built-in\nDEX",
description: "Trade any token for any other token using the native decentralized exchange.",
href: "#dex",
},
{
icon: <ShieldIcon />,
title: "Enterprise\nSecurity",
description: "Multi-signature support and advanced key management for institutional needs.",
href: "#security",
},
{
icon: <GlobeIcon />,
title: "Global\nReach",
description: "Connect to a worldwide network of validators in seconds, not minutes.",
href: "#global",
},
{
icon: <CodeIcon />,
title: "Developer\nFriendly",
description: "Comprehensive SDKs and APIs for JavaScript, Python, Java, and more.",
href: "#developer",
},
];
// Sample cards data for green variant
const greenCards = [
{
icon: <TokenIcon />,
title: "Stablecoin\nIssuance",
description: "Create and manage compliant stablecoins with built-in freeze and clawback capabilities.",
href: "#stablecoin",
},
{
icon: <WalletIcon />,
title: "Institutional\nCustody",
description: "Multi-signature accounts and escrow features for enterprise-grade custody solutions.",
href: "#custody",
},
{
icon: <ChartIcon />,
title: "Real-Time\nSettlement",
description: "Transactions settle in 3-5 seconds with finality, enabling real-time payments.",
href: "#settlement",
},
{
icon: <ShieldIcon />,
title: "Regulatory\nCompliance",
description: "Built-in features for AML/KYC compliance and regulatory reporting requirements.",
href: "#compliance",
},
{
icon: <GlobeIcon />,
title: "Cross-Border\nPayments",
description: "Seamless international transfers without correspondent banking delays.",
href: "#cross-border",
},
];
export default function CarouselCardListShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CarouselCardList Pattern</h1>
<p className="longform">
A horizontal scrolling carousel that displays CardOffgrid components with navigation buttons.
Supports neutral and green color variants, responsive sizing, and dark/light mode theming.
</p>
</div>
</section>
{/* Feature Overview */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Features</h2>
<div className="d-flex flex-row gap-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Layout</h6>
<ul className="mb-0">
<li>Horizontal scrolling cards</li>
<li>Navigation buttons (prev/next)</li>
<li>Title constrained to grid</li>
<li>Hidden scrollbar</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Responsive Behavior</h6>
<ul className="mb-0">
<li><strong>Mobile:</strong> 343×400px cards</li>
<li><strong>Tablet:</strong> 356×440px cards</li>
<li><strong>Desktop:</strong> 400×480px cards</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Button States</h6>
<ul className="mb-0">
<li>Enabled / Disabled</li>
<li>Hover / Active states</li>
<li>Focus ring for keyboard nav</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Theming</h6>
<ul className="mb-0">
<li>Dark mode (default)</li>
<li>Light mode (<code>html.light</code>)</li>
<li>Neutral &amp; Green card variants</li>
<li>Independent button colors (neutral, green, black)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Neutral Cards + Neutral Buttons (Default) */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Neutral Cards + Neutral Buttons (Default)</h2>
<p className="mb-0">
<code>variant="neutral"</code> - Gray cards with matching gray navigation buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselCardList
variant="neutral"
buttonVariant="neutral"
heading="Why Build on the XRP Ledger"
description="Discover the unique features that make XRPL the ideal blockchain for building tokenization, payments, and DeFi applications."
cards={neutralCards}
/>
</section>
{/* Neutral Cards + Green Buttons */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Neutral Cards + Green Buttons</h2>
<p className="mb-0">
<code>variant="neutral" buttonVariant="green"</code> - Gray cards with green navigation buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselCardList
variant="neutral"
buttonVariant="green"
heading="Platform Features"
description="Gray cards paired with vibrant green buttons for emphasis."
cards={neutralCards}
/>
</section>
{/* Neutral Cards + Black Buttons */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Neutral Cards + Black Buttons</h2>
<p className="mb-0">
<code>variant="neutral" buttonVariant="black"</code> - Gray cards with black navigation buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselCardList
variant="neutral"
buttonVariant="black"
heading="Developer Tools"
description="Gray cards paired with black buttons for high contrast."
cards={neutralCards}
/>
</section>
{/* Green Cards + Green Buttons */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Green Cards + Green Buttons</h2>
<p className="mb-0">
<code>variant="green" buttonVariant="green"</code> - Green cards with matching green navigation buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselCardList
variant="green"
buttonVariant="green"
heading="Enterprise Solutions"
description="Purpose-built features for institutional adoption with cohesive green theming."
cards={greenCards}
/>
</section>
{/* Green Cards + Black Buttons */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Green Cards + Black Buttons</h2>
<p className="mb-0">
<code>variant="green" buttonVariant="black"</code> - Green cards with black navigation buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselCardList
variant="green"
buttonVariant="black"
heading="Cross-Border Payments"
description="Green cards with contrasting black buttons for visual interest."
cards={greenCards}
/>
</section>
{/* Navigation Buttons */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Navigation Button Specifications</h2>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 220px' }}>
<h6 className="mb-3">Dimensions</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 40px × 40px</li>
<li><strong>Tablet/Mobile:</strong> 37px × 37px</li>
<li><strong>Gap:</strong> 8px between buttons</li>
</ul>
</div>
<div style={{ flex: '1 1 220px' }}>
<h6 className="mb-3">Neutral Colors (Dark Mode)</h6>
<ul className="mb-0">
<li><strong>Enabled:</strong> $gray-500 (#72777E)</li>
<li><strong>Hover:</strong> $gray-400 (#8A919A)</li>
<li><strong>Disabled:</strong> $gray-500 @ 50%</li>
</ul>
</div>
<div style={{ flex: '1 1 220px' }}>
<h6 className="mb-3">Green Colors (Dark Mode)</h6>
<ul className="mb-0">
<li><strong>Enabled:</strong> $green-300 (#21E46B)</li>
<li><strong>Hover:</strong> $green-200 (#70EE97)</li>
<li><strong>Disabled:</strong> $green-500 @ 50%</li>
</ul>
</div>
<div style={{ flex: '1 1 220px' }}>
<h6 className="mb-3">Black Colors (Dark Mode)</h6>
<ul className="mb-0">
<li><strong>Enabled:</strong> $black (#141414)</li>
<li><strong>Hover:</strong> $gray-500 (#72777E)</li>
<li><strong>Disabled:</strong> $black @ 50%</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Button Visual Showcase */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Visual Showcase</h2>
<p className="mb-6">Interactive carousel buttons showing all variants and states.</p>
{/* Grey/Neutral Buttons */}
<div className="mb-8">
<h5 className="mb-4">Grey (Neutral) Buttons</h5>
<div className="d-flex flex-row gap-4 align-items-center mb-3" style={{ padding: '24px', backgroundColor: 'var(--bs-body-bg)', borderRadius: '8px' }}>
<div className="text-center">
<CarouselButton direction="prev" variant="neutral" aria-label="Previous" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="neutral" aria-label="Next" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="prev" variant="neutral" disabled aria-label="Previous disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="neutral" disabled aria-label="Next disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
</div>
</div>
{/* Green Buttons */}
<div className="mb-8">
<h5 className="mb-4">Green Buttons</h5>
<div className="d-flex flex-row gap-4 align-items-center mb-3" style={{ padding: '24px', backgroundColor: 'var(--bs-body-bg)', borderRadius: '8px' }}>
<div className="text-center">
<CarouselButton direction="prev" variant="green" aria-label="Previous" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="green" aria-label="Next" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="prev" variant="green" disabled aria-label="Previous disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="green" disabled aria-label="Next disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
</div>
</div>
{/* Black Buttons */}
<div className="mb-8">
<h5 className="mb-4">Black Buttons</h5>
<div className="d-flex flex-row gap-4 align-items-center mb-3" style={{ padding: '24px', backgroundColor: 'var(--bs-body-bg)', borderRadius: '8px' }}>
<div className="text-center">
<CarouselButton direction="prev" variant="black" aria-label="Previous" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="black" aria-label="Next" />
<div className="mt-2" style={{ fontSize: '12px' }}>Enabled</div>
</div>
<div className="text-center">
<CarouselButton direction="prev" variant="black" disabled aria-label="Previous disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
<div className="text-center">
<CarouselButton direction="next" variant="black" disabled aria-label="Next disabled" />
<div className="mt-2" style={{ fontSize: '12px' }}>Disabled</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Spacing Tokens */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Spacing Tokens</h2>
<div className="mb-6">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '200px', flexShrink: 0 }}><strong>Token</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Mobile</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Tablet</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Desktop</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '200px', flexShrink: 0 }}>Header Gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>16px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '200px', flexShrink: 0 }}>Section Gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>24px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>32px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>40px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '200px', flexShrink: 0 }}>Cards Gap</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>8px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '200px', flexShrink: 0 }}>Card Dimensions</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>343×400px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>356×440px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>400×480px</code></div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '200px', flexShrink: 0 }}>Card Padding</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>16px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>20px</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>24px</code></div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<h5 className="mb-4">CarouselCardListProps</h5>
<div className="mb-8">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>variant</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'neutral' | 'green'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'neutral'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Color variant for cards</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttonVariant</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'neutral' | 'green' | 'black'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'neutral'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Color variant for navigation buttons (independent of cards)</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>heading</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ReactNode</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Section heading text</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>description</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ReactNode</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Section description text</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>cards</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>CarouselCardConfig[]</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Array of card configurations</div>
</div>
</div>
<h5 className="mb-4">CarouselCardConfig</h5>
<p className="mb-4">Each card in the <code>cards</code> array accepts the following properties (same as CardOffgrid, without variant):</p>
<div className="mb-6">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Required</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>icon</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ReactNode | string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Icon component or image URL</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>title</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card title (use \n for line breaks)</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>description</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Card description text</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>href</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>No</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Link destination URL</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>onClick</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>() =&gt; void</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>No</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Click handler function</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>disabled</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>boolean</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>No</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Disabled state</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Usage Example */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Usage Example</h2>
<div className="card p-4">
<pre className="mb-0" style={{ backgroundColor: 'var(--bs-gray-800)', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
{`import { CarouselCardList } from 'shared/patterns/CarouselCardList';
// Basic usage - button color matches card color by default
<CarouselCardList
variant="neutral"
heading="Why Build on the XRP Ledger"
description="Discover the unique features that make XRPL ideal for your project."
cards={[
{
icon: <TokenIcon />,
title: "Native\\nTokenization",
description: "Issue and manage digital assets directly on the ledger.",
href: "/docs/tokenization",
},
// ... more cards
]}
/>
// With independent button color
<CarouselCardList
variant="neutral"
buttonVariant="black" // Button color independent of card color
heading="Developer Tools"
description="Gray cards with black navigation buttons."
cards={cards}
/>`}
</pre>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Design References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Main Carousel Design:</strong>{' '}
<a href="https://www.figma.com/design/w0CVv1c40nWDRD27mLiMWS/Section-Carousel---Card-List?node-id=15055-3730&m=dev" target="_blank" rel="noopener noreferrer">
Section Carousel - Card List (Figma)
</a>
</div>
<div>
<strong>Button States:</strong>{' '}
<a href="https://www.figma.com/design/w0CVv1c40nWDRD27mLiMWS/Section-Carousel---Card-List?node-id=15055-1033&m=dev" target="_blank" rel="noopener noreferrer">
Carousel Button States (Figma)
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/CarouselCardList/</code>
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/patterns/CarouselCardList/CarouselCardList.md</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,365 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CarouselFeatured, type CarouselSlide, type CarouselFeatureItem } from "shared/patterns/CarouselFeatured";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CarouselFeatured Pattern Showcase',
description: "A comprehensive showcase of the CarouselFeatured pattern component demonstrating featured image carousels with navigation, background variants, and responsive behavior in the XRPL.org Design System.",
}
};
// Sample image URL for demonstration
const SAMPLE_IMAGE = "/img/demo-bg.png";
// Sample slides data
const sampleSlides: CarouselSlide[] = [
{
id: 1,
imageSrc: SAMPLE_IMAGE,
imageAlt: "Featured slide 1 - XRPL Overview",
},
{
id: 2,
imageSrc: SAMPLE_IMAGE,
imageAlt: "Featured slide 2 - Developer Tools",
},
{
id: 3,
imageSrc: SAMPLE_IMAGE,
imageAlt: "Featured slide 3 - Enterprise Solutions",
},
];
// Sample features data (matching Figma design)
const sampleFeatures: CarouselFeatureItem[] = [
{
title: "Easy-to-Integrate APIs",
description: "Build with common languages and skip complex smart contract development",
},
{
title: "Full Lifecycle Support",
description: "From dev tools and testnets to deployment and growth-stage",
},
{
title: "Enterprise-Grade Security",
description: "Battle-tested infrastructure with 12+ years of continuous uptime",
},
];
export default function CarouselFeaturedShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CarouselFeatured Pattern</h1>
<p className="longform">
A featured image carousel with two-column layout on desktop (image left, content right)
and single-column layout on tablet/mobile (content top, image bottom).
</p>
</div>
</section>
{/* Feature Overview */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Features</h2>
<div className="d-flex flex-row gap-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Layout</h6>
<ul className="mb-0">
<li>Two-column layout on desktop</li>
<li>Image left, content right</li>
<li>Feature list with dividers</li>
<li>Primary + tertiary buttons</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Background Colors</h6>
<ul className="mb-0">
<li><code>gray-200</code> (#E6EAF0) - default</li>
<li><code>gray-300</code> (#CAD4DF) - neutral</li>
<li><code>black</code> (#141414) - dark</li>
<li><code>yellow-100</code> (#F3F1EB) - warm</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Content</h6>
<ul className="mb-0">
<li>Heading (h-md typography)</li>
<li>Feature list items</li>
<li>Primary button (black pill)</li>
<li>Tertiary link (optional)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Responsive</h6>
<ul className="mb-0">
<li><strong>Mobile:</strong> Single column, content top</li>
<li><strong>Tablet:</strong> Single column, content top</li>
<li><strong>Desktop:</strong> Two columns, image left</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider weight="strong" color="gray" />
{/* Default: gray-200 background */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Grey Background</h2>
<p className="mb-0">
<code>background="grey"</code> - Light neutral background, the default option.
Light mode: gray-200 (#E6EAF0), Dark mode: gray-300 (#CAD4DF).
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselFeatured
background="grey"
heading="Powered by Developers"
features={sampleFeatures}
buttons={[
{ label: "Get Started", href: "#get-started" },
{ label: "Learn More", href: "#learn-more" }
]}
slides={sampleSlides.slice(0,1)}
/>
</section>
<Divider weight="strong" color="gray" />
{/* neutral background */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Neutral Background</h2>
<p className="mb-0">
<code>background="neutral"</code> - High contrast neutral background.
Light mode: white (#FFF), Dark mode: black (#141414).
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselFeatured
background="neutral"
heading="Platform Updates"
features={sampleFeatures}
buttons={[
{ label: "View Updates", href: "#updates" },
{ label: "See All", href: "#all" }
]}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
{/* yellow background */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Yellow Background</h2>
<p className="mb-0">
<code>background="yellow"</code> - Warm secondary background color.
Same in both modes: yellow-100 (#F3F1EB).
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselFeatured
background="yellow"
heading="Community Highlights"
features={sampleFeatures}
buttons={[
{ label: "Join Community", href: "#community" },
{ label: "Learn More", href: "#learn" }
]}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
{/* Single button example */}
<section className="py-10">
<PageGrid className="mb-6">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-3">Single Button (Same Line on Mobile)</h2>
<p className="mb-0">
When only one button is provided, the button and carousel navigation
stay on the same line on mobile instead of stacking.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<CarouselFeatured
background="grey"
heading="Single Button Example"
features={sampleFeatures}
buttons={[
{ label: "Get Started", href: "#get-started" }
]}
slides={sampleSlides}
/>
</section>
<Divider weight="strong" color="gray" />
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<h5 className="mb-4">CarouselFeaturedProps</h5>
<div className="mb-8">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '140px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>heading</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Section heading text</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>features</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>CarouselFeatureItem[]</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Array of feature items with title and description</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>buttons</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>ButtonConfig[]</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>optional</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Array of button configurations (1-2 buttons supported, uses ButtonGroup)</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>slides</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>CarouselSlide[]</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>required</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Array of slide configurations</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '140px', flexShrink: 0 }}><code>background</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'grey' | 'neutral' | 'yellow'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'grey'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Background color variant (adapts to light/dark mode)</div>
</div>
</div>
<h5 className="mb-4">CarouselSlide</h5>
<div className="mb-6">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Required</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>id</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string | number</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Unique identifier for the slide</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>imageSrc</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Image source URL</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>imageAlt</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Alt text for the image</div>
</div>
</div>
<h5 className="mb-4">CarouselFeatureItem</h5>
<div className="mb-6">
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Required</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>title</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Feature title text</div>
</div>
<Divider weight="thin" color="gray" />
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>description</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}>Yes</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Feature description text</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Design References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma:</strong>{' '}
<a href="https://www.figma.com/design/OO2UYKTmDZ7PJIekfaCGAg/Section-Carousel---Feature-Image?node-id=19075-4106" target="_blank" rel="noopener noreferrer">
Section Carousel - Feature Image
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/CarouselFeatured/</code>
</div>
<div>
<strong>Shared Button Component:</strong>{' '}
<code>shared/components/CarouselButton/</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,465 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'Divider Component Showcase',
description: "A comprehensive showcase of all Divider component variants, weights, colors, and orientations in the XRPL.org Design System.",
}
};
export default function DividerShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">Divider Component</h1>
<p className="longform">
A comprehensive showcase of all Divider component variants, weights, colors, and orientations.
</p>
</div>
</section>
{/* Weight by Color Matrix - Horizontal */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Horizontal Dividers: Weight by Color Matrix</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-4" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<h6 className="mb-0">Weight</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Gray</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Base</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Green</h6>
</div>
</div>
{/* Thin Row */}
<div className="d-flex flex-row mb-5 align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Thin</strong>
<br />
<small className="text-muted">0.5px</small>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="thin" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="thin" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="thin" color="green" />
</div>
</div>
{/* Regular Row */}
<div className="d-flex flex-row mb-5 align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Regular</strong>
<br />
<small className="text-muted">1px</small>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="regular" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="regular" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="regular" color="green" />
</div>
</div>
{/* Strong Row */}
<div className="d-flex flex-row align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Strong</strong>
<br />
<small className="text-muted">2px</small>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="strong" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="strong" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<Divider weight="strong" color="green" />
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Vertical Dividers */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Vertical Dividers: Weight by Color Matrix</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-4" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<h6 className="mb-0">Weight</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Gray</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Base</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Green</h6>
</div>
</div>
{/* Thin Row */}
<div className="d-flex flex-row mb-5 align-items-stretch" style={{ gap: '2rem', height: '120px' }}>
<div style={{ width: '120px', flexShrink: 0 }} className="d-flex align-items-center">
<div>
<strong>Thin</strong>
<br />
<small className="text-muted">0.5px</small>
</div>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="thin" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="thin" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="thin" color="green" />
</div>
</div>
{/* Regular Row */}
<div className="d-flex flex-row mb-5 align-items-stretch" style={{ gap: '2rem', height: '120px' }}>
<div style={{ width: '120px', flexShrink: 0 }} className="d-flex align-items-center">
<div>
<strong>Regular</strong>
<br />
<small className="text-muted">1px</small>
</div>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="regular" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="regular" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="regular" color="green" />
</div>
</div>
{/* Strong Row */}
<div className="d-flex flex-row align-items-stretch" style={{ gap: '2rem', height: '120px' }}>
<div style={{ width: '120px', flexShrink: 0 }} className="d-flex align-items-center">
<div>
<strong>Strong</strong>
<br />
<small className="text-muted">2px</small>
</div>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="strong" color="gray" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="strong" color="base" />
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }} className="d-flex justify-content-center">
<Divider orientation="vertical" weight="strong" color="green" />
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Weights Comparison */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Stroke Weights</h2>
<p className="mb-4">Dividers are available in three stroke weights to represent different levels of visual hierarchy.</p>
<div className="d-flex flex-column gap-5 mb-10">
<div>
<h6 className="mb-3">Thin (0.5px) - Subtle separation</h6>
<Divider weight="thin" />
</div>
<div>
<h6 className="mb-3">Regular (1px) - Default weight</h6>
<Divider weight="regular" />
</div>
<div>
<h6 className="mb-3">Strong (2px) - Emphasized boundaries</h6>
<Divider weight="strong" />
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Color Variants */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Variants</h2>
<p className="mb-4">Colors are mapped from the XRPL Design System color palette:</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Dark Mode Colors (Default) */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Dark Mode (Default)</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#454549', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Gray:</strong> <code>$gray-600</code>
<br />
<small className="text-muted">#454549</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#FFFFFF', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Base:</strong> <code>$white</code>
<br />
<small className="text-muted">#FFFFFF</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Green:</strong> <code>$green-300</code>
<br />
<small className="text-muted">#21E46B</small>
</div>
</div>
</div>
</div>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Light Mode</h6>
<div className="d-flex flex-column gap-3">
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#C1C1C2', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Gray:</strong> <code>$gray-300</code>
<br />
<small className="text-muted">#C1C1C2</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#111112', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Base:</strong> <code>$gray-900</code>
<br />
<small className="text-muted">#111112</small>
</div>
</div>
<div className="d-flex flex-row align-items-center gap-3">
<div style={{ width: '40px', height: '40px', backgroundColor: '#21E46B', borderRadius: '4px', flexShrink: 0, border: '1px solid var(--bs-border-color, #dee2e6)' }}></div>
<div>
<strong>Green:</strong> <code>$green-300</code>
<br />
<small className="text-muted">#21E46B</small>
</div>
</div>
</div>
</div>
</div>
<div className="d-flex flex-column gap-5 mb-10">
<div>
<h6 className="mb-3">Gray - Neutral separation (default)</h6>
<Divider color="gray" weight="regular" />
</div>
<div>
<h6 className="mb-3">Base - High contrast separation (adapts to theme)</h6>
<Divider color="base" weight="regular" />
</div>
<div>
<h6 className="mb-3">Green - Brand accent separation</h6>
<Divider color="green" weight="regular" />
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Real-World Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Real-World Examples</h2>
<div className="d-flex flex-column gap-6 mb-10">
{/* Content Section Separation */}
<div>
<h6 className="mb-4">Content Section Separation</h6>
<div className="card p-4">
<h5>Section Title</h5>
<p className="mb-4">This is some content in the first section that explains something important.</p>
<Divider color="gray" weight="thin" />
<p className="mt-4 mb-0">This is content in the second section that follows naturally from the first.</p>
</div>
</div>
{/* List Item Separation */}
<div>
<h6 className="mb-4">List Item Separation</h6>
<div className="card p-4">
<div className="d-flex flex-column">
<div className="py-3">
<strong>Feature One</strong>
<p className="mb-0 text-muted">Description of the first feature</p>
</div>
<Divider color="gray" weight="thin" />
<div className="py-3">
<strong>Feature Two</strong>
<p className="mb-0 text-muted">Description of the second feature</p>
</div>
<Divider color="gray" weight="thin" />
<div className="py-3">
<strong>Feature Three</strong>
<p className="mb-0 text-muted">Description of the third feature</p>
</div>
</div>
</div>
</div>
{/* Vertical Divider Between Columns */}
<div>
<h6 className="mb-4">Vertical Divider Between Columns</h6>
<div className="card p-4">
<div className="d-flex flex-row align-items-stretch" style={{ gap: '1.5rem', minHeight: '100px' }}>
<div style={{ flex: '1 1 0' }}>
<strong>Column One</strong>
<p className="mb-0 text-muted">Content for the first column</p>
</div>
<Divider orientation="vertical" color="gray" weight="regular" />
<div style={{ flex: '1 1 0' }}>
<strong>Column Two</strong>
<p className="mb-0 text-muted">Content for the second column</p>
</div>
<Divider orientation="vertical" color="gray" weight="regular" />
<div style={{ flex: '1 1 0' }}>
<strong>Column Three</strong>
<p className="mb-0 text-muted">Content for the third column</p>
</div>
</div>
</div>
</div>
{/* Major Section Break */}
<div>
<h6 className="mb-4">Major Section Break (Strong + Green)</h6>
<div className="card p-4">
<h5>Primary Section</h5>
<p className="mb-4">This section contains the main content of the page.</p>
<Divider color="green" weight="strong" />
<h5 className="mt-4">Secondary Section</h5>
<p className="mb-0">This section is clearly separated with a strong branded divider.</p>
</div>
</div>
{/* Navigation Separator */}
<div>
<h6 className="mb-4">Navigation Item Separator</h6>
<div className="card p-4">
<div className="d-flex flex-row align-items-center" style={{ gap: '1rem', height: '24px' }}>
<span>Home</span>
<Divider orientation="vertical" color="gray" weight="thin" />
<span>Documentation</span>
<Divider orientation="vertical" color="gray" weight="thin" />
<span>API Reference</span>
<Divider orientation="vertical" color="gray" weight="thin" />
<span>Community</span>
</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* orientation */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>orientation</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'horizontal' | 'vertical'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'horizontal'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Sets the divider orientation</div>
</div>
<Divider weight="thin" color="gray" />
{/* weight */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>weight</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'thin' | 'regular' | 'strong'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'regular'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Controls the stroke thickness</div>
</div>
<Divider weight="thin" color="gray" />
{/* color */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>color</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'gray' | 'base' | 'green'</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>'gray'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Sets the divider color</div>
</div>
<Divider weight="thin" color="gray" />
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>''</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
<Divider weight="thin" color="gray" />
{/* decorative */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>decorative</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>boolean</code></div>
<div style={{ width: '120px', flexShrink: 0 }}><code>true</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Whether the divider is purely decorative (hides from screen readers)</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,413 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { FeatureSingleTopic } from 'shared/patterns/FeatureSingleTopic';
export const frontmatter = {
seo: {
title: 'FeatureSingleTopic Pattern Showcase',
description: 'Interactive showcase of the FeatureSingleTopic pattern with all variants, orientations, and button configurations.',
},
};
export default function FeatureSingleTopicShowcase() {
// Placeholder image
const placeholderImage = '/img/demo-bg.png';
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="my-5 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">FeatureSingleTopic Pattern</h1>
<p className="longform">
A feature section pattern that pairs a title and description with a media element
in a two-column layout. Supports two variants (default and accentSurface) and
left/right orientation for flexible content positioning.
</p>
</div>
</section>
{/* Variant Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Variants</h2>
<p className="mb-4">
The component supports two variants that control the title section background:
</p>
<ul className="mb-6">
<li><strong>default:</strong> No background on title section</li>
<li><strong>accentSurface:</strong> Gray background (#E6EAF0) on title section</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Default Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Default Variant</strong> - <code>variant="default"</code>
<br />
<small className="text-muted">No background on title section. Clean, minimal look.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Feature illustration" }}
buttons={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* AccentSurface Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>AccentSurface Variant</strong> - <code>variant="accentSurface"</code>
<br />
<small className="text-muted">
Gray background (<code>$gray-200</code> / #E6EAF0) on title section.
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Feature illustration" }}
buttons={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* Orientation Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Orientation Variants</h2>
<p className="mb-6">
Control image/content position with the <code>orientation</code> prop.
Use alternating orientations for visual variety on pages with multiple sections.
</p>
<div className="mb-4 p-3" style={{ backgroundColor: '#f0f3f7', borderRadius: '8px' }}>
<strong>📱 Responsive Behavior:</strong>
<ul className="mb-0 mt-2">
<li><code>orientation="left"</code>: Image left, content right on desktop</li>
<li><code>orientation="right"</code>: Image right, content left on desktop</li>
<li><strong>Mobile/Tablet:</strong> Content always appears above image regardless of orientation</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Orientation Left */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Orientation Left (Default)</strong> - <code>orientation="left"</code>
<br />
<small className="text-muted">
Desktop: Image left, content right | Mobile/Tablet: Content above image
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Image on Left"
description="This layout places the image on the left side and content on the right on desktop screens."
media={{ src: placeholderImage, alt: "Left orientation" }}
buttons={[
{ label: "Primary Action", href: "#primary" },
{ label: "Secondary", href: "#secondary" }
]}
/>
</div>
{/* Orientation Right */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Orientation Right</strong> - <code>orientation="right"</code>
<br />
<small className="text-muted">
Desktop: Image right, content left | Mobile/Tablet: Content above image
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="right"
title="Image on Right"
description="This layout places the image on the right side and content on the left on desktop screens."
media={{ src: placeholderImage, alt: "Right orientation" }}
buttons={[
{ label: "Primary Action", href: "#primary" },
{ label: "Secondary", href: "#secondary" }
]}
/>
</div>
{/* Button Behavior Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Behavior</h2>
<p className="mb-4">
The component automatically adjusts button rendering based on the number of links provided:
</p>
<ul className="mb-6">
<li><strong>1 link:</strong> Primary or Secondary button (configurable via <code>singleButtonVariant</code> prop)</li>
<li><strong>2 links:</strong> Primary + Tertiary buttons side by side</li>
<li><strong>3+ links:</strong> All Tertiary buttons stacked</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* 1 Link - Primary */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>ex: 1 button</strong> - Primary Button (default)
<br />
<small className="text-muted">Single action rendered as a primary (filled) button.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Single button" }}
buttons={[
{ label: "Primary Link", href: "#start" }
]}
/>
</div>
{/* 1 Link - Secondary */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>ex: 1 button</strong> - Secondary Button
<br />
<small className="text-muted">Single action rendered as a secondary (outlined) button using <code>singleButtonVariant="secondary"</code>.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Single button secondary" }}
singleButtonVariant="secondary"
buttons={[
{ label: "Secondary Link", href: "#start" }
]}
/>
</div>
{/* 2 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>ex: 2 button</strong> - Primary + Tertiary Side by Side
<br />
<small className="text-muted">Primary and tertiary buttons displayed side by side on all breakpoints.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Two buttons" }}
buttons={[
{ label: "Primary Link", href: "#primary" },
{ label: "Tertiary Link", href: "#tertiary" }
]}
/>
</div>
{/* 5 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>ex: 5 button</strong> - All Tertiary Stacked
<br />
<small className="text-muted">3+ links render as all tertiary buttons stacked vertically.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Multiple buttons" }}
buttons={[
{ label: "Tertiary Link", href: "#link1" },
{ label: "Tertiary Link", href: "#link2" },
{ label: "Tertiary Link", href: "#link3" },
{ label: "Tertiary Link", href: "#link4" },
{ label: "Tertiary Link", href: "#link5" }
]}
/>
</div>
{/* 3 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>ex: 3 button</strong> - All Tertiary Stacked
<br />
<small className="text-muted">3+ links render as all tertiary buttons stacked vertically.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Three buttons" }}
buttons={[
{ label: "Tertiary Link", href: "#link1" },
{ label: "Tertiary Link", href: "#link2" },
{ label: "Tertiary Link", href: "#link3" }
]}
/>
</div>
{/* Alternating Pattern Example */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Alternating Pattern</h2>
<p className="mb-6">
Use alternating orientations and variants to create visual rhythm on feature-heavy pages.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="First Feature"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products."
buttons={[{ label: "Learn More", href: "#learn" }]}
media={{ src: placeholderImage, alt: "First feature" }}
/>
<FeatureSingleTopic
variant="accentSurface"
orientation="right"
title="Second Feature"
description="Build powerful applications on XRPL with comprehensive documentation and tools."
buttons={[
{ label: "Get Started", href: "#start" },
{ label: "Documentation", href: "#docs" }
]}
media={{ src: placeholderImage, alt: "Second feature" }}
/>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Third Feature"
description="Scale your business with blockchain technology and enterprise-grade solutions."
buttons={[
{ label: "Contact Sales", href: "#contact" },
{ label: "View Plans", href: "#plans" }
]}
media={{ src: placeholderImage, alt: "Third feature" }}
/>
{/* Design References */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design (Default):</strong>{' '}
<a href="https://www.figma.com/design/sg6T5EptbN0V2olfCSHzcx/Section-Feature---Single-Topic?node-id=18030-2250&m=dev" target="_blank" rel="noopener noreferrer">
Section Feature - Single Topic (Default Variant)
</a>
</div>
<div>
<strong>Figma Design (AccentSurface):</strong>{' '}
<a href="https://www.figma.com/design/sg6T5EptbN0V2olfCSHzcx/Section-Feature---Single-Topic?node-id=18030-2251&m=dev" target="_blank" rel="noopener noreferrer">
Section Feature - Single Topic (AccentSurface Variant)
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/FeatureSingleTopic/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
<div>
<strong>Typography:</strong>{' '}
<code>styles/_font.scss</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,427 +0,0 @@
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { FeatureTwoColumn } from 'shared/patterns/FeatureTwoColumn';
export const frontmatter = {
seo: {
title: 'FeatureTwoColumn Pattern Showcase',
description: 'Interactive showcase of the FeatureTwoColumn pattern with all color variants, arrangements, and button configurations.',
},
};
export default function FeatureTwoColumnShowcase() {
// Placeholder image
const placeholderImage = '/img/demo-bg.png';
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="my-5 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">FeatureTwoColumn Pattern</h1>
<p className="longform">
A feature section pattern that pairs editorial content with a media element
in a two-column layout. Supports four color themes, left/right arrangements,
and automatic button configuration based on link count.
</p>
</div>
</section>
{/* Button Behavior Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Behavior</h2>
<p className="mb-4">
The component uses the ButtonGroup pattern which automatically adjusts button rendering based on the number of links provided:
</p>
<ul className="mb-6">
<li><strong>1 link:</strong> Secondary button</li>
<li><strong>2 links:</strong> Primary + Tertiary buttons (responsive layout)</li>
<li><strong>3+ links:</strong> All Tertiary buttons in block layout (vertical on all screen sizes)</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* 1 Link - Secondary Button */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>1 Link</strong> - Secondary Button
<br />
<small className="text-muted">Single action rendered as a secondary (outline) button.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="lilac"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Secondary Link", href: "#link1" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
</div>
{/* 2 Links - Primary + Tertiary */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>2 Links</strong> - Primary + Tertiary Buttons
<br />
<small className="text-muted">Primary action with a secondary tertiary link.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="neutral"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Primary Link", href: "#link1" },
{ label: "Tertiary Link", href: "#link2" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
</div>
{/* 5 Links - Multiple Tertiary */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>5 Links</strong> - Multiple Links Configuration
<br />
<small className="text-muted">Primary + Tertiary in first row, Secondary below, remaining as Tertiary list.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="neutral"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Primary Link", href: "#link1" },
{ label: "Tertiary Link", href: "#link2" },
{ label: "Secondary Link", href: "#link3" },
{ label: "Tertiary Link", href: "#link4" },
{ label: "Tertiary Link", href: "#link5" }
]}
media={{ src: placeholderImage, alt: "Feature illustration" }}
/>
</div>
{/* Color Variants Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color Variants</h2>
<p className="mb-6">
Four color themes available: neutral, lilac, yellow, and green.
Each adapts automatically for light and dark modes.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Neutral Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Neutral</strong> - <code>color="neutral"</code>
<br />
<small className="text-muted">
Light: <code>$gray-100</code> (#F0F3F7) | Dark: <code>$gray-200</code> (#E6EAF0)
<br />
From <code>styles/_colors.scss</code>
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="neutral"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Neutral theme" }}
/>
</div>
{/* Lilac Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Lilac</strong> - <code>color="lilac"</code>
<br />
<small className="text-muted">
Light: <code>$lilac-200</code> (#D9CAFF) | Dark: <code>$lilac-200</code> (#D9CAFF)
<br />
From <code>styles/_colors.scss</code>
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="lilac"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Lilac theme" }}
/>
</div>
{/* Yellow Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Yellow</strong> - <code>color="yellow"</code>
<br />
<small className="text-muted">
Light: <code>$yellow-100</code> (#F3F1EB) | Dark: <code>$yellow-100</code> (#F3F1EB)
<br />
From <code>styles/_colors.scss</code>
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="yellow"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Yellow theme" }}
/>
</div>
{/* Green Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Green</strong> - <code>color="green"</code>
<br />
<small className="text-muted">
Light: <code>$green-300</code> (#21E46B) | Dark: <code>$green-300</code> (#21E46B)
<br />
From <code>styles/_colors.scss</code>
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="green"
arrange="left"
title="Institutions"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products and DeFi solutions efficiently and with more flexibility."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
media={{ src: placeholderImage, alt: "Green theme" }}
/>
</div>
{/* Arrangement Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Arrangement Variants</h2>
<p className="mb-6">
Control content position with the <code>arrange</code> prop.
Use alternating arrangements for visual variety on pages with multiple sections.
</p>
<div className="mb-4 p-3" style={{ backgroundColor: '#f0f3f7', borderRadius: '8px' }}>
<strong>📱 Responsive Behavior:</strong>
<ul className="mb-0 mt-2">
<li><code>arrange="left"</code>: Content above media on mobile/tablet, content left on desktop</li>
<li><code>arrange="right"</code>: Media above content on mobile/tablet, content right on desktop</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Arrange Left */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Arrange Left (Default)</strong> - <code>arrange="left"</code>
<br />
<small className="text-muted">
Desktop: Content left, media right | Mobile/Tablet: Content above media
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="lilac"
arrange="left"
title="Content Left"
description="This content appears on the left side of the layout on desktop, and above the media on mobile/tablet. This is the default arrangement."
links={[
{ label: "Primary", href: "#primary" },
{ label: "Learn More", href: "#secondary" }
]}
media={{ src: placeholderImage, alt: "Left arrangement" }}
/>
</div>
{/* Arrange Right */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Arrange Right</strong> - <code>arrange="right"</code>
<br />
<small className="text-muted">
Desktop: Content right, media left | Mobile/Tablet: Media above content
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="yellow"
arrange="right"
title="Content Right"
description="This content appears on the right side on desktop, and below the media on mobile/tablet. The media-first approach works well for visual hierarchy."
links={[
{ label: "Primary", href: "#primary" },
{ label: "Learn More", href: "#secondary" }
]}
media={{ src: placeholderImage, alt: "Right arrangement" }}
/>
</div>
{/* Alternating Pattern Example */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Alternating Pattern</h2>
<p className="mb-6">
Use alternating arrangements and colors to create visual rhythm on feature-heavy pages.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureTwoColumn
color="neutral"
arrange="left"
title="First Feature"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products."
links={[{ label: "Learn More", href: "#learn" }]}
media={{ src: placeholderImage, alt: "First feature" }}
/>
<FeatureTwoColumn
color="lilac"
arrange="right"
title="Second Feature"
description="Build powerful applications on XRPL with comprehensive documentation and tools."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Documentation", href: "#docs" }
]}
media={{ src: placeholderImage, alt: "Second feature" }}
/>
<FeatureTwoColumn
color="yellow"
arrange="left"
title="Third Feature"
description="Scale your business with blockchain technology and enterprise-grade solutions."
links={[
{ label: "Contact Sales", href: "#contact" },
{ label: "View Plans", href: "#plans" }
]}
media={{ src: placeholderImage, alt: "Third feature" }}
/>
<FeatureTwoColumn
color="green"
arrange="right"
title="Fourth Feature"
description="Join thousands of developers building the future of finance on XRPL."
links={[
{ label: "Start Building", href: "#build" },
{ label: "Tutorials", href: "#tutorials" },
{ label: "API Reference", href: "#api" }
]}
media={{ src: placeholderImage, alt: "Fourth feature" }}
/>
{/* Design References */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design:</strong>{' '}
<a href="https://www.figma.com/design/3tmqxMrEvOVvpYhgOCxv2D/Pattern-Feature---Two-Column?node-id=20017-3501&m=dev" target="_blank" rel="noopener noreferrer">
Pattern - Feature - Two Column (Figma)
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/FeatureTwoColumn/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
<div>
<strong>Typography:</strong>{' '}
<code>styles/_font.scss</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,495 +0,0 @@
import * as React from "react";
import {
PageGrid,
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import FeaturedVideoHero from "shared/patterns/FeaturedVideoHero/FeaturedVideoHero";
export const frontmatter = {
seo: {
title: "FeaturedVideoHero Pattern Showcase",
description:
"Interactive showcase of the FeaturedVideoHero pattern with video hero, CTAs, and responsive behavior.",
},
};
const DemoCaption = ({
title,
description,
code,
}: {
title: string;
description?: string;
code?: string;
}) => (
<div className="mb-6">
<h3 className="h4 mb-4">{title}</h3>
{description && <p className="mb-6">{description}</p>}
{code && (
<div
className="mb-6 p-4 bg-light br-4 text-black"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap", color: "#000" }}>
{code}
</pre>
</div>
)}
</div>
);
const placeholderVideo =
"https://cdn.sanity.io/files/ior4a5y3/production/6e2fcba46e3f045a5570c86fd5d20d5ba93d6aad.mp4";
export default function FeaturedVideoHeroShowcase() {
const videoRef = React.useRef<HTMLVideoElement>(null);
React.useEffect(() => {
if (videoRef.current) {
console.log("FeaturedVideoHero video element:", videoRef.current);
}
}, []);
return (
<div className="landing">
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol>
<div className="text-center mb-26">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="h2 mb-4">FeaturedVideoHero Pattern</h1>
<p className="longform">
A page-level hero pattern featuring a headline, optional
subtitle, call-to-action buttons, and a featured video. The
video uses native HTML video element props and is displayed in a
responsive two-column layout with content on the left and video
on the right.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Basic Usage */}
<PageGridRow>
<PageGridCol>
<DemoCaption
title="Basic Usage with Video"
description="The simplest implementation with a headline, subtitle, primary CTA, and video. This example assigns a ref to the video element and logs it to the console on mount."
code={`const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (videoRef.current) {
console.log("FeaturedVideoHero video element:", videoRef.current);
}
}, []);
<FeaturedVideoHero
headline="Build on XRPL"
subtitle={
<>
<p>Issue, manage, and trade real-world assets without needing to build smart contracts.</p>
<p>XRP Ledger's built-in functionality and compliance-enabling features allow asset tokenization without additional layers of complexity.</p>
</>
}
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
videoElement={{
ref: videoRef,
src: "/video/intro.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeaturedVideoHero
headline="Build on XRPL"
subtitle={
<>
<p>
Issue, manage, and trade real-world assets without needing to
build smart contracts.
</p>
<p>
XRP Ledger's built-in functionality and compliance-enabling
features allow asset tokenization without additional layers of
complexity.
</p>
</>
}
callsToAction={[{ children: "Get Started", href: "/docs" }]}
videoElement={{
ref: videoRef,
src: placeholderVideo,
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
}}
/>
<PageGrid className="py-26">
{/* Primary + Secondary CTA */}
<PageGridRow>
<PageGridCol>
<DemoCaption
title="Primary and Secondary CTAs"
description="Include both primary and secondary call-to-action buttons."
code={`<FeaturedVideoHero
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
videoElement={{
src: "/video/tokenization.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeaturedVideoHero
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" },
]}
videoElement={{
src: placeholderVideo,
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
}}
/>
<PageGrid className="py-26">
{/* Video with extended props */}
<PageGridRow>
<PageGridCol>
<DemoCaption
title="Extended Video Props"
description="Use native video element props for controls, preload, poster, etc."
code={`<FeaturedVideoHero
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" }
]}
videoElement={{
src: "/video/intro.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
controls: true,
preload: "metadata"
}}
/>`}
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeaturedVideoHero
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
callsToAction={[{ children: "Watch Tutorials", href: "/tutorials" }]}
videoElement={{
src: placeholderVideo,
autoPlay: false,
loop: true,
muted: true,
playsInline: true,
controls: true,
preload: "metadata",
}}
/>
<PageGrid className="py-26">
{/* Validation Examples */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Validation Examples</h2>
<p className="mb-6">
The component includes development-time validation that logs
warnings to the console when required props (headline,
videoElement) are missing. The component will return null and
not render when validation fails. The callsToAction prop is
optional; when provided, at least one non-empty CTA is needed to
show the CTA section.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Primary CTA Only */}
<PageGridRow>
<PageGridCol>
<DemoCaption
title="Primary CTA Only (No Secondary)"
description="The secondary CTA is optional. When omitted, only the primary CTA button is displayed."
code={`<FeaturedVideoHero
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
videoElement={{
src: "/video/intro.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeaturedVideoHero
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
callsToAction={[{ children: "Get Started", href: "/docs" }]}
videoElement={{
src: placeholderVideo,
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
}}
/>
<PageGrid className="py-26">
{/* Without subtitle */}
<PageGridRow>
<PageGridCol>
<DemoCaption
title="Without Subtitle"
description="Subtitle is optional. The component renders without a subtitle section when omitted."
code={`<FeaturedVideoHero
headline="Headline Only"
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
videoElement={{
src: "/video/intro.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeaturedVideoHero
headline="Headline Only"
callsToAction={[{ children: "Get Started", href: "/docs" }]}
videoElement={{
src: placeholderVideo,
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
}}
/>
<PageGrid className="py-26">
{/* Props Documentation */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Props Documentation</h2>
<h4 className="h5 mb-4">FeaturedVideoHeroProps</h4>
<div className="mb-6">
<ul>
<li>
<code>headline</code> (required) -{" "}
<code>React.ReactNode</code> - Hero headline text
</li>
<li>
<code>subtitle</code> (optional) -{" "}
<code>React.ReactNode</code> - Hero subtitle text
</li>
<li>
<code>callsToAction</code> (optional) -{" "}
<code>DesignConstrainedCallsToActions</code> - Array with
primary CTA and optional secondary CTA. Omit or pass
empty/non-rendering CTAs to hide the CTA section.
</li>
<li>
<code>videoElement</code> (required) - Native{" "}
<code>&lt;video&gt;</code> element props (e.g. src,
autoPlay, loop, muted, playsInline, controls, preload,
poster)
</li>
<li>
<code>className</code> (optional) - <code>string</code> -
Additional CSS classes for the header element
</li>
<li>
All standard HTML <code>&lt;header&gt;</code> attributes are
supported
</li>
</ul>
</div>
<h4 className="h5 mb-4">Button Props (callsToAction)</h4>
<p className="mb-4">
The <code>callsToAction</code> prop accepts design-constrained
Button props; <code>variant</code> and <code>color</code> are
set by the component:
</p>
<ul>
<li>
Primary CTA: <code>variant="primary"</code>,{" "}
<code>color="green"</code>
</li>
<li>
Secondary CTA: <code>variant="tertiary"</code>,{" "}
<code>color="green"</code>
</li>
<li>
All other Button props are supported (e.g.,{" "}
<code>children</code>, <code>href</code>, <code>onClick</code>
, etc.)
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
{/* Code Examples */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Code Examples</h2>
<h4 className="h5 mb-4">Import</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`import { FeaturedVideoHero } from "shared/patterns/FeaturedVideoHero";`}
</pre>
</div>
<h4 className="h5 mb-4">Basic Example</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<FeaturedVideoHero
headline="Build on XRPL"
subtitle={
<>
<p>Issue, manage, and trade real-world assets without needing to build smart contracts.</p>
<p>XRP Ledger's built-in functionality and compliance-enabling features allow asset tokenization without additional layers of complexity.</p>
</>
}
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
videoElement={{
src: "/video/intro.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
</pre>
</div>
<h4 className="h5 mb-4">With Secondary CTA</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<FeaturedVideoHero
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
videoElement={{
src: "/video/tokenization.mp4",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
{/* Best Practices */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Best Practices</h2>
<ul>
<li>
<strong>Video format:</strong> Use MP4 with H.264 for broad
compatibility. Keep file sizes reasonable for fast loading.
</li>
<li>
<strong>Autoplay:</strong> Use <code>muted</code> and{" "}
<code>playsInline</code> with <code>autoPlay</code> for
reliable autoplay on mobile.
</li>
<li>
<strong>CTAs:</strong> Keep CTA text concise and
action-oriented. Primary CTA should be the main action.
</li>
<li>
<strong>Headlines:</strong> Keep headlines concise and
impactful. Use the subtitle for additional context.
</li>
<li>
<strong>Responsive design:</strong> The layout stacks on
smaller screens; test across breakpoints to ensure video and
content display correctly.
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
);
}

View File

@@ -1,409 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
export const frontmatter = {
seo: {
title: 'PageGrid Showcase',
description: "A comprehensive showcase and documentation for the PageGrid component system.",
}
};
// Demo component with bordered divs to visualize grid
const GridDemo = ({ title, description, children, code }: {
title: string;
description?: string;
children: React.ReactNode;
code?: string;
}) => (
<div className="mb-26">
<h3 className="h4 mb-4">{title}</h3>
{description && <p className="mb-6">{description}</p>}
{code && (
<div className="mb-6 p-4 bg-light br-4 text-black" style={{ fontFamily: 'monospace', fontSize: '14px', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{code}</pre>
</div>
)}
<div style={{
border: '1px dashed #ccc',
padding: '16px',
backgroundColor: '#f9f9f9',
borderRadius: '8px'
}}>
{children}
</div>
</div>
);
// Bordered column component for visualization
const BorderedCol = ({ children, ...props }: { children: React.ReactNode } & any) => (
<PageGridCol
{...props}
style={{
border: '1px solid #0069ff',
backgroundColor: 'rgba(0, 105, 255, 0.05)',
padding: '16px',
minHeight: '60px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
...props.style
}}
>
{children}
</PageGridCol>
);
export default function GridShowcase() {
return (
<div className="landing">
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol>
<div className="text-center mb-26">
<h1 className="h2 mb-4">PageGrid Component Showcase</h1>
<p className="longform">
A comprehensive guide to using the PageGrid responsive grid system.
All examples below use bordered divs to visualize the grid structure.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Basic Grid Structure */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Basic Grid Structure"
description="The PageGrid system consists of three components: PageGrid (container), PageGrid.Row (rows), and PageGrid.Col (columns)."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={6}>Column 1</PageGrid.Col>
<PageGrid.Col span={6}>Column 2</PageGrid.Col>
<PageGrid.Col>[No span set]</PageGrid.Col>
<PageGrid.Col>[No span set]</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={6}>span={6}</BorderedCol>
<BorderedCol span={6}>span={6}</BorderedCol>
<BorderedCol>[No span set]</BorderedCol>
<BorderedCol>[No span set]</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Equal Columns */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Equal Width Columns"
description="Create equal-width columns that automatically divide the available space."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={4}>Column 1</PageGrid.Col>
<PageGrid.Col span={4}>Column 2</PageGrid.Col>
<PageGrid.Col span={4}>Column 3</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={4}>span={4}</BorderedCol>
<BorderedCol span={4}>span={4}</BorderedCol>
<BorderedCol span={4}>span={4}</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Auto and Fill */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Auto and Fill Columns"
description="Use 'auto' for columns that size to their content, and 'fill' for columns that take remaining space."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span="auto">Auto</PageGrid.Col>
<PageGrid.Col span="fill">Fill</PageGrid.Col>
<PageGrid.Col span="auto">Auto</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span="auto">span="auto"</BorderedCol>
<BorderedCol span="fill">span="fill"</BorderedCol>
<BorderedCol span="auto">span="auto"</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Responsive Layout */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Responsive Layout"
description="Create layouts that adapt to different screen sizes using responsive span values."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={{
base: 12, // Full width on mobile
md: 6, // Half width on tablet
lg: 4 // Third width on desktop
}}>
Responsive Column
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={{ base: 4, md: 6, lg: 4 }}>
base: 4, md: 6, lg: 4
</BorderedCol>
<BorderedCol span={{ base: 4, md: 6, lg: 4 }}>
base: 4, md: 6, lg: 4
</BorderedCol>
<BorderedCol span={{ base: 4, md: 8, lg: 4 }}>
base: 4, md: 8, lg: 4
</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Offsets */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Column Offsets"
description="Use offsets to push columns to the right, useful for centering content or creating spacing."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={8} offset={2}>
Centered (8 columns with 2 offset)
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={8} offset={{ lg: 2 }}>
span={8}, offset: lg: 2
</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Responsive Offsets */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Responsive Offsets"
description="Offsets can also be responsive, changing at different breakpoints."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col
span={6}
offset={{
base: 0, // No offset on mobile
md: 3 // Offset by 3 on tablet+
}}
>
Responsive Offset
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={6} offset={{ base: 0, md: 3 }}>
span={6}, offset: base=0, md=3
</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Complex Layout */}
<PageGridRow>
<PageGridCol>
<GridDemo
title="Complex Layout Example"
description="A real-world example showing a sidebar and main content area."
code={`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={{ base: 12, lg: 4 }}>
Sidebar
</PageGrid.Col>
<PageGrid.Col span={{ base: 12, lg: 8 }}>
Main Content
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}
>
<PageGrid>
<PageGridRow>
<BorderedCol span={{ base: 4, lg: 4 }} style={{ backgroundColor: 'rgba(255, 200, 0, 0.1)' }}>
Sidebar<br />(base: 4, lg: 4)
</BorderedCol>
<BorderedCol span={{ base: 4, lg: 8 }} style={{ backgroundColor: 'rgba(0, 200, 255, 0.1)' }}>
Main Content<br />(base: 4, lg: 8)
</BorderedCol>
</PageGridRow>
</PageGrid>
</GridDemo>
</PageGridCol>
</PageGridRow>
{/* Breakpoints Documentation */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Breakpoints</h2>
<p className="mb-6">
The PageGrid system uses the following breakpoints:
</p>
<div style={{ width: '100%', backgroundColor: '#FFFFFF', borderRadius: '4px', overflow: 'hidden', border: '1px solid #EEE', marginBottom: '24px' }}>
{/* Header */}
<div style={{
display: 'grid',
gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr',
borderBottom: '2px solid #E0E0E1',
background: '#FAFAFA'
}}>
<div style={{ padding: '12px', fontWeight: 600 }}>Breakpoint</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Min Width</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Columns</div>
<div style={{ padding: '12px', fontWeight: 600 }}>Container Padding</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>base</code></div>
<div style={{ padding: '12px' }}>0px</div>
<div style={{ padding: '12px' }}>4 columns</div>
<div style={{ padding: '12px' }}>16px</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>sm</code></div>
<div style={{ padding: '12px' }}>576px</div>
<div style={{ padding: '12px' }}>8 columns</div>
<div style={{ padding: '12px' }}>24px</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>md</code></div>
<div style={{ padding: '12px' }}>576px</div>
<div style={{ padding: '12px' }}>8 columns</div>
<div style={{ padding: '12px' }}>24px</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}><code>lg</code></div>
<div style={{ padding: '12px' }}>992px</div>
<div style={{ padding: '12px' }}>12 columns</div>
<div style={{ padding: '12px' }}>32px</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr 1.5fr' }}>
<div style={{ padding: '12px' }}><code>xl</code></div>
<div style={{ padding: '12px' }}>1280px</div>
<div style={{ padding: '12px' }}>12 columns</div>
<div style={{ padding: '12px' }}>112px (max-width: 1440px)</div>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
{/* Usage Documentation */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Usage</h2>
<h4 className="h5 mb-4">Import</h4>
<div className="p-4 bg-light br-4 mb-6" style={{ fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, color: '#000' }}>{`import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";`}</pre>
</div>
<h4 className="h5 mb-4">Basic Example</h4>
<div className="p-4 bg-light br-4 mb-6" style={{ fontFamily: 'monospace', fontSize: '14px', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`<PageGrid>
<PageGrid.Row>
<PageGrid.Col span={6}>
Column 1
</PageGrid.Col>
<PageGrid.Col span={6}>
Column 2
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}</pre>
</div>
<h4 className="h5 mb-4">Props</h4>
<div className="mb-6">
<h5 className="h6 mb-3">PageGrid.Col Props</h5>
<ul>
<li><code>span</code> - Column span (number, "auto", "fill", or responsive object)</li>
<li><code>offset</code> - Column offset (number or responsive object)</li>
<li><code>className</code> - Additional CSS classes</li>
<li>All standard HTML div attributes</li>
</ul>
</div>
<h4 className="h5 mb-4">Span Values</h4>
<ul className="mb-6">
<li><strong>Number</strong> (e.g., 1-12): Fixed column width</li>
<li><strong>"auto"</strong>: Column sizes to its content</li>
<li><strong>"fill"</strong>: Column fills remaining space</li>
<li><strong>Responsive Object</strong>: Different spans for different breakpoints</li>
</ul>
<h4 className="h5 mb-4">Best Practices</h4>
<ul>
<li>Always wrap columns in a <code>PageGrid.Row</code></li>
<li>Use responsive values for mobile-first design</li>
<li>Total column spans in a row should not exceed the grid columns (4 for base, 8 for sm/md, 12 for lg/xl)</li>
<li>Use offsets for spacing and centering content</li>
<li>Test layouts at all breakpoints</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
{/* Visual Grid Demonstration */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Visual Grid Demonstration</h2>
<p className="mb-6">
Below is a visual representation of the 12-column grid system at the lg breakpoint:
</p>
<PageGrid>
<PageGridRow>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((num) => (
<BorderedCol key={num} span={{ base: 2, lg: 1 }} style={{ fontSize: '12px' }}>
{num}
</BorderedCol>
))}
</PageGridRow>
</PageGrid>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
);
}

View File

@@ -1,769 +0,0 @@
import * as React from "react";
import {
PageGrid,
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import HeaderHeroPrimaryMedia from "shared/patterns/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";
export const frontmatter = {
seo: {
title: "HeaderHeroPrimaryMedia Pattern Showcase",
description:
"Interactive showcase of the HeaderHeroPrimaryMedia pattern with all variants, media types, and responsive behavior.",
},
};
// Demo component for code examples
const CodeDemo = ({
title,
description,
code,
children,
}: {
title: string;
description?: string;
code?: string;
children: React.ReactNode;
}) => (
<div className="mb-26">
<h3 className="h4 mb-4">{title}</h3>
{description && <p className="mb-6">{description}</p>}
{code && (
<div
className="mb-6 p-4 bg-light br-4 text-black"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap", color: "#000" }}>
{code}
</pre>
</div>
)}
<div
style={{
border: "1px dashed #ccc",
padding: "16px",
backgroundColor: "#f9f9f9",
borderRadius: "8px",
}}
>
{children}
</div>
</div>
);
// Sample placeholder images
const placeholderImage =
"https://cdn.sanity.io/images/ior4a5y3/production/6e150606bc0a051a83b90aa830cc32854cc3f7df-2928x1920.jpg";
const placeholderVideo =
"https://cdn.sanity.io/files/ior4a5y3/production/6e2fcba46e3f045a5570c86fd5d20d5ba93d6aad.mp4";
// Sample custom animation component
const SampleAnimation = () => (
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#0069ff",
color: "white",
fontSize: "24px",
fontWeight: "bold",
}}
>
Custom Animation Element
</div>
);
export default function HeaderHeroPrimaryMediaShowcase() {
return (
<div className="landing">
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol>
<div className="text-center mb-26">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="h2 mb-4">HeaderHeroPrimaryMedia Pattern</h1>
<p className="longform">
A page-level hero pattern featuring a headline, subtitle,
call-to-action buttons, and a primary media element. The media
supports images, videos, or custom React elements, all
constrained to maintain a 9:16 aspect ratio with object-fit:
cover.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Basic Usage */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Basic Usage with Image Media"
description="The simplest implementation with an image, headline, subtitle, and primary CTA."
code={`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools and APIs."
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
src: "/img/hero.png",
alt: "XRPL Development"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools and APIs."
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
alt: "XRPL Development",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Primary + Secondary CTA */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Primary and Secondary CTAs"
description="Include both primary and secondary call-to-action buttons."
code={`<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
media={{
type: "image",
src: "/img/tokenization.png",
alt: "Tokenization"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" },
]}
media={{
type: "image",
src: placeholderImage,
alt: "Tokenization",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Video Media */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Video Media"
description="Use video elements with native video props support. The video will maintain the 9:16 aspect ratio and object-fit: cover."
code={`<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" }
]}
media={{
type: "video",
src: "/video/intro.mp4",
alt: "Introduction video",
autoPlay: true,
loop: true,
muted: true,
playsInline: true
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials and guides."
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" },
]}
media={{
type: "video",
src: placeholderVideo,
alt: "Introduction video",
autoPlay: true,
loop: true,
muted: true,
playsInline: true,
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Custom Element Media */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Custom Element Media"
description="Use custom React elements for animations, interactive components, or any custom media type."
code={`<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with our custom interactive media."
callsToAction={[
{ children: "Explore", href: "/interactive" }
]}
media={{
type: "custom",
element: <MyAnimationComponent />
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with our custom interactive media."
callsToAction={[{ children: "Explore", href: "/interactive" }]}
media={{
type: "custom",
element: <SampleAnimation />,
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Extended Image Props */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Extended Image Props"
description="Leverage native img element props like loading, crossOrigin, etc. className and style are omitted from img props and only available on the container."
code={`<HeaderHeroPrimaryMedia
headline="Optimized Images"
subtitle="Use native image attributes for performance and security."
callsToAction={[
{ children: "View Gallery", href: "/gallery" }
]}
media={{
type: "image",
src: "/img/gallery.jpg",
alt: "Image gallery",
loading: "lazy",
crossOrigin: "anonymous",
decoding: "async"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Optimized Images"
subtitle="Use native image attributes for performance and security."
callsToAction={[{ children: "View Gallery", href: "/gallery" }]}
media={{
type: "image",
src: placeholderImage,
alt: "Image gallery",
loading: "lazy",
decoding: "async",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Extended Video Props */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Extended Video Props"
description="Use native video element props for controls, preload, poster, etc."
code={`<HeaderHeroPrimaryMedia
headline="Video Content"
subtitle="Rich video experiences with full control."
callsToAction={[
{ children: "Watch Now", href: "/videos" }
]}
media={{
type: "video",
src: "/video/demo.mp4",
alt: "Demo video",
controls: true,
preload: "metadata",
poster: "/img/video-poster.jpg"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Video Content"
subtitle="Rich video experiences with full control."
callsToAction={[{ children: "Watch Now", href: "/videos" }]}
media={{
type: "video",
src: placeholderVideo,
alt: "Demo video",
controls: true,
preload: "metadata",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Missing Optional Fields - Validation Examples */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Validation Examples</h2>
<p className="mb-6">
The component includes development-time validation that logs
warnings to the console when required props are missing. The
component will still render, but you'll see warnings in the
browser console.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Missing Subtitle */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Missing Subtitle (Warning Example)"
description="When subtitle is missing, a warning will be logged to the console. The component still renders but the subtitle area will be empty."
code={`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle={undefined}
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
src: "/img/hero.png",
alt: "XRPL Development"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle={undefined as any}
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
alt: "XRPL Development",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Missing Secondary CTA */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Primary CTA Only (No Secondary)"
description="The secondary CTA is optional. When omitted, only the primary CTA button is displayed. This is the recommended pattern when you want a single, focused call-to-action."
code={`<HeaderHeroPrimaryMedia
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
callsToAction={[
{ children: "Get Started", href: "/docs" }
// No secondary CTA - this is valid
]}
media={{
type: "image",
src: "/img/hero.png",
alt: "Single CTA example"
}}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Single Call to Action"
subtitle="Focus on one primary action for better conversion."
callsToAction={[{ children: "Get Started", href: "/docs" }]}
media={{
type: "image",
src: placeholderImage,
alt: "Single CTA example",
}}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Missing Media */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Missing Media (Warning Example)"
description="When media is missing, a warning will be logged to the console. The component still renders but the media section will not be displayed."
code={`<HeaderHeroPrimaryMedia
headline="Content Without Media"
subtitle="Sometimes you may want to focus purely on the content without media."
callsToAction={[
{ children: "Learn More", href: "/about" }
]}
media={undefined}
/>`}
>
<HeaderHeroPrimaryMedia
headline="Content Without Media"
subtitle="Sometimes you may want to focus purely on the content without media."
callsToAction={[{ children: "Learn More", href: "/about" }]}
media={undefined as any}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Design Constraints */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Design Constraints</h2>
<p className="mb-6">
The HeaderHeroPrimaryMedia pattern enforces specific design
requirements to maintain visual consistency across all
implementations:
</p>
<ul className="mb-6">
<li>
<strong>Aspect Ratio:</strong> All media maintains a 9:16
aspect ratio (portrait orientation)
</li>
<li>
<strong>Object Fit:</strong> Media uses{" "}
<code>object-fit: cover</code> to fill the container while
maintaining aspect ratio
</li>
<li>
<strong>Responsive Behavior:</strong> The media container
adapts responsively while maintaining the aspect ratio
constraint
</li>
<li>
<strong>Type Safety:</strong> TypeScript ensures proper media
type discrimination and prop validation
</li>
</ul>
<div
className="p-4 bg-light br-4"
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`.bds-header-hero-primary-media__media-container {
width: 100%;
aspect-ratio: 9 / 16; /* Design requirement */
overflow: hidden;
}
.bds-header-hero-primary-media__media-element {
width: 100%;
height: 100%;
object-fit: cover; /* Ensures media covers container */
object-position: center;
}`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
{/* Props Documentation */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Props Documentation</h2>
<h4 className="h5 mb-4">HeaderHeroPrimaryMediaProps</h4>
<div className="mb-6">
<ul>
<li>
<code>headline</code> (required) -{" "}
<code>React.ReactNode</code> - Hero headline text
</li>
<li>
<code>subtitle</code> (required) -{" "}
<code>React.ReactNode</code> - Hero subtitle text
</li>
<li>
<code>callsToAction</code> (required) -{" "}
<code>[ButtonProps, ButtonProps?]</code> - Array with
primary CTA (required) and optional secondary CTA
</li>
<li>
<code>media</code> (required) - <code>HeaderHeroMedia</code>{" "}
- Media element (image, video, or custom)
</li>
<li>
<code>className</code> (optional) - <code>string</code> -
Additional CSS classes for the header element
</li>
<li>
All standard HTML <code>&lt;header&gt;</code> attributes are
supported
</li>
</ul>
</div>
<h4 className="h5 mb-4">HeaderHeroMedia Types</h4>
<div className="mb-6">
<h5 className="h6 mb-3">ImageMediaProps</h5>
<ul className="mb-4">
<li>
<code>type: "image"</code> (required)
</li>
<li>
<code>src: string</code> (required) - Image source URL
</li>
<li>
<code>alt: string</code> (required) - Alt text for
accessibility
</li>
<li>
All native <code>&lt;img&gt;</code> props except{" "}
<code>className</code> and <code>style</code>
</li>
</ul>
<h5 className="h6 mb-3">VideoMediaProps</h5>
<ul className="mb-4">
<li>
<code>type: "video"</code> (required)
</li>
<li>
<code>src: string</code> (required) - Video source URL
</li>
<li>
<code>alt?: string</code> (optional) - Alt text for
accessibility
</li>
<li>
All native <code>&lt;video&gt;</code> props except{" "}
<code>className</code> and <code>style</code>
</li>
</ul>
<h5 className="h6 mb-3">CustomMediaProps</h5>
<ul>
<li>
<code>type: "custom"</code> (required)
</li>
<li>
<code>element: React.ReactElement</code> (required) - Custom
React element to render
</li>
</ul>
</div>
<h4 className="h5 mb-4">Button Props (callsToAction)</h4>
<p className="mb-4">
The <code>callsToAction</code> prop accepts Button component
props, but <code>variant</code> and <code>color</code> are
automatically set:
</p>
<ul>
<li>
Primary CTA: <code>variant="primary"</code>,{" "}
<code>color="green"</code>
</li>
<li>
Secondary CTA: <code>variant="tertiary"</code>,{" "}
<code>color="green"</code>
</li>
<li>
All other Button props are supported (e.g.,{" "}
<code>children</code>, <code>href</code>, <code>onClick</code>
, etc.)
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
{/* Code Examples */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Code Examples</h2>
<h4 className="h5 mb-4">Import</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`import HeaderHeroPrimaryMedia from "shared/patterns/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia";`}
</pre>
</div>
<h4 className="h5 mb-4">Basic Example</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<HeaderHeroPrimaryMedia
headline="Build on XRPL"
subtitle="Start developing today with our comprehensive developer tools."
callsToAction={[
{ children: "Get Started", href: "/docs" }
]}
media={{
type: "image",
src: "/img/hero.png",
alt: "XRPL Development"
}}
/>`}
</pre>
</div>
<h4 className="h5 mb-4">With Secondary CTA</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<HeaderHeroPrimaryMedia
headline="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build solutions."
callsToAction={[
{ children: "Get Started", href: "/docs" },
{ children: "Learn More", href: "/about" }
]}
media={{
type: "image",
src: "/img/tokenization.png",
alt: "Tokenization"
}}
/>`}
</pre>
</div>
<h4 className="h5 mb-4">Video Media</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<HeaderHeroPrimaryMedia
headline="Watch and Learn"
subtitle="Explore our video tutorials."
callsToAction={[
{ children: "Watch Tutorials", href: "/tutorials" }
]}
media={{
type: "video",
src: "/video/intro.mp4",
alt: "Introduction video",
autoPlay: true,
loop: true,
muted: true
}}
/>`}
</pre>
</div>
<h4 className="h5 mb-4">Custom Element</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<HeaderHeroPrimaryMedia
headline="Interactive Experience"
subtitle="Engage with custom media."
callsToAction={[
{ children: "Explore", href: "/interactive" }
]}
media={{
type: "custom",
element: <MyAnimationComponent />
}}
/>`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
{/* Best Practices */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Best Practices</h2>
<ul>
<li>
<strong>Media Selection:</strong> Choose media that works well
in a 9:16 portrait aspect ratio. Images and videos will be
cropped to fit.
</li>
<li>
<strong>Alt Text:</strong> Always provide meaningful alt text
for images. For videos, use the <code>alt</code> prop for
accessibility.
</li>
<li>
<strong>Performance:</strong> Use <code>loading="lazy"</code>{" "}
for images below the fold, and optimize video file sizes.
</li>
<li>
<strong>CTAs:</strong> Keep CTA text concise and
action-oriented. Primary CTA should be the main action.
</li>
<li>
<strong>Headlines:</strong> Keep headlines concise and
impactful. Use the subtitle for additional context.
</li>
<li>
<strong>Type Safety:</strong> Leverage TypeScript's
discriminated unions for type-safe media selection.
</li>
<li>
<strong>Responsive Design:</strong> Test your implementation
across all breakpoints to ensure media displays correctly.
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
);
}

View File

@@ -1,692 +0,0 @@
import * as React from 'react';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { HeaderHeroSplitMedia } from 'shared/patterns/HeaderHeroSplitMedia';
export const frontmatter = {
seo: {
title: 'HeaderHeroSplitMedia Pattern Showcase',
description: 'Interactive showcase of the HeaderHeroSplitMedia pattern with all variants, layouts, and responsive behavior.',
},
};
export default function HeaderHeroSplitMediaShowcase() {
// Placeholder image - using the demo-bg.png file
const placeholderImage = '/img/demo-bg.png';
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">HeaderHeroSplitMedia Pattern</h1>
<p className="longform">
A page-level hero pattern that pairs prominent editorial content with a primary media element in a split layout.
Designed to introduce major concepts, products, or use cases while maintaining strong visual hierarchy and clear calls to action.
</p>
</div>
</section>
{/* Surface Variants */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Surface Variants</h2>
<h6 className="eyebrow mb-3">Default vs Accent</h6>
</div>
<p className="mb-10">
The pattern supports two surface treatments: Default (no background) and Accent (green background on title section).
Theme is automatically controlled by <code>html.light</code> and <code>html.dark</code> classes.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Default Surface */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Default Surface</h3>
<p className="mb-6 text-muted">
No background surface behind the hero title. Entire content area has uniform background.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "Tokenization illustration" }}
/>
</div>
{/* Accent Surface */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Accent Surface</h3>
<p className="mb-6 text-muted">
Green accent background with internal padding behind the hero title and subtitle section.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "Tokenization illustration" }}
/>
</div>
</section>
{/* Layout Variants */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Layout Variants</h2>
<h6 className="eyebrow mb-3">Content Position</h6>
</div>
<p className="mb-10">
Control whether the content block appears on the left or right side of the media.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Content Left */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Content Left (Default)</h3>
<p className="mb-6 text-muted">
Content appears on the left, media on the right. This is the default layout.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Build on the XRP Ledger"
subtitle="Start developing with the most sustainable blockchain."
description="Join thousands of developers building the future of finance."
primaryCta={{ label: "Start Building", href: "#start" }}
secondaryCta={{ label: "View Tutorials", href: "#tutorials" }}
media={{ src: placeholderImage, alt: "XRPL development" }}
/>
</div>
{/* Content Right */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Content Right</h3>
<p className="mb-6 text-muted">
Content appears on the right, media on the left. Useful for visual variety.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-right"
title="Enterprise Solutions"
subtitle="Scale your business with blockchain technology."
description="Leverage the XRPL Ledger for enterprise-grade applications."
primaryCta={{ label: "Contact Sales", href: "#contact" }}
secondaryCta={{ label: "Learn More", href: "#learn" }}
media={{ src: placeholderImage, alt: "Enterprise solutions" }}
/>
</div>
</section>
{/* Responsive Behavior */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adaptations</h6>
</div>
<p className="mb-6">
The pattern adapts responsively across desktop, tablet, and mobile breakpoints. Resize your browser window to see the changes.
</p>
<div className="d-flex flex-column gap-4 mb-10">
<div className="d-flex flex-row gap-4 align-items-start" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Desktop (992px)</h6>
<ul className="mb-0">
<li><strong>Layout:</strong> Side-by-side (50/50 split)</li>
<li><strong>Grid columns:</strong> Content 6/12, Media 6/12</li>
<li><strong>Text width:</strong> 5/12 of grid (83.33% of column)</li>
<li><strong>Content gap:</strong> 8px</li>
<li><strong>CTA gap:</strong> 24px (horizontal)</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Tablet (576px991px)</h6>
<ul className="mb-0">
<li><strong>Layout:</strong> Stacked (content above media)</li>
<li><strong>Grid columns:</strong> Content 8/8, Media 8/8</li>
<li><strong>Text width:</strong> 6/8 of grid (75% of column)</li>
<li><strong>Content gap:</strong> 32px</li>
<li><strong>CTA gap:</strong> 24px (horizontal)</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Mobile (&lt;576px)</h6>
<ul className="mb-0">
<li><strong>Layout:</strong> Stacked (content above media)</li>
<li><strong>Grid columns:</strong> Content 4/4, Media 4/4</li>
<li><strong>Text width:</strong> Full width</li>
<li><strong>Content gap:</strong> 32px</li>
<li><strong>CTA gap:</strong> 16px (vertical)</li>
</ul>
</div>
</div>
</div>
<div className="p-4 mb-10" style={{ backgroundColor: 'rgba(114, 119, 126, 0.1)', borderRadius: '8px' }}>
<h6 className="mb-3">Accent Surface Padding</h6>
<ul className="mb-0">
<li><strong>Desktop:</strong> 16px top, 16px left/right, 24px bottom</li>
<li><strong>Tablet:</strong> 16px top, 16px left/right, 24px bottom</li>
<li><strong>Mobile:</strong> 8px top, 8px left/right, 16px bottom</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
{/* Optional Props Combinations */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Optional Props Combinations</h2>
<h6 className="eyebrow mb-3">Flexible Content</h6>
</div>
<p className="mb-10">
The pattern gracefully handles various combinations of optional props. Description, primaryCta, and secondaryCta are all optional.
The layout automatically adjusts based on which props are provided.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Full Content - All Props */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Full Content (All Props)</h3>
<p className="mb-6 text-muted">
Title, subtitle, description, primary CTA, and secondary CTA all provided.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "Full content example" }}
/>
</div>
{/* Full Content - Accent Surface */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Full Content (Accent Surface)</h3>
<p className="mb-6 text-muted">
Same as above but with accent surface for the title section.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "Full content accent example" }}
/>
</div>
{/* No Description - CTAs Only */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">No Description (CTAs Only)</h3>
<p className="mb-6 text-muted">
Title, subtitle, and both CTAs - no description text. CTAs appear directly below the title section.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "No description example" }}
/>
</div>
{/* No Description - Accent Surface */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">No Description (Accent Surface)</h3>
<p className="mb-6 text-muted">
Same as above but with accent surface.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
secondaryCta={{ label: "Tertiary Link", href: "#tertiary" }}
media={{ src: placeholderImage, alt: "No description accent example" }}
/>
</div>
{/* Primary CTA Only (No Description) */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Primary CTA Only (No Description)</h3>
<p className="mb-6 text-muted">
Title, subtitle, and only primary CTA - no description or secondary CTA.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Quick Start Guide"
subtitle="Get up and running in minutes."
primaryCta={{ label: "Begin", href: "#begin" }}
media={{ src: placeholderImage, alt: "Primary CTA only example" }}
/>
</div>
{/* Primary CTA Only (With Description) */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Primary CTA Only (With Description)</h3>
<p className="mb-6 text-muted">
Title, subtitle, description, and only primary CTA - no secondary CTA.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
primaryCta={{ label: "Primary Link", href: "#primary" }}
media={{ src: placeholderImage, alt: "Primary CTA with description example" }}
/>
</div>
{/* No CTAs (Description Only) */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">No CTAs (Description Only)</h3>
<p className="mb-6 text-muted">
Title, subtitle, and description - no CTA buttons.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
description="XRPL Payments Suite helps fintechs and payment providers move money fast, globally, and at low cost - all through simple APIs."
media={{ src: placeholderImage, alt: "No CTAs example" }}
/>
</div>
{/* Title and Subtitle Only (Minimum) */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Title and Subtitle Only (Minimum)</h3>
<p className="mb-6 text-muted">
Only the required props: title, subtitle, and media. No description or CTAs.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
media={{ src: placeholderImage, alt: "Minimum props example" }}
/>
</div>
{/* Title and Subtitle Only (Accent) */}
<div className="mb-10">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h3 className="h5 mb-4">Title and Subtitle Only (Accent Surface)</h3>
<p className="mb-6 text-muted">
Minimum props with accent surface - title section is centered vertically.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions with developer tools and APIs."
media={{ src: placeholderImage, alt: "Minimum props accent example" }}
/>
</div>
</section>
{/* Theme Support */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Theme Support</h2>
<h6 className="eyebrow mb-3">Light & Dark Modes</h6>
</div>
<p className="mb-6">
Theme is automatically controlled by the <code>html.light</code> and <code>html.dark</code> classes on the document root.
No theme prop is needed. Toggle your theme preference to see the pattern adapt.
</p>
<div className="p-4 mb-10" style={{ backgroundColor: 'rgba(114, 119, 126, 0.1)', borderRadius: '8px' }}>
<h6 className="mb-3">Theme Colors</h6>
<div className="d-flex flex-row gap-6" style={{ flexWrap: 'wrap' }}>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<strong className="d-block mb-2">Light Mode</strong>
<ul className="mb-0">
<li>Background: White</li>
<li>Title/Subtitle: Black (#141414)</li>
<li>Description: Gray 500 (#72777e)</li>
</ul>
</div>
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<strong className="d-block mb-2">Dark Mode</strong>
<ul className="mb-0">
<li>Background: Black (#141414)</li>
<li>Title/Subtitle: White</li>
<li>Description: Neutral 200 (#e6eaf0)</li>
</ul>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
{/* Code Examples */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8 mb-6" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { HeaderHeroSplitMedia } from 'shared/patterns/HeaderHeroSplitMedia';
// Full content - all props provided
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Real-world asset tokenization"
subtitle="Learn how to issue crypto tokens and build tokenization solutions."
description="XRPL helps fintechs move money fast, globally, at low cost."
primaryCta={{ label: "Get Started", href: "/docs" }}
secondaryCta={{ label: "Learn More", href: "/about" }}
media={{ src: "/img/hero.png", alt: "Hero illustration" }}
/>
// No description - CTAs directly below title
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="Build on XRPL"
subtitle="Start developing today."
primaryCta={{ label: "Start Building", href: "/docs" }}
secondaryCta={{ label: "View Tutorials", href: "/tutorials" }}
media={{ src: "/img/hero.png", alt: "Development" }}
/>
// Primary CTA only (no description, no secondary CTA)
<HeaderHeroSplitMedia
surface="accent"
layout="content-left"
title="Quick Start"
subtitle="Get up and running in minutes."
primaryCta={{ label: "Begin", href: "/quickstart" }}
media={{ src: "/img/quickstart.png", alt: "Quick start" }}
/>
// Primary CTA only with description
<HeaderHeroSplitMedia
surface="default"
layout="content-right"
title="Enterprise Solutions"
subtitle="Scale your business."
description="Leverage XRPL for enterprise applications."
primaryCta={{ label: "Contact Sales", href: "/contact" }}
media={{ src: "/img/enterprise.png", alt: "Enterprise" }}
/>
// No CTAs - description only
<HeaderHeroSplitMedia
surface="default"
layout="content-left"
title="About XRPL"
subtitle="The decentralized ledger for everyone."
description="Learn about the technology powering global payments."
media={{ src: "/img/about.png", alt: "About XRPL" }}
/>
// Title and subtitle only (minimum required props)
<HeaderHeroSplitMedia
title="Welcome"
subtitle="Explore the possibilities."
media={{ src: "/img/welcome.png", alt: "Welcome" }}
/>`}</code>
</pre>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
{/* Component API */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Component API</h2>
<h6 className="eyebrow mb-3">Props Reference</h6>
</div>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-3 pb-2" style={{ gap: '1rem', borderBottom: '2px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><strong>Prop</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Type</strong></div>
<div style={{ width: '100px', flexShrink: 0 }}><strong>Default</strong></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><strong>Description</strong></div>
</div>
{/* surface */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>surface</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'default' | 'accent'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'default'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Surface variant - accent adds green background on title section</div>
</div>
{/* layout */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>layout</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>'content-left' | 'content-right'</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>'content-left'</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Controls content position relative to media</div>
</div>
{/* title */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>title</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Hero title text (display-md typography)</div>
</div>
{/* subtitle */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>subtitle</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Hero subtitle text (subhead-sm-l typography)</div>
</div>
{/* description */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>description</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Description text below title section (body-l typography)</div>
</div>
{/* primaryCta */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>primaryCta</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{'{ label: string; href: string }'}</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Primary CTA button configuration</div>
</div>
{/* secondaryCta */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>secondaryCta</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{'{ label: string; href: string }'}</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Secondary/Tertiary CTA button configuration</div>
</div>
{/* media */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem', borderBottom: '1px solid var(--bs-border-color, #dee2e6)' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>media</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{'{ src: string; alt: string }'}</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><em>required</em></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Hero media (image) configuration</div>
</div>
{/* className */}
<div className="d-flex flex-row py-3" style={{ gap: '1rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}><code>className</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>string</code></div>
<div style={{ width: '100px', flexShrink: 0 }}><code>undefined</code></div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>Additional CSS classes</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
{/* Design References */}
<section className="py-26">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="d-flex flex-column-reverse mb-8">
<h2 className="h4 mb-0">Design References</h2>
<h6 className="eyebrow mb-3">Figma & Documentation</h6>
</div>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design:</strong>{' '}
<a href="https://www.figma.com/design/olsJKEo16jmwaNXpHxCdbN/Header-Hero---Split-Media?node-id=10066-2126&m=dev" target="_blank" rel="noopener noreferrer">
Header Hero - Split Media Pattern
</a>
</div>
<div>
<strong>Example Usage:</strong>{' '}
<a href="https://www.figma.com/design/olsJKEo16jmwaNXpHxCdbN/Header-Hero---Split-Media?node-id=10066-1792&m=dev" target="_blank" rel="noopener noreferrer">
Figma - Example Usage
</a>
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/patterns/HeaderHeroSplitMedia/HeaderHeroSplitMedia.md</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
</div>
</div>
);
}

View File

@@ -24,6 +24,14 @@ export default function History() {
return (
<div className="landing">
<div className="overflow-hidden">
<div className="position-relative">
<img
alt="background orange waves"
src={require("../static/img/backgrounds/history-orange.svg")}
className="landing-bg"
id="history-orange"
/>
</div>
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
@@ -53,6 +61,13 @@ export default function History() {
</p>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="background purple waves"
src={require("../static/img/backgrounds/history-purple.svg")}
id="history-purple"
/>
</div>
<div className="container-new marketing-wrapper">
<section className="row mb-60">
<div className="timeline">

View File

@@ -32,6 +32,14 @@ export default function Impact() {
return (
<div className="landing page-impact">
<div className="overflow-hidden">
<div className="position-relative d-none-sm">
<img
alt="purple waves"
src={require("../static/img/backgrounds/community-purple.svg")}
className="landing-bg"
id="impact-purple"
/>
</div>
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
@@ -44,6 +52,13 @@ export default function Impact() {
</div>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="green waves"
src={require("../static/img/backgrounds/home-green.svg")}
id="impact-green"
/>
</div>
{/* World map */}
<section className="container-new py-10">
<div className="col-sm-10 col-lg-6 offset-md-3 p-10-until-sm pl-0-sm pr-0-sm">
@@ -118,6 +133,16 @@ export default function Impact() {
{/* Card */}
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="purple waves"
src={require("../static/img/backgrounds/cta-community-purple.svg")}
className="cta cta-top-left"
/>
<img
alt="green waves"
src={require("../static/img/backgrounds/cta-calculator-green.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<div className="d-flex flex-column-reverse">
<h2 className="h4 h2-sm mb-10-until-sm mb-8-sm">

View File

@@ -1,7 +1,6 @@
import * as React from "react";
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { Link } from '@redocly/theme/components/Link/Link';
import { PageGrid, PageGridCol, PageGridRow } from "shared/components/PageGrid/page-grid";
export const frontmatter = {
seo: {
@@ -79,6 +78,14 @@ export default function XrplOverview() {
/>
</div>
</div>
<div className="position-relative">
<img
alt="purple waves"
src={require("../static/img/backgrounds/xrpl-overview-purple.svg")}
className="landing-bg"
id="xrpl-overview-purple"
/>
</div>
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
@@ -93,6 +100,13 @@ export default function XrplOverview() {
</div>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/xrpl-overview-orange.svg")}
id="xrpl-overview-orange"
/>
</div>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
@@ -119,7 +133,7 @@ export default function XrplOverview() {
{translate("Read Technical Docs")}
</Link>{" "}
<a
className="ms-4 video-external-link"
className="ml-4 video-external-link"
target="_blank"
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
>
@@ -154,7 +168,7 @@ export default function XrplOverview() {
{translate("Read Technical Docs")}
</Link>{" "}
<a
className="ms-4 video-external-link"
className="ml-4 video-external-link"
target="_blank"
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
>
@@ -164,9 +178,9 @@ export default function XrplOverview() {
</div>
</div>
</section>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="d-flex flex-column-reverse">
<h2 className="h4 h2-sm mb-8">
{translate("How the Consensus Protocol works")}
@@ -193,23 +207,25 @@ export default function XrplOverview() {
<p className="mb-0">
{translate('about.index.consensus.ppart1', 'Currently, over 120 ')}
<a href="https://livenet.xrpl.org/network/validators" target="_blank">{translate('about.index.consensus.ppart2', 'validators')}</a>
{translate('about.index.consensus.ppart3', ' are active on the ledger, operated by universities, exchanges, businesses, and individuals. As the validator pool grows, the consensus protocol ensures decentralization of the blockchain over time.')}
{translate('about.index.consensus.ppart3', ' are active on the ledger, operated by universities, exchanges, businesses, and individuals. As the validator pool grows, the consensus protocol ensures decentralization of the blockchain over time.')}
</p>
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
<div className="col mb-16-sm">
<img
className="mw-100"
id="validator-graphic"
alt="(Graphic: Validators in Consensus)"
/>
</div>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }} offset={{ lg: 3 }} className="p-6-sm p-10-until-sm br-8 cta-card">
</div>
<div className="col mb-16-sm">
<img
className="mw-100"
id="validator-graphic"
alt="(Graphic: Validators in Consensus)"
/>
</div>
</div>
</section>
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="green waves"
src={require("../static/img/backgrounds/cta-xrpl-overview-green.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate("A Sustainable Blockchain")}
@@ -223,13 +239,11 @@ export default function XrplOverview() {
{translate("Learn More")}
</a>
</div>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
</div>
</section>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="d-flex flex-column-reverse">
<h4 className="h4 h2-sm mb-8">
{translate("Building with confidence on ")}
@@ -251,8 +265,8 @@ export default function XrplOverview() {
<a className="btn btn-primary btn-arrow mb-10-sm" href="/about/uses">
{translate("Explore More")}
</a>
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
</div>
<div className="col mb-0">
<div className="d-flex flex-column-reverse">
<h4 className="h4 h2-sm mb-8">
{translate("Creating new value for long-term growth")}
@@ -269,11 +283,11 @@ export default function XrplOverview() {
"Significant investment in development, along with low transaction costs and energy usage, is fueling growth and opening up a wide variety of use cases at scale."
)}
</p>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
</div>
</div>
</section>
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 ps-0 ">
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 pl-0 ">
<h2 className="h4 h2-sm">
{translate(
"Watch the explainer video series to learn more about the XRP Ledger"
@@ -361,6 +375,11 @@ export default function XrplOverview() {
</section>
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="orange waves"
src={require("../static/img/backgrounds/cta-xrpl-overview-orange.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<h4 className="h4 mb-10-until-sm mb-8-sm">
{translate("Tomorrows Blockchain Starts With You")}
@@ -388,7 +407,7 @@ export default function XrplOverview() {
</section>
<section className="container-new py-26">
<div
className="col-md-10 offset-md-1 col-lg-8 offset-lg-2 ps-0 pe-0 mini-faq"
className="col-md-6 offset-md-3 w-100 pl-0 pr-0 mini-faq"
id="minifaq-accordion"
>
{faqs.map((faq, index) => (
@@ -396,8 +415,8 @@ export default function XrplOverview() {
<a
href={`#heading${index + 1}`}
className="expander collapsed"
data-bs-toggle="collapse"
data-bs-target={`#answer${index + 1}`}
data-toggle="collapse"
data-target={`#answer${index + 1}`}
aria-expanded="false"
aria-controls={`answer${index + 1}`}
>

View File

@@ -1,310 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { BdsLink } from "shared/components/Link/Link";
export const frontmatter = {
seo: {
title: 'Link Component Showcase',
description: "A comprehensive showcase of all Link component variants, sizes, and states in the XRPL.org Design System.",
}
};
export default function LinkShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">Link Component</h1>
<p className="longform">
A comprehensive showcase of all Link component variants, sizes, and states.
</p>
</div>
</section>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Size by Variant Matrix</h2>
<div className="mb-10">
{/* Header Row */}
<div className="d-flex flex-row mb-4" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<h6 className="mb-0">Size</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Internal Links</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">External Links</h6>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<h6 className="mb-0">Disabled State</h6>
</div>
</div>
{/* Small Row */}
<div className="d-flex flex-row mb-5 align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Small</strong>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="/docs" variant="internal" size="small">
Small Internal Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="https://example.com" variant="external" size="small" target="_blank" rel="noopener noreferrer">
Small External Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="#" variant="internal" size="small" disabled>
Disabled Internal Link
</BdsLink>
</div>
</div>
{/* Medium Row */}
<div className="d-flex flex-row mb-5 align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Medium</strong>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="/docs" variant="internal" size="medium">
Medium Internal Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="https://example.com" variant="external" size="medium" target="_blank" rel="noopener noreferrer">
Medium External Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="#" variant="external" size="medium" disabled>
Disabled External Link
</BdsLink>
</div>
</div>
{/* Large Row */}
<div className="d-flex flex-row align-items-center" style={{ gap: '2rem' }}>
<div style={{ width: '120px', flexShrink: 0 }}>
<strong>Large</strong>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="/docs" variant="internal" size="large">
Large Internal Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="https://example.com" variant="external" size="large" target="_blank" rel="noopener noreferrer">
Large External Link
</BdsLink>
</div>
<div style={{ flex: '1 1 0', minWidth: 0 }}>
<BdsLink href="#" variant="internal" size="large" disabled>
Disabled Internal Link
</BdsLink>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Sizes</h2>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Small</h6>
<BdsLink href="/docs" size="small">
Small Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">Medium</h6>
<BdsLink href="/docs" size="medium">
Medium Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">Large</h6>
<BdsLink href="/docs" size="large">
Large Link
</BdsLink>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Color States</h2>
<p className="mb-4">Links automatically handle color states via CSS per theme:</p>
<div className="d-flex flex-row gap-6 mb-6" style={{ flexWrap: 'wrap' }}>
{/* Light Mode Colors */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Light Mode</h6>
<ul className="mb-0">
<li><strong>Enabled:</strong> Green 400 <code style={{ color: '#0DAA3E' }}>#0DAA3E</code></li>
<li><strong>Hover/Focus:</strong> Green 500 <code style={{ color: '#078139' }}>#078139</code> + underline</li>
<li><strong>Active:</strong> Green 400 <code style={{ color: '#0DAA3E' }}>#0DAA3E</code> + underline</li>
<li><strong>Visited:</strong> Lilac 400 <code style={{ color: '#7649E3' }}>#7649E3</code></li>
<li><strong>Disabled:</strong> Gray 400 <code style={{ color: '#A2A2A4' }}>#A2A2A4</code></li>
<li><strong>Focus Outline:</strong> Black <code style={{ color: '#000000' }}>#000000</code></li>
</ul>
</div>
{/* Dark Mode Colors */}
<div style={{ flex: '1 1 300px', minWidth: '280px' }}>
<h6 className="mb-3">Dark Mode</h6>
<ul className="mb-0">
<li><strong>Enabled:</strong> Green 300 <code style={{ color: '#21E46B' }}>#21E46B</code></li>
<li><strong>Hover/Focus:</strong> Green 200 <code style={{ color: '#70EE97' }}>#70EE97</code> + underline</li>
<li><strong>Active:</strong> Green 300 <code style={{ color: '#21E46B' }}>#21E46B</code> + underline</li>
<li><strong>Visited:</strong> Lilac 300 <code style={{ color: '#C0A7FF' }}>#C0A7FF</code></li>
<li><strong>Disabled:</strong> Gray 500 <code style={{ color: '#838386' }}>#838386</code></li>
<li><strong>Focus Outline:</strong> White <code style={{ color: '#FFFFFF', backgroundColor: '#333', padding: '0 4px' }}>#FFFFFF</code></li>
</ul>
</div>
</div>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Default (hover to see state changes and arrow animation)</h6>
<BdsLink href="/docs" size="medium">
Default Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">Disabled</h6>
<BdsLink href="#" size="medium" disabled>
Disabled Link
</BdsLink>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Icon Types</h2>
<p className="mb-4">Arrow icons animate to chevron shape on hover (150ms cubic-bezier transition).</p>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Arrow (Internal) - animates to chevron on hover</h6>
<BdsLink href="/docs" size="medium" icon="arrow">
Arrow Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">External</h6>
<BdsLink href="https://example.com" size="medium" variant="external" target="_blank" rel="noopener noreferrer">
External Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">Inline (No Icon)</h6>
<p>
This is a paragraph with an{" "}
<BdsLink href="/docs" variant="inline">
inline link
</BdsLink>{" "}
embedded within the text.
</p>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Variants</h2>
<div className="d-flex flex-column gap-4 mb-10">
<div>
<h6 className="mb-3">Internal</h6>
<BdsLink href="/docs" variant="internal" size="medium">
Internal Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">External</h6>
<BdsLink href="https://example.com" variant="external" size="medium" target="_blank" rel="noopener noreferrer">
External Link
</BdsLink>
</div>
<div>
<h6 className="mb-3">Inline</h6>
<p>
This is a paragraph with an{" "}
<BdsLink href="/docs" variant="inline">
inline link
</BdsLink>{" "}
that appears within the text flow.
</p>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Real-World Examples</h2>
<div className="d-flex flex-column gap-6 mb-10">
<div>
<h6 className="mb-4">Navigation Links</h6>
<div className="d-flex flex-column gap-3">
<BdsLink href="/docs" size="medium">
View Documentation
</BdsLink>
<BdsLink href="/about" size="medium">
Learn More About XRPL
</BdsLink>
<BdsLink href="https://github.com/XRPLF/xrpl-dev-portal" variant="external" size="medium" target="_blank" rel="noopener noreferrer">
GitHub Repository
</BdsLink>
</div>
</div>
<div>
<h6 className="mb-4">Inline Text Links</h6>
<p className="longform">
The XRP Ledger is a decentralized public blockchain. You can{" "}
<BdsLink href="/docs" variant="inline">
read the technical documentation
</BdsLink>{" "}
to learn more about how it works. The network is maintained by a{" "}
<BdsLink href="/about" variant="inline">
global community
</BdsLink>{" "}
of developers and validators.
</p>
</div>
<div>
<h6 className="mb-4">Call-to-Action Links</h6>
<div className="d-flex flex-column gap-3">
<BdsLink href="/docs" size="large">
Get Started with XRPL
</BdsLink>
<BdsLink href="/about/uses" size="large">
Explore Use Cases
</BdsLink>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,148 +0,0 @@
import * as React from "react";
import { LinkSmallGrid } from "shared/patterns/LinkSmallGrid";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'LinkSmallGrid Component Showcase',
description: "A comprehensive showcase of the LinkSmallGrid pattern component with responsive grid layouts, color variants, and usage examples.",
}
};
export default function LinkSmallGridShowcase() {
const handleClick = (message: string) => {
console.log(`Link clicked: ${message}`);
};
// Sample links for demonstrations
const sampleLinks = [
{ label: "Documentation", href: "/docs" },
{ label: "Tutorials", href: "/tutorials" },
{ label: "API Reference", href: "/api" },
{ label: "Examples", href: "/examples" },
{ label: "Best Practices", href: "/best-practices" },
{ label: "Tools", href: "/tools" },
{ label: "Resources", href: "/resources" },
{ label: "Community", href: "/community" },
];
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">LinkSmallGrid Pattern</h1>
<p className="longform">
A responsive grid section pattern for displaying navigational links using TileLink components.
Features a heading, optional description, and a grid of clickable tiles with 2 color variants
and full light/dark mode support.
</p>
</div>
</section>
<Divider color="gray" />
{/* Full Example - Gray Variant */}
<LinkSmallGrid
variant="gray"
heading="Quick Links"
description="Navigate to key sections of our documentation and resources."
links={sampleLinks}
/>
<Divider color="gray" />
{/* Full Example - Lilac Variant */}
<LinkSmallGrid
variant="lilac"
heading="Get Started"
description="Explore tutorials and guides to begin your journey with XRPL."
links={sampleLinks.slice(0, 4)}
/>
<Divider color="gray" />
{/* Gray Variant - Heading Only */}
<LinkSmallGrid
variant="gray"
heading="Developer Resources"
links={sampleLinks.slice(0, 6)}
/>
<Divider color="gray" />
{/* Lilac Variant - With Click Handlers */}
<LinkSmallGrid
variant="lilac"
heading="Interactive Examples"
description="Click any tile to see the onClick handler in action (check console)."
links={[
{ label: "Example 1", onClick: () => handleClick('Example 1') },
{ label: "Example 2", onClick: () => handleClick('Example 2') },
{ label: "Example 3", onClick: () => handleClick('Example 3') },
{ label: "Example 4", onClick: () => handleClick('Example 4') },
]}
/>
<Divider color="gray" />
{/* Different Link Counts */}
<section className=" py-26">
<div className="d-flex flex-column-reverse mb-10">
<h2 className="h4 mb-8">Different Link Counts</h2>
<h6 className="eyebrow mb-3">Responsive Behavior</h6>
</div>
<div className="mb-10">
<h6 className="mb-4">2 Links</h6>
<LinkSmallGrid
variant="gray"
heading="Featured Sections"
links={sampleLinks.slice(0, 2)}
/>
</div>
<div className="mb-10">
<h6 className="mb-4">3 Links</h6>
<LinkSmallGrid
variant="lilac"
heading="Core Topics"
links={sampleLinks.slice(0, 3)}
/>
</div>
<div className="mb-10">
<h6 className="mb-4">5 Links</h6>
<LinkSmallGrid
variant="gray"
heading="Learning Paths"
description="Choose a path to start learning."
links={sampleLinks.slice(0, 5)}
/>
</div>
<div className="mb-10">
<h6 className="mb-4">12 Links</h6>
<LinkSmallGrid
variant="lilac"
heading="Complete Navigation"
description="Full grid with multiple rows."
links={[
...sampleLinks,
{ label: "Blog", href: "/blog" },
{ label: "Events", href: "/events" },
{ label: "Newsletter", href: "/newsletter" },
{ label: "Support", href: "/support" },
]}
/>
</div>
</section>
<Divider color="gray" />
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,820 +0,0 @@
import * as React from "react";
import {
PageGrid,
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import { SmallTilesSection } from "shared/patterns/SmallTilesSection/SmallTilesSection";
export const frontmatter = {
seo: {
title: "SmallTilesSection Component Showcase",
description:
"A comprehensive showcase of the SmallTilesSection component, demonstrating its grid layout, variants, and responsive behavior.",
},
};
export default function SmallTilesSectionShowcase() {
const handleClick = (message: string) => {
console.log(`Card clicked: ${message}`);
};
// Sample icon SVG (black version for light backgrounds)
const cardIconSvg =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='53' height='38' viewBox='0 0 53 38' fill='none'%3E%3Cpath d='M38.6603 0.0618191C35.7826 0.289503 33.3694 1.32168 31.5728 3.09764C29.7228 4.92673 28.8397 7.15805 28.8397 9.98896C28.8397 14.2239 30.5831 17.1839 34.4732 19.529C35.4629 20.121 36.8104 20.7661 39.1399 21.768C42.3144 23.1265 43.4944 23.7716 44.2481 24.5761C45.1769 25.5703 45.4357 27.1565 44.8495 28.3709C44.7353 28.6062 44.4384 29.0008 44.172 29.2664C43.2737 30.1696 41.8577 30.6477 40.0991 30.6477C37.1301 30.6477 34.9148 29.4334 33.1334 26.8074C32.8898 26.4583 32.669 26.1699 32.6385 26.1699C32.57 26.1699 26.7767 29.5017 26.6549 29.6156C26.5787 29.6839 26.6396 29.8433 26.9365 30.329C29.2508 34.2148 32.8669 36.4917 37.7544 37.1444C39.0333 37.319 41.4314 37.3114 42.657 37.1444C45.7326 36.7118 48.0393 35.6948 49.8283 33.9644C51.7315 32.1353 52.6679 29.7674 52.6679 26.7998C52.6679 24.9024 52.3558 23.4225 51.6478 21.9577C51.1605 20.9559 50.6733 20.2804 49.8359 19.4304C48.2296 17.8062 46.1513 16.5767 42.0023 14.8007C38.8658 13.4574 37.8153 12.8806 37.1225 12.1444C36.4602 11.4386 36.1785 10.6113 36.2394 9.57912C36.2927 8.75945 36.5211 8.20541 37.0235 7.66656C37.7468 6.88483 38.5842 6.55848 39.8783 6.56607C41.3476 6.56607 42.2992 6.94555 43.2661 7.91701C43.6086 8.25095 44.0502 8.78981 44.2557 9.11616C44.4917 9.48805 44.6668 9.69297 44.7277 9.6702C44.9256 9.58671 50.4602 6.01962 50.4602 5.9665C50.4602 5.93614 50.1785 5.49594 49.8359 4.97985C49.1051 3.88696 47.7881 2.52083 46.8821 1.92126C45.2073 0.813185 43.4183 0.243967 41.0583 0.0694065C39.9012 -0.0216694 39.7489 -0.0216694 38.6603 0.0618191Z' fill='black'/%3E%3Cpath d='M14.9592 13.8528L14.9364 27.2711L14.7689 27.901C14.5481 28.7283 14.2893 29.2216 13.8325 29.677C13.193 30.3145 12.3708 30.5802 11.0005 30.5877C9.04403 30.5953 7.87166 29.7681 6.50896 27.4457C6.28819 27.0814 6.09026 26.7854 6.06742 26.793C6.03697 26.8081 4.65905 27.6354 3.00706 28.6296L0 30.4511L0.228385 30.9065C1.59108 33.616 3.95105 35.6652 6.79064 36.6063C9.79009 37.6005 13.6422 37.5094 16.4665 36.3786C19.8542 35.0125 21.8412 32.1891 22.3665 27.9921C22.4121 27.5671 22.4426 22.8236 22.4426 13.8983V0.442009H18.7123H14.9896L14.9592 13.8528Z' fill='black'/%3E%3C/svg%3E";
// Sample card data sets
const languageTutorials = [
{
icon: cardIconSvg,
iconAlt: "JavaScript",
label: "JavaScript",
href: "#javascript",
},
{
icon: cardIconSvg,
iconAlt: "Python",
label: "Python",
href: "#python",
},
{
icon: cardIconSvg,
iconAlt: "Go",
label: "Go",
href: "#go",
},
{
icon: cardIconSvg,
iconAlt: "Rust",
label: "Rust",
href: "#rust",
},
{
icon: cardIconSvg,
iconAlt: "Java",
label: "Java",
href: "#java",
},
{
icon: cardIconSvg,
iconAlt: "C++",
label: "C++",
href: "#cpp",
},
];
const featuredTopics = [
{
icon: cardIconSvg,
iconAlt: "Quick Start",
label: "Quick Start Guide",
onClick: () => handleClick("quickstart"),
},
{
icon: cardIconSvg,
iconAlt: "Getting Started",
label: "Get Started",
onClick: () => handleClick("getting-started"),
},
{
icon: cardIconSvg,
iconAlt: "Tutorial",
label: "Build Your First App",
onClick: () => handleClick("first-app"),
},
{
icon: cardIconSvg,
iconAlt: "Advanced",
label: "Advanced Topics",
onClick: () => handleClick("advanced"),
},
];
const largeCardSet = [
{
icon: cardIconSvg,
iconAlt: "Topic 1",
label: "Topic One",
href: "#topic1",
},
{
icon: cardIconSvg,
iconAlt: "Topic 2",
label: "Topic Two",
href: "#topic2",
},
{
icon: cardIconSvg,
iconAlt: "Topic 3",
label: "Topic Three",
href: "#topic3",
},
{
icon: cardIconSvg,
iconAlt: "Topic 4",
label: "Topic Four",
href: "#topic4",
},
{
icon: cardIconSvg,
iconAlt: "Topic 5",
label: "Topic Five",
href: "#topic5",
},
{
icon: cardIconSvg,
iconAlt: "Topic 6",
label: "Topic Six",
href: "#topic6",
},
{
icon: cardIconSvg,
iconAlt: "Topic 7",
label: "Topic Seven",
href: "#topic7",
},
{
icon: cardIconSvg,
iconAlt: "Topic 8",
label: "Topic Eight",
href: "#topic8",
},
{
icon: cardIconSvg,
iconAlt: "Topic 9",
label: "Topic Nine",
href: "#topic9",
},
{
icon: cardIconSvg,
iconAlt: "Topic 10",
label: "Topic Ten",
href: "#topic10",
},
{
icon: cardIconSvg,
iconAlt: "Topic 11",
label: "Topic Eleven",
href: "#topic11",
},
];
const mixedStates = [
{
icon: cardIconSvg,
iconAlt: "Active Card",
label: "Active Card",
href: "#active",
},
{
icon: cardIconSvg,
iconAlt: "Clickable Card",
label: "Clickable Card",
onClick: () => handleClick("clickable"),
},
{
icon: cardIconSvg,
iconAlt: "Coming Soon",
label: "Coming Soon",
disabled: true,
},
{
icon: cardIconSvg,
iconAlt: "Another Active",
label: "Another Active",
href: "#another",
},
{
icon: cardIconSvg,
iconAlt: "Yet Another",
label: "Yet Another",
href: "#yetanother",
},
];
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">SmallTilesSection Component</h1>
<p className="longform">
A section component that displays multiple CardIcon components in
a responsive grid layout. Features automatic grid adjustments at
breakpoints, optional subtitle, and spacer support for large card
sets (9+ cards).
</p>
</div>
</section>
{/* Basic Usage */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Basic Usage</h2>
<p className="mb-6">
SmallTilesSection automatically creates a responsive grid of
CardIcon components. The grid adapts from 1 column on mobile, 2
columns on tablets, to 3 columns on desktop.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Language Tutorials"
subtitle="Choose a language to get started with XRPL development"
cardVariant="neutral"
cards={languageTutorials}
/>
{/* Green Variant */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Green Variant</h2>
<p className="mb-6">
Use the green variant to highlight featured or recommended
content.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Featured Topics"
subtitle="Recommended content to help you get started"
cardVariant="green"
cards={featuredTopics}
/>
{/* Without Subtitle */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Without Subtitle</h2>
<p className="mb-6">
The subtitle is optional. Here's an example without it.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Quick Links"
cardVariant="neutral"
cards={featuredTopics.slice(0, 3)}
/>
{/* Large Card Set with Spacer */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Large Card Set (Spacer Feature)</h2>
<p className="mb-6">
When a section contains more than 8 cards, the component
automatically adds a spacer element to improve grid alignment.
This ensures cards align properly even with varying card counts.
The spacer is visible on large screens (≥992px).
</p>
<div
className="p-4 mb-6"
style={{
backgroundColor: "rgba(114, 119, 126, 0.1)",
borderRadius: "8px",
}}
>
<h6 className="mb-3">Spacer Details</h6>
<ul className="mb-0">
<li>
<strong>Threshold:</strong> 8 cards (spacer appears with 9+
cards)
</li>
<li>
<strong>Visibility:</strong> Only visible on large screens
(≥992px)
</li>
<li>
<strong>Purpose:</strong> Ensures proper grid alignment with
varying card counts
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="All Topics"
subtitle={`${largeCardSet.length} topics available (spacer enabled)`}
cardVariant="neutral"
cards={largeCardSet}
/>
{/* Mixed States */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Mixed Card States</h2>
<p className="mb-6">
Individual cards within the section can have different states
and behaviors. Cards can be links (href), buttons (onClick), or
disabled.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Mixed States Example"
subtitle="Combination of links, click handlers, and disabled cards"
cardVariant="neutral"
cards={mixedStates}
/>
{/* Responsive Behavior */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Responsive Grid Behavior</h2>
<p className="mb-6">
The grid automatically adjusts based on viewport width. Resize
your browser to see the changes.
</p>
<div className="d-flex flex-column gap-4 mb-6">
<div
className="d-flex flex-row gap-4 align-items-start"
style={{ flexWrap: "wrap" }}
>
<div style={{ flex: "1 1 300px", minWidth: "280px" }}>
<h6 className="mb-3">Mobile (&lt;576px)</h6>
<ul className="mb-0">
<li>
<strong>Columns:</strong> 1
</li>
<li>
<strong>Gap:</strong> 8px
</li>
<li>
<strong>Layout:</strong> Single column stack
</li>
</ul>
</div>
<div style={{ flex: "1 1 300px", minWidth: "280px" }}>
<h6 className="mb-3">Tablet (576px991px)</h6>
<ul className="mb-0">
<li>
<strong>Columns:</strong> 2
</li>
<li>
<strong>Gap:</strong> 8px
</li>
<li>
<strong>Layout:</strong> Two column grid
</li>
</ul>
</div>
<div style={{ flex: "1 1 300px", minWidth: "280px" }}>
<h6 className="mb-3">Desktop (≥992px)</h6>
<ul className="mb-0">
<li>
<strong>Columns:</strong> 3
</li>
<li>
<strong>Gap:</strong> 8px
</li>
<li>
<strong>Layout:</strong> Three column grid
</li>
<li>
<strong>Spacer:</strong> Enabled for 9+ cards
</li>
</ul>
</div>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Responsive Demo"
subtitle="Resize your browser to see the grid adapt"
cardVariant="neutral"
cards={languageTutorials}
/>
{/* Real-World Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Real-World Examples</h2>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="d-flex flex-column gap-8 mb-10">
{/* Documentation Categories */}
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h6 className="mb-4">Documentation Categories</h6>
<p className="mb-4 text-muted">
Use SmallTilesSection to organize documentation by category or
topic.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Browse by Category"
subtitle="Explore documentation organized by topic"
cardVariant="neutral"
cards={[
{
icon: cardIconSvg,
iconAlt: "Concepts",
label: "Concepts",
href: "#concepts",
},
{
icon: cardIconSvg,
iconAlt: "Tutorials",
label: "Tutorials",
href: "#tutorials",
},
{
icon: cardIconSvg,
iconAlt: "References",
label: "References",
href: "#references",
},
{
icon: cardIconSvg,
iconAlt: "Use Cases",
label: "Use Cases",
href: "#use-cases",
},
]}
/>
{/* Featured Resources */}
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h6 className="mb-4">Featured Resources</h6>
<p className="mb-4 text-muted">
Highlight important resources using the green variant.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Featured Resources"
subtitle="Start here for the most important resources"
cardVariant="green"
cards={[
{
icon: cardIconSvg,
iconAlt: "Getting Started",
label: "Getting Started",
href: "#getting-started",
},
{
icon: cardIconSvg,
iconAlt: "Quick Start",
label: "Quick Start Guide",
href: "#quickstart",
},
{
icon: cardIconSvg,
iconAlt: "Best Practices",
label: "Best Practices",
href: "#best-practices",
},
]}
/>
{/* Development Tools */}
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<h6 className="mb-4">Development Tools</h6>
<p className="mb-4 text-muted">
Showcase available tools and SDKs.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<SmallTilesSection
headline="Development Tools & SDKs"
cardVariant="neutral"
cards={[
{
icon: cardIconSvg,
iconAlt: "xrpl.js",
label: "xrpl.js",
href: "#xrpl-js",
},
{
icon: cardIconSvg,
iconAlt: "xrpl-py",
label: "xrpl-py",
href: "#xrpl-py",
},
{
icon: cardIconSvg,
iconAlt: "xrpl-clio",
label: "xrpl-clio",
href: "#xrpl-clio",
},
{
icon: cardIconSvg,
iconAlt: "XRPL Explorer",
label: "XRPL Explorer",
href: "#explorer",
},
{
icon: cardIconSvg,
iconAlt: "Testnet Faucet",
label: "Testnet Faucet",
href: "#faucet",
},
{
icon: cardIconSvg,
iconAlt: "Validator",
label: "Validator",
href: "#validator",
},
]}
/>
</div>
{/* API Reference */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Component API</h2>
<div className="mb-10">
{/* Header Row */}
<div
className="d-flex flex-row mb-3 pb-2"
style={{
gap: "1rem",
borderBottom: "2px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<strong>Prop</strong>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<strong>Type</strong>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<strong>Default</strong>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<strong>Description</strong>
</div>
</div>
{/* headline */}
<div
className="d-flex flex-row py-3"
style={{
gap: "1rem",
borderBottom: "1px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>headline</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>React.ReactNode</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<em>required</em>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
Section headline displayed as h2
</div>
</div>
{/* subtitle */}
<div
className="d-flex flex-row py-3"
style={{
gap: "1rem",
borderBottom: "1px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>subtitle</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>React.ReactNode</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<code>undefined</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
Optional subtitle text displayed below headline
</div>
</div>
{/* cardVariant */}
<div
className="d-flex flex-row py-3"
style={{
gap: "1rem",
borderBottom: "1px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>cardVariant</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>'neutral' | 'green'</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<em>required</em>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
Color variant applied to all cards in the section
</div>
</div>
{/* cards */}
<div
className="d-flex flex-row py-3"
style={{
gap: "1rem",
borderBottom: "1px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>cards</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>CardIconProps[]</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<em>required</em>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
Array of card configurations (CardIconProps without variant
prop). Section renders nothing if array is empty.
</div>
</div>
{/* className */}
<div
className="d-flex flex-row py-3"
style={{
gap: "1rem",
borderBottom: "1px solid var(--bs-border-color, #dee2e6)",
}}
>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>className</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>string</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<code>''</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
Additional CSS classes for the section element
</div>
</div>
{/* Standard section props */}
<div className="d-flex flex-row py-3" style={{ gap: "1rem" }}>
<div style={{ width: "120px", flexShrink: 0 }}>
<code>...rest</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
<code>React.ComponentPropsWithoutRef&lt;"section"&gt;</code>
</div>
<div style={{ width: "100px", flexShrink: 0 }}>
<code>-</code>
</div>
<div style={{ flex: "1 1 0", minWidth: 0 }}>
All standard HTML section element props
</div>
</div>
</div>
<div className="mb-10">
<h3 className="h5 mb-4">Card Props (cards array items)</h3>
<p className="mb-4">
Each item in the <code>cards</code> array accepts all CardIcon
props except <code>variant</code> (which is set via{" "}
<code>cardVariant</code>).
</p>
<ul>
<li>
<code>icon</code> (string, required) - Icon image source
</li>
<li>
<code>iconAlt</code> (string, optional) - Alt text for icon
</li>
<li>
<code>label</code> (string, required) - Card label text
</li>
<li>
<code>href</code> (string, optional) - Link destination
(renders as anchor)
</li>
<li>
<code>onClick</code> (() =&gt; void, optional) - Click
handler (renders as button)
</li>
<li>
<code>disabled</code> (boolean, optional) - Disabled state
</li>
<li>
<code>className</code> (string, optional) - Additional CSS
classes
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Design Notes */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design Notes</h2>
<div className="d-flex flex-column gap-4">
<div>
<h6 className="mb-2">Grid Layout</h6>
<ul>
<li>
Responsive grid with automatic column adjustments at
breakpoints
</li>
<li>
8px gap between cards (consistent across all breakpoints)
</li>
<li>
Grid uses CSS Grid with <code>grid-auto-flow: row</code>
</li>
</ul>
</div>
<div>
<h6 className="mb-2">Spacer Feature</h6>
<ul>
<li>Automatically enabled when card count exceeds 8</li>
<li>Spacer is a grid-spanning invisible element</li>
<li>
Only visible on large screens (992px) to improve
alignment
</li>
<li>
Helps maintain consistent grid layout with varying card
counts
</li>
</ul>
</div>
<div>
<h6 className="mb-2">Typography</h6>
<ul>
<li>
Headline uses <code>h4</code> class
</li>
<li>
Subtitle uses <code>body-r</code> class
</li>
<li>8px margin below headline</li>
<li>24px margin below subtitle (when present)</li>
</ul>
</div>
<div>
<h6 className="mb-2">Component Structure</h6>
<ul>
<li>
Returns <code>null</code> if cards array is empty
</li>
<li>
Each card is wrapped in a <code>&lt;li&gt;</code> element
</li>
<li>Cards are rendered using the CardIcon component</li>
<li>All cards in a section share the same variant</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -1,559 +0,0 @@
import * as React from "react";
import {
PageGrid,
PageGridRow,
PageGridCol,
} from "shared/components/PageGrid/page-grid";
import StandardCardGroupSection, {
type StandardCardPropsWithoutVariant,
} from "shared/patterns/StandardCardGroupSection/StandardCardGroupSection";
export const frontmatter = {
seo: {
title: "StandardCardGroupSection Pattern Showcase",
description:
"Interactive showcase of the StandardCardGroupSection pattern with all variants, responsive behavior, and composition examples.",
},
};
// Demo component for code examples
const CodeDemo = ({
title,
description,
code,
children,
}: {
title: string;
description?: string;
code?: string;
children?: React.ReactNode;
}) => (
<div className="mb-26">
<h3 className="h4 mb-4">{title}</h3>
{description && <p className="mb-6">{description}</p>}
{code && (
<div
className="mb-6 p-4 bg-light br-4 text-black"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap", color: "#000" }}>
{code}
</pre>
</div>
)}
{children && (
<div
style={{
border: "1px dashed #ccc",
padding: "16px",
backgroundColor: "#f9f9f9",
borderRadius: "8px",
}}
>
{children}
</div>
)}
</div>
);
// Module-level card data to avoid recreating on each render
const BASIC_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Feature 1",
children: "Description of feature 1",
callsToAction: [{ children: "Learn More", href: "/feature1" }],
},
{
headline: "Feature 2",
children: "Description of feature 2",
callsToAction: [{ children: "Learn More", href: "/feature2" }],
},
];
const GREEN_VARIANT_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Developer Tools",
children: "Comprehensive APIs and SDKs for building on XRPL",
callsToAction: [{ children: "Get Started", href: "/docs" }],
},
{
headline: "Payment Solutions",
children: "Fast, low-cost global payment infrastructure",
callsToAction: [{ children: "Learn More", href: "/payments" }],
},
{
headline: "Tokenization",
children: "Issue and manage digital assets on XRPL",
callsToAction: [{ children: "Explore", href: "/tokens" }],
},
{
headline: "DeFi Protocols",
children: "Decentralized finance applications and liquidity pools",
callsToAction: [{ children: "Discover", href: "/defi" }],
},
{
headline: "NFT Marketplace",
children: "Create, trade, and manage non-fungible tokens",
callsToAction: [{ children: "View Marketplace", href: "/nfts" }],
},
{
headline: "Enterprise Solutions",
children: "Scalable blockchain infrastructure for businesses",
callsToAction: [{ children: "Contact Sales", href: "/enterprise" }],
},
];
const NEUTRAL_VARIANT_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Documentation",
children: "Comprehensive guides and API references",
callsToAction: [{ children: "View Docs", href: "/docs" }],
},
{
headline: "Tutorials",
children: "Step-by-step guides and examples",
callsToAction: [{ children: "Browse Tutorials", href: "/tutorials" }],
},
];
const YELLOW_VARIANT_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "New Features",
children: "Latest updates and enhancements to the platform",
callsToAction: [{ children: "See What's New", href: "/features" }],
},
{
headline: "Special Offers",
children: "Exclusive deals and promotions for early adopters",
callsToAction: [{ children: "View Offers", href: "/offers" }],
},
{
headline: "Community Events",
children: "Join upcoming workshops, webinars, and meetups",
callsToAction: [{ children: "Browse Events", href: "/events" }],
},
];
const BLUE_VARIANT_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Cross-Border Payments",
children: "Send money globally in seconds",
callsToAction: [{ children: "Learn More", href: "/payments" }],
},
{
headline: "NFT Marketplaces",
children: "Create and trade digital collectibles",
callsToAction: [{ children: "Explore", href: "/nfts" }],
},
{
headline: "Central Bank Digital Currencies",
children: "CBDC infrastructure and solutions",
callsToAction: [{ children: "Read More", href: "/cbdc" }],
},
];
const SECONDARY_CTA_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Enterprise Solutions",
children: "Scalable infrastructure for large organizations",
callsToAction: [
{ children: "Contact Sales", href: "/contact" },
{ children: "View Case Studies", href: "/cases" },
],
},
{
headline: "Developer Platform",
children: "Tools and APIs for building on XRPL",
callsToAction: [
{ children: "Get Started", href: "/start" },
{ children: "View Docs", href: "/docs" },
],
},
];
const SINGLE_CTA_CARDS: readonly StandardCardPropsWithoutVariant[] = [
{
headline: "Documentation",
children: "Complete API reference and guides",
callsToAction: [{ children: "View Docs", href: "/docs" }],
},
{
headline: "Community",
children: "Join developers and builders",
callsToAction: [{ children: "Join Now", href: "/community" }],
},
{
headline: "Blog",
children: "Latest news and updates",
callsToAction: [{ children: "Read Blog", href: "/blog" }],
},
];
export default function StandardCardGroupSectionShowcase() {
return (
<div className="landing">
<PageGridRow>
<PageGridCol>
<div className="text-center mb-26">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="h2 mb-4">StandardCardGroupSection Pattern</h1>
<p className="longform">
A section pattern that displays a headline, description, and a
responsive grid of StandardCard components. All cards share a
uniform variant determined by the section, ensuring visual
consistency across the group.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Basic Usage */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Basic Usage"
description="The simplest implementation with a headline, description, variant, and array of cards."
code={`<StandardCardGroupSection
headline="Our Features"
description="Explore what we offer"
variant="neutral"
cards={[
{
headline: "Feature 1",
children: "Description of feature 1",
callsToAction: [
{ children: "Learn More", href: "/feature1" }
]
},
{
headline: "Feature 2",
children: "Description of feature 2",
callsToAction: [
{ children: "Learn More", href: "/feature2" }
]
}
]}
/>`}
>
<StandardCardGroupSection
headline="Our Features"
description="Explore what we offer"
variant="neutral"
cards={BASIC_CARDS}
/>
</CodeDemo>
</PageGridCol>
</PageGridRow>
{/* Variant: Green */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Green Variant"
description="Using the green variant for brand-focused content."
code={`<StandardCardGroupSection
headline="XRPL Solutions"
description="Powerful tools and services built on XRPL"
variant="green"
cards={[...]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="XRPL Solutions"
description="Powerful tools and services built on XRPL"
variant="green"
cards={GREEN_VARIANT_CARDS}
/>
{/* Variant: Light Gray */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Neutral Variant"
description="Using the neutral variant for subtle, neutral content."
code={`<StandardCardGroupSection
headline="Resources"
description="Everything you need to get started"
variant="neutral"
cards={[...]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="Resources"
description="Everything you need to get started"
variant="neutral"
cards={NEUTRAL_VARIANT_CARDS}
/>
{/* Variant: Yellow */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Yellow Variant"
description="Using the yellow variant for attention-grabbing, high-energy content."
code={`<StandardCardGroupSection
headline="Featured Highlights"
description="Discover our most exciting features and opportunities"
variant="yellow"
cards={[...]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="Featured Highlights"
description="Discover our most exciting features and opportunities"
variant="yellow"
cards={YELLOW_VARIANT_CARDS}
/>
{/* Variant: Blue */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Blue Variant"
description="Using the blue variant for secondary content sections."
code={`<StandardCardGroupSection
headline="Use Cases"
description="Real-world applications of XRPL technology"
variant="blue"
cards={[...]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="Use Cases"
description="Real-world applications of XRPL technology"
variant="blue"
cards={BLUE_VARIANT_CARDS}
/>
{/* With Secondary CTA */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Cards with Secondary CTA"
description="Cards can include both primary and secondary call-to-action buttons."
code={`<StandardCardGroupSection
headline="Services"
description="Comprehensive solutions for your needs"
variant="neutral"
cards={[
{
headline: "Service 1",
children: "Description",
callsToAction: [
{ children: "Get Started", href: "/start" },
{ children: "Learn More", href: "/learn" }
]
}
]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="Services"
description="Comprehensive solutions for your needs"
variant="neutral"
cards={SECONDARY_CTA_CARDS}
/>
{/* Single CTA Only */}
<PageGridRow>
<PageGridCol>
<CodeDemo
title="Single CTA Only"
description="Cards can have just a primary call-to-action button."
code={`<StandardCardGroupSection
headline="Quick Links"
description="Fast access to key resources"
variant="green"
cards={[
{
headline: "Link 1",
children: "Description",
callsToAction: [
{ children: "Visit", href: "/link1" }
]
}
]}
/>`}
/>
</PageGridCol>
</PageGridRow>
<StandardCardGroupSection
headline="Quick Links"
description="Fast access to key resources"
variant="green"
cards={SINGLE_CTA_CARDS}
/>
{/* Responsive Behavior */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Responsive Behavior</h2>
<p className="mb-6">
The StandardCardGroupSection automatically adapts its layout based
on screen size:
</p>
<ul className="mb-6">
<li>
<strong>Mobile (base):</strong> 1 column - cards stack
vertically
</li>
<li>
<strong>Tablet (md):</strong> 3 columns - cards display in a
3-column grid
</li>
<li>
<strong>Desktop (lg):</strong> 3 columns - cards display in a
3-column grid
</li>
</ul>
<p className="mb-6">
Resize your browser window to see the responsive behavior in
action.
</p>
</div>
</PageGridCol>
</PageGridRow>
{/* Code Examples */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Code Examples</h2>
<h4 className="h5 mb-4">Import</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{ fontFamily: "monospace", fontSize: "14px" }}
>
<pre style={{ margin: 0, color: "#000" }}>
{`import StandardCardGroupSection from "shared/patterns/StandardCardGroupSection/StandardCardGroupSection";`}
</pre>
</div>
<h4 className="h5 mb-4">Basic Example</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<StandardCardGroupSection
headline="Our Features"
description="Explore what we offer"
variant="neutral"
cards={[
{
headline: "Feature 1",
children: "Description of feature 1",
callsToAction: [
{ children: "Learn More", href: "/feature1" }
]
},
{
headline: "Feature 2",
children: "Description of feature 2",
callsToAction: [
{ children: "Learn More", href: "/feature2" }
]
}
]}
/>`}
</pre>
</div>
<h4 className="h5 mb-4">With Secondary CTA</h4>
<div
className="p-4 bg-light br-4 mb-6"
style={{
fontFamily: "monospace",
fontSize: "14px",
overflow: "auto",
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
}}
>
<pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
{`<StandardCardGroupSection
headline="Services"
description="Comprehensive solutions"
variant="green"
cards={[
{
headline: "Service 1",
children: "Description",
callsToAction: [
{ children: "Get Started", href: "/start" },
{ children: "Learn More", href: "/learn" }
]
}
]}
/>`}
</pre>
</div>
</div>
</PageGridCol>
</PageGridRow>
{/* Best Practices */}
<PageGridRow>
<PageGridCol>
<div className="mb-26">
<h2 className="h3 mb-6">Best Practices</h2>
<ul>
<li>
<strong>Variant Consistency:</strong> All cards in a section
share the same variant. This ensures visual consistency and
prevents individual cards from having different variants.
</li>
<li>
<strong>Card Count:</strong> Aim for multiples of 3 for best
visual balance on desktop (3, 6, 9 cards).
</li>
<li>
<strong>Headlines:</strong> Keep card headlines concise and
impactful (1-2 lines preferred).
</li>
<li>
<strong>Descriptions:</strong> Provide clear, actionable
descriptions (2-3 lines max).
</li>
<li>
<strong>CTAs:</strong> Use action-oriented button text ("Get
Started" not "Click Here").
</li>
<li>
<strong>Section Headline:</strong> Make it descriptive and
specific to help users understand the card group's purpose.
</li>
<li>
<strong>Accessibility:</strong> The component includes ARIA
roles and labels for screen readers. Ensure card headlines are
descriptive.
</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</div>
);
}

View File

@@ -1,507 +0,0 @@
import * as React from "react";
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { TileLink } from "shared/patterns/TileLinks";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'TileLink Component Showcase',
description: "A comprehensive showcase of all TileLink component variants, states, and responsive behavior in the XRPL.org Design System.",
}
};
export default function TileLinkShowcase() {
const handleClick = (message: string) => {
console.log(`TileLink clicked: ${message}`);
};
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Component Showcase</h6>
<h1 className="mb-4">TileLink Component</h1>
<p className="longform">
A clickable tile component for link grids, featuring text content with an arrow icon.
Supports gray and lilac color variants with full light/dark mode theming.
</p>
</div>
</section>
<Divider color="gray" />
{/* Gray Variant Section */}
<section className="container-new py-10">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Gray Variant</h2>
<h6 className="eyebrow mb-3">Color Variants</h6>
</div>
<p className="mb-6 text-muted">
The gray variant uses neutral gray tones. In light mode, it displays with gray-200 background.
In dark mode, it uses gray-500 with white text.
</p>
</section>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="Documentation"
href="/docs"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="Get Started"
href="/get-started"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="Tutorials"
onClick={() => handleClick('Tutorials')}
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="API Reference"
href="/api"
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider variant="gray" />
{/* Lilac Variant Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Lilac Variant</h2>
<h6 className="eyebrow mb-3">Color Variants</h6>
</div>
<p className="mb-6 text-muted">
The lilac variant uses purple/lilac tones. In light mode, it displays with lilac-300 background.
In dark mode, it uses lilac-400 with white text.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Community"
href="/community"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Events"
href="/events"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Blog"
onClick={() => handleClick('Blog')}
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Newsletter"
href="/newsletter"
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider variant="gray" />
{/* Mixed Variants Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Mixed Variants</h2>
<h6 className="eyebrow mb-3">Combinations</h6>
</div>
<p className="mb-6 text-muted">
Gray and lilac variants can be mixed in the same grid for visual variety.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="Introduction"
href="/intro"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Quick Start"
href="/quick-start"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="gray"
label="Concepts"
href="/concepts"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink
variant="lilac"
label="Advanced Topics"
href="/advanced"
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider variant="gray" />
{/* Interactive States Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Interactive States</h2>
<h6 className="eyebrow mb-3">States</h6>
</div>
<p className="mb-6 text-muted">
TileLink supports multiple interaction states: default, hover, focus, pressed, and disabled.
Hover over the tiles to see the window shade animation.
</p>
<div className="mb-8">
<h6 className="mb-4">Gray Variant States</h6>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Default / Hover / Pressed</small>
</div>
<TileLink
variant="gray"
label="Interactive Link"
href="/link"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Button with onClick</small>
</div>
<TileLink
variant="gray"
label="Click Handler"
onClick={() => handleClick('Gray button clicked')}
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Disabled State</small>
</div>
<TileLink
variant="gray"
label="Coming Soon"
disabled
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
<div className="mb-8">
<h6 className="mb-4">Lilac Variant States</h6>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Default / Hover / Pressed</small>
</div>
<TileLink
variant="lilac"
label="Interactive Link"
href="/link"
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Button with onClick</small>
</div>
<TileLink
variant="lilac"
label="Click Handler"
onClick={() => handleClick('Lilac button clicked')}
/>
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<div className="mb-2">
<small className="text-muted">Disabled State</small>
</div>
<TileLink
variant="lilac"
label="Coming Soon"
disabled
/>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</section>
<Divider variant="gray" />
{/* Responsive Behavior Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Layout</h6>
</div>
<p className="mb-6 text-muted">
TileLink adapts to different screen sizes. Resize your browser to see the responsive behavior:
</p>
<ul className="mb-6 text-muted">
<li><strong>Mobile (&lt; 576px):</strong> 1 column, 80px height, 12px padding, 16px font</li>
<li><strong>Tablet (576px - 991px):</strong> 2 columns, 88px height, 16px padding, 16px font</li>
<li><strong>Desktop ( 992px):</strong> 4 columns, 96px height, 20px padding, 18px font</li>
</ul>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Responsive Tile 1" href="#1" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Responsive Tile 2" href="#2" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Responsive Tile 3" href="#3" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Responsive Tile 4" href="#4" />
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider variant="gray" />
{/* Large Grid Example */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Large Grid Example</h2>
<h6 className="eyebrow mb-3">Real-World Usage</h6>
</div>
<p className="mb-6 text-muted">
Example of a larger grid with multiple rows, demonstrating how TileLink works in a typical section layout.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Getting Started" href="/start" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Core Concepts" href="/concepts" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Tutorials" href="/tutorials" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="API Reference" href="/api" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Examples" href="/examples" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Best Practices" href="/best-practices" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Tools" href="/tools" />
</PageGridCol>
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Resources" href="/resources" />
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider variant="gray" />
{/* Code Examples Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="mb-8">
<h6 className="mb-4">Basic Usage</h6>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { TileLink } from 'shared/patterns/TileLinks';
// Gray variant with link
<TileLink
variant="gray"
label="Documentation"
href="/docs"
/>
// Lilac variant with click handler
<TileLink
variant="lilac"
label="Get Started"
onClick={() => navigate('/start')}
/>
// Disabled state
<TileLink
variant="gray"
label="Coming Soon"
disabled
/>`}</code>
</pre>
</div>
</div>
<div className="mb-8">
<h6 className="mb-4">Grid Layout with PageGrid</h6>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { TileLink } from 'shared/patterns/TileLinks';
<PageGrid>
<PageGrid.Row>
{/* Mobile: 1 column, Tablet: 2 columns, Desktop: 4 columns */}
<PageGrid.Col span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Link 1" href="/link1" />
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Link 2" href="/link2" />
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="gray" label="Link 3" href="/link3" />
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, md: 4, lg: 3 }}>
<TileLink variant="lilac" label="Link 4" href="/link4" />
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>`}</code>
</pre>
</div>
</div>
<div className="mb-8">
<h6 className="mb-4">Props API</h6>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`interface TileLinkProps {
/** Color variant: 'gray' (default) or 'lilac' */
variant?: 'gray' | 'lilac';
/** Link text/label */
label: string;
/** Link destination - renders as <a> */
href?: string;
/** Click handler - renders as <button> */
onClick?: () => void;
/** Disabled state - prevents interaction */
disabled?: boolean;
/** Additional CSS classes */
className?: string;
}`}</code>
</pre>
</div>
</div>
</section>
<Divider variant="gray" />
{/* Features Section */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Features</h2>
<h6 className="eyebrow mb-3">Component Capabilities</h6>
</div>
<div className="row">
<div className="col-md-6 mb-6">
<h6 className="mb-3">🎨 Color Variants</h6>
<ul className="text-muted">
<li>Gray variant with neutral tones</li>
<li>Lilac variant with purple/lilac tones</li>
<li>Full light and dark mode support</li>
</ul>
</div>
<div className="col-md-6 mb-6">
<h6 className="mb-3"> Animations</h6>
<ul className="text-muted">
<li>Window shade hover effect (bottom-to-top)</li>
<li>Arrow animation on hover</li>
<li>Smooth transitions (200ms cubic-bezier)</li>
</ul>
</div>
<div className="col-md-6 mb-6">
<h6 className="mb-3">📱 Responsive Design</h6>
<ul className="text-muted">
<li>Mobile: 80px height, 12px padding</li>
<li>Tablet: 88px height, 16px padding</li>
<li>Desktop: 96px height, 20px padding</li>
</ul>
</div>
<div className="col-md-6 mb-6">
<h6 className="mb-3"> Accessibility</h6>
<ul className="text-muted">
<li>Proper ARIA labels and roles</li>
<li>Keyboard navigation support</li>
<li>Focus states with visible outlines</li>
<li>Disabled state handling</li>
</ul>
</div>
<div className="col-md-6 mb-6">
<h6 className="mb-3">🔗 Flexible Rendering</h6>
<ul className="text-muted">
<li>Renders as &lt;a&gt; tag when href is provided</li>
<li>Renders as &lt;button&gt; for onClick handlers</li>
<li>Supports disabled state for both</li>
</ul>
</div>
<div className="col-md-6 mb-6">
<h6 className="mb-3">🎯 Grid Integration</h6>
<ul className="text-muted">
<li>Designed to work with PageGrid system</li>
<li>Responsive column spans</li>
<li>Consistent 8px gap between tiles</li>
</ul>
</div>
</div>
</section>
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1,656 +0,0 @@
import * as React from 'react';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { Divider } from 'shared/components/Divider';
export const frontmatter = {
seo: {
title: 'Typography Showcase',
description: 'A comprehensive showcase of the XRPL.org typography system including displays, headings, subheads, body text, and labels.',
},
};
export default function TypographyShowcase() {
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">Typography System</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
A comprehensive overview of the XRPL typography system featuring responsive type scales,
font families (Booton and Tobias), and semantic styling for consistent visual hierarchy.
</p>
</section>
{/* Display Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Display Styles</h2>
<h6 className="eyebrow mb-3">Largest Headlines</h6>
</div>
<p className="mb-10 text-muted">
Display styles are used for the largest, most prominent headlines. They feature negative letter spacing
and are optimized for impact at large sizes.
</p>
<div className="d-flex flex-column gap-10">
<div>
<div className="mb-3">
<code className="text-muted">display-lg</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Booton Light (300)</span>
</div>
<div className="display-lg">Display Large</div>
<div className="mt-3 text-muted">
<small>Mobile: 64px · Tablet: 72px · Desktop: 92px · XL: 112px</small>
</div>
</div>
<div>
<div className="mb-3">
<code className="text-muted">display-md</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Tobias Light (300)</span>
</div>
<div className="display-md">Display Medium</div>
<div className="mt-3 text-muted">
<small>Mobile: 48px · Tablet: 60px · Desktop: 72px</small>
</div>
</div>
<div>
<div className="mb-3">
<code className="text-muted">display-sm</code>
<span className="text-muted mx-2">·</span>
<span className="text-muted">Tobias Light (300)</span>
</div>
<div className="display-sm">Display Small</div>
<div className="mt-3 text-muted">
<small>Mobile: 40px · Tablet: 56px · Desktop: 64px</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Heading Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Heading Styles</h2>
<h6 className="eyebrow mb-3">Section Headers</h6>
</div>
<p className="mb-10 text-muted">
Heading styles are used for major section headers and page titles. All headings use the Tobias monospace
font family for a distinctive technical aesthetic.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-lg / .h-lg</code>
</div>
<div className="h-lg">Heading Large</div>
<div className="mt-3 text-muted">
<small>Mobile: 36px · Tablet: 42px · Desktop: 48px</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-md / .h-md</code>
</div>
<div className="h-md">Heading Medium</div>
<div className="mt-3 text-muted">
<small>Mobile: 32px · Tablet: 36px · Desktop: 40px</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 4 }}>
<div className="mb-6">
<div className="mb-3">
<code className="text-muted">heading-sm / .h-sm</code>
</div>
<div className="h-sm">Heading Small</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Legacy H1-H6 Styles</h5>
<p className="mb-6 text-muted">
Traditional semantic HTML heading tags are also supported with responsive sizing.
</p>
<div className="d-flex flex-column gap-6">
<div>
<code className="text-muted mb-2 d-block">h1, .h1</code>
<h1 className="mb-2">The quick brown fox jumps</h1>
<small className="text-muted">Desktop: 62px / Mobile: 42px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h2, .h2</code>
<h2 className="mb-2">The quick brown fox jumps</h2>
<small className="text-muted">Desktop: 56px / Mobile: 28px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h3, .h3</code>
<h3 className="mb-2">The quick brown fox jumps</h3>
<small className="text-muted">Desktop: 48px / Mobile: 24px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h4, .h4</code>
<h4 className="mb-2">The quick brown fox jumps over the lazy dog</h4>
<small className="text-muted">Desktop: 32px / Mobile: 20px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h5, .h5</code>
<h5 className="mb-2">The quick brown fox jumps over the lazy dog</h5>
<small className="text-muted">Desktop: 24px / Mobile: 18px</small>
</div>
<div>
<code className="text-muted mb-2 d-block">h6, .h6</code>
<h6 className="mb-2">The quick brown fox jumps over the lazy dog</h6>
<small className="text-muted">Desktop: 20px / Mobile: 16px</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Subhead Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Subhead Styles</h2>
<h6 className="eyebrow mb-3">Supporting Headers</h6>
</div>
<p className="mb-10 text-muted">
Subheads provide intermediate hierarchy between headings and body text. Available in two weights:
Regular (400) for emphasis and Light (300) for subtler styling.
</p>
<div className="d-flex flex-column gap-10">
{/* Large Subheads */}
<div>
<h5 className="mb-6">Large Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-lg-r / .sh-lg-r</code>
<div className="sh-lg-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-lg-l / .sh-lg-l</code>
<div className="sh-lg-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 28px · Desktop: 32px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
{/* Medium Subheads */}
<div>
<h5 className="mb-6">Medium Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-md-r / .sh-md-r</code>
<div className="sh-md-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 26px · Desktop: 28px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-md-l / .sh-md-l</code>
<div className="sh-md-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 24px · Tablet: 26px · Desktop: 28px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
{/* Small Subheads */}
<div>
<h5 className="mb-6">Small Subheads</h5>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-sm-r / .sh-sm-r</code>
<div className="sh-sm-r">Regular Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 18px · Tablet: 18px · Desktop: 24px · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">subhead-sm-l / .sh-sm-l</code>
<div className="sh-sm-l">Light Weight Subhead</div>
<div className="mt-3 text-muted">
<small>Mobile: 18px · Tablet: 18px · Desktop: 24px · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Body Text */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Body Text</h2>
<h6 className="eyebrow mb-3">Content Typography</h6>
</div>
<p className="mb-10 text-muted">
Body text styles are used for the main content areas. Available in Regular (400) for standard text
and Light (300) for less prominent content.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">body-r / .body-r</code>
<div className="body-r">
The quick brown fox jumps over the lazy dog. This is the standard body text style
used throughout the site. It provides good readability at comfortable sizes across
all devices. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 16px (line: 23.2px) · Desktop: 18px (line: 26.1px) · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">body-l / .body-l</code>
<div className="body-l">
The quick brown fox jumps over the lazy dog. This is the light body text style
for secondary content. It maintains the same size as regular body but with lighter
weight for visual hierarchy. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 16px (line: 23.2px) · Desktop: 18px (line: 26.1px) · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Standard Paragraph</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">p / default paragraph</code>
<p>
The standard paragraph element uses a base size of 16px with a 24px line height.
This is the default for all unstyled paragraph text. The quick brown fox jumps over
the lazy dog. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</p>
<div className="text-muted">
<small>Font-size: 16px · Line-height: 24px</small>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">Longform Text</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">.longform</code>
<div className="longform">
Longform text is designed for extended reading experiences with larger sizing and
medium weight. Perfect for article introductions or key content blocks.
</div>
<div className="mt-4 text-muted">
<small>Mobile: 20px (line: 26px) · Desktop: 24px (line: 32px) · Weight: 500</small>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Labels */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Label Text</h2>
<h6 className="eyebrow mb-3">UI Elements</h6>
</div>
<p className="mb-10 text-muted">
Label styles are designed for UI elements, form labels, captions, and supplementary text.
Smaller and optimized for clarity at reduced sizes.
</p>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">label-r / .label-r</code>
<div className="label-r mb-3">
Regular label text for form inputs, captions, and UI elements.
Maintains readability at smaller sizes.
</div>
<div className="text-muted">
<small>Mobile: 14px (line: 20.1px) · Desktop: 16px (line: 23.2px) · Weight: 400</small>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">label-l / .label-l</code>
<div className="label-l mb-3">
Light label text for secondary UI text, metadata, and supplementary information.
Provides subtle hierarchy.
</div>
<div className="text-muted">
<small>Mobile: 14px (line: 20.1px) · Desktop: 16px (line: 23.2px) · Weight: 300</small>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Special Styles */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Special Styles</h2>
<h6 className="eyebrow mb-3">Distinctive Elements</h6>
</div>
<div className="d-flex flex-column gap-10">
<div>
<h5 className="mb-4">Eyebrow Text</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<code className="text-muted mb-3 d-block">.eyebrow</code>
<div className="eyebrow">Brand Design System</div>
<p className="mt-4 mb-0 text-muted">
Small uppercase labels that appear above headings to provide context or categorization.
</p>
</div>
</div>
<div>
<h5 className="mb-4">Numbers (Statistics)</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#141414', color: 'white' }}>
<code className="mb-3 d-block" style={{ color: '#70EE97' }}>.numbers</code>
<div className="numbers">1,234</div>
<p className="mt-4 mb-0" style={{ color: '#C1C1C2' }}>
Extra-large bold numbers for statistics and key metrics. Desktop: 96px / Mobile: 62px
</p>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Font Families */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Font Families</h2>
<h6 className="eyebrow mb-3">Type Stack</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Booton</h5>
<p className="mb-4 text-muted">Primary sans-serif font family</p>
<div style={{ fontFamily: 'Booton, sans-serif', fontSize: '32px', lineHeight: '1.2' }}>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />
abcdefghijklmnopqrstuvwxyz<br />
0123456789
</div>
<div className="mt-4">
<p className="mb-2"><strong>Used for:</strong></p>
<ul className="mb-0">
<li>Display Large</li>
<li>All Subheads</li>
<li>Body Text</li>
<li>Labels</li>
</ul>
</div>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Tobias</h5>
<p className="mb-4 text-muted">Monospace font family</p>
<div style={{ fontFamily: 'Tobias, monospace', fontSize: '32px', lineHeight: '1.2' }}>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />
abcdefghijklmnopqrstuvwxyz<br />
0123456789
</div>
<div className="mt-4">
<p className="mb-2"><strong>Used for:</strong></p>
<ul className="mb-0">
<li>Display Medium & Small</li>
<li>All Headings</li>
<li>Legacy H1-H6</li>
<li>Code blocks</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Typography in Context */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Typography in Context</h2>
<h6 className="eyebrow mb-3">Real-World Examples</h6>
</div>
<div className="d-flex flex-column gap-10">
{/* Article Layout */}
<div>
<h5 className="mb-6">Article Layout</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<div className="eyebrow mb-3">Documentation</div>
<h2 className="h4 mb-4">Understanding the XRP Ledger</h2>
<p className="longform mb-6">
A comprehensive introduction to the decentralized blockchain that powers fast,
low-cost global payments.
</p>
<h5 className="mb-3">What is the XRP Ledger?</h5>
<p className="mb-4">
The XRP Ledger is a decentralized public blockchain. It was built to be a better
blockchain specifically for payments, with a unique design optimized for enterprise
use cases. The XRP Ledger allows anyone to transfer money across borders instantly,
reliably, and for fractions of a penny.
</p>
<h5 className="mb-3">Key Features</h5>
<p className="mb-2">
The ledger provides a number of innovative features that make it ideal for financial
applications:
</p>
<ul>
<li>Fast transaction settlement (3-5 seconds)</li>
<li>Low transaction costs (fractions of a penny)</li>
<li>High throughput (1,500 transactions per second)</li>
</ul>
</div>
</div>
{/* Hero Section */}
<div>
<h5 className="mb-6">Hero Section</h5>
<div className="p-6-sm p-10-until-sm br-8 text-center" style={{ backgroundColor: '#141414', color: 'white' }}>
<div className="eyebrow mb-4" style={{ color: '#70EE97' }}>Blockchain for Payments</div>
<div className="display-md mb-6" style={{ color: 'white' }}>
Build the Future of Finance
</div>
<p className="longform col-lg-8 mx-auto mb-0" style={{ color: '#C1C1C2' }}>
Join a global community developing on the XRP Ledgerthe most sustainable
blockchain optimized for payments and tokenization.
</p>
</div>
</div>
{/* Card with Stats */}
<div>
<h5 className="mb-6">Statistics Card</h5>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#141414', color: 'white' }}>
<div className="text-center">
<div className="numbers" style={{ color: '#21E46B' }}>10+</div>
<div className="sh-md-r mb-3">Years of Operation</div>
<p className="label-l mb-0" style={{ color: '#C1C1C2' }}>
Reliably processing transactions since 2012
</p>
</div>
</div>
</div>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-6 text-muted">
All typography styles automatically adjust across breakpoints for optimal readability on any device.
</p>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Breakpoint System</h5>
<ul className="mb-4">
<li><strong>Mobile:</strong> Base styles (0-767px)</li>
<li><strong>Tablet:</strong> Medium breakpoint (768px-1023px)</li>
<li><strong>Desktop:</strong> Large breakpoint (1024px+)</li>
<li><strong>XL:</strong> Extra large (1200px+) only used for Display Large</li>
</ul>
<p className="mb-0 text-muted">
Resize your browser window to see typography scale responsively. Font sizes, line heights,
and letter spacing all adjust to maintain optimal readability at every viewport size.
</p>
</div>
</section>
<Divider color="gray" weight="thin" />
{/* Usage Guidelines */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Usage Guidelines</h2>
<h6 className="eyebrow mb-3">Best Practices</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Do</h5>
<ul>
<li>Use Display styles sparingly for maximum impact</li>
<li>Maintain consistent hierarchy with heading levels</li>
<li>Pair Regular and Light weights for visual contrast</li>
<li>Use Eyebrow text above headings for context</li>
<li>Apply Longform class to article introductions</li>
<li>Let typography breathe with adequate spacing</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Don't</h5>
<ul>
<li>Don't skip heading levels in the hierarchy</li>
<li>Don't use Display styles for body content</li>
<li>Don't mix too many font weights on one screen</li>
<li>Don't override letter spacing on Display text</li>
<li>Don't use Tobias for long-form reading</li>
<li>Don't ignore responsive scaling</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
</section>
<Divider color="gray" weight="thin" />
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`// Display Styles
<div className="display-lg">Largest headline</div>
<div className="display-md">Medium display</div>
<div className="display-sm">Small display</div>
// Headings
<div className="h-lg">Large heading</div>
<div className="h-md">Medium heading</div>
<div className="h-sm">Small heading</div>
// Or use semantic HTML
<h1>Heading Level 1</h1>
<h2>Heading Level 2</h2>
// Subheads
<div className="sh-lg-r">Large subhead regular</div>
<div className="sh-md-l">Medium subhead light</div>
<div className="sh-sm-r">Small subhead regular</div>
// Body Text
<div className="body-r">Regular body text</div>
<div className="body-l">Light body text</div>
<p className="longform">Extended reading text</p>
// Labels
<div className="label-r">UI label regular</div>
<div className="label-l">UI label light</div>
// Special Styles
<div className="eyebrow">Category Label</div>
<div className="numbers">1,234</div>`}</code>
</pre>
</div>
</section>
</div>
);
}

View File

@@ -26,7 +26,7 @@ const logos = {
],
developer_tooling: ["cryptum", "evernode", "threezy", "tokenize"],
interoperability: ["multichain"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet", "bifrost-wallet", "bitget-wallet"],
nfts: [
"aesthetes",
"audiotarky",
@@ -413,6 +413,24 @@ const cardsData = [
category_name: "Wallet",
link: "https://xaman.app/#team",
},
{
id: "bifrost-wallet",
title: "Bifrost Wallet",
description:
"Bifrost Wallet is a secure, independently audited self-custodial wallet for the XRP Ledger with multi-chain support. Purpose-built for XRPFi: a wallet where your XRP works for you and earns yield.",
category_id: "wallet",
category_name: "Wallet",
link: "https://bifrostwallet.com/",
},
{
id: "bitget-wallet",
title: "Bitget Wallet",
description:
"Bitget Wallet is a non-custodial wallet designed to make crypto simple and secure for everyone.",
category_id: "wallet",
category_name: "Wallet",
link: "https://web3.bitget.com/",
},
];
const featured_categories = {
@@ -459,7 +477,7 @@ const uses = [
{
id: "wallet",
title: "Wallet",
number: 5,
number: 7,
description:
"Build digital wallets to store passwords and interact with various blockchains to send and receive digital assets, including XRP."
},
@@ -850,17 +868,17 @@ export default function Uses() {
</div>
<a
className="btn d-block d-lg-none"
data-bs-toggle="modal"
data-bs-target="#categoryFilterModal"
data-toggle="modal"
data-target="#categoryFilterModal"
>
<span className="me-3">
<span className="mr-3">
<img
src={require("../static/img/uses/usecase-filter.svg")}
alt="Filter button"
/>
</span>
{translate("Filter by Categories")}
<span className="ms-3 total_count category_count">2</span>
<span className="ml-3 total_count category_count">2</span>
</a>
{/* Start company cards */}
<div className="row col-12 m-0 p-0 mt-4 pt-2">

View File

@@ -18,19 +18,15 @@ const links = [
];
const softwallets = [
{ href: "https://towolabs.com/", id: "wallet-towo", alt: "Towo" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{
href: "https://gatehub.net/",
id: "wallet-gatehub",
alt: "Gatehub",
imgclasses: "invertible-img",
},
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://bifrostwallet.com/", id: "wallet-bifrost", alt: "Bifrost Wallet" },
{ href: "https://web3.bitget.com/", id: "wallet-bitget", alt: "Bitget Wallet" },
{ href: "https://coin.space/", id: "wallet-coin", alt: "Coin Space" },
{ href: "https://crossmark.io/", id: "wallet-crossmark", alt: "Crossmark Wallet" },
{ href: "https://gatehub.net/", id: "wallet-gatehub", alt: "Gatehub", imgclasses: "invertible-img" },
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://joeywallet.xyz/", id: "wallet-joey", alt: "Joey Wallet" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" }
];
const hardwallets = [
@@ -61,16 +57,8 @@ const exchanges = [
{ href: "https://www.liquid.com/", id: "exch-liquid", alt: "Liquid" },
{ href: "https://www.lmax.com/", id: "exch-lmax", alt: "LMAX" },
{ href: "https://www.bitfinex.com/", id: "exch-bitfinex", alt: "Bitfinex" },
{
href: "https://www.etoro.com/crypto/exchange/",
id: "exch-etoro",
alt: "eToro",
},
{
href: "https://currency.com",
id: "exch-currency-com",
alt: "Currency.com",
},
{ href: "https://www.etoro.com/crypto/exchange/", id: "exch-etoro", alt: "eToro"},
{ href: "https://currency.com", id: "exch-currency-com", alt: "Currency.com"},
{ href: "https://bittrex.com/", id: "exch-bittrex", alt: "Bittrex" },
];
@@ -116,380 +104,400 @@ export default function XrpOverview() {
const totalCols = Math.max(softwallets.length, hardwallets.length) + 1;
return (
<div className="landing">
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Your Questions About XRP, Answered")}
</h1>
<h6 className="eyebrow mb-3">{translate("XRP Overview")}</h6>
</div>
<div>
<div className="position-relative">
<img
alt="blue waves"
src={require("../static/img/backgrounds/xrp-overview-blue.svg")}
className="landing-bg"
id="xrp-overview-blue"
/>
</div>
</section>
<section className="container-new my-20">
<div className="card-grid card-grid-1x2">
<div className="d-none-sm mt-lg-0">
<ul className="page-toc no-sideline p-0 sticky-top floating-nav">
{links.map((link) => (
<li
key={link.hash}
className={`nav-item ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
>
<a
className={`sidelinks nav-link ${
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Your Questions About XRP, Answered")}
</h1>
<h6 className="eyebrow mb-3">{translate("XRP Overview")}</h6>
</div>
</div>
</section>
<section className="container-new my-20">
<div className="card-grid card-grid-1x2">
<div className="d-none-sm mt-lg-0">
<ul className="page-toc no-sideline p-0 sticky-top floating-nav">
{links.map((link) => (
<li
key={link.hash}
className={`nav-item ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
href={link.hash}
>
{translate(link.text)}
<a
className={`sidelinks nav-link ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
href={link.hash}
>
{translate(link.text)}
</a>
</li>
))}
</ul>
</div>
<div className="col mt-lg-0">
<div className="link-section pb-26" id="about-xrp">
<h2 className="h4 h2-sm mb-8">{translate("What Is XRP?")}</h2>
<h5 className="longform mb-10">
{translate(
"about.xrp.what-is-xrp.ppart1",
"XRP is a digital asset thats native to the XRP Ledger—an open-source, permissionless and decentralized ",
)}
<a
href="https://www.distributedagreement.com/2018/09/24/what-is-a-blockchain/"
target="_blank"
rel="noopener noreferrer"
>
{translate("about.xrp.what-is-xrp.ppart2", "blockchain technology.")}
</a>
</li>
))}
</ul>
</div>
<div className="col mt-lg-0">
<div className="link-section pb-26" id="about-xrp">
<h2 className="h4 h2-sm mb-8">{translate("What Is XRP?")}</h2>
<h5 className="longform mb-10">
{translate(
"about.xrp.what-is-xrp.ppart1",
"XRP is a digital asset thats native to the XRP Ledger—an open-source, permissionless and decentralized ",
)}
<a
href="https://www.distributedagreement.com/2018/09/24/what-is-a-blockchain/"
target="_blank"
rel="noopener noreferrer"
>
{translate("about.xrp.what-is-xrp.ppart2", "blockchain technology.")}
</a>
{translate("about.xrp.what-is-xrp.ppart3", " ")}
</h5>
{translate("about.xrp.what-is-xrp.ppart3", " ")}
</h5>
<p className="mb-6">
{translate(
"Created in 2012 specifically for payments, XRP can settle transactions on the ledger in 3-5 seconds. It was built to be a better Bitcoin—faster, cheaper and greener than any other digital asset."
)}
</p>
<div className="overflow-x-xs">
<table className="mb-10 landing-table">
<thead>
<tr>
<th>
<h6>{translate("Benefits")}</h6>
</th>
<th>
<h6>{translate("XRP")}</h6>
</th>
<th>
<h6>{translate("Bitcoin")}</h6>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate("Fast")}</td>
<td>{translate("3-5 seconds to settle")}</td>
<td>{translate("500 seconds to settle")}</td>
</tr>
<tr>
<td>{translate("Low-Cost")}</td>
<td>{translate("$0.0002/tx")}</td>
<td>{translate("$0.50/tx")}</td>
</tr>
<tr>
<td>{translate("Scalable")}</td>
<td>{translate("1,500 tx per second")}</td>
<td>{translate("3 tx per second")}</td>
</tr>
<tr>
<td>{translate("Sustainable")}</td>
<td>
{translate(
"Environmentally sustainable (negligible energy consumption)"
)}
</td>
<td>
{translate("0.3% of global energy consumption")}
</td>
</tr>
</tbody>
</table>
</div>
<p className="mb-10">
{translate(
"XRP can be sent directly without needing a central intermediary, making it a convenient instrument in bridging two different currencies quickly and efficiently. It is freely exchanged on the open market and used in the real world for enabling cross-border payments and microtransactions."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<p className="mb-6">
{translate(
"Created in 2012 specifically for payments, XRP can settle transactions on the ledger in 3-5 seconds. It was built to be a better Bitcoin—faster, cheaper and greener than any other digital asset."
)}
</p>
<div className="overflow-x-xs">
<table className="mb-10 landing-table">
<thead>
<tr>
<th>
<h6>{translate("Benefits")}</h6>
</th>
<th>
<h6>{translate("XRP")}</h6>
</th>
<th>
<h6>{translate("Bitcoin")}</h6>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate("Fast")}</td>
<td>{translate("3-5 seconds to settle")}</td>
<td>{translate("500 seconds to settle")}</td>
</tr>
<tr>
<td>{translate("Low-Cost")}</td>
<td>{translate("$0.0002/tx")}</td>
<td>{translate("$0.50/tx")}</td>
</tr>
<tr>
<td>{translate("Scalable")}</td>
<td>{translate("1,500 tx per second")}</td>
<td>{translate("3 tx per second")}</td>
</tr>
<tr>
<td>{translate("Sustainable")}</td>
<td>
{translate(
"Environmentally sustainable (negligible energy consumption)"
)}
</td>
<td>
{translate("0.3% of global energy consumption")}
</td>
</tr>
</tbody>
</table>
</div>
<p className="mb-10">
{translate(
"XRP can be sent directly without needing a central intermediary, making it a convenient instrument in bridging two different currencies quickly and efficiently. It is freely exchanged on the open market and used in the real world for enabling cross-border payments and microtransactions."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<img
alt="briefcase"
className="mw-100 mb-2 invertible-img"
src={briefcaseIcon}
/>
<h6 className="fs-4-5">
{translate("Financial Institutions")}
</h6>
<p className="">
{translate(
"Leverage XRP as a bridge currency to facilitate faster, more affordable cross-border payments around the world."
)}
</p>
</div>
<div>
<img
alt="user"
className="mw-100 mb-2 invertible-img"
src={userIcon}
/>
<h6 className="fs-4-5">
{translate("Individual Consumers")}
</h6>
<p>
{translate(
"Use XRP to move different currencies around the world."
)}
</p>
</div>
</div>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<img
alt="briefcase"
className="mw-100 mb-2 invertible-img"
src={briefcaseIcon}
alt="magenta waves"
src={require("../static/img/backgrounds/cta-xrp-overview-magenta.svg")}
className="cta cta-bottom-right"
/>
<h6 className="subhead-sm-r">
{translate("Financial Institutions")}
</h6>
<p className="">
{translate(
"Leverage XRP as a bridge currency to facilitate faster, more affordable cross-border payments around the world."
)}
</p>
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate(
"The XRP Ledger is built for business."
)}
</h2>
<p className="mb-10">
{translate(
"The only major L-1 blockchain thats built for business and designed specifically to power finance use cases and applications at scale. Powerful enough to bootstrap a new economy, the XRP Ledger (XRPL) is fast, scalable, and sustainable."
)}
</p>
</div>
</div>
<div>
</div>
<div className="py-26 link-section" id="xrp-trading">
<h2 className="h4 h2-sm mb-8">
{translate("How Is XRP Used in Trading?")}
</h2>
<h5 className="longform mb-10">
{translate(
"XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-6">
{translate(
"about.xrp.xrp-in-trading.ppart1",
"XRPs low transaction fees, reliability and high-speed enable traders to use the digital asset as high-speed, cost-efficient and reliable collateral across trading venues—"
)}
<a
href="https://ripple.com/insights/xrp-a-preferred-base-currency-for-arbitrage-trading/"
target="_blank"
>
{translate("about.xrp.xrp-in-trading.ppart2","seizing arbitrage opportunities")}
</a>
{translate(
"about.xrp.xrp-in-trading.ppart3",
", servicing margin calls and managing general trading inventory in real time."
)}
</p>
<p>
{translate(
"Because of the properties inherent to XRP and the ecosystem around it, traders worldwide are able to shift collateral, bridge currencies and switch from one crypto into another nearly instantly, across any exchange on the planet."
)}
</p>
</div>
<div className="py-26 link-section" id="ripple">
<h2 className="h4 h2-sm mb-8">
{translate(
"What Is the Relationship Between Ripple and XRP?"
)}
</h2>
<h5 className="longform mb-10">
<a href="https://ripple.com" target="_blank">
{translate("Ripple")}
</a>
{translate(
" is a technology company that makes it easier to build a high-performance, global payments business. XRP is a digital asset independent of this."
)}
</h5>
<p>
{translate(
"There is a finite amount of XRP. All XRP is already in existence today—no more than the original 100 billion can be created. The XRPL founders gifted 80 billion XRP, the platforms native currency, to Ripple. To provide predictability to the XRP supply, Ripple has locked 55 billion XRP (55% of the total possible supply) into a series of escrows using the XRP Ledger itself. The XRPL's transaction processing rules, enforced by the consensus protocol, control the release of the XRP."
)}
</p>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<img
alt="user"
className="mw-100 mb-2 invertible-img"
src={userIcon}
alt="green waves"
src={require("../static/img/backgrounds/cta-xrp-overview-green-2.svg")}
className="landing-bg cta cta-bottom-right"
/>
<h6 className="subhead-sm-r">
{translate("Individual Consumers")}
</h6>
<p>
{translate(
"Use XRP to move different currencies around the world."
)}
</p>
</div>
</div>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate(
"The XRP Ledger is built for business."
)}
</h2>
<p className="mb-10">
{translate(
"The only major L-1 blockchain thats built for business and designed specifically to power finance use cases and applications at scale. Powerful enough to bootstrap a new economy, the XRP Ledger (XRPL) is fast, scalable, and sustainable."
)}
</p>
</div>
</div>
</div>
<div className="py-26 link-section" id="xrp-trading">
<h2 className="h4 h2-sm mb-8">
{translate("How Is XRP Used in Trading?")}
</h2>
<h5 className="longform mb-10">
{translate(
"XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-6">
{translate(
"about.xrp.xrp-in-trading.ppart1",
"XRPs low transaction fees, reliability and high-speed enable traders to use the digital asset as high-speed, cost-efficient and reliable collateral across trading venues—"
)}
<a
href="https://ripple.com/insights/xrp-a-preferred-base-currency-for-arbitrage-trading/"
target="_blank"
>
{translate("about.xrp.xrp-in-trading.ppart2","seizing arbitrage opportunities")}
</a>
{translate(
"about.xrp.xrp-in-trading.ppart3",
", servicing margin calls and managing general trading inventory in real time."
)}
</p>
<p>
{translate(
"Because of the properties inherent to XRP and the ecosystem around it, traders worldwide are able to shift collateral, bridge currencies and switch from one crypto into another nearly instantly, across any exchange on the planet."
)}
</p>
</div>
<div className="py-26 link-section" id="ripple">
<h2 className="h4 h2-sm mb-8">
{translate(
"What Is the Relationship Between Ripple and XRP?"
)}
</h2>
<h5 className="longform mb-10">
<a href="https://ripple.com" target="_blank">
{translate("Ripple")}
</a>
{translate(
" is a technology company that makes it easier to build a high-performance, global payments business. XRP is a digital asset independent of this."
)}
</h5>
<p>
{translate(
"There is a finite amount of XRP. All XRP is already in existence today—no more than the original 100 billion can be created. The XRPL founders gifted 80 billion XRP, the platforms native currency, to Ripple. To provide predictability to the XRP supply, Ripple has locked 55 billion XRP (55% of the total possible supply) into a series of escrows using the XRP Ledger itself. The XRPL's transaction processing rules, enforced by the consensus protocol, control the release of the XRP."
)}
</p>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<div className="z-index-1 position-relative">
<h3 className="h4">
{translate("about.xrp.ripple-escrow.ppart1","As of ")}
<span className="stat-highlight" id="ripple-escrow-as-of">
{translate("about.xrp.ripple-escrow.ppart2","October 2024")}
</span>
{translate("about.xrp.ripple-escrow.ppart3"," ")}
<br />
<span className="d-inline-flex">
<img
id="xrp-mark-overview"
className="mw-100 invertible-img me-2"
src={require("../static/img/logos/xrp-mark.svg")}
alt="XRP Logo Mark"
/>
<span
className="numbers stat-highlight"
id="ripple-escrow-amount"
>
{translate("38B")}
<div className="z-index-1 position-relative">
<h3 className="h4">
{translate("about.xrp.ripple-escrow.ppart1","As of ")}
<span className="stat-highlight" id="ripple-escrow-as-of">
{translate("about.xrp.ripple-escrow.ppart2","October 2024")}
</span>
</span>
<br />
{translate("XRP remains in escrow")}
</h3>
{translate("about.xrp.ripple-escrow.ppart3"," ")}
<br />
<span className="d-inline-flex">
<img
id="xrp-mark-overview"
className="mw-100 invertible-img mr-2"
src={require("../static/img/logos/xrp-mark.svg")}
alt="XRP Logo Mark"
/>
<span
className="numbers stat-highlight"
id="ripple-escrow-amount"
>
{translate("38B")}
</span>
</span>
<br />
{translate("XRP remains in escrow")}
</h3>
</div>
</div>
</div>
</div>
<div className="link-section py-26" id="wallets">
<h2 className="h4 h2-sm mb-8">
{translate("What Wallets Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Digital wallets are pieces of software that allow people to send, receive, and store cryptocurrencies, including XRP. There are two types of digital wallets: hardware and software."
)}
</h5>
<ul className={`nav nav-grid-lg cols-of-${totalCols}`} id="wallets">
<li className="nav-item nav-grid-head">
<h6 className="subhead-sm-r">{translate("Software Wallets")}</h6>
</li>
{softwallets.map((wallet) => (
<li key={wallet.id} className="nav-item">
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet?.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
<div className="link-section py-26" id="wallets">
<h2 className="h4 h2-sm mb-8">
{translate("What Wallets Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Digital wallets are pieces of software that allow people to send, receive, and store cryptocurrencies, including XRP. There are two types of digital wallets: hardware and software."
)}
</h5>
<ul className={`nav nav-grid-lg cols-of-${totalCols}`} id="wallets">
<li className="nav-item nav-grid-head">
<h6 className="fs-4-5">{translate("Software Wallets")}</h6>
</li>
))}
<li className="nav-item nav-grid-head">
<h6 className="subhead-sm-r">{translate("Hardware Wallets")}</h6>
</li>
{hardwallets.map((wallet) => (
<li className="nav-item" key={wallet.id}>
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
{softwallets.map((wallet) => (
<li key={wallet.id} className="nav-item">
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet?.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
</li>
))}
<li className="nav-item nav-grid-head">
<h6 className="fs-4-5">{translate("Hardware Wallets")}</h6>
</li>
))}
</ul>
<p className="label-l mt-10">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<div className="py-26 link-section" id="exchanges">
<h2 className="h4 h2-sm mb-8">
{translate("What Exchanges Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Exchanges are where people trade currencies. XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-10">
{translate(
"There are different types of exchanges that vary depending on the type of market (spot, futures, options, swaps), and the type of security model (custodial, non-custodial)."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<h6 className="subhead-sm-r">{translate("Spot Exchanges")}</h6>
<p className="mb-0">
{translate(
"Spot exchanges allow people to buy and sell cryptocurrencies at current (spot) market rates."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Futures, Options and Swap Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Futures, options and swap exchanges allow people to buy and sell standardized contracts of cryptocurrency market rates in the future."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Custodial exchanges manage a users private keys, and publish centralized order books of buyers and sellers."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Non-Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Non-custodial exchanges, also known as decentralized exchanges, do not manage a users private keys, and publish decentralized order books of buyers and sellers on a blockchain."
)}
</p>
</div>
{hardwallets.map((wallet) => (
<li className="nav-item" key={wallet.id}>
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
</li>
))}
</ul>
<p className="fs-3 mt-10">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<div className="py-26 link-section" id="exchanges">
<h2 className="h4 h2-sm mb-8">
{translate("What Exchanges Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Exchanges are where people trade currencies. XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-10">
{translate(
"There are different types of exchanges that vary depending on the type of market (spot, futures, options, swaps), and the type of security model (custodial, non-custodial)."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<h6 className="fs-4-5">{translate("Spot Exchanges")}</h6>
<p className="mb-0">
{translate(
"Spot exchanges allow people to buy and sell cryptocurrencies at current (spot) market rates."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Futures, Options and Swap Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Futures, options and swap exchanges allow people to buy and sell standardized contracts of cryptocurrency market rates in the future."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Custodial exchanges manage a users private keys, and publish centralized order books of buyers and sellers."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Non-Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Non-custodial exchanges, also known as decentralized exchanges, do not manage a users private keys, and publish decentralized order books of buyers and sellers on a blockchain."
)}
</p>
</div>
</div>
<h6>
{translate("Top Exchanges, according to CryptoCompare")}
</h6>
<ul
className="nav nav-grid-lg cols-of-5 mb-10"
id="top-exchanges"
>
{exchanges.map((exch, i) => (
<li className="nav-item" key={exch.id}>
<a
className="nav-link external-link"
href={exch.href}
target="_blank"
>
<span className="longform mr-3">{i+1}</span>
<img className="mw-100" id={exch.id} alt={exch.alt} />
</a>
</li>
))}
</ul>
<p className="fs-3 mt-10 mb-0">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<h6>
{translate("Top Exchanges, according to CryptoCompare")}
</h6>
<ul
className="nav nav-grid-lg cols-of-5 mb-10"
id="top-exchanges"
>
{exchanges.map((exch, i) => (
<li className="nav-item" key={exch.id}>
<a
className="nav-link external-link"
href={exch.href}
target="_blank"
>
<span className="longform me-3">{i+1}</span>
<img className="mw-100" id={exch.id} alt={exch.alt} />
</a>
</li>
))}
</ul>
<p className="label-l mt-10 mb-0">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
</div>
</div>
</section>
</section>
</div>
</div>
);
}

192
blog/2026/clio-2.7.0.md Normal file
View File

@@ -0,0 +1,192 @@
---
category: 2026
date: "2026-01-27"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing Clio version 2.7.0
description: Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
labels:
- Clio Release Notes
markdown:
editPage:
hide: true
---
# Introducing Clio version 2.7.0
Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
## Install / Upgrade
| Package |
| :------- |
| [Clio Server Linux Release (GCC)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_Linux_Release_gcc.zip) |
| [Clio Server Linux Debian Release (amd64)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_2.7.0_amd64.deb) |
| [Clio Server macOS Release (Apple Clang 17)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_macOS_Release_apple-clang.zip) |
For other platforms, please [build from source](https://github.com/XRPLF/clio/releases/tag/2.7.0). The most recent commit in the git log should be:
```text
Author: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Date: Thu Jan 15 19:03:47 2026 +0000
ci: Restart colima on macOS (#2923)
```
## What's Changed
See the [Full Changelog on GitHub](https://github.com/XRPLF/clio/compare/2.6.0...2.7.0).
### Features
- Adds `account_mptoken_issuances` API method to retrieve all `MPTokenIssuances` created by a specified account, and `account_mptokens` API method to retrieve all `MPTokens` held by a specified account. ([#2680](https://github.com/XRPLF/clio/pull/2680))
- Adds DynamicMPT support to `account_mptoken_issuances` handler. ([#2820](https://github.com/XRPLF/clio/pull/2820))
### Improvements
- Removed old ETL implementation and enabled ETLng by default. ([#2752](https://github.com/XRPLF/clio/pull/2752))
- Added async framework `submit` method for running one-shot tasks that don't require a handle to retrieve the result. ([#2751](https://github.com/XRPLF/clio/pull/2751))
- Updated the Ledger Publisher to use async framework instead of directly using `io_context`. ([#2756](https://github.com/XRPLF/clio/pull/2756))
- Added support for normal/high priority to `WorkQueue`. ([#2721](https://github.com/XRPLF/clio/pull/2721))
- Added ability to read and write `LedgerCache` to file. ([#2761](https://github.com/XRPLF/clio/pull/2761))
- Added graceful shutdown for old web server. ([#2786](https://github.com/XRPLF/clio/pull/2786))
- Prometheus requests are now handled in `WorkQueue`. ([#2790](https://github.com/XRPLF/clio/pull/2790))
- Added observable value utility to enable reactive approach across the codebase. ([#2831](https://github.com/XRPLF/clio/pull/2831))
- Added option to save cache asynchronously. ([#2883](https://github.com/XRPLF/clio/pull/2883))
- Added basic support for channels. ([#2859](https://github.com/XRPLF/clio/pull/2859))
- Added build information to `clio_server --version` command. ([#2893](https://github.com/XRPLF/clio/pull/2893))
### Bug Fixes
- Fixed an issue where `account_info` was omitting the `signer_lists` field when requested for accounts with no signer lists. ([#2746](https://github.com/XRPLF/clio/pull/2746))
- Fixed an issue where `ledger_entry` error codes didn't match with `rippled`. ([#2549](https://github.com/XRPLF/clio/pull/2549))
- Enhanced cache saving error to include more information. ([#2794](https://github.com/XRPLF/clio/pull/2794))
- Fixed `WorkQueue` contention issues. ([#2866](https://github.com/XRPLF/clio/pull/2866))
- Fixed issue where failed asserts in tests produced no output. ([#2905](https://github.com/XRPLF/clio/pull/2905))
- Added workaround for an edge case exception in `AmendmentCenter`. ([#2897](https://github.com/XRPLF/clio/pull/2897))
- Fixed `WorkQueue` performance. ([#2887](https://github.com/XRPLF/clio/pull/2887))
### Refactor
- Refactored duplicate `ledger_index` pattern in RPC handlers into a common function. ([#2755](https://github.com/XRPLF/clio/pull/2755))
- Refactored `getLedgerIndex` to return `std::expected` instead of throwing exceptions. ([#2788](https://github.com/XRPLF/clio/pull/2788))
- Added writing command to `etl::SystemState`. ([#2842](https://github.com/XRPLF/clio/pull/2842))
### Documentation
- Removed `logging.md` from README. ([#2710](https://github.com/XRPLF/clio/pull/2710))
- Fixed `graceful_period` description. ([#2791](https://github.com/XRPLF/clio/pull/2791))
### Styling
- Fixed pre-commit style issues. ([#2743](https://github.com/XRPLF/clio/pull/2743))
- Fixed comment in `pre-commit-autoupdate.yml`. ([#2750](https://github.com/XRPLF/clio/pull/2750))
- Fixed hadolint issues. ([#2777](https://github.com/XRPLF/clio/pull/2777))
- Added black pre-commit hook. ([#2811](https://github.com/XRPLF/clio/pull/2811))
- Updated pre-commit hooks. ([#2825](https://github.com/XRPLF/clio/pull/2825), [#2875](https://github.com/XRPLF/clio/pull/2875))
- Used `shfmt` for shell scripts. ([#2841](https://github.com/XRPLF/clio/pull/2841))
- Fixed clang-tidy error. ([#2901](https://github.com/XRPLF/clio/pull/2901))
### Testing
- Fixed flaky `DeadlineIsHandledCorrectly` test. ([#2716](https://github.com/XRPLF/clio/pull/2716))
- Fixed flaky test. ([#2729](https://github.com/XRPLF/clio/pull/2729))
### Miscellaneous Tasks
- Pinned all GitHub actions. ([#2712](https://github.com/XRPLF/clio/pull/2712))
- Updated CI to use intermediate environment variables for improved security. ([#2713](https://github.com/XRPLF/clio/pull/2713))
- Updated CI to save full logs for failed sanitizer tests. ([#2715](https://github.com/XRPLF/clio/pull/2715))
- Enabled clang asan builds. ([#2717](https://github.com/XRPLF/clio/pull/2717))
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0. ([#2722](https://github.com/XRPLF/clio/pull/2722))
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0 in /.github/actions/code-coverage. ([#2725](https://github.com/XRPLF/clio/pull/2725))
- [DEPENDABOT] bump actions/download-artifact from 5.0.0 to 6.0.0. ([#2723](https://github.com/XRPLF/clio/pull/2723))
- Improved pre-commit failure message. ([#2720](https://github.com/XRPLF/clio/pull/2720))
- Updated CI to use XRPLF/get-nproc Github action. ([#2727](https://github.com/XRPLF/clio/pull/2727))
- [DEPENDABOT] bump actions/checkout from 4.3.0 to 5.0.0. ([#2724](https://github.com/XRPLF/clio/pull/2724))
- Added date to nightly release version. ([#2731](https://github.com/XRPLF/clio/pull/2731))
- Fixed nightly commits link. ([#2738](https://github.com/XRPLF/clio/pull/2738))
- Updated CI to use new prepare-runner. ([#2742](https://github.com/XRPLF/clio/pull/2742))
- Updated tooling in Docker images. ([#2737](https://github.com/XRPLF/clio/pull/2737))
- Installed pre-commit in the main CI image. ([#2744](https://github.com/XRPLF/clio/pull/2744))
- Updated docker images. ([#2745](https://github.com/XRPLF/clio/pull/2745))
- Added date to nightly release title. ([#2748](https://github.com/XRPLF/clio/pull/2748))
- Updated prepare-runner to fix `ccache` on macOS. ([#2749](https://github.com/XRPLF/clio/pull/2749))
- Removed backticks from release date. ([#2754](https://github.com/XRPLF/clio/pull/2754))
- Specified apple-clang 17.0 in Conan profile. ([#2757](https://github.com/XRPLF/clio/pull/2757))
- Fixed pre-commit hook failing on empty file. ([#2766](https://github.com/XRPLF/clio/pull/2766))
- [DEPENDABOT] bump docker/setup-qemu-action from 3.6.0 to 3.7.0 in /.github/actions/build-docker-image. ([#2763](https://github.com/XRPLF/clio/pull/2763))
- [DEPENDABOT] bump docker/metadata-action from 5.8.0 to 5.9.0 in /.github/actions/build-docker-image. ([#2762](https://github.com/XRPLF/clio/pull/2762))
- Changed default `max_queue_size` to 1000. ([#2771](https://github.com/XRPLF/clio/pull/2771))
- Specified bash as default shell in Github workflows. ([#2772](https://github.com/XRPLF/clio/pull/2772))
- Updated CI to use `ucontext` in ASAN builds. ([#2775](https://github.com/XRPLF/clio/pull/2775))
- Updated `xrpl` version to 3.0.0-rc1. ([#2776](https://github.com/XRPLF/clio/pull/2776))
- Forced usage of `ucontext` with ASAN. ([#2774](https://github.com/XRPLF/clio/pull/2774))
- Removed redundant silencing of ASAN errors in CI. ([#2779](https://github.com/XRPLF/clio/pull/2779))
- Updated CI to use environment variables instead of input. ([#2781](https://github.com/XRPLF/clio/pull/2781))
- Improved cache implementation. ([#2780](https://github.com/XRPLF/clio/pull/2780))
- Updated nudb recipe to remove linker warnings. ([#2787](https://github.com/XRPLF/clio/pull/2787))
- Updated CI to use environment variables instead of input in cache-key. ([#2789](https://github.com/XRPLF/clio/pull/2789))
- Added defines for asan/tsan to Conan profile. ([#2784](https://github.com/XRPLF/clio/pull/2784))
- Enabled TSAN in CI. ([#2785](https://github.com/XRPLF/clio/pull/2785))
- Updated CI to stop downloading `ccache` on develop branch. ([#2792](https://github.com/XRPLF/clio/pull/2792))
- Updated CI to always upload cache on develop. ([#2793](https://github.com/XRPLF/clio/pull/2793))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.8 to 7.0.9. ([#2805](https://github.com/XRPLF/clio/pull/2805))
- [DEPENDABOT] bump actions/checkout from 5.0.0 to 6.0.0. ([#2806](https://github.com/XRPLF/clio/pull/2806))
- Updated `spdlog` and `fmt` libraries. ([#2804](https://github.com/XRPLF/clio/pull/2804))
- Ran clang-tidy multiple times to ensure all issues were resolved. ([#2803](https://github.com/XRPLF/clio/pull/2803))
- Fixed Repeat-based tests TSAN issues. ([#2810](https://github.com/XRPLF/clio/pull/2810))
- Fixed `WebServerAdminTestsSuit` TSAN issues. ([#2809](https://github.com/XRPLF/clio/pull/2809))
- Used `boost::asio::ssl::stream` instead of `boost::beast::ssl_stream`. ([#2814](https://github.com/XRPLF/clio/pull/2814))
- Installed latest Ninja in images. ([#2813](https://github.com/XRPLF/clio/pull/2813))
- Updated images to use latest Ninja. ([#2817](https://github.com/XRPLF/clio/pull/2817))
- Updated lockfile. ([#2818](https://github.com/XRPLF/clio/pull/2818))
- Added mathbunnyru to maintainers. ([#2823](https://github.com/XRPLF/clio/pull/2823))
- Fixed TSAN async-signal-unsafe issue. ([#2824](https://github.com/XRPLF/clio/pull/2824))
- [DEPENDABOT] bump docker/metadata-action from 5.9.0 to 5.10.0 in /.github/actions/build-docker-image. ([#2826](https://github.com/XRPLF/clio/pull/2826))
- [DEPENDABOT] bump actions/checkout from 6.0.0 to 6.0.1. ([#2837](https://github.com/XRPLF/clio/pull/2837))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.9 to 7.0.11. ([#2836](https://github.com/XRPLF/clio/pull/2836))
- [DEPENDABOT] bump ytanikin/pr-conventional-commits from 1.4.2 to 1.5.1. ([#2835](https://github.com/XRPLF/clio/pull/2835))
- Reduced delay in ETL taskman. ([#2802](https://github.com/XRPLF/clio/pull/2802))
- Added systemd file to the Debian package. ([#2844](https://github.com/XRPLF/clio/pull/2844))
- Switched to `xrpl` version 3.0.0. ([#2843](https://github.com/XRPLF/clio/pull/2843))
- Added a Debian package to the Github release. ([#2850](https://github.com/XRPLF/clio/pull/2850))
- Added a script to regenerate the Conan lockfile. ([#2849](https://github.com/XRPLF/clio/pull/2849))
- [DEPENDABOT] bump tj-actions/changed-files from 46.0.5 to 47.0.1. ([#2853](https://github.com/XRPLF/clio/pull/2853))
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.11 to 8.0.0. ([#2854](https://github.com/XRPLF/clio/pull/2854))
- [DEPENDABOT] bump actions/download-artifact from 6.0.0 to 7.0.0. ([#2855](https://github.com/XRPLF/clio/pull/2855))
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0. ([#2856](https://github.com/XRPLF/clio/pull/2856))
- [DEPENDABOT] bump codecov/codecov-action from 5.5.1 to 5.5.2. ([#2857](https://github.com/XRPLF/clio/pull/2857))
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0 in /.github/actions/code-coverage. ([#2858](https://github.com/XRPLF/clio/pull/2858))
- Updated shared Github actions. ([#2852](https://github.com/XRPLF/clio/pull/2852))
- Removed unnecessary creation of build directory in CI. ([#2867](https://github.com/XRPLF/clio/pull/2867))
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in /.github/actions/build-docker-image. ([#2872](https://github.com/XRPLF/clio/pull/2872))
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0. ([#2870](https://github.com/XRPLF/clio/pull/2870))
- [DEPENDABOT] bump actions/cache from 4.3.0 to 5.0.1. ([#2871](https://github.com/XRPLF/clio/pull/2871))
- Updated prepare-runner in Github actions and workflows. ([#2889](https://github.com/XRPLF/clio/pull/2889))
- Fixed branch name and commit SHA for GitHub PRs. ([#2888](https://github.com/XRPLF/clio/pull/2888))
- Updated CI to show `ccache` stats. ([#2902](https://github.com/XRPLF/clio/pull/2902))
- Changed build process to pass version explicitly instead of relying on tags. ([#2904](https://github.com/XRPLF/clio/pull/2904))
- Updated `gtest` and `spdlog`. ([#2908](https://github.com/XRPLF/clio/pull/2908))
- Updated tooling in Docker images. ([#2907](https://github.com/XRPLF/clio/pull/2907))
- Updated CI workflows to use new Docker images and GitHub actions. ([#2909](https://github.com/XRPLF/clio/pull/2909))
- Updated CI to use actual build date instead of date of last commit. ([#2911](https://github.com/XRPLF/clio/pull/2911))
- Updated CI to use environment variable for `BUILD_TYPE` in `reusable-build.yml`. ([#2913](https://github.com/XRPLF/clio/pull/2913))
- Changed build date format. ([#2914](https://github.com/XRPLF/clio/pull/2914))
- Updated CI to restart colima on macOS. ([#2923](https://github.com/XRPLF/clio/pull/2923))
- Reverted "refactor: Add writing command to etl::SystemState". ([#2860](https://github.com/XRPLF/clio/pull/2860))
## Contributors
The following people contributed directly to this release:
- [@godexsoft](https://github.com/godexsoft)
- [@mathbunnyru](https://github.com/mathbunnyru)
- [@kuznetsss](https://github.com/kuznetsss)
- [@yinyiqian1](https://github.com/yinyiqian1)
- [@emreariyurek](https://github.com/emreariyurek)
- [@PeterChen13579](https://github.com/PeterChen13579)
- [@bthomee](https://github.com/bthomee)
## Feedback
To report an issue or propose a new idea, please [open an issue](https://github.com/XRPLF/clio/issues).

View File

@@ -0,0 +1,76 @@
---
category: 2026
date: "2026-01-28"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing XRP Ledger version 3.1.0
description: rippled version 3.1.0 is now available. This version introduces new amendments and bug fixes.
labels:
- rippled Release Notes
markdown:
editPage:
hide: true
---
# Introducing XRP Ledger version 3.1.0
Version 3.1.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release introduces Single Asset Vaults, the Lending Protocol, and bug fixes.
## Action Required
If you run an XRP Ledger server, upgrade to version 3.1.0 as soon as possible to ensure service continuity.
## Install / Upgrade
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
| Package | SHA-256 |
|:--------|:--------|
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.0-1.el9.x86_64.rpm) | `8ac8c529718566e6ebef3cb177d170fda1efc81ee08c4ea99d7e8fa3db0a2c70` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.0-1_amd64.deb) | `58574a2299db2edf567e09efa25504677cdc66e4fa26f8a84322ab05f3a02996` |
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
commit d325f20c76fa798d0286d25e80b126ec0a2ee679
Author: Ed Hennis <ed@ripple.com>
Date: Tue Jan 27 21:13:06 2026 -0400
Set version to 3.1.0 (#6284)
```
## Full Changelog
### Amendments
- **SingleAssetVault**: Adds vaults, which pool a single asset for use with the Lending Protocol. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
- **LendingProtocol**: Adds the ability to create loans on the XRP Ledger. Loan brokers can create fixed-term, uncollateralized loans using the pooled funds from a Single Asset Vault. The protocol is highly configurable, enabling loan brokers to tune risk appetite, depostitor protections, and economic incentives. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
- **fixBatchInnerSigs**: Fixes an issue where inner transactions of a `Batch` transaction would be flagged as having valid signatures. Inner transactions never have valid signatures. ([#6069](https://github.com/XRPLF/rippled/pull/6069))
### Bug Fixes
- Expand `Number` to support full integer range. ([#6192](https://github.com/XRPLF/rippled/pull/6192))
- Fix: Reorder Batch Preflight Errors. ([#6176](https://github.com/XRPLF/rippled/pull/6176))
- Fix dependencies so clio can use libxrpl. ([#6251](https://github.com/XRPLF/rippled/pull/6251))
- Fix: Remove DEFAULT fields that change to the default in associateAsset (was Add Vault creation tests for showing valid range for AssetsMaximum). ([#6259](https://github.com/XRPLF/rippled/pull/6259))
## Credits
The following RippleX teams and GitHub users contributed to this release:
- RippleX Engineering
- RippleX Docs
- RippleX Product
- @dangell7
## Bug Bounties and Responsible Disclosures
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
To report a bug, please send a detailed report to: <bugs@xrpl.org>

View File

@@ -57,6 +57,13 @@ export default function Index() {
return (
<div className="landing dev-blog">
<div className="justify-content-center align-items-lg-center">
<div className="position-relative d-none-sm">
<img
alt="background purple waves"
src={require("../static/img/backgrounds/home-purple.svg")}
id="blog-purple"
/>
</div>
<section className="py-lg-5 text-center mt-lg-5">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column">

View File

@@ -7,6 +7,11 @@
page: index.page.tsx
expanded: true
items:
- group: '2026'
expanded: false
items:
- page: 2026/rippled-3.1.0.md
- page: 2026/clio-2.7.0.md
- group: '2025'
expanded: false
items:

2025
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
export const frontmatter = {
seo: {
title: 'Ambassadors',
description: "The XRPL Campus Ambassador program connects and empowers student champions of the XRPL.",
title: 'Ambassadors',
description: "The XRPL Campus Ambassador program connects and empowers student champions of the XRPL.",
}
};
@@ -17,403 +17,409 @@ export default function Ambassadors() {
const { translate } = useTranslate();
return (
<div className="landing page-ambassadors">
<div className="landing page-ambassadors">
<div>
<div className="position-relative d-none-sm">
<img alt="background purple waves" src={require("../static/img/backgrounds/ambassador-purple.svg")} className="position-absolute" style={{top: 0, right: 0}} />
</div>
<section className="container-new py-26 text-lg-center">
{/* For translater: This section could change dynamically based on the time of year */}
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">{translate("Become an XRP Ledger Campus Ambassador")}</h1>
<h6 className="eyebrow mb-3">{translate("Join the Student Cohort")}</h6>
{/* For translater: This section could change dynamically based on the time of year */}
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">{translate("Become an XRP Ledger Campus Ambassador")}</h1>
<h6 className="eyebrow mb-3">{translate("Join the Student Cohort")}</h6>
</div>
<p className="mt-3 pt-3 col-lg-8 mx-lg-auto p-0">{translate("This fall, the ")} <b>{translate("XRPL Student Builder Residency ")}</b> {translate("offers top technical students a 3-week online program (Oct 21 - Nov 13) to develop XRPL projects with expert mentorship. Apply by Oct 14, 2024")}</p>
<p className=" col-lg-8 mx-lg-auto p-0">{translate("This program will run from October 21 - November 13 and will be conducted entirely online. ")}</p>
<p className="pb-3 col-lg-8 mx-lg-auto p-0"><b>{translate("Applications due October 14, 2024")}</b>{translate(" @ 11:59pm PDT")}</p>
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
<p className="mt-3 pt-3 col-lg-8 mx-lg-auto p-0">{translate("This fall, the ")} <b>{translate("XRPL Student Builder Residency ")}</b> {translate("offers top technical students a 3-week online program (Oct 21 - Nov 13) to develop XRPL projects with expert mentorship. Apply by Oct 14, 2024")}</p>
<p className=" col-lg-8 mx-lg-auto p-0">{translate("This program will run from October 21 - November 13 and will be conducted entirely online. ")}</p>
<p className="pb-3 col-lg-8 mx-lg-auto p-0"><b>{translate("Applications due October 14, 2024")}</b>{translate(" @ 11:59pm PDT")}</p>
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</section>
{/* Current Students */}
<section className="container-new py-26">
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-lg-2 mx-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 pr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Campus Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Empowering Students")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("The XRPL Campus Ambassador program aims to elevate the impact of college students who are passionate about blockchain technology.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-lg-1 col-lg-6 px-0 mr-lg-4">
<div className="row m-0">
<img alt="Person speaking and person taking photo" src={require("../static/img/ambassadors/developer-hero@2x.png")} className="w-100" />
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3 p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-lg-2 mx-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 pr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Campus Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Empowering Students")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("The XRPL Campus Ambassador program aims to elevate the impact of college students who are passionate about blockchain technology.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-lg-1 col-lg-6 px-0 mr-lg-4">
<div className="row m-0">
<img alt="Person speaking and person taking photo" src={require("../static/img/ambassadors/developer-hero@2x.png")} className="w-100" />
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3 p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
{/* Benefits */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Why become an XRPL Campus Ambassador?")}</h3>
<h6 className="eyebrow mb-3">{translate("Benefits")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Join a global cohort of students empowering others to build on the XRPL.")}</p>
</div>
<div className="order-2 col-lg-6 px-0 mr-lg-5">
<div className="row align-items-center m-0" id="benefits-list">
{/* benefitslist */}
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Smiley face" id="benefits-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Exclusive Opportunities")}</h6>
<p>{translate("Get access and invitations to Ambassador-only events and opportunities")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Why become an XRPL Campus Ambassador?")}</h3>
<h6 className="eyebrow mb-3">{translate("Benefits")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Gift" id="benefits-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Swag")}</h6>
<p>{translate("New XRPL swag for Ambassadors and swag to share with other students")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Up Arrow" id="benefits-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Career Acceleration")}</h6>
<p className="pb-lg-0">{translate("Gain hands-on experience building communities and grow your professional network in the blockchain industry")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Join a global cohort of students empowering others to build on the XRPL.")}</p>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
<div className="order-2 col-lg-6 px-0 mr-lg-5">
<div className="row align-items-center m-0" id="benefits-list">
{/* benefitslist */}
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Smiley face" id="benefits-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Exclusive Opportunities")}</h6>
<p>{translate("Get access and invitations to Ambassador-only events and opportunities")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Gift" id="benefits-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Swag")}</h6>
<p>{translate("New XRPL swag for Ambassadors and swag to share with other students")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Up Arrow" id="benefits-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Career Acceleration")}</h6>
<p className="pb-lg-0">{translate("Gain hands-on experience building communities and grow your professional network in the blockchain industry")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
</div>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 mr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Should You Apply?")}</h3>
<h6 className="eyebrow mb-3">{translate("Eligibility for XRPL Campus Ambassadors")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Students currently enrolled in an undergraduate or postgraduate program at an accredited college or university are eligible to apply.")}</p>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0" id="eligibility-list">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Calendar" id="eligibility-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("A Leader")}</h6>
<p>{translate("Interested in leading meetups and workshops for your local campus community")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 mr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Should You Apply?")}</h3>
<h6 className="eyebrow mb-3">{translate("Eligibility for XRPL Campus Ambassadors")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="CPU" id="eligibility-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Curious")}</h6>
<p>{translate("Eager to learn more about technical blockchain topics and the XRPL")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p>{translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
<div className="p-lg-3 pb-3">
<img alt="People" id="eligibility-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Creative")}</h6>
<p className="pb-lg-0 mb-0">{translate("Ability to think outside the box and have an impact in the XRPL student community")}</p>
</div>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Students currently enrolled in an undergraduate or postgraduate program at an accredited college or university are eligible to apply.")}</p>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")} </p>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0" id="eligibility-list">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Calendar" id="eligibility-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("A Leader")}</h6>
<p>{translate("Interested in leading meetups and workshops for your local campus community")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="CPU" id="eligibility-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Curious")}</h6>
<p>{translate("Eager to learn more about technical blockchain topics and the XRPL")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p>{translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
<div className="p-lg-3 pb-3">
<img alt="People" id="eligibility-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Creative")}</h6>
<p className="pb-lg-0 mb-0">{translate("Ability to think outside the box and have an impact in the XRPL student community")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")} </p>
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p> {translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p> {translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
</div>
</section>
{/* Current Students */}
<section className="container-new py-26">
{/* Quotes */}
<div id="carouselSlidesOnly" className="carousel slide col-lg-10 mx-auto px-0" data-ride="carousel">
<div className="carousel-inner">
<div className="carousel-item active">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1-small.svg")} className="h-100 d-lg-none mb-4" />
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Derrick N.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
{/* Quotes */}
<div id="carouselSlidesOnly" className="carousel slide col-lg-10 mx-auto px-0" data-ride="carousel">
<div className="carousel-inner">
<div className="carousel-item active">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1-small.svg")} className="h-100 d-lg-none mb-4" />
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Derrick N.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="carousel-item mb-20">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Sally Z.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
<div className="carousel-item mb-20">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Sally Z.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="carousel-item mb-40">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Nick D.</strong><br />
Miami University<br />
Spring 2023 XRPL Campus Ambassador</p>
<div className="carousel-item mb-40">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Nick D.</strong><br />
Miami University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* How it Works */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mr-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Process to become a Campus Ambassador")}</h3>
<h6 className="eyebrow mb-3">{translate("How it Works")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Apply now to become an XRPL Campus Ambassador.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-2 col-lg-6 px-0 ml-lg-2">
<div className="row m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/01.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Apply")}</h6>
<p>{translate("Submit an application to be considered for the Campus Ambassador program.")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mr-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Process to become a Campus Ambassador")}</h3>
<h6 className="eyebrow mb-3">{translate("How it Works")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
<p className="p-lg-3 mb-2 longform">{translate("Apply now to become an XRPL Campus Ambassador.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/03.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Join")}</h6>
<p>{translate("Congrats on your new role! Join the global cohort of Ambassadors and meet with community participants during onboarding.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="p-lg-3 pb-3 d-lg-none">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Learn")}</h6>
<p> {translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block mt-5">
<div className="px-lg-3 pb-3 mt-5">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
<div className="order-2 col-lg-6 px-0 ml-lg-2">
<div className="row m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/01.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Apply")}</h6>
<p>{translate("Submit an application to be considered for the Campus Ambassador program.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/03.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Join")}</h6>
<p>{translate("Congrats on your new role! Join the global cohort of Ambassadors and meet with community participants during onboarding.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="p-lg-3 pb-3 d-lg-none">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Learn")}</h6>
<p> {translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block mt-5">
<div className="px-lg-3 pb-3 mt-5">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
</div>
</div>
<div className="p-lg-3 pb-3 ">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Learn")}</h6>
<p className="pb-lg-0">{translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="p-lg-3 pb-3 ">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Learn")}</h6>
<p className="pb-lg-0">{translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
{/* Image Block */}
<div>
<img alt="Ripple Conferences and two people Sitting" src={require("../static/img/ambassadors/students-large.png")} className="w-100" />
<img alt="Ripple Conferences and two people Sitting" src={require("../static/img/ambassadors/students-large.png")} className="w-100" />
</div>
{/* Global Community Carousel */}
<section className="container-new pt-26">
<div className="p-0 col-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Join a global cohort of Student Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Global Community")}</h6>
<div className="p-0 col-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Join a global cohort of Student Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Global Community")}</h6>
</div>
</div>
</div>
</section>
<div id="container-scroll">
<div className="photobanner">
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner photobanner-bottom">
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner">
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner photobanner-bottom">
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
</div>
{/* Connect */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Stay connected to the XRPL Community")}</h3>
<h6 className="eyebrow mb-3">{translate("Connect")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("To stay up-to-date on the latest activity, meetups, and events of the XRPL Community be sure to follow these channels:")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-2 col-lg-6 px-0 ml-lg-5">
<div className="row align-items-center m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="p-lg-3 mb-3 pb-3">
<img alt="meetup" src={require("../static/img/ambassadors/icon_meetup.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://www.meetup.com/pro/xrpl-community/">{translate("MeetUp")}</a></h6>
<p>{translate("Attend an XRPL Meetup in your local area")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Stay connected to the XRPL Community")}</h3>
<h6 className="eyebrow mb-3">{translate("Connect")}</h6>
</div>
</div>
<div className="p-lg-3 mb-3 pb-3">
<img alt="devto" src={require("../static/img/ambassadors/icon_devto.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://dev.to/t/xrpl">{translate("Dev.to Blog")}</a></h6>
<p>{translate("Read more about the activity of the XRPL Ambassadors")}</p>
<p className="p-lg-3 mb-2 longform">{translate("To stay up-to-date on the latest activity, meetups, and events of the XRPL Community be sure to follow these channels:")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</div>
<div className="col-12 col-lg-6 p-0 pl-lg-4">
<div className="p-lg-3 mb-3 pb-3 ">
<img alt="discord" src={require("../static/img/ambassadors/icon_discord.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://xrpldevs.org">{translate("Discord")}</a></h6>
<p>{translate("Join the conversation on the XRPL Developer Discord")}</p>
<div className="order-2 col-lg-6 px-0 ml-lg-5">
<div className="row align-items-center m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="p-lg-3 mb-3 pb-3">
<img alt="meetup" src={require("../static/img/ambassadors/icon_meetup.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://www.meetup.com/pro/xrpl-community/">{translate("MeetUp")}</a></h6>
<p>{translate("Attend an XRPL Meetup in your local area")}</p>
</div>
</div>
<div className="p-lg-3 mb-3 pb-3">
<img alt="devto" src={require("../static/img/ambassadors/icon_devto.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://dev.to/t/xrpl">{translate("Dev.to Blog")}</a></h6>
<p>{translate("Read more about the activity of the XRPL Ambassadors")}</p>
</div>
</div>
</div>
<div className="col-12 col-lg-6 p-0 pl-lg-4">
<div className="p-lg-3 mb-3 pb-3 ">
<img alt="discord" src={require("../static/img/ambassadors/icon_discord.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://xrpldevs.org">{translate("Discord")}</a></h6>
<p>{translate("Join the conversation on the XRPL Developer Discord")}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
</div>
</div>
</div>
)
}

View File

@@ -19,148 +19,261 @@ export default function Funding() {
return (
<div className="landing page-funding">
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-6 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("XRPL Developer Funding Programs")}
</h1>
<h6 className="eyebrow mb-3">{translate("Project Resources")}</h6>
</div>
<div>
<div className="position-relative d-none-sm">
<img
alt="purple waves"
src={require("../static/img/backgrounds/funding-purple.svg")}
className="position-absolute"
style={{ top: 0, right: 0 }}
/>
</div>
</section>
<section className="container-new py-26">
<div className="p-0 col-lg-6 mx-lg-auto" style={{ maxWidth: 520 }}>
<div className="d-flex flex-column-reverse">
<h1 className="mb-0 h4 h2-sm">
{translate(
"Explore funding opportunities for developers and teams"
)}
</h1>
<h6 className="eyebrow mb-3">{translate("Funding Overview")}</h6>
</div>
<p className="mt-3 py-3 p-0 longform">
{translate(
"If youre a software developer or team looking to build your next project or venture on the XRP Ledger (XRPL), there are a number of opportunities to fund your next innovation."
)}
</p>
</div>
</section>
{/* Hackathons */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Hackathons")}</h3>
<h6 className="eyebrow mb-3">{translate("Join an Event")}</h6>
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-6 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("XRPL Developer Funding Programs")}
</h1>
<h6 className="eyebrow mb-3">{translate("Project Resources")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">
</div>
</section>
<section className="container-new py-26">
<div className="p-0 col-lg-6 mx-lg-auto" style={{ maxWidth: 520 }}>
<div className="d-flex flex-column-reverse">
<h1 className="mb-0 h4 h2-sm">
{translate(
"Explore funding opportunities for developers and teams"
)}
</h1>
<h6 className="eyebrow mb-3">{translate("Funding Overview")}</h6>
</div>
<p className="mt-3 py-3 p-0 longform">
{translate(
"Hackathons are open to all developers to explore and invent a project on the XRP Ledger. Visit the events page for updates on upcoming hackathons."
"If youre a software developer or team looking to build your next project or venture on the XRP Ledger (XRPL), there are a number of opportunities to fund your next innovation."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers and teams building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>{translate("XRPL beginner to advanced developers")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</section>
{/* Hackathons */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Hackathons")}</h3>
<h6 className="eyebrow mb-3">{translate("Join an Event")}</h6>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<p className="p-lg-3 mb-2 longform">
{translate(
"Hackathons are open to all developers to explore and invent a project on the XRP Ledger. Visit the events page for updates on upcoming hackathons."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers and teams building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>{translate("XRPL beginner to advanced developers")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 p-lg-3">
<div className="d-flex flex-column-reverse py-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Grants")}</h3>
<h6 className="eyebrow mb-3">
{translate("Fund Your Project")}
</h6>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 p-lg-3">
<div className="d-flex flex-column-reverse py-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Grants")}</h3>
<h6 className="eyebrow mb-3">
{translate("Fund Your Project")}
</h6>
</div>
<p className="py-lg-3 mb-2 longform" style={{ maxWidth: 520 }}>
{translate(
"Developer grants for projects that contribute to the growing XRP Ledger community."
)}
</p>
<div className="mt-4 pt-3" style={{ maxWidth: 520 }}>
<span className="h6" style={{ fontSize: "1rem" }}>
{translate("Past awardees include:")}
</span>
<div className="mb-4 py-3" id="xrplGrantsDark" />
</div>
<div className="d-none d-lg-block py-lg-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://xrplgrants.org/"
>
{translate("Visit XRPL Grants")}
</a>
</div>
</div>
<p className="py-lg-3 mb-2 longform" style={{ maxWidth: 520 }}>
{translate(
"Developer grants for projects that contribute to the growing XRP Ledger community."
)}
</p>
<div className="mt-4 pt-3" style={{ maxWidth: 520 }}>
<span className="h6" style={{ fontSize: "1rem" }}>
{translate("Past awardees include:")}
</span>
<div className="mb-4 py-3" id="xrplGrantsDark" />
<div className="order-2 col-lg-6 px-0 pl-lg-3">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers, teams, and start-ups building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
{translate("XRPL intermediate to advanced developers")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="d-none d-lg-block py-lg-3">
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
@@ -170,202 +283,52 @@ export default function Funding() {
</a>
</div>
</div>
<div className="order-2 col-lg-6 px-0 pl-lg-3">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers, teams, and start-ups building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
{translate("XRPL intermediate to advanced developers")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</section>
{/* Accelerator */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Accelerator")}</h3>
<h6 className="eyebrow mb-3">
{translate("Advance your project")}
</h6>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<p className="p-lg-3 mb-2 longform">
{translate(
"12-week program for entrepreneurs building on the XRP Ledger to scale their projects into thriving businesses."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
{translate(
"Start-ups building scalable products on XRPL that can capture a large market opportunity"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://xrplgrants.org/"
>
{translate("Visit XRPL Grants")}
</a>
</div>
</div>
</section>
{/* Accelerator */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Accelerator")}</h3>
<h6 className="eyebrow mb-3">
{translate("Advance your project")}
</h6>
</div>
<p className="p-lg-3 mb-2 longform">
{translate(
"12-week program for entrepreneurs building on the XRP Ledger to scale their projects into thriving businesses."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Start-ups building scalable products on XRPL that can capture a large market opportunity"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Strong founding team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Ideally an MVP and monetization strategy")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("XRPL advanced developers")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Business acumen")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
@@ -377,38 +340,92 @@ export default function Funding() {
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Ideally an MVP and monetization strategy")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("XRPL advanced developers")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Business acumen")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"Ideally an MVP and monetization strategy"
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Strong founding team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate(
"Ideally an MVP and monetization strategy"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/funding-orange.svg")}
id="funding-orange"
/>
</div>
</section>
</div>
</div>
);
}

View File

@@ -1317,6 +1317,61 @@ const events = [
image: require("../static/img/events/hackathon-kaigi.png"),
end_date: "December 06, 2025",
},
{
name: "XRP Community Day EMEA",
description:
"Join the EMEA XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/w118fmkh?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/emea_xrplorg.png"),
end_date: "February 11, 2026",
},
{
name: "XRP Community Day Americas",
description:
"Join the Americas XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/1powvqnc?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 11, 2026",
image: require("../static/img/events/amer_xrplorg.png"),
end_date: "February 11, 2026",
},
{
name: "XRP Community Day APAC",
description:
"Join the APAC XRP community on February 12 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
type: "meetup",
link: "https://luma.com/ckzg3l3r?utm_source=xrplorg",
location: "Virtual - X Spaces",
date: "February 12, 2026",
image: require("../static/img/events/apac_xrplorg.png"),
end_date: "February 12, 2026",
},
{
name: "Building On The XRP Ledger",
description:
"This 2-day intensive hands-on training is designed for developers who are curious to learn about XRP Ledger. Meet your peers, share insights, and join a community of builders.",
type: "meetup",
link: "https://luma.com/lxb5ttsc",
location: "Paris, France",
date: "January 26 - 27, 2026",
image: require("../static/img/events/building-xrpl.png"),
end_date: "January 27, 2026",
},
{
name: "XRPL Meetup in London",
description:
"Calling all blockchain and XRP Ledger enthusiasts in London! Join XRPL Meetups to share knowledge, build real-life connections, and foster communities centered around blockchain and XRP Ledger. We're establishing local “XRPL Hubs” across Europe, and we want you to be a part of it!",
type: "meetup",
link: "https://luma.com/xshnm19t",
location: "London, UK",
date: "February 18, 2026",
image: require("../static/img/events/meetup-london.png"),
end_date: "February 18, 2026",
},
];
@@ -1375,303 +1430,311 @@ export default function Events() {
return (
<div className="landing page-events">
<section className="text-center py-26">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Find the XRPL Community Around the World")}
</h1>
<h6 className="mb-3 eyebrow">{translate("Events")}</h6>
</div>
<div>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/events-orange.svg")}
id="events-orange"
/>
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pe-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrp-community-night.png")}
className="w-100"
/>
</div>
<div className="pt-5 pe-2 col">
<section className="text-center py-26">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night NYC")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
<h1 className="mb-0">
{translate("Find the XRPL Community Around the World")}
</h1>
<h6 className="mb-3 eyebrow">{translate("Events")}</h6>
</div>
<p className="mb-4">
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pr-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrpl-hero.png")}
className="w-100"
/>
</div>
<div className="pt-5 pr-2 col">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night Denver")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
</div>
<p className="mb-4">
{translate(
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: Denver, CO")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("February 18, 2026")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://luma.com/chz145tf?utm_source=xprlorg"
>
{translate("Register Now")}
</a>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className="container-new py-26" id="upcoming-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate(
"Join the XRP community in NYC—meet builders, users, and projects innovating on the XRP Ledger."
"Check out meetups, hackathons, and other events hosted by the XRPL Community"
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: New York, NY")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("November 5, 2025")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://lu.ma/g5uja58m?utm_source=xrpleventspage"
>
{translate("Register Now")}
</a>
</h3>
<h6 className="mb-3 eyebrow">{translate("Upcoming Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-upcoming"
name="conference-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.conference}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="conference-upcoming">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-upcoming"
name="meetup-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.meetup}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="meetup-upcoming">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-upcoming"
name="hackathon-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.hackathon}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="hackathon-upcoming">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-upcoming"
name="ama-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.ama}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="ama-upcoming">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-upcoming"
name="cc-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.cc}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="cc-upcoming">
{translate("Community Calls")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-upcoming"
name="zone-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.zone}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="zone-upcoming">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-upcoming"
name="info-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters["info"]}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="info-upcoming">
{translate("Info Session")}
</label>
</div>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className="container-new py-26" id="upcoming-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate(
"Check out meetups, hackathons, and other events hosted by the XRPL Community"
)}
</h3>
<h6 className="mb-3 eyebrow">{translate("Upcoming Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-upcoming"
name="conference-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.conference}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="conference-upcoming">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-upcoming"
name="meetup-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.meetup}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="meetup-upcoming">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-upcoming"
name="hackathon-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.hackathon}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="hackathon-upcoming">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-upcoming"
name="ama-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.ama}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="ama-upcoming">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-upcoming"
name="cc-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.cc}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="cc-upcoming">
{translate("Community Calls")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-upcoming"
name="zone-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.zone}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="zone-upcoming">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-upcoming"
name="info-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters["info"]}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="info-upcoming">
{translate("Info Session")}
</label>
</div>
</div>
</div>
{/* # Available Types - conference, hackathon, ama, cc, zone, meetup, info */}
<div className="row row-cols-1 row-cols-lg-3 g-4 mt-2">
{filteredUpcoming.map((event, i) => (
<div key={event.name + i} className="col">
{/* # Available Types - conference, hackathon, ama, cc, zone, meetup, info */}
<div className="mt-2 row row-cols-1 row-cols-lg-3 card-deck">
{filteredUpcoming.map((event, i) => (
<a
className={`event-card ${event.type} h-100`}
key={event.name + i}
className={`event-card ${event.type}`}
href={event.link}
style={{}}
target="_blank"
>
<div
className="event-card-header"
style={{
background: `url(${event.image}) no-repeat`,
}}
>
<div className="event-card-title">
{translate(event.name)}
<div
className="event-card-header"
style={{
background: `url(${event.image}) no-repeat`,
}}
>
<div className="event-card-title">
{translate(event.name)}
</div>
</div>
</div>
<div className="event-card-body">
<p>{translate(event.description)}</p>
</div>
<div className="mt-lg-auto event-card-footer d-flex flex-column">
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
<div className="event-card-body">
<p>{translate(event.description)}</p>
</div>
<div className="mt-lg-auto event-card-footer d-flex flex-column">
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
))}
</div>
))}
</div>
</section>
{/* Past Events */}
<section className="container-new pt-26" id="past-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate("Explore past community-hosted events")}
</h3>
<h6 className="mb-3 eyebrow">{translate("Past Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-past"
name="conference-past"
type="checkbox"
className="events-filter"
checked={pastFilters.conference}
onChange={handlePastFilterChange}
/>
<label htmlFor="conference-past">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-past"
name="meetup-past"
type="checkbox"
className="events-filter"
checked={pastFilters.meetup}
onChange={handlePastFilterChange}
/>
<label htmlFor="meetup-past">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-past"
name="hackathon-past"
type="checkbox"
className="events-filter"
checked={pastFilters.hackathon}
onChange={handlePastFilterChange}
/>
<label htmlFor="hackathon-past">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-past"
name="ama-past"
type="checkbox"
className="events-filter"
checked={pastFilters.ama}
onChange={handlePastFilterChange}
/>
<label htmlFor="ama-past">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-past"
name="cc-past"
type="checkbox"
className="events-filter"
checked={pastFilters.cc}
onChange={handlePastFilterChange}
/>
<label htmlFor="cc-past">{translate("Community Calls")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-past"
name="zone-past"
type="checkbox"
className="events-filter"
checked={pastFilters.zone}
onChange={handlePastFilterChange}
/>
<label htmlFor="zone-past">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-past"
name="info-past"
type="checkbox"
className="events-filter"
checked={pastFilters["info"]}
onChange={handlePastFilterChange}
/>
<label htmlFor="info-past">
{translate("Info Session")}
</label>
</section>
{/* Past Events */}
<section className="container-new pt-26" id="past-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate("Explore past community-hosted events")}
</h3>
<h6 className="mb-3 eyebrow">{translate("Past Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-past"
name="conference-past"
type="checkbox"
className="events-filter"
checked={pastFilters.conference}
onChange={handlePastFilterChange}
/>
<label htmlFor="conference-past">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-past"
name="meetup-past"
type="checkbox"
className="events-filter"
checked={pastFilters.meetup}
onChange={handlePastFilterChange}
/>
<label htmlFor="meetup-past">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-past"
name="hackathon-past"
type="checkbox"
className="events-filter"
checked={pastFilters.hackathon}
onChange={handlePastFilterChange}
/>
<label htmlFor="hackathon-past">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-past"
name="ama-past"
type="checkbox"
className="events-filter"
checked={pastFilters.ama}
onChange={handlePastFilterChange}
/>
<label htmlFor="ama-past">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-past"
name="cc-past"
type="checkbox"
className="events-filter"
checked={pastFilters.cc}
onChange={handlePastFilterChange}
/>
<label htmlFor="cc-past">{translate("Community Calls")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-past"
name="zone-past"
type="checkbox"
className="events-filter"
checked={pastFilters.zone}
onChange={handlePastFilterChange}
/>
<label htmlFor="zone-past">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-past"
name="info-past"
type="checkbox"
className="events-filter"
checked={pastFilters["info"]}
onChange={handlePastFilterChange}
/>
<label htmlFor="info-past">
{translate("Info Session")}
</label>
</div>
</div>
</div>
</div>
<div className="row row-cols-1 row-cols-lg-3 g-4 mt-2 mb-0">
{filteredPast.map((event, i) => (
<div key={event.name + i} className="col">
<div className="mt-2 mb-0 row row-cols-1 row-cols-lg-3 card-deck ">
{filteredPast.map((event, i) => (
<a
className={`event-card ${event.type} h-100`}
key={event.name + i}
className="event-card {event.type}"
href={event.link}
target="_blank"
>
@@ -1692,13 +1755,13 @@ export default function Events() {
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
</div>
))}
</div>
</section>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
))}
</div>
</section>
</div>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More