forked from koshikraj/ottopus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwallet-filter.tsx
More file actions
155 lines (146 loc) · 5.8 KB
/
Copy pathwallet-filter.tsx
File metadata and controls
155 lines (146 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
'use client'
import { useEffect, useRef, useState } from 'react'
import { WalletMark, type WalletRef } from '@/components/portfolio/wallet-marks'
import { Button, Dialog, SHEET_MEDIA } from '@/components/ui'
import { cn } from '@/lib/cn'
import { useMediaQuery } from '@/lib/use-media-query'
import type { WalletOption } from './plans'
export interface WalletFilterProps {
wallets: readonly (WalletOption & { ref: WalletRef })[]
value: string
onChange: (caip10: string) => void
}
/**
* The wallet picker on the requests header — the portfolio's network filter,
* with wallets in it. A dropdown anchored to the chip on a wide screen and a
* bottom sheet on a phone, the same rows in both.
*/
export function WalletFilter({ wallets, value, onChange }: WalletFilterProps) {
const [open, setOpen] = useState(false)
const sheet = useMediaQuery(SHEET_MEDIA)
const root = useRef<HTMLDivElement>(null)
const trigger = useRef<HTMLButtonElement>(null)
const selected = wallets.find((w) => w.address === value)
useEffect(() => {
if (!open || sheet) return
const outside = (event: PointerEvent) => {
if (event.target instanceof Node && !root.current?.contains(event.target)) setOpen(false)
}
document.addEventListener('pointerdown', outside)
return () => document.removeEventListener('pointerdown', outside)
}, [open, sheet])
if (wallets.length < 2) return null
const pick = (caip10: string) => {
onChange(caip10)
setOpen(false)
if (!sheet) trigger.current?.focus()
}
const rows = (rowClassName: string) => (
<>
<button
type="button"
aria-pressed={value === 'all'}
onClick={() => pick('all')}
className={cn(
'flex w-full cursor-pointer items-center gap-2.5 rounded-lg text-left transition-colors hover:bg-[var(--ot-surface-3)]',
'focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ot-plan)]',
rowClassName,
value === 'all' && 'bg-[var(--ot-surface-3)] font-semibold',
)}
>
<span aria-hidden className="flex -space-x-2 pr-1">
{wallets.slice(0, 3).map((w) => (
<WalletMark key={w.address} wallet={w.ref} size={22} className="ring-2 ring-[var(--ot-card)]" />
))}
</span>
<span className="flex-1 truncate">All wallets</span>
<span className="text-[11px] text-[var(--ot-text-3)]">{wallets.length}</span>
{value === 'all' ? <span aria-hidden>✓</span> : null}
</button>
{wallets.map((w) => (
<button
key={w.address}
type="button"
aria-pressed={value === w.address}
onClick={() => pick(w.address)}
className={cn(
'flex w-full cursor-pointer items-center gap-2.5 rounded-lg text-left transition-colors hover:bg-[var(--ot-surface-3)]',
'focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ot-plan)]',
rowClassName,
value === w.address && 'bg-[var(--ot-surface-3)] font-semibold',
)}
>
<WalletMark wallet={w.ref} size={24} />
<span className="flex-1 truncate">{w.label}</span>
<span className="text-[11px] text-[var(--ot-text-3)]">{w.count}</span>
{value === w.address ? <span aria-hidden>✓</span> : null}
</button>
))}
</>
)
return (
<div
ref={root}
className="relative"
onBlur={(event) => {
if (!sheet && !event.currentTarget.contains(event.relatedTarget)) setOpen(false)
}}
onKeyDown={(event) => {
if (!sheet && event.key === 'Escape') {
setOpen(false)
trigger.current?.focus()
}
}}
>
<button
ref={trigger}
type="button"
aria-expanded={open}
aria-haspopup={sheet ? 'dialog' : undefined}
aria-label={`Filter by wallet: ${selected?.label ?? 'All wallets'}`}
onClick={() => setOpen(!open)}
className="flex cursor-pointer items-center gap-2 rounded-full border border-[var(--ot-border)] bg-[var(--ot-card)] px-3 py-2 text-[12px] font-medium shadow-sm transition-colors hover:bg-[var(--ot-surface-3)]"
>
{selected ? (
<WalletMark wallet={selected.ref} size={20} />
) : (
<span aria-hidden className="flex -space-x-2 pr-1">
{wallets.slice(0, 3).map((w) => (
<WalletMark key={w.address} wallet={w.ref} size={20} className="ring-2 ring-[var(--ot-card)]" />
))}
</span>
)}
<span className="max-w-32 truncate">{selected?.label ?? 'All wallets'}</span>
<svg aria-hidden viewBox="0 0 12 12" className={cn('h-3 w-3 transition-transform', open && 'rotate-180')}>
<path d="m3 4.5 3 3 3-3" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
{sheet ? (
<Dialog
open={open}
onClose={() => setOpen(false)}
title="Wallet"
description="The list follows the wallet you pick."
className="[&_.ot-dialog-panel]:max-h-[85dvh]"
actions={
<Button variant="secondary" fullWidth onClick={() => setOpen(false)}>
Close
</Button>
}
>
<div className="ot-scroll -mx-1 min-h-0 overflow-y-auto overscroll-contain" role="group" aria-label="Wallets">
{rows('px-3 py-3 text-[14px]')}
</div>
</Dialog>
) : open ? (
<div
className="absolute right-0 top-full z-30 mt-2 max-h-80 w-64 overflow-y-auto rounded-2xl border border-[var(--ot-border)] bg-[var(--ot-card)] p-1.5 shadow-xl"
role="group"
aria-label="Wallets"
>
{rows('px-2.5 py-2 text-[13px]')}
</div>
) : null}
</div>
)
}