diff --git a/packages/element-library/src/product/display.js b/packages/element-library/src/product/display.js index 322d4f5dc2e0..3e31ae4fb38e 100644 --- a/packages/element-library/src/product/display.js +++ b/packages/element-library/src/product/display.js @@ -94,7 +94,7 @@ const pulseDotAfter = keyframes` } `; -// See https://github.com/ampproject/amphtml/blob/160dcd9c93d34bb7da073a1240c287fc0cf14591/extensions/amp-story-shopping/0.1/amp-story-shopping-tag.css#L56-L154 +// See https://github.com/ampproject/amphtml/blob/b2dbb6b805529b7cf699dad3a91f6d7556131543/extensions/amp-story-shopping/0.1/amp-story-shopping-tag.css const ShoppingTagDot = styled.div` border-radius: 100%; position: relative; diff --git a/packages/story-editor/src/components/canvas/frameElement.js b/packages/story-editor/src/components/canvas/frameElement.js index e4ff6580faae..6a75de2f8543 100644 --- a/packages/story-editor/src/components/canvas/frameElement.js +++ b/packages/story-editor/src/components/canvas/frameElement.js @@ -58,6 +58,7 @@ import { useDropTargets, } from '../../app'; import WithLink from '../elementLink/frame'; +import WithProductPill from '../shopping/frame'; import useDoubleClick from '../../utils/useDoubleClick'; import usePerformanceTracking from '../../utils/usePerformanceTracking'; import { TRACKING_EVENTS } from '../../constants'; @@ -304,60 +305,66 @@ function FrameElement({ id }) { ); return ( - - {Controls && ( - - )} - {/* eslint-disable-next-line styled-components-a11y/click-events-have-key-events -- False positive */} - - + + {Controls && ( + + )} + {/* eslint-disable-next-line styled-components-a11y/click-events-have-key-events -- False positive */} + - {Frame ? ( - - ) : ( - - )} - - - + + {Frame ? ( + + ) : ( + + )} + + + + ); } diff --git a/packages/story-editor/src/components/canvas/test/_utils.js b/packages/story-editor/src/components/canvas/test/_utils.js index 67c196853086..fcb97e33e5cb 100644 --- a/packages/story-editor/src/components/canvas/test/_utils.js +++ b/packages/story-editor/src/components/canvas/test/_utils.js @@ -64,6 +64,9 @@ export function TestFrameElement({ video: [], ...(inputConfigContext && inputConfigContext.allowedMimeTypes), }, + locale: { + locale: 'en-US', + }, }; const storyContext = { ...inputStoryContext, diff --git a/packages/story-editor/src/components/library/panes/shopping/karma/shopping.karma.js b/packages/story-editor/src/components/library/panes/shopping/karma/shopping.karma.js index fdca9f956933..2350e49c131f 100644 --- a/packages/story-editor/src/components/library/panes/shopping/karma/shopping.karma.js +++ b/packages/story-editor/src/components/library/panes/shopping/karma/shopping.karma.js @@ -17,7 +17,7 @@ /** * External dependencies */ -import { waitFor } from '@testing-library/react'; +import { waitFor, within } from '@testing-library/react'; /** * Internal dependencies */ @@ -207,5 +207,39 @@ describe('Shopping integration', () => { expect(firstOption.textContent).toContain('WordPress Pennant'); }); + + it('should display product pill tooltip when adding product on canvas', async () => { + await insertProduct('Big Logo Collection'); + + const container = fixture.container; + // Unselect element. + const fullbleedElements = await within(container).findAllByTestId( + 'fullbleed', + { + timeout: 2000, + } + ); + // There are three fullbleed elements; [0](Display layer), [1](Frames layer), and [2](Edit layer), + const { left, top } = fullbleedElements[1].getBoundingClientRect(); + await fixture.events.mouse.click(left - 5, top - 5); + + // Move mouse to hover over the element. + const frame = await waitFor(() => { + const frameNode = fixture.editor.canvas.framesLayer.frames[1].node; + if (!frameNode) { + throw new Error('node not ready'); + } + expect(frameNode).toBeTruthy(); + return frameNode; + }); + + const tooltipText = '$28.00'; + + await fixture.events.mouse.moveRel(frame, 10, 10); + + const tooltip = await fixture.screen.findByText(tooltipText); + expect(tooltip).toHaveTextContent(tooltipText); + await fixture.snapshot('Product pill tooltip on hover'); + }); }); }); diff --git a/packages/story-editor/src/components/shopping/frame.js b/packages/story-editor/src/components/shopping/frame.js new file mode 100644 index 000000000000..c744f58470ac --- /dev/null +++ b/packages/story-editor/src/components/shopping/frame.js @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { TOOLTIP_PLACEMENT } from '@googleforcreators/design-system'; +import { ELEMENT_TYPES } from '@googleforcreators/elements'; +import { useEffect } from '@googleforcreators/react'; + +/** + * Internal dependencies + */ +import StoryPropTypes from '../../types'; +import Tooltip from '../tooltip'; +import { useConfig, useFont } from '../../app'; +import PillIcon from './icons/pill.svg'; + +// See https://github.com/ampproject/amphtml/blob/b2dbb6b805529b7cf699dad3a91f6d7556131543/extensions/amp-story-shopping/0.1/amp-story-shopping-tag.css + +const StyledTooltip = styled(Tooltip)` + align-items: center; + p { + display: flex; + max-width: 100%; + color: #fff; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 14px; + line-height: 24px; + padding: 0; + } + + background-color: rgba(125, 125, 125, 0.75); + border-radius: 18px; + padding-inline-start: 6px; + padding-inline-end: 12px; + cursor: pointer; + height: 36px; +`; + +const PillImage = styled.span` + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 100%; + flex-shrink: 0; + flex-shrink: 0; + margin-inline-end: 4px; +`; + +function TooltipTitle({ productTagText }) { + return ( + <> + + + + {productTagText} + + ); +} + +TooltipTitle.propTypes = { + productTagText: PropTypes.string.isRequired, +}; + +function WithProductPill({ element, active, children, anchorRef }) { + const { + actions: { maybeEnqueueFontStyle }, + } = useFont(); + + const { + locale: { locale }, + } = useConfig(); + + const elementType = element.type; + useEffect(() => { + if (elementType !== ELEMENT_TYPES.PRODUCT) { + return; + } + + maybeEnqueueFontStyle([ + { + font: { + family: 'Poppins', + service: 'fonts.google.com', + weights: [700], + styles: ['regular'], + variants: [[0, 700]], + }, + fontWeight: 700, + }, + ]); + }, [elementType, maybeEnqueueFontStyle]); + + if (elementType !== ELEMENT_TYPES.PRODUCT) { + return children; + } + + const { productPrice, productPriceCurrency, productTitle } = element.product; + + const productTagText = + productPrice && productPriceCurrency && locale + ? new Intl.NumberFormat(locale, { + style: 'currency', + currency: productPriceCurrency, + }).format(productPrice) + : productTitle; + + return ( + : null} + > + {children} + + ); +} + +WithProductPill.propTypes = { + element: StoryPropTypes.element.isRequired, + anchorRef: PropTypes.object, + active: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired, +}; + +export default WithProductPill; diff --git a/packages/story-editor/src/components/shopping/icons/pill.svg b/packages/story-editor/src/components/shopping/icons/pill.svg new file mode 100644 index 000000000000..5a322a19295b --- /dev/null +++ b/packages/story-editor/src/components/shopping/icons/pill.svg @@ -0,0 +1 @@ +