208 lines
7.9 KiB
TypeScript
208 lines
7.9 KiB
TypeScript
import { memo, Fragment } from 'react';
|
|
import { Group, Button, Menu, Divider, ActionIcon, Box, ButtonProps, Tooltip, MantineSpacing } from '@mantine/core';
|
|
import { ChevronDown, MoreVertical } from 'lucide-react';
|
|
import { PageActionProps } from './types';
|
|
import { getIntentColor } from './utils';
|
|
|
|
export interface PageActionsProps {
|
|
/** Array of configured page-level actions. */
|
|
actions?: PageActionProps[];
|
|
customButtonProps?: (action: PageActionProps) => ButtonProps;
|
|
gapActionDesktop?: MantineSpacing;
|
|
gapActionMobile?: MantineSpacing;
|
|
}
|
|
|
|
/**
|
|
* A responsive and flexible presentational component for page-level actions.
|
|
* Automatically adapts layout based on screen size.
|
|
*/
|
|
export const PageActions = memo(function PageActions(props: PageActionsProps) {
|
|
const { actions = [], customButtonProps, gapActionDesktop = 'xs', gapActionMobile = 'sm' } = props;
|
|
if (!actions || actions?.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
function defaultButtonStyle(isPremiumGlow: boolean) {
|
|
return {
|
|
size: 'sm',
|
|
radius: 'md',
|
|
fw: 500,
|
|
style: isPremiumGlow
|
|
? { boxShadow: '0 4px 14px 0 color-mix(in srgb, var(--mantine-primary-color-filled) 40%, transparent)' }
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
return (
|
|
<Box style={{ display: 'inline-flex' }}>
|
|
{/* --- DESKTOP VIEW --- */}
|
|
<Group gap={gapActionDesktop} wrap="nowrap" visibleFrom="md">
|
|
{actions.map((action, index) => {
|
|
if (action.type === 'divider') {
|
|
return <Divider key={`divider-${index}`} orientation="vertical" mr="sm" ml="sm" />;
|
|
}
|
|
|
|
const isPremiumGlow = action.intent === 'primary' && action.variant === 'filled';
|
|
const showLabel = action.showLabel !== false;
|
|
const tooltipContent = action.tooltipLabel || (!showLabel ? action.label : undefined);
|
|
|
|
// 1. Button with Dropdown (Menu.Target)
|
|
if (action.children && action.children.length > 0) {
|
|
const ButtonWithDropdown = showLabel ? (
|
|
<Button
|
|
variant={action.variant || 'transparent'}
|
|
color={action?.color ? action.color : getIntentColor(action.intent)}
|
|
leftSection={action.icon}
|
|
rightSection={<ChevronDown size={14} />}
|
|
disabled={action.disabled}
|
|
{...defaultButtonStyle(isPremiumGlow)}
|
|
{...(customButtonProps ? customButtonProps(action) : {})}
|
|
>
|
|
{action.label}
|
|
</Button>
|
|
) : (
|
|
<ActionIcon
|
|
variant={action.variant || 'transparent'}
|
|
color={action?.color ? action.color : getIntentColor(action.intent)}
|
|
disabled={action.disabled}
|
|
size="lg"
|
|
style={isPremiumGlow ? defaultButtonStyle(true).style : undefined}
|
|
>
|
|
{action.icon}
|
|
</ActionIcon>
|
|
);
|
|
|
|
return (
|
|
<Menu key={action.key} position="bottom-start" withArrow withinPortal trigger="hover">
|
|
<Menu.Target>
|
|
{tooltipContent ? (
|
|
<Tooltip position="bottom" label={tooltipContent} withArrow openDelay={500}>
|
|
{ButtonWithDropdown}
|
|
</Tooltip>
|
|
) : (
|
|
ButtonWithDropdown
|
|
)}
|
|
</Menu.Target>
|
|
<Menu.Dropdown>
|
|
{action.children.map((child, childIndex) => {
|
|
if (child.type === 'divider') {
|
|
return <Menu.Divider key={`child-divider-${childIndex}`} />;
|
|
}
|
|
return (
|
|
<Menu.Item
|
|
key={child.key}
|
|
leftSection={child.icon}
|
|
color={action?.color ? action.color : getIntentColor(child.intent)}
|
|
disabled={child.disabled}
|
|
onClick={() => child.onClick?.(child.key || '')}
|
|
>
|
|
{child.label}
|
|
</Menu.Item>
|
|
);
|
|
})}
|
|
</Menu.Dropdown>
|
|
</Menu>
|
|
);
|
|
}
|
|
|
|
// 2. Regular Button (Standalone)
|
|
const StandaloneButton = showLabel ? (
|
|
<Button
|
|
variant={action.variant || 'transparent'}
|
|
color={action?.color ? action.color : getIntentColor(action.intent)}
|
|
leftSection={action.icon}
|
|
disabled={action.disabled}
|
|
onClick={() => action.onClick?.(action.key || '')}
|
|
{...defaultButtonStyle(isPremiumGlow)}
|
|
{...(customButtonProps ? customButtonProps(action) : {})}
|
|
>
|
|
{action.label}
|
|
</Button>
|
|
) : (
|
|
<ActionIcon
|
|
variant={action.variant || 'transparent'}
|
|
color={action?.color ? action.color : getIntentColor(action.intent)}
|
|
disabled={action.disabled}
|
|
onClick={() => action.onClick?.(action.key || '')}
|
|
size="lg"
|
|
style={isPremiumGlow ? defaultButtonStyle(true).style : undefined}
|
|
>
|
|
{action.icon}
|
|
</ActionIcon>
|
|
);
|
|
|
|
return tooltipContent ? (
|
|
<Tooltip position="bottom" key={action.key} label={tooltipContent} withArrow openDelay={500}>
|
|
{StandaloneButton}
|
|
</Tooltip>
|
|
) : (
|
|
<Fragment key={action.key}>{StandaloneButton}</Fragment>
|
|
);
|
|
})}
|
|
</Group>
|
|
|
|
{/* --- MOBILE VIEW --- */}
|
|
<Group gap={gapActionMobile} wrap="nowrap" hiddenFrom="md">
|
|
<Menu position="bottom-end" withArrow withinPortal>
|
|
<Menu.Target>
|
|
<ActionIcon variant="outline" size="md">
|
|
<MoreVertical size={16} />
|
|
</ActionIcon>
|
|
</Menu.Target>
|
|
<Menu.Dropdown px={'sm'} miw={180}>
|
|
{actions.map((action, index) => {
|
|
if (action.type === 'divider') {
|
|
return <Menu.Divider key={`mobile-divider-${index}`} mt="sm" mb="sm" />;
|
|
}
|
|
|
|
if (action.children && action.children.length > 0) {
|
|
return (
|
|
<Fragment key={action.key}>
|
|
<Menu.Label>{action.label}</Menu.Label>
|
|
{action.children.map((child, childIndex) => {
|
|
if (child.type === 'divider') {
|
|
return <Menu.Divider key={`mobile-child-divider-${childIndex}`} mt="xs" mb="xs" />;
|
|
}
|
|
return (
|
|
<Menu.Item
|
|
key={child.key}
|
|
leftSection={child.icon}
|
|
color={action?.color ? action.color : getIntentColor(child.intent)}
|
|
disabled={child.disabled}
|
|
onClick={() => child.onClick?.(child.key || '')}
|
|
style={{ paddingLeft: '1.5rem' }}
|
|
mt="sm"
|
|
mb="sm"
|
|
>
|
|
{child.label}
|
|
</Menu.Item>
|
|
);
|
|
})}
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
const color = action?.color ? action.color : getIntentColor(action.intent);
|
|
const realColor = color === undefined ? 'brand' : color;
|
|
|
|
return (
|
|
<Menu.Item
|
|
key={action.key}
|
|
leftSection={action.icon}
|
|
color={realColor}
|
|
disabled={action.disabled}
|
|
onClick={() => action.onClick?.(action.key || '')}
|
|
mt="sm"
|
|
mb="sm"
|
|
>
|
|
{action.label}
|
|
</Menu.Item>
|
|
);
|
|
})}
|
|
</Menu.Dropdown>
|
|
</Menu>
|
|
</Group>
|
|
</Box>
|
|
);
|
|
});
|