@tastic/profile 0.1.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 ADDED
@@ -0,0 +1,81 @@
1
+ # @tastic/profile
2
+
3
+ Player profile identity for local-multiplayer React Native games — a saved name/color/tag a player
4
+ selects per seat, with a selection popover, an inline-editable roster screen, and a reusable identity
5
+ badge. Deliberately blind to anything app-specific: a control scheme, a stats bucket, or anything
6
+ else keyed by profile id lives entirely on the host app's own side.
7
+
8
+ ## Why a separate `Profile` type at all?
9
+
10
+ Every app built on this package needs its own extra fields on a profile — a key/gesture scheme, a
11
+ push/pull control mode, whatever's specific to that game. Baking any of those into this package's
12
+ own `Profile` type would mean every OTHER app carries fields it doesn't use. Instead, this package's
13
+ `Profile` is deliberately minimal:
14
+
15
+ ```ts
16
+ interface Profile {
17
+ id: string
18
+ name: string
19
+ color: string
20
+ tag: string
21
+ createdAt: number
22
+ updatedAt: number
23
+ }
24
+ ```
25
+
26
+ A host app extends this on its own (`interface Profile extends BaseProfile { keyScheme: KeyScheme }`)
27
+ and structural typing carries the richer shape straight through every component here that only ever
28
+ reads the base fields — no wrapper or adapter needed. The one place this needs a little more than
29
+ plain structural typing is `ProfilePicker`'s `onSelect` callback, which hands a profile back *out* —
30
+ see its own doc comment for why that component is generic over your own `Profile` type.
31
+
32
+ ## What's in here
33
+
34
+ - **`Profile`** — the base identity type described above.
35
+ - **`isValidProfile` / `isValidTag`** — hand-rolled validators (no RN import, so they stay usable in
36
+ plain-Node contexts like Jest without touching `react-native-paper`/`@rific/auto-paper`'s own
37
+ RN-touching barrels). `isValidTag` accepts up to `MAX_TAG_LENGTH` plain characters or exactly one
38
+ emoji (including a ZWJ-joined compound), and rejects mixing the two. `isValidProfile` checks only
39
+ this package's own base fields — a host app composes its own richer validator on top the same way
40
+ it extends the type:
41
+ ```ts
42
+ function isValidProfile(v: unknown): v is Profile {
43
+ return isValidBaseProfile(v) && isValidKeyScheme((v as Partial<Profile>).keyScheme)
44
+ }
45
+ ```
46
+ - **`ProfileChip`** — a profile's identity at a glance: its own color as a filled circle, with its
47
+ tag (or a generic fallback icon, if empty) in a contrast-safe color on top. `filled={false}` drops
48
+ the background fill for a selected-row context that already fills its own background.
49
+ - **`ProfilePicker`** — a name trigger + selection popover: pick an existing profile, fall back to a
50
+ null/"guest" selection, or (if `onManage` is passed) navigate to a management screen. Selection
51
+ only — creating, renaming, and deleting all live in `ProfilesManager`. Generic over your own
52
+ `Profile` type (see above).
53
+ - **`ProfilesManager`** — the roster screen itself: create/rename/recolor/retag/delete, editing
54
+ inline in the row you tap, with a destructive-delete confirmation. No navigation of its own (no
55
+ router, no back button) — a host app renders this as a routed screen's body and supplies its own
56
+ persistence via `profiles`/`onCreate`/`onSave`/`onDelete`.
57
+
58
+ ## Install (local dev via yalc)
59
+
60
+ Not published to the public npm registry yet.
61
+
62
+ ```bash
63
+ cd react-native-game-profile
64
+ npm run build
65
+ yalc publish
66
+
67
+ cd ../your-game
68
+ yalc add @tastic/profile
69
+ npm install
70
+ ```
71
+
72
+ Re-run `npm run build && yalc push` from this package after any change to propagate it to every
73
+ linked consumer at once.
74
+
75
+ ## Peer dependencies
76
+
77
+ `react`, `react-native`, `react-native-paper`, `react-native-safe-area-context`, `@rific/auto-paper`
78
+ (`getContrastColor`, `useAutoPaperTheme`), `@rific/feedback-press` (`TouchableRipple`),
79
+ `@rific/focus-chain` (`useFocusChain`), `@rific/toaster` (`useToast`), `@tastic/hud`
80
+ (`InlineColorPicker`, `PopoverBody`, `PopoverHost`, `useAutoAlign`, `usePopoverHost`) — none of these
81
+ are bundled, so use whatever versions your app already has.
@@ -0,0 +1,58 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { PopoverHost } from '@tastic/hud';
4
+
5
+ interface Profile {
6
+ id: string;
7
+ name: string;
8
+ color: string;
9
+ tag: string;
10
+ createdAt: number;
11
+ updatedAt: number;
12
+ }
13
+
14
+ interface Props {
15
+ profile: Profile;
16
+ size?: number;
17
+ filled?: boolean;
18
+ }
19
+ declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
20
+
21
+ interface ProfilePickerProps<P extends Profile> {
22
+ idPrefix: string;
23
+ host: PopoverHost;
24
+ profiles: P[];
25
+ selectedId: string | null;
26
+ takenId?: string | null;
27
+ color: string;
28
+ dark: boolean;
29
+ align?: 'left' | 'right' | 'center';
30
+ guestLabel: string;
31
+ nullLabel?: string;
32
+ nullIcon?: string;
33
+ onSelect: (profile: P | null) => void;
34
+ onManage?: () => void;
35
+ }
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
+
38
+ interface ProfileEditPatch {
39
+ name: string;
40
+ color: string;
41
+ tag: string;
42
+ }
43
+ interface ProfilesManagerProps {
44
+ profiles: Profile[];
45
+ defaultColor: string;
46
+ onCreate: (input: ProfileEditPatch) => void;
47
+ onSave: (id: string, patch: ProfileEditPatch) => void;
48
+ onDelete: (id: string) => void;
49
+ headerLeft?: ReactNode;
50
+ }
51
+ declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft }: ProfilesManagerProps): react.JSX.Element;
52
+
53
+ declare const MAX_PROFILE_NAME_LENGTH = 20;
54
+ declare const MAX_TAG_LENGTH = 3;
55
+ declare function isValidTag(value: unknown): value is string;
56
+ declare function isValidProfile(value: unknown): value is Profile;
57
+
58
+ export { MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfilePicker, ProfilesManager, type ProfilesManagerProps, isValidProfile, isValidTag };
@@ -0,0 +1,58 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { PopoverHost } from '@tastic/hud';
4
+
5
+ interface Profile {
6
+ id: string;
7
+ name: string;
8
+ color: string;
9
+ tag: string;
10
+ createdAt: number;
11
+ updatedAt: number;
12
+ }
13
+
14
+ interface Props {
15
+ profile: Profile;
16
+ size?: number;
17
+ filled?: boolean;
18
+ }
19
+ declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
20
+
21
+ interface ProfilePickerProps<P extends Profile> {
22
+ idPrefix: string;
23
+ host: PopoverHost;
24
+ profiles: P[];
25
+ selectedId: string | null;
26
+ takenId?: string | null;
27
+ color: string;
28
+ dark: boolean;
29
+ align?: 'left' | 'right' | 'center';
30
+ guestLabel: string;
31
+ nullLabel?: string;
32
+ nullIcon?: string;
33
+ onSelect: (profile: P | null) => void;
34
+ onManage?: () => void;
35
+ }
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
+
38
+ interface ProfileEditPatch {
39
+ name: string;
40
+ color: string;
41
+ tag: string;
42
+ }
43
+ interface ProfilesManagerProps {
44
+ profiles: Profile[];
45
+ defaultColor: string;
46
+ onCreate: (input: ProfileEditPatch) => void;
47
+ onSave: (id: string, patch: ProfileEditPatch) => void;
48
+ onDelete: (id: string) => void;
49
+ headerLeft?: ReactNode;
50
+ }
51
+ declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft }: ProfilesManagerProps): react.JSX.Element;
52
+
53
+ declare const MAX_PROFILE_NAME_LENGTH = 20;
54
+ declare const MAX_TAG_LENGTH = 3;
55
+ declare function isValidTag(value: unknown): value is string;
56
+ declare function isValidProfile(value: unknown): value is Profile;
57
+
58
+ export { MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfilePicker, ProfilesManager, type ProfilesManagerProps, isValidProfile, isValidTag };