Skip to content

Commit

Permalink
Add ImageButton component (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
AyIong authored Sep 12, 2024
1 parent 7e607b2 commit 625bada
Show file tree
Hide file tree
Showing 4 changed files with 594 additions and 0 deletions.
230 changes: 230 additions & 0 deletions lib/components/ImageButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/**
* @file
* @copyright 2024 Aylong (https://github.com/AyIong)
* @license MIT
*/

import { Placement } from '@popperjs/core';
import { ReactNode } from 'react';

import { BooleanLike, classes } from '../common/react';
import styles from '../styles/components/ImageButton.module.scss';
import { BoxProps, computeBoxProps } from './Box';
import { DmIcon } from './DmIcon';
import { Icon } from './Icon';
import { Image } from './Image';
import { Stack } from './Stack';
import { Tooltip } from './Tooltip';

type Props = Partial<{
/** Asset cache. Example: `asset={['assetname32x32', thing.key]}` */
asset: string[];
/** Classic way to put images. Example: `base64={thing.image}` */
base64: string;
/**
* Special container for buttons.
* You can put any other component here.
* Has some special stylings!
* Example: `buttons={<Button>Send</Button>}`
*/
buttons: ReactNode;
/**
* Enables alternate layout for `buttons` container.
* Without fluid, buttons will be on top and with `pointer-events: none`, useful for text info.
* With fluid, buttons will be in "hamburger" style.
*/
buttonsAlt: boolean;
/** Content under image. Or on the right if fluid. */
children: ReactNode;
/** Applies a CSS class to the element. */
className: string;
/** Color of the button. See [Button](#button) but without `transparent`. */
color: string;
/** Makes button disabled and dark red if true. Also disables onClick. */
disabled: BooleanLike;
/** Optional. Adds a "stub" when loading DmIcon. */
dmFallback: ReactNode;
/** Parameter `icon` of component `DmIcon`. */
dmIcon: string | null;
/** Parameter `icon_state` of component `DmIcon`. */
dmIconState: string | null;
/**
* Changes the layout of the button, making it fill the entire horizontally available space.
* Allows the use of `title`
*/
fluid: boolean;
/** Parameter responsible for the size of the image, component and standard "stubs". */
imageSize: number;
/** Prop `src` of Image component. Example: `imageSrc={resolveAsset(thing.image}` */
imageSrc: string;
/** Called when button is clicked with LMB. */
onClick: (e: any) => void;
/** Called when button is clicked with RMB. */
onRightClick: (e: any) => void;
/** Makes button selected and green if true. */
selected: BooleanLike;
/** Requires `fluid` for work. Bold text with divider betwen content. */
title: string;
/** A fancy, boxy tooltip, which appears when hovering over the button */
tooltip: ReactNode;
/** Position of the tooltip. See [`Popper`](#Popper) for valid options. */
tooltipPosition: Placement;
}> &
BoxProps;

export function ImageButton(props: Props) {
const {
asset,
base64,
buttons,
buttonsAlt,
children,
className,
color,
disabled,
dmFallback,
dmIcon,
dmIconState,
fluid,
imageSize = 64,
imageSrc,
onClick,
onRightClick,
selected,
title,
tooltip,
tooltipPosition,
...rest
} = props;

function getFallback(iconName: string, iconSpin: boolean) {
return (
<Stack height={`${imageSize}px`} width={`${imageSize}px`}>
<Stack.Item grow textAlign="center" align="center">
<Icon
spin={iconSpin}
name={iconName}
color="gray"
style={{ fontSize: `calc(${imageSize}px * 0.75)` }}
/>
</Stack.Item>
</Stack>
);
}

let buttonContent = (
<div
className={classes([
styles.container,
buttons && styles.hasButtons,
!onClick && !onRightClick && styles.noAction,
selected && styles.selected,
disabled && styles.disabled,
color && typeof color === 'string'
? styles['color__' + color]
: styles['color__default'],
])}
tabIndex={!disabled ? 0 : undefined}
onClick={(event) => {
if (!disabled && onClick) {
onClick(event);
}
}}
onContextMenu={(event) => {
event.preventDefault();
if (!disabled && onRightClick) {
onRightClick(event);
}
}}
style={{ width: !fluid ? `calc(${imageSize}px + 0.5em + 2px)` : 'auto' }}
>
<div className={classes([styles.image])}>
{base64 || asset || imageSrc ? (
<Image
className={classes(!base64 && !imageSrc && asset || [])}
src={base64 ? `data:image/png;base64,${base64}` : imageSrc}
height={`${imageSize}px`}
width={`${imageSize}px`}
/>
) : dmIcon && dmIconState ? (
<DmIcon
icon={dmIcon}
icon_state={dmIconState}
fallback={dmFallback ? dmFallback : getFallback('spinner', true)}
height={`${imageSize}px`}
width={`${imageSize}px`}
/>
) : (
getFallback('question', false)
)}
</div>
{fluid ? (
<div className={classes([styles.info])}>
{title && (
<span
className={classes([styles.title, children && styles.divider])}
>
{title}
</span>
)}
{children && (
<span className={classes([styles.contentFluid])}>{children}</span>
)}
</div>
) : (
children && (
<span
className={classes([
styles.content,
selected && styles.contentSelected,
disabled && styles.contentDisabled,
color && typeof color === 'string'
? styles['contentColor__' + color]
: styles['contentColor__default'],
])}
>
{children}
</span>
)
)}
</div>
);

if (tooltip) {
buttonContent = (
<Tooltip content={tooltip} position={tooltipPosition as Placement}>
{buttonContent}
</Tooltip>
);
}

return (
<div
className={classes([
styles.ImageButton,
fluid && styles.fluid,
className,
])}
{...computeBoxProps(rest)}
>
{buttonContent}
{buttons && (
<div
className={classes([
styles.buttonsContainer,
buttonsAlt && styles.buttonsAltContainer,
!children && styles.buttonsEmpty,
fluid && color && typeof color === 'string'
? styles['buttonsContainerColor__' + color]
: fluid && styles['buttonsContainerColor__default'],
])}
style={{
width: buttonsAlt ? `calc(${imageSize}px + 0.5em)` : 'auto',
}}
>
{buttons}
</div>
)}
</div>
);
}
1 change: 1 addition & 0 deletions lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { FitText } from './FitText';
export { Flex } from './Flex';
export { Icon } from './Icon';
export { Image } from './Image';
export { ImageButton } from './ImageButton';
export { InfinitePlane } from './InfinitePlane';
export { Input } from './Input';
export { KeyListener } from './KeyListener';
Expand Down
Loading

0 comments on commit 625bada

Please sign in to comment.