fix: fixed carousels ux

- made categories badges a carousel
- added custom arrows to apps carousel on non-tocuh devices
- fixed invalid items offset on mobile sometimes
This commit is contained in:
MTG2000
2022-02-03 18:54:38 +02:00
parent 262d54cc43
commit 923e28282d
10 changed files with 310 additions and 25 deletions

148
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"dependencies": {
"@apollo/client": "^3.5.5",
"@prisma/client": "3.5.0",
"@react-spring/web": "^9.4.2",
"@reduxjs/toolkit": "^1.6.2",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^11.2.7",
@@ -18,6 +19,7 @@
"@types/node": "^12.20.36",
"@types/react": "^17.0.33",
"@types/react-dom": "^17.0.10",
"@use-gesture/react": "^10.2.5",
"apollo-server": "^3.5.0",
"apollo-server-lambda": "^3.5.0",
"axios": "^0.24.0",
@@ -4029,6 +4031,73 @@
"react-dom": "15.x || 16.x || 16.4.0-alpha.0911da3"
}
},
"node_modules/@react-spring/animated": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.4.2.tgz",
"integrity": "sha512-Dzum5Ho8e+LIAegAqRyoQFakD2IVH3ZQ2nsFXJorAFq3Xjv6IVPz/+TNxb/wSvnsMludfoF+ZIf319FSFmgD5w==",
"dependencies": {
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@react-spring/core": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.4.2.tgz",
"integrity": "sha512-Ej/ULwdx8rQtMAWEpLgwbKcQEx6vPfjyG3cxLP05zAInpCoWkYpl+sXOp9tn3r99mTNQPTTt7BgQsSnmQA8+rQ==",
"dependencies": {
"@react-spring/animated": "~9.4.0",
"@react-spring/rafz": "~9.4.0",
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-spring/donate"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@react-spring/rafz": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.4.2.tgz",
"integrity": "sha512-rSm+G8E/XEEpnCGtT/xYN6o8VvEXlU8wN/hyKp4Q44XAZzGSMHLIFP7pY94/MmWsxCxjkw1AxUWhiFYxWrnI5Q=="
},
"node_modules/@react-spring/shared": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.4.2.tgz",
"integrity": "sha512-mZtbQLpMm6Vy5+O1MSlY9KuAcMO8rdUQvtdnC7Or7y7xiZlnzj8oAILyO6Y2rD2ZC1PmgVS0gMev/8T+MykW+Q==",
"dependencies": {
"@react-spring/rafz": "~9.4.0",
"@react-spring/types": "~9.4.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@react-spring/types": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.4.2.tgz",
"integrity": "sha512-GGiIscTM+CEUNV52anj3g5FqAZKL2+eRKtvBOAlC99qGBbvJ3qTLImrUR/I3lXY7PRuLgzI6kh34quA1oUxWYQ=="
},
"node_modules/@react-spring/web": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.4.2.tgz",
"integrity": "sha512-sWfA9NkVuvVOpjSlMkD2zcF6X3i8NSHTeH/uHCGKsN3mYqgkhvAF+E8GASO/H4KKGNhbRvgCZiwJXOtOGyUg6A==",
"dependencies": {
"@react-spring/animated": "~9.4.0",
"@react-spring/core": "~9.4.0",
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.6.2",
"integrity": "sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==",
@@ -8268,6 +8337,22 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@use-gesture/core": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.5.tgz",
"integrity": "sha512-Pggq5qLipJNYjMq6R7p7Y5h1juUlTkrhi9KqFJH6PcRFCNn+evjbOvpX7JEFehrx4Ik9UZcQpkmR+E0s7a5Lbg=="
},
"node_modules/@use-gesture/react": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.5.tgz",
"integrity": "sha512-AEVi2BBKOms7UNoRzthxwe4t3UVe/S1Vm4K2Vitbup3TkbhF3x70/ZbgE/TxhucYFcOPUcrQ89squEs/liQezg==",
"dependencies": {
"@use-gesture/core": "10.2.5"
},
"peerDependencies": {
"react": ">= 16.8.0"
}
},
"node_modules/@vendia/serverless-express": {
"version": "4.5.2",
"integrity": "sha512-mekBOPnBxfhIvBYKVwfvjp9NtS+bOs3F08Vudxa3Fb7zkxtdjRn0UMLRT6zwWf6i4V5rjk2aEhzbIDSCV1GQ0w==",
@@ -56662,6 +56747,56 @@
"react-lifecycles-compat": "^3.0.4"
}
},
"@react-spring/animated": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.4.2.tgz",
"integrity": "sha512-Dzum5Ho8e+LIAegAqRyoQFakD2IVH3ZQ2nsFXJorAFq3Xjv6IVPz/+TNxb/wSvnsMludfoF+ZIf319FSFmgD5w==",
"requires": {
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
}
},
"@react-spring/core": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.4.2.tgz",
"integrity": "sha512-Ej/ULwdx8rQtMAWEpLgwbKcQEx6vPfjyG3cxLP05zAInpCoWkYpl+sXOp9tn3r99mTNQPTTt7BgQsSnmQA8+rQ==",
"requires": {
"@react-spring/animated": "~9.4.0",
"@react-spring/rafz": "~9.4.0",
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
}
},
"@react-spring/rafz": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.4.2.tgz",
"integrity": "sha512-rSm+G8E/XEEpnCGtT/xYN6o8VvEXlU8wN/hyKp4Q44XAZzGSMHLIFP7pY94/MmWsxCxjkw1AxUWhiFYxWrnI5Q=="
},
"@react-spring/shared": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.4.2.tgz",
"integrity": "sha512-mZtbQLpMm6Vy5+O1MSlY9KuAcMO8rdUQvtdnC7Or7y7xiZlnzj8oAILyO6Y2rD2ZC1PmgVS0gMev/8T+MykW+Q==",
"requires": {
"@react-spring/rafz": "~9.4.0",
"@react-spring/types": "~9.4.0"
}
},
"@react-spring/types": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.4.2.tgz",
"integrity": "sha512-GGiIscTM+CEUNV52anj3g5FqAZKL2+eRKtvBOAlC99qGBbvJ3qTLImrUR/I3lXY7PRuLgzI6kh34quA1oUxWYQ=="
},
"@react-spring/web": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.4.2.tgz",
"integrity": "sha512-sWfA9NkVuvVOpjSlMkD2zcF6X3i8NSHTeH/uHCGKsN3mYqgkhvAF+E8GASO/H4KKGNhbRvgCZiwJXOtOGyUg6A==",
"requires": {
"@react-spring/animated": "~9.4.0",
"@react-spring/core": "~9.4.0",
"@react-spring/shared": "~9.4.0",
"@react-spring/types": "~9.4.0"
}
},
"@reduxjs/toolkit": {
"version": "1.6.2",
"integrity": "sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==",
@@ -59624,6 +59759,19 @@
"eslint-visitor-keys": "^2.0.0"
}
},
"@use-gesture/core": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.5.tgz",
"integrity": "sha512-Pggq5qLipJNYjMq6R7p7Y5h1juUlTkrhi9KqFJH6PcRFCNn+evjbOvpX7JEFehrx4Ik9UZcQpkmR+E0s7a5Lbg=="
},
"@use-gesture/react": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.5.tgz",
"integrity": "sha512-AEVi2BBKOms7UNoRzthxwe4t3UVe/S1Vm4K2Vitbup3TkbhF3x70/ZbgE/TxhucYFcOPUcrQ89squEs/liQezg==",
"requires": {
"@use-gesture/core": "10.2.5"
}
},
"@vendia/serverless-express": {
"version": "4.5.2",
"integrity": "sha512-mekBOPnBxfhIvBYKVwfvjp9NtS+bOs3F08Vudxa3Fb7zkxtdjRn0UMLRT6zwWf6i4V5rjk2aEhzbIDSCV1GQ0w=="

View File

@@ -6,6 +6,7 @@
"dependencies": {
"@apollo/client": "^3.5.5",
"@prisma/client": "3.5.0",
"@react-spring/web": "^9.4.2",
"@reduxjs/toolkit": "^1.6.2",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^11.2.7",
@@ -14,6 +15,7 @@
"@types/node": "^12.20.36",
"@types/react": "^17.0.33",
"@types/react-dom": "^17.0.10",
"@use-gesture/react": "^10.2.5",
"apollo-server": "^3.5.0",
"apollo-server-lambda": "^3.5.0",
"axios": "^0.24.0",

View File

@@ -29,8 +29,8 @@ const badgeSize: UnionToObjectKeys<Props, 'size'> = {
}
const loadingBadgeSize: UnionToObjectKeys<Props, 'size'> = {
sm: "w-48 h-24 text-body6",
md: "w-64 h-32 text-body4",
sm: "w-48 h-[28px] text-body6",
md: "w-64 h-[38px] text-body4",
lg: "w-64 h-42 text-body3"
}
@@ -50,7 +50,8 @@ export default function Badge(
: PropsWithChildren<Props>) {
const classes = `
rounded-48 shadow-${shadow} border inline-block relative align-middle
rounded-48 border inline-block relative align-middle
shadow-${shadow}
${badgrColor[color]}
${badgeSize[size]}
${className}
@@ -58,7 +59,8 @@ export default function Badge(
`
if (isLoading)
return <Skeleton width="6ch" className={`${loadingBadgeSize[size]} !rounded-48 leading-0`} />
return <Skeleton width="7ch" className={`${loadingBadgeSize[size]} !rounded-48`} />
return (

View File

@@ -0,0 +1,35 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Slider from './Slider';
export default {
title: 'Shared/Slider',
component: Slider,
} as ComponentMeta<typeof Slider>;
const Template: ComponentStory<typeof Slider> = (args) => <div className="bg-blue-100 max-w-[600px]"><Slider {...args} >
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(1) }}>
1
</div>
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(2) }}>
2
</div>
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(3) }}>
3
</div>
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(4) }}>
4
</div>
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(5) }}>
5
</div>
<div className="px-64 py-16 bg-gray-300" onClick={() => { alert(6) }}>
6
</div>
</Slider></div>;
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -0,0 +1,45 @@
import React, { useRef, useState, useEffect, useCallback, PropsWithChildren } from 'react';
import { useDrag } from '@use-gesture/react'
import { useSpring, animated } from '@react-spring/web'
import { useResizeListener } from 'src/utils/hooks';
interface Props {
gap?: number
}
export default function Slider({
gap = 12,
children
}: PropsWithChildren<Props>) {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const containerRef = useRef<HTMLDivElement>(null!);
const sliderRef = useRef<HTMLDivElement>(null!);
const [sliderWidth, setSliderWidth] = useState(-1);
const bind = useDrag(({ down, offset: [ox, oy] }) => api.start({ x: ox, y: oy, immediate: down }), {
bounds: () => ({ left: -sliderWidth, right: 0, top: 0, bottom: 0 }),
rubberband: [.15, 0],
filterTaps: true
})
const resizeListener = useCallback(() => {
setSliderWidth(Math.max(sliderRef.current?.scrollWidth - containerRef.current?.clientWidth, 0));
}, [setSliderWidth]);
useResizeListener(resizeListener)
useEffect(() => {
setSliderWidth(Math.max(sliderRef.current?.scrollWidth - containerRef.current?.clientWidth, 0));
}, [setSliderWidth]);
return <>
<div className='w-full relative ' ref={containerRef} style={{ height: sliderRef.current?.scrollHeight + 'px' }}>
<animated.div {...bind()} style={{ x, y, touchAction: 'none', gap: `${gap}px` }} className={`absolute top-0 left-0 flex w-max select-none`} ref={sliderRef} >
{children}
</animated.div>
</div>
</>;
}

View File

@@ -94,3 +94,9 @@ svg {
.no-scrollbar ::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
@media (pointer: coarse) {
.touch-device\:hidden {
display: none;
}
}

View File

@@ -1,6 +1,8 @@
import { useQuery } from '@apollo/client';
import { ALL_CATEGORIES_QUERY, ALL_CATEGORIES_QUERY_RES } from './query';
import Badge from 'src/Components/Badge/Badge'
import Slider from 'src/Components/Slider/Slider'
export default function Categories() {
@@ -11,17 +13,19 @@ export default function Categories() {
}
if (loading || !data)
return <div className="flex gap-12 flex-wrap">
return <div className="flex gap-12">
{Array(5).fill(0).map((_, idx) =>
<Badge key={idx} isLoading></Badge>
)}
</div>
return (
<div className="flex gap-12 flex-wrap">
// <div className="flex gap-12 flex-wrap">
<Slider>
{data?.allCategories.map(category =>
<Badge key={category.id} onClick={() => handleClick(category.id)}>{category.title}</Badge>
)}
</div>
</Slider>
// </div>
)
}

View File

@@ -1,21 +1,30 @@
import { ReactElement, useCallback, useRef, useState } from "react";
import { ReactElement, useRef, useState } from "react";
import { ProjectCard } from "../../../utils/interfaces";
import Carousel from 'react-multi-carousel';
import { MdDoubleArrow, } from 'react-icons/md';
import { MdArrowRight, MdDoubleArrow, } from 'react-icons/md';
import { useAppDispatch } from "../../../utils/hooks";
import { openModal } from "../../../redux/features/modals.slice";
import ProjectCardMini from "../ProjectCardMini/ProjectCardMini";
import { useResizeListener } from 'src/utils/hooks'
import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
import './style.css';
const responsive = {
all: {
breakpoint: { max: 5000, min: 0 },
items: (((window.innerWidth - 64) / (296 + 48))),
items: calcNumItems()
}
}
const calcNumItems = () => {
const items = (((window.innerWidth - 32 - 296) / (296 + 20)));
// const calcNumItems = () => {
// const items = (((window.innerWidth - 32 - 296) / (296 + 20)));
// return items;
// }
function calcNumItems() {
const items = (((window.innerWidth - 2 * 32) / (296 + 20)));
return items;
}
@@ -39,9 +48,14 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
}
useResizeListener(() => {
console.log(calcNumItems());
setCarouselItmsCnt(calcNumItems());
}, [setCarouselItmsCnt])
if (projects.length === 0)
return <></>
return (
<div className='mb-48'>
@@ -52,18 +66,31 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
}} />
</span>
</h3>
<Carousel
containerClass='pl-32 pr-[-32px]'
showDots={false}
arrows={false}
responsive={responsive}
centerMode
itemClass='pb-[1px]'
>
{projects.map((project, idx) =>
<ProjectCardMini key={idx} project={project} onClick={handleClick} />
)}
</Carousel>
<div className="px-32">
<Carousel
showDots={false}
// arrows={false}
responsive={responsive}
// centerMode
itemClass='pb-[1px]'
containerClass='group'
customLeftArrow={
<button className='carousel-btns opacity-0 group-hover:opacity-100 transition-opacity w-64 h-full absolute top-0 left-0 rounded-l-12 bg-gradient-to-r from-gray-700 text-white' >
<IoIosArrowBack className='scale-150' />
</button>
}
customRightArrow={
<button className='carousel-btns opacity-0 group-hover:opacity-100 transition-opacity w-64 h-full absolute top-0 right-0 rounded-r-12 bg-gradient-to-l from-gray-700 text-white' >
<IoIosArrowForward className='scale-150' />
</button>
}
>
{projects.map((project, idx) =>
<ProjectCardMini key={idx} project={project} onClick={handleClick} />
)}
</Carousel>
</div>
</div>
)

View File

@@ -0,0 +1,9 @@
@media (pointer: coarse) {
.carousel-btns {
display: none;
}
.react-multi-carousel-list {
overflow: visible;
}
}

View File

@@ -72,8 +72,15 @@ export default function TipCard({ onClose, direction, tipValue, ...props }: Prop
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
const webln = await Wallet_Service.getWebln()
const paymentResponse = await webln.sendPayment(votingData.vote.payment_request);
console.log(paymentResponse);
setPaymentStatus(PaymentStatus.PAID);
confirmVote({ variables: { paymentRequest: votingData.vote.payment_request, preimage: paymentResponse.preimage } })
confirmVote({
variables: {
paymentRequest: votingData.vote.payment_request,
preimage: paymentResponse.preimage
}
})
.catch() // ONLY TEMPROARY !!! SHOULD BE FIXED FROM BACKEND
.finally(() => {
setTimeout(() => {