From 2e0f2ec620bce242709acbeea0bff060abc848b4 Mon Sep 17 00:00:00 2001 From: Simon Guo Date: Fri, 2 Feb 2024 14:34:36 +0800 Subject: [PATCH] feat(Message): add support for `bordered` and `centered` props on Message (#3605) * feat(Message): add support for `bordered` and `centered` props on Message * fix: add version --- docs/pages/components/message/en-US/index.md | 12 +++ .../components/message/fragments/bordered.md | 26 +++++ .../components/message/fragments/centered.md | 21 ++++ .../components/message/fragments/header.md | 8 +- docs/pages/components/message/zh-CN/index.md | 12 +++ src/Message/Message.tsx | 98 +++++++++++++------ src/Message/stories/Message.stories.tsx | 29 ++++++ src/Message/styles/index.less | 37 ++++++- src/Message/styles/mixin.less | 15 ++- src/Message/test/MessageSpec.tsx | 12 +++ src/styles/color-modes/dark.less | 4 + src/styles/color-modes/light.less | 8 ++ src/utils/statusIcons.tsx | 4 +- 13 files changed, 245 insertions(+), 41 deletions(-) create mode 100644 docs/pages/components/message/fragments/bordered.md create mode 100644 docs/pages/components/message/fragments/centered.md diff --git a/docs/pages/components/message/en-US/index.md b/docs/pages/components/message/en-US/index.md index 97c73208d..2869fb8d3 100644 --- a/docs/pages/components/message/en-US/index.md +++ b/docs/pages/components/message/en-US/index.md @@ -24,6 +24,14 @@ Used to show important tips on a page. +### Bordered + + + +### Centered + + + ### Closable @@ -52,8 +60,12 @@ No keyboard interaction needed. ### `` + + | Property | Type `(Default)` | Description | | ----------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| bordered | boolean | Show a border around the message box.
![](https://img.shields.io/badge/min-v5.53.0-blue) | +| centered | boolean | Center the message vertically.
![](https://img.shields.io/badge/min-v5.53.0-blue) | | children | ReactNode | The description information for the message. | | classPrefix | string `('message')` | The prefix of the component CSS class. | | closable | boolean | Whether it is possible to close the message box | diff --git a/docs/pages/components/message/fragments/bordered.md b/docs/pages/components/message/fragments/bordered.md new file mode 100644 index 000000000..e5d510e91 --- /dev/null +++ b/docs/pages/components/message/fragments/bordered.md @@ -0,0 +1,26 @@ + + +```js +import { Message } from 'rsuite'; + +const App = () => ( + <> + + Info! You can use the `Message` component to display a info message. + + + Success! You can use the `Message` component to display a success message. + + + Warning! You can use the `Message` component to display a warning message. + + + Error! You can use the `Message` component to display a error message. + + +); + +ReactDOM.render(, document.getElementById('root')); +``` + + diff --git a/docs/pages/components/message/fragments/centered.md b/docs/pages/components/message/fragments/centered.md new file mode 100644 index 000000000..8ced14d61 --- /dev/null +++ b/docs/pages/components/message/fragments/centered.md @@ -0,0 +1,21 @@ + + +```js +import { Message } from 'rsuite'; + +const App = () => ( + +

+ Your application has been successfully submitted, and we will process it within 1-3 working + days. +

+

+ You can check the application status in the application record. +

+
+); + +ReactDOM.render(, document.getElementById('root')); +``` + + diff --git a/docs/pages/components/message/fragments/header.md b/docs/pages/components/message/fragments/header.md index 5290a667f..b1504de85 100644 --- a/docs/pages/components/message/fragments/header.md +++ b/docs/pages/components/message/fragments/header.md @@ -5,11 +5,11 @@ import { Message, Button, Divider } from 'rsuite'; const App = () => ( <> - Cannot delete the file}> + We are sorry, the file cannot be deleted. Please try again later. - A problem occurred}> +
  1. Please check your network connection.
  2. Please check the file permissions.
  3. @@ -18,8 +18,8 @@ const App = () => (
- Do you want to allow notifications?}> - We can let you know when new messages arrive. + +

We can let you know when new messages arrive.


diff --git a/docs/pages/components/message/zh-CN/index.md b/docs/pages/components/message/zh-CN/index.md index b1bbaa7a8..04b97f4d1 100644 --- a/docs/pages/components/message/zh-CN/index.md +++ b/docs/pages/components/message/zh-CN/index.md @@ -24,6 +24,14 @@ +### 带边框 + + + +### 垂直居中 + + + ### 可关闭的 @@ -52,8 +60,12 @@ Message 的 `role` 为 `alert`。 ### `` + + | 属性名称 | 类型 `(默认值)` | 描述 | | ----------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| bordered | boolean | 显示消息框边框
![](https://img.shields.io/badge/min-v5.53.0-blue) | +| centered | boolean | 垂直居中消息框
![](https://img.shields.io/badge/min-v5.53.0-blue) | | children | ReactNode | 消息描述信息 | | classPrefix | string `('message')` | 组件 CSS 类的前缀 | | closable | boolean | 可以关闭消息框 | diff --git a/src/Message/Message.tsx b/src/Message/Message.tsx index a7bdcfc80..bf655c7b6 100644 --- a/src/Message/Message.tsx +++ b/src/Message/Message.tsx @@ -1,16 +1,39 @@ -import React, { useState, useCallback, useContext } from 'react'; +import React, { useState, useContext } from 'react'; import PropTypes from 'prop-types'; -import { useClassNames, useTimeout, MESSAGE_STATUS_ICONS, STATUS, useIsMounted } from '../utils'; +import { + useClassNames, + useTimeout, + MESSAGE_STATUS_ICONS, + STATUS, + useIsMounted, + useEventCallback +} from '../utils'; import { WithAsProps, TypeAttributes, RsRefForwardingComponent } from '../@types/common'; import { oneOf } from '../internals/propTypes'; import CloseButton from '../internals/CloseButton'; import ToastContext from '../toaster/ToastContext'; export interface MessageProps extends WithAsProps { - /** The type of the message box. */ + /** + * The type of the message box. + */ type?: TypeAttributes.Status; - /** Whether it is possible to close the message box */ + /** + * Show a border around the message box. + * @version 5.53.0 + */ + bordered?: boolean; + + /** + * Center the message vertically. + * @version 5.53.0 + */ + centered?: boolean; + + /** + * Whether it is possible to close the message box + */ closable?: boolean; /** @@ -24,16 +47,24 @@ export interface MessageProps extends WithAsProps { */ duration?: number; - /** The title of the message */ + /** + * The title of the message + */ header?: React.ReactNode; - /** Whether to display an icon */ + /** + * Whether to display an icon + */ showIcon?: boolean; - /** Fill the container */ + /** + * Fill the container + */ full?: boolean; - /** Callback after the message is removed */ + /** + * Callback after the message is removed + */ onClose?: (event?: React.MouseEvent) => void; } @@ -47,6 +78,8 @@ const Message: RsRefForwardingComponent<'div', MessageProps> = React.forwardRef( (props: MessageProps, ref) => { const { as: Component = 'div', + bordered, + centered, className, classPrefix = 'message', children, @@ -68,20 +101,17 @@ const Message: RsRefForwardingComponent<'div', MessageProps> = React.forwardRef( // Timed close message const { clear } = useTimeout(onClose, duration, usedToaster && duration > 0); - const handleClose = useCallback( - (event: React.MouseEvent) => { - setDisplay('hiding'); - onClose?.(event); - clear(); - - setTimeout(() => { - if (isMounted()) { - setDisplay('hide'); - } - }, 1000); - }, - [clear, isMounted, onClose] - ); + const handleClose = useEventCallback((event: React.MouseEvent) => { + setDisplay('hiding'); + onClose?.(event); + clear(); + + setTimeout(() => { + if (isMounted()) { + setDisplay('hide'); + } + }, 1000); + }); if (display === 'hide') { return null; @@ -89,14 +119,20 @@ const Message: RsRefForwardingComponent<'div', MessageProps> = React.forwardRef( const classes = merge( className, - withClassPrefix(type, display, { full, ['has-title']: header, ['has-icon']: showIcon }) + withClassPrefix(type, display, { + full, + bordered, + centered, + ['has-title']: header, + ['has-icon']: showIcon + }) ); return (
{closable && } - {showIcon &&
{MESSAGE_STATUS_ICONS[type]}
} + {showIcon &&
{MESSAGE_STATUS_ICONS[type]}
}
{header &&
{header}
} {children &&
{children}
} @@ -109,15 +145,17 @@ const Message: RsRefForwardingComponent<'div', MessageProps> = React.forwardRef( Message.displayName = 'Message'; Message.propTypes = { - type: oneOf(STATUS), - className: PropTypes.string, - onClose: PropTypes.func, + bordered: PropTypes.bool, + centered: PropTypes.bool, closable: PropTypes.bool, - title: PropTypes.node, + className: PropTypes.string, + classPrefix: PropTypes.string, description: PropTypes.node, - showIcon: PropTypes.bool, full: PropTypes.bool, - classPrefix: PropTypes.string + onClose: PropTypes.func, + showIcon: PropTypes.bool, + title: PropTypes.node, + type: oneOf(STATUS) }; export default Message; diff --git a/src/Message/stories/Message.stories.tsx b/src/Message/stories/Message.stories.tsx index 7ea0c6d2c..521728a18 100644 --- a/src/Message/stories/Message.stories.tsx +++ b/src/Message/stories/Message.stories.tsx @@ -87,6 +87,35 @@ export const Closable: Story = { } }; +export const Bordered: Story = { + args: { + ...defaultArgs, + showIcon: true, + bordered: true + } +}; + +export const Centered: Story = { + args: { + ...defaultArgs, + showIcon: true, + centered: true, + type: 'success', + header: 'Application has been accepted !', + children: ( + <> +

+ Your application has been successfully submitted, and we will process it within 1-3 + working days. +

+

+ You can check the application status in the application record. +

+ + ) + } +}; + export const WithToaster: Story = { render: props => ( diff --git a/src/Message/styles/index.less b/src/Message/styles/index.less index 744014cba..02273e49e 100644 --- a/src/Message/styles/index.less +++ b/src/Message/styles/index.less @@ -24,8 +24,30 @@ display: flex; } - // Icon wrapper - &-icon-wrapper { + &-centered { + .rs-message-container { + flex-direction: column; + align-items: center; + text-align: center; + gap: 20px; + } + + .rs-message-icon { + align-self: center !important; + + .rs-icon { + font-size: 40px !important; + } + } + + &.rs-message-bordered { + border-left-width: 1px !important; + border-top-width: 4px !important; + } + } + + // Message icon + &-icon { align-self: center; font-size: 0; // remove whitespace before svg margin-right: 10px; @@ -45,6 +67,7 @@ &-header { color: var(--rs-text-heading); line-height: unit((round((@message-icon-large-size / @message-title-size), 16))); + font-weight: bold; .ellipsis(); & + .rs-message-body { @@ -66,7 +89,7 @@ // Message with a title &.rs-message-has-title { // Bigger icon - .rs-message-icon-wrapper { + .rs-message-icon { align-self: flex-start; .rs-icon { @@ -125,6 +148,8 @@ var(--rs-message-success-header); var(--rs-message-success-text); var(--rs-message-success-icon); + var(--rs-message-success-border); + var(--rs-message-success-icon-border); ); .high-contrast-mode({ @@ -138,6 +163,8 @@ var(--rs-message-info-header); var(--rs-message-info-text); var(--rs-message-info-icon); + var(--rs-message-info-border); + var(--rs-message-info-icon-border); ); .high-contrast-mode({ @@ -151,6 +178,8 @@ var(--rs-message-warning-header); var(--rs-message-warning-text); var(--rs-message-warning-icon); + var(--rs-message-warning-border); + var(--rs-message-warning-icon-border); ); .high-contrast-mode({ @@ -164,6 +193,8 @@ var(--rs-message-error-header); var(--rs-message-error-text); var(--rs-message-error-icon); + var(--rs-message-error-border); + var(--rs-message-error-icon-border); ); .high-contrast-mode({ diff --git a/src/Message/styles/mixin.less b/src/Message/styles/mixin.less index 7ec2f6e62..02fb8b273 100644 --- a/src/Message/styles/mixin.less +++ b/src/Message/styles/mixin.less @@ -1,8 +1,19 @@ // Messages -.message-variant(@background; @header-color; @text-color; @icon-color) { +.message-variant(@background; @header-color; @text-color; @icon-color; @border-color; @icon-border-color;) { background-color: #fff; + &.rs-message-bordered { + border: 1px solid @border-color; + border-left-width: 4px; + + .rs-message-icon { + box-sizing: content-box; + border: 4px solid @icon-border-color; + border-radius: 50%; + } + } + .rs-message-container { background-color: @background; } @@ -23,7 +34,7 @@ font-size: 12px; } - .rs-message-icon-wrapper > .rs-icon, + .rs-message-icon > .rs-icon, .rs-btn-close { color: @icon-color; } diff --git a/src/Message/test/MessageSpec.tsx b/src/Message/test/MessageSpec.tsx index 5d2cccbb1..79a92fc13 100644 --- a/src/Message/test/MessageSpec.tsx +++ b/src/Message/test/MessageSpec.tsx @@ -27,6 +27,18 @@ describe('Message', () => { expect(screen.getByRole('alert')).to.text('description'); }); + it('Should be bordered', () => { + render(); + + expect(screen.getByRole('alert')).to.have.class('rs-message-bordered'); + }); + + it('Should be centered', () => { + render(); + + expect(screen.getByRole('alert')).to.have.class('rs-message-centered'); + }); + it('Should have a type', () => { render(); diff --git a/src/styles/color-modes/dark.less b/src/styles/color-modes/dark.less index 0bbf1efca..f4c8be070 100644 --- a/src/styles/color-modes/dark.less +++ b/src/styles/color-modes/dark.less @@ -134,18 +134,22 @@ --rs-message-success-text: #fff; --rs-message-success-icon: #fff; --rs-message-success-bg: var(--rs-green-500); + --rs-message-success-border: var(--rs-green-800); --rs-message-info-header: #fff; --rs-message-info-text: #fff; --rs-message-info-icon: #fff; --rs-message-info-bg: var(--rs-blue-500); + --rs-message-info-border: var(--rs-blue-800); --rs-message-warning-header: var(--rs-gray-900); --rs-message-warning-text: var(--rs-gray-900); --rs-message-warning-icon: var(--rs-gray-900); --rs-message-warning-bg: var(--rs-yellow-500); + --rs-message-warning-border: var(--rs-yellow-800); --rs-message-error-header: #fff; --rs-message-error-text: #fff; --rs-message-error-icon: #fff; --rs-message-error-bg: var(--rs-red-500); + --rs-message-error-border: var(--rs-red-800); // Tooltip --rs-tooltip-bg: var(--rs-gray-500); diff --git a/src/styles/color-modes/light.less b/src/styles/color-modes/light.less index 5c7882907..a3d9bc7f6 100644 --- a/src/styles/color-modes/light.less +++ b/src/styles/color-modes/light.less @@ -140,19 +140,27 @@ --rs-message-success-header: var(--rs-text-heading); --rs-message-success-text: var(--rs-text-primary); --rs-message-success-icon: var(--rs-color-green); + --rs-message-success-icon-border: var(--rs-green-200); --rs-message-success-bg: rgb(from var(--rs-green-100) r g b / 60%); + --rs-message-success-border: var(--rs-color-green); --rs-message-info-header: var(--rs-text-heading); --rs-message-info-text: var(--rs-text-primary); --rs-message-info-icon: var(--rs-color-blue); + --rs-message-info-icon-border: var(--rs-blue-200); --rs-message-info-bg: rgb(from var(--rs-blue-100) r g b / 60%); + --rs-message-info-border: var(--rs-color-blue); --rs-message-warning-header: var(--rs-text-heading); --rs-message-warning-text: var(--rs-text-primary); --rs-message-warning-icon: var(--rs-color-yellow); + --rs-message-warning-icon-border: var(--rs-yellow-200); --rs-message-warning-bg: rgb(from var(--rs-yellow-100) r g b / 60%); + --rs-message-warning-border: var(--rs-color-yellow); --rs-message-error-header: var(--rs-text-heading); --rs-message-error-text: var(--rs-text-primary); --rs-message-error-icon: var(--rs-color-red); + --rs-message-error-icon-border: var(--rs-red-200); --rs-message-error-bg: rgb(from var(--rs-red-100) r g b / 60%); + --rs-message-error-border: var(--rs-color-red); // Tooltip --rs-tooltip-bg: var(--rs-gray-900); diff --git a/src/utils/statusIcons.tsx b/src/utils/statusIcons.tsx index ecde117aa..f3c932dfb 100644 --- a/src/utils/statusIcons.tsx +++ b/src/utils/statusIcons.tsx @@ -2,15 +2,15 @@ import React from 'react'; import Info from '@rsuite/icons/legacy/Info'; import CheckCircle from '@rsuite/icons/legacy/CheckCircle'; import CloseCircle from '@rsuite/icons/legacy/CloseCircle'; -import Remind from '@rsuite/icons/legacy/Remind'; import Check from '@rsuite/icons/Check'; import Close from '@rsuite/icons/Close'; +import RemindRoundIcon from '@rsuite/icons/RemindRound'; export const MESSAGE_STATUS_ICONS = { info: , success: , error: , - warning: + warning: }; export const PROGRESS_STATUS_ICON = {