@pagopa/io-app-design-system 2.1.1 → 2.1.2

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 (81) hide show
  1. package/lib/commonjs/components/common/LogoPaymentWithFallback.js +11 -20
  2. package/lib/commonjs/components/common/LogoPaymentWithFallback.js.map +1 -1
  3. package/lib/commonjs/components/listitems/ListItemTransaction.js +25 -27
  4. package/lib/commonjs/components/listitems/ListItemTransaction.js.map +1 -1
  5. package/lib/commonjs/components/listitems/__test__/__snapshots__/listitem.test.tsx.snap +10 -0
  6. package/lib/commonjs/components/listitems/__test__/listitem.test.js +4 -10
  7. package/lib/commonjs/components/listitems/__test__/listitem.test.js.map +1 -1
  8. package/lib/commonjs/components/modules/ModuleIDP.js +9 -2
  9. package/lib/commonjs/components/modules/ModuleIDP.js.map +1 -1
  10. package/lib/commonjs/components/modules/ModulePaymentNotice.js +6 -8
  11. package/lib/commonjs/components/modules/ModulePaymentNotice.js.map +1 -1
  12. package/lib/commonjs/components/tag/Tag.js +11 -4
  13. package/lib/commonjs/components/tag/Tag.js.map +1 -1
  14. package/lib/commonjs/core/IODSExperimentalContextProvider.js +2 -1
  15. package/lib/commonjs/core/IODSExperimentalContextProvider.js.map +1 -1
  16. package/lib/commonjs/core/IOThemeContextProvider.js +2 -1
  17. package/lib/commonjs/core/IOThemeContextProvider.js.map +1 -1
  18. package/lib/commonjs/utils/accessibility.js +14 -1
  19. package/lib/commonjs/utils/accessibility.js.map +1 -1
  20. package/lib/commonjs/utils/dates.js +20 -0
  21. package/lib/commonjs/utils/dates.js.map +1 -0
  22. package/lib/commonjs/utils/image.js +14 -23
  23. package/lib/commonjs/utils/image.js.map +1 -1
  24. package/lib/commonjs/utils/object.js +5 -10
  25. package/lib/commonjs/utils/object.js.map +1 -1
  26. package/lib/module/components/common/LogoPaymentWithFallback.js +11 -20
  27. package/lib/module/components/common/LogoPaymentWithFallback.js.map +1 -1
  28. package/lib/module/components/listitems/ListItemTransaction.js +23 -27
  29. package/lib/module/components/listitems/ListItemTransaction.js.map +1 -1
  30. package/lib/module/components/listitems/__test__/__snapshots__/listitem.test.tsx.snap +10 -0
  31. package/lib/module/components/listitems/__test__/listitem.test.js +4 -10
  32. package/lib/module/components/listitems/__test__/listitem.test.js.map +1 -1
  33. package/lib/module/components/modules/ModuleIDP.js +10 -3
  34. package/lib/module/components/modules/ModuleIDP.js.map +1 -1
  35. package/lib/module/components/modules/ModulePaymentNotice.js +6 -8
  36. package/lib/module/components/modules/ModulePaymentNotice.js.map +1 -1
  37. package/lib/module/components/tag/Tag.js +9 -4
  38. package/lib/module/components/tag/Tag.js.map +1 -1
  39. package/lib/module/core/IODSExperimentalContextProvider.js +2 -1
  40. package/lib/module/core/IODSExperimentalContextProvider.js.map +1 -1
  41. package/lib/module/core/IOThemeContextProvider.js +2 -1
  42. package/lib/module/core/IOThemeContextProvider.js.map +1 -1
  43. package/lib/module/utils/accessibility.js +9 -0
  44. package/lib/module/utils/accessibility.js.map +1 -1
  45. package/lib/module/utils/dates.js +11 -0
  46. package/lib/module/utils/dates.js.map +1 -0
  47. package/lib/module/utils/image.js +12 -23
  48. package/lib/module/utils/image.js.map +1 -1
  49. package/lib/module/utils/object.js +3 -10
  50. package/lib/module/utils/object.js.map +1 -1
  51. package/lib/typescript/components/common/LogoPaymentWithFallback.d.ts.map +1 -1
  52. package/lib/typescript/components/listitems/ListItemTransaction.d.ts +5 -11
  53. package/lib/typescript/components/listitems/ListItemTransaction.d.ts.map +1 -1
  54. package/lib/typescript/components/modules/ModuleIDP.d.ts.map +1 -1
  55. package/lib/typescript/components/modules/ModulePaymentNotice.d.ts +4 -10
  56. package/lib/typescript/components/modules/ModulePaymentNotice.d.ts.map +1 -1
  57. package/lib/typescript/components/tag/Tag.d.ts.map +1 -1
  58. package/lib/typescript/core/IODSExperimentalContextProvider.d.ts.map +1 -1
  59. package/lib/typescript/core/IOThemeContextProvider.d.ts.map +1 -1
  60. package/lib/typescript/utils/accessibility.d.ts +5 -0
  61. package/lib/typescript/utils/accessibility.d.ts.map +1 -1
  62. package/lib/typescript/utils/dates.d.ts +9 -0
  63. package/lib/typescript/utils/dates.d.ts.map +1 -0
  64. package/lib/typescript/utils/image.d.ts +2 -2
  65. package/lib/typescript/utils/image.d.ts.map +1 -1
  66. package/lib/typescript/utils/object.d.ts +2 -1
  67. package/lib/typescript/utils/object.d.ts.map +1 -1
  68. package/package.json +4 -1
  69. package/src/components/common/LogoPaymentWithFallback.tsx +17 -15
  70. package/src/components/listitems/ListItemTransaction.tsx +46 -40
  71. package/src/components/listitems/__test__/__snapshots__/listitem.test.tsx.snap +10 -0
  72. package/src/components/listitems/__test__/listitem.test.tsx +4 -10
  73. package/src/components/modules/ModuleIDP.tsx +13 -4
  74. package/src/components/modules/ModulePaymentNotice.tsx +10 -14
  75. package/src/components/tag/Tag.tsx +19 -10
  76. package/src/core/IODSExperimentalContextProvider.tsx +2 -1
  77. package/src/core/IOThemeContextProvider.tsx +2 -1
  78. package/src/utils/accessibility.ts +17 -0
  79. package/src/utils/dates.ts +18 -0
  80. package/src/utils/image.ts +28 -28
  81. package/src/utils/object.ts +12 -13
@@ -1,3 +1,5 @@
1
+ import * as O from "fp-ts/lib/Option";
2
+ import { pipe } from "fp-ts/lib/function";
1
3
  import React from "react";
2
4
  import { ImageURISource, StyleSheet, View } from "react-native";
3
5
  import Placeholder from "rn-placeholder";
@@ -12,6 +14,8 @@ import {
12
14
  useIOExperimentalDesign,
13
15
  useIOTheme
14
16
  } from "../../core";
17
+
18
+ import { getAccessibleAmountText } from "../../utils/accessibility";
15
19
  import { WithTestID } from "../../utils/types";
16
20
  import { isImageUri } from "../../utils/url";
17
21
  import { Avatar } from "../avatar/Avatar";
@@ -69,20 +73,14 @@ export type ListItemTransaction = WithTestID<
69
73
  accessible?: boolean;
70
74
  } & (
71
75
  | {
72
- transaction: {
73
- amount: string;
74
- amountAccessibilityLabel: string;
75
- status: ListItemTransactionStatusWithoutBadge;
76
- };
76
+ transactionStatus: ListItemTransactionStatusWithoutBadge;
77
77
  badgeText?: string;
78
+ transactionAmount: string;
78
79
  }
79
80
  | {
80
- transaction: {
81
- amount?: string;
82
- amountAccessibilityLabel?: string;
83
- status: ListItemTransactionStatusWithBadge;
84
- };
81
+ transactionStatus: ListItemTransactionStatusWithBadge;
85
82
  badgeText: string;
83
+ transactionAmount?: string;
86
84
  }
87
85
  )
88
86
  >;
@@ -121,15 +119,20 @@ export const ListItemTransaction = ({
121
119
  subtitle,
122
120
  testID,
123
121
  title,
124
- transaction: { amount, amountAccessibilityLabel, status = "success" },
122
+ transactionAmount,
125
123
  badgeText,
124
+ transactionStatus = "success",
126
125
  numberOfLines = 2,
127
126
  accessible
128
127
  }: ListItemTransaction) => {
129
128
  const { isExperimental } = useIOExperimentalDesign();
130
129
  const theme = useIOTheme();
131
130
 
132
- const maybeBadgeText = badgeText ?? "-";
131
+ const maybeBadgeText = pipe(
132
+ badgeText,
133
+ O.fromNullable,
134
+ O.getOrElse(() => "-")
135
+ );
133
136
 
134
137
  if (isLoading) {
135
138
  return <SkeletonComponent />;
@@ -144,25 +147,25 @@ export const ListItemTransaction = ({
144
147
 
145
148
  const ListItemTransactionContent = () => {
146
149
  const TransactionAmountOrBadgeComponent = () => {
147
- switch (status) {
150
+ switch (transactionStatus) {
148
151
  case "success":
149
152
  return (
150
153
  <H6
151
- accessibilityLabel={amountAccessibilityLabel}
154
+ accessibilityLabel={getAccessibleAmountText(transactionAmount)}
152
155
  color={hasChevronRight ? interactiveColor : amountColor}
153
156
  numberOfLines={numberOfLines}
154
157
  >
155
- {amount || ""}
158
+ {transactionAmount || ""}
156
159
  </H6>
157
160
  );
158
161
  case "refunded":
159
162
  return (
160
163
  <H6
161
- accessibilityLabel={amountAccessibilityLabel}
164
+ accessibilityLabel={getAccessibleAmountText(transactionAmount)}
162
165
  color={hasChevronRight ? interactiveColor : successColor}
163
166
  numberOfLines={numberOfLines}
164
167
  >
165
- {amount || ""}
168
+ {transactionAmount || ""}
166
169
  </H6>
167
170
  );
168
171
  case "failure":
@@ -214,30 +217,33 @@ export const ListItemTransaction = ({
214
217
  );
215
218
  };
216
219
 
217
- if (onPress) {
218
- return (
219
- <PressableListItemBase
220
- onPress={onPress}
221
- testID={testID}
222
- accessibilityLabel={accessibilityLabel}
223
- >
224
- <ListItemTransactionContent />
225
- </PressableListItemBase>
226
- );
227
- } else {
228
- return (
229
- <View
230
- style={IOListItemStyles.listItem}
231
- testID={testID}
232
- accessible={accessible}
233
- accessibilityLabel={accessibilityLabel}
234
- >
235
- <View style={IOListItemStyles.listItemInner}>
236
- <ListItemTransactionContent />
220
+ return pipe(
221
+ onPress,
222
+ O.fromNullable,
223
+ O.fold(
224
+ () => (
225
+ <View
226
+ style={IOListItemStyles.listItem}
227
+ testID={testID}
228
+ accessible={accessible}
229
+ accessibilityLabel={accessibilityLabel}
230
+ >
231
+ <View style={IOListItemStyles.listItemInner}>
232
+ <ListItemTransactionContent />
233
+ </View>
237
234
  </View>
238
- </View>
239
- );
240
- }
235
+ ),
236
+ onPress => (
237
+ <PressableListItemBase
238
+ onPress={onPress}
239
+ testID={testID}
240
+ accessibilityLabel={accessibilityLabel}
241
+ >
242
+ <ListItemTransactionContent />
243
+ </PressableListItemBase>
244
+ )
245
+ )
246
+ );
241
247
  };
242
248
 
243
249
  const SkeletonComponent = () => (
@@ -187,6 +187,11 @@ exports[`Test List Item Components - Experimental Enabled ListItemIDP Snapshot
187
187
  />
188
188
  <Image
189
189
  accessibilityIgnoresInvertColors={true}
190
+ source={
191
+ {
192
+ "uri": "undefined",
193
+ }
194
+ }
190
195
  style={
191
196
  {
192
197
  "height": 30,
@@ -1879,6 +1884,11 @@ exports[`Test List Item Components ListItemIDP Snapshot 1`] = `
1879
1884
  />
1880
1885
  <Image
1881
1886
  accessibilityIgnoresInvertColors={true}
1887
+ source={
1888
+ {
1889
+ "uri": "undefined",
1890
+ }
1891
+ }
1882
1892
  style={
1883
1893
  {
1884
1894
  "height": 30,
@@ -80,11 +80,8 @@ describe("Test List Item Components", () => {
80
80
  <ListItemTransaction
81
81
  title="TITLE"
82
82
  subtitle="subtitle"
83
- transaction={{
84
- status: "success",
85
- amount: "€ 1.000,00",
86
- amountAccessibilityLabel: "€ 1.000,00"
87
- }}
83
+ transactionStatus="success"
84
+ transactionAmount="€ 1.000,00"
88
85
  isLoading={true}
89
86
  onPress={onButtonPress}
90
87
  />
@@ -183,11 +180,8 @@ describe("Test List Item Components - Experimental Enabled ", () => {
183
180
  <ListItemTransaction
184
181
  title="TITLE"
185
182
  subtitle="subtitle"
186
- transaction={{
187
- status: "success",
188
- amount: "€ 1.000,00",
189
- amountAccessibilityLabel: "€ 1.000,00"
190
- }}
183
+ transactionStatus="success"
184
+ transactionAmount="€ 1.000,00"
191
185
  isLoading={true}
192
186
  onPress={onButtonPress}
193
187
  />
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
- import { Image, ImageSourcePropType, StyleSheet } from "react-native";
3
- import { addCacheTimestampToUri } from "../../utils/image";
2
+ import { Image, ImageSourcePropType, Platform, StyleSheet } from "react-native";
4
3
  import {
5
4
  IOListItemLogoMargin,
6
5
  useIOExperimentalDesign,
7
6
  useIOTheme
8
7
  } from "../../core";
8
+ import { toAndroidCacheTimestamp } from "../../utils/dates";
9
9
  import { IOText } from "../typography";
10
10
  import {
11
11
  PressableModuleBase,
@@ -28,6 +28,12 @@ const styles = StyleSheet.create({
28
28
  }
29
29
  });
30
30
 
31
+ // https://github.com/facebook/react-native/issues/12606
32
+ // Image cache forced refresh for Android by appending
33
+ // the `ts` query parameter as DDMMYYYY to simulate a 24h TTL.
34
+ const androidIdpLogoForcedRefreshed = () =>
35
+ Platform.OS === "android" ? `?ts=${toAndroidCacheTimestamp()}` : "";
36
+
31
37
  export const ModuleIDP = ({
32
38
  name,
33
39
  localLogo,
@@ -41,8 +47,11 @@ export const ModuleIDP = ({
41
47
  const { isExperimental } = useIOExperimentalDesign();
42
48
 
43
49
  // eslint-disable-next-line no-console
44
- const urlLogoIDP = localLogo ? localLogo : addCacheTimestampToUri(logo);
45
-
50
+ const urlLogoIDP = localLogo
51
+ ? localLogo
52
+ : {
53
+ uri: `${logo}${androidIdpLogoForcedRefreshed()}`
54
+ };
46
55
  return (
47
56
  <PressableModuleBase
48
57
  onPress={onPress}
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import { GestureResponderEvent, StyleSheet, View } from "react-native";
3
3
  import Placeholder from "rn-placeholder";
4
4
  import { IOListItemVisualParams, IOSpacer, useIOTheme } from "../../core";
5
+ import { getAccessibleAmountText } from "../../utils/accessibility";
5
6
  import { WithTestID } from "../../utils/types";
6
7
  import { Badge } from "../badge";
7
8
  import { Icon } from "../icons";
@@ -28,19 +29,13 @@ export type ModulePaymentNoticeProps = WithTestID<
28
29
  onPress: (event: GestureResponderEvent) => void;
29
30
  } & (
30
31
  | {
31
- paymentNotice: {
32
- status: Extract<PaymentNoticeStatus, "default">;
33
- amount: string;
34
- amountAccessibilityLabel: string;
35
- };
32
+ paymentNoticeStatus: Extract<PaymentNoticeStatus, "default">;
33
+ paymentNoticeAmount: string;
36
34
  badgeText?: never;
37
35
  }
38
36
  | {
39
- paymentNotice: {
40
- status: Exclude<PaymentNoticeStatus, "default">;
41
- amount?: string;
42
- amountAccessibilityLabel?: string;
43
- };
37
+ paymentNoticeStatus: Exclude<PaymentNoticeStatus, "default">;
38
+ paymentNoticeAmount?: never;
44
39
  badgeText: string;
45
40
  }
46
41
  )
@@ -57,21 +52,22 @@ const styles = StyleSheet.create({
57
52
  const ModulePaymentNoticeContent = ({
58
53
  title,
59
54
  subtitle,
60
- paymentNotice: { status, amount, amountAccessibilityLabel },
55
+ paymentNoticeStatus,
56
+ paymentNoticeAmount,
61
57
  badgeText = ""
62
58
  }: Omit<ModulePaymentNoticeProps, "isLoading" | "onPress" | "testID">) => {
63
59
  const theme = useIOTheme();
64
60
 
65
61
  const AmountOrBadgeComponent = () => {
66
- switch (status) {
62
+ switch (paymentNoticeStatus) {
67
63
  case "default":
68
64
  return (
69
65
  <H6
70
- accessibilityLabel={amountAccessibilityLabel}
66
+ accessibilityLabel={getAccessibleAmountText(paymentNoticeAmount)}
71
67
  color={theme["interactiveElem-default"]}
72
68
  numberOfLines={1}
73
69
  >
74
- {amount}
70
+ {paymentNoticeAmount}
75
71
  </H6>
76
72
  );
77
73
  case "paid":
@@ -1,3 +1,5 @@
1
+ import * as O from "fp-ts/lib/Option";
2
+ import { pipe } from "fp-ts/lib/function";
1
3
  import React from "react";
2
4
  import { Platform, StyleSheet, View } from "react-native";
3
5
  import {
@@ -146,16 +148,23 @@ export const Tag = ({
146
148
 
147
149
  return (
148
150
  <View testID={testID} style={styles.tag}>
149
- {variantProps && (
150
- <View style={styles.iconWrapper}>
151
- <Icon
152
- name={variantProps.iconName}
153
- color={variantProps.iconColor}
154
- size={IOTagIconSize}
155
- accessible={!!iconAccessibilityLabel}
156
- accessibilityLabel={iconAccessibilityLabel}
157
- />
158
- </View>
151
+ {pipe(
152
+ variantProps,
153
+ O.fromNullable,
154
+ O.fold(
155
+ () => null,
156
+ ({ iconColor, iconName }) => (
157
+ <View style={styles.iconWrapper}>
158
+ <Icon
159
+ name={iconName}
160
+ color={iconColor}
161
+ size={IOTagIconSize}
162
+ accessible={!!iconAccessibilityLabel}
163
+ accessibilityLabel={iconAccessibilityLabel}
164
+ />
165
+ </View>
166
+ )
167
+ )
159
168
  )}
160
169
  {variantProps && text && <View style={styles.spacer} />}
161
170
  {text && (
@@ -1,3 +1,4 @@
1
+ import { constVoid } from "fp-ts/function";
1
2
  import React from "react";
2
3
 
3
4
  type IOExperimentalContextType = {
@@ -10,7 +11,7 @@ type IOExperimentalContextType = {
10
11
  export const IOExperimentalDesignContext =
11
12
  React.createContext<IOExperimentalContextType>({
12
13
  isExperimental: false,
13
- setExperimental: () => void 0
14
+ setExperimental: constVoid
14
15
  });
15
16
 
16
17
  export const useIOExperimentalDesign = () =>
@@ -1,3 +1,4 @@
1
+ import { constVoid } from "fp-ts/function";
1
2
  import React, { useMemo } from "react";
2
3
  import { Appearance } from "react-native";
3
4
  import {
@@ -23,7 +24,7 @@ export const IOThemeContext: React.Context<IOThemeContextType> =
23
24
  themeType: Appearance.getColorScheme() === "dark" ? "dark" : "light",
24
25
  theme:
25
26
  Appearance.getColorScheme() === "dark" ? IOThemes.dark : IOThemes.light,
26
- setTheme: () => void 0
27
+ setTheme: constVoid
27
28
  });
28
29
 
29
30
  export const useIOThemeContext = () => React.useContext(IOThemeContext);
@@ -1,6 +1,23 @@
1
+ import { pipe } from "fp-ts/lib/function";
2
+ import * as O from "fp-ts/lib/Option";
3
+ import I18n from "i18n-js";
1
4
  import { useEffect, useState } from "react";
2
5
  import { AccessibilityInfo, Platform } from "react-native";
3
6
 
7
+ /**
8
+ * This function is used to get the text that will be read by the screen reader
9
+ * with the correct minus symbol pronunciation.
10
+ */
11
+ export const getAccessibleAmountText = (amount?: string) =>
12
+ pipe(
13
+ amount,
14
+ O.fromNullable,
15
+ O.map(amount =>
16
+ amount.replace("-", I18n.t("global.accessibility.minusSymbol"))
17
+ ),
18
+ O.getOrElseW(() => undefined)
19
+ );
20
+
4
21
  /**
5
22
  * Query whether a bold text is currently enabled. The result is true
6
23
  * when bold text is enabled and false otherwise.
@@ -0,0 +1,18 @@
1
+ import I18n from "i18n-js";
2
+
3
+ export const localeDateFormat = (date: Date, format: string): string =>
4
+ isNaN(date.getTime())
5
+ ? I18n.t("global.date.invalid")
6
+ : I18n.strftime(date, format);
7
+
8
+ /**
9
+ * Generates a locale formatted timestamp,
10
+ * used to force the refresh of the Image component cache for Android devices
11
+ * every 24 hours.
12
+ * @returns the actual locale date short format without slashes.
13
+ */
14
+ export const toAndroidCacheTimestamp = () =>
15
+ localeDateFormat(
16
+ new Date(),
17
+ I18n.t("global.dateFormats.shortFormat").replace(/\//g, "")
18
+ );
@@ -1,4 +1,9 @@
1
- import { ImageSourcePropType, Platform } from "react-native";
1
+ import { pipe } from "fp-ts/lib/function";
2
+ import { ImageURISource, Platform } from "react-native";
3
+ import * as B from "fp-ts/boolean";
4
+ import * as T from "io-ts";
5
+ import * as E from "fp-ts/Either";
6
+ import { toAndroidCacheTimestamp } from "./dates";
2
7
 
3
8
  /**
4
9
  * Adds a locale timestamp to the image URI to invalidate cache on the following day if the current platform is Android.
@@ -7,32 +12,27 @@ import { ImageSourcePropType, Platform } from "react-native";
7
12
  * @returns a new source with a modified URI which includes the actual timestamp in the locale format without slashes
8
13
  * if the platform is Android and the source contains an URI. The same source otherwise.
9
14
  */
10
- export const addCacheTimestampToUri = (source: ImageSourcePropType) => {
11
- // If the platform is not Android, return the source as is
12
- if (Platform.OS !== "android") {
13
- return source;
14
- }
15
+ export const addCacheTimestampToUri = (source: ImageURISource) => {
16
+ const UriSource = T.type({
17
+ uri: T.string
18
+ });
15
19
 
16
- // If the source is a number, it's a local image return as is
17
- if (typeof source === "number") {
18
- return source;
19
- }
20
-
21
- // This invalidates the cache on the following day
22
- const cacheBurstParam = new Date()
23
- .toISOString()
24
- .split("T")[0]
25
- .replace(/-/g, "");
26
-
27
- if (Array.isArray(source)) {
28
- return source.map(image =>
29
- image.uri
30
- ? { ...image, uri: `${image.uri}?ts=${cacheBurstParam}` }
31
- : image
32
- );
33
- } else {
34
- return source.uri
35
- ? { ...source, uri: `${source.uri}?ts=${cacheBurstParam}` }
36
- : source;
37
- }
20
+ return pipe(
21
+ Platform.OS === "android",
22
+ B.fold(
23
+ () => source,
24
+ () =>
25
+ pipe(
26
+ source,
27
+ UriSource.decode,
28
+ E.fold(
29
+ () => source,
30
+ () => ({
31
+ ...source,
32
+ uri: `${source.uri}?ts=${toAndroidCacheTimestamp()}`
33
+ })
34
+ )
35
+ )
36
+ )
37
+ );
38
38
  };
@@ -1,13 +1,12 @@
1
- // Function to find the first case-insensitive logo
2
- export const findFirstCaseInsensitive = <T>(
3
- obj: { [key: string]: T },
4
- key: string
5
- ) => {
6
- const lowerKey = key.toLowerCase();
7
- for (const [k] of Object.entries(obj)) {
8
- if (k.toLowerCase() === lowerKey) {
9
- return k;
10
- }
11
- }
12
- return null;
13
- };
1
+ import * as A from "fp-ts/Array";
2
+ import * as O from "fp-ts/Option";
3
+ import { pipe } from "fp-ts/lib/function";
4
+
5
+ export const findFirstCaseInsensitive =
6
+ <T>(obj: { [key: string]: T }) =>
7
+ (key: string): O.Option<[string, T]> =>
8
+ pipe(
9
+ obj,
10
+ Object.entries,
11
+ A.findFirst(([k, _]) => k.toLowerCase() === key.toLowerCase())
12
+ );