chore: Adding Group component in ADS Templates (#38512)

## Description

Adding Group component in ADS Templates to use the same for a single
list of grouped entities in the product.

Fixes [#37615](https://github.com/appsmithorg/appsmith/issues/37615)
[#37616](https://github.com/appsmithorg/appsmith/issues/37616)
[#38288](https://github.com/appsmithorg/appsmith/issues/38288)
[#38287](https://github.com/appsmithorg/appsmith/issues/38287)

## Automation

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

### 🔍 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/12704682977>
> Commit: b62fecb2aeb4f3ebc9a9ed683f5d1d1949a943cc
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12704682977&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 10 Jan 2025 09:11:50 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 component with improved grouping capabilities.
  - Added support for dynamic list rendering with custom children.
  - Introduced `EntityGroupsList` for more flexible group management.
- Added new styled components for better visual representation of lists.
  - Added `LoadMore` styled component for improved UX in entity groups.
- New Storybook examples for `EntityGroupsList` and `EntityGroup`
components.

- **Improvements**
  - Refined List component to support more flexible item rendering.
  - Updated styling for list components.
  - Improved type safety for list-related components.
  - Streamlined code structure for better maintainability.

- **Changes**
  - Removed `GroupedList` component.
  - Updated list rendering across multiple components.
  - Simplified list item management.
  - Expanded public API for `EntityExplorer` module with new exports.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ankita Kinger 2025-01-10 18:34:05 +05:30 committed by GitHub
parent ca265494cf
commit 3d97706da3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 518 additions and 258 deletions

View File

@ -26,39 +26,47 @@ const ListTemplate = (args: ListProps) => {
return <List {...args} />;
};
const items = [
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 1",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 2",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 3",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 4",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 5",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 6",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 7",
},
];
export const ListStory = ListTemplate.bind({}) as StoryObj;
ListStory.storyName = "List";
ListStory.args = {
items: [
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 1",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 2",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 3",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 4",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 5",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 6",
},
{
startIcon: <Icon name="file-list-2-line" size={"md"} />,
title: "Action item 7",
},
],
children: items.map((item) => (
<ListItem
key={`item-${item.title}`}
{...item}
onClick={() => alert("Clicked")}
/>
)),
};
const ListItemArgTypes = {

View File

@ -6,6 +6,8 @@ import {
ListItemTextOverflowClassName,
ListItemTitleClassName,
} from "./List.constants";
import { Flex } from "../Flex";
import { Text } from "../Text";
const Variables = css`
--listitem-title-font-size: var(--ads-v2-font-size-4);
@ -71,7 +73,6 @@ export const StyledList = styled.div`
padding: var(--ads-v2-spaces-1);
display: flex;
flex-direction: column;
gap: var(--ads-v2-spaces-2);
`;
export const StyledListItem = styled.div<{
@ -159,3 +160,22 @@ export const StyledListItem = styled.div<{
padding-right: var(--ads-v2-spaces-2);
}
`;
export const StyledGroup = styled(Flex)`
& .ads-v2-listitem .ads-v2-listitem__idesc {
opacity: 0;
}
& .ads-v2-listitem:hover .ads-v2-listitem__idesc {
opacity: 1;
}
`;
export const GroupedList = styled(StyledList)`
padding: 0;
`;
export const GroupTitle = styled(Text)`
padding: var(--ads-v2-spaces-1) 0;
color: var(--ads-v2-color-fg-muted);
`;

View File

@ -4,8 +4,11 @@ import clsx from "classnames";
import type { ListItemProps, ListProps } from "./List.types";
import {
BottomContentWrapper,
GroupedList,
GroupTitle,
InlineDescriptionWrapper,
RightControlWrapper,
StyledGroup,
StyledList,
StyledListItem,
TooltipTextWrapper,
@ -24,12 +27,15 @@ import {
} from "./List.constants";
import { useEventCallback } from "usehooks-ts";
function List({ className, items, ...rest }: ListProps) {
return (
function List({ children, className, groupTitle, ...rest }: ListProps) {
return groupTitle ? (
<StyledGroup flexDirection="column">
<GroupTitle kind="body-s">{groupTitle}</GroupTitle>
<GroupedList className={className}>{children}</GroupedList>
</StyledGroup>
) : (
<StyledList className={clsx(ListClassName, className)} {...rest}>
{items.map((item) => {
return <ListItem key={item.title} {...item} />;
})}
{children}
</StyledList>
);
}

View File

@ -39,7 +39,8 @@ export interface ListItemProps {
}
export interface ListProps {
items: ListItemProps[];
className?: string;
id?: string;
children: ReactNode | ReactNode[];
groupTitle?: string;
}

View File

@ -0,0 +1,185 @@
/* eslint-disable no-console */
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { EntityGroupsList, EntityGroup } from "./EntityGroupsList";
import type {
EntityGroupsListProps,
EntityGroupProps,
} from "./EntityGroupsList.types";
import { Icon } from "../../../Icon";
const meta: Meta<typeof EntityGroupsList> = {
title: "ADS/Templates/Entity Explorer/Entity Groups List",
component: EntityGroupsList,
};
export default meta;
const EntityGroupTemplate = <T,>(props: EntityGroupProps<T>) => {
const { addConfig, className, groupTitle, items } = props;
return <EntityGroup group={{ addConfig, className, groupTitle, items }} />;
};
export const SingleGroupWithAddNLazyLoad = EntityGroupTemplate.bind(
{},
) as StoryObj;
SingleGroupWithAddNLazyLoad.args = {
groupTitle: "Datasources",
className: "t--from-source-list",
items: [
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-users",
title: "Users",
onClick: () => console.log("Users clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-movies",
title: "Movies",
onClick: () => console.log("Movies clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_1",
title: "Untitled datasource 1",
onClick: () => console.log("Untitled datasource 1 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_2",
title: "Untitled datasource 2",
onClick: () => console.log("Untitled datasource 2 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_3",
title: "Untitled datasource 3",
onClick: () => console.log("Untitled datasource 3 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_4",
title: "Untitled datasource 4",
onClick: () => console.log("Untitled datasource 4 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-users_(1)",
title: "Users (1)",
onClick: () => console.log("Users(1) clicked"),
},
],
addConfig: {
icon: <Icon name="plus" />,
title: "New datasource",
onClick: () => console.log("New datasource clicked"),
},
};
const EntityGroupsListTemplate = <T,>(props: EntityGroupsListProps<T>) => {
const { groups } = props;
return <EntityGroupsList groups={groups} showDivider />;
};
export const MultipleGroupsWithAddNLazyLoad = EntityGroupsListTemplate.bind(
{},
) as StoryObj;
MultipleGroupsWithAddNLazyLoad.args = {
groups: [
{
groupTitle: "Datasources",
className: "t--from-source-list",
items: [
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-users",
title: "Users",
onClick: () => console.log("Users clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-movies",
title: "Movies",
onClick: () => console.log("Movies clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_1",
title: "Untitled datasource 1",
onClick: () => console.log("Untitled datasource 1 clicked"),
},
],
addConfig: {
icon: <Icon name="plus" />,
title: "New datasource",
onClick: () => console.log("New datasource clicked"),
},
},
{
groupTitle: "Apis",
className: "t--from-source-list",
items: [
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-users",
title: "Users",
onClick: () => console.log("Users clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-movies",
title: "Movies",
onClick: () => console.log("Movies clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_1",
title: "Untitled datasource 1",
onClick: () => console.log("Untitled datasource 1 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_2",
title: "Untitled datasource 2",
onClick: () => console.log("Untitled datasource 2 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_3",
title: "Untitled datasource 3",
onClick: () => console.log("Untitled datasource 3 clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-untitled_datasource_4",
title: "Untitled datasource 4",
onClick: () => console.log("Untitled datasource 4 clicked"),
},
],
},
{
groupTitle: "Apis",
className: "t--from-source-list",
items: [
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-users",
title: "Users",
onClick: () => console.log("Users clicked"),
},
{
startIcon: <Icon name="database-2-line" />,
className: "t--datasource-create-option-movies",
title: "Movies",
onClick: () => console.log("Movies clicked"),
},
],
},
],
};

View File

@ -0,0 +1,8 @@
import styled from "styled-components";
import { ListItem } from "../../../List";
export const LoadMore = styled(ListItem)`
.ads-v2-listitem__title {
color: var(--ads-v2-color-fg-subtle);
}
`;

View File

@ -0,0 +1,80 @@
import React, { useMemo } from "react";
import { LoadMore } from "./EntityGroupsList.styles";
import type {
EntityGroupProps,
EntityGroupsListProps,
} from "./EntityGroupsList.types";
import { Flex } from "../../../Flex";
import { List, ListItem, type ListItemProps } from "../../../List";
import { Divider } from "../../../Divider";
const EntityGroupsList = <T,>(props: EntityGroupsListProps<T>) => {
const { flexProps, groups, showDivider } = 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} />
{showDivider && index < groups.length - 1 && (
<Divider
style={{ borderColor: "var(--ads-v2-color-border-muted)" }}
/>
)}
</Flex>
))}
</Flex>
);
};
const EntityGroup = <T,>({ group }: { group: EntityGroupProps<T> }) => {
const [visibleItemsCount, setVisibleItemsCount] = React.useState(5);
const lazyLoading = useMemo(() => {
return {
visibleItemsCount,
hasMore: visibleItemsCount < group.items.length,
handleLoadMore: () => setVisibleItemsCount(group.items.length),
};
}, [visibleItemsCount, group.items.length]);
const updatedGroup = lazyLoading.hasMore
? {
...group,
items: group.items.slice(0, lazyLoading.visibleItemsCount),
}
: group;
return (
<Flex
className="entity-group"
flexDirection={"column"}
key={group.groupTitle}
>
<List className={group.className} groupTitle={group.groupTitle}>
{updatedGroup.items.map((item: T, index) =>
group.renderList ? (
group.renderList(item)
) : (
<ListItem
key={(item as ListItemProps)?.title || `item-${index}`}
{...(item as ListItemProps)}
/>
),
)}
</List>
{lazyLoading?.hasMore && (
<LoadMore onClick={lazyLoading?.handleLoadMore} title="Load more..." />
)}
{group.addConfig && (
<ListItem
onClick={group.addConfig?.onClick}
startIcon={group.addConfig?.icon}
title={group.addConfig?.title}
/>
)}
</Flex>
);
};
export { EntityGroup, EntityGroupsList };

View File

@ -0,0 +1,19 @@
import type { MouseEvent, ReactNode } from "react";
import type { FlexProps } from "../../../Flex";
export interface EntityGroupProps<T> {
groupTitle: string;
className: string;
items: T[];
addConfig?: {
icon: ReactNode;
title: string;
onClick: (e: MouseEvent) => void;
};
renderList?: (item: T) => React.ReactNode;
}
export interface EntityGroupsListProps<T> {
groups: EntityGroupProps<T>[];
flexProps?: FlexProps;
showDivider?: boolean;
}

View File

@ -0,0 +1,5 @@
export { EntityGroup, EntityGroupsList } from "./EntityGroupsList";
export type {
EntityGroupProps,
EntityGroupsListProps,
} from "./EntityGroupsList.types";

View File

@ -1 +1,2 @@
export { EntityItem } from "./EntityItem";
export type { EntityItemProps } from "./EntityItem.types";

View File

@ -7,3 +7,4 @@ export { NoSearchResults } from "./NoSearchResults";
export * from "./ExplorerContainer";
export * from "./EntityItem";
export { useEditableText } from "./Editable";
export * from "./EntityGroupsList";

View File

@ -6,7 +6,7 @@ import { IDEHeaderSwitcher } from "./HeaderSwitcher";
import { noop } from "lodash";
import { Icon } from "../../Icon";
import { Button } from "../../Button";
import { List } from "../../List";
import { List, ListItem } from "../../List";
import { Flex } from "../../Flex";
import { Text } from "../../Text";
import { ListHeaderContainer } from "../EntityExplorer/styles";
@ -58,6 +58,16 @@ export const WithHeaderTitle = () => {
export const WithHeaderDropdown = () => {
const [open, setOpen] = React.useState(false);
const items = [
{
title: "Page1",
onClick: noop,
},
{
title: "Page2",
onClick: noop,
},
];
return (
<IDEHeader>
@ -79,18 +89,11 @@ export const WithHeaderDropdown = () => {
<Text kind="heading-xs">Pages</Text>
<Button isIconButton kind="tertiary" startIcon="plus" />
</ListHeaderContainer>
<List
items={[
{
title: "Page1",
onClick: noop,
},
{
title: "Page2",
onClick: noop,
},
]}
/>
<List>
{items.map((item) => (
<ListItem key={item.title} {...item} />
))}
</List>
</Flex>
</IDEHeaderSwitcher>
</IDEHeader.Left>

View File

@ -28,7 +28,11 @@ import type { UseRoutes } from "ee/entities/IDE/constants";
import type { AppState } from "ee/reducers";
import keyBy from "lodash/keyBy";
import { getPluginEntityIcon } from "pages/Editor/Explorer/ExplorerIcons";
import type { ListItemProps } from "@appsmith/ads";
import {
type EntityGroupProps,
type ListItemProps,
type EntityItemProps,
} from "@appsmith/ads";
import { createAddClassName } from "pages/Editor/IDE/EditorPane/utils";
import { getIDEViewMode } from "selectors/ideSelectors";
import { EditorViewMode } from "ee/entities/IDE/constants";
@ -71,9 +75,10 @@ export type GroupedAddOperations = Array<{
operations: ActionOperation[];
}>;
export const useGroupedAddQueryOperations = (): GroupedAddOperations => {
export const useGroupedAddQueryOperations = () => {
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const pagePermissions = useSelector(getPagePermissions);
const { getListItems } = useAddQueryListItems();
const canCreateActions = getHasCreateActionPermission(
isFeatureEnabled,
@ -91,6 +96,7 @@ export const useGroupedAddQueryOperations = (): GroupedAddOperations => {
);
const groups: GroupedAddOperations = [];
const groupedItems: EntityGroupProps<ListItemProps | EntityItemProps>[] = [];
/** From existing Datasource **/
@ -108,7 +114,35 @@ export const useGroupedAddQueryOperations = (): GroupedAddOperations => {
operations: fromNewBlankAPI,
});
return groups;
groups.map((group) => {
const items = getListItems(group.operations);
const lastItem = items[items.length - 1];
if (group.title === "Datasources" && lastItem?.title === "New datasource") {
items.splice(items.length - 1);
const addConfig = {
icon: lastItem.startIcon,
onClick: lastItem.onClick,
title: lastItem.title,
};
groupedItems.push({
groupTitle: group.title,
className: group.className,
items,
addConfig,
});
} else {
groupedItems.push({
groupTitle: group.title || "",
className: group.className,
items,
});
}
});
return groupedItems;
};
const PluginActionEditor = lazy(async () =>

View File

@ -3,6 +3,7 @@ import ReactJson from "react-json-view";
import {
Flex,
List,
ListItem,
type ListItemProps,
SearchInput,
Text,
@ -69,7 +70,11 @@ export const StateInspector = () => {
>
{item.group}
</Styled.GroupName>
<List items={item.items} />
<List>
{item.items.map((eachItem) => (
<ListItem key={eachItem.title} {...eachItem} />
))}
</List>
</Styled.Group>
))}
</Flex>

View File

@ -6,6 +6,7 @@ import {
PopoverTrigger,
PopoverContent,
List,
ListItem,
} from "@appsmith/ads";
import styles from "./styles.module.css";
import type { DebuggerLog } from "../../types";
@ -17,6 +18,41 @@ export default function HelpDropdown(props: DebuggerLog) {
const errorMessage = args?.[0]?.message;
const items = [
{
startIcon: <Icon name="book" size="md" />,
title: "Documentation",
onClick: () => {
window.open(CUSTOM_WIDGET_DOC_URL, "_blank");
},
},
// {
// startIcon: <Icon name="wand" size="md" />,
// title: "Troubleshoot with AI",
// onClick: noop,
// },
{
startIcon: <Icon name="snippet" size="md" />,
title: createMessage(
CUSTOM_WIDGET_FEATURE.debugger.helpDropdown.stackoverflow,
),
onClick: () => {
args[0] &&
window.open(
`https://stackoverflow.com/search?q=${
"[javascript] " + encodeURIComponent(errorMessage as string)
}}`,
"_blank",
);
},
},
// {
// startIcon: <Icon name="support" size="md" />,
// title: "Appsmith Support",
// onClick: noop,
// },
];
return (
<Popover>
<PopoverTrigger>
@ -28,43 +64,11 @@ export default function HelpDropdown(props: DebuggerLog) {
/>
</PopoverTrigger>
<PopoverContent className={styles.consoleItemHelpContent}>
<List
items={[
{
startIcon: <Icon name="book" size="md" />,
title: "Documentation",
onClick: () => {
window.open(CUSTOM_WIDGET_DOC_URL, "_blank");
},
},
// {
// startIcon: <Icon name="wand" size="md" />,
// title: "Troubleshoot with AI",
// onClick: noop,
// },
{
startIcon: <Icon name="snippet" size="md" />,
title: createMessage(
CUSTOM_WIDGET_FEATURE.debugger.helpDropdown.stackoverflow,
),
onClick: () => {
args[0] &&
window.open(
`https://stackoverflow.com/search?q=${
"[javascript] " +
encodeURIComponent(errorMessage as string)
}}`,
"_blank",
);
},
},
// {
// startIcon: <Icon name="support" size="md" />,
// title: "Appsmith Support",
// onClick: noop,
// },
]}
/>
<List>
{items.map((item) => (
<ListItem key={item.title} {...item} />
))}
</List>
</PopoverContent>
</Popover>
);

View File

@ -2,10 +2,14 @@ import React, { useCallback, useState } from "react";
import SegmentAddHeader from "../components/SegmentAddHeader";
import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages";
import type { ListItemProps } from "@appsmith/ads";
import { Flex, SearchInput, NoSearchResults } from "@appsmith/ads";
import {
EntityGroupsList,
Flex,
SearchInput,
NoSearchResults,
} from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { getCurrentPageId } from "selectors/editorSelectors";
import GroupedList from "../components/GroupedList";
import {
useGroupedAddJsOperations,
useJSAdd,
@ -54,7 +58,7 @@ const AddJS = () => {
const itemGroups = groupedJsOperations.map(
({ className, operations, title }) => ({
groupTitle: title,
groupTitle: title || "",
className: className,
items: operations.map(getListItems),
}),
@ -94,7 +98,7 @@ const AddJS = () => {
/>
<SearchInput onChange={setSearchTerm} value={searchTerm} />
{filteredItemGroups.length > 0 ? (
<GroupedList groups={filteredItemGroups} />
<EntityGroupsList groups={filteredItemGroups} showDivider />
) : null}
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
<NoSearchResults

View File

@ -1,16 +1,16 @@
import React, { useState } from "react";
import {
EntityGroupsList,
Flex,
SearchInput,
NoSearchResults,
type FlexProps,
type ListItemProps,
} from "@appsmith/ads";
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
import SegmentAddHeader from "../components/SegmentAddHeader";
import GroupedList from "../components/GroupedList";
import {
useAddQueryListItems,
useGroupedAddQueryOperations,
useQueryAdd,
} from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
@ -21,21 +21,14 @@ import { filterEntityGroupsBySearchTerm } from "IDE/utils";
const AddQuery = () => {
const [searchTerm, setSearchTerm] = useState("");
const { getListItems } = useAddQueryListItems();
const groupedActionOperations = useGroupedAddQueryOperations();
const itemGroups = useGroupedAddQueryOperations();
const { closeAddQuery } = useQueryAdd();
const ideViewMode = useSelector(getIDEViewMode);
const itemGroups = groupedActionOperations.map((group) => ({
groupTitle: group.title,
className: group.className,
items: getListItems(group.operations),
}));
const filteredItemGroups = filterEntityGroupsBySearchTerm(
searchTerm,
itemGroups,
);
const filteredItemGroups = filterEntityGroupsBySearchTerm<
{ groupTitle: string; className: string },
ListItemProps
>(searchTerm, itemGroups);
const extraPadding: FlexProps =
ideViewMode === EditorViewMode.FullScreen
@ -66,7 +59,7 @@ const AddQuery = () => {
/>
<SearchInput autoFocus onChange={setSearchTerm} value={searchTerm} />
{filteredItemGroups.length > 0 ? (
<GroupedList groups={filteredItemGroups} />
<EntityGroupsList groups={filteredItemGroups} showDivider />
) : null}
{filteredItemGroups.length === 0 && searchTerm !== "" ? (
<NoSearchResults

View File

@ -1,79 +0,0 @@
import React, { useMemo, useState } from "react";
import type { GroupedListProps } from "./types";
import { DEFAULT_GROUP_LIST_SIZE } from "./constants";
import { Flex, List, Text } from "@appsmith/ads";
import styled from "styled-components";
interface GroupProps {
group: GroupedListProps;
}
const StyledList = styled(List)`
padding: 0;
gap: 0;
& .ds-load-more .ads-v2-listitem__title {
--color: var(--ads-v2-color-fg-subtle);
}
& .ads-v2-listitem .ads-v2-listitem__idesc {
opacity: 0;
}
& .ads-v2-listitem:hover .ads-v2-listitem__idesc {
opacity: 1;
}
`;
const Group: React.FC<GroupProps> = ({ group }) => {
const [visibleItemsCount, setVisibleItemsCount] = useState<number>(
DEFAULT_GROUP_LIST_SIZE,
);
const { className, groupTitle, items: groupItems } = group;
const items = useMemo(() => {
const items = groupItems.slice(0, visibleItemsCount);
const hasMoreItems = groupItems.length > visibleItemsCount;
const handleLoadMore = () => {
setVisibleItemsCount(groupItems.length);
};
if (hasMoreItems) {
items.push({
title: "Load more...",
onClick: handleLoadMore,
className: "ds-load-more",
});
}
// TODO: try to avoid this
if (hasMoreItems && groupTitle === "Datasources") {
items.push(groupItems[groupItems.length - 1]);
}
return items;
}, [groupItems, visibleItemsCount, groupTitle]);
return (
<Flex
borderBottom="1px solid var(--ads-v2-color-border-muted)"
className="groups-list-group"
flexDirection="column"
key={groupTitle}
pb="spaces-3"
>
{groupTitle ? (
<Text
className="px-0 py-[var(--ads-v2-spaces-1)]"
color="var(--ads-v2-color-fg-muted)"
kind="body-s"
>
{groupTitle}
</Text>
) : null}
<StyledList className={className} items={items} />
</Flex>
);
};
export { Group };

View File

@ -1,35 +0,0 @@
import React from "react";
import type { FlexProps } from "@appsmith/ads";
import { Flex } from "@appsmith/ads";
import styled from "styled-components";
import type { GroupedListProps } from "./types";
import { Group } from "./Group";
interface Props {
groups: GroupedListProps[];
flexProps?: FlexProps;
}
const StyledFlex = styled(Flex)`
& .groups-list-group:last-child {
border-bottom: none;
}
`;
const GroupedList = (props: Props) => {
return (
<StyledFlex
flex="1"
flexDirection="column"
gap="spaces-4"
overflowY="auto"
{...props.flexProps}
>
{props.groups.map((group) => (
<Group group={group} key={group.groupTitle} />
))}
</StyledFlex>
);
};
export default GroupedList;

View File

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

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { Flex, List, Text } from "@appsmith/ads";
import { Flex, List, ListItem, Text } from "@appsmith/ads";
import { useSelector } from "react-redux";
import {
getDatasourceUsageCountForApp,
@ -144,23 +144,26 @@ const DataSidePane = (props: DataSidePaneProps) => {
{key}
</Text>
</Flex>
<StyledList
items={value.map((data) => ({
className: "t--datasource",
title: data.name,
onClick: () => goToDatasource(data.id),
description: get(dsUsageMap, data.id, ""),
descriptionType: "block",
isSelected: currentSelectedDatasource === data.id,
startIcon: (
<DatasourceIcon
src={getAssetUrl(
groupedPlugins[data.pluginId].iconLocation,
)}
/>
),
}))}
/>
<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>

View File

@ -1,20 +1,21 @@
import React, { useEffect } from "react";
import { Modal, ModalContent, ModalHeader, ModalBody } from "@appsmith/ads";
import {
EntityGroupsList,
Modal,
ModalContent,
ModalHeader,
ModalBody,
} from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { CREATE_A_NEW_ITEM, createMessage } from "ee/constants/messages";
import GroupedList from "pages/Editor/IDE/EditorPane/components/GroupedList";
import {
useAddQueryListItems,
useGroupedAddQueryOperations,
} from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
import { useGroupedAddQueryOperations } from "ee/pages/Editor/IDE/EditorPane/Query/hooks";
import { getShowCreateNewModal } from "selectors/ideSelectors";
import { setShowQueryCreateNewModal } from "actions/ideActions";
const CreateNewQueryModal: React.FC = () => {
const dispatch = useDispatch();
const groupedActionOperations = useGroupedAddQueryOperations();
const { getListItems } = useAddQueryListItems();
const itemGroups = useGroupedAddQueryOperations();
const showCreateNewModal = useSelector(getShowCreateNewModal);
const onCloseHandler = (open: boolean) => {
@ -35,13 +36,7 @@ const CreateNewQueryModal: React.FC = () => {
<ModalContent className="!w-[400px] action-creator-create-new-modal">
<ModalHeader>{createMessage(CREATE_A_NEW_ITEM, "query")}</ModalHeader>
<ModalBody>
<GroupedList
groups={groupedActionOperations.map((group) => ({
groupTitle: group.title,
className: group.className,
items: getListItems(group.operations),
}))}
/>
<EntityGroupsList groups={itemGroups} showDivider />
</ModalBody>
</ModalContent>
</Modal>