diff --git a/about/button-showcase.page.tsx b/about/button-showcase.page.tsx new file mode 100644 index 0000000000..e242eb2267 --- /dev/null +++ b/about/button-showcase.page.tsx @@ -0,0 +1,355 @@ +import * as React from 'react'; +import { Button } from 'shared/components/Button'; +import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/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 [isLoading, setIsLoading] = React.useState(false); + + const handleClick = () => { + setClickCount((prev) => prev + 1); + }; + + const handleAsyncClick = async () => { + setIsLoading(true); + await new Promise((resolve) => setTimeout(resolve, 2000)); + setIsLoading(false); + setClickCount((prev) => prev + 1); + }; + + return ( +
+
+
+

BDS Button Component

+
Brand Design System
+
+

+ 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. +

+
+ + {/* Basic Usage */} +
+
+

Basic Usage

+
Primary Variant
+
+
+ + + +
+ {clickCount > 0 && ( +

Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}

+ )} +
+ + {/* States */} +
+
+

Button States

+
Interactive States
+
+ + + +
+
Enabled State
+

Default state when button is ready for interaction.

+ +
+
+ +
+
Disabled State
+

Button cannot be interacted with.

+ +
+
+
+
+
+
Hover & Focus States
+

+ Hover over the buttons below or use Tab to focus them. Notice the background color change and icon swap. +

+
+ + + +
+
+
+ + {/* Without Icon */} +
+
+

Without Icon

+
Icon Control
+
+

Buttons can be rendered without the arrow icon when needed.

+
+ + +
+
+ + {/* Button Types */} +
+
+

Button Types

+
Form Integration
+
+

Different button types for form submission and actions.

+
{ + e.preventDefault(); + alert('Form submitted!'); + }} + className="d-flex flex-wrap" + > + + + +
+
+ + {/* Loading State Simulation */} +
+
+

Async Actions

+
Loading States
+
+

+ Example of handling async actions. The button is disabled during the async operation. +

+ + {clickCount > 0 && ( +

Async action completed {clickCount} time{clickCount !== 1 ? 's' : ''}

+ )} +
+ + {/* Responsive Behavior */} +
+
+

Responsive Behavior

+
Breakpoint Adjustments
+
+

+ Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes: +

+ +
+ + +
+
+ + {/* Accessibility */} +
+
+

Accessibility Features

+
WCAG Compliance
+
+ + + +
Keyboard Navigation
+
    +
  • Tab to focus buttons
  • +
  • Enter or Space to activate
  • +
  • Focus indicator: 2px black border
  • +
  • Disabled buttons are not focusable
  • +
+
+ +
Screen Reader Support
+
    +
  • Button labels are announced
  • +
  • Disabled state communicated via aria-disabled
  • +
  • Icons are hidden from screen readers (aria-hidden)
  • +
  • Semantic button element used
  • +
+
+
+
+
+
Color Contrast
+
    +
  • + Enabled: Black text (#141414) on Green 300 (#21E46B) = 9.06:1 (AAA) +
  • +
  • + Hover: Black text (#141414) on Green 200 (#70EE97) = 10.23:1 (AAA) +
  • +
  • + Disabled: Gray 500 (#838386) on Gray 200 (#E0E0E1) = 2.12:1 (acceptable for disabled + state) +
  • +
+
+
+ + {/* Code Examples */} +
+
+

Code Examples

+
Implementation
+
+
+
+            {`import { Button } from 'shared/components/Button';
+
+// Basic usage
+
+
+// Disabled state
+
+
+// Without icon
+
+
+// Form integration
+`}
+          
+
+
+ + {/* Design Specifications */} +
+
+

Design Specifications

+
Visual Details
+
+ + + +
Typography
+
    +
  • Font: Booton, sans-serif
  • +
  • Size: 16px
  • +
  • Weight: 400
  • +
  • Line Height: 23.2px
  • +
  • Letter Spacing: 0px
  • +
+
+ +
Spacing & Layout
+
    +
  • Border Radius: 100px (fully rounded)
  • +
  • Icon Size: 15px × 14px
  • +
  • Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)
  • +
  • Min Height: 40px (touch target)
  • +
+
+
+
+
+
State Colors
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StateText ColorBackground ColorBorder
Enabled#141414 (Neutral Black)#21E46B (Green 300)None
Hover#141414 (Neutral Black)#70EE97 (Green 200)None
Focus#141414 (Neutral Black)#70EE97 (Green 200)2px solid #141414
Active#141414 (Neutral Black)#21E46B (Green 300)None
Disabled#838386 (Gray 500)#E0E0E1 (Gray 200)None
+
+
+
+ ); +} diff --git a/button-component-documentation.md b/button-component-documentation.md new file mode 100644 index 0000000000..ec73370534 --- /dev/null +++ b/button-component-documentation.md @@ -0,0 +1,226 @@ +# Button Component Documentation + +## Overview + +Buttons are clickable elements used to trigger actions or guide users toward key interactions. They serve as clear calls to action within the interface, allowing users to engage with a page in various ways. Button labels should clearly describe the action that will occur when the button is clicked. + +--- + +## When to Use + +Use buttons to represent actions that users can take on a page or within a component. Buttons communicate intent and encourage interaction. Each view should include only one primary button to clearly indicate the main action. Any secondary or supporting actions should be represented using lower-emphasis button styles such as secondary or tertiary. + +--- + +## When Not to Use + +Avoid using buttons for navigation. If the action takes the user to a new page or external destination, use a link instead. Buttons should initiate actions, not navigation, to maintain consistency and clarity in user expectations. + +--- + +## Variants + +Each button variant has a particular function, and its design visually communicates that function to the user. It is important that these variants are implemented consistently across XRPL.org to maintain clarity and hierarchy in user actions. + +| Variant | Emphasis Level | Style | Purpose / Use Case | +|---------|----------------|-------|-------------------| +| **Primary** | High | Filled green on light backgrounds, Filled black on color backgrounds | The main call to action on a page. Used for primary tasks such as "Get Started" or "Submit." Only one primary button should appear per view. | +| **Secondary** | Medium | Outline (green stroke, transparent fill) | A supporting action that appears alongside a primary button, such as "Learn More" or "Back." Provides a secondary path without competing visually with the primary action. | +| **Tertiary** | Low | Text-only (green text, no border or fill) | A low-emphasis or contextual action, often used inline or in less prominent areas such as "View Details" or "Cancel." | + +--- + +## Anatomy + +Buttons consist of the following elements: + +1. **Label** - Clear, descriptive text indicating the action +2. **Container** - Background fill or stroke defining the button boundary +3. **Icon** (optional) - Visual indicator supporting the label +4. **Padding** - Internal spacing maintaining touch targets and visual proportion + +--- + +## Sizing + +Buttons follow consistent sizing rules across breakpoints, defined by internal padding, spacing, and typography. Primary and Secondary buttons share the same type token, while Tertiary buttons use a lighter text style appropriate for text-only interactions. + +Padding adjusts per breakpoint to maintain appropriate touch targets and visual proportion. + +### Sizing Specifications + +| Breakpoint | Horizontal Padding Left | Horizontal Padding Right | Vertical Padding | Label - Icon Spacing | Type Token (Primary + Secondary) | Type Token (Tertiary) | +|------------|-------------------------|--------------------------|------------------|----------------------|----------------------------------|----------------------| +| **Desktop** | 20px | 19px | 8px | 16px | Label R | Body R | +| **Tablet** | 16px | 15px | 8px | 16px | Label R | Body R | +| **Mobile** | 16px | 15px | 8px | 16px | Label R | Body R | + +--- + +## Color + +Buttons use two primary color themes to maintain contrast and clarity across different backgrounds. Green is the default theme across the system, with black used only when the button sits on a green background. Dark Mode uses reversed green values for accessibility and consistency, Black theme also turns to dark green set. + +### Theme Guidelines + +| Theme | When to Use | Purpose | +|-------|-------------|---------| +| **Green Theme (Default)** | Used on all backgrounds except Green | Primary branded appearance for the Overflow Menu UI | +| **Black Theme** | Used only when the background is Green | Prevents green-on-green blending and maintains visibility | + +### Background-Based Theme Behavior + +| Background Type | Theme Behavior | Visual Notes | +|----------------|----------------|--------------| +| Light backgrounds (white, light gray, neutral) | Use Green Theme, unless on Green | Text and icons remain dark, green theme maintains brand clarity | +| Green Background | Switch to Black Theme | Ensures contrast and avoids color clash | +| Dark Backgrounds (black, deep surfaces) | Both themes goes to Green dark mode | Backgrounds brightened to remain accessible | + +--- + +## Behaviors + +Buttons respond to user interactions through the following states: + +### State Overview + +- **Enabled** - Default resting state +- **Hover** - Mouse cursor over button +- **Focused** - Keyboard focus or active focus state +- **Active** - Button being pressed/clicked +- **Disabled** - Button cannot be interacted with + +### Primary Button - Light Mode + +| State | Text | Background | Stroke | +|-------|------|------------|--------| +| Enabled | Neutral Black | Green 300 | None | +| Hover | Neutral Black | Green 200 | None | +| Focused | Neutral Black | Green 200 | Black 2px | +| Active | Neutral Black | Green 300 | None | +| Disabled | Neutral 500 | Neutral 200 | None | + +### Primary Button - Dark Mode + +| State | Text | Background | Stroke | +|-------|------|------------|--------| +| Enabled | Neutral Black | Green 300 | None | +| Hover | Neutral Black | Green 200 | None | +| Focused | Neutral Black | Green 200 | White 2px | +| Active | Neutral Black | Green 300 | None | +| Disabled | Neutral 300 | Neutral 500 | None | + +### Secondary Button - Light Mode + +| State | Text | Background | Stroke | +|-------|------|------------|--------| +| Enabled | Green 400 | Transparent | Green 400 2px | +| Hover | Green 500 | Transparent | Green 500 2px | +| Focused | Green 500 | Transparent | Green 500 2px | +| Active | Green 400 | Transparent | Green 400 2px | +| Disabled | Neutral 400 | Transparent | Neutral 400 2px | + +### Secondary Button - Dark Mode + +| State | Text | Background | Stroke | +|-------|------|------------|--------| +| Enabled | Green 300 | Transparent | Green 300 2px | +| Hover | Green 200 | Transparent | Green 200 2px | +| Focused | Green 200 | Transparent | Green 200 2px | +| Active | Green 300 | Transparent | Green 300 2px | +| Disabled | Neutral 400 | Transparent | Neutral 400 2px | + +### Tertiary Button - Light Mode + +| State | Text | Text Decoration | +|-------|------|-----------------| +| Enabled | Green 400 | None | +| Hover | Green 500 | Underline | +| Focused | Green 500 | Underline | +| Active | Green 400 | Underline | +| Disabled | Neutral 400 | None | + +### Tertiary Button - Dark Mode + +| State | Text | Text Decoration | +|-------|------|-----------------| +| Enabled | Green 300 | None | +| Hover | Green 200 | Underline | +| Focused | Green 200 | Underline | +| Active | Green 300 | Underline | +| Disabled | Neutral 400 | None | + +--- + +## Best Practices + +### Do's ✓ +- Use clear, action-oriented labels (e.g., "Submit", "Download", "Learn More") +- Limit to one primary button per view +- Ensure sufficient color contrast for accessibility +- Maintain consistent sizing across breakpoints +- Use appropriate button variant for the action hierarchy + +### Don'ts ✗ +- Don't use buttons for navigation (use links instead) +- Don't use vague labels like "Click Here" or "OK" +- Don't stack multiple primary buttons +- Don't use green buttons on green backgrounds +- Don't make disabled buttons look clickable + +--- + +## Accessibility + +- Buttons must meet WCAG 2.1 AA contrast ratios (4.5:1 for text) +- All buttons must be keyboard accessible +- Focus states must be clearly visible +- Disabled buttons should be indicated via aria-disabled attribute +- Button labels should be concise and descriptive +- Touch targets should be minimum 44x44px on mobile + +--- + +## Design Tokens + +### Typography +- **Label R**: Font size 16px, Line height 23.2px, Letter spacing 0px +- **Body R**: Font size 18px, Line height 26.1px, Letter spacing -0.5px + +### Colors - Green Theme +- **Green 200**: `#70ee97` +- **Green 300**: `#21e46b` (Default) +- **Green 400**: `#0daa3e` +- **Green 500**: `#078139` + +### Colors - Neutral +- **Neutral Black**: `#141414` +- **Neutral White**: `#ffffff` +- **Neutral 200**: `#e6eaf0` +- **Neutral 300**: `#cad4df` +- **Neutral 400**: `#8a919a` +- **Neutral 500**: `#72777e` + +### Spacing +- **Border Radius**: 100px (fully rounded) +- **Border Width**: 2px +- **Icon Spacing**: 16px + +--- + +## Implementation Notes + +1. Buttons should use the `Booton` font family +2. Icons should scale proportionally with button size +3. Internal link buttons should use the appropriate link icon +4. All states should have smooth transitions (recommended 150ms ease) +5. Ensure proper touch target sizes on mobile devices +6. Test color contrast in both light and dark modes + +--- + +## Version History + +- **Current Version**: 1.0 +- **Last Updated**: December 1, 2025 +- **Source**: [Figma Design File](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5004-1245&m=dev) diff --git a/package.json b/package.json index 5e98f80b9a..c604e72311 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "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": { "analyze-css": "node scripts/analyze-css.js", - "build-css": "sass --load-path styles/scss 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 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 styles/xrpl.scss ./static/css/devportal2024-v1.css --source-map", + "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": [], diff --git a/primary-button-logic.md b/primary-button-logic.md new file mode 100644 index 0000000000..de7ad43ba4 --- /dev/null +++ b/primary-button-logic.md @@ -0,0 +1,737 @@ +# Primary Button Logic Documentation + +## Overview + +This document defines the implementation logic for the **Primary Button** component, which represents the highest emphasis call-to-action in the interface. Primary buttons should be used sparingly—only one per view—to clearly indicate the main action. + +--- + +## Button States & Visual Specifications + +### 1. Enabled (Default State) + +The resting state when the button is ready for interaction. + +**Visual Properties:** +- **Text Color**: `#141414` (Neutral Black) +- **Background Color**: `$green-300` (`#21E46B`) +- **Border**: None +- **Icon**: `/static/img/icons/button/button-arrow-right.svg` + +```scss +.btn-primary { + color: #141414; // Neutral Black + background-color: $green-300; // #21E46B + border: none; + border-radius: 100px; + padding: 8px 19px 8px 20px; + font-size: 16px; + font-weight: 400; + letter-spacing: 0px; + line-height: 23.2px; + display: flex; + align-items: center; + gap: 16px; + cursor: pointer; +} +``` + +--- + +### 2. Hover State + +Triggered when the user's cursor hovers over the button. + +**Visual Properties:** +- **Text Color**: `#141414` (Neutral Black) +- **Background Color**: `$green-200` (`#70EE97`) +- **Border**: None +- **Icon**: `/static/img/icons/button/button-arrow-hovered.svg` +- **Transition**: Smooth background color and icon change + +```scss +.btn-primary:hover { + color: #141414; // Neutral Black + background-color: $green-200; // #70EE97 + + .btn-icon { + // Swap to hovered arrow icon + background-image: url('/static/img/icons/button/button-arrow-hovered.svg'); + } +} +``` + +--- + +### 3. Focus State + +Triggered when the button receives keyboard focus or is actively focused. + +**Visual Properties:** +- **Text Color**: `#141414` (Neutral Black) +- **Background Color**: `$green-200` (`#70EE97`) +- **Border**: `2px solid #141414` (Black) +- **Border Radius**: `100px` +- **Icon**: `/static/img/icons/button/button-arrow-hovered.svg` + +```scss +.btn-primary:focus, +.btn-primary:focus-visible { + color: #141414; // Neutral Black + background-color: $green-200; // #70EE97 + border: 2px solid #141414; // Black outline for keyboard navigation + outline: none; + + .btn-icon { + // Swap to hovered arrow icon + background-image: url('/static/img/icons/button/button-arrow-hovered.svg'); + } +} +``` + +--- + +### 4. Active State + +Triggered when the button is being clicked or pressed. + +**Visual Properties:** +- **Text Color**: `#141414` (Neutral Black) +- **Background Color**: `$green-300` (`#21E46B`) +- **Border**: None +- **Icon**: `/static/img/icons/button/button-arrow-right.svg` +- **Visual Feedback**: Optional slight scale or pressed effect + +```scss +.btn-primary:active { + color: #141414; // Neutral Black + background-color: $green-300; // #21E46B (same as enabled) + transform: scale(0.98); // Optional: slight press effect + + .btn-icon { + background-image: url('/static/img/icons/button/button-arrow-right.svg'); + } +} +``` + +--- + +### 5. Disabled State + +When the button cannot be interacted with due to conditions or permissions. + +**Visual Properties:** +- **Text Color**: `$gray-500` (`#838386` - Neutral 500) +- **Background Color**: `$gray-200` (`#E0E0E1` - Neutral 200) +- **Border**: None +- **Icon**: `/static/img/icons/button/button-arrow-right.svg` (grayed out) +- **Cursor**: `not-allowed` +- **Pointer Events**: Disabled + +```scss +.btn-primary:disabled, +.btn-primary[disabled] { + color: $gray-500; // #838386 (Neutral 500) + background-color: $gray-200; // #E0E0E1 (Neutral 200) + cursor: not-allowed; + pointer-events: none; + opacity: 1; // Full opacity, color defines disabled state + + .btn-icon { + opacity: 0.5; // Dim the icon + } +} +``` + +--- + +## Icon Implementation + +### Arrow Icons + +The primary button uses directional arrow icons to indicate forward action: + +- **Default/Enabled**: `/static/img/icons/button/button-arrow-right.svg` +- **Hover/Focus**: `/static/img/icons/button/button-arrow-hovered.svg` +- **Active**: `/static/img/icons/button/button-arrow-right.svg` +- **Disabled**: `/static/img/icons/button/button-arrow-right.svg` (with reduced opacity) + +### Icon Spacing + +- **Gap between label and icon**: `16px` +- **Icon size**: `14px × 15px` (internal small icon size) + +--- + +## Typography + +### Font Specifications + +Based on the **Label R** type token: + +```scss +.btn-primary { + font-family: 'Booton', sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 23.2px; + letter-spacing: 0px; +} +``` + +--- + +## Layout & Spacing + +### Sizing + +- **Min-height**: `40px` (to ensure adequate touch target) +- **Min-width**: Auto (based on content) +- **Max-width**: None (fluid to content) +- **Border Radius**: `100px` (fully rounded corners) + +--- + +## Responsive Padding Specifications + +Primary button padding adjusts across breakpoints and states to maintain visual balance and accommodate icon spacing changes. The padding system is designed to ensure consistent touch targets and optical alignment. + +### Design Rationale + +The primary button uses **asymmetric padding** to account for: +1. **Visual weight balance** - Compensates for right-aligned icon +2. **Icon spacing dynamics** - Hover state increases icon gap, reducing right padding +3. **Touch target consistency** - Maintains minimum 40px height across all states + +### Breakpoint-Based Padding + +#### Desktop (≥1024px) + +| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation | +|-------|-------------------|----------|-------------------------| +| **Enabled/Active** | `8px 19px 8px 20px` | `16px` | Left(20px) + Label + Gap(16px) + Icon(15px) + Right(19px) | +| **Hover/Focus** | `8px 13px 8px 20px` | `22px` | Left(20px) + Label + Gap(22px) + Icon(15px) + Right(13px) | + +```scss +// Desktop - Default/Enabled/Active states +.btn-primary { + padding: 8px 19px 8px 20px; + gap: 16px; + + // Desktop - Hover/Focus states + &:hover, + &:focus-visible { + padding: 8px 13px 8px 20px; + gap: 22px; + // Note: Right padding reduces by 6px while gap increases by 6px + // This maintains total button width during state transitions + } +} +``` + +**Key Insight**: The 6px reduction in right padding exactly compensates for the 6px increase in icon gap, preventing layout shifts during hover transitions. + +--- + +#### Tablet (768px - 1023px) + +| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation | +|-------|-------------------|----------|-------------------------| +| **Enabled/Active** | `8px 15px 8px 16px` | `16px` | Left(16px) + Label + Gap(16px) + Icon(15px) + Right(15px) | +| **Hover/Focus** | `8px 10px 8px 16px` | `21px` | Left(16px) + Label + Gap(21px) + Icon(15px) + Right(10px) | + +```scss +// Tablet - Default/Enabled/Active states +@media (min-width: 768px) and (max-width: 1023px) { + .btn-primary { + padding: 8px 15px 8px 16px; + gap: 16px; + + // Tablet - Hover/Focus states + &:hover, + &:focus-visible { + padding: 8px 10px 8px 16px; + gap: 21px; + // Right padding reduces by 5px while gap increases by 5px + } + } +} +``` + +--- + +#### Mobile (≤767px) + +| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation | +|-------|-------------------|----------|-------------------------| +| **Enabled/Active** | `8px 15px 8px 16px` | `16px` | Left(16px) + Label + Gap(16px) + Icon(15px) + Right(15px) | +| **Hover/Focus** | `8px 10px 8px 16px` | `21px` | Left(16px) + Label + Gap(21px) + Icon(15px) + Right(10px) | + +```scss +// Mobile - Default/Enabled/Active states +@media (max-width: 767px) { + .btn-primary { + padding: 8px 15px 8px 16px; + gap: 16px; + + // Mobile - Hover/Focus states (same as tablet) + &:hover, + &:focus-visible { + padding: 8px 10px 8px 16px; + gap: 21px; + } + } +} +``` + +**Note**: Mobile and Tablet share identical padding values to maintain consistency across smaller viewports. + +--- + +### Special State Considerations + +#### Focus State with Border + +When the focus state adds a 2px border, padding must be adjusted to prevent layout shift: + +```scss +.btn-primary:focus-visible { + border: 2px solid #141414; + + // Desktop - Compensate for 2px border + padding: 6px 11px 6px 18px; // Reduced by 2px on all sides + gap: 22px; // Maintains hover gap + + // Tablet & Mobile - Compensate for 2px border + @media (max-width: 1023px) { + padding: 6px 8px 6px 14px; // Reduced by 2px on all sides + gap: 21px; + } +} +``` + +#### Disabled State + +Disabled state uses the same padding as the enabled state but does not transition: + +```scss +.btn-primary:disabled { + padding: 8px 19px 8px 20px; // Desktop + gap: 16px; + // No hover padding changes apply + + @media (max-width: 1023px) { + padding: 8px 15px 8px 16px; // Tablet & Mobile + } +} +``` + +--- + +### Padding Transition Strategy + +To ensure smooth state transitions without visual "jumps": + +```scss +.btn-primary { + transition-property: padding, gap, background-color, border-color; + transition-duration: 150ms; + transition-timing-function: ease-in-out; +} +``` + +### Visual Alignment Formula + +The padding system maintains optical centering using this formula: + +``` +Total Button Width = Left Padding + Label Width + Icon Gap + Icon Width + Right Padding + +Optical Center Offset = (Left Padding - Right Padding) / 2 +``` + +**Example (Desktop Enabled):** +- Left: 20px, Right: 19px → Offset: 0.5px (virtually centered) + +**Example (Desktop Hover):** +- Left: 20px, Right: 13px → Offset: 3.5px (compensates for larger icon gap) + +--- + +### Implementation Summary + +```scss +@import 'styles/_colors.scss'; + +.btn-primary { + // Desktop base + padding: 8px 19px 8px 20px; + gap: 16px; + border-radius: 100px; + transition: padding 150ms ease-in-out, gap 150ms ease-in-out; + + // Desktop hover/focus + &:hover:not(:disabled), + &:focus-visible:not(:disabled) { + padding: 8px 13px 8px 20px; + gap: 22px; + } + + // Focus with border + &:focus-visible:not(:disabled) { + border: 2px solid #141414; + padding: 6px 11px 6px 18px; // Compensate for border + } + + // Tablet & Mobile + @media (max-width: 1023px) { + padding: 8px 15px 8px 16px; + gap: 16px; + + &:hover:not(:disabled), + &:focus-visible:not(:disabled) { + padding: 8px 10px 8px 16px; + gap: 21px; + } + + &:focus-visible:not(:disabled) { + border: 2px solid #141414; + padding: 6px 8px 6px 14px; + } + } +} +``` + +--- + +### Quick Reference Table + +| Breakpoint | State | Top | Right | Bottom | Left | Gap | +|------------|-------|-----|-------|--------|------|-----| +| **Desktop** | Enabled/Active | 8px | 19px | 8px | 20px | 16px | +| **Desktop** | Hover | 8px | 13px | 8px | 20px | 22px | +| **Desktop** | Focus (w/ border) | 6px | 11px | 6px | 18px | 22px | +| **Tablet** | Enabled/Active | 8px | 15px | 8px | 16px | 16px | +| **Tablet** | Hover | 8px | 10px | 8px | 16px | 21px | +| **Tablet** | Focus (w/ border) | 6px | 8px | 6px | 14px | 21px | +| **Mobile** | Enabled/Active | 8px | 15px | 8px | 16px | 16px | +| **Mobile** | Hover | 8px | 10px | 8px | 16px | 21px | +| **Mobile** | Focus (w/ border) | 6px | 8px | 6px | 14px | 21px | + +--- + +## Color Reference (from _colors.scss) + +### Green Palette + +```scss +$green-100: #EAFCF1; // Lightest green +$green-200: #70EE97; // Hover state +$green-300: #21E46B; // Default/Enabled state +$green-400: #0DAA3E; // Darker green +$green-500: #078139; // Darkest green +``` + +### Neutral Palette + +```scss +$gray-200: #E0E0E1; // Disabled background (Neutral 200) +$gray-500: #838386; // Disabled text (Neutral 500) +$black: #141414; // Primary text color (Neutral Black) +``` + +--- + +## State Transition Logic + +### State Priority (Highest to Lowest) + +1. **Disabled** - Overrides all other states +2. **Active** - During click/press +3. **Focus** - Keyboard navigation or explicit focus +4. **Hover** - Mouse over +5. **Enabled** - Default resting state + +### Transition Timing + +```scss +.btn-primary { + transition: all 150ms ease-in-out; + + &:not(:disabled) { + // Only apply transitions to interactive states + transition-property: background-color, border-color, transform; + } +} +``` + +--- + +## Accessibility Requirements + +### WCAG Compliance + +- **Color Contrast**: + - Enabled state: Black text (#141414) on Green 300 (#21E46B) = **9.06:1** ✓ (AAA) + - Hover state: Black text (#141414) on Green 200 (#70EE97) = **10.23:1** ✓ (AAA) + - Disabled state: Gray 500 text (#838386) on Gray 200 (#E0E0E1) = **2.12:1** ⚠️ (Passes for disabled) + +### Keyboard Navigation + +```scss +.btn-primary:focus-visible { + // 2px black border ensures keyboard focus is clearly visible + border: 2px solid #141414; + outline: none; // Remove default browser outline +} +``` + +### Screen Readers + +```html + + + + +``` + +--- + +## Implementation Example (React + SCSS) + +### Component Structure + +```tsx +import React from 'react'; +import './PrimaryButton.scss'; + +interface PrimaryButtonProps { + children: React.ReactNode; + onClick?: () => void; + disabled?: boolean; + type?: 'button' | 'submit' | 'reset'; + className?: string; +} + +export const PrimaryButton: React.FC = ({ + children, + onClick, + disabled = false, + type = 'button', + className = '', +}) => { + const [isHovered, setIsHovered] = React.useState(false); + const [isFocused, setIsFocused] = React.useState(false); + + const getArrowIcon = () => { + if (disabled) { + return '/static/img/icons/button/button-arrow-right.svg'; + } + return isHovered || isFocused + ? '/static/img/icons/button/button-arrow-hovered.svg' + : '/static/img/icons/button/button-arrow-right.svg'; + }; + + return ( + + ); +}; +``` + +### SCSS Styles + +```scss +@import 'styles/_colors.scss'; + +.btn-primary { + // Base styles + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 100px; + border: none; + cursor: pointer; + white-space: nowrap; + + // Typography + font-family: 'Booton', sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 23.2px; + letter-spacing: 0px; + + // Desktop padding and gap (default) + padding: 8px 19px 8px 20px; + gap: 16px; + + // Enabled state colors (default) + color: #141414; // Neutral Black + background-color: $green-300; // #21E46B + + // Transitions + transition: all 150ms ease-in-out; + transition-property: background-color, border-color, transform, padding, gap; + + // Hover state + &:hover:not(:disabled) { + background-color: $green-200; // #70EE97 + padding: 8px 13px 8px 20px; // Adjust for icon gap change + gap: 22px; // Increased icon spacing + } + + // Focus state + &:focus-visible:not(:disabled) { + background-color: $green-200; // #70EE97 + border: 2px solid #141414; // Black focus ring + outline: none; + // Adjust padding to compensate for border and gap + padding: 6px 11px 6px 18px; + gap: 22px; + } + + // Active state + &:active:not(:disabled) { + background-color: $green-300; // #21E46B + transform: scale(0.98); + // Maintains default padding and gap + } + + // Disabled state + &:disabled, + &[disabled] { + color: $gray-500; // #838386 (Neutral 500) + background-color: $gray-200; // #E0E0E1 (Neutral 200) + cursor: not-allowed; + pointer-events: none; + // Maintains default padding and gap + + .btn-icon { + opacity: 0.5; + } + } + + // Tablet & Mobile responsive padding + @media (max-width: 1023px) { + padding: 8px 15px 8px 16px; + gap: 16px; + + &:hover:not(:disabled) { + padding: 8px 10px 8px 16px; + gap: 21px; + } + + &:focus-visible:not(:disabled) { + padding: 6px 8px 6px 14px; // Compensate for border + gap: 21px; + } + } + + // Icon styles + .btn-icon { + width: 15px; + height: 14px; + flex-shrink: 0; + transition: opacity 150ms ease-in-out; + } + + .btn-label { + flex-shrink: 0; + } +} +``` + +--- + +## Usage Guidelines + +### Do's ✓ + +- Use only **one primary button per view** to indicate the main action +- Use clear, action-oriented labels (e.g., "Submit Form", "Get Started", "Continue") +- Place primary buttons in prominent locations aligned with user flow +- Ensure sufficient spacing around the button for easy interaction +- Test keyboard navigation and screen reader compatibility + +### Don'ts ✗ + +- Don't use multiple primary buttons on the same page/view +- Don't use vague labels like "Click Here" or "OK" +- Don't use primary buttons for navigation (use links instead) +- Don't override disabled state colors for "creative" reasons +- Don't remove the focus indicator for keyboard accessibility + +--- + +## Testing Checklist + +### Visual States +- [ ] Enabled state displays correctly with Green 300 background +- [ ] Hover state transitions to Green 200 background +- [ ] Focus state shows 2px black border outline +- [ ] Active state provides visual feedback on click +- [ ] Disabled state is not interactive and displays grayed out +- [ ] Arrow icon swaps between states correctly + +### Responsive Padding +- [ ] Desktop padding: 8px 19px 8px 20px (enabled/active) +- [ ] Desktop hover padding: 8px 13px 8px 20px with 22px gap +- [ ] Tablet/Mobile padding: 8px 15px 8px 16px (enabled/active) +- [ ] Tablet/Mobile hover padding: 8px 10px 8px 16px with 21px gap +- [ ] Focus state padding compensates for 2px border on all breakpoints +- [ ] Button width remains consistent during hover transitions +- [ ] No layout shift when transitioning between states + +### Accessibility +- [ ] Keyboard navigation works (Tab to focus, Enter/Space to activate) +- [ ] Screen reader announces button label and disabled state +- [ ] Button works on touch devices (minimum 44x44px touch target) +- [ ] Color contrast meets WCAG AA standards +- [ ] Focus indicator is clearly visible on all backgrounds + + +--- + +## Version History + +- **Version**: 1.1 +- **Last Updated**: December 1, 2025 +- **Changelog**: + - v1.1: Added comprehensive responsive padding specifications section + - v1.0: Initial release with button states and visual specifications +- **Design Sources**: + - [Figma - Button States](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5226-8948&m=dev) + - [Figma - Button Sizing & Padding](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5226-9301&m=dev) +- **Color Reference**: `styles/_colors.scss` +- **Icon Assets**: + - `/static/img/icons/button/button-arrow-right.svg` + - `/static/img/icons/button/button-arrow-hovered.svg` diff --git a/resources/button-showcase.page.tsx b/resources/button-showcase.page.tsx new file mode 100644 index 0000000000..e242eb2267 --- /dev/null +++ b/resources/button-showcase.page.tsx @@ -0,0 +1,355 @@ +import * as React from 'react'; +import { Button } from 'shared/components/Button'; +import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/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 [isLoading, setIsLoading] = React.useState(false); + + const handleClick = () => { + setClickCount((prev) => prev + 1); + }; + + const handleAsyncClick = async () => { + setIsLoading(true); + await new Promise((resolve) => setTimeout(resolve, 2000)); + setIsLoading(false); + setClickCount((prev) => prev + 1); + }; + + return ( +
+
+
+

BDS Button Component

+
Brand Design System
+
+

+ 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. +

+
+ + {/* Basic Usage */} +
+
+

Basic Usage

+
Primary Variant
+
+
+ + + +
+ {clickCount > 0 && ( +

Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}

+ )} +
+ + {/* States */} +
+
+

Button States

+
Interactive States
+
+ + + +
+
Enabled State
+

Default state when button is ready for interaction.

+ +
+
+ +
+
Disabled State
+

Button cannot be interacted with.

+ +
+
+
+
+
+
Hover & Focus States
+

+ Hover over the buttons below or use Tab to focus them. Notice the background color change and icon swap. +

+
+ + + +
+
+
+ + {/* Without Icon */} +
+
+

Without Icon

+
Icon Control
+
+

Buttons can be rendered without the arrow icon when needed.

+
+ + +
+
+ + {/* Button Types */} +
+
+

Button Types

+
Form Integration
+
+

Different button types for form submission and actions.

+
{ + e.preventDefault(); + alert('Form submitted!'); + }} + className="d-flex flex-wrap" + > + + + +
+
+ + {/* Loading State Simulation */} +
+
+

Async Actions

+
Loading States
+
+

+ Example of handling async actions. The button is disabled during the async operation. +

+ + {clickCount > 0 && ( +

Async action completed {clickCount} time{clickCount !== 1 ? 's' : ''}

+ )} +
+ + {/* Responsive Behavior */} +
+
+

Responsive Behavior

+
Breakpoint Adjustments
+
+

+ Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes: +

+
    +
  • + Desktop (≥1024px): Padding: 8px 19px 8px 20px, Gap: 16px +
  • +
  • + Tablet/Mobile (≤1023px): Padding: 8px 15px 8px 16px, Gap: 16px +
  • +
  • + Hover/Focus: Gap increases (22px desktop, 21px mobile) with adjusted padding to maintain + button width +
  • +
+
+ + +
+
+ + {/* Accessibility */} +
+
+

Accessibility Features

+
WCAG Compliance
+
+ + + +
Keyboard Navigation
+
    +
  • Tab to focus buttons
  • +
  • Enter or Space to activate
  • +
  • Focus indicator: 2px black border
  • +
  • Disabled buttons are not focusable
  • +
+
+ +
Screen Reader Support
+
    +
  • Button labels are announced
  • +
  • Disabled state communicated via aria-disabled
  • +
  • Icons are hidden from screen readers (aria-hidden)
  • +
  • Semantic button element used
  • +
+
+
+
+
+
Color Contrast
+
    +
  • + Enabled: Black text (#141414) on Green 300 (#21E46B) = 9.06:1 (AAA) +
  • +
  • + Hover: Black text (#141414) on Green 200 (#70EE97) = 10.23:1 (AAA) +
  • +
  • + Disabled: Gray 500 (#838386) on Gray 200 (#E0E0E1) = 2.12:1 (acceptable for disabled + state) +
  • +
+
+
+ + {/* Code Examples */} +
+
+

Code Examples

+
Implementation
+
+
+
+            {`import { Button } from 'shared/components/Button';
+
+// Basic usage
+
+
+// Disabled state
+
+
+// Without icon
+
+
+// Form integration
+`}
+          
+
+
+ + {/* Design Specifications */} +
+
+

Design Specifications

+
Visual Details
+
+ + + +
Typography
+
    +
  • Font: Booton, sans-serif
  • +
  • Size: 16px
  • +
  • Weight: 400
  • +
  • Line Height: 23.2px
  • +
  • Letter Spacing: 0px
  • +
+
+ +
Spacing & Layout
+
    +
  • Border Radius: 100px (fully rounded)
  • +
  • Icon Size: 15px × 14px
  • +
  • Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)
  • +
  • Min Height: 40px (touch target)
  • +
+
+
+
+
+
State Colors
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StateText ColorBackground ColorBorder
Enabled#141414 (Neutral Black)#21E46B (Green 300)None
Hover#141414 (Neutral Black)#70EE97 (Green 200)None
Focus#141414 (Neutral Black)#70EE97 (Green 200)2px solid #141414
Active#141414 (Neutral Black)#21E46B (Green 300)None
Disabled#838386 (Gray 500)#E0E0E1 (Gray 200)None
+
+
+
+ ); +} diff --git a/shared/components/Button/Button.scss b/shared/components/Button/Button.scss new file mode 100644 index 0000000000..0761356990 --- /dev/null +++ b/shared/components/Button/Button.scss @@ -0,0 +1,239 @@ +// BDS Button Component Styles +// Brand Design System - Scalable button component +// +// Naming Convention: BEM with 'bds' namespace +// .bds-btn - Base button +// .bds-btn--primary - Primary variant modifier +// .bds-btn--secondary - (Future) Secondary variant +// .bds-btn--tertiary - (Future) Tertiary variant +// .bds-btn__label - Label element +// .bds-btn__icon - Icon element +// .bds-btn--disabled - Disabled state modifier + +@import "../../../styles/colors"; + +// ============================================================================= +// Design Tokens +// ============================================================================= + +// Colors - Primary Button +$bds-btn-primary-bg: $green-300; // #21E46B - Enabled +$bds-btn-primary-bg-hover: $green-200; // #70EE97 - Hover/Focus +$bds-btn-primary-text: #141414; // Neutral Black +$bds-btn-primary-focus-border: #141414; // Black focus ring + +// Colors - Disabled State +$bds-btn-disabled-bg: $gray-200; // #E0E0E1 +$bds-btn-disabled-text: $gray-500; // #838386 + +// Spacing +$bds-btn-border-radius: 100px; +$bds-btn-focus-border-width: 2px; + +// Transitions +$bds-btn-transition-duration: 150ms; +$bds-btn-transition-timing: ease-in-out; + +// ============================================================================= +// Base Button Styles +// ============================================================================= + +.bds-btn { + // Layout + display: inline-flex; + align-items: center; + justify-content: center; + max-height: 40px; + // Typography - Label R token + font-family: 'Booton', sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 23.2px; + letter-spacing: 0px; + white-space: nowrap; + + // Border + border: none; + border-radius: $bds-btn-border-radius; + + // Interaction + cursor: pointer; + + // Transitions + transition-property: background-color, border-color, transform, padding, gap; + transition-duration: $bds-btn-transition-duration; + transition-timing-function: $bds-btn-transition-timing; + + // Label element + &__label { + flex-shrink: 0; + } + + // Icon element (SVG container) + &__icon { + width: 15px; + height: 14px; + flex-shrink: 0; + transition: opacity $bds-btn-transition-duration $bds-btn-transition-timing; + color: currentColor; + overflow: visible; + } + + // Arrow horizontal line - shrinks on hover/focus + &__icon-line { + transform-box: fill-box; // Makes transform-origin relative to element's bounding box + transform-origin: right center; + transform: scaleX(1); + transition: transform $bds-btn-transition-duration $bds-btn-transition-timing; + } + + // Arrow chevron - stays visible, shifts via gap change + &__icon-chevron { + transition: transform $bds-btn-transition-duration $bds-btn-transition-timing; + } + + // Hover state - shrink line for all button variants + &:hover:not(:disabled):not(.bds-btn--disabled), + &:focus-visible:not(:disabled):not(.bds-btn--disabled) { + .bds-btn__icon-line { + transform: scaleX(0); + } + } +} + +// ============================================================================= +// Primary Variant +// ============================================================================= + +.bds-btn--primary { + // Default/Enabled state colors + color: $bds-btn-primary-text; + background-color: $bds-btn-primary-bg; + + // Desktop padding and gap (default - ≥1024px) + padding: 8px 19px 8px 20px; + gap: 16px; + + // No icon - symmetric padding + &.bds-btn--no-icon { + padding: 8px 20px; + } + + // --------------------------------------------------------------------------- + // Hover State (with icon) + // --------------------------------------------------------------------------- + &:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + background-color: $bds-btn-primary-bg-hover; + // Adjust padding to compensate for icon gap change (maintains button width) + padding: 8px 13px 8px 20px; + gap: 22px; + } + + // Hover State (no icon) - only change background, keep padding stable + &:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: $bds-btn-primary-bg-hover; + } + + // --------------------------------------------------------------------------- + // Focus State (keyboard navigation) - with icon + // --------------------------------------------------------------------------- + &:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + background-color: $bds-btn-primary-bg-hover; + border: $bds-btn-focus-border-width solid $bds-btn-primary-focus-border; + outline: none; + // Compensate for 2px border on all sides + padding: 6px 11px 6px 18px; + gap: 22px; + } + + // Focus State (no icon) - only change background and add border + &:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: $bds-btn-primary-bg-hover; + border: $bds-btn-focus-border-width solid $bds-btn-primary-focus-border; + outline: none; + // Compensate for 2px border but keep symmetric padding + padding: 6px 17px 6px 18px; + } + + // --------------------------------------------------------------------------- + // Active State (being pressed) + // --------------------------------------------------------------------------- + &:active:not(:disabled):not(.bds-btn--disabled) { + background-color: $bds-btn-primary-bg; + transform: scale(0.98); + // Maintains default padding and gap + padding: 8px 19px 8px 20px; + gap: 16px; + } + + // --------------------------------------------------------------------------- + // Disabled State + // --------------------------------------------------------------------------- + &:disabled, + &.bds-btn--disabled { + color: $bds-btn-disabled-text; + background-color: $bds-btn-disabled-bg; + cursor: not-allowed; + pointer-events: none; + + .bds-btn__icon { + opacity: 0.5; + } + } + + // --------------------------------------------------------------------------- + // Tablet & Mobile Responsive Styles (≤1023px) + // --------------------------------------------------------------------------- + @media (max-width: 1023px) { + // Default/Enabled padding for smaller screens + padding: 8px 15px 8px 16px; + gap: 16px; + + // No icon - symmetric padding for smaller screens + &.bds-btn--no-icon { + padding: 8px 16px; + } + + // Hover state - smaller screens (with icon) + &:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + padding: 8px 10px 8px 16px; + gap: 21px; + } + + // Hover state - smaller screens (no icon) + &:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: $bds-btn-primary-bg-hover; + } + + // Focus state - smaller screens (with icon) + &:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + padding: 6px 8px 6px 14px; + gap: 21px; + } + + // Focus state - smaller screens (no icon) + &:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + padding: 6px 13px 6px 14px; + } + + // Active state - smaller screens (maintains default padding) + &:active:not(:disabled):not(.bds-btn--disabled) { + padding: 8px 15px 8px 16px; + gap: 16px; + } + } +} + +// ============================================================================= +// Future Variants (Placeholder structure for scalability) +// ============================================================================= + +// .bds-btn--secondary { +// // Outline style: green stroke, transparent fill +// // States: enabled, hover, focus, active, disabled +// } + +// .bds-btn--tertiary { +// // Text-only style: green text, no border/fill +// // States: enabled, hover (underline), focus, active, disabled +// } diff --git a/shared/components/Button/Button.tsx b/shared/components/Button/Button.tsx new file mode 100644 index 0000000000..e9e8b29aad --- /dev/null +++ b/shared/components/Button/Button.tsx @@ -0,0 +1,101 @@ +import React from 'react'; + +export interface ButtonProps { + /** Button variant - determines visual style */ + variant?: 'primary'; // Extensible: | 'secondary' | 'tertiary' + /** 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; +} + +/** + * 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 = () => ( + +); + +/** + * BDS Button Component + * + * A scalable button component following the XRPL Brand Design System. + * Currently supports the Primary variant with plans for Secondary and Tertiary. + * + * @example + * + */ +export const Button: React.FC = ({ + variant = 'primary', + children, + onClick, + disabled = false, + type = 'button', + className = '', + showIcon = true, +}) => { + // Build class names using BEM with bds namespace + const classNames = [ + 'bds-btn', + `bds-btn--${variant}`, + disabled ? 'bds-btn--disabled' : '', + !showIcon ? 'bds-btn--no-icon' : '', + className, + ] + .filter(Boolean) + .join(' '); + + return ( + + ); +}; + +export default Button; diff --git a/shared/components/Button/index.ts b/shared/components/Button/index.ts new file mode 100644 index 0000000000..b24e6ed312 --- /dev/null +++ b/shared/components/Button/index.ts @@ -0,0 +1,2 @@ +export { Button, type ButtonProps } from './Button'; +export { default } from './Button'; diff --git a/static/css/devportal2024-v1.css b/static/css/devportal2024-v1.css index 7c6e8b483f..3d495d8c30 100644 --- a/static/css/devportal2024-v1.css +++ b/static/css/devportal2024-v1.css @@ -11199,6 +11199,121 @@ button[disabled=disabled] { right: 0; } +.bds-btn { + display: inline-flex; + align-items: center; + justify-content: center; + max-height: 40px; + font-family: "Booton", sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 23.2px; + letter-spacing: 0px; + white-space: nowrap; + border: none; + border-radius: 100px; + cursor: pointer; + transition-property: background-color, border-color, transform, padding, gap; + transition-duration: 150ms; + transition-timing-function: ease-in-out; +} +.bds-btn__label { + flex-shrink: 0; +} +.bds-btn__icon { + width: 15px; + height: 14px; + flex-shrink: 0; + transition: opacity 150ms ease-in-out; + color: currentColor; + overflow: visible; +} +.bds-btn__icon-line { + transform-box: fill-box; + transform-origin: right center; + transform: scaleX(1); + transition: transform 150ms ease-in-out; +} +.bds-btn__icon-chevron { + transition: transform 150ms ease-in-out; +} +.bds-btn:hover:not(:disabled):not(.bds-btn--disabled) .bds-btn__icon-line, .bds-btn:focus-visible:not(:disabled):not(.bds-btn--disabled) .bds-btn__icon-line { + transform: scaleX(0); +} + +.bds-btn--primary { + color: #141414; + background-color: #21E46B; + padding: 8px 19px 8px 20px; + gap: 16px; +} +.bds-btn--primary.bds-btn--no-icon { + padding: 8px 20px; +} +.bds-btn--primary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + background-color: #70EE97; + padding: 8px 13px 8px 20px; + gap: 22px; +} +.bds-btn--primary:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: #70EE97; +} +.bds-btn--primary:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + background-color: #70EE97; + border: 2px solid #141414; + outline: none; + padding: 6px 11px 6px 18px; + gap: 22px; +} +.bds-btn--primary:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: #70EE97; + border: 2px solid #141414; + outline: none; + padding: 6px 17px 6px 18px; +} +.bds-btn--primary:active:not(:disabled):not(.bds-btn--disabled) { + background-color: #21E46B; + transform: scale(0.98); + padding: 8px 19px 8px 20px; + gap: 16px; +} +.bds-btn--primary:disabled, .bds-btn--primary.bds-btn--disabled { + color: #838386; + background-color: #E0E0E1; + cursor: not-allowed; + pointer-events: none; +} +.bds-btn--primary:disabled .bds-btn__icon, .bds-btn--primary.bds-btn--disabled .bds-btn__icon { + opacity: 0.5; +} +@media (max-width: 1023px) { + .bds-btn--primary { + padding: 8px 15px 8px 16px; + gap: 16px; + } + .bds-btn--primary.bds-btn--no-icon { + padding: 8px 16px; + } + .bds-btn--primary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + padding: 8px 10px 8px 16px; + gap: 21px; + } + .bds-btn--primary:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + background-color: #70EE97; + } + .bds-btn--primary:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) { + padding: 6px 8px 6px 14px; + gap: 21px; + } + .bds-btn--primary:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon { + padding: 6px 13px 6px 14px; + } + .bds-btn--primary:active:not(:disabled):not(.bds-btn--disabled) { + padding: 8px 15px 8px 16px; + gap: 16px; + } +} + /* TABLE STYLING */ article table { clear: right; diff --git a/static/img/icons/button/button-arrow-hovered.svg b/static/img/icons/button/button-arrow-hovered.svg new file mode 100644 index 0000000000..e08a6f4a5a --- /dev/null +++ b/static/img/icons/button/button-arrow-hovered.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/icons/button/button-arrow-right.svg b/static/img/icons/button/button-arrow-right.svg new file mode 100644 index 0000000000..550a0dcc6a --- /dev/null +++ b/static/img/icons/button/button-arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/styles/xrpl.scss b/styles/xrpl.scss index fccfd89490..07e507b429 100644 --- a/styles/xrpl.scss +++ b/styles/xrpl.scss @@ -85,6 +85,7 @@ $grid-breakpoints: ( @import "_side-nav.scss"; @import "_helpers.scss"; @import "_buttons.scss"; +@import "../shared/components/Button/Button.scss"; @import "_tables.scss"; @import "_tables.scss"; @import "_use-cases.scss";