@onlynative/components 0.1.0 → 0.1.1-alpha.1
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/dist/appbar/index.js +133 -62
- package/dist/button/index.js +125 -33
- package/dist/card/index.js +88 -20
- package/dist/checkbox/index.js +88 -17
- package/dist/chip/index.js +122 -30
- package/dist/icon-button/index.js +107 -36
- package/dist/index.js +335 -251
- package/dist/list/index.js +71 -24
- package/dist/radio/index.js +43 -14
- package/dist/switch/index.js +90 -19
- package/dist/text-field/index.js +82 -26
- package/package.json +4 -23
- package/src/appbar/AppBar.tsx +0 -302
- package/src/appbar/index.ts +0 -2
- package/src/appbar/styles.ts +0 -92
- package/src/appbar/types.ts +0 -67
- package/src/button/Button.tsx +0 -133
- package/src/button/index.ts +0 -2
- package/src/button/styles.ts +0 -287
- package/src/button/types.ts +0 -42
- package/src/card/Card.tsx +0 -69
- package/src/card/index.ts +0 -2
- package/src/card/styles.ts +0 -150
- package/src/card/types.ts +0 -27
- package/src/checkbox/Checkbox.tsx +0 -113
- package/src/checkbox/index.ts +0 -2
- package/src/checkbox/styles.ts +0 -155
- package/src/checkbox/types.ts +0 -20
- package/src/chip/Chip.tsx +0 -188
- package/src/chip/index.ts +0 -2
- package/src/chip/styles.ts +0 -239
- package/src/chip/types.ts +0 -58
- package/src/icon-button/IconButton.tsx +0 -362
- package/src/icon-button/index.ts +0 -6
- package/src/icon-button/styles.ts +0 -259
- package/src/icon-button/types.ts +0 -55
- package/src/index.ts +0 -54
- package/src/keyboard-avoiding-wrapper/KeyboardAvoidingWrapper.tsx +0 -69
- package/src/keyboard-avoiding-wrapper/index.ts +0 -2
- package/src/keyboard-avoiding-wrapper/styles.ts +0 -10
- package/src/keyboard-avoiding-wrapper/types.ts +0 -37
- package/src/layout/Box.tsx +0 -99
- package/src/layout/Column.tsx +0 -16
- package/src/layout/Grid.tsx +0 -49
- package/src/layout/Layout.tsx +0 -81
- package/src/layout/Row.tsx +0 -22
- package/src/layout/index.ts +0 -13
- package/src/layout/resolveSpacing.ts +0 -11
- package/src/layout/types.ts +0 -82
- package/src/list/List.tsx +0 -17
- package/src/list/ListDivider.tsx +0 -20
- package/src/list/ListItem.tsx +0 -128
- package/src/list/index.ts +0 -9
- package/src/list/styles.ts +0 -132
- package/src/list/types.ts +0 -54
- package/src/radio/Radio.tsx +0 -103
- package/src/radio/index.ts +0 -2
- package/src/radio/styles.ts +0 -139
- package/src/radio/types.ts +0 -20
- package/src/switch/Switch.tsx +0 -121
- package/src/switch/index.ts +0 -2
- package/src/switch/styles.ts +0 -172
- package/src/switch/types.ts +0 -32
- package/src/text-field/TextField.tsx +0 -301
- package/src/text-field/index.ts +0 -2
- package/src/text-field/styles.ts +0 -239
- package/src/text-field/types.ts +0 -49
- package/src/typography/Typography.tsx +0 -79
- package/src/typography/index.ts +0 -3
- package/src/typography/types.ts +0 -17
package/dist/text-field/index.js
CHANGED
|
@@ -26,13 +26,69 @@ module.exports = __toCommonJS(text_field_exports);
|
|
|
26
26
|
|
|
27
27
|
// src/text-field/TextField.tsx
|
|
28
28
|
var import_react = require("react");
|
|
29
|
-
var
|
|
29
|
+
var import_react_native4 = require("react-native");
|
|
30
30
|
var import_core = require("@onlynative/core");
|
|
31
|
-
var import_utils2 = require("@onlynative/utils");
|
|
32
31
|
|
|
33
|
-
//
|
|
32
|
+
// ../utils/dist/chunk-OQRDRRQA.mjs
|
|
33
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
34
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
35
|
+
}) : x)(function(x) {
|
|
36
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
37
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ../utils/dist/index.mjs
|
|
34
41
|
var import_react_native = require("react-native");
|
|
35
|
-
var
|
|
42
|
+
var import_react_native2 = require("react-native");
|
|
43
|
+
function parseHexColor(color) {
|
|
44
|
+
const normalized = color.replace("#", "");
|
|
45
|
+
if (normalized.length !== 6 && normalized.length !== 8) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const r = Number.parseInt(normalized.slice(0, 2), 16);
|
|
49
|
+
const g = Number.parseInt(normalized.slice(2, 4), 16);
|
|
50
|
+
const b = Number.parseInt(normalized.slice(4, 6), 16);
|
|
51
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return { r, g, b };
|
|
55
|
+
}
|
|
56
|
+
function clampAlpha(alpha) {
|
|
57
|
+
return Math.max(0, Math.min(1, alpha));
|
|
58
|
+
}
|
|
59
|
+
function alphaColor(color, alpha) {
|
|
60
|
+
const channels = parseHexColor(color);
|
|
61
|
+
const boundedAlpha = clampAlpha(alpha);
|
|
62
|
+
if (!channels) {
|
|
63
|
+
return color;
|
|
64
|
+
}
|
|
65
|
+
return `rgba(${channels.r}, ${channels.g}, ${channels.b}, ${boundedAlpha})`;
|
|
66
|
+
}
|
|
67
|
+
var _MCIcons = null;
|
|
68
|
+
var _resolved = false;
|
|
69
|
+
function getMaterialCommunityIcons() {
|
|
70
|
+
if (!_resolved) {
|
|
71
|
+
_resolved = true;
|
|
72
|
+
try {
|
|
73
|
+
const mod = __require("@expo/vector-icons/MaterialCommunityIcons");
|
|
74
|
+
_MCIcons = mod.default || mod;
|
|
75
|
+
} catch {
|
|
76
|
+
_MCIcons = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!_MCIcons) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"@expo/vector-icons is required for icon support. Install it with: npx expo install @expo/vector-icons"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return _MCIcons;
|
|
85
|
+
}
|
|
86
|
+
function transformOrigin(vertical = "top") {
|
|
87
|
+
return import_react_native2.I18nManager.isRTL ? `right ${vertical}` : `left ${vertical}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/text-field/styles.ts
|
|
91
|
+
var import_react_native3 = require("react-native");
|
|
36
92
|
var CONTAINER_HEIGHT = 56;
|
|
37
93
|
var ICON_SIZE = 24;
|
|
38
94
|
var LABEL_FLOATED_LINE_HEIGHT = 16;
|
|
@@ -57,15 +113,15 @@ function getVariantColors(theme, variant) {
|
|
|
57
113
|
focusedLabelColor: theme.colors.primary,
|
|
58
114
|
errorLabelColor: theme.colors.error,
|
|
59
115
|
textColor: theme.colors.onSurface,
|
|
60
|
-
disabledTextColor:
|
|
61
|
-
disabledLabelColor:
|
|
62
|
-
disabledBorderColor:
|
|
116
|
+
disabledTextColor: alphaColor(theme.colors.onSurface, disabledOpacity),
|
|
117
|
+
disabledLabelColor: alphaColor(theme.colors.onSurface, disabledOpacity),
|
|
118
|
+
disabledBorderColor: alphaColor(theme.colors.onSurface, 0.12),
|
|
63
119
|
placeholderColor: theme.colors.onSurfaceVariant,
|
|
64
120
|
supportingTextColor: theme.colors.onSurfaceVariant,
|
|
65
121
|
errorSupportingTextColor: theme.colors.error,
|
|
66
122
|
iconColor: theme.colors.onSurfaceVariant,
|
|
67
123
|
errorIconColor: theme.colors.error,
|
|
68
|
-
disabledIconColor:
|
|
124
|
+
disabledIconColor: alphaColor(theme.colors.onSurface, disabledOpacity)
|
|
69
125
|
};
|
|
70
126
|
if (variant === "outlined") {
|
|
71
127
|
return {
|
|
@@ -80,7 +136,7 @@ function getVariantColors(theme, variant) {
|
|
|
80
136
|
...common,
|
|
81
137
|
backgroundColor: theme.colors.surfaceContainerHighest,
|
|
82
138
|
borderColor: theme.colors.onSurfaceVariant,
|
|
83
|
-
disabledBackgroundColor:
|
|
139
|
+
disabledBackgroundColor: alphaColor(theme.colors.onSurface, 0.04),
|
|
84
140
|
labelColor: theme.colors.onSurfaceVariant
|
|
85
141
|
};
|
|
86
142
|
}
|
|
@@ -91,7 +147,7 @@ function createStyles(theme, variant) {
|
|
|
91
147
|
const isFilled = variant === "filled";
|
|
92
148
|
return {
|
|
93
149
|
colors,
|
|
94
|
-
styles:
|
|
150
|
+
styles: import_react_native3.StyleSheet.create({
|
|
95
151
|
root: {
|
|
96
152
|
alignSelf: "stretch"
|
|
97
153
|
},
|
|
@@ -162,7 +218,7 @@ function createStyles(theme, variant) {
|
|
|
162
218
|
fontWeight: bodySmall.fontWeight,
|
|
163
219
|
letterSpacing: bodySmall.letterSpacing,
|
|
164
220
|
color: colors.labelColor,
|
|
165
|
-
transformOrigin:
|
|
221
|
+
transformOrigin: transformOrigin("top")
|
|
166
222
|
},
|
|
167
223
|
labelNotch: {
|
|
168
224
|
paddingHorizontal: 4
|
|
@@ -255,7 +311,7 @@ function TextField({
|
|
|
255
311
|
const isError = Boolean(error) || Boolean(errorText);
|
|
256
312
|
const isFilled = variant === "filled";
|
|
257
313
|
const hasLeadingIcon = Boolean(leadingIcon);
|
|
258
|
-
const MaterialCommunityIcons = leadingIcon || trailingIcon ?
|
|
314
|
+
const MaterialCommunityIcons = leadingIcon || trailingIcon ? getMaterialCommunityIcons() : null;
|
|
259
315
|
const { colors, styles } = (0, import_react.useMemo)(
|
|
260
316
|
() => createStyles(theme, variant),
|
|
261
317
|
[theme, variant]
|
|
@@ -268,13 +324,13 @@ function TextField({
|
|
|
268
324
|
const isControlled = value !== void 0;
|
|
269
325
|
const hasValue = isControlled ? value !== "" : internalHasText;
|
|
270
326
|
const isLabelFloated = isFocused || hasValue;
|
|
271
|
-
const labelAnimRef = (0, import_react.useRef)(new
|
|
327
|
+
const labelAnimRef = (0, import_react.useRef)(new import_react_native4.Animated.Value(isLabelFloated ? 1 : 0));
|
|
272
328
|
const labelAnim = labelAnimRef.current;
|
|
273
329
|
(0, import_react.useEffect)(() => {
|
|
274
|
-
|
|
330
|
+
import_react_native4.Animated.timing(labelAnim, {
|
|
275
331
|
toValue: isLabelFloated ? 1 : 0,
|
|
276
332
|
duration: 150,
|
|
277
|
-
useNativeDriver:
|
|
333
|
+
useNativeDriver: import_react_native4.Platform.OS !== "web"
|
|
278
334
|
}).start();
|
|
279
335
|
}, [isLabelFloated, labelAnim]);
|
|
280
336
|
const labelScale = (0, import_react.useMemo)(() => {
|
|
@@ -352,9 +408,9 @@ function TextField({
|
|
|
352
408
|
[styles, isFocused, isError, isDisabled]
|
|
353
409
|
);
|
|
354
410
|
const displaySupportingText = isError ? errorText : supportingText;
|
|
355
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
356
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
357
|
-
leadingIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native4.View, { style: [styles.root, style], children: [
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Pressable, { onPress: handleContainerPress, disabled: isDisabled, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native4.View, { style: containerStyle, children: [
|
|
413
|
+
leadingIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: styles.leadingIcon, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
358
414
|
MaterialCommunityIcons,
|
|
359
415
|
{
|
|
360
416
|
name: leadingIcon,
|
|
@@ -363,14 +419,14 @@ function TextField({
|
|
|
363
419
|
}
|
|
364
420
|
) }) : null,
|
|
365
421
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
366
|
-
|
|
422
|
+
import_react_native4.View,
|
|
367
423
|
{
|
|
368
424
|
style: [
|
|
369
425
|
styles.inputWrapper,
|
|
370
426
|
label ? styles.inputWrapperWithLabel : void 0
|
|
371
427
|
],
|
|
372
428
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
373
|
-
|
|
429
|
+
import_react_native4.TextInput,
|
|
374
430
|
{
|
|
375
431
|
ref: inputRef,
|
|
376
432
|
...textInputProps,
|
|
@@ -396,14 +452,14 @@ function TextField({
|
|
|
396
452
|
}
|
|
397
453
|
),
|
|
398
454
|
trailingIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
399
|
-
|
|
455
|
+
import_react_native4.Pressable,
|
|
400
456
|
{
|
|
401
457
|
onPress: onTrailingIconPress,
|
|
402
458
|
disabled: isDisabled || !onTrailingIconPress,
|
|
403
459
|
accessibilityRole: "button",
|
|
404
460
|
hitSlop: 12,
|
|
405
461
|
style: styles.trailingIconPressable,
|
|
406
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
462
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: styles.trailingIcon, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
407
463
|
MaterialCommunityIcons,
|
|
408
464
|
{
|
|
409
465
|
name: trailingIcon,
|
|
@@ -414,7 +470,7 @@ function TextField({
|
|
|
414
470
|
}
|
|
415
471
|
) : null,
|
|
416
472
|
label ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
417
|
-
|
|
473
|
+
import_react_native4.Animated.Text,
|
|
418
474
|
{
|
|
419
475
|
numberOfLines: 1,
|
|
420
476
|
style: [
|
|
@@ -434,10 +490,10 @@ function TextField({
|
|
|
434
490
|
children: label
|
|
435
491
|
}
|
|
436
492
|
) : null,
|
|
437
|
-
isFilled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
493
|
+
isFilled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: indicatorStyle }) : null
|
|
438
494
|
] }) }),
|
|
439
|
-
displaySupportingText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
440
|
-
|
|
495
|
+
displaySupportingText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: styles.supportingTextRow, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
496
|
+
import_react_native4.Text,
|
|
441
497
|
{
|
|
442
498
|
style: [
|
|
443
499
|
styles.supportingText,
|
package/package.json
CHANGED
|
@@ -1,83 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlynative/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-alpha.1",
|
|
4
4
|
"description": "Material Design 3 UI components for React Native — Button, Card, Chip, TextField, and more.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
|
-
"react-native": "src/index.ts",
|
|
11
|
-
"source": "src/index.ts",
|
|
12
10
|
"exports": {
|
|
13
11
|
".": {
|
|
14
12
|
"types": "./dist/index.d.ts",
|
|
15
|
-
"react-native": "./src/index.ts",
|
|
16
13
|
"default": "./dist/index.js"
|
|
17
14
|
},
|
|
18
15
|
"./typography": {
|
|
19
16
|
"types": "./dist/typography/index.d.ts",
|
|
20
|
-
"react-native": "./src/typography/index.ts",
|
|
21
17
|
"default": "./dist/typography/index.js"
|
|
22
18
|
},
|
|
23
19
|
"./layout": {
|
|
24
20
|
"types": "./dist/layout/index.d.ts",
|
|
25
|
-
"react-native": "./src/layout/index.ts",
|
|
26
21
|
"default": "./dist/layout/index.js"
|
|
27
22
|
},
|
|
28
23
|
"./button": {
|
|
29
24
|
"types": "./dist/button/index.d.ts",
|
|
30
|
-
"react-native": "./src/button/index.ts",
|
|
31
25
|
"default": "./dist/button/index.js"
|
|
32
26
|
},
|
|
33
27
|
"./icon-button": {
|
|
34
28
|
"types": "./dist/icon-button/index.d.ts",
|
|
35
|
-
"react-native": "./src/icon-button/index.ts",
|
|
36
29
|
"default": "./dist/icon-button/index.js"
|
|
37
30
|
},
|
|
38
31
|
"./appbar": {
|
|
39
32
|
"types": "./dist/appbar/index.d.ts",
|
|
40
|
-
"react-native": "./src/appbar/index.ts",
|
|
41
33
|
"default": "./dist/appbar/index.js"
|
|
42
34
|
},
|
|
43
35
|
"./card": {
|
|
44
36
|
"types": "./dist/card/index.d.ts",
|
|
45
|
-
"react-native": "./src/card/index.ts",
|
|
46
37
|
"default": "./dist/card/index.js"
|
|
47
38
|
},
|
|
48
39
|
"./chip": {
|
|
49
40
|
"types": "./dist/chip/index.d.ts",
|
|
50
|
-
"react-native": "./src/chip/index.ts",
|
|
51
41
|
"default": "./dist/chip/index.js"
|
|
52
42
|
},
|
|
53
43
|
"./checkbox": {
|
|
54
44
|
"types": "./dist/checkbox/index.d.ts",
|
|
55
|
-
"react-native": "./src/checkbox/index.ts",
|
|
56
45
|
"default": "./dist/checkbox/index.js"
|
|
57
46
|
},
|
|
58
47
|
"./radio": {
|
|
59
48
|
"types": "./dist/radio/index.d.ts",
|
|
60
|
-
"react-native": "./src/radio/index.ts",
|
|
61
49
|
"default": "./dist/radio/index.js"
|
|
62
50
|
},
|
|
63
51
|
"./switch": {
|
|
64
52
|
"types": "./dist/switch/index.d.ts",
|
|
65
|
-
"react-native": "./src/switch/index.ts",
|
|
66
53
|
"default": "./dist/switch/index.js"
|
|
67
54
|
},
|
|
68
55
|
"./text-field": {
|
|
69
56
|
"types": "./dist/text-field/index.d.ts",
|
|
70
|
-
"react-native": "./src/text-field/index.ts",
|
|
71
57
|
"default": "./dist/text-field/index.js"
|
|
72
58
|
},
|
|
73
59
|
"./list": {
|
|
74
60
|
"types": "./dist/list/index.d.ts",
|
|
75
|
-
"react-native": "./src/list/index.ts",
|
|
76
61
|
"default": "./dist/list/index.js"
|
|
77
62
|
},
|
|
78
63
|
"./keyboard-avoiding-wrapper": {
|
|
79
64
|
"types": "./dist/keyboard-avoiding-wrapper/index.d.ts",
|
|
80
|
-
"react-native": "./src/keyboard-avoiding-wrapper/index.ts",
|
|
81
65
|
"default": "./dist/keyboard-avoiding-wrapper/index.js"
|
|
82
66
|
}
|
|
83
67
|
},
|
|
@@ -129,9 +113,7 @@
|
|
|
129
113
|
"provenance": true
|
|
130
114
|
},
|
|
131
115
|
"files": [
|
|
132
|
-
"dist"
|
|
133
|
-
"src",
|
|
134
|
-
"!src/__tests__"
|
|
116
|
+
"dist"
|
|
135
117
|
],
|
|
136
118
|
"license": "MIT",
|
|
137
119
|
"repository": {
|
|
@@ -148,14 +130,13 @@
|
|
|
148
130
|
"material-you"
|
|
149
131
|
],
|
|
150
132
|
"scripts": {
|
|
151
|
-
"build": "tsup
|
|
133
|
+
"build": "tsup",
|
|
152
134
|
"typecheck": "tsc --noEmit",
|
|
153
135
|
"test": "jest --passWithNoTests"
|
|
154
136
|
},
|
|
155
137
|
"peerDependencies": {
|
|
156
138
|
"@expo/vector-icons": ">=14.0.0",
|
|
157
|
-
"@onlynative/core": ">=0.1.
|
|
158
|
-
"@onlynative/utils": ">=0.1.0-alpha.3",
|
|
139
|
+
"@onlynative/core": ">=0.1.1-alpha.1",
|
|
159
140
|
"react": ">=18.0.0",
|
|
160
141
|
"react-native": ">=0.72.0",
|
|
161
142
|
"react-native-safe-area-context": ">=4.0.0"
|
package/src/appbar/AppBar.tsx
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from 'react'
|
|
2
|
-
import type { ReactNode } from 'react'
|
|
3
|
-
import type { LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native'
|
|
4
|
-
import { Platform, View } from 'react-native'
|
|
5
|
-
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
6
|
-
import { defaultTopAppBarTokens, useTheme } from '@onlynative/core'
|
|
7
|
-
|
|
8
|
-
import { IconButton } from '../icon-button'
|
|
9
|
-
import type { IconButtonProps } from '../icon-button'
|
|
10
|
-
import { Typography } from '../typography'
|
|
11
|
-
import type { TypographyVariant } from '../typography'
|
|
12
|
-
import { selectRTL } from '@onlynative/utils'
|
|
13
|
-
import { createStyles } from './styles'
|
|
14
|
-
import type { AppBarProps } from './types'
|
|
15
|
-
|
|
16
|
-
type AppBarSize = 'small' | 'medium' | 'large'
|
|
17
|
-
function getBackIcon(): IconButtonProps['icon'] {
|
|
18
|
-
if (Platform.OS === 'ios') {
|
|
19
|
-
return selectRTL('chevron-left', 'chevron-right')
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return selectRTL('arrow-left', 'arrow-right')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const titleVariantBySize: Record<AppBarSize, TypographyVariant> = {
|
|
26
|
-
small: 'titleLarge',
|
|
27
|
-
medium: 'headlineSmall',
|
|
28
|
-
large: 'headlineMedium',
|
|
29
|
-
}
|
|
30
|
-
const APP_BAR_TITLE_TEXT_PROPS = {
|
|
31
|
-
numberOfLines: 1,
|
|
32
|
-
ellipsizeMode: 'tail',
|
|
33
|
-
accessibilityRole: 'header',
|
|
34
|
-
} as const
|
|
35
|
-
|
|
36
|
-
function resolveSize(variant: AppBarProps['variant']): AppBarSize {
|
|
37
|
-
if (variant === 'medium' || variant === 'large') {
|
|
38
|
-
return variant
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return 'small'
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getSizeStyle(
|
|
45
|
-
styles: ReturnType<typeof createStyles>,
|
|
46
|
-
size: AppBarSize,
|
|
47
|
-
) {
|
|
48
|
-
if (size === 'large') {
|
|
49
|
-
return styles.largeContainer
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return styles.mediumContainer
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function withTopInset(
|
|
56
|
-
enabled: boolean,
|
|
57
|
-
content: ReactNode,
|
|
58
|
-
style: StyleProp<ViewStyle>,
|
|
59
|
-
) {
|
|
60
|
-
if (enabled) {
|
|
61
|
-
return (
|
|
62
|
-
<SafeAreaView edges={['top']} style={style}>
|
|
63
|
-
{content}
|
|
64
|
-
</SafeAreaView>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return <View style={style}>{content}</View>
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function measureWidth(event: LayoutChangeEvent): number {
|
|
72
|
-
return Math.round(event.nativeEvent.layout.width)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function AppBar({
|
|
76
|
-
title,
|
|
77
|
-
variant = 'small',
|
|
78
|
-
canGoBack = false,
|
|
79
|
-
onBackPress,
|
|
80
|
-
insetTop = false,
|
|
81
|
-
elevated = false,
|
|
82
|
-
leading,
|
|
83
|
-
trailing,
|
|
84
|
-
actions,
|
|
85
|
-
containerColor,
|
|
86
|
-
contentColor,
|
|
87
|
-
titleStyle,
|
|
88
|
-
style,
|
|
89
|
-
}: AppBarProps) {
|
|
90
|
-
const theme = useTheme()
|
|
91
|
-
const topAppBar = theme.topAppBar ?? defaultTopAppBarTokens
|
|
92
|
-
const styles = useMemo(() => createStyles(theme), [theme])
|
|
93
|
-
const [leadingWidth, setLeadingWidth] = useState(0)
|
|
94
|
-
const [actionsWidth, setActionsWidth] = useState(0)
|
|
95
|
-
const titleColorStyle = useMemo(
|
|
96
|
-
() => ({ color: contentColor ?? theme.colors.onSurface }),
|
|
97
|
-
[contentColor, theme.colors.onSurface],
|
|
98
|
-
)
|
|
99
|
-
const size = resolveSize(variant)
|
|
100
|
-
const titleVariant = titleVariantBySize[size]
|
|
101
|
-
const isCenterAligned = variant === 'center-aligned'
|
|
102
|
-
const isExpanded = size !== 'small'
|
|
103
|
-
const titleStartInset =
|
|
104
|
-
topAppBar.horizontalPadding +
|
|
105
|
-
Math.max(topAppBar.titleStartInset, leadingWidth)
|
|
106
|
-
const compactTitleEndInset = topAppBar.horizontalPadding + actionsWidth
|
|
107
|
-
const centeredSideInset =
|
|
108
|
-
topAppBar.horizontalPadding + Math.max(leadingWidth, actionsWidth)
|
|
109
|
-
const expandedTitleInsetStyle = useMemo<ViewStyle>(
|
|
110
|
-
() => ({ paddingStart: titleStartInset }),
|
|
111
|
-
[titleStartInset],
|
|
112
|
-
)
|
|
113
|
-
const overlayTitleInsetStyle = useMemo<ViewStyle>(
|
|
114
|
-
() =>
|
|
115
|
-
isCenterAligned
|
|
116
|
-
? { start: centeredSideInset, end: centeredSideInset }
|
|
117
|
-
: { start: titleStartInset, end: compactTitleEndInset },
|
|
118
|
-
[centeredSideInset, compactTitleEndInset, isCenterAligned, titleStartInset],
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
const leadingContent = useMemo(() => {
|
|
122
|
-
if (leading) {
|
|
123
|
-
return leading
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!canGoBack) {
|
|
127
|
-
return null
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<View style={styles.iconFrame}>
|
|
132
|
-
<IconButton
|
|
133
|
-
icon={getBackIcon()}
|
|
134
|
-
size="medium"
|
|
135
|
-
variant="standard"
|
|
136
|
-
iconColor={contentColor ?? theme.colors.onSurface}
|
|
137
|
-
accessibilityLabel="Go back"
|
|
138
|
-
onPress={onBackPress}
|
|
139
|
-
/>
|
|
140
|
-
</View>
|
|
141
|
-
)
|
|
142
|
-
}, [
|
|
143
|
-
canGoBack,
|
|
144
|
-
contentColor,
|
|
145
|
-
leading,
|
|
146
|
-
onBackPress,
|
|
147
|
-
styles.iconFrame,
|
|
148
|
-
theme.colors.onSurface,
|
|
149
|
-
])
|
|
150
|
-
|
|
151
|
-
const actionsContent = useMemo(() => {
|
|
152
|
-
if (trailing) {
|
|
153
|
-
return trailing
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!actions || actions.length === 0) {
|
|
157
|
-
return null
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<View style={styles.actionsRow}>
|
|
162
|
-
{actions.map((action, index) => (
|
|
163
|
-
<View
|
|
164
|
-
key={`${String(action.icon)}-${index}`}
|
|
165
|
-
style={styles.iconFrame}
|
|
166
|
-
>
|
|
167
|
-
<IconButton
|
|
168
|
-
icon={action.icon}
|
|
169
|
-
size="medium"
|
|
170
|
-
variant="standard"
|
|
171
|
-
iconColor={contentColor}
|
|
172
|
-
accessibilityLabel={action.accessibilityLabel}
|
|
173
|
-
onPress={action.onPress}
|
|
174
|
-
disabled={action.disabled}
|
|
175
|
-
/>
|
|
176
|
-
</View>
|
|
177
|
-
))}
|
|
178
|
-
</View>
|
|
179
|
-
)
|
|
180
|
-
}, [actions, contentColor, styles.actionsRow, styles.iconFrame, trailing])
|
|
181
|
-
|
|
182
|
-
const onLeadingLayout = useCallback((event: LayoutChangeEvent) => {
|
|
183
|
-
const nextWidth = measureWidth(event)
|
|
184
|
-
|
|
185
|
-
setLeadingWidth((currentWidth) => {
|
|
186
|
-
if (currentWidth === nextWidth) {
|
|
187
|
-
return currentWidth
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return nextWidth
|
|
191
|
-
})
|
|
192
|
-
}, [])
|
|
193
|
-
|
|
194
|
-
const onActionsLayout = useCallback((event: LayoutChangeEvent) => {
|
|
195
|
-
const nextWidth = measureWidth(event)
|
|
196
|
-
|
|
197
|
-
setActionsWidth((currentWidth) => {
|
|
198
|
-
if (currentWidth === nextWidth) {
|
|
199
|
-
return currentWidth
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return nextWidth
|
|
203
|
-
})
|
|
204
|
-
}, [])
|
|
205
|
-
|
|
206
|
-
const topRow = (
|
|
207
|
-
<View style={styles.topRow}>
|
|
208
|
-
<View
|
|
209
|
-
collapsable={false}
|
|
210
|
-
onLayout={onLeadingLayout}
|
|
211
|
-
style={styles.sideSlot}
|
|
212
|
-
>
|
|
213
|
-
{leadingContent}
|
|
214
|
-
</View>
|
|
215
|
-
<View style={styles.topRowSpacer} />
|
|
216
|
-
<View
|
|
217
|
-
collapsable={false}
|
|
218
|
-
onLayout={onActionsLayout}
|
|
219
|
-
style={styles.sideSlot}
|
|
220
|
-
>
|
|
221
|
-
{actionsContent}
|
|
222
|
-
</View>
|
|
223
|
-
</View>
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
const containerOverride = containerColor
|
|
227
|
-
? ({ backgroundColor: containerColor } as ViewStyle)
|
|
228
|
-
: undefined
|
|
229
|
-
const rootStyle: StyleProp<ViewStyle> = [
|
|
230
|
-
styles.root,
|
|
231
|
-
elevated ? styles.elevatedRoot : undefined,
|
|
232
|
-
containerOverride,
|
|
233
|
-
style,
|
|
234
|
-
]
|
|
235
|
-
const safeAreaStyle: StyleProp<ViewStyle> = [
|
|
236
|
-
styles.safeArea,
|
|
237
|
-
elevated ? styles.elevatedSafeArea : undefined,
|
|
238
|
-
containerOverride,
|
|
239
|
-
]
|
|
240
|
-
|
|
241
|
-
if (isExpanded) {
|
|
242
|
-
const content = (
|
|
243
|
-
<View style={[styles.expandedContainer, getSizeStyle(styles, size)]}>
|
|
244
|
-
{topRow}
|
|
245
|
-
<View
|
|
246
|
-
style={[
|
|
247
|
-
styles.expandedTitleContainer,
|
|
248
|
-
size === 'large'
|
|
249
|
-
? styles.largeTitlePadding
|
|
250
|
-
: styles.mediumTitlePadding,
|
|
251
|
-
expandedTitleInsetStyle,
|
|
252
|
-
]}
|
|
253
|
-
>
|
|
254
|
-
<Typography
|
|
255
|
-
{...APP_BAR_TITLE_TEXT_PROPS}
|
|
256
|
-
variant={titleVariant}
|
|
257
|
-
style={[
|
|
258
|
-
styles.title,
|
|
259
|
-
titleColorStyle,
|
|
260
|
-
styles.startAlignedTitle,
|
|
261
|
-
titleStyle,
|
|
262
|
-
]}
|
|
263
|
-
>
|
|
264
|
-
{title}
|
|
265
|
-
</Typography>
|
|
266
|
-
</View>
|
|
267
|
-
</View>
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<View style={rootStyle}>
|
|
272
|
-
{withTopInset(insetTop, content, safeAreaStyle)}
|
|
273
|
-
</View>
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const content = (
|
|
278
|
-
<View style={styles.smallContainer}>
|
|
279
|
-
{topRow}
|
|
280
|
-
<View style={[styles.overlayTitleContainer, overlayTitleInsetStyle]}>
|
|
281
|
-
<Typography
|
|
282
|
-
{...APP_BAR_TITLE_TEXT_PROPS}
|
|
283
|
-
variant={titleVariant}
|
|
284
|
-
style={[
|
|
285
|
-
styles.title,
|
|
286
|
-
titleColorStyle,
|
|
287
|
-
isCenterAligned ? styles.centeredTitle : styles.startAlignedTitle,
|
|
288
|
-
titleStyle,
|
|
289
|
-
]}
|
|
290
|
-
>
|
|
291
|
-
{title}
|
|
292
|
-
</Typography>
|
|
293
|
-
</View>
|
|
294
|
-
</View>
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
return (
|
|
298
|
-
<View style={rootStyle}>
|
|
299
|
-
{withTopInset(insetTop, content, safeAreaStyle)}
|
|
300
|
-
</View>
|
|
301
|
-
)
|
|
302
|
-
}
|
package/src/appbar/index.ts
DELETED