Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions projects/js-packages/components/changelog/add-gravatar
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add a `Gravatar` component (with hovercard support) at the `./gravatar` subpath, shared across products that render subscriber or response avatars.
138 changes: 138 additions & 0 deletions projects/js-packages/components/components/gravatar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* External dependencies
*/
import { Hovercards } from '@gravatar-com/hovercards';
import '@gravatar-com/hovercards/dist/style.css';
import { useEffect, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { sha256 } from 'js-sha256';
import './style.scss';

/**
* Gravatar `defaultImage` styles, mirroring https://docs.gravatar.com/sdk/images/#default-image
*/
type DefaultImage =
| 'blank'
| 'color'
| 'identicon'
| 'initials'
| 'monsterid'
| 'mp'
| 'retro'
| 'robohash'
| 'wavatar';

export type GravatarProps = {
/**
* Style of the placeholder image when the email has no Gravatar profile.
* @default 'initials'
*/
defaultImage?: DefaultImage;
/**
* Display name for accessibility (used as `alt` text and the hovercard label).
*/
displayName?: string;
/**
* Email address to look up on Gravatar.
*/
email: string;
/**
* Rendered avatar size in pixels.
* @default 48
*/
size?: number;
/**
* Optional class name forwarded to the underlying `<img>` element. The
* default `jetpack-components-gravatar` class is always applied so consumers
* can target it directly.
*/
className?: string;
/**
* Whether to attach a Gravatar profile hovercard to the avatar.
* @default true
*/
useHovercard?: boolean;
};

/**
* Renders a Gravatar profile image with an optional Gravatar profile hovercard.
*
* If the email has no Gravatar profile, the configured `defaultImage` style is
* used (initials by default).
*
* Long-term, this is the seam for switching to a core or Gravatar-shipped
* component (see https://github.com/WordPress/gutenberg/issues/76836); until
* then, Forms and Newsletter share this implementation.
*
* @param props - The component props.
* @param props.defaultImage - Style of the placeholder image when the email has no Gravatar.
* @param props.displayName - Display name used for `alt` text + hovercard label.
* @param props.email - Email address to look up on Gravatar.
* @param props.size - Rendered avatar size in pixels.
* @param props.className - Optional class name forwarded to the underlying `<img>`.
* @param props.useHovercard - Whether to attach the Gravatar profile hovercard.
* @return The Gravatar avatar `<img>`, or null when no email is available.
*/
export default function Gravatar( {
defaultImage = 'initials',
displayName,
email,
size = 48,
className,
useHovercard = true,
}: GravatarProps ): JSX.Element | null {
const profileImageRef = useRef< HTMLImageElement | null >( null );
const hovercardRef = useRef< Hovercards | null >( null );

useEffect( () => {
if ( ! useHovercard || ! profileImageRef.current ) {
return;
}
hovercardRef.current = new Hovercards( {
// See https://github.com/Automattic/gravatar/tree/trunk/web/packages/hovercards#translations
i18n: {
'Edit your profile →': __( 'Edit your profile →', 'jetpack-components' ),
'View profile →': __( 'View profile →', 'jetpack-components' ),
Contact: __( 'Contact', 'jetpack-components' ),
'Send money': __( 'Send money', 'jetpack-components' ),
'Sorry, we are unable to load this Gravatar profile.': __(
'Sorry, we are unable to load this Gravatar profile.',
'jetpack-components'
),
'Gravatar not found.': __( 'Gravatar not found.', 'jetpack-components' ),
'This profile is private.': __( 'This profile is private.', 'jetpack-components' ),
'Too Many Requests.': __( 'Too many requests.', 'jetpack-components' ),
'Internal Server Error.': __( 'Internal server error.', 'jetpack-components' ),
'Is this you?': __( 'Is this you?', 'jetpack-components' ),
'Claim your free profile.': __( 'Claim your free profile.', 'jetpack-components' ),
Email: __( 'Email', 'jetpack-components' ),
'Home Phone': __( 'Home phone', 'jetpack-components' ),
'Work Phone': __( 'Work phone', 'jetpack-components' ),
'Cell Phone': __( 'Cell phone', 'jetpack-components' ),
'Contact Form': __( 'Contact form', 'jetpack-components' ),
Calendar: __( 'Calendar', 'jetpack-components' ),
},
} );
hovercardRef.current.attach( profileImageRef.current );
}, [ useHovercard ] );

if ( ! email ) {
return null;
}

const hashedEmail = sha256( email );
const hovercardName = displayName ? `&name=${ encodeURIComponent( displayName ) }` : '';

return (
<img
ref={ profileImageRef }
className={ clsx( 'jetpack-components-gravatar', className ) }
alt={ displayName || '' }
src={ `https://secure.gravatar.com/avatar/${ hashedEmail }?d=${ defaultImage }${ hovercardName }` }
width={ size }
height={ size }
loading="lazy"
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.jetpack-components-gravatar {
background-color: var(--jp-gray-5, #dcdcde);
border-radius: 50%;
display: block;
flex-shrink: 0; // Prevent shrinking when Gravatar is inside flex containers.
overflow: hidden;
}
2 changes: 2 additions & 0 deletions projects/js-packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export { default as AutomatticForAgenciesLogo } from './components/automattic-fo
export { default as JetpackFooter } from './components/jetpack-footer/index.tsx';
/** @deprecated Use `Spinner` from `@wordpress/components` instead. */
export { default as Spinner } from './components/spinner/index.tsx';
export { default as Gravatar } from './components/gravatar/index.tsx';
export type { GravatarProps } from './components/gravatar/index.tsx';
export { default as Gridicon } from './components/gridicon/index.tsx';
export { default as IconTooltip } from './components/icon-tooltip/index.tsx';
export { default as ActionButton } from './components/action-button/index.tsx';
Expand Down
7 changes: 7 additions & 0 deletions projects/js-packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"types": "./build/components/admin-page/index.d.ts",
"default": "./build/components/admin-page/index.js"
},
"./gravatar": {
"jetpack:src": "./components/gravatar/index.tsx",
"types": "./build/components/gravatar/index.d.ts",
"default": "./build/components/gravatar/index.js"
},
"./jetpack-footer": {
"jetpack:src": "./components/jetpack-footer/index.tsx",
"types": "./build/components/jetpack-footer/index.d.ts",
Expand Down Expand Up @@ -63,6 +68,7 @@
"@automattic/jetpack-script-data": "workspace:*",
"@automattic/number-formatters": "workspace:*",
"@babel/runtime": "^7",
"@gravatar-com/hovercards": "0.16.0",
"@wordpress/admin-ui": "1.12.0",
"@wordpress/browserslist-config": "6.44.0",
"@wordpress/components": "32.6.0",
Expand All @@ -76,6 +82,7 @@
"@wordpress/theme": "0.11.0",
"@wordpress/ui": "0.11.0",
"clsx": "2.1.1",
"js-sha256": "0.11.1",
"prop-types": "^15.7.2",
"qrcode.react": "4.2.0",
"react-slider": "2.0.5",
Expand Down
4 changes: 4 additions & 0 deletions projects/packages/forms/changelog/use-shared-gravatar
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Forms now uses the shared `Gravatar` component from `@automattic/jetpack-components/gravatar`. No user-visible change.
2 changes: 0 additions & 2 deletions projects/packages/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"@automattic/jetpack-shared-extension-utils": "workspace:*",
"@automattic/number-formatters": "workspace:*",
"@automattic/request-external-access": "1.0.1",
"@gravatar-com/hovercards": "0.16.0",
"@wordpress/admin-ui": "1.12.0",
"@wordpress/base-styles": "6.20.0",
"@wordpress/block-editor": "15.17.0",
Expand Down Expand Up @@ -83,7 +82,6 @@
"debug": "4.4.3",
"email-validator": "2.0.4",
"gridicons": "3.4.2",
"js-sha256": "0.11.1",
"libphonenumber-js": "1.12.42",
"lodash": "4.18.1",
"photon": "4.1.1",
Expand Down
2 changes: 1 addition & 1 deletion projects/packages/forms/routes/responses/stage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import Gravatar from '@automattic/jetpack-components/gravatar';
import { formatNumber } from '@automattic/number-formatters';
/**
* WordPress dependencies
Expand All @@ -23,7 +24,6 @@ import * as React from 'react';
*/
import IntegrationsModal from '../../src/blocks/contact-form/components/jetpack-integrations-modal';
import EmptyResponses from '../../src/dashboard/components/empty-responses';
import Gravatar from '../../src/dashboard/components/gravatar';
import TextWithFlag from '../../src/dashboard/components/text-with-flag/index.tsx';
import useInboxData from '../../src/dashboard/hooks/use-inbox-data.ts';
import WpRouteDashboardSearchParamsProvider from '../../src/dashboard/router/wp-route-dashboard-search-params-provider.tsx';
Expand Down
Loading
Loading