diff --git a/README.md b/README.md index d7ad75b..c0931be 100755 --- a/README.md +++ b/README.md @@ -23,84 +23,85 @@ npm i @phntms/react-share Example usage in Next.js: ```JSX -import { MetaHeadEmbed, TwitterHeadEmbed } from "@phntms/react-share"; -import type { AppProps } from "next/app"; +import Head from 'next/head'; +import { MetaHeadEmbed } from "@phntms/react-share"; -const App = ({ Component }: AppProps) => ( +const PageLayout: React.FC = ({children}) => { <> - - - - - + {meta}} + siteTitle="PHANTOM" + pageTitle="Our Work" + titleTemplate="[siteTitle] | [pageTitle]" + description="Transforming challenges of all shapes and sizes into inventive, engaging and performance driven solutions that change the game." + baseSiteUrl="https://phantom.land" + pagePath="work" + keywords={["creative-agency", "phantom", "work"]} + imageUrl="https://bit.ly/3wiUOuk" + imageAlt="PHANTOM logo." + twitter={{ + cardSize: "large", + siteUsername: "@phntmLDN", + creatorUsername: "@phntmLDN", + }} + /> + {children} ); -export default App; +export default PageLayout; ``` ### <MetaHeadEmbed /> -| Property | Type | Required | Notes | -| ----------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **pageTitle** | string | **Yes** | Every page should have a unique title that describes the page, such as 'Home', 'About' etc. | -| **siteTitle** | string | **Yes** | Title of the site, usually the organization / brand name. | -| **titleTemplate** | string | **No** | Title template used to display `pageTitle` and `siteTitle` in a template, displays the values using corresponding `[pageTitle]` and `[siteTitle]`. Example template: "[pageTitle] | [siteTitle]". | -| **description** | string | **Yes** | A one to two sentence description of your webpage. Keep it within 160 characters, and write it to catch the user's attention. | -| **siteBaseUrl** | string | **Yes** | Base URL of the site, excluding trailing slash. | -| **pagePath** | string | **No** | The path of the current page, excluding leading slash. | -| **canonicalUrl** | string | **No** | The canonical URL, if your page is a duplicate. | -| **keywords** | string|string[] | **Yes** | List of SEO keywords describing what your webpage does. For example, `"your, tags"` or `["your", "tags"]`. | -| **imageUrl** | string | **Yes** | Image url of asset to share. Recommended aspect ratio for landscape is 1.9:1 (1200x630) or for squares 1:1 (1200x1200). For more info, visit [here](https://iamturns.com/open-graph-image-size/). | -| **imageAlt** | string | **Yes** | Image alt for users who are visually impaired. | -| **locale** | string | **No** | The locale these tags are marked up in, such as; `en_GB`, `fr_FR` and `es_ES`. Defaults to `en_US`. | +| Property | Type | Required | Notes | +| ----------------- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **render** | React.ReactNode | **Yes** | Unfortunately `react-helmet` and `next/head` are strict with how they accept meta tags. `react-helmet` doesn't support nesting. Whereas Next.JS only supports some children and not all, therefore a render function is required. | +| **pageTitle** | string | **Yes** | Every page should have a unique title that describes the page, such as 'Home', 'About' etc. | +| **siteTitle** | string | **Yes** | Title of the site, usually the organization / brand name. | +| **titleTemplate** | string | **No** | Title template used to display `pageTitle` and `siteTitle` in a template, displays the values using corresponding `[pageTitle]` and `[siteTitle]`. Example template: "[pageTitle] | [siteTitle]". | +| **description** | string | **Yes** | A one to two sentence description of your webpage. Keep it within 160 characters, and write it to catch the user's attention. | +| **baseSiteUrl** | string | **Yes** | Base site URL, excluding trailing slash. | +| **pagePath** | string | **No** | The path of the current page, excluding leading slash. | +| **canonicalUrl** | string | **No** | The canonical URL, if your page is a duplicate. | +| **keywords** | string|string[] | **No** | List of SEO keywords describing what your webpage does. Example, `"your, tags"` or `["your", "tags"]`. | +| **imageUrl** | string | **Yes** | Image url of asset to share. Recommended aspect ratio for landscape is 1.9:1 (1200x630) or for squares 1:1 (1200x1200). For more info, visit [here](https://iamturns.com/open-graph-image-size/). | +| **imageAlt** | string | **Yes** | Image alt for users who are visually impaired. | +| **locale** | string | **No** | The locale these tags are marked up in, such as; `en_GB`, `fr_FR` and `es_ES`. Defaults to `en_US`. | +| **twitter** | TwitterEmbedProps | **No** | Optional twitter embed properties to include. | -To add all page meta properties, add `MetaHeadEmbed` to the `head` of the page. +To use simply add `MetaHeadEmbed` to a shared layout to get the best out of page specific properties such as `pagePath`. -**Note**: `imageUrl` and `canonicalUrl` must start with `https://`, else they won't work. +**Note**: `baseSiteUrl` and `imageUrl` must start with `https://`, else they won't work when sharing. -### <TwitterHeadEmbed /> +### TwitterEmbedProps -| Property | Type | Required | Notes | -| ------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| **useLargeCard** | boolean | **No** | Summary card type. If `true`, uses large card, and if `false` uses small card. Defaults to `false`. | -| **title** | string | **Yes** | A concise title for the related content. | -| **description** | string | **No** | A description that concisely summarizes the content as appropriate for presentation within a Tweet. Should not be the same as title. | -| **siteUsername** | string | **No** | The Twitter @username the card should be attributed to. | -| **creatorUsername** | string | **No** | The Twitter @username for the content creator / author. | -| **imageUrl** | string | **No** | Image to show in card. Images must be less than 5MB in size. Supported file types; JPG, PNG, WEBP and GIF. | -| **imageAlt** | string | **No** | Image alt for users who are visually impaired. Maximum 420 characters. | +| Property | Type | Required | Notes | +| ------------------- | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **cardSize** | 'small'|'large' | **Yes** | Summary card size. | +| **title** | string | **No** | A concise title for the related content. If left blank, page title will be inherited instead. | +| **description** | string | **No** | A description that concisely summarizes the content as appropriate for presentation within a Tweet. Should not be the same as title. If left blank, `MetaHeadEmbed` description will be inherited instead. | +| **siteUsername** | string | **No** | The Twitter @username the card should be attributed to. | +| **creatorUsername** | string | **No** | The Twitter @username for the content creator / author. | +| **imageUrl** | string | **No** | Image to show in card. Images must be less than 5MB in size. Supported file types; JPG, PNG, WEBP and GIF. | +| **imageAlt** | string | **No** | Image alt for users who are visually impaired. Maximum 420 characters. | -`TwitterHeadEmbed` _should_ be used alongside `MetaHeadEmbed` for full sharing support. +**Note**: Image used should be different based on `cardSize`: -**Note**: Image used should be different based on `useLargeCard`: +- For `large` cards, use a 2:1 aspect ratio (300x157 px minium or 4096x4096 px maximum). +- For `small` cards, use a 1:1 aspect ratio (144x144 px minium or 4096x4096 px maximum). -- For large cards, use a 2:1 aspect ratio (300x157 px minium or 4096x4096 px maximum). -- For small cards, use a 1:1 aspect ratio (144x144 px minium or 4096x4096 px maximum). +**A Note on Twitter Tags** + +Twitter will inherit `og:title`, `og:description` and `og:image` tags by default, so unless you want unique fields, respective fields in `TwitterEmbedProps` should be left blank to avoid duplication. ### getFacebookUrl() -| Parameter | Type | Required | Notes | -| --------- | ------ | -------- | -------------------------------- | -| url | string | **Yes** | URL of shared webpage. | -| quote | string | **No** | Quote to show in Facebook card. | -| hashtag | string | **No** | Hashtag to show in Facebook card | +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | --------------------------------- | +| url | string | **Yes** | URL of shared webpage. | +| quote | string | **No** | Quote to show in Facebook card. | +| hashtag | string | **No** | Hashtag to show in Facebook card. | Basic component example usage: @@ -108,11 +109,7 @@ Basic component example usage: import { getFacebookUrl } from "@phntms/react-share"; const ShareToFacebook = () => ( - + Share to Facebook ); @@ -122,12 +119,12 @@ export default ShareToFacebook; ### getLinkedinUrl() -| Parameter | Type | Required | Notes | -| --------- | ------ | -------- | ------------------------------------------------------------------------ | -| url | string | **Yes** | URL of shared webpage. | -| title | string | **No** | Title to show in card. | -| summary | string | **No** | Description to show in card | -| source | string | **No** | Source of the content (for example... your website or application name). | +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | --------------------------------------------------------------------- | +| url | string | **Yes** | URL of shared webpage. | +| title | string | **No** | Title to show in card. | +| summary | string | **No** | Description to show in card. | +| source | string | **No** | Source of the content. For example, your website or application name. | Basic component example usage: @@ -145,12 +142,12 @@ export default ShareToLinkedin; ### getTwitterUrl() -| Parameter | Type | Required | Notes | -| --------- | ------ | -------- | -------------------------------- | -| url | string | **Yes** | URL of shared webpage. | -| text | string | **No** | Text to show in Twitter card. | -| hashtags | string | **No** | Hashtags to show in Twitter card | -| related | string | **No** | Accounts to recommend following. | +| Parameter | Type | Required | Notes | +| --------- | -------------------- | -------- | ------------------------------------------------------------------------------- | +| url | string | **Yes** | URL of shared webpage. | +| text | string | **No** | Text to show in Twitter card. | +| hashtags | string|string[] | **No** | Hashtags to show in Twitter card. Example, `"your,tags"` or `["your", "tags"]`. | +| related | string|string[] | **No** | Accounts to recommend following. Example, `"your, tags"` or `["your", "tags"]`. | Basic component example usage: diff --git a/src/components/MetaHeadEmbed.tsx b/src/components/MetaHeadEmbed.tsx index 56e7038..adbff1a 100644 --- a/src/components/MetaHeadEmbed.tsx +++ b/src/components/MetaHeadEmbed.tsx @@ -2,7 +2,53 @@ import React from "react"; import isAbsoluteUrl from "is-absolute-url"; +import { commaSeparate } from "../utils"; + +export interface TwitterEmbedProps { + /** Summary card size. */ + cardSize: "small" | "large"; + + /** A concise title for the related content. */ + title?: string; + + /** + * A description that concisely summarizes the content as appropriate for + * presentation within a Tweet. Should not be the same as title. + */ + description?: string; + + /** The Twitter @username the card should be attributed to. */ + siteUsername?: string; + + /** The Twitter @username for the content creator / author. */ + creatorUsername?: string; + + /** + * Image to show in card. _Should_ only be used if image is different to + * `MetaHeadEmbed` image. + * + * Should be different based on `useLargeCard`: + * - For large cards, use a 2:1 aspect ratio (300x157 px minium or + * 4096x4096 px maximum). + * - For small cards, use a 1:1 aspect ratio (144x144 px minium or + * 4096x4096 px maximum). + * + * Images must be less than 5MB in size. + * + * Supported file types; JPG, PNG, WEBP and GIF. + * + * Note: Only the first frame of an animated GIF will be used. + */ + imageUrl?: string; + + /** Image alt for users who are visually impaired. Maximum 420 characters. */ + imageAlt?: string; +} + export interface MetaEmbedProps { + /** Returns meta properties to be rendered. */ + render: (meta: React.ReactNode) => JSX.Element; + /** Unique page title that describes the page, such as `Home`, `About` etc. */ pageTitle: string; @@ -23,17 +69,17 @@ export interface MetaEmbedProps { /** Canonical URL of your webpage that will be used as its default app URL. */ canonicalUrl?: string; - /** Base URL of the site, excluding trailing slash. */ - siteBaseUrl: string; + /** Base site URL, excluding trailing slash. */ + baseSiteUrl: string; /** The path of the page, excluding leading slash. */ pagePath?: string; /** * List of SEO keywords describing what your webpage does. - * For example, `"your, tags"` or `["your", "tags"]`. + * Example: `"your, tags"` or `["your", "tags"]`. */ - keywords: string | string[]; + keywords?: string | string[]; /** * Image url of asset to share. Recommended aspect ratio for landscape is @@ -51,24 +97,26 @@ export interface MetaEmbedProps { * Defaults to `en_US`. */ locale?: string; + + /** Twitter embed properties */ + twitter?: TwitterEmbedProps; } const MetaHeadEmbed = ({ + render, pageTitle, siteTitle, titleTemplate, description, canonicalUrl, - siteBaseUrl, + baseSiteUrl, pagePath, keywords, imageUrl, imageAlt, locale = "en_US", + twitter, }: MetaEmbedProps) => { - const joinedKeywords = - typeof keywords === "string" ? keywords : keywords?.join(", "); - const title = titleTemplate ? pageTitle === siteTitle ? pageTitle @@ -81,28 +129,78 @@ const MetaHeadEmbed = ({ canonicalUrl && (isAbsoluteUrl(canonicalUrl) ? canonicalUrl - : `${siteBaseUrl}/${canonicalUrl}`); - - const pageUrl = pagePath ? `${siteBaseUrl}/${pagePath}` : siteBaseUrl; - - return ( - <> - {title} - - - - {canonicalUrl && } - - - - - - - - - - - ); + : `${baseSiteUrl}/${canonicalUrl}`); + + const pageUrl = pagePath ? `${baseSiteUrl}/${pagePath}` : baseSiteUrl; + + const metaEmbed = [ + {title}, + , + , + keywords && ( + + ), + canonicalUrl && , + + , + , + , + , + , + , + , + , + ]; + + const twitterEmbed = ({ + cardSize, + title, + description, + siteUsername, + creatorUsername, + imageUrl, + imageAlt, + }: TwitterEmbedProps) => [ + , + title && , + description && ( + + ), + siteUsername && ( + + ), + creatorUsername && ( + + ), + imageUrl && ( + + ), + imageAlt && ( + + ), + ]; + + return render([metaEmbed, twitter && twitterEmbed({ ...twitter })]); }; export default MetaHeadEmbed; diff --git a/src/components/TwitterHeadEmbed.tsx b/src/components/TwitterHeadEmbed.tsx deleted file mode 100644 index bce3370..0000000 --- a/src/components/TwitterHeadEmbed.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; - -export interface TwitterEmbedProps { - /** Summary card type. */ - useLargeCard?: boolean; - - /** A concise title for the related content. */ - title: string; - - /** - * A description that concisely summarizes the content as appropriate for - * presentation within a Tweet. Should not be the same as title. - */ - description?: string; - - /** The Twitter @username the card should be attributed to. */ - siteUsername?: string; - - /** The Twitter @username for the content creator / author. */ - creatorUsername?: string; - - /** - * Image to show in card. _Should_ only be used if image is different to - * `MetaHeadEmbed` image. - * - * Should be different based on `useLargeCard`: - * - For large cards, use a 2:1 aspect ratio (300x157 px minium or - * 4096x4096 px maximum). - * - For small cards, use a 1:1 aspect ratio (144x144 px minium or - * 4096x4096 px maximum). - * - * Images must be less than 5MB in size. - * - * Supported file types; JPG, PNG, WEBP and GIF. - * - * Note: Only the first frame of an animated GIF will be used. - */ - imageUrl?: string; - - /** Image alt for users who are visually impaired. Maximum 420 characters. */ - imageAlt?: string; -} - -const TwitterHeadEmbed = ({ - useLargeCard = false, - title, - description, - siteUsername, - creatorUsername, - imageUrl, - imageAlt, -}: TwitterEmbedProps) => ( - <> - - - {description && } - {siteUsername && } - {creatorUsername && ( - - )} - {imageUrl && } - {imageAlt && } - -); - -export default TwitterHeadEmbed; diff --git a/src/index.ts b/src/index.ts index 6debba3..1631fe0 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,8 @@ export { - default as SharingHeadEmbed, + default as MetaHeadEmbed, MetaEmbedProps, -} from "./components/MetaHeadEmbed"; - -export { - default as TwitterHeadEmbed, TwitterEmbedProps, -} from "./components/TwitterHeadEmbed"; +} from "./components/MetaHeadEmbed"; export { default as getLinkedinUrl, @@ -20,4 +16,9 @@ export { FacebookProps, } from "./utils/getFacebookUrl"; +export { + default as getShareUrl, + AllSocialPlatformProps, +} from "./utils/getShareUrl"; + export { SocialPlatforms } from "./types"; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..021c24b --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,2 @@ +export const commaSeparate = (words?: string | string[]) => + typeof words === "string" ? words : words?.join(","); diff --git a/src/utils/getFacebookUrl.ts b/src/utils/getFacebookUrl.ts index 7fe1ef5..ebe92c7 100644 --- a/src/utils/getFacebookUrl.ts +++ b/src/utils/getFacebookUrl.ts @@ -6,14 +6,14 @@ export interface FacebookProps extends BaseShareProps { quote?: string; /** Hashtag to show in Facebook card. */ - hashtag: string; + hashtag?: string; } export const getFacebookUrl = ({ url, quote, hashtag }: FacebookProps) => `https://www.facebook.com/sharer/sharer.php${objectToUrlParams({ u: url, quote, - hashtag, + hashtag: hashtag?.charAt(0) === "#" ? hashtag : `#${hashtag}`, })}`; export default getFacebookUrl; diff --git a/src/utils/getShareUrl.ts b/src/utils/getShareUrl.ts index 0f53bd3..f869d6f 100644 --- a/src/utils/getShareUrl.ts +++ b/src/utils/getShareUrl.ts @@ -3,7 +3,9 @@ import getFacebookUrl, { FacebookProps } from "./getFacebookUrl"; import getLinkedinUrl, { LinkedinProps } from "./getLinkedinUrl"; import getTwitterUrl, { TwitterProps } from "./getTwitterUrl"; -interface Props extends FacebookProps, LinkedinProps, TwitterProps {} +export type AllSocialPlatformProps = FacebookProps & + LinkedinProps & + TwitterProps; export const getShareUrl = ( socialPlatform: SocialPlatforms, @@ -17,7 +19,7 @@ export const getShareUrl = ( text, hashtags, related, - }: Props + }: AllSocialPlatformProps ) => { switch (socialPlatform) { case SocialPlatforms.Facebook: diff --git a/src/utils/getTwitterUrl.ts b/src/utils/getTwitterUrl.ts index 99e5d32..2214df5 100644 --- a/src/utils/getTwitterUrl.ts +++ b/src/utils/getTwitterUrl.ts @@ -1,23 +1,30 @@ import { BaseShareProps } from "../types"; +import { commaSeparate } from "../utils"; import objectToUrlParams from "./objectToUrlParams"; export interface TwitterProps extends BaseShareProps { /** Text to show in card. */ text?: string; - /** Hashtags to show in Twitter card. */ - hashtags?: string[]; + /** + * Hashtags to show in Twitter card. + * Example: `"your, tags"` or `["your", "tags"]`. + */ + hashtags?: string | string[]; - /** Accounts to recommend following. */ - related?: string[]; + /** + * Accounts to recommend following. + * Example: `"your, tags"` or `["your", "tags"]`. + */ + related?: string | string[]; } export const getTwitterUrl = ({ url, text, hashtags, related }: TwitterProps) => `https://twitter.com/share${objectToUrlParams({ url, text, - hashtags: hashtags?.join(","), - related: related?.join(","), + hashtags: commaSeparate(hashtags), + related: commaSeparate(related), })}`; export default getTwitterUrl;