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 @@
+