Skip to content

Commit 0fc09d7

Browse files
Merge pull request #740 from devtron-labs/fix/segment-control
fix: segment control
2 parents 67d8895 + 1d790c2 commit 0fc09d7

File tree

12 files changed

+86
-105
lines changed

12 files changed

+86
-105
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.14.0-pre-0",
3+
"version": "1.14.1-pre-1",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/SegmentedControl/Segment.tsx

Lines changed: 16 additions & 18 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'
@@ -15,16 +16,7 @@ const wrapWithTooltip = (tooltipProps: SegmentType['tooltipProps']) => (children
1516
</Tooltip>
1617
)
1718

18-
const Segment = ({
19-
segment,
20-
isSelected,
21-
name,
22-
selectedSegmentRef,
23-
onChange,
24-
fullWidth,
25-
size,
26-
disabled,
27-
}: SegmentProps) => {
19+
const Segment = ({ segment, isSelected, name, onChange, fullWidth, size, disabled }: SegmentProps) => {
2820
const inputId = useMemo(getUniqueId, [])
2921

3022
const { value, icon, isError, label, tooltipProps, ariaLabel } = segment
@@ -34,26 +26,32 @@ const Segment = ({
3426

3527
return (
3628
<ConditionalWrap key={value} condition={!!tooltipProps?.content} wrap={wrapWithTooltip(tooltipProps)}>
37-
<div
38-
className={`dc__position-rel dc__text-center dc__no-shrink ${fullWidth ? 'flex-grow-1' : ''}`}
39-
ref={selectedSegmentRef}
40-
>
29+
<div className={`dc__position-rel dc__text-center dc__no-shrink ${fullWidth ? 'flex-grow-1' : ''}`}>
4130
<input
4231
type="radio"
4332
value={value}
4433
id={inputId}
4534
name={name}
4635
onChange={handleChange}
4736
checked={isSelected}
48-
className="dc__opacity-0 m-0-imp dc__top-0 dc__left-0 dc__position-abs dc__bottom-0 dc__right-0 w-100 pointer h-100 dc__visibility-hidden"
37+
className="sr-only"
4938
disabled={disabled}
5039
/>
5140

52-
<label
41+
<motion.label
5342
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]}`}
43+
layout
44+
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]}`}
45+
key={inputId}
5546
aria-label={ariaLabel}
5647
>
48+
{isSelected && (
49+
<motion.div
50+
layoutId={`active-segment-control-${name}`}
51+
className="dc__position-abs active-mask dc__top-0 dc__left-0 dc__right-0 dc__bottom-0 bg__primary br-4"
52+
/>
53+
)}
54+
5755
{(isError || icon) && (
5856
<span className={`flex ${COMPONENT_SIZE_TO_ICON_CLASS_MAP[size]}`}>
5957
<Icon
@@ -71,7 +69,7 @@ const Segment = ({
7169
</span>
7270
)}
7371
{label && <span>{label}</span>}
74-
</label>
72+
</motion.label>
7573
</div>
7674
</ConditionalWrap>
7775
)

src/Common/SegmentedControl/SegmentedControl.component.tsx

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

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

@@ -17,25 +17,9 @@ const SegmentedControl = ({
1717
disabled,
1818
}: SegmentedControlProps) => {
1919
const isUnControlledComponent = controlledValue === undefined
20-
21-
const segmentedControlRefContainer = useRef<HTMLDivElement>(null)
22-
/**
23-
* Using this ref to show the selected segment highlight with transition
24-
*/
25-
const selectedSegmentRef = useRef<HTMLDivElement>(null)
2620
const [selectedSegmentValue, setSelectedSegmentValue] = useState<SegmentType['value'] | null>(segments[0].value)
2721
const segmentValue = isUnControlledComponent ? selectedSegmentValue : controlledValue
2822

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-
3923
const handleSegmentChange = (updatedSegment: SegmentType) => {
4024
if (isUnControlledComponent) {
4125
setSelectedSegmentValue(updatedSegment.value)
@@ -47,16 +31,12 @@ const SegmentedControl = ({
4731
<div
4832
className={`segmented-control ${!fullWidth ? 'dc__inline-flex' : ''} ${disabled ? 'dc__disabled' : ''} br-6 ${size === ComponentSizeType.xs ? 'p-1' : 'p-2'}`}
4933
>
50-
<div
51-
className="segmented-control__container flex left dc__position-rel dc__align-items-center dc__gap-2"
52-
ref={segmentedControlRefContainer}
53-
>
34+
<div className="segmented-control__container flex left dc__position-rel dc__align-items-center dc__gap-2">
5435
{segments.map((segment) => {
5536
const isSelected = segment.value === segmentValue
5637

5738
return (
5839
<Segment
59-
selectedSegmentRef={isSelected ? selectedSegmentRef : undefined}
6040
segment={segment}
6141
key={segment.value}
6242
name={name}

src/Common/SegmentedControl/segmentedControl.scss

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +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-
10-
&::before {
11-
content: '';
12-
background: var(--bg-primary);
13-
border-radius: 4px;
14-
width: var(--segmented-control-highlight-width);
15-
transform: translateX(var(--segmented-control-highlight-x-position));
16-
position: absolute;
17-
left: 0;
18-
z-index: 0;
19-
border: 0.5px solid var(--border-primary);
20-
box-shadow: 0px 1px 2px 0px var(--black-20);
21-
height: 100%;
22-
transition: transform 0.3s ease, width 0.3s ease;
23-
24-
&:has(#{$segmented-control-selector}__segment--xs) {
25-
top: 1px;
26-
bottom: 1px;
27-
}
28-
29-
&:has(#{$segmented-control-selector}__segment--small),
30-
&:has(#{$segmented-control-selector}__segment--medium) {
31-
top: 2px;
32-
bottom: 2px;
7+
&:has(input[type='radio']:checked:focus-visible) {
8+
.segmented-control__segment--selected {
9+
.active-mask {
10+
outline: 2px solid var(--B500) !important;
11+
outline-offset: 2px;
12+
}
3313
}
3414
}
3515
}
@@ -38,10 +18,6 @@
3818
$parent-selector: &;
3919
transition: color 0.3s ease;
4020

41-
&:hover:not(#{$parent-selector}--selected):not(.cursor-not-allowed) {
42-
background-color: var(--bg-secondary);
43-
}
44-
4521
&--selected {
4622
&#{$parent-selector} {
4723

@@ -56,6 +32,19 @@
5632
padding-inline: 7.5px;
5733
}
5834
}
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);
5948
}
6049
}
6150
}

src/Common/SegmentedControl/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { RefObject } from 'react'
2-
31
import { TooltipProps } from '@Common/Tooltip'
42
import { IconsProps, SelectPickerOptionType } from '@Shared/Components'
53
import { ComponentSizeType } from '@Shared/constants'
@@ -56,6 +54,9 @@ export type SegmentedControlProps = {
5654
* List of segments to be displayed
5755
*/
5856
segments: SegmentType[]
57+
/**
58+
* Please make sure this is unique
59+
*/
5960
name: string
6061
size?: Extract<ComponentSizeType, ComponentSizeType.xs | ComponentSizeType.small | ComponentSizeType.medium>
6162
fullWidth?: boolean
@@ -81,5 +82,4 @@ export interface SegmentProps
8182
extends Required<Pick<SegmentedControlProps, 'name' | 'onChange' | 'fullWidth' | 'size' | 'disabled'>> {
8283
isSelected: boolean
8384
segment: SegmentType
84-
selectedSegmentRef: RefObject<HTMLDivElement> | undefined
8585
}

src/Shared/Components/ActionMenu/ActionMenuItem.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Icon } from '../Icon'
77
import { getTooltipProps } from '../SelectPicker/common'
88
import { ActionMenuItemProps } from './types'
99

10+
const COMMON_ACTION_MENU_ITEM_CLASS = 'flex-grow-1 flex left top dc__gap-8 py-6 px-8'
11+
1012
export const ActionMenuItem = <T extends string | number>({
1113
item,
1214
itemRef,
@@ -52,19 +54,25 @@ export const ActionMenuItem = <T extends string | number>({
5254

5355
const renderContent = () => (
5456
<>
55-
<Tooltip content={label} placement="right">
56-
<span className={`m-0 fs-13 fw-4 lh-20 dc__truncate ${isNegativeType ? 'cr-5' : 'cn-9'}`}>{label}</span>
57-
</Tooltip>
58-
{description &&
59-
(typeof description === 'string' ? (
60-
<span
61-
className={`m-0 fs-12 fw-4 lh-18 cn-7 ${!disableDescriptionEllipsis ? 'dc__ellipsis-right__2nd-line' : 'dc__word-break'}`}
62-
>
63-
{description}
57+
{renderIcon(startIcon)}
58+
<span>
59+
<Tooltip content={label} placement="right">
60+
<span className={`m-0 fs-13 fw-4 lh-20 dc__truncate ${isNegativeType ? 'cr-5' : 'cn-9'}`}>
61+
{label}
6462
</span>
65-
) : (
66-
description
67-
))}
63+
</Tooltip>
64+
{description &&
65+
(typeof description === 'string' ? (
66+
<span
67+
className={`m-0 fs-12 fw-4 lh-18 cn-7 ${!disableDescriptionEllipsis ? 'dc__ellipsis-right__2nd-line' : 'dc__word-break'}`}
68+
>
69+
{description}
70+
</span>
71+
) : (
72+
description
73+
))}
74+
</span>
75+
{renderIcon(endIcon)}
6876
</>
6977
)
7078

@@ -74,7 +82,7 @@ export const ActionMenuItem = <T extends string | number>({
7482
return (
7583
<a
7684
ref={itemRef as LegacyRef<HTMLAnchorElement>}
77-
className="flex-grow-1"
85+
className={COMMON_ACTION_MENU_ITEM_CLASS}
7886
href={item.href}
7987
target="_blank"
8088
rel="noreferrer"
@@ -84,7 +92,11 @@ export const ActionMenuItem = <T extends string | number>({
8492
)
8593
case 'link':
8694
return (
87-
<Link ref={itemRef as Ref<HTMLAnchorElement>} className="flex-grow-1" to={item.to}>
95+
<Link
96+
ref={itemRef as Ref<HTMLAnchorElement>}
97+
className={COMMON_ACTION_MENU_ITEM_CLASS}
98+
to={item.to}
99+
>
88100
{renderContent()}
89101
</Link>
90102
)
@@ -94,7 +106,7 @@ export const ActionMenuItem = <T extends string | number>({
94106
<button
95107
ref={itemRef as LegacyRef<HTMLButtonElement>}
96108
type="button"
97-
className="dc__transparent p-0 flex-grow-1"
109+
className={`dc__transparent ${COMMON_ACTION_MENU_ITEM_CLASS}`}
98110
>
99111
{renderContent()}
100112
</button>
@@ -111,13 +123,11 @@ export const ActionMenuItem = <T extends string | number>({
111123
onMouseEnter={onMouseEnter}
112124
tabIndex={-1}
113125
// Intentionally added margin to the left and right to have the gap on the edges of the options
114-
className={`action-menu__option br-4 flex left top dc__gap-8 mr-4 ml-4 py-6 px-8 ${isDisabled ? 'dc__disabled' : 'cursor'} ${isNegativeType ? 'dc__hover-r50' : 'dc__hover-n50'} ${isFocused ? `action-menu__option--focused${isNegativeType ? '-negative' : ''}` : ''}`}
126+
className={`action-menu__option br-4 mr-4 ml-4 ${isDisabled ? 'dc__disabled' : 'cursor'} ${isNegativeType ? 'dc__hover-r50' : 'dc__hover-n50'} ${isFocused ? `action-menu__option--focused${isNegativeType ? '-negative' : ''}` : ''}`}
115127
onClick={!isDisabled ? handleClick : undefined}
116128
aria-disabled={isDisabled}
117129
>
118-
{renderIcon(startIcon)}
119130
{renderComponent()}
120-
{renderIcon(endIcon)}
121131
</li>
122132
</Tooltip>
123133
)

src/Shared/Components/AppStatusModal/AppStatusBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { getAppStatusMessageFromAppDetails } from './utils'
1919

2020
const InfoCardItem = ({ heading, value, isLast = false, alignCenter = false }: InfoCardItemProps) => (
2121
<div
22-
className={`py-12 px-16 dc__grid dc__column-gap-16 info-card-item ${alignCenter ? 'dc__align-items-center' : ''} ${!isLast ? 'border__secondary--bottom' : ''} ${alignCenter ? 'dc__align-center' : ''}`}
22+
className={`py-12 px-16 dc__grid dc__column-gap-16 info-card-item ${alignCenter ? 'dc__align-items-center' : ''} ${!isLast ? 'border__secondary--bottom' : ''}`}
2323
>
2424
<Tooltip content={heading}>
2525
<h3 className="cn-9 fs-13 fw-4 lh-1-5 dc__truncate m-0 dc__no-shrink">{heading}</h3>

src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useEffect, useState } from 'react'
17+
import { useEffect, useRef, useState } from 'react'
1818

1919
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
2020
import { SegmentType } from '@Common/SegmentedControl/types'
2121
import { ComponentSizeType } from '@Shared/constants'
22+
import { getUniqueId } from '@Shared/Helpers'
2223

2324
import { PopupMenu, SegmentedControl } from '../../../Common'
2425
import { StatusFilterButtonType } from './types'
@@ -32,6 +33,8 @@ export const StatusFilterButtonComponent = ({
3233
handleFilterClick,
3334
maxInlineFiltersCount = 0,
3435
}: StatusFilterButtonType) => {
36+
const segmentControlName = useRef(getUniqueId())
37+
3538
// STATES
3639
const [overflowFilterIndex, setOverflowFilterIndex] = useState(0)
3740

@@ -104,19 +107,13 @@ export const StatusFilterButtonComponent = ({
104107

105108
const segmentValue = segments.find(({ value }) => value === selectedTab)?.value || null
106109

107-
const segmentControlKey = inlineFilters.reduce<string>(
108-
(acc, inlineFilter) => `${acc}-${inlineFilter.status}`,
109-
`${allResourceKindFilter.status}`,
110-
)
111-
112110
return (
113111
<div className="flexbox status-filter__container">
114112
<SegmentedControl
115-
key={segmentControlKey}
116113
segments={segments}
117114
value={segmentValue}
118115
onChange={handleInlineFilterClick}
119-
name="status-filter-button"
116+
name={segmentControlName.current}
120117
size={ComponentSizeType.small}
121118
/>
122119

src/Shared/Components/DeploymentStatusBreakdown/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ export const WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP: Readonly<
3939
[WorkflowRunnerStatusDTO.INITIATING]: DEPLOYMENT_STATUS.INITIATING,
4040
[WorkflowRunnerStatusDTO.STARTING]: DEPLOYMENT_STATUS.STARTING,
4141
[WorkflowRunnerStatusDTO.PROGRESSING]: DEPLOYMENT_STATUS.INPROGRESS,
42+
[WorkflowRunnerStatusDTO.SUSPENDED]: DEPLOYMENT_STATUS.INPROGRESS,
4243

4344
[WorkflowRunnerStatusDTO.QUEUED]: DEPLOYMENT_STATUS.QUEUED,
45+
46+
[WorkflowRunnerStatusDTO.UNKNOWN]: DEPLOYMENT_STATUS.UNABLE_TO_FETCH,
47+
[WorkflowRunnerStatusDTO.MISSING]: DEPLOYMENT_STATUS.UNABLE_TO_FETCH,
4448
}
4549

4650
export const PROGRESSING_DEPLOYMENT_STATUS: Readonly<(typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS][]> = [

0 commit comments

Comments
 (0)