init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import Popover from '@mui/material/Popover';
import { listClasses } from '@mui/material/List';
import { menuItemClasses } from '@mui/material/MenuItem';
import { Arrow } from './styles';
import { calculateAnchorOrigin } from './utils';
import type { CustomPopoverProps } from './types';
// ----------------------------------------------------------------------
export function CustomPopover({
open,
onClose,
children,
anchorEl,
slotProps,
...other
}: CustomPopoverProps) {
const { arrow: arrowProps, paper: paperProps, ...otherSlotProps } = slotProps ?? {};
const arrowSize = arrowProps?.size ?? 14;
const arrowOffset = arrowProps?.offset ?? 17;
const arrowPlacement = arrowProps?.placement ?? 'top-right';
const { paperStyles, anchorOrigin, transformOrigin } = calculateAnchorOrigin(arrowPlacement);
return (
<Popover
open={!!open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={anchorOrigin}
transformOrigin={transformOrigin}
slotProps={{
...otherSlotProps,
paper: {
...paperProps,
sx: [
paperStyles,
{
overflow: 'inherit',
[`& .${listClasses.root}`]: { minWidth: 140 },
[`& .${menuItemClasses.root}`]: { gap: 2 },
},
...(Array.isArray(paperProps?.sx) ? (paperProps?.sx ?? []) : [paperProps?.sx]),
],
},
}}
{...other}
>
{!arrowProps?.hide && (
<Arrow
size={arrowSize}
offset={arrowOffset}
placement={arrowPlacement}
sx={arrowProps?.sx}
/>
)}
{children}
</Popover>
);
}

View File

@@ -0,0 +1,3 @@
export * from './custom-popover';
export type * from './types';

View File

@@ -0,0 +1,140 @@
import type { Theme, CSSObject } from '@mui/material/styles';
import { varAlpha } from 'minimal-shared/utils';
import { styled } from '@mui/material/styles';
import type { PopoverArrow } from './types';
// ----------------------------------------------------------------------
const centerStyles: Record<string, CSSObject> = {
hCenter: { left: 0, right: 0, margin: 'auto' },
vCenter: { top: 0, bottom: 0, margin: 'auto' },
};
const getRtlPosition = (position: 'left' | 'right', isRtl: boolean, value: number): CSSObject => ({
[position]: isRtl ? 'auto' : value,
[position === 'left' ? 'right' : 'left']: isRtl ? value : 'auto',
});
const createBackgroundStyles = (theme: Theme, color: 'cyan' | 'red', size: number): CSSObject => {
const colorChannel = theme.vars.palette[color === 'cyan' ? 'info' : 'error'].mainChannel;
return {
backgroundRepeat: 'no-repeat',
backgroundSize: `${size * 3}px ${size * 3}px`,
backgroundColor: theme.vars.palette.background.paper,
backgroundPosition: color === 'cyan' ? 'top right' : 'bottom left',
backgroundImage: `linear-gradient(45deg, ${varAlpha(colorChannel, 0.1)}, ${varAlpha(colorChannel, 0.1)})`,
};
};
const arrowDirection: Record<string, CSSObject> = {
top: { top: 0, rotate: '135deg', translate: '0 -50%' },
bottom: { bottom: 0, rotate: '-45deg', translate: '0 50%' },
left: { rotate: '45deg', translate: '-50% 0' },
right: { rotate: '-135deg', translate: '50% 0' },
};
export const Arrow = styled('span', {
shouldForwardProp: (prop: string) => !['size', 'placement', 'offset', 'sx'].includes(prop),
})<PopoverArrow>(({ offset = 0, size = 0, theme }) => {
const isRtl = theme.direction === 'rtl';
const cyanBackgroundStyles = createBackgroundStyles(theme, 'cyan', size);
const redBackgroundStyles = createBackgroundStyles(theme, 'red', size);
return {
width: size,
height: size,
position: 'absolute',
backdropFilter: '6px',
borderBottomLeftRadius: size / 4,
clipPath: 'polygon(0% 0%, 100% 100%, 0% 100%)',
backgroundColor: theme.vars.palette.background.paper,
border: `solid 1px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.12)}`,
...theme.applyStyles('dark', {
border: `solid 1px ${varAlpha(theme.vars.palette.common.blackChannel, 0.12)}`,
}),
variants: [
/**
* @position top*
*/
{
props: ({ placement }) => placement?.startsWith('top-'),
style: { ...arrowDirection.top },
},
{
props: { placement: 'top-left' },
style: { ...getRtlPosition('left', isRtl, offset) },
},
{
props: { placement: 'top-center' },
style: { ...centerStyles.hCenter },
},
{
props: { placement: 'top-right' },
style: { ...getRtlPosition('right', isRtl, offset), ...cyanBackgroundStyles },
},
/**
* @position bottom*
*/
{
props: ({ placement }) => placement?.startsWith('bottom-'),
style: { ...arrowDirection.bottom },
},
{
props: { placement: 'bottom-left' },
style: { ...getRtlPosition('left', isRtl, offset), ...redBackgroundStyles },
},
{
props: { placement: 'bottom-center' },
style: { ...centerStyles.hCenter },
},
{
props: { placement: 'bottom-right' },
style: { ...getRtlPosition('right', isRtl, offset) },
},
/**
* @position left*
*/
{
props: ({ placement }) => placement?.startsWith('left-'),
style: { ...getRtlPosition('left', isRtl, 0), ...arrowDirection.left },
},
{
props: { placement: 'left-top' },
style: { top: offset },
},
{
props: { placement: 'left-center' },
style: { ...centerStyles.vCenter, ...redBackgroundStyles },
},
{
props: { placement: 'left-bottom' },
style: { ...redBackgroundStyles, bottom: offset },
},
/**
* @position right*
*/
{
props: ({ placement }) => placement?.startsWith('right-'),
style: { ...getRtlPosition('right', isRtl, 0), ...arrowDirection.right },
},
{
props: { placement: 'right-top' },
style: { ...cyanBackgroundStyles, top: offset },
},
{
props: { placement: 'right-center' },
style: { ...centerStyles.vCenter, ...cyanBackgroundStyles },
},
{
props: { placement: 'right-bottom' },
style: { bottom: offset },
},
],
};
});

View File

@@ -0,0 +1,32 @@
import type { PaperProps } from '@mui/material/Paper';
import type { PopoverProps } from '@mui/material/Popover';
import type { Theme, SxProps } from '@mui/material/styles';
// ----------------------------------------------------------------------
export type PopoverArrow = {
hide?: boolean;
size?: number;
offset?: number;
sx?: SxProps<Theme>;
placement?:
| 'top-left'
| 'top-center'
| 'top-right'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right'
| 'left-top'
| 'left-center'
| 'left-bottom'
| 'right-top'
| 'right-center'
| 'right-bottom';
};
export type CustomPopoverProps = PopoverProps & {
slotProps?: PopoverProps['slotProps'] & {
arrow?: PopoverArrow;
paper?: PaperProps;
};
};

View File

@@ -0,0 +1,129 @@
import type { CSSObject } from '@mui/material/styles';
import type { PopoverOrigin } from '@mui/material/Popover';
import type { PopoverArrow } from './types';
// ----------------------------------------------------------------------
const POPOVER_DISTANCE = 0.75;
export type CalculateAnchorOriginProps = {
paperStyles?: CSSObject;
anchorOrigin: PopoverOrigin;
transformOrigin: PopoverOrigin;
};
export function calculateAnchorOrigin(
arrow: PopoverArrow['placement']
): CalculateAnchorOriginProps {
let props: CalculateAnchorOriginProps;
switch (arrow) {
/**
* top-*
*/
case 'top-left':
props = {
paperStyles: { ml: -POPOVER_DISTANCE },
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
transformOrigin: { vertical: 'top', horizontal: 'left' },
};
break;
case 'top-center':
props = {
paperStyles: undefined,
anchorOrigin: { vertical: 'bottom', horizontal: 'center' },
transformOrigin: { vertical: 'top', horizontal: 'center' },
};
break;
case 'top-right':
props = {
paperStyles: { ml: POPOVER_DISTANCE },
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
transformOrigin: { vertical: 'top', horizontal: 'right' },
};
break;
/**
* bottom-*
*/
case 'bottom-left':
props = {
paperStyles: { ml: -POPOVER_DISTANCE },
anchorOrigin: { vertical: 'top', horizontal: 'left' },
transformOrigin: { vertical: 'bottom', horizontal: 'left' },
};
break;
case 'bottom-center':
props = {
paperStyles: undefined,
anchorOrigin: { vertical: 'top', horizontal: 'center' },
transformOrigin: { vertical: 'bottom', horizontal: 'center' },
};
break;
case 'bottom-right':
props = {
paperStyles: { ml: POPOVER_DISTANCE },
anchorOrigin: { vertical: 'top', horizontal: 'right' },
transformOrigin: { vertical: 'bottom', horizontal: 'right' },
};
break;
/**
* left-*
*/
case 'left-top':
props = {
paperStyles: { mt: -POPOVER_DISTANCE },
anchorOrigin: { vertical: 'top', horizontal: 'right' },
transformOrigin: { vertical: 'top', horizontal: 'left' },
};
break;
case 'left-center':
props = {
paperStyles: undefined,
anchorOrigin: { vertical: 'center', horizontal: 'right' },
transformOrigin: { vertical: 'center', horizontal: 'left' },
};
break;
case 'left-bottom':
props = {
paperStyles: { mt: POPOVER_DISTANCE },
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
transformOrigin: { vertical: 'bottom', horizontal: 'left' },
};
break;
/**
* right-*
*/
case 'right-top':
props = {
paperStyles: { mt: -POPOVER_DISTANCE },
anchorOrigin: { vertical: 'top', horizontal: 'left' },
transformOrigin: { vertical: 'top', horizontal: 'right' },
};
break;
case 'right-center':
props = {
paperStyles: undefined,
anchorOrigin: { vertical: 'center', horizontal: 'left' },
transformOrigin: { vertical: 'center', horizontal: 'right' },
};
break;
case 'right-bottom':
props = {
paperStyles: { mt: POPOVER_DISTANCE },
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
transformOrigin: { vertical: 'bottom', horizontal: 'right' },
};
break;
// top-right
default:
props = {
paperStyles: { ml: POPOVER_DISTANCE },
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
transformOrigin: { vertical: 'top', horizontal: 'right' },
};
}
return props;
}