diff --git a/special-pages/pages/new-tab/app/components/Icons.js b/special-pages/pages/new-tab/app/components/Icons.js index dd0198ecd..65e46bcf0 100644 --- a/special-pages/pages/new-tab/app/components/Icons.js +++ b/special-pages/pages/new-tab/app/components/Icons.js @@ -82,3 +82,23 @@ export function Cross() { ); } + +export function CheckColor() { + return ( + + + + + + ); +} diff --git a/special-pages/pages/new-tab/app/next-steps/components/NextSteps.module.css b/special-pages/pages/new-tab/app/next-steps/components/NextSteps.module.css index 21ca36c21..dfbe3d23d 100644 --- a/special-pages/pages/new-tab/app/next-steps/components/NextSteps.module.css +++ b/special-pages/pages/new-tab/app/next-steps/components/NextSteps.module.css @@ -47,6 +47,18 @@ font-size: calc(11 * var(--px-in-rem)); line-height: calc(14 * var(--px-in-rem)); color: var(--ntp-color-primary); + text-wrap: wrap; /* needed for some languages */ + + /* the active state created an awkward flash, adding transition out */ + &.supressActiveStateForSwitchToConfirmationText { + opacity: 1; + transition: opacity .3s ease-out; + + &:active { + background-color: var(--color-black-at-6); + opacity: 0; + } + } &:hover { background-color: var(--color-black-at-6); @@ -73,6 +85,12 @@ } @media screen and (prefers-color-scheme: dark) { + &.supressActiveStateForSwitchToConfirmationText { + &:active { + background-color: var(--color-black-at-9); + } + } + &:hover:not(:active) { background-color: var(--color-black-at-9); } @@ -89,6 +107,26 @@ } } +.confirmation { + display: flex; + align-items: center; + transition: all .2s ease-in; + min-height: 26px; + + svg { + height: 1rem; + width: 1rem; + margin-right: var(--sp-2); + } + + p { + font-size: calc(11 * var(--px-in-rem)); + line-height: calc(14 * var(--px-in-rem)); + font-weight: 600; + max-width: 8rem; + } +} + .dismissBtn { position: absolute; top: 0.5rem; diff --git a/special-pages/pages/new-tab/app/next-steps/components/NextStepsCard.js b/special-pages/pages/new-tab/app/next-steps/components/NextStepsCard.js index 9e8103e48..c844715a8 100644 --- a/special-pages/pages/new-tab/app/next-steps/components/NextStepsCard.js +++ b/special-pages/pages/new-tab/app/next-steps/components/NextStepsCard.js @@ -1,8 +1,12 @@ import { h } from 'preact'; -import styles from './NextSteps.module.css'; +import cn from 'classnames'; + +import { useState } from 'preact/hooks'; import { DismissButton } from '../../components/DismissButton'; -import { variants } from '../nextsteps.data'; +import { CheckColor } from '../../components/Icons'; import { useTypedTranslationWith } from '../../types'; +import { variants, additionalCardStates } from '../nextsteps.data'; +import styles from './NextSteps.module.css'; /** * @param {object} props @@ -14,14 +18,35 @@ import { useTypedTranslationWith } from '../../types'; export function NextStepsCard({ type, dismiss, action }) { const { t } = useTypedTranslationWith(/** @type {import("../strings.json")} */ ({})); const message = variants[type]?.(t); + const [showConfirmation, setShowConfirmation] = useState(false); + const hasConfirmationState = additionalCardStates.hasConfirmationText(message.id); + + const handleClick = () => { + if (!hasConfirmationState) { + return action(message.id); + } + + action(message.id); + setShowConfirmation(true); + }; return (

{message.title}

{message.summary}

- + {hasConfirmationState && !!showConfirmation ? ( +
+ +

{message.confirmationText}

+
+ ) : ( + + )} dismiss(message.id)} />
diff --git a/special-pages/pages/new-tab/app/next-steps/components/NextStepsGroup.js b/special-pages/pages/new-tab/app/next-steps/components/NextStepsGroup.js index 52b7ba16b..4846d11be 100644 --- a/special-pages/pages/new-tab/app/next-steps/components/NextStepsGroup.js +++ b/special-pages/pages/new-tab/app/next-steps/components/NextStepsGroup.js @@ -1,11 +1,11 @@ import { h } from 'preact'; import cn from 'classnames'; -import styles from './NextSteps.module.css'; +import { useId } from 'preact/hooks'; +import { ShowHideButton } from '../../components/ShowHideButton'; import { useTypedTranslationWith } from '../../types'; -import { NextStepsCard } from './NextStepsCard'; import { otherText } from '../nextsteps.data'; -import { ShowHideButton } from '../../components/ShowHideButton'; -import { useId } from 'preact/hooks'; +import styles from './NextSteps.module.css'; +import { NextStepsCard } from './NextStepsCard'; /** * @import enStrings from '../strings.json'; diff --git a/special-pages/pages/new-tab/app/next-steps/integrations-tests/next-steps.spec.js b/special-pages/pages/new-tab/app/next-steps/integrations-tests/next-steps.spec.js index 8d677b66c..8719a0ba3 100644 --- a/special-pages/pages/new-tab/app/next-steps/integrations-tests/next-steps.spec.js +++ b/special-pages/pages/new-tab/app/next-steps/integrations-tests/next-steps.spec.js @@ -58,4 +58,14 @@ test.describe('newtab NextSteps cards', () => { await page.getByRole('button', { name: 'Try DuckPlayer' }).click(); await ntp.mocks.waitForCallCount({ method: 'nextSteps_action', count: 1 }); }); + + test('shows a confirmation state', async ({ page }, workerInfo) => { + const ntp = NewtabPage.create(page, workerInfo); + await ntp.reducedMotion(); + await ntp.openPage({ nextSteps: ['addAppToDockMac', 'defaultApp'] }); + await page.getByRole('button', { name: 'Add to Dock' }).click(); + + await expect(page.getByText('Added to Dock!')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Add to Dock' })).not.toBeVisible(); + }); }); diff --git a/special-pages/pages/new-tab/app/next-steps/nextsteps.data.js b/special-pages/pages/new-tab/app/next-steps/nextsteps.data.js index c29849147..0455c98f8 100644 --- a/special-pages/pages/new-tab/app/next-steps/nextsteps.data.js +++ b/special-pages/pages/new-tab/app/next-steps/nextsteps.data.js @@ -50,6 +50,7 @@ export const variants = { title: t('nextSteps_addAppDockMac_title'), summary: t('nextSteps_addAppDockMac_summary'), actionText: t('nextSteps_addAppDockMac_actionText'), + confirmationText: t('nextSteps_addAppDockMac_confirmationText'), }), /** @param {(translationId: keyof enStrings) => string} t */ pinAppToTaskbarWindows: (t) => ({ @@ -69,3 +70,10 @@ export const otherText = { /** @param {(translationId: keyof enStrings) => string} t */ nextSteps_sectionTitle: (t) => t('nextSteps_sectionTitle'), }; + +/** @type {string[]} cardsWithConfirmationText */ +const cardsWithConfirmationText = ['addAppToDockMac']; + +export const additionalCardStates = { + hasConfirmationText: (/** @type {keyof variants} */ variantId) => cardsWithConfirmationText.includes(variantId), +};