Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
aelmanaa committed Dec 8, 2024
1 parent 785f603 commit cf582a8
Show file tree
Hide file tree
Showing 10 changed files with 1,075 additions and 167 deletions.
166 changes: 166 additions & 0 deletions src/components/CCIP/TutorialBlockchainSelector/ChainSelect.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
.container {
position: relative;
width: 100%;
}

.trigger {
width: 100%;
height: 48px;
padding: 0 16px;
background: linear-gradient(
to bottom,
var(--color-background-secondary),
rgba(var(--color-background-secondary-rgb), 0.8)
);
border: none;
border-radius: 12px;
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
font-weight: 500;
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(255, 255, 255, 0.1), 0 2px 4px rgba(0, 0, 0, 0.02);
}

.trigger:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.trigger.active {
background: var(--color-background);
color: var(--color-accent);
font-weight: 600;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.placeholder {
color: var(--color-text-secondary);
font-weight: 500;
}

.chainLogo {
width: 24px;
height: 24px;
border-radius: 50%;
flex-shrink: 0;
}

.arrow {
margin-left: auto;
color: var(--color-accent);
font-size: 16px;
transition: transform 0.2s ease;
}

.active .arrow {
transform: rotate(180deg);
}

.dropdown {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: var(--color-background);
border: 1px solid rgba(var(--color-border-rgb), 0.1);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
max-height: 300px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
z-index: 100;
animation: slideDown 0.2s ease;
backdrop-filter: blur(8px);
}

.option {
width: 100%;
padding: 12px 16px;
border: none;
background: var(--color-background);
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
color: var(--color-text-primary);
cursor: pointer;
transition: all 0.15s ease;
border-bottom: 1px solid rgba(var(--color-border-rgb), 0.06);
}

.option:hover {
background: var(--color-background-secondary);
transform: translateX(4px);
}

.option.selected {
background: rgba(var(--color-accent-rgb), 0.1);
color: var(--color-accent);
font-weight: 500;
}

.option:last-child {
border-bottom: none;
}

@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.dropdown::-webkit-scrollbar {
width: 8px;
}

.dropdown::-webkit-scrollbar-track {
background: transparent;
}

.dropdown::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 4px;
}

.chainSection {
background: var(--color-background);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06), 0 2px 4px rgba(0, 0, 0, 0.03);
margin-bottom: 24px;
}

.option.focused {
background: var(--color-background-secondary);
outline: none;
position: relative;
}

.option.focused::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--color-accent);
}

.dropdown::before {
content: "Use arrow keys or Page Up/Down to navigate";
display: block;
padding: 8px 16px;
color: var(--color-text-secondary);
font-size: 12px;
border-bottom: 1px solid var(--color-border);
}
142 changes: 142 additions & 0 deletions src/components/CCIP/TutorialBlockchainSelector/ChainSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useState, useRef, useEffect } from "react"
import styles from "./ChainSelect.module.css"
import type { Network } from "@config/data/ccip/types"

interface ChainSelectProps {
value: string
onChange: (value: string) => void
options: Network[]
placeholder: string
}

export const ChainSelect = ({ value, onChange, options, placeholder }: ChainSelectProps) => {
const [isOpen, setIsOpen] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
const selectedOption = options.find((opt) => opt.chain === value)
const [focusedIndex, setFocusedIndex] = useState(-1)
const dropdownRef = useRef<HTMLDivElement>(null)

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false)
setFocusedIndex(-1)
}
}

const handleKeyDown = (event: KeyboardEvent) => {
if (!isOpen) return

switch (event.key) {
case "ArrowDown":
event.preventDefault()
setFocusedIndex((prev) => Math.min(prev + 1, options.length - 1))
break
case "ArrowUp":
event.preventDefault()
setFocusedIndex((prev) => Math.max(prev - 1, 0))
break
case "PageDown":
event.preventDefault()
setFocusedIndex((prev) => Math.min(prev + 5, options.length - 1))
break
case "PageUp":
event.preventDefault()
setFocusedIndex((prev) => Math.max(prev - 5, 0))
break
case "Home":
event.preventDefault()
setFocusedIndex(0)
break
case "End":
event.preventDefault()
setFocusedIndex(options.length - 1)
break
case "Enter":
if (focusedIndex >= 0) {
onChange(options[focusedIndex].chain)
setIsOpen(false)
setFocusedIndex(-1)
}
break
case "Escape":
setIsOpen(false)
setFocusedIndex(-1)
break
}
}

document.addEventListener("mousedown", handleClickOutside)
document.addEventListener("keydown", handleKeyDown)
return () => {
document.removeEventListener("mousedown", handleClickOutside)
document.removeEventListener("keydown", handleKeyDown)
}
}, [isOpen, options, onChange, focusedIndex])

useEffect(() => {
if (isOpen && focusedIndex >= 0 && dropdownRef.current) {
const focusedElement = dropdownRef.current.children[focusedIndex] as HTMLElement
if (focusedElement) {
focusedElement.scrollIntoView({ block: "nearest", behavior: "smooth" })
}
}
}, [focusedIndex, isOpen])

return (
<div
ref={containerRef}
className={styles.container}
role="combobox"
aria-expanded={isOpen}
aria-haspopup="listbox"
aria-controls="chain-select-dropdown"
>
<button
className={`${styles.trigger} ${isOpen ? styles.active : ""}`}
onClick={() => setIsOpen(!isOpen)}
type="button"
>
{selectedOption ? (
<>
<img src={selectedOption.logo} alt="" className={styles.chainLogo} />
<span>{selectedOption.name}</span>
</>
) : (
<span className={styles.placeholder}>{placeholder}</span>
)}
<span className={styles.arrow}></span>
</button>

{isOpen && (
<div
ref={dropdownRef}
id="chain-select-dropdown"
className={styles.dropdown}
role="listbox"
aria-label="Select blockchain"
tabIndex={-1}
>
{options.map((option, idx) => (
<button
key={option.chain}
role="option"
aria-selected={value === option.chain}
className={`${styles.option} ${value === option.chain ? styles.selected : ""} ${
focusedIndex === idx ? styles.focused : ""
}`}
onClick={() => {
onChange(option.chain)
setIsOpen(false)
}}
onMouseEnter={() => setFocusedIndex(idx)}
>
<img src={option.logo} alt="" className={styles.chainLogo} />
<span>{option.name}</span>
</button>
))}
</div>
)}
</div>
)
}
Loading

0 comments on commit cf582a8

Please sign in to comment.