Skip to content

Commit 64cc114

Browse files
committed
feat: enhance SegmentedControl with framer-motion for improved animations and update styling for active segments
1 parent f314914 commit 64cc114

File tree

3 files changed

+32
-49
lines changed

3 files changed

+32
-49
lines changed

src/Common/SegmentedControl/Segment.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ReactElement, useMemo } from 'react'
2+
import { motion } from 'framer-motion'
23

34
import { Tooltip } from '@Common/Tooltip'
45
import { Icon } from '@Shared/Components'
@@ -49,11 +50,21 @@ const Segment = ({
4950
disabled={disabled}
5051
/>
5152

52-
<label
53+
<motion.label
5354
htmlFor={inputId}
54-
className={`pointer m-0 flex ${!fullWidth ? 'left' : ''} dc__gap-4 br-4 segmented-control__segment segmented-control__segment--${size} ${isSelected ? 'fw-6 segmented-control__segment--selected' : 'fw-4'} ${segment.isError ? 'cr-5' : 'cn-9'} ${disabled ? 'cursor-not-allowed' : ''} ${COMPONENT_SIZE_TO_SEGMENT_CLASS_MAP[size]}`}
55+
layout
56+
className={`pointer m-0 dc__position-rel flex ${!fullWidth ? 'left' : ''} dc__gap-4 br-4 segmented-control__segment segmented-control__segment--${size} ${isSelected ? 'fw-6 segmented-control__segment--selected' : 'fw-4'} ${segment.isError ? 'cr-5' : 'cn-9'} ${disabled ? 'cursor-not-allowed' : ''} ${COMPONENT_SIZE_TO_SEGMENT_CLASS_MAP[size]}`}
57+
whileTap={{ scale: 0.95 }}
58+
key={inputId}
5559
aria-label={ariaLabel}
5660
>
61+
{isSelected && (
62+
<motion.div
63+
layoutId="active-segment-control"
64+
className="dc__position-abs active-mask dc__top-0 dc__left-0 dc__right-0 dc__bottom-0 bg__primary br-4"
65+
/>
66+
)}
67+
5768
{(isError || icon) && (
5869
<span className={`flex ${COMPONENT_SIZE_TO_ICON_CLASS_MAP[size]}`}>
5970
<Icon
@@ -71,7 +82,7 @@ const Segment = ({
7182
</span>
7283
)}
7384
{label && <span>{label}</span>}
74-
</label>
85+
</motion.label>
7586
</div>
7687
</ConditionalWrap>
7788
)

src/Common/SegmentedControl/SegmentedControl.component.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef, useState } from 'react'
1+
import { useRef, useState } from 'react'
22

33
import { ComponentSizeType } from '@Shared/constants'
44

@@ -26,16 +26,6 @@ const SegmentedControl = ({
2626
const [selectedSegmentValue, setSelectedSegmentValue] = useState<SegmentType['value'] | null>(segments[0].value)
2727
const segmentValue = isUnControlledComponent ? selectedSegmentValue : controlledValue
2828

29-
useEffect(() => {
30-
if (segmentValue) {
31-
const { offsetWidth, offsetLeft } = selectedSegmentRef.current
32-
const { style } = segmentedControlRefContainer.current
33-
34-
style.setProperty('--segmented-control-highlight-width', `${offsetWidth}px`)
35-
style.setProperty('--segmented-control-highlight-x-position', `${offsetLeft}px`)
36-
}
37-
}, [segmentValue, size, fullWidth])
38-
3929
const handleSegmentChange = (updatedSegment: SegmentType) => {
4030
if (isUnControlledComponent) {
4131
setSelectedSegmentValue(updatedSegment.value)

src/Common/SegmentedControl/segmentedControl.scss

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,12 @@
44
background: var(--bg-segmented-control);
55

66
&__container {
7-
--segmented-control-highlight-width: auto;
8-
--segmented-control-highlight-x-position: 0;
9-
107
&:has(input[type='radio']:checked:focus-visible) {
118
.segmented-control__segment--selected {
12-
outline: 2px solid var(--B500) !important;
13-
outline-offset: 2px;
14-
}
15-
}
16-
17-
&::before {
18-
content: '';
19-
background: var(--bg-primary);
20-
border-radius: 4px;
21-
width: var(--segmented-control-highlight-width);
22-
transform: translateX(var(--segmented-control-highlight-x-position));
23-
position: absolute;
24-
left: 0;
25-
z-index: 0;
26-
border: 0.5px solid var(--border-primary);
27-
box-shadow: 0px 1px 2px 0px var(--black-20);
28-
height: 100%;
29-
transition: transform 0.3s ease, width 0.3s ease;
30-
31-
&:has(#{$segmented-control-selector}__segment--xs) {
32-
top: 1px;
33-
bottom: 1px;
34-
}
35-
36-
&:has(#{$segmented-control-selector}__segment--small),
37-
&:has(#{$segmented-control-selector}__segment--medium) {
38-
top: 2px;
39-
bottom: 2px;
9+
.active-mask {
10+
outline: 2px solid var(--B500) !important;
11+
outline-offset: 2px;
12+
}
4013
}
4114
}
4215
}
@@ -45,10 +18,6 @@
4518
$parent-selector: &;
4619
transition: color 0.3s ease;
4720

48-
&:hover:not(#{$parent-selector}--selected):not(.cursor-not-allowed) {
49-
background-color: var(--bg-hover);
50-
}
51-
5221
&--selected {
5322
&#{$parent-selector} {
5423

@@ -63,6 +32,19 @@
6332
padding-inline: 7.5px;
6433
}
6534
}
35+
36+
.active-mask {
37+
border: 0.5px solid var(--border-primary);
38+
box-shadow: 0px 1px 2px 0px var(--black-20);
39+
}
40+
41+
> *:not(.active-mask) {
42+
z-index: 1;
43+
}
44+
}
45+
46+
&:hover:not(#{$parent-selector}--selected):not(.cursor-not-allowed) {
47+
background-color: var(--bg-hover);
6648
}
6749
}
6850
}

0 commit comments

Comments
 (0)