Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 102 additions & 54 deletions packages/jsonschema-page/src/Fields/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RcListItemSecondaryAction,
RcAvatar,
RcIcon,
RcButton,
RcCard,
RcCardContent,
RcCardActionArea,
Expand Down Expand Up @@ -53,6 +54,7 @@ const StyledItem = styled(RcListItem)<{
$hoverOnMoreMenu?: boolean
$hasActions?: boolean
$readOnly?: boolean
$hideMetaOnActionHover?: boolean
}>`
border-bottom: 1px solid ${palette2('neutral', 'l02')};
cursor: ${({ $readOnly }) => ($readOnly ? 'default' : 'pointer')};
Expand All @@ -61,31 +63,37 @@ const StyledItem = styled(RcListItem)<{
display: none;
}

${({ $hoverOnMoreMenu }) =>
${({ $hoverOnMoreMenu, $hideMetaOnActionHover }) =>
$hoverOnMoreMenu &&
`
.list-item-action-menu {
display: flex;
}

.list-item-meta {
display: none;
}
`}

${({ $hasActions, $readOnly }) =>
$hasActions && !$readOnly &&
`
&:hover {
css`
.list-item-action-menu {
display: flex;
}

.list-item-meta {
display: none;
${$hideMetaOnActionHover &&
css`
.list-item-meta {
display: none;
}
`}
`}

${({ $hasActions, $readOnly, $hideMetaOnActionHover }) =>
$hasActions && !$readOnly &&
css`
&:hover {
.list-item-action-menu {
display: flex;
}

${$hideMetaOnActionHover &&
css`
.list-item-meta {
display: none;
}
`}
}
}
`}
`}
`;

const StyledAvatar = styled(RcAvatar)<{ $round?: boolean }>`
Expand Down Expand Up @@ -118,16 +126,20 @@ const MetaContainer = styled.div`
`;

export const StyledActionMenu = styled(ActionMenu)`
position: absolute;
right: 16px;
top: 50%;
margin-top: -16px;

.RcIconButton-root {
margin-left: 6px;
}
`;

const SecondaryActionContainer = styled.div`
display: flex;
align-items: center;
`;

const StyledActionButton = styled(RcButton)`
margin-left: 8px;
`;

const ICONS_MAP = {
'edit': Edit,
'delete': Delete,
Expand All @@ -149,6 +161,12 @@ const ICONS_MAP = {
'settings': SettingsBorder,
};

const BUTTON_ACTION_TYPE = 'button';
const DEFAULT_ACTION_MENU_MAX_ACTIONS = 3;
const ACTION_MENU_MAX_ACTIONS_WITH_BUTTON = 1;

const isButtonAction = (action) => action?.type === BUTTON_ACTION_TYPE;

function ListItem({
item,
disabled,
Expand All @@ -161,7 +179,9 @@ function ListItem({
readOnly,
}) {
const [hoverOnMoreMenu, setHoverOnMoreMenu] = useState(false);
const formattedActions = actions.map((action) => {
const buttonAction = actions.find(isButtonAction);
const iconActions = actions.filter((action) => !isButtonAction(action));
const formattedActions = iconActions.map((action) => {
const icon = ICONS_MAP[action.icon];
return {
title: action.title,
Expand All @@ -175,6 +195,11 @@ function ListItem({
disabled: action.disabled,
};
});
const actionMenuMaxActions = buttonAction ?
ACTION_MENU_MAX_ACTIONS_WITH_BUTTON :
DEFAULT_ACTION_MENU_MAX_ACTIONS;
const hasSecondaryAction = item.meta || item.authorName || showAsNavigation || buttonAction || iconActions.length > 0;
const hideMetaOnActionHover = iconActions.length > 0 && !buttonAction;
return (
<StyledItem
key={item.const}
Expand All @@ -184,8 +209,9 @@ function ListItem({
canHover={!readOnly}
disableRipple={readOnly}
$hoverOnMoreMenu={hoverOnMoreMenu}
$hasActions={actions.length > 0}
$hasActions={iconActions.length > 0}
$readOnly={readOnly}
$hideMetaOnActionHover={hideMetaOnActionHover}
>
{
item.icon ? (
Expand All @@ -204,38 +230,60 @@ function ListItem({
secondary={item.description}
/>
{
(item.meta || item.authorName || showAsNavigation) ? (
hasSecondaryAction ? (
<RcListItemSecondaryAction>
<MetaContainer className="list-item-meta">
{item.authorName && <span>{item.authorName}</span>}
{item.meta && <span>{item.meta}</span>}
</MetaContainer>
{
showAsNavigation ? (
<NavigationIcon
symbol={ArrowRight}
size="large"
/>
) : null
}
<SecondaryActionContainer>
{
(item.meta || item.authorName) ? (
<MetaContainer className="list-item-meta">
{item.authorName && <span>{item.authorName}</span>}
{item.meta && <span>{item.meta}</span>}
</MetaContainer>
) : null
}
{
showAsNavigation ? (
<NavigationIcon
symbol={ArrowRight}
size="large"
/>
) : null
}
{
buttonAction ? (
<StyledActionButton
size="small"
variant={buttonAction.variant || 'contained'}
color={buttonAction.color || 'primary'}
disabled={buttonAction.disabled}
onClick={(e) => {
e.stopPropagation();
onClickAction(buttonAction);
}}
>
{buttonAction.title}
</StyledActionButton>
) : null
}
{
iconActions.length > 0 && (
<StyledActionMenu
actions={formattedActions}
size="small"
maxActions={actionMenuMaxActions}
className="list-item-action-menu"
iconVariant="contained"
color="neutral.b01"
onMoreMenuOpen={(open) => {
setHoverOnMoreMenu(open);
}}
/>
)
}
</SecondaryActionContainer>
</RcListItemSecondaryAction>
) : null
}
{
actions.length > 0 && (
<StyledActionMenu
actions={formattedActions}
size="small"
maxActions={3}
className="list-item-action-menu"
iconVariant="contained"
color="neutral.b01"
onMoreMenuOpen={(open) => {
setHoverOnMoreMenu(open);
}}
/>
)
}
</StyledItem>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,7 @@ export const ListWithActions: Story = {
icon: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg',
meta: 'Last contacted: 2d ago',
actions: [
{ id: 'invite', title: 'Invite', type: 'button', variant: 'contained', color: 'primary' },
{ id: 'call', title: 'Call', icon: 'phone' },
{ id: 'sms', title: 'SMS', icon: 'sms' },
{ id: 'edit', title: 'Edit', icon: 'edit' },
Expand Down Expand Up @@ -1035,6 +1036,7 @@ export const ListWithActions: Story = {
icon: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-original.svg',
meta: 'Last contacted: 5h ago',
actions: [
{ id: 'followUp', title: 'Follow Up', type: 'button', variant: 'outlined', color: 'secondary' },
{ id: 'call', title: 'Call', icon: 'phone' },
{ id: 'sms', title: 'SMS', icon: 'sms' },
{ id: 'edit', title: 'Edit', icon: 'edit' },
Expand Down Expand Up @@ -1106,6 +1108,7 @@ export const ListWithActions: Story = {
<h4 style={{ margin: '0 0 10px 0', color: '#555' }}>📊Supported icons</h4>
<div style={{ fontSize: '12px', marginBottom: '15px' }}>
<p>call, sms, edit, delete, newAction, info, view, refresh, copy, share, download, people, insertLink, connect, viewLog, read, unread</p>
<p>Button actions use <code>{`{ type: 'button', title, variant, color }`}</code>, stay visible without hover, and limit the icon action menu to 1 visible action.</p>
</div>
<h4 style={{ margin: '0 0 10px 0', color: '#555' }}>📊 Action Activity</h4>
<div style={{ fontSize: '12px', marginBottom: '15px' }}>
Expand Down Expand Up @@ -1140,7 +1143,7 @@ export const ListWithActions: Story = {
formData={formData}
onFormDataChange={setFormData}
onButtonClick={(name: string) => {
// name format: `${item.const}-${action.id}-action` or `${item.const}-author`
// name format: `${action.id}-${item.const}-action` or `${item.const}-author`
const timestamp = new Date().toLocaleTimeString();
setActionHistory(prev => [...prev, `${timestamp}: ${name}`]);
const actionMatch = name.match(/^(.*?)-(.*?)-(action)$/);
Expand Down