2026-04-05 18:31:16 -07:00
|
|
|
import React, { CSSProperties } from 'react';
|
|
|
|
|
import { useNode, UserComponent } from '@craftjs/core';
|
|
|
|
|
import { cssPropsToString } from '../../utils/style-helpers';
|
2026-07-12 12:05:57 -07:00
|
|
|
import { escapeAttr, safeUrl } from '../../utils/escape';
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
interface IconProps {
|
|
|
|
|
icon?: string;
|
|
|
|
|
size?: string;
|
|
|
|
|
color?: string;
|
|
|
|
|
bgColor?: string;
|
|
|
|
|
bgShape?: 'none' | 'circle' | 'square' | 'rounded';
|
|
|
|
|
bgSize?: string;
|
|
|
|
|
link?: string;
|
|
|
|
|
style?: CSSProperties;
|
2026-07-14 06:47:58 -07:00
|
|
|
animation?: string;
|
|
|
|
|
animationDelay?: string;
|
|
|
|
|
hideOnDesktop?: boolean;
|
|
|
|
|
hideOnTablet?: boolean;
|
|
|
|
|
hideOnMobile?: boolean;
|
2026-04-05 18:31:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getBgBorderRadius(shape: string): string {
|
|
|
|
|
if (shape === 'circle') return '50%';
|
|
|
|
|
if (shape === 'rounded') return '8px';
|
|
|
|
|
if (shape === 'square') return '0px';
|
|
|
|
|
return '0px';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const Icon: UserComponent<IconProps> = ({
|
|
|
|
|
icon = 'fa-star',
|
|
|
|
|
size = '32px',
|
|
|
|
|
color = '#3b82f6',
|
|
|
|
|
bgColor = 'transparent',
|
|
|
|
|
bgShape = 'none',
|
|
|
|
|
bgSize = '56px',
|
|
|
|
|
link = '',
|
|
|
|
|
style = {},
|
|
|
|
|
}) => {
|
|
|
|
|
const {
|
|
|
|
|
connectors: { connect, drag },
|
|
|
|
|
selected,
|
|
|
|
|
} = useNode((node) => ({
|
|
|
|
|
selected: node.events.selected,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const iconEl = (
|
|
|
|
|
<i
|
|
|
|
|
className={`fa ${icon}`}
|
|
|
|
|
style={{ fontSize: size, color, lineHeight: 1 }}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const hasBg = bgShape !== 'none' && bgColor !== 'transparent';
|
|
|
|
|
|
|
|
|
|
const wrapperEl = hasBg ? (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
width: bgSize,
|
|
|
|
|
height: bgSize,
|
|
|
|
|
backgroundColor: bgColor,
|
|
|
|
|
borderRadius: getBgBorderRadius(bgShape || 'none'),
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{iconEl}
|
|
|
|
|
</div>
|
|
|
|
|
) : iconEl;
|
|
|
|
|
|
|
|
|
|
const content = link ? (
|
|
|
|
|
<a href={link} onClick={(e) => e.preventDefault()} style={{ textDecoration: 'none', color: 'inherit' }}>
|
|
|
|
|
{wrapperEl}
|
|
|
|
|
</a>
|
|
|
|
|
) : wrapperEl;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={(ref: HTMLDivElement | null): void => { if (ref) connect(drag(ref)); }}
|
|
|
|
|
style={{
|
|
|
|
|
display: 'inline-block',
|
|
|
|
|
outline: selected ? '2px solid #3b82f6' : 'none',
|
|
|
|
|
...style,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{content}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ---------- Craft config ---------- */
|
|
|
|
|
|
|
|
|
|
Icon.craft = {
|
|
|
|
|
displayName: 'Icon',
|
|
|
|
|
props: {
|
|
|
|
|
icon: 'fa-star',
|
|
|
|
|
size: '32px',
|
|
|
|
|
color: '#3b82f6',
|
|
|
|
|
bgColor: 'transparent',
|
|
|
|
|
bgShape: 'none',
|
|
|
|
|
bgSize: '56px',
|
|
|
|
|
link: '',
|
|
|
|
|
style: {},
|
2026-07-14 06:47:58 -07:00
|
|
|
animation: '',
|
|
|
|
|
animationDelay: '',
|
|
|
|
|
hideOnDesktop: false,
|
|
|
|
|
hideOnTablet: false,
|
|
|
|
|
hideOnMobile: false,
|
2026-04-05 18:31:16 -07:00
|
|
|
},
|
|
|
|
|
rules: {
|
|
|
|
|
canDrag: () => true,
|
|
|
|
|
canMoveIn: () => false,
|
|
|
|
|
canMoveOut: () => true,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ---------- HTML export ---------- */
|
|
|
|
|
|
|
|
|
|
(Icon as any).toHtml = (props: IconProps, _childrenHtml: string) => {
|
|
|
|
|
const {
|
|
|
|
|
icon = 'fa-star',
|
|
|
|
|
size = '32px',
|
|
|
|
|
color = '#3b82f6',
|
|
|
|
|
bgColor = 'transparent',
|
|
|
|
|
bgShape = 'none',
|
|
|
|
|
bgSize = '56px',
|
|
|
|
|
link = '',
|
|
|
|
|
style = {},
|
|
|
|
|
} = props;
|
|
|
|
|
|
|
|
|
|
const iconStyle = cssPropsToString({ fontSize: size, color, lineHeight: '1' });
|
2026-07-12 12:05:57 -07:00
|
|
|
let iconHtml = `<i class="fa ${escapeAttr(icon)}"${iconStyle ? ` style="${iconStyle}"` : ''}></i>`;
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
const hasBg = bgShape !== 'none' && bgColor !== 'transparent';
|
|
|
|
|
if (hasBg) {
|
|
|
|
|
const bgStyle = cssPropsToString({
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
width: bgSize,
|
|
|
|
|
height: bgSize,
|
|
|
|
|
backgroundColor: bgColor,
|
|
|
|
|
borderRadius: getBgBorderRadius(bgShape || 'none'),
|
|
|
|
|
});
|
|
|
|
|
iconHtml = `<div${bgStyle ? ` style="${bgStyle}"` : ''}>${iconHtml}</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (link) {
|
2026-07-12 12:05:57 -07:00
|
|
|
iconHtml = `<a href="${escapeAttr(safeUrl(link))}" style="text-decoration:none;color:inherit">${iconHtml}</a>`;
|
2026-04-05 18:31:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wrapperStyle = cssPropsToString({ display: 'inline-block', ...style });
|
|
|
|
|
return { html: `<div${wrapperStyle ? ` style="${wrapperStyle}"` : ''}>${iconHtml}</div>` };
|
|
|
|
|
};
|