diff --git a/docs/install.mdx b/docs/install.mdx index c78ff9d..745b270 100644 --- a/docs/install.mdx +++ b/docs/install.mdx @@ -3,9 +3,9 @@ sidebar_position: 1 description: 'Install `@react-native-google-signin/google-signin`. Covers paid (Universal Sign In) and free (Original) versions, requirements, and package manager setup.' --- -# Installation +import CreemEmbeddedCheckout from '@site/src/components/CreemEmbeddedCheckout'; -> We recommend [Expo](https://expo.dev) and [EAS](https://expo.dev/eas) for building and deploying your React Native app. Expo offers the best developer experience and is well-supported by this library. +# Installation The recommended option is [Universal Sign In](https://universal-sign-in.com) (paid). A free legacy version is also available — see [below](#public-version-free) for the differences. If you are an EAS customer, you may be able to access the paid version for free, [learn more](https://forms.gle/tpP7TfUGW1CwgaEZ8). @@ -13,6 +13,8 @@ Why paid? According to the [State of React Native Survey](https://results.2024.s [//]: # '🌟' + + ### Universal Sign In (premium) {/* #premium */} ⭐️ **Key Features**: @@ -166,3 +168,5 @@ The latest version of the Universal Sign In package supports (use older versions | ------------ | --------------- | | expo | 52.0.40 - 56 | | react-native | 0.76.0 - 0.86 | + +> We recommend [Expo](https://expo.dev) and [EAS](https://expo.dev/eas) for building and deploying your React Native app. Expo offers the best developer experience and is well-supported by this library. diff --git a/src/components/CreemEmbeddedCheckout/index.tsx b/src/components/CreemEmbeddedCheckout/index.tsx new file mode 100644 index 0000000..14ff934 --- /dev/null +++ b/src/components/CreemEmbeddedCheckout/index.tsx @@ -0,0 +1,191 @@ +import React, { useEffect, useId, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import './styles.css'; + +type CreemEmbeddedCheckoutProps = { + checkoutUrl: string; +}; + +const CREEM_ORIGIN = 'https://www.creem.io'; + +export default function CreemEmbeddedCheckout({ + checkoutUrl, +}: CreemEmbeddedCheckoutProps) { + const [isOpen, setIsOpen] = useState(false); + const [shouldLoadCheckout, setShouldLoadCheckout] = useState(false); + const titleId = useId(); + const closeButtonRef = useRef(null); + const iframeRef = useRef(null); + const modalContentRef = useRef(null); + + const loadCheckout = () => { + setShouldLoadCheckout(true); + }; + + const openCheckout = () => { + loadCheckout(); + setIsOpen(true); + }; + + useEffect(() => { + if (document.querySelector(`link[rel="preconnect"][href="${CREEM_ORIGIN}"]`)) { + return; + } + + const link = document.createElement('link'); + link.href = CREEM_ORIGIN; + link.rel = 'preconnect'; + link.crossOrigin = 'anonymous'; + document.head.appendChild(link); + }, []); + + useEffect(() => { + if (!isOpen) { + return; + } + + const previousOverflow = document.body.style.overflow; + const previousActiveElement = document.activeElement; + const getFocusableElements = () => + Array.from( + modalContentRef.current?.querySelectorAll( + 'button, iframe, [data-focus-sentinel]', + ) ?? [], + ).filter( + (element) => + !element.hasAttribute('disabled') && + element.getAttribute('aria-hidden') !== 'true', + ); + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setIsOpen(false); + return; + } + + if (event.key !== 'Tab') { + return; + } + + if ( + event.shiftKey && + document.activeElement === closeButtonRef.current + ) { + event.preventDefault(); + iframeRef.current?.focus(); + return; + } + + const focusableElements = getFocusableElements(); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (!firstElement || !lastElement) { + event.preventDefault(); + return; + } + + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + return; + } + + if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } + }; + + document.addEventListener('keydown', onKeyDown); + document.body.style.overflow = 'hidden'; + closeButtonRef.current?.focus(); + + return () => { + document.removeEventListener('keydown', onKeyDown); + document.body.style.overflow = previousOverflow; + + if (previousActiveElement instanceof HTMLElement) { + previousActiveElement.focus(); + } + }; + }, [isOpen]); + + return ( + + + + Universal Sign In license + Ready to install Universal Sign In? + + Buy a license, then use the private npm registry setup below to add + the package to your app. + + + + Buy license + + + {shouldLoadCheckout && + createPortal( + + setIsOpen(false)} + /> + + + + Secure checkout + Complete your purchase + + setIsOpen(false)} + /> + + iframeRef.current?.focus()} + /> + + closeButtonRef.current?.focus()} + /> + + , + document.body, + )} + + ); +} diff --git a/src/components/CreemEmbeddedCheckout/styles.css b/src/components/CreemEmbeddedCheckout/styles.css new file mode 100644 index 0000000..79b5ce4 --- /dev/null +++ b/src/components/CreemEmbeddedCheckout/styles.css @@ -0,0 +1,163 @@ +.creem-embed { + margin: 2rem 0; + padding: 1rem; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 8px; + background: var(--ifm-background-surface-color); +} + +.creem-embed__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; +} + +.creem-embed__button { + flex-shrink: 0; +} + +.creem-embed__eyebrow, +.creem-embed-modal__eyebrow { + margin: 0 0 0.25rem; + color: var(--ifm-color-primary); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.creem-embed__header h2 { + max-width: 32rem; + margin: 0 0 0.5rem; +} + +.creem-embed__header p { + margin: 0; +} + +.creem-embed-modal { + position: fixed; + inset: 0; + z-index: 9999; + display: grid; + place-items: center; + padding: 1rem 0; +} + +.creem-embed-modal--preload { + visibility: hidden; + opacity: 0; + pointer-events: none; +} + +.creem-embed-modal__backdrop { + position: absolute; + inset: 0; + border: 0; + background: rgba(15, 23, 42, 0.68); + cursor: pointer; +} + +.creem-embed-modal__content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + width: min(100%, 580px); + max-height: min(900px, calc(100vh - 2rem)); + overflow: hidden; + border-radius: 8px; + background: var(--ifm-background-surface-color); + box-shadow: 0 24px 80px rgba(15, 23, 42, 0.35); +} + +.creem-embed-modal__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--ifm-color-emphasis-200); +} + +.creem-embed-modal__header h2 { + margin: 0; + font-size: 1rem; +} + +.creem-embed-modal__close { + position: relative; + flex-shrink: 0; + width: 2.25rem; + height: 2.25rem; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 999px; + background: var(--ifm-background-surface-color); + color: var(--ifm-font-color-base); + cursor: pointer; + transition: + background 160ms ease, + border-color 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.creem-embed-modal__close::before, +.creem-embed-modal__close::after { + position: absolute; + top: 50%; + left: 50%; + width: 0.9rem; + height: 2px; + border-radius: 999px; + background: currentColor; + content: ''; +} + +.creem-embed-modal__close::before { + transform: translate(-50%, -50%) rotate(45deg); +} + +.creem-embed-modal__close::after { + transform: translate(-50%, -50%) rotate(-45deg); +} + +.creem-embed-modal__close:hover, +.creem-embed-modal__close:focus-visible { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-emphasis-100); + box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.18); + outline: none; +} + +.creem-embed-modal__close:hover { + transform: translateY(-1px); +} + +.creem-embed-modal__frame { + align-self: center; + width: 100%; + height: min(820px, calc(100vh - 6.5rem)); + border: 0; + background: #ffffff; +} + +.creem-embed-modal__sentinel { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + +@media (max-width: 700px) { + .creem-embed__header { + flex-direction: column; + } + + .creem-embed__button { + width: 100%; + } +}
Universal Sign In license
+ Buy a license, then use the private npm registry setup below to add + the package to your app. +
Secure checkout