@wlloyalty/wll-react-sdk 1.0.58 → 1.0.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ export declare const COLOR_CONSTANTS: {
2
+ readonly MINIMUM_CONTRAST_RATIO: 2;
3
+ readonly DEFAULT_LIGHTNESS_ADJUSTMENT: 0.2;
4
+ readonly DESATURATION_LIGHTEN_AMOUNT: 2;
5
+ readonly DEFAULT_COLOR: "#FFFFFF";
6
+ };
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { TGroup } from '../types/group';
3
3
  import { NavigationConfig } from '../types/navigation';
4
4
  import { TSection } from '../types/section';
@@ -20,10 +20,12 @@ type WllSdkContextType = ThemeContextType & {
20
20
  getGroupByID: (id: string) => Promise<APIResponse<TGroup>>;
21
21
  getSectionByID: (id: string) => Promise<APIResponse<TSection>>;
22
22
  getTileByID: (id: string) => Promise<APIResponse<Tile>>;
23
- handleNavigation: (link: string, target: CTALinkTarget) => void;
24
- };
23
+ handleNavigation: (link: string, target: CTALinkTarget) => Promise<void>;
24
+ } & Readonly<{
25
+ readonly config: SDKConfig;
26
+ }>;
25
27
  type WllSdkProviderProps = {
26
- children: React.ReactNode;
28
+ children: ReactNode;
27
29
  theme?: Partial<BaseThemeObject>;
28
30
  config: SDKConfig;
29
31
  navigationConfig?: NavigationConfig;
@@ -1,2 +1,2 @@
1
1
  import { Tile } from '../types/tile';
2
- export declare const useHandleTilePress: (tile: Tile, ctaLink?: string | null, ctaLinkTarget?: string) => () => void;
2
+ export declare const useHandleTilePress: (tile: Tile, ctaLink?: string | null, ctaLinkTarget?: string) => () => Promise<void> | undefined;
@@ -1,3 +1,3 @@
1
1
  import { NavigationConfig } from '../types/navigation';
2
2
  import { CTALinkTarget } from '../types/tile';
3
- export declare const useNavigation: (config: NavigationConfig) => (link: string, ctaTarget: CTALinkTarget) => void;
3
+ export declare const useNavigation: (config: NavigationConfig) => (link: string, ctaTarget: CTALinkTarget) => Promise<void>;
@@ -5,10 +5,13 @@ type NavigationHandlerParams = {
5
5
  };
6
6
  export type NavigationConfig = {
7
7
  navigationHandlers?: {
8
- external?: (params: NavigationHandlerParams) => void;
9
- internal?: (params: NavigationHandlerParams) => void;
10
- modal?: (params: NavigationHandlerParams) => void;
8
+ external?: (params: NavigationHandlerParams) => Promise<void>;
9
+ internal?: (params: NavigationHandlerParams) => Promise<void>;
10
+ modal?: (params: NavigationHandlerParams) => Promise<void>;
11
11
  };
12
+ external?: (params: NavigationHandlerParams) => Promise<void>;
13
+ internal?: (params: NavigationHandlerParams) => Promise<void>;
14
+ modal?: (params: NavigationHandlerParams) => Promise<void>;
12
15
  baseUrl?: string;
13
16
  };
14
17
  export {};
@@ -1,6 +1,11 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { sizes } from '../utils/styling';
3
- import { DerivedColors } from '../utils/themeHelpers';
3
+ import { BadgeTileType } from './tile';
4
+ export type PercentageKey = 5 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 95;
5
+ export type DerivedColors = {
6
+ [K in PercentageKey]: string;
7
+ };
8
+ export type DesaturationType = BadgeTileType.Specific | BadgeTileType.Latest;
4
9
  export type BaseThemeObject = {
5
10
  accent: string;
6
11
  background: string;
@@ -1,13 +1,54 @@
1
+ import { BaseThemeObject, DerivedColors, DesaturationType } from '../types/theme';
1
2
  import { themeItems, ThemeName, themes } from './storybookThemes';
3
+ /**
4
+ * Validates a theme object to ensure all required colors are present and valid
5
+ * @param theme - Partial theme object to validate
6
+ * @returns Boolean indicating if theme is valid
7
+ */
8
+ export declare const validateTheme: (theme: Partial<BaseThemeObject>) => boolean;
9
+ /**
10
+ * Returns a derived color based on the background darkness
11
+ * @param color - Base color to derive from
12
+ * @returns Derived color string or default color if invalid
13
+ */
2
14
  export declare const getDerivedColor: (color: string) => string;
15
+ /**
16
+ * Determines the most readable text color (black or white) for a given background
17
+ * @param backgroundColor - Background color to check against
18
+ * @returns Hex color string for text
19
+ */
3
20
  export declare const getReadableTextColor: (backgroundColor: string) => string;
4
- type PercentageKey = 5 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 95;
5
- export type DerivedColors = {
6
- [K in PercentageKey]: string;
7
- };
21
+ /**
22
+ * Generates a set of colors with varying lightness based on the base color
23
+ * @param color - Base color to derive from
24
+ * @returns Object containing derived colors at different lightness levels
25
+ */
8
26
  export declare const getDerivedColorPercentages: (color: string) => DerivedColors;
27
+ /**
28
+ * Generates a set of colors with varying alpha values
29
+ * @param color - Base color to derive from
30
+ * @returns Object containing derived colors at different alpha levels
31
+ */
9
32
  export declare const getAlphaDerivedColors: (color: string) => DerivedColors;
10
- export declare const shouldDesaturate: (type: string, count: number) => boolean;
33
+ /**
34
+ * Determines if a color should be desaturated based on type and count
35
+ * @param type - Type of desaturation to apply
36
+ * @param count - Count value affecting desaturation
37
+ * @returns Boolean indicating if color should be desaturated
38
+ */
39
+ export declare const shouldDesaturate: (type: DesaturationType, count: number) => boolean;
40
+ /**
41
+ * Desaturates a color and lightens it
42
+ * @param color - Color to desaturate
43
+ * @returns Desaturated color string or default color if invalid
44
+ */
11
45
  export declare const desaturateColor: (color: string) => string;
12
- export declare const getStateColor: (baseColor: string, type: string, count: number) => string;
46
+ /**
47
+ * Gets the appropriate state color based on type and count
48
+ * @param baseColor - Base color to process
49
+ * @param type - Type of state
50
+ * @param count - Count affecting the state
51
+ * @returns Processed color string
52
+ */
53
+ export declare const getStateColor: (baseColor: string, type: DesaturationType, count: number) => string;
13
54
  export { themeItems, ThemeName, themes };
package/dist/web.js CHANGED
@@ -7824,17 +7824,21 @@ var parseNavigationLink = function (link) {
7824
7824
 
7825
7825
  var useNavigation = function (config) {
7826
7826
  var handleNavigation = React.useCallback(function (link, ctaTarget) {
7827
- var _a;
7828
- var _b = parseNavigationLink(link),
7829
- type = _b.type,
7830
- target = _b.target;
7831
- var windowTarget = ctaTarget === 'SAME_FRAME' ? '_self' : '_blank';
7832
- if ((_a = config.navigationHandlers) === null || _a === void 0 ? void 0 : _a[type]) {
7833
- return config.navigationHandlers[type]({
7834
- target: target,
7835
- windowTarget: windowTarget
7827
+ return __awaiter(void 0, void 0, void 0, function () {
7828
+ var _a, type, target, windowTarget;
7829
+ var _b;
7830
+ return __generator(this, function (_c) {
7831
+ _a = parseNavigationLink(link), type = _a.type, target = _a.target;
7832
+ windowTarget = ctaTarget === 'SAME_FRAME' ? '_self' : '_blank';
7833
+ if ((_b = config.navigationHandlers) === null || _b === void 0 ? void 0 : _b[type]) {
7834
+ return [2 /*return*/, Promise.resolve(config.navigationHandlers[type]({
7835
+ target: target,
7836
+ windowTarget: windowTarget
7837
+ }))];
7838
+ }
7839
+ return [2 /*return*/, Promise.resolve()];
7836
7840
  });
7837
- }
7841
+ });
7838
7842
  }, [config]);
7839
7843
  return handleNavigation;
7840
7844
  };
@@ -7960,6 +7964,13 @@ var defaultTheme = {
7960
7964
  negative: '#ff0000'
7961
7965
  };
7962
7966
 
7967
+ var COLOR_CONSTANTS = {
7968
+ MINIMUM_CONTRAST_RATIO: 2,
7969
+ DEFAULT_LIGHTNESS_ADJUSTMENT: 0.2,
7970
+ DESATURATION_LIGHTEN_AMOUNT: 2,
7971
+ DEFAULT_COLOR: '#FFFFFF'
7972
+ };
7973
+
7963
7974
  // Storybook Themes
7964
7975
  ({
7965
7976
  modern: __assign(__assign({}, defaultTheme), {
@@ -8000,48 +8011,144 @@ var defaultTheme = {
8000
8011
  })
8001
8012
  });
8002
8013
 
8014
+ var isValidColor = function (color) {
8015
+ try {
8016
+ Color(color);
8017
+ return true;
8018
+ } catch (_a) {
8019
+ return false;
8020
+ }
8021
+ };
8022
+ /**
8023
+ * Validates a theme object to ensure all required colors are present and valid
8024
+ * @param theme - Partial theme object to validate
8025
+ * @returns Boolean indicating if theme is valid
8026
+ */
8027
+ var validateTheme = function (theme) {
8028
+ var requiredColors = ['accent', 'background', 'errorPrimary', 'negative', 'pageButtonBackground', 'pageButtonText', 'positive', 'primary', 'surface', 'surfaceText', 'text'];
8029
+ return requiredColors.every(function (color) {
8030
+ return theme[color] && isValidColor(theme[color]);
8031
+ });
8032
+ };
8033
+ /**
8034
+ * Safely creates a Color instance with error handling
8035
+ * @param color - Color string to process
8036
+ * @returns Color instance or null if invalid
8037
+ */
8038
+ var createSafeColor = function (color) {
8039
+ try {
8040
+ return Color(color);
8041
+ } catch (error) {
8042
+ console.error("Invalid color value: ".concat(color), error);
8043
+ return null;
8044
+ }
8045
+ };
8046
+ /**
8047
+ * Returns a derived color based on the background darkness
8048
+ * @param color - Base color to derive from
8049
+ * @returns Derived color string or default color if invalid
8050
+ */
8003
8051
  var getDerivedColor = function (color) {
8004
- var backgroundColor = Color(color);
8005
- return backgroundColor.isDark() ? backgroundColor.lighten(0.2).string() : backgroundColor.darken(0.2).string();
8052
+ var backgroundColor = createSafeColor(color);
8053
+ if (!backgroundColor) return COLOR_CONSTANTS.DEFAULT_COLOR;
8054
+ return backgroundColor.isDark() ? backgroundColor.lighten(COLOR_CONSTANTS.DEFAULT_LIGHTNESS_ADJUSTMENT).string() : backgroundColor.darken(COLOR_CONSTANTS.DEFAULT_LIGHTNESS_ADJUSTMENT).string();
8006
8055
  };
8007
- // Same implementation as Microsite
8056
+ /**
8057
+ * Determines the most readable text color (black or white) for a given background
8058
+ * @param backgroundColor - Background color to check against
8059
+ * @returns Hex color string for text
8060
+ */
8008
8061
  var getReadableTextColor = function (backgroundColor) {
8009
- var bgColor = Color(backgroundColor);
8062
+ var bgColor = createSafeColor(backgroundColor);
8063
+ if (!bgColor) return COLOR_CONSTANTS.DEFAULT_COLOR;
8010
8064
  var white = Color('#fff');
8011
8065
  var black = Color('#000');
8012
- // Calculate contrast ratio with white
8013
8066
  var contrastWithWhite = bgColor.contrast(white);
8014
- // If contrast with white is at least 2:1, use white; otherwise, use black
8015
- return contrastWithWhite >= 2 ? white.hex() : black.hex();
8067
+ return contrastWithWhite >= COLOR_CONSTANTS.MINIMUM_CONTRAST_RATIO ? white.hex() : black.hex();
8016
8068
  };
8017
8069
  var percentages = [5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95];
8070
+ /**
8071
+ * Generates a set of derived colors based on a base color and derivation function
8072
+ * @param color - Base color to derive from
8073
+ * @param derivationFunction - Function to generate variations
8074
+ * @returns Object containing derived colors at different percentages
8075
+ */
8018
8076
  var generateDerivedColors = function (color, derivationFunction) {
8019
- var baseColor = Color(color);
8077
+ var baseColor = createSafeColor(color);
8078
+ if (!baseColor) {
8079
+ return percentages.reduce(function (acc, percentage) {
8080
+ acc[percentage] = COLOR_CONSTANTS.DEFAULT_COLOR;
8081
+ return acc;
8082
+ }, {});
8083
+ }
8020
8084
  var result = {};
8021
8085
  percentages.forEach(function (percentage) {
8022
- var derivedColor = derivationFunction(baseColor, percentage);
8023
- result[percentage] = derivedColor.toString();
8086
+ try {
8087
+ var derivedColor = derivationFunction(baseColor, percentage);
8088
+ result[percentage] = derivedColor.toString();
8089
+ } catch (error) {
8090
+ console.error("Error generating derived color for ".concat(percentage, "%"), error);
8091
+ result[percentage] = COLOR_CONSTANTS.DEFAULT_COLOR;
8092
+ }
8024
8093
  });
8025
8094
  return result;
8026
8095
  };
8096
+ /**
8097
+ * Generates a set of colors with varying lightness based on the base color
8098
+ * @param color - Base color to derive from
8099
+ * @returns Object containing derived colors at different lightness levels
8100
+ */
8027
8101
  var getDerivedColorPercentages = function (color) {
8028
- var isDark = Color(color).isDark();
8102
+ var baseColor = createSafeColor(color);
8103
+ if (!baseColor) return generateDerivedColors(COLOR_CONSTANTS.DEFAULT_COLOR, function (color) {
8104
+ return color;
8105
+ });
8106
+ var isDark = baseColor.isDark();
8029
8107
  return generateDerivedColors(color, function (baseColor, percentage) {
8030
8108
  return isDark ? baseColor.lightness(percentage) : baseColor.lightness(100 - percentage);
8031
8109
  });
8032
8110
  };
8111
+ /**
8112
+ * Generates a set of colors with varying alpha values
8113
+ * @param color - Base color to derive from
8114
+ * @returns Object containing derived colors at different alpha levels
8115
+ */
8033
8116
  var getAlphaDerivedColors = function (color) {
8034
8117
  return generateDerivedColors(color, function (baseColor, percentage) {
8035
8118
  return baseColor.alpha(percentage / 100);
8036
8119
  });
8037
8120
  };
8121
+ /**
8122
+ * Determines if a color should be desaturated based on type and count
8123
+ * @param type - Type of desaturation to apply
8124
+ * @param count - Count value affecting desaturation
8125
+ * @returns Boolean indicating if color should be desaturated
8126
+ */
8038
8127
  var shouldDesaturate = function (type, count) {
8039
- //TODO: Add conditions if neccecerry
8040
- return type === 'SPECIFIC' && count === 0;
8128
+ switch (type) {
8129
+ case 'SPECIFIC':
8130
+ return count === 0;
8131
+ default:
8132
+ return false;
8133
+ }
8041
8134
  };
8135
+ /**
8136
+ * Desaturates a color and lightens it
8137
+ * @param color - Color to desaturate
8138
+ * @returns Desaturated color string or default color if invalid
8139
+ */
8042
8140
  var desaturateColor = function (color) {
8043
- return Color(color).grayscale().lighten(2).toString();
8141
+ var baseColor = createSafeColor(color);
8142
+ if (!baseColor) return COLOR_CONSTANTS.DEFAULT_COLOR;
8143
+ return baseColor.grayscale().lighten(COLOR_CONSTANTS.DESATURATION_LIGHTEN_AMOUNT).toString();
8044
8144
  };
8145
+ /**
8146
+ * Gets the appropriate state color based on type and count
8147
+ * @param baseColor - Base color to process
8148
+ * @param type - Type of state
8149
+ * @param count - Count affecting the state
8150
+ * @returns Processed color string
8151
+ */
8045
8152
  var getStateColor = function (baseColor, type, count) {
8046
8153
  return shouldDesaturate(type, count) ? desaturateColor(baseColor) : baseColor;
8047
8154
  };
@@ -8294,29 +8401,29 @@ function useResponsive$1() {
8294
8401
  var ResponsiveContext = /*#__PURE__*/React.createContext(undefined);
8295
8402
  function ResponsiveProvider(_a) {
8296
8403
  var children = _a.children;
8404
+ var getDerivedState = React.useCallback(function (window) {
8405
+ var dimensionMode = getDimensionMode(window);
8406
+ return {
8407
+ dimensionMode: dimensionMode,
8408
+ isDesktop: dimensionMode === DIMENSION_MODES.DESKTOP,
8409
+ isTablet: dimensionMode === DIMENSION_MODES.TABLET,
8410
+ isMobile: dimensionMode === DIMENSION_MODES.MOBILE
8411
+ };
8412
+ }, []);
8297
8413
  var _b = React.useState(function () {
8298
8414
  var window = Dimensions.get('window');
8299
- var dimensionMode = getDimensionMode(window);
8300
- return {
8301
- dimensionMode: dimensionMode,
8302
- isDesktop: dimensionMode === DIMENSION_MODES.DESKTOP,
8303
- isTablet: dimensionMode === DIMENSION_MODES.TABLET,
8304
- isMobile: dimensionMode === DIMENSION_MODES.MOBILE
8305
- };
8415
+ return getDerivedState(window);
8306
8416
  }),
8307
8417
  state = _b[0],
8308
8418
  setState = _b[1];
8419
+ var value = React.useMemo(function () {
8420
+ return state;
8421
+ }, [state]);
8309
8422
  React.useEffect(function () {
8310
8423
  var subscription = Dimensions.addEventListener('change', function (_a) {
8311
8424
  var window = _a.window;
8312
8425
  if (window) {
8313
- var dimensionMode = getDimensionMode(window);
8314
- setState({
8315
- dimensionMode: dimensionMode,
8316
- isDesktop: dimensionMode === DIMENSION_MODES.DESKTOP,
8317
- isTablet: dimensionMode === DIMENSION_MODES.TABLET,
8318
- isMobile: dimensionMode === DIMENSION_MODES.MOBILE
8319
- });
8426
+ setState(getDerivedState(window));
8320
8427
  }
8321
8428
  });
8322
8429
  return function () {
@@ -8324,9 +8431,9 @@ function ResponsiveProvider(_a) {
8324
8431
  subscription.remove();
8325
8432
  }
8326
8433
  };
8327
- }, []);
8434
+ }, [getDerivedState]);
8328
8435
  return /*#__PURE__*/React.createElement(ResponsiveContext.Provider, {
8329
- value: state
8436
+ value: value
8330
8437
  }, children);
8331
8438
  }
8332
8439
  function useResponsive() {
@@ -8342,25 +8449,17 @@ var createTheme = function (baseTheme) {
8342
8449
  baseTheme = {};
8343
8450
  }
8344
8451
  var mergedTheme = __assign(__assign({}, defaultTheme), baseTheme);
8345
- var background = mergedTheme.background,
8346
- primary = mergedTheme.primary,
8347
- accent = mergedTheme.accent,
8348
- positive = mergedTheme.positive,
8349
- negative = mergedTheme.negative,
8350
- surfaceText = mergedTheme.surfaceText,
8351
- surface = mergedTheme.surface,
8352
- text = mergedTheme.text;
8353
8452
  return __assign(__assign({}, mergedTheme), {
8354
8453
  sizes: sizes,
8355
- derivedBackground: getDerivedColor(background),
8356
- primaryText: getReadableTextColor(primary),
8357
- accentText: getReadableTextColor(accent),
8358
- positiveText: getReadableTextColor(positive),
8359
- negativeText: getReadableTextColor(negative),
8360
- derivedSurface: getDerivedColorPercentages(surface),
8361
- derivedSurfaceText: getDerivedColorPercentages(surfaceText),
8362
- alphaDerivedPrimary: getAlphaDerivedColors(primary),
8363
- alphaDerivedText: getAlphaDerivedColors(text)
8454
+ derivedBackground: getDerivedColor(mergedTheme.background),
8455
+ primaryText: getReadableTextColor(mergedTheme.primary),
8456
+ accentText: getReadableTextColor(mergedTheme.accent),
8457
+ positiveText: getReadableTextColor(mergedTheme.positive),
8458
+ negativeText: getReadableTextColor(mergedTheme.negative),
8459
+ derivedSurface: getDerivedColorPercentages(mergedTheme.surface),
8460
+ derivedSurfaceText: getDerivedColorPercentages(mergedTheme.surfaceText),
8461
+ alphaDerivedPrimary: getAlphaDerivedColors(mergedTheme.primary),
8462
+ alphaDerivedText: getAlphaDerivedColors(mergedTheme.text)
8364
8463
  });
8365
8464
  };
8366
8465
  var WllSdkContext = /*#__PURE__*/React.createContext(undefined);
@@ -8370,15 +8469,27 @@ var WllSdkProvider = function (_a) {
8370
8469
  config = _a.config,
8371
8470
  _b = _a.navigationConfig,
8372
8471
  navigationConfig = _b === void 0 ? {} : _b;
8472
+ validateConfig(config);
8373
8473
  var _c = React.useState(function () {
8374
- return createTheme(providedTheme || {});
8474
+ var themeToUse = providedTheme || {};
8475
+ if (!validateTheme(themeToUse)) {
8476
+ console.warn('Invalid theme provided. Some required colors are missing or invalid.');
8477
+ }
8478
+ return createTheme(themeToUse);
8375
8479
  }),
8376
8480
  theme = _c[0],
8377
8481
  setThemeState = _c[1];
8378
8482
  React.useEffect(function () {
8379
- setThemeState(createTheme(providedTheme || {}));
8483
+ var themeToUse = providedTheme || {};
8484
+ if (!validateTheme(themeToUse)) {
8485
+ console.warn('Invalid theme provided. Some required colors are missing or invalid.');
8486
+ }
8487
+ setThemeState(createTheme(themeToUse));
8380
8488
  }, [providedTheme]);
8381
8489
  var setTheme = React.useCallback(function (newTheme) {
8490
+ if (!validateTheme(newTheme)) {
8491
+ console.warn('Invalid theme provided. Some required colors are missing or invalid.');
8492
+ }
8382
8493
  setThemeState(function (prevTheme) {
8383
8494
  return createTheme(__assign(__assign({}, prevTheme), newTheme));
8384
8495
  });
@@ -8394,9 +8505,10 @@ var WllSdkProvider = function (_a) {
8394
8505
  getGroupByID: getGroupByID,
8395
8506
  getSectionByID: getSectionByID,
8396
8507
  getTileByID: getTileByID,
8397
- handleNavigation: handleNavigation
8508
+ handleNavigation: handleNavigation,
8509
+ config: config
8398
8510
  };
8399
- }, [theme, setTheme, getGroupByID, getSectionByID, getTileByID, handleNavigation]);
8511
+ }, [theme, setTheme, getGroupByID, getSectionByID, getTileByID, handleNavigation, config]);
8400
8512
  return /*#__PURE__*/React.createElement(WllSdkContext.Provider, {
8401
8513
  value: contextValue
8402
8514
  }, /*#__PURE__*/React.createElement(ResponsiveProvider, null, children));
@@ -8408,6 +8520,20 @@ var useWllSdk = function () {
8408
8520
  }
8409
8521
  return context;
8410
8522
  };
8523
+ /**
8524
+ * Validates the SDK configuration object
8525
+ * @param config - The configuration object to validate
8526
+ * @throws {Error} If apiKey is missing
8527
+ * @throws {Error} If locale is provided but not in ISO 639-1 format (e.g. 'en')
8528
+ */
8529
+ var validateConfig = function (config) {
8530
+ if (!config.apiKey) {
8531
+ throw new Error('API key is required');
8532
+ }
8533
+ if (config.locale && !/^[a-z]{2}$/.test(config.locale)) {
8534
+ throw new Error('Invalid locale format. Expected ISO 639-1 language code (e.g. "en")');
8535
+ }
8536
+ };
8411
8537
 
8412
8538
  function createVariantSystem(baseStyle, variantStyles) {
8413
8539
  return function (theme, variant) {