From 4e1005e3b4dde1d586c135c27c2d25c766a9fb09 Mon Sep 17 00:00:00 2001 From: Isaev Alexandr Date: Mon, 11 Dec 2023 16:23:22 +0300 Subject: [PATCH 1/6] feat(Text): support multiline and fixed width ellipsis (#1122) --- src/components/Text/README.md | 26 +++--- src/components/Text/Text.tsx | 89 ++++++++++++------- .../Text/__stories__/Text.stories.tsx | 37 ++++++-- src/components/Text/text/text.scss | 12 +++ src/components/Text/text/text.ts | 21 ++++- 5 files changed, 128 insertions(+), 57 deletions(-) diff --git a/src/components/Text/README.md b/src/components/Text/README.md index db2bab04c9..339d5def75 100644 --- a/src/components/Text/README.md +++ b/src/components/Text/README.md @@ -163,16 +163,16 @@ LANDING_BLOCK--> ## Properties -| Name | Description | Type | Default | -| :--------- | :------------------------------------------------------ | :--------------------------------------: | :--------: | -| children | Text content | `React.ReactNode` | | -| className | HTML `class` attribute | `string` | | -| as | Ability to override default html tag | `React.ElementType` | | -| style | HTML `style` attribute | `React.CSSProperties` | | -| variant | Font of the text | `string` | `"body-1"` | -| ellipsis | Hidden overflow content will be replaced by an ellipsis | `boolean` | | -| whiteSpace | The white-space css property | `"nowrap"` `"break-spaces"` | | -| wordBreak | The word-break css property | `"break-all"` | | -| color | Color of the text | `string` (see values in "Color" section) | | -| ref | | `any` | | -| qa | HTML `data-qa` attribute, used in tests | `string` | | +| Name | Description | Type | Default | +| :------------ | :------------------------------------------------------------------------ | :--------------------------------------: | :--------: | +| children | Text content | `React.ReactNode` | | +| className | HTML `class` attribute | `string` | | +| as | Ability to override default html tag | `React.ElementType` | | +| style | HTML `style` attribute | `React.CSSProperties` | | +| variant | Font of the text | `string` | `"body-1"` | +| ellipsis | Hidden overflow content will be replaced by an ellipsis | `boolean` | | +| ellipsisLines | The number of whole lines of text after which the content will be cut off | `number` | | +| whiteSpace | The white-space css property | `"nowrap"` `"break-spaces"` | | +| wordBreak | The word-break css property | `"break-all"` | | +| color | Color of the text | `string` (see values in "Color" section) | | +| qa | HTML `data-qa` attribute, used in tests | `string` | | diff --git a/src/components/Text/Text.tsx b/src/components/Text/Text.tsx index dbe290058a..7bd91e5837 100644 --- a/src/components/Text/Text.tsx +++ b/src/components/Text/Text.tsx @@ -7,7 +7,10 @@ import type {ColorTextBaseProps} from './colorText/colorText'; import {text} from './text/text'; import type {TextBaseProps} from './text/text'; -export interface TextProps extends TextBaseProps, ColorTextBaseProps, QAProps { +export interface TextProps + extends Omit, + ColorTextBaseProps, + QAProps { /** * Ability to override default html tag */ @@ -16,11 +19,14 @@ export interface TextProps extends TextBaseProps, ColorTextBaseProps, QAProps { className?: string; children?: React.ReactNode; title?: string; + ellipsisLines?: number; } type TextRef = React.ComponentPropsWithRef['ref']; -type TextPropsWithoutRef = {as?: C} & Omit; +type TextPropsWithoutRef = { + as?: C; +} & Omit; /** * A component for working with typography. @@ -48,39 +54,54 @@ type TextPropsWithoutRef = {as?: C} & Omitsome text * ``` */ -export const Text = React.forwardRef( - ( - { - as, - children, - variant, - className, - ellipsis, - color, - whiteSpace, - wordBreak, - qa, - ...rest - }: TextPropsWithoutRef, - ref?: TextRef, - ) => { - const Tag: React.ElementType = as || 'span'; +export const Text = React.forwardRef(function Text( + { + as, + children, + variant, + className, + ellipsis, + color, + whiteSpace, + wordBreak, + ellipsisLines, + style: outerStyle, + qa, + ...rest + }: TextPropsWithoutRef, + ref?: TextRef, +) { + const Tag: React.ElementType = as || 'span'; - return ( - - {children} - - ); - }, -) as (({ + const style: React.CSSProperties = { + ...outerStyle, + }; + + if (typeof ellipsisLines === 'number') { + style.WebkitLineClamp = ellipsisLines; + } + + return ( + + {children} + + ); +}) as (({ ref, ...props }: TextPropsWithoutRef & {ref?: TextRef}) => React.ReactElement) & {displayName: string}; diff --git a/src/components/Text/__stories__/Text.stories.tsx b/src/components/Text/__stories__/Text.stories.tsx index e2f1623e1a..d2c5f13e1b 100644 --- a/src/components/Text/__stories__/Text.stories.tsx +++ b/src/components/Text/__stories__/Text.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type {Meta, StoryFn} from '@storybook/react'; -import {Text, colorText, text} from '../.'; -import type {TextProps} from '../.'; +import {Flex} from '../../layout'; +import type {TextProps} from '../index'; +import {Text, colorText, text} from '../index'; export default { title: 'Components/Data Display/Text', @@ -26,16 +27,36 @@ export const UsingTextUtilities = () => ( ); -const EllipsisDefault: StoryFn = (args) => ; +const EllipsisDefault: StoryFn = (args) => ( + + + With fixed container size (ellipsis=true) + + + + + With text utility and fixed container size (ellipsis=true) + + {args.children} + + + + With text utility (ellipsisLines=true, style: WebkitLineClamp: 3) + + + {args.children} + + + + With line clamp property (ellipsisLines={3}) + + + +); export const Ellipsis = EllipsisDefault.bind({}); Ellipsis.args = { - as: 'div', - ellipsis: true, - style: { - width: 200, - }, children: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptates asperiores accusamus est, ab rerum harum hic delectus fuga veniam! Hic, atque, quia sunt consectetur eius corrupti, expedita sapiente exercitationem aperiam quibusdam libero ipsa veritatis quisquam! Debitis eos unde, blanditiis ipsam adipisci, soluta incidunt architecto quidem, repellat commodi tempore! Enim assumenda nam esse laudantium sequi quaerat maiores, voluptatum quibusdam temporibus nulla perspiciatis! Corrupti error aliquid iure asperiores voluptate. Nisi temporibus nesciunt quasi animi, accusamus officia debitis voluptatum ratione ullam delectus, adipisci, repellendus vitae in amet sit magni iste impedit? Exercitationem rerum impedit sed earum iusto modi et officia aspernatur quibusdam? Fugit.', }; diff --git a/src/components/Text/text/text.scss b/src/components/Text/text/text.scss index e563a1cbd1..776fcb684d 100644 --- a/src/components/Text/text/text.scss +++ b/src/components/Text/text/text.scss @@ -68,8 +68,20 @@ $block: '.#{variables.$ns}text'; @include mixins.text-subheader-3(); } &_ellipsis { + display: inline-block; @include mixins.overflow-ellipsis(); } + + &_ellipsis-lines { + display: -webkit-box; // stylelint-disable-line value-no-vendor-prefix + -webkit-box-orient: vertical; + overflow: hidden; + align-self: center; + white-space: normal; + // by default clamp 2 lines + -webkit-line-clamp: 2; + } + &_ws_nowrap { white-space: nowrap; } diff --git a/src/components/Text/text/text.ts b/src/components/Text/text/text.ts index 0dc7ae5066..2bc2e9ec2b 100644 --- a/src/components/Text/text/text.ts +++ b/src/components/Text/text/text.ts @@ -71,6 +71,13 @@ export interface TextBaseProps { * - text-overflow: ellipsis; */ ellipsis?: boolean; + /** + * With this prop you need to pass `-webkit-line-clamp` css property with number of cropped lines + * + * !Note: supports only modern browsers + * https://caniuse.com/?search=display%3A%20-webkit-box%3B + */ + ellipsisLines?: boolean; /** * white-space css property */ @@ -93,6 +100,16 @@ export interface TextBaseProps { *``` */ export const text = ( - {variant = 'body-1', ellipsis, whiteSpace, wordBreak}: TextBaseProps, + {variant = 'body-1', ellipsis, ellipsisLines, whiteSpace, wordBreak}: TextBaseProps, className?: string, -) => b({variant, ellipsis, ws: whiteSpace, wb: wordBreak}, className); +) => + b( + { + variant, + ellipsis, + ws: whiteSpace, + wb: wordBreak, + 'ellipsis-lines': ellipsisLines, + }, + className, + ); From c86782fcec7741176dd13d94a21a87492c02c5b5 Mon Sep 17 00:00:00 2001 From: Isaev Alexandr Date: Thu, 14 Dec 2023 15:25:51 +0300 Subject: [PATCH 2/6] feat: add new layout component `Box` (#1121) Co-authored-by: Alexandr Isaev --- .../Alert/__snapshots__/Alert.test.tsx.snap | 12 +-- src/components/Card/Card.tsx | 18 ++-- src/components/layout/Box/Box.scss | 23 +++++ src/components/layout/Box/Box.tsx | 92 +++++++++++++++++++ .../layout/Col/__stories__/Col.stories.tsx | 2 +- .../__stories__/Container.stories.tsx | 2 +- src/components/layout/Flex/Flex.scss | 5 + src/components/layout/Flex/Flex.tsx | 38 ++++---- .../layout/Flex/__stories__/Flex.stories.tsx | 10 +- .../LayoutProvider/__stories__/Layout.mdx | 26 +++++- .../__stories__/Layout.stories.tsx | 14 --- .../layout/Row/__stories__/Row.stories.tsx | 2 +- src/components/layout/index.ts | 1 + src/components/layout/variables.scss | 1 + 14 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 src/components/layout/Box/Box.scss create mode 100644 src/components/layout/Box/Box.tsx delete mode 100644 src/components/layout/LayoutProvider/__stories__/Layout.stories.tsx diff --git a/src/components/Alert/__snapshots__/Alert.test.tsx.snap b/src/components/Alert/__snapshots__/Alert.test.tsx.snap index 3070108ca6..117c792346 100644 --- a/src/components/Alert/__snapshots__/Alert.test.tsx.snap +++ b/src/components/Alert/__snapshots__/Alert.test.tsx.snap @@ -3,10 +3,10 @@ exports[`Alert has predicted styles if inline layout rendered 1`] = `
: child, ) : children} - + ); }); diff --git a/src/components/layout/Flex/__stories__/Flex.stories.tsx b/src/components/layout/Flex/__stories__/Flex.stories.tsx index adf4853564..483c0b964e 100644 --- a/src/components/layout/Flex/__stories__/Flex.stories.tsx +++ b/src/components/layout/Flex/__stories__/Flex.stories.tsx @@ -12,7 +12,7 @@ import {Flex} from '../Flex'; import type {FlexProps} from '../Flex'; export default { - title: 'Layout/Flex', + title: 'Components/Layout/Flex', component: Flex, } as Meta; @@ -112,17 +112,17 @@ export const ChildrenWithBgColor = ChildrenWithBgColorTemplate.bind({}); ChildrenWithBgColor.args = {}; const WithNullChildrenTemplate: StoryFn> = (args) => ( - + - + Box {null} {null} - + Box {null} - + Box diff --git a/src/components/layout/LayoutProvider/__stories__/Layout.mdx b/src/components/layout/LayoutProvider/__stories__/Layout.mdx index 01208b998a..3d25f6e380 100644 --- a/src/components/layout/LayoutProvider/__stories__/Layout.mdx +++ b/src/components/layout/LayoutProvider/__stories__/Layout.mdx @@ -1,8 +1,6 @@ import {Meta} from '@storybook/addon-docs'; -import * as LayoutStories from './Layout.stories'; - - + # Layout Components @@ -27,7 +25,6 @@ import {Container, Row, Col, Flex} from '@gravity-ui/uikit'; **All components supports `jsdoc` on hover feature. Just hover over the component or components prop in you favorite editor to see documentation** - ### Base concepts: - [Spacing](#spacing) @@ -41,6 +38,7 @@ import {Container, Row, Col, Flex} from '@gravity-ui/uikit'; - [Col](#col) - [Container](#container) - [Flex](#flex) +- [Box](#box) ### Hooks @@ -157,6 +155,26 @@ export const App = () => { }; ``` +## Box + +The `Box` component is a developer friend and basic block to build other components. Aware about spacing, its own sizes and most commonly used CSS properties. + +Use it to declaratively describe elements with a fixed height/width. It also has built-in support for the most commonly used properties, such as `overflow`. +It is mainly used as a base unit for other components such as `Flex` and `Card`. + +It is also well suited for use as a base for data loading containers, for example: + +```tsx +import React, {Suspence} from 'react'; + +// `Flex` extended from `Box` component and enriched flexbox model properties + + }> + + + +``` + ## Layout Grid Base components to describe 12-th column layout grid for you app. diff --git a/src/components/layout/LayoutProvider/__stories__/Layout.stories.tsx b/src/components/layout/LayoutProvider/__stories__/Layout.stories.tsx deleted file mode 100644 index 73b7b8e950..0000000000 --- a/src/components/layout/LayoutProvider/__stories__/Layout.stories.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import type {Meta, StoryFn} from '@storybook/react'; - -export default { - title: 'Layout', - id: 'Layout', - parameters: { - order: -100, - }, -} as Meta; - -export const Playground: StoryFn = () => ; -Playground.storyName = 'Layout'; diff --git a/src/components/layout/Row/__stories__/Row.stories.tsx b/src/components/layout/Row/__stories__/Row.stories.tsx index 17f1a3077f..4bb904fe7d 100644 --- a/src/components/layout/Row/__stories__/Row.stories.tsx +++ b/src/components/layout/Row/__stories__/Row.stories.tsx @@ -8,7 +8,7 @@ import {Row} from '../Row'; import type {RowProps} from '../Row'; export default { - title: 'Layout/Row', + title: 'Components/Layout/Row', component: Row, } as Meta; diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index eb1aea7bc0..3a565b5525 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -2,6 +2,7 @@ import type {RecursivePartial, LayoutTheme as StrictLayoutTheme} from './types'; export * from './Col/Col'; export * from './Row/Row'; export * from './Flex/Flex'; +export * from './Box/Box'; export * from './Container/Container'; export * from './LayoutProvider/LayoutProvider'; export * from './spacing/spacing'; diff --git a/src/components/layout/variables.scss b/src/components/layout/variables.scss index 742f8be266..c401825269 100644 --- a/src/components/layout/variables.scss +++ b/src/components/layout/variables.scss @@ -1,5 +1,6 @@ @use '../variables.scss'; +$boxBlock: '.#{variables.$ns}box'; $colBlock: '.#{variables.$ns}col'; $rowBlock: '.#{variables.$ns}row'; $flexBlock: '.#{variables.$ns}flex'; From 5bcec1c9c574b2db0a9a4beaa054fad8e544047f Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Mon, 18 Dec 2023 16:48:19 +0300 Subject: [PATCH 3/6] feat(Toaster)!: use theme prop instead of type (#1198) --- src/components/Toaster/README.md | 22 +++--- src/components/Toaster/Toast/Toast.scss | 68 +++++++------------ src/components/Toaster/Toast/Toast.tsx | 21 +++--- .../Toaster/__stories__/ToasterShowcase.scss | 2 +- .../Toaster/__stories__/ToasterShowcase.tsx | 46 ++++++------- src/components/Toaster/types.ts | 4 +- 6 files changed, 71 insertions(+), 92 deletions(-) diff --git a/src/components/Toaster/README.md b/src/components/Toaster/README.md index 901f7533e8..c56f348e37 100644 --- a/src/components/Toaster/README.md +++ b/src/components/Toaster/README.md @@ -114,17 +114,17 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18'; Accepts the argument `toastOptions` with ongoing notification details: -| Parameter | Type | Required | Default | Description | -| :--------- | :-------------------------------------- | :------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | `string` | yes | | A unique notification name. Notifications with identical names are collapsed into one | -| title | `string` | | | Notification title | -| className | `string` | | | CSS-class | -| autoHiding | `number` or `false` | | 5000 | Number of ms to delay before hiding the notification. Use `false` to disable toast hiding after timeout. | -| content | `node` | | `undefined` | Notification content. [Anything that can be rendered: numbers, strings, elements or an array](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes) | -| type | `string` | | `undefined` | A notification type. Possible values: `error`, `success`. If `type` is set, the icon (success/error) will be added into notification title. _By default, there is no icon_ | -| isClosable | `boolean` | | `true` | A configuration that manages the visibility of the X icon, which allows the user to close the notification | -| actions | `ToastAction[]` | | `undefined` | An array of [actions](./types.ts#L9) that display after `content` | -| renderIcon | `(toastProps: ToastProps) => ReactNode` | | `undefined` | Used to customize the toast icon. Type-based behavior is used by default | +| Parameter | Type | Required | Default | Description | +| :--------- | :-------------------------------------- | :------- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | yes | | A unique notification name. Notifications with identical names are collapsed into one | +| title | `string` | | | Notification title | +| className | `string` | | | CSS-class | +| autoHiding | `number` or `false` | | 5000 | Number of ms to delay before hiding the notification. Use `false` to disable toast hiding after timeout. | +| content | `node` | | `undefined` | Notification content. [Anything that can be rendered: numbers, strings, elements or an array](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes) | +| theme | `string` | | `"normal"` | A notification theme. Possible values: `"normal"`, `"info"`, `"success"`, `"warning"`, `danger`, `"utility"`. If `theme` is set to other than `"normal"`, the icon will be added into notification title. _By default, there is no icon_ | +| isClosable | `boolean` | | `true` | A configuration that manages the visibility of the X icon, which allows the user to close the notification | +| actions | `ToastAction[]` | | `undefined` | An array of [actions](./types.ts#L9) that display after `content` | +| renderIcon | `(toastProps: ToastProps) => ReactNode` | | `undefined` | Used to customize the toast icon. Type-based behavior is used by default | Every `action` is an object with following parameters: diff --git a/src/components/Toaster/Toast/Toast.scss b/src/components/Toaster/Toast/Toast.scss index 24a0997812..2a470a87fb 100644 --- a/src/components/Toaster/Toast/Toast.scss +++ b/src/components/Toaster/Toast/Toast.scss @@ -7,11 +7,10 @@ $block: '.#{variables.$ns}toast'; $closeButtonSize: 24px; $closeButtonEdgesSpacing: 16px; $closeButtonTitleSpacing: 8px; - $containerClass: #{&}__container; - $iconClass: #{&}__icon; --_--item-gap: 10px; --_--item-padding: 16px; + --_--background-color: var(--g-color-base-background); display: flex; box-sizing: border-box; @@ -22,7 +21,7 @@ $block: '.#{variables.$ns}toast'; font-size: var(--g-text-body-2-font-size); border-radius: 8px; box-shadow: 0px 0px 15px var(--g-color-sfx-shadow); - background-color: var(--g-color-base-background); + background-color: var(--_--background-color); overflow: hidden; z-index: 0; @@ -30,61 +29,38 @@ $block: '.#{variables.$ns}toast'; width: 100%; } - &_default { - background-color: var(--g-color-base-float); - } - - &_info { - & #{$containerClass}:before { - background-color: var(--g-color-base-info-light); - } - - & #{$iconClass} { - color: var(--g-color-text-info-heavy); - } - } - - &_success { - & #{$containerClass}:before { - background-color: var(--g-color-base-positive-light); + &_theme { + &_normal { + --_--background-color: var(--g-color-base-float); } - & #{$iconClass} { - color: var(--g-color-text-positive-heavy); - } - } - - &_warning { - & #{$containerClass}:before { - background-color: var(--g-color-base-warning-light); + &_info { + --_--container-background-color: var(--g-color-base-info-light); + --_--icon-color: var(--g-color-text-info-heavy); } - & #{$iconClass} { - color: var(--g-color-text-warning-heavy); + &_success { + --_--container-background-color: var(--g-color-base-positive-light); + --_--icon-color: var(--g-color-text-positive-heavy); } - } - &_error { - & #{$containerClass}:before { - background-color: var(--g-color-base-danger-light); + &_warning { + --_--container-background-color: var(--g-color-base-warning-light); + --_--icon-color: var(--g-color-text-warning-heavy); } - & #{$iconClass} { - color: var(--g-color-text-danger-heavy); - } - } - - &_utility { - & #{$containerClass}:before { - background-color: var(--g-color-base-utility-light); + &_danger { + --_--container-background-color: var(--g-color-base-danger-light); + --_--icon-color: var(--g-color-text-danger-heavy); } - & #{$iconClass} { - color: var(--g-color-text-utility-heavy); + &_utility { + --_--container-background-color: var(--g-color-base-utility-light); + --_--icon-color: var(--g-color-text-utility-heavy); } } - #{$containerClass} { + &__container { flex: 1 1 auto; display: grid; row-gap: 8px; @@ -103,6 +79,7 @@ $block: '.#{variables.$ns}toast'; height: 100%; pointer-events: none; z-index: -1; + background-color: var(--_--container-background-color); } } @@ -110,6 +87,7 @@ $block: '.#{variables.$ns}toast'; flex: 0 0 auto; padding-inline-end: 8px; padding-block-start: 2px; + color: var(--_--icon-color); } &__title { diff --git a/src/components/Toaster/Toast/Toast.tsx b/src/components/Toaster/Toast/Toast.tsx index f5a82ee6ec..eda63618bd 100644 --- a/src/components/Toaster/Toast/Toast.tsx +++ b/src/components/Toaster/Toast/Toast.tsx @@ -8,17 +8,18 @@ import {Icon} from '../../Icon'; import type {IconProps} from '../../Icon'; import {block} from '../../utils/cn'; import i18n from '../i18n'; -import type {InternalToastProps, ToastAction, ToastType} from '../types'; +import type {InternalToastProps, ToastAction, ToastTheme} from '../types'; import './Toast.scss'; const b = block('toast'); const DEFAULT_TIMEOUT = 5000; -const TITLE_ICONS: Record = { +const TITLE_ICONS: Record = { + normal: null, info: CircleInfo, success: CircleCheck, warning: TriangleExclamation, - error: TriangleExclamation, + danger: TriangleExclamation, utility: Thunderbolt, }; @@ -68,15 +69,15 @@ function renderActions({actions, onClose}: RenderActionsProps) { } interface RenderIconProps { - type?: ToastType; + theme?: ToastTheme; } -function renderIconByType({type}: RenderIconProps) { - if (!type) { +function renderIconByType({theme}: RenderIconProps) { + if (!theme || !TITLE_ICONS[theme]) { return null; } - return ; + return ; } export const Toast = React.forwardRef(function Toast(props, ref) { @@ -86,7 +87,7 @@ export const Toast = React.forwardRef(function actions, title, className, - type, + theme = 'normal', renderIcon, autoHiding: timeoutProp = DEFAULT_TIMEOUT, isClosable = true, @@ -100,13 +101,13 @@ export const Toast = React.forwardRef(function const mods = { mobile, - [type || 'default']: true, + theme, }; const hasTitle = Boolean(title); const hasContent = Boolean(content); - const icon = renderIcon ? renderIcon(props) : renderIconByType({type}); + const icon = renderIcon ? renderIcon(props) : renderIconByType({theme}); return (
{icon &&
{icon}
} diff --git a/src/components/Toaster/__stories__/ToasterShowcase.scss b/src/components/Toaster/__stories__/ToasterShowcase.scss index 5681cc5441..28f471c37f 100644 --- a/src/components/Toaster/__stories__/ToasterShowcase.scss +++ b/src/components/Toaster/__stories__/ToasterShowcase.scss @@ -18,7 +18,7 @@ color: var(--g-color-text-warning-heavy); } - &_error { + &_danger { color: var(--g-color-text-danger-heavy); } diff --git a/src/components/Toaster/__stories__/ToasterShowcase.tsx b/src/components/Toaster/__stories__/ToasterShowcase.tsx index b490b32ef9..a47a1872f9 100644 --- a/src/components/Toaster/__stories__/ToasterShowcase.tsx +++ b/src/components/Toaster/__stories__/ToasterShowcase.tsx @@ -72,7 +72,7 @@ export const ToasterDemo = ({ function getToastProps(extra: { name: string; title?: string; - type?: ToastProps['type']; + theme?: ToastProps['theme']; className?: string; content?: React.ReactNode; actions?: ToastAction[]; @@ -101,7 +101,7 @@ export const ToasterDemo = ({ name: getToastName(extra.name), className: extra.className, title, - type: extra.type, + theme: extra.theme, isClosable: showCloseIcon, autoHiding: timeout, actions: setActions @@ -113,10 +113,10 @@ export const ToasterDemo = ({ }; } - const createDefaultToast = () => { + const createNormalToast = () => { const toastProps = getToastProps({ - name: 'default', - title: 'Default toast', + name: 'normal', + title: 'Normal toast', }); toaster.add(toastProps); @@ -127,7 +127,7 @@ export const ToasterDemo = ({ const createInfoToast = () => { const toastProps = getToastProps({ name: 'info', - type: 'info', + theme: 'info', title: 'Info toast', }); @@ -139,7 +139,7 @@ export const ToasterDemo = ({ const createSuccessToast = () => { const toastProps = getToastProps({ name: 'success', - type: 'success', + theme: 'success', title: 'Success toast', }); @@ -151,7 +151,7 @@ export const ToasterDemo = ({ const createWarningToast = () => { const toastProps = getToastProps({ name: 'warning', - type: 'warning', + theme: 'warning', title: 'Warning toast', }); @@ -160,11 +160,11 @@ export const ToasterDemo = ({ setState((state) => ({...state, lastToastName: toastProps.name})); }; - const createErrorToast = () => { + const createDangerToast = () => { const toastProps = getToastProps({ - name: 'error', - type: 'error', - title: 'Error toast', + name: 'danger', + theme: 'danger', + title: 'Danger toast', }); toaster.add(toastProps); @@ -175,7 +175,7 @@ export const ToasterDemo = ({ const createUtilityToast = () => { const toastProps = getToastProps({ name: 'utility', - type: 'utility', + theme: 'utility', title: 'Utility toast', }); @@ -236,7 +236,7 @@ export const ToasterDemo = ({ const createToastLongContent = () => { const toastProps = getToastProps({ name: 'overflow', - type: 'error', + theme: 'danger', title: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.', content: 'Excepturi cumque dicta, et a repellat culpa totam minus vero, error ducimus nesciunt? Dicta soluta earum sapiente explicabo commodi pariatur nulla eius?', @@ -300,9 +300,9 @@ export const ToasterDemo = ({ const btnStyle = {marginInlineStart: 20}; - const defaultToastBtn = ( - ); @@ -327,10 +327,10 @@ export const ToasterDemo = ({ ); - const errorToastBtn = ( - ); @@ -375,11 +375,11 @@ export const ToasterDemo = ({ return ( -

{defaultToastBtn}

+

{normalToastBtn}

{infoToastBtn}

{successToastBtn}

{warningToastBtn}

-

{errorToastBtn}

+

{dangerToastBtn}

{utilityToastBtn}

{customToastBtn}

{toastWithLongContent}

diff --git a/src/components/Toaster/types.ts b/src/components/Toaster/types.ts index fe74158bbb..72dce8616a 100644 --- a/src/components/Toaster/types.ts +++ b/src/components/Toaster/types.ts @@ -5,7 +5,7 @@ export type ToasterArgs = { mobile?: boolean; }; -export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'utility'; +export type ToastTheme = 'normal' | 'info' | 'success' | 'warning' | 'danger' | 'utility'; export type ToastAction = { onClick: VoidFunction; @@ -20,7 +20,7 @@ export type ToastProps = { className?: string; autoHiding?: number | false; content?: React.ReactNode; - type?: ToastType; + theme?: ToastTheme; isClosable?: boolean; actions?: ToastAction[]; From a0a7e643f2322880e3b4c4bad969b543d9b808eb Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Tue, 19 Dec 2023 09:37:25 +0100 Subject: [PATCH 4/6] feat(TextInput): support rtl (#1207) --- .../controls/TextInput/AdditionalContent.tsx | 2 +- .../controls/TextInput/TextInput.scss | 55 +++++++++++-------- .../controls/TextInput/TextInput.tsx | 50 +++++++++++------ 3 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/components/controls/TextInput/AdditionalContent.tsx b/src/components/controls/TextInput/AdditionalContent.tsx index ec878f5550..71bb8e1bb8 100644 --- a/src/components/controls/TextInput/AdditionalContent.tsx +++ b/src/components/controls/TextInput/AdditionalContent.tsx @@ -5,7 +5,7 @@ import {block} from '../../utils/cn'; const b = block('text-input'); type Props = { - placement: 'left' | 'right'; + placement: 'start' | 'end'; children?: React.ReactNode; onClick: React.MouseEventHandler; }; diff --git a/src/components/controls/TextInput/TextInput.scss b/src/components/controls/TextInput/TextInput.scss index de554e0d44..285124e352 100644 --- a/src/components/controls/TextInput/TextInput.scss +++ b/src/components/controls/TextInput/TextInput.scss @@ -8,28 +8,32 @@ @include mixins.text-body-short; line-height: #{variables.$s-height - control-variables.$border-width * 2}; - padding: 0 4px 0 8px; + padding-block: 0; + padding-inline: 8px 4px; } @if $size == 'm' { @include mixins.text-body-short; line-height: #{variables.$m-height - control-variables.$border-width * 2}; - padding: 0 4px 0 8px; + padding-block: 0; + padding-inline: 8px 4px; } @if $size == 'l' { @include mixins.text-body-short; line-height: #{variables.$l-height - control-variables.$border-width * 2}; - padding: 0 4px 0 12px; + padding-block: 0; + padding-inline: 12px 4px; } @if $size == 'xl' { @include mixins.text-body-2; line-height: #{variables.$xl-height - control-variables.$border-width * 2}; - padding: 0 4px 0 12px; + padding-block: 0; + padding-inline: 12px 4px; } @include mixins.text-accent; @@ -105,7 +109,8 @@ $block: '.#{variables.$ns}text-input'; &__error-icon { box-sizing: content-box; color: var(--g-color-text-danger); - padding: var(--_--text-input-error-icon-padding); + padding-block: var(--_--text-input-error-icon-padding-block); + padding-inline: var(--_--text-input-error-icon-padding-inline); } &__additional-content { @@ -123,7 +128,7 @@ $block: '.#{variables.$ns}text-input'; @include input-label(s); } - &#{$block}_has-left-content #{$block}__label { + &#{$block}_has-start-content #{$block}__label { padding-inline-start: 2px; } @@ -131,15 +136,16 @@ $block: '.#{variables.$ns}text-input'; height: #{variables.$s-height - control-variables.$border-width * 2}; } - #{$block}__additional-content_placement_left { + #{$block}__additional-content_placement_start { padding-inline-start: 1px; } - #{$block}__additional-content_placement_right { + #{$block}__additional-content_placement_end { padding-inline-end: 1px; } - --_--text-input-error-icon-padding: 5px 5px 5px 0; + --_--text-input-error-icon-padding-block: 5px; + --_--text-input-error-icon-padding-inline: 0 5px; --_--text-input-border-radius: var(--g-border-radius-s); } @@ -153,7 +159,7 @@ $block: '.#{variables.$ns}text-input'; @include input-label(m); } - &#{$block}_has-left-content #{$block}__label { + &#{$block}_has-start-content #{$block}__label { padding-inline-start: 2px; } @@ -161,15 +167,16 @@ $block: '.#{variables.$ns}text-input'; height: #{variables.$m-height - control-variables.$border-width * 2}; } - #{$block}__additional-content_placement_left { + #{$block}__additional-content_placement_start { padding-inline-start: 1px; } - #{$block}__additional-content_placement_right { + #{$block}__additional-content_placement_end { padding-inline-end: 1px; } - --_--text-input-error-icon-padding: 5px 5px 5px 0; + --_--text-input-error-icon-padding-block: 5px; + --_--text-input-error-icon-padding-inline: 0 5px; --_--text-input-border-radius: var(--g-border-radius-m); } @@ -183,7 +190,7 @@ $block: '.#{variables.$ns}text-input'; @include input-label(l); } - &#{$block}_has-left-content #{$block}__label { + &#{$block}_has-start-content #{$block}__label { padding-inline-start: 3px; } @@ -191,15 +198,16 @@ $block: '.#{variables.$ns}text-input'; height: #{variables.$l-height - control-variables.$border-width * 2}; } - #{$block}__additional-content_placement_left { + #{$block}__additional-content_placement_start { padding-inline-start: 3px; } - #{$block}__additional-content_placement_right { + #{$block}__additional-content_placement_end { padding-inline-end: 3px; } - --_--text-input-error-icon-padding: 9px 9px 9px 0; + --_--text-input-error-icon-padding-block: 9px; + --_--text-input-error-icon-padding-inline: 0 9px; --_--text-input-border-radius: var(--g-border-radius-l); } @@ -213,7 +221,7 @@ $block: '.#{variables.$ns}text-input'; @include input-label(xl); } - &#{$block}_has-left-content #{$block}__label { + &#{$block}_has-start-content #{$block}__label { padding-inline-start: 3px; } @@ -221,15 +229,16 @@ $block: '.#{variables.$ns}text-input'; height: #{variables.$xl-height - control-variables.$border-width * 2}; } - #{$block}__additional-content_placement_left { + #{$block}__additional-content_placement_start { padding-inline-start: 3px; } - #{$block}__additional-content_placement_right { + #{$block}__additional-content_placement_end { padding-inline-end: 3px; } - --_--text-input-error-icon-padding: 13px 13px 13px 0; + --_--text-input-error-icon-padding-block: 13px; + --_--text-input-error-icon-padding-inline: 0 13px; --_--text-input-border-radius: var(--g-border-radius-xl); } @@ -296,13 +305,13 @@ $block: '.#{variables.$ns}text-input'; } } - &_has-left-content { + &_has-start-content { #{$block}__control { padding-inline-start: 2px; } } - &_has-right-content { + &_has-end-content { #{$block}__control { padding-inline-end: 2px; } diff --git a/src/components/controls/TextInput/TextInput.tsx b/src/components/controls/TextInput/TextInput.tsx index bbf6f5c7bf..543df3fb12 100644 --- a/src/components/controls/TextInput/TextInput.tsx +++ b/src/components/controls/TextInput/TextInput.tsx @@ -36,10 +36,18 @@ export type TextInputProps = BaseInputControlProps & { controlProps?: React.InputHTMLAttributes; /** Help text rendered to the left of the input node */ label?: string; - /** User`s node rendered before label and input node */ + /** User`s node rendered before label and input node + * @deprecated use `startContent` instead + */ leftContent?: React.ReactNode; - /** User`s node rendered after input node and clear button */ + /** User`s node rendered after input node and clear button + * @deprecated use `endContent` instead + */ rightContent?: React.ReactNode; + /** User`s node rendered before label and input node */ + startContent?: React.ReactNode; + /** User`s node rendered after input node and clear button */ + endContent?: React.ReactNode; /** An optional element displayed under the lower right corner of the control and sharing the place with the error container */ note?: React.ReactNode; }; @@ -75,6 +83,8 @@ export const TextInput = React.forwardRef(funct controlProps: originalControlProps, leftContent, rightContent, + startContent = leftContent, + endContent = rightContent, note, onUpdate, onChange, @@ -91,7 +101,7 @@ export const TextInput = React.forwardRef(funct const innerControlRef = React.useRef(null); const handleRef = useForkRef(props.controlRef, innerControlRef); const labelRef = React.useRef(null); - const leftContentRef = React.useRef(null); + const startContentRef = React.useRef(null); const state = getInputControlState(validationState); const isControlled = value !== undefined; @@ -102,8 +112,8 @@ export const TextInput = React.forwardRef(funct const isErrorIconVisible = validationState === 'invalid' && Boolean(errorMessage) && errorPlacement === 'inside'; const isClearControlVisible = Boolean(hasClear && !disabled && inputValue); - const isLeftContentVisible = Boolean(leftContent); - const isRightContentVisible = Boolean(rightContent); + const isStartContentVisible = Boolean(startContent); + const isEndContentVisible = Boolean(endContent); const isAutoCompleteOff = isLabelVisible && !originalId && !name && typeof autoComplete === 'undefined'; @@ -111,7 +121,7 @@ export const TextInput = React.forwardRef(funct const id = isLabelVisible ? originalId || innerId : originalId; const labelSize = useElementSize(isLabelVisible ? labelRef : null, size); - const leftContentSize = useElementSize(isLeftContentVisible ? leftContentRef : null, size); + const startContentSize = useElementSize(isStartContentVisible ? startContentRef : null, size); const errorMessageId = useUniqId(); const noteId = useUniqId(); @@ -127,7 +137,9 @@ export const TextInput = React.forwardRef(funct ...originalControlProps, style: { ...originalControlProps?.style, - ...(isLabelVisible && labelSize.width ? {paddingLeft: `${labelSize.width}px`} : {}), + ...(isLabelVisible && labelSize.width + ? {paddingInlineStart: `${labelSize.width}px`} + : {}), }, 'aria-invalid': validationState === 'invalid' || undefined, 'aria-describedby': ariaDescribedBy || undefined, @@ -199,29 +211,31 @@ export const TextInput = React.forwardRef(funct state, pin: view === 'clear' ? undefined : pin, 'has-clear': isClearControlVisible, - 'has-left-content': isLeftContentVisible, - 'has-right-content': isClearControlVisible || isRightContentVisible, + 'has-start-content': isStartContentVisible, + 'has-end-content': isClearControlVisible || isEndContentVisible, }, className, )} data-qa={qa} > - {isLeftContentVisible && ( + {isStartContentVisible && ( - {leftContent} + {startContent} )} {isLabelVisible && (