import { bindGlobalStyle, CssProps, RefProps, VNode } from 'lupine.web'; export const ToggleBaseSize = { Small: { w: 30, h: 30 }, Medium: { w: 50, h: 50 }, Large: { w: 70, h: 70 }, }; export type ToggleBaseSizeProps = { w: number | string; h: number | string; }; export type ToggleBaseHookProps = { setChecked?: (checked: boolean) => void; getChecked?: () => boolean; setEnabled?: (enabled: boolean) => void; getEnabled?: () => boolean; }; export type ToggleBaseProps = { size: ToggleBaseSizeProps; disabled?: boolean; checked?: boolean; onClick?: (checked: boolean) => void; hook?: ToggleBaseHookProps; noToggle?: boolean; // if true, it will be like a button children: VNode; className?: string; }; export const ToggleBase = (props: ToggleBaseProps) => { const applyToggle = (checked: boolean, disabled: boolean) => { const childDom = ref.$all('.toggle-base-container .toggle-placeholder'); childDom.forEach((dom: HTMLElement) => { dom.classList.toggle('toggle-on', checked); dom.classList.toggle('toggle-off', !checked); dom.classList.toggle('disabled', disabled); }); }; let disabled = props.disabled || false; const ref: RefProps = { onLoad: async (el: Element) => { applyToggle(props.checked || false, disabled); }, }; const onClick = (e: MouseEvent) => { e.preventDefault(); if (disabled) { return; } const input = ref.$('input.toggle-base-checkbox') as HTMLInputElement; const checked = !input.checked; input.checked = checked; !props.noToggle && applyToggle(checked, disabled); if (props.onClick) { props.onClick(checked); } }; if (props.hook) { props.hook.setChecked = (checked: boolean) => { (ref.$('input.toggle-base-checkbox') as HTMLInputElement).checked = checked; applyToggle(checked, disabled); }; props.hook.getChecked = () => { return (ref.$('input.toggle-base-checkbox') as HTMLInputElement).checked; }; props.hook.setEnabled = (enabled: boolean) => { disabled = !enabled; const dom = ref.$('input.toggle-base-checkbox') as HTMLInputElement; dom.disabled = disabled; applyToggle(dom.checked, disabled); }; props.hook.getEnabled = () => { return !disabled; }; } const css: CssProps = { display: 'inline-block', '.toggle-base-box, .toggle-base-container': { position: 'relative', width: `100%`, height: `100%`, }, '.toggle-base-box': { cursor: 'pointer', userSelect: 'none', '-webkit-tap-highlight-color': 'transparent', touchAction: 'manipulation', }, '.toggle-base-checkbox': { opacity: 0, width: 0, height: 0, position: 'absolute', pointerEvents: 'none', }, }; bindGlobalStyle('toggle-base-component', css); return (
); }; export type ToggleWaveFrameProps = { children: VNode; }; export const ToggleWaveFrame = (props: ToggleWaveFrameProps) => { const css: CssProps = { width: `100%`, height: `100%`, '@keyframes pulse-border': { '0%': { transform: 'scale(0.6)', opacity: 1, }, '100%': { transform: 'scale(1)', opacity: 0, }, }, '.toggle-waves': { position: 'absolute', width: `100%`, height: `100%`, top: '0', left: '0', borderRadius: '50%', backgroundColor: '#eb205580', opacity: 0, animation: 'pulse-border 3s ease-in-out infinite', }, '.toggle-waves-1': { '-webkit-animation-delay': '0s', 'animation-delay': '0s', }, '.toggle-waves-2': { '-webkit-animation-delay': '1s', 'animation-delay': '1s', }, '.toggle-waves-3': { '-webkit-animation-delay': '2s', 'animation-delay': '2s', }, '.toggle-waves-box': { position: 'absolute', width: `100%`, height: `100%`, top: '0', left: '0', padding: `18%`, }, '&.disabled .toggle-waves': { backgroundColor: '#5d578b', }, }; bindGlobalStyle('toggle-waves-box', css); return (
{props.children}
); }; export const TogglePlayButtonSize = { SmallSmall: { w: 30, h: 30 }, Small: { w: 50, h: 50 }, Medium: { w: 70, h: 70 }, Large: { w: 90, h: 90 }, LargeLarge: { w: 120, h: 120 }, }; export type TogglePlayButtonSizeProps = { w: number; h: number; }; export type TogglePlayButtonProps = { size: TogglePlayButtonSizeProps; disabled?: boolean; checked?: boolean; onClick?: (checked: boolean) => void; hook?: ToggleBaseHookProps; textColor?: string; backgroundColor?: string; noWave?: boolean; className?: string; }; export const TogglePlayButton = (props: TogglePlayButtonProps) => { const css: CssProps = { width: `100%`, height: `100%`, borderRadius: '50%', backgroundColor: '#3b29cc', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.3s', cursor: 'pointer', '&:hover': { opacity: 0.8, }, '.play-icon': { width: '50%', height: '50%', transition: 'all 0.2s ease-in-out', backgroundColor: '#fff', }, '&.toggle-off .play-icon': { clipPath: 'polygon(20% 0, 20% 100%, 90% 50%, 90% 50%, 90% 50%, 90% 50%, 90% 50%, 90% 50%, 90% 50%)', translate: '6% 0', }, '&.toggle-on .play-icon': { clipPath: 'polygon(0 0, 0 100%, 33.33% 100%, 33.33% 0, 66.66% 0, 100% 0, 100% 100%, 66.66% 100%, 66.66% 0)', translate: '0 0', }, '&.disabled': { cursor: 'not-allowed', backgroundColor: '#5d578b', }, }; bindGlobalStyle('toggle-play-button-component', css); const Btn = () => (
); return props.noWave ? ( ) : ( ); }; export type ToggleButtonProps = { text: string | VNode; size?: 'button-ss' | 'button-s' | '' | 'button-l' | 'button-ll'; disabled?: boolean; checked?: boolean; onClick?: (checked: boolean) => void; hook?: ToggleBaseHookProps; className?: string; }; export const ToggleButton = (props: ToggleButtonProps) => { const css: CssProps = { display: 'flex', cursor: 'pointer', '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)', '&.disabled': { cursor: 'not-allowed', opacity: 0.6, }, '.button-base': { display: 'flex', alignItems: 'center', gap: '8px', position: 'relative', overflow: 'hidden', transition: 'all 0.15s ease-out', backgroundColor: 'var(--primary-bg-color, #f0f0f0)', border: '1px solid var(--primary-border-color, #e0e0e0)', boxShadow: '4px 4px 8px rgba(0,0,0,0.15), -4px -4px 8px rgba(255,255,255,0.9)', }, '&.toggle-on .button-base, &:active:not(.disabled) .button-base': { boxShadow: 'inset 4px 4px 8px rgba(0,0,0,0.2), inset -4px -4px 8px rgba(255,255,255,0.8)', }, // The Indicator Circle '.icon': { flexShrink: 0, width: '0.8em', height: '0.8em', borderRadius: '50%', // Default Sunken un-lit hole backgroundColor: 'rgba(0,0,0,0.1)', border: '1px solid rgba(0,0,0,0.1)', boxShadow: 'inset 2px 2px 4px rgba(0,0,0,0.3), inset -2px -2px 4px rgba(255,255,255,0.6)', transition: 'all 0.25s ease', }, // The Lit State '&.toggle-on .icon': { backgroundColor: 'var(--primary-accent-color, #1a588a)', boxShadow: '0 0 8px var(--primary-accent-color, #1a588a), inset 1px 1px 2px rgba(255,255,255,0.5)', borderColor: 'rgba(255,255,255,0.2)', }, '.text': { userSelect: 'none', }, }; bindGlobalStyle('toggle-button-component', css); return (
{props.text}
); }; export const ToggleIconSize = { SmallSmall: { w: 22, h: 22 }, Small: { w: 26, h: 26 }, Medium: { w: 32, h: 32 }, Large: { w: 40, h: 40 }, LargeLarge: { w: 48, h: 48 }, Auto: { w: 'fit-content', h: 'fit-content' }, }; export const ToggleTextSize = { SmallSmall: { w: 'fit-content', h: ToggleIconSize.SmallSmall.h, padding: '2px 6px' }, Small: { w: 'fit-content', h: ToggleIconSize.Small.h, padding: '4px 8px' }, Medium: { w: 'fit-content', h: ToggleIconSize.Medium.h, padding: '6px 10px' }, Large: { w: 'fit-content', h: ToggleIconSize.Large.h, padding: '8px 12px' }, LargeLarge: { w: 'fit-content', h: ToggleIconSize.LargeLarge.h, padding: '10px 16px' }, Auto: { w: 'fit-content', h: 'fit-content', padding: '4px 8px' }, }; export type ToggleIconSizeProps = ToggleBaseSizeProps & { padding?: string; }; export type ToggleIconProps = { size: ToggleIconSizeProps; borderRadius?: string; icon: string | VNode; disabled?: boolean; checked?: boolean; onClick?: (checked: boolean) => void; hook?: ToggleBaseHookProps; noToggle?: boolean; // if true, it will be like a button className?: string; }; export const ToggleIcon = (props: ToggleIconProps) => { const css: CssProps = { display: 'flex', cursor: 'pointer', '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)', position: 'relative', // borderRadius: '50%', backgroundColor: 'var(--secondary-bg-color, rgba(0, 0, 0, 0.75))', boxShadow: '-0.15em -0.15em 0.15em -0.075em rgba(5, 5, 5, 0.25), 0.0375em 0.0375em 0.0675em 0 rgba(0, 0, 0, 0.1)', transition: 'all 0.15s ease-out', fontSize: '1rem', height: '100%', width: '100%', '&.disabled': { cursor: 'not-allowed', opacity: 'var(--primary-disabled-opacity, 0.5)', filter: 'grayscale(1)', }, '.button-outer': { width: '100%', height: '100%', borderRadius: 'inherit', transition: 'box-shadow 300ms ease', willChange: 'box-shadow', boxShadow: '0 0 0.05em -0.01em rgba(5, 5, 5, 0.8), 0 0.01em 0.01em -0.01em rgba(5, 5, 5, 0.5), 0.15em 0.3em 0.1em -0.01em rgba(0, 0, 0, 0.25)', }, '.button-inner': { width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 'inherit', background: 'var(--primary-bg-color, linear-gradient(135deg, #ffffff, #eeeeee))', transition: 'box-shadow 300ms ease, background-image 250ms ease, transform 250ms ease', willChange: 'box-shadow, background-image, transform', overflow: 'clip', boxShadow: '0 0 0 0 inset rgba(0, 0, 0, 0.1), -0.05em -0.05em 0.05em 0 inset rgba(0, 0, 0, 0.25), 0 0 0 0 inset rgba(0, 0, 0, 0.1), 0 0 0.05em 0.2em inset rgba(255, 255, 255, 0.25), 0.025em 0.05em 0.1em 0 inset rgba(255, 255, 255, 1), 0.12em 0.12em 0.12em inset rgba(255, 255, 255, 0.25), -0.075em -0.25em 0.25em 0.1em inset rgba(0, 0, 0, 0.25)', }, '.icon': { transition: 'all 0.25s ease', willChange: 'transform', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', }, '.icon > *': { // to work with icon font and Mask SVG Icon width: '1em !important', height: '1em !important', fontSize: 'inherit', }, '&.toggle-on .button-outer, &:active:not(.disabled) .button-outer': { boxShadow: '0 0 0 0 rgba(5, 5, 5, 1), 0 0 0 0 rgba(5, 5, 5, 0.5), 0 0 0 0 rgba(0, 0, 0, 0.25)', }, '&.toggle-on .button-inner, &:active:not(.disabled) .button-inner': { boxShadow: '1px 1px 0.05em 0 inset rgba(0, 0, 0, 0.75), -0.025em -0.03em 0.05em 0.025em inset rgba(0, 0, 0, 0.5), 0.25em 0.25em 0.2em 0 inset rgba(0, 0, 0, 0.5), 0 0 0.05em 0.5em inset rgba(255, 255, 255, 0.15), 0 0 0 0 inset rgba(255, 255, 255, 1), 0.12em 0.12em 0.12em inset rgba(255, 255, 255, 0.25), -0.075em -0.12em 0.2em 0.1em inset rgba(0, 0, 0, 0.25)', }, '&.toggle-on .icon, &:active:not(.disabled) .icon': { transform: 'scale(0.95)', }, '&:hover:not(.disabled) .button-inner': { transform: 'scale(0.95)', }, }; bindGlobalStyle('toggle-icon-component', css); const iconStyle = { fontSize: typeof props.size.h === 'number' ? `${Number((props.size.h * 0.75).toFixed(2))}px` : '1rem', padding: props.size.padding || '4px 12px', }; return (
{props.icon}
); };