@tastic/profile 0.12.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 +15 -11
- package/dist/index.d.mts +17 -8
- package/dist/index.d.ts +17 -8
- package/dist/index.js +211 -111
- package/dist/index.mjs +222 -122
- package/package.json +10 -10
- package/src/IdentityChip.tsx +58 -0
- package/src/ProfileChip.tsx +2 -37
- package/src/ProfilePicker.tsx +309 -77
- package/src/ProfilesManager.tsx +21 -67
- package/src/ProfilesScreen.tsx +3 -4
- 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,14 +364,14 @@ 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
|
-
import { getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud";
|
|
243
|
-
import { useCallback, useEffect, useImperativeHandle, useMemo as useMemo2, useRef, useState } from "react";
|
|
244
|
-
import { KeyboardAvoidingView, Platform, ScrollView
|
|
245
|
-
import {
|
|
371
|
+
import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud";
|
|
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";
|
|
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
|
|
|
248
377
|
// src/profilesValidation.ts
|
|
@@ -278,31 +407,27 @@ 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() : "";
|
|
286
415
|
}
|
|
287
|
-
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride,
|
|
416
|
+
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant = "headlineSmall", colorPreview: colorPreviewProp, useColorPickerAlign }) {
|
|
288
417
|
const contextColorPreview = useProfileColorPreview();
|
|
289
418
|
const colorPreview = colorPreviewProp ?? contextColorPreview;
|
|
290
|
-
const { dark
|
|
419
|
+
const { dark } = useAutoPaperTheme();
|
|
291
420
|
const insets = useSafeAreaInsets();
|
|
292
421
|
const { error: showErrorToast } = useToast();
|
|
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
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const [
|
|
301
|
-
const [
|
|
302
|
-
const [draftColor, setDraftColor] = useState(defaultColor);
|
|
303
|
-
const [draftTag, setDraftTag] = useState("");
|
|
304
|
-
const [tagManuallySet, setTagManuallySet] = useState(false);
|
|
305
|
-
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
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);
|
|
306
431
|
const commitEdit = useCallback(() => {
|
|
307
432
|
const name = draftName.trim();
|
|
308
433
|
if (editingId && name) {
|
|
@@ -362,45 +487,35 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
362
487
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
363
488
|
/* @__PURE__ */ jsxs2(View3, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
364
489
|
headerLeft,
|
|
365
|
-
/* @__PURE__ */
|
|
490
|
+
/* @__PURE__ */ jsx5(Text3, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
|
|
366
491
|
] }),
|
|
367
|
-
/* @__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) => {
|
|
368
493
|
if (row.kind === "new") {
|
|
369
494
|
if (editingId !== "new") {
|
|
370
|
-
return /* @__PURE__ */
|
|
371
|
-
/* @__PURE__ */
|
|
372
|
-
/* @__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" })
|
|
373
498
|
] }) }, "new");
|
|
374
499
|
}
|
|
375
|
-
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");
|
|
376
501
|
}
|
|
377
502
|
const { profile } = row;
|
|
378
503
|
if (editingId === profile.id) {
|
|
379
|
-
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);
|
|
380
505
|
}
|
|
381
|
-
return /* @__PURE__ */
|
|
382
|
-
/* @__PURE__ */
|
|
383
|
-
/* @__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 })
|
|
384
509
|
] }) }, profile.id);
|
|
385
510
|
}) }) }),
|
|
386
|
-
|
|
387
|
-
/* @__PURE__ */ jsx4(Icon3, { source: "alert-outline", size: 64, color: colors.danger }),
|
|
388
|
-
/* @__PURE__ */ jsx4(Text3, { variant: "headlineLarge", style: [styles3.overlayTitle, { color: colors.danger }], children: "Delete Profile?" }),
|
|
389
|
-
/* @__PURE__ */ jsxs2(Text3, { variant: "bodyLarge", style: [styles3.overlayBody, { color: fg }], children: [
|
|
390
|
-
"This permanently deletes ",
|
|
391
|
-
confirmDeleteProfile.name,
|
|
392
|
-
"\u2019s profile. This cannot be undone."
|
|
393
|
-
] }),
|
|
394
|
-
/* @__PURE__ */ jsx4(Button, { mode: "contained", onPress: () => setConfirmDeleteId(null), style: styles3.overlayButton, buttonColor: cancelBg, textColor: cancelFg, children: "Cancel" }),
|
|
395
|
-
/* @__PURE__ */ jsx4(Button, { mode: "contained", onPress: handleConfirmDelete, style: styles3.overlayButton, buttonColor: colors.danger, textColor: colors.onDanger, children: "Delete" })
|
|
396
|
-
] }) }) })
|
|
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) })
|
|
397
512
|
] });
|
|
398
513
|
}
|
|
399
514
|
function AlignedColorPicker({ useAlign, ...props }) {
|
|
400
515
|
const { width: windowWidth } = useWindowDimensions();
|
|
401
|
-
const { width, height } = getInlineColorPickerContentSize(
|
|
516
|
+
const { width, height } = getInlineColorPickerContentSize(defaultColors2.length, windowWidth);
|
|
402
517
|
const alignOverride = useAlign(props.host.openId === props.id, width, height);
|
|
403
|
-
return /* @__PURE__ */
|
|
518
|
+
return /* @__PURE__ */ jsx5(InlineColorPicker, { ...props, alignOverride });
|
|
404
519
|
}
|
|
405
520
|
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
|
|
406
521
|
const register = useFocusChain();
|
|
@@ -414,10 +529,10 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
414
529
|
if (host.openId === "color") tagFocusRef.current();
|
|
415
530
|
}, [host.openId]);
|
|
416
531
|
return /* @__PURE__ */ jsxs2(View3, { style: styles3.editRow, children: [
|
|
417
|
-
useColorPickerAlign ? /* @__PURE__ */
|
|
418
|
-
/* @__PURE__ */
|
|
419
|
-
/* @__PURE__ */
|
|
420
|
-
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 }) })
|
|
421
536
|
] });
|
|
422
537
|
}
|
|
423
538
|
var styles3 = StyleSheet3.create({
|
|
@@ -425,11 +540,17 @@ var styles3 = StyleSheet3.create({
|
|
|
425
540
|
gap: 4,
|
|
426
541
|
paddingHorizontal: 20
|
|
427
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.
|
|
428
548
|
editRow: {
|
|
429
549
|
alignItems: "center",
|
|
430
550
|
flexDirection: "row",
|
|
431
551
|
gap: 8,
|
|
432
|
-
paddingVertical: 6
|
|
552
|
+
paddingVertical: 6,
|
|
553
|
+
zIndex: 1
|
|
433
554
|
},
|
|
434
555
|
header: {
|
|
435
556
|
alignItems: "center",
|
|
@@ -464,37 +585,16 @@ var styles3 = StyleSheet3.create({
|
|
|
464
585
|
borderStyle: "dashed",
|
|
465
586
|
borderWidth: 2
|
|
466
587
|
},
|
|
467
|
-
overlay: {
|
|
468
|
-
alignItems: "center",
|
|
469
|
-
backgroundColor: "rgba(0,0,0,0.72)",
|
|
470
|
-
bottom: 0,
|
|
471
|
-
justifyContent: "center",
|
|
472
|
-
left: 0,
|
|
473
|
-
position: "absolute",
|
|
474
|
-
right: 0,
|
|
475
|
-
top: 0
|
|
476
|
-
},
|
|
477
|
-
overlayBody: { textAlign: "center" },
|
|
478
|
-
overlayButton: { width: 160 },
|
|
479
|
-
overlayCard: {
|
|
480
|
-
alignItems: "center",
|
|
481
|
-
borderRadius: 20,
|
|
482
|
-
borderWidth: 1,
|
|
483
|
-
gap: 16,
|
|
484
|
-
maxWidth: 360,
|
|
485
|
-
padding: 32
|
|
486
|
-
},
|
|
487
|
-
overlayTitle: { fontWeight: "bold", marginBottom: -8 },
|
|
488
588
|
row: {
|
|
489
589
|
borderRadius: 8,
|
|
490
590
|
paddingVertical: 8
|
|
491
591
|
},
|
|
492
592
|
rowChip: {
|
|
493
593
|
alignItems: "center",
|
|
494
|
-
borderRadius:
|
|
495
|
-
height:
|
|
594
|
+
borderRadius: CHIP_SIZE2 / 2,
|
|
595
|
+
height: CHIP_SIZE2,
|
|
496
596
|
justifyContent: "center",
|
|
497
|
-
width:
|
|
597
|
+
width: CHIP_SIZE2
|
|
498
598
|
},
|
|
499
599
|
rowInner: {
|
|
500
600
|
alignItems: "center",
|
|
@@ -518,8 +618,8 @@ import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
|
|
|
518
618
|
import { IconButton } from "@rific/feedback-press";
|
|
519
619
|
import { useRef as useRef2 } from "react";
|
|
520
620
|
import { StyleSheet as StyleSheet4, View as View4 } from "react-native";
|
|
521
|
-
import { jsx as
|
|
522
|
-
function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride,
|
|
621
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
622
|
+
function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
|
|
523
623
|
const { dark } = useAutoPaperTheme2();
|
|
524
624
|
const managerRef = useRef2(null);
|
|
525
625
|
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
@@ -528,7 +628,7 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
|
|
|
528
628
|
managerRef.current?.commitPendingEdit();
|
|
529
629
|
onBack();
|
|
530
630
|
};
|
|
531
|
-
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" }) }) });
|
|
532
632
|
}
|
|
533
633
|
var styles4 = StyleSheet4.create({
|
|
534
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",
|
|
@@ -65,13 +65,13 @@
|
|
|
65
65
|
"@infinitetoken/eslint-config": "0.2.0",
|
|
66
66
|
"@infinitetoken/jest-config": "^0.4.0",
|
|
67
67
|
"@infinitetoken/tsconfig": "^0.5.0",
|
|
68
|
-
"@rific/auto-paper": "^0.
|
|
69
|
-
"@rific/core": "^0.
|
|
70
|
-
"@rific/feedback-press": "^0.
|
|
71
|
-
"@rific/focus-chain": "^0.4.
|
|
72
|
-
"@rific/toaster": "^0.
|
|
73
|
-
"@tastic/core": "^0.
|
|
74
|
-
"@tastic/hud": "^0.
|
|
68
|
+
"@rific/auto-paper": "^0.13.2",
|
|
69
|
+
"@rific/core": "^0.3.1",
|
|
70
|
+
"@rific/feedback-press": "^0.13.0",
|
|
71
|
+
"@rific/focus-chain": "^0.4.8",
|
|
72
|
+
"@rific/toaster": "^0.8.0",
|
|
73
|
+
"@tastic/core": "^0.14.0",
|
|
74
|
+
"@tastic/hud": "^0.19.0",
|
|
75
75
|
"@testing-library/dom": "^10.4.1",
|
|
76
76
|
"@testing-library/react": "^16.3.2",
|
|
77
77
|
"@types/jest": "^30.0.0",
|
|
@@ -85,14 +85,14 @@
|
|
|
85
85
|
"prettier": "^3.8.3",
|
|
86
86
|
"react": "^19.0.0",
|
|
87
87
|
"react-dom": "^19.0.0",
|
|
88
|
-
"react-native": "^0.
|
|
88
|
+
"react-native": "^0.86.3",
|
|
89
89
|
"react-native-paper": "^5.15.2",
|
|
90
90
|
"react-native-safe-area-context": "^5.0.0",
|
|
91
91
|
"tsup": "^8.0.0",
|
|
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",
|