@rango-dev/widget-embedded 0.28.3-next.5 → 0.28.3-next.7

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.
Files changed (100) hide show
  1. package/dist/components/BlockchainList/BlockchainList.styles.d.ts +12 -21
  2. package/dist/components/BlockchainList/BlockchainList.styles.d.ts.map +1 -1
  3. package/dist/components/BlockchainsSection/BlockchainsSection.styles.d.ts +4 -7
  4. package/dist/components/BlockchainsSection/BlockchainsSection.styles.d.ts.map +1 -1
  5. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts +60 -105
  6. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts.map +1 -1
  7. package/dist/components/ConfirmWalletsModal/ConfirmWalletsModal.d.ts.map +1 -1
  8. package/dist/components/ConfirmWalletsModal/WalletList.d.ts.map +1 -1
  9. package/dist/components/ConfirmWalletsModal/WalletList.styles.d.ts +12 -21
  10. package/dist/components/ConfirmWalletsModal/WalletList.styles.d.ts.map +1 -1
  11. package/dist/components/ConfirmWalletsModal/WalletList.type.d.ts +1 -0
  12. package/dist/components/ConfirmWalletsModal/WalletList.type.d.ts.map +1 -1
  13. package/dist/components/CustomCollapsible/CustomCollapsible.styles.d.ts +16 -28
  14. package/dist/components/CustomCollapsible/CustomCollapsible.styles.d.ts.map +1 -1
  15. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts +32 -56
  16. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts.map +1 -1
  17. package/dist/components/HistoryGroupedList/HistoryGroupedList.styles.d.ts +24 -42
  18. package/dist/components/HistoryGroupedList/HistoryGroupedList.styles.d.ts.map +1 -1
  19. package/dist/components/Layout/Layout.styles.d.ts +16 -28
  20. package/dist/components/Layout/Layout.styles.d.ts.map +1 -1
  21. package/dist/components/Layout/PageContainer.d.ts +4 -7
  22. package/dist/components/Layout/PageContainer.d.ts.map +1 -1
  23. package/dist/components/Layout/ScrollableArea.d.ts +4 -7
  24. package/dist/components/Layout/ScrollableArea.d.ts.map +1 -1
  25. package/dist/components/LoadingLiquiditySourceList/LoadingLiquiditySourceList.styles.d.ts +4 -7
  26. package/dist/components/LoadingLiquiditySourceList/LoadingLiquiditySourceList.styles.d.ts.map +1 -1
  27. package/dist/components/LoadingSwapDetails/LoadingSwapDetails.styles.d.ts +56 -98
  28. package/dist/components/LoadingSwapDetails/LoadingSwapDetails.styles.d.ts.map +1 -1
  29. package/dist/components/NoResult/NoResult.styles.d.ts +16 -28
  30. package/dist/components/NoResult/NoResult.styles.d.ts.map +1 -1
  31. package/dist/components/NotificationContent/NotificationContent.styles.d.ts +33 -57
  32. package/dist/components/NotificationContent/NotificationContent.styles.d.ts.map +1 -1
  33. package/dist/components/Quote/Quote.styles.d.ts +85 -148
  34. package/dist/components/Quote/Quote.styles.d.ts.map +1 -1
  35. package/dist/components/Quote/QuoteCostDetails.styles.d.ts +20 -35
  36. package/dist/components/Quote/QuoteCostDetails.styles.d.ts.map +1 -1
  37. package/dist/components/Quote/QuoteSummary.styles.d.ts +8 -14
  38. package/dist/components/Quote/QuoteSummary.styles.d.ts.map +1 -1
  39. package/dist/components/QuoteSkeleton/QuoteSkeleton.styles.d.ts +16 -28
  40. package/dist/components/QuoteSkeleton/QuoteSkeleton.styles.d.ts.map +1 -1
  41. package/dist/components/QuoteSkeleton/QuoteSummarySkeleton.styles.d.ts +40 -70
  42. package/dist/components/QuoteSkeleton/QuoteSummarySkeleton.styles.d.ts.map +1 -1
  43. package/dist/components/QuoteSkeleton/StepSkeleton.styles.d.ts +20 -35
  44. package/dist/components/QuoteSkeleton/StepSkeleton.styles.d.ts.map +1 -1
  45. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts +16 -28
  46. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts.map +1 -1
  47. package/dist/components/Quotes/Quotes.styles.d.ts +12 -21
  48. package/dist/components/Quotes/Quotes.styles.d.ts.map +1 -1
  49. package/dist/components/RefreshModal/RefreshModal.styles.d.ts +5 -8
  50. package/dist/components/RefreshModal/RefreshModal.styles.d.ts.map +1 -1
  51. package/dist/components/SearchInput/SearchInput.styles.d.ts +4 -7
  52. package/dist/components/SearchInput/SearchInput.styles.d.ts.map +1 -1
  53. package/dist/components/SettingsContainer/SettingsContainer.style.d.ts +12 -21
  54. package/dist/components/SettingsContainer/SettingsContainer.style.d.ts.map +1 -1
  55. package/dist/components/Slippage/Slippage.styles.d.ts +20 -35
  56. package/dist/components/Slippage/Slippage.styles.d.ts.map +1 -1
  57. package/dist/components/SwapDetails/SwapDetails.styles.d.ts +60 -105
  58. package/dist/components/SwapDetails/SwapDetails.styles.d.ts.map +1 -1
  59. package/dist/components/SwapDetailsAlerts/SwapDetailsAlerts.styles.d.ts +12 -21
  60. package/dist/components/SwapDetailsAlerts/SwapDetailsAlerts.styles.d.ts.map +1 -1
  61. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts +8 -14
  62. package/dist/components/SwapDetailsModal/SwapDetailsModal.styles.d.ts.map +1 -1
  63. package/dist/components/SwitchFromAndTo/SwitchFromAndTo.styles.d.ts +8 -14
  64. package/dist/components/SwitchFromAndTo/SwitchFromAndTo.styles.d.ts.map +1 -1
  65. package/dist/components/TokenList/TokenList.d.ts.map +1 -1
  66. package/dist/components/TokenList/TokenList.styles.d.ts +72 -126
  67. package/dist/components/TokenList/TokenList.styles.d.ts.map +1 -1
  68. package/dist/components/WalletModal/WalletModalContent.styles.d.ts +16 -28
  69. package/dist/components/WalletModal/WalletModalContent.styles.d.ts.map +1 -1
  70. package/dist/components/WalletNamespacesModal/WalletNamespacesModal.styles.d.ts +4 -7
  71. package/dist/components/WalletNamespacesModal/WalletNamespacesModal.styles.d.ts.map +1 -1
  72. package/dist/containers/App/App.styles.d.ts +4 -7
  73. package/dist/containers/App/App.styles.d.ts.map +1 -1
  74. package/dist/containers/ExpandedQuotes/ExpandedQuotes.styles.d.ts +8 -14
  75. package/dist/containers/ExpandedQuotes/ExpandedQuotes.styles.d.ts.map +1 -1
  76. package/dist/containers/Inputs/Inputs.styles.d.ts +8 -14
  77. package/dist/containers/Inputs/Inputs.styles.d.ts.map +1 -1
  78. package/dist/containers/QuoteInfo/QuoteInfo.styles.d.ts +12 -21
  79. package/dist/containers/QuoteInfo/QuoteInfo.styles.d.ts.map +1 -1
  80. package/dist/index.js +2 -2
  81. package/dist/index.js.map +3 -3
  82. package/dist/store/wallets.d.ts.map +1 -1
  83. package/dist/types/config.d.ts +5 -3
  84. package/dist/types/config.d.ts.map +1 -1
  85. package/dist/utils/colors.d.ts +7 -10
  86. package/dist/utils/colors.d.ts.map +1 -1
  87. package/dist/utils/ui.d.ts +2 -1
  88. package/dist/utils/ui.d.ts.map +1 -1
  89. package/dist/widget-embedded.build.json +1 -1
  90. package/package.json +2 -2
  91. package/src/components/ConfirmWalletsModal/ConfirmWalletsModal.tsx +86 -23
  92. package/src/components/ConfirmWalletsModal/WalletList.tsx +4 -2
  93. package/src/components/ConfirmWalletsModal/WalletList.type.ts +1 -0
  94. package/src/components/HeaderButtons/HeaderButtons.styles.ts +1 -1
  95. package/src/components/Quote/Quote.styles.ts +5 -5
  96. package/src/components/TokenList/TokenList.tsx +8 -13
  97. package/src/store/wallets.ts +7 -2
  98. package/src/types/config.ts +7 -3
  99. package/src/utils/colors.ts +160 -117
  100. package/src/utils/ui.ts +28 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rango-dev/widget-embedded",
3
- "version": "0.28.3-next.5",
3
+ "version": "0.28.3-next.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "source": "./src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "@rango-dev/queue-manager-rango-preset": "^0.33.3-next.0",
31
31
  "@rango-dev/queue-manager-react": "^0.26.0",
32
32
  "@rango-dev/signer-solana": "^0.28.1-next.2",
33
- "@rango-dev/ui": "^0.34.1-next.4",
33
+ "@rango-dev/ui": "^0.34.1-next.5",
34
34
  "@rango-dev/wallets-react": "^0.19.1-next.1",
35
35
  "@rango-dev/wallets-shared": "^0.33.1-next.1",
36
36
  "bignumber.js": "^9.1.1",
@@ -18,7 +18,7 @@ import {
18
18
  Typography,
19
19
  WalletIcon,
20
20
  } from '@rango-dev/ui';
21
- import React, { useEffect, useRef, useState } from 'react';
21
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
22
22
  import { useNavigate } from 'react-router-dom';
23
23
 
24
24
  import { WIDGET_UI_ID } from '../../constants';
@@ -59,6 +59,7 @@ const NUMBER_OF_WALLETS_TO_DISPLAY = 2;
59
59
  export function ConfirmWalletsModal(props: PropTypes) {
60
60
  //TODO: move component's logics to a custom hook
61
61
  const { open, onClose, onCancel, onCheckBalance, loading } = props;
62
+ const navigate = useNavigate();
62
63
  const config = useAppStore().config;
63
64
 
64
65
  const customDestinationInputRef = useRef<HTMLInputElement | null>(null);
@@ -104,16 +105,34 @@ export function ConfirmWalletsModal(props: PropTypes) {
104
105
  const isWalletRequiredFor = (blockchain: string) =>
105
106
  requiredWallets.includes(blockchain);
106
107
 
107
- const getInitialSelectableWallets = () =>
108
- connectedWallets.filter((connectedWallet) => {
109
- return (
110
- connectedWallet.selected && quoteWallets.includes(connectedWallet.chain)
111
- );
112
- });
108
+ const getInitialSelectableWallets = useCallback(
109
+ () =>
110
+ connectedWallets.filter((connectedWallet) => {
111
+ return (
112
+ connectedWallet.selected &&
113
+ quoteWallets.includes(connectedWallet.chain)
114
+ );
115
+ }),
116
+ [connectedWallets.length, quoteWallets.length]
117
+ );
113
118
 
114
119
  const [selectableWallets, setSelectableWallets] = useState<ConnectedWallet[]>(
115
120
  getInitialSelectableWallets()
116
121
  );
122
+ const [nextSelectedWallets, setNextSelectedWallets] = useState<
123
+ {
124
+ blockchain: string;
125
+ walletType: string;
126
+ }[]
127
+ >([]);
128
+
129
+ const addNextSelectedWallets = (blockchain: string, walletType: string) =>
130
+ setNextSelectedWallets((prevState) =>
131
+ prevState.concat({
132
+ blockchain,
133
+ walletType,
134
+ })
135
+ );
117
136
 
118
137
  const isInsufficientBalanceModalOpen = balanceWarnings.length > 0;
119
138
 
@@ -206,7 +225,11 @@ export function ConfirmWalletsModal(props: PropTypes) {
206
225
  }
207
226
 
208
227
  onCancel();
209
- if (wallet.chain === lastStepToBlockchain?.name && showCustomDestination) {
228
+ if (
229
+ wallet.chain === lastStepToBlockchain?.name &&
230
+ showCustomDestination &&
231
+ !isWalletRequiredFor(lastStepToBlockchain.name)
232
+ ) {
210
233
  setShowCustomDestination(false);
211
234
  setCustomDestination(null);
212
235
  }
@@ -273,10 +296,30 @@ export function ConfirmWalletsModal(props: PropTypes) {
273
296
  };
274
297
 
275
298
  useEffect(() => {
276
- setSelectableWallets((selectableWallets) =>
277
- selectableWallets.concat(
299
+ setSelectableWallets((selectableWallets) => {
300
+ let nextState: typeof selectableWallets = [];
301
+
302
+ //if wallet disconnected we should unselect the wallet from the list
303
+ selectableWallets.forEach((selectableWallet) => {
304
+ const walletDisconnected = !connectedWallets.some(
305
+ (connectedWallet) =>
306
+ connectedWallet.chain === selectableWallet.chain &&
307
+ connectedWallet.walletType === selectableWallet.walletType &&
308
+ connectedWallet.address === selectableWallet.address
309
+ );
310
+ if (!walletDisconnected) {
311
+ nextState.push(selectableWallet);
312
+ }
313
+ });
314
+
315
+ /**
316
+ * We confirm if a newly connected wallet for a blockchain is marked as selected in the store,
317
+ * but there are no selected wallets for that blockchain in our list.
318
+ * If this condition is met, we include the wallet in our list as selected.
319
+ */
320
+ nextState = nextState.concat(
278
321
  connectedWallets.filter((connectedWallet) => {
279
- const anyWalletSelected = !!selectableWallets.find(
322
+ const anyWalletSelected = !!nextState.find(
280
323
  (selectableWallet) =>
281
324
  selectableWallet.chain === connectedWallet.chain
282
325
  );
@@ -287,16 +330,35 @@ export function ConfirmWalletsModal(props: PropTypes) {
287
330
  quoteWallets.includes(connectedWallet.chain)
288
331
  );
289
332
  })
290
- )
291
- );
292
- }, [connectedWallets.length]);
333
+ );
334
+ return nextState;
335
+ });
336
+ }, [connectedWallets.length, quoteWallets.length]);
337
+
338
+ useEffect(() => {
339
+ const nextState: typeof nextSelectedWallets = [];
340
+
341
+ if (nextSelectedWallets.length > 0) {
342
+ nextSelectedWallets.forEach((selectedWallet) => {
343
+ const wallet = connectedWallets.find(
344
+ (wallet) =>
345
+ wallet.chain === selectedWallet.blockchain &&
346
+ wallet.walletType === selectedWallet.walletType
347
+ );
348
+ if (wallet) {
349
+ onChange(wallet);
350
+ } else {
351
+ nextState.push(selectedWallet);
352
+ }
353
+ });
354
+ setNextSelectedWallets(nextState);
355
+ }
356
+ }, [connectedWallets.length, nextSelectedWallets.length]);
293
357
 
294
358
  const modalContainer = document.getElementById(
295
359
  WIDGET_UI_ID.SWAP_BOX_ID
296
360
  ) as HTMLDivElement;
297
361
 
298
- const navigate = useNavigate();
299
-
300
362
  return (
301
363
  <WatermarkedModal
302
364
  open={open}
@@ -381,7 +443,10 @@ export function ConfirmWalletsModal(props: PropTypes) {
381
443
  chain={showMoreWalletFor}
382
444
  isSelected={isSelected}
383
445
  selectWallet={onChange}
384
- onShowMore={setShowMoreWalletFor.bind(null, showMoreWalletFor)}
446
+ onShowMore={() => setShowMoreWalletFor(showMoreWalletFor)}
447
+ onConnect={(walletType) => {
448
+ addNextSelectedWallets(showMoreWalletFor, walletType);
449
+ }}
385
450
  />
386
451
  </div>
387
452
  </WalletsContainer>
@@ -442,6 +507,9 @@ export function ConfirmWalletsModal(props: PropTypes) {
442
507
  onShowMore={() =>
443
508
  setShowMoreWalletFor(blockchain?.name ?? '')
444
509
  }
510
+ onConnect={(walletType) => {
511
+ addNextSelectedWallets(requiredWallet, walletType);
512
+ }}
445
513
  />
446
514
  </ListContainer>
447
515
  {!isLastWallet && <Divider size={32} />}
@@ -501,12 +569,7 @@ export function ConfirmWalletsModal(props: PropTypes) {
501
569
  suffix={renderCustomDestinationSuffix()}
502
570
  onChange={(e) => {
503
571
  const value = e.target.value;
504
- if (!value.length) {
505
- setShowCustomDestination(false);
506
- setCustomDestination(null);
507
- } else {
508
- setCustomDestination(value.length ? value : null);
509
- }
572
+ setCustomDestination(value);
510
573
  }}
511
574
  {...(!customDestination && { autoFocus: true })}
512
575
  />
@@ -50,7 +50,8 @@ interface WalletNamespacesModalState {
50
50
 
51
51
  const ACCOUNT_ADDRESS_MAX_CHARACTERS = 7;
52
52
  export function WalletList(props: PropTypes) {
53
- const { chain, isSelected, selectWallet, limit, onShowMore } = props;
53
+ const { chain, isSelected, selectWallet, limit, onShowMore, onConnect } =
54
+ props;
54
55
  const isActiveTab = useUiStore.use.isActiveTab();
55
56
 
56
57
  const connectedWallets = useWalletsStore.use.connectedWallets();
@@ -80,7 +81,8 @@ export function WalletList(props: PropTypes) {
80
81
  setOpenWalletStateModal(type);
81
82
  }, TIME_TO_IGNORE_MODAL);
82
83
  },
83
- onConnect: () => {
84
+ onConnect: (type) => {
85
+ onConnect?.(type);
84
86
  if (modalTimerId) {
85
87
  clearTimeout(modalTimerId);
86
88
  }
@@ -6,4 +6,5 @@ export type PropTypes = {
6
6
  selectWallet: (wallet: Wallet) => void;
7
7
  limit?: number;
8
8
  onShowMore: () => void;
9
+ onConnect?: (walletType: string) => void;
9
10
  };
@@ -17,7 +17,7 @@ export const ConnectedIcon = styled('div', {
17
17
  position: 'absolute',
18
18
  background: '$secondary500',
19
19
  [`.${darkTheme} &`]: {
20
- $$color: '$colors$secondary400',
20
+ $$color: '$colors$secondary250',
21
21
  },
22
22
  width: '$6',
23
23
  height: '$6',
@@ -48,7 +48,7 @@ export const QuoteContainer = styled(Collapsible.Root, {
48
48
  listItem: false,
49
49
  selected: true,
50
50
  css: {
51
- $$color: '$colors$info300',
51
+ $$color: '$colors$secondary200',
52
52
  [`.${darkTheme} &`]: {
53
53
  $$color: '$colors$secondary800',
54
54
  },
@@ -63,13 +63,13 @@ export const stepsDetailsStyles = css({
63
63
  });
64
64
  export const AllRoutesButton = styled(Button, {
65
65
  backgroundColor: 'transparent',
66
- border: '1px solid $secondary600',
66
+ border: '1px solid $secondary550',
67
67
  [`.${darkTheme} &`]: {
68
68
  border: '1px solid $secondary',
69
69
  },
70
70
  transition: 'background-color 0.3s ease',
71
71
  '&:hover': {
72
- backgroundColor: '$secondary600',
72
+ backgroundColor: '$secondary550',
73
73
  [`.${darkTheme} &`]: {
74
74
  backgroundColor: '$secondary',
75
75
  },
@@ -149,7 +149,7 @@ export const SummaryContainer = styled('div', {
149
149
  css: {
150
150
  $$color: '$colors$secondary100',
151
151
  [`.${darkTheme} &`]: {
152
- $$color: '$colors$secondary600',
152
+ $$color: '$colors$secondary550',
153
153
  },
154
154
  backgroundColor: '$$color',
155
155
  '&:hover': {
@@ -262,7 +262,7 @@ export const Trigger = styled(Collapsible.Trigger, {
262
262
  listItem: false,
263
263
  selected: true,
264
264
  css: {
265
- $$color: '$colors$info300',
265
+ $$color: '$colors$secondary200',
266
266
  [`.${darkTheme} &`]: {
267
267
  $$color: '$colors$secondary800',
268
268
  },
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-magic-numbers */
2
2
  import type { PropTypes, RenderDescProps } from './TokenList.types';
3
- import type { Token } from 'rango-sdk';
3
+ import type { BlockchainMeta, Token } from 'rango-sdk';
4
4
 
5
5
  import { i18n } from '@lingui/core';
6
6
  import {
@@ -19,7 +19,7 @@ import React, { useEffect, useState } from 'react';
19
19
 
20
20
  import { useAppStore } from '../../store/AppStore';
21
21
  import { useWalletsStore } from '../../store/wallets';
22
- import { generateShade, isBright } from '../../utils/colors';
22
+ import { createTintsAndShades } from '../../utils/colors';
23
23
  import { formatBalance } from '../../utils/wallets';
24
24
 
25
25
  import { LoadingTokenList } from './LoadingTokenList';
@@ -133,25 +133,20 @@ export function TokenList(props: PropTypes) {
133
133
  const address = token.address || '';
134
134
  const blockchain = blockchains.find(
135
135
  (blockchain) => blockchain.name === token.blockchain
136
- );
137
- const lightShade = generateShade(
138
- blockchain?.color || '',
139
- isBright(blockchain?.color || '') ? 5 : 125
140
- );
141
- const darkShade = generateShade(blockchain?.color || '', -100);
142
-
136
+ ) as BlockchainMeta;
137
+ const colors = createTintsAndShades(blockchain.color, 'main');
143
138
  const customCssForTag = {
144
- $$color: lightShade,
139
+ $$color: colors.main250,
145
140
  [`.${darkTheme} &`]: {
146
- $$color: darkShade,
141
+ $$color: colors.main850,
147
142
  },
148
143
  backgroundColor: '$$color',
149
144
  };
150
145
 
151
146
  const customCssForTagTitle = {
152
- $$color: darkShade,
147
+ $$color: colors.main600,
153
148
  [`.${darkTheme} &`]: {
154
- $$color: lightShade,
149
+ $$color: colors.main200,
155
150
  },
156
151
  color: '$$color',
157
152
  };
@@ -65,10 +65,15 @@ export const useWalletsStore = createSelectors(
65
65
  .filter((wallet) => wallet.walletType !== accounts[0].walletType)
66
66
  .concat(
67
67
  accounts.map((account) => {
68
- const shouldMarkWalletAsSelected = !state.connectedWallets.find(
68
+ const shouldMarkWalletAsSelected = !state.connectedWallets.some(
69
69
  (connectedWallet) =>
70
70
  connectedWallet.chain === account.chain &&
71
- connectedWallet.selected
71
+ connectedWallet.selected &&
72
+ /**
73
+ * Sometimes, the connect function can be called multiple times for a particular wallet type when using the auto-connect feature.
74
+ * This check is there to make sure the chosen wallet doesn't end up unselected.
75
+ */
76
+ connectedWallet.walletType !== account.walletType
72
77
  );
73
78
  return {
74
79
  balances: [],
@@ -14,14 +14,18 @@ export type WidgetVariant = 'default' | 'expanded' | 'full-expanded';
14
14
  *
15
15
  */
16
16
  export type WidgetColorsKeys = keyof WidgetColors;
17
- export type WidgetColors = {
17
+
18
+ export type ThemeColors = {
19
+ [key in string]?: string;
20
+ };
21
+
22
+ export interface WidgetColors extends ThemeColors {
18
23
  background?: string;
19
24
  foreground?: string;
20
25
  neutral?: string;
21
26
  primary?: string;
22
27
  secondary?: string;
23
- info?: string;
24
- };
28
+ }
25
29
 
26
30
  /**
27
31
  * The `WidgetTheme` defines the properties of a widget theme, including mode, font family, colors, border
@@ -1,141 +1,184 @@
1
- import type { WidgetColors, WidgetColorsKeys } from '../types';
1
+ /* eslint-disable @typescript-eslint/no-magic-numbers */
2
+ // Types
3
+ import type { ThemeColors, WidgetColors, WidgetColorsKeys } from '../types';
4
+
5
+ type RGB = {
6
+ red: number;
7
+ green: number;
8
+ blue: number;
9
+ };
2
10
 
3
- export function generateShades(
4
- baseColor: string,
5
- prefix: string,
6
- reverse?: boolean
7
- ): { [key: string]: string } {
8
- const BASE_SHADE_INDEX = 100;
9
- const NUMBER_OF_COLORS = 9;
10
- const SHADING_INCREMENT_MAX = 10; // For bright colors
11
- const SHADING_INCREMENT_MIN = 6; // For dark colors
12
-
13
- const HALF_NUMBER_OF_COLORS = 4;
14
-
15
- const shadeIncrements: number[] = [];
16
- let increment = 90;
17
- for (let i = 0; i < NUMBER_OF_COLORS; i++) {
18
- shadeIncrements.push(increment);
19
- if (i + 1 === HALF_NUMBER_OF_COLORS) {
20
- increment = 0;
21
- } else {
22
- increment -=
23
- i >= HALF_NUMBER_OF_COLORS
24
- ? SHADING_INCREMENT_MIN
25
- : SHADING_INCREMENT_MAX;
26
- }
27
- }
11
+ type ShadeOrTintFunction = (rgb: RGB, step: number) => RGB;
28
12
 
29
- // If 'reverse' is true, that means the color should be from dark to light
30
- const decrementAmounts = reverse
31
- ? shadeIncrements.slice().reverse()
32
- : shadeIncrements;
13
+ const HEX_BASE = 16;
14
+ const MAX_COLOR_VALUE = 255;
15
+ const PERCENT = 0.11;
33
16
 
34
- const shades: { [key: string]: string } = {};
17
+ // Utility Functions
35
18
 
36
- // Generate shades
37
- for (let i = 0; i < NUMBER_OF_COLORS; i++) {
38
- const shadeIndex = BASE_SHADE_INDEX + i * BASE_SHADE_INDEX;
39
- const decrementAmount = decrementAmounts[i];
19
+ function expandShortHexColor(hexColor: string) {
20
+ // Remove '#' if it exists
21
+ if (hexColor.startsWith('#')) {
22
+ hexColor = hexColor.slice(1);
23
+ }
24
+
25
+ // Check if the hexColor has a length of 3
26
+ if (hexColor.length === 3) {
27
+ // Convert the string to an array of characters
28
+ const characters = hexColor.split('');
40
29
 
41
- const shade: { [key: string]: string } = {
42
- [`${prefix}${shadeIndex}`]: generateShade(baseColor, decrementAmount),
43
- };
44
- Object.assign(shades, shade);
30
+ // Duplicate each character and combine them into a single string
31
+ const expandedHex = characters.reduce(function (acc, char) {
32
+ return acc + char + char;
33
+ }, '');
34
+
35
+ // Return the resulting 6-character hex color
36
+ return `#${expandedHex}`;
45
37
  }
46
38
 
47
- return { ...shades, [prefix]: baseColor };
39
+ // Return the original hexColor if it's not 3 characters
40
+ return `#${hexColor}`;
48
41
  }
49
42
 
50
- export function generateShade(
51
- baseColor: string,
52
- decrementAmount: number
53
- ): string {
54
- const [r, g, b] = convertToRGB(baseColor);
55
- return `#${clamp(r + decrementAmount)}${clamp(g + decrementAmount)}${clamp(
56
- b + decrementAmount
57
- )}`;
43
+ /*
44
+ * We letting users to override some specific colors (e.g. `primary550`, `secondary100`).
45
+ * So we are generating a range of colors if `primary` (or other keys) has passed but if user is passing a specific color,
46
+ * we will override the user color to generated range.
47
+ */
48
+ function isOverridingColor(colorKey: string): boolean {
49
+ return /[0-9]+$/.test(colorKey);
58
50
  }
59
51
 
60
- export function convertToRGB(baseColor: string): number[] {
61
- const COLOR_SLICE_START = 1;
62
- const COLOR_SLICE_END = 3;
63
- const BASE_COLOR_LENGTH = 7;
52
+ // pad a hexadecimal string with zeros if it needs it
53
+ function pad(number: string, length: number) {
54
+ return number.padStart(length, '0');
55
+ }
64
56
 
65
- // Convert the base color to RGB components
66
- const r: number = parseInt(
67
- baseColor.slice(COLOR_SLICE_START, COLOR_SLICE_END),
68
- 16
69
- );
70
- const g: number = parseInt(
71
- baseColor.slice(COLOR_SLICE_END, COLOR_SLICE_END + 2),
72
- 16
73
- );
74
- const b: number = parseInt(
75
- baseColor.slice(COLOR_SLICE_END + 2, BASE_COLOR_LENGTH),
76
- 16
57
+ // Convert an integer to a 2-character hex string
58
+ function intToHex(value: number) {
59
+ const clampedValue = Math.min(
60
+ Math.max(Math.round(value), 0),
61
+ MAX_COLOR_VALUE
77
62
  );
63
+ return pad(clampedValue.toString(HEX_BASE), 2);
64
+ }
65
+
66
+ // convert one of our rgb color objects to a full hex color string
67
+ function rgbToHex(rgb: RGB) {
68
+ return `#${intToHex(rgb.red)}${intToHex(rgb.green)}${intToHex(rgb.blue)}`;
69
+ }
70
+
71
+ // Convert a hex color string to an RGB object
72
+ function hexToRGB(hex: string): RGB {
73
+ const red = parseInt(hex.slice(1, 3), HEX_BASE);
74
+ const green = parseInt(hex.slice(3, 5), HEX_BASE);
75
+ const blue = parseInt(hex.slice(5, 7), HEX_BASE);
76
+
77
+ return { red, green, blue };
78
+ }
78
79
 
79
- return [r, g, b];
80
+ // Generate a shade of a given RGB color
81
+ function generateShade(rgb: RGB, step: number): RGB {
82
+ const factor = 1 - PERCENT * step;
83
+ return {
84
+ red: rgb.red * factor,
85
+ green: rgb.green * factor,
86
+ blue: rgb.blue * factor,
87
+ };
80
88
  }
81
89
 
82
- // Function to clamp color component to valid range [0, 255] and convert to hex
83
- function clamp(color: number): string {
84
- const MAX_COLOR_VALUE = 255;
85
- const HEX_BASE = 16;
86
- const HEX_LENGTH = -2;
87
- return (
88
- '0' + Math.max(0, Math.min(MAX_COLOR_VALUE, color)).toString(HEX_BASE)
89
- )
90
- .slice(HEX_LENGTH)
91
- .toUpperCase();
90
+ // Generate a tint of a given RGB color
91
+ function generateTint(rgb: RGB, step: number): RGB {
92
+ const factor = PERCENT * step;
93
+ return {
94
+ red: rgb.red + (MAX_COLOR_VALUE - rgb.red) * factor,
95
+ green: rgb.green + (MAX_COLOR_VALUE - rgb.green) * factor,
96
+ blue: rgb.blue + (MAX_COLOR_VALUE - rgb.blue) * factor,
97
+ };
92
98
  }
93
99
 
94
- export function isBright(baseColor: string) {
95
- const BRIGHTNESS_THRESHOLD = 188;
96
- const RED_COEFFICIENT = 299;
97
- const GREEN_COEFFICIENT = 587;
98
- const BLUE_COEFFICIENT = 114;
99
- const BRIGHTNESS_DIVISOR = 1000;
100
-
101
- const [r, g, b] = convertToRGB(baseColor);
102
- const brightness =
103
- (r * RED_COEFFICIENT + g * GREEN_COEFFICIENT + b * BLUE_COEFFICIENT) /
104
- BRIGHTNESS_DIVISOR;
105
- if (brightness > BRIGHTNESS_THRESHOLD) {
106
- return true;
100
+ // Main Functions
101
+
102
+ // Calculate an array of shades from a hex color string
103
+ export function calculateShades(hex: string): string[] {
104
+ return calculateColors(hex, generateShade);
105
+ }
106
+
107
+ // Calculate an array of tints from a hex color string
108
+ export function calculateTints(hex: string): string[] {
109
+ return calculateColors(hex, generateTint);
110
+ }
111
+
112
+ // General function to calculate shades or tints
113
+ function calculateColors(
114
+ hex: string,
115
+ generateColor: ShadeOrTintFunction
116
+ ): string[] {
117
+ const rgb = hexToRGB(hex);
118
+ const colors: string[] = [];
119
+ for (let i = 1; i < 9; i++) {
120
+ colors.push(rgbToHex(generateColor(rgb, i)));
107
121
  }
108
- return false;
122
+ return colors;
109
123
  }
110
124
 
111
- export const generateColors = (
112
- mainColors: { [x: string]: string },
113
- reverseNeutralRange: boolean,
114
- colors?: WidgetColors
115
- ) => {
116
- if (!colors || !Object.entries(colors).length) {
117
- return {};
125
+ // Create tints and shades for a given color
126
+ export function createTintsAndShades(
127
+ hex: string,
128
+ colorKey: string,
129
+ isReversed?: boolean
130
+ ): { [key: string]: string } {
131
+ const BASE_INDEX = 100;
132
+ const STEP = 50;
133
+
134
+ const tints = calculateTints(hex).reverse().concat(hex);
135
+ const shades = calculateShades(hex);
136
+ const combinedColors = tints.concat(shades);
137
+
138
+ const colorMap: { [key: string]: string } = {};
139
+ const length = combinedColors.length;
140
+
141
+ for (let i = 0; i < length; i++) {
142
+ const index = BASE_INDEX + (isReversed ? length - 1 - i : i) * STEP;
143
+ colorMap[`${colorKey}${index}`] = combinedColors[i];
118
144
  }
119
- let changeColors = false;
120
- let listOfColors = { ...mainColors };
121
- for (const colorKey in colors) {
122
- const color = colors[colorKey as WidgetColorsKeys];
123
-
124
- if (!!color && color !== mainColors[colorKey]) {
125
- changeColors = true;
126
- if (colorKey === 'background' || colorKey === 'foreground') {
127
- listOfColors = {
128
- ...listOfColors,
129
- [colorKey]: color,
130
- };
131
- } else {
132
- const reverse = colorKey === 'neutral' && reverseNeutralRange;
133
- listOfColors = {
134
- ...listOfColors,
135
- ...generateShades(color, colorKey, reverse),
136
- };
137
- }
145
+
146
+ return colorMap;
147
+ }
148
+
149
+ // Generate a color palette based on base colors and "Expand Colors"
150
+ export function expandToGenerateThemeColors(
151
+ baseColors: ThemeColors,
152
+ expandColors: WidgetColors,
153
+ options?: { reverseNeutralRange?: boolean }
154
+ ): ThemeColors {
155
+ const output = { ...baseColors };
156
+
157
+ for (const colorKey in expandColors) {
158
+ const expandColor = expandColors[colorKey as WidgetColorsKeys] as string;
159
+ const isNeutralReversed =
160
+ colorKey === 'neutral' && !!options?.reverseNeutralRange;
161
+ /*
162
+ *Most of the colors are range but there are some colors that should be a single value e.g.
163
+ *{
164
+ * // single value
165
+ * background: "#fff",
166
+ * // range
167
+ * primary: '#1C3CF1',
168
+ * primary500: '#1C3CF1',
169
+ * primary550: '#0B27C4',
170
+ *}
171
+ */
172
+ const isSingleColor = ['background', 'foreground'].includes(colorKey);
173
+
174
+ if (!isSingleColor && !isOverridingColor(colorKey)) {
175
+ const expandedHexColor = expandShortHexColor(expandColor);
176
+ Object.assign(
177
+ output,
178
+ createTintsAndShades(expandedHexColor, colorKey, isNeutralReversed)
179
+ );
138
180
  }
139
181
  }
140
- return changeColors ? listOfColors : {};
141
- };
182
+
183
+ return { ...output, ...expandColors };
184
+ }