@tastic/profile 0.3.7 → 0.3.8
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/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +18 -18
- package/dist/index.mjs +18 -18
- package/package.json +6 -6
- package/src/ProfilesManager.tsx +43 -18
package/dist/index.d.mts
CHANGED
|
@@ -35,6 +35,7 @@ interface ProfilePickerProps<P extends Profile> {
|
|
|
35
35
|
}
|
|
36
36
|
declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: alignOverride, guestLabel, nullLabel, nullIcon, onSelect, onManage }: ProfilePickerProps<P>): react.JSX.Element;
|
|
37
37
|
|
|
38
|
+
type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
38
39
|
interface ProfileEditPatch {
|
|
39
40
|
name: string;
|
|
40
41
|
color: string;
|
|
@@ -51,8 +52,13 @@ interface ProfilesManagerProps {
|
|
|
51
52
|
onDelete: (id: string) => void;
|
|
52
53
|
headerLeft?: ReactNode;
|
|
53
54
|
ref?: Ref<ProfilesManagerHandle>;
|
|
55
|
+
fg?: string;
|
|
56
|
+
fgMuted?: string;
|
|
57
|
+
cardBg?: string;
|
|
58
|
+
titleVariant?: MD3TextVariant;
|
|
59
|
+
colorPreview?: (hex: string) => string;
|
|
54
60
|
}
|
|
55
|
-
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref }: ProfilesManagerProps): react.JSX.Element;
|
|
61
|
+
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant, colorPreview }: ProfilesManagerProps): react.JSX.Element;
|
|
56
62
|
|
|
57
63
|
declare const MAX_PROFILE_NAME_LENGTH = 20;
|
|
58
64
|
declare const MAX_TAG_LENGTH = 3;
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ interface ProfilePickerProps<P extends Profile> {
|
|
|
35
35
|
}
|
|
36
36
|
declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: alignOverride, guestLabel, nullLabel, nullIcon, onSelect, onManage }: ProfilePickerProps<P>): react.JSX.Element;
|
|
37
37
|
|
|
38
|
+
type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
38
39
|
interface ProfileEditPatch {
|
|
39
40
|
name: string;
|
|
40
41
|
color: string;
|
|
@@ -51,8 +52,13 @@ interface ProfilesManagerProps {
|
|
|
51
52
|
onDelete: (id: string) => void;
|
|
52
53
|
headerLeft?: ReactNode;
|
|
53
54
|
ref?: Ref<ProfilesManagerHandle>;
|
|
55
|
+
fg?: string;
|
|
56
|
+
fgMuted?: string;
|
|
57
|
+
cardBg?: string;
|
|
58
|
+
titleVariant?: MD3TextVariant;
|
|
59
|
+
colorPreview?: (hex: string) => string;
|
|
54
60
|
}
|
|
55
|
-
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref }: ProfilesManagerProps): react.JSX.Element;
|
|
61
|
+
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant, colorPreview }: ProfilesManagerProps): react.JSX.Element;
|
|
56
62
|
|
|
57
63
|
declare const MAX_PROFILE_NAME_LENGTH = 20;
|
|
58
64
|
declare const MAX_TAG_LENGTH = 3;
|
package/dist/index.js
CHANGED
|
@@ -287,14 +287,14 @@ function deriveTag(name) {
|
|
|
287
287
|
const trimmed = name.trim();
|
|
288
288
|
return trimmed ? trimmed[0].toUpperCase() : "";
|
|
289
289
|
}
|
|
290
|
-
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref }) {
|
|
290
|
+
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant = "headlineSmall", colorPreview }) {
|
|
291
291
|
const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
|
|
292
292
|
const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
|
|
293
293
|
const { error: showErrorToast } = (0, import_toaster.useToast)();
|
|
294
294
|
const host = (0, import_hud2.usePopoverHost)();
|
|
295
|
-
const fg = dark ? "#FFFFFF" : "#000000";
|
|
296
|
-
const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
|
|
297
|
-
const cardBg = dark ? "#111111" : "#F2F2F2";
|
|
295
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
296
|
+
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
297
|
+
const cardBg = cardBgOverride ?? (dark ? "#111111" : "#F2F2F2");
|
|
298
298
|
const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
|
|
299
299
|
const cancelBg = dark ? "#2A2A2A" : "#E0E0E0";
|
|
300
300
|
const cancelFg = dark ? "#FFFFFF" : "#000000";
|
|
@@ -363,7 +363,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
363
363
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
364
364
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
365
365
|
headerLeft,
|
|
366
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant:
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
|
|
367
367
|
] }),
|
|
368
368
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
369
369
|
if (row.kind === "new") {
|
|
@@ -373,11 +373,11 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
373
373
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
374
374
|
] }) }, "new");
|
|
375
375
|
}
|
|
376
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark }, "new");
|
|
376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview }, "new");
|
|
377
377
|
}
|
|
378
378
|
const { profile } = row;
|
|
379
379
|
if (editingId === profile.id) {
|
|
380
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark }, profile.id);
|
|
380
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview }, profile.id);
|
|
381
381
|
}
|
|
382
382
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
|
|
383
383
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProfileChip, { profile, size: CHIP_SIZE }),
|
|
@@ -397,7 +397,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
397
397
|
] }) }) })
|
|
398
398
|
] });
|
|
399
399
|
}
|
|
400
|
-
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark }) {
|
|
400
|
+
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview }) {
|
|
401
401
|
const register = (0, import_focus_chain.useFocusChain)();
|
|
402
402
|
const tag = register();
|
|
403
403
|
const name = register();
|
|
@@ -409,7 +409,7 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
409
409
|
if (host.openId === "color") tagFocusRef.current();
|
|
410
410
|
}, [host.openId]);
|
|
411
411
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
|
|
412
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, tag: draftTag, dark }),
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
413
413
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
|
|
414
414
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.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 }),
|
|
415
415
|
onDelete && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
@@ -432,6 +432,15 @@ var styles3 = import_react_native3.StyleSheet.create({
|
|
|
432
432
|
gap: 4,
|
|
433
433
|
paddingBottom: 8
|
|
434
434
|
},
|
|
435
|
+
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
436
|
+
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
437
|
+
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
438
|
+
hiddenTagInput: {
|
|
439
|
+
height: 1,
|
|
440
|
+
opacity: 0,
|
|
441
|
+
position: "absolute",
|
|
442
|
+
width: 1
|
|
443
|
+
},
|
|
435
444
|
iconButton: {
|
|
436
445
|
alignItems: "center",
|
|
437
446
|
borderRadius: 8,
|
|
@@ -493,15 +502,6 @@ var styles3 = import_react_native3.StyleSheet.create({
|
|
|
493
502
|
scrollView: {
|
|
494
503
|
flex: 1
|
|
495
504
|
},
|
|
496
|
-
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
497
|
-
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
498
|
-
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
499
|
-
hiddenTagInput: {
|
|
500
|
-
height: 1,
|
|
501
|
-
opacity: 0,
|
|
502
|
-
position: "absolute",
|
|
503
|
-
width: 1
|
|
504
|
-
},
|
|
505
505
|
title: {
|
|
506
506
|
flexShrink: 1,
|
|
507
507
|
fontWeight: "bold"
|
package/dist/index.mjs
CHANGED
|
@@ -259,14 +259,14 @@ function deriveTag(name) {
|
|
|
259
259
|
const trimmed = name.trim();
|
|
260
260
|
return trimmed ? trimmed[0].toUpperCase() : "";
|
|
261
261
|
}
|
|
262
|
-
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref }) {
|
|
262
|
+
function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant = "headlineSmall", colorPreview }) {
|
|
263
263
|
const { dark, colors } = useAutoPaperTheme();
|
|
264
264
|
const insets = useSafeAreaInsets();
|
|
265
265
|
const { error: showErrorToast } = useToast();
|
|
266
266
|
const host = usePopoverHost();
|
|
267
|
-
const fg = dark ? "#FFFFFF" : "#000000";
|
|
268
|
-
const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
|
|
269
|
-
const cardBg = dark ? "#111111" : "#F2F2F2";
|
|
267
|
+
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
268
|
+
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
269
|
+
const cardBg = cardBgOverride ?? (dark ? "#111111" : "#F2F2F2");
|
|
270
270
|
const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
|
|
271
271
|
const cancelBg = dark ? "#2A2A2A" : "#E0E0E0";
|
|
272
272
|
const cancelFg = dark ? "#FFFFFF" : "#000000";
|
|
@@ -335,7 +335,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
335
335
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
336
336
|
/* @__PURE__ */ jsxs2(View3, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
337
337
|
headerLeft,
|
|
338
|
-
/* @__PURE__ */ jsx3(Text3, { variant:
|
|
338
|
+
/* @__PURE__ */ jsx3(Text3, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
|
|
339
339
|
] }),
|
|
340
340
|
/* @__PURE__ */ jsx3(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ jsx3(ScrollView2, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
341
341
|
if (row.kind === "new") {
|
|
@@ -345,11 +345,11 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
345
345
|
/* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
346
346
|
] }) }, "new");
|
|
347
347
|
}
|
|
348
|
-
return /* @__PURE__ */ jsx3(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark }, "new");
|
|
348
|
+
return /* @__PURE__ */ jsx3(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview }, "new");
|
|
349
349
|
}
|
|
350
350
|
const { profile } = row;
|
|
351
351
|
if (editingId === profile.id) {
|
|
352
|
-
return /* @__PURE__ */ jsx3(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark }, profile.id);
|
|
352
|
+
return /* @__PURE__ */ jsx3(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview }, profile.id);
|
|
353
353
|
}
|
|
354
354
|
return /* @__PURE__ */ jsx3(TouchableRipple2, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.rowInner, children: [
|
|
355
355
|
/* @__PURE__ */ jsx3(ProfileChip, { profile, size: CHIP_SIZE }),
|
|
@@ -369,7 +369,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
369
369
|
] }) }) })
|
|
370
370
|
] });
|
|
371
371
|
}
|
|
372
|
-
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark }) {
|
|
372
|
+
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview }) {
|
|
373
373
|
const register = useFocusChain();
|
|
374
374
|
const tag = register();
|
|
375
375
|
const name = register();
|
|
@@ -381,7 +381,7 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
381
381
|
if (host.openId === "color") tagFocusRef.current();
|
|
382
382
|
}, [host.openId]);
|
|
383
383
|
return /* @__PURE__ */ jsxs2(View3, { style: styles3.editRow, children: [
|
|
384
|
-
/* @__PURE__ */ jsx3(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, tag: draftTag, dark }),
|
|
384
|
+
/* @__PURE__ */ jsx3(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
385
385
|
/* @__PURE__ */ jsx3(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
|
|
386
386
|
/* @__PURE__ */ jsx3(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 }),
|
|
387
387
|
onDelete && /* @__PURE__ */ jsx3(TouchableRipple2, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ jsx3(Icon3, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
@@ -404,6 +404,15 @@ var styles3 = StyleSheet3.create({
|
|
|
404
404
|
gap: 4,
|
|
405
405
|
paddingBottom: 8
|
|
406
406
|
},
|
|
407
|
+
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
408
|
+
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
409
|
+
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
410
|
+
hiddenTagInput: {
|
|
411
|
+
height: 1,
|
|
412
|
+
opacity: 0,
|
|
413
|
+
position: "absolute",
|
|
414
|
+
width: 1
|
|
415
|
+
},
|
|
407
416
|
iconButton: {
|
|
408
417
|
alignItems: "center",
|
|
409
418
|
borderRadius: 8,
|
|
@@ -465,15 +474,6 @@ var styles3 = StyleSheet3.create({
|
|
|
465
474
|
scrollView: {
|
|
466
475
|
flex: 1
|
|
467
476
|
},
|
|
468
|
-
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
469
|
-
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
470
|
-
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
471
|
-
hiddenTagInput: {
|
|
472
|
-
height: 1,
|
|
473
|
-
opacity: 0,
|
|
474
|
-
position: "absolute",
|
|
475
|
-
width: 1
|
|
476
|
-
},
|
|
477
477
|
title: {
|
|
478
478
|
flexShrink: 1,
|
|
479
479
|
fontWeight: "bold"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/profile",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
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",
|
|
@@ -62,15 +62,15 @@
|
|
|
62
62
|
},
|
|
63
63
|
"prettier": "@infinitetoken/eslint-config/prettier",
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@infinitetoken/eslint-config": "
|
|
66
|
-
"@infinitetoken/jest-config": "^0.2.
|
|
67
|
-
"@infinitetoken/tsconfig": "^0.
|
|
65
|
+
"@infinitetoken/eslint-config": "0.2.0",
|
|
66
|
+
"@infinitetoken/jest-config": "^0.2.4",
|
|
67
|
+
"@infinitetoken/tsconfig": "^0.4.1",
|
|
68
68
|
"@rific/auto-paper": "^0.10.3",
|
|
69
69
|
"@rific/feedback-press": "^0.10.8",
|
|
70
70
|
"@rific/focus-chain": "^0.4.7",
|
|
71
71
|
"@rific/toaster": "^0.6.3",
|
|
72
|
-
"@tastic/core": "^0.
|
|
73
|
-
"@tastic/hud": "^0.
|
|
72
|
+
"@tastic/core": "^0.2.0",
|
|
73
|
+
"@tastic/hud": "^0.6.0",
|
|
74
74
|
"@testing-library/dom": "^10.4.1",
|
|
75
75
|
"@testing-library/react": "^16.3.2",
|
|
76
76
|
"@types/jest": "^30.0.0",
|
package/src/ProfilesManager.tsx
CHANGED
|
@@ -16,6 +16,11 @@ import { Profile } from './types'
|
|
|
16
16
|
// size for every circle on this screen, rather than each picking its own.
|
|
17
17
|
const CHIP_SIZE = 40
|
|
18
18
|
|
|
19
|
+
// react-native-paper's own MD3 Text variant names — mirrored locally rather than importing its
|
|
20
|
+
// internal VariantProp/MD3TypescaleKey (not exported from the package's public entry point), same
|
|
21
|
+
// as @tastic/hud's BaseStatsScreen does for its own identical titleVariant prop.
|
|
22
|
+
type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall'
|
|
23
|
+
|
|
19
24
|
interface ProfileEditPatch {
|
|
20
25
|
name: string
|
|
21
26
|
color: string
|
|
@@ -52,6 +57,25 @@ export interface ProfilesManagerProps {
|
|
|
52
57
|
// React 19 accepts `ref` as a plain prop on a function component (no forwardRef needed) — see
|
|
53
58
|
// ProfilesManagerHandle's own doc for why a host may need this.
|
|
54
59
|
ref?: Ref<ProfilesManagerHandle>
|
|
60
|
+
// Independently optional overrides for this screen's text/card colors — each defaults to the same
|
|
61
|
+
// literal-black/white-by-appearance formula as before when omitted, so every existing caller
|
|
62
|
+
// (none of which pass these) renders identically to before. Same convention as @tastic/hud's
|
|
63
|
+
// BaseStatsScreen fg/cardBg overrides, for a caller with its own app-wide chrome palette instead
|
|
64
|
+
// of that literal black/white convention. cardBorder/cancelBg/cancelFg have no override, matching
|
|
65
|
+
// BaseStatsScreen's identical reasoning for cardBorder: their low-alpha/neutral values already
|
|
66
|
+
// read fine against any cardBg, and (for Cancel specifically) are deliberately neutral rather than
|
|
67
|
+
// theme-derived so a host's own live gameplay/seat color can't land there by coincidence.
|
|
68
|
+
fg?: string
|
|
69
|
+
fgMuted?: string
|
|
70
|
+
cardBg?: string
|
|
71
|
+
// 'headlineSmall' (this screen's own default, below), matching BaseStatsScreen's own settled
|
|
72
|
+
// default and the reasoning behind it — this title sits next to a small headerLeft back-button
|
|
73
|
+
// glyph the same way, and the previous hardcoded 'displaySmall' read as oversized there too.
|
|
74
|
+
titleVariant?: MD3TextVariant
|
|
75
|
+
// Transforms the color swatch grid's own preview per-swatch — forwarded straight to the internal
|
|
76
|
+
// InlineColorPicker's identical prop (see its own doc). Leaves draftColor/onCreate/onSave's actual
|
|
77
|
+
// color value untouched; only what's rendered changes.
|
|
78
|
+
colorPreview?: (hex: string) => string
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
type Row = { kind: 'profile'; profile: Profile } | { kind: 'new' }
|
|
@@ -74,16 +98,16 @@ function deriveTag(name: string): string {
|
|
|
74
98
|
// really unmounts it on a given host's router — see ProfilesManagerHandle's own doc for the
|
|
75
99
|
// react-navigation-web case where it doesn't, and why a host there needs the `ref` escape hatch
|
|
76
100
|
// instead of relying on the unmount fallback alone.
|
|
77
|
-
export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref }: ProfilesManagerProps) {
|
|
101
|
+
export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant = 'headlineSmall', colorPreview }: ProfilesManagerProps) {
|
|
78
102
|
const { dark, colors } = useAutoPaperTheme()
|
|
79
103
|
const insets = useSafeAreaInsets()
|
|
80
104
|
const { error: showErrorToast } = useToast()
|
|
81
105
|
// Only one row is ever in edit mode at a time (see commitEdit, called before any new row opens),
|
|
82
106
|
// so every EditRow instance can safely share this one host instead of each needing its own.
|
|
83
107
|
const host = usePopoverHost()
|
|
84
|
-
const fg = dark ? '#FFFFFF' : '#000000'
|
|
85
|
-
const fgMuted = dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)'
|
|
86
|
-
const cardBg = dark ? '#111111' : '#F2F2F2'
|
|
108
|
+
const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
|
|
109
|
+
const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
|
|
110
|
+
const cardBg = cardBgOverride ?? (dark ? '#111111' : '#F2F2F2')
|
|
87
111
|
const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
88
112
|
// A contained neutral, not an outlined one — reads as "the other button" next to Delete's solid
|
|
89
113
|
// danger fill, rather than looking unfinished/secondary beside it.
|
|
@@ -198,7 +222,7 @@ export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDe
|
|
|
198
222
|
<>
|
|
199
223
|
<View style={[styles.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }]}>
|
|
200
224
|
{headerLeft}
|
|
201
|
-
<Text variant=
|
|
225
|
+
<Text variant={titleVariant} style={[styles.title, { color: fg }]}>
|
|
202
226
|
Profiles
|
|
203
227
|
</Text>
|
|
204
228
|
</View>
|
|
@@ -226,12 +250,12 @@ export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDe
|
|
|
226
250
|
</TouchableRipple>
|
|
227
251
|
)
|
|
228
252
|
}
|
|
229
|
-
return <EditRow key='new' draftName={draftName} onNameChange={handleNameChange} draftColor={draftColor} onColorChange={setDraftColor} draftTag={draftTag} onTagChange={handleTagChange} onSubmit={commitEdit} autoFocus fgMuted={fgMuted} host={host} dark={dark} />
|
|
253
|
+
return <EditRow key='new' draftName={draftName} onNameChange={handleNameChange} draftColor={draftColor} onColorChange={setDraftColor} draftTag={draftTag} onTagChange={handleTagChange} onSubmit={commitEdit} autoFocus fgMuted={fgMuted} host={host} dark={dark} colorPreview={colorPreview} />
|
|
230
254
|
}
|
|
231
255
|
|
|
232
256
|
const { profile } = row
|
|
233
257
|
if (editingId === profile.id) {
|
|
234
|
-
return <EditRow key={profile.id} draftName={draftName} onNameChange={handleNameChange} draftColor={draftColor} onColorChange={setDraftColor} draftTag={draftTag} onTagChange={handleTagChange} onSubmit={commitEdit} onDelete={() => setConfirmDeleteId(profile.id)} fgMuted={fgMuted} host={host} dark={dark} />
|
|
258
|
+
return <EditRow key={profile.id} draftName={draftName} onNameChange={handleNameChange} draftColor={draftColor} onColorChange={setDraftColor} draftTag={draftTag} onTagChange={handleTagChange} onSubmit={commitEdit} onDelete={() => setConfirmDeleteId(profile.id)} fgMuted={fgMuted} host={host} dark={dark} colorPreview={colorPreview} />
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
// No delete affordance at rest — tap the row to start editing, which is the one place
|
|
@@ -295,6 +319,7 @@ interface EditRowProps {
|
|
|
295
319
|
fgMuted: string
|
|
296
320
|
host: PopoverHost
|
|
297
321
|
dark: boolean
|
|
322
|
+
colorPreview?: (hex: string) => string
|
|
298
323
|
}
|
|
299
324
|
|
|
300
325
|
// One row, expanded: the color trigger (@tastic/hud's InlineColorPicker) — doubling as the tag's
|
|
@@ -309,7 +334,7 @@ interface EditRowProps {
|
|
|
309
334
|
// own — submitting the *name* field is what actually commits the row, so its registration's own
|
|
310
335
|
// onSubmitEditing (a no-op — it's last in the chain) is overridden with onSubmit below, matching
|
|
311
336
|
// how a normal multi-field form reads: fill fields in order, the last one finishes it.
|
|
312
|
-
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark }: EditRowProps) {
|
|
337
|
+
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview }: EditRowProps) {
|
|
313
338
|
const register = useFocusChain()
|
|
314
339
|
const tag = register()
|
|
315
340
|
const name = register()
|
|
@@ -336,7 +361,7 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
336
361
|
|
|
337
362
|
return (
|
|
338
363
|
<View style={styles.editRow}>
|
|
339
|
-
<InlineColorPicker id='color' host={host} value={draftColor} onChange={onColorChange} tag={draftTag} dark={dark} />
|
|
364
|
+
<InlineColorPicker id='color' host={host} value={draftColor} onChange={onColorChange} previewValue={colorPreview} tag={draftTag} dark={dark} />
|
|
340
365
|
{/* Invisible on purpose (styles.hiddenTagInput: zero footprint, position: 'absolute' so it
|
|
341
366
|
doesn't reserve space in the row's own flex layout) — the swatch above is the only place this
|
|
342
367
|
value is ever meant to be seen. pointerEvents='none' is what actually keeps it out of the
|
|
@@ -380,6 +405,15 @@ const styles = StyleSheet.create({
|
|
|
380
405
|
gap: 4,
|
|
381
406
|
paddingBottom: 8
|
|
382
407
|
},
|
|
408
|
+
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
409
|
+
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
410
|
+
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
411
|
+
hiddenTagInput: {
|
|
412
|
+
height: 1,
|
|
413
|
+
opacity: 0,
|
|
414
|
+
position: 'absolute',
|
|
415
|
+
width: 1
|
|
416
|
+
},
|
|
383
417
|
iconButton: {
|
|
384
418
|
alignItems: 'center',
|
|
385
419
|
borderRadius: 8,
|
|
@@ -441,15 +475,6 @@ const styles = StyleSheet.create({
|
|
|
441
475
|
scrollView: {
|
|
442
476
|
flex: 1
|
|
443
477
|
},
|
|
444
|
-
// 1x1 and fully transparent — there's nothing to see here on purpose (see EditRow's own comment
|
|
445
|
-
// above the field this styles). position: 'absolute' pulls it out of editRow's flex flow
|
|
446
|
-
// entirely, so it doesn't reserve a gap in the row the way a normal flex sibling would.
|
|
447
|
-
hiddenTagInput: {
|
|
448
|
-
height: 1,
|
|
449
|
-
opacity: 0,
|
|
450
|
-
position: 'absolute',
|
|
451
|
-
width: 1
|
|
452
|
-
},
|
|
453
478
|
title: {
|
|
454
479
|
flexShrink: 1,
|
|
455
480
|
fontWeight: 'bold'
|