@tastic/profile 0.13.0 → 0.14.0
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/README.md +10 -3
- package/dist/index.d.mts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +208 -73
- package/dist/index.mjs +217 -82
- package/package.json +2 -2
- package/src/IdentityChip.tsx +58 -0
- package/src/ProfileChip.tsx +2 -37
- package/src/ProfilePicker.tsx +309 -77
- package/src/ProfilesManager.tsx +7 -1
- package/src/index.ts +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5,17 +5,34 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
// src/
|
|
8
|
+
// src/IdentityChip.tsx
|
|
9
9
|
import { getContrastColor } from "@rific/auto-paper";
|
|
10
10
|
import { StyleSheet, View } from "react-native";
|
|
11
11
|
import { Icon, Text } from "react-native-paper";
|
|
12
|
+
import { jsx } from "react/jsx-runtime";
|
|
13
|
+
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
14
|
+
function IdentityChip({ color, tag, icon, size, filled = true }) {
|
|
15
|
+
const contrastColor = getContrastColor(color);
|
|
16
|
+
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
|
|
17
|
+
return /* @__PURE__ */ jsx(View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: tag ? /* @__PURE__ */ jsx(Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx(Icon, { source: icon, size: size * 0.6, color: contrastColor }) });
|
|
18
|
+
}
|
|
19
|
+
var styles = StyleSheet.create({
|
|
20
|
+
chip: {
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center"
|
|
23
|
+
},
|
|
24
|
+
tag: {
|
|
25
|
+
fontWeight: "700",
|
|
26
|
+
paddingHorizontal: 2
|
|
27
|
+
}
|
|
28
|
+
});
|
|
12
29
|
|
|
13
30
|
// src/ProfileColorContext.tsx
|
|
14
31
|
import { createContext, useContext } from "react";
|
|
15
|
-
import { jsx } from "react/jsx-runtime";
|
|
32
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
16
33
|
var ProfileColorContext = createContext(void 0);
|
|
17
34
|
function ProfileColorProvider({ preview, children }) {
|
|
18
|
-
return /* @__PURE__ */
|
|
35
|
+
return /* @__PURE__ */ jsx2(ProfileColorContext.Provider, { value: preview, children });
|
|
19
36
|
}
|
|
20
37
|
function useProfileColorPreview() {
|
|
21
38
|
return useContext(ProfileColorContext);
|
|
@@ -26,42 +43,53 @@ function useProfileDisplayColor(hex) {
|
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
// src/ProfileChip.tsx
|
|
29
|
-
import { jsx as
|
|
30
|
-
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
46
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
31
47
|
function ProfileChip({ profile, size = 22, filled = true }) {
|
|
32
48
|
const color = useProfileDisplayColor(profile.color);
|
|
33
|
-
|
|
34
|
-
const tagFontSize = size * (profile.tag && EMOJI_PATTERN.test(profile.tag) ? 0.6 : 0.4);
|
|
35
|
-
return /* @__PURE__ */ jsx2(View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: profile.tag ? /* @__PURE__ */ jsx2(Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: profile.tag }) : /* @__PURE__ */ jsx2(Icon, { source: "account", size: size * 0.6, color: contrastColor }) });
|
|
49
|
+
return /* @__PURE__ */ jsx3(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
|
|
36
50
|
}
|
|
37
|
-
var styles = StyleSheet.create({
|
|
38
|
-
chip: {
|
|
39
|
-
alignItems: "center",
|
|
40
|
-
justifyContent: "center"
|
|
41
|
-
},
|
|
42
|
-
tag: {
|
|
43
|
-
fontWeight: "700",
|
|
44
|
-
paddingHorizontal: 2
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
51
|
|
|
48
52
|
// src/ProfilePicker.tsx
|
|
49
|
-
import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
|
|
53
|
+
import { defaultColors, FadingScrollView, getContrastColor as getContrastColor2 } from "@rific/auto-paper";
|
|
50
54
|
import { TouchableRipple } from "@rific/feedback-press";
|
|
51
55
|
import { PopoverBody, useAutoAlign } from "@tastic/hud";
|
|
52
|
-
import { useMemo } from "react";
|
|
53
|
-
import {
|
|
56
|
+
import { useMemo, useState } from "react";
|
|
57
|
+
import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
|
|
54
58
|
import { Icon as Icon2, Text as Text2 } from "react-native-paper";
|
|
55
|
-
import { Fragment, jsx as
|
|
59
|
+
import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
56
60
|
var TRIGGER_HEIGHT = 28;
|
|
57
61
|
var POPOVER_WIDTH = 220;
|
|
58
62
|
var ROW_HEIGHT = 36;
|
|
59
63
|
var LIST_PADDING = 8;
|
|
60
|
-
|
|
64
|
+
var MENU_BORDER_WIDTH = 2;
|
|
65
|
+
var ROW_INSET = 12;
|
|
66
|
+
var CHIP_SIZE = 32;
|
|
67
|
+
var CHIP_TRIGGER_PADDING = 6;
|
|
68
|
+
var SWATCH_SIZE = 28;
|
|
69
|
+
var SWATCH_MIN_GAP = 6;
|
|
70
|
+
var COLOR_ROW_CAPTIONED_HEIGHT = 48;
|
|
71
|
+
var COLOR_LABEL_LINE = 18;
|
|
72
|
+
var COLOR_CAPTION_LINE = 16;
|
|
73
|
+
var COLOR_ROW_DIVIDER = 9;
|
|
74
|
+
var GRID_TOP = 4;
|
|
75
|
+
var GRID_BOTTOM = 8;
|
|
76
|
+
var GRID_WIDTH = POPOVER_WIDTH - 2 * MENU_BORDER_WIDTH - 2 * LIST_PADDING - 2 * ROW_INSET;
|
|
77
|
+
var GRID_COLUMNS = Math.floor((GRID_WIDTH + SWATCH_MIN_GAP) / (SWATCH_SIZE + SWATCH_MIN_GAP));
|
|
78
|
+
var GRID_GAP = (GRID_WIDTH - GRID_COLUMNS * SWATCH_SIZE) / (GRID_COLUMNS - 1);
|
|
79
|
+
function colorRowHeight(caption) {
|
|
80
|
+
return caption ? COLOR_ROW_CAPTIONED_HEIGHT : ROW_HEIGHT;
|
|
81
|
+
}
|
|
82
|
+
function colorPageHeight(swatches) {
|
|
83
|
+
const rows = Math.ceil(swatches.length / GRID_COLUMNS);
|
|
84
|
+
return ROW_HEIGHT + GRID_TOP + rows * SWATCH_SIZE + (rows - 1) * GRID_GAP + GRID_BOTTOM;
|
|
85
|
+
}
|
|
86
|
+
function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
|
|
61
87
|
const rowCount = profileCount + 1 + (hasManageRow ? 1 : 0);
|
|
62
|
-
|
|
88
|
+
const list = rowCount * ROW_HEIGHT + (colorSection ? colorRowHeight(colorSection.caption) + COLOR_ROW_DIVIDER : 0);
|
|
89
|
+
const content = colorSection ? Math.max(list, colorPageHeight(colorSection.swatches ?? defaultColors)) : list;
|
|
90
|
+
return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + content };
|
|
63
91
|
}
|
|
64
|
-
function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage }) {
|
|
92
|
+
function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
|
|
65
93
|
const id = `${idPrefix}-profile`;
|
|
66
94
|
const colorPreview = useProfileColorPreview();
|
|
67
95
|
const menuBg = dark ? "#000000" : "#FFFFFF";
|
|
@@ -69,7 +97,7 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
69
97
|
const open = host.openId === id;
|
|
70
98
|
const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
|
|
71
99
|
const sortedProfiles = useMemo(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
|
|
72
|
-
const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage);
|
|
100
|
+
const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
|
|
73
101
|
const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
|
|
74
102
|
const placement = alignOverride ?? auto;
|
|
75
103
|
const { maxHeight, measured, triggerRef, verticalAlign } = placement;
|
|
@@ -87,13 +115,23 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
87
115
|
host.close();
|
|
88
116
|
onManage?.();
|
|
89
117
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
const chipTrigger = !!colorSection;
|
|
119
|
+
const [triggerWidth, setTriggerWidth] = useState(0);
|
|
120
|
+
const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
|
|
121
|
+
const [colorPage, setColorPage] = useState(false);
|
|
122
|
+
const handleTriggerPress = () => {
|
|
123
|
+
setColorPage(false);
|
|
124
|
+
host.toggle(id);
|
|
125
|
+
};
|
|
126
|
+
const triggerLabel = (selectedProfile?.name ?? guestLabel).toUpperCase();
|
|
127
|
+
return /* @__PURE__ */ jsxs(View2, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
|
|
128
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ jsxs(View2, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
|
|
129
|
+
chipTrigger && /* @__PURE__ */ jsx4(IdentityChip, { color, tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon, size: CHIP_SIZE }),
|
|
130
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
|
|
131
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
|
|
94
132
|
] }) }),
|
|
95
|
-
/* @__PURE__ */
|
|
96
|
-
|
|
133
|
+
/* @__PURE__ */ jsx4(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ jsx4(
|
|
134
|
+
View2,
|
|
97
135
|
{
|
|
98
136
|
style: [
|
|
99
137
|
styles2.menu,
|
|
@@ -104,20 +142,19 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
104
142
|
maxHeight
|
|
105
143
|
}
|
|
106
144
|
],
|
|
107
|
-
contentContainerStyle: styles2.menuContent,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/* @__PURE__ */
|
|
112
|
-
/* @__PURE__ */ jsx3(Text2, { style: [styles2.rowLabel, { color: selectedId === null ? getContrastColor2(color) : fg }], children: nullLabel })
|
|
145
|
+
children: /* @__PURE__ */ jsx4(FadingScrollView, { contentContainerStyle: styles2.menuContent, showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ jsx4(ColorPage, { section: colorSection, profileColor: selectedProfile?.color, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
146
|
+
colorSection && /* @__PURE__ */ jsx4(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
|
|
147
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
|
|
148
|
+
/* @__PURE__ */ jsx4(View2, { style: styles2.guestChip, children: /* @__PURE__ */ jsx4(Icon2, { source: nullIcon, size: 13, color: selectedId === null ? getContrastColor2(color) : fg }) }),
|
|
149
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles2.rowLabel, { color: selectedId === null ? getContrastColor2(color) : fg }], children: nullLabel })
|
|
113
150
|
] }) }),
|
|
114
151
|
sortedProfiles.map((profile) => {
|
|
115
152
|
const taken = profile.id === takenId;
|
|
116
153
|
const selected = selectedId === profile.id;
|
|
117
154
|
const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
|
|
118
|
-
return /* @__PURE__ */
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
/* @__PURE__ */
|
|
155
|
+
return /* @__PURE__ */ jsx4(TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ jsxs(View2, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
|
|
156
|
+
/* @__PURE__ */ jsx4(ProfileChip, { profile, filled: !selected }),
|
|
157
|
+
/* @__PURE__ */ jsx4(
|
|
121
158
|
Text2,
|
|
122
159
|
{
|
|
123
160
|
style: [
|
|
@@ -133,17 +170,48 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
133
170
|
] }) }, profile.id);
|
|
134
171
|
}),
|
|
135
172
|
onManage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
/* @__PURE__ */
|
|
138
|
-
/* @__PURE__ */
|
|
139
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ jsx4(View2, { style: [styles2.divider, { backgroundColor: color }] }),
|
|
174
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ jsxs(View2, { style: styles2.manageRowInner, children: [
|
|
175
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "account-cog-outline", size: 16, color }),
|
|
176
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles2.manageLabel, { color: fg }], children: "Manage" })
|
|
140
177
|
] }) })
|
|
141
178
|
] })
|
|
142
|
-
]
|
|
179
|
+
] }) }, colorSection && colorPage ? "color" : "list")
|
|
143
180
|
}
|
|
144
181
|
) })
|
|
145
182
|
] });
|
|
146
183
|
}
|
|
184
|
+
function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
185
|
+
const { value, previewValue, label = "Color", caption } = section;
|
|
186
|
+
const drawn = previewValue ? previewValue(value) : value;
|
|
187
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
188
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress, style: [styles2.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
|
|
189
|
+
/* @__PURE__ */ jsx4(View2, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
|
|
190
|
+
/* @__PURE__ */ jsxs(View2, { style: styles2.colorRowText, children: [
|
|
191
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
192
|
+
caption ? /* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "chevron-right", size: 18, color: fg })
|
|
195
|
+
] }) }),
|
|
196
|
+
/* @__PURE__ */ jsx4(View2, { style: [styles2.divider, { backgroundColor: dividerColor }] })
|
|
197
|
+
] });
|
|
198
|
+
}
|
|
199
|
+
function ColorPage({ section, profileColor, fg, onBack }) {
|
|
200
|
+
const { value, onChange, previewValue, swatches = defaultColors, label = "Color" } = section;
|
|
201
|
+
const displayFor = previewValue ?? ((hex) => hex);
|
|
202
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
203
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: onBack, style: styles2.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
|
|
204
|
+
/* @__PURE__ */ jsx4(View2, { style: styles2.guestChip, children: /* @__PURE__ */ jsx4(Icon2, { source: "chevron-left", size: 20, color: fg }) }),
|
|
205
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
|
|
206
|
+
] }) }),
|
|
207
|
+
/* @__PURE__ */ jsx4(View2, { style: styles2.swatches, children: swatches.map((swatch) => {
|
|
208
|
+
const selected = swatch.value.toLowerCase() === value.toLowerCase();
|
|
209
|
+
const isProfileColor = !!profileColor && swatch.value.toLowerCase() === profileColor.toLowerCase();
|
|
210
|
+
const swatchDisplay = displayFor(swatch.value);
|
|
211
|
+
return /* @__PURE__ */ jsx4(TouchableRipple, { onPress: () => onChange(swatch.value), borderless: true, style: [styles2.swatch, { backgroundColor: swatchDisplay }, selected && styles2.swatchSelected], accessibilityRole: "button", accessibilityLabel: isProfileColor ? `${swatch.label}, profile color` : swatch.label, accessibilityState: { selected }, children: selected ? /* @__PURE__ */ jsx4(Icon2, { source: "check", size: 16, color: getContrastColor2(swatchDisplay) }) : isProfileColor ? /* @__PURE__ */ jsx4(View2, { style: [styles2.profileDot, { backgroundColor: getContrastColor2(swatchDisplay) }] }) : /* @__PURE__ */ jsx4(View2, {}) }, swatch.value);
|
|
212
|
+
}) })
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
147
215
|
var styles2 = StyleSheet2.create({
|
|
148
216
|
anchor: {
|
|
149
217
|
position: "relative"
|
|
@@ -153,6 +221,40 @@ var styles2 = StyleSheet2.create({
|
|
|
153
221
|
anchorOpen: {
|
|
154
222
|
zIndex: 100
|
|
155
223
|
},
|
|
224
|
+
// The pill, rounded at both ends. The host adds its own background through `triggerStyle`.
|
|
225
|
+
chipTrigger: {
|
|
226
|
+
borderRadius: (CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING) / 2
|
|
227
|
+
},
|
|
228
|
+
// The padding lives on the measured view, not the pressable around it, so the placement measures
|
|
229
|
+
// the pill's real edge: measured from inside the padding, a popover opening toward a screen edge
|
|
230
|
+
// was sized as if the pill were 6pt further from it, and ran 2pt past that edge.
|
|
231
|
+
chipTriggerInner: {
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
flexDirection: "row",
|
|
234
|
+
gap: 8,
|
|
235
|
+
height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
|
|
236
|
+
paddingLeft: CHIP_TRIGGER_PADDING,
|
|
237
|
+
paddingRight: CHIP_TRIGGER_PADDING + 8
|
|
238
|
+
},
|
|
239
|
+
colorRowCaption: {
|
|
240
|
+
fontSize: 12,
|
|
241
|
+
lineHeight: COLOR_CAPTION_LINE,
|
|
242
|
+
opacity: 0.6
|
|
243
|
+
},
|
|
244
|
+
// A row's label, with a line height so the captioned Color row's height is exact.
|
|
245
|
+
colorRowLabel: {
|
|
246
|
+
fontSize: 14,
|
|
247
|
+
lineHeight: COLOR_LABEL_LINE
|
|
248
|
+
},
|
|
249
|
+
// The color in effect, in the same 22pt slot as every row's chip.
|
|
250
|
+
colorRowSwatch: {
|
|
251
|
+
borderRadius: 11,
|
|
252
|
+
height: 22,
|
|
253
|
+
width: 22
|
|
254
|
+
},
|
|
255
|
+
colorRowText: {
|
|
256
|
+
flex: 1
|
|
257
|
+
},
|
|
156
258
|
// Separates Manage (a navigation action) from the selectable rows above it - different kind of
|
|
157
259
|
// action, not just another row in the same list. Tinted with the seat's own color (passed in as
|
|
158
260
|
// backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
|
|
@@ -187,7 +289,7 @@ var styles2 = StyleSheet2.create({
|
|
|
187
289
|
},
|
|
188
290
|
menu: {
|
|
189
291
|
borderRadius: 12,
|
|
190
|
-
borderWidth:
|
|
292
|
+
borderWidth: MENU_BORDER_WIDTH,
|
|
191
293
|
elevation: 8,
|
|
192
294
|
shadowColor: "#000000",
|
|
193
295
|
shadowOffset: { height: 2, width: 0 },
|
|
@@ -200,6 +302,12 @@ var styles2 = StyleSheet2.create({
|
|
|
200
302
|
menuContent: {
|
|
201
303
|
padding: LIST_PADDING
|
|
202
304
|
},
|
|
305
|
+
// The selected profile's own color (see ColorSection).
|
|
306
|
+
profileDot: {
|
|
307
|
+
borderRadius: 4,
|
|
308
|
+
height: 8,
|
|
309
|
+
width: 8
|
|
310
|
+
},
|
|
203
311
|
row: {
|
|
204
312
|
borderRadius: 8,
|
|
205
313
|
height: ROW_HEIGHT
|
|
@@ -212,12 +320,33 @@ var styles2 = StyleSheet2.create({
|
|
|
212
320
|
flexDirection: "row",
|
|
213
321
|
flex: 1,
|
|
214
322
|
gap: 8,
|
|
215
|
-
paddingHorizontal:
|
|
323
|
+
paddingHorizontal: ROW_INSET
|
|
216
324
|
},
|
|
217
325
|
rowLabel: {
|
|
218
326
|
flex: 1,
|
|
219
327
|
fontSize: 14
|
|
220
328
|
},
|
|
329
|
+
swatch: {
|
|
330
|
+
alignItems: "center",
|
|
331
|
+
borderRadius: SWATCH_SIZE / 2,
|
|
332
|
+
height: SWATCH_SIZE,
|
|
333
|
+
justifyContent: "center",
|
|
334
|
+
width: SWATCH_SIZE
|
|
335
|
+
},
|
|
336
|
+
swatchSelected: {
|
|
337
|
+
borderColor: "#ffffff",
|
|
338
|
+
borderWidth: 2
|
|
339
|
+
},
|
|
340
|
+
swatches: {
|
|
341
|
+
columnGap: GRID_GAP,
|
|
342
|
+
flexDirection: "row",
|
|
343
|
+
flexWrap: "wrap",
|
|
344
|
+
marginBottom: GRID_BOTTOM,
|
|
345
|
+
marginHorizontal: ROW_INSET,
|
|
346
|
+
marginTop: GRID_TOP,
|
|
347
|
+
rowGap: GRID_GAP,
|
|
348
|
+
width: GRID_WIDTH
|
|
349
|
+
},
|
|
221
350
|
trigger: {
|
|
222
351
|
alignSelf: "flex-start"
|
|
223
352
|
},
|
|
@@ -235,13 +364,13 @@ var styles2 = StyleSheet2.create({
|
|
|
235
364
|
});
|
|
236
365
|
|
|
237
366
|
// src/ProfilesManager.tsx
|
|
238
|
-
import { defaultColors, useAutoPaperTheme } from "@rific/auto-paper";
|
|
367
|
+
import { defaultColors as defaultColors2, useAutoPaperTheme } from "@rific/auto-paper";
|
|
239
368
|
import { TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
|
|
240
369
|
import { useFocusChain } from "@rific/focus-chain";
|
|
241
370
|
import { useToast } from "@rific/toaster";
|
|
242
371
|
import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud";
|
|
243
|
-
import { useCallback, useEffect, useImperativeHandle, useMemo as useMemo2, useRef, useState } from "react";
|
|
244
|
-
import { KeyboardAvoidingView, Platform, ScrollView
|
|
372
|
+
import { useCallback, useEffect, useImperativeHandle, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
373
|
+
import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet as StyleSheet3, useWindowDimensions, View as View3 } from "react-native";
|
|
245
374
|
import { Icon as Icon3, Text as Text3, TextInput } from "react-native-paper";
|
|
246
375
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
247
376
|
|
|
@@ -278,8 +407,8 @@ function isValidProfile(value) {
|
|
|
278
407
|
}
|
|
279
408
|
|
|
280
409
|
// src/ProfilesManager.tsx
|
|
281
|
-
import { Fragment as Fragment2, jsx as
|
|
282
|
-
var
|
|
410
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
411
|
+
var CHIP_SIZE2 = 40;
|
|
283
412
|
function deriveTag(name) {
|
|
284
413
|
const trimmed = name.trim();
|
|
285
414
|
return trimmed ? trimmed[0].toUpperCase() : "";
|
|
@@ -293,12 +422,12 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
293
422
|
const host = usePopoverHost();
|
|
294
423
|
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
295
424
|
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
296
|
-
const [editingId, setEditingId] =
|
|
297
|
-
const [draftName, setDraftName] =
|
|
298
|
-
const [draftColor, setDraftColor] =
|
|
299
|
-
const [draftTag, setDraftTag] =
|
|
300
|
-
const [tagManuallySet, setTagManuallySet] =
|
|
301
|
-
const [confirmDeleteId, setConfirmDeleteId] =
|
|
425
|
+
const [editingId, setEditingId] = useState2(null);
|
|
426
|
+
const [draftName, setDraftName] = useState2("");
|
|
427
|
+
const [draftColor, setDraftColor] = useState2(defaultColor);
|
|
428
|
+
const [draftTag, setDraftTag] = useState2("");
|
|
429
|
+
const [tagManuallySet, setTagManuallySet] = useState2(false);
|
|
430
|
+
const [confirmDeleteId, setConfirmDeleteId] = useState2(null);
|
|
302
431
|
const commitEdit = useCallback(() => {
|
|
303
432
|
const name = draftName.trim();
|
|
304
433
|
if (editingId && name) {
|
|
@@ -358,35 +487,35 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
358
487
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
359
488
|
/* @__PURE__ */ jsxs2(View3, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
360
489
|
headerLeft,
|
|
361
|
-
/* @__PURE__ */
|
|
490
|
+
/* @__PURE__ */ jsx5(Text3, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
|
|
362
491
|
] }),
|
|
363
|
-
/* @__PURE__ */
|
|
492
|
+
/* @__PURE__ */ jsx5(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ jsx5(ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
364
493
|
if (row.kind === "new") {
|
|
365
494
|
if (editingId !== "new") {
|
|
366
|
-
return /* @__PURE__ */
|
|
367
|
-
/* @__PURE__ */
|
|
368
|
-
/* @__PURE__ */
|
|
495
|
+
return /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.rowInner, children: [
|
|
496
|
+
/* @__PURE__ */ jsx5(View3, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ jsx5(Icon3, { source: "plus", size: 20, color: fg }) }),
|
|
497
|
+
/* @__PURE__ */ jsx5(Text3, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
369
498
|
] }) }, "new");
|
|
370
499
|
}
|
|
371
|
-
return /* @__PURE__ */
|
|
500
|
+
return /* @__PURE__ */ jsx5(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
|
|
372
501
|
}
|
|
373
502
|
const { profile } = row;
|
|
374
503
|
if (editingId === profile.id) {
|
|
375
|
-
return /* @__PURE__ */
|
|
504
|
+
return /* @__PURE__ */ jsx5(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
|
|
376
505
|
}
|
|
377
|
-
return /* @__PURE__ */
|
|
378
|
-
/* @__PURE__ */
|
|
379
|
-
/* @__PURE__ */
|
|
506
|
+
return /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.rowInner, children: [
|
|
507
|
+
/* @__PURE__ */ jsx5(ProfileChip, { profile, size: CHIP_SIZE2 }),
|
|
508
|
+
/* @__PURE__ */ jsx5(Text3, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
|
|
380
509
|
] }) }, profile.id);
|
|
381
510
|
}) }) }),
|
|
382
|
-
/* @__PURE__ */
|
|
511
|
+
/* @__PURE__ */ jsx5(ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
|
|
383
512
|
] });
|
|
384
513
|
}
|
|
385
514
|
function AlignedColorPicker({ useAlign, ...props }) {
|
|
386
515
|
const { width: windowWidth } = useWindowDimensions();
|
|
387
|
-
const { width, height } = getInlineColorPickerContentSize(
|
|
516
|
+
const { width, height } = getInlineColorPickerContentSize(defaultColors2.length, windowWidth);
|
|
388
517
|
const alignOverride = useAlign(props.host.openId === props.id, width, height);
|
|
389
|
-
return /* @__PURE__ */
|
|
518
|
+
return /* @__PURE__ */ jsx5(InlineColorPicker, { ...props, alignOverride });
|
|
390
519
|
}
|
|
391
520
|
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
|
|
392
521
|
const register = useFocusChain();
|
|
@@ -400,10 +529,10 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
400
529
|
if (host.openId === "color") tagFocusRef.current();
|
|
401
530
|
}, [host.openId]);
|
|
402
531
|
return /* @__PURE__ */ jsxs2(View3, { style: styles3.editRow, children: [
|
|
403
|
-
useColorPickerAlign ? /* @__PURE__ */
|
|
404
|
-
/* @__PURE__ */
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
onDelete && /* @__PURE__ */
|
|
532
|
+
useColorPickerAlign ? /* @__PURE__ */ jsx5(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ jsx5(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
533
|
+
/* @__PURE__ */ jsx5(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
|
|
534
|
+
/* @__PURE__ */ jsx5(TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles3.nameInput }),
|
|
535
|
+
onDelete && /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ jsx5(Icon3, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
407
536
|
] });
|
|
408
537
|
}
|
|
409
538
|
var styles3 = StyleSheet3.create({
|
|
@@ -411,11 +540,17 @@ var styles3 = StyleSheet3.create({
|
|
|
411
540
|
gap: 4,
|
|
412
541
|
paddingHorizontal: 20
|
|
413
542
|
},
|
|
543
|
+
// zIndex: the color popover lives inside this row, and a zIndex only orders siblings of one parent
|
|
544
|
+
// (React Native Web also gives every positioned view its own stacking context), so the popover's
|
|
545
|
+
// own zIndex never reached the rows after this one: they were drawn over it (a Kid row and the New
|
|
546
|
+
// Profile row across the swatches, 2026-10-16). Raising the row lifts the popover with it. There
|
|
547
|
+
// is only ever one edit row.
|
|
414
548
|
editRow: {
|
|
415
549
|
alignItems: "center",
|
|
416
550
|
flexDirection: "row",
|
|
417
551
|
gap: 8,
|
|
418
|
-
paddingVertical: 6
|
|
552
|
+
paddingVertical: 6,
|
|
553
|
+
zIndex: 1
|
|
419
554
|
},
|
|
420
555
|
header: {
|
|
421
556
|
alignItems: "center",
|
|
@@ -456,10 +591,10 @@ var styles3 = StyleSheet3.create({
|
|
|
456
591
|
},
|
|
457
592
|
rowChip: {
|
|
458
593
|
alignItems: "center",
|
|
459
|
-
borderRadius:
|
|
460
|
-
height:
|
|
594
|
+
borderRadius: CHIP_SIZE2 / 2,
|
|
595
|
+
height: CHIP_SIZE2,
|
|
461
596
|
justifyContent: "center",
|
|
462
|
-
width:
|
|
597
|
+
width: CHIP_SIZE2
|
|
463
598
|
},
|
|
464
599
|
rowInner: {
|
|
465
600
|
alignItems: "center",
|
|
@@ -483,7 +618,7 @@ import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
|
|
|
483
618
|
import { IconButton } from "@rific/feedback-press";
|
|
484
619
|
import { useRef as useRef2 } from "react";
|
|
485
620
|
import { StyleSheet as StyleSheet4, View as View4 } from "react-native";
|
|
486
|
-
import { jsx as
|
|
621
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
487
622
|
function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
|
|
488
623
|
const { dark } = useAutoPaperTheme2();
|
|
489
624
|
const managerRef = useRef2(null);
|
|
@@ -493,7 +628,7 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
|
|
|
493
628
|
managerRef.current?.commitPendingEdit();
|
|
494
629
|
onBack();
|
|
495
630
|
};
|
|
496
|
-
return /* @__PURE__ */
|
|
631
|
+
return /* @__PURE__ */ jsx6(View4, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ jsx6(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ jsx6(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
|
|
497
632
|
}
|
|
498
633
|
var styles4 = StyleSheet4.create({
|
|
499
634
|
container: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Player profile identity for local-multiplayer React Native games: a saved name/color/tag a player selects per seat, with a picker, an inline-editable roster screen, and a reusable identity badge, deliberately blind to any app-specific fields (control scheme, stats) an app layers on top",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"typescript": "^6.0.3"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@rific/auto-paper": ">=0.
|
|
95
|
+
"@rific/auto-paper": ">=0.13.0",
|
|
96
96
|
"@rific/feedback-press": ">=0.10.0",
|
|
97
97
|
"@rific/focus-chain": ">=0.4.0",
|
|
98
98
|
"@rific/toaster": ">=0.4.0",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getContrastColor } from '@rific/auto-paper'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { Icon, Text } from 'react-native-paper'
|
|
4
|
+
|
|
5
|
+
// Presence check, not isValidTag's own full-string emoji validation (profilesValidation.ts) - this
|
|
6
|
+
// only decides a font-size ratio, so it doesn't need that check's ZWJ/variation-selector precision,
|
|
7
|
+
// just "does this tag contain a pictograph at all."
|
|
8
|
+
const EMOJI_PATTERN = /\p{Extended_Pictographic}/u
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
// The color drawn, exactly as given (no ProfileColorProvider transform: the caller has already
|
|
12
|
+
// decided what is drawn).
|
|
13
|
+
color: string
|
|
14
|
+
// Shown when non-empty, else `icon`.
|
|
15
|
+
tag?: string
|
|
16
|
+
icon: string
|
|
17
|
+
size: number
|
|
18
|
+
// See ProfileChip's `filled`.
|
|
19
|
+
filled?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// A filled circle with a tag (or an icon) in a contrast-safe color on top: the drawing behind
|
|
23
|
+
// ProfileChip (a profile's own colors) and ProfilePicker's chip trigger (the live color in effect,
|
|
24
|
+
// which may not be any profile's saved one).
|
|
25
|
+
export function IdentityChip({ color, tag, icon, size, filled = true }: Props) {
|
|
26
|
+
const contrastColor = getContrastColor(color)
|
|
27
|
+
// An emoji glyph reads visually smaller than a bold letter at the same fontSize (the system emoji
|
|
28
|
+
// font leaves more of its own em-box empty), so a plain-text ratio that looks right for "J" still
|
|
29
|
+
// looks small for "🎮" at the identical size - this bumps emoji up to the icon's own ratio instead,
|
|
30
|
+
// since a single emoji is closer in visual weight to an icon glyph than to a line of text.
|
|
31
|
+
//
|
|
32
|
+
// Three letters don't fit across the circle at 0.4 in a wide bold face (DAD in Solitaire's DejaVu
|
|
33
|
+
// Serif Bold is a whole chip wide), and the web build ignores adjustsFontSizeToFit, so it cut
|
|
34
|
+
// them off there ("D..."); 0.32 fits them, with adjustsFontSizeToFit still the backstop on native.
|
|
35
|
+
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4)
|
|
36
|
+
return (
|
|
37
|
+
<View style={[styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }]}>
|
|
38
|
+
{tag ? (
|
|
39
|
+
<Text style={[styles.tag, { color: contrastColor, fontSize: tagFontSize }]} numberOfLines={1} adjustsFontSizeToFit>
|
|
40
|
+
{tag}
|
|
41
|
+
</Text>
|
|
42
|
+
) : (
|
|
43
|
+
<Icon source={icon} size={size * 0.6} color={contrastColor} />
|
|
44
|
+
)}
|
|
45
|
+
</View>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
chip: {
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
justifyContent: 'center'
|
|
53
|
+
},
|
|
54
|
+
tag: {
|
|
55
|
+
fontWeight: '700',
|
|
56
|
+
paddingHorizontal: 2
|
|
57
|
+
}
|
|
58
|
+
})
|
package/src/ProfileChip.tsx
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { StyleSheet, View } from 'react-native'
|
|
3
|
-
import { Icon, Text } from 'react-native-paper'
|
|
4
|
-
|
|
1
|
+
import { IdentityChip } from './IdentityChip'
|
|
5
2
|
import { useProfileDisplayColor } from './ProfileColorContext'
|
|
6
3
|
import { Profile } from './types'
|
|
7
4
|
|
|
8
|
-
// Presence check, not isValidTag's own full-string emoji validation (profilesValidation.ts) - this
|
|
9
|
-
// only decides a font-size ratio, so it doesn't need that check's ZWJ/variation-selector precision,
|
|
10
|
-
// just "does this tag contain a pictograph at all."
|
|
11
|
-
const EMOJI_PATTERN = /\p{Extended_Pictographic}/u
|
|
12
|
-
|
|
13
5
|
interface Props {
|
|
14
6
|
profile: Profile
|
|
15
7
|
size?: number
|
|
@@ -32,32 +24,5 @@ interface Props {
|
|
|
32
24
|
export function ProfileChip({ profile, size = 22, filled = true }: Props) {
|
|
33
25
|
// The drawn color, not necessarily the saved one - see ProfileColorProvider.
|
|
34
26
|
const color = useProfileDisplayColor(profile.color)
|
|
35
|
-
|
|
36
|
-
// An emoji glyph reads visually smaller than a bold letter at the same fontSize (the system emoji
|
|
37
|
-
// font leaves more of its own em-box empty), so a plain-text ratio that looks right for "J" still
|
|
38
|
-
// looks small for "🎮" at the identical size - this bumps emoji up to the icon's own ratio instead,
|
|
39
|
-
// since a single emoji is closer in visual weight to an icon glyph than to a line of text.
|
|
40
|
-
const tagFontSize = size * (profile.tag && EMOJI_PATTERN.test(profile.tag) ? 0.6 : 0.4)
|
|
41
|
-
return (
|
|
42
|
-
<View style={[styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }]}>
|
|
43
|
-
{profile.tag ? (
|
|
44
|
-
<Text style={[styles.tag, { color: contrastColor, fontSize: tagFontSize }]} numberOfLines={1} adjustsFontSizeToFit>
|
|
45
|
-
{profile.tag}
|
|
46
|
-
</Text>
|
|
47
|
-
) : (
|
|
48
|
-
<Icon source='account' size={size * 0.6} color={contrastColor} />
|
|
49
|
-
)}
|
|
50
|
-
</View>
|
|
51
|
-
)
|
|
27
|
+
return <IdentityChip color={color} tag={profile.tag} icon='account' size={size} filled={filled} />
|
|
52
28
|
}
|
|
53
|
-
|
|
54
|
-
const styles = StyleSheet.create({
|
|
55
|
-
chip: {
|
|
56
|
-
alignItems: 'center',
|
|
57
|
-
justifyContent: 'center'
|
|
58
|
-
},
|
|
59
|
-
tag: {
|
|
60
|
-
fontWeight: '700',
|
|
61
|
-
paddingHorizontal: 2
|
|
62
|
-
}
|
|
63
|
-
})
|