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:
parent
ca265494cf
commit
3d97706da3
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ export interface ListItemProps {
|
|||
}
|
||||
|
||||
export interface ListProps {
|
||||
items: ListItemProps[];
|
||||
className?: string;
|
||||
id?: string;
|
||||
children: ReactNode | ReactNode[];
|
||||
groupTitle?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
`;
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export { EntityGroup, EntityGroupsList } from "./EntityGroupsList";
|
||||
export type {
|
||||
EntityGroupProps,
|
||||
EntityGroupsListProps,
|
||||
} from "./EntityGroupsList.types";
|
||||
|
|
@ -1 +1,2 @@
|
|||
export { EntityItem } from "./EntityItem";
|
||||
export type { EntityItemProps } from "./EntityItem.types";
|
||||
|
|
|
|||
|
|
@ -7,3 +7,4 @@ export { NoSearchResults } from "./NoSearchResults";
|
|||
export * from "./ExplorerContainer";
|
||||
export * from "./EntityItem";
|
||||
export { useEditableText } from "./Editable";
|
||||
export * from "./EntityGroupsList";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 () =>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
@ -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;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export const DEFAULT_GROUP_LIST_SIZE = 5;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user