Skip to content

Commit c939218

Browse files
committed
feat: DynamicDataTable - scss updates, SelectPickerTextArea - handle textarea height change
1 parent c25953d commit c939218

File tree

7 files changed

+158
-46
lines changed

7 files changed

+158
-46
lines changed

src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
176176
isClearable
177177
{...props}
178178
variant={SelectPickerVariantType.BORDER_LESS}
179-
classNamePrefix="dynamic-data-table__cell__select-picker"
179+
classNamePrefix="dynamic-data-table__cell__select-picker-text-area"
180180
inputId={`data-table-${row.id}-${key}-cell`}
181+
minHeight={20}
182+
maxHeight={160}
181183
value={getSelectPickerOptionByValue(
182184
props?.options,
183185
value,
@@ -186,6 +188,8 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
186188
onChange={onChange(row, key)}
187189
isDisabled={isDisabled}
188190
formatCreateLabel={(input) => `Use ${input}`}
191+
refVar={cellRef?.current?.[row.id]?.[key]}
192+
dependentRefs={cellRef?.current?.[row.id]}
189193
fullWidth
190194
/>
191195
</div>
@@ -299,7 +303,7 @@ export const DynamicDataTableRow = <K extends string, CustomStateType = Record<s
299303
plugins={[followCursor]}
300304
>
301305
<div
302-
className={`dynamic-data-table__cell bcn-0 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${isDisabled ? 'dc__disabled no-hover' : ''} ${!isDisabled && hasError ? 'dynamic-data-table__cell--error no-hover' : ''} ${!rowTypeHasInputField(row.data[key].type) ? 'no-hover no-focus' : ''}`}
306+
className={`dynamic-data-table__cell bcn-0 flexbox dc__align-items-center dc__gap-4 dc__position-rel ${isDisabled ? 'cursor-not-allowed no-hover' : ''} ${!isDisabled && hasError ? 'dynamic-data-table__cell--error no-hover' : ''} ${!rowTypeHasInputField(row.data[key].type) ? 'no-hover no-focus' : ''}`}
303307
>
304308
{renderCellIcon(row, key, true)}
305309
{renderCellContent(row, key)}

src/Shared/Components/DynamicDataTable/styles.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@
8686
max-height: 160px !important;
8787
}
8888

89+
&__select-picker-text-area {
90+
&__control {
91+
padding: 0 !important;
92+
max-height: 160px !important;
93+
}
94+
95+
&__placeholder {
96+
align-self: start;
97+
padding: 8px 50px 8px 8px;
98+
}
99+
100+
&__indicators {
101+
position: absolute;
102+
top: 8px;
103+
right: 8px;
104+
}
105+
106+
&__input-container {
107+
display: flex !important;
108+
width: 100%;
109+
110+
&::after {
111+
content: none !important;
112+
}
113+
}
114+
115+
&__input {
116+
flex-grow: 1;
117+
padding: 8px 50px 8px 8px !important;
118+
}
119+
}
120+
89121
&:hover:not(:focus-within):not(.no-hover) {
90122
outline: 1px solid var(--N200);
91123
}

src/Shared/Components/DynamicDataTable/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,18 @@ export type DynamicDataTableCellPropsMap = {
7070
>
7171
[DynamicDataTableRowDataType.SELECT_TEXT]: Omit<
7272
SelectPickerTextAreaProps,
73-
'inputId' | 'value' | 'onChange' | 'fullWidth' | 'isDisabled' | 'variant' | 'formatCreateLabel'
73+
| 'inputId'
74+
| 'value'
75+
| 'onChange'
76+
| 'fullWidth'
77+
| 'isDisabled'
78+
| 'variant'
79+
| 'formatCreateLabel'
80+
| 'minHeight'
81+
| 'maxHeight'
82+
| 'refVar'
83+
| 'dependentRef'
84+
| 'dependentRefs'
7485
>
7586
[DynamicDataTableRowDataType.BUTTON]: Pick<
7687
DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,

src/Shared/Components/SelectPicker/SelectPicker.component.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
SelectPickerOption,
4545
SelectPickerValueContainer,
4646
SelectPickerInput,
47-
SelectPickerIndicatorsContainer,
4847
} from './common'
4948
import { SelectPickerOptionType, SelectPickerProps, SelectPickerVariantType } from './type'
5049
import { GenericSectionErrorState } from '../GenericSectionErrorState'
@@ -441,7 +440,6 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
441440
Control: SelectPickerControl,
442441
Option: renderOption,
443442
MenuList: SelectPickerMenuList,
444-
IndicatorsContainer: SelectPickerIndicatorsContainer,
445443
ClearIndicator: SelectPickerClearIndicator,
446444
ValueContainer: renderValueContainer,
447445
MultiValueLabel: renderMultiValueLabel,
@@ -467,7 +465,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
467465
onKeyDown={handleKeyDown}
468466
shouldRenderTextArea={shouldRenderTextArea}
469467
customDisplayText={customDisplayText}
470-
onFocus={handleFocus}
468+
{...(!shouldRenderTextArea ? { onFocus: handleFocus } : {})}
471469
onBlur={handleBlur}
472470
onChange={handleChange}
473471
controlShouldRenderValue={controlShouldRenderValue}

src/Shared/Components/SelectPicker/SelectPickerTextArea.component.tsx

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export const SelectPickerTextArea = ({
1111
options,
1212
isCreatable,
1313
onChange,
14+
minHeight,
15+
maxHeight,
16+
refVar,
17+
dependentRef,
18+
dependentRefs,
1419
...props
1520
}: SelectPickerTextAreaProps) => {
1621
// STATES
@@ -19,15 +24,77 @@ export const SelectPickerTextArea = ({
1924
// REFS
2025
const selectRef = useRef<SelectInstance<SelectPickerOptionType<string>>>(null)
2126

27+
useEffect(() => {
28+
const inputRef = refVar
29+
if (inputRef) {
30+
inputRef.current = selectRef.current.inputRef as unknown as HTMLTextAreaElement
31+
}
32+
}, [])
33+
2234
useEffect(() => {
2335
const selectValue = value as SingleValue<SelectPickerOptionType<string>>
2436
setInputValue(selectValue?.value || '')
2537
}, [value])
2638

2739
// METHODS
40+
const updateDependentRefsHeight = (height: number) => {
41+
const refElement = dependentRef?.current
42+
if (refElement) {
43+
refElement.style.height = `${height}px`
44+
}
45+
46+
Object.values(dependentRefs || {}).forEach((ref) => {
47+
const dependentRefElement = ref?.current
48+
if (dependentRefElement) {
49+
dependentRefElement.style.height = `${height}px`
50+
}
51+
})
52+
}
53+
54+
const updateRefsHeight = (height: number) => {
55+
const refElement = refVar?.current
56+
if (refElement) {
57+
refElement.style.height = `${height}px`
58+
}
59+
updateDependentRefsHeight(height)
60+
}
61+
62+
const reInitHeight = () => {
63+
updateRefsHeight(minHeight || 0)
64+
65+
let nextHeight = refVar?.current?.scrollHeight || 0
66+
67+
if (dependentRef) {
68+
const refElement = dependentRef.current
69+
if (refElement && refElement.scrollHeight > nextHeight) {
70+
nextHeight = refElement.scrollHeight
71+
}
72+
}
73+
74+
if (dependentRefs) {
75+
Object.values(dependentRefs).forEach((ref) => {
76+
const refElement = ref.current
77+
if (refElement && refElement.scrollHeight > nextHeight) {
78+
nextHeight = refElement.scrollHeight
79+
}
80+
})
81+
}
82+
83+
if (minHeight && nextHeight < minHeight) {
84+
nextHeight = minHeight
85+
}
86+
87+
if (maxHeight && nextHeight > maxHeight) {
88+
nextHeight = maxHeight
89+
}
90+
91+
updateRefsHeight(nextHeight)
92+
}
93+
2894
const onInputChange = (newValue: string, { action }: InputActionMeta) => {
2995
if (action === ReactSelectInputAction.inputChange) {
3096
setInputValue(newValue)
97+
reInitHeight()
3198

3299
if (!newValue) {
33100
onChange?.(null, {
@@ -62,24 +129,27 @@ export const SelectPickerTextArea = ({
62129
setInputValue(updatedText)
63130

64131
const textarea = selectRef.current.inputRef
65-
const wrapper = selectRef.current.controlRef
66132

67-
// Get the caret position relative to the scrollable area
68-
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10)
133+
// Get the caret position
69134
const caretPosition = textarea.selectionStart
70-
const linesAboveCaret = textarea.value.substring(0, caretPosition).split('\n').length
71135

72-
// Estimate caret position by line height
73-
const caretY = linesAboveCaret * lineHeight
74-
const scrollOffset = caretY - wrapper.scrollTop
136+
// Split the text up to the caret position into lines
137+
const textBeforeCaret = textarea.value.substring(0, caretPosition)
138+
const lines = textBeforeCaret.split('\n')
139+
140+
// Calculate the caret position in pixels
141+
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10)
142+
const caretY = lines.length * lineHeight
143+
144+
// Check if caret is outside of the visible area
145+
const scrollOffset = caretY - textarea.scrollTop
75146

76-
// Adjust scroll to ensure caret is visible
77147
if (scrollOffset < 0) {
78-
// Scroll up
79-
wrapper.scrollTop += scrollOffset
80-
} else if (scrollOffset > wrapper.offsetHeight - lineHeight) {
81-
// Scroll down
82-
wrapper.scrollTop += scrollOffset - wrapper.offsetHeight + lineHeight
148+
// Scroll up if the caret is above the visible area
149+
textarea.scrollTop += scrollOffset
150+
} else if (scrollOffset > textarea.offsetHeight - lineHeight) {
151+
// Scroll down if the caret is below the visible area
152+
textarea.scrollTop += scrollOffset - textarea.offsetHeight + lineHeight
83153
}
84154

85155
// Move the cursor to the next line

src/Shared/Components/SelectPicker/common.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
MultiValueProps,
2727
MultiValue,
2828
InputProps,
29-
IndicatorsContainerProps,
3029
} from 'react-select'
3130
import { Progressing } from '@Common/Progressing'
3231
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
@@ -62,30 +61,22 @@ const getTooltipProps = (tooltipProps: SelectPickerOptionType['tooltipProps'] =
6261
}
6362
}
6463

65-
export const SelectPickerIndicatorsContainer = <OptionValue,>({
66-
className = '',
67-
...props
68-
}: IndicatorsContainerProps<SelectPickerOptionType<OptionValue>>) => (
69-
<components.IndicatorsContainer {...props} className={`${className} dc__position-sticky dc__top-0 pt-2`} />
70-
)
71-
7264
export const SelectPickerDropdownIndicator = <OptionValue,>(
7365
props: DropdownIndicatorProps<SelectPickerOptionType<OptionValue>>,
7466
) => {
75-
const { isDisabled, className = '' } = props
67+
const { isDisabled } = props
7668

7769
return (
78-
<components.DropdownIndicator {...props} className={`${className} dc__align-self-start`}>
70+
<components.DropdownIndicator {...props}>
7971
<ICCaretDown className={isDisabled ? 'scn-3' : 'scn-6'} />
8072
</components.DropdownIndicator>
8173
)
8274
}
8375

84-
export const SelectPickerClearIndicator = <OptionValue,>({
85-
className = '',
86-
...props
87-
}: ClearIndicatorProps<SelectPickerOptionType<OptionValue>>) => (
88-
<components.ClearIndicator {...props} className={`${className} dc__align-self-start`}>
76+
export const SelectPickerClearIndicator = <OptionValue,>(
77+
props: ClearIndicatorProps<SelectPickerOptionType<OptionValue>>,
78+
) => (
79+
<components.ClearIndicator {...props}>
8980
<ICClose className="icon-use-fill-n6" />
9081
</components.ClearIndicator>
9182
)
@@ -136,9 +127,14 @@ export const SelectPickerValueContainer = <OptionValue, IsMulti extends boolean>
136127
}) => {
137128
const {
138129
getValue,
139-
selectProps: { customDisplayText },
130+
selectProps: { customDisplayText, shouldRenderTextArea },
140131
children,
141132
} = props
133+
134+
if (shouldRenderTextArea) {
135+
return <components.ValueContainer {...props} />
136+
}
137+
142138
const selectedOptionsLength = isNullOrUndefined(customSelectedOptionsCount)
143139
? (getValue() ?? []).length
144140
: customSelectedOptionsCount

src/Shared/Components/SelectPicker/type.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CreatableProps } from 'react-select/creatable'
2323
// This import allows to extend the base interface in react-select module via module augmentation
2424
import type {} from 'react-select/base'
2525
import { TooltipProps } from '@Common/Tooltip/types'
26+
import { ResizableTagTextAreaProps } from '@Common/CustomTagSelector'
2627

2728
export interface SelectPickerOptionType<OptionValue = string | number> extends OptionType<OptionValue, ReactNode> {
2829
/**
@@ -307,14 +308,14 @@ export interface FilterSelectPickerProps
307308
handleApplyFilter: (filtersToApply: SelectPickerOptionType<number | string>[]) => void
308309
}
309310

310-
export interface SelectPickerTextAreaProps
311-
extends Omit<
312-
SelectPickerProps<string, false>,
313-
| 'selectRef'
314-
| 'inputValue'
315-
| 'onInputChange'
316-
| 'controlShouldRenderValue'
317-
| 'onKeyDown'
318-
| 'onCreateOption'
319-
| 'shouldRenderTextArea'
320-
> {}
311+
export type SelectPickerTextAreaProps = Omit<
312+
SelectPickerProps<string, false>,
313+
| 'selectRef'
314+
| 'inputValue'
315+
| 'onInputChange'
316+
| 'controlShouldRenderValue'
317+
| 'onKeyDown'
318+
| 'onCreateOption'
319+
| 'shouldRenderTextArea'
320+
> &
321+
Pick<ResizableTagTextAreaProps, 'maxHeight' | 'minHeight' | 'refVar' | 'dependentRef' | 'dependentRefs'>

0 commit comments

Comments
 (0)