add full primary button

This commit is contained in:
akcodez
2025-12-01 12:01:50 -08:00
parent e92929e148
commit 9cf1b07954
12 changed files with 2140 additions and 3 deletions

View File

@@ -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
// }

View File

@@ -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 = () => (
<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.
* Currently supports the Primary variant with plans for Secondary and Tertiary.
*
* @example
* <Button variant="primary" onClick={handleClick}>Get Started</Button>
*/
export const Button: React.FC<ButtonProps> = ({
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 (
<button
type={type}
className={classNames}
onClick={onClick}
disabled={disabled}
aria-disabled={disabled}
>
<span className="bds-btn__label">{children}</span>
{showIcon && <ArrowIcon />}
</button>
);
};
export default Button;

View File

@@ -0,0 +1,2 @@
export { Button, type ButtonProps } from './Button';
export { default } from './Button';