Skip to content

Commit

Permalink
Create Slider component
Browse files Browse the repository at this point in the history
  • Loading branch information
vczb committed Aug 4, 2022
1 parent 8ac892d commit 716e0a7
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 2 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ lib
build
dist
storybook-static
docs-build

# misc
.DS_Store
Expand All @@ -21,7 +22,7 @@ yarn-error.log*


#vscode

.vscode

docs-build
#nodejs
.tool-versions
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ rollup.config.*
node_modules/
docs-build/
.github
.tool-versions
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export default App
/lib
/src
├── index.ts
├── animations
| ├── placeholder.ts
├── packages
| ├── index.ts
| ├── Button
Expand All @@ -67,6 +69,7 @@ export default App
├── styles
| ├── global.ts
| ├── theme.ts
| ├── provider.ts
```

## License
Expand Down
1 change: 1 addition & 0 deletions src/animations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as placeholder } from './placeholder'
26 changes: 26 additions & 0 deletions src/animations/placeholder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { css } from 'styled-components'
import { theme } from './../styles'

const placeholder = css`
color: ${theme.colors.neutral.lighter};
background-image: linear-gradient(
to right,
currentColor 0%,
${theme.colors.neutral.base} 20%,
currentColor 40%,
currentColor 100%
);
background-size: 80rem 14rem;
animation: placeholder 1s linear infinite forwards;
@keyframes placeholder {
0% {
background-position: -40rem 0;
}
100% {
background-position: 40rem 0;
}
}
`

export default placeholder
14 changes: 14 additions & 0 deletions src/icons/Down.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
export const Down = () => (
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
opacity="0.3"
fillRule="evenodd"
clipRule="evenodd"
d="m8 9.491 3.403-3.308a.656.656 0 0 1 .909 0 .613.613 0 0 1 0 .884l-3.857 3.75a.656.656 0 0 1-.91 0l-3.857-3.75a.613.613 0 0 1 0-.884.656.656 0 0 1 .91 0L8 9.491Z"
fill="currentColor"
/>
</svg>
)

export default Down
14 changes: 14 additions & 0 deletions src/icons/Up.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
export const Up = () => (
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
opacity="0.3"
fillRule="evenodd"
clipRule="evenodd"
d="M8 6.509 4.597 9.817a.656.656 0 0 1-.909 0 .613.613 0 0 1 0-.884l3.857-3.75a.656.656 0 0 1 .91 0l3.857 3.75a.613.613 0 0 1 0 .884.656.656 0 0 1-.91 0L8 6.509Z"
fill="currentColor"
/>
</svg>
)

export default Up
2 changes: 2 additions & 0 deletions src/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Down } from './Down'
export { default as Up } from './Up'
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './packages'
export * from './styles'
export * from './animations'
export * from './icons'
58 changes: 58 additions & 0 deletions src/packages/Slider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useMemo, useState } from 'react'
import { Down, Up } from '../../icons'
import * as S from './styles'

export type ImageProps = {
url: string
listOrder: number
}

export type SliderProps = {
images: ImageProps[]
size?: 'small' | 'large'
loading?: boolean
}

const Slider = ({ images, size = 'small', loading }: SliderProps) => {
const sortedImages = useMemo(
() => images.slice().sort((a, b) => a.listOrder - b.listOrder),
[images]
)

const [active, setActive] = useState(0)

if (loading) return <S.Loading size={size} />

return (
<S.Figure size={size}>
{sortedImages?.map(({ url, listOrder }, index) => (
<S.Image
active={active === index}
position={index - active}
loading="lazy"
key={index}
src={url}
alt={`Slider image ${listOrder}`}
/>
))}
{sortedImages.length > 1 && (
<>
<S.SlideButton
disabled={active === 0}
onClick={() => setActive(active - 1)}
>
<Up />
</S.SlideButton>
<S.SlideButton
disabled={active === sortedImages.length - 1}
onClick={() => setActive(active + 1)}
>
<Down />
</S.SlideButton>
</>
)}
</S.Figure>
)
}

export default Slider
42 changes: 42 additions & 0 deletions src/packages/Slider/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const mock = [
{
url: 'https://picsum.photos/id/237/400/200',
listOrder: 0
},
{
url: 'https://picsum.photos/id/238/400/200',
listOrder: 1
},
{
url: 'https://picsum.photos/id/239/400/200',
listOrder: 2
},
{
url: 'https://picsum.photos/id/240/400/200',
listOrder: 3
},
{
url: 'https://picsum.photos/id/241/400/200',
listOrder: 4
},
{
url: 'https://picsum.photos/id/242/400/200',
listOrder: 5
},
{
url: 'https://picsum.photos/id/243/400/200',
listOrder: 6
},
{
url: 'https://picsum.photos/id/244/400/200',
listOrder: 7
},
{
url: 'https://picsum.photos/id/500/400/200',
listOrder: 8
},
{
url: 'https://picsum.photos/id/501/400/200',
listOrder: 9
}
]
18 changes: 18 additions & 0 deletions src/packages/Slider/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import { Story, Meta } from '@storybook/react/types-6-0'
import Slider, { SliderProps } from '.'
import { mock } from './mock'

export default {
title: 'Slider',
component: Slider,
args: {
images: mock
}
} as Meta

export const Default: Story<SliderProps> = (args) => <Slider {...args} />

export const OneImageOnly: Story<SliderProps> = (args) => (
<Slider {...args} images={[mock[0]]} />
)
78 changes: 78 additions & 0 deletions src/packages/Slider/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import styled, { css } from 'styled-components'
import { placeholder } from '../../animations/'
import { SliderProps } from '.'

const figureModifiers = {
small: () => css`
max-width: 40rem;
height: 20rem;
`,
large: () => css`
max-width: 80rem;
height: 40rem;
`
}

export const Figure = styled.figure<Pick<SliderProps, 'size'>>`
${({ size }) => css`
position: relative;
overflow: hidden;
${!!size && figureModifiers[size]()}
`}
`

export const Loading = styled.div<Pick<SliderProps, 'size'>>`
${({ size }) => css`
${!!size && figureModifiers[size]()}
${placeholder}
`}
`

export const Image = styled.img<{ active?: boolean; position: number }>`
${({ theme, active, position }) => css`
position: absolute;
width: 100%;
height: 100%;
transition: transform ${theme.transitions.default};
transform: translateX(${active ? 0 : position * 100 + '%'});
object-fit: cover;
border-radius: ${theme.border.mini};
${placeholder}
`}
`

export const SlideButton = styled.button`
${({ theme }) => css`
position: absolute;
top: 50%;
transform: translateY(-50%) rotate(270deg);
width: 2rem;
height: 2rem;
border-radius: 50%;
background: ${theme.colors.neutral.lightest};
border: none;
outline: none;
cursor: pointer;
color: ${theme.colors.primary.light};
transition: all ${theme.transitions.default};
opacity: 0.7;
&:hover,
&:focus {
box-shadow: ${theme.shadows.focus};
opacity: 1;
color: ${theme.colors.neutral.darkest};
}
&:first-of-type {
left: ${theme.spacings.xsmall};
}
&:last-of-type {
right: ${theme.spacings.xsmall};
}
&:disabled {
opacity: 0.2;
cursor: default;
}
`}
`

0 comments on commit 716e0a7

Please sign in to comment.