chore: Replacing the entity group list component on state inspector and data side pane with new ADS component (#38621)

## Description

Replacing the entity group list component on state inspector and data
side pane with new ADS component

Fixes [#38290](https://github.com/appsmithorg/appsmith/issues/38290)

## Automation

/ok-to-test tags="@tag.Datasource"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12763738469>
> Commit: a4b717244604617d39fb1869d039899dd024c687
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12763738469&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Datasource`
> Spec:
> <hr>Tue, 14 Jan 2025 09:50:48 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
  - Enhanced list item styling with configurable height.
  - Added support for dynamic item count in entity groups.
  - Improved icon rendering with customizable dimensions.
  - Introduced a new constant for default group list size.

- **Improvements**
  - Simplified rendering of grouped items in various components.
  - Refined type safety for boolean assignments.
  - Updated state management for entity groups.
  - Enhanced control over item visibility in modals and lists.

- **Technical Updates**
  - Introduced new data attributes for enhanced component interactions.
  - Modernized component rendering logic.
- Replaced custom styled list with a dedicated `EntityGroupsList`
component for better maintainability.
- Expanded story examples for the `ListItem` component to showcase
various configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ankita Kinger 2025-01-14 15:27:08 +05:30 committed by GitHub
parent b6f7164c14
commit d187854e40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 143 additions and 96 deletions

View File

@ -211,20 +211,36 @@ export const ListItemInlineDescStory = ListItemTemplate.bind({}) as StoryObj;
ListItemInlineDescStory.storyName = "List item inline description";
ListItemInlineDescStory.argTypes = ListItemArgTypes;
ListItemInlineDescStory.args = {
title: "Action item 1",
title:
"Action_item_1_with_a_very_long_name_that_should_show_ellipsis_in_the_same_line",
description: "inline",
};
export const ListItemBlockDescStory = ListItemTemplate.bind({}) as StoryObj;
ListItemBlockDescStory.storyName = "List item block description";
ListItemBlockDescStory.argTypes = ListItemArgTypes;
ListItemBlockDescStory.args = {
export const ListItemBlockDescWithIconStory = ListItemTemplate.bind(
{},
) as StoryObj;
ListItemBlockDescWithIconStory.storyName =
"List item block description with icon";
ListItemBlockDescWithIconStory.argTypes = ListItemArgTypes;
ListItemBlockDescWithIconStory.args = {
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 1",
description: "block",
descriptionType: "block",
};
export const ListItemBlockDescWithoutIconStory = ListItemTemplate.bind(
{},
) as StoryObj;
ListItemBlockDescWithoutIconStory.storyName =
"List item block description without icon";
ListItemBlockDescWithoutIconStory.argTypes = ListItemArgTypes;
ListItemBlockDescWithoutIconStory.args = {
title: "Action item 1",
description: "Action item 1 block description",
descriptionType: "block",
};
export const ListItemOverflowStory = ListItemTemplate.bind({}) as StoryObj;
ListItemOverflowStory.storyName = "List item title overflow";
ListItemOverflowStory.argTypes = ListItemArgTypes;

View File

@ -31,6 +31,10 @@ const Sizes = {
export const TooltipTextWrapper = styled.div`
display: flex;
min-width: 0;
&.${ListItemIDescClassName}-wrapper {
min-width: unset;
}
`;
export const RightControlWrapper = styled.div`
@ -54,8 +58,11 @@ export const TopContentWrapper = styled.div`
`;
export const BottomContentWrapper = styled.div`
padding-left: var(--ads-v2-spaces-7);
padding-bottom: var(--ads-v2-spaces-2);
&[data-isiconpresent="true"] {
padding-left: var(--ads-v2-spaces-7);
}
`;
export const InlineDescriptionWrapper = styled.div`
@ -91,8 +98,14 @@ export const StyledListItem = styled.div<{
gap: var(--ads-v2-spaces-1);
flex-shrink: 0;
flex-direction: column;
max-height: 32px;
${({ size }) => Sizes[size]}
&[data-isblockdescription="true"] {
max-height: 54px;
}
&[data-rightcontrolvisibility="hover"] {
${RightControlWrapper} {
display: none;
@ -128,6 +141,7 @@ export const StyledListItem = styled.div<{
white-space: nowrap;
text-overflow: ellipsis;
flex: 1;
padding-right: var(--ads-v2-spaces-2);
}
& .${ListItemTitleClassName} {

View File

@ -64,7 +64,10 @@ function TextWithTooltip(props: TextProps & { isMultiline?: boolean }) {
return (
<Tooltip content={props.children} isDisabled={disableTooltip}>
<TooltipTextWrapper onMouseOver={handleShowFullText}>
<TooltipTextWrapper
className={`${props.className}-wrapper`}
onMouseOver={handleShowFullText}
>
<Text
{...props}
className={clsx(ListItemTextOverflowClassName, props.className)}
@ -87,8 +90,12 @@ function ListItem(props: ListItemProps) {
startIcon,
title,
} = props;
const isBlockDescription = descriptionType === "block" && description;
const isInlineDescription = descriptionType === "inline" && description;
const isBlockDescription = Boolean(
descriptionType === "block" && description,
);
const isInlineDescription = Boolean(
descriptionType === "inline" && description,
);
const handleOnClick = useEventCallback((e: React.MouseEvent) => {
e.stopPropagation();
@ -114,6 +121,7 @@ function ListItem(props: ListItemProps) {
<StyledListItem
className={clsx(ListItemClassName, props.className)}
data-disabled={props.isDisabled || false}
data-isblockdescription={isBlockDescription}
data-rightcontrolvisibility={rightControlVisibility}
data-selected={props.isSelected}
id={props.id}
@ -152,7 +160,7 @@ function ListItem(props: ListItemProps) {
)}
</TopContentWrapper>
{isBlockDescription && (
<BottomContentWrapper>
<BottomContentWrapper data-isiconpresent={Boolean(startIcon)}>
<TextWithTooltip
className={ListItemBDescClassName}
color="var(--ads-v2-color-fg-muted)"

View File

@ -19,7 +19,12 @@ export default meta;
const EntityGroupTemplate = <T,>(props: EntityGroupProps<T>) => {
const { addConfig, className, groupTitle, items } = props;
return <EntityGroup group={{ addConfig, className, groupTitle, items }} />;
return (
<EntityGroup
group={{ addConfig, className, groupTitle, items }}
visibleItems={5}
/>
);
};
export const SingleGroupWithAddNLazyLoad = EntityGroupTemplate.bind(
@ -83,7 +88,7 @@ SingleGroupWithAddNLazyLoad.args = {
const EntityGroupsListTemplate = <T,>(props: EntityGroupsListProps<T>) => {
const { groups } = props;
return <EntityGroupsList groups={groups} showDivider />;
return <EntityGroupsList groups={groups} showDivider visibleItems={5} />;
};
export const MultipleGroupsWithAddNLazyLoad = EntityGroupsListTemplate.bind(

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo } from "react";
import { LoadMore } from "./EntityGroupsList.styles";
import type {
EntityGroupProps,
@ -9,13 +9,13 @@ import { List, ListItem, type ListItemProps } from "../../../List";
import { Divider } from "../../../Divider";
const EntityGroupsList = <T,>(props: EntityGroupsListProps<T>) => {
const { flexProps, groups, showDivider } = props;
const { flexProps, groups, showDivider, visibleItems } = props;
return (
<Flex flexDirection="column" gap="spaces-4" overflowY="auto" {...flexProps}>
{groups.map((group, index) => (
<Flex flexDirection="column" gap="spaces-3" key={group.groupTitle}>
<EntityGroup group={group} />
<EntityGroup group={group} visibleItems={visibleItems} />
{showDivider && index < groups.length - 1 && (
<Divider
style={{ borderColor: "var(--ads-v2-color-border-muted)" }}
@ -27,8 +27,20 @@ const EntityGroupsList = <T,>(props: EntityGroupsListProps<T>) => {
);
};
const EntityGroup = <T,>({ group }: { group: EntityGroupProps<T> }) => {
const [visibleItemsCount, setVisibleItemsCount] = React.useState(5);
const EntityGroup = <T,>({
group,
visibleItems,
}: {
group: EntityGroupProps<T>;
visibleItems?: number;
}) => {
const [visibleItemsCount, setVisibleItemsCount] = React.useState(
visibleItems || group.items.length,
);
useEffect(() => {
setVisibleItemsCount(visibleItems || group.items.length);
}, [group.items.length, visibleItems]);
const lazyLoading = useMemo(() => {
return {

View File

@ -16,4 +16,5 @@ export interface EntityGroupsListProps<T> {
groups: EntityGroupProps<T>[];
flexProps?: FlexProps;
showDivider?: boolean;
visibleItems?: number;
}

View File

@ -1670,7 +1670,7 @@ export const getQuerySegmentItems = createSelector(
}
return {
icon: ActionUrlIcon(iconUrl),
icon: ActionUrlIcon(iconUrl, "16", "16"),
title: action.config.name,
key: action.config.baseId,
type: action.config.pluginType,

View File

@ -1,9 +1,8 @@
import React, { useState } from "react";
import ReactJson from "react-json-view";
import {
EntityGroupsList,
Flex,
List,
ListItem,
type ListItemProps,
SearchInput,
Text,
@ -51,33 +50,19 @@ export const StateInspector = () => {
value={searchTerm}
/>
</Flex>
<Flex
flexDirection="column"
gap="spaces-3"
overflowY="auto"
pl="spaces-3"
pr="spaces-3"
>
{filteredItemGroups.map((item) => (
<Styled.Group
flexDirection="column"
gap="spaces-2"
key={item.group}
>
<Styled.GroupName
className="overflow-hidden overflow-ellipsis whitespace-nowrap flex-shrink-0"
kind="body-s"
>
{item.group}
</Styled.GroupName>
<List>
{item.items.map((eachItem) => (
<ListItem key={eachItem.title} {...eachItem} />
))}
</List>
</Styled.Group>
))}
</Flex>
<EntityGroupsList
flexProps={{
pl: "spaces-3",
pr: "spaces-3",
}}
groups={filteredItemGroups.map((item) => {
return {
groupTitle: item.group,
items: item.items,
className: "",
};
})}
/>
</Flex>
{selectedItem ? (
<Flex

View File

@ -350,8 +350,8 @@ export function AppsmithAIIcon() {
);
}
export function ActionUrlIcon(url: string) {
return <img src={url} />;
export function ActionUrlIcon(url: string, height?: string, width?: string) {
return <img height={height} src={url} width={width} />;
}
export function DefaultModuleIcon() {

View File

@ -21,6 +21,7 @@ import { getIDEViewMode } from "selectors/ideSelectors";
import type { FlexProps } from "@appsmith/ads";
import { EditorViewMode } from "ee/entities/IDE/constants";
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
import { DEFAULT_GROUP_LIST_SIZE } from "../../constants";
const AddJS = () => {
const dispatch = useDispatch();
@ -98,7 +99,14 @@ const AddJS = () => {
/>
<SearchInput onChange={setSearchTerm} value={searchTerm} />
{filteredItemGroups.length > 0 ? (
<EntityGroupsList groups={filteredItemGroups} showDivider />
<EntityGroupsList
flexProps={{
pb: "spaces-3",
}}
groups={filteredItemGroups}
showDivider
visibleItems={DEFAULT_GROUP_LIST_SIZE}
/>
) : null}
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
<NoSearchResults

View File

@ -18,6 +18,7 @@ import { useSelector } from "react-redux";
import { getIDEViewMode } from "selectors/ideSelectors";
import { EditorViewMode } from "ee/entities/IDE/constants";
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
import { DEFAULT_GROUP_LIST_SIZE } from "../../constants";
const AddQuery = () => {
const [searchTerm, setSearchTerm] = useState("");
@ -59,7 +60,14 @@ const AddQuery = () => {
/>
<SearchInput autoFocus onChange={setSearchTerm} value={searchTerm} />
{filteredItemGroups.length > 0 ? (
<EntityGroupsList groups={filteredItemGroups} showDivider />
<EntityGroupsList
flexProps={{
pb: "spaces-3",
}}
groups={filteredItemGroups}
showDivider
visibleItems={DEFAULT_GROUP_LIST_SIZE}
/>
) : null}
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
<NoSearchResults

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { Flex, List, ListItem, Text } from "@appsmith/ads";
import { EntityGroupsList, Flex } from "@appsmith/ads";
import { useSelector } from "react-redux";
import {
getDatasourceUsageCountForApp,
@ -43,10 +43,6 @@ const DatasourceIcon = styled.img`
width: 16px;
`;
const StyledList = styled(List)`
gap: 0;
`;
interface DataSidePaneProps {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -128,45 +124,33 @@ const DataSidePane = (props: DataSidePaneProps) => {
icon={"datasource-v3"}
/>
) : null}
<Flex
flexDirection={"column"}
gap="spaces-4"
overflowY="auto"
px="spaces-3"
>
{Object.entries(groupedDatasources).map(([key, value]) => (
<Flex flexDirection={"column"} key={key}>
<Flex px="spaces-3" py="spaces-1">
<Text
className="overflow-hidden overflow-ellipsis whitespace-nowrap"
kind="body-s"
>
{key}
</Text>
</Flex>
<StyledList>
{value.map((data) => (
<ListItem
className="t--datasource"
description={get(dsUsageMap, data.id, "")}
descriptionType="block"
isSelected={currentSelectedDatasource === data.id}
key={data.id}
onClick={() => goToDatasource(data.id)}
startIcon={
<DatasourceIcon
src={getAssetUrl(
groupedPlugins[data.pluginId].iconLocation,
)}
/>
}
title={data.name}
/>
))}
</StyledList>
</Flex>
))}
</Flex>
<EntityGroupsList
flexProps={{ px: "spaces-3" }}
groups={Object.entries(groupedDatasources).map(([key, value]) => {
return {
groupTitle: key,
items: value.map((data) => {
return {
id: data.id,
title: data.name,
startIcon: (
<DatasourceIcon
src={getAssetUrl(
groupedPlugins[data.pluginId].iconLocation,
)}
/>
),
description: get(dsUsageMap, data.id, ""),
descriptionType: "block",
className: "t--datasource",
isSelected: currentSelectedDatasource === data.id,
onClick: () => goToDatasource(data.id),
};
}),
className: "",
};
})}
/>
</PaneBody>
</Flex>
);

View File

@ -12,6 +12,7 @@ import { CREATE_A_NEW_ITEM, createMessage } from "ee/constants/messages";
import { useGroupedAddQueryOperations } from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
import { getShowCreateNewModal } from "selectors/ideSelectors";
import { setShowQueryCreateNewModal } from "actions/ideActions";
import { DEFAULT_GROUP_LIST_SIZE } from "../../constants";
const CreateNewQueryModal: React.FC = () => {
const dispatch = useDispatch();
@ -36,7 +37,11 @@ const CreateNewQueryModal: React.FC = () => {
<ModalContent className="!w-[400px] action-creator-create-new-modal">
<ModalHeader>{createMessage(CREATE_A_NEW_ITEM, "query")}</ModalHeader>
<ModalBody>
<EntityGroupsList groups={itemGroups} showDivider />
<EntityGroupsList
groups={itemGroups}
showDivider
visibleItems={DEFAULT_GROUP_LIST_SIZE}
/>
</ModalBody>
</ModalContent>
</Modal>

View File

@ -0,0 +1 @@
export const DEFAULT_GROUP_LIST_SIZE = 5;