mirror of
https://github.com/dergigi/boris.git
synced 2026-01-22 16:24:30 +01:00
feat(ui): replace custom pull-to-refresh with use-pull-to-refresh library for simplicity
- Remove custom usePullToRefresh hook and PullToRefreshIndicator - Add use-pull-to-refresh library dependency - Create simple RefreshIndicator component - Apply pull-to-refresh to Explore and Me screens - Simplify implementation while maintaining functionality
This commit is contained in:
@@ -18,8 +18,8 @@ import { UserSettings } from '../services/settingsService'
|
||||
import BlogPostCard from './BlogPostCard'
|
||||
import { HighlightItem } from './HighlightItem'
|
||||
import { getCachedPosts, upsertCachedPost, setCachedPosts, getCachedHighlights, upsertCachedHighlight, setCachedHighlights } from '../services/exploreCache'
|
||||
import { usePullToRefresh } from '../hooks/usePullToRefresh'
|
||||
import PullToRefreshIndicator from './PullToRefreshIndicator'
|
||||
import { usePullToRefresh } from 'use-pull-to-refresh'
|
||||
import RefreshIndicator from './RefreshIndicator'
|
||||
import { classifyHighlights } from '../utils/highlightClassification'
|
||||
import { HighlightVisibility } from './HighlightsPanel'
|
||||
|
||||
@@ -41,7 +41,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
||||
const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set())
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const exploreContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||
|
||||
// Visibility filters (defaults from settings)
|
||||
@@ -229,11 +228,13 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
||||
}, [relayPool, activeAccount, refreshTrigger, eventStore, settings])
|
||||
|
||||
// Pull-to-refresh
|
||||
const pullToRefreshState = usePullToRefresh(exploreContainerRef, {
|
||||
const { isRefreshing, pullPosition } = usePullToRefresh({
|
||||
onRefresh: () => {
|
||||
setRefreshTrigger(prev => prev + 1)
|
||||
},
|
||||
isRefreshing: loading
|
||||
maximumPullLength: 240,
|
||||
refreshThreshold: 80,
|
||||
isDisabled: !activeAccount
|
||||
})
|
||||
|
||||
const getPostUrl = (post: BlogPostPreview) => {
|
||||
@@ -393,15 +394,10 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={exploreContainerRef}
|
||||
className={`explore-container pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
|
||||
>
|
||||
<PullToRefreshIndicator
|
||||
isPulling={pullToRefreshState.isPulling}
|
||||
pullDistance={pullToRefreshState.pullDistance}
|
||||
canRefresh={pullToRefreshState.canRefresh}
|
||||
isRefreshing={loading && pullToRefreshState.canRefresh}
|
||||
<div className="explore-container">
|
||||
<RefreshIndicator
|
||||
isRefreshing={isRefreshing}
|
||||
pullPosition={pullPosition}
|
||||
/>
|
||||
<div className="explore-header">
|
||||
<h1>
|
||||
|
||||
@@ -22,8 +22,8 @@ import { ViewMode } from './Bookmarks'
|
||||
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||
import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../services/meCache'
|
||||
import { faBooks } from '../icons/customIcons'
|
||||
import { usePullToRefresh } from '../hooks/usePullToRefresh'
|
||||
import PullToRefreshIndicator from './PullToRefreshIndicator'
|
||||
import { usePullToRefresh } from 'use-pull-to-refresh'
|
||||
import RefreshIndicator from './RefreshIndicator'
|
||||
import { getProfileUrl } from '../config/nostrGateways'
|
||||
|
||||
interface MeProps {
|
||||
@@ -49,7 +49,6 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('cards')
|
||||
const meContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||
|
||||
// Update local state when prop changes
|
||||
@@ -125,11 +124,13 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
}, [relayPool, viewingPubkey, isOwnProfile, activeAccount, refreshTrigger])
|
||||
|
||||
// Pull-to-refresh
|
||||
const pullToRefreshState = usePullToRefresh(meContainerRef, {
|
||||
const { isRefreshing, pullPosition } = usePullToRefresh({
|
||||
onRefresh: () => {
|
||||
setRefreshTrigger(prev => prev + 1)
|
||||
},
|
||||
isRefreshing: loading
|
||||
maximumPullLength: 240,
|
||||
refreshThreshold: 80,
|
||||
isDisabled: !viewingPubkey
|
||||
})
|
||||
|
||||
const handleHighlightDelete = (highlightId: string) => {
|
||||
@@ -367,15 +368,10 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={meContainerRef}
|
||||
className={`explore-container pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
|
||||
>
|
||||
<PullToRefreshIndicator
|
||||
isPulling={pullToRefreshState.isPulling}
|
||||
pullDistance={pullToRefreshState.pullDistance}
|
||||
canRefresh={pullToRefreshState.canRefresh}
|
||||
isRefreshing={loading && pullToRefreshState.canRefresh}
|
||||
<div className="explore-container">
|
||||
<RefreshIndicator
|
||||
isRefreshing={isRefreshing}
|
||||
pullPosition={pullPosition}
|
||||
/>
|
||||
<div className="explore-header">
|
||||
{viewingPubkey && <AuthorCard authorPubkey={viewingPubkey} clickable={false} />}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArrowDown } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
interface PullToRefreshIndicatorProps {
|
||||
isPulling: boolean
|
||||
pullDistance: number
|
||||
canRefresh: boolean
|
||||
isRefreshing: boolean
|
||||
threshold?: number
|
||||
}
|
||||
|
||||
const PullToRefreshIndicator: React.FC<PullToRefreshIndicatorProps> = ({
|
||||
isPulling,
|
||||
pullDistance,
|
||||
canRefresh,
|
||||
threshold = 80
|
||||
}) => {
|
||||
// Only show when actively pulling, not when refreshing
|
||||
if (!isPulling) return null
|
||||
|
||||
const opacity = Math.min(pullDistance / threshold, 1)
|
||||
const rotation = (pullDistance / threshold) * 180
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pull-to-refresh-indicator"
|
||||
style={{
|
||||
opacity,
|
||||
transform: `translateY(${-20 + pullDistance / 2}px)`
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="pull-to-refresh-icon"
|
||||
style={{
|
||||
transform: `rotate(${rotation}deg)`
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowDown}
|
||||
style={{ color: canRefresh ? 'var(--accent-color, #3b82f6)' : 'var(--text-secondary)' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="pull-to-refresh-text">
|
||||
{canRefresh ? 'Release to refresh' : 'Pull to refresh'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PullToRefreshIndicator
|
||||
|
||||
63
src/components/RefreshIndicator.tsx
Normal file
63
src/components/RefreshIndicator.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArrowRotateRight } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
interface RefreshIndicatorProps {
|
||||
isRefreshing: boolean
|
||||
pullPosition: number
|
||||
}
|
||||
|
||||
const THRESHOLD = 80
|
||||
|
||||
/**
|
||||
* Simple pull-to-refresh visual indicator
|
||||
*/
|
||||
const RefreshIndicator: React.FC<RefreshIndicatorProps> = ({
|
||||
isRefreshing,
|
||||
pullPosition
|
||||
}) => {
|
||||
const isVisible = isRefreshing || pullPosition > 0
|
||||
if (!isVisible) return null
|
||||
|
||||
const opacity = Math.min(pullPosition / THRESHOLD, 1)
|
||||
const translateY = isRefreshing ? THRESHOLD / 3 : pullPosition / 3
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: `${translateY}px`,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: 30,
|
||||
opacity,
|
||||
transition: isRefreshing ? 'opacity 0.2s' : 'none'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--surface-secondary, #ffffff)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRotateRight}
|
||||
style={{
|
||||
transform: isRefreshing ? 'none' : `rotate(${pullPosition}deg)`,
|
||||
color: 'var(--accent-color, #3b82f6)'
|
||||
}}
|
||||
className={isRefreshing ? 'fa-spin' : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RefreshIndicator
|
||||
|
||||
Reference in New Issue
Block a user