chore: Updating List item and List components in ADS to start using it in Entity explorer (#38344)

This commit is contained in:
Ankita Kinger 2024-12-30 11:07:06 +05:30 committed by GitHub
parent aaabb154f8
commit 2246bde9bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 122 additions and 137 deletions

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { List, ListItem, Icon } from "@appsmith/ads"; import { List, ListItem, Icon, Button } from "@appsmith/ads";
import type { StoryObj } from "@storybook/react"; import type { StoryObj } from "@storybook/react";
import type { ListItemProps, ListProps } from "@appsmith/ads"; import type { ListItemProps, ListProps } from "@appsmith/ads";
@ -79,15 +79,21 @@ const ListItemArgTypes = {
}, },
}, },
}, },
endIcon: { rightControl: {
control: "text", description: "The control to display at the end of the list item",
description: "The icon to display at the end of the list item",
table: { table: {
type: { type: {
summary: "string", summary: "ReactNode",
}, },
}, },
}, },
rightControlVisibility: {
description:
"`always` type will show the right control always. `hover` type will show the right control only when the list item is hovered.",
control: "radio",
options: ["always", "hover"],
defaultValue: "always",
},
description: { description: {
control: "text", control: "text",
description: "Description text to be shown alongside the title", description: "Description text to be shown alongside the title",
@ -140,11 +146,12 @@ const ListItemArgTypes = {
}, },
}, },
}, },
onEndIconClick: { customTitleComponent: {
description: "callback for when the end icon is clicked", description:
"A custom title component for the list item to use input component for name editing",
table: { table: {
type: { type: {
summary: "() => void", summary: "ReactNode",
}, },
}, },
}, },
@ -163,7 +170,7 @@ ListItemLargeStory.args = {
description: "inline", description: "inline",
descriptionType: "inline", descriptionType: "inline",
size: "lg", size: "lg",
endIcon: "add-more", rightControl: <Icon name="add-more" size={"md"} />,
}; };
export const ListItemErrorStory = ListItemTemplate.bind({}) as StoryObj; export const ListItemErrorStory = ListItemTemplate.bind({}) as StoryObj;
@ -215,10 +222,12 @@ ListItemOverflowStory.args = {
title: "Action item 1 Action item 1 Action item 1 Action item 1", title: "Action item 1 Action item 1 Action item 1 Action item 1",
}; };
export const ListItemEndIconStory = ListItemTemplate.bind({}) as StoryObj; export const ListItemRightControlStory = ListItemTemplate.bind({}) as StoryObj;
ListItemEndIconStory.storyName = "List item end icon"; ListItemRightControlStory.storyName = "List item right control";
ListItemEndIconStory.argTypes = ListItemArgTypes; ListItemRightControlStory.argTypes = ListItemArgTypes;
ListItemEndIconStory.args = { ListItemRightControlStory.args = {
title: "Action item 1", title: "Action item 1",
endIcon: "add-more", rightControl: (
<Button isIconButton kind="tertiary" size={"sm"} startIcon="add-more" />
),
}; };

View File

@ -26,47 +26,15 @@ const Sizes = {
`, `,
}; };
export const StyledList = styled.div`
width: 100%;
height: 100%;
overflow: auto;
padding: var(--ads-v2-spaces-1);
display: flex;
flex-direction: column;
gap: var(--ads-v2-spaces-2);
`;
export const Wrapper = styled.div`
display: flex;
width: 100%;
align-items: center;
gap: var(--ads-v2-spaces-3);
cursor: pointer;
box-sizing: border-box;
position: relative;
`;
export const TooltipTextWrapper = styled.div` export const TooltipTextWrapper = styled.div`
display: flex; display: flex;
min-width: 0; min-width: 0;
`; `;
export const ContentWrapper = styled.div`
display: flex;
gap: var(--ads-v2-spaces-3);
`;
export const ContentTextWrapper = styled.div`
display: flex;
gap: var(--ads-v2-spaces-3);
flex: 1;
min-width: 0;
`;
export const DescriptionWrapper = styled.div` export const DescriptionWrapper = styled.div`
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
gap: var(--ads-v2-spaces-3); gap: var(--ads-v2-spaces-2);
display: flex; display: flex;
`; `;
@ -74,34 +42,24 @@ export const InlineDescriptionWrapper = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
min-width: 0;
gap: var(--ads-v2-spaces-3);
flex: 1; flex: 1;
min-width: 0;
`; `;
export const EndIconWrapper = styled.div` export const RightControlWrapper = styled.div`
position: absolute; height: 100%;
right: var(--ads-v2-spaces-3); line-height: normal;
`; `;
export const StyledListItem = styled.div<{ export const ContentTextWrapper = styled.div`
size: ListSizes;
endIcon?: string;
isBlockDescription: boolean;
}>`
${Variables};
display: flex; display: flex;
width: 100%; width: 100%;
align-items: center; align-items: center;
border-radius: var(--ads-v2-border-radius);
padding: var(--ads-v2-spaces-3);
box-sizing: border-box; box-sizing: border-box;
// 40px is the offset to make it look like the end icon is part of this div gap: var(--ads-v2-spaces-3);
${(props) => !!props.endIcon && `padding: 8px 40px 8px 8px;`}} overflow: hidden;
flex: 1;
${({ size }) => Sizes[size]} min-width: 0;
& .${ListItemTextOverflowClassName} { & .${ListItemTextOverflowClassName} {
overflow: hidden; overflow: hidden;
@ -115,7 +73,7 @@ export const StyledListItem = styled.div<{
} }
& .${ListItemBDescClassName} { & .${ListItemBDescClassName} {
-webkit-line-clamp: 2; -webkit-line-clamp: 1;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
text-overflow: initial; text-overflow: initial;
@ -126,14 +84,53 @@ export const StyledListItem = styled.div<{
& .${ListItemIDescClassName} { & .${ListItemIDescClassName} {
font-size: var(--listitem-idescription-font-size); font-size: var(--listitem-idescription-font-size);
line-height: 16px;
padding-right: var(--ads-v2-spaces-2);
}
`;
export const StyledList = styled.div`
width: 100%;
height: 100%;
overflow: auto;
padding: var(--ads-v2-spaces-1);
display: flex;
flex-direction: column;
gap: var(--ads-v2-spaces-2);
`;
export const StyledListItem = styled.div<{
size: ListSizes;
}>`
${Variables};
display: flex;
width: 100%;
align-items: center;
cursor: pointer;
box-sizing: border-box;
position: relative;
border-radius: var(--ads-v2-border-radius);
padding: var(--ads-v2-spaces-2);
padding-left: var(--ads-v2-spaces-3);
${({ size }) => Sizes[size]}
&[data-isblockdescription="true"] {
height: 54px;
} }
&:hover { &[data-isblockdescription="false"] {
background-color: var(--ads-v2-colors-content-surface-hover-bg); height: 32px;
} }
&:active { &[data-rightcontrolvisibility="hover"] {
background-color: var(--ads-v2-colors-content-surface-active-bg); ${RightControlWrapper} {
display: none;
}
&:hover ${RightControlWrapper} {
display: block;
}
} }
&[data-selected="true"] { &[data-selected="true"] {
@ -144,6 +141,15 @@ export const StyledListItem = styled.div<{
&[data-disabled="true"] { &[data-disabled="true"] {
cursor: not-allowed; cursor: not-allowed;
opacity: var(--ads-v2-opacity-disabled); opacity: var(--ads-v2-opacity-disabled);
background-color: var(--ads-v2-colors-content-surface-default-bg);
}
&:hover {
background-color: var(--ads-v2-colors-content-surface-hover-bg);
}
&:active {
background-color: var(--ads-v2-colors-content-surface-active-bg);
} }
/* Focus styles */ /* Focus styles */

View File

@ -5,16 +5,14 @@ import type { ListItemProps, ListProps } from "./List.types";
import { import {
ContentTextWrapper, ContentTextWrapper,
DescriptionWrapper, DescriptionWrapper,
EndIconWrapper,
InlineDescriptionWrapper, InlineDescriptionWrapper,
RightControlWrapper,
StyledList, StyledList,
StyledListItem, StyledListItem,
TooltipTextWrapper, TooltipTextWrapper,
Wrapper,
} from "./List.styles"; } from "./List.styles";
import type { TextProps } from "../Text"; import type { TextProps } from "../Text";
import { Text } from "../Text"; import { Text } from "../Text";
import { Button } from "../Button";
import { Tooltip } from "../Tooltip"; import { Tooltip } from "../Tooltip";
import { import {
ListClassName, ListClassName,
@ -23,7 +21,6 @@ import {
ListItemIDescClassName, ListItemIDescClassName,
ListItemTextOverflowClassName, ListItemTextOverflowClassName,
ListItemTitleClassName, ListItemTitleClassName,
ListItemWrapperClassName,
} from "./List.constants"; } from "./List.constants";
function List({ className, items, ...rest }: ListProps) { function List({ className, items, ...rest }: ListProps) {
@ -85,8 +82,9 @@ function ListItem(props: ListItemProps) {
const { const {
description, description,
descriptionType = "inline", descriptionType = "inline",
endIcon,
hasError, hasError,
rightControl,
rightControlVisibility = "always",
size = "md", size = "md",
startIcon, startIcon,
title, title,
@ -104,27 +102,6 @@ function ListItem(props: ListItemProps) {
} }
}; };
const endIconhandleKeyDown = (e: React.KeyboardEvent) => {
e.stopPropagation();
if (!props.isDisabled && props.onEndIconClick) {
switch (e.key) {
case "Enter":
case " ":
props.onEndIconClick();
break;
}
}
};
const endIconOnClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (!props.isDisabled && props.onEndIconClick) {
props.onEndIconClick();
}
};
const handleOnClick = () => { const handleOnClick = () => {
if (!props.isDisabled && props.onClick) { if (!props.isDisabled && props.onClick) {
props.onClick(); props.onClick();
@ -132,20 +109,23 @@ function ListItem(props: ListItemProps) {
}; };
return ( return (
<Wrapper className={clsx(ListItemWrapperClassName, props.wrapperClassName)}>
<StyledListItem <StyledListItem
className={clsx(ListItemClassName, props.className)} className={clsx(ListItemClassName, props.className)}
data-disabled={props.isDisabled || false} data-disabled={props.isDisabled || false}
data-isblockdescription={isBlockDescription}
data-rightcontrolvisibility={rightControlVisibility}
data-selected={props.isSelected} data-selected={props.isSelected}
endIcon={props.endIcon}
isBlockDescription={isBlockDescription}
onClick={handleOnClick}
onKeyDown={listItemhandleKeyDown}
size={size} size={size}
tabIndex={props.isDisabled ? -1 : 0} tabIndex={props.isDisabled ? -1 : 0}
> >
<ContentTextWrapper> <ContentTextWrapper
onClick={handleOnClick}
onKeyDown={listItemhandleKeyDown}
>
{startIcon} {startIcon}
{props.customTitleComponent ? (
props.customTitleComponent
) : (
<InlineDescriptionWrapper> <InlineDescriptionWrapper>
<DescriptionWrapper> <DescriptionWrapper>
<TextWithTooltip <TextWithTooltip
@ -175,22 +155,12 @@ function ListItem(props: ListItemProps) {
</TextWithTooltip> </TextWithTooltip>
)} )}
</InlineDescriptionWrapper> </InlineDescriptionWrapper>
</ContentTextWrapper>
</StyledListItem>
{endIcon && (
<EndIconWrapper>
<Button
isDisabled={props.isDisabled}
isIconButton
kind="tertiary"
onClick={endIconOnClick}
onKeyDown={endIconhandleKeyDown}
size={"sm"}
startIcon={endIcon}
/>
</EndIconWrapper>
)} )}
</Wrapper> </ContentTextWrapper>
{rightControl && (
<RightControlWrapper>{rightControl}</RightControlWrapper>
)}
</StyledListItem>
); );
} }

View File

@ -6,10 +6,10 @@ export type ListSizes = Extract<Sizes, "md" | "lg">;
export interface ListItemProps { export interface ListItemProps {
/** The icon to display before the list item title. */ /** The icon to display before the list item title. */
startIcon?: ReactNode; startIcon?: ReactNode;
/** The icon to display at the end. Pass name of the icon from remix-icon library(eg: home-2-line) or an svg icon. */ /** The control to display at the end. */
endIcon?: string; rightControl?: ReactNode;
/** callback for when the endIcon is clicked */ /** */
onEndIconClick?: () => void; rightControlVisibility?: "hover" | "always";
/** callback for when the list item is clicked */ /** callback for when the list item is clicked */
onClick: () => void; onClick: () => void;
/** Whether the list item is disabled. */ /** Whether the list item is disabled. */
@ -23,17 +23,17 @@ export interface ListItemProps {
/** The title/label of the list item */ /** The title/label of the list item */
title: string; title: string;
/** Description text to be shown alongside the title */ /** Description text to be shown alongside the title */
description: string; description?: string;
/** `inline` type will show the description beside the title. `block` type will show the description /** `inline` type will show the description beside the title. `block` type will show the description
* below the title. * below the title.
*/ */
descriptionType: "inline" | "block"; descriptionType?: "inline" | "block";
/** class names for the list item */ /** class names for the list item */
className?: string; className?: string;
/** class names for the wrapper */
wrapperClassName?: string;
/** id for the list item */ /** id for the list item */
id?: string; id?: string;
/** customTitleComponent for the list item to use input component for name editing */
customTitleComponent?: ReactNode | ReactNode[];
} }
export interface ListProps { export interface ListProps {

View File

@ -319,7 +319,7 @@ export const useAddQueryListItems = () => {
return { return {
startIcon: icon, startIcon: icon,
wrapperClassName: className, className: className,
title, title,
description: description:
fileOperation.focusEntityType === FocusEntity.QUERY_MODULE_INSTANCE fileOperation.focusEntityType === FocusEntity.QUERY_MODULE_INSTANCE

View File

@ -266,8 +266,8 @@ export { EntityIcon };
// fontSize is set to 56% by default. // fontSize is set to 56% by default.
export function ApiMethodIcon( export function ApiMethodIcon(
type: keyof typeof HTTP_METHOD, type: keyof typeof HTTP_METHOD,
height = "18px", height = "16px",
width = "36px", width = "34px",
fontSize = 52, fontSize = 52,
) { ) {
return ( return (

View File

@ -48,7 +48,7 @@ const AddJS = () => {
: "", : "",
descriptionType: "inline", descriptionType: "inline",
onClick: onCreateItemClick.bind(null, data), onClick: onCreateItemClick.bind(null, data),
wrapperClassName: createAddClassName(title), className: createAddClassName(title),
} as ListItemProps; } as ListItemProps;
}; };

View File

@ -15,11 +15,11 @@ const StyledList = styled(List)`
& .ds-load-more .ads-v2-listitem__title { & .ds-load-more .ads-v2-listitem__title {
--color: var(--ads-v2-color-fg-subtle); --color: var(--ads-v2-color-fg-subtle);
} }
& .ads-v2-listitem__wrapper .ads-v2-listitem__idesc { & .ads-v2-listitem .ads-v2-listitem__idesc {
opacity: 0; opacity: 0;
} }
& .ads-v2-listitem__wrapper:hover .ads-v2-listitem__idesc { & .ads-v2-listitem:hover .ads-v2-listitem__idesc {
opacity: 1; opacity: 1;
} }
`; `;