@colisweb/rescript-toolkit 2.45.0 → 2.46.3

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.
package/.gitlab-ci.yml CHANGED
@@ -35,6 +35,7 @@ publish rescript-toolkit:
35
35
  script:
36
36
  - export NPM_TOKEN=${NPM_TOKEN:-undefined}
37
37
  - npm --no-git-tag-version version $(echo $CI_COMMIT_TAG | sed -e 's/^v//')
38
+ - yarn
38
39
  - npm publish
39
40
  cache:
40
41
  key: "yarn-$CI_COMMIT_REF_SLUG"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ yarn intl:extract && yarn intl:check
package/locale/fr.json ADDED
@@ -0,0 +1,102 @@
1
+ [
2
+ {
3
+ "id": "_05a04636",
4
+ "defaultMessage": "Bin packing",
5
+ "message": "Bin packing"
6
+ },
7
+ {
8
+ "id": "_09d99cb1",
9
+ "defaultMessage": "Mardi",
10
+ "message": "Mardi"
11
+ },
12
+ {
13
+ "id": "_1484eaf0",
14
+ "defaultMessage": "Format requis : 14 chiffres",
15
+ "message": "Format requis : 14 chiffres"
16
+ },
17
+ {
18
+ "id": "_24a9ec30",
19
+ "defaultMessage": "Format requis : 4 caractères ou nombres (exemples : 'A1b2' 'abcd')",
20
+ "message": "Format requis : 4 caractères ou nombres (exemples : 'A1b2' 'abcd')"
21
+ },
22
+ {
23
+ "id": "_29102a96",
24
+ "defaultMessage": "Volume total",
25
+ "message": "Volume total"
26
+ },
27
+ {
28
+ "id": "_337be526",
29
+ "defaultMessage": "Jeudi",
30
+ "message": "Jeudi"
31
+ },
32
+ {
33
+ "id": "_49a79a00",
34
+ "defaultMessage": "Samedi",
35
+ "message": "Samedi"
36
+ },
37
+ {
38
+ "id": "_5689ac4d",
39
+ "defaultMessage": "Masquer le mot de passe",
40
+ "message": "Masquer le mot de passe"
41
+ },
42
+ {
43
+ "id": "_8f8eb0df",
44
+ "defaultMessage": "Vendredi",
45
+ "message": "Vendredi"
46
+ },
47
+ {
48
+ "id": "_90cfad83",
49
+ "defaultMessage": "Mercredi",
50
+ "message": "Mercredi"
51
+ },
52
+ {
53
+ "id": "_9fc912ee",
54
+ "defaultMessage": "Lundi",
55
+ "message": "Lundi"
56
+ },
57
+ {
58
+ "id": "_aaa4996e",
59
+ "defaultMessage": "Dimanche",
60
+ "message": "Dimanche"
61
+ },
62
+ {
63
+ "id": "_c05fffa7",
64
+ "defaultMessage": "Afficher le mot de passe",
65
+ "message": "Afficher le mot de passe"
66
+ },
67
+ {
68
+ "id": "_c98895d1",
69
+ "defaultMessage": "Doit etre un entier positif",
70
+ "message": "Doit etre un entier positif"
71
+ },
72
+ {
73
+ "id": "_d2c9771a",
74
+ "defaultMessage": "Mauvais format (req: {exemple})",
75
+ "message": "Mauvais format (req: {exemple})"
76
+ },
77
+ {
78
+ "id": "_d56f025d",
79
+ "defaultMessage": "Champs requis",
80
+ "message": "Champs requis"
81
+ },
82
+ {
83
+ "id": "_d7f4a16b",
84
+ "defaultMessage": "Ce nom est deja utilisé dans un autre forfait",
85
+ "message": "Ce nom est deja utilisé dans un autre forfait"
86
+ },
87
+ {
88
+ "id": "_dd093040",
89
+ "defaultMessage": "Doit être un entier positif ou un nombre a virgule",
90
+ "message": "Doit être un entier positif ou un nombre a virgule"
91
+ },
92
+ {
93
+ "id": "_e6b5a42a",
94
+ "defaultMessage": "Nbr colis",
95
+ "message": "Nbr colis"
96
+ },
97
+ {
98
+ "id": "clipboard.copied",
99
+ "defaultMessage": "Copied to clipboard",
100
+ "message": "Copied to clipboard"
101
+ }
102
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colisweb/rescript-toolkit",
3
- "version": "2.45.0",
3
+ "version": "2.46.3",
4
4
  "scripts": {
5
5
  "clean": "rescript clean",
6
6
  "build": "rescript build -with-deps",
@@ -9,7 +9,10 @@
9
9
  "build:reacticons": "node tools/extract-react-icons.js",
10
10
  "build:scss": "node tools/build-scss.js",
11
11
  "storybook": "STORYBOOK=true start-storybook -p 6006 --no-manager-cache",
12
- "build-storybook": "TAILWIND_MODE=build STORYBOOK=true build-storybook"
12
+ "build-storybook": "TAILWIND_MODE=build STORYBOOK=true build-storybook",
13
+ "intl:check": "node src/intl/check.js",
14
+ "intl:extract": "node src/intl/extract.js",
15
+ "prepare": "is-ci || husky install"
13
16
  },
14
17
  "keywords": [
15
18
  "ReScript",
@@ -21,6 +24,7 @@
21
24
  "author": "Colisweb",
22
25
  "license": "MIT",
23
26
  "dependencies": {
27
+ "@colisweb/bs-react-intl-extractor-bin": "0.12.2",
24
28
  "@colisweb/react-day-picker": "7.4.16",
25
29
  "@colisweb/restorative": "0.5.1",
26
30
  "@datadog/browser-rum": "4.14.0",
@@ -38,6 +42,7 @@
38
42
  "@reach/tooltip": "0.17.0",
39
43
  "@reach/visually-hidden": "0.17.0",
40
44
  "@rescript/react": "0.10.3",
45
+ "@tailwindcss/line-clamp": "0.4.0",
41
46
  "autoprefixer": "10.4.7",
42
47
  "axios": "0.24.0",
43
48
  "bs-axios": "0.0.43",
@@ -88,6 +93,8 @@
88
93
  "@storybook/theming": "6.1.21",
89
94
  "babel-loader": "8.2.5",
90
95
  "highlight.js": "11.3.1",
96
+ "husky": "8.0.0",
97
+ "is-ci": "3.0.1",
91
98
  "raw-loader": "4.0.2",
92
99
  "react-is": "17.0.2",
93
100
  "sass": "1.45.0"
package/src/Toolkit.res CHANGED
@@ -12,3 +12,5 @@ module Form = Toolkit__Form
12
12
  module FormValidationFunctions = Toolkit__FormValidationFunctions
13
13
  module BrowserLogger = Toolkit__BrowserLogger
14
14
  module NativeLogger = Toolkit__NativeLogger
15
+ module Intl = Toolkit__Intl
16
+ module LocalesHelpers = Toolkit__LocalesHelpers
@@ -166,12 +166,8 @@ module Make = (StateLenses: Config) => {
166
166
  | "password" =>
167
167
  <Toolkit__Ui_Tooltip
168
168
  label={showPassword
169
- ? <ReactIntl.FormattedMessage
170
- id="toolkit.hidePassword" defaultMessage="Hide password"
171
- />
172
- : <ReactIntl.FormattedMessage
173
- id="toolkit.showPassword" defaultMessage="Show password"
174
- />}>
169
+ ? <ReactIntl.FormattedMessage defaultMessage="Masquer le mot de passe" />
170
+ : <ReactIntl.FormattedMessage defaultMessage="Afficher le mot de passe" />}>
175
171
  <button
176
172
  type_="button"
177
173
  className="p-1 bg-neutral-300 rounded-r border border-gray-300 text-neutral-800"
@@ -2,20 +2,22 @@ open ReactIntl
2
2
 
3
3
  module Msg = {
4
4
  @@intl.messages
5
- let requiredValue = {defaultMessage: "required value"}
6
- let requiredPosInt = {defaultMessage: "must be a positive integer"}
7
- let requiredPosIntOrFloat = {defaultMessage: "must be a positive integer or float"}
8
- let wrongFormat = {defaultMessage: "Wrong format (req: {exemple})"}
9
- let maxNumberOfPackets = {defaultMessage: "Nb packets"}
10
- let totalVolume = {defaultMessage: "Total volume"}
5
+ let requiredValue = {defaultMessage: "Champs requis"}
6
+ let requiredPosInt = {defaultMessage: "Doit etre un entier positif"}
7
+ let requiredPosIntOrFloat = {
8
+ defaultMessage: "Doit être un entier positif ou un nombre a virgule",
9
+ }
10
+ let wrongFormat = {defaultMessage: "Mauvais format (req: {exemple})"}
11
+ let maxNumberOfPackets = {defaultMessage: "Nbr colis"}
12
+ let totalVolume = {defaultMessage: "Volume total"}
11
13
  let binPacking = {defaultMessage: "Bin packing"}
12
- let vehicleNameAlreadyUsed = {defaultMessage: "This name is already used in another flat rate"}
14
+ let vehicleNameAlreadyUsed = {defaultMessage: "Ce nom est deja utilisé dans un autre forfait"}
13
15
 
14
16
  let requiredAlphanumericMax4 = {
15
- defaultMessage: "required format : 4 characters or numbers (exemples: 'A1b2' 'abcd')",
17
+ defaultMessage: "Format requis : 4 caractères ou nombres (exemples : 'A1b2' 'abcd')",
16
18
  }
17
19
  let required14Digits = {
18
- defaultMessage: "required format : 14 digits",
20
+ defaultMessage: "Format requis : 14 chiffres",
19
21
  }
20
22
  }
21
23
 
@@ -0,0 +1,101 @@
1
+ open ReactIntl
2
+
3
+ type rec messages = {fr: array<translation>}
4
+ and translation = {
5
+ id: string,
6
+ defaultMessage: string,
7
+ message: Js.nullable<string>,
8
+ }
9
+
10
+ let toDict = (translations: array<translation>) =>
11
+ translations->Array.reduce(Js.Dict.empty(), (dict, entry) => {
12
+ dict->Js.Dict.set(
13
+ entry.id,
14
+ entry.message->Js.Nullable.toOption->Option.getWithDefault(entry.defaultMessage),
15
+ )
16
+ dict
17
+ })
18
+
19
+ type availableLanguages = [
20
+ | #fr
21
+ ]
22
+
23
+ let availableLanguagesToString = (availableLanguages: availableLanguages) =>
24
+ switch availableLanguages {
25
+ | #fr => "fr"
26
+ }
27
+
28
+ let availableLanguagesFromString = v =>
29
+ switch v {
30
+ | v if v->Js.String2.includes("fr") => #fr
31
+ | _ => #fr
32
+ }
33
+
34
+ let createIntl = (locale: availableLanguages, messages) => {
35
+ let cache = createIntlCache()
36
+
37
+ createIntl(
38
+ intlConfig(
39
+ ~locale=locale->availableLanguagesToString,
40
+ ~messages,
41
+ ~onError=message => Toolkit__BrowserLogger.error2("create intl error", message),
42
+ (),
43
+ ),
44
+ cache,
45
+ )
46
+ }
47
+
48
+ module type IntlConfig = {
49
+ let messages: messages
50
+ let defaultLocale: option<availableLanguages>
51
+ }
52
+
53
+ module Make = (Config: IntlConfig) => {
54
+ let browserLocale = Browser.Navigator.getBrowserLanguage()
55
+ let locale =
56
+ Config.defaultLocale->Option.getWithDefault(browserLocale->availableLanguagesFromString)
57
+
58
+ let messages = switch locale {
59
+ | #fr => Config.messages.fr->toDict
60
+ }
61
+ let intl = createIntl(locale, messages)
62
+
63
+ type state = {
64
+ locale: availableLanguages,
65
+ intl: Intl.t,
66
+ }
67
+
68
+ type action = SetLocale(availableLanguages)
69
+
70
+ let store = Restorative.createStore({locale: locale, intl: intl}, (_state, action) =>
71
+ switch action {
72
+ | SetLocale(locale) => {
73
+ locale: locale,
74
+ intl: {
75
+ let messages = switch locale {
76
+ | #fr => Config.messages.fr->toDict
77
+ }
78
+ createIntl(locale, messages)
79
+ },
80
+ }
81
+ }
82
+ )
83
+
84
+ let setCurrentLocale = (locale: availableLanguages) => store.dispatch(SetLocale(locale))
85
+
86
+ let useCurrentLocale = () => store.useStore().locale
87
+ let useIntl = () => store.useStore().intl
88
+
89
+ let getDateFnsLocale = locale =>
90
+ switch locale {
91
+ | #fr => BsDateFns.frLocale
92
+ }
93
+
94
+ module Provider = {
95
+ @react.component
96
+ let make = (~children) => {
97
+ let intl = useIntl()
98
+ <RawIntlProvider value=intl> children </RawIntlProvider>
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,33 @@
1
+ type availableLanguages = [
2
+ | #fr
3
+ ]
4
+ type rec messages = {fr: array<translation>}
5
+ and translation = {
6
+ id: string,
7
+ defaultMessage: string,
8
+ message: Js.nullable<string>,
9
+ }
10
+
11
+ let availableLanguagesToString: availableLanguages => string
12
+ let availableLanguagesFromString: string => availableLanguages
13
+
14
+ module type IntlConfig = {
15
+ let messages: messages
16
+ let defaultLocale: option<availableLanguages>
17
+ }
18
+
19
+ module Make: (Config: IntlConfig) =>
20
+ {
21
+ let browserLocale: string
22
+ let locale: availableLanguages
23
+ let intl: ReactIntl.Intl.t
24
+ let useIntl: unit => ReactIntl.Intl.t
25
+ let useCurrentLocale: unit => availableLanguages
26
+ let setCurrentLocale: availableLanguages => unit
27
+ let getDateFnsLocale: availableLanguages => BsDateFns.dateFnsLocale
28
+
29
+ module Provider: {
30
+ @react.component
31
+ let make: (~children: React.element) => React.element
32
+ }
33
+ }
@@ -0,0 +1,35 @@
1
+ open ReactIntl
2
+
3
+ module DatePicker = {
4
+ module Fr = {
5
+ let weekdaysShort = ["Di", "Lun", "Ma", "Me", "Je", "Ve", "Sa"]
6
+
7
+ let months = [
8
+ j`Janvier`,
9
+ j`Février`,
10
+ j`Mars`,
11
+ j`Avril`,
12
+ j`Mai`,
13
+ j`Juin`,
14
+ j`Juillet`,
15
+ j`Août`,
16
+ j`Septembre`,
17
+ j`Octobre`,
18
+ j`Novembre`,
19
+ j`Décembre`,
20
+ ]
21
+
22
+ let firstDayOfWeek = 1
23
+ }
24
+ }
25
+
26
+ module Days = {
27
+ @@intl.messages
28
+ let monday = {defaultMessage: "Lundi"}
29
+ let tuesday = {defaultMessage: "Mardi"}
30
+ let wednesday = {defaultMessage: "Mercredi"}
31
+ let thursday = {defaultMessage: "Jeudi"}
32
+ let friday = {defaultMessage: "Vendredi"}
33
+ let saturday = {defaultMessage: "Samedi"}
34
+ let sunday = {defaultMessage: "Dimanche"}
35
+ }
package/src/intl/check.js CHANGED
@@ -1,25 +1,26 @@
1
- const fs = require('fs')
2
- const cp = require('child_process')
3
- const path = require('path')
1
+ const fs = require("fs");
2
+ const cp = require("child_process");
3
+ const path = require("path");
4
+ const cwd = process.cwd();
5
+ const translations = path.join(cwd, "locale");
4
6
 
5
- const cwd = process.cwd()
6
- const translations = path.join(cwd, 'locale', 'translations')
7
+ const AVAILABLE_LANGUAGES = ["fr"];
7
8
 
8
- const [enMissing, frMissing] = ['en', 'fr'].map((locale) => {
9
- const file = path.join(translations, `${locale}.json`)
10
- const content = JSON.parse(fs.readFileSync(file))
9
+ const missingMessagesLanguages = AVAILABLE_LANGUAGES.map((locale) => {
10
+ const file = path.join(translations, `${locale}.json`);
11
+ const content = JSON.parse(fs.readFileSync(file));
11
12
 
12
- return content.filter((item) => item.message === '').length
13
- })
13
+ return [content.filter((item) => item.message === "").length, locale];
14
+ });
14
15
 
15
- if (enMissing > 0) {
16
- console.log(`=== ⚠️ [EN] There are ${enMissing} messages missing.`)
17
- }
18
-
19
- if (frMissing > 0) {
20
- console.log(`=== ⚠️ [FR] There are ${frMissing} messages missing.`)
21
- }
16
+ missingMessagesLanguages.forEach(([missingMessagesLanguage, language]) => {
17
+ if (missingMessagesLanguage > 0) {
18
+ console.log(
19
+ `=== ⚠️ [${language}] There are ${missingMessagesLanguage} messages missing.`
20
+ );
21
+ }
22
+ });
22
23
 
23
- if (enMissing || frMissing) {
24
- process.exit(1)
24
+ if (missingMessagesLanguages.some(([missingMessages]) => missingMessages > 0)) {
25
+ process.exit(1);
25
26
  }
@@ -0,0 +1,37 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const glob = require("glob");
4
+
5
+ const cwd = process.cwd();
6
+ const originFile = path.join(cwd, "locale/translations/fr.json");
7
+ const base = JSON.parse(fs.readFileSync(originFile, "utf8"));
8
+ const dict = base.reduce((acc, value) => {
9
+ acc[value.defaultMessage] = value.message;
10
+ return acc;
11
+ }, {});
12
+ /**
13
+ * Match :
14
+ * - defaultMessage=""
15
+ * - defaultMessage={""}
16
+ * - defaultMessage:
17
+ */
18
+ const pattern = /(defaultMessage(=|:|={|:\s))"(.*?|$)"/g;
19
+
20
+ glob("**/*.res", (err, files) => {
21
+ files.forEach((file) => {
22
+ const filePath = path.join(cwd, file);
23
+ let fileContent = fs.readFileSync(filePath, "utf-8");
24
+ const matches = fileContent.matchAll(pattern);
25
+
26
+ for (const match of matches) {
27
+ let [, , , key] = match;
28
+ let message = dict[key];
29
+
30
+ if (message) {
31
+ fileContent = fileContent.replace(`"${key}"`, `"${message}"`);
32
+ }
33
+ }
34
+
35
+ fs.writeFileSync(filePath, fileContent);
36
+ });
37
+ });
@@ -1,33 +1,44 @@
1
1
  const fs = require("fs");
2
2
  const cp = require("child_process");
3
3
  const path = require("path");
4
-
5
- const locales = ["en", "fr"];
6
-
7
4
  const cwd = process.cwd();
8
5
  const src = path.join(cwd, "src");
9
- const locale = path.join(cwd, "locale");
10
- const toolkit = path.join(cwd, "node_modules/@colisweb/rescript-toolkit/src");
6
+ const localeFolderPath = path.join(cwd, "locale");
7
+
8
+ const AVAILABLE_LANGUAGES = ["fr"];
9
+ const DEFAULT_LANGUAGE = "fr";
10
+
11
+ if (!fs.existsSync(localeFolderPath)) {
12
+ fs.mkdirSync(localeFolderPath);
13
+ }
14
+
11
15
  const bin = path.join(cwd, "node_modules", ".bin", "bs-react-intl-extractor");
12
16
 
13
17
  console.log("=== ⏳ Extracting messages...");
14
18
  const extracted = JSON.parse(
15
- cp.execSync(`${bin} --allow-duplicates ${src} ${locale} ${toolkit}`)
19
+ cp.execSync(`${bin} --allow-duplicates ${src} ${localeFolderPath}`)
16
20
  );
17
21
  console.log("=== ✅ Extracting messages... done.");
18
22
 
19
- for (const locale of locales) {
20
- console.log(`\n=== ⏳ Updating ${locale} translation...`);
21
- const file = path.join(src, "../locale/translations", `${locale}.json`);
23
+ for (const LANGUAGE of AVAILABLE_LANGUAGES) {
24
+ console.log(`\n=== ⏳ Updating ${LANGUAGE} translation...`);
25
+ const file = path.join(src, "../locale", `${LANGUAGE}.json`);
22
26
 
23
27
  let content;
24
28
  try {
25
- content = JSON.parse(fs.readFileSync(file));
29
+ if (fs.existsSync(file)) {
30
+ content = JSON.parse(fs.readFileSync(file));
31
+ } else {
32
+ console.log(
33
+ `=== ⚠️ Translation for ${LANGUAGE} wasn't found. Creating new one.`
34
+ );
35
+ content = [];
36
+ }
26
37
  } catch (error) {
27
38
  console.log(error.code);
28
39
  if (error.code === "ENOENT") {
29
40
  console.log(
30
- `=== ⚠️ Translation for ${locale} wasn't found. Creating new one.`
41
+ `=== ⚠️ Translation for ${LANGUAGE} wasn't found. Creating new one.`
31
42
  );
32
43
  content = [];
33
44
  } else {
@@ -42,11 +53,11 @@ for (const locale of locales) {
42
53
  message:
43
54
  cache[msg.id] && cache[msg.id].message
44
55
  ? cache[msg.id].message
45
- : locale === "en"
56
+ : LANGUAGE === DEFAULT_LANGUAGE
46
57
  ? msg.defaultMessage
47
58
  : "",
48
59
  }));
49
60
 
50
61
  fs.writeFileSync(file, JSON.stringify(messages, null, 2) + "\n");
51
- console.log(`=== ✅ Updating ${locale} translation... done.`);
62
+ console.log(`=== ✅ Updating ${LANGUAGE} translation... done.`);
52
63
  }
@@ -11,7 +11,8 @@ module.exports = {
11
11
  },
12
12
  boxShadow: {
13
13
  sidenav: "inset 0 0 1px 2px rgba(0, 0, 0, 0.2)",
14
- sm: "0px 2px 1px -1px rgba(0,0,0,0.1), 0px 1px 1px 0px rgba(0,0,0,0.1), 0px 1px 3px 0px rgba(0,0,0,0.1)",
14
+ sm:
15
+ "0px 2px 1px -1px rgba(0,0,0,0.1), 0px 1px 1px 0px rgba(0,0,0,0.1), 0px 1px 3px 0px rgba(0,0,0,0.1)",
15
16
  },
16
17
  borderColor: {
17
18
  currentColor: "currentColor",
@@ -149,6 +150,7 @@ module.exports = {
149
150
  },
150
151
  },
151
152
  },
153
+ plugins: [require("@tailwindcss/line-clamp")],
152
154
  content: [
153
155
  "public/index.html",
154
156
  "lib/es6_global/src/**/**.js",
@@ -18,18 +18,20 @@ let make = (
18
18
  ~buttonVariant: Toolkit__Ui_Button.variant=#default,
19
19
  ~position=#bottom,
20
20
  ) => {
21
- let ref = React.useRef(Js.Nullable.null)
21
+ let dropDownRef = React.useRef(Js.Nullable.null)
22
+ let buttonRef = React.useRef(Js.Nullable.null)
22
23
  let (position, setPosition) = React.useState(() => position)
23
24
  let {isOpen, hide, toggle} = Toolkit__Hooks.useDisclosure(~defaultIsOpen, ())
24
25
 
25
- Toolkit__Hooks.useOnClickOutside(ref, _ => {
26
+ Toolkit__Hooks.useOnClickOutside(buttonRef, _ => {
26
27
  hide()
27
28
  })
29
+
28
30
  let (adjustmentStyle, setAdjustmentStyle) = React.useState(() => None)
29
31
 
30
32
  React.useEffect2(() => {
31
33
  if isOpen {
32
- ref.current
34
+ dropDownRef.current
33
35
  ->Js.Nullable.toOption
34
36
  ->Option.forEach(dom => {
35
37
  let {left, right} = dom->Browser.DomElement.getBoundingClientRect
@@ -52,7 +54,7 @@ let make = (
52
54
  setAdjustmentStyle(_ => None)
53
55
  }
54
56
  None
55
- }, (isOpen, ref))
57
+ }, (isOpen, dropDownRef))
56
58
 
57
59
  <div className={cx(["relative", containerClassName])}>
58
60
  <Toolkit__Ui_Button
@@ -60,11 +62,12 @@ let make = (
60
62
  size=buttonSize
61
63
  type_="button"
62
64
  color=buttonColor
65
+ buttonRef={ReactDOM.Ref.domRef(buttonRef)}
63
66
  onClick={event => {
64
- switch ref.current->Js.Nullable.toOption {
67
+ switch dropDownRef.current->Js.Nullable.toOption {
65
68
  | None => toggle()
66
- | Some(domRef) => {
67
- let isInDropdown = ReactEvent.Mouse.currentTarget(event)["contains"](. domRef)
69
+ | Some(dropDownRef) => {
70
+ let isInDropdown = ReactEvent.Mouse.currentTarget(event)["contains"](. dropDownRef)
68
71
  if !isInDropdown {
69
72
  toggle()
70
73
  }
@@ -75,7 +78,7 @@ let make = (
75
78
  label
76
79
  {isOpen
77
80
  ? <div
78
- ref={ReactDOM.Ref.domRef(ref)}
81
+ ref={ReactDOM.Ref.domRef(dropDownRef)}
79
82
  className={cx([
80
83
  "dropdown",
81
84
  "absolute z-20 bg-white p-2 transform shadow rounded text-base font-normal text-neutral-700 opacity-0",
@@ -45,6 +45,7 @@ module Core = {
45
45
  ~className="",
46
46
  ~emptyMessage,
47
47
  ~additionalRow: option<(ReactTable.instanceRow<'a>, 'a) => React.element>=?,
48
+ ~trClassName="",
48
49
  ) =>
49
50
  <div className={cx([className, "flex flex-auto w-full overflow-x-auto relative"])}>
50
51
  <Toolkit__Ui_Spread props={table.getTableProps()}>
@@ -98,7 +99,7 @@ module Core = {
98
99
 
99
100
  <React.Fragment key={"tr" ++ index->Int.toString}>
100
101
  <Toolkit__Ui_Spread props={row.getRowProps()}>
101
- <tr className={"block even:bg-gray-200 min-h-[5.5rem]"}>
102
+ <tr className={cx([trClassName, "block even:bg-gray-200 min-h-[5.5rem]"])}>
102
103
  {row.cells
103
104
  ->Array.mapWithIndex((index, cell) =>
104
105
  <Toolkit__Ui_Spread
@@ -10,6 +10,7 @@ module Core: {
10
10
  ~className: string=?,
11
11
  ~emptyMessage: string,
12
12
  ~additionalRow: (ReactTable.instanceRow<'a>, 'a) => React.element=?,
13
+ ~trClassName: string=?,
13
14
  ) => React.element
14
15
  }
15
16
 
@@ -56,3 +56,19 @@ external screenWidth: int = "window.screen.width"
56
56
 
57
57
  @val
58
58
  external screenHeight: int = "window.screen.height"
59
+
60
+ module Navigator = {
61
+ type t
62
+
63
+ @val
64
+ external userLanguage: option<string> = "window.navigator.userLanguage"
65
+
66
+ @val external language: option<string> = "window.navigator.language"
67
+
68
+ let getBrowserLanguage = () =>
69
+ switch (userLanguage, language) {
70
+ | (Some(l), _) => l
71
+ | (_, Some(l)) => l
72
+ | (None, None) => "fr"
73
+ }
74
+ }
@@ -36,3 +36,14 @@ module DomElement: {
36
36
  }
37
37
 
38
38
  let innerWidth: int
39
+
40
+ module Navigator: {
41
+ type t
42
+
43
+ @val
44
+ external userLanguage: option<string> = "window.navigator.userLanguage"
45
+
46
+ @val external language: option<string> = "window.navigator.language"
47
+
48
+ let getBrowserLanguage: unit => string
49
+ }