From 2989beab62fb1a2ce811255a6c8742a1f0d35ce6 Mon Sep 17 00:00:00 2001 From: Graeme Houston Date: Fri, 22 Nov 2024 23:32:01 +0000 Subject: [PATCH] refactor: (tiles) improve tiles loading state and fix empty sections handling --- .storybook/preview.tsx | 184 ++-- lib/components/atoms/Skeleton/index.tsx | 114 +++ lib/components/atoms/index.ts | 1 + .../molecules/SectionHeader/index.tsx | 3 +- .../organisms/Group/Group.stories.tsx | 944 +++++++++++++++++- lib/components/organisms/Group/index.tsx | 32 +- lib/components/organisms/PointsTile/index.tsx | 4 +- lib/context/WllSdkContext.tsx | 4 + lib/types/tile.ts | 1 + lib/utils/themeHelpers.ts | 76 ++ 10 files changed, 1225 insertions(+), 138 deletions(-) create mode 100644 lib/components/atoms/Skeleton/index.tsx diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a376694..91172ba 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -4,12 +4,14 @@ import { SectionContext } from '../lib/components/organisms/Section'; import { WllSdkProvider } from '../lib/context/WllSdkContext'; import { SectionType, TSection } from '../lib/types/section'; import { + CTALinkTarget, ProgressType, TierTileType, TileHeight, TileType, } from '../lib/types/tile'; import { defaultTheme } from '../lib/utils/styling'; +import { ThemeName, themes } from '../lib/utils/themeHelpers'; const sdkConfig = { apiKey: 'test', @@ -26,146 +28,123 @@ type MockSectionProviderProps = { export const MockSectionProvider: React.FC = ({ children, sectionData = { - name: 'Section2', + details: [], + defaultLocale: 'en', + name: 'Misc Tiles', type: SectionType.Grid, active: true, - pointsMultiplier: 1, - pointsPrefix: undefined, - pointsSuffix: undefined, - id: '84739810-37f5-4621-8433-8e0b61e09a5e', - createdAt: '2024-10-15T13:36:00.204Z', - updatedAt: '2024-10-15T13:36:00.204Z', - title: undefined, - description: undefined, - priority: 2, + id: 'cc1bf03b-42fd-4276-b543-84ec7a1cc527', + createdAt: '2024-11-14T19:00:31.894Z', + updatedAt: '2024-11-15T09:48:37.232Z', + priority: 5, tiles: [ + { + tileHeight: TileHeight.Half, + active: true, + type: TileType.Content, + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: CTALinkTarget.newWindow, + locale: 'en', + title: 'Welcome Graeme', + body: 'Your culinary adventure awaits, tuck in and start earning!', + ctaLink: 'https://www.google.com', + }, + id: 'fb42e341-c477-4b84-a8cf-02aa1f76a351', + createdAt: '2024-11-14T17:58:13.107Z', + updatedAt: '2024-11-21T21:44:41.237Z', + priority: 8, + }, { tileHeight: TileHeight.Half, active: true, type: TileType.Tier, - priority: 0, configuration: { type: TierTileType.currentTier, tierId: '54f0658a-9409-41f7-a508-ca02c6ac23e1', progressType: ProgressType.Name, tier: { + id: '5607df63-8e18-4a5c-ab71-06330368c1b3', + name: 'Platinum', + description: null, + artworkUrl: null, + pointsRequirement: 10000, priority: 0, - id: '11ff96d2-c87b-4c89-bf5c-996604fc4d28', - name: 'Bronze', - description: undefined, - artworkUrl: undefined, - pointsRequirement: 100, }, - pointsMultiplier: 1, - pointsPrefix: undefined, - pointsSuffix: undefined, + locale: 'en', + title: 'Your Tier', + pointsSuffix: 'points', + emptyArtworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + emptyDescription: 'Oops. You’re not in a tier.', + pointsToTierPrefix: 'Earn', + pointsToTierSuffix: 'to achieve this tier.', }, - id: '89255d20-737a-4aa9-af12-9266b4b1dedf', - createdAt: '2024-10-15T13:24:28.991Z', - updatedAt: '2024-10-15T13:24:28.991Z', + id: 'bcd700c0-1288-4117-9ff0-fe2a72913e77', + createdAt: '2024-11-14T18:06:25.686Z', + updatedAt: '2024-11-14T18:06:25.686Z', + priority: 7, }, { tileHeight: TileHeight.Half, active: true, - type: TileType.Tier, - priority: 0, + type: 'POINTS', configuration: { - type: TierTileType.currentTargetSpecific, - tierId: '95a6405a-36d9-471e-9ccb-75088c6f2ba6', - progressType: ProgressType.Points, - tier: { - priority: 0, - id: '11ff96d2-c87b-4c89-bf5c-996604fc4d28', - name: 'Bronze', - description: undefined, - artworkUrl: undefined, - pointsRequirement: 100, - }, - targetTier: { - id: '95a6405a-36d9-471e-9ccb-75088c6f2ba6', - name: 'Silver', - pointsRequirement: 2000, - attained: false, - artworkUrl: undefined, - earnedPoints: 300, - priority: 1, - }, + defaultLocale: 'en', + details: [], + points: 5600, + locale: 'en', + title: 'Points balance', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + pointsPrefix: '', + pointsSuffix: 'pts', pointsMultiplier: 1, - pointsPrefix: undefined, - pointsSuffix: undefined, }, - id: '11ac9b70-f025-4184-8372-7acbf0ac08cd', - createdAt: '2024-10-15T13:21:40.133Z', - updatedAt: '2024-10-15T13:21:40.133Z', + id: '7530fc60-6352-47bc-86a1-8d54e9848fcc', + createdAt: '2024-11-14T18:07:37.939Z', + updatedAt: '2024-11-14T18:07:37.939Z', + priority: 6, }, { tileHeight: TileHeight.Half, active: true, type: TileType.Content, configuration: { - title: 'Welcome Graeme', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + defaultLocale: 'en', + details: [], + ctaLinkTarget: CTALinkTarget.newWindow, + locale: 'en', + title: '', + body: 'Every bite brings you closer to rewards! Earn 1 point for every $1 spent!', + artworkUrl: null, + ctaLink: 'https://google.co.uk', }, - id: '95d0cf59-198b-48b2-9f42-931cd28bee6e', - createdAt: '2024-10-15T11:38:59.522Z', - updatedAt: '2024-10-15T11:38:59.522Z', - priority: 0, + id: '288c9873-729e-4766-83fc-1e15cafde6ba', + createdAt: '2024-11-14T18:44:18.897Z', + updatedAt: '2024-11-14T18:44:18.897Z', + priority: 5, }, ], + title: 'Profile', + locale: 'en', + description: + 'Welcome to WLL Explorer! Find out more about our exciting loyalty program.', }, }) => { return ( + // @ts-ignore {children} ); }; -const darkTheme = { - ...defaultTheme, - background: '#1a1a1a', - text: '#ffffff', - surface: '#2c2c2c', - surfaceText: '#ffffff', - primary: '#FFD23C', -}; - -const clientATheme = { - ...defaultTheme, - primary: '#e63946', - accent: '#a8dadc', - surface: '#fff', -}; - -const clientBTheme = { - ...defaultTheme, - primary: '#2E8840', - accent: '#2E8840', - background: '#ECE3D7', - surface: '#fff', -}; - -const themes = { - default: defaultTheme, - dark: darkTheme, - clientA: clientATheme, - clientB: clientBTheme, -}; -const styleTag = ` - -`; const preview: Preview = { decorators: [ (Story, context) => { - const selectedTheme = context.globals.theme; + const selectedTheme = context.globals.theme as ThemeName; const theme = themes[selectedTheme] || defaultTheme; return ( @@ -187,11 +166,12 @@ const preview: Preview = { ], parameters: { layout: 'fullscreen', - backgrounds: { disable: true }, // Disable the backgrounds addon + backgrounds: { disable: true }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, - date: /Date$/i, + date: /Date$/, }, }, }, @@ -205,8 +185,10 @@ const preview: Preview = { items: [ { value: 'default', title: 'Default' }, { value: 'dark', title: 'Dark' }, - { value: 'clientA', title: 'Red' }, - { value: 'clientB', title: 'Green' }, + { value: 'modern', title: 'Modern' }, + { value: 'warm', title: 'Warm' }, + { value: 'forest', title: 'Forest' }, + { value: 'sunset', title: 'Sunset' }, ], }, }, diff --git a/lib/components/atoms/Skeleton/index.tsx b/lib/components/atoms/Skeleton/index.tsx new file mode 100644 index 0000000..f2db7ee --- /dev/null +++ b/lib/components/atoms/Skeleton/index.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import { Animated, StyleSheet, View, ViewStyle } from 'react-native'; +import { GRID_GAP } from '../../../constants/grid'; +import { useWllSdk } from '../../../context/WllSdkContext'; + +interface SkeletonProps { + width?: number | `${number}%`; + height?: number | `${number}%`; + borderRadius?: number; + style?: ViewStyle; + numberOfSquares?: number; +} + +const Skeleton: React.FC = ({ style, numberOfSquares = 4 }) => { + const { theme } = useWllSdk(); + const animatedValue = React.useRef(new Animated.Value(0)).current; + + React.useEffect(() => { + const pulseAnimation = Animated.loop( + Animated.sequence([ + Animated.timing(animatedValue, { + toValue: 1, + duration: 1000, + useNativeDriver: true, + }), + Animated.timing(animatedValue, { + toValue: 0, + duration: 1000, + useNativeDriver: true, + }), + ]) + ); + + pulseAnimation.start(); + + return () => pulseAnimation.stop(); + }, [animatedValue]); + + const opacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0.3, 0.7], + }); + + const sharedSkeletonStyle = { + opacity, + backgroundColor: theme.alphaDerivedText[20], + borderRadius: theme.sizes.borderRadiusLg, + }; + + return ( + + + + + {Array.from({ length: numberOfSquares }).map((_, index) => ( + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: GRID_GAP, + justifyContent: 'space-between', + width: '100%', + maxWidth: 1080, + }, + title: { + height: 40, + marginBottom: 10, + width: 300, + }, + body: { + height: 25, + width: '85%', + marginBottom: 24, + }, + skeleton: { + overflow: 'hidden', + }, + square: { + width: 1000 / 4, + aspectRatio: 1, + }, +}); + +export default Skeleton; diff --git a/lib/components/atoms/index.ts b/lib/components/atoms/index.ts index 4c71dad..fc0e440 100644 --- a/lib/components/atoms/index.ts +++ b/lib/components/atoms/index.ts @@ -9,4 +9,5 @@ export { default as Text } from './Text'; export { default as BaseBanner } from './BaseBanner'; export { default as BaseTile } from './BaseTile'; +export { default as Skeleton } from './Skeleton'; export { default as TileContainer } from './TileContainer'; diff --git a/lib/components/molecules/SectionHeader/index.tsx b/lib/components/molecules/SectionHeader/index.tsx index d66140d..e39f58e 100644 --- a/lib/components/molecules/SectionHeader/index.tsx +++ b/lib/components/molecules/SectionHeader/index.tsx @@ -27,6 +27,7 @@ const SectionHeader: React.FC = ({ styles.sectionTitle, { fontWeight: '700', + color: theme.text, }, ]} > @@ -38,7 +39,7 @@ const SectionHeader: React.FC = ({ style={[ styles.sectionDescription, { - color: theme.alphaDerivedText[20], + color: theme.alphaDerivedText[80], }, ]} > diff --git a/lib/components/organisms/Group/Group.stories.tsx b/lib/components/organisms/Group/Group.stories.tsx index 4512cf1..c0e4da7 100644 --- a/lib/components/organisms/Group/Group.stories.tsx +++ b/lib/components/organisms/Group/Group.stories.tsx @@ -1,56 +1,938 @@ -import { Meta, StoryFn } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; -import Group from './index'; +import { WllSdkProvider } from '../../../context/WllSdkContext'; +import { defaultTheme } from '../../../utils/styling'; -export default { - title: 'components/organisms/Group', - component: Group, - decorators: [(Story) => ], -} as Meta; +import { themes } from '../../../utils/themeHelpers'; +import Group from './index'; const mockGroupData = { status: 'success', data: { - name: 'Group2', + name: 'Demo Group', active: true, - id: '29719640-b1ee-4b3c-b746-e563bc136769', - createdAt: '2024-10-15T14:21:06.135Z', - updatedAt: '2024-10-16T10:00:09.823Z', + id: '32697712-8dc0-4717-9775-e1f3502acc48', + createdAt: '2024-11-14T19:14:30.212Z', + updatedAt: '2024-11-14T19:14:30.212Z', sections: [ { - name: 'Banner Tiles', - type: 'BANNER', + details: [], + defaultLocale: 'en', + name: 'Misc Tiles', + type: 'GRID', active: true, - pointsMultiplier: '1', - pointsPrefix: null, - pointsSuffix: null, - id: '3a70a8dc-54a9-47b4-893b-ab1b882fc2d6', - createdAt: '2024-10-17T11:40:50.073Z', - updatedAt: '2024-10-17T11:40:50.073Z', - title: 'Banner Rewards', + id: 'cc1bf03b-42fd-4276-b543-84ec7a1cc527', + createdAt: '2024-11-14T19:00:31.894Z', + updatedAt: '2024-11-15T09:48:37.232Z', + visibilityCriteria: null, + priority: 5, + tiles: [ + { + tileHeight: 'HALF', + active: true, + type: 'CONTENT', + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: 'NEW_TAB', + locale: 'en', + title: 'Welcome Graeme', + body: 'Your culinary adventure awaits, tuck in and start earning!', + ctaLink: 'https://www.google.com', + }, + id: 'fb42e341-c477-4b84-a8cf-02aa1f76a351', + createdAt: '2024-11-14T17:58:13.107Z', + updatedAt: '2024-11-21T21:44:41.237Z', + priority: 8, + }, + { + tileHeight: 'HALF', + active: true, + type: 'TIER', + configuration: { + defaultLocale: 'en', + details: [], + type: 'CURRENT', + tierId: '54f0658a-9409-41f7-a508-ca02c6ac23e1', + progressType: 'NAME', + tier: { + id: '5607df63-8e18-4a5c-ab71-06330368c1b3', + name: 'Platinum', + description: null, + artworkUrl: null, + pointsRequirement: 10000, + }, + locale: 'en', + title: 'Your Tier', + pointsSuffix: 'points', + emptyArtworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + emptyDescription: 'Oops. You’re not in a tier.', + pointsToTierPrefix: 'Earn', + pointsToTierSuffix: 'to achieve this tier.', + }, + id: 'bcd700c0-1288-4117-9ff0-fe2a72913e77', + createdAt: '2024-11-14T18:06:25.686Z', + updatedAt: '2024-11-14T18:06:25.686Z', + priority: 7, + }, + { + tileHeight: 'HALF', + active: true, + type: 'POINTS', + configuration: { + defaultLocale: 'en', + details: [], + points: 5600, + locale: 'en', + title: 'Points balance', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + pointsPrefix: '', + pointsSuffix: 'pts', + pointsMultiplier: 1, + }, + id: '7530fc60-6352-47bc-86a1-8d54e9848fcc', + createdAt: '2024-11-14T18:07:37.939Z', + updatedAt: '2024-11-14T18:07:37.939Z', + priority: 6, + }, + { + tileHeight: 'HALF', + active: true, + type: 'CONTENT', + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: 'NEW_TAB', + locale: 'en', + title: '', + body: 'Every bite brings you closer to rewards! Earn 1 point for every $1 spent!', + artworkUrl: '', + ctaLink: 'https://google.co.uk', + }, + id: '288c9873-729e-4766-83fc-1e15cafde6ba', + createdAt: '2024-11-14T18:44:18.897Z', + updatedAt: '2024-11-14T18:44:18.897Z', + priority: 5, + }, + ], + title: 'Profile', + locale: 'en', description: 'Welcome to WLL Explorer! Find out more about our exciting loyalty program.', + }, + { + details: [], + defaultLocale: 'en', + name: 'Badge Tiles', + type: 'GRID', + active: true, + id: '60a6b9e3-14c7-4b49-a363-b6dc2f16dae0', + createdAt: '2024-11-11T16:28:32.848Z', + updatedAt: '2024-11-11T16:28:32.848Z', + visibilityCriteria: null, + priority: 4, + tiles: [ + { + tileHeight: 'FULL', + active: true, + type: 'BADGE', + configuration: { + defaultLocale: 'en', + details: [], + type: 'SPECIFIC', + badgeId: '900a2477-95c4-4c42-ae2d-3795e7f0f5f2', + internalName: 'Epicurean Elite', + priority: 0, + internalDescription: null, + status: 'ACTIVE', + id: '8962205c-e32a-4651-ac7a-4c584449d9fa', + createdAt: '2024-08-06T08:53:24.307Z', + updatedAt: '2024-08-06T08:53:24.307Z', + name: 'Epicurean Elite', + locale: 'en', + description: + 'Granted to those who achieve the highest tier in the loyalty program.', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + count: 0, + awardedDatePrefix: 'Awarded', + emptyBadgeMessage: 'You haven’t earned any badges yet.', + emptyBadgeArtworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + badgeNotEarnedMessage: 'Badge not earned yet', + }, + id: '902258a6-f7af-4da5-9069-43042ac1a326', + createdAt: '2024-11-11T16:17:23.784Z', + updatedAt: '2024-11-11T16:17:23.784Z', + priority: 4, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BADGE', + configuration: { + defaultLocale: 'en', + details: [], + type: 'SPECIFIC', + badgeId: 'd3a2b1fa-3963-4117-8c6f-69e5f8fea009', + internalName: 'Tasty Trailblazer', + priority: 0, + status: 'ACTIVE', + id: '79021b66-a7cf-4809-bef9-872467c0cd66', + createdAt: '2024-08-06T08:52:49.093Z', + updatedAt: '2024-08-06T08:52:49.093Z', + name: 'Tasty Trailblazer', + locale: 'en', + description: + 'Awarded for being among the first to try new menu items.', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + count: 1, + awardedDatePrefix: 'Earned on', + emptyBadgeMessage: 'You haven’t earned any badges yet.', + emptyBadgeArtworkUrl: 'You haven’t earned any badges yet.', + badgeNotEarnedMessage: 'Badge not earned yet', + }, + id: '29b1982c-2774-4ce6-9712-c8732e790dbf', + createdAt: '2024-11-11T16:18:47.130Z', + updatedAt: '2024-11-11T16:18:47.130Z', + priority: 3, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BADGE', + configuration: { + defaultLocale: 'en', + details: [], + type: 'SPECIFIC', + badgeId: '30528c0a-30c2-44aa-9640-25647b8a594c', + internalName: 'Flavour Fanatic', + priority: 0, + status: 'ACTIVE', + id: '656539e7-0b33-4952-8cff-8fbca4883621', + createdAt: '2024-08-06T08:43:13.594Z', + updatedAt: '2024-08-06T08:51:48.468Z', + name: 'Flavour Fanatic', + locale: 'en', + description: ' Earned by frequently rating or reviewing dishes.', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + count: 1, + awardedDatePrefix: 'Swindled on', + emptyBadgeMessage: 'You haven’t earned any badges yet.', + emptyBadgeArtworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + badgeNotEarnedMessage: 'Badge not earned yet', + }, + id: '87ada9f9-5a9f-4dde-a9bc-12dec8385a7f', + createdAt: '2024-11-11T16:21:30.457Z', + updatedAt: '2024-11-11T16:21:30.457Z', + priority: 2, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BADGE', + configuration: { + defaultLocale: 'en', + details: [], + type: 'SPECIFIC', + badgeId: '84bd0af7-bef3-4201-8541-d60431116597', + internalName: 'Gourmet Explorer', + priority: 0, + status: 'ACTIVE', + id: '4570996f-5af5-4adb-a59f-67c817cc5874', + createdAt: '2024-08-06T08:50:54.043Z', + updatedAt: '2024-08-06T08:50:54.043Z', + name: 'Gourmet Explorer', + locale: 'en', + description: + 'Awarded for trying a diverse range of menu items or cuisines.', + artworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + count: 3, + awardedDatePrefix: 'Swiped upon', + emptyBadgeMessage: 'You haven’t earned any badges yet.', + emptyBadgeArtworkUrl: + 'https://ucarecdn.com/3d3731b2-faec-4779-9cd8-3691631d280c/', + badgeNotEarnedMessage: 'Badge not earned yet', + }, + id: '9d4cd1a7-637d-4a53-8a83-69a458d86f69', + createdAt: '2024-11-11T16:22:51.819Z', + updatedAt: '2024-11-11T16:22:51.819Z', + priority: 1, + }, + ], + title: 'Achievement Gallery', + locale: 'en', + description: + "Your achievements, but make them sparkle. Each badge here is a little piece of bragging rights you've earned along the way. ", + pointsPrefix: 'Ca$h', + pointsSuffix: 'pts', + pointsMultiplier: 1, + }, + { + details: [], + defaultLocale: 'en', + name: 'Reward Tiles Updated', + type: 'GRID', + active: true, + id: '852785cf-d72a-4c62-b9d0-989a035610c3', + createdAt: '2024-11-14T19:09:04.432Z', + updatedAt: '2024-11-14T19:28:23.150Z', visibilityCriteria: null, priority: 3, tiles: [ - // ... (include all tiles from the API response) + { + tileHeight: 'FULL', + active: true, + type: 'REWARD', + configuration: { + defaultLocale: 'en', + details: [], + rewardId: 'cb39dd5e-bdd9-4595-a924-4f2dfb36c53b', + showPrice: true, + showArtwork: true, + showDetails: true, + name: 'Chicken Noodles', + pictureUrl: + 'https://ucarecdn.com/ea0a6ca5-1ede-4ab4-a304-54a1c401eef8/', + price: 10, + availability: { + start: '2023-05-05T13:49:36.490Z', + end: null, + }, + purchasable: true, + priority: 100, + value: 1000, + type: 'VOUCHER', + codeType: 'HUMAN', + hideCode: false, + notificationConfig: null, + id: 'cb39dd5e-bdd9-4595-a924-4f2dfb36c53b', + createdAt: '2023-05-05T13:50:30.598Z', + updatedAt: '2024-11-14T19:30:08.744Z', + summary: 'Test', + redemptionMessage: null, + metadata: null, + visibilityCriteria: null, + code: null, + purchaseExpiration: null, + tier: null, + category: { + name: 'Chicken Salad', + priority: 0, + type: 'REWARD', + details: null, + defaultLocale: 'en', + id: '677b6b88-d97c-4b8d-80f4-88b04c86948f', + createdAt: '2023-05-05T10:39:57.595Z', + updatedAt: '2023-05-05T10:39:57.595Z', + description: null, + metadata: null, + pictureUrl: + 'https://ucarecdn.com/9cda5d3c-75f4-48f0-9f3f-1ece144f6136/', + }, + discounts: [], + artworkUrl: + 'https://ucarecdn.com/ea0a6ca5-1ede-4ab4-a304-54a1c401eef8/', + }, + id: 'bacda827-cdb9-4e30-b783-6c0a3f7e718f', + createdAt: '2024-11-14T18:49:10.387Z', + updatedAt: '2024-11-14T18:49:10.387Z', + priority: 4, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD', + configuration: { + defaultLocale: 'en', + details: [], + rewardId: 'f7ce508f-ca52-46ff-bfb7-03e3761feb4a', + showPrice: true, + showArtwork: true, + showDetails: true, + name: 'Sweet Chilli Lobster Noodles', + pictureUrl: + 'https://ucarecdn.com/a486d015-b4ab-4831-bfa5-68f5bbbf63b1/', + price: 1000, + availability: { + start: '2023-05-05T15:10:31.274Z', + end: null, + }, + purchasable: true, + priority: 10, + value: 800, + type: 'VOUCHER', + codeType: 'HUMAN', + hideCode: false, + notificationConfig: null, + id: 'f7ce508f-ca52-46ff-bfb7-03e3761feb4a', + createdAt: '2023-05-05T15:11:42.342Z', + updatedAt: '2024-11-14T19:39:16.764Z', + summary: 'test', + redemptionMessage: null, + metadata: null, + visibilityCriteria: null, + code: null, + purchaseExpiration: null, + tier: null, + category: { + name: 'Chicken Salad', + priority: 0, + type: 'REWARD', + details: null, + defaultLocale: 'en', + id: '677b6b88-d97c-4b8d-80f4-88b04c86948f', + createdAt: '2023-05-05T10:39:57.595Z', + updatedAt: '2023-05-05T10:39:57.595Z', + description: null, + metadata: null, + pictureUrl: + 'https://ucarecdn.com/9cda5d3c-75f4-48f0-9f3f-1ece144f6136/', + }, + discounts: [], + artworkUrl: + 'https://ucarecdn.com/a486d015-b4ab-4831-bfa5-68f5bbbf63b1/', + }, + id: '5c2c7a46-c662-43c9-b6e4-0d5b470e4904', + createdAt: '2024-11-14T18:49:00.659Z', + updatedAt: '2024-11-14T18:49:00.659Z', + priority: 3, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD', + configuration: { + defaultLocale: 'en', + details: [], + rewardId: 'e8d63f0b-7911-4275-9651-441840a18b18', + showPrice: true, + showArtwork: true, + showDetails: true, + name: 'Sesame Salmon Noodles', + pictureUrl: + 'https://ucarecdn.com/42216985-1225-46f5-bec6-6ff50841fc24/', + price: 10, + availability: { + start: '2023-05-05T15:11:51.211Z', + end: null, + }, + purchasable: true, + priority: 10, + value: 1000, + type: 'VOUCHER', + codeType: 'HUMAN', + hideCode: false, + notificationConfig: null, + id: 'e8d63f0b-7911-4275-9651-441840a18b18', + createdAt: '2023-05-05T15:12:45.405Z', + updatedAt: '2024-07-17T10:44:53.570Z', + summary: 'Test', + redemptionMessage: null, + metadata: null, + visibilityCriteria: null, + code: null, + purchaseExpiration: null, + tier: null, + category: { + name: 'Chicken Salad', + priority: 0, + type: 'REWARD', + details: null, + defaultLocale: 'en', + id: '677b6b88-d97c-4b8d-80f4-88b04c86948f', + createdAt: '2023-05-05T10:39:57.595Z', + updatedAt: '2023-05-05T10:39:57.595Z', + description: null, + metadata: null, + pictureUrl: + 'https://ucarecdn.com/9cda5d3c-75f4-48f0-9f3f-1ece144f6136/', + }, + discounts: [], + artworkUrl: + 'https://ucarecdn.com/42216985-1225-46f5-bec6-6ff50841fc24/', + }, + id: '0b7fa0b4-a6d5-4a2b-af21-910121bf7efa', + createdAt: '2024-11-14T18:48:45.931Z', + updatedAt: '2024-11-14T18:48:45.931Z', + priority: 2, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD', + configuration: { + defaultLocale: 'en', + details: [], + rewardId: '56254b8a-f142-4131-a396-6b313cac651d', + showPrice: true, + showArtwork: true, + showDetails: true, + name: 'Thai Chicken Peanut Noodles', + pictureUrl: + 'https://ucarecdn.com/0c4d4474-baf7-4563-bb02-ad67d803a970/', + price: 10, + availability: { + start: '2022-10-25T14:23:10.021Z', + end: null, + }, + purchasable: true, + priority: 0, + value: 1000, + type: 'VOUCHER', + codeType: 'HUMAN', + hideCode: false, + notificationConfig: null, + id: '56254b8a-f142-4131-a396-6b313cac651d', + createdAt: '2022-10-25T14:24:55.591Z', + updatedAt: '2023-05-05T14:55:21.691Z', + summary: 'An easy, healthy meal you will love!', + redemptionMessage: null, + metadata: null, + visibilityCriteria: null, + code: null, + purchaseExpiration: null, + tier: null, + category: { + name: 'Noodle Salad', + priority: 0, + type: 'REWARD', + details: null, + defaultLocale: 'en', + id: 'b6dca41a-9e8b-45d9-83ce-4f3cf2aa32e0', + createdAt: '2023-05-05T10:40:19.837Z', + updatedAt: '2023-05-05T15:20:15.402Z', + description: null, + metadata: null, + pictureUrl: + 'https://ucarecdn.com/0bd46eee-97b1-4f4d-943a-32d8005d62e1/', + }, + discounts: [ + { + id: 'feb17453-5600-4b64-8aed-1f101797e0f6', + type: 'PERCENTAGE', + value: '1000', + sku: null, + productSku: null, + }, + ], + artworkUrl: + 'https://ucarecdn.com/0c4d4474-baf7-4563-bb02-ad67d803a970/', + }, + id: 'ff4a431d-3626-4515-baef-0794e69f9e53', + createdAt: '2024-11-14T19:24:32.976Z', + updatedAt: '2024-11-14T19:24:32.976Z', + priority: 1, + }, ], + title: 'Just Added', + locale: 'en', + description: + 'Fresh flavors and new rewards added weekly. Your next food adventure awaits!', + }, + { + details: [], + defaultLocale: 'en', + name: 'Reward Category Tiles', + type: 'GRID', + active: true, + id: 'c7671657-8634-4b2f-8c25-49022ba4f950', + createdAt: '2024-11-14T19:11:03.930Z', + updatedAt: '2024-11-14T19:29:02.516Z', + visibilityCriteria: null, + priority: 2, + tiles: [ + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '9d8287e0-bae9-47b4-8bf0-0115fd64336a', + allowDecorationOverlay: true, + showName: true, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/9efdd2f9-0e76-4634-8713-f20510415268/', + name: 'Multi Salad', + }, + id: 'f57f2f98-cabf-4b81-82ac-a0249172d65c', + createdAt: '2024-10-18T14:40:37.835Z', + updatedAt: '2024-10-18T14:40:37.835Z', + priority: 8, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: 'e2aa1cab-60c3-4fad-adfb-88f1f7bc6825', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/c840ef87-e737-4038-ae27-042088f88ea4/', + name: 'Peanut Crunch Chicken Noodle Salad', + }, + id: 'ed598f88-cda4-4b3d-930e-3d38bf0a0fc2', + createdAt: '2024-10-18T14:41:02.719Z', + updatedAt: '2024-10-18T14:41:02.719Z', + priority: 7, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '257c8c80-00a0-4512-ab15-235d6fd8f749', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/2b20b6a1-ee34-40b7-b57f-9a45e21befe4/', + name: 'Firecracker Chicken Noodle Salad', + }, + id: 'e0945f47-8d89-4d7c-bf70-9b91d41f97b6', + createdAt: '2024-10-18T14:41:20.515Z', + updatedAt: '2024-10-18T14:41:20.515Z', + priority: 6, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '0251ca85-d75a-4293-a0f9-ad7f86a1ca3d', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/a5dde823-44ba-42bc-8391-8b7fea0ccc9f/', + name: 'Thai Noodle Salads', + }, + id: '0132a078-4398-41f2-aea9-8d32a43193ac', + createdAt: '2024-10-18T14:41:37.328Z', + updatedAt: '2024-10-18T14:41:37.328Z', + priority: 5, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '1a7e0123-4f3e-4bf5-8bea-fb7ba98e0df3', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/b2946fef-3f11-47c2-822f-6880de60cc18/', + name: 'Japanese Miso Chicken Peanut Salad', + }, + id: 'e00fdd09-cb16-4d78-8279-2ac02935952b', + createdAt: '2024-10-18T14:41:53.020Z', + updatedAt: '2024-10-18T14:41:53.020Z', + priority: 4, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: 'ea38991c-1d2b-4d89-851b-123201ca2249', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/b748969f-316a-46c6-aa9c-580c3539f2f2/', + name: 'Seafood Salad', + }, + id: '051426fa-3cf8-4b74-b8a4-34c40e2d2903', + createdAt: '2024-10-18T14:42:11.381Z', + updatedAt: '2024-10-18T14:42:11.381Z', + priority: 3, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + details: [], + title: 'New iPhone 15 Pro', + ctaLink: '/products/iphone15pro', + ctaText: 'Shop Now', + artworkUrl: + 'https://www.istore.co.uk/wp-content/uploads/2023/09/iPhone_15_Pro_Blue_Titanium_PDP_Image_Position-5__GBEN-1.jpg', + description: + 'Experience the power of our most advanced iPhone yet. Available now with exclusive launch offers.', + ctaLinkTarget: 'SAME_FRAME', + }, + id: '8854230a-222a-42a2-b34a-d6db9f9218bf', + createdAt: '2024-10-17T11:33:09.167Z', + updatedAt: '2024-10-17T11:33:09.167Z', + priority: 3, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + details: [], + title: 'Unlock Your Rewards', + ctaLink: '#member-card', + ctaText: 'View Rewards', + artworkUrl: + 'https://images.pexels.com/photos/39389/keyboard-key-success-online-39389.jpeg', + description: + 'You have 5000 points waiting! Redeem now for exclusive discounts and perks.', + ctaLinkTarget: 'SAME_FRAME', + }, + id: '076015e9-74cb-4c2a-8f50-9e661abb9c66', + createdAt: '2024-10-17T11:36:48.595Z', + updatedAt: '2024-10-17T11:36:48.595Z', + priority: 2, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '3be7a630-556b-490f-b00f-f8f162875b56', + allowDecorationOverlay: true, + showName: false, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/9b86476f-a70e-4423-94df-4c0bda8ef867/', + name: 'Burger Joints', + }, + id: '0b75ab9e-0734-46c2-b847-c8a9a7bfa17b', + createdAt: '2024-10-18T14:42:38.830Z', + updatedAt: '2024-10-18T14:42:38.830Z', + priority: 2, + }, + { + tileHeight: 'FULL', + active: true, + type: 'REWARD_CATEGORY', + configuration: { + details: [], + rewardCategoryId: '7597ec32-bc50-4d10-bb33-4d193d11f641', + allowDecorationOverlay: true, + showName: true, + showArtwork: true, + artworkUrl: + 'https://ucarecdn.com/f19e08e3-49fc-4e86-b023-50f20101075f/', + name: 'Asian Food', + }, + id: '62a2dea5-5a26-4b52-b7ab-6cdd12b318d3', + createdAt: '2024-10-18T14:42:58.657Z', + updatedAt: '2024-10-18T14:42:58.657Z', + priority: 1, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + details: [], + title: 'TechCon 2024', + ctaLink: 'https://techcon2024.com/register', + ctaText: 'Register Now', + artworkUrl: + 'https://images.pexels.com/photos/8721318/pexels-photo-8721318.jpeg', + description: + 'Join the biggest tech conference of the year. Keynotes, workshops, and networking opportunities await!', + ctaLinkTarget: 'NEW_TAB', + }, + id: 'dfbb2ac3-533f-4bc8-a9bf-c143249ab5d3', + createdAt: '2024-10-17T11:36:54.141Z', + updatedAt: '2024-10-17T11:36:54.141Z', + priority: 1, + }, + ], + title: 'Reward category', + locale: 'en', + description: + 'Welcome to WLL Explorer! Find out more about our exciting loyalty program.', + }, + { + details: [], + defaultLocale: 'en', + name: 'Banner Section', + type: 'BANNER', + active: true, + id: '6cf0f959-a758-41bf-b99e-93d9b689a5d9', + createdAt: '2024-11-11T16:44:57.197Z', + updatedAt: '2024-11-11T16:44:57.197Z', + visibilityCriteria: null, + priority: 1, + tiles: [ + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: 'SAME_FRAME', + locale: 'en', + title: 'New iPhone 15 Pro', + ctaLink: '/products/iphone15pro', + ctaText: 'Shop Now', + artworkUrl: + 'https://www.istore.co.uk/wp-content/uploads/2023/09/iPhone_15_Pro_Blue_Titanium_PDP_Image_Position-5__GBEN-1.jpg', + description: + 'Experience the power of our most advanced iPhone yet. Available now with exclusive launch offers.', + }, + id: '8f28093b-6251-4ea9-a43b-1c9a9fe1e181', + createdAt: '2024-11-08T15:02:53.149Z', + updatedAt: '2024-11-08T15:02:53.149Z', + priority: 3, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: 'SAME_FRAME', + locale: 'en', + title: 'Unlock Your Rewards', + ctaLink: '#member-card', + ctaText: 'View Rewards', + artworkUrl: + 'https://images.pexels.com/photos/39389/keyboard-key-success-online-39389.jpeg', + description: + 'You have 5000 points waiting! Redeem now for exclusive discounts and perks.', + }, + id: 'ec7d5b2d-1dff-4491-a936-4f5acc1c5a44', + createdAt: '2024-11-08T15:03:03.906Z', + updatedAt: '2024-11-08T15:03:03.906Z', + priority: 2, + }, + { + tileHeight: 'FULL', + active: true, + type: 'BANNER', + configuration: { + defaultLocale: 'en', + details: [], + ctaLinkTarget: 'NEW_TAB', + locale: 'en', + title: 'TechCon 2024', + ctaLink: 'https://techcon2024.com/register', + ctaText: 'Register Now', + artworkUrl: + 'https://images.pexels.com/photos/8721318/pexels-photo-8721318.jpeg', + description: + 'Join the biggest tech conference of the year. Keynotes, workshops, and networking opportunities await!', + }, + id: 'a1e7f8e8-f4d0-4c5b-a018-cca673fadc2c', + createdAt: '2024-11-08T15:03:22.485Z', + updatedAt: '2024-11-08T15:03:22.485Z', + priority: 1, + }, + ], + title: 'Highlights Hub', + locale: 'en', + description: + 'Your front-row seat to all things exciting. Discover special events, unlock rewards, and stay in the know with our latest and greatest offerings – all in one place.', + pointsMultiplier: 1, }, - // ... (include all other sections from the API response) ], }, }; -const Template: StoryFn = (args) => { - // Mock the useWllSdk hook - const mockUseWllSdk = () => ({ - getGroupByID: async () => Promise.resolve(mockGroupData), - }); +const mockEmptyGroupData = { + status: 'success', + data: null, +}; - return ; +const mockEmptySectionsData = { + status: 'success', + data: { + name: 'Empty Sections Group', + active: true, + id: '29719640-b1ee-4b3c-b746-e563bc136769', + createdAt: '2024-10-15T14:21:06.135Z', + updatedAt: '2024-10-16T10:00:09.823Z', + sections: [], + }, }; -export const Default = Template.bind({}); -Default.args = { - id: '00a2cb62-911f-49fd-8f1c-a61ddc0fbdce', +const meta: Meta = { + title: 'components/organisms/Group', + component: Group, + decorators: [ + (Story, context) => { + const [currentTheme, setCurrentTheme] = React.useState( + themes[context.globals.theme as keyof typeof themes] || defaultTheme + ); + + React.useEffect(() => { + const newTheme = + themes[context.globals.theme as keyof typeof themes] || defaultTheme; + setCurrentTheme(newTheme); + }, [context.globals.theme]); + + const mockData = context.parameters.mockData?.[0]?.response || null; + + const config = { + apiKey: 'test-api-key', + fetcher: async () => mockData, + }; + + return ( + + + + ); + }, + ], + parameters: { + mockData: [{ response: mockGroupData }], + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const EmptyGroup: Story = { + args: { + id: 'empty-group-id', + }, + parameters: { + mockData: [{ response: mockEmptyGroupData }], + }, +}; + +export const EmptySections: Story = { + args: { + id: 'empty-sections-id', + }, + parameters: { + mockData: [{ response: mockEmptySectionsData }], + }, +}; + +export const FullGroup: Story = { + args: { + id: 'full-group-id', + }, + parameters: { + mockData: [{ response: mockGroupData }], + }, }; diff --git a/lib/components/organisms/Group/index.tsx b/lib/components/organisms/Group/index.tsx index b14ba37..d11164c 100644 --- a/lib/components/organisms/Group/index.tsx +++ b/lib/components/organisms/Group/index.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; -import { ActivityIndicator, Text, View } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import { useWllSdk } from '../../../context/WllSdkContext'; import { TGroup } from '../../../types/group'; +import { Skeleton } from '../../atoms'; import Section from '../Section'; type GroupProps = { @@ -17,7 +18,6 @@ const Group: React.FC = ({ id }) => { const fetchGroup = async () => { setLoading(true); const response = await getGroupByID(id); - console.log(response.data); setGroupData(response.data); setLoading(false); }; @@ -26,11 +26,27 @@ const Group: React.FC = ({ id }) => { }, [id, getGroupByID]); if (loading) { - return ; + return ( + + + + ); } if (!groupData) { - return No group data available; + return ( + + No group data available + + ); + } + + if (!groupData.sections || groupData.sections.length === 0) { + return ( + + No sections available; + + ); } return ( @@ -51,4 +67,12 @@ const Group: React.FC = ({ id }) => { ); }; +const styles = StyleSheet.create({ + emptyConatiner: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); + export default Group; diff --git a/lib/components/organisms/PointsTile/index.tsx b/lib/components/organisms/PointsTile/index.tsx index cd604df..135bfab 100644 --- a/lib/components/organisms/PointsTile/index.tsx +++ b/lib/components/organisms/PointsTile/index.tsx @@ -104,11 +104,13 @@ const PointsTileImage: React.FC = ({ isFullSize }) => { : theme.surface, justifyContent: 'center', alignItems: 'center', + overflow: 'hidden', }), image: { width: '100%', height: '100%', position: 'absolute', + resizeMode: 'contain', }, }); @@ -119,7 +121,7 @@ const PointsTileImage: React.FC = ({ isFullSize }) => { ); diff --git a/lib/context/WllSdkContext.tsx b/lib/context/WllSdkContext.tsx index d44f6bd..e59b316 100644 --- a/lib/context/WllSdkContext.tsx +++ b/lib/context/WllSdkContext.tsx @@ -94,6 +94,10 @@ export const WllSdkProvider: React.FC = ({ createTheme(providedTheme || {}) ); + React.useEffect(() => { + setThemeState(createTheme(providedTheme || {})); + }, [providedTheme]); + const setTheme = React.useCallback((newTheme: Partial) => { setThemeState((prevTheme) => createTheme({ ...prevTheme, ...newTheme })); }, []); diff --git a/lib/types/tile.ts b/lib/types/tile.ts index 77c7570..45b2a11 100644 --- a/lib/types/tile.ts +++ b/lib/types/tile.ts @@ -168,6 +168,7 @@ export class TierTileConfig { emptyArtworkUrl?: string = ''; pointsToTierPrefix?: string = ''; pointsToTierSuffix?: string = ''; + locale: string = 'en'; } export type Tile = { diff --git a/lib/utils/themeHelpers.ts b/lib/utils/themeHelpers.ts index 05c240f..e2dd6e4 100644 --- a/lib/utils/themeHelpers.ts +++ b/lib/utils/themeHelpers.ts @@ -1,4 +1,6 @@ import Color from 'color'; +import { BaseThemeObject } from '../types/theme'; +import { defaultTheme } from './styling'; export const getDerivedColor = (color: string): string => { const backgroundColor = Color(color); @@ -77,3 +79,77 @@ export const getStateColor = ( ): string => { return shouldDesaturate(type, count) ? desaturateColor(baseColor) : baseColor; }; + +// Storybook Themes +const storyBookThemes = { + modern: { + ...defaultTheme, + primary: '#f72585', + accent: '#ffc300', + background: '#e5e5e5', + surface: '#f4f3ee', + surfaceText: '#f72585', + }, + warm: { + ...defaultTheme, + primary: '#014F5C', + accent: '#F38A51', + background: '#BE9C80', + surface: '#F1CFB3', + surfaceText: '#014F5C', + }, + dark: { + ...defaultTheme, + background: '#1e1e1e', + text: '#ffffff', + primary: '#ffbe0b', + accent: '#d00000', + surface: '#2f3037', + surfaceText: '#ffffff', + }, + forest: { + ...defaultTheme, + primary: '#354f52', + accent: '#84a98c', + background: '#f0f4f0', + surface: '#cad2c5', + surfaceText: '#2d3436', + }, + sunset: { + ...defaultTheme, + primary: '#c32f27', + accent: '#db7c26', + background: '#f9edcc', + surface: '#f0dfad', + surfaceText: '#2d3436', + }, +}; + +// Define the theme type +export type ThemeName = + | 'default' + | 'dark' + | 'modern' + | 'warm' + | 'forest' + | 'sunset'; + +// Create the themes object +export const themes: Record = { + default: defaultTheme, + dark: storyBookThemes.dark, + modern: storyBookThemes.modern, + warm: storyBookThemes.warm, + forest: storyBookThemes.forest, + sunset: storyBookThemes.sunset, +}; + +// Theme selector items +export const themeItems: Array<{ value: ThemeName; title: string }> = [ + { value: 'default', title: 'Default' }, + { value: 'dark', title: 'Dark' }, + { value: 'modern', title: 'Modern' }, + { value: 'warm', title: 'Warm' }, + { value: 'forest', title: 'Forest' }, + { value: 'sunset', title: 'Sunset' }, +];