diff --git a/package-lock.json b/package-lock.json index 9438c49e9..792a15a00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.14.1-pre-2", + "version": "1.14.1-pre-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.14.1-pre-2", + "version": "1.14.1-pre-3", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index a1816526f..83e709071 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.14.1-pre-2", + "version": "1.14.1-pre-3", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Assets/IconV2/ic-spray-can.svg b/src/Assets/IconV2/ic-spray-can.svg new file mode 100644 index 000000000..d7ac168eb --- /dev/null +++ b/src/Assets/IconV2/ic-spray-can.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Shared/Components/DynamicDataTable/DynamicDataTable.tsx b/src/Shared/Components/DynamicDataTable/DynamicDataTable.tsx index 95349fc75..dd6580a52 100644 --- a/src/Shared/Components/DynamicDataTable/DynamicDataTable.tsx +++ b/src/Shared/Components/DynamicDataTable/DynamicDataTable.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useMemo, useState } from 'react' +import { useMemo } from 'react' import { DynamicDataTableHeader } from './DynamicDataTableHeader' import { DynamicDataTableRow } from './DynamicDataTableRow' @@ -24,30 +24,15 @@ import './styles.scss' export const DynamicDataTable = >({ headers, - onRowAdd, ...props }: DynamicDataTableProps) => { - // STATES - const [isAddRowButtonClicked, setIsAddRowButtonClicked] = useState(false) - // CONSTANTS const filteredHeaders = useMemo(() => headers.filter(({ isHidden }) => !isHidden), [headers]) - // HANDLERS - const handleRowAdd = () => { - setIsAddRowButtonClicked(true) - onRowAdd() - } - return (
- - + +
) } diff --git a/src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx b/src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx index 45b141d80..3b1c438e4 100644 --- a/src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx +++ b/src/Shared/Components/DynamicDataTable/DynamicDataTableRow.tsx @@ -14,21 +14,10 @@ * limitations under the License. */ -import { - createElement, - createRef, - Fragment, - ReactElement, - RefObject, - useEffect, - useMemo, - useRef, - useState, -} from 'react' +import { createElement, createRef, Fragment, ReactElement, RefObject, useEffect, useMemo, useRef } from 'react' // eslint-disable-next-line import/no-extraneous-dependencies import { followCursor } from 'tippy.js' -import { ReactComponent as ICClose } from '@Icons/ic-close.svg' import { ResizableTagTextArea } from '@Common/CustomTagSelector' import { ConditionalWrap } from '@Common/Helper' import { Tooltip } from '@Common/Tooltip' @@ -36,6 +25,7 @@ import { ComponentSizeType } from '@Shared/constants' import { Button, ButtonStyleType, ButtonVariantType } from '../Button' import { FileUpload } from '../FileUpload' +import { Icon } from '../Icon' import { getSelectPickerOptionByValue, SelectPicker, @@ -73,8 +63,7 @@ export const DynamicDataTableRow = ) => { // CONSTANTS const isFirstRowEmpty = headers.every(({ key }) => !rows[0]?.data[key].value) @@ -88,25 +77,18 @@ export const DynamicDataTableRow = >>>() + const shouldAutoFocusNewRowRef = useRef(shouldAutoFocusOnMount) + const cellRef = useRef>>>(null) if (!cellRef.current) { - cellRef.current = rows.reduce( - (acc, curr) => ({ - ...acc, - [curr.id]: headers.reduce((headerAcc, { key }) => ({ ...headerAcc, [key]: createRef() }), {}), - }), - {}, - ) + cellRef.current = rows.reduce((acc, curr) => { + acc[curr.id] = headers.reduce((headerAcc, { key }) => ({ ...headerAcc, [key]: createRef() }), {}) + return acc + }, {}) } const rowIds = useMemo(() => rows.map(({ id }) => id), [rows]) useEffect(() => { - setIsRowAdded(rows.length > 0 && Object.keys(cellRef.current).length < rows.length) - // When a new row is added, we create references for its cells. // This logic ensures that references are created only for the newly added row, while retaining the existing references. const updatedCellRef = rowIds.reduce((acc, curr) => { @@ -121,18 +103,6 @@ export const DynamicDataTableRow = { - if (isAddRowButtonClicked && isRowAdded) { - // Using the below logic to ensure the cell is focused after row addition. - const cell = cellRef.current[rows[0].id][focusableFieldKey || headers[0].key].current - if (cell) { - cell.focus() - setIsRowAdded(false) - setIsAddRowButtonClicked(false) - } - } - }, [isRowAdded, isAddRowButtonClicked]) - // METHODS const onChange = (row: DynamicDataTableRowType, key: K) => @@ -163,14 +133,30 @@ export const DynamicDataTableRow = , key: K) => { + const renderCellContent = (row: DynamicDataTableRowType, key: K, index: number) => { const isDisabled = readOnly || row.data[key].disabled + const autoFocus = + shouldAutoFocusNewRowRef.current && key === (focusableFieldKey ?? headers[0].key) && index === 0 + + // This logic ensures only newly added rows get autofocus. + // On the initial mount, all rows are treated as new, so autofocus is enabled. + // After the first render, when cellRef is set (DOM rendered), we set shouldAutoFocusNewRowRef to true, + // so autofocus is applied only to the correct cell in any subsequently added row. + if ( + !shouldAutoFocusOnMount && + !shouldAutoFocusNewRowRef.current && + index === 0 && + cellRef?.current?.[row.id]?.[key].current + ) { + shouldAutoFocusNewRowRef.current = true + } switch (row.data[key].type) { case DynamicDataTableRowDataType.DROPDOWN: return (
+ autoFocus={autoFocus} {...row.data[key].props} inputId={`data-table-${row.id}-${key}-cell`} classNamePrefix="dynamic-data-table__cell__select-picker" @@ -193,6 +179,7 @@ export const DynamicDataTableRow = ( -
- +
+

{errorMessage}

) @@ -329,7 +317,7 @@ export const DynamicDataTableRow = {renderCellIcon(row, key, true)} - {renderCellContent(row, key)} + {renderCellContent(row, key, index)} {renderAsterisk(row, key)} {renderCellIcon(row, key)} {renderErrorMessages(row, key)} @@ -383,7 +371,7 @@ export const DynamicDataTableRow = } + icon={} disabled={disableDeleteRow || row.disableDelete} onClick={onDelete(row)} variant={ButtonVariantType.borderLess} diff --git a/src/Shared/Components/DynamicDataTable/styles.scss b/src/Shared/Components/DynamicDataTable/styles.scss index bea6f38b1..015d955f1 100644 --- a/src/Shared/Components/DynamicDataTable/styles.scss +++ b/src/Shared/Components/DynamicDataTable/styles.scss @@ -69,21 +69,21 @@ height: 36px; width: 100%; background: inherit; - - &--add { - resize: none; - border-radius: 4px; - outline: none; - } } &__cell { min-width: 0; - &__select-picker__control { - gap: 6px !important; - padding: 8px !important; - max-height: 160px !important; + &__select-picker { + &__control { + gap: 6px !important; + padding: 8px !important; + max-height: 160px !important; + } + + &__single-value { + font-weight: 400 !important; + } } &__select-picker-text-area { diff --git a/src/Shared/Components/DynamicDataTable/types.ts b/src/Shared/Components/DynamicDataTable/types.ts index 3e9574cb1..a7b8b3981 100644 --- a/src/Shared/Components/DynamicDataTable/types.ts +++ b/src/Shared/Components/DynamicDataTable/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DetailedHTMLProps, Dispatch, ReactElement, ReactNode, SetStateAction } from 'react' +import { DetailedHTMLProps, ReactElement, ReactNode } from 'react' import { ResizableTagTextAreaProps } from '@Common/CustomTagSelector' import { UseStateFiltersReturnType } from '@Common/Hooks' @@ -228,6 +228,12 @@ export type DynamicDataTableProps> @@ -261,7 +267,5 @@ export interface DynamicDataTableRowProps { - isAddRowButtonClicked: boolean - setIsAddRowButtonClicked: Dispatch> -} + | 'shouldAutoFocusOnMount' + > {} diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index 0a543e5fe..64ab92468 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -136,6 +136,7 @@ import { ReactComponent as ICSortDescending } from '@IconsV2/ic-sort-descending. import { ReactComponent as ICSortable } from '@IconsV2/ic-sortable.svg' import { ReactComponent as ICSparkleColor } from '@IconsV2/ic-sparkle-color.svg' import { ReactComponent as ICSpinny } from '@IconsV2/ic-spinny.svg' +import { ReactComponent as ICSprayCan } from '@IconsV2/ic-spray-can.svg' import { ReactComponent as ICStack } from '@IconsV2/ic-stack.svg' import { ReactComponent as ICStamp } from '@IconsV2/ic-stamp.svg' import { ReactComponent as ICSuccess } from '@IconsV2/ic-success.svg' @@ -298,6 +299,7 @@ export const iconMap = { 'ic-sortable': ICSortable, 'ic-sparkle-color': ICSparkleColor, 'ic-spinny': ICSpinny, + 'ic-spray-can': ICSprayCan, 'ic-stack': ICStack, 'ic-stamp': ICStamp, 'ic-success': ICSuccess, diff --git a/src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx b/src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx index e43a693a9..6b11c369a 100644 --- a/src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx +++ b/src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx @@ -41,6 +41,7 @@ export const KeyValueTable = ({ headerComponent, onChange, isAdditionNotAllowed, + shouldAutoFocusOnMount, readOnly, showError, validationSchema: parentValidationSchema, @@ -180,6 +181,7 @@ export const KeyValueTable = ({ headerComponent={headerComponent} readOnly={readOnly} isAdditionNotAllowed={isAdditionNotAllowed} + shouldAutoFocusOnMount={shouldAutoFocusOnMount} sortingConfig={{ sortBy, sortOrder, diff --git a/src/Shared/Components/KeyValueTable/KeyValueTable.types.ts b/src/Shared/Components/KeyValueTable/KeyValueTable.types.ts index fff70a1b0..a245542b7 100644 --- a/src/Shared/Components/KeyValueTable/KeyValueTable.types.ts +++ b/src/Shared/Components/KeyValueTable/KeyValueTable.types.ts @@ -85,7 +85,7 @@ export interface KeyValueTableData extends Pick { */ export type KeyValueTableProps = Pick< DynamicDataTableProps, - 'isAdditionNotAllowed' | 'readOnly' | 'headerComponent' + 'isAdditionNotAllowed' | 'readOnly' | 'headerComponent' | 'shouldAutoFocusOnMount' > & { /** * The label for the table header. diff --git a/src/Shared/DocLink/constants.ts b/src/Shared/DocLink/constants.ts index 9df6d6791..ed342b4c4 100644 --- a/src/Shared/DocLink/constants.ts +++ b/src/Shared/DocLink/constants.ts @@ -33,6 +33,7 @@ export const DOCUMENTATION = { EXTERNAL_SECRET: 'usage/applications/creating-application/secrets#external-secrets', HOME_PAGE: 'https://devtron.ai', KUBE_CONFIG: 'usage/resource-browser#running-kubectl-commands-locally', + TAINT: 'usage/resource-browser#taint-a-node', // Global Configurations GLOBAL_CONFIG_API_TOKEN: 'getting-started/global-configurations/authorization/api-tokens',