Skip to content
27 changes: 14 additions & 13 deletions components/KernDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function KernDropdown(props: KernDropdownProps) {
setDropdownCaptions(prepareOptions);
setSearchIndexes(null);
}
}, [props.options, searchText, selectedCheckboxes, props.hasSearchBar, props.hasCheckboxes, props.selectedCheckboxes, props.hasSelectAll, props.valuePropertyPath]);
}, [props.options, searchText, props.hasSearchBar, props.hasCheckboxes, props.valuePropertyPath, props.selectedCheckboxes, props.hasSelectAll]);

useEffect(() => {
if (!props.disabledOptions || !props.options) return;
Expand Down Expand Up @@ -94,8 +94,7 @@ export default function KernDropdown(props: KernDropdownProps) {
return { "maxHeight": `${maxHeight}rem`, "overflowY": "auto" };
}, [props.scrollAfterNOptions]);

function setOptionsWithCheckboxes(options: any[]) {
if (selectedCheckboxes.length > 0) return;
const setOptionsWithCheckboxes = useCallback((options: any[]) => {
const newSelectedCheckboxes = options.map((option: any, index: number) => {
return {
name: option,
Expand All @@ -105,25 +104,27 @@ export default function KernDropdown(props: KernDropdownProps) {
if (props.hasSelectAll) {
newSelectedCheckboxes.push({
name: SELECT_ALL,
checked: false
checked: newSelectedCheckboxes.every((checkbox) => checkbox.checked)
});
}
setSelectedCheckboxes(newSelectedCheckboxes);
setDropdownCaptions(newSelectedCheckboxes.map((option: any) => option.name));
}
}, [props.hasSelectAll, props.selectedCheckboxes]);

function toggleDropdown() {
if (isDisabled && !props.hasCheckboxes) return; // if the dropdown has checkboxes, it shouldn't be disabled because the user can still select options
if (isOpen && props.keepDrownOpen) return;
setIsOpen(!isOpen);
}

function handleSelectedCheckboxes(option: string, index: number, e: any) {
const handleSelectedCheckboxes = useCallback((option: string, index: number, e: any) => {
let newSelectedCheckboxes = [...selectedCheckboxes];
if (option == SELECT_ALL) {
newSelectedCheckboxes.forEach((checkbox) => {
checkbox.checked = e.target.checked;
});
const allSelected = newSelectedCheckboxes.every((checkbox) => checkbox.checked);
newSelectedCheckboxes[newSelectedCheckboxes.length - 1].checked = allSelected;
} else {
const lastIdx = newSelectedCheckboxes.length - 1;
if (props.hasSelectAll && newSelectedCheckboxes[lastIdx].checked) {
Expand All @@ -136,7 +137,7 @@ export default function KernDropdown(props: KernDropdownProps) {
newSelectedCheckboxes = newSelectedCheckboxes.filter((checkbox) => checkbox.name != SELECT_ALL);
}
props.selectedOption(newSelectedCheckboxes);
}
}, [selectedCheckboxes, props.hasSelectAll]);

function handleSelectedCheckboxesThreeStates(index: number) {
const optionSave = { ...props.options[index] };
Expand All @@ -161,8 +162,7 @@ export default function KernDropdown(props: KernDropdownProps) {
setSavedIndex(index);
}


function performActionOnClick(option: string, index: number) {
const performActionOnClick = useCallback((option: string, index: number) => {
if (props.hasCheckboxes) {
handleSelectedCheckboxes(option, index, { target: { checked: !selectedCheckboxes[index].checked } });
return;
Expand All @@ -180,8 +180,9 @@ export default function KernDropdown(props: KernDropdownProps) {
props.selectedOption(props.options[index]);
}
setIsOpen(false);
setSelectedCheckboxes([]);
}
}
}, [props, selectedCheckboxes, searchIndexes]);

return (
<Menu ref={dropdownRef} as="div" className={`relative inline-block text-left ${props.dropdownWidth ?? 'w-full'} ${props.dropdownClasses ?? ''} ${props.fontClass ?? ''}`}>
Expand Down Expand Up @@ -212,8 +213,8 @@ export default function KernDropdown(props: KernDropdownProps) {
</Menu.Button>
) : (<Menu.Button onClick={toggleDropdown} className={`inline-flex w-full justify-between items-center rounded-md border border-gray-300 px-4 py-2 text-sm font-semibold text-gray-700 shadow-sm focus:outline-none focus:ring-2
focus:ring-gray-300 focus:ring-offset-2 focus:ring-offset-gray-100 disabled:opacity-50 disabled:cursor-not-allowed ${props.buttonClasses ?? ''} ${props.buttonCaptionBgColor ?? 'bg-white hover:bg-gray-50'}`}
disabled={isDisabled && !props.hasCheckboxes}>
<div className='flex items-center gap-x-1'>
disabled={isDisabled}>
<div className={`flex items-center gap-x-1 ${props.truncateButtonName ? 'max-w-[300px] truncate' : ''}`}>
{props.buttonPrefixIcon}
{!props.hasCheckboxesThreeStates && props.buttonName}
</div>
Expand Down Expand Up @@ -266,7 +267,7 @@ export default function KernDropdown(props: KernDropdownProps) {
if (!props.optionsHaveHoverBox) return;
setHoverBoxPosition(null);
}}>
{props.hasCheckboxes && <input checked={selectedCheckboxes[index].checked} name="option" type="checkbox" className="mr-3 cursor-pointer"
{props.hasCheckboxes && <input checked={!!selectedCheckboxes[index]?.checked} name="option" type="checkbox" className="mr-3 cursor-pointer"
onChange={(e) => handleSelectedCheckboxes(option, index, e)} />}
{props.hasCheckboxesThreeStates && <div className="h-4 w-4 border-gray-300 mr-3 border rounded hover:bg-gray-200 min-w-4"
style={{ backgroundColor: getActiveNegateGroupColor(props.options[index]), borderColor: getActiveNegateGroupColor(props.options[index]) }}>
Expand Down
3 changes: 2 additions & 1 deletion components/kern-icons/icons.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { memo } from 'react';
import { IconActivity, IconAdjustments, IconAdjustmentsAlt, IconAdjustmentsOff, IconAlertCircle, IconAlertTriangle, IconAlertTriangleFilled, IconAngle, IconApi, IconArchive, IconArrowAutofitDown, IconArrowCurveRight, IconArrowDown, IconArrowLeft, IconArrowNarrowLeft, IconArrowNarrowRight, IconArrowRight, IconArrowsRandom, IconArrowsSort, IconArrowUp, IconArrowUpRight, IconAssembly, IconBallpen, IconBallpenOff, IconBell, IconBolt, IconBottle, IconBox, IconBoxOff, IconBrandGithub, IconBrandOpenai, IconBrandPython, IconBulb, IconBulldozer, IconCamera, IconCategoryPlus, IconCell, IconChartBubble, IconChartCircles, IconChartDots3, IconChartLine, IconChartPie, IconCheck, IconChecks, IconChevronCompactLeft, IconChevronCompactRight, IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronUp, IconCircle, IconCircleCheck, IconCircleCheckFilled, IconCircleMinus, IconCirclePlus, IconClick, IconClipboard, IconClipboardCheck, IconClipboardOff, IconClock, IconCode, IconCodePlus, IconColorPicker, IconColumns, IconColumns1, IconColumns2, IconColumns3, IconCopy, IconCrown, IconCrownOff, IconDatabase, IconDatabasePlus, IconDeviceFloppy, IconDots, IconDotsVertical, IconDownload, IconEdit, IconEngine, IconExclamationCircle, IconExclamationMark, IconExternalLink, IconEye, IconEyeCancel, IconEyeCheck, IconEyeOff, IconFile, IconFileDownload, IconFileImport, IconFileInfo, IconFilePencil, IconFiles, IconFileText, IconFileUpload, IconFilter, IconFilterOff, IconFishHook, IconFolderBolt, IconGitCommit, IconGripVertical, IconHandClick, IconHeading, IconHelp, IconHexagons, IconHierarchy, IconHierarchy3, IconHistory, IconHome, IconHourglass, IconHourglassEmpty, IconInfoCircle, IconInfoCircleFilled, IconInfoSquare, IconLayoutList, IconLayoutNavbarCollapse, IconLayoutSidebar, IconLetterGSmall, IconLink, IconList, IconLoader, IconLoader2, IconLockAccess, IconMap, IconMaximize, IconMessageCircle, IconMinus, IconMessageCircleSearch, IconMessages, IconMinimize, IconMoustache, IconNews, IconNotes, IconPencil, IconPlayCardStar, IconPlayerPlay, IconPlayerPlayFilled, IconPlus, IconPoint, IconPointerOff, IconPointerSearch, IconPointFilled, IconQuestionMark, IconRecycle, IconRefresh, IconRefreshAlert, IconResize, IconRobot, IconRotate, IconScissors, IconScreenshot, IconSearch, IconSend, IconSettings, IconShare, IconShieldCheckFilled, IconShieldFilled, IconSquare, IconSquareCheck, IconStar, IconTag, IconTemplate, IconTerminal, IconThumbDown, IconThumbDownFilled, IconThumbUp, IconThumbUpFilled, IconTrash, IconTrashXFilled, IconTriangleInverted, IconTriangleSquareCircle, IconUpload, IconUser, IconUsersGroup, IconUserX, IconVariable, IconVariablePlus, IconVersions, IconWand, IconWaveSine, IconWebhook, IconWreckingBall, IconX, IconZoomCode, IconDragDrop2, IconWorld } from '@tabler/icons-react';
import { IconActivity, IconAdjustments, IconAdjustmentsAlt, IconAdjustmentsOff, IconAlertCircle, IconAlertTriangle, IconAlertTriangleFilled, IconAngle, IconApi, IconArchive, IconArrowAutofitDown, IconArrowCurveRight, IconArrowDown, IconArrowLeft, IconArrowNarrowLeft, IconArrowNarrowRight, IconArrowRight, IconArrowsRandom, IconArrowsSort, IconArrowUp, IconArrowUpRight, IconAssembly, IconBallpen, IconBallpenOff, IconBell, IconBolt, IconBottle, IconBox, IconBoxOff, IconBrandGithub, IconBrandOpenai, IconBrandPython, IconBulb, IconBulldozer, IconCamera, IconCategoryPlus, IconCell, IconChartBubble, IconChartCircles, IconChartDots3, IconChartLine, IconChartPie, IconCheck, IconChecks, IconChevronCompactLeft, IconChevronCompactRight, IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronUp, IconCircle, IconCircleCheck, IconCircleCheckFilled, IconCircleMinus, IconCirclePlus, IconClick, IconClipboard, IconClipboardCheck, IconClipboardOff, IconClock, IconCode, IconCodePlus, IconColorPicker, IconColumns, IconColumns1, IconColumns2, IconColumns3, IconCopy, IconCrown, IconCrownOff, IconDatabase, IconDatabasePlus, IconDeviceFloppy, IconDots, IconDotsVertical, IconDownload, IconEdit, IconEngine, IconExclamationCircle, IconExclamationMark, IconExternalLink, IconEye, IconEyeCancel, IconEyeCheck, IconEyeOff, IconFile, IconFileDownload, IconFileImport, IconFileInfo, IconFilePencil, IconFiles, IconFileText, IconFileUpload, IconFilter, IconFilterOff, IconFishHook, IconFolderBolt, IconGitCommit, IconGripVertical, IconHandClick, IconHeading, IconHelp, IconHexagons, IconHierarchy, IconHierarchy3, IconHistory, IconHome, IconHourglass, IconHourglassEmpty, IconInfoCircle, IconInfoCircleFilled, IconInfoSquare, IconLayoutList, IconLayoutNavbarCollapse, IconLayoutSidebar, IconLetterGSmall, IconLink, IconList, IconLoader, IconLoader2, IconLockAccess, IconMap, IconMaximize, IconMessageCircle, IconMinus, IconMessageCircleSearch, IconMessages, IconMinimize, IconMoustache, IconNews, IconNotes, IconPencil, IconPlayCardStar, IconPlayerPlay, IconPlayerPlayFilled, IconPlus, IconPoint, IconPointerOff, IconPointerSearch, IconPointFilled, IconQuestionMark, IconRecycle, IconRefresh, IconRefreshAlert, IconResize, IconRobot, IconRotate, IconScissors, IconScreenshot, IconSearch, IconSend, IconSettings, IconShare, IconShieldCheckFilled, IconShieldFilled, IconSquare, IconSquareCheck, IconStar, IconTag, IconTemplate, IconTerminal, IconThumbDown, IconThumbDownFilled, IconThumbUp, IconThumbUpFilled, IconTrash, IconTrashXFilled, IconTriangleInverted, IconTriangleSquareCircle, IconUpload, IconUser, IconUsersGroup, IconUserX, IconVariable, IconVariablePlus, IconVersions, IconWand, IconWaveSine, IconWebhook, IconWreckingBall, IconX, IconZoomCode, IconDragDrop2, IconWorld, IconCircleDotted } from '@tabler/icons-react';

export const MemoIconHome = memo(IconHome);
export const MemoIconInfoCircle = memo(IconInfoCircle);
Expand Down Expand Up @@ -184,3 +184,4 @@ export const MemoIconArrowNarrowRight = memo(IconArrowNarrowRight);
export const MemoIconMinus = memo(IconMinus);
export const MemoIconDragDrop2 = memo(IconDragDrop2);
export const MemoIconWorld = memo(IconWorld);
export const MemoIconCircleDotted = memo(IconCircleDotted);
52 changes: 51 additions & 1 deletion components/kern-table/CellComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,54 @@ function ConfigReleaseNotificationCell({ onClickView, onClickEdit }) {
</div>;
}

export { OrganizationAndUsersCell, MaxRowsColsCharsCell, CommentsCell, ExportConsumptionAndDeleteCell, BadgeCell, OrganizationUserCell, DeleteCell, LevelCell, ArchiveReasonCell, ProjectNameTaskCell, CancelTaskCell, IconCell, ConfigCell, EditDeleteOrgButtonCell, ViewStackCell, AbortSessionButtonCell, FeedbackMessageCell, FeedbackMessageTextCell, JumpToConversationCell, RemoteVersionCell, ExternalLinkCell, ModelDateCell, FileSizeCell, StatusModelCell, DeleteModelCell, LabelCell, ViewCell, EvaluationRunStateCell, EvaluationRunDetailsCell, EtlApiTokenCell, EmailCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell }
function TruncateAndTooltipCell({ value, hasError = false }) {
return <div className="flex items-center">
{hasError && <MemoIconAlertTriangleFilled className="h-5 w-5 text-red-600 mr-2" />}
{value ? <Tooltip content={<span className="block max-w-[300px] break-words max-h-[500px] overflow-y-auto">{value}</span>} color="invert" hideArrow={true} placement='bottom'>
<span className="block max-w-56 truncate">{value}</span>
</Tooltip> : <NotApplicableBadge />}
</div>;
}

function JumpToConversationAndAssignCell({ onClick, jumpTo }) {
return <div className="flex justify-center">
<Tooltip content={`Assign user to the org and jump to ${jumpTo}`} color="invert" className="cursor-auto">
<button onClick={onClick}
className='inline-flex p-2 items-center justify-center rounded-lg hover:bg-gray-200'>
<MemoIconArrowRight className='h-4 w-4' />
</button>
</Tooltip>
</div>
}

function TaskStateCell({ value, color, tooltipValue }) {
const className = useMemo(() => {
return `inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${color === 'green'
? 'bg-green-300'
: 'bg-' + color + '-100 text-' + color + '-800'}`;
}, [color]);

return (
<>
{tooltipValue ? (
<Tooltip content={tooltipValue} color="invert" className="cursor-auto">
<span className={className}>
<svg className={`mr-1.5 h-2 w-2 ${'text-' + color + '-400'}`} fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
<span className={' text-' + color + '-800'}>{value}</span>
</span>
</Tooltip>
) : (
<span className={className}>
<svg className={`mr-1.5 h-2 w-2 ${'text-' + color + '-400'}`} fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
<span className={' text-' + color + '-800'}>{value}</span>
</span>
)}
</>
);
}

export { OrganizationAndUsersCell, MaxRowsColsCharsCell, CommentsCell, ExportConsumptionAndDeleteCell, BadgeCell, OrganizationUserCell, DeleteCell, LevelCell, ArchiveReasonCell, ProjectNameTaskCell, CancelTaskCell, IconCell, ConfigCell, EditDeleteOrgButtonCell, ViewStackCell, AbortSessionButtonCell, FeedbackMessageCell, FeedbackMessageTextCell, JumpToConversationCell, RemoteVersionCell, ExternalLinkCell, ModelDateCell, FileSizeCell, StatusModelCell, DeleteModelCell, LabelCell, ViewCell, EvaluationRunStateCell, EvaluationRunDetailsCell, EtlApiTokenCell, EmailCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell }
8 changes: 7 additions & 1 deletion components/kern-table/KernTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SortArrows from "@/submodules/react-components/components/kern-table/SortArrows";
import { KernTableProps } from "../../types/kern-table";
import { AbortSessionButtonCell, ArchiveReasonCell, BadgeCell, CancelTaskCell, CommentsCell, ConfigCell, DeleteModelCell, DeleteCell, EditDeleteOrgButtonCell, EmailCell, EtlApiTokenCell, EvaluationRunDetailsCell, EvaluationRunStateCell, ExportConsumptionAndDeleteCell, ExternalLinkCell, FeedbackMessageCell, FeedbackMessageTextCell, FileSizeCell, IconCell, JumpToConversationCell, LabelCell, LevelCell, MaxRowsColsCharsCell, ModelDateCell, OrganizationAndUsersCell, OrganizationUserCell, ProjectNameTaskCell, RemoteVersionCell, StatusModelCell, ViewCell, ViewStackCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell } from "./CellComponents";
import { AbortSessionButtonCell, ArchiveReasonCell, BadgeCell, CancelTaskCell, CommentsCell, ConfigCell, DeleteModelCell, DeleteCell, EditDeleteOrgButtonCell, EmailCell, EtlApiTokenCell, EvaluationRunDetailsCell, EvaluationRunStateCell, ExportConsumptionAndDeleteCell, ExternalLinkCell, FeedbackMessageCell, FeedbackMessageTextCell, FileSizeCell, IconCell, JumpToConversationCell, LabelCell, LevelCell, MaxRowsColsCharsCell, ModelDateCell, OrganizationAndUsersCell, OrganizationUserCell, ProjectNameTaskCell, RemoteVersionCell, StatusModelCell, ViewCell, ViewStackCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell } from "./CellComponents";
import { Fragment, useMemo } from "react";
import KernDropdown from "../KernDropdown";
import { NotApplicableBadge } from "@/submodules/react-components/components/Badges";
Expand Down Expand Up @@ -162,6 +162,12 @@ function ComponentMapper(cell: any) {
return <LinkCell {...cell} />;
case 'ConfigReleaseNotificationCell':
return <ConfigReleaseNotificationCell {...cell} />;
case 'TruncateAndTooltipCell':
return <TruncateAndTooltipCell {...cell} />;
case 'JumpToConversationAndAssignCell':
return <JumpToConversationAndAssignCell {...cell} />;
case 'TaskStateCell':
return <TaskStateCell {...cell} />;
case '@provided@':
return cell.jsx ?? <NotApplicableBadge />;
}
Expand Down
2 changes: 2 additions & 0 deletions types/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import React from "react";
* @positionDropdown {string} - The position of the dropdown
* @dropdownAdd {JSX.Element} - array of JSX elements that will be added to the dropdown items
* @forceOverwriteOpen {boolean} - forces the dropdown to stay open until set to false/undefined
* @truncateButtonName {boolean} - If the button name should be truncated when it exceeds the button width
*/
export type KernDropdownProps = {
buttonName?: string;
Expand Down Expand Up @@ -93,6 +94,7 @@ export type KernDropdownProps = {
scrollAfterNOptions?: number;
dropdownAdd?: JSX.Element[];
forceOverwriteOpen?: boolean;
truncateButtonName?: boolean;
}

export type AppSelectionDropdownProps = {
Expand Down