diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/ColorPickerField.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/ColorPickerField.tsx index 05fc4877e2..9b038155cc 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/ColorPickerField.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/ColorPickerField.tsx @@ -1,14 +1,15 @@ import ColorIndicator, {SwatchOption} from './ColorIndicator'; import ColorPicker from './ColorPicker'; import clsx from 'clsx'; -import {ReactNode, createContext, useContext, useEffect, useId, useState} from 'react'; +import {ReactNode, createContext, useContext, useEffect, useId, useMemo, useState} from 'react'; import {ToggleDirections} from './Toggle'; +import {debounce} from '../../../utils/debounce'; const ColorPickerContext = createContext<{colorPickers: Array<{ id: string; setExpanded: ((expanded: boolean) => void) }>}>({ colorPickers: [] }); -const ColorPickerField = ({testId, title, direction, value, hint, error, eyedropper, clearButtonValue, onChange, swatches = []}: { +const ColorPickerField = ({testId, title, direction, value, hint, error, eyedropper, clearButtonValue, onChange, swatches = [], alwaysOpen = false, debounceMs}: { testId?: string; title?: ReactNode; direction?: ToggleDirections; @@ -19,11 +20,18 @@ const ColorPickerField = ({testId, title, direction, value, hint, error, eyedrop clearButtonValue?: string | null; onChange?: (newValue: string | null) => void; swatches?: SwatchOption[]; + alwaysOpen?: boolean; + debounceMs?: number; }) => { const [isExpanded, setExpanded] = useState(false); + const [localValue, setLocalValue] = useState(value); const context = useContext(ColorPickerContext); const id = useId(); + useEffect(() => { + setLocalValue(value); + }, [value]); + useEffect(() => { context.colorPickers.push({id, setExpanded}); @@ -48,16 +56,29 @@ const ColorPickerField = ({testId, title, direction, value, hint, error, eyedrop } }, [context, id, isExpanded]); + const debouncedOnChange = useMemo(() => { + if (onChange && debounceMs) { + return debounce(onChange, debounceMs); + } else { + return onChange; + } + }, [debounceMs, onChange]); + + const handleChange = (newValue: string | null) => { + setLocalValue(newValue); + debouncedOnChange?.(newValue); + }; + let content = ( { - onChange?.(newValue); + handleChange(newValue); setExpanded(false); }} - onTogglePicker={() => setExpanded(!isExpanded)} + onTogglePicker={() => !alwaysOpen && setExpanded(!isExpanded)} /> ); @@ -67,7 +88,7 @@ const ColorPickerField = ({testId, title, direction, value, hint, error, eyedrop
{content}
-
setExpanded(!isExpanded)}> +
!alwaysOpen && setExpanded(!isExpanded)}> {title} {hint &&
{hint}
}
@@ -75,12 +96,12 @@ const ColorPickerField = ({testId, title, direction, value, hint, error, eyedrop ); } - let selectedSwatch = swatches.find(swatch => swatch.value === value); + let selectedSwatch = swatches.find(swatch => swatch.value === localValue); return (
event.stopPropagation()}> {content} - {isExpanded && } + {(alwaysOpen || isExpanded) && }
); }; diff --git a/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx b/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx index 9a2360bcd2..312ce8dce8 100644 --- a/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx +++ b/apps/admin-x-settings/src/components/providers/RoutingProvider.tsx @@ -28,59 +28,61 @@ export const RouteContext = createContext({ }); export type RoutingModalProps = { + pathName: string; params?: Record } -const AddIntegrationModal = () => import('../settings/advanced/integrations/AddIntegrationModal'); -const AddNewsletterModal = () => import('../settings/email/newsletters/AddNewsletterModal'); -const AddRecommendationModal = () => import('../settings/site/recommendations/AddRecommendationModal'); -const AmpModal = () => import('../settings/advanced/integrations/AmpModal'); -const ChangeThemeModal = () => import('../settings/site/ThemeModal'); -const CustomIntegrationModal = () => import('../settings/advanced/integrations/CustomIntegrationModal'); -const DesignModal = () => import('../settings/site/DesignModal'); -const EditRecommendationModal = () => import('../settings/site/recommendations/EditRecommendationModal'); -const FirstpromoterModal = () => import('../settings/advanced/integrations/FirstPromoterModal'); -const HistoryModal = () => import('../settings/advanced/HistoryModal'); -const InviteUserModal = () => import('../settings/general/InviteUserModal'); -const NavigationModal = () => import('../settings/site/NavigationModal'); -const NewsletterDetailModal = () => import('../settings/email/newsletters/NewsletterDetailModal'); -const PinturaModal = () => import('../settings/advanced/integrations/PinturaModal'); -const PortalModal = () => import('../settings/membership/portal/PortalModal'); -const SlackModal = () => import('../settings/advanced/integrations/SlackModal'); -const StripeConnectModal = () => import('../settings/membership/stripe/StripeConnectModal'); -const TierDetailModal = () => import('../settings/membership/tiers/TierDetailModal'); -const UnsplashModal = () => import('../settings/advanced/integrations/UnsplashModal'); -const UserDetailModal = () => import('../settings/general/UserDetailModal'); -const ZapierModal = () => import('../settings/advanced/integrations/ZapierModal'); -const AnnouncementBarModal = () => import('../settings/site/AnnouncementBarModal'); -const EmbedSignupFormModal = () => import('../settings/membership/embedSignup/EmbedSignupFormModal'); +const modals: {[key: string]: () => Promise<{default: React.FC}>} = { + AddIntegrationModal: () => import('../settings/advanced/integrations/AddIntegrationModal'), + AddNewsletterModal: () => import('../settings/email/newsletters/AddNewsletterModal'), + AddRecommendationModal: () => import('../settings/site/recommendations/AddRecommendationModal'), + AmpModal: () => import('../settings/advanced/integrations/AmpModal'), + CustomIntegrationModal: () => import('../settings/advanced/integrations/CustomIntegrationModal'), + DesignAndThemeModal: () => import('../settings/site/DesignAndThemeModal'), + EditRecommendationModal: () => import('../settings/site/recommendations/EditRecommendationModal'), + FirstpromoterModal: () => import('../settings/advanced/integrations/FirstPromoterModal'), + HistoryModal: () => import('../settings/advanced/HistoryModal'), + InviteUserModal: () => import('../settings/general/InviteUserModal'), + NavigationModal: () => import('../settings/site/NavigationModal'), + NewsletterDetailModal: () => import('../settings/email/newsletters/NewsletterDetailModal'), + PinturaModal: () => import('../settings/advanced/integrations/PinturaModal'), + PortalModal: () => import('../settings/membership/portal/PortalModal'), + SlackModal: () => import('../settings/advanced/integrations/SlackModal'), + StripeConnectModal: () => import('../settings/membership/stripe/StripeConnectModal'), + TierDetailModal: () => import('../settings/membership/tiers/TierDetailModal'), + UnsplashModal: () => import('../settings/advanced/integrations/UnsplashModal'), + UserDetailModal: () => import('../settings/general/UserDetailModal'), + ZapierModal: () => import('../settings/advanced/integrations/ZapierModal'), + AnnouncementBarModal: () => import('../settings/site/AnnouncementBarModal'), + EmbedSignupFormModal: () => import('../settings/membership/embedSignup/EmbedSignupFormModal') +}; -const modalPaths: {[key: string]: () => Promise<{default: React.FC}>} = { - 'design/edit/themes': ChangeThemeModal, - 'design/edit': DesignModal, - 'navigation/edit': NavigationModal, - 'users/invite': InviteUserModal, - 'users/show/:slug': UserDetailModal, - 'portal/edit': PortalModal, - 'tiers/add': TierDetailModal, - 'tiers/show/:id': TierDetailModal, - 'stripe-connect': StripeConnectModal, - 'newsletters/add': AddNewsletterModal, - 'newsletters/show/:id': NewsletterDetailModal, - 'history/view': HistoryModal, - 'history/view/:user': HistoryModal, - 'integrations/zapier': ZapierModal, - 'integrations/slack': SlackModal, - 'integrations/amp': AmpModal, - 'integrations/unsplash': UnsplashModal, - 'integrations/firstpromoter': FirstpromoterModal, - 'integrations/pintura': PinturaModal, - 'integrations/add': AddIntegrationModal, - 'integrations/show/:id': CustomIntegrationModal, - 'recommendations/add': AddRecommendationModal, - 'recommendations/:id': EditRecommendationModal, - 'announcement-bar/edit': AnnouncementBarModal, - 'embed-signup-form/show': EmbedSignupFormModal +const modalPaths: {[key: string]: keyof typeof modals} = { + 'design/edit/themes': 'DesignAndThemeModal', + 'design/edit': 'DesignAndThemeModal', + 'navigation/edit': 'NavigationModal', + 'users/invite': 'InviteUserModal', + 'users/show/:slug': 'UserDetailModal', + 'portal/edit': 'PortalModal', + 'tiers/add': 'TierDetailModal', + 'tiers/show/:id': 'TierDetailModal', + 'stripe-connect': 'StripeConnectModal', + 'newsletters/add': 'AddNewsletterModal', + 'newsletters/show/:id': 'NewsletterDetailModal', + 'history/view': 'HistoryModal', + 'history/view/:user': 'HistoryModal', + 'integrations/zapier': 'ZapierModal', + 'integrations/slack': 'SlackModal', + 'integrations/amp': 'AmpModal', + 'integrations/unsplash': 'UnsplashModal', + 'integrations/firstpromoter': 'FirstpromoterModal', + 'integrations/pintura': 'PinturaModal', + 'integrations/add': 'AddIntegrationModal', + 'integrations/show/:id': 'CustomIntegrationModal', + 'recommendations/add': 'AddRecommendationModal', + 'recommendations/:id': 'EditRecommendationModal', + 'announcement-bar/edit': 'AnnouncementBarModal', + 'embed-signup-form/show': 'EmbedSignupFormModal' }; function getHashPath(urlPath: string | undefined) { @@ -97,7 +99,7 @@ function getHashPath(urlPath: string | undefined) { return null; } -const handleNavigation = () => { +const handleNavigation = (currentRoute: string | undefined) => { // Get the hash from the URL let hash = window.location.hash; hash = hash.substring(1); @@ -109,13 +111,15 @@ const handleNavigation = () => { const pathName = getHashPath(url.pathname); if (pathName) { - const [path, modal] = Object.entries(modalPaths).find(([modalPath]) => matchRoute(pathName, modalPath)) || []; + const [, currentModalName] = Object.entries(modalPaths).find(([modalPath]) => matchRoute(currentRoute || '', modalPath)) || []; + const [path, modalName] = Object.entries(modalPaths).find(([modalPath]) => matchRoute(pathName, modalPath)) || []; return { pathName, - modal: (path && modal) ? - modal().then(({default: component}) => { - NiceModal.show(component, {params: matchRoute(pathName, path)}); + changingModal: modalName && modalName !== currentModalName, + modal: (path && modalName) ? + modals[modalName]().then(({default: component}) => { + NiceModal.show(component, {pathName, params: matchRoute(pathName, path)}); }) : undefined }; @@ -143,7 +147,7 @@ const RoutingProvider: React.FC = ({externalNavigate, childr useEffect(() => { // Preload all the modals after initial render to avoid a delay when opening them setTimeout(() => { - Object.values(modalPaths).forEach(modal => modal()); + Object.values(modalPaths).forEach(modal => modals[modal]()); }, 1000); }, []); @@ -166,13 +170,16 @@ const RoutingProvider: React.FC = ({externalNavigate, childr useEffect(() => { const handleHashChange = () => { - const {pathName, modal} = handleNavigation(); - setRoute(pathName); + setRoute((currentRoute) => { + const {pathName, modal, changingModal} = handleNavigation(currentRoute); - if (modal) { - setLoadingModal(true); - modal.then(() => setLoadingModal(false)); - } + if (modal && changingModal) { + setLoadingModal(true); + modal.then(() => setLoadingModal(false)); + } + + return pathName; + }); }; handleHashChange(); diff --git a/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx new file mode 100644 index 0000000000..a9de04411d --- /dev/null +++ b/apps/admin-x-settings/src/components/settings/site/DesignAndThemeModal.tsx @@ -0,0 +1,18 @@ +import ChangeThemeModal from './ThemeModal'; +import DesignModal from './DesignModal'; +import NiceModal, {useModal} from '@ebay/nice-modal-react'; +import {RoutingModalProps} from '../../providers/RoutingProvider'; + +const DesignAndThemeModal: React.FC = ({pathName}) => { + const modal = useModal(); + + if (pathName === 'design/edit') { + return ; + } else if (pathName === 'design/edit/themes') { + return ; + } else { + modal.remove(); + } +}; + +export default NiceModal.create(DesignAndThemeModal); diff --git a/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx b/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx index 9c0f153654..8ee97870e8 100644 --- a/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/DesignModal.tsx @@ -2,7 +2,6 @@ import BrandSettings, {BrandSettingValues} from './designAndBranding/BrandSettin // import Button from '../../../admin-x-ds/global/Button'; // import ChangeThemeModal from './ThemeModal'; import Icon from '../../../admin-x-ds/global/Icon'; -import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; import StickyFooter from '../../../admin-x-ds/global/StickyFooter'; import TabView, {Tab} from '../../../admin-x-ds/global/TabView'; @@ -21,7 +20,6 @@ import {useGlobalData} from '../../providers/GlobalDataProvider'; const Sidebar: React.FC<{ brandSettings: BrandSettingValues themeSettingSections: Array<{id: string, title: string, settings: CustomThemeSetting[]}> - modal: NiceModalHandler>; updateBrandSetting: (key: string, value: SettingValue) => void updateThemeSetting: (updated: CustomThemeSetting) => void onTabChange: (id: string) => void @@ -29,7 +27,6 @@ const Sidebar: React.FC<{ }> = ({ brandSettings, themeSettingSections, - modal, updateBrandSetting, updateThemeSetting, onTabChange, @@ -68,7 +65,6 @@ const Sidebar: React.FC<{