mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(ui): implement Confirm Modal (#449)
* feat(ui): implement Confirm Modal add Confirm Modal * fix(ui): remove lock file remove lock filee * fix(ui): confirmModal CR fix confirmModal CR fix
This commit is contained in:
parent
14ac317358
commit
b99c11e211
10 changed files with 123 additions and 14 deletions
|
@ -5,6 +5,8 @@ const translation = {
|
|||
skip: 'Skip',
|
||||
next: 'Next',
|
||||
retry: 'Try again',
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
sign_in: {
|
||||
action: 'Sign In',
|
||||
|
|
|
@ -7,6 +7,8 @@ const translation = {
|
|||
skip: '跳过',
|
||||
next: '下一步',
|
||||
retry: '重试',
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
},
|
||||
sign_in: {
|
||||
action: '登录',
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.15.4",
|
||||
"react-modal": "^3.14.4",
|
||||
"react-phone-number-input": "^3.1.46",
|
||||
"react-router-dom": "^5.2.0"
|
||||
},
|
||||
|
@ -40,6 +41,7 @@
|
|||
"@types/jest": "^27.4.0",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"eslint": "^8.10.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
|
|
|
@ -68,7 +68,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
--color-secondary-background-active: #{$color-neutral-10};
|
||||
--color-secondary-background-disabled: #{$color-neutral-10};
|
||||
--color-border: #{$color-neutral-100};
|
||||
--color-border-active: #{$color-neutral-70};
|
||||
--color-border-secondary: #{$color-neutral-70};
|
||||
--color-border-disabled: #{$color-neutral-30};
|
||||
--color-control-background: #{$color-neutral-10};
|
||||
--color-control-focus: #{$color-primary-tint-60};
|
||||
|
@ -84,7 +84,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
--color-font-divider: #bbb;
|
||||
--color-font-button-text: #{$color-neutral-0};
|
||||
--color-font-button-text-active: #{rgba($color-neutral-0, 0.4)};
|
||||
--color-font-secondary-active: #{$color-neutral-70};
|
||||
--color-font-secondary-dialog: #{$color-neutral-70};
|
||||
--color-font-secondary-disabled: #{rgba($color-neutral-100, 0.4)};
|
||||
--color-font-link: #{$color-primary};
|
||||
--color-font-link-secondary: #{$color-neutral-70};
|
||||
|
@ -108,6 +108,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
--font-heading-2-bold: 600 18px/22px #{$font-family};
|
||||
--font-control: 500 18px/20px #{$font-family};
|
||||
--font-button-text: 600 20px/24px #{$font-family};
|
||||
--font-button-text-small: 500 18px/22px #{$font-family};
|
||||
--font-body: 400 16px/20px #{$font-family};
|
||||
--font-body-bold: 600 16px/20px #{$font-family};
|
||||
--font-body-small: 400 14px/18px #{$font-family};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(10);
|
||||
padding: _.unit(3);
|
||||
border-radius: _.unit(2);
|
||||
font: var(--font-button-text);
|
||||
transition: var(--transition-default-control);
|
||||
|
@ -19,20 +19,19 @@
|
|||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-font-button-text);
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 16%), rgba(0, 0, 0, 16%)), var(--color-primary);
|
||||
color: var(--color-font-button-text-active);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary {
|
||||
border: _.border(var(--color-border));
|
||||
background: var(--color-background);
|
||||
color: var(--color-font-primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border: _.border(var(--color-border-active));
|
||||
color: var(--color-font-secondary-active);
|
||||
.small {
|
||||
font: var(--font-button-text-small);
|
||||
|
||||
&.secondary {
|
||||
border: _.border(var(--color-border-secondary));
|
||||
color: var(--color-font-secondary-dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
htmlType?: 'button' | 'submit' | 'reset';
|
||||
size?: 'large' | 'small';
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
children: string; // TODO: make it i18nKey with optional params
|
||||
children: ReactNode; // TODO: make it i18nKey with optional params
|
||||
type?: 'primary' | 'secondary';
|
||||
onClick?: React.MouseEventHandler;
|
||||
};
|
||||
|
@ -15,6 +16,7 @@ export type Props = {
|
|||
const Button = ({
|
||||
htmlType = 'button',
|
||||
type = 'primary',
|
||||
size = 'large',
|
||||
isDisabled,
|
||||
className,
|
||||
children,
|
||||
|
@ -22,7 +24,7 @@ const Button = ({
|
|||
}: Props) => (
|
||||
<button
|
||||
disabled={isDisabled}
|
||||
className={classNames(styles.button, styles[type], className)}
|
||||
className={classNames(styles.button, styles[type], styles[size], className)}
|
||||
type={htmlType}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
27
packages/ui/src/components/ConfirmModal/index.module.scss
Normal file
27
packages/ui/src/components/ConfirmModal/index.module.scss
Normal file
|
@ -0,0 +1,27 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
background: var(--color-background);
|
||||
padding: _.unit(6);
|
||||
border-radius: _.unit(2);
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
font: var(--font-body);
|
||||
color: var(--color-font-primary);
|
||||
}
|
||||
|
||||
.footer {
|
||||
@include _.flex_row;
|
||||
margin-top: _.unit(6);
|
||||
justify-content: space-between;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
> button:first-child {
|
||||
margin-right: _.unit(2);
|
||||
}
|
||||
}
|
56
packages/ui/src/components/ConfirmModal/index.tsx
Normal file
56
packages/ui/src/components/ConfirmModal/index.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { I18nKey } from '@logto/phrases';
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
classname?: string;
|
||||
isOpen?: boolean;
|
||||
children: ReactNode;
|
||||
cancelText?: I18nKey;
|
||||
confirmText?: I18nKey;
|
||||
onConfirm?: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ConfirmModal = ({
|
||||
classname,
|
||||
isOpen = false,
|
||||
children,
|
||||
cancelText = 'general.cancel',
|
||||
confirmText = 'general.confirm',
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
role="dialog"
|
||||
isOpen={isOpen}
|
||||
className={classNames(modalStyles.modal, classname)}
|
||||
overlayClassName={modalStyles.overlay}
|
||||
parentSelector={() => document.querySelector('main') ?? document.body}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>{children}</div>
|
||||
<div className={styles.footer}>
|
||||
<Button type="secondary" size="small" onClick={onClose}>
|
||||
{t(cancelText)}
|
||||
</Button>
|
||||
<Button size="small" onClick={onConfirm ?? onClose}>
|
||||
{t(confirmText)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
14
packages/ui/src/scss/modal.module.scss
Normal file
14
packages/ui/src/scss/modal.module.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
.modal {
|
||||
position: absolute;
|
||||
left: 40px;
|
||||
right: 40px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 16%);
|
||||
inset: 0;
|
||||
}
|
|
@ -283,6 +283,7 @@ importers:
|
|||
'@types/jest': ^27.4.0
|
||||
'@types/react': ^17.0.14
|
||||
'@types/react-dom': ^17.0.9
|
||||
'@types/react-modal': ^3.13.1
|
||||
'@types/react-router-dom': ^5.3.2
|
||||
classnames: ^2.3.1
|
||||
eslint: ^8.10.0
|
||||
|
@ -301,6 +302,7 @@ importers:
|
|||
react: ^17.0.2
|
||||
react-dom: ^17.0.2
|
||||
react-i18next: ^11.15.4
|
||||
react-modal: ^3.14.4
|
||||
react-phone-number-input: ^3.1.46
|
||||
react-router-dom: ^5.2.0
|
||||
stylelint: ^13.13.1
|
||||
|
@ -317,6 +319,7 @@ importers:
|
|||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
react-i18next: 11.15.4_3fb644aa30122a07f960d67fa51d6dc1
|
||||
react-modal: 3.14.4_react-dom@17.0.2+react@17.0.2
|
||||
react-phone-number-input: 3.1.46_react-dom@17.0.2+react@17.0.2
|
||||
react-router-dom: 5.3.0_react@17.0.2
|
||||
devDependencies:
|
||||
|
@ -331,6 +334,7 @@ importers:
|
|||
'@types/jest': 27.4.0
|
||||
'@types/react': 17.0.37
|
||||
'@types/react-dom': 17.0.11
|
||||
'@types/react-modal': 3.13.1
|
||||
'@types/react-router-dom': 5.3.2
|
||||
eslint: 8.10.0
|
||||
identity-obj-proxy: 3.0.0
|
||||
|
|
Loading…
Reference in a new issue