Skip to content
This repository was archived by the owner on Jul 17, 2024. It is now read-only.

Commit 13d691a

Browse files
authored
Merge pull request #155 from turistikrota/feature/calendar-v2
Feature/calendar v2
2 parents d385a19 + b08d6f8 commit 13d691a

7 files changed

Lines changed: 270 additions & 75 deletions

File tree

packages/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@turistikrota/ui",
3-
"version": "1.0.9",
3+
"version": "1.1.1",
44
"description": "the turistikrota ui library for React",
55
"main": "./index.js",
66
"module": "./index.js",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react'
2+
import { useAgentMobile } from '../hooks/dom'
3+
import { CarouselButtonProps } from './carousel.types'
4+
5+
const CarouselButton: React.FC<CarouselButtonProps> = ({ position, onClick }) => {
6+
const isMobile = useAgentMobile()
7+
const icon = position === 'left' ? 'bx-chevron-left' : 'bx-chevron-right'
8+
const left = position === 'left' ? 'left-2' : 'right-2'
9+
return (
10+
<button
11+
className={`${
12+
!isMobile ? 'flex' : 'hidden'
13+
} invisible absolute top-1/2 opacity-0 transition-all duration-200 group-hover:visible group-hover:opacity-90 group-hover:hover:opacity-100 ${left} shadow-xs bg-second h-8 w-8 -translate-y-1/2 transform items-center justify-center rounded-full border bg-opacity-95 text-gray-600 dark:bg-opacity-10 dark:text-white`}
14+
onClick={onClick}
15+
>
16+
<i className={`bx bx-sm ${icon}`} />
17+
</button>
18+
)
19+
}
20+
21+
export default CarouselButton
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { PreviewComponent } from './carousel.types'
2+
import CarouselSidePreview from './side-preview'
3+
import CarouselSubPreview from './sub-preview'
4+
5+
export enum CarouselVariant {
6+
Map = 'map',
7+
List = 'list',
8+
Detail = 'detail',
9+
DetailHorizontal = 'detail-horizontal',
10+
DetailVertical = 'detail-vertical',
11+
}
12+
13+
export type PreviewStyles = {
14+
provider: string
15+
item: string
16+
}
17+
18+
export type CarouselStyles = {
19+
container: string
20+
provider: string
21+
item: string
22+
itemLoading: string
23+
preview: PreviewStyles
24+
Preview?: PreviewComponent
25+
}
26+
27+
export const CarouselVariants: Record<CarouselVariant, CarouselStyles> = {
28+
[CarouselVariant.Map]: {
29+
container: '',
30+
provider: 'h-75 w-75',
31+
item: 'rounded-b-none',
32+
itemLoading: '',
33+
preview: {
34+
provider: '',
35+
item: '',
36+
},
37+
},
38+
[CarouselVariant.List]: {
39+
container: '',
40+
provider: 'h-72',
41+
item: 'rounded-b-none',
42+
itemLoading: 'rounded-t-md',
43+
preview: {
44+
provider: '',
45+
item: '',
46+
},
47+
},
48+
[CarouselVariant.DetailHorizontal]: {
49+
container: 'grid grid-cols-12 gap-2',
50+
provider: 'h-104 col-span-12 lg:col-span-9',
51+
item: '',
52+
itemLoading: '',
53+
preview: {
54+
provider: 'col-span-12 lg:col-span-3 grid-rows-1 lg:grid-rows-3',
55+
item: 'col-span-3 lg:col-span-6 row-span-1 lg:row-span-1',
56+
},
57+
Preview: CarouselSidePreview,
58+
},
59+
[CarouselVariant.DetailVertical]: {
60+
container: '',
61+
provider: 'h-104',
62+
item: '',
63+
itemLoading: '',
64+
preview: {
65+
provider: '',
66+
item: '',
67+
},
68+
Preview: CarouselSubPreview,
69+
},
70+
[CarouselVariant.Detail]: {
71+
container: '',
72+
provider: 'h-104',
73+
item: '',
74+
itemLoading: '',
75+
preview: {
76+
provider: '',
77+
item: '',
78+
},
79+
},
80+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { CarouselVariant, PreviewStyles } from './carousel.design'
2+
3+
export type CarouselProps = {
4+
images: string[]
5+
variant?: CarouselVariant
6+
imageAltPrefix: string
7+
imageTitlePrefix?: string
8+
activeIndex?: number
9+
onClick?: (image: string, idx: number) => void
10+
autoPlay?: boolean
11+
autoPlayInterval?: number
12+
showDots?: boolean
13+
showPreview?: boolean
14+
}
15+
16+
export type CarouselButtonProps = {
17+
position: 'left' | 'right'
18+
onClick: () => void
19+
}
20+
21+
export type PreviewProps = {
22+
styles?: PreviewStyles
23+
images: string[]
24+
imageTitlePrefix?: string
25+
currentIndex: number
26+
setCurrentIndex: (idx: number) => void
27+
onClick?: (image: string, idx: number) => void
28+
}
29+
30+
export type PreviewComponent = React.FC<PreviewProps>
31+
32+
export type CarouselComponent = React.FC<CarouselProps> & {
33+
Variants: typeof CarouselVariant
34+
}

packages/ui/src/carousel/index.tsx

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,28 @@
1-
import React, { useEffect, useState } from 'react'
1+
import React, { useEffect, useMemo, useState } from 'react'
22
import { useInterval } from '../hooks/async'
3-
import { useAgentMobile } from '../hooks/dom'
43
import PerfectImage from '../image/perfect'
4+
import CarouselButton from './button'
5+
import { CarouselStyles, CarouselVariant, CarouselVariants } from './carousel.design'
6+
import { CarouselComponent } from './carousel.types'
57
import DotRenderer from './dot-renderer'
68

7-
type Props = {
8-
images: string[]
9-
imageAltPrefix: string
10-
imageTitlePrefix?: string
11-
imageClassName?: string
12-
imgLoadingClassName?: string
13-
pictureClassName?: string
14-
sizeClassName: string
15-
activeIndex?: number
16-
className?: string
17-
onClick?: (image: string, idx: number) => void
18-
autoPlay?: boolean
19-
autoPlayInterval?: number
20-
showDots?: boolean
21-
showSubImages?: boolean
22-
}
23-
24-
type ButtonProps = {
25-
position: 'left' | 'right'
26-
onClick: () => void
27-
}
28-
29-
const CarouselButton: React.FC<ButtonProps> = ({ position, onClick }) => {
30-
const isMobile = useAgentMobile()
31-
const icon = position === 'left' ? 'bx-chevron-left' : 'bx-chevron-right'
32-
const left = position === 'left' ? 'left-2' : 'right-2'
33-
return (
34-
<button
35-
className={`${
36-
!isMobile ? 'flex' : 'hidden'
37-
} invisible absolute top-1/2 opacity-0 transition-all duration-200 group-hover:visible group-hover:opacity-90 group-hover:hover:opacity-100 ${left} shadow-xs bg-second h-8 w-8 -translate-y-1/2 transform items-center justify-center rounded-full border bg-opacity-95 text-gray-600 dark:bg-opacity-10 dark:text-white`}
38-
onClick={onClick}
39-
>
40-
<i className={`bx bx-sm ${icon}`} />
41-
</button>
42-
)
43-
}
44-
45-
const Carousel: React.FC<Props> = ({
9+
const Carousel: CarouselComponent = ({
4610
images,
4711
imageTitlePrefix,
4812
imageAltPrefix,
49-
sizeClassName,
50-
imageClassName,
51-
pictureClassName,
52-
imgLoadingClassName = 'rounded-md',
53-
className,
13+
variant = CarouselVariant.List,
5414
onClick,
5515
showDots = true,
56-
showSubImages = false,
16+
showPreview = false,
5717
autoPlay = false,
5818
autoPlayInterval = 5000,
5919
activeIndex = 0,
6020
}) => {
6121
const [indexes, setIndexes] = useState([0, 0, 2])
6222
const [currentIndex, setCurrentIndex] = useState(activeIndex)
6323

24+
const styles = useMemo<CarouselStyles>(() => CarouselVariants[variant], [variant])
25+
6426
useInterval(
6527
() => {
6628
goToNextSlide()
@@ -113,8 +75,8 @@ const Carousel: React.FC<Props> = ({
11375
}
11476

11577
return (
116-
<div className={`group w-full ${className ? className : ''}`} onClick={checkImageClick}>
117-
<div className={`relative ${sizeClassName}`}>
78+
<div className={`group w-full ${styles.container}`} onClick={checkImageClick}>
79+
<div className={`relative col-span-10 ${styles.provider}`}>
11880
{images.map((img, idx) => (
11981
<PerfectImage
12082
key={idx}
@@ -124,9 +86,9 @@ const Carousel: React.FC<Props> = ({
12486
isActive={idx === currentIndex}
12587
className={`absolute left-0 top-0 h-full w-full object-cover transition-opacity duration-200 ${
12688
idx === currentIndex ? 'opacity-100' : 'cursor-pointer opacity-0'
127-
} ${pictureClassName ? pictureClassName : ''}`}
128-
imgClassName={`rounded-md ${imageClassName ? imageClassName : ''}`}
129-
loadingClassName={imgLoadingClassName}
89+
}`}
90+
imgClassName={`rounded-md ${styles.item}`}
91+
loadingClassName={styles.itemLoading}
13092
onLeftSwipe={checkLeftSwipe}
13193
onRightSwipe={checkRightSwipe}
13294
/>
@@ -135,31 +97,28 @@ const Carousel: React.FC<Props> = ({
13597
{currentIndex !== 0 && <CarouselButton position='left' onClick={goToPreviousSlide} />}
13698
{currentIndex !== images.length - 1 && <CarouselButton position='right' onClick={goToNextSlide} />}
13799
</div>
138-
{showSubImages && (
139-
<div className='mt-2 flex justify-start gap-1 overflow-x-auto pb-2'>
140-
{images.map((img, idx) => (
141-
<div key={idx} className='max-w-12 relative h-12 max-h-12 min-h-[3rem] w-12 min-w-[3rem]'>
142-
<PerfectImage
143-
src={img}
144-
full={false}
145-
alt={``}
146-
title={imageTitlePrefix ? `${imageTitlePrefix}-${idx}` : undefined}
147-
imgClassName='rounded-md w-full h-full'
148-
loadingClassName='rounded-md w-full h-full'
149-
className={`max-w-12 h-12 max-h-12 min-h-[3rem] w-12 min-w-[3rem] rounded-md object-cover transition-opacity duration-200 ${
150-
idx === currentIndex ? 'opacity-100' : 'cursor-pointer opacity-50'
151-
}`}
152-
onClick={(e) => {
153-
setCurrentIndex(idx)
154-
e.stopPropagation()
155-
}}
156-
/>
157-
</div>
158-
))}
159-
</div>
100+
{showPreview && styles.Preview && (
101+
<styles.Preview
102+
currentIndex={currentIndex}
103+
imageTitlePrefix={imageTitlePrefix}
104+
images={images}
105+
styles={styles.preview}
106+
onClick={onClick}
107+
setCurrentIndex={(idx) => {
108+
setIndexes(([fromIndex, currentIndex, toIndex]) => {
109+
if (idx === currentIndex) return [idx - 1, idx, idx + 1]
110+
if (idx === fromIndex) return [idx - 1, idx, idx + 1]
111+
if (idx === toIndex) return [idx - 1, idx, idx + 1]
112+
return [idx - 1, idx, idx + 1]
113+
})
114+
setCurrentIndex(idx)
115+
}}
116+
/>
160117
)}
161118
</div>
162119
)
163120
}
164121

122+
Carousel.Variants = CarouselVariant
123+
165124
export default Carousel
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { FC, useMemo } from 'react'
2+
import { useIsDesktop } from '../hooks/dom'
3+
import PerfectImage from '../image/perfect'
4+
import { PreviewProps } from './carousel.types'
5+
6+
const CarouselSidePreview: FC<PreviewProps> = ({
7+
styles,
8+
images,
9+
imageTitlePrefix,
10+
currentIndex,
11+
setCurrentIndex,
12+
onClick,
13+
}) => {
14+
const isDesktop = useIsDesktop()
15+
const max = useMemo<number>(() => (isDesktop ? 6 : 4), [isDesktop])
16+
const [firstImages, totalCount] = useMemo(() => {
17+
if (images.length > max) return [images.slice(0, max - 1), images.length]
18+
return [images, images.length]
19+
}, [images, max])
20+
21+
return (
22+
<div className={`grid grid-cols-12 gap-2 ${styles ? styles.provider : ''}`}>
23+
{firstImages.map((img, idx) => (
24+
<div key={idx} className={`${styles ? styles.item : ''}`}>
25+
<PerfectImage
26+
src={img}
27+
full={false}
28+
alt={``}
29+
title={imageTitlePrefix ? `${imageTitlePrefix}-${idx}` : undefined}
30+
imgClassName='rounded-md w-full h-full'
31+
loadingClassName='rounded-md w-full h-full'
32+
className={`rounded-md object-cover transition-opacity duration-200 ${
33+
idx === currentIndex ? 'opacity-100' : 'cursor-pointer opacity-50'
34+
}`}
35+
onClick={(e) => {
36+
setCurrentIndex(idx)
37+
e.stopPropagation()
38+
}}
39+
/>
40+
</div>
41+
))}
42+
{totalCount > max && (
43+
<div className={`relative ${styles ? styles.item : ''}`}>
44+
<div className='absolute inset-0 flex items-center justify-center bg-white bg-opacity-100 dark:bg-black'>
45+
<span className='text-2xl font-bold text-black dark:text-white lg:text-4xl'>{totalCount - max}+</span>
46+
</div>
47+
<PerfectImage
48+
src={images[max - 1]}
49+
full={false}
50+
alt={``}
51+
title={imageTitlePrefix ? `${imageTitlePrefix}-${5}` : undefined}
52+
imgClassName='rounded-md w-full h-full'
53+
loadingClassName='rounded-md w-full h-full'
54+
className={`cursor-pointer rounded-md object-cover opacity-20 transition-opacity duration-200`}
55+
onClick={(e) => {
56+
if (onClick) onClick(images[max - 1], max - 1)
57+
else setCurrentIndex(5)
58+
e.stopPropagation()
59+
}}
60+
/>
61+
</div>
62+
)}
63+
</div>
64+
)
65+
}
66+
67+
export default CarouselSidePreview
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { FC } from 'react'
2+
import PerfectImage from '../image/perfect'
3+
import { PreviewProps } from './carousel.types'
4+
5+
const CarouselSubPreview: FC<PreviewProps> = ({ styles, images, imageTitlePrefix, currentIndex, setCurrentIndex }) => {
6+
return (
7+
<div className={`mt-2 flex justify-start gap-1 overflow-x-auto pb-2 ${styles ? styles.provider : ''}`}>
8+
{images.map((img, idx) => (
9+
<div
10+
key={idx}
11+
className={`max-w-12 relative h-12 max-h-12 min-h-[3rem] w-12 min-w-[3rem] ${styles ? styles.item : ''}`}
12+
>
13+
<PerfectImage
14+
src={img}
15+
full={false}
16+
alt={``}
17+
title={imageTitlePrefix ? `${imageTitlePrefix}-${idx}` : undefined}
18+
imgClassName='rounded-md w-full h-full'
19+
loadingClassName='rounded-md w-full h-full'
20+
className={`max-w-12 h-12 max-h-12 min-h-[3rem] w-12 min-w-[3rem] rounded-md object-cover transition-opacity duration-200 ${
21+
idx === currentIndex ? 'opacity-100' : 'cursor-pointer opacity-50'
22+
}`}
23+
onClick={(e) => {
24+
setCurrentIndex(idx)
25+
e.stopPropagation()
26+
}}
27+
/>
28+
</div>
29+
))}
30+
</div>
31+
)
32+
}
33+
34+
export default CarouselSubPreview

0 commit comments

Comments
 (0)