mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-01-14 03:35:19 +00:00
Compare commits
139 Commits
master
...
go/feat/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a7f9479d4 | ||
|
|
c21785855f | ||
|
|
887e1f38f5 | ||
|
|
79294acb05 | ||
|
|
5cb06eaf86 | ||
|
|
fe0057aa9f | ||
|
|
1e3ca30ace | ||
|
|
66356984b4 | ||
|
|
1fcc294ffb | ||
|
|
0d0fc38344 | ||
|
|
cd1759332d | ||
|
|
62d23ce36b | ||
|
|
678e168029 | ||
|
|
31a9cac20b | ||
|
|
79f40fb2c6 | ||
|
|
9738402921 | ||
|
|
e064ce02d0 | ||
|
|
862a5c42d8 | ||
|
|
d29a5083d1 | ||
|
|
60fc8eb22e | ||
|
|
ec4ef6e9fc | ||
|
|
42552e4d24 | ||
|
|
e46e4006d5 | ||
|
|
5cf22174dc | ||
|
|
7fd39abb2b | ||
|
|
d7e042bdb6 | ||
|
|
9006dc3812 | ||
|
|
f3bef3784f | ||
|
|
b8286bf6b4 | ||
|
|
3feb69a1da | ||
|
|
e94be3ca20 | ||
|
|
4776c45c33 | ||
|
|
1278b1aca9 | ||
|
|
da529fd71e | ||
|
|
e467e27448 | ||
|
|
de84fa25a8 | ||
|
|
b4ddfa7955 | ||
|
|
ce75b4388c | ||
|
|
46add22436 | ||
|
|
93ca38ed76 | ||
|
|
e0430b9899 | ||
|
|
5df5f38e83 | ||
|
|
2b15495835 | ||
|
|
ab366c79ef | ||
|
|
1074670da7 | ||
|
|
1a2cb105f3 | ||
|
|
3badef78c1 | ||
|
|
a1f4c82e3a | ||
|
|
478f5784ee | ||
|
|
471bf7f193 | ||
|
|
f346a80ce0 | ||
|
|
e849cc95b9 | ||
|
|
0bff1aab4c | ||
|
|
e6aa704841 | ||
|
|
9415ae085a | ||
|
|
4cb232a068 | ||
|
|
35958ebede | ||
|
|
688ac5dc91 | ||
|
|
e298a45902 | ||
|
|
176e187c6a | ||
|
|
15046f431e | ||
|
|
ecd4a1bb66 | ||
|
|
3fbed79209 | ||
|
|
fc472a4f77 | ||
|
|
bebc019daa | ||
|
|
b3f235ded6 | ||
|
|
7b223aafc2 | ||
|
|
ffe0eff61a | ||
|
|
be0e324d0b | ||
|
|
fadfde1775 | ||
|
|
32899e9c41 | ||
|
|
15f48991c3 | ||
|
|
642c0dd2ce | ||
|
|
a08b24ed5d | ||
|
|
74e8be5a13 | ||
|
|
7895d6dee9 | ||
|
|
8e6d8f7c30 | ||
|
|
36785bc0f1 | ||
|
|
977c37ef83 | ||
|
|
52d895b1d0 | ||
|
|
230ddcbe21 | ||
|
|
1ee5828747 | ||
|
|
314980a667 | ||
|
|
2783d90cf6 | ||
|
|
5fbdbb8d42 | ||
|
|
b7ba976fb2 | ||
|
|
a6eb9e63e5 | ||
|
|
7d694c76a5 | ||
|
|
cbc56937e6 | ||
|
|
1a9fa9b970 | ||
|
|
cd82ea5484 | ||
|
|
57898ab010 | ||
|
|
2dbb111943 | ||
|
|
cb6323d153 | ||
|
|
08941588aa | ||
|
|
e183369ef6 | ||
|
|
702e180de6 | ||
|
|
a265f82980 | ||
|
|
021899906d | ||
|
|
f022c48f6c | ||
|
|
3e07b8400d | ||
|
|
5433894f20 | ||
|
|
a6d84de417 | ||
|
|
6f76d4ece5 | ||
|
|
17778ad84b | ||
|
|
518585227d | ||
|
|
ad0631f701 | ||
|
|
01c19628a9 | ||
|
|
44614dba9d | ||
|
|
621db81c7d | ||
|
|
c01749eba2 | ||
|
|
2ff14e4224 | ||
|
|
7685c2eb1e | ||
|
|
2de2bac211 | ||
|
|
bdc69f047a | ||
|
|
f20177b5f9 | ||
|
|
73b2127f87 | ||
|
|
32b309c878 | ||
|
|
9cf1b07954 | ||
|
|
5b73ccb8be | ||
|
|
c4188c47d6 | ||
|
|
2429574182 | ||
|
|
42ec50df27 | ||
|
|
37e96a9dae | ||
|
|
f3ae760c40 | ||
|
|
97c302822a | ||
|
|
e92929e148 | ||
|
|
9d3d11800a | ||
|
|
a956d5ae78 | ||
|
|
52e070dcf6 | ||
|
|
605eb70aed | ||
|
|
0c2a1bc249 | ||
|
|
51e763b967 | ||
|
|
86998c82d6 | ||
|
|
c2287a7fe6 | ||
|
|
f09ab44280 | ||
|
|
08807db2e9 | ||
|
|
201479ced6 | ||
|
|
ce49c8b6ba |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,8 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
*.css.map
|
||||
|
||||
# PHP
|
||||
composer.lock
|
||||
.cursor/
|
||||
@@ -1,449 +1,152 @@
|
||||
import * as React from "react";
|
||||
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 { useSearchDialog } from "@redocly/theme/core/hooks";
|
||||
import { SearchDialog } from "@redocly/theme/components/Search/SearchDialog";
|
||||
|
||||
// @ts-ignore
|
||||
// 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";
|
||||
|
||||
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"
|
||||
};
|
||||
// Re-export AlertBanner for backwards compatibility
|
||||
export { AlertBanner } from "./components/AlertBanner";
|
||||
|
||||
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");
|
||||
|
||||
React.useEffect(() => {
|
||||
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);
|
||||
};
|
||||
|
||||
// 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 () => {
|
||||
banner.removeEventListener("mouseenter", handleMouseEnter);
|
||||
};
|
||||
}, []);
|
||||
|
||||
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;
|
||||
// Props interface for Navbar (extensible for future use)
|
||||
interface NavbarProps {
|
||||
className?: string;
|
||||
}
|
||||
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 = "";
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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>
|
||||
);
|
||||
// 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');
|
||||
};
|
||||
}, [activeSubmenu, closingSubmenu]);
|
||||
|
||||
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
|
||||
return () => {
|
||||
if (submenuTimeoutRef.current) {
|
||||
clearTimeout(submenuTimeoutRef.current);
|
||||
}
|
||||
if (closingTimeoutRef.current) {
|
||||
clearTimeout(closingTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navbarClasses = [
|
||||
"bds-navbar",
|
||||
alertBanner.show ? "bds-navbar--with-banner" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<>
|
||||
<AlertBanner {...alertBanner} />
|
||||
<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>
|
||||
{/* 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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
|
||||
82
@theme/components/Navbar/components/AlertBanner.tsx
Normal file
82
@theme/components/Navbar/components/AlertBanner.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
114
@theme/components/Navbar/components/NavItems.tsx
Normal file
114
@theme/components/Navbar/components/NavItems.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
34
@theme/components/Navbar/components/NavLogo.tsx
Normal file
34
@theme/components/Navbar/components/NavLogo.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
5
@theme/components/Navbar/components/index.ts
Normal file
5
@theme/components/Navbar/components/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Re-export navbar components
|
||||
export { AlertBanner } from './AlertBanner';
|
||||
export { NavLogo } from './NavLogo';
|
||||
export { NavItems } from './NavItems';
|
||||
|
||||
85
@theme/components/Navbar/constants/icons.ts
Normal file
85
@theme/components/Navbar/constants/icons.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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
|
||||
export { default as resourcesPurplePattern } from "../../../../static/img/navbar/resources-purple.svg";
|
||||
export { default as insightsGreenPattern } from "../../../../static/img/navbar/insights-green.svg";
|
||||
export { default as darkInsightsGreenPattern } from "../../../../static/img/navbar/dark-insights-green.svg";
|
||||
export { default as darkLilacPattern } from "../../../../static/img/navbar/dark-lilac.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,
|
||||
};
|
||||
|
||||
4
@theme/components/Navbar/constants/index.ts
Normal file
4
@theme/components/Navbar/constants/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Re-export all constants
|
||||
export * from './icons';
|
||||
export * from './navigation';
|
||||
|
||||
166
@theme/components/Navbar/constants/navigation.ts
Normal file
166
@theme/components/Navbar/constants/navigation.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
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: "/docs", hasSubmenu: true },
|
||||
{ label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/about/uses", hasSubmenu: true },
|
||||
{ label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true },
|
||||
{ label: "Network", labelTranslationKey: "navbar.network", href: "/docs/concepts/networks-and-servers", hasSubmenu: true },
|
||||
];
|
||||
|
||||
// Develop submenu data structure
|
||||
export const developSubmenuData: {
|
||||
left: SubmenuItemBase[];
|
||||
right: SubmenuItemWithChildren[];
|
||||
} = {
|
||||
left: [
|
||||
{ label: "Developer's Home", href: "/docs", icon: "dev_home" },
|
||||
{ label: "Learn", href: "/docs/tutorials", icon: "learn" },
|
||||
{ label: "Code Samples", href: "/_code-samples", icon: "code_samples" },
|
||||
],
|
||||
right: [
|
||||
{
|
||||
label: "Docs",
|
||||
href: "/docs",
|
||||
icon: "docs",
|
||||
children: [
|
||||
{ label: "API Reference", href: "/docs/references" },
|
||||
{ label: "Tutorials", href: "/docs/tutorials" },
|
||||
{ label: "Concepts", href: "/docs/concepts" },
|
||||
{ label: "Infrastructure", href: "/docs/infrastructure" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Client Libraries",
|
||||
href: "/docs/references/client-libraries",
|
||||
icon: "client_lib",
|
||||
children: [
|
||||
{ label: "JavaScript", href: "/docs/references/xrpljs" },
|
||||
{ label: "Python", href: "/docs/references/xrpl-py" },
|
||||
{ label: "PHP", href: "/docs/references/xrpl-php" },
|
||||
{ label: "Go", href: "/docs/references/xrpl-go" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Use Cases submenu data structure
|
||||
export const useCasesSubmenuData: {
|
||||
left: SubmenuItemWithChildren[];
|
||||
right: SubmenuItemWithChildren[];
|
||||
} = {
|
||||
left: [
|
||||
{
|
||||
label: "Payments",
|
||||
href: "/about/uses/payments",
|
||||
icon: "payments",
|
||||
children: [
|
||||
{ label: "Direct XRP Payments", href: "/about/uses/direct-xrp-payments" },
|
||||
{ label: "Cross-currency Payments", href: "/about/uses/cross-currency-payments" },
|
||||
{ label: "Escrow", href: "/about/uses/escrow" },
|
||||
{ label: "Checks", href: "/about/uses/checks" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Tokenization",
|
||||
href: "/about/uses/tokenization",
|
||||
icon: "tokenization",
|
||||
children: [
|
||||
{ label: "Stablecoin", href: "/about/uses/stablecoin" },
|
||||
{ label: "NFT", href: "/about/uses/nft" },
|
||||
],
|
||||
},
|
||||
],
|
||||
right: [
|
||||
{
|
||||
label: "Credit",
|
||||
href: "/about/uses/credit",
|
||||
icon: "credit",
|
||||
children: [
|
||||
{ label: "Lending", href: "/about/uses/lending" },
|
||||
{ label: "Collateralization", href: "/about/uses/collateralization" },
|
||||
{ label: "Sustainability", href: "/about/uses/sustainability" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Trading",
|
||||
href: "/about/uses/trading",
|
||||
icon: "trading",
|
||||
children: [
|
||||
{ label: "DEX", href: "/about/uses/dex" },
|
||||
{ label: "Permissioned Trading", href: "/about/uses/permissioned-trading" },
|
||||
{ label: "AMM", href: "/about/uses/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: "News", href: "/blog", active: true },
|
||||
{ label: "Blog", href: "/blog" },
|
||||
{ label: "Marketplace", href: "/community/marketplace" },
|
||||
{ label: "Partner Connect", href: "/community/partner-connect" },
|
||||
],
|
||||
},
|
||||
{ label: "Funding", href: "/community/developer-funding", icon: "code_samples" },
|
||||
],
|
||||
right: [
|
||||
{
|
||||
label: "Contribute",
|
||||
href: "/resources/contribute-documentation",
|
||||
icon: "client_lib",
|
||||
children: [
|
||||
{ label: "Ecosystem Map", href: "/community/ecosystem-map" },
|
||||
{ label: "Bug Bounty", href: "/community/bug-bounty" },
|
||||
{ label: "Research", href: "/community/research" },
|
||||
],
|
||||
},
|
||||
{ label: "Creators", href: "/community/ambassadors", icon: "learn" },
|
||||
],
|
||||
};
|
||||
|
||||
// Network submenu data
|
||||
export const networkSubmenuData: NetworkSubmenuSection[] = [
|
||||
{
|
||||
label: "Resources",
|
||||
href: "/docs/concepts/networks-and-servers",
|
||||
icon: "resources",
|
||||
children: [
|
||||
{ label: "Validators", href: "/docs/concepts/networks-and-servers/validators" },
|
||||
{ label: "Governance", href: "/docs/concepts/networks-and-servers/governance", active: true },
|
||||
{ label: "XRPL Roadmap", href: "/docs/concepts/networks-and-servers/xrpl-roadmap" },
|
||||
],
|
||||
patternColor: 'lilac',
|
||||
},
|
||||
{
|
||||
label: "Insights",
|
||||
href: "/docs/concepts/networks-and-servers/insights",
|
||||
icon: "insights",
|
||||
children: [
|
||||
{ label: "Explorer", href: "https://livenet.xrpl.org" },
|
||||
{ label: "Data Dashboard", href: "/docs/concepts/networks-and-servers/data-dashboard" },
|
||||
{ label: "Amendment Voting Status", href: "/docs/concepts/networks-and-servers/amendments" },
|
||||
],
|
||||
patternColor: 'green',
|
||||
},
|
||||
];
|
||||
|
||||
19
@theme/components/Navbar/controls/HamburgerButton.tsx
Normal file
19
@theme/components/Navbar/controls/HamburgerButton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
28
@theme/components/Navbar/controls/IconButton.tsx
Normal file
28
@theme/components/Navbar/controls/IconButton.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
85
@theme/components/Navbar/controls/LanguageDropdown.tsx
Normal file
85
@theme/components/Navbar/controls/LanguageDropdown.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
53
@theme/components/Navbar/controls/LanguagePill.tsx
Normal file
53
@theme/components/Navbar/controls/LanguagePill.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
19
@theme/components/Navbar/controls/ModeToggleButton.tsx
Normal file
19
@theme/components/Navbar/controls/ModeToggleButton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
46
@theme/components/Navbar/controls/NavControls.tsx
Normal file
46
@theme/components/Navbar/controls/NavControls.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
19
@theme/components/Navbar/controls/SearchButton.tsx
Normal file
19
@theme/components/Navbar/controls/SearchButton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
11
@theme/components/Navbar/controls/index.ts
Normal file
11
@theme/components/Navbar/controls/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// 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';
|
||||
|
||||
30
@theme/components/Navbar/icons/ChevronIcon.tsx
Normal file
30
@theme/components/Navbar/icons/ChevronIcon.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
14
@theme/components/Navbar/icons/CloseIcon.tsx
Normal file
14
@theme/components/Navbar/icons/CloseIcon.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
56
@theme/components/Navbar/icons/SubmenuArrow.tsx
Normal file
56
@theme/components/Navbar/icons/SubmenuArrow.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
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} />
|
||||
);
|
||||
|
||||
6
@theme/components/Navbar/icons/index.ts
Normal file
6
@theme/components/Navbar/icons/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// 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';
|
||||
|
||||
13
@theme/components/Navbar/index.ts
Normal file
13
@theme/components/Navbar/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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';
|
||||
|
||||
215
@theme/components/Navbar/mobile-menu/MobileMenu.tsx
Normal file
215
@theme/components/Navbar/mobile-menu/MobileMenu.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
47
@theme/components/Navbar/mobile-menu/MobileMenuContent.tsx
Normal file
47
@theme/components/Navbar/mobile-menu/MobileMenuContent.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
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" />;
|
||||
|
||||
54
@theme/components/Navbar/mobile-menu/MobileMenuSection.tsx
Normal file
54
@theme/components/Navbar/mobile-menu/MobileMenuSection.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
16
@theme/components/Navbar/mobile-menu/index.ts
Normal file
16
@theme/components/Navbar/mobile-menu/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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';
|
||||
|
||||
16
@theme/components/Navbar/submenus/CommunitySubmenu.tsx
Normal file
16
@theme/components/Navbar/submenus/CommunitySubmenu.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
16
@theme/components/Navbar/submenus/DevelopSubmenu.tsx
Normal file
16
@theme/components/Navbar/submenus/DevelopSubmenu.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
16
@theme/components/Navbar/submenus/NetworkSubmenu.tsx
Normal file
16
@theme/components/Navbar/submenus/NetworkSubmenu.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
290
@theme/components/Navbar/submenus/Submenu.tsx
Normal file
290
@theme/components/Navbar/submenus/Submenu.tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import * as React from "react";
|
||||
import { useThemeHooks } from "@redocly/theme/core/hooks";
|
||||
import { SubmenuSection } from "./SubmenuSection";
|
||||
import { ArrowIcon } from "../icons";
|
||||
import { walletIcons, resourcesPurplePattern, insightsGreenPattern, darkInsightsGreenPattern, darkLilacPattern } 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 theme-aware pattern images */
|
||||
function NetworkSubmenuContent({ isActive, isClosing, onClose }: { isActive: boolean; isClosing: boolean; onClose?: () => void }) {
|
||||
const { useTranslate } = useThemeHooks();
|
||||
const { translate } = useTranslate();
|
||||
// Start with null to indicate "not yet determined" - avoids hydration mismatch
|
||||
// by ensuring server and client both render the same initial state
|
||||
const [isDarkMode, setIsDarkMode] = React.useState<boolean | null>(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]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDarkMode(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
// Default to light mode patterns until client-side detection runs
|
||||
const patternImages = React.useMemo(() => ({
|
||||
lilac: isDarkMode === true ? darkLilacPattern : resourcesPurplePattern,
|
||||
green: isDarkMode === true ? darkInsightsGreenPattern : insightsGreenPattern,
|
||||
}), [isDarkMode]);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
70
@theme/components/Navbar/submenus/SubmenuSection.tsx
Normal file
70
@theme/components/Navbar/submenus/SubmenuSection.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
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} />
|
||||
);
|
||||
|
||||
16
@theme/components/Navbar/submenus/UseCasesSubmenu.tsx
Normal file
16
@theme/components/Navbar/submenus/UseCasesSubmenu.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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} />;
|
||||
}
|
||||
|
||||
12
@theme/components/Navbar/submenus/index.ts
Normal file
12
@theme/components/Navbar/submenus/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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';
|
||||
|
||||
42
@theme/components/Navbar/types.ts
Normal file
42
@theme/components/Navbar/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function XRPLCard(props: XRPLCardProps) {
|
||||
<p className="card-text">{props.body}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="card-footer"> </div>
|
||||
{/* <div className="card-footer"> </div> */}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
178
COLOR-MIGRATION-SUMMARY.md
Normal file
178
COLOR-MIGRATION-SUMMARY.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# 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
|
||||
|
||||
154
CSS-OPTIMIZATION-SUMMARY.md
Normal file
154
CSS-OPTIMIZATION-SUMMARY.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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*
|
||||
381
CSS-OPTIMIZATION.md
Normal file
381
CSS-OPTIMIZATION.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 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*
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
|
||||
<h1 class="modal-title subhead-sm-r" 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">
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
|
||||
<h1 class="modal-title subhead-sm-r" 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">
|
||||
|
||||
488
about/button-showcase-primary.page.tsx
Normal file
488
about/button-showcase-primary.page.tsx
Normal file
@@ -0,0 +1,488 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
528
about/button-showcase-secondary.page.tsx
Normal file
528
about/button-showcase-secondary.page.tsx
Normal file
@@ -0,0 +1,528 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
561
about/button-showcase-tertiary.page.tsx
Normal file
561
about/button-showcase-tertiary.page.tsx
Normal file
@@ -0,0 +1,561 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
622
about/callout-media-banner-showcase.page.tsx
Normal file
622
about/callout-media-banner-showcase.page.tsx
Normal file
@@ -0,0 +1,622 @@
|
||||
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."
|
||||
primaryButton={{ label: "Get Started", onClick: () => handleClick('responsive-demo-primary') }}
|
||||
tertiaryButton={{ 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 (768px–1023px)</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 (<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."
|
||||
primaryButton={{ 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."
|
||||
primaryButton={{ label: "View Docs", href: "#docs" }}
|
||||
tertiaryButton={{ 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."
|
||||
primaryButton={{ 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."
|
||||
primaryButton={{ label: "Get Started", href: "#get-started" }}
|
||||
tertiaryButton={{ 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."
|
||||
primaryButton={{ label: "Join Discord", href: "#discord" }}
|
||||
tertiaryButton={{ 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."
|
||||
primaryButton={{ 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."
|
||||
primaryButton={{ label: "Start Building", onClick: () => handleClick('image-black-primary') }}
|
||||
tertiaryButton={{ 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."
|
||||
primaryButton={{ label: "Get Started", href: "#start" }}
|
||||
tertiaryButton={{ 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."
|
||||
primaryButton={{ 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."
|
||||
primaryButton={{ label: "Take Action", href: "#action" }}
|
||||
tertiaryButton={{ 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>
|
||||
|
||||
{/* primaryButton */}
|
||||
<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>primaryButton</code></div>
|
||||
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
|
||||
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
|
||||
<div style={{ flex: '1 1 0', minWidth: 0 }}>Primary button configuration</div>
|
||||
</div>
|
||||
|
||||
{/* tertiaryButton */}
|
||||
<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>tertiaryButton</code></div>
|
||||
<div style={{ flex: '1 1 0', minWidth: 0 }}><code>{`{ label, href?, onClick? }`}</code></div>
|
||||
<div style={{ width: '120px', flexShrink: 0 }}><code>undefined</code></div>
|
||||
<div style={{ flex: '1 1 0', minWidth: 0 }}>Tertiary button configuration</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>
|
||||
);
|
||||
}
|
||||
691
about/card-icon-showcase.page.tsx
Normal file
691
about/card-icon-showcase.page.tsx
Normal file
@@ -0,0 +1,691 @@
|
||||
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 (576px–991px)</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 (<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>() => 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>
|
||||
);
|
||||
}
|
||||
782
about/card-image-showcase.page.tsx
Normal file
782
about/card-image-showcase.page.tsx
Normal file
@@ -0,0 +1,782 @@
|
||||
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 }}><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>() => 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>
|
||||
);
|
||||
}
|
||||
630
about/card-offgrid-showcase.page.tsx
Normal file
630
about/card-offgrid-showcase.page.tsx
Normal file
@@ -0,0 +1,630 @@
|
||||
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>() => 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>
|
||||
);
|
||||
}
|
||||
537
about/cardstat-showcase.page.tsx
Normal file
537
about/cardstat-showcase.page.tsx
Normal file
@@ -0,0 +1,537 @@
|
||||
import * as React from 'react';
|
||||
import { CardStat } from 'shared/components/CardStat';
|
||||
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
|
||||
|
||||
export const frontmatter = {
|
||||
seo: {
|
||||
title: 'CardStat Component Showcase',
|
||||
description: 'Interactive showcase of the Brand Design System CardStat component with all variants and configurations.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function CardStatShowcase() {
|
||||
const [clickCount, setClickCount] = React.useState<Record<string, number>>({});
|
||||
|
||||
const handleClick = (id: string) => {
|
||||
setClickCount((prev) => ({ ...prev, [id]: (prev[id] || 0) + 1 }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="landing">
|
||||
{/* Hero Section */}
|
||||
<PageGrid className="py-26">
|
||||
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
|
||||
<h1 className="mb-0">CardStat Component</h1>
|
||||
<h6 className="eyebrow mb-3">Brand Design System</h6>
|
||||
</div>
|
||||
<p className="col-lg-8 mx-auto mt-10">
|
||||
A statistics card component following the XRPL Brand Design System. This showcase demonstrates
|
||||
all color variants, button configurations, and responsive behavior using PageGrid.
|
||||
</p>
|
||||
</PageGrid>
|
||||
|
||||
{/* Basic Usage */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-100">
|
||||
<h2 className="h4 mb-8">Basic Usage</h2>
|
||||
<h6 className="eyebrow mb-3">Simple Statistics</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
CardStat components display prominent statistics with descriptive labels. They adapt responsively
|
||||
and can be used without buttons for purely informational displays.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="2"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="*"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Color Variants */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-100">
|
||||
<h2 className="h4 mb-8">Color Variants</h2>
|
||||
<h6 className="eyebrow mb-3">Visual Themes</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Four color variants are available to match different types of statistics and visual contexts.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="6M"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
<p className="mt-4 text-muted"><strong>Lilac</strong> - User metrics, community stats</p>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="$1T"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
<p className="mt-4 text-muted"><strong>Green</strong> - Financial metrics, growth</p>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Uptime years"
|
||||
variant="light-gray"
|
||||
/>
|
||||
<p className="mt-4 text-muted"><strong>Light Gray</strong> - Technical stats, reliability</p>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="70+"
|
||||
label="Partners"
|
||||
variant="dark-gray"
|
||||
/>
|
||||
<p className="mt-4 text-muted"><strong>Dark Gray</strong> - Neutral metrics, secondary info</p>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* With Single Button */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-full">
|
||||
<h2 className="h4 mb-8">With Primary Button</h2>
|
||||
<h6 className="eyebrow mb-3">Single CTA</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Add a primary button for a main call-to-action. Buttons use the black variant for proper
|
||||
contrast on colored backgrounds.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 6 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Explore",
|
||||
onClick: () => handleClick('explore-1')
|
||||
}}
|
||||
/>
|
||||
{clickCount['explore-1'] > 0 && (
|
||||
<p className="mt-4 text-muted">Clicked {clickCount['explore-1']} time{clickCount['explore-1'] !== 1 ? 's' : ''}</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 6 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: () => handleClick('learn-1')
|
||||
}}
|
||||
/>
|
||||
{clickCount['learn-1'] > 0 && (
|
||||
<p className="mt-4 text-muted">Clicked {clickCount['learn-1']} time{clickCount['learn-1'] !== 1 ? 's' : ''}</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 12 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
primaryButton={{
|
||||
label: "View Details",
|
||||
onClick: () => handleClick('view-1')
|
||||
}}
|
||||
/>
|
||||
{clickCount['view-1'] > 0 && (
|
||||
<p className="mt-4 text-muted">Clicked {clickCount['view-1']} time{clickCount['view-1'] !== 1 ? 's' : ''}</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* With Two Buttons */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-full">
|
||||
<h2 className="h4 mb-8">With Two Buttons</h2>
|
||||
<h6 className="eyebrow mb-3">Multiple CTAs</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Include both primary and secondary buttons for multiple action options. Buttons wrap responsively
|
||||
and maintain consistent spacing.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: () => handleClick('primary-1')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Get Started",
|
||||
onClick: () => handleClick('secondary-1')
|
||||
}}
|
||||
/>
|
||||
{(clickCount['primary-1'] > 0 || clickCount['secondary-1'] > 0) && (
|
||||
<p className="mt-4 text-muted">
|
||||
Primary: {clickCount['primary-1'] || 0}, Secondary: {clickCount['secondary-1'] || 0}
|
||||
</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
primaryButton={{
|
||||
label: "Explore",
|
||||
onClick: () => handleClick('primary-2')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "View Stats",
|
||||
onClick: () => handleClick('secondary-2')
|
||||
}}
|
||||
/>
|
||||
{(clickCount['primary-2'] > 0 || clickCount['secondary-2'] > 0) && (
|
||||
<p className="mt-4 text-muted">
|
||||
Primary: {clickCount['primary-2'] || 0}, Secondary: {clickCount['secondary-2'] || 0}
|
||||
</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
primaryButton={{
|
||||
label: "Read More",
|
||||
onClick: () => handleClick('primary-3')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Try It",
|
||||
onClick: () => handleClick('secondary-3')
|
||||
}}
|
||||
/>
|
||||
{(clickCount['primary-3'] > 0 || clickCount['secondary-3'] > 0) && (
|
||||
<p className="mt-4 text-muted">
|
||||
Primary: {clickCount['primary-3'] || 0}, Secondary: {clickCount['secondary-3'] || 0}
|
||||
</p>
|
||||
)}
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Responsive Behavior */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-full">
|
||||
<h2 className="h4 mb-8">Responsive Layout</h2>
|
||||
<h6 className="eyebrow mb-3">Adaptive Grid</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Cards adapt to different screen sizes. On mobile (base), cards stack vertically. On tablet (md),
|
||||
they can be arranged in 2 columns. On desktop (lg+), up to 3-4 columns are supported.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="1M"
|
||||
superscript="+"
|
||||
label="Transactions daily"
|
||||
variant="lilac"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="150"
|
||||
superscript="+"
|
||||
label="Countries"
|
||||
variant="green"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="99.9"
|
||||
superscript="%"
|
||||
label="Uptime"
|
||||
variant="light-gray"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 3 }}>
|
||||
<CardStat
|
||||
statistic="24/7"
|
||||
label="Support"
|
||||
variant="dark-gray"
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Mixed Configurations */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-100">
|
||||
<h2 className="h4 mb-8">Mixed Configurations</h2>
|
||||
<h6 className="eyebrow mb-3">Flexible Usage</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Mix and match cards with different button configurations in the same layout.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: () => handleClick('mixed-1')
|
||||
}}
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
primaryButton={{
|
||||
label: "Explore",
|
||||
onClick: () => handleClick('mixed-2')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Get Started",
|
||||
onClick: () => handleClick('mixed-3')
|
||||
}}
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Wide Layout */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-100">
|
||||
<h2 className="h4 mb-8">Wide Card Layout</h2>
|
||||
<h6 className="eyebrow mb-3">Larger Spans</h6>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
Cards can span multiple columns for wider layouts on larger screens.
|
||||
</p>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 8, lg: 6 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets using XRPL"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Explore Wallets",
|
||||
onClick: () => handleClick('wide-1')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Get Started",
|
||||
onClick: () => handleClick('wide-2')
|
||||
}}
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 8, lg: 6 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Total value moved on the network"
|
||||
variant="green"
|
||||
primaryButton={{
|
||||
label: "View Statistics",
|
||||
onClick: () => handleClick('wide-3')
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: () => handleClick('wide-4')
|
||||
}}
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Usage Guidelines */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="d-flex flex-column-reverse w-100">
|
||||
<h2 className="h4 mb-8">Usage Guidelines</h2>
|
||||
<h6 className="eyebrow mb-3">Best Practices</h6>
|
||||
</div>
|
||||
<div className="col-lg-8 mx-auto w-100">
|
||||
<h5 className="mb-4">When to Use</h5>
|
||||
<ul className="mb-8">
|
||||
<li><strong>Key metrics</strong> - Highlight important numbers prominently</li>
|
||||
<li><strong>Dashboard sections</strong> - Create stat-focused areas on landing pages</li>
|
||||
<li><strong>About pages</strong> - Showcase company or product statistics</li>
|
||||
<li><strong>Feature sections</strong> - Emphasize quantitative benefits</li>
|
||||
</ul>
|
||||
|
||||
<h5 className="mb-4">Color Variant Selection</h5>
|
||||
<ul className="mb-8">
|
||||
<li><strong>Lilac</strong> - User-focused statistics, community metrics</li>
|
||||
<li><strong>Green</strong> - Financial metrics, growth indicators</li>
|
||||
<li><strong>Light Gray</strong> - Technical statistics, reliability metrics</li>
|
||||
<li><strong>Dark Gray</strong> - Neutral or secondary information</li>
|
||||
</ul>
|
||||
|
||||
<h5 className="mb-4">Button Configuration</h5>
|
||||
<ul className="mb-8">
|
||||
<li><strong>No buttons</strong> - For purely informational displays</li>
|
||||
<li><strong>Single button</strong> - For one clear call-to-action</li>
|
||||
<li><strong>Two buttons</strong> - For multiple action options</li>
|
||||
</ul>
|
||||
|
||||
<h5 className="mb-4">Tips</h5>
|
||||
<ul>
|
||||
<li>Keep statistics concise using abbreviations (M, K, T, +)</li>
|
||||
<li>Use descriptive labels that clearly explain the metric</li>
|
||||
<li>Choose colors that match the type of statistic</li>
|
||||
<li>Test on all breakpoints to ensure proper responsive behavior</li>
|
||||
<li>Limit buttons to essential actions</li>
|
||||
</ul>
|
||||
</div>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
{/* Implementation Examples */}
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<div className="col-lg-10 mx-auto d-flex flex-column-reverse">
|
||||
<h2 className="h4 mb-8">Code Examples</h2>
|
||||
<h6 className="eyebrow mb-3">Implementation</h6>
|
||||
</div>
|
||||
<div className="col-lg-10 mx-auto">
|
||||
<h5 className="mb-4">Basic Card</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' }}>{`<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>`}</pre>
|
||||
</div>
|
||||
|
||||
<h5 className="mb-4">With Primary Button</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' }}>{`<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
href: "/about"
|
||||
}}
|
||||
/>`}</pre>
|
||||
</div>
|
||||
|
||||
<h5 className="mb-4">With Two Buttons</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' }}>{`<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: handleLearnMore
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Get Started",
|
||||
href: "/start"
|
||||
}}
|
||||
/>`}</pre>
|
||||
</div>
|
||||
|
||||
<h5 className="mb-4">In PageGrid Layout</h5>
|
||||
<div className="p-4 br-4" style={{ backgroundColor: '#f5f5f7', fontFamily: 'monospace', fontSize: '14px' }}>
|
||||
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#000' }}>{`<PageGrid>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="6 Million"
|
||||
superscript="+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion"
|
||||
superscript="+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="12"
|
||||
superscript="+"
|
||||
label="Uptime years"
|
||||
variant="light-gray"
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</ PageGridRow>
|
||||
</ PageGrid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
465
about/divider-showcase.page.tsx
Normal file
465
about/divider-showcase.page.tsx
Normal file
@@ -0,0 +1,465 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
409
about/grid-showcase.page.tsx
Normal file
409
about/grid-showcase.page.tsx
Normal file
@@ -0,0 +1,409 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
692
about/header-hero-split-media-showcase.page.tsx
Normal file
692
about/header-hero-split-media-showcase.page.tsx
Normal file
@@ -0,0 +1,692 @@
|
||||
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 (576px–991px)</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 (<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>
|
||||
);
|
||||
}
|
||||
@@ -24,14 +24,6 @@ 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">
|
||||
@@ -61,13 +53,6 @@ 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">
|
||||
|
||||
@@ -32,14 +32,6 @@ 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">
|
||||
@@ -52,13 +44,6 @@ 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">
|
||||
@@ -133,16 +118,6 @@ 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">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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: {
|
||||
@@ -78,14 +79,6 @@ 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">
|
||||
@@ -100,13 +93,6 @@ 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">
|
||||
@@ -133,7 +119,7 @@ export default function XrplOverview() {
|
||||
{translate("Read Technical Docs")}
|
||||
</Link>{" "}
|
||||
<a
|
||||
className="ml-4 video-external-link"
|
||||
className="ms-4 video-external-link"
|
||||
target="_blank"
|
||||
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
|
||||
>
|
||||
@@ -168,7 +154,7 @@ export default function XrplOverview() {
|
||||
{translate("Read Technical Docs")}
|
||||
</Link>{" "}
|
||||
<a
|
||||
className="ml-4 video-external-link"
|
||||
className="ms-4 video-external-link"
|
||||
target="_blank"
|
||||
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
|
||||
>
|
||||
@@ -178,9 +164,9 @@ export default function XrplOverview() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
<div className="card-grid card-grid-2xN">
|
||||
<div className="col">
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<PageGrid.Col span={{ base: 4, lg: 6 }}>
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h2 className="h4 h2-sm mb-8">
|
||||
{translate("How the Consensus Protocol works")}
|
||||
@@ -207,25 +193,23 @@ 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>
|
||||
</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"
|
||||
/>
|
||||
</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 className="z-index-1 position-relative">
|
||||
<h2 className="h4 mb-10-until-sm mb-8-sm">
|
||||
{translate("A Sustainable Blockchain")}
|
||||
@@ -239,11 +223,13 @@ export default function XrplOverview() {
|
||||
{translate("Learn More")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
<div className="card-grid card-grid-2xN">
|
||||
<div className="col">
|
||||
</PageGrid.Col>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
|
||||
<PageGrid className="py-26">
|
||||
<PageGridRow>
|
||||
<PageGrid.Col span={{ base: 4, lg: 6 }}>
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h4 className="h4 h2-sm mb-8">
|
||||
{translate("Building with confidence on ")}
|
||||
@@ -265,8 +251,8 @@ export default function XrplOverview() {
|
||||
<a className="btn btn-primary btn-arrow mb-10-sm" href="/about/uses">
|
||||
{translate("Explore More")}
|
||||
</a>
|
||||
</div>
|
||||
<div className="col mb-0">
|
||||
</PageGrid.Col>
|
||||
<PageGrid.Col span={{ base: 4, lg: 6 }}>
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h4 className="h4 h2-sm mb-8">
|
||||
{translate("Creating new value for long-term growth")}
|
||||
@@ -283,11 +269,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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</PageGrid.Col>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
<section className="container-new py-26">
|
||||
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 pl-0 ">
|
||||
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 ps-0 ">
|
||||
<h2 className="h4 h2-sm">
|
||||
{translate(
|
||||
"Watch the explainer video series to learn more about the XRP Ledger"
|
||||
@@ -375,11 +361,6 @@ 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("Tomorrow’s Blockchain Starts With You")}
|
||||
@@ -407,7 +388,7 @@ export default function XrplOverview() {
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
<div
|
||||
className="col-md-6 offset-md-3 w-100 pl-0 pr-0 mini-faq"
|
||||
className="col-md-10 offset-md-1 col-lg-8 offset-lg-2 ps-0 pe-0 mini-faq"
|
||||
id="minifaq-accordion"
|
||||
>
|
||||
{faqs.map((faq, index) => (
|
||||
@@ -415,8 +396,8 @@ export default function XrplOverview() {
|
||||
<a
|
||||
href={`#heading${index + 1}`}
|
||||
className="expander collapsed"
|
||||
data-toggle="collapse"
|
||||
data-target={`#answer${index + 1}`}
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={`#answer${index + 1}`}
|
||||
aria-expanded="false"
|
||||
aria-controls={`answer${index + 1}`}
|
||||
>
|
||||
|
||||
310
about/link-showcase.page.tsx
Normal file
310
about/link-showcase.page.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
820
about/small-tiles-section-showcase.page.tsx
Normal file
820
about/small-tiles-section-showcase.page.tsx
Normal file
@@ -0,0 +1,820 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
PageGrid,
|
||||
PageGridRow,
|
||||
PageGridCol,
|
||||
} from "shared/components/PageGrid/page-grid";
|
||||
import { SmallTilesSection } from "shared/components/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 (<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 (576px–991px)</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<"section"></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> (() => 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><li></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>
|
||||
);
|
||||
}
|
||||
755
about/tile-logo-showcase.page.tsx
Normal file
755
about/tile-logo-showcase.page.tsx
Normal file
File diff suppressed because one or more lines are too long
656
about/typography-showcase.page.tsx
Normal file
656
about/typography-showcase.page.tsx
Normal file
@@ -0,0 +1,656 @@
|
||||
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 Ledger—the 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -850,17 +850,17 @@ export default function Uses() {
|
||||
</div>
|
||||
<a
|
||||
className="btn d-block d-lg-none"
|
||||
data-toggle="modal"
|
||||
data-target="#categoryFilterModal"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#categoryFilterModal"
|
||||
>
|
||||
<span className="mr-3">
|
||||
<span className="me-3">
|
||||
<img
|
||||
src={require("../static/img/uses/usecase-filter.svg")}
|
||||
alt="Filter button"
|
||||
/>
|
||||
</span>
|
||||
{translate("Filter by Categories")}
|
||||
<span className="ml-3 total_count category_count">2</span>
|
||||
<span className="ms-3 total_count category_count">2</span>
|
||||
</a>
|
||||
{/* Start company cards */}
|
||||
<div className="row col-12 m-0 p-0 mt-4 pt-2">
|
||||
|
||||
@@ -116,400 +116,380 @@ export default function XrpOverview() {
|
||||
const totalCols = Math.max(softwallets.length, hardwallets.length) + 1;
|
||||
return (
|
||||
<div className="landing">
|
||||
<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 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>
|
||||
<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>
|
||||
</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 ${
|
||||
</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 ${
|
||||
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 that’s 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>
|
||||
|
||||
<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="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>
|
||||
<div>
|
||||
<img
|
||||
alt="user"
|
||||
className="mw-100 mb-2 invertible-img"
|
||||
src={userIcon}
|
||||
/>
|
||||
<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 that’s 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",
|
||||
"XRP’s 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 platform’s 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")}
|
||||
</span>
|
||||
</span>
|
||||
<br />
|
||||
{translate("XRP remains in escrow")}
|
||||
</h3>
|
||||
</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={`sidelinks nav-link ${
|
||||
activeSection === link.hash.substring(1) ? "active" : ""
|
||||
}`}
|
||||
href={link.hash}
|
||||
className="nav-link external-link"
|
||||
href={wallet.href}
|
||||
target="_blank"
|
||||
>
|
||||
{translate(link.text)}
|
||||
<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="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>
|
||||
</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. It’s 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="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 that’s 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>
|
||||
|
||||
<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 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>
|
||||
<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>
|
||||
<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 className="mt-10 p-10 br-8 cta-card position-relative">
|
||||
<img
|
||||
alt="magenta waves"
|
||||
src={require("../static/img/backgrounds/cta-xrp-overview-magenta.svg")}
|
||||
className="cta cta-bottom-right"
|
||||
/>
|
||||
<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 that’s 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>
|
||||
<h6 className="subhead-sm-r">
|
||||
{translate("Custodial Exchanges")}
|
||||
</h6>
|
||||
<p className="mb-0">
|
||||
{translate(
|
||||
"Custodial exchanges manage a user’s 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 user’s private keys, and publish decentralized order books of buyers and sellers on a blockchain."
|
||||
)}
|
||||
</p>
|
||||
</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",
|
||||
"XRP’s 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 platform’s 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="green waves"
|
||||
src={require("../static/img/backgrounds/cta-xrp-overview-green-2.svg")}
|
||||
className="landing-bg cta cta-bottom-right"
|
||||
/>
|
||||
<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 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 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>
|
||||
<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>
|
||||
{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>
|
||||
{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. It’s 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 user’s 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 user’s 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. It’s 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>
|
||||
))}
|
||||
</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. It’s 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>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,13 +57,6 @@ 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">
|
||||
|
||||
@@ -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,409 +17,403 @@ export default function Ambassadors() {
|
||||
const { translate } = useTranslate();
|
||||
|
||||
return (
|
||||
<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>
|
||||
<div className="landing page-ambassadors">
|
||||
<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>
|
||||
</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>
|
||||
{/* 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>
|
||||
</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>
|
||||
</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>
|
||||
{/* 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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
{/* 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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
{/* 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 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 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>
|
||||
</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>
|
||||
</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="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>
|
||||
{/* 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>
|
||||
</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="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>
|
||||
<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>
|
||||
</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>
|
||||
</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 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>
|
||||
{/* 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>
|
||||
</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 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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,261 +19,148 @@ export default function Funding() {
|
||||
|
||||
return (
|
||||
<div className="landing page-funding">
|
||||
<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 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>
|
||||
<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>
|
||||
</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">
|
||||
</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(
|
||||
"If you’re 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."
|
||||
"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 you’re 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>
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
||||
<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 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 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>
|
||||
<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>
|
||||
{/* Hide on large */}
|
||||
<div className="px-lg-3 pb-3 d-lg-none">
|
||||
</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>
|
||||
</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 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 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>
|
||||
{/* 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>
|
||||
</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>
|
||||
{/* end col 2 */}
|
||||
</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>
|
||||
</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 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>
|
||||
</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>
|
||||
</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>
|
||||
<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-lg-none order-3 mt-4 pt-3">
|
||||
<div className="d-none d-lg-block py-lg-3">
|
||||
<a
|
||||
className="btn btn-primary btn-arrow-out"
|
||||
target="_blank"
|
||||
@@ -283,52 +170,202 @@ export default function Funding() {
|
||||
</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 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>
|
||||
<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" />
|
||||
{/* 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("Best for")}</h6>
|
||||
<h6 className="mb-3">{translate("Required")}</h6>
|
||||
<p>
|
||||
{translate(
|
||||
"Start-ups building scalable products on XRPL that can capture a large market opportunity"
|
||||
)}
|
||||
<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>
|
||||
{/* Hide on large */}
|
||||
<div className="px-lg-3 pb-3 d-lg-none">
|
||||
</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">
|
||||
<img alt="book" id="funding-02" />
|
||||
<div className="pt-3">
|
||||
<h6 className="mb-3">{translate("Required")}</h6>
|
||||
@@ -340,92 +377,38 @@ 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(
|
||||
"$50,000 (grant) + pitch for venture funding"
|
||||
"Ideally an MVP and monetization strategy"
|
||||
)}
|
||||
</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("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 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>
|
||||
{/* end col 2 */}
|
||||
</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>
|
||||
{/* end col 2 */}
|
||||
</div>
|
||||
</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 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>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1364,311 +1364,303 @@ export default function Events() {
|
||||
|
||||
return (
|
||||
<div className="landing page-events">
|
||||
<div>
|
||||
<div className="position-relative d-none-sm">
|
||||
<img
|
||||
alt="orange waves"
|
||||
src={require("../static/img/backgrounds/events-orange.svg")}
|
||||
id="events-orange"
|
||||
/>
|
||||
<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>
|
||||
<section className="text-center py-26">
|
||||
<div className="mx-auto text-center col-lg-5">
|
||||
</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">
|
||||
<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>
|
||||
<h2 className="mb-8 h4 h2-sm">
|
||||
{translate("XRP Community Night NYC")}
|
||||
</h2>
|
||||
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
|
||||
</div>
|
||||
</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/xrp-community-night.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 NYC")}
|
||||
</h2>
|
||||
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
|
||||
</div>
|
||||
<p className="mb-4">
|
||||
{translate(
|
||||
"Join the XRP community in NYC—meet builders, users, and projects innovating on the XRP Ledger."
|
||||
)}
|
||||
</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>
|
||||
</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">
|
||||
<p className="mb-4">
|
||||
{translate(
|
||||
"Check out meetups, hackathons, and other events hosted by the XRPL Community"
|
||||
"Join the XRP community in NYC—meet builders, users, and projects innovating on the XRP Ledger."
|
||||
)}
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
{/* # 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) => (
|
||||
</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">
|
||||
<a
|
||||
key={event.name + i}
|
||||
className={`event-card ${event.type}`}
|
||||
className={`event-card ${event.type} h-100`}
|
||||
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>
|
||||
<div
|
||||
className="event-card-header"
|
||||
style={{
|
||||
background: `url(${event.image}) no-repeat`,
|
||||
}}
|
||||
>
|
||||
<div className="event-card-title">
|
||||
{translate(event.name)}
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</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 className="mt-2 mb-0 row row-cols-1 row-cols-lg-3 card-deck ">
|
||||
{filteredPast.map((event, i) => (
|
||||
</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">
|
||||
<a
|
||||
key={event.name + i}
|
||||
className="event-card {event.type}"
|
||||
className={`event-card ${event.type} h-100`}
|
||||
href={event.link}
|
||||
target="_blank"
|
||||
>
|
||||
@@ -1689,13 +1681,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>
|
||||
</section>
|
||||
</div>
|
||||
<span className="d-flex icon icon-date">{event.date}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -618,7 +618,7 @@ const CommunityPage: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto text-left col-lg-6 text-md-center hero-title">
|
||||
<div className="mx-auto text-start col-lg-6 text-md-center hero-title">
|
||||
<div className="d-flex flex-column-reverse align-items-center sm-align-items-start">
|
||||
<img
|
||||
src={require("../static/img/icons/arrow-down.svg")}
|
||||
@@ -870,7 +870,6 @@ const CommunityPage: React.FC = () => {
|
||||
{/* Bottom Cards Section 2 cards */}
|
||||
<section className="bottom-cards-section bug-bounty">
|
||||
<div className="com-card ripplex-bug-bounty">
|
||||
<img className="top-right-img bug-bounty-card-bg" alt="Top Right Image" />
|
||||
<div className="card-content">
|
||||
<h6 className="card-title">
|
||||
{translate("RippleX Bug Bounty Program")}
|
||||
@@ -910,7 +909,6 @@ const CommunityPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="com-card">
|
||||
<img className="bottom-right-img bug-bounty-card-bg-2" alt="Bottom Right Image" />
|
||||
<div className="card-content">
|
||||
<h6 className="card-title">{translate("Report a Scam")}</h6>
|
||||
<h6 className="card-subtitle pr-bt28">
|
||||
@@ -938,7 +936,6 @@ const CommunityPage: React.FC = () => {
|
||||
{/* Bottom Cards Section */}
|
||||
<section className="bottom-cards-section">
|
||||
<div className="com-card">
|
||||
<img className="top-left-img" alt="Top Left Image" />
|
||||
<div className="card-content">
|
||||
<h6 className="card-title">
|
||||
{translate("Contribute to Consensus")}
|
||||
@@ -984,7 +981,6 @@ const CommunityPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="com-card">
|
||||
<img className="bottom-right-img" alt="Bottom Right Image" />
|
||||
<div className="card-content">
|
||||
<h6 className="card-title">{translate("XRPL Careers")}</h6>
|
||||
<h6 className="card-subtitle pr-bt16">
|
||||
@@ -1009,7 +1005,6 @@ const CommunityPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="com-card">
|
||||
<img className="top-right-img" alt="Top Right Image" />
|
||||
<div className="card-content">
|
||||
<h6 className="card-title">
|
||||
{translate("Contribute to XRPL.org")}
|
||||
|
||||
@@ -396,8 +396,6 @@ export default function Docs() {
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="card cta-card p-8-sm p-10-until-sm br-8">
|
||||
<img src={require('../static/img/backgrounds/cta-home-purple.svg')} className="d-none-sm cta cta-top-left" />
|
||||
<img src={require('../static/img/backgrounds/cta-home-green.svg')} className="cta cta-bottom-right" />
|
||||
<div className="z-index-1 position-relative">
|
||||
<h2 className="h4 mb-8-sm mb-10-until-sm">{translate('Get Free Test XRP')}</h2>
|
||||
<p className="mb-10">
|
||||
|
||||
@@ -1106,7 +1106,7 @@ const { sendXrp } = require('./library/7_helpers')
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
|
||||
<h1 class="modal-title subhead-sm-r" 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">
|
||||
|
||||
@@ -221,7 +221,7 @@ const PaymentsPage: React.FC = () => {
|
||||
|
||||
</div>
|
||||
<div className="text-content">
|
||||
<h6 className="eyebrow mb-3 text-large">
|
||||
<h6 className="eyebrow mb-3 subhead-sm-r">
|
||||
{translate("Payments")}
|
||||
</h6>
|
||||
<h2 className="h4 h2-sm mb-10">
|
||||
|
||||
@@ -199,7 +199,7 @@ export default function Tokenization() {
|
||||
"Work with a variety of tokens supported by the XRP Ledger."
|
||||
)}
|
||||
</h2>
|
||||
<h6 className="eyebrow mb-3 text-large">
|
||||
<h6 className="eyebrow mb-3 subhead-sm-r">
|
||||
{translate("Tokenization")}
|
||||
</h6>
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@ export default function Tokenization() {
|
||||
{translate("Quick Start")}
|
||||
</Link>{" "}
|
||||
<a
|
||||
className="ml-4 video-external-link btn-none"
|
||||
className="ms-4 video-external-link btn-none"
|
||||
target="_blank"
|
||||
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
|
||||
>
|
||||
@@ -273,7 +273,7 @@ export default function Tokenization() {
|
||||
target="_blank"
|
||||
href={article.url}
|
||||
>
|
||||
<div className="time h4 normal mb-8">
|
||||
<div className="time h4 mb-8">
|
||||
{translate(article.time)}
|
||||
</div>
|
||||
<div className="h5 mb-4">{translate(article.title)}</div>
|
||||
|
||||
@@ -401,13 +401,6 @@ function TokenHeroSection() {
|
||||
const { translate } = useTranslate();
|
||||
return (
|
||||
<section className="token-hero-section">
|
||||
<div className="position-relative d-none-sm">
|
||||
<img
|
||||
alt="orange waves"
|
||||
src={require("./../../../static/img/backgrounds/events-orange.svg")}
|
||||
id="events-orange"
|
||||
/>
|
||||
</div>
|
||||
<div className="token-title-container">
|
||||
<h1 className="token-title">
|
||||
{translate("Real-World Asset (RWA) Tokenization")}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
||||
import { Link } from '@redocly/theme/components/Link/Link';
|
||||
import { BenefitsSection } from 'shared/components/benefits-section';
|
||||
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
|
||||
|
||||
export const frontmatter = {
|
||||
seo: {
|
||||
@@ -117,7 +118,7 @@ export default function Index() {
|
||||
</div>
|
||||
<div className="col-lg-6 mx-auto text-center pl-0 pr-0">
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h1 className="mb-10">
|
||||
<h1 className="display-lg mb-10">
|
||||
{translate('home.hero.h1part1', 'The Blockchain')}
|
||||
<br className="until-sm" />
|
||||
{translate('home.hero.h1part2', 'Built for Business')}
|
||||
@@ -129,12 +130,8 @@ export default function Index() {
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
<div className="position-relative d-none-sm">
|
||||
<img src={require('./static/img/backgrounds/home-purple.svg')} id="home-purple" loading="lazy" />
|
||||
<img src={require('./static/img/backgrounds/home-green.svg')} id="home-green" loading="lazy" />
|
||||
</div>
|
||||
<section className="container-new py-26">
|
||||
<div className="col-lg-6 offset-lg-3 pl-0-sm pr-0-sm p-8-sm p-10-until-sm">
|
||||
<PageGrid className="py-26">
|
||||
<PageGrid.Col span={{ base: 12, lg: 6 }} offset={{ lg: 3 }}>
|
||||
<h2 className="h4 mb-8 h2-sm">{translate('The XRP Ledger: The Blockchain Built for Business')}</h2>
|
||||
<h6 className="longform mb-10">
|
||||
{translate(
|
||||
@@ -146,8 +143,8 @@ export default function Index() {
|
||||
'Proven reliable over more than a decade of error-free functioning, the XRPL offers streamlined development, low transaction costs, high performance, and sustainability. So you can build with confidence–and move your most critical projects forward.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</PageGrid.Col>
|
||||
</PageGrid>
|
||||
<BenefitsSection
|
||||
eyebrow="Benefits"
|
||||
title="Why developers choose the XRP Ledger"
|
||||
@@ -194,8 +191,6 @@ export default function Index() {
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
<div className="col-lg-6 offset-lg-3 p-6-sm p-10-until-sm br-8 cta-card">
|
||||
<img src={require('./static/img/backgrounds/cta-home-purple.svg')} className="d-none-sm cta cta-top-left" />
|
||||
<img src={require('./static/img/backgrounds/cta-home-green.svg')} className="cta cta-bottom-right" />
|
||||
<div className="z-index-1 position-relative">
|
||||
<h2 className="h4 mb-8-sm mb-10-until-sm">{translate('Our Shared Vision for XRPL’s Future')}</h2>
|
||||
<p className="mb-10">
|
||||
@@ -232,7 +227,6 @@ export default function Index() {
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
<div className="col-md-6 offset-md-3 p-8-sm p-10-until-sm br-8 cta-card">
|
||||
<img alt="" src={require('./static/img/backgrounds/cta-home-magenta.svg')} className="cta cta-bottom-right" />
|
||||
<div className="z-index-1 position-relative">
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h2 className="h4 mb-8-sm mb-10-until-sm">
|
||||
|
||||
2509
package-lock.json
generated
2509
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -5,8 +5,10 @@
|
||||
"type": "module",
|
||||
"description": "The XRP Ledger Dev Portal is the authoritative source for XRP Ledger documentation, including the `rippled` server, client libraries, and other open-source XRP Ledger software.",
|
||||
"scripts": {
|
||||
"build-css": "sass --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map",
|
||||
"build-css-watch": "sass --watch --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map",
|
||||
"analyze-css": "node scripts/analyze-css.js",
|
||||
"build-css": "sass --load-path styles/scss --load-path . styles/xrpl.scss ./static/css/devportal2024-v1.tmp.css && NODE_ENV=production postcss ./static/css/devportal2024-v1.tmp.css -o ./static/css/devportal2024-v1.css && rm -f ./static/css/devportal2024-v1.tmp.css",
|
||||
"build-css:dev": "sass --load-path styles/scss --load-path . styles/xrpl.scss ./static/css/devportal2024-v1.tmp.css --source-map && postcss ./static/css/devportal2024-v1.tmp.css -o ./static/css/devportal2024-v1.css && rm -f ./static/css/devportal2024-v1.tmp.css",
|
||||
"build-css:watch": "sass --watch --load-path styles/scss --load-path . styles/xrpl.scss:static/css/devportal2024-v1.css --source-map",
|
||||
"start": "realm develop"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -37,8 +39,13 @@
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bootstrap": "^4.6.2",
|
||||
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"bootstrap": "^5.3.3",
|
||||
"cssnano": "^7.1.1",
|
||||
"htmltojsx": "^0.3.0",
|
||||
"sass": "1.26.10"
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"sass": "^1.93.2"
|
||||
}
|
||||
}
|
||||
|
||||
151
postcss.config.cjs
Normal file
151
postcss.config.cjs
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* PostCSS Configuration
|
||||
*
|
||||
* Processes compiled Sass output through:
|
||||
* 1. PurgeCSS - Removes unused CSS selectors
|
||||
* 2. Autoprefixer - Adds vendor prefixes for browser compatibility
|
||||
* 3. cssnano - Minifies and optimizes CSS (production only)
|
||||
*/
|
||||
|
||||
const purgecss = require('@fullhuman/postcss-purgecss').default;
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// Only run PurgeCSS in production or when explicitly enabled
|
||||
...(isProduction || process.env.PURGECSS === 'true'
|
||||
? [
|
||||
purgecss({
|
||||
// Scan all content files for class names
|
||||
content: [
|
||||
'./**/*.tsx',
|
||||
'./**/*.ts',
|
||||
'./**/*.md',
|
||||
'./**/*.yaml',
|
||||
'./**/*.html',
|
||||
'./static/js/**/*.js',
|
||||
'./static/vendor/**/*.js',
|
||||
// Ignore node_modules except for specific libraries that inject classes
|
||||
'!./node_modules/**/*',
|
||||
],
|
||||
|
||||
// Default extractor - looks for class names in content
|
||||
defaultExtractor: content => {
|
||||
// Match all words, including those with dashes and numbers
|
||||
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
|
||||
// Match class names in className="..." or class="..."
|
||||
const classMatches = content.match(/(?:class|className)=["']([^"']*)["']/g) || [];
|
||||
const classes = classMatches.flatMap(match => {
|
||||
const m = match.match(/["']([^"']*)["']/);
|
||||
return m ? m[1].split(/\s+/) : [];
|
||||
});
|
||||
return [...broadMatches, ...classes];
|
||||
},
|
||||
|
||||
// Safelist - classes that should never be removed
|
||||
safelist: {
|
||||
// Standard safelist - dynamic state classes
|
||||
standard: [
|
||||
'html',
|
||||
'body',
|
||||
'light',
|
||||
'dark',
|
||||
'show',
|
||||
'hide',
|
||||
'active',
|
||||
'disabled',
|
||||
'open',
|
||||
'collapsed',
|
||||
'collapsing',
|
||||
'lang-ja', // Japanese language class
|
||||
/^lang-/, // All language classes
|
||||
// Common Bootstrap utility patterns that should always be kept
|
||||
/^container/, // All container classes
|
||||
/^row$/, // Row class
|
||||
/^col-/, // Column classes
|
||||
/^g-/, // Gap utilities
|
||||
/^p-/, // Padding utilities
|
||||
/^m-/, // Margin utilities
|
||||
/^px-/, /^py-/, /^pt-/, /^pb-/, /^ps-/, /^pe-/, // Directional padding
|
||||
/^mx-/, /^my-/, /^mt-/, /^mb-/, /^ms-/, /^me-/, // Directional margin
|
||||
/^d-/, // Display utilities
|
||||
/^flex-/, // Flexbox utilities
|
||||
/^justify-/, // Justify content
|
||||
/^align-/, // Align items
|
||||
/^w-/, // Width utilities
|
||||
/^h-/, // Height utilities
|
||||
/^text-/, // Text utilities
|
||||
/^bg-/, // Background utilities
|
||||
/^border/, // Border utilities
|
||||
/^rounded/, // Border radius
|
||||
],
|
||||
|
||||
// Deep safelist - MINIMAL - only truly dynamic components
|
||||
deep: [
|
||||
// Bootstrap JS components (only if actually used with JS)
|
||||
/dropdown-menu/,
|
||||
/dropdown-item/,
|
||||
/modal-backdrop/,
|
||||
/fade/,
|
||||
|
||||
// Third-party libraries
|
||||
/cm-/,
|
||||
/CodeMirror/,
|
||||
/lottie/,
|
||||
],
|
||||
|
||||
// Greedy safelist - VERY MINIMAL
|
||||
greedy: [
|
||||
/data-theme/, // Theme switching
|
||||
],
|
||||
},
|
||||
|
||||
// Reject specific patterns - don't remove these even if not found
|
||||
rejected: [],
|
||||
|
||||
// Variables - keep CSS custom properties
|
||||
variables: true,
|
||||
|
||||
// Keyframes - keep animation keyframes
|
||||
keyframes: true,
|
||||
|
||||
// Font-face rules
|
||||
fontFace: true,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
// Autoprefixer - adds vendor prefixes
|
||||
autoprefixer({
|
||||
overrideBrowserslist: [
|
||||
'>0.2%',
|
||||
'not dead',
|
||||
'not op_mini all',
|
||||
'last 2 versions',
|
||||
],
|
||||
}),
|
||||
|
||||
// cssnano - minification (production only)
|
||||
...(isProduction
|
||||
? [
|
||||
cssnano({
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
discardComments: {
|
||||
removeAll: true,
|
||||
},
|
||||
normalizeWhitespace: true,
|
||||
colormin: true,
|
||||
minifySelectors: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -56,16 +56,12 @@ scripts:
|
||||
head:
|
||||
- src: https://cmp.osano.com/AzyjT6TIZMlgyLyy8/f11f7772-8ed5-4b73-bd17-c0814edcc440/osano.js
|
||||
- src: ./static/js/xrpl-2.11.0.min.js
|
||||
- src: ./static/vendor/jquery-3.7.1.min.js
|
||||
# - src: ./static/vendor/jquery-3.7.1.min.js
|
||||
- src: ./static/vendor/bootstrap.min.js
|
||||
- src: ./static/js/osano.js
|
||||
type: text/javascript
|
||||
links:
|
||||
- href: https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700&display=swap
|
||||
rel: stylesheet
|
||||
- href: https://fonts.googleapis.com/css?family=Source+Code+Pro:300,400,600,700&display=swap
|
||||
rel: stylesheet
|
||||
- href: https://fonts.googleapis.com/css?family=Space+Grotesk:300,400,600,700&display=swap
|
||||
- href: https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600;700&family=Noto+Serif:wght@400;500;600;700&family=Noto+Sans+JP:wght@300;400;500;600;700&display=swap
|
||||
rel: stylesheet
|
||||
- href: ./static/css/devportal2024-v1.css
|
||||
rel: stylesheet
|
||||
|
||||
@@ -40,25 +40,17 @@ export default function CodeSamples() {
|
||||
{/* <a className="mt-12 btn btn-primary btn-arrow">Submit Code Samples</a> */}
|
||||
</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="d-flex flex-column col-sm-8 p-0">
|
||||
<h3 className="h4 h2-sm">
|
||||
{translate('Browse sample code for building common use cases on the XRP Ledger')}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="row col-12 card-deck mt-10" id="code-samples-deck">
|
||||
<div className="row col-md-12 px-0" id="code_samples_list">
|
||||
{codeSamples.map(card => (
|
||||
<div className="row gx-4 gy-5 mt-10 mb-20" id="code-samples-deck">
|
||||
{codeSamples.map(card => (
|
||||
<div key={card.href} className="col-12 col-lg-6 mb-4">
|
||||
<a
|
||||
key={card.href}
|
||||
className={`card cardtest col-12 col-lg-5 ${card.langs.join(' ')}`}
|
||||
className={`card cardtest h-100 ${card.langs.join(' ')}`}
|
||||
href={target.github_forkurl + `/tree/${target.github_branch}/${card.href}`.replace('/content','')}
|
||||
>
|
||||
<div className="card-header">
|
||||
@@ -72,10 +64,9 @@ export default function CodeSamples() {
|
||||
<h4 className="card-title h5">{card.title}</h4>
|
||||
<p className="card-text">{card.description}</p>
|
||||
</div>
|
||||
<div className="card-footer"> </div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="container-new py-26">
|
||||
@@ -86,8 +77,8 @@ export default function CodeSamples() {
|
||||
{translate('Help the XRPL community by submitting your own code samples')}
|
||||
</h6>
|
||||
</div>
|
||||
<div className="row pl-4">
|
||||
<div className=" col-lg-3 pl-4 pl-lg-0 pr-4 contribute dot contribute_1">
|
||||
<div className="row ps-4">
|
||||
<div className=" col-lg-3 ps-4 ps-lg-0 pe-4 contribute dot contribute_1">
|
||||
<span className="dot" />
|
||||
<h5 className="pb-4 pt-md-5">{translate('Fork and clone')}</h5>
|
||||
<p className="pb-4">
|
||||
@@ -98,7 +89,7 @@ export default function CodeSamples() {
|
||||
{translate('resources.contribute.1.part3', '. Using git, clone the fork to your computer.')}
|
||||
</p>
|
||||
</div>
|
||||
<div className=" col-lg-3 pl-4 pl-lg-0 pr-4 contribute dot contribute_2">
|
||||
<div className=" col-lg-3 ps-4 ps-lg-0 pe-4 contribute dot contribute_2">
|
||||
<span className="dot" />
|
||||
<h5 className="pb-4 pt-md-5">{translate('Add to folder')}</h5>
|
||||
<p className="pb-4">
|
||||
|
||||
@@ -62,8 +62,8 @@ export function CurlButton ({selectedConnection, currentBody}: CurlButtonProps)
|
||||
return <>
|
||||
<button
|
||||
className="btn btn-outline-secondary curl"
|
||||
data-toggle="modal"
|
||||
data-target="#wstool-1-curl"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#wstool-1-curl"
|
||||
title={translate("cURL Syntax")}
|
||||
onClick={() => setShowCurlModal(true)}
|
||||
>
|
||||
|
||||
@@ -62,8 +62,8 @@ export function PermalinkButton ({currentBody, selectedConnection}: PermaLinkBut
|
||||
return <>
|
||||
<button
|
||||
className="btn btn-outline-secondary permalink"
|
||||
data-toggle="modal"
|
||||
data-target="#wstool-1-permalink"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#wstool-1-permalink"
|
||||
title={translate("Permalink")}
|
||||
onClick={() => setShowPermalinkModal(true)}
|
||||
>
|
||||
|
||||
@@ -146,8 +146,8 @@ export default function DevTools() {
|
||||
<button
|
||||
className="nav-link active dev-tools-tab"
|
||||
id="explorers-tab"
|
||||
data-toggle="tab"
|
||||
data-target="#explorers"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#explorers"
|
||||
role="tab"
|
||||
aria-controls="explorers"
|
||||
aria-selected="true"
|
||||
@@ -159,8 +159,8 @@ export default function DevTools() {
|
||||
<button
|
||||
className="nav-link dev-tools-tab"
|
||||
id="api-access-tab"
|
||||
data-toggle="tab"
|
||||
data-target="#api-access"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#api-access"
|
||||
role="tab"
|
||||
aria-controls="api-access"
|
||||
aria-selected="false"
|
||||
@@ -172,8 +172,8 @@ export default function DevTools() {
|
||||
<button
|
||||
className="nav-link dev-tools-tab"
|
||||
id="other-tab"
|
||||
data-toggle="tab"
|
||||
data-target="#other"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#other"
|
||||
role="tab"
|
||||
aria-controls="other"
|
||||
aria-selected="false"
|
||||
@@ -278,16 +278,6 @@ export default function DevTools() {
|
||||
</section>
|
||||
<section className="container-new py-10 px-0">
|
||||
<div className="col-lg-12 p-6-sm p-10-until-sm br-8 cta-card">
|
||||
<img
|
||||
alt="purple waves"
|
||||
src={require("../../static/img/backgrounds/cta-home-purple.svg")}
|
||||
className="d-none-sm cta cta-top-left"
|
||||
/>
|
||||
<img
|
||||
alt="green waves"
|
||||
src={require("../../static/img/backgrounds/cta-home-green.svg")}
|
||||
className="cta cta-bottom-right"
|
||||
/>
|
||||
<div className="z-index-1 position-relative">
|
||||
<h2 className="h4 mb-8-sm mb-10-until-sm">
|
||||
{translate("Have an Idea For a Tool?")}
|
||||
|
||||
@@ -238,7 +238,7 @@ export function WebsocketApiTool() {
|
||||
className="btn-toolbar justify-content-between pt-4"
|
||||
role="toolbar"
|
||||
>
|
||||
<div className="btn-group mr-3" role="group">
|
||||
<div className="btn-group me-3" role="group">
|
||||
<button
|
||||
className="btn btn-outline-secondary send-request"
|
||||
onClick={() => sendWebSocketMessage(currentBody)}
|
||||
@@ -259,8 +259,8 @@ export function WebsocketApiTool() {
|
||||
connected ? "btn-success" : "btn-outline-secondary"
|
||||
} ${connectionError ?? "btn-danger"}`}
|
||||
onClick={openConnectionModal}
|
||||
data-toggle="modal"
|
||||
data-target="#wstool-1-connection-settings"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#wstool-1-connection-settings"
|
||||
>
|
||||
{`${selectedConnection.shortname}${
|
||||
connected ? ` (${translate('Connected')})` : ` (${translate('Not Connected')})`
|
||||
|
||||
@@ -184,7 +184,7 @@ function TestCredentials({selectedFaucet, translate}) {
|
||||
setBalance,
|
||||
setSequence,
|
||||
translate)
|
||||
} className="btn btn-primary mr-2 mb-2">
|
||||
} className="btn btn-primary me-2 mb-2">
|
||||
{`${translate('resources.dev-tools.faucet.cred-btn.part1', 'Generate ')}${selectedFaucet.shortName}${translate('resources.dev-tools.faucet.cred-btn.part2', ' credentials')}`}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
166
scripts/analyze-css.js
Normal file
166
scripts/analyze-css.js
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* CSS Analysis Script
|
||||
*
|
||||
* Analyzes the compiled CSS bundle to identify:
|
||||
* - Total size and line count
|
||||
* - Bootstrap vs custom CSS breakdown
|
||||
* - Most common selectors and patterns
|
||||
* - Potential optimization opportunities
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const CSS_FILE = path.join(__dirname, '../static/css/devportal2024-v1.css');
|
||||
|
||||
function analyzeCSS() {
|
||||
console.log('🔍 Analyzing CSS Bundle...\n');
|
||||
|
||||
if (!fs.existsSync(CSS_FILE)) {
|
||||
console.error(`❌ CSS file not found: ${CSS_FILE}`);
|
||||
console.log('💡 Run "npm run build-css" first to generate the CSS bundle.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const css = fs.readFileSync(CSS_FILE, 'utf-8');
|
||||
const stats = fs.statSync(CSS_FILE);
|
||||
|
||||
// Basic stats
|
||||
const lines = css.split('\n').length;
|
||||
const sizeKB = (stats.size / 1024).toFixed(2);
|
||||
const selectors = css.match(/[^{}]+(?=\{)/g) || [];
|
||||
const uniqueSelectors = new Set(selectors.map(s => s.trim())).size;
|
||||
|
||||
console.log('📊 Bundle Statistics:');
|
||||
console.log('━'.repeat(50));
|
||||
console.log(` Size: ${sizeKB} KB`);
|
||||
console.log(` Lines: ${lines.toLocaleString()}`);
|
||||
console.log(` Total Selectors: ${selectors.length.toLocaleString()}`);
|
||||
console.log(` Unique Selectors: ${uniqueSelectors.toLocaleString()}`);
|
||||
console.log('');
|
||||
|
||||
// Bootstrap component detection
|
||||
const bootstrapPatterns = {
|
||||
'Grid System': /\.(container|row|col-)/g,
|
||||
'Buttons': /\.btn-/g,
|
||||
'Forms': /\.(form-control|form-select|form-check)/g,
|
||||
'Cards': /\.card-/g,
|
||||
'Navbar': /\.navbar-/g,
|
||||
'Dropdown': /\.dropdown-/g,
|
||||
'Modal': /\.modal-/g,
|
||||
'Alert': /\.alert-/g,
|
||||
'Badge': /\.badge-/g,
|
||||
'Breadcrumb': /\.breadcrumb/g,
|
||||
'Pagination': /\.page-/g,
|
||||
'Accordion': /\.accordion/g,
|
||||
'Carousel': /\.carousel/g,
|
||||
'Tooltip': /\.tooltip/g,
|
||||
'Popover': /\.popover/g,
|
||||
'Toast': /\.toast/g,
|
||||
'Spinner': /\.spinner-/g,
|
||||
};
|
||||
|
||||
console.log('🎨 Bootstrap Component Usage:');
|
||||
console.log('━'.repeat(50));
|
||||
|
||||
const componentUsage = [];
|
||||
for (const [component, pattern] of Object.entries(bootstrapPatterns)) {
|
||||
const matches = css.match(pattern);
|
||||
const count = matches ? matches.length : 0;
|
||||
componentUsage.push({ component, count });
|
||||
}
|
||||
|
||||
componentUsage.sort((a, b) => b.count - a.count);
|
||||
componentUsage.forEach(({ component, count }) => {
|
||||
const bar = '█'.repeat(Math.min(Math.floor(count / 10), 40));
|
||||
console.log(` ${component.padEnd(20)} ${count.toString().padStart(4)} ${bar}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Custom classes analysis
|
||||
const customPatterns = [
|
||||
{ name: 'Dev Tools', pattern: /\.(rpc-tool|websocket|code-tab)/g },
|
||||
{ name: 'Navigation', pattern: /\.(top-nav|side-nav|breadcrumb)/g },
|
||||
{ name: 'Content', pattern: /\.(content-|landing-|page-)/g },
|
||||
{ name: 'Cards', pattern: /\.(card-deck|project-card)/g },
|
||||
{ name: 'Video', pattern: /\.video-/g },
|
||||
{ name: 'Blog', pattern: /\.blog-/g },
|
||||
];
|
||||
|
||||
console.log('🎯 Custom Component Patterns:');
|
||||
console.log('━'.repeat(50));
|
||||
customPatterns.forEach(({ name, pattern }) => {
|
||||
const matches = css.match(pattern);
|
||||
const count = matches ? matches.length : 0;
|
||||
if (count > 0) {
|
||||
console.log(` ${name.padEnd(20)} ${count.toString().padStart(4)}`);
|
||||
}
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Optimization recommendations
|
||||
console.log('💡 Optimization Recommendations:');
|
||||
console.log('━'.repeat(50));
|
||||
|
||||
const recommendations = [];
|
||||
|
||||
// Check for unused Bootstrap components
|
||||
const lowUsageComponents = componentUsage.filter(c => c.count < 5 && c.count > 0);
|
||||
if (lowUsageComponents.length > 0) {
|
||||
recommendations.push({
|
||||
priority: 'HIGH',
|
||||
message: `${lowUsageComponents.length} Bootstrap components with <5 usages detected`,
|
||||
action: 'Consider manually importing only needed Bootstrap modules'
|
||||
});
|
||||
}
|
||||
|
||||
const noUsageComponents = componentUsage.filter(c => c.count === 0);
|
||||
if (noUsageComponents.length > 0) {
|
||||
recommendations.push({
|
||||
priority: 'HIGH',
|
||||
message: `${noUsageComponents.length} Bootstrap components with 0 usages detected`,
|
||||
action: 'Remove unused components from Bootstrap import'
|
||||
});
|
||||
}
|
||||
|
||||
if (sizeKB > 200) {
|
||||
recommendations.push({
|
||||
priority: 'CRITICAL',
|
||||
message: 'Bundle size exceeds 200KB',
|
||||
action: 'Implement PurgeCSS to remove unused styles'
|
||||
});
|
||||
}
|
||||
|
||||
recommendations.push({
|
||||
priority: 'MEDIUM',
|
||||
message: 'No code splitting detected',
|
||||
action: 'Consider splitting vendor CSS from custom styles'
|
||||
});
|
||||
|
||||
recommendations.forEach(({ priority, message, action }) => {
|
||||
const emoji = priority === 'CRITICAL' ? '🔴' : priority === 'HIGH' ? '🟡' : '🔵';
|
||||
console.log(` ${emoji} [${priority}] ${message}`);
|
||||
console.log(` → ${action}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// Estimate potential savings
|
||||
const estimatedReduction = Math.round(parseFloat(sizeKB) * 0.75);
|
||||
const estimatedFinal = Math.round(parseFloat(sizeKB) * 0.25);
|
||||
|
||||
console.log('📈 Estimated Optimization Potential:');
|
||||
console.log('━'.repeat(50));
|
||||
console.log(` Current Size: ${sizeKB} KB`);
|
||||
console.log(` Potential Savings: ~${estimatedReduction} KB (75%)`);
|
||||
console.log(` Expected Size: ~${estimatedFinal} KB`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
analyzeCSS();
|
||||
|
||||
614
shared/components/Button/Button.md
Normal file
614
shared/components/Button/Button.md
Normal file
@@ -0,0 +1,614 @@
|
||||
# Button Component Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Button component is a scalable, accessible button implementation following the XRPL Brand Design System (BDS). It supports three visual variants (Primary, Secondary, Tertiary) and two color themes (Green, Black), with comprehensive state management and smooth animations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Three Variants**: Primary (solid), Secondary (outline), Tertiary (text-only)
|
||||
- **Two Color Themes**: Green (default) and Black
|
||||
- **Link Support**: Can render as anchor elements for navigation via `href` prop
|
||||
- **Animated Arrow Icon**: Optional icon with smooth hover animations
|
||||
- **Full State Support**: Enabled, Hover, Focus, Active, and Disabled states
|
||||
- **Responsive Design**: Adaptive padding and spacing across breakpoints
|
||||
- **Accessibility**: WCAG compliant with keyboard navigation and screen reader support
|
||||
- **Smooth Animations**: 150ms transitions with custom bezier timing
|
||||
|
||||
## Props API
|
||||
|
||||
```typescript
|
||||
interface ButtonProps {
|
||||
/** Button variant - determines visual style */
|
||||
variant?: 'primary' | 'secondary' | 'tertiary';
|
||||
/** Color theme - green (default) or black */
|
||||
color?: 'green' | 'black';
|
||||
/** Button content/label */
|
||||
children: React.ReactNode;
|
||||
/** Click handler */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Button type attribute */
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
/** Whether to show the arrow icon */
|
||||
showIcon?: boolean;
|
||||
/** Accessibility label - defaults to button text if not provided */
|
||||
ariaLabel?: string;
|
||||
/** URL to navigate to - renders as a Link instead of button */
|
||||
href?: string;
|
||||
/** Link target - only applies when href is provided */
|
||||
target?: '_self' | '_blank';
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
- `variant`: `'primary'`
|
||||
- `color`: `'green'`
|
||||
- `disabled`: `false`
|
||||
- `type`: `'button'`
|
||||
- `className`: `''`
|
||||
- `showIcon`: `true`
|
||||
- `ariaLabel`: (derived from children text)
|
||||
- `href`: `undefined`
|
||||
- `target`: `'_self'`
|
||||
|
||||
## Variants
|
||||
|
||||
### Primary Button
|
||||
|
||||
The Primary button is used for the main call-to-action on a page. It features a solid background that fills from bottom-to-top on hover.
|
||||
|
||||
**Visual Characteristics:**
|
||||
- Solid background (Green 300 / Black)
|
||||
- High visual emphasis
|
||||
- Background color transitions on hover
|
||||
- Black text on green background, white text on black background
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<Button variant="primary" onClick={handleClick}>
|
||||
Get Started
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Secondary Button
|
||||
|
||||
The Secondary button is used for supporting actions. It features an outline style with a transparent background that fills on hover.
|
||||
|
||||
**Visual Characteristics:**
|
||||
- Transparent background with 2px border
|
||||
- Medium visual emphasis
|
||||
- Background fills from bottom-to-top on hover
|
||||
- Green/Black text and border
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<Button variant="secondary" onClick={handleClick}>
|
||||
Learn More
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Tertiary Button
|
||||
|
||||
The Tertiary button is used for low-emphasis or contextual actions. It appears as text-only with optional underline on hover.
|
||||
|
||||
**Visual Characteristics:**
|
||||
- Text-only, no background or border
|
||||
- Lowest visual emphasis
|
||||
- Underline appears on hover/focus
|
||||
- Different typography (Body R token vs Label R)
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<Button variant="tertiary" onClick={handleClick}>
|
||||
View Details
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Color Themes
|
||||
|
||||
### Green Theme (Default)
|
||||
|
||||
The green theme uses the XRPL brand green colors:
|
||||
- **Primary**: Green 300 background (#21E46B), Green 200 hover (#70EE97)
|
||||
- **Secondary**: Green 400 text/border (#0DAA3E), Green 500 hover (#078139)
|
||||
- **Tertiary**: Green 400 text (#0DAA3E), Green 500 hover (#078139)
|
||||
|
||||
### Black Theme
|
||||
|
||||
The black theme provides an alternative color scheme:
|
||||
- **Primary**: Black background (#141414), 80% black hover
|
||||
- **Secondary**: Black text/border (#141414), 15% black hover fill
|
||||
- **Tertiary**: Black text (#141414)
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<Button variant="primary" color="black" onClick={handleClick}>
|
||||
Dark Button
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Link Buttons
|
||||
|
||||
The Button component can render as an anchor element for navigation by passing the `href` prop. When `href` is provided, the button is wrapped in a Redocly `Link` component for proper routing support within the application.
|
||||
|
||||
### How It Works
|
||||
|
||||
- When `href` is provided and button is not disabled, renders as `<Link>` (anchor element)
|
||||
- When `href` is not provided or button is disabled, renders as `<button>` element
|
||||
- All visual styles and animations remain identical regardless of element type
|
||||
- The Redocly Link component handles internal routing and external link handling
|
||||
|
||||
### Internal Navigation
|
||||
|
||||
For navigating within the application:
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" href="/docs">
|
||||
View Documentation
|
||||
</Button>
|
||||
|
||||
<Button variant="secondary" href="/about">
|
||||
About Us
|
||||
</Button>
|
||||
```
|
||||
|
||||
### External Links
|
||||
|
||||
For external URLs, use `target="_blank"` to open in a new tab:
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" href="https://xrpl.org" target="_blank">
|
||||
Visit XRPL.org
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Link with Click Handler
|
||||
|
||||
You can combine `href` with `onClick` for tracking or additional logic:
|
||||
|
||||
```tsx
|
||||
<Button
|
||||
variant="primary"
|
||||
href="/signup"
|
||||
onClick={() => trackEvent('signup_click')}
|
||||
>
|
||||
Sign Up
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Disabled Link Buttons
|
||||
|
||||
When `disabled={true}` and `href` is provided, the component renders as a `<button>` element instead of a link to prevent navigation:
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" href="/checkout" disabled>
|
||||
Checkout (Unavailable)
|
||||
</Button>
|
||||
```
|
||||
|
||||
### All Variants as Links
|
||||
|
||||
All button variants support link functionality:
|
||||
|
||||
```tsx
|
||||
{/* Primary link button */}
|
||||
<Button variant="primary" href="/get-started">
|
||||
Get Started
|
||||
</Button>
|
||||
|
||||
{/* Secondary link button */}
|
||||
<Button variant="secondary" href="/learn-more">
|
||||
Learn More
|
||||
</Button>
|
||||
|
||||
{/* Tertiary link button */}
|
||||
<Button variant="tertiary" href="/view-details">
|
||||
View Details
|
||||
</Button>
|
||||
|
||||
{/* Black theme link button */}
|
||||
<Button variant="primary" color="black" href="/dashboard">
|
||||
Dashboard
|
||||
</Button>
|
||||
```
|
||||
|
||||
## States
|
||||
|
||||
### Enabled State
|
||||
|
||||
The default interactive state of the button. All variants display their base styling.
|
||||
|
||||
### Hover State
|
||||
|
||||
Triggered when the user hovers over the button with a mouse:
|
||||
- **Primary/Secondary**: Background fills from bottom-to-top
|
||||
- **Tertiary**: Underline appears, text color darkens
|
||||
- **All Variants**: Arrow icon line shrinks, gap increases (with icon)
|
||||
|
||||
### Focus State
|
||||
|
||||
Triggered when the button receives keyboard focus (Tab key):
|
||||
- Similar visual changes to hover state
|
||||
- Additional focus outline (2px border/outline)
|
||||
- Ensures keyboard accessibility
|
||||
|
||||
### Active State
|
||||
|
||||
Triggered when the button is being pressed:
|
||||
- Returns to default padding/gap
|
||||
- Background resets (for Primary/Secondary)
|
||||
- Maintains visual feedback during press
|
||||
|
||||
### Disabled State
|
||||
|
||||
When `disabled={true}`:
|
||||
- Icon is automatically hidden
|
||||
- Gray text and background (Primary) or border (Secondary)
|
||||
- Cursor changes to `not-allowed`
|
||||
- `pointer-events: none` prevents interaction
|
||||
- `aria-disabled` attribute set for screen readers
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<Button variant="primary" disabled>
|
||||
Unavailable
|
||||
</Button>
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Component Structure
|
||||
|
||||
The Button component uses BEM (Block Element Modifier) naming convention with the `bds` namespace:
|
||||
|
||||
- `.bds-btn` - Base button class
|
||||
- `.bds-btn--primary` - Primary variant modifier
|
||||
- `.bds-btn--secondary` - Secondary variant modifier
|
||||
- `.bds-btn--tertiary` - Tertiary variant modifier
|
||||
- `.bds-btn--green` - Green color theme (default)
|
||||
- `.bds-btn--black` - Black color theme
|
||||
- `.bds-btn--disabled` - Disabled state modifier
|
||||
- `.bds-btn--no-icon` - No icon modifier
|
||||
- `.bds-btn__label` - Label element
|
||||
- `.bds-btn__icon` - Icon container
|
||||
- `.bds-btn__icon-line` - Arrow horizontal line
|
||||
- `.bds-btn__icon-chevron` - Arrow chevron
|
||||
|
||||
### Background Animation
|
||||
|
||||
Primary and Secondary variants use a shared animation pattern:
|
||||
|
||||
1. **Pseudo-element (`::before`)**: Creates the hover background fill
|
||||
2. **Transform Origin**: Set to `bottom center` for bottom-to-top fill
|
||||
3. **Initial State**: `scaleY(0)` - background hidden
|
||||
4. **Hover/Focus**: `scaleY(1)` - background fills from bottom
|
||||
5. **Active**: `scaleY(0)` - background resets during press
|
||||
|
||||
This creates a smooth, directional fill animation that feels natural and responsive.
|
||||
|
||||
### Arrow Icon Animation
|
||||
|
||||
The arrow icon consists of two parts:
|
||||
1. **Horizontal Line**: Shrinks from right to left (`scaleX(0)`) on hover/focus
|
||||
2. **Chevron**: Stays visible, shifts right via increased gap
|
||||
|
||||
The gap between label and icon increases on hover/focus:
|
||||
- **Default**: 16px (desktop), 16px (mobile)
|
||||
- **Hover/Focus**: 22px (desktop), 21px (mobile)
|
||||
|
||||
This creates the illusion of the arrow "moving forward" as the line disappears.
|
||||
|
||||
### Padding Adjustments
|
||||
|
||||
On hover/focus, padding adjusts to accommodate the increased gap:
|
||||
- **Primary**: `8px 19px 8px 20px` → `8px 13px 8px 20px` (desktop)
|
||||
- **Secondary**: `6px 17px 6px 18px` → `6px 11px 6px 18px` (desktop)
|
||||
- **Tertiary**: `8px 20px` → `8px 14px 8px 20px` (desktop)
|
||||
|
||||
These adjustments maintain visual balance while allowing the icon animation to work smoothly.
|
||||
|
||||
### Responsive Behavior
|
||||
|
||||
The component adapts to screen size at the `1023px` breakpoint:
|
||||
|
||||
**Desktop (≥1024px):**
|
||||
- Larger padding values
|
||||
- 22px gap on hover/focus
|
||||
|
||||
**Tablet/Mobile (≤1023px):**
|
||||
- Reduced padding values
|
||||
- 21px gap on hover/focus
|
||||
|
||||
All transitions remain smooth across breakpoints.
|
||||
|
||||
## Typography
|
||||
|
||||
### Primary & Secondary Variants
|
||||
- **Font**: Booton, sans-serif
|
||||
- **Size**: 16px (Label R token)
|
||||
- **Weight**: 400
|
||||
- **Line Height**: 23.2px
|
||||
- **Letter Spacing**: 0px
|
||||
|
||||
### Tertiary Variant
|
||||
- **Font**: Booton, sans-serif
|
||||
- **Size**: 18px (Body R token)
|
||||
- **Weight**: 400
|
||||
- **Line Height**: 26.1px
|
||||
- **Letter Spacing**: -0.5px
|
||||
|
||||
## Spacing & Layout
|
||||
|
||||
- **Border Radius**: 100px (fully rounded)
|
||||
- **Max Height**: 40px
|
||||
- **Icon Size**: 15px × 14px
|
||||
- **Transitions**: 150ms with `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { Button } from 'shared/components/Button';
|
||||
|
||||
// Primary button (default)
|
||||
<Button onClick={handleClick}>
|
||||
Get Started
|
||||
</Button>
|
||||
|
||||
// Secondary button
|
||||
<Button variant="secondary" onClick={handleClick}>
|
||||
Learn More
|
||||
</Button>
|
||||
|
||||
// Tertiary button
|
||||
<Button variant="tertiary" onClick={handleClick}>
|
||||
View Details
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Form Integration
|
||||
|
||||
```tsx
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button variant="tertiary" type="reset">
|
||||
Reset
|
||||
</Button>
|
||||
<Button variant="tertiary" type="button" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Without Icon
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" showIcon={false} onClick={handleClick}>
|
||||
No Arrow
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Disabled State
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" disabled>
|
||||
Unavailable
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Color Themes
|
||||
|
||||
```tsx
|
||||
{/* Green theme (default) */}
|
||||
<Button variant="primary" color="green" onClick={handleClick}>
|
||||
Green Button
|
||||
</Button>
|
||||
|
||||
{/* Black theme */}
|
||||
<Button variant="primary" color="black" onClick={handleClick}>
|
||||
Black Button
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Link Buttons
|
||||
|
||||
```tsx
|
||||
{/* Internal navigation */}
|
||||
<Button variant="primary" href="/docs">
|
||||
View Documentation
|
||||
</Button>
|
||||
|
||||
{/* External link (opens in new tab) */}
|
||||
<Button variant="primary" href="https://xrpl.org" target="_blank">
|
||||
Visit XRPL.org
|
||||
</Button>
|
||||
|
||||
{/* Link with click tracking */}
|
||||
<Button
|
||||
variant="secondary"
|
||||
href="/signup"
|
||||
onClick={() => analytics.track('signup_button')}
|
||||
>
|
||||
Sign Up
|
||||
</Button>
|
||||
|
||||
{/* Black theme link button */}
|
||||
<Button variant="primary" color="black" href="/dashboard">
|
||||
Go to Dashboard
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Visual Hierarchy
|
||||
|
||||
```tsx
|
||||
{/* Use variants to create clear visual hierarchy */}
|
||||
<Button variant="primary" onClick={handlePrimaryAction}>
|
||||
Main Action
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleSecondaryAction}>
|
||||
Secondary Action
|
||||
</Button>
|
||||
<Button variant="tertiary" onClick={handleTertiaryAction}>
|
||||
Tertiary Action
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- **Tab**: Focus next button
|
||||
- **Shift+Tab**: Focus previous button
|
||||
- **Enter/Space**: Activate button
|
||||
- **Focus Indicator**: Visible outline/border (2px)
|
||||
- **Disabled buttons**: Not focusable
|
||||
|
||||
### Screen Reader Support
|
||||
|
||||
- Semantic `<button>` element (or `<a>` for link buttons)
|
||||
- Button label announced via `aria-label` attribute
|
||||
- `aria-disabled` attribute for disabled state
|
||||
- Icon marked with `aria-hidden="true"`
|
||||
- Link buttons use proper anchor semantics for navigation
|
||||
|
||||
### Color Contrast
|
||||
|
||||
All variants meet WCAG AA standards:
|
||||
- **Primary**: Black on Green 300 = sufficient contrast
|
||||
- **Secondary/Tertiary**: Green 400/500 on White = 4.52:1 / 5.12:1
|
||||
- **Disabled**: Gray 400/500 indicates non-interactive state
|
||||
|
||||
### Focus Management
|
||||
|
||||
- Focus outline appears on keyboard navigation (`:focus-visible`)
|
||||
- Focus styles match hover styles for consistency
|
||||
- Square corners on Tertiary focus outline for better visibility
|
||||
|
||||
## Design Tokens
|
||||
|
||||
The component uses design tokens from the XRPL Brand Design System:
|
||||
|
||||
### Colors
|
||||
- `$green-100` through `$green-500`
|
||||
- `$gray-200`, `$gray-400`, `$gray-500`
|
||||
- `$white`
|
||||
- Neutral black (`#141414`)
|
||||
|
||||
### Spacing
|
||||
- Border radius: `100px`
|
||||
- Focus border width: `2px`
|
||||
- Responsive breakpoint: `1023px`
|
||||
|
||||
### Motion
|
||||
- Transition duration: `150ms`
|
||||
- Timing function: `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Primary for main actions**: Reserve primary buttons for the most important action on a page
|
||||
2. **Use Secondary for supporting actions**: Use secondary buttons for actions that support the primary action
|
||||
3. **Use Tertiary for low-emphasis actions**: Use tertiary buttons for cancel, skip, or less important actions
|
||||
4. **Maintain visual hierarchy**: Don't use multiple primary buttons on the same page
|
||||
5. **Provide clear labels**: Button text should clearly indicate the action
|
||||
6. **Handle disabled states**: Always provide feedback when actions are unavailable
|
||||
7. **Test keyboard navigation**: Ensure all buttons are accessible via keyboard
|
||||
8. **Consider context**: Choose color theme based on background and design context
|
||||
9. **Use `href` for navigation**: When the button navigates to a new page, use the `href` prop instead of `onClick` with router navigation
|
||||
10. **Use `target="_blank"` for external links**: Always open external URLs in a new tab for better UX
|
||||
11. **Combine `href` with `onClick` for tracking**: When you need both navigation and analytics tracking
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Class Name Generation
|
||||
|
||||
The component builds class names dynamically:
|
||||
|
||||
```typescript
|
||||
const classNames = [
|
||||
'bds-btn',
|
||||
`bds-btn--${variant}`,
|
||||
`bds-btn--${color}`,
|
||||
disabled ? 'bds-btn--disabled' : '',
|
||||
!shouldShowIcon ? 'bds-btn--no-icon' : '',
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
```
|
||||
|
||||
### Icon Visibility Logic
|
||||
|
||||
The icon is automatically hidden when:
|
||||
- `showIcon={false}` is passed
|
||||
- `disabled={true}` is set
|
||||
|
||||
This ensures disabled buttons don't show interactive elements.
|
||||
|
||||
### Link Rendering Logic
|
||||
|
||||
The component conditionally renders as different elements:
|
||||
|
||||
```typescript
|
||||
// Render as Link when href is provided and not disabled
|
||||
if (href && !disabled) {
|
||||
return (
|
||||
<Link to={href} target={target} className={classNames}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise render as button
|
||||
return (
|
||||
<button type={type} className={classNames} disabled={disabled}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Link buttons use proper anchor semantics for navigation
|
||||
- Disabled state always renders as a button to prevent navigation
|
||||
- Visual styles remain consistent across both element types
|
||||
|
||||
### State Management
|
||||
|
||||
The component manages states through CSS classes and props:
|
||||
- **Disabled**: Controlled via `disabled` prop and `aria-disabled` attribute
|
||||
- **Hover/Focus**: Handled by CSS `:hover` and `:focus-visible` pseudo-classes
|
||||
- **Active**: Handled by CSS `:active` pseudo-class
|
||||
- **Link vs Button**: Determined by presence of `href` prop
|
||||
|
||||
## Browser Support
|
||||
|
||||
The component uses modern CSS features:
|
||||
- CSS Grid/Flexbox (widely supported)
|
||||
- `:focus-visible` (supported in modern browsers)
|
||||
- CSS transforms and transitions (widely supported)
|
||||
- CSS custom properties (supported in modern browsers)
|
||||
|
||||
For older browser support, consider polyfills or fallbacks as needed.
|
||||
|
||||
## Related Components
|
||||
|
||||
- See showcase pages for interactive examples:
|
||||
- `about/button-showcase-tertiary.page.tsx`
|
||||
- Other variant showcase pages
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
shared/components/Button/
|
||||
├── Button.tsx # Component implementation
|
||||
├── Button.scss # Component styles
|
||||
├── Button.md # This documentation
|
||||
└── index.ts # Exports
|
||||
```
|
||||
1594
shared/components/Button/Button.scss
Normal file
1594
shared/components/Button/Button.scss
Normal file
File diff suppressed because it is too large
Load Diff
167
shared/components/Button/Button.tsx
Normal file
167
shared/components/Button/Button.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Link } from '@redocly/theme/components/Link/Link';
|
||||
|
||||
export interface ButtonProps {
|
||||
/** Button variant - determines visual style */
|
||||
variant?: 'primary' | 'secondary' | 'tertiary';
|
||||
/** Color theme - green (default) or black */
|
||||
color?: 'green' | 'black';
|
||||
/** Button content/label */
|
||||
children: React.ReactNode;
|
||||
/** Click handler */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Button type attribute */
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
/** Whether to show the arrow icon */
|
||||
showIcon?: boolean;
|
||||
/** Accessibility label - defaults to button text if not provided */
|
||||
ariaLabel?: string;
|
||||
/** URL to navigate to - renders as a Link instead of button */
|
||||
href?: string;
|
||||
/** Link target - only applies when href is provided */
|
||||
target?: '_self' | '_blank';
|
||||
}
|
||||
|
||||
/**
|
||||
* Animated Arrow Icon Component
|
||||
* The horizontal line shrinks from left to right on hover/focus,
|
||||
* while the chevron shifts right via the gap change.
|
||||
*/
|
||||
const ArrowIcon: React.FC = () => (
|
||||
<svg
|
||||
className="bds-btn__icon"
|
||||
width="15"
|
||||
height="14"
|
||||
viewBox="0 0 15 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* Horizontal line - shrinks on hover */}
|
||||
<line
|
||||
className="bds-btn__icon-line"
|
||||
x1="0"
|
||||
y1="7"
|
||||
x2="14"
|
||||
y2="7"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.3"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
{/* Chevron - stays visible */}
|
||||
<path
|
||||
className="bds-btn__icon-chevron"
|
||||
d="M8.16755 1.16743L14.0005 7.00038L8.16755 12.8333"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.3"
|
||||
strokeMiterlimit="10"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* BDS Button Component
|
||||
*
|
||||
* A scalable button component following the XRPL Brand Design System.
|
||||
* Supports Primary, Secondary, and Tertiary variants with green (default) or black color themes.
|
||||
*
|
||||
* @example
|
||||
* <Button variant="primary" onClick={handleClick}>Get Started</Button>
|
||||
* <Button variant="secondary" onClick={handleClick}>Learn More</Button>
|
||||
* <Button variant="tertiary" onClick={handleClick}>View Details</Button>
|
||||
* <Button variant="primary" color="black" onClick={handleClick}>Dark Button</Button>
|
||||
*/
|
||||
/**
|
||||
* Helper function to extract text content from ReactNode
|
||||
*/
|
||||
const getTextFromChildren = (children: React.ReactNode): string => {
|
||||
if (typeof children === 'string' || typeof children === 'number') {
|
||||
return String(children);
|
||||
}
|
||||
if (Array.isArray(children)) {
|
||||
return children.map(getTextFromChildren).join('');
|
||||
}
|
||||
if (React.isValidElement(children)) {
|
||||
const props = children.props as { children?: React.ReactNode };
|
||||
if (props.children) {
|
||||
return getTextFromChildren(props.children);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
variant = 'primary',
|
||||
color = 'green',
|
||||
children,
|
||||
onClick,
|
||||
disabled = false,
|
||||
type = 'button',
|
||||
className = '',
|
||||
showIcon = true,
|
||||
ariaLabel,
|
||||
href,
|
||||
target = '_self',
|
||||
}) => {
|
||||
// Hide icon when disabled (per design spec)
|
||||
const shouldShowIcon = showIcon && !disabled;
|
||||
|
||||
// Default ariaLabel to button text if not provided
|
||||
const buttonAriaLabel = ariaLabel || getTextFromChildren(children);
|
||||
|
||||
// Build class names using BEM with bds namespace
|
||||
const classNames = clsx(
|
||||
'bds-btn',
|
||||
`bds-btn--${variant}`,
|
||||
`bds-btn--${color}`,
|
||||
{
|
||||
'bds-btn--disabled': disabled,
|
||||
'bds-btn--no-icon': !shouldShowIcon,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
// Inner content shared between button and link
|
||||
const content = (
|
||||
<>
|
||||
<span className="bds-btn__label">{children}</span>
|
||||
{shouldShowIcon && <ArrowIcon />}
|
||||
</>
|
||||
);
|
||||
|
||||
// Render as Link when href is provided
|
||||
if (href && !disabled) {
|
||||
return (
|
||||
<Link
|
||||
to={href}
|
||||
target={target}
|
||||
className={classNames}
|
||||
onClick={onClick}
|
||||
aria-label={buttonAriaLabel}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
className={classNames}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-disabled={disabled}
|
||||
aria-label={buttonAriaLabel}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
2
shared/components/Button/index.ts
Normal file
2
shared/components/Button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Button, type ButtonProps } from './Button';
|
||||
export { default } from './Button';
|
||||
167
shared/components/CardIcon/CardIcon.md
Normal file
167
shared/components/CardIcon/CardIcon.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# CardIcon Component
|
||||
|
||||
A clickable card component featuring an icon (top-left) and label text with arrow (bottom). Supports two color variants with responsive sizing that adapts at breakpoints.
|
||||
|
||||
## Features
|
||||
|
||||
- **Two Color Variants**: Neutral (gray) and Green
|
||||
- **Five Interaction States**: Default, Hover, Focused, Pressed, Disabled
|
||||
- **Responsive Sizing**: Automatically adapts at SM/MD/LG breakpoints
|
||||
- **Window Shade Animation**: Smooth hover effect with color wipe
|
||||
- **Accessible**: Full keyboard navigation and ARIA support
|
||||
- **Flexible Rendering**: Renders as `<a>` or `<button>` based on props
|
||||
|
||||
## Responsive Sizes
|
||||
|
||||
The component automatically adapts its dimensions based on viewport width:
|
||||
|
||||
| Breakpoint | Height | Icon Size | Padding |
|
||||
|------------|--------|-----------|---------|
|
||||
| SM (< 576px) | 136px | 56x56 | 8px |
|
||||
| MD (576px - 991px) | 140px | 60x60 | 12px |
|
||||
| LG (>= 992px) | 144px | 64x64 | 16px |
|
||||
|
||||
## Color States
|
||||
|
||||
### Light Mode
|
||||
|
||||
#### Neutral Variant
|
||||
- **Default**: Gray 200 (#E6EAF0) - black text
|
||||
- **Hover**: Gray 300 (#CAD4DF) - black text
|
||||
- **Focused**: Gray 300 + 2px black border - black text
|
||||
- **Pressed**: Gray 400 (#8A919A) - black text
|
||||
- **Disabled**: Gray 100 (#F0F3F7) - muted text (Gray 400), 50% icon opacity
|
||||
|
||||
#### Green Variant
|
||||
- **Default**: Green 200 (#70EE97) - black text
|
||||
- **Hover**: Green 300 (#21E46B) - black text
|
||||
- **Focused**: Green 300 + 2px black border - black text
|
||||
- **Pressed**: Green 400 (#0DAA3E) - black text
|
||||
- **Disabled**: Green 100 (#EAFCF1) - muted text (Gray 400), 50% icon opacity
|
||||
|
||||
### Dark Mode
|
||||
|
||||
#### Neutral Variant
|
||||
- **Default**: Gray 500 (#72777E) - white text
|
||||
- **Hover**: Gray 400 (#8A919A) - white text
|
||||
- **Focused**: Gray 400 + 2px white border - white text
|
||||
- **Pressed**: Gray 500 at 70% opacity - white text
|
||||
- **Disabled**: Gray 500 at 30% opacity - white text
|
||||
|
||||
#### Green Variant
|
||||
- **Default**: Green 300 (#21E46B) - black text
|
||||
- **Hover**: Green 200 (#70EE97) - black text
|
||||
- **Focused**: Green 200 + 2px white border - black text
|
||||
- **Pressed**: Green 400 (#0DAA3E) - black text
|
||||
- **Disabled**: Gray 500 at 30% opacity - white text
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `variant` | `'neutral' \| 'green'` | `'neutral'` | Color variant |
|
||||
| `icon` | `string` | *required* | Icon image source (URL or path) |
|
||||
| `iconAlt` | `string` | `''` | Alt text for the icon image |
|
||||
| `label` | `string` | *required* | Card label text |
|
||||
| `href` | `string` | - | Link destination (renders as `<a>`) |
|
||||
| `onClick` | `() => void` | - | Click handler (renders as `<button>`) |
|
||||
| `disabled` | `boolean` | `false` | Disabled state |
|
||||
| `className` | `string` | `''` | Additional CSS classes |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Link Card
|
||||
|
||||
```tsx
|
||||
import { CardIcon } from '@/shared/components/CardIcon';
|
||||
|
||||
<CardIcon
|
||||
variant="neutral"
|
||||
icon="/icons/javascript.svg"
|
||||
iconAlt="JavaScript logo"
|
||||
label="Get Started with Javascript"
|
||||
href="/docs/tutorials/javascript"
|
||||
/>
|
||||
```
|
||||
|
||||
### Green Variant with Click Handler
|
||||
|
||||
```tsx
|
||||
<CardIcon
|
||||
variant="green"
|
||||
icon="/icons/python.svg"
|
||||
iconAlt="Python logo"
|
||||
label="Python Tutorial"
|
||||
onClick={() => openTutorial('python')}
|
||||
/>
|
||||
```
|
||||
|
||||
### Disabled State
|
||||
|
||||
```tsx
|
||||
<CardIcon
|
||||
variant="neutral"
|
||||
icon="/icons/coming-soon.svg"
|
||||
label="Coming Soon"
|
||||
disabled
|
||||
/>
|
||||
```
|
||||
|
||||
### Within a Grid Layout
|
||||
|
||||
```tsx
|
||||
import { PageGrid, PageGridItem } from '@/shared/components/PageGrid';
|
||||
import { CardIcon } from '@/shared/components/CardIcon';
|
||||
|
||||
<PageGrid>
|
||||
<PageGridItem colSpan={{ sm: 2, md: 4, lg: 3 }}>
|
||||
<CardIcon
|
||||
variant="neutral"
|
||||
icon="/icons/javascript.svg"
|
||||
label="JavaScript"
|
||||
href="/docs/javascript"
|
||||
/>
|
||||
</PageGridItem>
|
||||
<PageGridItem colSpan={{ sm: 2, md: 4, lg: 3 }}>
|
||||
<CardIcon
|
||||
variant="green"
|
||||
icon="/icons/python.svg"
|
||||
label="Python"
|
||||
href="/docs/python"
|
||||
/>
|
||||
</PageGridItem>
|
||||
</PageGrid>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Uses semantic `<a>` or `<button>` elements based on interaction type
|
||||
- Includes `aria-label` for screen readers
|
||||
- Supports keyboard navigation (Tab, Enter, Space)
|
||||
- Focus states meet WCAG 2.1 AA contrast requirements
|
||||
- Disabled state includes `aria-disabled` attribute
|
||||
|
||||
## CSS Classes (BEM)
|
||||
|
||||
| Class | Description |
|
||||
|-------|-------------|
|
||||
| `.bds-card-icon` | Base card styles |
|
||||
| `.bds-card-icon--neutral` | Neutral color variant |
|
||||
| `.bds-card-icon--green` | Green color variant |
|
||||
| `.bds-card-icon--hovered` | Hover state (JS-controlled) |
|
||||
| `.bds-card-icon--disabled` | Disabled state |
|
||||
| `.bds-card-icon__overlay` | Hover animation overlay |
|
||||
| `.bds-card-icon__icon` | Icon container |
|
||||
| `.bds-card-icon__icon-img` | Icon image |
|
||||
| `.bds-card-icon__content` | Bottom content row |
|
||||
| `.bds-card-icon__label` | Text label |
|
||||
| `.bds-card-icon__arrow` | Arrow icon wrapper |
|
||||
|
||||
## Design Tokens
|
||||
|
||||
The component uses these design tokens from the style system:
|
||||
|
||||
- **Colors**: `$gray-100` through `$gray-400`, `$green-100` through `$green-400`
|
||||
- **Typography**: `body-r` token from `_font.scss`
|
||||
- **Breakpoints**: `$grid-breakpoints` from `_breakpoints.scss`
|
||||
- **Animation**: `cubic-bezier(0.98, 0.12, 0.12, 0.98)` timing function
|
||||
521
shared/components/CardIcon/CardIcon.scss
Normal file
521
shared/components/CardIcon/CardIcon.scss
Normal file
@@ -0,0 +1,521 @@
|
||||
// BDS CardIcon Component Styles
|
||||
// Brand Design System - Icon card component with responsive sizing
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-card-icon - Base card (responsive layout)
|
||||
// .bds-card-icon--neutral - Neutral color variant (gray tones)
|
||||
// .bds-card-icon--green - Green color variant
|
||||
// .bds-card-icon--hovered - Hovered state (triggers overlay animation)
|
||||
// .bds-card-icon--disabled - Disabled state modifier
|
||||
// .bds-card-icon__overlay - Hover gradient overlay (window shade animation)
|
||||
// .bds-card-icon__icon - Icon container (responsive size)
|
||||
// .bds-card-icon__icon-img - Icon image element
|
||||
// .bds-card-icon__content - Bottom content row
|
||||
// .bds-card-icon__label - Text label
|
||||
// .bds-card-icon__arrow - Arrow icon wrapper
|
||||
|
||||
@import '../../../styles/breakpoints';
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens
|
||||
// =============================================================================
|
||||
|
||||
// Focus border
|
||||
$bds-card-icon-focus-border-color: $black;
|
||||
$bds-card-icon-focus-border-width: 2px;
|
||||
|
||||
// Animation (matching TileLogo/CardOffgrid)
|
||||
$bds-card-icon-transition-duration: 200ms;
|
||||
$bds-card-icon-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Responsive Size Tokens
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// SM breakpoint (mobile - default)
|
||||
$bds-card-icon-height-sm: 136px;
|
||||
$bds-card-icon-padding-sm: 8px;
|
||||
$bds-card-icon-icon-size-sm: 56px;
|
||||
|
||||
// MD breakpoint (tablet)
|
||||
$bds-card-icon-height-md: 140px;
|
||||
$bds-card-icon-padding-md: 12px;
|
||||
$bds-card-icon-icon-size-md: 60px;
|
||||
|
||||
// LG breakpoint (desktop)
|
||||
$bds-card-icon-height-lg: 144px;
|
||||
$bds-card-icon-padding-lg: 16px;
|
||||
$bds-card-icon-icon-size-lg: 64px;
|
||||
|
||||
// =============================================================================
|
||||
// Base Card Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon {
|
||||
// Reset button/anchor styles
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
text-align: left;
|
||||
|
||||
// Layout
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
// Responsive sizing - SM (mobile-first)
|
||||
height: $bds-card-icon-height-sm;
|
||||
padding: $bds-card-icon-padding-sm;
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, md)) {
|
||||
height: $bds-card-icon-height-md;
|
||||
padding: $bds-card-icon-padding-md;
|
||||
}
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, lg)) {
|
||||
height: $bds-card-icon-height-lg;
|
||||
padding: $bds-card-icon-padding-lg;
|
||||
}
|
||||
|
||||
// Interaction
|
||||
cursor: pointer;
|
||||
|
||||
// Transitions
|
||||
transition:
|
||||
background-color $bds-card-icon-transition-duration $bds-card-icon-transition-timing,
|
||||
opacity $bds-card-icon-transition-duration $bds-card-icon-transition-timing;
|
||||
|
||||
// Hover styles - prevent text underline
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Focus styles
|
||||
&:focus {
|
||||
outline: $bds-card-icon-focus-border-width solid $bds-card-icon-focus-border-color;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: $bds-card-icon-focus-border-width solid $bds-card-icon-focus-border-color;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Overlay (Color wipe animation - "Window Shade" effect)
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
|
||||
// Default: hidden (shade is "rolled up" at bottom)
|
||||
clip-path: inset(100% 0 0 0);
|
||||
transition: clip-path $bds-card-icon-transition-duration $bds-card-icon-transition-timing;
|
||||
}
|
||||
|
||||
// Hovered state: shade fully raised (visible)
|
||||
.bds-card-icon--hovered .bds-card-icon__overlay {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Icon Container
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon__icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
// Responsive icon size - SM
|
||||
width: $bds-card-icon-icon-size-sm;
|
||||
height: $bds-card-icon-icon-size-sm;
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, md)) {
|
||||
width: $bds-card-icon-icon-size-md;
|
||||
height: $bds-card-icon-icon-size-md;
|
||||
}
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, lg)) {
|
||||
width: $bds-card-icon-icon-size-lg;
|
||||
height: $bds-card-icon-icon-size-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Content Row (Bottom)
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bds-card-icon__label {
|
||||
// Typography from Figma - Body R token
|
||||
font-family: $font-family-sans-serif;
|
||||
font-weight: 400;
|
||||
color: $black;
|
||||
margin-bottom: 0;
|
||||
|
||||
// SM/MD breakpoint (mobile-first)
|
||||
font-size: 16px;
|
||||
line-height: 26.1px;
|
||||
letter-spacing: 0px;
|
||||
|
||||
// LG breakpoint (desktop)
|
||||
@media (min-width: map-get($grid-breakpoints, lg)) {
|
||||
font-size: 18px;
|
||||
line-height: 26.1px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-card-icon__arrow {
|
||||
flex-shrink: 0;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
// Arrow animation on hover - works for both <a> and <button> elements
|
||||
.bds-card-icon:hover .bds-card-icon__arrow .bds-link-icon--internal:not(.bds-link-icon--disabled) svg .arrow-horizontal,
|
||||
.bds-card-icon:focus .bds-card-icon__arrow .bds-link-icon--internal:not(.bds-link-icon--disabled) svg .arrow-horizontal,
|
||||
.bds-card-icon--hovered .bds-card-icon__arrow .bds-link-icon--internal:not(.bds-link-icon--disabled) svg .arrow-horizontal {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
.bds-card-icon:hover .bds-card-icon__arrow .bds-link-icon--external:not(.bds-link-icon--disabled) svg .arrow-horizontal,
|
||||
.bds-card-icon:focus .bds-card-icon__arrow .bds-link-icon--external:not(.bds-link-icon--disabled) svg .arrow-horizontal,
|
||||
.bds-card-icon--hovered .bds-card-icon__arrow .bds-link-icon--external:not(.bds-link-icon--disabled) svg .arrow-horizontal {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Neutral Variant
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon--neutral {
|
||||
background-color: $gray-200;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $gray-300;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $gray-400;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Green Variant
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon--green {
|
||||
background-color: $green-200;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-300;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-400;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Disabled State
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-icon--disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// Neutral disabled
|
||||
&.bds-card-icon--neutral {
|
||||
background-color: $gray-100;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Green disabled
|
||||
&.bds-card-icon--green {
|
||||
background-color: $green-100;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dark Mode Styles (html.dark)
|
||||
// =============================================================================
|
||||
// Dark mode uses different color palette per Figma specs:
|
||||
// - Neutral: gray-500 base with white text
|
||||
// - Green: green-300 base with black text
|
||||
// - Focus border: white
|
||||
// - Pressed: 70% opacity
|
||||
// - Disabled: 30% opacity
|
||||
|
||||
html.dark {
|
||||
// Focus styles - white border in dark mode
|
||||
.bds-card-icon {
|
||||
&:focus {
|
||||
outline-color: $white;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Neutral Variant - Dark Mode
|
||||
// Default: gray-500, Hover: gray-400, Pressed: 70% gray-500, Disabled: 30% opacity
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--neutral {
|
||||
background-color: $gray-500;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $gray-400;
|
||||
}
|
||||
|
||||
// Pressed state - 70% opacity background
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: rgba($gray-500, 0.7);
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Green Variant - Dark Mode
|
||||
// Default: green-300, Hover: green-200, Pressed: green-400, Disabled: 30% gray-500
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--green {
|
||||
background-color: $green-300;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-200;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-400;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Disabled State - Dark Mode
|
||||
// Both variants: 30% opacity with white text
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--disabled {
|
||||
opacity: 0.3;
|
||||
|
||||
&.bds-card-icon--neutral {
|
||||
background-color: $gray-500;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 1; // Reset since parent has opacity
|
||||
}
|
||||
}
|
||||
|
||||
&.bds-card-icon--green {
|
||||
background-color: $gray-500;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 1; // Reset since parent has opacity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Light Mode Styles (html.light)
|
||||
// =============================================================================
|
||||
// Light mode matches the default styles (mobile-first approach)
|
||||
// Explicitly defined for specificity when html.light class is present
|
||||
|
||||
html.light {
|
||||
// Focus styles - black border in light mode
|
||||
.bds-card-icon {
|
||||
&:focus {
|
||||
outline-color: $black;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Neutral Variant - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--neutral {
|
||||
background-color: $gray-200;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $gray-300;
|
||||
}
|
||||
|
||||
// Maintain background color on focus (override generic link focus styles)
|
||||
&:focus {
|
||||
background-color: $gray-200 !important;
|
||||
}
|
||||
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $gray-400;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Green Variant - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--green {
|
||||
background-color: $green-200;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-300;
|
||||
}
|
||||
|
||||
// Maintain background color on focus (override generic link focus styles)
|
||||
&:focus {
|
||||
background-color: $green-200 !important;
|
||||
}
|
||||
|
||||
&:active:not(.bds-card-icon--disabled) {
|
||||
.bds-card-icon__overlay {
|
||||
background-color: $green-400;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Disabled State - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-icon--disabled {
|
||||
opacity: 1; // Reset opacity for light mode
|
||||
|
||||
&.bds-card-icon--neutral {
|
||||
background-color: $gray-100;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.bds-card-icon--green {
|
||||
background-color: $green-100;
|
||||
|
||||
.bds-card-icon__label,
|
||||
.bds-card-icon__arrow {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
.bds-card-icon__icon-img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
shared/components/CardIcon/CardIcon.tsx
Normal file
150
shared/components/CardIcon/CardIcon.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React, { useState } from 'react';
|
||||
import { LinkArrow } from '../Link/LinkArrow';
|
||||
|
||||
export interface CardIconProps {
|
||||
/** Color variant: 'neutral' (default) or 'green' */
|
||||
variant?: 'neutral' | 'green';
|
||||
/** Icon image source (URL or path) */
|
||||
icon: string;
|
||||
/** Alt text for the icon image */
|
||||
iconAlt?: string;
|
||||
/** Card label text */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* CardIcon Component
|
||||
*
|
||||
* A clickable card component featuring an icon (top-left) and label text with
|
||||
* arrow (bottom). Supports two color variants (Neutral and Green) with five
|
||||
* interaction states (Default, Hover, Focused, Pressed, Disabled).
|
||||
*
|
||||
* The component is fully responsive, adapting its size at breakpoints:
|
||||
* - SM (< 576px): 136px height, 56x56 icon, 8px padding
|
||||
* - MD (576px - 991px): 140px height, 60x60 icon, 12px padding
|
||||
* - LG (>= 992px): 144px height, 64x64 icon, 16px padding
|
||||
*
|
||||
* Features a "window shade" hover animation where the hover color wipes from
|
||||
* bottom to top on mouse enter, and top to bottom on mouse leave.
|
||||
*
|
||||
* @example
|
||||
* // Basic usage with link
|
||||
* <CardIcon
|
||||
* variant="neutral"
|
||||
* icon="/icons/javascript.svg"
|
||||
* label="Get Started with Javascript"
|
||||
* href="/docs/javascript"
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Green variant with click handler
|
||||
* <CardIcon
|
||||
* variant="green"
|
||||
* icon="/icons/python.svg"
|
||||
* label="Python Tutorial"
|
||||
* onClick={() => openTutorial('python')}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Disabled state
|
||||
* <CardIcon
|
||||
* variant="neutral"
|
||||
* icon="/icons/coming-soon.svg"
|
||||
* label="Coming Soon"
|
||||
* disabled
|
||||
* />
|
||||
*/
|
||||
export const CardIcon: React.FC<CardIconProps> = ({
|
||||
variant = 'neutral',
|
||||
icon,
|
||||
iconAlt = '',
|
||||
label,
|
||||
href,
|
||||
onClick,
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
// Track hover state for animation
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
// Build class names using BEM convention
|
||||
const classNames = [
|
||||
'bds-card-icon',
|
||||
`bds-card-icon--${variant}`,
|
||||
disabled ? 'bds-card-icon--disabled' : '',
|
||||
isHovered && !disabled ? 'bds-card-icon--hovered' : '',
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
// Hover handlers
|
||||
const handleMouseEnter = () => !disabled && setIsHovered(true);
|
||||
const handleMouseLeave = () => setIsHovered(false);
|
||||
|
||||
// Common content
|
||||
const content = (
|
||||
<>
|
||||
{/* Hover overlay for window shade animation */}
|
||||
<div className="bds-card-icon__overlay" aria-hidden="true" />
|
||||
|
||||
{/* Icon container */}
|
||||
<div className="bds-card-icon__icon">
|
||||
<img
|
||||
src={icon}
|
||||
alt={iconAlt}
|
||||
className="bds-card-icon__icon-img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bottom content row */}
|
||||
<div className="bds-card-icon__content">
|
||||
<span className="bds-card-icon__label">{label}</span>
|
||||
<span className="bds-card-icon__arrow">
|
||||
<LinkArrow variant="internal" size="medium" />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// Render as anchor tag when href is provided
|
||||
if (href && !disabled) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classNames}
|
||||
aria-label={label}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Render as button (for onClick or disabled state)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
aria-disabled={disabled}
|
||||
aria-label={label}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardIcon;
|
||||
2
shared/components/CardIcon/index.ts
Normal file
2
shared/components/CardIcon/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CardIcon, type CardIconProps } from './CardIcon';
|
||||
export { default } from './CardIcon';
|
||||
243
shared/components/CardImage/CardImage.md
Normal file
243
shared/components/CardImage/CardImage.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# CardImage Component Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The CardImage component is a responsive card implementation following the XRPL Brand Design System (BDS). It displays an image, title, subtitle, and call-to-action button with three responsive size variants that adapt to viewport width.
|
||||
|
||||
## Features
|
||||
|
||||
- **Three Responsive Variants**: LG (≥992px), MD (576px-991px), SM (<576px)
|
||||
- **Interactive States**: Default, Hover, Focus, Pressed, Disabled
|
||||
- **Button Animation on Card Hover**: Hovering the card triggers the button's hover animation
|
||||
- **Flexible Usage**: Supports both link navigation and click handlers
|
||||
- **Accessibility**: Keyboard navigation and screen reader support
|
||||
|
||||
## Props API
|
||||
|
||||
```typescript
|
||||
interface CardImageProps {
|
||||
/** Image source URL */
|
||||
image: string;
|
||||
/** Alt text for the image */
|
||||
imageAlt: string;
|
||||
/** Card title (1 line only) */
|
||||
title: string;
|
||||
/** Card subtitle (max 3 lines) */
|
||||
subtitle: string;
|
||||
/** Button label text */
|
||||
buttonLabel: string;
|
||||
/** Link destination (renders card as clickable link) */
|
||||
href?: string;
|
||||
/** Click handler for the button */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Optional className for custom styling */
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
- `disabled`: `false`
|
||||
- `className`: `''`
|
||||
|
||||
## Responsive Variants
|
||||
|
||||
### LG (Large) - Desktop (≥992px)
|
||||
|
||||
- Card height: 620px
|
||||
- Image height: 400px (1:1 aspect ratio preserved)
|
||||
- 3-column grid width
|
||||
|
||||
### MD (Medium) - Tablet (576px - 991px)
|
||||
|
||||
- Card height: 560px
|
||||
- Image height: 280px
|
||||
- 2-column grid width
|
||||
|
||||
### SM (Small) - Mobile (<576px)
|
||||
|
||||
- Card height: 536px
|
||||
- Image height: 268px
|
||||
- 1-column grid width (full width)
|
||||
|
||||
## States
|
||||
|
||||
### Default State
|
||||
|
||||
The default interactive state with the button showing its primary green background.
|
||||
|
||||
### Hover State
|
||||
|
||||
When the user hovers over the card:
|
||||
- Button background fills from bottom-to-top (green-300 → green-200)
|
||||
- Arrow icon line shrinks
|
||||
- Gap between label and icon increases
|
||||
|
||||
### Focus State
|
||||
|
||||
When the card receives keyboard focus:
|
||||
- Black border outline around the card
|
||||
- Button shows focus styling
|
||||
|
||||
### Pressed State
|
||||
|
||||
When the button is being pressed:
|
||||
- Button returns to default styling momentarily
|
||||
|
||||
### Disabled State
|
||||
|
||||
When `disabled={true}`:
|
||||
- Card is non-interactive
|
||||
- Button shows disabled styling (gray background, no icon)
|
||||
- Text colors muted
|
||||
- Cursor changes to `not-allowed`
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Card with Link
|
||||
|
||||
```tsx
|
||||
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 working with the XRPL."
|
||||
buttonLabel="Get Started"
|
||||
href="/docs"
|
||||
/>
|
||||
```
|
||||
|
||||
### Card with Click Handler
|
||||
|
||||
```tsx
|
||||
<CardImage
|
||||
image="/images/feature.png"
|
||||
imageAlt="Feature illustration"
|
||||
title="New Feature"
|
||||
subtitle="Learn about our latest feature and how it can help you build better applications."
|
||||
buttonLabel="Learn More"
|
||||
onClick={() => console.log('clicked')}
|
||||
/>
|
||||
```
|
||||
|
||||
### Disabled Card
|
||||
|
||||
```tsx
|
||||
<CardImage
|
||||
image="/images/coming-soon.png"
|
||||
imageAlt="Coming soon"
|
||||
title="Coming Soon"
|
||||
subtitle="This feature is not yet available."
|
||||
buttonLabel="Unavailable"
|
||||
disabled
|
||||
/>
|
||||
```
|
||||
|
||||
### Card with Custom Class
|
||||
|
||||
```tsx
|
||||
<CardImage
|
||||
image="/images/hero.png"
|
||||
imageAlt="Hero image"
|
||||
title="Custom Styled"
|
||||
subtitle="Card with additional custom styling."
|
||||
buttonLabel="Explore"
|
||||
href="/explore"
|
||||
className="my-custom-card"
|
||||
/>
|
||||
```
|
||||
|
||||
## Design Tokens
|
||||
|
||||
### Spacing
|
||||
|
||||
| Token | Value | Description |
|
||||
|-------|-------|-------------|
|
||||
| Image-to-content gap | 24px | Gap between image and content area |
|
||||
| Title-to-subtitle gap | 12px | Gap between title and subtitle |
|
||||
| Content horizontal padding | 8px | Left/right padding for content |
|
||||
| Button margin-top | 30px | Button locked to bottom |
|
||||
| Border radius | 16px | Card corner radius |
|
||||
|
||||
### Colors
|
||||
|
||||
| Element | Light Mode | Dark Mode |
|
||||
|---------|------------|-----------|
|
||||
| Card background | #FFFFFF | Gray 800 |
|
||||
| Card border | Gray 300 (#CAD4DF) | Gray 700 |
|
||||
| Image background | Gray 100 (#F0F3F7) | Gray 700 |
|
||||
| Text color | #141414 | White |
|
||||
|
||||
### Typography
|
||||
|
||||
| Element | Token | Specs |
|
||||
|---------|-------|-------|
|
||||
| Title | `.sh-md-l` | 28px/35px, -0.5px letter-spacing, light weight |
|
||||
| Subtitle | `.body-l` | 18px/26.1px, -0.5px letter-spacing, light weight |
|
||||
|
||||
## How It Works
|
||||
|
||||
### Hover Animation
|
||||
|
||||
The card tracks hover state via React's `useState`. When hovered:
|
||||
1. A `bds-card-image--hovered` class is added to the card
|
||||
2. CSS rules target the nested Button component and apply hover styles
|
||||
3. The button's `::before` pseudo-element animates (background fill)
|
||||
4. The arrow icon line shrinks via `scaleX(0)`
|
||||
|
||||
This approach ensures the button animation triggers even when hovering areas of the card outside the button itself.
|
||||
|
||||
### Link Navigation
|
||||
|
||||
When `href` is provided:
|
||||
- The entire card becomes clickable
|
||||
- Clicking anywhere on the card navigates to the href
|
||||
- The card is keyboard accessible (Enter/Space to activate)
|
||||
|
||||
### Button Integration
|
||||
|
||||
The component uses the existing BDS Button component with:
|
||||
- `variant="primary"` - Solid green background
|
||||
- `color="green"` - Green color theme
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- **Tab**: Focus the card
|
||||
- **Enter/Space**: Activate the card (navigate to href or trigger onClick)
|
||||
|
||||
### Screen Reader Support
|
||||
|
||||
- Card has appropriate `role="link"` when href is provided
|
||||
- Image has descriptive alt text
|
||||
- `aria-disabled` attribute for disabled state
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
shared/components/CardImage/
|
||||
├── CardImage.tsx # Component implementation
|
||||
├── CardImage.scss # Component styles
|
||||
├── CardImage.md # This documentation
|
||||
└── index.ts # Exports
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- **Button**: Used internally for the CTA button
|
||||
- **CardOffgrid**: Similar card component with different layout
|
||||
|
||||
## Browser Support
|
||||
|
||||
The component uses modern CSS features:
|
||||
- CSS Grid/Flexbox
|
||||
- CSS transforms and transitions
|
||||
- `:focus-visible` pseudo-class
|
||||
- `-webkit-line-clamp` for text truncation
|
||||
|
||||
All features are widely supported in modern browsers.
|
||||
351
shared/components/CardImage/CardImage.scss
Normal file
351
shared/components/CardImage/CardImage.scss
Normal file
@@ -0,0 +1,351 @@
|
||||
// BDS CardImage Component Styles
|
||||
// Brand Design System - Responsive card with image, title, subtitle, and CTA button
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-card-image - Base card container
|
||||
// .bds-card-image--hovered - Hover state (scales image only)
|
||||
// .bds-card-image--disabled - Disabled state
|
||||
// .bds-card-image__image-container - Gray background image wrapper
|
||||
// .bds-card-image__image - The actual image element
|
||||
// .bds-card-image__content - Title/subtitle/button wrapper
|
||||
// .bds-card-image__text - Title and subtitle wrapper
|
||||
// .bds-card-image__title - Card title (uses .sh-md-r)
|
||||
// .bds-card-image__subtitle - Card subtitle (uses .body-l)
|
||||
//
|
||||
// Note: This file is imported within xrpl.scss after Bootstrap and project
|
||||
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens (from Figma)
|
||||
// =============================================================================
|
||||
|
||||
// Card dimensions - LG (Large) variant (default, ≥992px)
|
||||
$bds-card-image-height-lg: 620px;
|
||||
$bds-card-image-image-height-lg: 400px;
|
||||
|
||||
// Card dimensions - MD (Medium) variant (576px - 991px)
|
||||
$bds-card-image-height-md: 560px;
|
||||
$bds-card-image-image-height-md: 280px;
|
||||
|
||||
// Card dimensions - SM (Small) variant (<576px)
|
||||
$bds-card-image-height-sm: 536px;
|
||||
$bds-card-image-image-height-sm: 268px;
|
||||
|
||||
// Spacing - LG (Large) variant (default, ≥992px)
|
||||
$bds-card-image-gap-lg: 24px; // Gap between image and title
|
||||
$bds-card-image-title-gap-lg: 12px; // Gap between title and subtitle
|
||||
$bds-card-image-content-padding: 8px; // Horizontal padding for content (same for all)
|
||||
$bds-card-image-button-margin-lg: 30px; // Margin above button
|
||||
|
||||
// Spacing - MD (Medium) variant (576px - 991px)
|
||||
$bds-card-image-gap-md: 20px; // Gap between image and title
|
||||
$bds-card-image-title-gap-md: 8px; // Gap between title and subtitle
|
||||
$bds-card-image-button-margin-md: 34px; // Margin above button
|
||||
|
||||
// Spacing - SM (Small) variant (<576px)
|
||||
$bds-card-image-gap-sm: 16px; // Gap between image and title
|
||||
$bds-card-image-title-gap-sm: 4px; // Gap between title and subtitle
|
||||
$bds-card-image-button-margin-sm: 35px; // Margin above button
|
||||
|
||||
// Colors
|
||||
$bds-card-image-bg: $white;
|
||||
$bds-card-image-image-bg: $gray-100;
|
||||
$bds-card-image-text-color: #141414; // Neutral black from Figma
|
||||
|
||||
// Animation
|
||||
$bds-card-image-transition-duration: 150ms;
|
||||
$bds-card-image-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
|
||||
|
||||
// =============================================================================
|
||||
// Base Card Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-image {
|
||||
// Card container
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $bds-card-image-gap-lg; // Gap between image container and content
|
||||
width: 100%; // Fill available width (4-column span on LG+)
|
||||
height: $bds-card-image-height-lg;
|
||||
background-color: $bds-card-image-bg;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
// When inside a grid column, fill the column width
|
||||
.bds-grid__col & {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Focus styles
|
||||
&:focus {
|
||||
outline: 2px solid $bds-card-image-text-color;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $bds-card-image-text-color;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Image Container
|
||||
// =============================================================================
|
||||
// Image container has fixed height per breakpoint.
|
||||
// Image within maintains its aspect ratio using object-fit: contain.
|
||||
|
||||
.bds-card-image__image-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: $bds-card-image-image-height-lg;
|
||||
background-color: var(--bds-card-image-bg, $bds-card-image-image-bg);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bds-card-image__image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain; // Maintain image aspect ratio (1:1 for this design)
|
||||
object-position: center;
|
||||
transition: transform $bds-card-image-transition-duration $bds-card-image-transition-timing;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Full Bleed Modifier
|
||||
// =============================================================================
|
||||
// When fullBleed is true, image fills entire container with object-fit: cover
|
||||
|
||||
.bds-card-image--full-bleed {
|
||||
.bds-card-image__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Content Area
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-image__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between; // Distribute space between text and button
|
||||
flex: 1;
|
||||
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
||||
padding-bottom: $bds-card-image-content-padding;
|
||||
min-height: 0; // Allow flex shrinking
|
||||
}
|
||||
|
||||
.bds-card-image__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $bds-card-image-title-gap-lg;
|
||||
flex-shrink: 0; // Don't shrink text container
|
||||
}
|
||||
|
||||
.bds-card-image__title {
|
||||
// Typography handled by .sh-md-r class from _font.scss
|
||||
color: $bds-card-image-text-color;
|
||||
margin: 0;
|
||||
text-align: left; // Always left-align, regardless of parent
|
||||
// Title should be 1 line only
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bds-card-image__subtitle {
|
||||
// Typography handled by .body-l class from _font.scss
|
||||
color: $bds-card-image-text-color;
|
||||
margin: 0;
|
||||
text-align: left; // Always left-align, regardless of parent
|
||||
// Subtitle max 3 lines
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Button Positioning
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-image__content .bds-btn {
|
||||
align-self: flex-start;
|
||||
flex-shrink: 0; // Don't shrink button
|
||||
// Button positioned at bottom via justify-content: space-between on parent
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hover State - Image Animation Only
|
||||
// =============================================================================
|
||||
// When the card is hovered, scale the image (button hover state is independent)
|
||||
|
||||
.bds-card-image--hovered:not(.bds-card-image--disabled),
|
||||
.bds-card-image:hover:not(.bds-card-image--disabled) {
|
||||
// Scale image by 10% on hover
|
||||
.bds-card-image__image {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Focus State
|
||||
// =============================================================================
|
||||
// Focus styles are handled by base card styles (outline)
|
||||
// Button focus state is independent
|
||||
|
||||
// =============================================================================
|
||||
// Disabled State
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-image--disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
|
||||
.bds-card-image__title,
|
||||
.bds-card-image__subtitle {
|
||||
color: $gray-500;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Styles - MD (Medium) Variant
|
||||
// =============================================================================
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
.bds-card-image {
|
||||
height: $bds-card-image-height-md;
|
||||
gap: $bds-card-image-gap-md; // Gap between image container and content for MD
|
||||
}
|
||||
|
||||
.bds-card-image__image-container {
|
||||
height: $bds-card-image-image-height-md;
|
||||
}
|
||||
|
||||
.bds-card-image__content {
|
||||
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
||||
}
|
||||
|
||||
.bds-card-image__text {
|
||||
gap: $bds-card-image-title-gap-md;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Styles - SM (Small) Variant
|
||||
// =============================================================================
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.bds-card-image {
|
||||
height: $bds-card-image-height-sm;
|
||||
gap: $bds-card-image-gap-sm; // Gap between image container and content for SM
|
||||
}
|
||||
|
||||
.bds-card-image__image-container {
|
||||
height: $bds-card-image-image-height-sm;
|
||||
}
|
||||
|
||||
.bds-card-image__content {
|
||||
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
||||
}
|
||||
|
||||
.bds-card-image__text {
|
||||
gap: $bds-card-image-title-gap-sm;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Light Mode Styles
|
||||
// =============================================================================
|
||||
|
||||
html.light {
|
||||
.bds-card-image {
|
||||
&:focus {
|
||||
outline-color: $bds-card-image-text-color;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: $bds-card-image-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dark Mode Styles (from Figma design tokens)
|
||||
// =============================================================================
|
||||
// Design tokens:
|
||||
// - Card background: neutral/black (#141414) → $gray-900
|
||||
// - Image container: neutral/500 (#72777E) → $gray-500
|
||||
// - Title: neutral/white (#FFFFFF) → $white
|
||||
// - Subtitle: neutral/200 (#E6EAF0) → $gray-200
|
||||
// - Focus outline: neutral/white (#FFFFFF) → $white
|
||||
// - Hover overlay: 15% black (rgba(114,119,126,0.15))
|
||||
// - Pressed overlay: 45% black (rgba(114,119,126,0.45))
|
||||
// - Disabled button bg: neutral/500 (#72777E) → $gray-500
|
||||
// - Disabled button text: neutral/300 (#CAD4DF) → $gray-300
|
||||
|
||||
html.dark {
|
||||
.bds-card-image {
|
||||
background-color: $gray-900;
|
||||
border-color: $gray-900; // Border blends with background in dark mode
|
||||
|
||||
// Focus styles - white outline
|
||||
&:focus {
|
||||
outline: 2px solid $white;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $white;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Image container - neutral/500 (#72777E)
|
||||
.bds-card-image__image-container {
|
||||
background-color: var(--bds-card-image-bg, $gray-500);
|
||||
}
|
||||
|
||||
// Title - neutral/white (#FFFFFF)
|
||||
.bds-card-image__title {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// Subtitle - neutral/200 (#E6EAF0)
|
||||
.bds-card-image__subtitle {
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dark Mode - Disabled State
|
||||
// ---------------------------------------------------------------------------
|
||||
.bds-card-image--disabled {
|
||||
// Text colors remain but muted
|
||||
.bds-card-image__title {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.bds-card-image__subtitle {
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
// Button in disabled state uses gray-500 bg and gray-300 text
|
||||
// (handled by Button component's disabled styles)
|
||||
}
|
||||
}
|
||||
185
shared/components/CardImage/CardImage.tsx
Normal file
185
shared/components/CardImage/CardImage.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Button } from '../Button';
|
||||
|
||||
export interface CardImageProps {
|
||||
/** Image source URL */
|
||||
image: string;
|
||||
/** Alt text for the image */
|
||||
imageAlt: string;
|
||||
/** Card title (1 line only) */
|
||||
title: string;
|
||||
/** Card subtitle (max 3 lines) */
|
||||
subtitle: string;
|
||||
/** Button label text */
|
||||
buttonLabel: string;
|
||||
/** Link destination (renders card as clickable link) */
|
||||
href?: string;
|
||||
/** Click handler for the button */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Optional className for custom styling */
|
||||
className?: string;
|
||||
/** When true, image fills entire container with object-fit: cover (no visible background) */
|
||||
fullBleed?: boolean;
|
||||
/** Custom background color for image container (defaults to gray-100) */
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* BDS CardImage Component
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Key behaviors:
|
||||
* - Hovering the card triggers the button's hover animation
|
||||
* - Card can link to a URL or trigger a click handler
|
||||
* - Supports disabled state
|
||||
*
|
||||
* @example
|
||||
* // Basic card with link
|
||||
* <CardImage
|
||||
* image="/images/docs-hero.png"
|
||||
* imageAlt="Documentation illustration"
|
||||
* title="Documentation"
|
||||
* subtitle="Access everything you need to get started working with the XRPL."
|
||||
* buttonLabel="Get Started"
|
||||
* href="/docs"
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Card with click handler
|
||||
* <CardImage
|
||||
* image="/images/feature.png"
|
||||
* imageAlt="Feature illustration"
|
||||
* title="New Feature"
|
||||
* subtitle="Learn about our latest feature."
|
||||
* buttonLabel="Learn More"
|
||||
* onClick={() => console.log('clicked')}
|
||||
* />
|
||||
*/
|
||||
export const CardImage: React.FC<CardImageProps> = ({
|
||||
image,
|
||||
imageAlt,
|
||||
title,
|
||||
subtitle,
|
||||
buttonLabel,
|
||||
href,
|
||||
onClick,
|
||||
disabled = false,
|
||||
className = '',
|
||||
fullBleed = false,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
// Track hover state for button animation
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (!disabled) {
|
||||
setIsHovered(true);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (!disabled) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
// Build class names using BEM with bds namespace
|
||||
const classNames = clsx(
|
||||
'bds-card-image',
|
||||
disabled && 'bds-card-image--disabled',
|
||||
isHovered && 'bds-card-image--hovered',
|
||||
fullBleed && 'bds-card-image--full-bleed',
|
||||
className
|
||||
);
|
||||
|
||||
// Handle card click for linked cards
|
||||
const handleCardClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
// If clicking the button directly, don't navigate via card
|
||||
if ((e.target as HTMLElement).closest('.bds-btn')) {
|
||||
return;
|
||||
}
|
||||
if (href && !disabled) {
|
||||
window.location.href = href;
|
||||
}
|
||||
},
|
||||
[href, disabled]
|
||||
);
|
||||
|
||||
// Handle button click
|
||||
const handleButtonClick = useCallback(() => {
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
} else if (onClick) {
|
||||
onClick();
|
||||
}
|
||||
}, [href, onClick]);
|
||||
|
||||
// Build inline style for image container background color
|
||||
const imageContainerStyle = backgroundColor
|
||||
? { '--bds-card-image-bg': backgroundColor } as React.CSSProperties
|
||||
: undefined;
|
||||
|
||||
// Common content structure
|
||||
const content = (
|
||||
<>
|
||||
{/* Image container with customizable background */}
|
||||
<div className="bds-card-image__image-container" style={imageContainerStyle}>
|
||||
<img
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
className="bds-card-image__image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content area: title, subtitle, and button */}
|
||||
<div className="bds-card-image__content">
|
||||
<div className="bds-card-image__text">
|
||||
<h3 className="bds-card-image__title sh-md-r">{title}</h3>
|
||||
<p className="bds-card-image__subtitle body-l">{subtitle}</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
color="green"
|
||||
disabled={disabled}
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// Render as clickable div (card itself handles navigation)
|
||||
return (
|
||||
<div
|
||||
className={classNames}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={href ? handleCardClick : undefined}
|
||||
role={href ? 'link' : undefined}
|
||||
tabIndex={href && !disabled ? 0 : undefined}
|
||||
onKeyDown={
|
||||
href && !disabled
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
aria-disabled={disabled}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardImage;
|
||||
2
shared/components/CardImage/index.ts
Normal file
2
shared/components/CardImage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CardImage, type CardImageProps } from './CardImage';
|
||||
export { default } from './CardImage';
|
||||
436
shared/components/CardOffgrid/CardOffgrid.md
Normal file
436
shared/components/CardOffgrid/CardOffgrid.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# CardOffgrid Component - Usage Guide
|
||||
|
||||
## Overview
|
||||
|
||||
`CardOffgrid` is a feature highlight card component designed to showcase key capabilities, features, or resources. It combines an icon, title, and description in a visually engaging, interactive card format with smooth hover animations.
|
||||
|
||||
**Use CardOffgrid when:**
|
||||
- Highlighting key features or capabilities
|
||||
- Creating feature grids or showcases
|
||||
- Linking to important documentation or resources
|
||||
- Presenting product/service highlights
|
||||
|
||||
**Don't use CardOffgrid for:**
|
||||
- Simple content cards (use standard Bootstrap cards)
|
||||
- Navigation items (use navigation components)
|
||||
- Data display (use tables or data components)
|
||||
- Long-form content (use article/page layouts)
|
||||
|
||||
---
|
||||
|
||||
## When to Use Each Variant
|
||||
|
||||
### Neutral Variant (`variant="neutral"`)
|
||||
|
||||
**Use for:**
|
||||
- General feature highlights
|
||||
- Standard content cards
|
||||
- Secondary or supporting features
|
||||
- When you want subtle, professional presentation
|
||||
|
||||
**Example use cases:**
|
||||
- Documentation sections
|
||||
- Feature lists
|
||||
- Service offerings
|
||||
- Standard informational cards
|
||||
|
||||
### Green Variant (`variant="green"`)
|
||||
|
||||
**Use for:**
|
||||
- Primary or featured highlights
|
||||
- Call-to-action cards
|
||||
- Important announcements
|
||||
- Brand-emphasized content
|
||||
|
||||
**Example use cases:**
|
||||
- Hero feature cards
|
||||
- Primary CTAs
|
||||
- Featured resources
|
||||
- Branded highlights
|
||||
|
||||
---
|
||||
|
||||
## Content Best Practices
|
||||
|
||||
### Title Guidelines
|
||||
|
||||
✅ **Do:**
|
||||
- Keep titles concise (1-3 words ideal)
|
||||
- Use line breaks (`\n`) for multi-word titles when needed
|
||||
- Make titles action-oriented or descriptive
|
||||
- Examples: "Onchain Metadata", "Token\nManagement", "Cross-Chain\nBridges"
|
||||
|
||||
❌ **Don't:**
|
||||
- Write long sentences as titles
|
||||
- Use more than 2 lines
|
||||
- Include punctuation (periods, commas)
|
||||
- Make titles too generic ("Feature", "Service")
|
||||
|
||||
### Description Guidelines
|
||||
|
||||
✅ **Do:**
|
||||
- Write 1-2 sentences (15-25 words ideal)
|
||||
- Focus on benefits or key information
|
||||
- Use clear, simple language
|
||||
- Keep descriptions scannable
|
||||
|
||||
❌ **Don't:**
|
||||
- Write paragraphs (save for full pages)
|
||||
- Use jargon without context
|
||||
- Include multiple ideas in one description
|
||||
- Make descriptions too short (< 10 words) or too long (> 40 words)
|
||||
|
||||
### Icon Guidelines
|
||||
|
||||
✅ **Do:**
|
||||
- Use SVG icons for crisp rendering
|
||||
- Choose icons that represent the feature clearly
|
||||
- Ensure icons are recognizable at 68×68px
|
||||
- Use consistent icon style across cards
|
||||
|
||||
❌ **Don't:**
|
||||
- Use low-resolution raster images
|
||||
- Choose overly complex icons
|
||||
- Mix icon styles within a single grid
|
||||
- Use icons that don't relate to the content
|
||||
|
||||
---
|
||||
|
||||
## Interaction Patterns
|
||||
|
||||
### Using `onClick` vs `href`
|
||||
|
||||
**Use `onClick` when:**
|
||||
- Triggering JavaScript actions (modals, analytics, state changes)
|
||||
- Opening external links in new tabs
|
||||
- Performing client-side navigation
|
||||
- Handling complex interactions
|
||||
|
||||
```tsx
|
||||
<CardOffgrid
|
||||
variant="neutral"
|
||||
icon={<AnalyticsIcon />}
|
||||
title="View Analytics"
|
||||
description="See detailed usage statistics and insights."
|
||||
onClick={() => {
|
||||
trackEvent('analytics_viewed');
|
||||
openModal('analytics');
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
**Use `href` when:**
|
||||
- Navigating to internal pages
|
||||
- Linking to documentation
|
||||
- Simple page navigation
|
||||
- SEO-friendly links
|
||||
|
||||
```tsx
|
||||
<CardOffgrid
|
||||
variant="green"
|
||||
icon="/icons/docs.svg"
|
||||
title="API\nReference"
|
||||
description="Complete API documentation and examples."
|
||||
href="/docs/api"
|
||||
/>
|
||||
```
|
||||
|
||||
### Disabled State
|
||||
|
||||
Use `disabled` when:
|
||||
- Feature is coming soon
|
||||
- Feature requires authentication
|
||||
- Feature is temporarily unavailable
|
||||
- You want to show but not allow interaction
|
||||
|
||||
```tsx
|
||||
<CardOffgrid
|
||||
variant="neutral"
|
||||
icon={<BetaIcon />}
|
||||
title="Coming\nSoon"
|
||||
description="This feature will be available in the next release."
|
||||
disabled
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layout Best Practices
|
||||
|
||||
### Grid Layouts
|
||||
|
||||
**Recommended grid patterns:**
|
||||
|
||||
```tsx
|
||||
// 2-column grid (desktop)
|
||||
<div className="row">
|
||||
<div className="col-md-6 mb-4">
|
||||
<CardOffgrid {...props1} />
|
||||
</div>
|
||||
<div className="col-md-6 mb-4">
|
||||
<CardOffgrid {...props2} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// 3-column grid (desktop)
|
||||
<div className="row">
|
||||
{cards.map(card => (
|
||||
<div key={card.id} className="col-md-4 mb-4">
|
||||
<CardOffgrid {...card} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Spacing:**
|
||||
- Use Bootstrap spacing utilities (`mb-4`, `mb-5`) between cards
|
||||
- Maintain consistent spacing in grids
|
||||
- Cards are responsive and stack on mobile automatically
|
||||
|
||||
### Single Card Usage
|
||||
|
||||
For hero sections or featured highlights:
|
||||
|
||||
```tsx
|
||||
<div className="d-flex justify-content-center">
|
||||
<CardOffgrid
|
||||
variant="green"
|
||||
icon={<FeaturedIcon />}
|
||||
title="New Feature"
|
||||
description="Introducing our latest capability..."
|
||||
href="/features/new"
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Best Practices
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
The component automatically renders as:
|
||||
- `<button>` when using `onClick`
|
||||
- `<a>` when using `href`
|
||||
|
||||
This ensures proper semantic meaning for screen readers.
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
✅ **Always test:**
|
||||
- Tab navigation moves focus to cards
|
||||
- Enter/Space activates cards
|
||||
- Focus ring is clearly visible
|
||||
- Focus order follows logical reading order
|
||||
|
||||
### Screen Reader Content
|
||||
|
||||
✅ **Ensure:**
|
||||
- Titles are descriptive and unique
|
||||
- Descriptions provide context
|
||||
- Icons have appropriate `aria-hidden="true"` (handled automatically)
|
||||
- Disabled cards communicate their state
|
||||
|
||||
### Color Contrast
|
||||
|
||||
All variants meet WCAG AA standards:
|
||||
- Dark mode: White text on colored backgrounds
|
||||
- Light mode: Dark text on light backgrounds
|
||||
- Focus rings provide sufficient contrast
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Feature Showcase Grid
|
||||
|
||||
```tsx
|
||||
const features = [
|
||||
{
|
||||
variant: 'green',
|
||||
icon: <TokenIcon />,
|
||||
title: 'Token\nManagement',
|
||||
description: 'Create and manage fungible and non-fungible tokens.',
|
||||
href: '/docs/tokens'
|
||||
},
|
||||
{
|
||||
variant: 'neutral',
|
||||
icon: <MetadataIcon />,
|
||||
title: 'Onchain\nMetadata',
|
||||
description: 'Store key asset information using simple APIs.',
|
||||
href: '/docs/metadata'
|
||||
},
|
||||
// ... more features
|
||||
];
|
||||
|
||||
<div className="row">
|
||||
{features.map((feature, index) => (
|
||||
<div key={index} className="col-md-4 mb-4">
|
||||
<CardOffgrid {...feature} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Mixed Variants for Hierarchy
|
||||
|
||||
Use green variant for primary features, neutral for supporting:
|
||||
|
||||
```tsx
|
||||
<div className="row">
|
||||
<div className="col-md-6 mb-4">
|
||||
<CardOffgrid
|
||||
variant="green" // Primary feature
|
||||
icon={<PrimaryIcon />}
|
||||
title="Main Feature"
|
||||
description="Our flagship capability..."
|
||||
href="/feature/main"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6 mb-4">
|
||||
<CardOffgrid
|
||||
variant="neutral" // Supporting feature
|
||||
icon={<SupportIcon />}
|
||||
title="Supporting Feature"
|
||||
description="Complementary capability..."
|
||||
href="/feature/support"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Coming Soon Pattern
|
||||
|
||||
```tsx
|
||||
<CardOffgrid
|
||||
variant="neutral"
|
||||
icon={<ComingSoonIcon />}
|
||||
title="Coming\nSoon"
|
||||
description="This feature is currently in development and will be available soon."
|
||||
disabled
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Icon Optimization
|
||||
|
||||
✅ **Best practices:**
|
||||
- Use SVG React components (inlined) for small icons
|
||||
- Use optimized SVG files for image icons
|
||||
- Avoid large raster images
|
||||
- Consider lazy loading for below-the-fold cards
|
||||
|
||||
### Rendering Performance
|
||||
|
||||
- Cards are lightweight components
|
||||
- Hover animations use CSS transforms (GPU-accelerated)
|
||||
- No heavy JavaScript calculations
|
||||
- Suitable for grids with 10+ cards
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Card not clickable:**
|
||||
- Ensure `onClick` or `href` is provided
|
||||
- Check that `disabled` is not set to `true`
|
||||
- Verify no parent element is blocking pointer events
|
||||
|
||||
**Icon not displaying:**
|
||||
- Verify icon path is correct (if using string)
|
||||
- Check icon component is properly imported
|
||||
- Ensure icon fits within 68×68px bounds
|
||||
|
||||
**Hover animation not working:**
|
||||
- Check browser supports CSS `clip-path`
|
||||
- Verify no conflicting CSS is overriding transitions
|
||||
- Test in different browsers
|
||||
|
||||
**Focus ring not visible:**
|
||||
- Ensure keyboard navigation (Tab key)
|
||||
- Check focus ring color contrasts with background
|
||||
- Verify `outline-offset: 2px` is applied
|
||||
|
||||
---
|
||||
|
||||
## Design System Integration
|
||||
|
||||
### Color Tokens
|
||||
|
||||
All colors reference `styles/_colors.scss`:
|
||||
- Dark mode (default): Uses `$gray-500`, `$gray-400`, `$green-300`, `$green-200`
|
||||
- Light mode (`html.light`): Uses `$gray-200`, `$gray-300`, `$green-200`, `$green-300`
|
||||
|
||||
### Typography
|
||||
|
||||
- Title: Booton Light, 32px, -1px letter-spacing
|
||||
- Description: Booton Light, 18px, -0.5px letter-spacing
|
||||
|
||||
### Spacing
|
||||
|
||||
- Card padding: 24px
|
||||
- Content gap: 40px (between title and description)
|
||||
- Icon container: 84×84px
|
||||
|
||||
---
|
||||
|
||||
## Figma References
|
||||
|
||||
- **Light Mode Colors**: [Figma Design - Light Mode](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-1963&m=dev)
|
||||
- **Dark Mode Colors**: [Figma Design - Dark Mode](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-2321&m=dev)
|
||||
- **Animation Specs**: [Figma Design - Storyboard](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8007-1096&m=dev)
|
||||
|
||||
---
|
||||
|
||||
## Component API
|
||||
|
||||
```typescript
|
||||
interface CardOffgridProps {
|
||||
/** Color variant: 'neutral' (default) or 'green' */
|
||||
variant?: 'neutral' | 'green';
|
||||
|
||||
/** Icon element (ReactNode) or image path (string) */
|
||||
icon: React.ReactNode | string;
|
||||
|
||||
/** Card title - use \n for line breaks */
|
||||
title: string;
|
||||
|
||||
/** Card description text (1-2 sentences) */
|
||||
description: string;
|
||||
|
||||
/** Click handler - renders as <button> */
|
||||
onClick?: () => void;
|
||||
|
||||
/** Link destination - renders as <a> */
|
||||
href?: string;
|
||||
|
||||
/** Disabled state - prevents interaction */
|
||||
disabled?: boolean;
|
||||
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Use Case | Variant | Interaction |
|
||||
|----------|---------|-------------|
|
||||
| Standard feature | `neutral` | `href` or `onClick` |
|
||||
| Primary feature | `green` | `href` or `onClick` |
|
||||
| Coming soon | `neutral` | `disabled` |
|
||||
| Feature grid | Mix both | `href` preferred |
|
||||
| Hero section | `green` | `href` |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
See the [CardOffgrid Showcase](/about/card-offgrid-showcase) for live examples and interactive demos.
|
||||
360
shared/components/CardOffgrid/CardOffgrid.scss
Normal file
360
shared/components/CardOffgrid/CardOffgrid.scss
Normal file
@@ -0,0 +1,360 @@
|
||||
// BDS CardOffgrid Component Styles
|
||||
// Brand Design System - Feature card with icon, title, and description
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-card-offgrid - Base card (resets button/anchor styles)
|
||||
// .bds-card-offgrid--neutral - Neutral gray color variant (default)
|
||||
// .bds-card-offgrid--green - Green color variant
|
||||
// .bds-card-offgrid--disabled - Disabled state
|
||||
// .bds-card-offgrid__overlay - Hover gradient overlay
|
||||
// .bds-card-offgrid__icon-container - Icon wrapper (84x84px)
|
||||
// .bds-card-offgrid__icon-image - Image icon styling
|
||||
// .bds-card-offgrid__content - Title and description wrapper
|
||||
// .bds-card-offgrid__title - Card title (uses .subhead-lg-l)
|
||||
// .bds-card-offgrid__description - Card description (uses .body-l)
|
||||
//
|
||||
// Note: This file is imported within xrpl.scss after Bootstrap and project
|
||||
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
|
||||
//
|
||||
// Theme: Site defaults to DARK mode. Light mode uses html.light selector.
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens
|
||||
// =============================================================================
|
||||
|
||||
// Dimensions (from Figma design spec)
|
||||
$bds-card-offgrid-width: 400px;
|
||||
$bds-card-offgrid-height: 480px;
|
||||
$bds-card-offgrid-padding: 24px;
|
||||
$bds-card-offgrid-icon-container: 84px;
|
||||
$bds-card-offgrid-icon-size: 68px;
|
||||
$bds-card-offgrid-content-gap: 40px;
|
||||
|
||||
// Animation (from Figma design spec)
|
||||
$bds-card-offgrid-transition-duration: 200ms;
|
||||
$bds-card-offgrid-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
|
||||
|
||||
// Typography - Title (now using .subhead-lg-l from _font.scss)
|
||||
// Typography - Description (now using .body-l from _font.scss)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dark Mode Colors (Default)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Neutral variant - Dark Mode
|
||||
$bds-card-offgrid-neutral-default-dark: $gray-500; // #72777E
|
||||
$bds-card-offgrid-neutral-hover-dark: $gray-400; // #8A919A
|
||||
$bds-card-offgrid-neutral-pressed-dark: rgba($gray-500, 0.7); // 70% opacity
|
||||
$bds-card-offgrid-neutral-text-dark: $white; // #FFFFFF
|
||||
|
||||
// Green variant - Dark Mode
|
||||
$bds-card-offgrid-green-default-dark: $green-300; // #21E46B
|
||||
$bds-card-offgrid-green-hover-dark: $green-200; // #70EE97
|
||||
$bds-card-offgrid-green-pressed-dark: $green-400; // #0DAA3E
|
||||
$bds-card-offgrid-green-text-dark: $black; // #000000
|
||||
|
||||
// Disabled - Dark Mode (30% opacity on gray-500)
|
||||
$bds-card-offgrid-disabled-opacity-dark: 0.3;
|
||||
$bds-card-offgrid-disabled-text-dark: $white; // #FFFFFF
|
||||
|
||||
// Focus ring - Dark Mode
|
||||
$bds-card-offgrid-focus-color-dark: $white; // #FFFFFF
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Light Mode Colors
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Neutral variant - Light Mode
|
||||
$bds-card-offgrid-neutral-default-light: $gray-200; // #E6EAF0
|
||||
$bds-card-offgrid-neutral-hover-light: $gray-300; // #CAD4DF
|
||||
$bds-card-offgrid-neutral-pressed-light: $gray-400; // #8A919A
|
||||
$bds-card-offgrid-neutral-text-light: $gray-900; // #111112
|
||||
$bds-card-offgrid-neutral-text-hover-light: $black; // #000000
|
||||
|
||||
// Green variant - Light Mode
|
||||
$bds-card-offgrid-green-default-light: $green-200; // #70EE97
|
||||
$bds-card-offgrid-green-hover-light: $green-300; // #21E46B
|
||||
$bds-card-offgrid-green-pressed-light: $green-400; // #0DAA3E
|
||||
$bds-card-offgrid-green-text-light: $black; // #000000
|
||||
|
||||
// Disabled - Light Mode
|
||||
$bds-card-offgrid-disabled-bg-light: $gray-100; // #F0F3F7
|
||||
$bds-card-offgrid-disabled-text-light: $gray-500; // #72777E
|
||||
|
||||
// Focus ring - Light Mode
|
||||
$bds-card-offgrid-focus-color-light: $gray-900; // #111112
|
||||
|
||||
// =============================================================================
|
||||
// Base Card Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-offgrid {
|
||||
// Reset button/anchor styles
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
|
||||
// Card layout
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: $bds-card-offgrid-width;
|
||||
height: $bds-card-offgrid-height;
|
||||
padding: $bds-card-offgrid-padding;
|
||||
overflow: hidden;
|
||||
|
||||
// Animation
|
||||
transition: background-color $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing,
|
||||
opacity $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing;
|
||||
|
||||
// Focus styles - Dark Mode (default)
|
||||
// 1px gap between card and focus ring
|
||||
&:focus {
|
||||
outline: 2px solid $bds-card-offgrid-focus-color-dark;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $bds-card-offgrid-focus-color-dark;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Overlay (Color wipe animation - "Window Shade" effect)
|
||||
// =============================================================================
|
||||
// Hover in: shade rises from bottom to top (reveals)
|
||||
// Hover out: shade falls from top to bottom (hides)
|
||||
|
||||
.bds-card-offgrid__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
|
||||
// Default: hidden (shade is "rolled up" at bottom, top is 100% clipped)
|
||||
// When transitioning TO this state, the top inset increases = shade falls down
|
||||
clip-path: inset(100% 0 0 0);
|
||||
transition: clip-path $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing;
|
||||
}
|
||||
|
||||
// Hovered state: shade fully raised (visible)
|
||||
// When transitioning TO this state, the top inset decreases = shade rises up
|
||||
.bds-card-offgrid--hovered .bds-card-offgrid__overlay {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Icon Container
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-offgrid__icon-container {
|
||||
position: relative;
|
||||
z-index: 1; // Above overlay
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $bds-card-offgrid-icon-container;
|
||||
height: $bds-card-offgrid-icon-container;
|
||||
flex-shrink: 0;
|
||||
|
||||
// Icon sizing
|
||||
> * {
|
||||
max-width: $bds-card-offgrid-icon-size;
|
||||
max-height: $bds-card-offgrid-icon-size;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-card-offgrid__icon-image {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: $bds-card-offgrid-icon-size;
|
||||
max-height: $bds-card-offgrid-icon-size;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Content (Title + Description)
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-offgrid__content {
|
||||
position: relative;
|
||||
z-index: 1; // Above overlay
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $bds-card-offgrid-content-gap;
|
||||
}
|
||||
|
||||
.bds-card-offgrid__title {
|
||||
// Typography handled by .subhead-lg-l class from _font.scss
|
||||
margin-bottom: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DARK MODE (Default)
|
||||
// =============================================================================
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Neutral Variant - Dark Mode
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--neutral {
|
||||
background-color: $bds-card-offgrid-neutral-default-dark;
|
||||
color: $bds-card-offgrid-neutral-text-dark;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-neutral-hover-dark;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-offgrid--disabled) {
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-neutral-pressed-dark;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Green Variant - Dark Mode
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--green {
|
||||
background-color: $bds-card-offgrid-green-default-dark;
|
||||
color: $bds-card-offgrid-green-text-dark;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-green-hover-dark;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-offgrid--disabled) {
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-green-pressed-dark;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Disabled State - Dark Mode
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--disabled {
|
||||
background-color: $bds-card-offgrid-neutral-default-dark;
|
||||
color: $bds-card-offgrid-disabled-text-dark;
|
||||
opacity: $bds-card-offgrid-disabled-opacity-dark;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LIGHT MODE (html.light)
|
||||
// =============================================================================
|
||||
|
||||
html.light {
|
||||
// Focus styles - Light Mode
|
||||
.bds-card-offgrid {
|
||||
&:focus {
|
||||
outline-color: $bds-card-offgrid-focus-color-light;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: $bds-card-offgrid-focus-color-light;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Neutral Variant - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--neutral {
|
||||
background-color: $bds-card-offgrid-neutral-default-light;
|
||||
color: $bds-card-offgrid-neutral-text-light;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-neutral-hover-light;
|
||||
}
|
||||
|
||||
// Text color on hover
|
||||
&.bds-card-offgrid--hovered {
|
||||
color: $bds-card-offgrid-neutral-text-hover-light;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-offgrid--disabled) {
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-neutral-pressed-light;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Green Variant - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--green {
|
||||
background-color: $bds-card-offgrid-green-default-light;
|
||||
color: $bds-card-offgrid-green-text-light;
|
||||
|
||||
// Overlay color for hover wipe
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-green-hover-light;
|
||||
}
|
||||
|
||||
// Pressed state
|
||||
&:active:not(.bds-card-offgrid--disabled) {
|
||||
.bds-card-offgrid__overlay {
|
||||
background-color: $bds-card-offgrid-green-pressed-light;
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Disabled State - Light Mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
.bds-card-offgrid--disabled {
|
||||
background-color: $bds-card-offgrid-disabled-bg-light;
|
||||
color: $bds-card-offgrid-disabled-text-light;
|
||||
opacity: 1; // Reset opacity, use background color instead
|
||||
|
||||
.bds-card-offgrid__icon-container {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Styles
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.bds-card-offgrid {
|
||||
width: 100%;
|
||||
min-height: $bds-card-offgrid-height;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
168
shared/components/CardOffgrid/CardOffgrid.tsx
Normal file
168
shared/components/CardOffgrid/CardOffgrid.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export interface CardOffgridProps {
|
||||
/** Color variant of the card */
|
||||
variant?: 'neutral' | 'green';
|
||||
/** Icon element or image source */
|
||||
icon: React.ReactNode | string;
|
||||
/** Card title (supports multi-line via \n) */
|
||||
title: string;
|
||||
/** Card description text */
|
||||
description: string;
|
||||
/** Click handler */
|
||||
onClick?: () => void;
|
||||
/** Link destination (renders as anchor if provided) */
|
||||
href?: string;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
/** Optional className for custom styling */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* BDS CardOffgrid Component
|
||||
*
|
||||
* A versatile card component for displaying feature highlights with an icon,
|
||||
* title, and description. Supports neutral and green color variants with
|
||||
* interactive states (hover, focus, pressed, disabled).
|
||||
*
|
||||
* Features a "window shade" color wipe animation:
|
||||
* - Hover in: shade rises from bottom to top (reveals hover color)
|
||||
* - Hover out: shade falls from top to bottom (hides hover color)
|
||||
*
|
||||
* @example
|
||||
* // Basic neutral card
|
||||
* <CardOffgrid
|
||||
* variant="neutral"
|
||||
* icon={<MetadataIcon />}
|
||||
* title="Onchain Metadata"
|
||||
* description="Easily store key asset information."
|
||||
* onClick={() => console.log('clicked')}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Green card with link
|
||||
* <CardOffgrid
|
||||
* variant="green"
|
||||
* icon="/icons/metadata.svg"
|
||||
* title="Onchain Metadata"
|
||||
* description="Easily store key asset information."
|
||||
* href="/docs/metadata"
|
||||
* />
|
||||
*/
|
||||
export const CardOffgrid: React.FC<CardOffgridProps> = ({
|
||||
variant = 'neutral',
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
onClick,
|
||||
href,
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
// Track hover state for animation
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (!disabled) {
|
||||
setIsHovered(true);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (!disabled) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
// Build class names using BEM with bds namespace
|
||||
const classNames = clsx(
|
||||
'bds-card-offgrid',
|
||||
`bds-card-offgrid--${variant}`,
|
||||
{
|
||||
'bds-card-offgrid--disabled': disabled,
|
||||
'bds-card-offgrid--hovered': isHovered,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
// Render icon - supports both React nodes and image URLs
|
||||
const renderIcon = () => {
|
||||
if (typeof icon === 'string') {
|
||||
return (
|
||||
<img
|
||||
src={icon}
|
||||
alt=""
|
||||
className="bds-card-offgrid__icon-image"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return icon;
|
||||
};
|
||||
|
||||
// Split title by newline for multi-line support
|
||||
const renderTitle = () => {
|
||||
const lines = title.split('\n');
|
||||
return lines.map((line, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{line}
|
||||
{index < lines.length - 1 && <br />}
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
|
||||
// Common content for both button and anchor
|
||||
const content = (
|
||||
<>
|
||||
{/* Hover color wipe overlay */}
|
||||
<span className="bds-card-offgrid__overlay" aria-hidden="true" />
|
||||
|
||||
<span className="bds-card-offgrid__icon-container">
|
||||
{renderIcon()}
|
||||
</span>
|
||||
|
||||
<span className="bds-card-offgrid__content">
|
||||
<span className="bds-card-offgrid__title sh-lg-r">
|
||||
{renderTitle()}
|
||||
</span>
|
||||
<span className="bds-card-offgrid__description body-l">
|
||||
{description}
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
// Render as anchor if href is provided
|
||||
if (href && !disabled) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classNames}
|
||||
aria-disabled={disabled}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Render as button for onClick or disabled state
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
aria-disabled={disabled}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardOffgrid;
|
||||
2
shared/components/CardOffgrid/index.ts
Normal file
2
shared/components/CardOffgrid/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CardOffgrid, type CardOffgridProps } from './CardOffgrid';
|
||||
export { default } from './CardOffgrid';
|
||||
456
shared/components/CardStat/CardStat.md
Normal file
456
shared/components/CardStat/CardStat.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# CardStat Component Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The CardStat component is a statistics card that displays prominent numerical data with a descriptive label and optional call-to-action buttons. Following the XRPL Brand Design System (BDS), it supports four color variants and responsive layouts to showcase key metrics and statistics effectively.
|
||||
|
||||
## Features
|
||||
|
||||
- **Four Color Variants**: Lilac (default), Green, Light Gray, and Dark Gray
|
||||
- **Flexible Content**: Large statistic text with descriptive label
|
||||
- **Optional CTAs**: Support for 0, 1, or 2 buttons (Primary and/or Secondary)
|
||||
- **Responsive Design**: Adaptive sizing and typography across breakpoints
|
||||
- **Theme Support**: Automatic light/dark mode adaptation
|
||||
- **Accessible**: Semantic HTML with proper button accessibility
|
||||
|
||||
## Props API
|
||||
|
||||
```typescript
|
||||
interface ButtonConfig {
|
||||
/** Button label text */
|
||||
label: string;
|
||||
/** Click handler for button */
|
||||
onClick?: () => void;
|
||||
/** Link href for button */
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface CardStatProps {
|
||||
/** The main statistic to display (e.g., "6 Million+") */
|
||||
statistic: string;
|
||||
/** Descriptive label for the statistic */
|
||||
label: string;
|
||||
/** Background color variant */
|
||||
variant?: 'lilac' | 'green' | 'light-gray' | 'dark-gray';
|
||||
/** Primary button configuration */
|
||||
primaryButton?: ButtonConfig;
|
||||
/** Secondary button configuration */
|
||||
secondaryButton?: ButtonConfig;
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
- `variant`: `'lilac'`
|
||||
- `primaryButton`: `undefined` (no button)
|
||||
- `secondaryButton`: `undefined` (no button)
|
||||
- `className`: `''`
|
||||
|
||||
## Color Variants
|
||||
|
||||
### Lilac (Default)
|
||||
|
||||
A purple/lavender background that provides a soft, friendly appearance.
|
||||
|
||||
**Best For:**
|
||||
- User-focused statistics (active wallets, users)
|
||||
- Community metrics
|
||||
- Engagement statistics
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
```
|
||||
|
||||
### Green
|
||||
|
||||
XRPL brand green background that reinforces brand identity.
|
||||
|
||||
**Best For:**
|
||||
- Financial metrics (transaction volume, value moved)
|
||||
- Growth statistics
|
||||
- Positive indicators
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="$1 Trillion+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
```
|
||||
|
||||
### Light Gray
|
||||
|
||||
Light gray background for technology and reliability metrics.
|
||||
|
||||
**Best For:**
|
||||
- Technical statistics (uptime, performance)
|
||||
- Infrastructure metrics
|
||||
- Stability indicators
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="12+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
/>
|
||||
```
|
||||
|
||||
### Dark Gray
|
||||
|
||||
Neutral dark gray background for general-purpose statistics.
|
||||
|
||||
**Best For:**
|
||||
- Neutral metrics
|
||||
- Secondary information
|
||||
- Supporting statistics
|
||||
|
||||
**Usage:**
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="70+"
|
||||
label="Ecosystem partners"
|
||||
variant="dark-gray"
|
||||
/>
|
||||
```
|
||||
|
||||
## Button Configurations
|
||||
|
||||
### No Buttons
|
||||
|
||||
Display statistics without call-to-action buttons for informational purposes.
|
||||
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
```
|
||||
|
||||
### Single Button (Primary)
|
||||
|
||||
Include one primary button for a main call-to-action.
|
||||
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
href: "/wallets"
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Two Buttons (Primary + Secondary)
|
||||
|
||||
Include both primary and secondary buttons for multiple actions.
|
||||
|
||||
```tsx
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
href: "/wallets"
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Get Started",
|
||||
onClick: handleGetStarted
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Component Structure
|
||||
|
||||
The CardStat component uses BEM (Block Element Modifier) naming convention with the `bds` namespace:
|
||||
|
||||
- `.bds-card-stat` - Base card class
|
||||
- `.bds-card-stat--lilac` - Lilac variant modifier
|
||||
- `.bds-card-stat--green` - Green variant modifier
|
||||
- `.bds-card-stat--light-gray` - Light gray variant modifier
|
||||
- `.bds-card-stat--dark-gray` - Dark gray variant modifier
|
||||
- `.bds-card-stat__content` - Inner content wrapper
|
||||
- `.bds-card-stat__text` - Text section container
|
||||
- `.bds-card-stat__statistic` - Large statistic text
|
||||
- `.bds-card-stat__label` - Descriptive label text
|
||||
- `.bds-card-stat__buttons` - Button container
|
||||
- `.bds-card-stat__button-link` - Link wrapper for buttons
|
||||
|
||||
### Layout
|
||||
|
||||
The card uses flexbox for vertical layout:
|
||||
|
||||
1. **Content wrapper** (`.bds-card-stat__content`): Main container with padding
|
||||
2. **Text section** (`.bds-card-stat__text`): Contains statistic and label
|
||||
3. **Buttons section** (`.bds-card-stat__buttons`): Optional CTA buttons
|
||||
|
||||
The layout automatically adjusts spacing between sections using flexbox `justify-content: space-between`.
|
||||
|
||||
### Responsive Behavior
|
||||
|
||||
The component adapts to different screen sizes:
|
||||
|
||||
**Base (Mobile, <576px):**
|
||||
- Padding: 8px
|
||||
- Min height: 200px
|
||||
- Statistic: 64px font size
|
||||
- Label: 16px font size
|
||||
|
||||
**MD (Tablet, ≥576px):**
|
||||
- Padding: 12px
|
||||
- Min height: 208px
|
||||
- Same typography as mobile
|
||||
|
||||
**LG+ (Desktop, ≥992px):**
|
||||
- Padding: 16px
|
||||
- Min height: 298px
|
||||
- Statistic: 72px font size
|
||||
- Label: 18px font size
|
||||
|
||||
### Button Integration
|
||||
|
||||
Buttons use the existing BDS Button component with `color="black"` variant to ensure proper contrast on colored backgrounds. Buttons can be either:
|
||||
|
||||
- **Links** (`href` provided): Wrapped in an anchor tag for navigation
|
||||
- **Actions** (`onClick` provided): Rendered as interactive buttons
|
||||
|
||||
## Typography
|
||||
|
||||
### Statistic Text
|
||||
- **Font**: Booton, sans-serif
|
||||
- **Weight**: 228 (Light)
|
||||
- **Size**: 64px (mobile), 72px (desktop)
|
||||
- **Line Height**: 70.4px (mobile), 79.2px (desktop)
|
||||
- **Letter Spacing**: -4.5px (mobile), -5px (desktop)
|
||||
|
||||
### Label Text
|
||||
- **Font**: Booton, sans-serif
|
||||
- **Weight**: 400 (Regular)
|
||||
- **Size**: 16px (mobile), 18px (desktop)
|
||||
- **Line Height**: 23.2px (mobile), 26.1px (desktop)
|
||||
- **Letter Spacing**: 0px (mobile), -0.5px (desktop)
|
||||
|
||||
## Spacing & Layout
|
||||
|
||||
- **Border Radius**: 8px
|
||||
- **Button Gap**: 8px between buttons
|
||||
- **Text Gap**: 4px between statistic and label
|
||||
- **Min Heights**: 200px (mobile), 208px (tablet), 298px (desktop)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { CardStat } from 'shared/components/CardStat';
|
||||
|
||||
// Simple statistic card
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
/>
|
||||
|
||||
// With color variant
|
||||
<CardStat
|
||||
statistic="$1 Trillion+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
```
|
||||
|
||||
### With Buttons
|
||||
|
||||
```tsx
|
||||
// Single button (Light Gray variant)
|
||||
<CardStat
|
||||
statistic="12+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
href: "/about/uptime"
|
||||
}}
|
||||
/>
|
||||
|
||||
// Two buttons (Dark Gray variant)
|
||||
<CardStat
|
||||
statistic="70+"
|
||||
label="Ecosystem partners"
|
||||
variant="dark-gray"
|
||||
primaryButton={{
|
||||
label: "View Partners",
|
||||
href: "/partners"
|
||||
}}
|
||||
secondaryButton={{
|
||||
label: "Become Partner",
|
||||
onClick: handlePartnerSignup
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### In PageGrid Layout
|
||||
|
||||
```tsx
|
||||
import { CardStat } from 'shared/components/CardStat';
|
||||
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
|
||||
|
||||
<PageGrid>
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="$1 Trillion+"
|
||||
label="Value moved"
|
||||
variant="green"
|
||||
/>
|
||||
</PageGridCol>
|
||||
<PageGridCol span={{ base: 4, md: 4, lg: 4 }}>
|
||||
<CardStat
|
||||
statistic="12+"
|
||||
label="Continuous uptime years"
|
||||
variant="light-gray"
|
||||
/>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
```
|
||||
|
||||
### With Click Handlers
|
||||
|
||||
```tsx
|
||||
const handleLearnMore = () => {
|
||||
console.log('Learn more clicked');
|
||||
};
|
||||
|
||||
<CardStat
|
||||
statistic="6 Million+"
|
||||
label="Active wallets"
|
||||
variant="lilac"
|
||||
primaryButton={{
|
||||
label: "Learn More",
|
||||
onClick: handleLearnMore
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
- Uses `<div>` elements with proper structure
|
||||
- Button components handle their own accessibility
|
||||
- Text content is readable by screen readers
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- Buttons are fully keyboard accessible (Tab, Enter, Space)
|
||||
- Links are keyboard navigable (Tab, Enter)
|
||||
- Focus indicators provided by Button component
|
||||
|
||||
### Color Contrast
|
||||
|
||||
All variants meet WCAG AA standards:
|
||||
- **Black text on colored backgrounds**: Sufficient contrast
|
||||
- **Buttons**: Use black variant with proper contrast on all backgrounds
|
||||
- **Dark mode dark gray**: Switches to white text for proper contrast
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep statistics concise** - Use abbreviations (M for million, K for thousand)
|
||||
2. **Use descriptive labels** - Make it clear what the statistic represents
|
||||
3. **Choose appropriate variants** - Match color to the type of metric
|
||||
4. **Limit button usage** - Use buttons only when actionable context is needed
|
||||
5. **Consider grid layouts** - Use PageGrid for consistent spacing and alignment
|
||||
6. **Test responsiveness** - Verify cards adapt properly at all breakpoints
|
||||
|
||||
## When to Use
|
||||
|
||||
Use CardStat to:
|
||||
|
||||
- **Highlight key metrics** - Show important numbers prominently
|
||||
- **Dashboard sections** - Create stat-focused areas on landing pages
|
||||
- **About pages** - Showcase company or product statistics
|
||||
- **Feature sections** - Emphasize quantitative benefits
|
||||
- **Report summaries** - Display high-level data points
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
Avoid CardStat when:
|
||||
|
||||
- **Complex data** - Use charts or tables for detailed information
|
||||
- **Multiple related metrics** - Consider data visualization components
|
||||
- **Non-numeric content** - Use regular cards for text-heavy content
|
||||
- **Real-time updates** - Consider dedicated dashboard components
|
||||
|
||||
## Design Tokens
|
||||
|
||||
The component uses design tokens from the XRPL Brand Design System:
|
||||
|
||||
### Colors
|
||||
- `$bds-card-stat-lilac-bg`: `#D9CAFF`
|
||||
- `$bds-card-stat-green-bg`: `$green-300` (#EAFCF1)
|
||||
- `$bds-card-stat-light-gray-bg`: `#CAD4DF` (light gray)
|
||||
- `$bds-card-stat-dark-gray-bg`: `#8A919A` (dark gray)
|
||||
- Text: `#141414` (neutral black)
|
||||
|
||||
### Spacing
|
||||
- Border radius: `8px`
|
||||
- Button gap: `8px`
|
||||
- Text gap: `4px`
|
||||
- Content padding: `8px` (mobile), `12px` (tablet), `16px` (desktop)
|
||||
|
||||
### Motion
|
||||
- Transition duration: `150ms`
|
||||
- Timing function: `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
|
||||
|
||||
## Theme Support
|
||||
|
||||
The component automatically adapts for light and dark modes:
|
||||
|
||||
### Light Mode (Default)
|
||||
- All color variants use their defined light backgrounds
|
||||
- Black text on colored backgrounds
|
||||
|
||||
### Dark Mode
|
||||
- **Dark Gray variant**: Switches to darker gray background (`$gray-400`) with white text
|
||||
- **Other variants**: Maintain colored backgrounds with black text
|
||||
|
||||
## Showcase
|
||||
|
||||
See the interactive showcase at `/about/cardstat-showcase` for live examples of all variants, button configurations, and responsive behavior in PageGrid layouts.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
shared/components/CardStat/
|
||||
├── CardStat.tsx # Component implementation
|
||||
├── CardStat.scss # Component styles
|
||||
├── CardStat.md # This documentation
|
||||
└── index.ts # Exports
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
- **Button** - Used for CTA buttons within cards
|
||||
- **PageGrid** - For laying out multiple cards in responsive grids
|
||||
- **Divider** - Can be used to separate card sections if needed
|
||||
171
shared/components/CardStat/CardStat.scss
Normal file
171
shared/components/CardStat/CardStat.scss
Normal file
@@ -0,0 +1,171 @@
|
||||
// BDS CardStat Component Styles
|
||||
// Brand Design System - Statistics card component
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-card-stat - Base card
|
||||
// .bds-card-stat--lilac - Lilac variant
|
||||
// .bds-card-stat--green - Green variant
|
||||
// .bds-card-stat--light-gray - Light gray variant
|
||||
// .bds-card-stat--dark-gray - Dark gray variant
|
||||
// .bds-card-stat__content - Inner content wrapper
|
||||
// .bds-card-stat__text - Text section
|
||||
// .bds-card-stat__statistic - Large statistic text
|
||||
// .bds-card-stat__label - Descriptive label
|
||||
// .bds-card-stat__buttons - Button container
|
||||
// .bds-card-stat__button-link - Link wrapper for buttons
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens
|
||||
// =============================================================================
|
||||
|
||||
// Color variants
|
||||
$bds-card-stat-lilac-bg: $lilac-300;
|
||||
$bds-card-stat-green-bg: $green-300; // #EAFCF1
|
||||
$bds-card-stat-light-gray-bg: #E6EAF0; // Light gray
|
||||
$bds-card-stat-dark-gray-bg: #CAD4DF; // Dark gray
|
||||
|
||||
// Text colors
|
||||
$bds-card-stat-text: $black; // Neutral black
|
||||
|
||||
// Spacing
|
||||
$bds-card-stat-gap: 8px;
|
||||
|
||||
// Transitions
|
||||
$bds-card-stat-transition-duration: 150ms;
|
||||
$bds-card-stat-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
|
||||
|
||||
// =============================================================================
|
||||
// Base Card Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-stat {
|
||||
// Layout
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
|
||||
// Visual
|
||||
background-color: $bds-card-stat-lilac-bg; // Default
|
||||
|
||||
// Typography
|
||||
color: $bds-card-stat-text;
|
||||
|
||||
// Transitions
|
||||
transition: transform $bds-card-stat-transition-duration $bds-card-stat-transition-timing;
|
||||
|
||||
// Content wrapper
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
gap: 4px;
|
||||
|
||||
// Tablet (md) breakpoint
|
||||
@include media-breakpoint-up(md) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
// Desktop (lg+) breakpoint
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// Text section
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// Statistic (large number)
|
||||
&__statistic {
|
||||
@include type(display-lg);
|
||||
margin-bottom: 0;
|
||||
sup {
|
||||
top: -0.4em;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons section
|
||||
&__buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $bds-card-stat-gap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Color Variants
|
||||
// =============================================================================
|
||||
|
||||
// Lilac variant (default)
|
||||
.bds-card-stat--lilac {
|
||||
background-color: $bds-card-stat-lilac-bg;
|
||||
}
|
||||
|
||||
// Green variant
|
||||
.bds-card-stat--green {
|
||||
background-color: $bds-card-stat-green-bg;
|
||||
}
|
||||
|
||||
// Light gray variant
|
||||
.bds-card-stat--light-gray {
|
||||
background-color: $bds-card-stat-light-gray-bg;
|
||||
}
|
||||
|
||||
// Dark gray variant
|
||||
.bds-card-stat--dark-gray {
|
||||
background-color: $bds-card-stat-dark-gray-bg;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dark Mode Styles
|
||||
// =============================================================================
|
||||
|
||||
html.dark {
|
||||
.bds-card-stat {
|
||||
// Light gray variant gets dark-gray background in dark mode
|
||||
&--light-gray {
|
||||
background-color: $bds-card-stat-dark-gray-bg; // Darker gray for dark mode
|
||||
}
|
||||
|
||||
// Dark gray variant gets darker gray background in dark mode
|
||||
&--dark-gray {
|
||||
background-color: #8A919A; // Darker gray for dark mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Height Adjustments
|
||||
// =============================================================================
|
||||
|
||||
.bds-card-stat {
|
||||
// Base (mobile) - ~200px height
|
||||
min-height: 200px;
|
||||
|
||||
// Tablet (md) - ~208px height
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: 208px;
|
||||
}
|
||||
// Desktop (lg+) - ~298px height (more vertical space)
|
||||
@include media-breakpoint-up(lg) {
|
||||
min-height: 298px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-grid__col-lg-3 {
|
||||
.bds-card-stat {
|
||||
@include media-breakpoint-up(lg) {
|
||||
aspect-ratio: 1 / 1;
|
||||
min-height: unset;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user