@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/src/ProfilesManager.tsx
CHANGED
|
@@ -2,10 +2,10 @@ import { defaultColors, useAutoPaperTheme } from '@rific/auto-paper'
|
|
|
2
2
|
import { TouchableRipple } from '@rific/feedback-press'
|
|
3
3
|
import { useFocusChain } from '@rific/focus-chain'
|
|
4
4
|
import { useToast } from '@rific/toaster'
|
|
5
|
-
import { getInlineColorPickerContentSize, InlineColorPicker, PopoverHost, usePopoverHost } from '@tastic/hud'
|
|
5
|
+
import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, PopoverHost, usePopoverHost } from '@tastic/hud'
|
|
6
6
|
import { ComponentProps, ReactNode, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
|
7
7
|
import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
8
|
-
import {
|
|
8
|
+
import { Icon, Text, TextInput } from 'react-native-paper'
|
|
9
9
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
10
10
|
|
|
11
11
|
import { ProfileChip } from './ProfileChip'
|
|
@@ -62,17 +62,13 @@ export interface ProfilesManagerProps {
|
|
|
62
62
|
// React 19 accepts `ref` as a plain prop on a function component (no forwardRef needed) - see
|
|
63
63
|
// ProfilesManagerHandle's own doc for why a host may need this.
|
|
64
64
|
ref?: Ref<ProfilesManagerHandle>
|
|
65
|
-
// Independently optional overrides for this screen's text
|
|
66
|
-
// literal-black/white-by-appearance formula as before when omitted
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// BaseStatsScreen's identical reasoning for cardBorder: their low-alpha/neutral values already
|
|
71
|
-
// read fine against any cardBg, and (for Cancel specifically) are deliberately neutral rather than
|
|
72
|
-
// theme-derived so a host's own live gameplay/seat color can't land there by coincidence.
|
|
65
|
+
// Independently optional overrides for this screen's text colors - each defaults to the same
|
|
66
|
+
// literal-black/white-by-appearance formula as before when omitted. Same convention as @tastic/hud's
|
|
67
|
+
// BaseStatsScreen fg override, for a caller with its own app-wide chrome palette instead of that
|
|
68
|
+
// literal black/white convention. (cardBg, which only colored the delete confirmation, went with it
|
|
69
|
+
// when that became hud's ConfirmDialog, 0.13.0.)
|
|
73
70
|
fg?: string
|
|
74
71
|
fgMuted?: string
|
|
75
|
-
cardBg?: string
|
|
76
72
|
// 'headlineSmall' (this screen's own default, below), matching BaseStatsScreen's own settled
|
|
77
73
|
// default and the reasoning behind it - this title sits next to a small headerLeft back-button
|
|
78
74
|
// glyph the same way, and the previous hardcoded 'displaySmall' read as oversized there too.
|
|
@@ -115,12 +111,12 @@ function deriveTag(name: string): string {
|
|
|
115
111
|
// really unmounts it on a given host's router - see ProfilesManagerHandle's own doc for the
|
|
116
112
|
// react-navigation-web case where it doesn't, and why a host there needs the `ref` escape hatch
|
|
117
113
|
// instead of relying on the unmount fallback alone.
|
|
118
|
-
export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride,
|
|
114
|
+
export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant = 'headlineSmall', colorPreview: colorPreviewProp, useColorPickerAlign }: ProfilesManagerProps) {
|
|
119
115
|
// An explicit prop wins; otherwise the app-wide ProfileColorProvider's, so the swatch previews
|
|
120
116
|
// match the ProfileChips drawn in the list.
|
|
121
117
|
const contextColorPreview = useProfileColorPreview()
|
|
122
118
|
const colorPreview = colorPreviewProp ?? contextColorPreview
|
|
123
|
-
const { dark
|
|
119
|
+
const { dark } = useAutoPaperTheme()
|
|
124
120
|
const insets = useSafeAreaInsets()
|
|
125
121
|
const { error: showErrorToast } = useToast()
|
|
126
122
|
// Only one row is ever in edit mode at a time (see commitEdit, called before any new row opens),
|
|
@@ -128,12 +124,6 @@ export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDe
|
|
|
128
124
|
const host = usePopoverHost()
|
|
129
125
|
const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
|
|
130
126
|
const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
|
|
131
|
-
const cardBg = cardBgOverride ?? (dark ? '#111111' : '#F2F2F2')
|
|
132
|
-
const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
|
|
133
|
-
// A contained neutral, not an outlined one - reads as "the other button" next to Delete's solid
|
|
134
|
-
// danger fill, rather than looking unfinished/secondary beside it.
|
|
135
|
-
const cancelBg = dark ? '#2A2A2A' : '#E0E0E0'
|
|
136
|
-
const cancelFg = dark ? '#FFFFFF' : '#000000'
|
|
137
127
|
|
|
138
128
|
// 'new' clears back to null once a commit resolves - there's only ever one row open at a time.
|
|
139
129
|
const [editingId, setEditingId] = useState<string | 'new' | null>(null)
|
|
@@ -296,32 +286,11 @@ export function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDe
|
|
|
296
286
|
</ScrollView>
|
|
297
287
|
</KeyboardAvoidingView>
|
|
298
288
|
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
<Text variant='headlineLarge' style={[styles.overlayTitle, { color: colors.danger }]}>
|
|
305
|
-
Delete Profile?
|
|
306
|
-
</Text>
|
|
307
|
-
<Text variant='bodyLarge' style={[styles.overlayBody, { color: fg }]}>
|
|
308
|
-
This permanently deletes {confirmDeleteProfile.name}’s profile. This cannot be undone.
|
|
309
|
-
</Text>
|
|
310
|
-
{/* Contained neutral, not buttonColor={colors.primary} - colors.primary is often a
|
|
311
|
-
live gameplay/seat color in host apps, so a contained Cancel in that color would just
|
|
312
|
-
be whatever's currently painted there, with no relation to "safe to tap". */}
|
|
313
|
-
<Button mode='contained' onPress={() => setConfirmDeleteId(null)} style={styles.overlayButton} buttonColor={cancelBg} textColor={cancelFg}>
|
|
314
|
-
Cancel
|
|
315
|
-
</Button>
|
|
316
|
-
{/* colors.danger/onDanger - auto-paper's actual semantic role for this, not a seat's
|
|
317
|
-
own live color by luck of the default palette. */}
|
|
318
|
-
<Button mode='contained' onPress={handleConfirmDelete} style={styles.overlayButton} buttonColor={colors.danger} textColor={colors.onDanger}>
|
|
319
|
-
Delete
|
|
320
|
-
</Button>
|
|
321
|
-
</View>
|
|
322
|
-
</View>
|
|
323
|
-
</Portal>
|
|
324
|
-
)}
|
|
289
|
+
{/* hud's ConfirmDialog, the one confirm card every app in the fleet uses, so this reads like the
|
|
290
|
+
Reset Everything?/New Game confirms beside it (Jay, 2026-10-15: one look for every dialog). It
|
|
291
|
+
used to be its own card here, in this screen's literal black/white (or the host's cardBg), with
|
|
292
|
+
two stacked contained buttons. */}
|
|
293
|
+
<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 onConfirm={handleConfirmDelete} onCancel={() => setConfirmDeleteId(null)} />
|
|
325
294
|
</>
|
|
326
295
|
)
|
|
327
296
|
}
|
|
@@ -427,11 +396,17 @@ const styles = StyleSheet.create({
|
|
|
427
396
|
gap: 4,
|
|
428
397
|
paddingHorizontal: 20
|
|
429
398
|
},
|
|
399
|
+
// zIndex: the color popover lives inside this row, and a zIndex only orders siblings of one parent
|
|
400
|
+
// (React Native Web also gives every positioned view its own stacking context), so the popover's
|
|
401
|
+
// own zIndex never reached the rows after this one: they were drawn over it (a Kid row and the New
|
|
402
|
+
// Profile row across the swatches, 2026-10-16). Raising the row lifts the popover with it. There
|
|
403
|
+
// is only ever one edit row.
|
|
430
404
|
editRow: {
|
|
431
405
|
alignItems: 'center',
|
|
432
406
|
flexDirection: 'row',
|
|
433
407
|
gap: 8,
|
|
434
|
-
paddingVertical: 6
|
|
408
|
+
paddingVertical: 6,
|
|
409
|
+
zIndex: 1
|
|
435
410
|
},
|
|
436
411
|
header: {
|
|
437
412
|
alignItems: 'center',
|
|
@@ -466,27 +441,6 @@ const styles = StyleSheet.create({
|
|
|
466
441
|
borderStyle: 'dashed',
|
|
467
442
|
borderWidth: 2
|
|
468
443
|
},
|
|
469
|
-
overlay: {
|
|
470
|
-
alignItems: 'center',
|
|
471
|
-
backgroundColor: 'rgba(0,0,0,0.72)',
|
|
472
|
-
bottom: 0,
|
|
473
|
-
justifyContent: 'center',
|
|
474
|
-
left: 0,
|
|
475
|
-
position: 'absolute',
|
|
476
|
-
right: 0,
|
|
477
|
-
top: 0
|
|
478
|
-
},
|
|
479
|
-
overlayBody: { textAlign: 'center' },
|
|
480
|
-
overlayButton: { width: 160 },
|
|
481
|
-
overlayCard: {
|
|
482
|
-
alignItems: 'center',
|
|
483
|
-
borderRadius: 20,
|
|
484
|
-
borderWidth: 1,
|
|
485
|
-
gap: 16,
|
|
486
|
-
maxWidth: 360,
|
|
487
|
-
padding: 32
|
|
488
|
-
},
|
|
489
|
-
overlayTitle: { fontWeight: 'bold', marginBottom: -8 },
|
|
490
444
|
row: {
|
|
491
445
|
borderRadius: 8,
|
|
492
446
|
paddingVertical: 8
|
package/src/ProfilesScreen.tsx
CHANGED
|
@@ -33,11 +33,10 @@ export interface ProfilesScreenProps {
|
|
|
33
33
|
// Independently optional overrides, each defaulting to the same literal-black/white-by-appearance
|
|
34
34
|
// formula every current caller renders with unchanged (none of them pass these today) - same
|
|
35
35
|
// convention as BaseStatsScreen's and ProfilesManager's own fg/bg-shaped overrides. fg/fgMuted/
|
|
36
|
-
//
|
|
36
|
+
// titleVariant/colorPreview are forwarded straight through to the internal ProfilesManager.
|
|
37
37
|
fg?: string
|
|
38
38
|
fgMuted?: string
|
|
39
39
|
bg?: string
|
|
40
|
-
cardBg?: string
|
|
41
40
|
titleVariant?: MD3TextVariant
|
|
42
41
|
colorPreview?: (hex: string) => string
|
|
43
42
|
// Forwarded straight through to the internal ProfilesManager - a placement hook for the color swatch popover, for
|
|
@@ -53,7 +52,7 @@ export interface ProfilesScreenProps {
|
|
|
53
52
|
// still doesn't call useProfiles()/useGameStats() (or any host-specific hook) itself - those two
|
|
54
53
|
// hooks' shapes are genuinely app-specific (different CreateProfileInput/GameStatsContextValue per
|
|
55
54
|
// app), so this stays props-in/callbacks-out, exactly like ProfilesManager itself.
|
|
56
|
-
export function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride,
|
|
55
|
+
export function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }: ProfilesScreenProps) {
|
|
57
56
|
const { dark } = useAutoPaperTheme()
|
|
58
57
|
// Owns the ref itself - nothing outside this component ever needs it, since this is now the one
|
|
59
58
|
// thing that both renders ProfilesManager AND owns the only navigation trigger next to it.
|
|
@@ -75,7 +74,7 @@ export function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDel
|
|
|
75
74
|
|
|
76
75
|
return (
|
|
77
76
|
<View style={[styles.container, { backgroundColor: bg }]}>
|
|
78
|
-
<ProfilesManager ref={managerRef} profiles={profiles} defaultColor={defaultColor} onCreate={onCreate} onSave={onSave} onDelete={onDelete} fg={fg} fgMuted={fgMuted}
|
|
77
|
+
<ProfilesManager ref={managerRef} profiles={profiles} defaultColor={defaultColor} onCreate={onCreate} onSave={onSave} onDelete={onDelete} fg={fg} fgMuted={fgMuted} titleVariant={titleVariant} colorPreview={colorPreview} useColorPickerAlign={useColorPickerAlign} headerLeft={<IconButton icon='arrow-left' iconColor={fg} size={24} onPress={handleBack} accessibilityLabel='Back' />} />
|
|
79
78
|
</View>
|
|
80
79
|
)
|
|
81
80
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ProfileChip } from './ProfileChip'
|
|
2
2
|
export { ProfileColorProvider, useProfileColorPreview, useProfileDisplayColor } from './ProfileColorContext'
|
|
3
|
-
export { getProfilePickerContentSize, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerContentSize } from './ProfilePicker'
|
|
3
|
+
export { getProfilePickerContentSize, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize } from './ProfilePicker'
|
|
4
4
|
export { type ProfileEditPatch, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps } from './ProfilesManager'
|
|
5
5
|
export { ProfilesScreen, type ProfilesScreenProps } from './ProfilesScreen'
|
|
6
6
|
export { isValidProfile, isValidTag, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH } from './profilesValidation'
|