import { CssProps, RefProps, VNode, callUnload, mountInnerComponent } from 'lupine.web'; import { backActionHelper } from '../lib'; import { NotificationColor, NotificationMessage } from './notice-message'; export type ActionSheetCloseProps = (reason?: ActionSheetCloseReasonProps) => void; export type ActionSheetCloseReasonProps = 'cancel' | 'confirm' | 'select' | undefined; export type ActionSheetShowProps = { title: string | VNode; children: string | VNode; contentMaxWidth?: string; contentMaxHeight?: string; closeEvent?: (reason?: ActionSheetCloseReasonProps) => void; closeWhenClickOutside?: boolean; // default true confirmButtonText?: string; // no showing if not set handleConfirmClicked?: (close: ActionSheetCloseProps) => void; cancelButtonText?: string; // no showing if not set zIndex?: string; buttonsPosition?: 'bottom' | 'header'; }; // because it's over a mask, so it can use primary colors export class ActionSheet { static hostNode: HTMLElement; static async show({ title, children, contentMaxWidth, contentMaxHeight, closeEvent, closeWhenClickOutside = true, confirmButtonText = '', handleConfirmClicked, cancelButtonText = 'Cancel', zIndex, buttonsPosition = 'bottom', }: ActionSheetShowProps): Promise { const onConfirm = () => { if (handleConfirmClicked) { handleConfirmClicked(handleClose); } else { handleClose('confirm'); } }; const onCancel = () => { handleClose('cancel'); }; const onClickContainer = (event: any) => { if (closeWhenClickOutside !== false && event.target.classList.contains('act-sheet-box')) { handleClose('cancel'); } }; const handleClose = (reason?: ActionSheetCloseReasonProps) => { closeEvent?.(reason); ref.current.classList.remove('animation-open'); setTimeout(async () => { await callUnload(base); base.remove(); }, 300); }; const base = document.createElement('div'); const ref: RefProps = { onLoad: async () => { setTimeout(() => { ref.current.classList.add('animation-open'); }, 1); }, }; const cssContainer: CssProps = { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'var(--cover-mask-bg-color)', '.act-sheet-body': { display: 'flex', flexDirection: 'column', textAlign: 'center', position: 'fixed', bottom: '0px', left: '0px', width: '100%', maxHeight: contentMaxHeight ? contentMaxHeight : '100%', color: 'var(--primary-color)', padding: '8px', transition: 'all 0.3s', transform: 'translateY(100%)', '&.animation-open': { transform: 'translateY(0)', }, '.act-sheet-title': { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', width: '100%', '.t': { padding: '12px 15px 12px 15px' }, '.btn': { display: 'flex', gap: '4px' }, '.act-sheet-header-item': { border: 'none', background: 'transparent', fontSize: '12px', fontWeight: 'bold', cursor: 'pointer', padding: '4px 8px', borderRadius: '6px', }, }, '.act-sheet-content': { display: 'flex', flexDirection: 'column', flex: 1, overflowY: 'auto', borderRadius: '8px', backgroundColor: 'var(--cover-bg-color)', //'#fefefe', width: '100%', maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 90%, 600px)`, margin: '0 auto', }, '.act-sheet-bottom-item, .act-sheet-item': { backgroundColor: 'var(--cover-bg-color)', //'#fefefe', padding: '20px 0', cursor: 'pointer', transition: 'all 0.3s ease', width: '100%', maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 90%, 600px)`, borderTop: '1px solid var(--primary-border-color)', }, '.act-sheet-bottom-item': { borderRadius: '8px', margin: '0 auto', marginTop: '4px', }, '.act-sheet-bottom-item:hover, .act-sheet-item:hover': { fontWeight: 'bold', }, '.act-sheet-confirm, .act-sheet-item': { borderRadius: 'unset', marginTop: 'unset', maxWidth: 'unset', }, '.act-sheet-wrap-container': { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center', padding: '16px', }, '.act-sheet-wrap-item': { cursor: 'pointer', padding: '8px', borderRadius: '8px', border: '1px solid var(--primary-border-color)', margin: '4px', width: 'calc(25% - 8px)', minWidth: '60px', textAlign: 'center', transition: 'all 0.3s ease', }, '.act-sheet-wrap-item:hover': { fontWeight: 'bold', backgroundColor: 'var(--primary-bg-color-hover, rgba(0,0,0,0.04))', }, }, }; const component = (
{title}
{buttonsPosition === 'header' && cancelButtonText && (
{cancelButtonText}
)} {buttonsPosition === 'header' && confirmButtonText && (
{confirmButtonText}
)}
{children} {buttonsPosition === 'bottom' && confirmButtonText && (
{confirmButtonText}
)}
{buttonsPosition === 'bottom' && cancelButtonText && (
{cancelButtonText}
)}
); base.style.position = 'fixed'; base.style.zIndex = zIndex || 'var(--layer-notice)'; document.body.appendChild(base); await mountInnerComponent(base, component); return handleClose; } } export const ActionSheetSelectOptionsProps = { YesNo: ['Yes', 'No'], Ok: ['OK'], }; export type ActionSheetSelectProps = Omit & { options: string[]; handleClicked: (index: number, close: ActionSheetCloseProps) => void; }; export class ActionSheetSelect { static async show({ title, contentMaxHeight, options = ActionSheetSelectOptionsProps.Ok, closeEvent, handleClicked, closeWhenClickOutside = true, confirmButtonText, handleConfirmClicked, cancelButtonText = 'Cancel', }: ActionSheetSelectProps): Promise { const handleClose = await ActionSheet.show({ title, children: (
{options.map((option, index) => (
handleClicked(index, handleClose)}> {option}
))}
), contentMaxHeight, confirmButtonText, handleConfirmClicked, cancelButtonText, closeEvent, closeWhenClickOutside, }); return handleClose; } } export type ActionSheetMessageProps = Omit & { message: string | VNode; closeWhenClickMessage?: boolean; }; export class ActionSheetMessage { static async show({ title, message, contentMaxWidth, contentMaxHeight, closeWhenClickOutside = true, confirmButtonText, handleConfirmClicked, cancelButtonText = '', closeWhenClickMessage = false, }: ActionSheetMessageProps): Promise { const handleClose = await ActionSheet.show({ title, children: (
closeWhenClickMessage && handleClose('select')} > {message}
), contentMaxWidth, contentMaxHeight, confirmButtonText, handleConfirmClicked, cancelButtonText, closeWhenClickOutside, }); return handleClose; } } export type ActionSheetMessagePromiseProps = { message: string | VNode; title: string | VNode; contentMaxWidth?: string; contentMaxHeight?: string; closeWhenClickOutside?: boolean; confirmButtonText?: string; cancelButtonText?: string; zIndex?: string; }; export const ActionSheetMessagePromise = async ({ title, message, contentMaxWidth, contentMaxHeight, closeWhenClickOutside = true, confirmButtonText, cancelButtonText = '', zIndex, }: ActionSheetMessagePromiseProps): Promise => { return new Promise(async (resolve, reject) => { const closeEvent = (reason?: ActionSheetCloseReasonProps) => { resolve(); }; await ActionSheet.show({ title, children:
{message}
, contentMaxWidth, contentMaxHeight, confirmButtonText, cancelButtonText, closeEvent, closeWhenClickOutside, zIndex, }); }); }; export type ActionSheetInputProps = Omit< ActionSheetShowProps, 'children' | 'handleClicked' | 'closeEvent' | 'handleConfirmClicked' > & { defaultValue?: string; handleConfirmValue: (value: string, close: ActionSheetCloseProps) => void; }; export class ActionSheetInput { static async show({ title, defaultValue, contentMaxHeight, closeWhenClickOutside = true, confirmButtonText = 'OK', handleConfirmValue: handleConfirmValue, cancelButtonText = 'Cancel', }: ActionSheetInputProps): Promise { let value: string = defaultValue || ''; const handleClose = await ActionSheet.show({ title, children: (
(value = (e.target as HTMLInputElement).value)} />
), contentMaxHeight, confirmButtonText, handleConfirmClicked: (close) => { handleConfirmValue(value, close); }, cancelButtonText, closeWhenClickOutside, }); return handleClose; } } export type ActionSheetInputPromiseProps = { defaultValue?: string; title: string | VNode; contentMaxWidth?: string; contentMaxHeight?: string; closeWhenClickOutside?: boolean; confirmButtonText?: string; cancelButtonText?: string; zIndex?: string; }; export const ActionSheetInputPromise = async ({ title, defaultValue, contentMaxWidth, contentMaxHeight, closeWhenClickOutside = true, confirmButtonText = 'OK', cancelButtonText = 'Cancel', zIndex, }: ActionSheetInputPromiseProps): Promise => { return new Promise(async (resolve, reject) => { const closeEvent = (reason?: ActionSheetCloseReasonProps) => { if (reason !== 'confirm') { resolve(undefined); } }; let value: string = defaultValue || ''; await ActionSheet.show({ title, children: (
(value = (e.target as HTMLInputElement).value)} />
), contentMaxWidth, contentMaxHeight, confirmButtonText, handleConfirmClicked: (close) => { resolve(value); close('confirm'); }, closeEvent, cancelButtonText, closeWhenClickOutside, zIndex, }); }); }; export type ActionSheetSelectPromiseProps = { title: string | VNode; contentMaxWidth?: string; contentMaxHeight?: string; options?: string[]; closeWhenClickOutside?: boolean; cancelButtonText?: string; zIndex?: string; itemWidth?: string; }; export const ActionSheetSelectPromise = async ({ title, contentMaxWidth, contentMaxHeight, options = ActionSheetSelectOptionsProps.Ok, closeWhenClickOutside = true, cancelButtonText = 'Cancel', zIndex, }: ActionSheetSelectPromiseProps): Promise => { return new Promise(async (resolve, reject) => { const handleClicked = async (index: number, close: ActionSheetCloseProps) => { resolve(index); close('select'); }; const closeEvent = (reason?: ActionSheetCloseReasonProps) => { if (reason !== 'select') { resolve(-1); } }; const handleClose = await ActionSheet.show({ title, children: (
{options.map((option, index) => (
handleClicked(index, handleClose)}> {option}
))}
), contentMaxWidth, contentMaxHeight, cancelButtonText, closeEvent, closeWhenClickOutside, zIndex, }); }); }; export const ActionSheetSelectWrapPromise = async ({ title, contentMaxWidth, contentMaxHeight, options = ActionSheetSelectOptionsProps.Ok, closeWhenClickOutside = true, cancelButtonText = 'Cancel', zIndex, itemWidth, }: ActionSheetSelectPromiseProps): Promise => { return new Promise(async (resolve, reject) => { const handleClicked = async (index: number, close: ActionSheetCloseProps) => { resolve(index); close('select'); }; const closeEvent = (reason?: ActionSheetCloseReasonProps) => { if (reason !== 'select') { resolve(-1); } }; const handleClose = await ActionSheet.show({ title, children: (
{options.map((option, index) => (
handleClicked(index, handleClose)} > {option}
))}
), contentMaxWidth, contentMaxHeight, cancelButtonText, closeEvent, closeWhenClickOutside, zIndex, }); }); }; export type ActionSheetMultiSelectPromiseProps = { title: string | VNode; contentMaxWidth?: string; contentMaxHeight?: string; options: string[]; initialSelected?: number[]; maxCount?: number; align?: 'left' | 'center' | 'right'; closeWhenClickOutside?: boolean; confirmButtonText?: string; cancelButtonText?: string; zIndex?: string; maxCountMessage?: string; }; export const ActionSheetMultiSelectPromise = async ({ title, contentMaxWidth, contentMaxHeight, options, initialSelected = [], maxCount, align = 'center', closeWhenClickOutside = true, confirmButtonText = 'Confirm', cancelButtonText = 'Cancel', zIndex, maxCountMessage = 'Max count reached', }: ActionSheetMultiSelectPromiseProps): Promise => { return new Promise(async (resolve) => { const selected = new Set(initialSelected); const itemRefs: RefProps[] = options.map(() => ({})); const toggleOption = (index: number) => { if (selected.has(index)) { selected.delete(index); if (itemRefs[index].current) { (itemRefs[index].current as HTMLElement).classList.remove('selected'); } } else { if (maxCount !== undefined && selected.size >= maxCount) { NotificationMessage.sendMessage(maxCountMessage, NotificationColor.Error); return; // Do not allow selecting more than maxCount } selected.add(index); if (itemRefs[index].current) { (itemRefs[index].current as HTMLElement).classList.add('selected'); } } }; const closeEvent = (reason?: ActionSheetCloseReasonProps) => { if (reason !== 'confirm') { resolve([...initialSelected]); // Return original if cancelled } }; const alignMap = { left: 'flex-start', center: 'center', right: 'flex-end', }; const css: CssProps = { '.act-sheet-item.row-box': { cursor: 'pointer', justifyContent: alignMap[align], paddingRight: '36px', // reserve space for checkmark paddingLeft: '16px', position: 'relative', }, '.checkmark': { position: 'absolute', right: '16px', color: 'var(--primary-color)', opacity: '0', transition: 'opacity 0.2s', fontSize: '18px', }, '.act-sheet-item.selected .checkmark': { opacity: '1', }, '.act-sheet-item.selected': { color: 'var(--primary-color)', fontWeight: 'bold', backgroundColor: 'var(--primary-bg-color-hover, rgba(0,0,0,0.04))', }, }; const handleClose = await ActionSheet.show({ title, children: (
{options.map((option, index) => (
toggleOption(index)} > {option}
))}
), contentMaxWidth, contentMaxHeight, confirmButtonText, handleConfirmClicked: (close) => { resolve(Array.from(selected).sort((a, b) => a - b)); close('confirm'); }, cancelButtonText, closeEvent, closeWhenClickOutside, zIndex, buttonsPosition: 'header', }); }); };