From 16b04eee7df010d29b1a358384ea289ab59e8045 Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Tue, 17 Sep 2024 16:59:31 -0400 Subject: [PATCH 01/10] refactor: improve language and currency typing --- .../src/Templates/Stores/LibraryStore.ts | 24 ++++++++++------- .../src/Templates/Stores/TemplateStore.ts | 26 +++++++++---------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts index 8e4260010..79eeec409 100644 --- a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts +++ b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts @@ -1,6 +1,6 @@ import { FunctionalComponent, RenderableProps } from 'preact'; -import type { Theme } from '../../../components/src'; +import type { Theme, ThemeMinimal } from '../../../components/src'; import type { TemplateCustomComponentTypes, TemplateTypes } from './TemplateStore'; import type { TemplateStoreComponentConfig } from './TemplateStore'; @@ -26,10 +26,10 @@ type LibraryImports = { result: LibraryComponentImport; }; language: { - [languageName: string]: () => Promise>; + [languageName in LanguageCodes]: () => Promise; }; currency: { - [currencyName: string]: () => Promise>; + [currencyName in CurrencyCodes]: () => Promise; }; }; const ALLOWED_CUSTOM_COMPONENT_TYPES: TemplateCustomComponentTypes[] = ['result', 'badge']; @@ -38,6 +38,9 @@ type LibraryStoreConfig = { components?: TemplateStoreComponentConfig; }; +export type CurrencyCodes = 'usd' | 'eur' | 'aud'; +export type LanguageCodes = 'en' | 'fr'; + export class LibraryStore { themes: { [themeName: string]: Theme; @@ -67,10 +70,10 @@ export class LibraryStore { locales: { currencies: { - [currencyName: string]: Partial; + [currencyName in CurrencyCodes]?: ThemeMinimal; }; languages: { - [languageName: string]: Partial; + [languageName in LanguageCodes]?: ThemeMinimal; }; } = { currencies: {}, @@ -217,7 +220,7 @@ export class LibraryStore { Object.keys(importList).forEach((importName) => { if (importGroup === 'component') { if (importName === 'recommendation') { - const componentSubType = importList.recommendation; + const componentSubType = (importList as LibraryStore['import']['component']).recommendation; Object.keys(componentSubType).forEach((type) => { const componentGroup = componentSubType[type as keyof typeof componentSubType] as { [componentName: string]: () => Promise }; Object.keys(componentGroup).forEach((componentName) => { @@ -225,13 +228,16 @@ export class LibraryStore { }); }); } else { - const componentGroup = importList[importName as keyof typeof importList] as { [componentName: string]: () => Promise }; + const componentGroup = importList[importName as keyof typeof importList] as LibraryComponentImport; Object.keys(componentGroup).forEach((componentName) => { loadPromises.push(componentGroup[componentName]()); }); } - } else { - const importer = importList[importName as keyof typeof importList] as () => Promise; + } else if (importGroup === 'language' || importGroup === 'currency') { + const importer = importList[importName as keyof typeof importList] as () => Promise; + loadPromises.push(importer()); + } else if (importGroup === 'theme') { + const importer = importList[importName as keyof typeof importList] as () => Promise; loadPromises.push(importer()); } }); diff --git a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts index 4e4018369..7c2c7bc05 100644 --- a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts +++ b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts @@ -3,7 +3,7 @@ import { StorageStore, StorageType } from '@searchspring/snap-store-mobx'; import { SnapTemplatesConfig } from '../SnapTemplate'; import { ThemeStore, ThemeStoreThemeConfig } from './ThemeStore'; import { TargetStore } from './TargetStore'; -import { LibraryStore } from './LibraryStore'; +import { CurrencyCodes, LanguageCodes, LibraryStore } from './LibraryStore'; import { debounce } from '@searchspring/snap-toolbox'; import type { ResultComponent, ThemeOverrides, ThemeVariablesPartial } from '../../../components/src'; @@ -51,8 +51,8 @@ export type TemplateStoreConfig = { components?: TemplateStoreComponentConfig; config?: { siteId?: string; - currency?: string; - language?: string; + currency?: CurrencyCodes; + language?: LanguageCodes; }; themes: { global: TemplateStoreThemeConfig; @@ -70,8 +70,8 @@ export class TemplatesStore { loading = false; config: SnapTemplatesConfig; storage: StorageStore; - language: string; - currency: string; + language: LanguageCodes; + currency: CurrencyCodes; settings: TemplatesStoreSettings; dependencies: TemplatesStoreDependencies; @@ -155,8 +155,8 @@ export class TemplatesStore { const base = this.library.themes[themeConfig.extends]; const overrides = themeConfig.overrides || {}; const variables = themeConfig.variables || {}; - const currency = this.library.locales.currencies[this.currency]; - const language = this.library.locales.languages[this.language]; + const currency = this.library.locales.currencies[this.currency] || {}; + const language = this.library.locales.languages[this.language] || {}; this.addTheme({ name: themeKey, @@ -248,9 +248,9 @@ export class TemplatesStore { } } - public async setCurrency(currencyCode: string) { + public async setCurrency(currencyCode: CurrencyCodes) { if (currencyCode in this.library.import.currency) { - await this.library.import.currency[currencyCode as keyof typeof this.library.import.currency](); + await this.library.import.currency[currencyCode](); const currency = this.library.locales.currencies[currencyCode]; if (currency) { @@ -268,9 +268,9 @@ export class TemplatesStore { } } - public async setLanguage(languageCode: string) { + public async setLanguage(languageCode: LanguageCodes) { if (languageCode in this.library.import.language) { - await this.library.import.language[languageCode as keyof typeof this.library.import.language](); + await this.library.import.language[languageCode](); const language = this.library.locales.languages[languageCode]; if (language) { @@ -301,8 +301,8 @@ export class TemplatesStore { name: themeName, type: 'library', base: theme, - language: this.library.locales.languages[this.language], - currency: this.library.locales.currencies[this.currency], + language: this.library.locales.languages[this.language] || {}, + currency: this.library.locales.currencies[this.currency] || {}, innerWidth: this.window.innerWidth, }); } From 052babc8cd913a4ec4e4c8cb8ef986b0b4f00cad Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 18 Sep 2024 09:12:44 -0600 Subject: [PATCH 02/10] feat(preact/templates): adding translation layer for language translations --- .../snap-preact-demo/templates/src/index.ts | 6 ---- .../src/Templates/Stores/LibraryStore.ts | 5 +-- .../src/Templates/Stores/TemplateStore.ts | 22 ++++++++++++- .../src/Templates/Stores/ThemeStore.tsx | 31 +++++++++++++------ .../Templates/Stores/library/languages/fr.ts | 1 - 5 files changed, 44 insertions(+), 21 deletions(-) delete mode 100644 packages/snap-preact/src/Templates/Stores/library/languages/fr.ts diff --git a/packages/snap-preact-demo/templates/src/index.ts b/packages/snap-preact-demo/templates/src/index.ts index e42a6da84..961a54fef 100644 --- a/packages/snap-preact-demo/templates/src/index.ts +++ b/packages/snap-preact-demo/templates/src/index.ts @@ -2,17 +2,11 @@ import { SnapTemplates } from '@searchspring/snap-preact'; import { CustomResult } from './components/Result'; import { globalStyles } from './styles'; -// import { en_translation, fr_translation } from '@searchspring/snap-preact/translations'; - new SnapTemplates({ config: { siteId: '8uyt2m', language: 'en', currency: 'usd', - // translations: { - // en: en_translation, - // fr: fr_translation, - // } }, components: { result: { diff --git a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts index 79eeec409..94a9c55a7 100644 --- a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts +++ b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts @@ -39,7 +39,7 @@ type LibraryStoreConfig = { }; export type CurrencyCodes = 'usd' | 'eur' | 'aud'; -export type LanguageCodes = 'en' | 'fr'; +export type LanguageCodes = 'en'; export class LibraryStore { themes: { @@ -155,9 +155,6 @@ export class LibraryStore { en: async () => { return this.locales.languages.en || (this.locales.languages.en = (await import('./library/languages/en')).en); }, - fr: async () => { - return this.locales.languages.fr || (this.locales.languages.fr = (await import('./library/languages/fr')).fr); - }, }, currency: { usd: async () => { diff --git a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts index 7c2c7bc05..b417760e5 100644 --- a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts +++ b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts @@ -6,7 +6,7 @@ import { TargetStore } from './TargetStore'; import { CurrencyCodes, LanguageCodes, LibraryStore } from './LibraryStore'; import { debounce } from '@searchspring/snap-toolbox'; -import type { ResultComponent, ThemeOverrides, ThemeVariablesPartial } from '../../../components/src'; +import type { LangComponentOverrides, ResultComponent, ThemeMinimal, ThemeOverrides, ThemeVariablesPartial } from '../../../components/src'; import type { GlobalThemeStyleScript } from '../../types'; export type TemplateThemeTypes = 'library' | 'local'; export type TemplateTypes = 'search' | 'autocomplete' | `recommendation/${RecsTemplateTypes}`; @@ -54,6 +54,9 @@ export type TemplateStoreConfig = { currency?: CurrencyCodes; language?: LanguageCodes; }; + translations?: { + [currencyName in LanguageCodes]?: LangComponentOverrides; + }; themes: { global: TemplateStoreThemeConfig; } & { [themeName: string]: TemplateStoreThemeConfig }; @@ -157,6 +160,7 @@ export class TemplatesStore { const variables = themeConfig.variables || {}; const currency = this.library.locales.currencies[this.currency] || {}; const language = this.library.locales.languages[this.language] || {}; + const languageOverrides = transformTranslationsToTheme((this.config.translations && this.config.translations[this.language]) || {}); this.addTheme({ name: themeKey, @@ -167,6 +171,7 @@ export class TemplatesStore { variables, currency, language, + languageOverrides, innerWidth: this.window.innerWidth, }); }); @@ -302,6 +307,7 @@ export class TemplatesStore { type: 'library', base: theme, language: this.library.locales.languages[this.language] || {}, + languageOverrides: transformTranslationsToTheme((this.config.translations && this.config.translations[this.language]) || {}), currency: this.library.locales.currencies[this.currency] || {}, innerWidth: this.window.innerWidth, }); @@ -309,3 +315,17 @@ export class TemplatesStore { this.loading = false; } } + +function transformTranslationsToTheme(translations: LangComponentOverrides): ThemeMinimal { + const translationTheme: ThemeMinimal = { + components: {}, + }; + + Object.keys(translations).forEach((component) => { + translationTheme.components![component as keyof typeof translationTheme.components] = { + lang: translations[component as keyof typeof translationTheme.components], + }; + }); + + return translationTheme; +} diff --git a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx index 509988881..1c94dda66 100644 --- a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx +++ b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx @@ -20,6 +20,7 @@ export type ThemeStoreThemeConfig = { variables?: ThemeVariablesPartial; currency: ThemeMinimal; language: ThemeMinimal; + languageOverrides: ThemeMinimal; innerWidth?: number; style?: GlobalThemeStyleScript; }; @@ -64,6 +65,7 @@ export class ThemeStore { variables: ThemeVariablesPartial; currency: ThemeMinimal; language: ThemeMinimal; + languageOverrides: ThemeMinimal; stored: ThemePartial; innerWidth?: number; @@ -71,7 +73,7 @@ export class ThemeStore { const { config, dependencies, settings } = params; this.dependencies = dependencies; - const { name, style, type, base, overrides, variables, currency, language, innerWidth } = config; + const { name, style, type, base, overrides, variables, currency, language, languageOverrides, innerWidth } = config; this.name = name; this.type = type; this.base = base; @@ -80,6 +82,7 @@ export class ThemeStore { this.variables = variables || {}; this.currency = currency; this.language = language; + this.languageOverrides = languageOverrides; this.stored = (settings.editMode && this.dependencies.storage.get(`themes.${this.type}.${this.name}.variables`)) || {}; this.innerWidth = innerWidth; @@ -120,11 +123,12 @@ export class ThemeStore { 2. base theme responsive breakpoints 3. currency overrides 4. language overrides - 5. theme overrides - 6. theme overrides at responsive breakpoints - 7. altered theme variables - 8. layout option overrides - 9. stored theme editor overrides + 5. language translation overrides + 6. theme overrides + 7. theme overrides at responsive breakpoints + 8. altered theme variables + 9. layout option overrides + 10. stored theme editor overrides */ const breakpoints = (this.variables.breakpoints || this.base.variables?.breakpoints) as number[]; @@ -132,9 +136,18 @@ export class ThemeStore { const baseBreakpoint = getOverridesAtWidth(this.innerWidth, breakpoints, this.base); const overrideBreakpoint = getOverridesAtWidth(this.innerWidth, breakpoints, this.overrides); - let theme: Theme = mergeThemeLayers(this.base, baseBreakpoint, this.currency, this.language, this.overrides, overrideBreakpoint, { - variables: this.variables, - } as ThemePartial) as Theme; + let theme: Theme = mergeThemeLayers( + this.base, + baseBreakpoint, + this.currency, + this.language, + this.languageOverrides, + this.overrides, + overrideBreakpoint, + { + variables: this.variables, + } as ThemePartial + ) as Theme; // find layout option overrides const layoutOptions = theme.layoutOptions; diff --git a/packages/snap-preact/src/Templates/Stores/library/languages/fr.ts b/packages/snap-preact/src/Templates/Stores/library/languages/fr.ts deleted file mode 100644 index fd4fc8978..000000000 --- a/packages/snap-preact/src/Templates/Stores/library/languages/fr.ts +++ /dev/null @@ -1 +0,0 @@ -export const fr = {}; From 144774659dabd28806c6ba2c635555c851e3fefb Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 18 Sep 2024 12:47:12 -0600 Subject: [PATCH 03/10] refactor(preact/templates): improving types for components in targets --- .../Atoms/TemplateSelect/TemplateSelect.tsx | 11 +++++- .../src/Templates/SnapTemplate.tsx | 35 +++++++++++++----- .../src/Templates/Stores/LibraryStore.ts | 37 ++++++++++++++----- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/packages/snap-preact/components/src/components/Atoms/TemplateSelect/TemplateSelect.tsx b/packages/snap-preact/components/src/components/Atoms/TemplateSelect/TemplateSelect.tsx index 7137136be..28c64969f 100644 --- a/packages/snap-preact/components/src/components/Atoms/TemplateSelect/TemplateSelect.tsx +++ b/packages/snap-preact/components/src/components/Atoms/TemplateSelect/TemplateSelect.tsx @@ -21,14 +21,21 @@ export const TemplateSelect = observer((properties: TemplateSelectProps): JSX.El if (targeter.resultComponent) { ResultComponent = templatesStore.library.components.result[targeter.resultComponent]; if (!ResultComponent) { - controller.log.error(`Result component "${targeter.resultComponent}" not found in library for target "${targetId}"`); - return ; + const error = `Result component "${targeter.resultComponent}" not found in library for target "${targetId}"`; + controller.log.error(error); + throw error; } } const themeLocation = templatesStore?.themes?.[targeter.theme.location as TemplateThemeTypes]; const themeStore = themeLocation && themeLocation[targeter.theme.name]; const theme = themeStore?.theme; + if (!theme) { + const error = `Theme "${targeter.theme.name}" not found in library for target "${targetId}"`; + controller.log.error(error); + throw error; + } + // ensuring that theme and component are ready to render return !loading && theme && Component ? ( diff --git a/packages/snap-preact/src/Templates/SnapTemplate.tsx b/packages/snap-preact/src/Templates/SnapTemplate.tsx index e447ff7e3..9906a3509 100644 --- a/packages/snap-preact/src/Templates/SnapTemplate.tsx +++ b/packages/snap-preact/src/Templates/SnapTemplate.tsx @@ -14,6 +14,7 @@ import type { RecommendationInstantiatorConfigSettings, RecommendationComponentO import type { SnapFeatures } from '../types'; import type { SnapConfig, ExtendedTarget } from '../Snap'; import type { RecsTemplateTypes, TemplateStoreConfig, TemplateTypes } from './Stores/TemplateStore'; +import { LibraryImports } from './Stores/LibraryStore'; export const THEME_EDIT_COOKIE = 'ssThemeEdit'; @@ -21,30 +22,30 @@ export const THEME_EDIT_COOKIE = 'ssThemeEdit'; export type SearchTargetConfig = { selector: string; theme?: string; - component: 'Search' | 'SearchHorizontal'; // various component (template) types allowed + component: keyof LibraryImports['component']['search']; resultComponent?: string; }; export type AutocompleteTargetConfig = { selector: string; theme?: string; - component: 'Autocomplete'; // various components (templates) available + component: keyof LibraryImports['component']['autocomplete']; resultComponent?: string; }; export type RecommendationDefaultTargetConfig = { theme?: string; - component: 'Recommendation' | 'RecommendationGrid'; // various components (templates) available + component: keyof LibraryImports['component']['recommendation']['default']; resultComponent?: string; }; export type RecommendationEmailTargetConfig = { theme?: string; - component: 'RecommendationEmail'; // various components (templates) available + component: keyof LibraryImports['component']['recommendation']['email']; resultComponent?: string; }; export type RecommendationBundleTargetConfig = { theme?: string; - component: 'RecommendationBundle'; // various components (templates) available + component: keyof LibraryImports['component']['recommendation']['bundle']; resultComponent?: string; }; @@ -214,14 +215,28 @@ export function createRecommendationComponentMapping( return Object.keys(templateConfig.recommendation || {}) .filter((key) => ['default', 'email', 'bundle'].includes(key)) - .reduce((mapping, recsType) => { - Object.keys(templateConfig.recommendation![recsType as RecsTemplateTypes] || {}).forEach((targetName) => { - const type: TemplateTypes = `recommendation/${recsType as RecsTemplateTypes}`; - const target = templateConfig.recommendation![recsType as RecsTemplateTypes]![targetName] as TemplateTarget; + .reduce((mapping, type) => { + const recsType = type as RecsTemplateTypes; + Object.keys(templateConfig.recommendation![recsType] || {}).forEach((targetName) => { + const type: TemplateTypes = `recommendation/${recsType}`; + const target = templateConfig.recommendation![recsType]![targetName] as TemplateTarget; const mappedConfig: RecommendationComponentObject = { component: async () => { const componentImportPromises = []; - componentImportPromises.push(templatesStore.library.import.component.recommendation[recsType as RecsTemplateTypes][target.component]()); + switch (recsType) { + case 'default': { + const importLocation = templatesStore.library.import.component.recommendation.default; + componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + } + case 'bundle': { + const importLocation = templatesStore.library.import.component.recommendation.bundle; + componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + } + case 'email': { + const importLocation = templatesStore.library.import.component.recommendation.email; + componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + } + } if (target.resultComponent && templatesStore.library.import.component.result[target.resultComponent]) { componentImportPromises.push(templatesStore.library.import.component.result[target.resultComponent]()); } diff --git a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts index 94a9c55a7..7c1f4fd0b 100644 --- a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts +++ b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts @@ -7,23 +7,42 @@ import type { TemplateStoreComponentConfig } from './TemplateStore'; type LibraryComponentImport = { [componentName: string]: (args?: any) => Promise>>; }; + type LibraryComponentMap = { [componentName: string]: FunctionalComponent>; }; -type LibraryImports = { + +export type LibraryImports = { theme: { - [themeName: string]: (args?: any) => Theme | Promise; + bocachica: (args?: any) => Theme | Promise; }; component: { - search: LibraryComponentImport; - autocomplete: LibraryComponentImport; + search: { + Search: (args?: any) => Promise>>; + SearchHorizontal: (args?: any) => Promise>>; + }; + autocomplete: { + Autocomplete: (args?: any) => Promise>>; + }; recommendation: { - bundle: LibraryComponentImport; - default: LibraryComponentImport; - email: LibraryComponentImport; + bundle: { + RecommendationBundle: (args?: any) => Promise>>; + }; + default: { + Recommendation: (args?: any) => Promise>>; + RecommendationGrid: (args?: any) => Promise>>; + }; + email: { + RecommendationEmail: (args?: any) => Promise>>; + }; + }; + badge: { + [componentName: string]: (args?: any) => Promise>>; + }; + result: { + Result: (args?: any) => Promise>>; + [componentName: string]: (args?: any) => Promise>>; }; - badge: LibraryComponentImport; - result: LibraryComponentImport; }; language: { [languageName in LanguageCodes]: () => Promise; From 757c6f4043609d5d76ad84180da8e0b3c8318cc6 Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Wed, 18 Sep 2024 17:58:13 -0400 Subject: [PATCH 04/10] refactor: wip --- .../components/Atoms/NoResults/NoResults.tsx | 13 ++- .../TemplatesEditor/TemplatesEditor.tsx | 5 +- .../src/utilities/mergeProps.test.ts | 13 +-- .../src/Templates/SnapTemplate.tsx | 81 +++++++++++++++---- .../src/Templates/Stores/LibraryStore.test.ts | 26 +++--- .../src/Templates/Stores/LibraryStore.ts | 2 +- .../src/Templates/Stores/TargetStore.test.ts | 4 +- .../src/Templates/Stores/TargetStore.ts | 2 +- .../src/Templates/Stores/TemplateStore.ts | 31 +++++-- .../Templates/Stores/TemplatesStore.test.ts | 20 +++-- .../src/Templates/Stores/ThemeStore.test.ts | 32 +++++--- 11 files changed, 159 insertions(+), 70 deletions(-) diff --git a/packages/snap-preact/components/src/components/Atoms/NoResults/NoResults.tsx b/packages/snap-preact/components/src/components/Atoms/NoResults/NoResults.tsx index 5a566b7ee..63d18a48b 100644 --- a/packages/snap-preact/components/src/components/Atoms/NoResults/NoResults.tsx +++ b/packages/snap-preact/components/src/components/Atoms/NoResults/NoResults.tsx @@ -18,6 +18,7 @@ import type { SearchController } from '@searchspring/snap-controller'; import deepmerge from 'deepmerge'; import { useLang } from '../../../hooks'; import type { Lang } from '../../../hooks'; +import type { LibraryImports } from '../../../../../src/Templates/Stores/LibraryStore'; const CSS = { noResults: () => css({}), @@ -93,8 +94,14 @@ export const NoResults = observer((properties: NoResultsProps): JSX.Element => { if (templates?.recommendation?.enabled) { const componentName = templates?.recommendation?.component || 'Recommendation'; - const resultComponentName = templates?.recommendation?.resultComponent; const snap = useSnap() as SnapTemplates; + const themeName = properties.theme?.name; + let defaultResultComponentFromTheme; + if (themeName) { + defaultResultComponentFromTheme = snap?.templates?.config.themes[themeName]?.resultComponent; + } + + const resultComponentName = (templates?.recommendation?.resultComponent || defaultResultComponentFromTheme) as string; const mergedConfig = Object.assign( { id: '', @@ -220,8 +227,8 @@ export interface NoResultsProps extends ComponentProps { templates?: { recommendation?: { enabled: boolean; - component?: 'Recommendation'; // Need a type for allowed recommendation component names (that would exist in the library) - resultComponent?: string; + component?: keyof LibraryImports['component']['recommendation']['default']; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); config?: Partial; }; }; diff --git a/packages/snap-preact/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.tsx b/packages/snap-preact/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.tsx index 2dd6ba70f..ffd3364c5 100644 --- a/packages/snap-preact/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.tsx +++ b/packages/snap-preact/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.tsx @@ -10,6 +10,7 @@ import { Button } from '../../Atoms/Button'; import { observer } from 'mobx-react-lite'; import { debounce } from '@searchspring/snap-toolbox'; import { CacheProvider } from '../../../providers'; +import { GLOBAL_THEME_NAME } from '../../../../../src/Templates/Stores/TargetStore'; const CSS = { ColorDisplay: ({ color, isColorPickerVisible }: any) => @@ -185,8 +186,8 @@ export const TemplatesEditor = observer((properties: TemplatesEditorProps): JSX. const currencyKeys = Object.keys(currencies); const libraryThemes = Object.keys(templatesStore.themes.library || {}); const lcoalThemes = Object.keys(templatesStore.themes.local || {}).sort((a, b) => { - if (a === 'global') return -1; - if (b === 'global') return 1; + if (a === GLOBAL_THEME_NAME) return -1; + if (b === GLOBAL_THEME_NAME) return 1; return 0; }); const selectedTargetConfig = templatesStore.getTarget(selectedTarget.type, selectedTarget.target); diff --git a/packages/snap-preact/components/src/utilities/mergeProps.test.ts b/packages/snap-preact/components/src/utilities/mergeProps.test.ts index 397689667..693a1a319 100644 --- a/packages/snap-preact/components/src/utilities/mergeProps.test.ts +++ b/packages/snap-preact/components/src/utilities/mergeProps.test.ts @@ -1,4 +1,5 @@ import { Theme } from '..'; +import { GLOBAL_THEME_NAME } from '../../../src/Templates/Stores/TargetStore'; import { SelectProps } from '../components/Molecules/Select'; import { sortSelectors, filterSelectors, mergeProps } from './mergeProps'; @@ -136,7 +137,7 @@ describe('mergeProps function with theme name', () => { it('has named theme with variables, layoutOptions, and treePath', () => { const componentType = 'select'; const globalTheme: Theme = { - name: 'global', + name: GLOBAL_THEME_NAME, variables: { breakpoints: [0, 540, 767, 1200], colors: { @@ -191,7 +192,7 @@ describe('mergeProps function with theme name', () => { it('globalTheme components overrides defaultProps', () => { const componentType = 'select'; const globalTheme = { - name: 'global', + name: GLOBAL_THEME_NAME, components: { [componentType]: { startOpen: true, @@ -225,7 +226,7 @@ describe('mergeProps function with theme name', () => { it('properties overrides defaultProps', () => { const componentType = 'select'; const globalTheme = { - name: 'global', + name: GLOBAL_THEME_NAME, }; const defaultProps: Partial = { @@ -252,7 +253,7 @@ describe('mergeProps function with theme name', () => { it('globalTheme overrides properties', () => { const componentType = 'select'; const globalTheme = { - name: 'global', + name: GLOBAL_THEME_NAME, components: { [componentType]: { startOpen: '1', @@ -292,7 +293,7 @@ describe('mergeProps function with theme name', () => { it('nested theme on properties', () => { const componentType = 'select'; const globalTheme = { - name: 'global', + name: GLOBAL_THEME_NAME, components: { [componentType]: { startOpen: '1', @@ -345,7 +346,7 @@ describe('mergeProps function with theme name', () => { it('nested treePath and named component', () => { const componentType = 'select'; const globalTheme = { - name: 'global', + name: GLOBAL_THEME_NAME, }; const defaultProps: Partial = {}; diff --git a/packages/snap-preact/src/Templates/SnapTemplate.tsx b/packages/snap-preact/src/Templates/SnapTemplate.tsx index 9906a3509..5fa6560a7 100644 --- a/packages/snap-preact/src/Templates/SnapTemplate.tsx +++ b/packages/snap-preact/src/Templates/SnapTemplate.tsx @@ -10,43 +10,48 @@ import { TemplateTarget, TemplatesStore } from './Stores/TemplateStore'; import type { Target } from '@searchspring/snap-toolbox'; import type { SearchStoreConfigSettings, AutocompleteStoreConfigSettings } from '@searchspring/snap-store-mobx'; import type { UrlTranslatorConfig } from '@searchspring/snap-url-manager'; -import type { RecommendationInstantiatorConfigSettings, RecommendationComponentObject } from '../Instantiators/RecommendationInstantiator'; +import type { + RecommendationInstantiatorConfigSettings, + RecommendationComponentObject, + RecommendationInstantiatorConfig, +} from '../Instantiators/RecommendationInstantiator'; import type { SnapFeatures } from '../types'; import type { SnapConfig, ExtendedTarget } from '../Snap'; import type { RecsTemplateTypes, TemplateStoreConfig, TemplateTypes } from './Stores/TemplateStore'; import { LibraryImports } from './Stores/LibraryStore'; +import { GLOBAL_THEME_NAME } from './Stores/TargetStore'; export const THEME_EDIT_COOKIE = 'ssThemeEdit'; // TODO: tabbing, finder export type SearchTargetConfig = { selector: string; - theme?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); component: keyof LibraryImports['component']['search']; - resultComponent?: string; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type AutocompleteTargetConfig = { selector: string; - theme?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); component: keyof LibraryImports['component']['autocomplete']; - resultComponent?: string; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type RecommendationDefaultTargetConfig = { - theme?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); component: keyof LibraryImports['component']['recommendation']['default']; - resultComponent?: string; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type RecommendationEmailTargetConfig = { - theme?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); component: keyof LibraryImports['component']['recommendation']['email']; - resultComponent?: string; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type RecommendationBundleTargetConfig = { - theme?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); component: keyof LibraryImports['component']['recommendation']['bundle']; - resultComponent?: string; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type SnapTemplatesConfig = TemplateStoreConfig & { @@ -55,14 +60,14 @@ export type SnapTemplatesConfig = TemplateStoreConfig & { search?: { targets: [SearchTargetConfig, ...SearchTargetConfig[]]; settings?: SearchStoreConfigSettings; - breakpointSettings?: SearchStoreConfigSettings[]; + // breakpointSettings?: SearchStoreConfigSettings[]; /* controller settings breakpoints work with caveat of having settings locked to initialized breakpoint */ }; autocomplete?: { inputSelector: string; targets: [AutocompleteTargetConfig, ...AutocompleteTargetConfig[]]; settings?: AutocompleteStoreConfigSettings; - breakpointSettings?: AutocompleteStoreConfigSettings[]; + // breakpointSettings?: AutocompleteStoreConfigSettings[]; /* controller settings breakpoints work with caveat of having settings locked to initialized breakpoint */ }; recommendation?: { @@ -75,8 +80,8 @@ export type SnapTemplatesConfig = TemplateStoreConfig & { bundle?: { [profileComponentName: string]: RecommendationBundleTargetConfig; }; - settings: RecommendationInstantiatorConfigSettings; - breakpointSettings?: RecommendationInstantiatorConfigSettings[]; + settings?: RecommendationInstantiatorConfigSettings; + // breakpointSettings?: RecommendationInstantiatorConfigSettings[]; /* controller settings breakpoints work with caveat of having settings locked to initialized breakpoint */ }; }; @@ -164,6 +169,10 @@ export function mapBreakpoints( export const createSearchTargeters = (templateConfig: SnapTemplatesConfig, templatesStore: TemplatesStore): ExtendedTarget[] => { const targets = templateConfig.search?.targets || []; return targets.map((target) => { + // use theme provided resultComponent if specified + if (!target.resultComponent && templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent) { + target.resultComponent = templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent; + } const targetId = templatesStore.addTarget('search', target); const targeter: ExtendedTarget = { selector: target.selector, @@ -187,6 +196,11 @@ export const createSearchTargeters = (templateConfig: SnapTemplatesConfig, templ export function createAutocompleteTargeters(templateConfig: SnapTemplatesConfig, templatesStore: TemplatesStore): ExtendedTarget[] { const targets = templateConfig.autocomplete?.targets || []; return targets.map((target) => { + // use theme provided resultComponent if specified + if (!target.resultComponent && templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent) { + target.resultComponent = templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent; + } + const targetId = templatesStore.addTarget('autocomplete', target); const targeter: ExtendedTarget = { selector: target.selector, @@ -220,6 +234,12 @@ export function createRecommendationComponentMapping( Object.keys(templateConfig.recommendation![recsType] || {}).forEach((targetName) => { const type: TemplateTypes = `recommendation/${recsType}`; const target = templateConfig.recommendation![recsType]![targetName] as TemplateTarget; + + // use theme provided resultComponent if specified + if (!target.resultComponent && templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent) { + target.resultComponent = templateConfig.themes[target.theme || GLOBAL_THEME_NAME].resultComponent; + } + const mappedConfig: RecommendationComponentObject = { component: async () => { const componentImportPromises = []; @@ -227,14 +247,17 @@ export function createRecommendationComponentMapping( case 'default': { const importLocation = templatesStore.library.import.component.recommendation.default; componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + break; } case 'bundle': { const importLocation = templatesStore.library.import.component.recommendation.bundle; componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + break; } case 'email': { const importLocation = templatesStore.library.import.component.recommendation.email; componentImportPromises.push(importLocation[target.component as keyof typeof importLocation]()); + break; } } if (target.resultComponent && templatesStore.library.import.component.result[target.resultComponent]) { @@ -327,10 +350,34 @@ export function createSnapConfig(templateConfig: SnapTemplatesConfig, templatesS } /* RECOMMENDATION INSTANTIATOR */ + const originalRecsConfig = templateConfig.recommendation || {}; + templateConfig.recommendation = deepmerge( + { + settings: { + branch: 'production', + }, + bundle: { + Bundle: { + component: 'RecommendationBundle', + }, + }, + default: { + Recs: { + component: 'Recommendation', + }, + }, + email: { + Email: { + component: 'RecommendationEmail', + }, + }, + }, + originalRecsConfig + ) as SnapTemplatesConfig['recommendation']; if (templateConfig.recommendation && snapConfig.instantiators) { - const recommendationInstantiatorConfig = { + const recommendationInstantiatorConfig: RecommendationInstantiatorConfig = { components: createRecommendationComponentMapping(templateConfig, templatesStore), - config: templateConfig.recommendation?.settings || {}, + config: templateConfig.recommendation?.settings!, }; // merge the responsive settings if there are any diff --git a/packages/snap-preact/src/Templates/Stores/LibraryStore.test.ts b/packages/snap-preact/src/Templates/Stores/LibraryStore.test.ts index 68a50f303..588bc95c5 100644 --- a/packages/snap-preact/src/Templates/Stores/LibraryStore.test.ts +++ b/packages/snap-preact/src/Templates/Stores/LibraryStore.test.ts @@ -61,15 +61,15 @@ describe('LibraryStore', () => { const themes = Object.keys(store.import.theme); for (let index = 0; index < themes.length; index++) { const theme = themes[index]; - expect(store.import.theme[theme]).toBeDefined(); + expect(store.import.theme[theme as keyof typeof store.import.theme]).toBeDefined(); expect(store.themes[theme]).not.toBeDefined(); - await store.import.theme[theme](); + await store.import.theme[theme as keyof typeof store.import.theme](); expect(store.themes[theme]).toBeDefined(); } const autocompleteComponents = Object.keys(store.import.component.autocomplete); for (let index = 0; index < autocompleteComponents.length; index++) { - const componentName = autocompleteComponents[index]; + const componentName = autocompleteComponents[index] as keyof typeof store.import.component.autocomplete; expect(store.import.component.autocomplete[componentName]).toBeDefined(); expect(store.components.autocomplete[componentName]).not.toBeDefined(); await store.import.component.autocomplete[componentName](); @@ -79,9 +79,9 @@ describe('LibraryStore', () => { const searchComponents = Object.keys(store.import.component.search); for (let index = 0; index < searchComponents.length; index++) { const componentName = searchComponents[index]; - expect(store.import.component.search[componentName]).toBeDefined(); + expect(store.import.component.search[componentName as keyof typeof store.import.component.search]).toBeDefined(); expect(store.components.search[componentName]).not.toBeDefined(); - await store.import.component.search[componentName](); + await store.import.component.search[componentName as keyof typeof store.import.component.search](); expect(store.components.search[componentName]).toBeDefined(); } @@ -115,19 +115,19 @@ describe('LibraryStore', () => { const languages = Object.keys(store.import.language); for (let index = 0; index < languages.length; index++) { const language = languages[index]; - expect(store.import.language[language]).toBeDefined(); - expect(store.locales.languages[language]).not.toBeDefined(); - await store.import.language[language](); - expect(store.locales.languages[language]).toBeDefined(); + expect(store.import.language[language as keyof typeof store.import.language]).toBeDefined(); + expect(store.locales.languages[language as keyof typeof store.locales.languages]).not.toBeDefined(); + await store.import.language[language as keyof typeof store.import.language](); + expect(store.locales.languages[language as keyof typeof store.locales.languages]).toBeDefined(); } const currencies = Object.keys(store.import.currency); for (let index = 0; index < currencies.length; index++) { const currency = currencies[index]; - expect(store.import.currency[currency]).toBeDefined(); - expect(store.locales.currencies[currency]).not.toBeDefined(); - await store.import.currency[currency](); - expect(store.locales.currencies[currency]).toBeDefined(); + expect(store.import.currency[currency as keyof typeof store.import.currency]).toBeDefined(); + expect(store.locales.currencies[currency as keyof typeof store.locales.currencies]).not.toBeDefined(); + await store.import.currency[currency as keyof typeof store.import.currency](); + expect(store.locales.currencies[currency as keyof typeof store.locales.currencies]).toBeDefined(); } }); diff --git a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts index 7c1f4fd0b..edb7df16b 100644 --- a/packages/snap-preact/src/Templates/Stores/LibraryStore.ts +++ b/packages/snap-preact/src/Templates/Stores/LibraryStore.ts @@ -14,7 +14,7 @@ type LibraryComponentMap = { export type LibraryImports = { theme: { - bocachica: (args?: any) => Theme | Promise; + bocachica: (args?: any) => Promise; }; component: { search: { diff --git a/packages/snap-preact/src/Templates/Stores/TargetStore.test.ts b/packages/snap-preact/src/Templates/Stores/TargetStore.test.ts index be07ab4e3..ed3132aca 100644 --- a/packages/snap-preact/src/Templates/Stores/TargetStore.test.ts +++ b/packages/snap-preact/src/Templates/Stores/TargetStore.test.ts @@ -1,5 +1,5 @@ import { StorageStore } from '@searchspring/snap-store-mobx'; -import { TargetStore } from './TargetStore'; +import { GLOBAL_THEME_NAME, TargetStore } from './TargetStore'; import { TemplatesStoreDependencies, TemplatesStoreSettings, TemplateThemeTypes } from './TemplateStore'; describe('TargetStore', () => { @@ -26,7 +26,7 @@ describe('TargetStore', () => { expect(store.resultComponent).toBeUndefined(); expect(store.theme).toStrictEqual({ location: 'local', - name: 'global', + name: GLOBAL_THEME_NAME, }); // @ts-ignore - private property expect(store.dependencies).toBe(dependencies); diff --git a/packages/snap-preact/src/Templates/Stores/TargetStore.ts b/packages/snap-preact/src/Templates/Stores/TargetStore.ts index d5319a368..5391da64a 100644 --- a/packages/snap-preact/src/Templates/Stores/TargetStore.ts +++ b/packages/snap-preact/src/Templates/Stores/TargetStore.ts @@ -1,7 +1,7 @@ import { observable, makeObservable } from 'mobx'; import { TemplateTarget, type TemplatesStoreSettings, type TemplatesStoreDependencies, type TemplateThemeTypes } from './TemplateStore'; -const GLOBAL_THEME_NAME = 'global'; +export const GLOBAL_THEME_NAME = 'global'; type TargetStoreConfig = { target: TemplateTarget; diff --git a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts index b417760e5..6c355dc88 100644 --- a/packages/snap-preact/src/Templates/Stores/TemplateStore.ts +++ b/packages/snap-preact/src/Templates/Stores/TemplateStore.ts @@ -3,7 +3,7 @@ import { StorageStore, StorageType } from '@searchspring/snap-store-mobx'; import { SnapTemplatesConfig } from '../SnapTemplate'; import { ThemeStore, ThemeStoreThemeConfig } from './ThemeStore'; import { TargetStore } from './TargetStore'; -import { CurrencyCodes, LanguageCodes, LibraryStore } from './LibraryStore'; +import { CurrencyCodes, LanguageCodes, LibraryImports, LibraryStore } from './LibraryStore'; import { debounce } from '@searchspring/snap-toolbox'; import type { LangComponentOverrides, ResultComponent, ThemeMinimal, ThemeOverrides, ThemeVariablesPartial } from '../../../components/src'; @@ -15,11 +15,18 @@ export type RecsTemplateTypes = 'bundle' | 'default' | 'email'; type TargetMap = { [targetId: string]: TargetStore }; +type ComponentLibraryType = + | keyof LibraryImports['component']['autocomplete'] + | keyof LibraryImports['component']['search'] + | keyof LibraryImports['component']['recommendation']['default'] + | keyof LibraryImports['component']['recommendation']['bundle'] + | keyof LibraryImports['component']['recommendation']['email']; + export type TemplateTarget = { selector?: string; - theme?: string; - component: string; - resultComponent?: string; + theme?: keyof LibraryImports['theme'] | (string & NonNullable); + component: ComponentLibraryType | (string & NonNullable); + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); }; export type TemplatesStoreSettings = { @@ -35,8 +42,9 @@ type WindowProperties = { }; type TemplateStoreThemeConfig = { - extends: 'bocachica'; // various themes available + extends: keyof LibraryImports['theme']; style?: GlobalThemeStyleScript; + resultComponent?: keyof LibraryImports['component']['result'] | (string & NonNullable); variables?: ThemeVariablesPartial; overrides?: ThemeOverrides; }; @@ -88,7 +96,7 @@ export class TemplatesStore { themes: { local: { - [themeName: 'global' | string]: ThemeStore; + [themeName: string]: ThemeStore; }; library: { [themeName: string]: ThemeStore; @@ -152,9 +160,16 @@ export class TemplatesStore { // setup local themes Object.keys(config.themes).map((themeKey) => { const themeConfig = config.themes[themeKey]; - const imports = [importCurrency, importLanguage, this.library.import.theme[themeConfig.extends]()]; - Promise.all(imports).then(() => { + // import component if defined + if (themeConfig.resultComponent && this.library.import.component.result[themeConfig.resultComponent]) { + this.library.import.component.result[themeConfig.resultComponent](); + } + + // import theme dependencies + const themeImports = [importCurrency, importLanguage, this.library.import.theme[themeConfig.extends]()]; + + Promise.all(themeImports).then(() => { const base = this.library.themes[themeConfig.extends]; const overrides = themeConfig.overrides || {}; const variables = themeConfig.variables || {}; diff --git a/packages/snap-preact/src/Templates/Stores/TemplatesStore.test.ts b/packages/snap-preact/src/Templates/Stores/TemplatesStore.test.ts index 5795bf77b..2cd530d2d 100644 --- a/packages/snap-preact/src/Templates/Stores/TemplatesStore.test.ts +++ b/packages/snap-preact/src/Templates/Stores/TemplatesStore.test.ts @@ -1,5 +1,6 @@ import { TemplatesStore } from './TemplateStore'; import type { SnapTemplatesConfig } from '../SnapTemplate'; +import { GLOBAL_THEME_NAME } from './TargetStore'; describe('TemplateStore', () => { it('has expected defaults', () => { @@ -20,7 +21,8 @@ describe('TemplateStore', () => { expect(store.window.innerWidth).toBe(global.window.innerWidth); }); - it('can define config', () => { + // TODO: unskip and uncomment below when we have more languages + it.skip('can define config', () => { const config: SnapTemplatesConfig = { themes: { global: { @@ -30,7 +32,7 @@ describe('TemplateStore', () => { config: { siteId: '8uyt2m', currency: 'eur', - language: 'fr', + // language: 'fr', }, }; const store = new TemplatesStore({ config }); @@ -48,7 +50,9 @@ describe('TemplateStore', () => { }, config: { siteId: '8uyt2m', + // @ts-ignore - testing invalid values currency: 'dne', + // @ts-ignore - testing invalid values language: 'dne', }, }; @@ -58,7 +62,8 @@ describe('TemplateStore', () => { expect(store.currency).toBe('usd'); }); - it('can change language and currency', async () => { + // TODO: unskip and uncomment below when we have more languages + it.skip('can change language and currency', async () => { const config: SnapTemplatesConfig = { themes: { global: { @@ -73,7 +78,7 @@ describe('TemplateStore', () => { expect(store.language).toBe('en'); expect(store.currency).toBe('usd'); - await store.setLanguage('fr'); + // await store.setLanguage('fr'); await store.setCurrency('eur'); expect(store.language).toBe('fr'); @@ -102,7 +107,7 @@ describe('TemplateStore', () => { }); it('can addTheme', async () => { - const theme = 'global'; + const theme = GLOBAL_THEME_NAME; const config: SnapTemplatesConfig = { themes: { [theme]: { @@ -141,6 +146,7 @@ describe('TemplateStore', () => { variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: store.window.innerWidth, style: undefined, }); @@ -153,7 +159,7 @@ describe('TemplateStore', () => { }); it('can addTarget', async () => { - const theme = 'global'; + const theme = GLOBAL_THEME_NAME; const config: SnapTemplatesConfig = { themes: { [theme]: { @@ -165,7 +171,7 @@ describe('TemplateStore', () => { const type = 'search'; const target = { selector: '.test', - theme: 'global', + theme: GLOBAL_THEME_NAME, component: 'Search', resultComponent: 'Result', }; diff --git a/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts b/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts index 27d4fadd6..40ecfd09d 100644 --- a/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts +++ b/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts @@ -6,6 +6,7 @@ import { ThemeStore, ThemeStoreThemeConfig, mergeThemeLayers } from './ThemeStor import { StorageStore } from '@searchspring/snap-store-mobx'; import type { TemplatesStoreDependencies, TemplateThemeTypes, TemplatesStoreSettings } from './TemplateStore'; import type { Theme, ThemeVariables, ThemePartial } from '../../../components/src/providers/theme'; +import { GLOBAL_THEME_NAME } from './TargetStore'; // configure MobX configureMobx({ enforceActions: 'never' }); @@ -112,7 +113,7 @@ describe('ThemeStore', () => { it('has expected defaults and can invoke methods', () => { const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: { name: 'empty', @@ -124,6 +125,7 @@ describe('ThemeStore', () => { variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, }; @@ -188,13 +190,14 @@ describe('ThemeStore', () => { it('can get theme', () => { const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: {}, variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, }; @@ -216,7 +219,7 @@ describe('ThemeStore', () => { it('can get theme with overrides applied', () => { const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: { @@ -229,6 +232,7 @@ describe('ThemeStore', () => { variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, }; @@ -247,7 +251,7 @@ describe('ThemeStore', () => { it('can get theme with overrides and variables applied', () => { const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: { @@ -277,6 +281,7 @@ describe('ThemeStore', () => { }, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, }; @@ -302,7 +307,7 @@ describe('ThemeStore', () => { it('can get theme with currency and language applied', () => { const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: { @@ -344,6 +349,7 @@ describe('ThemeStore', () => { }, }, }, + languageOverrides: {}, innerWidth: 0, }; @@ -372,13 +378,14 @@ describe('ThemeStore', () => { expect(testTheme.variables?.breakpoints[bpIndex]).toBe(0); const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: {}, variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 300, }; @@ -415,13 +422,14 @@ describe('ThemeStore', () => { expect(testTheme.variables?.breakpoints[bpIndex]).toBeGreaterThan(0); const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: {}, variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 1600, }; @@ -456,7 +464,7 @@ describe('ThemeStore', () => { expect(testTheme.variables?.breakpoints[bpIndex]).toBe(0); const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: { @@ -476,6 +484,7 @@ describe('ThemeStore', () => { }, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 50, }; @@ -517,7 +526,7 @@ describe('ThemeStore', () => { expect(testTheme.variables?.breakpoints[bpIndex]).toBe(0); const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: { @@ -545,6 +554,7 @@ describe('ThemeStore', () => { results: { columns: 11 }, }, }, + languageOverrides: {}, innerWidth: 50, }; @@ -590,13 +600,14 @@ describe('ThemeStore', () => { it('can select layoutOption', () => { const bpIndex = 0; const config: ThemeStoreThemeConfig = { - name: 'global', + name: GLOBAL_THEME_NAME, type: 'local', base: testTheme, overrides: {}, variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, }; @@ -652,6 +663,7 @@ describe('ThemeStore', () => { variables: {}, currency: {}, language: {}, + languageOverrides: {}, innerWidth: 0, style: (theme) => { return { From 8c113a4c45881480b933279cfce80c9dd1702902 Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Wed, 18 Sep 2024 17:58:32 -0400 Subject: [PATCH 05/10] refactor: wip --- .../snap-preact-demo/templates/src/index.ts | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/snap-preact-demo/templates/src/index.ts b/packages/snap-preact-demo/templates/src/index.ts index 961a54fef..0c443bcc2 100644 --- a/packages/snap-preact-demo/templates/src/index.ts +++ b/packages/snap-preact-demo/templates/src/index.ts @@ -10,6 +10,7 @@ new SnapTemplates({ }, components: { result: { + Global: async () => (await import('./components/Result')).CustomResultSecondary, CustomResult: () => CustomResult, CustomResultSecondary: async () => (await import('./components/Result')).CustomResultSecondary, }, @@ -20,9 +21,13 @@ new SnapTemplates({ themes: { myTheme: { extends: 'bocachica', + resultComponent: 'CustomResult', }, global: { extends: 'bocachica', + // resultComponent: 'CustomResultSecondary', + // resultComponent: 'Global', + resultComponent: 'CustomResult', variables: { colors: { primary: 'red', @@ -33,6 +38,18 @@ new SnapTemplates({ style: globalStyles, overrides: { components: { + noResults: { + templates: { + recommendation: { + enabled: true, + component: 'Recommendation', + // resultComponent: 'Global', + config: { + tag: 'similar', + }, + }, + }, + }, price: { style: { background: 'red', @@ -164,33 +181,10 @@ new SnapTemplates({ { selector: '#searchspring-layout', component: 'Search', - resultComponent: 'CustomResultSecondary', + // resultComponent: 'Result', }, ], }, - recommendation: { - settings: { - branch: BRANCHNAME, - }, - bundle: { - Bundle: { - component: 'RecommendationBundle', - // resultComponent: 'CustomResultSecondary', - }, - }, - default: { - Recs: { - component: 'Recommendation', - // resultComponent: 'CustomResultSecondary', - }, - }, - email: { - Email: { - component: 'RecommendationEmail', - // resultComponent: 'CustomResultSecondary', - }, - }, - }, autocomplete: { inputSelector: 'input.searchspring-ac, .thing2', targets: [ From 9f54848591413c9ef70c06de1357536a1400d7b7 Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Thu, 19 Sep 2024 11:07:39 -0400 Subject: [PATCH 06/10] fix: add lang to layoutselector --- .../LayoutSelector/LayoutSelector.tsx | 32 +++++++++++++++++-- .../src/providers/langComponents.ts | 6 ++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/snap-preact/components/src/components/Molecules/LayoutSelector/LayoutSelector.tsx b/packages/snap-preact/components/src/components/Molecules/LayoutSelector/LayoutSelector.tsx index 90c9b75b0..f63261f1c 100644 --- a/packages/snap-preact/components/src/components/Molecules/LayoutSelector/LayoutSelector.tsx +++ b/packages/snap-preact/components/src/components/Molecules/LayoutSelector/LayoutSelector.tsx @@ -10,6 +10,8 @@ import { ComponentProps, ListOption, RootNodeProperties } from '../../../types'; import { Select, SelectProps } from '../Select'; import { List, ListProps } from '../List'; import { RadioList, RadioListProps } from '../RadioList'; +import { Lang } from '../../../hooks'; +import deepmerge from 'deepmerge'; const CSS = { LayoutSelector: ({}: Partial) => css({}), @@ -27,7 +29,7 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El const props = mergeProps('layoutSelector', globalTheme, defaultProps, properties); - const { options, selected, type, onSelect, label, showSingleOption, disableStyles, className, style, styleScript, treePath } = props; + const { options, selected, type, onSelect, showSingleOption, label, disableStyles, className, style, styleScript, treePath } = props; const subProps: SelectSubProps = { Select: { @@ -79,6 +81,16 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El styling.css = [style]; } + //initialize lang + const defaultLang = { + label: { + value: label, + }, + }; + + //deep merge with props.lang + const lang = deepmerge(defaultLang, props.lang || {}); + // options can be an Array or ObservableArray - but should have length return (options && options.length > 1) || (options?.length === 1 && showSingleOption) ? ( @@ -93,6 +105,9 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El onSelect={(e, option) => { onSelect(e, option); }} + lang={{ + buttonLabel: lang.label, + }} /> )} @@ -107,6 +122,9 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El options={options} selected={selected} titleText={label} + lang={{ + title: lang.label, + }} /> )} @@ -121,6 +139,9 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El options={options} selected={selected} titleText={label} + lang={{ + title: lang.label, + }} /> )} @@ -128,7 +149,6 @@ export const LayoutSelector = observer((properties: LayoutSelectorProps): JSX.El ); }); - interface SelectSubProps { Select: Partial; RadioList: Partial; @@ -142,4 +162,12 @@ export interface LayoutSelectorProps extends ComponentProps { label?: string; type?: 'dropdown' | 'list' | 'radio'; showSingleOption?: boolean; + lang?: Partial; +} + +export interface LayoutSelectorLang { + label: Lang<{ + options: ListOption[]; + selectedOptions: ListOption[]; + }>; } diff --git a/packages/snap-preact/components/src/providers/langComponents.ts b/packages/snap-preact/components/src/providers/langComponents.ts index bc4feb6f3..977ba40d3 100644 --- a/packages/snap-preact/components/src/providers/langComponents.ts +++ b/packages/snap-preact/components/src/providers/langComponents.ts @@ -27,7 +27,7 @@ import type { FacetSliderLang } from '../components/Molecules/FacetSlider'; // import type { FacetToggleLang } from '../components/Molecules/FacetToggle'; import type { FilterLang } from '../components/Molecules/Filter'; import type { GridLang } from '../components/Molecules/Grid'; -// import type { LayoutSelectorLang } from '../components/Molecules/LayoutSelector'; +import type { LayoutSelectorLang } from '../components/Molecules/LayoutSelector'; import type { ListLang } from '../components/Molecules/List'; import type { LoadMoreLang } from '../components/Molecules/LoadMore'; // import type { OverlayBadgeLang } from '../components/Molecules/OverlayBadge'; @@ -87,7 +87,7 @@ export type LangComponentOverrides = { // carousel?: Partial checkbox?: Partial; grid?: Partial; - // layoutSelector?: Partial + layoutSelector?: Partial; list?: Partial; radio?: Partial; errorHandler?: Partial; @@ -157,7 +157,7 @@ export type LangComponents = { // carousel: CarouselLang checkbox: CheckboxLang; grid: GridLang; - // layoutSelector: LayoutSelectorLang + layoutSelector: LayoutSelectorLang; list: ListLang; radio: RadioLang; errorHandler: ErrorHandlerLang; From 32e6400ec7e68fb0556852c903e3c25279097967 Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Thu, 19 Sep 2024 12:34:05 -0400 Subject: [PATCH 07/10] fix: themeStore variable.breakpoints undefined in theme due to mobx observable obj --- .../src/Templates/Stores/ThemeStore.test.ts | 29 +++++++++++++++++-- .../src/Templates/Stores/ThemeStore.tsx | 4 +-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts b/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts index 40ecfd09d..840b0adbe 100644 --- a/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts +++ b/packages/snap-preact/src/Templates/Stores/ThemeStore.test.ts @@ -8,8 +8,8 @@ import type { TemplatesStoreDependencies, TemplateThemeTypes, TemplatesStoreSett import type { Theme, ThemeVariables, ThemePartial } from '../../../components/src/providers/theme'; import { GLOBAL_THEME_NAME } from './TargetStore'; -// configure MobX -configureMobx({ enforceActions: 'never' }); +// configure MobX - useProxies: 'never' matches what we are doing for browser support (IE 11) +configureMobx({ enforceActions: 'never', useProxies: 'never' }); const testThemeVariables: ThemeVariables = { breakpoints: [0, 420, 720, 1440], @@ -676,7 +676,7 @@ describe('ThemeStore', () => { new ThemeStore({ config, dependencies, settings }); - // wait for rendering of BranchOverride component + // wait for rendering of stylesheets await waitFor(() => { const styleElements = document.querySelectorAll('head style')!; expect(styleElements).toHaveLength(2); @@ -686,6 +686,29 @@ describe('ThemeStore', () => { expect(styleElements[1]).toHaveAttribute('data-emotion', 'ss-global'); }); }); + + it('correctly merges observable variable arrays', async () => { + // due to mobx observable arrays being objects, we need to ensure that the mergeThemeLayers function + // correctly merges observable arrays (ThemeStore uses toJS function) + + const config: ThemeStoreThemeConfig = { + name: 'globally', + type: 'local', + base: testTheme, + overrides: {}, + variables: { + breakpoints: [1, 2, 3, 4], + }, + currency: {}, + language: {}, + languageOverrides: {}, + innerWidth: 0, + }; + + const themeStore = new ThemeStore({ config, dependencies, settings }); + + expect(themeStore.theme.variables?.breakpoints).toStrictEqual(config.variables?.breakpoints); + }); }); describe('mergeThemeLayers function', () => { diff --git a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx index 1c94dda66..cf79bc3cc 100644 --- a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx +++ b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx @@ -1,5 +1,5 @@ import { h, render } from 'preact'; -import { observable, makeObservable } from 'mobx'; +import { observable, makeObservable, toJS } from 'mobx'; import deepmerge from 'deepmerge'; import { isPlainObject } from 'is-plain-object'; import { StorageStore } from '@searchspring/snap-store-mobx'; @@ -145,7 +145,7 @@ export class ThemeStore { this.overrides, overrideBreakpoint, { - variables: this.variables, + variables: toJS(this.variables), } as ThemePartial ) as Theme; From d7747e673ce14599f160429dd799fa99e6e3d07e Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Thu, 19 Sep 2024 14:28:05 -0400 Subject: [PATCH 08/10] refactor: wip --- .../src/components/Molecules/Grid/Grid.tsx | 13 +++++++ .../src/components/Molecules/List/List.tsx | 13 +++++++ .../Molecules/RadioList/RadioList.tsx | 13 +++++++ .../components/Molecules/Select/Select.tsx | 17 +++++---- .../components/Organisms/Results/Results.tsx | 2 ++ .../Templates/Autocomplete/Autocomplete.tsx | 1 + .../components/Templates/Search/Search.tsx | 1 + .../SearchHorizontal/SearchHorizontal.tsx | 1 + .../components/src/providers/theme.ts | 2 +- .../src/providers/themeComponents.ts | 4 +-- .../src/themes/bocachica/layoutOptions.ts | 36 +++++++++---------- .../src/Templates/Stores/ThemeStore.tsx | 27 +++++++------- 12 files changed, 88 insertions(+), 42 deletions(-) diff --git a/packages/snap-preact/components/src/components/Molecules/Grid/Grid.tsx b/packages/snap-preact/components/src/components/Molecules/Grid/Grid.tsx index 5d3143a7a..62a1aa279 100644 --- a/packages/snap-preact/components/src/components/Molecules/Grid/Grid.tsx +++ b/packages/snap-preact/components/src/components/Molecules/Grid/Grid.tsx @@ -179,6 +179,19 @@ export function Grid(properties: GridProps): JSX.Element { // selection state const [selection, setSelection] = useState((selected as ListOption[]) || []); + // reset selection if 'selected' prop changes + try { + if (selection && selected) { + const selectionstr = JSON.stringify(selection); + const selectedstr = JSON.stringify(selected); + if (selectionstr !== selectedstr) { + setSelection(selected); + } + } + } catch (e) { + // noop + } + const makeSelection = (e: React.MouseEvent, option: ListOption) => { if (multiSelect) { let newArray: ListOption[]; diff --git a/packages/snap-preact/components/src/components/Molecules/List/List.tsx b/packages/snap-preact/components/src/components/Molecules/List/List.tsx index 0d8d26807..5800f4c84 100644 --- a/packages/snap-preact/components/src/components/Molecules/List/List.tsx +++ b/packages/snap-preact/components/src/components/Molecules/List/List.tsx @@ -137,6 +137,19 @@ export function List(properties: ListProps): JSX.Element { // selection state const [selection, setSelection] = useState((selected as ListOption[]) || []); + // reset selection if 'selected' prop changes + try { + if (selection && selected) { + const selectionstr = JSON.stringify(selection); + const selectedstr = JSON.stringify(selected); + if (selectionstr !== selectedstr) { + setSelection(selected); + } + } + } catch (e) { + // noop + } + const makeSelection = (e: React.MouseEvent, option: ListOption) => { let newArray: ListOption[]; diff --git a/packages/snap-preact/components/src/components/Molecules/RadioList/RadioList.tsx b/packages/snap-preact/components/src/components/Molecules/RadioList/RadioList.tsx index b54e1f479..c4fc42c75 100644 --- a/packages/snap-preact/components/src/components/Molecules/RadioList/RadioList.tsx +++ b/packages/snap-preact/components/src/components/Molecules/RadioList/RadioList.tsx @@ -114,6 +114,19 @@ export function RadioList(properties: RadioListProps): JSX.Element { // selection state const [selection, setSelection] = useState(selected); + // reset selection if 'selected' prop changes + try { + if (selection && selected) { + const selectionstr = JSON.stringify(selection); + const selectedstr = JSON.stringify(selected); + if (selectionstr !== selectedstr) { + setSelection(selected); + } + } + } catch (e) { + // noop + } + const makeSelection = (e: React.MouseEvent, option: ListOption) => { if (onSelect) { onSelect(e, option!); diff --git a/packages/snap-preact/components/src/components/Molecules/Select/Select.tsx b/packages/snap-preact/components/src/components/Molecules/Select/Select.tsx index b13a46567..2d91649d6 100644 --- a/packages/snap-preact/components/src/components/Molecules/Select/Select.tsx +++ b/packages/snap-preact/components/src/components/Molecules/Select/Select.tsx @@ -166,13 +166,16 @@ export const Select = observer((properties: SelectProps): JSX.Element => { const [selection, setSelection] = useState(selected); // reset selection if 'selected' prop changes - if (selection && selected && selection != selected) { - setSelection(selected); - } - - // reset selection if 'selected' prop changes - if (selection && selected && selection != selected) { - setSelection(selected); + try { + if (selection && selected) { + const selectionstr = JSON.stringify(selection); + const selectedstr = JSON.stringify(selected); + if (selectionstr !== selectedstr) { + setSelection(selected); + } + } + } catch (e) { + // noop } if (selection && clearSelection) { diff --git a/packages/snap-preact/components/src/components/Organisms/Results/Results.tsx b/packages/snap-preact/components/src/components/Organisms/Results/Results.tsx index 430c8390e..44eb615f8 100644 --- a/packages/snap-preact/components/src/components/Organisms/Results/Results.tsx +++ b/packages/snap-preact/components/src/components/Organisms/Results/Results.tsx @@ -188,3 +188,5 @@ interface ResultsSubProps { result: Partial; inlineBanner: Partial; } + +export type ResultsNames = 'search' | 'autocomplete'; diff --git a/packages/snap-preact/components/src/components/Templates/Autocomplete/Autocomplete.tsx b/packages/snap-preact/components/src/components/Templates/Autocomplete/Autocomplete.tsx index fdb6d66a0..6c038e031 100644 --- a/packages/snap-preact/components/src/components/Templates/Autocomplete/Autocomplete.tsx +++ b/packages/snap-preact/components/src/components/Templates/Autocomplete/Autocomplete.tsx @@ -370,6 +370,7 @@ export const Autocomplete = observer((properties: AutocompleteProps): JSX.Elemen treePath, }, results: { + name: 'autocomplete', // default props className: 'ss__autocomplete__results', breakpoints: props.breakpoints, diff --git a/packages/snap-preact/components/src/components/Templates/Search/Search.tsx b/packages/snap-preact/components/src/components/Templates/Search/Search.tsx index 11335868f..0a943fd43 100644 --- a/packages/snap-preact/components/src/components/Templates/Search/Search.tsx +++ b/packages/snap-preact/components/src/components/Templates/Search/Search.tsx @@ -157,6 +157,7 @@ export const Search = observer((properties: SearchProps): JSX.Element => { treePath, }, Results: { + name: 'search', // default props resultComponent: resultComponent, // inherited props diff --git a/packages/snap-preact/components/src/components/Templates/SearchHorizontal/SearchHorizontal.tsx b/packages/snap-preact/components/src/components/Templates/SearchHorizontal/SearchHorizontal.tsx index 927cf6e54..3869a70cd 100644 --- a/packages/snap-preact/components/src/components/Templates/SearchHorizontal/SearchHorizontal.tsx +++ b/packages/snap-preact/components/src/components/Templates/SearchHorizontal/SearchHorizontal.tsx @@ -113,6 +113,7 @@ export const SearchHorizontal = observer((properties: SearchHorizontalProps): JS treePath, }, Results: { + name: 'search', // default props resultComponent: resultComponent, // inherited props diff --git a/packages/snap-preact/components/src/providers/theme.ts b/packages/snap-preact/components/src/providers/theme.ts index a6ac81bdf..64e58afdc 100644 --- a/packages/snap-preact/components/src/providers/theme.ts +++ b/packages/snap-preact/components/src/providers/theme.ts @@ -57,7 +57,7 @@ export type Theme = { variables?: ThemeVariables; responsive?: [ThemeResponsive, ThemeResponsive, ThemeResponsive, ThemeResponsive]; components?: ThemeComponentOverrides; - layoutOptions?: ListOption[]; + layoutOptions?: (Omit & { overrides: ThemeMinimal })[]; }; export type ThemeResponsive = Pick; diff --git a/packages/snap-preact/components/src/providers/themeComponents.ts b/packages/snap-preact/components/src/providers/themeComponents.ts index e353fa492..4ac631e80 100644 --- a/packages/snap-preact/components/src/providers/themeComponents.ts +++ b/packages/snap-preact/components/src/providers/themeComponents.ts @@ -48,7 +48,7 @@ import type { FacetProps } from '../components/Organisms/Facet'; import type { FacetsHorizontalProps } from '../components/Organisms/FacetsHorizontal'; import type { FacetsProps } from '../components/Organisms/Facets'; import type { FilterSummaryProps } from '../components/Organisms/FilterSummary'; -import type { ResultsProps } from '../components/Organisms/Results'; +import type { ResultsNames, ResultsProps } from '../components/Organisms/Results'; import type { SearchHeaderProps } from '../components/Atoms/SearchHeader'; import type { SidebarProps } from '../components/Organisms/Sidebar'; import type { ToolbarProps, ToolbarNames } from '../components/Organisms/Toolbar'; @@ -199,7 +199,7 @@ export type ThemeComponentOverrides = { } & { [K in UnNamedThemeComponentSelectors<'filterSummary'>]?: GenericComponentProps } & { [K in UnNamedThemeComponentSelectors<'noResults'>]?: GenericComponentProps; } & { - [K in UnNamedThemeComponentSelectors<'results'>]?: GenericComponentProps; + [K in NamedThemeComponentSelectors<'results', ResultsNames>]?: GenericComponentProps; } & { [K in UnNamedThemeComponentSelectors<'searchHeader'>]?: GenericComponentProps } & { [K in UnNamedThemeComponentSelectors<'sidebar'>]?: GenericComponentProps; } & { [K in UnNamedThemeComponentSelectors<'mobileSidebar'>]?: GenericComponentProps } & { diff --git a/packages/snap-preact/components/src/themes/bocachica/layoutOptions.ts b/packages/snap-preact/components/src/themes/bocachica/layoutOptions.ts index 4f9e3ca74..9d40adb3e 100644 --- a/packages/snap-preact/components/src/themes/bocachica/layoutOptions.ts +++ b/packages/snap-preact/components/src/themes/bocachica/layoutOptions.ts @@ -1,31 +1,27 @@ -import { ListOption } from '../../types'; +import { Theme } from '../../providers/theme'; -export const layoutOptions: ListOption[] = [ +export const layoutOptions: Theme['layoutOptions'] = [ { value: 1, icon: 'square', - // overrides: { - // components: { - // results: { - // named: { - // searchResults: { columns: 1 }, - // }, - // }, - // }, - // }, + overrides: { + components: { + 'results.search': { + columns: 1, + }, + }, + }, }, { value: 2, default: true, icon: 'layout-large', - // overrides: { - // components: { - // results: { - // named: { - // searchResults: { columns: 2 }, - // }, - // }, - // }, - // }, + overrides: { + components: { + 'results.search': { + columns: 2, + }, + }, + }, }, ]; diff --git a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx index cf79bc3cc..e1ff6b6e3 100644 --- a/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx +++ b/packages/snap-preact/src/Templates/Stores/ThemeStore.tsx @@ -136,18 +136,21 @@ export class ThemeStore { const baseBreakpoint = getOverridesAtWidth(this.innerWidth, breakpoints, this.base); const overrideBreakpoint = getOverridesAtWidth(this.innerWidth, breakpoints, this.overrides); - let theme: Theme = mergeThemeLayers( - this.base, - baseBreakpoint, - this.currency, - this.language, - this.languageOverrides, - this.overrides, - overrideBreakpoint, - { - variables: toJS(this.variables), - } as ThemePartial - ) as Theme; + const base = { ...this.base }; + const overrides = { ...this.overrides }; + + if (this.overrides.layoutOptions?.length) { + base.layoutOptions = []; + } + + if (overrideBreakpoint.layoutOptions?.length) { + base.layoutOptions = []; + overrides.layoutOptions = []; + } + + let theme: Theme = mergeThemeLayers(base, baseBreakpoint, this.currency, this.language, this.languageOverrides, overrides, overrideBreakpoint, { + variables: toJS(this.variables), + } as ThemePartial) as Theme; // find layout option overrides const layoutOptions = theme.layoutOptions; From fc97eb0c57bb373c6a70d0155f0f55f86e1f603c Mon Sep 17 00:00:00 2001 From: Dennis Konieczek Date: Thu, 19 Sep 2024 17:33:50 -0400 Subject: [PATCH 09/10] refactor: wip --- package-lock.json | 74 ++++--- packages/snap-preact-demo/package.json | 1 + .../snap/src/components/Content/Content.tsx | 46 +++-- .../snap/src/components/Finder/Finder.tsx | 126 ++++++------ .../snap/src/components/Header/Header.tsx | 16 +- .../snap/src/components/Profile/Profile.tsx | 135 ++++++------- .../snap/src/components/Results/Results.tsx | 187 +++++++++--------- .../snap/src/components/Sidebar/Sidebar.tsx | 46 ++--- .../snap/src/components/Toolbar/PerPage.tsx | 23 +-- .../snap/src/components/Toolbar/SortBy.tsx | 23 +-- .../snap/src/components/Toolbar/Toolbar.tsx | 46 ++--- packages/snap-preact-demo/tsconfig.json | 2 +- .../src/components/Molecules/Grid/Grid.tsx | 9 +- .../src/components/Molecules/List/List.tsx | 10 +- .../Molecules/RadioList/RadioList.tsx | 9 +- .../components/Molecules/Select/Select.tsx | 9 +- .../components/Organisms/Results/Results.tsx | 19 +- 17 files changed, 372 insertions(+), 409 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76aa7c7c0..d1cf0cd1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2452,11 +2452,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" - }, "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", @@ -2467,6 +2462,29 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, + "node_modules/@emotion/react": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz", + "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@emotion/serialize": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz", @@ -2502,6 +2520,11 @@ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -25146,9 +25169,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -27476,9 +27499,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -30809,6 +30832,7 @@ "@babel/preset-env": "^7.23.7", "@babel/preset-react": "^7.23.3", "@babel/runtime": "^7.23.7", + "@emotion/react": "11.13.0", "@lhci/cli": "^0.13.0", "@searchspring/browserslist-config-snap": "^1.0.6", "babel-loader": "^9.1.2", @@ -31028,34 +31052,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/snap-preact/node_modules/@emotion/react": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz", - "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.12.0", - "@emotion/cache": "^11.13.0", - "@emotion/serialize": "^1.3.0", - "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", - "@emotion/utils": "^1.4.0", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "packages/snap-preact/node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" - }, "packages/snap-preact/node_modules/@storybook/addon-actions": { "version": "7.6.7", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.7.tgz", diff --git a/packages/snap-preact-demo/package.json b/packages/snap-preact-demo/package.json index 7b2d6383e..32863c048 100644 --- a/packages/snap-preact-demo/package.json +++ b/packages/snap-preact-demo/package.json @@ -47,6 +47,7 @@ "@babel/preset-env": "^7.23.7", "@babel/preset-react": "^7.23.3", "@babel/runtime": "^7.23.7", + "@emotion/react": "11.13.0", "@lhci/cli": "^0.13.0", "@searchspring/browserslist-config-snap": "^1.0.6", "babel-loader": "^9.1.2", diff --git a/packages/snap-preact-demo/snap/src/components/Content/Content.tsx b/packages/snap-preact-demo/snap/src/components/Content/Content.tsx index 4db2d9d68..c27b073a5 100644 --- a/packages/snap-preact-demo/snap/src/components/Content/Content.tsx +++ b/packages/snap-preact-demo/snap/src/components/Content/Content.tsx @@ -1,4 +1,4 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; import { observer } from 'mobx-react'; import { ThemeProvider, LoadingBar, defaultTheme, StoreProvider, ControllerProvider, SnapProvider } from '@searchspring/snap-preact/components'; @@ -11,29 +11,27 @@ type ContentProps = { snap?: Snap; }; -@observer -export class Content extends Component { - render() { - const store = this.props.controller.store; - const snap = this.props.snap; - const theme = snap?.templates?.themes.local.global.theme; +export const Content = observer(({ controller, snap }: ContentProps) => { + const store = controller.store; + const theme = snap?.templates?.themes.local.global.theme; - return ( - - - - -
- + return ( + + + + +
+ -
+
- {store.pagination.totalResults ? : store.pagination.totalResults === 0 && } -
-
-
-
-
- ); - } -} + {store.pagination.totalResults ? : store.pagination.totalResults === 0 && } +
+
+
+
+
+ ); +}); + +export default Content; diff --git a/packages/snap-preact-demo/snap/src/components/Finder/Finder.tsx b/packages/snap-preact-demo/snap/src/components/Finder/Finder.tsx index 9664b900d..17ac99556 100644 --- a/packages/snap-preact-demo/snap/src/components/Finder/Finder.tsx +++ b/packages/snap-preact-demo/snap/src/components/Finder/Finder.tsx @@ -1,60 +1,56 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; import { observer } from 'mobx-react'; type FinderProps = { controller?: FinderController; }; -@observer -export class Finder extends Component { - render() { - const controller = this.props.controller; - const store = controller.store; - const { selections, loading, pagination } = store; +export const Finder = observer(({ controller }: FinderProps) => { + const store = controller.store; + const { selections, loading, pagination } = store; - return ( - selections.length > 0 && ( -
-
- {selections.map((selection) => - controller.config.wrapSelect ? ( -
- -
- ) : ( + return ( + selections.length > 0 && ( +
+
+ {selections.map((selection) => + controller.config.wrapSelect ? ( +
- ) - )} +
+ ) : ( + + ) + )} - {` ${pagination.totalResults} results`} + {` ${pagination.totalResults} results`} -
- -   - -
+
+ +   +
- ) - ); - } -} +
+ ) + ); +}); type DropdownProps = { store: FinderController['store']; @@ -62,26 +58,20 @@ type DropdownProps = { loading: boolean; }; -@observer -class Dropdown extends Component { - render() { - const selection = this.props.selection; - const loading = this.props.loading; - - return ( - - ); - } -} +export const Dropdown = observer(({ selection, loading }: DropdownProps) => { + return ( + + ); +}); diff --git a/packages/snap-preact-demo/snap/src/components/Header/Header.tsx b/packages/snap-preact-demo/snap/src/components/Header/Header.tsx index 1808ab658..67186d004 100644 --- a/packages/snap-preact-demo/snap/src/components/Header/Header.tsx +++ b/packages/snap-preact-demo/snap/src/components/Header/Header.tsx @@ -1,4 +1,4 @@ -import { h, Fragment, Component } from 'preact'; +import { h, Fragment } from 'preact'; import { observer } from 'mobx-react'; import { withController } from '@searchspring/snap-preact/components'; @@ -7,13 +7,11 @@ type HeaderProps = { controller?: SearchController; }; -@withController -@observer -export class Header extends Component { - render() { - const { pagination, search } = this.props.controller.store; +export const Header = withController( + observer(({ controller }: HeaderProps) => { + const { pagination, search } = controller.store; + const landingPage = controller.store.merchandising.landingPage; - const landingPage = this.props.controller.store.merchandising.landingPage; return (
{landingPage ? ( @@ -60,5 +58,5 @@ export class Header extends Component { )}
); - } -} + }) +); diff --git a/packages/snap-preact-demo/snap/src/components/Profile/Profile.tsx b/packages/snap-preact-demo/snap/src/components/Profile/Profile.tsx index 863ce5fbb..eec85a68e 100644 --- a/packages/snap-preact-demo/snap/src/components/Profile/Profile.tsx +++ b/packages/snap-preact-demo/snap/src/components/Profile/Profile.tsx @@ -1,108 +1,91 @@ // TODO move to components library -import { h, Component } from 'preact'; +import { h } from 'preact'; +import { useEffect, useRef } from 'preact/hooks'; type ProfileProps = { name: string; controller: SearchController; context?: any; + children?: any; }; -export class Profile extends Component { - name; - controller; - log; - profiler; - profile; +export const Profile = ({ name, controller, context, children }: ProfileProps) => { + const log = controller.log; + const profiler = controller.profiler; + const profileRef = useRef(profiler.create({ type: 'component', name, context }).start()); - logComponent = { + const logComponent = { creation: ({ name }: { name: string }) => { - this.log.dev( + log.dev( `%c + %c<${name}/> %c:: %cCREATED`, - `color: ${this.log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, - `color: ${this.log.colors.orange};`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.orange}; font-weight: bold;` + `color: ${log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, + `color: ${log.colors.orange};`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.orange}; font-weight: bold;` ); }, change: ({ name, info = 'changed' }: { name: string; info: string }) => { - this.log.dev( - `%c ${this.log.emoji.lightning} %c<${name}/> %c:: %c${info.toUpperCase()}`, - `color: ${this.log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, - `color: ${this.log.colors.orange};`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.orangedark}; font-weight: bold;` + log.dev( + `%c ${log.emoji.lightning} %c<${name}/> %c:: %c${info.toUpperCase()}`, + `color: ${log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, + `color: ${log.colors.orange};`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.orangedark}; font-weight: bold;` ); }, error: ({ name, error = 'component crash' }: { name: string; error: string }) => { - this.log.dev( - `%c ${this.log.emoji.bang} %c<${name}/> %c:: %cERROR %c:: %c${error}`, - `color: ${this.log.colors.red}`, - `color: ${this.log.colors.red};`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.red}; font-weight: bold;`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.redlight};` + log.dev( + `%c ${log.emoji.bang} %c<${name}/> %c:: %cERROR %c:: %c${error}`, + `color: ${log.colors.red}`, + `color: ${log.colors.red};`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.red}; font-weight: bold;`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.redlight};` ); }, render: ({ name, time }: { name: string; time: number }) => { - this.log.dev( - `%c ${this.log.emoji.magic} %c<${name}/> %c:: %cRENDERED %c:: %c${time}ms`, - `color: ${this.log.colors.orange};`, - `color: ${this.log.colors.orange};`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.orangedark}; font-weight: bold;`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.grey};` + log.dev( + `%c ${log.emoji.magic} %c<${name}/> %c:: %cRENDERED %c:: %c${time}ms`, + `color: ${log.colors.orange};`, + `color: ${log.colors.orange};`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.orangedark}; font-weight: bold;`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.grey};` ); }, removal: ({ name }: { name: string }) => { - this.log.dev( + log.dev( `%c - %c<${name}/> %c:: %cREMOVED`, - `color: ${this.log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, - `color: ${this.log.colors.orange};`, - `color: ${this.log.colors.orangedark};`, - `color: ${this.log.colors.reddark}; font-weight: bold;` + `color: ${log.colors.orange}; font-weight: bold; font-size: 14px; line-height: 12px;`, + `color: ${log.colors.orange};`, + `color: ${log.colors.orangedark};`, + `color: ${log.colors.reddark}; font-weight: bold;` ); }, }; - constructor(props: ProfileProps) { - super(props); + useEffect(() => { + const profile = profileRef.current; + profile.stop(); + logComponent.render({ name, time: profile.time.run }); - this.name = props.name; - this.controller = props.controller; - this.log = this.controller.log; - this.profiler = this.controller.profiler; - this.context = props.context; - this.profile = this.createProfile(); - } + return () => { + logComponent.removal({ name }); + }; + }, []); - shouldComponentUpdate() { - this.profile = this.createProfile(); - this.logComponent.change({ name: this.name, info: 'update triggered' }); - return true; - } + useEffect(() => { + const profile = profiler.create({ type: 'component', name, context }).start(); + profileRef.current = profile; + logComponent.change({ name, info: 'update triggered' }); - componentDidMount() { - this.profile.stop(); - this.logComponent.render({ name: this.name, time: this.profile.time.run }); - } + return () => { + profile.stop(); + logComponent.change({ name, info: 'updated' }); + }; + }); - componentDidUpdate() { - this.profile.stop(); - - this.logComponent.change({ name: this.name, info: 'updated' }); - } - - componentDidCatch(error: string) { - this.logComponent.error({ name: this.name, error }); - } - - createProfile() { - return this.profiler.create({ type: 'component', name: this.name, context: this.context }).start(); - } - - render() { - return this.props.children; - } -} + return <>{children}; +}; diff --git a/packages/snap-preact-demo/snap/src/components/Results/Results.tsx b/packages/snap-preact-demo/snap/src/components/Results/Results.tsx index a78038218..c8644c55c 100644 --- a/packages/snap-preact-demo/snap/src/components/Results/Results.tsx +++ b/packages/snap-preact-demo/snap/src/components/Results/Results.tsx @@ -1,11 +1,12 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; +import { useEffect } from 'preact/hooks'; + import { observer } from 'mobx-react'; import { Pagination, Results as ResultsComponent, LoadMore, - withStore, withController, withSnap, Recommendation, @@ -32,14 +33,11 @@ const resultsBreakpoints = { }, }; -@withStore -@withController -@observer -export class Results extends Component { - render() { - const results = this.props.store.results; - const pagination = this.props.store.pagination; - const controller = this.props.controller; +export const Results = withController( + observer(({ controller }: ResultsProps) => { + const store = controller.store; + const results = store.results; + const pagination = store.pagination; return (
@@ -56,104 +54,97 @@ export class Results extends Component {
- {(() => { - if (controller.config.settings.infinite) { - return ; - } else if (pagination.totalPages > 1) { - return ; - } - })()} + {controller.config.settings.infinite ? ( + + ) : ( + pagination.totalPages > 1 && + )}
); - } -} - -type NoResultsProps = { - store?: SearchStore; - controller?: SearchController; - snap?: Snap; -}; - -@withSnap -@withController -@withStore -@observer -export class NoResults extends Component { - render() { - const store = this.props.store; - const dym = store.search.didYouMean; - - const recsController = useCreateController(this.props.snap, 'recommendation', { - id: 'no-results', - tag: 'no-results', - branch: 'production', - }); - if (!recsController?.store?.loaded && recsController?.store.error?.type !== 'error') { - recsController?.search(); - } - - return ( -
-
- {dym && ( -

- Did you mean {dym.string}? -

- )} -
- -
-

Suggestions

- -
    -
  • Check for misspellings.
  • -
  • Remove possible redundant keywords (ie. "products").
  • -
  • Use other words to describe what you are searching for.
  • -
- -

- Still can't find what you're looking for? Contact us. -

- -
-

Address

-

- 1234 Random Street -
- Some City, XX, 12345 -

+ }) +); + +export const NoResults = withSnap( + withController( + observer(({ controller, snap }) => { + const store = controller.store; + const dym = store.search.didYouMean; + + const recsController = useCreateController(snap, 'recommendation', { + id: 'no-results', + tag: 'no-results', + branch: 'production', + }); + + useEffect(() => { + if (!recsController?.store?.loaded && recsController?.store.error?.type !== 'error') { + recsController?.search(); + } + }, [recsController]); + + return ( +
+
+ {dym && ( +

+ Did you mean {dym.string}? +

+ )}
-
-

Hours

-

- Mon - Sat, 00:00am - 00:00pm -
- Sun, 00:00am - 00:00pm -

-
+
+

Suggestions

-
-

Call Us

-

- Telephone: 123-456-7890 -
- Toll Free: 123-456-7890 -

-
+
    +
  • Check for misspellings.
  • +
  • Remove possible redundant keywords (ie. "products").
  • +
  • Use other words to describe what you are searching for.
  • +
-
-

Email

- email@sitename.com + Still can't find what you're looking for? Contact us.

+ +
+

Address

+

+ 1234 Random Street +
+ Some City, XX, 12345 +

+
+ +
+

Hours

+

+ Mon - Sat, 00:00am - 00:00pm +
+ Sun, 00:00am - 00:00pm +

+
+ +
+

Call Us

+

+ Telephone: 123-456-7890 +
+ Toll Free: 123-456-7890 +

+
+ +
+

Email

+

+ email@sitename.com +

+
+
{recsController?.store?.loaded && }
-
{recsController?.store?.loaded && }
-
- ); - } -} + ); + }) + ) +); diff --git a/packages/snap-preact-demo/snap/src/components/Sidebar/Sidebar.tsx b/packages/snap-preact-demo/snap/src/components/Sidebar/Sidebar.tsx index ccc297d26..ffdad786f 100644 --- a/packages/snap-preact-demo/snap/src/components/Sidebar/Sidebar.tsx +++ b/packages/snap-preact-demo/snap/src/components/Sidebar/Sidebar.tsx @@ -1,4 +1,4 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; import { observer } from 'mobx-react'; import { @@ -7,7 +7,6 @@ import { FilterSummary, Facets, StoreProvider, - withStore, withController, ControllerProvider, } from '@searchspring/snap-preact/components'; @@ -16,40 +15,35 @@ type SidebarProps = { controller?: SearchController; }; -@observer -export class Sidebar extends Component { - render() { - const store = this.props.controller.store; +export const Sidebar = observer(({ controller }: SidebarProps) => { + const store = controller.store; - return ( - - - - - - - - ); - } -} + return ( + + + + + + + + ); +}); type SidebarContentsProps = { controller?: SearchController; store?: SearchStore; }; -@withController -@withStore -@observer -export class SidebarContents extends Component { - render() { - const { filters, facets } = this.props.store; +export const SidebarContents = withController( + observer(({ controller }: SidebarContentsProps) => { + const store = controller.store; + const { filters, facets } = store; return (
- +
); - } -} + }) +); diff --git a/packages/snap-preact-demo/snap/src/components/Toolbar/PerPage.tsx b/packages/snap-preact-demo/snap/src/components/Toolbar/PerPage.tsx index c77d86f0e..5d14820a5 100644 --- a/packages/snap-preact-demo/snap/src/components/Toolbar/PerPage.tsx +++ b/packages/snap-preact-demo/snap/src/components/Toolbar/PerPage.tsx @@ -1,21 +1,16 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; import { observer } from 'mobx-react'; -import { Select, withStore } from '@searchspring/snap-preact/components'; +import { Select, withController } from '@searchspring/snap-preact/components'; type PerPageProps = { - store?: SearchStore; + controller?: SearchController; }; -@withStore -@observer -export class PerPage extends Component { - constructor(props: PerPageProps) { - super(props); - } - - render() { - const { pagination } = this.props.store; +export const PerPage = withController( + observer(({ controller }: PerPageProps) => { + const store = controller.store; + const { pagination } = store; return (
@@ -29,5 +24,5 @@ export class PerPage extends Component { />
); - } -} + }) +); diff --git a/packages/snap-preact-demo/snap/src/components/Toolbar/SortBy.tsx b/packages/snap-preact-demo/snap/src/components/Toolbar/SortBy.tsx index 2e17f1c24..b749a9508 100644 --- a/packages/snap-preact-demo/snap/src/components/Toolbar/SortBy.tsx +++ b/packages/snap-preact-demo/snap/src/components/Toolbar/SortBy.tsx @@ -1,21 +1,16 @@ -import { h, Component } from 'preact'; +import { h } from 'preact'; import { observer } from 'mobx-react'; -import { Select, withStore } from '@searchspring/snap-preact/components'; +import { Select, withController } from '@searchspring/snap-preact/components'; type SortByProps = { - store?: SearchStore; + controller?: SearchController; }; -@withStore -@observer -export class SortBy extends Component { - constructor(props: SortByProps) { - super(props); - } - - render() { - const { sorting } = this.props.store; +export const SortBy = withController( + observer(({ controller }: SortByProps) => { + const store = controller.store; + const { sorting } = store; return (