@kbach/react-native 1.0.0-beta.16 → 1.0.0-beta.17

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,35 @@
1
+ /**
2
+ * Cross-platform runtime-settable style values — the native counterpart to
3
+ * a real CSS custom property. Real `var(--sidebar-width)` already works on
4
+ * Expo Web today with ZERO code here (raw CSS text, resolved by the
5
+ * browser's own engine — see `jsxRuntimeCoreWeb.ts`, which does no
6
+ * className rewriting at all). Native has no such thing: RN's style system
7
+ * needs an actual JS-computed value at render time, so this module exists
8
+ * to give `var(--x)` an equivalent that works there too — same
9
+ * "module-level store is the real source of truth, no Context required"
10
+ * architecture as `darkModeStore.ts`, and `setDynamicToken` ALSO writes the
11
+ * real CSS custom property to the DOM when one exists (mirroring that
12
+ * file's own `applyToDom`), so a single call updates both platforms
13
+ * identically: instantly via native CSS reactivity on web, via this
14
+ * store's subscribers (see `jsxRuntimeCore.ts`'s `ReactiveElement`, which
15
+ * substitutes a registered token's current value into the className
16
+ * string before every resolve) on native.
17
+ *
18
+ * Deliberately ONE listener set shared by every token, not per-token
19
+ * subscriptions — same trade-off `darkModeStore.ts` already makes for
20
+ * "any relevant change re-renders every subscribed element" simplicity.
21
+ * `jsxRuntimeCore.ts`'s own key-suffix scoping (only the SPECIFIC token
22
+ * names a given className references) is what keeps an unrelated token
23
+ * change from forcing an unnecessary remount on an element that doesn't
24
+ * use it, same shape as the responsive-breakpoint scoping already does.
25
+ */
26
+ /** `name` is the bare token name, WITHOUT the leading `--` (matching how `getDynamicToken`/`var(--name)` substitution both address it). */
27
+ declare function setDynamicToken(name: string, value: string): void;
28
+ declare function getDynamicToken(name: string): string | undefined;
29
+ declare function deleteDynamicToken(name: string): void;
30
+ declare function subscribeDynamicTokens(listener: () => void): () => void;
31
+ declare function getDynamicTokensVersion(): number;
32
+ /** Exported for tests only — resets every module-level field back to its as-if-just-imported state. */
33
+ declare function _resetForTests(): void;
34
+
35
+ export { _resetForTests, deleteDynamicToken, getDynamicToken, getDynamicTokensVersion, setDynamicToken, subscribeDynamicTokens };
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/dynamicTokens.ts
21
+ var dynamicTokens_exports = {};
22
+ __export(dynamicTokens_exports, {
23
+ _resetForTests: () => _resetForTests,
24
+ deleteDynamicToken: () => deleteDynamicToken,
25
+ getDynamicToken: () => getDynamicToken,
26
+ getDynamicTokensVersion: () => getDynamicTokensVersion,
27
+ setDynamicToken: () => setDynamicToken,
28
+ subscribeDynamicTokens: () => subscribeDynamicTokens
29
+ });
30
+ module.exports = __toCommonJS(dynamicTokens_exports);
31
+ var tokens = /* @__PURE__ */ new Map();
32
+ var listeners = /* @__PURE__ */ new Set();
33
+ var version = 0;
34
+ function applyToDom(name, value) {
35
+ if (typeof document === "undefined") return;
36
+ document.documentElement.style.setProperty(`--${name}`, value);
37
+ }
38
+ function notify() {
39
+ version++;
40
+ for (const listener of listeners) listener();
41
+ }
42
+ function setDynamicToken(name, value) {
43
+ tokens.set(name, value);
44
+ applyToDom(name, value);
45
+ notify();
46
+ }
47
+ function getDynamicToken(name) {
48
+ return tokens.get(name);
49
+ }
50
+ function deleteDynamicToken(name) {
51
+ if (!tokens.delete(name)) return;
52
+ if (typeof document !== "undefined") document.documentElement.style.removeProperty(`--${name}`);
53
+ notify();
54
+ }
55
+ function subscribeDynamicTokens(listener) {
56
+ listeners.add(listener);
57
+ return () => listeners.delete(listener);
58
+ }
59
+ function getDynamicTokensVersion() {
60
+ return version;
61
+ }
62
+ function _resetForTests() {
63
+ tokens.clear();
64
+ listeners.clear();
65
+ version = 0;
66
+ }
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ _resetForTests,
70
+ deleteDynamicToken,
71
+ getDynamicToken,
72
+ getDynamicTokensVersion,
73
+ setDynamicToken,
74
+ subscribeDynamicTokens
75
+ });
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { ColorEntry, ContainerConfig, ThemeConfig, defaultTheme, getTheme, setTheme } from './theme';
2
- export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as applyKbachConfig, r as resolveKbachConfig, u as useColors, e as useTheme } from './useColors-Br8QhIlE.js';
2
+ export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as applyKbachConfig, r as resolveKbachConfig, u as useColors, e as useDynamicToken, f as useTheme } from './useDynamicToken-Dr15c88t.js';
3
3
  export { ThemeMode, getGlobalDarkMode, getGlobalThemeMode, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode } from './darkModeStore';
4
+ export { deleteDynamicToken, getDynamicToken, setDynamicToken, subscribeDynamicTokens } from './dynamicTokens';
4
5
  import 'react';
5
6
 
6
7
  type StyleValue = string | number | {
@@ -79,6 +80,19 @@ interface StyleObject {
79
80
  * exactly as before. The JS engine covers a strict subset of what the
80
81
  * native/WASM engines resolve (see jsEngine/resolveUtilityNative.ts) — an
81
82
  * intentional, documented parity gap, not a bug.
83
+ *
84
+ * An arbitrary value that couldn't be resolved to something RN's style
85
+ * system can actually use (an unreduced percentage/viewport-relative
86
+ * `calc()`, a raw `var(...)`, ...) is dropped from the returned style —
87
+ * never passed through as an invalid string — and surfaced via
88
+ * `console.warn` here (dev builds only, mirroring RN's own convention for
89
+ * this kind of non-fatal correctness warning) rather than failing
90
+ * silently. The native/JNI path embeds these under a `"__kbachWarnings"`
91
+ * key in the same JSON `resolve_style_json` already returns (see that
92
+ * function's own doc comment for why — reusing the one JSON round-trip
93
+ * rather than adding a second FFI call); the jsEngine fallback returns
94
+ * them directly via `resolveStyleJsWithWarnings`. Both paths converge here
95
+ * so call sites never need to know which engine actually resolved anything.
82
96
  */
83
97
  declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
84
98
 
package/dist/index.js CHANGED
@@ -23,16 +23,21 @@ __export(src_exports, {
23
23
  ThemeProvider: () => ThemeProvider,
24
24
  applyKbachConfig: () => applyKbachConfig,
25
25
  defaultTheme: () => import_theme4.defaultTheme,
26
+ deleteDynamicToken: () => import_dynamicTokens2.deleteDynamicToken,
27
+ getDynamicToken: () => import_dynamicTokens2.getDynamicToken,
26
28
  getGlobalDarkMode: () => import_darkModeStore4.getGlobalDarkMode,
27
29
  getGlobalThemeMode: () => import_darkModeStore4.getGlobalThemeMode,
28
30
  getTheme: () => import_theme4.getTheme,
29
31
  resolveKbachConfig: () => resolveKbachConfig,
30
32
  resolveStyle: () => resolveStyle,
33
+ setDynamicToken: () => import_dynamicTokens2.setDynamicToken,
31
34
  setGlobalThemeMode: () => import_darkModeStore4.setGlobalThemeMode,
32
35
  setTheme: () => import_theme4.setTheme,
36
+ subscribeDynamicTokens: () => import_dynamicTokens2.subscribeDynamicTokens,
33
37
  subscribeGlobalDarkMode: () => import_darkModeStore4.subscribeGlobalDarkMode,
34
38
  toggleGlobalDarkMode: () => import_darkModeStore4.toggleGlobalDarkMode,
35
39
  useColors: () => useColors,
40
+ useDynamicToken: () => useDynamicToken,
36
41
  useTheme: () => useTheme
37
42
  });
38
43
  module.exports = __toCommonJS(src_exports);
@@ -1139,6 +1144,153 @@ function resolveUtilityNative(parsed, theme) {
1139
1144
  return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed) ?? nativeShadowDeclarations(parsed);
1140
1145
  }
1141
1146
 
1147
+ // src/jsEngine/calc.ts
1148
+ function reduceConstantMath(raw) {
1149
+ const trimmed = raw.trim();
1150
+ const calc = stripCall(trimmed, "calc");
1151
+ if (calc !== null) {
1152
+ return new Evaluator(calc).parseExprToEnd();
1153
+ }
1154
+ const clamp = stripCall(trimmed, "clamp");
1155
+ if (clamp !== null) {
1156
+ const args = splitTopLevelCommas(clamp);
1157
+ if (args.length !== 3) return null;
1158
+ const [min2, preferred, max2] = args.map((a) => new Evaluator(a).parseExprToEnd());
1159
+ if (min2 === null || preferred === null || max2 === null) return null;
1160
+ return Math.max(min2, Math.min(preferred, max2));
1161
+ }
1162
+ const min = stripCall(trimmed, "min");
1163
+ if (min !== null) {
1164
+ const values = splitTopLevelCommas(min).map((a) => new Evaluator(a).parseExprToEnd());
1165
+ return values.some((v) => v === null) ? null : Math.min(...values);
1166
+ }
1167
+ const max = stripCall(trimmed, "max");
1168
+ if (max !== null) {
1169
+ const values = splitTopLevelCommas(max).map((a) => new Evaluator(a).parseExprToEnd());
1170
+ return values.some((v) => v === null) ? null : Math.max(...values);
1171
+ }
1172
+ return null;
1173
+ }
1174
+ function stripCall(raw, name) {
1175
+ if (!raw.startsWith(name)) return null;
1176
+ const afterName = raw.slice(name.length).trimStart();
1177
+ if (!afterName.startsWith("(") || !afterName.endsWith(")")) return null;
1178
+ return afterName.slice(1, -1);
1179
+ }
1180
+ function splitTopLevelCommas(s) {
1181
+ const parts = [];
1182
+ let depth = 0;
1183
+ let start = 0;
1184
+ for (let i = 0; i < s.length; i++) {
1185
+ const ch = s[i];
1186
+ if (ch === "(") depth++;
1187
+ else if (ch === ")") depth--;
1188
+ else if (ch === "," && depth === 0) {
1189
+ parts.push(s.slice(start, i).trim());
1190
+ start = i + 1;
1191
+ }
1192
+ }
1193
+ parts.push(s.slice(start).trim());
1194
+ return parts;
1195
+ }
1196
+ var Evaluator = class {
1197
+ constructor(s) {
1198
+ this.pos = 0;
1199
+ this.s = s;
1200
+ }
1201
+ parseExprToEnd() {
1202
+ const value = this.expr();
1203
+ if (value === null) return null;
1204
+ this.skipWs();
1205
+ return this.pos === this.s.length ? value : null;
1206
+ }
1207
+ skipWs() {
1208
+ while (this.pos < this.s.length && /\s/.test(this.s[this.pos])) this.pos++;
1209
+ }
1210
+ peek() {
1211
+ this.skipWs();
1212
+ return this.s[this.pos];
1213
+ }
1214
+ expr() {
1215
+ let value = this.term();
1216
+ if (value === null) return null;
1217
+ for (; ; ) {
1218
+ const ch = this.peek();
1219
+ if (ch === "+") {
1220
+ this.pos++;
1221
+ const rhs = this.term();
1222
+ if (rhs === null) return null;
1223
+ value += rhs;
1224
+ } else if (ch === "-") {
1225
+ this.pos++;
1226
+ const rhs = this.term();
1227
+ if (rhs === null) return null;
1228
+ value -= rhs;
1229
+ } else {
1230
+ return value;
1231
+ }
1232
+ }
1233
+ }
1234
+ term() {
1235
+ let value = this.factor();
1236
+ if (value === null) return null;
1237
+ for (; ; ) {
1238
+ const ch = this.peek();
1239
+ if (ch === "*") {
1240
+ this.pos++;
1241
+ const rhs = this.factor();
1242
+ if (rhs === null) return null;
1243
+ value *= rhs;
1244
+ } else if (ch === "/") {
1245
+ this.pos++;
1246
+ const divisor = this.factor();
1247
+ if (divisor === null || divisor === 0) return null;
1248
+ value /= divisor;
1249
+ } else {
1250
+ return value;
1251
+ }
1252
+ }
1253
+ }
1254
+ factor() {
1255
+ const ch = this.peek();
1256
+ if (ch === void 0) return null;
1257
+ if (ch === "(") {
1258
+ this.pos++;
1259
+ const value = this.expr();
1260
+ if (value === null || this.peek() !== ")") return null;
1261
+ this.pos++;
1262
+ return value;
1263
+ }
1264
+ if (ch === "-") {
1265
+ this.pos++;
1266
+ const value = this.factor();
1267
+ return value === null ? null : -value;
1268
+ }
1269
+ return this.numberWithUnit();
1270
+ }
1271
+ numberWithUnit() {
1272
+ this.skipWs();
1273
+ const start = this.pos;
1274
+ while (this.pos < this.s.length && (/[0-9]/.test(this.s[this.pos]) || this.s[this.pos] === ".")) this.pos++;
1275
+ if (this.pos === start) return null;
1276
+ const n = Number(this.s.slice(start, this.pos));
1277
+ if (!Number.isFinite(n)) return null;
1278
+ const unitStart = this.pos;
1279
+ while (this.pos < this.s.length && /[a-zA-Z]/.test(this.s[this.pos])) this.pos++;
1280
+ const unit = this.s.slice(unitStart, this.pos);
1281
+ switch (unit) {
1282
+ case "":
1283
+ return n;
1284
+ case "px":
1285
+ return n;
1286
+ case "rem":
1287
+ return n * 16;
1288
+ default:
1289
+ return null;
1290
+ }
1291
+ }
1292
+ };
1293
+
1142
1294
  // src/jsEngine/resolveStyle.ts
1143
1295
  var RESPONSIVE_BREAKPOINTS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1144
1296
  function nativeModifierState(modifier, theme, colorScheme, pressed, width) {
@@ -1208,25 +1360,41 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
1208
1360
  "outline-width",
1209
1361
  "outline-offset"
1210
1362
  ]);
1363
+ function isPlainPercentage(value) {
1364
+ if (!value.endsWith("%")) return false;
1365
+ let digits = value.slice(0, -1);
1366
+ if (digits.startsWith("-")) digits = digits.slice(1);
1367
+ return digits.length > 0 && /^[0-9.]+$/.test(digits);
1368
+ }
1211
1369
  function rnStyleValue(property, value) {
1212
- if (NUMERIC_LENGTH_PROPS.has(property)) {
1213
- let px = null;
1214
- if (value.endsWith("px")) {
1215
- const n = Number(value.slice(0, -2));
1216
- px = Number.isFinite(n) ? n : null;
1217
- } else if (value.endsWith("rem")) {
1218
- const n = Number(value.slice(0, -3));
1219
- px = Number.isFinite(n) ? n * 16 : null;
1220
- } else {
1221
- const n = Number(value);
1222
- px = value.trim() !== "" && Number.isFinite(n) ? n : null;
1223
- }
1224
- if (px !== null) return px;
1370
+ if (!NUMERIC_LENGTH_PROPS.has(property)) {
1371
+ return { value, warning: null };
1372
+ }
1373
+ let px = null;
1374
+ if (value.endsWith("px")) {
1375
+ const n = Number(value.slice(0, -2));
1376
+ px = Number.isFinite(n) ? n : null;
1377
+ } else if (value.endsWith("rem")) {
1378
+ const n = Number(value.slice(0, -3));
1379
+ px = Number.isFinite(n) ? n * 16 : null;
1380
+ } else {
1381
+ const n = Number(value);
1382
+ px = value.trim() !== "" && Number.isFinite(n) ? n : null;
1225
1383
  }
1226
- return value;
1384
+ if (px !== null) return { value: px, warning: null };
1385
+ if (isPlainPercentage(value)) {
1386
+ return { value, warning: null };
1387
+ }
1388
+ const reduced = reduceConstantMath(value);
1389
+ if (reduced !== null) {
1390
+ return { value: reduced, warning: null };
1391
+ }
1392
+ const warning = `Kbach: "${value}" is not a valid native value for "${property}" \u2014 dropped. calc()/clamp()/min()/max() only resolve on native when every operand is a constant px/rem length (no %, vw, vh, var(), or other viewport/CSS-variable units \u2014 those need real layout/DOM, which doesn't exist on native at paint time). Use a plain px/rem calc, a fraction utility (e.g. w-1/2), or resolve this value in JS instead.`;
1393
+ return { value: null, warning };
1227
1394
  }
1228
- function resolveStyleJs(classString, theme, colorScheme, pressed, width) {
1395
+ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, width) {
1229
1396
  const style = {};
1397
+ const warnings = [];
1230
1398
  let shadowOffset = null;
1231
1399
  for (const token of classString.split(/\s+/).filter(Boolean)) {
1232
1400
  const parsed = parseClass(token);
@@ -1247,13 +1415,15 @@ function resolveStyleJs(classString, theme, colorScheme, pressed, width) {
1247
1415
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1248
1416
  continue;
1249
1417
  }
1250
- style[kebabToCamel(d.property)] = rnStyleValue(d.property, d.value);
1418
+ const { value, warning } = rnStyleValue(d.property, d.value);
1419
+ if (warning !== null) warnings.push(warning);
1420
+ if (value !== null) style[kebabToCamel(d.property)] = value;
1251
1421
  }
1252
1422
  }
1253
1423
  if (shadowOffset !== null) {
1254
1424
  style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
1255
1425
  }
1256
- return style;
1426
+ return { style, warnings };
1257
1427
  }
1258
1428
 
1259
1429
  // src/nativeBridge.ts
@@ -1262,10 +1432,29 @@ function resolveStyle(classString, pressed = false) {
1262
1432
  const width = import_react_native.Dimensions.get("window").width;
1263
1433
  const KbachModule = import_react_native.TurboModuleRegistry.get("KbachModule");
1264
1434
  if (!KbachModule) {
1265
- return resolveStyleJs(classString, (0, import_theme.getTheme)(), colorScheme, pressed, width);
1435
+ const { style: style2, warnings: warnings2 } = resolveStyleJsWithWarnings(classString, (0, import_theme.getTheme)(), colorScheme, pressed, width);
1436
+ warnIfDev(classString, warnings2);
1437
+ return style2;
1266
1438
  }
1267
1439
  const json = KbachModule.resolveStyle(classString, (0, import_theme.getThemeJson)(), colorScheme, pressed, width);
1268
- return JSON.parse(json);
1440
+ const style = JSON.parse(json);
1441
+ const warnings = style.__kbachWarnings;
1442
+ if (warnings !== void 0) {
1443
+ delete style.__kbachWarnings;
1444
+ warnIfDev(classString, warnings);
1445
+ }
1446
+ return style;
1447
+ }
1448
+ var warnedMessages = /* @__PURE__ */ new Set();
1449
+ function warnIfDev(classString, warnings) {
1450
+ if (warnings.length === 0) return;
1451
+ if (typeof __DEV__ !== "undefined" && !__DEV__) return;
1452
+ for (const warning of warnings) {
1453
+ const message = `${warning} (from className "${classString}")`;
1454
+ if (warnedMessages.has(message)) continue;
1455
+ warnedMessages.add(message);
1456
+ console.warn(message);
1457
+ }
1269
1458
  }
1270
1459
 
1271
1460
  // src/config.ts
@@ -1439,20 +1628,35 @@ function useColors() {
1439
1628
  const { isDark } = useTheme();
1440
1629
  return makeColorsProxy((0, import_theme3.getTheme)(), isDark);
1441
1630
  }
1631
+
1632
+ // src/index.ts
1633
+ var import_dynamicTokens2 = require("./dynamicTokens");
1634
+
1635
+ // src/useDynamicToken.ts
1636
+ var import_react3 = require("react");
1637
+ var import_dynamicTokens = require("./dynamicTokens");
1638
+ function useDynamicToken(name) {
1639
+ return (0, import_react3.useSyncExternalStore)(import_dynamicTokens.subscribeDynamicTokens, () => (0, import_dynamicTokens.getDynamicToken)(name));
1640
+ }
1442
1641
  // Annotate the CommonJS export names for ESM import in node:
1443
1642
  0 && (module.exports = {
1444
1643
  ThemeProvider,
1445
1644
  applyKbachConfig,
1446
1645
  defaultTheme,
1646
+ deleteDynamicToken,
1647
+ getDynamicToken,
1447
1648
  getGlobalDarkMode,
1448
1649
  getGlobalThemeMode,
1449
1650
  getTheme,
1450
1651
  resolveKbachConfig,
1451
1652
  resolveStyle,
1653
+ setDynamicToken,
1452
1654
  setGlobalThemeMode,
1453
1655
  setTheme,
1656
+ subscribeDynamicTokens,
1454
1657
  subscribeGlobalDarkMode,
1455
1658
  toggleGlobalDarkMode,
1456
1659
  useColors,
1660
+ useDynamicToken,
1457
1661
  useTheme
1458
1662
  });
@@ -1,6 +1,7 @@
1
1
  export { ColorEntry, ContainerConfig, ThemeConfig, defaultTheme, getTheme, setTheme } from './theme';
2
- export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as applyKbachConfig, r as resolveKbachConfig, u as useColors, e as useTheme } from './useColors-Br8QhIlE.js';
2
+ export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as applyKbachConfig, r as resolveKbachConfig, u as useColors, e as useDynamicToken, f as useTheme } from './useDynamicToken-Dr15c88t.js';
3
3
  export { ThemeMode, getGlobalDarkMode, getGlobalThemeMode, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode } from './darkModeStore';
4
+ export { deleteDynamicToken, getDynamicToken, setDynamicToken, subscribeDynamicTokens } from './dynamicTokens';
4
5
  import 'react';
5
6
 
6
7
  /**