diff --git a/package.json b/package.json index 2acef8b..1e4496a 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,9 @@ "TabsFullContent": "TabsFullContent", "TabsFullHead": "TabsFullHead", "Marquee": "Marquee", - "ImageViewer": "ImageViewer" + "ImageViewer": "ImageViewer", + "Redirect": "Redirect", + "RedirectByLanguage": "RedirectByLanguage" } }, "quarklyInfoDeleted": { diff --git a/preview/docs/Redirect.md b/preview/docs/Redirect.md new file mode 100644 index 0000000..d5c38e3 --- /dev/null +++ b/preview/docs/Redirect.md @@ -0,0 +1,69 @@ +## 📖 Detailed overview + +Redirect to internal or external URL. + +## ⚙️ Usage + +Add the component to the page, set `Destination` and check how it works in the preview mode. + +> Redirect to external destination will work only on published site + +### Redirect from your component + +Use hook `useNavigate`: + +```jsx +... +import { useNavigate } from './QuarklycommunityKitRedirect'; + +const Component = () => { + const navigate = useNavigate(); + + const onClick = () => { + navigate('/foo') + // OR + navigate('https://example.com') + } + + ... +} +``` + +## 🧩 Components and Props + +### In the interface + +| Prop name | Default | Example | +| :---------- | :-----: | :-------------------: | +| Destination | `-` | `https://example.com` | + +### In the code (for developers) + +| Prop name | Name in the code | Type | Default | Example | +| :---------- | :--------------: | :------: | :-----: | :-------------------: | +| Destination | `destination` | `string` | `-` | `https://example.com` | + +## 🗄 GitHub + +[Link to GitHub](https://github.com/quarkly/community-kit/blob/master/src/Redirect) + +## 🗓 Changelog + +- 11/08/2023 (v1.0) +- First version + +## 📮 Feedback + +If you encountered a bug, please contact us so we can fix it promptly. We’re developing all the time, so don’t hesitate to send us your feedback and request new features you would like to see added. Feel free to share what you’re working on - we'd **love** to see what you’re building with Quarkly! + +[Help with components](https://community.quarkly.io/c/requests/11) + +[We're on Discord](https://discord.gg/SuF9vCMJGW) + +[Our Twitter](https://twitter.com/quarklyapp) + +[dev@quarkly.io](mailto:dev@quarkly.io) + +## 📝 License + +Licensed under the [MIT License](https://raw.githubusercontent.com/quarkly/community-kit/master/LICENSE). diff --git a/preview/docs/RedirectByLanguage.md b/preview/docs/RedirectByLanguage.md new file mode 100644 index 0000000..2b65fe9 --- /dev/null +++ b/preview/docs/RedirectByLanguage.md @@ -0,0 +1,50 @@ +## 📖 Detailed overview + +Redirect users based on browser language to internal or external URL. + +## ⚙️ Usage + +Add the component to the page, set `Destination` and `Languages` and check how it works in the preview mode. + +> Redirect to external destination will work only on published site + +## 🧩 Components and Props + +### In the interface + +| Prop name | Default | Example | +| :---------- | :-----: | :-------------------: | +| Destination | `-` | `https://example.com` | +| Languages | `[]` | `[en, de]` | + +### In the code (for developers) + +| Prop name | Name in the code | Type | Default | Example | +| :---------- | :--------------: | :------: | :-----: | :-------------------: | +| Destination | `destination` | `string` | `-` | `https://example.com` | +| Languages | `languages` | `string` | `-` | `en,de` | + +## 🗄 GitHub + +[Link to GitHub](https://github.com/quarkly/community-kit/blob/master/src/RedirectByLanguage) + +## 🗓 Changelog + +- 11/08/2023 (v1.0) +- First version + +## 📮 Feedback + +If you encountered a bug, please contact us so we can fix it promptly. We’re developing all the time, so don’t hesitate to send us your feedback and request new features you would like to see added. Feel free to share what you’re working on - we'd **love** to see what you’re building with Quarkly! + +[Help with components](https://community.quarkly.io/c/requests/11) + +[We're on Discord](https://discord.gg/SuF9vCMJGW) + +[Our Twitter](https://twitter.com/quarklyapp) + +[dev@quarkly.io](mailto:dev@quarkly.io) + +## 📝 License + +Licensed under the [MIT License](https://raw.githubusercontent.com/quarkly/community-kit/master/LICENSE). diff --git a/preview/docs/ru/Redirect.md b/preview/docs/ru/Redirect.md new file mode 100644 index 0000000..b533b98 --- /dev/null +++ b/preview/docs/ru/Redirect.md @@ -0,0 +1,69 @@ +## 📖 Подробный обзор + +Перенаправление на внутренний или внешний URL. + +## ⚙️ Использование + +Добавьте компонент на страницу, установите `Конечный URL-адрес` и проверьте, как он работает в режиме превью. + +> Перенаправление на внешний URL будет работать только на опубликованном сайте + +### Перенаправление из вашего компонента + +Используйте хук `useNavigate`: + +```jsx +... +import { useNavigate } from './QuarklycommunityKitRedirect'; + +const Component = () => { + const navigate = useNavigate(); + + const onClick = () => { + navigate('/foo') + // ИЛИ + navigate('https://example.com') + } + + ... +} +``` + +## 🧩 Компоненты и свойства + +### В интерфейсе + +| Названия свойств | По умолчанию | Пример | +| :----------------- | :----------: | :-------------------: | +| Конечный URL-адрес | `-` | `https://example.com` | + +### В коде (для разработчиков) + +| Названия свойств | Название в коде | Тип | По умолчанию | Пример | +| :----------------- | :-------------: | :------: | :----------: | :-------------------: | +| Конечный URL-адрес | `destination` | `string` | `-` | `https://example.com` | + +## 🗄 GitHub + +[Ссылка на GitHub](https://github.com/quarkly/community-kit/blob/master/src/Redirect) + +## 🗓 Changelog + +- 11/08/2023 (v1.0) +- Первая версия + +## 📮 Feedback + +If you encountered a bug, please contact us so we can fix it promptly. We’re developing all the time, so don’t hesitate to send us your feedback and request new features you would like to see added. Feel free to share what you’re working on - we'd **love** to see what you’re building with Quarkly! + +[Help with components](https://community.quarkly.io/c/requests/11) + +[We're on Discord](https://discord.gg/SuF9vCMJGW) + +[Our Twitter](https://twitter.com/quarklyapp) + +[dev@quarkly.io](mailto:dev@quarkly.io) + +## 📝 License + +Licensed under the [MIT License](https://raw.githubusercontent.com/quarkly/community-kit/master/LICENSE). diff --git a/preview/docs/ru/RedirectByLanguage.md b/preview/docs/ru/RedirectByLanguage.md new file mode 100644 index 0000000..262e587 --- /dev/null +++ b/preview/docs/ru/RedirectByLanguage.md @@ -0,0 +1,50 @@ +## 📖 Подробный обзор + +Перенаправление в зависимости от языка браузера на внутренний или внешний URL. + +## ⚙️ Использование + +Добавьте компонент на страницу, установите `Конечный URL-адрес`, `Языки` и проверьте, как он работает в режиме превью. + +> Перенаправление на внешний URL будет работать только на опубликованном сайте + +## 🧩 Компоненты и свойства + +### В интерфейсе + +| Названия свойств | По умолчанию | Пример | +| :----------------- | :----------: | :-------------------: | +| Конечный URL-адрес | `-` | `https://example.com` | +| Языки | `[]` | `[en, de]` | + +### В коде (для разработчиков) + +| Названия свойств | Название в коде | Тип | По умолчанию | Пример | +| :----------------- | :-------------: | :------: | :----------: | :-------------------: | +| Конечный URL-адрес | `destination` | `string` | `-` | `https://example.com` | +| Языки | `languages` | `string` | `-` | `en,de` | + +## 🗄 GitHub + +[Ссылка на GitHub](https://github.com/quarkly/community-kit/blob/master/src/RedirectByLanguages) + +## 🗓 Changelog + +- 11/08/2023 (v1.0) +- Первая версия + +## 📮 Feedback + +If you encountered a bug, please contact us so we can fix it promptly. We’re developing all the time, so don’t hesitate to send us your feedback and request new features you would like to see added. Feel free to share what you’re working on - we'd **love** to see what you’re building with Quarkly! + +[Help with components](https://community.quarkly.io/c/requests/11) + +[We're on Discord](https://discord.gg/SuF9vCMJGW) + +[Our Twitter](https://twitter.com/quarklyapp) + +[dev@quarkly.io](mailto:dev@quarkly.io) + +## 📝 License + +Licensed under the [MIT License](https://raw.githubusercontent.com/quarkly/community-kit/master/LICENSE). diff --git a/preview/static/Redirect.png b/preview/static/Redirect.png new file mode 100644 index 0000000..2b39418 Binary files /dev/null and b/preview/static/Redirect.png differ diff --git a/preview/static/RedirectByLanguage.png b/preview/static/RedirectByLanguage.png new file mode 100644 index 0000000..2b39418 Binary files /dev/null and b/preview/static/RedirectByLanguage.png differ diff --git a/src/Redirect/Redirect.js b/src/Redirect/Redirect.js new file mode 100644 index 0000000..74cbf20 --- /dev/null +++ b/src/Redirect/Redirect.js @@ -0,0 +1,21 @@ +import React, { useEffect } from 'react'; +import { Box } from '@quarkly/widgets'; +import { useNavigate } from './navigate'; +import { propInfo, defaultProps } from './props'; + +const Redirect = ({ destination, ...props }) => { + const navigate = useNavigate(); + + useEffect(() => { + navigate(destination); + }, [navigate, destination]); + + return Redirect to {destination}...; +}; + +Object.assign(Redirect, { + propInfo, + defaultProps, +}); + +export default Redirect; diff --git a/src/Redirect/index.js b/src/Redirect/index.js new file mode 100644 index 0000000..c507ba8 --- /dev/null +++ b/src/Redirect/index.js @@ -0,0 +1,2 @@ +export { useNavigate, useLocation } from './navigate'; +export { default } from './Redirect'; diff --git a/src/Redirect/navigate.js b/src/Redirect/navigate.js new file mode 100644 index 0000000..0388bf7 --- /dev/null +++ b/src/Redirect/navigate.js @@ -0,0 +1,97 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useHistory, useLocation as useCRALocation } from 'react-router-dom'; +import { navigate as gatsbyNavigate } from 'gatsby-link'; +import { useLocation as useGatsbyLocation } from '@reach/router'; +import { getAPI } from '../utils'; + +function checkDomain(url) { + if (url.indexOf('//') === 0) { + url = window.location.protocol + url; + } + return url + .toLowerCase() + .replace(/([a-z])?:\/\//, '$1') + .split('/')[0]; +} + +// https://stackoverflow.com/a/28054735 +function isExternal(url) { + return ( + (url.indexOf(':') > -1 || url.indexOf('//') > -1) && + checkDomain(window.location.href) !== checkDomain(url) + ); +} + +export const useNavigate = () => { + const { projectType, mode } = getAPI() || {}; + if (mode === 'development') return useQuarklyPreviewNavigate(); // eslint-disable-line react-hooks/rules-of-hooks + return projectType === 'gatsby' ? useGatsbyNavigate() : useCRANavigate(); // eslint-disable-line react-hooks/rules-of-hooks +}; + +export const useLocation = () => { + const { projectType, mode } = getAPI() || {}; + if (mode === 'development') return useQuarklyPreviewLocation(); // eslint-disable-line react-hooks/rules-of-hooks + return projectType === 'gatsby' ? useGatsbyLocation() : useCRALocation(); // eslint-disable-line react-hooks/rules-of-hooks +}; + +function externalRedirect(dest) { + window.location.redirect(dest); +} + +const useQuarklyPreviewNavigate = () => { + return useCallback((dest) => { + if (isExternal(dest)) return; + window.location.hash = `#${dest}`; + }, []); +}; + +const useCRANavigate = () => { + const history = useHistory(); + return useCallback( + (dest) => { + if (isExternal(dest)) return externalRedirect(dest); + history.push(dest); + }, + [history] + ); +}; + +const useGatsbyNavigate = () => { + return useCallback((dest) => { + gatsbyNavigate(dest); + }, []); +}; + +const isBrowser = typeof window !== 'undefined'; + +const getCurrentLocation = () => { + if (!isBrowser) + return { + pathname: '', + hash: '', + search: '', + }; + + const pathname = window.location.hash.slice(1); + const possibleHash = pathname.split('#')[1] ?? ''; + return { + pathname, + hash: possibleHash ? `#${possibleHash}` : possibleHash, + search: window.location.search, + }; +}; + +const useQuarklyPreviewLocation = () => { + const [location, setLocation] = useState(getCurrentLocation()); + + useEffect(() => { + window.addEventListener('popstate', handleChange); + return () => window.removeEventListener('popstate', handleChange); + }, []); + + function handleChange() { + setLocation(getCurrentLocation()); + } + + return location; +}; diff --git a/src/Redirect/props/index.js b/src/Redirect/props/index.js new file mode 100644 index 0000000..fc536fb --- /dev/null +++ b/src/Redirect/props/index.js @@ -0,0 +1,2 @@ +export { default as propInfo } from './propsInfo'; +export { default as defaultProps } from './propsDefault'; diff --git a/src/Redirect/props/propsDefault.js b/src/Redirect/props/propsDefault.js new file mode 100644 index 0000000..a2c9216 --- /dev/null +++ b/src/Redirect/props/propsDefault.js @@ -0,0 +1,3 @@ +export default { + destination: '', +}; diff --git a/src/Redirect/props/propsInfo.js b/src/Redirect/props/propsInfo.js new file mode 100644 index 0000000..11a4d85 --- /dev/null +++ b/src/Redirect/props/propsInfo.js @@ -0,0 +1,10 @@ +export default { + destination: { + title: { + en: 'Destination', + ru: 'Конечный URL-адрес', + }, + control: 'href', + weight: 1, + }, +}; diff --git a/src/RedirectByLanguage/RedirectByLanguage.js b/src/RedirectByLanguage/RedirectByLanguage.js new file mode 100644 index 0000000..0b8c077 --- /dev/null +++ b/src/RedirectByLanguage/RedirectByLanguage.js @@ -0,0 +1,44 @@ +import React, { useCallback, useEffect, useMemo } from 'react'; +import { Box } from '@quarkly/widgets'; +import { useNavigate } from '../Redirect'; +import { propInfo, defaultProps } from './props'; + +const RedirectByLanguage = ({ + destination, + languages: languagesFromProps, + ...props +}) => { + const navigate = useNavigate(); + + const languages = useMemo(() => { + return languagesFromProps?.length > 0 + ? languagesFromProps.split(',') + : []; + }, [languagesFromProps]); + + const redirectIfNeeded = useCallback(() => { + const navigatorLanguage = window.navigator.language || 'en'; + const language = + new Intl.Locale(navigatorLanguage)?.language || navigatorLanguage; + + if (languages.includes(language)) { + navigate(destination); + } + }, [languages, navigate, destination]); + + useEffect(() => { + redirectIfNeeded(); + window.addEventListener('languagechange', redirectIfNeeded); + return () => + window.removeEventListener('languagechange', redirectIfNeeded); + }, [redirectIfNeeded]); + + return Redirect to {destination}...; +}; + +Object.assign(RedirectByLanguage, { + propInfo, + defaultProps, +}); + +export default RedirectByLanguage; diff --git a/src/RedirectByLanguage/index.js b/src/RedirectByLanguage/index.js new file mode 100644 index 0000000..73f3f8f --- /dev/null +++ b/src/RedirectByLanguage/index.js @@ -0,0 +1 @@ +export { default } from './RedirectByLanguage'; diff --git a/src/RedirectByLanguage/props/index.js b/src/RedirectByLanguage/props/index.js new file mode 100644 index 0000000..fc536fb --- /dev/null +++ b/src/RedirectByLanguage/props/index.js @@ -0,0 +1,2 @@ +export { default as propInfo } from './propsInfo'; +export { default as defaultProps } from './propsDefault'; diff --git a/src/RedirectByLanguage/props/propsDefault.js b/src/RedirectByLanguage/props/propsDefault.js new file mode 100644 index 0000000..a96098f --- /dev/null +++ b/src/RedirectByLanguage/props/propsDefault.js @@ -0,0 +1,4 @@ +export default { + destination: '', + languages: '', +}; diff --git a/src/RedirectByLanguage/props/propsInfo.js b/src/RedirectByLanguage/props/propsInfo.js new file mode 100644 index 0000000..cb6f633 --- /dev/null +++ b/src/RedirectByLanguage/props/propsInfo.js @@ -0,0 +1,20 @@ +export default { + languages: { + title: { + en: 'Languages', + }, + control: 'input', + type: 'text', + variants: ['en', 'de', 'uk', 'ru'], + multiply: true, + weight: 1, + }, + destination: { + title: { + en: 'Destination', + ru: 'Конечный URL-адрес', + }, + control: 'href', + weight: 1, + }, +}; diff --git a/src/index.js b/src/index.js index f919503..abc48fb 100644 --- a/src/index.js +++ b/src/index.js @@ -26,9 +26,6 @@ export { default as ScrollIndicator } from './ScrollIndicator'; export { default as LoopText } from './LoopText'; export { default as BackToTop } from './BackToTop'; export { default as YoomoneyDonateForm } from './YoomoneyDonateForm'; -export { default as PayPalDonateButton } from './PayPalDonateButton'; -export { default as TwitterFeed } from './TwitterFeed'; -export { default as NetlifyForm } from './NetlifyForm'; export { default as Carousel } from './Carousel'; export { default as Animation } from './Animation'; export { default as CardFlip } from './CardFlip'; @@ -41,8 +38,11 @@ export { default as Track } from './Track'; export { default as Source } from './Source'; export { default as Picture } from './Picture'; export { default as Counter } from './Counter'; -export { default as Breadcrumbs } from './Breadcrumbs'; +export { default as TwitterFeed } from './TwitterFeed'; +export { default as PayPalDonateButton } from './PayPalDonateButton'; +export { default as NetlifyForm } from './NetlifyForm'; export { default as Lottie } from './Lottie'; +export { default as Breadcrumbs } from './Breadcrumbs'; export { default as ReCaptcha } from './ReCaptcha'; export { default as ShareButton } from './ShareButton'; export { default as Form } from './Form'; @@ -63,11 +63,13 @@ export { default as CSVToTable } from './CSVToTable'; export { default as Slider } from './Slider'; export { default as MailChimp } from './MailChimp'; export { default as ScrollAnimationCustom } from './ScrollAnimationCustom'; +export { default as RadioGroup } from './RadioGroup'; export { default as TabsFull } from './TabsFull'; export { default as TabsFullBody } from './TabsFullBody'; export { default as TabsFullButton } from './TabsFullButton'; export { default as TabsFullContent } from './TabsFullContent'; export { default as TabsFullHead } from './TabsFullHead'; -export { default as RadioGroup } from './RadioGroup'; export { default as Marquee } from './Marquee'; export { default as ImageViewer } from './ImageViewer'; +export { default as Redirect } from './Redirect'; +export { default as RedirectByLanguage } from './RedirectByLanguage';