Skip to content

Commit 331cd9e

Browse files
feat: context values - implement multi-select dropdown on envs (#6239)
1 parent 9085cb4 commit 331cd9e

File tree

21 files changed

+518
-326
lines changed

21 files changed

+518
-326
lines changed

frontend/common/types/responses.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export type Operator = {
3434
hideValue?: boolean
3535
warning?: string
3636
valuePlaceholder?: string
37+
append?: string
38+
type?: string
3739
}
3840
export type ChangeRequestSummary = {
3941
id: number

frontend/web/components/base/select/MultiSelect.tsx

Lines changed: 0 additions & 177 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { MultiValueProps } from 'react-select/lib/components/MultiValue'
2+
import { MultiSelectOption } from './MultiSelect'
3+
4+
export const CustomMultiValue = ({
5+
color,
6+
data,
7+
removeProps,
8+
}: MultiValueProps<MultiSelectOption> & { color?: string }) => {
9+
return (
10+
<div
11+
className='d-flex align-items-center gap-x-1'
12+
style={{
13+
backgroundColor: color,
14+
borderRadius: '4px',
15+
color: 'white',
16+
fontSize: '12px',
17+
maxWidth: '150px',
18+
overflow: 'hidden',
19+
padding: '2px 6px',
20+
textOverflow: 'ellipsis',
21+
whiteSpace: 'nowrap',
22+
maxHeight: '24px'
23+
}}
24+
>
25+
<span
26+
style={{
27+
overflow: 'hidden',
28+
textOverflow: 'ellipsis',
29+
whiteSpace: 'nowrap',
30+
}}
31+
>
32+
{data.label}
33+
</span>
34+
<button
35+
{...removeProps}
36+
style={{
37+
cursor: 'pointer',
38+
fontSize: '14px',
39+
lineHeight: '1',
40+
backgroundColor: 'transparent',
41+
border: 'none',
42+
padding: 0,
43+
margin: 0,
44+
color: 'white',
45+
}}
46+
>
47+
×
48+
</button>
49+
</div>
50+
)
51+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { OptionProps } from 'react-select/lib/components/Option'
2+
import { MultiSelectOption } from './MultiSelect'
3+
import Icon from 'components/Icon'
4+
import { useEffect, useRef } from 'react'
5+
6+
export const CustomOption = ({
7+
children,
8+
color,
9+
...props
10+
}: OptionProps<MultiSelectOption> & { color?: string }) => {
11+
const ref = useRef<HTMLDivElement>(null)
12+
13+
useEffect(() => {
14+
if (props.isFocused && ref.current) {
15+
ref.current.scrollIntoView({
16+
behavior: 'smooth',
17+
block: 'nearest',
18+
})
19+
}
20+
}, [props.isFocused])
21+
22+
return (
23+
<div
24+
ref={ref}
25+
{...props.innerProps}
26+
role="option"
27+
aria-selected={props.isSelected}
28+
aria-disabled={props.isDisabled}
29+
style={{
30+
display: 'flex',
31+
alignItems: 'center',
32+
justifyContent: 'space-between',
33+
padding: '8px 12px',
34+
cursor: props.isDisabled ? 'not-allowed' : 'pointer',
35+
backgroundColor: props.isFocused ? '#f0f0f0' : 'transparent',
36+
gap: '8px',
37+
}}
38+
>
39+
<div style={{ display: 'flex', alignItems: 'center', flex: 1, wordWrap: 'break-word', minWidth: 0, gap: '8px' }}>
40+
{color && (
41+
<div
42+
aria-hidden="true"
43+
style={{
44+
backgroundColor: color,
45+
borderRadius: '2px',
46+
flexShrink: 0,
47+
height: '12px',
48+
width: '12px',
49+
}}
50+
/>
51+
)}
52+
<span style={{ flex: 1, wordWrap: 'break-word', minWidth: 0 }}>{children}</span>
53+
</div>
54+
{props.isSelected && (
55+
<div aria-hidden="true">
56+
<Icon width={14} name='checkmark-circle' fill='#6837fc' />
57+
</div>
58+
)}
59+
</div>
60+
)
61+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { MultiSelectOption } from "./MultiSelect"
2+
import { MultiValueProps } from "react-select/lib/components/MultiValue"
3+
4+
export const InlineMultiValue = (props: MultiValueProps<MultiSelectOption>) => {
5+
const { data } = props
6+
const selectedOptions = props.getValue() as MultiSelectOption[]
7+
const currentIndex = selectedOptions.findIndex((opt) => opt.value === data.value)
8+
9+
if (currentIndex !== 0) return null
10+
11+
let formattedText: string
12+
if (selectedOptions.length === 1) {
13+
formattedText = selectedOptions[0].label
14+
} else if (selectedOptions.length === 2) {
15+
formattedText = `${selectedOptions[0].label} and ${selectedOptions[1].label}`
16+
} else {
17+
const allButLast = selectedOptions.slice(0, -1).map((opt) => opt.label).join(', ')
18+
const last = selectedOptions[selectedOptions.length - 1].label
19+
formattedText = `${allButLast} and ${last}`
20+
}
21+
22+
return (
23+
<div
24+
title={formattedText}
25+
style={{
26+
whiteSpace: 'nowrap',
27+
overflow: 'hidden',
28+
textOverflow: 'ellipsis',
29+
maxWidth: '100%',
30+
fontWeight: '500',
31+
}}>
32+
{formattedText}
33+
</div>
34+
)
35+
}

0 commit comments

Comments
 (0)