Skip to content

Commit

Permalink
feat(component): dialog.confirm func
Browse files Browse the repository at this point in the history
  • Loading branch information
CatsJuice committed Apr 24, 2024
1 parent 478a5ba commit 8ca0f06
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 43 deletions.
30 changes: 30 additions & 0 deletions packages/frontend/component/src/ui/dialog/confirm-dialog.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { style } from '@vanilla-extract/css';

export const confirmModalContent = style({
marginTop: '12px',
marginBottom: '20px',
height: '100%',
overflowY: 'auto',
padding: '0 4px',
});
export const confirmModalContainer = style({
display: 'flex',
flexDirection: 'column',
});
export const modalFooter = style({
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
paddingTop: '40px',
marginTop: 'auto',
gap: '20px',
selectors: {
'&.modalFooterWithChildren': {
paddingTop: '20px',
},
'&.reverse': {
flexDirection: 'row-reverse',
justifyContent: 'flex-start',
},
},
});
45 changes: 45 additions & 0 deletions packages/frontend/component/src/ui/dialog/confirm-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DialogTrigger } from '@radix-ui/react-dialog';
import clsx from 'clsx';
import { useCallback } from 'react';

import { Button } from '../button';
import type { ConfirmModalProps } from '../modal';
import * as styles from './confirm-dialog.css';

export const ConfirmModalInner = ({
children,
confirmButtonOptions,
// FIXME: we need i18n
cancelText = 'Cancel',
cancelButtonOptions,
reverseFooter,
onConfirm,
onCancel,
}: ConfirmModalProps) => {
const onConfirmClick = useCallback(() => {
Promise.resolve(onConfirm?.()).catch(err => {
console.error(err);
});
}, [onConfirm]);

return (
<>
{children ? (
<div className={styles.confirmModalContent}>{children}</div>
) : null}
<div
className={clsx(styles.modalFooter, {
modalFooterWithChildren: !!children,
reverse: reverseFooter,
})}
>
<DialogTrigger asChild>
<Button onClick={onCancel} {...cancelButtonOptions}>
{cancelText}
</Button>
</DialogTrigger>
<Button onClick={onConfirmClick} {...confirmButtonOptions}></Button>
</div>
</>
);
};
41 changes: 41 additions & 0 deletions packages/frontend/component/src/ui/dialog/dialog.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,44 @@ const Template = (_: any) => {
};

export const Basic = Template.bind(undefined);

export const ConfirmAsync = () => {
const onConfirm = useCallback(() => {
return new Promise(resolve => setTimeout(() => resolve(1), 2000));
}, []);

const showConfirm = useCallback(() => {
dialog.confirm({
title: 'Confirm',
description: 'Are you sure?',
confirmButtonOptions: {
children: 'Yes',
},
cancelText: 'No',
onConfirm,
});
}, [onConfirm]);

return <Button onClick={showConfirm}>Show Confirm</Button>;
};

export const ConfirmSync = () => {
const onConfirm = useCallback(() => {
console.log('Confirmed');
return null;
}, []);

const showConfirm = useCallback(() => {
dialog.confirm({
title: 'Confirm',
description: 'Are you sure?',
confirmButtonOptions: {
children: 'Yes',
},
cancelText: 'No',
onConfirm,
});
}, [onConfirm]);

return <Button onClick={showConfirm}>Show Confirm</Button>;
};
42 changes: 0 additions & 42 deletions packages/frontend/component/src/ui/dialog/dialog.ts

This file was deleted.

111 changes: 111 additions & 0 deletions packages/frontend/component/src/ui/dialog/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import clsx from 'clsx';

import type { ConfirmModalProps, OpenConfirmModalOptions } from '../modal';
import { ConfirmModalInner } from './confirm-dialog';
import { confirmModalContainer } from './confirm-dialog.css';
import { dialogs$ } from './state';
import type { Dialog, DialogOptions } from './types';

let _internalId = 0;

/**
* Create/Open a dialog
*/
export const dialog = (info: DialogOptions) => {
const id = info.id ?? `internal:${_internalId++}`;
dialogs$.next({
[id]: {
...dialogs$.value[id],
...info,
id,
open: true,
},
});
return id;
};

/**
* Close specific dialog
*/
dialog.close = (id: Dialog['id']) => {
if (id.startsWith('internal:')) {
return dialog.destroy(id);
}
dialogs$.next({
...dialogs$.value,
[id]: { ...dialogs$.value[id], open: false },
});
};

/**
* Destroy specific dialog
*/
dialog.destroy = (id: Dialog['id']) => {
const { [id]: _, ...rest } = dialogs$.value;
dialogs$.next(rest);
};

dialog.update = (id: Dialog['id'], info: Partial<DialogOptions>) => {
const _dialog = dialogs$.value[id];
if (!_dialog) {
throw new Error(`Dialog with id ${id} not found`);
}
dialogs$.next({
...dialogs$.value,
[id]: {
// TODO: merge deeply
..._dialog,
...info,
},
});
};

/**
* Open confirm dialog
* @returns
*/
dialog.confirm = (
props: ConfirmModalProps & { id?: Dialog['id'] },
options?: OpenConfirmModalOptions
) => {
const { autoClose = true, onSuccess } = options ?? {};
const { onConfirm: _onConfirm, width = 480, ...otherProps } = props;

const setLoading = (value: boolean) => {
dialog.confirm({
id,
...props,
confirmButtonOptions: {
...props.confirmButtonOptions,
loading: value,
},
});
};

const onConfirm = () => {
setLoading(true);
return Promise.resolve(_onConfirm?.())
.then(() => onSuccess?.())
.catch(console.error)
.finally(() => autoClose && dialog.close(id));
};

const id = dialog({
...props,
width,
contentOptions: {
...props.contentOptions,
className: clsx(
confirmModalContainer,
props.confirmButtonOptions?.className
),
},
component: (
<ConfirmModalInner onConfirm={onConfirm} {...otherProps}>
{props.children}
</ConfirmModalInner>
),
});

return id;
};
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './dialog';
export * from './dialog-center';
export * from './state';
3 changes: 3 additions & 0 deletions packages/frontend/component/src/ui/dialog/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ import { LiveData } from '@toeverything/infra';
import type { Dialog } from './types';

export const dialogs$ = new LiveData<Record<Dialog['id'], Dialog>>({});
export const openedDialogIds$ = LiveData.computed(get =>
Object.keys(get(dialogs$)).filter(id => get(dialogs$)[id]?.open)
);
2 changes: 1 addition & 1 deletion packages/frontend/component/src/ui/modal/confirm-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const ConfirmModal = ({
);
};

interface OpenConfirmModalOptions {
export interface OpenConfirmModalOptions {
autoClose?: boolean;
onSuccess?: () => void;
}
Expand Down

0 comments on commit 8ca0f06

Please sign in to comment.