Last active
August 7, 2022 02:38
-
-
Save Rasukarusan/9314aee54677ec0fb998683b9b0fdcb4 to your computer and use it in GitHub Desktop.
React,styled-jsxでセグメントコントロール
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import css from 'styled-jsx/css' | |
import { useState, useEffect } from 'react' | |
const styles = css` | |
.controls { | |
display: inline-flex; | |
justify-content: space-between; | |
background: #e7e7e7; | |
border-radius: 8px; | |
overflow: hidden; | |
position: relative; | |
width: 100%; | |
} | |
.controls::before { | |
content: ''; | |
background: #5465ff; | |
border-radius: 8px; | |
position: absolute; | |
top: 0px; | |
bottom: 0px; | |
left: 0; | |
transition: transform 0.3s ease, width 0.3s ease; | |
} | |
.segment { | |
color: gray; | |
position: relative; | |
text-align: center; | |
} | |
.segment.active { | |
color: #fff; | |
} | |
label { | |
cursor: pointer; | |
display: block; | |
padding: 5px 0px; | |
transition: color 0.5s ease; | |
} | |
input { | |
opacity: 0; | |
margin: 0; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
position: absolute; | |
cursor: pointer; | |
width: 100%; | |
height: 100%; | |
} | |
` | |
interface Props { | |
segments: { | |
label: string | |
value: string | |
}[] | |
callback: (value: string, index: number) => void | |
defaultIndex?: number | |
} | |
export const SegmentedControl: React.FC<Props> = ({ | |
segments, | |
callback, | |
defaultIndex = 0, | |
}) => { | |
const [activeIndex, setActiveIndex] = useState(defaultIndex) | |
const [segmentLeft, setSegmentLeft] = useState(0) | |
const segmentWidth = `${100 / segments.length}%` | |
useEffect(() => { | |
const id = `${activeIndex}-${segments[activeIndex].value}` | |
const segment = document.getElementById(id) | |
const { width } = segment.getBoundingClientRect() | |
setSegmentLeft(width * activeIndex) | |
}, [activeIndex]) | |
const onChange = (value, index) => { | |
setActiveIndex(index) | |
callback(value, index) | |
} | |
return ( | |
<div className="controls"> | |
{segments.map((item, i) => { | |
return ( | |
<div | |
id={`${i}-${item.value}`} | |
key={item.value} | |
className={`segment ${i === activeIndex && 'active'}`} | |
> | |
<input | |
type="radio" | |
value={item.value} | |
onChange={() => onChange(item.value, i)} | |
checked={i === activeIndex} | |
/> | |
<label htmlFor={item.label}>{item.label}</label> | |
</div> | |
) | |
})} | |
<style jsx>{styles}</style> | |
<style jsx>{` | |
.segment { | |
min-width: ${segmentWidth}; | |
} | |
.controls::before { | |
width: ${segmentWidth}; | |
transform: translateX(${segmentLeft}px); | |
} | |
`}</style> | |
</div> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
呼び出し側
pages/index.tsx