diff --git a/.github/add-a-feature.md b/.github/add-a-feature.md index dc5ea3fd7f..02f568e43e 100644 --- a/.github/add-a-feature.md +++ b/.github/add-a-feature.md @@ -3,6 +3,7 @@ + - [Propose feature](#propose-feature) - [Prototype](#prototype) - [Spec out the API](#spec-out-the-api) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1bee8b163..87653724b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Simplify rendering when tooltip is not visible @jurokapsiar ([#1981](https://github.com/stardust-ui/react/pull/1981)) - Add `thumbtack`, `thumbtack-slash` and `question-circle` icons to Teams theme @codepretty ([#2000](https://github.com/stardust-ui/react/pull/2000)) +### Documentation +- Copy to clipboard prototype - attached confirmation @jurokapsiar ([#1900](https://github.com/stardust-ui/react/pull/1900)) + + ## [v0.39.0](https://github.com/stardust-ui/react/tree/v0.39.0) (2019-09-23) [Compare changes](https://github.com/stardust-ui/react/compare/v0.38.1...v0.39.0) diff --git a/docs/src/prototypes/CopyToClipboard/CopyToClipboard.tsx b/docs/src/prototypes/CopyToClipboard/CopyToClipboard.tsx index 8a80f25009..9b1341c210 100644 --- a/docs/src/prototypes/CopyToClipboard/CopyToClipboard.tsx +++ b/docs/src/prototypes/CopyToClipboard/CopyToClipboard.tsx @@ -1,41 +1,25 @@ -import { Text, Tooltip, ShorthandValue, TextProps, TooltipProps } from '@stardust-ui/react' +import { ShorthandValue, Tooltip, TooltipProps } from '@stardust-ui/react' import * as copyToClipboard from 'copy-to-clipboard' import * as _ from 'lodash' import * as React from 'react' -import { NotificationContext } from './NotificationProvider' +import { Notification, NotificationContext } from './NotificationProvider' export type CopyToClipboardProps = { + tooltip?: ShorthandValue attached?: boolean - pointing?: boolean + target?: HTMLElement + notification?: React.ReactNode timeout?: number value: string - - noticeText?: ShorthandValue - promptText?: ShorthandValue - - align?: TooltipProps['align'] - position?: TooltipProps['position'] - trigger: JSX.Element } const CopyToClipboard: React.FC = props => { - const { - align, - attached, - noticeText, - pointing, - position, - promptText, - timeout, - trigger, - value, - } = props + const { value, trigger, tooltip, attached, notification, timeout, target } = props const setNotification = React.useContext(NotificationContext) const [copied, setCopied] = React.useState(false) - const [promptOpen, setPromptOpen] = React.useState(false) const timeoutId = React.useRef() React.useEffect(() => { @@ -50,7 +34,7 @@ const CopyToClipboard: React.FC = props => { (e: React.MouseEvent, ...args) => { setCopied(true) if (!attached) { - setNotification(Text.create(noticeText), timeout) + setNotification(notification, target, timeout) } copyToClipboard(value) @@ -59,31 +43,27 @@ const CopyToClipboard: React.FC = props => { [value], ) - const tooltipContent = copied - ? { content: Text.create(noticeText), variables: { primary: true } } - : { - content: Text.create(promptText), - variables: { basic: true }, - } - const tooltipOpen = (promptOpen && !copied) || (copied && attached) - return ( - setPromptOpen(data.open)} - open={tooltipOpen} - trigger={React.cloneElement(trigger, { onClick: handleTriggerClick })} - /> - ) + const renderedTrigger = React.cloneElement(trigger, { onClick: handleTriggerClick }) + + if (copied && attached) { + return + } + + if (copied || !tooltip) { + return renderedTrigger + } + + return Tooltip.create(tooltip, { + overrideProps: { + trigger: renderedTrigger, + children: undefined, // force-reset `children` defined for `Tooltip` as it collides with the `trigger + }, + }) } CopyToClipboard.defaultProps = { - align: 'center', - noticeText: 'Copied to clipboard', - position: 'below', - promptText: 'Click to copy', + notification: 'Copied to clipboard', + tooltip: 'Click to copy', timeout: 4000, } diff --git a/docs/src/prototypes/CopyToClipboard/NotificationProvider.tsx b/docs/src/prototypes/CopyToClipboard/NotificationProvider.tsx index 3d2ae5cf66..7ceb7f17fb 100644 --- a/docs/src/prototypes/CopyToClipboard/NotificationProvider.tsx +++ b/docs/src/prototypes/CopyToClipboard/NotificationProvider.tsx @@ -1,11 +1,18 @@ -import { Portal, createComponent } from '@stardust-ui/react' +import { Portal, Tooltip, createComponent, TooltipProps } from '@stardust-ui/react' import * as React from 'react' type NotificationProps = { - children?: React.ReactNode + content: React.ReactNode + target?: HTMLElement + trigger?: JSX.Element } -type NotificationContextValue = (value: React.ReactNode, timeout: number) => void +type NotificationContextValue = ( + content: React.ReactNode, + target: HTMLElement | null, + timeout: number, +) => void + export const NotificationContext = React.createContext(() => { throw new Error('No matching NotificationContext.Provider') }) @@ -13,12 +20,15 @@ export const NotificationContext = React.createContext export const NotificationProvider: React.FC = props => { const { children } = props const [notification, setNotification] = React.useState() + const [target, setTarget] = React.useState() const timeoutId = React.useRef() - const update = React.useCallback((value, timeout) => { - setNotification(value) + const update = React.useCallback((notification, target, timeout) => { + setNotification(notification) + setTarget(target) timeoutId.current = window.setTimeout(() => { setNotification(null) + setTarget(null) }, timeout) }, []) @@ -28,9 +38,7 @@ export const NotificationProvider: React.FC = props => { return ( <> - - {notification} - + {!!notification && } {children} ) @@ -38,13 +46,30 @@ export const NotificationProvider: React.FC = props => { export const Notification = createComponent({ displayName: 'Notification', - render: ({ children, stardust: { classes } }) => { + render: ({ target, trigger, content, stardust: { classes } }) => { + const tooltipProps: TooltipProps = { + content, + open: true, + pointing: false, + target, + trigger, + } + + if (target || trigger) { + return Tooltip.create({ ...tooltipProps, offset: '0 10' }) + } + return ( -
-
-
{children}
+ +
+
+ {Tooltip.create({ + ...tooltipProps, + trigger:
, + })} +
-
+
) }, }) diff --git a/docs/src/prototypes/CopyToClipboard/index.tsx b/docs/src/prototypes/CopyToClipboard/index.tsx index 966643c77a..e9cadac0c4 100644 --- a/docs/src/prototypes/CopyToClipboard/index.tsx +++ b/docs/src/prototypes/CopyToClipboard/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Flex, Provider, Text, Button, Menu } from '@stardust-ui/react' +import { Flex, Provider, Text, Button, Menu, Ref } from '@stardust-ui/react' import CopyToClipboard from './CopyToClipboard' import { PrototypeSection, ComponentPrototype } from '../Prototypes' import themeOverrides from './themeOverrides' @@ -7,6 +7,7 @@ import { NotificationProvider } from './NotificationProvider' type CopyToClipboardPrototypeProps = { value: string + target?: HTMLElement attached?: boolean } @@ -18,7 +19,7 @@ const CopyToClipboardPrototype: React.FC = props } /> @@ -27,27 +28,51 @@ const CopyToClipboardPrototype: React.FC = props } const CopyToClipboardInMenu: React.FC = props => { + const item = { + key: 'edit', + content: 'Edit', + menu: [ + 'Open File...', + 'Save File...', + render => + render('Copy text', (Component, props) => { + return } /> + }), + ], + } + + return +} + +const CopyToClipboardAttached: React.FC = props => { + const [target, setTarget] = React.useState(null) + + const item = { + key: 'edit', + content: 'Edit', + menu: [ + 'Open File...', + 'Save File...', + render => + render('Copy text', (Component, props) => { + return ( + } + /> + ) + }), + ], + } + const items = [ - { - key: 'edit', - content: 'Edit', - menu: [ - 'Open File...', - 'Save File...', - render => - render('Copy text', (Component, props) => { - return ( - } - /> - ) - }), - ], - }, + render => + render(item, (Component, props) => ( + + + + )), ] return } @@ -66,7 +91,7 @@ const CopyToClipboardPrototypes: React.FC = () => { title="Attached" description="Attached version of Copy to Clipboard prototype" > - + { > + + + diff --git a/docs/src/prototypes/CopyToClipboard/themeOverrides.ts b/docs/src/prototypes/CopyToClipboard/themeOverrides.ts index a0371c97a9..3fbb0b8c02 100644 --- a/docs/src/prototypes/CopyToClipboard/themeOverrides.ts +++ b/docs/src/prototypes/CopyToClipboard/themeOverrides.ts @@ -1,46 +1,11 @@ -import { - ComponentSlotStylesInput, - ComponentVariablesInput, - pxToRem, - ThemeInput, - TooltipProps, -} from '@stardust-ui/react' - -type NotificationVariables = { - contentBackgroundColor: string - contentColor: string - contentFontSize: string - contentFontWeight: number - contentPadding: string -} - -type NotificationTooltipVariables = { - basic?: boolean - primary?: boolean - - basicContentBackgroundColor: string - basicContentColor: string - - primaryContentBackgroundColor: string - primaryContentColor: string -} +import { ComponentSlotStylesInput, ThemeInput } from '@stardust-ui/react' type ThemeOverrides = ThemeInput & { componentStyles: { - Notification: ComponentSlotStylesInput<{}, NotificationVariables> - TooltipContent: ComponentSlotStylesInput - } - componentVariables: { - Notification: ComponentVariablesInput - TooltipContent: ComponentVariablesInput + Notification: ComponentSlotStylesInput } } -const pointerSvgUrl = (backgroundColor: string) => - `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='${encodeURIComponent( - backgroundColor, - )}' viewBox='0 0 6 16'%3E%3Cpath d='M.708 9.527a2.002 2.002 0 0 1 0-3.055l3.284-2.78C5.324 2.562 5.991 1.332 5.991 0c0 1.002.02 15.013 0 16 0-1.333-.665-2.562-1.995-3.689L.708 9.527z' fill-rule='evenodd' clip-rule='evenodd'/%3E%3C/svg%3E%0A");` - const themeOverrides: ThemeOverrides = { componentStyles: { Notification: { @@ -57,14 +22,6 @@ const themeOverrides: ThemeOverrides = { visibility: 'hidden', zIndex: 1000, }), - content: ({ variables: v }) => ({ - backgroundColor: v.contentBackgroundColor, - color: v.contentColor, - fontSize: v.contentFontSize, - fontWeight: v.contentFontWeight, - padding: v.contentPadding, - visibility: 'visible', - }), overlay: () => ({ alignItems: 'center', bottom: 0, @@ -78,44 +35,6 @@ const themeOverrides: ThemeOverrides = { top: 0, }), }, - TooltipContent: { - root: ({ variables: v }) => ({ - ...(v.basic && { - background: v.basicContentBackgroundColor, - color: v.basicContentColor, - }), - - ...(v.primary && { - background: v.primaryContentBackgroundColor, - color: v.primaryContentColor, - }), - }), - pointer: ({ variables: v }) => ({ - ...(v.basic && { - backgroundImage: pointerSvgUrl(v.basicContentBackgroundColor), - }), - - ...(v.primary && { - backgroundImage: pointerSvgUrl(v.primaryContentBackgroundColor), - }), - }), - }, - }, - componentVariables: { - Notification: (siteVariables): NotificationVariables => ({ - contentBackgroundColor: siteVariables.colorScheme.default.foreground, - contentColor: siteVariables.colorScheme.default.background, - contentPadding: pxToRem(10), - contentFontSize: siteVariables.fontSizes.larger, - contentFontWeight: siteVariables.fontWeightSemibold, - }), - TooltipContent: (siteVariables): NotificationTooltipVariables => ({ - basicContentBackgroundColor: siteVariables.colorScheme.default.background, - basicContentColor: siteVariables.colorScheme.default.foreground, - - primaryContentBackgroundColor: siteVariables.colorScheme.brand.background, - primaryContentColor: siteVariables.colorScheme.brand.foreground4, - }), }, } diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx index 8356eb8b9d..2cd63f77fe 100644 --- a/packages/react/src/components/Tooltip/Tooltip.tsx +++ b/packages/react/src/components/Tooltip/Tooltip.tsx @@ -16,6 +16,8 @@ import { isFromKeyboard, setWhatInputSource, getOrGenerateIdFromShorthand, + createShorthandFactory, + ShorthandFactory, } from '../../lib' import { ShorthandValue, Props } from '../../types' import { @@ -66,6 +68,17 @@ export interface TooltipProps /** Defines whether tooltip is displayed. */ open?: boolean + /** + * TODO: should this be centralized? + * Offset value to apply to rendered component. Accepts the following units: + * - px or unit-less, interpreted as pixels + * - %, percentage relative to the length of the trigger element + * - %p, percentage relative to the length of the component element + * - vw, CSS viewport width unit + * - vh, CSS viewport height unit + */ + offset?: string + /** A tooltip can show a pointer to trigger. */ pointing?: boolean @@ -107,6 +120,7 @@ export default class Tooltip extends AutoControlledComponent + pointerTargetRef = React.createRef() triggerRef = React.createRef() contentRef = React.createRef() @@ -160,9 +176,8 @@ export default class Tooltip extends AutoControlledComponent + const triggerNode = childrenExist(children) ? children : trigger + const triggerElement = triggerNode && (React.Children.only(triggerNode) as React.ReactElement) const triggerProps = this.getTriggerProps(triggerElement) return ( @@ -234,13 +249,14 @@ export default class Tooltip extends AutoControlledComponent