From 608295c00eea3fc57b211ad06bd221308bc3936b Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Wed, 29 Nov 2023 12:10:26 +0300 Subject: [PATCH] feat(plasma-new-hope): Tooltip added --- .../components/Dropdown/Dropdown.styles.ts | 9 +- .../src/components/Dropdown/Dropdown.tsx | 76 ++++--- .../src/components/Dropdown/Dropdown.types.ts | 6 +- .../src/components/Popover/Popover.styles.ts | 95 +++++++++ .../src/components/Popover/Popover.tokens.ts | 14 ++ .../src/components/Popover/Popover.tsx | 137 ++++++++---- .../src/components/Popover/Popover.types.ts | 27 ++- .../src/components/Popover/index.ts | 2 + .../src/components/Popover/styles/index.ts | 31 --- .../src/components/Popover/utils/index.ts | 3 +- .../Popover/variations/_view/base.ts | 3 + .../Popover/variations/_view/tokens.json | 8 + .../src/components/Tooltip/Tooltip.styles.ts | 42 ++++ .../src/components/Tooltip/Tooltip.tokens.ts | 25 +++ .../src/components/Tooltip/Tooltip.tsx | 112 ++++++++++ .../src/components/Tooltip/Tooltip.types.ts | 69 ++++++ .../src/components/Tooltip/index.ts | 3 + .../Tooltip/variations/_size/base.ts | 3 + .../Tooltip/variations/_size/tokens.json | 15 ++ .../Tooltip/variations/_view/base.ts | 3 + .../Tooltip/variations/_view/tokens.json | 1 + .../components/Popover/Popover.config.ts | 23 ++ .../components/Popover/Popover.stories.tsx | 25 +-- .../plasma_b2c/components/Popover/Popover.ts | 12 +- .../components/Tooltip/Tooltip.config.ts | 98 +++++++++ .../components/Tooltip/Tooltip.stories.tsx | 200 ++++++++++++++++++ .../plasma_b2c/components/Tooltip/Tooltip.tsx | 22 ++ .../components/Tooltip/Tooltip.config.ts | 98 +++++++++ .../components/Tooltip/Tooltip.stories.tsx | 200 ++++++++++++++++++ .../plasma_web/components/Tooltip/Tooltip.tsx | 22 ++ .../components/Tooltip/Tooltip.config.ts | 60 ++++++ .../components/Tooltip/Tooltip.stories.tsx | 200 ++++++++++++++++++ .../components/Tooltip/Tooltip.tsx | 20 ++ .../examples/themes/sds_engineer.module.css | 3 +- packages/plasma-new-hope/src/index.ts | 1 + 35 files changed, 1533 insertions(+), 135 deletions(-) create mode 100644 packages/plasma-new-hope/src/components/Popover/Popover.styles.ts create mode 100644 packages/plasma-new-hope/src/components/Popover/Popover.tokens.ts delete mode 100644 packages/plasma-new-hope/src/components/Popover/styles/index.ts create mode 100644 packages/plasma-new-hope/src/components/Popover/variations/_view/base.ts create mode 100644 packages/plasma-new-hope/src/components/Popover/variations/_view/tokens.json create mode 100644 packages/plasma-new-hope/src/components/Tooltip/Tooltip.styles.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/Tooltip.tokens.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/Tooltip.tsx create mode 100644 packages/plasma-new-hope/src/components/Tooltip/Tooltip.types.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/index.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/variations/_size/base.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/variations/_size/tokens.json create mode 100644 packages/plasma-new-hope/src/components/Tooltip/variations/_view/base.ts create mode 100644 packages/plasma-new-hope/src/components/Tooltip/variations/_view/tokens.json create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.config.ts create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.config.ts create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.stories.tsx create mode 100644 packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.tsx create mode 100644 packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.config.ts create mode 100644 packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.stories.tsx create mode 100644 packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.tsx create mode 100644 packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.config.ts create mode 100644 packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.stories.tsx create mode 100644 packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.tsx diff --git a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.styles.ts b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.styles.ts index 506dccf332..3ca6a2ef25 100644 --- a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.styles.ts +++ b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.styles.ts @@ -1,13 +1,6 @@ import { styled } from '@linaria/react'; -import { css } from '@linaria/core'; -import { classes, tokens } from './Dropdown.tokens'; - -export const base = css` - .${String(classes.nestedDropdown)} { - display: block; - } -`; +import { tokens } from './Dropdown.tokens'; export const StyledDropdown = styled.div` box-sizing: border-box; diff --git a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx index ffb4662b84..1c399b8d32 100644 --- a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx +++ b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.tsx @@ -1,19 +1,42 @@ import React, { forwardRef, useRef } from 'react'; import { useFocusTrap, useForkRef, useUniqId } from '@salutejs/plasma-core'; +import { styled } from '@linaria/react'; import { RootProps, component } from '../../engines'; -import { popoverConfig } from '../Popover'; +import { popoverClasses, popoverConfig } from '../Popover'; import { cx } from '../../utils'; +import { PopoverPlacementBasic } from '../Popover/Popover.types'; import { base as viewCSS } from './variations/_view/base'; import { base as sizeCSS } from './variations/_size/base'; -import { StyledDropdown, base } from './Dropdown.styles'; +import { StyledDropdown } from './Dropdown.styles'; import { classes } from './Dropdown.tokens'; -import type { DropdownProps } from './Dropdown.types'; +import type { DropdownPlacement, DropdownPlacementBasic, DropdownProps } from './Dropdown.types'; + +export const getPlacement = (placement: DropdownPlacement) => { + return `${placement}-start` as PopoverPlacementBasic; +}; + +export const getPlacements = (placements?: DropdownPlacement | DropdownPlacementBasic[]) => { + if (!placements) { + return; + } + const isArray = Array.isArray(placements); + + if (!isArray) { + return getPlacement(placements as DropdownPlacement); + } + return ((placements || []) as DropdownPlacementBasic[]).map((placement) => getPlacement(placement)); +}; // issue #823 const Popover = component(popoverConfig); +const StyledPopover = styled(Popover)` + .${String(classes.nestedDropdown)} > .${String(popoverClasses.target)} { + display: block; + } +`; /** * Выпадающий список без внешнего контроля видимости. */ @@ -24,10 +47,11 @@ export const dropdownRoot = (Root: RootProps) => id, target, children, - arrow, + hasArrow, role, view, size, + frame, onToggle, isFocusTrapped = true, isOpen = false, @@ -56,27 +80,29 @@ export const dropdownRoot = (Root: RootProps) => const nestedClass = isNested ? classes.nestedDropdown : undefined; return ( - - onToggle?.(is, event)} - id={innerId} - ref={dropdownForkRef} - target={target} - offset={offset} - preventOverflow={preventOverflow} - className={cx(nestedClass)} - arrow={arrow} - placement={placement} - trigger={trigger} - closeOnOverlayClick={closeOnOverlayClick} - closeOnEsc={closeOnEsc} - isFocusTrapped={isFocusTrapped} - > + onToggle?.(is, event)} + id={innerId} + ref={dropdownForkRef} + target={target} + offset={offset} + preventOverflow={preventOverflow} + className={cx(nestedClass)} + hasArrow={hasArrow} + placement={getPlacements(placement)} + trigger={trigger} + closeOnOverlayClick={closeOnOverlayClick} + closeOnEsc={closeOnEsc} + isFocusTrapped={isFocusTrapped} + frame={frame} + > + {children} - - + + ); }, ); @@ -85,7 +111,7 @@ export const dropdownConfig = { name: 'Dropdown', tag: 'div', layout: dropdownRoot, - base, + base: '', variations: { view: { css: viewCSS, diff --git a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.types.ts b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.types.ts index 711000ce85..c2f3a2d235 100644 --- a/packages/plasma-new-hope/src/components/Dropdown/Dropdown.types.ts +++ b/packages/plasma-new-hope/src/components/Dropdown/Dropdown.types.ts @@ -45,7 +45,7 @@ export type CustomDropdownProps = { /** * Стрелка над элементом. */ - arrow?: ReactNode; + hasArrow?: boolean; /** * Контент всплывающего окна. */ @@ -72,6 +72,10 @@ export type CustomDropdownProps = { * Событие сворачивания/разворачивания дропдауна. */ onToggle?: (isOpen: boolean, event: SyntheticEvent | Event) => void; + /** + * В каком контейнере позиционируется(по умолчанию document), можно также указать id элемента или ref для него. + */ + frame?: 'document' | string | React.RefObject; size?: string; view?: string; diff --git a/packages/plasma-new-hope/src/components/Popover/Popover.styles.ts b/packages/plasma-new-hope/src/components/Popover/Popover.styles.ts new file mode 100644 index 0000000000..f26cc1120f --- /dev/null +++ b/packages/plasma-new-hope/src/components/Popover/Popover.styles.ts @@ -0,0 +1,95 @@ +import { styled } from '@linaria/react'; + +import { DEFAULT_Z_INDEX } from '../Popup/utils'; + +import { tokens } from './Popover.tokens'; +import { PopoverProps } from './Popover.types'; + +export const StyledRoot = styled.div` + position: relative; + box-sizing: border-box; + display: inline-flex; +`; + +export const StyledArrow = styled.div` + visibility: hidden; + + &, + &::before { + position: absolute; + width: var(${String(tokens.arrowMaskWidth)}); + height: var(${String(tokens.arrowMaskHeight)}); + mask-image: var(${String(tokens.arrowMaskImage)}); + background: var(${String(tokens.arrowBackground)}); + } + + &::before { + visibility: visible; + content: ''; + transform: rotate(0deg); + } +`; + +export const StyledPopover = styled.div>` + position: absolute; + z-index: ${({ zIndex }) => zIndex || DEFAULT_Z_INDEX}; + + &[data-popper-placement^='top'] > .popover-arrow { + bottom: calc(0px - var(${String(tokens.arrowHeight)})); + } + + &[data-popper-placement^='bottom'] > .popover-arrow { + top: calc(0px - var(${String(tokens.arrowHeight)})); + &::before { + transform: rotate(180deg); + } + } + + &[data-popper-placement^='left'] > .popover-arrow { + right: calc(0px - var(${String(tokens.arrowHeight)})); + &::before { + transform: rotate(-90deg); + } + } + + &[data-popper-placement^='right'] > .popover-arrow { + left: calc(0px - var(${String(tokens.arrowHeight)})); + &::before { + transform: rotate(90deg); + } + } + + &[data-popper-placement^='top-start'] > .popover-arrow { + bottom: calc(0px - var(${String(tokens.arrowHeight)})); + left: var(${String(tokens.arrowEdgeMargin)}) !important; + transform: unset !important; + } + + &[data-popper-placement^='top-end'] > .popover-arrow { + bottom: calc(0px - var(${String(tokens.arrowHeight)})); + left: unset !important; + right: var(${String(tokens.arrowEdgeMargin)}) !important; + transform: unset !important; + } + + &[data-popper-placement^='bottom-start'] > .popover-arrow { + top: calc(0px - var(${String(tokens.arrowHeight)})); + left: var(${String(tokens.arrowEdgeMargin)}) !important; + transform: unset !important; + + &::before { + transform: rotate(180deg); + } + } + + &[data-popper-placement^='bottom-end'] > .popover-arrow { + top: calc(0px - var(${String(tokens.arrowHeight)})); + left: unset !important; + right: var(${String(tokens.arrowEdgeMargin)}) !important; + transform: unset !important; + + &::before { + transform: rotate(180deg); + } + } +`; diff --git a/packages/plasma-new-hope/src/components/Popover/Popover.tokens.ts b/packages/plasma-new-hope/src/components/Popover/Popover.tokens.ts new file mode 100644 index 0000000000..614ba7c279 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Popover/Popover.tokens.ts @@ -0,0 +1,14 @@ +export const classes = { + root: 'popover-root', + target: 'popover-target', + arrow: 'popover-arrow', +}; + +export const tokens = { + arrowMaskWidth: '--plasma-popover-arrow-mask-width', + arrowMaskHeight: '--plasma-popover-arrow-mask-height', + arrowMaskImage: '--plasma-popover-arrow-mask-image', + arrowBackground: '--plasma-popover-arrow-background', + arrowHeight: '--plasma-popover-arrow-height', + arrowEdgeMargin: '--plasma-popover-arrow-edge-margin', +}; diff --git a/packages/plasma-new-hope/src/components/Popover/Popover.tsx b/packages/plasma-new-hope/src/components/Popover/Popover.tsx index 46a1802532..74232605dd 100644 --- a/packages/plasma-new-hope/src/components/Popover/Popover.tsx +++ b/packages/plasma-new-hope/src/components/Popover/Popover.tsx @@ -1,12 +1,15 @@ import React, { useRef, useCallback, useEffect, useState, forwardRef } from 'react'; +import ReactDOM from 'react-dom'; import { usePopper } from 'react-popper'; import { useFocusTrap, useForkRef } from '@salutejs/plasma-core'; import { RootProps } from '../../engines/types'; +import { base as viewCSS } from './variations/_view/base'; import type { PopoverPlacement, PopoverProps } from './Popover.types'; -import { ESCAPE_KEYCODE, getAutoPlacements, getPlacement } from './utils'; -import { StyledArrow, StyledPopover, StyledRoot } from './styles'; +import { ESCAPE_KEYCODE, POPOVER_PORTAL_ID, getAutoPlacements, getPlacement } from './utils'; +import { StyledArrow, StyledPopover, StyledRoot } from './Popover.styles'; +import { classes } from './Popover.tokens'; /** * Всплывающее окно с возможностью позиционирования @@ -20,14 +23,18 @@ export const popoverRoot = (Root: RootProps) => children, isOpen, trigger, - arrow, + hasArrow, + frame = 'document', className, placement = 'auto', offset = [0, 0], + zIndex, isFocusTrapped = true, closeOnOverlayClick = true, closeOnEsc = true, preventOverflow = true, + insidePortal = true, + view, onToggle, ...rest }, @@ -36,6 +43,7 @@ export const popoverRoot = (Root: RootProps) => const rootRef = useRef(null); const popoverRef = useRef(null); const handleRef = useForkRef(rootRef, outerRootRef); + const portalRef = useRef(null); const trapRef = useFocusTrap(isOpen && isFocusTrapped); @@ -43,8 +51,10 @@ export const popoverRoot = (Root: RootProps) => const [arrowElement, setArrowElement] = useState(null); + const [, forceRender] = useState(false); + const isAutoArray = Array.isArray(placement); - const isAuto = isAutoArray || placement === 'auto'; + const isAuto = isAutoArray || (placement as PopoverPlacement).startsWith('auto'); const { styles, attributes, forceUpdate } = usePopper(rootRef.current, popoverRef.current, { placement: getPlacement(isAutoArray ? 'auto' : (placement as PopoverPlacement)), @@ -65,7 +75,12 @@ export const popoverRoot = (Root: RootProps) => ), }, }, - { name: 'arrow', options: { element: arrowElement } }, + { + name: 'arrow', + options: { + element: arrowElement, + }, + }, ], }); @@ -152,6 +167,37 @@ export const popoverRoot = (Root: RootProps) => return () => window.removeEventListener('keydown', onEscape); }, [closeOnEsc, isOpen, onToggle]); + useEffect(() => { + let portal = document.getElementById(POPOVER_PORTAL_ID); + + if (typeof frame !== 'string' && frame && frame.current) { + portal = frame.current; + } + + if (!insidePortal) { + portal = rootRef.current; + } + + if (!portal) { + portal = document.createElement('div'); + portal.setAttribute('id', POPOVER_PORTAL_ID); + + if (typeof frame === 'string' && frame !== 'document') { + document.getElementById(frame)?.appendChild(portal); + } else { + document.body.appendChild(portal); + } + } + + portalRef.current = portal; + + /** + * Изменение стейта нужно для того, чтобы Popup + * отобразился после записи DOM элемента в portalRef.current + */ + forceRender(true); + }, []); + useEffect(() => { if (!isOpen || !forceUpdate) { return; @@ -164,40 +210,47 @@ export const popoverRoot = (Root: RootProps) => * вызов метода в очередь микрозадач. */ Promise.resolve().then(forceUpdate); - }, [isOpen, forceUpdate]); + }, [isOpen, children, forceUpdate]); return ( - - {target} - {children && ( - - {arrow && ( - + + {target} + + {children && + portalRef.current && + ReactDOM.createPortal( + + - {arrow} - - )} - {children} - - )} - + {hasArrow && ( + + )} + {children} + + , + portalRef.current, + )} + ); }, ); @@ -206,7 +259,13 @@ export const popoverConfig = { name: 'Popover', tag: 'div', layout: popoverRoot, - base: StyledRoot, - variations: {}, - defaults: {}, + base: '', + variations: { + view: { + css: viewCSS, + }, + }, + defaults: { + view: 'default', + }, }; diff --git a/packages/plasma-new-hope/src/components/Popover/Popover.types.ts b/packages/plasma-new-hope/src/components/Popover/Popover.types.ts index 1b353c3ff9..78de518dd8 100644 --- a/packages/plasma-new-hope/src/components/Popover/Popover.types.ts +++ b/packages/plasma-new-hope/src/components/Popover/Popover.types.ts @@ -1,10 +1,11 @@ +import { Placement, ComputedPlacement } from '@popperjs/core'; import type { HTMLAttributes, ReactNode, SyntheticEvent } from 'react'; -export type PopoverPlacementBasic = 'top' | 'bottom' | 'right' | 'left'; -export type PopoverPlacement = PopoverPlacementBasic | 'auto'; - export type PopoverTrigger = 'hover' | 'click'; +export type PopoverPlacementBasic = ComputedPlacement; +export type PopoverPlacement = Placement; + export type CustomPopoverProps = { /** * Всплывающее окно раскрыто или нет. @@ -28,14 +29,22 @@ export type CustomPopoverProps = { * [0, 0] */ offset?: [number, number]; + /** + * В каком контейнере позиционируется(по умолчанию document), можно также указать id элемента или ref для него. + */ + frame?: 'document' | string | React.RefObject; /** * Элемент, рядом с которым произойдет вызов всплывающего окна. */ target?: ReactNode; /** - * Стрелка над элементом. + * Есть ли стрелка над элементом. + */ + hasArrow?: boolean; + /** + * Значение z-index для Popover. */ - arrow?: ReactNode; + zIndex?: string; /** * Контент всплывающего окна. */ @@ -66,6 +75,14 @@ export type CustomPopoverProps = { * true */ closeOnEsc?: boolean; + /** + * Находится ли в портале. + * @default + * true + */ + insidePortal?: boolean; + + view?: string; }; export type PopoverProps = HTMLAttributes & CustomPopoverProps; diff --git a/packages/plasma-new-hope/src/components/Popover/index.ts b/packages/plasma-new-hope/src/components/Popover/index.ts index 2c2db18227..52c9949c03 100644 --- a/packages/plasma-new-hope/src/components/Popover/index.ts +++ b/packages/plasma-new-hope/src/components/Popover/index.ts @@ -1,2 +1,4 @@ export { popoverRoot, popoverConfig } from './Popover'; +export { classes as popoverClasses, tokens as popoverTokens } from './Popover.tokens'; + export type { PopoverProps, PopoverPlacement, PopoverTrigger } from './Popover.types'; diff --git a/packages/plasma-new-hope/src/components/Popover/styles/index.ts b/packages/plasma-new-hope/src/components/Popover/styles/index.ts deleted file mode 100644 index 2208712e16..0000000000 --- a/packages/plasma-new-hope/src/components/Popover/styles/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { css } from '@linaria/core'; -import { styled } from '@linaria/react'; - -export const StyledRoot = css` - position: relative; - box-sizing: border-box; - display: inline-flex; -`; - -export const StyledArrow = styled.div``; - -export const StyledPopover = styled.div` - position: absolute; - z-index: 1; - - &[data-popper-placement^='top'] > #popover-arrow { - bottom: -0.25rem; - } - - &[data-popper-placement^='bottom'] > #popover-arrow { - top: -0.25rem; - } - - &[data-popper-placement^='left'] > #popover-arrow { - right: -0.25rem; - } - - &[data-popper-placement^='right'] > #popover-arrow { - left: -0.25rem; - } -`; diff --git a/packages/plasma-new-hope/src/components/Popover/utils/index.ts b/packages/plasma-new-hope/src/components/Popover/utils/index.ts index 746da72b1a..0d23d3ca66 100644 --- a/packages/plasma-new-hope/src/components/Popover/utils/index.ts +++ b/packages/plasma-new-hope/src/components/Popover/utils/index.ts @@ -3,9 +3,10 @@ import type { Placement } from '@popperjs/core'; import type { PopoverPlacement } from '../Popover.types'; export const ESCAPE_KEYCODE = 27; +export const POPOVER_PORTAL_ID = 'plasma-popover-root'; export const getPlacement = (placement: PopoverPlacement) => { - return `${placement}-start` as Placement; + return `${placement}` as Placement; }; export const getAutoPlacements = (placements?: PopoverPlacement[]) => { diff --git a/packages/plasma-new-hope/src/components/Popover/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Popover/variations/_view/base.ts new file mode 100644 index 0000000000..cd585b76c4 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Popover/variations/_view/base.ts @@ -0,0 +1,3 @@ +import { css } from '@linaria/core'; + +export const base = css``; diff --git a/packages/plasma-new-hope/src/components/Popover/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Popover/variations/_view/tokens.json new file mode 100644 index 0000000000..ddd25980e1 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Popover/variations/_view/tokens.json @@ -0,0 +1,8 @@ +[ + "--plasma-popover-arrow-mask-width", + "--plasma-popover-arrow-mask-height", + "--plasma-popover-arrow-mask-image", + "--plasma-popover-arrow-height", + "--plasma-popover-arrow-edge-margin", + "--plasma-popover-arrow-background" +] diff --git a/packages/plasma-new-hope/src/components/Tooltip/Tooltip.styles.ts b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.styles.ts new file mode 100644 index 0000000000..c8a66847cd --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.styles.ts @@ -0,0 +1,42 @@ +import { styled } from '@linaria/react'; + +import { classes, tokens } from './Tooltip.tokens'; +import { TooltipProps } from './Tooltip.types'; + +export const TooltipRoot = styled.div>` + padding: var(${tokens.paddingTop}) var(${tokens.paddingRight}) var(${tokens.paddingBottom}) + var(${tokens.paddingLeft}); + min-height: var(${tokens.minHeight}); + box-sizing: border-box; + + background-color: var(${tokens.backgroundColor}); + border-radius: var(${tokens.borderRadius}); + box-shadow: var(${tokens.boxShadow}); + color: var(${tokens.color}); + + font-family: var(${tokens.textFontFamily}); + font-size: var(${tokens.textFontSize}); + font-style: var(${tokens.textFontStyle}); + font-weight: var(${tokens.textFontWeight}); + letter-spacing: var(${tokens.textFontLetterSpacing}); + line-height: var(${tokens.textFontLineHeight}); + + max-width: ${({ maxWidth }) => maxWidth || ''}; + min-width: ${({ minWidth }) => minWidth || ''}; + width: max-content; + word-break: break-word; + + pointer-events: none; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + &.${classes.hasContentLeft} { + display: flex; + } +`; + +export const StyledContentLeft = styled.div` + margin-right: var(${tokens.contentLeftMargin}); + height: var(${tokens.textFontLineHeight}); +`; diff --git a/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tokens.ts b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tokens.ts new file mode 100644 index 0000000000..d9e4b0a8df --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tokens.ts @@ -0,0 +1,25 @@ +export const classes = { + hasContentLeft: 'has-content-left', +}; + +export const tokens = { + paddingTop: '--plasma-tooltip-padding-top', + paddingRight: '--plasma-tooltip-padding-right', + paddingBottom: '--plasma-tooltip-padding-bottom', + paddingLeft: '--plasma-tooltip-padding-left', + minHeight: '--plasma-tooltip-min-height', + + backgroundColor: '--plasma-tooltip-background-color', + boxShadow: '--plasma-tooltip-box-shadow', + borderRadius: '--plasma-tooltip-border-radius', + + color: '--plasma-tooltip-color', + textFontFamily: '--plasma-tooltip-text-font-family', + textFontSize: '--plasma-tooltip-text-font-size', + textFontStyle: '--plasma-tooltip-text-font-style', + textFontWeight: '--plasma-tooltip-text-font-weight', + textFontLetterSpacing: '--plasma-tooltip-text-font-letter-spacing', + textFontLineHeight: '--plasma-tooltip-text-font-line-height', + + contentLeftMargin: '--plasma-tooltip-content-left-margin', +}; diff --git a/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tsx b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..a5df0f0055 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, forwardRef, ForwardRefExoticComponent, RefAttributes, useMemo } from 'react'; + +import { RootProps, component } from '../../engines'; +import { PopoverProps } from '../Popover'; +import { cx } from '../../utils'; + +import { TooltipProps, TooltipPropsWithConfig } from './Tooltip.types'; +import { StyledContentLeft, TooltipRoot } from './Tooltip.styles'; +import { base as viewCSS } from './variations/_view/base'; +import { base as sizeCSS } from './variations/_size/base'; +import { classes } from './Tooltip.tokens'; + +const ESCAPE_KEYCODE = 27; + +const getStringValue = (value?: number | string) => { + return typeof value === 'number' ? `${value}rem` : value; +}; + +/** + * Компонент для текстовых подсказок. Основное предназначение — подписи к блокам. + */ + +export const tooltipRoot = (Root: RootProps>) => + forwardRef( + ( + { + id, + text, + isOpen, + hasArrow = true, + offset = [0, 8], + minWidth, + maxWidth, + placement = 'bottom', + insidePortal = true, + target, + onDismiss, + view, + size, + contentLeft, + config, + ...rest + }, + outerRef, + ) => { + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.keyCode === ESCAPE_KEYCODE) { + onDismiss?.(); + } + }; + + window.addEventListener('keydown', onKeyDown); + + return () => { + window.removeEventListener('keydown', onKeyDown); + }; + }, []); + + const Popover = useMemo(() => component(config), [config]) as ForwardRefExoticComponent< + PopoverProps & RefAttributes + >; + + return ( + + + + {contentLeft && {contentLeft}} + {text} + + + + ); + }, + ); + +export const tooltipConfig = { + name: 'Tooltip', + tag: 'div', + layout: tooltipRoot, + base: '', + variations: { + view: { + css: viewCSS, + }, + size: { + css: sizeCSS, + }, + }, + defaults: { + view: 'default', + size: 'm', + }, +}; diff --git a/packages/plasma-new-hope/src/components/Tooltip/Tooltip.types.ts b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.types.ts new file mode 100644 index 0000000000..44039d96a7 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/Tooltip.types.ts @@ -0,0 +1,69 @@ +import { ReactNode, RefAttributes } from 'react'; + +import { PopoverPlacement, PopoverProps } from '../Popover'; +import { ComponentConfig, PropsType, Variants } from '../../engines/types'; + +export interface TooltipProps extends React.HTMLAttributes { + /** + * Текст тултипа. + */ + text: string; + /** + * Видимость тултипа. + */ + isOpen: boolean; + /** + * Элемент, рядом с которым произойдет вызов всплывающего окна. + */ + target?: ReactNode; + /** + * Направление раскрытия тултипа. + */ + placement?: PopoverPlacement; + /** + * Видимость стрелки (хвоста). + */ + hasArrow?: boolean; + /** + * Отступ окна относительно элемента, у которого оно вызвано. + * @default + * [0, 8] + */ + offset?: [number, number]; + /** + * В каком контейнере позиционируется(по умолчанию document), можно также указать id элемента или ref для него. + */ + frame?: 'document' | string | React.RefObject; + /** + * Находится ли в портале. + * @default + * true + */ + insidePortal?: boolean; + /** + * Минимальная ширина окна (в rem). + */ + minWidth?: number | string; + /** + * Максимальная ширина окна (в rem). + */ + maxWidth?: number | string; + /** + * Событие закрытия тултипа по кнопке Esc. + */ + onDismiss?: () => void; + /** + * Слот для контента слева, например `Icon`. + */ + contentLeft?: ReactNode; + + size?: string; + view?: string; +} + +export interface TooltipPropsWithConfig extends TooltipProps { + /** + * Конфиг компонента Popover. + */ + config: ComponentConfig, PopoverProps & RefAttributes>; +} diff --git a/packages/plasma-new-hope/src/components/Tooltip/index.ts b/packages/plasma-new-hope/src/components/Tooltip/index.ts new file mode 100644 index 0000000000..aaf9f44268 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/index.ts @@ -0,0 +1,3 @@ +export { tooltipRoot, tooltipConfig } from './Tooltip'; +export { tokens as tooltipTokens } from './Tooltip.tokens'; +export type { TooltipProps, TooltipPropsWithConfig } from './Tooltip.types'; diff --git a/packages/plasma-new-hope/src/components/Tooltip/variations/_size/base.ts b/packages/plasma-new-hope/src/components/Tooltip/variations/_size/base.ts new file mode 100644 index 0000000000..cd585b76c4 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/variations/_size/base.ts @@ -0,0 +1,3 @@ +import { css } from '@linaria/core'; + +export const base = css``; diff --git a/packages/plasma-new-hope/src/components/Tooltip/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/Tooltip/variations/_size/tokens.json new file mode 100644 index 0000000000..091f121cb4 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/variations/_size/tokens.json @@ -0,0 +1,15 @@ +[ + "--plasma-tooltip-padding-top", + "--plasma-tooltip-padding-right", + "--plasma-tooltip-padding-bottom", + "--plasma-tooltip-padding-left", + "--plasma-tooltip-min-height", + "--plasma-tooltip-border-radius", + "--plasma-tooltip-text-font-family", + "--plasma-tooltip-text-font-size", + "--plasma-tooltip-text-font-style", + "--plasma-tooltip-text-font-weight", + "--plasma-tooltip-text-font-letter-spacing", + "--plasma-tooltip-text-font-line-height", + "--plasma-tooltip-content-left-margin" +] diff --git a/packages/plasma-new-hope/src/components/Tooltip/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Tooltip/variations/_view/base.ts new file mode 100644 index 0000000000..cd585b76c4 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/variations/_view/base.ts @@ -0,0 +1,3 @@ +import { css } from '@linaria/core'; + +export const base = css``; diff --git a/packages/plasma-new-hope/src/components/Tooltip/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Tooltip/variations/_view/tokens.json new file mode 100644 index 0000000000..6c8c4c3c8c --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tooltip/variations/_view/tokens.json @@ -0,0 +1 @@ +["--plasma-tooltip-color", "--plasma-tooltip-background-color", "--plasma-tooltip-box-shadow"] diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.config.ts new file mode 100644 index 0000000000..9d74706cc5 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.config.ts @@ -0,0 +1,23 @@ +import { css } from '@linaria/core'; + +import { popoverTokens } from '../../../../components/Popover'; + +export const config = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 16px; + ${String(popoverTokens.arrowMaskHeight)}: 16px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMCw5Ljg1bDE2LDBjLTQuNDEsMCAtOCwyLjY5IC04LDZjMCwtMy4zMSAtMy41OSwtNiAtOCwtNnoiIGZpbGw9IiMxNzE3MTciIGZpbGwtcnVsZT0iZXZlbm9kZCIgaWQ9IlRhaWwiLz4KPC9zdmc+"); + ${String(popoverTokens.arrowHeight)}: 6px; + ${String(popoverTokens.arrowEdgeMargin)}: 9px; + ${String(popoverTokens.arrowBackground)}: var(--plasma-colors-surface-solid03); + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.stories.tsx index 67be913224..5dae0ca651 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.stories.tsx @@ -76,27 +76,10 @@ type StoryPopoverProps = ComponentProps & { distance?: number; }; -const StyledArrow = styled.div` - visibility: hidden; - - &, - &::before { - position: absolute; - width: 0.5rem; - height: 0.5rem; - background: var(--plasma-colors-surface-solid03); - } - - &::before { - visibility: visible; - content: ''; - transform: rotate(45deg); - } -`; - const StyledContent = styled.div` background: var(--plasma-colors-surface-solid03); padding: 1rem; + border-radius: 0.5rem; display: flex; flex-direction: column; @@ -112,16 +95,18 @@ const StoryDefault = (args: StoryPopoverProps) => { setIsOpen(is)} + frame="theme-root" + insidePortal={false} role="presentation" id="popover" target={} - arrow={} + hasArrow offset={[skidding, distance]} {...args} > <>Content - + ); diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.ts index 9219af7569..21392e43fa 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.ts +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Popover/Popover.ts @@ -1,8 +1,14 @@ -import { popoverConfig } from '../../../../components/Popover'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; + +import { PopoverProps, popoverConfig } from '../../../../components/Popover'; import { component, mergeConfig } from '../../../../engines'; +import { config } from './Popover.config'; + export type { PopoverProps, PopoverPlacement, PopoverTrigger } from '../../../..'; -const mergedConfig = mergeConfig(popoverConfig); +const mergedConfig = mergeConfig(popoverConfig, config); -export const Popover = component(mergedConfig); +export const Popover = component(mergedConfig) as ForwardRefExoticComponent< + PopoverProps & RefAttributes +>; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.config.ts new file mode 100644 index 0000000000..7ff16a13f9 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.config.ts @@ -0,0 +1,98 @@ +import { css } from '@linaria/core'; + +import { tooltipTokens } from '../../../../components/Tooltip'; +import { popoverTokens } from '../../../../components/Popover'; + +export const config = { + defaults: { + view: 'default', + size: 'm', + }, + variations: { + size: { + s: css` + ${tooltipTokens.paddingTop}: 0.5rem; + ${tooltipTokens.paddingRight}: 0.75rem; + ${tooltipTokens.paddingBottom}: 0.5rem; + ${tooltipTokens.paddingLeft}: 0.75rem; + + ${tooltipTokens.minHeight}: 2rem; + ${tooltipTokens.borderRadius}: 0.5rem; + + ${tooltipTokens.textFontFamily}: var(--plasma-typo-body-xs-font-family); + ${tooltipTokens.textFontSize}: var(--plasma-typo-body-xs-font-size); + ${tooltipTokens.textFontStyle}: var(--plasma-typo-body-xs-font-style); + ${tooltipTokens.textFontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tooltipTokens.textFontLetterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tooltipTokens.textFontLineHeight}: var(--plasma-typo-body-xs-line-height); + + ${tooltipTokens.contentLeftMargin}: 0.25rem; + `, + m: css` + ${tooltipTokens.paddingTop}: 0.6875rem; + ${tooltipTokens.paddingRight}: 0.875rem; + ${tooltipTokens.paddingBottom}: 0.6875rem; + ${tooltipTokens.paddingLeft}: 0.875rem; + + ${tooltipTokens.minHeight}: 2.5rem; + ${tooltipTokens.borderRadius}: 0.625rem; + + ${tooltipTokens.textFontFamily}: var(--plasma-typo-body-s-font-family); + ${tooltipTokens.textFontSize}: var(--plasma-typo-body-s-font-size); + ${tooltipTokens.textFontStyle}: var(--plasma-typo-body-s-font-style); + ${tooltipTokens.textFontWeight}: var(--plasma-typo-body-s-font-weight); + ${tooltipTokens.textFontLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tooltipTokens.textFontLineHeight}: var(--plasma-typo-body-s-line-height); + + ${tooltipTokens.contentLeftMargin}: 0.375rem; + `, + }, + view: { + default: css` + ${tooltipTokens.backgroundColor}: var(--surface-solid-card); + ${tooltipTokens.boxShadow}: var(--shadow-down-hard-s); + ${tooltipTokens.color}: var(--text-primary); + `, + }, + }, +}; + +export const popoverCustomConfigS = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 16px; + ${String(popoverTokens.arrowMaskHeight)}: 16px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMCw5Ljg1bDE2LDBjLTQuNDEsMCAtOCwyLjY5IC04LDZjMCwtMy4zMSAtMy41OSwtNiAtOCwtNnoiIGZpbGw9IiMxNzE3MTciIGZpbGwtcnVsZT0iZXZlbm9kZCIgaWQ9IlRhaWwiLz4KPC9zdmc+"); + ${String(popoverTokens.arrowHeight)}: 6px; + ${String(popoverTokens.arrowEdgeMargin)}: 9px; + ${String(popoverTokens.arrowBackground)}: var(--surface-solid-card); + `, + }, + }, +}; + +export const popoverCustomConfigM = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 20px; + ${String(popoverTokens.arrowMaskHeight)}: 20px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMC4xNywxMS44M2wyMCwwYy01LjUyLDAgLTEwLDMuNTkgLTEwLDhjMCwtNC40MSAtNC40OCwtOCAtMTAsLTh6IiBmaWxsPSIjMTcxNzE3IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGlkPSJUYWlsIi8+Cjwvc3ZnPg=="); + ${String(popoverTokens.arrowHeight)}: 8px; + ${String(popoverTokens.arrowEdgeMargin)}: 0.625rem; + ${String(popoverTokens.arrowBackground)}: var(--surface-solid-card); + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..1816c718a0 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,200 @@ +import React, { useState } from 'react'; +import { styled } from '@linaria/react'; +import type { StoryObj, Meta } from '@storybook/react'; + +import { WithTheme } from '../../../_helpers'; +import { Button } from '../Button/Button'; +import { PopoverPlacement } from '../Popover/Popover'; +import { IconDisclosureRight } from '../../../../components/_Icon'; + +import { Tooltip } from './Tooltip'; +import type { TooltipProps } from './Tooltip'; + +const placements: Array = [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-start', + 'left-end', + + 'right', + 'right-start', + 'right-end', + + 'auto', +]; + +const meta: Meta = { + title: 'plasma_b2c/Tooltip', + decorators: [WithTheme], +}; + +export default meta; + +const StyledGrid = styled.div` + display: grid; + grid-template-columns: repeat(3, max-content); + grid-gap: 1rem 3.5rem; + padding: 3.5rem; +`; + +const StoryDefault = ({ size }: TooltipProps) => { + return ( + + Btn} + placement="left" + size={size} + isOpen + hasArrow + text="left" + frame="theme-root" + /> + } + placement="top-start" + size={size} + isOpen + hasArrow + text="top-start" + frame="theme-root" + /> + Btn} + placement="top" + size={size} + isOpen + hasArrow + text="top" + frame="theme-root" + /> + Btn} + placement="right" + size={size} + isOpen + hasArrow + text="right" + frame="theme-root" + /> + } + placement="top-end" + size={size} + isOpen + hasArrow + text="top-end" + frame="theme-root" + /> + Btn} + placement="bottom-start" + size={size} + isOpen + hasArrow + text="bottom-start" + frame="theme-root" + /> + Btn} + placement="bottom" + size={size} + isOpen + hasArrow + text="bottom" + frame="theme-root" + /> + Btn} + placement="bottom-end" + size={size} + isOpen + hasArrow + text="bottom-end" + frame="theme-root" + /> + + ); +}; + +export const Default: StoryObj = { + argTypes: { + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + size: 'm', + }, + render: (args) => , +}; + +const StyledRow = styled.div` + display: flex; + width: 150vw; + height: 150vh; + padding: 10rem; +`; + +const StoryLive = (args: TooltipProps) => { + const [text, setText] = useState('Type here'); + + return ( + <> + + setText(e.target.value)} + aria-describedby="example-tooltip-firstname" + /> + } + contentLeft={} + {...args} + id="example-tooltip-firstname" + text={text} + isOpen + frame="theme-root" + /> + + + ); +}; + +export const Live: StoryObj = { + argTypes: { + placement: { + options: placements, + control: { + type: 'select', + }, + }, + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + placement: 'bottom', + maxWidth: 10, + minWidth: 3, + hasArrow: true, + size: 'm', + }, + render: (args) => , +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..3420e28e66 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tooltip/Tooltip.tsx @@ -0,0 +1,22 @@ +import React, { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react'; + +import { TooltipProps, TooltipPropsWithConfig, tooltipConfig } from '../../../../components/Tooltip'; +import { component, mergeConfig } from '../../../../engines'; +import { popoverConfig } from '../../../../components/Popover'; + +import { config, popoverCustomConfigS, popoverCustomConfigM } from './Tooltip.config'; + +export type { TooltipProps } from '../../../../components/Tooltip'; + +const mergedPopoverConfigS = mergeConfig(popoverConfig, popoverCustomConfigS); +const mergedPopoverConfigM = mergeConfig(popoverConfig, popoverCustomConfigM); +const mergedConfig = mergeConfig(tooltipConfig, config); + +export const TooltipComponent = component(mergedConfig) as ForwardRefExoticComponent< + TooltipPropsWithConfig & RefAttributes +>; + +export const Tooltip = forwardRef((props, outerRef) => { + const popoverInnerConfig = props.size === 'm' ? mergedPopoverConfigM : mergedPopoverConfigS; + return ; +}); diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.config.ts new file mode 100644 index 0000000000..7ff16a13f9 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.config.ts @@ -0,0 +1,98 @@ +import { css } from '@linaria/core'; + +import { tooltipTokens } from '../../../../components/Tooltip'; +import { popoverTokens } from '../../../../components/Popover'; + +export const config = { + defaults: { + view: 'default', + size: 'm', + }, + variations: { + size: { + s: css` + ${tooltipTokens.paddingTop}: 0.5rem; + ${tooltipTokens.paddingRight}: 0.75rem; + ${tooltipTokens.paddingBottom}: 0.5rem; + ${tooltipTokens.paddingLeft}: 0.75rem; + + ${tooltipTokens.minHeight}: 2rem; + ${tooltipTokens.borderRadius}: 0.5rem; + + ${tooltipTokens.textFontFamily}: var(--plasma-typo-body-xs-font-family); + ${tooltipTokens.textFontSize}: var(--plasma-typo-body-xs-font-size); + ${tooltipTokens.textFontStyle}: var(--plasma-typo-body-xs-font-style); + ${tooltipTokens.textFontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tooltipTokens.textFontLetterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tooltipTokens.textFontLineHeight}: var(--plasma-typo-body-xs-line-height); + + ${tooltipTokens.contentLeftMargin}: 0.25rem; + `, + m: css` + ${tooltipTokens.paddingTop}: 0.6875rem; + ${tooltipTokens.paddingRight}: 0.875rem; + ${tooltipTokens.paddingBottom}: 0.6875rem; + ${tooltipTokens.paddingLeft}: 0.875rem; + + ${tooltipTokens.minHeight}: 2.5rem; + ${tooltipTokens.borderRadius}: 0.625rem; + + ${tooltipTokens.textFontFamily}: var(--plasma-typo-body-s-font-family); + ${tooltipTokens.textFontSize}: var(--plasma-typo-body-s-font-size); + ${tooltipTokens.textFontStyle}: var(--plasma-typo-body-s-font-style); + ${tooltipTokens.textFontWeight}: var(--plasma-typo-body-s-font-weight); + ${tooltipTokens.textFontLetterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tooltipTokens.textFontLineHeight}: var(--plasma-typo-body-s-line-height); + + ${tooltipTokens.contentLeftMargin}: 0.375rem; + `, + }, + view: { + default: css` + ${tooltipTokens.backgroundColor}: var(--surface-solid-card); + ${tooltipTokens.boxShadow}: var(--shadow-down-hard-s); + ${tooltipTokens.color}: var(--text-primary); + `, + }, + }, +}; + +export const popoverCustomConfigS = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 16px; + ${String(popoverTokens.arrowMaskHeight)}: 16px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMCw5Ljg1bDE2LDBjLTQuNDEsMCAtOCwyLjY5IC04LDZjMCwtMy4zMSAtMy41OSwtNiAtOCwtNnoiIGZpbGw9IiMxNzE3MTciIGZpbGwtcnVsZT0iZXZlbm9kZCIgaWQ9IlRhaWwiLz4KPC9zdmc+"); + ${String(popoverTokens.arrowHeight)}: 6px; + ${String(popoverTokens.arrowEdgeMargin)}: 9px; + ${String(popoverTokens.arrowBackground)}: var(--surface-solid-card); + `, + }, + }, +}; + +export const popoverCustomConfigM = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 20px; + ${String(popoverTokens.arrowMaskHeight)}: 20px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggY2xpcC1ydWxlPSJldmVub2RkIiBkPSJtMC4xNywxMS44M2wyMCwwYy01LjUyLDAgLTEwLDMuNTkgLTEwLDhjMCwtNC40MSAtNC40OCwtOCAtMTAsLTh6IiBmaWxsPSIjMTcxNzE3IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGlkPSJUYWlsIi8+Cjwvc3ZnPg=="); + ${String(popoverTokens.arrowHeight)}: 8px; + ${String(popoverTokens.arrowEdgeMargin)}: 0.625rem; + ${String(popoverTokens.arrowBackground)}: var(--surface-solid-card); + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..61c81b2bc2 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,200 @@ +import React, { useState } from 'react'; +import { styled } from '@linaria/react'; +import type { StoryObj, Meta } from '@storybook/react'; + +import { WithTheme } from '../../../_helpers'; +import { Button } from '../Button/Button'; +import { PopoverPlacement } from '../Popover/Popover'; +import { IconDisclosureRight } from '../../../../components/_Icon'; + +import { Tooltip } from './Tooltip'; +import type { TooltipProps } from './Tooltip'; + +const placements: Array = [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-start', + 'left-end', + + 'right', + 'right-start', + 'right-end', + + 'auto', +]; + +const meta: Meta = { + title: 'plasma_web/Tooltip', + decorators: [WithTheme], +}; + +export default meta; + +const StyledGrid = styled.div` + display: grid; + grid-template-columns: repeat(3, max-content); + grid-gap: 1rem 3.5rem; + padding: 3.5rem; +`; + +const StoryDefault = ({ size }: TooltipProps) => { + return ( + + Btn} + placement="left" + size={size} + isOpen + hasArrow + text="left" + frame="theme-root" + /> + } + placement="top-start" + size={size} + isOpen + hasArrow + text="top-start" + frame="theme-root" + /> + Btn} + placement="top" + size={size} + isOpen + hasArrow + text="top" + frame="theme-root" + /> + Btn} + placement="right" + size={size} + isOpen + hasArrow + text="right" + frame="theme-root" + /> + } + placement="top-end" + size={size} + isOpen + hasArrow + text="top-end" + frame="theme-root" + /> + Btn} + placement="bottom-start" + size={size} + isOpen + hasArrow + text="bottom-start" + frame="theme-root" + /> + Btn} + placement="bottom" + size={size} + isOpen + hasArrow + text="bottom" + frame="theme-root" + /> + Btn} + placement="bottom-end" + size={size} + isOpen + hasArrow + text="bottom-end" + frame="theme-root" + /> + + ); +}; + +export const Default: StoryObj = { + argTypes: { + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + size: 'm', + }, + render: (args) => , +}; + +const StyledRow = styled.div` + display: flex; + width: 150vw; + height: 150vh; + padding: 10rem; +`; + +const StoryLive = (args: TooltipProps) => { + const [text, setText] = useState('Type here'); + + return ( + <> + + setText(e.target.value)} + aria-describedby="example-tooltip-firstname" + /> + } + contentLeft={} + {...args} + id="example-tooltip-firstname" + text={text} + isOpen + frame="theme-root" + /> + + + ); +}; + +export const Live: StoryObj = { + argTypes: { + placement: { + options: placements, + control: { + type: 'select', + }, + }, + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + placement: 'bottom', + maxWidth: 10, + minWidth: 3, + hasArrow: true, + size: 'm', + }, + render: (args) => , +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..3420e28e66 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tooltip/Tooltip.tsx @@ -0,0 +1,22 @@ +import React, { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react'; + +import { TooltipProps, TooltipPropsWithConfig, tooltipConfig } from '../../../../components/Tooltip'; +import { component, mergeConfig } from '../../../../engines'; +import { popoverConfig } from '../../../../components/Popover'; + +import { config, popoverCustomConfigS, popoverCustomConfigM } from './Tooltip.config'; + +export type { TooltipProps } from '../../../../components/Tooltip'; + +const mergedPopoverConfigS = mergeConfig(popoverConfig, popoverCustomConfigS); +const mergedPopoverConfigM = mergeConfig(popoverConfig, popoverCustomConfigM); +const mergedConfig = mergeConfig(tooltipConfig, config); + +export const TooltipComponent = component(mergedConfig) as ForwardRefExoticComponent< + TooltipPropsWithConfig & RefAttributes +>; + +export const Tooltip = forwardRef((props, outerRef) => { + const popoverInnerConfig = props.size === 'm' ? mergedPopoverConfigM : mergedPopoverConfigS; + return ; +}); diff --git a/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.config.ts b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.config.ts new file mode 100644 index 0000000000..4499834e9b --- /dev/null +++ b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.config.ts @@ -0,0 +1,60 @@ +import { css } from '@linaria/core'; + +import { tooltipTokens } from '../../../../components/Tooltip'; +import { popoverTokens } from '../../../../components/Popover'; + +export const config = { + defaults: { + view: 'default', + size: 'm', + }, + variations: { + size: { + m: css` + ${tooltipTokens.paddingTop}: 0.25rem; + ${tooltipTokens.paddingRight}: 0.5rem; + ${tooltipTokens.paddingBottom}: 0.25rem; + ${tooltipTokens.paddingLeft}: 0.5rem; + + ${tooltipTokens.minHeight}: 1.5rem; + ${tooltipTokens.borderRadius}: 0.25rem; + + ${tooltipTokens.textFontFamily}: var(--plasma-typo-body-s-font-family); + ${tooltipTokens.textFontSize}: var(--plasma-typo-body-s-font-size); + ${tooltipTokens.textFontStyle}: var(--plasma-typo-body-s-font-style); + ${tooltipTokens.textFontWeight}: 500; + ${tooltipTokens.textFontLetterSpacing}: 0px; + ${tooltipTokens.textFontLineHeight}: 1rem; + + ${tooltipTokens.contentLeftMargin}: 0.25rem; + `, + }, + view: { + default: css` + ${tooltipTokens.backgroundColor}: #475056; + ${tooltipTokens.boxShadow}: 0px 1px 2px 0px rgba(2, 13, 76, 0.15),0px 3.6px 7.1px 0px rgba(2, 13, 76, 0.12),0px 16px 32px 0px rgba(2, 13, 76, 0.2); + ${tooltipTokens.color}: var(--on-dark-text-primary); + `, + }, + }, +}; + +export const popoverCustomConfig = { + defaults: { + view: 'default', + }, + variations: { + view: { + default: css` + ${String(popoverTokens.arrowMaskWidth)}: 16px; + ${String(popoverTokens.arrowMaskHeight)}: 16px; + ${String( + popoverTokens.arrowMaskImage, + )}: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0ibTgsMTUuNzhsLTIuMjQsLTQuMzRjLTAuOTIsLTEuNzkgLTEuMzgsLTIuNjggLTIuMTUsLTMuMTdjLTAuNzcsLTAuNDkgLTEuNzIsLTAuNDkgLTMuNjEsLTAuNDlsMTYsMGMtMS44OSwwIC0yLjgzLDAgLTMuNiwwLjQ5Yy0wLjc4LDAuNDkgLTEuMjQsMS4zOCAtMi4xNiwzLjE2bC0yLjI0LDQuMzV6IiBmaWxsPSIjMTcxNzE3IiBmaWxsLW9wYWNpdHk9IjEiIGZpbGwtcnVsZT0iZXZlbm9kZCIgaWQ9InBvaW50ZXIiLz4KPC9zdmc+"); + ${String(popoverTokens.arrowHeight)}: 8px; + ${String(popoverTokens.arrowEdgeMargin)}: 0.625rem; + ${String(popoverTokens.arrowBackground)}: #475056; + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.stories.tsx b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..4d0061a2af --- /dev/null +++ b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,200 @@ +import React, { useState } from 'react'; +import { styled } from '@linaria/react'; +import type { StoryObj, Meta } from '@storybook/react'; + +import { WithTheme } from '../../../_helpers'; +import { Button } from '../Button/Button'; +import { PopoverPlacement } from '../Popover/Popover'; +import { IconDisclosureRight } from '../../../../components/_Icon'; + +import { Tooltip } from './Tooltip'; +import type { TooltipProps } from './Tooltip'; + +const placements: Array = [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-start', + 'left-end', + + 'right', + 'right-start', + 'right-end', + + 'auto', +]; + +const meta: Meta = { + title: 'sds_engineer/Tooltip', + decorators: [WithTheme], +}; + +export default meta; + +const StyledGrid = styled.div` + display: grid; + grid-template-columns: repeat(3, max-content); + grid-gap: 1rem 3.5rem; + padding: 3.5rem; +`; + +const StoryDefault = ({ size }: TooltipProps) => { + return ( + + Btn} + placement="left" + size={size} + isOpen + hasArrow + text="left" + frame="theme-root" + /> + } + placement="top-start" + size={size} + isOpen + hasArrow + text="top-start" + frame="theme-root" + /> + Btn} + placement="top" + size={size} + isOpen + hasArrow + text="top" + frame="theme-root" + /> + Btn} + placement="right" + size={size} + isOpen + hasArrow + text="right" + frame="theme-root" + /> + } + placement="top-end" + size={size} + isOpen + hasArrow + text="top-end" + frame="theme-root" + /> + Btn} + placement="bottom-start" + size={size} + isOpen + hasArrow + text="bottom-start" + frame="theme-root" + /> + Btn} + placement="bottom" + size={size} + isOpen + hasArrow + text="bottom" + frame="theme-root" + /> + Btn} + placement="bottom-end" + size={size} + isOpen + hasArrow + text="bottom-end" + frame="theme-root" + /> + + ); +}; + +export const Default: StoryObj = { + argTypes: { + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + size: 'm', + }, + render: (args) => , +}; + +const StyledRow = styled.div` + display: flex; + width: 150vw; + height: 150vh; + padding: 10rem; +`; + +const StoryLive = (args: TooltipProps) => { + const [text, setText] = useState('Type here'); + + return ( + <> + + setText(e.target.value)} + aria-describedby="example-tooltip-firstname" + /> + } + contentLeft={} + {...args} + id="example-tooltip-firstname" + text={text} + isOpen + frame="theme-root" + /> + + + ); +}; + +export const Live: StoryObj = { + argTypes: { + placement: { + options: placements, + control: { + type: 'select', + }, + }, + size: { + options: ['m', 's'], + control: { + type: 'select', + }, + }, + }, + args: { + placement: 'bottom', + maxWidth: 10, + minWidth: 3, + hasArrow: true, + size: 'm', + }, + render: (args) => , +}; diff --git a/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.tsx b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..30e0a6d03a --- /dev/null +++ b/packages/plasma-new-hope/src/examples/sds_engineer/components/Tooltip/Tooltip.tsx @@ -0,0 +1,20 @@ +import React, { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react'; + +import { TooltipProps, TooltipPropsWithConfig, tooltipConfig } from '../../../../components/Tooltip'; +import { component, mergeConfig } from '../../../../engines'; +import { popoverConfig } from '../../../../components/Popover'; + +import { config, popoverCustomConfig } from './Tooltip.config'; + +export type { TooltipProps } from '../../../../components/Tooltip'; + +const mergedPopoverConfig = mergeConfig(popoverConfig, popoverCustomConfig); +const mergedConfig = mergeConfig(tooltipConfig, config); + +export const TooltipComponent = component(mergedConfig) as ForwardRefExoticComponent< + TooltipPropsWithConfig & RefAttributes +>; + +export const Tooltip = forwardRef((props, outerRef) => { + return ; +}); diff --git a/packages/plasma-new-hope/src/examples/themes/sds_engineer.module.css b/packages/plasma-new-hope/src/examples/themes/sds_engineer.module.css index ec04031c30..e1ffa8b3d0 100644 --- a/packages/plasma-new-hope/src/examples/themes/sds_engineer.module.css +++ b/packages/plasma-new-hope/src/examples/themes/sds_engineer.module.css @@ -318,5 +318,4 @@ --plasma-colors-button-critical: var(--surface-negative); color: var(--text-primary); background-color: var(--background-primary); -} - +} \ No newline at end of file diff --git a/packages/plasma-new-hope/src/index.ts b/packages/plasma-new-hope/src/index.ts index 28aafa2890..483570d4f2 100644 --- a/packages/plasma-new-hope/src/index.ts +++ b/packages/plasma-new-hope/src/index.ts @@ -16,3 +16,4 @@ export * from './components/Modal'; export * from './components/Notification'; export * from './components/Popover'; export * from './components/Dropdown'; +export * from './components/Tooltip';