@tastic/profile 0.3.8 → 0.7.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 +162 -15
- package/dist/index.d.mts +124 -3
- package/dist/index.d.ts +124 -3
- package/dist/index.js +183 -2
- package/dist/index.mjs +173 -1
- package/package.json +8 -7
- package/src/ProfilesManager.tsx +4 -1
- package/src/ProfilesScreen.tsx +83 -0
- package/src/index.ts +8 -1
- package/src/redux/createActionCreator.ts +21 -0
- package/src/redux/profileExtensionSlice.ts +55 -0
- package/src/redux/profileSelectionSlice.ts +81 -0
- package/src/redux/profilesSlice.ts +47 -0
- package/src/redux/resolveInitialProfiles.ts +26 -0
- package/src/redux/seatColorsSlice.ts +96 -0
- package/src/redux/useSharedProfilesSync.ts +74 -0
package/README.md
CHANGED
|
@@ -53,7 +53,17 @@ see its own doc comment for why that component is generic over your own `Profile
|
|
|
53
53
|
- **`ProfilesManager`** — the roster screen itself: create/rename/recolor/retag/delete, editing
|
|
54
54
|
inline in the row you tap, with a destructive-delete confirmation. No navigation of its own (no
|
|
55
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`.
|
|
56
|
+
persistence via `profiles`/`onCreate`/`onSave`/`onDelete`. `onCreate`/`onSave` both take a
|
|
57
|
+
`ProfileEditPatch` (`{ name, color, tag }`) — exported from this package's own public API (it was
|
|
58
|
+
a private, unexported interface inside `ProfilesManager.tsx` until `ProfilesScreen` below needed
|
|
59
|
+
to reference it from its own props).
|
|
60
|
+
- **`ProfilesScreen`** — the routed-screen shell around `ProfilesManager`: the container `View`,
|
|
61
|
+
theme-derived background/foreground, a back button, and the commit-pending-edit-before-navigate
|
|
62
|
+
composition every app in the fleet used to hand-roll on its own around `ProfilesManager` (see
|
|
63
|
+
"Routed screens: `ProfilesScreen`" below). Same `ProfileEditPatch`-shaped
|
|
64
|
+
`profiles`/`defaultColor`/`onCreate`/`onSave`/`onDelete` as `ProfilesManager` itself, plus
|
|
65
|
+
`onBack`. Owns its own `ProfilesManagerHandle` ref internally — there's no `ref` prop here, since
|
|
66
|
+
nothing outside it needs one anymore.
|
|
57
67
|
- **`loadSharedProfiles` / `saveSharedProfiles` / `isSharedProfileStoreAvailable`** — an *optional*
|
|
58
68
|
cross-app roster, shared between multiple apps of yours via a native iOS App Group (same Apple
|
|
59
69
|
Developer Team, same `"com.apple.security.application-groups"` entitlement value on every
|
|
@@ -67,7 +77,8 @@ see its own doc comment for why that component is generic over your own `Profile
|
|
|
67
77
|
TypeScript type.
|
|
68
78
|
|
|
69
79
|
```ts
|
|
70
|
-
//
|
|
80
|
+
// Conceptual sketch of the pattern — see "Redux helpers for the shared roster" below for a
|
|
81
|
+
// ready-made version of it.
|
|
71
82
|
const GROUP_ID = 'group.com.yourteam.yourgames'
|
|
72
83
|
const base = isSharedProfileStoreAvailable ? await loadSharedProfiles(GROUP_ID) : localBaseFallback
|
|
73
84
|
const profiles = base.map((p) => ({ ...p, controlScheme: localExtensions[p.id]?.controlScheme ?? DEFAULT_CONTROL_SCHEME }))
|
|
@@ -87,27 +98,163 @@ see its own doc comment for why that component is generic over your own `Profile
|
|
|
87
98
|
{ "expo": { "ios": { "entitlements": { "com.apple.security.application-groups": ["group.com.yourteam.yourgames"] } } } }
|
|
88
99
|
```
|
|
89
100
|
|
|
90
|
-
|
|
101
|
+
### Routed screens: `ProfilesScreen`
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
`ProfilesScreen` is the routed-screen shell every app in the fleet was hand-rolling on its own
|
|
104
|
+
around `ProfilesManager`: a container `View`, theme-derived bg/fg, a `headerLeft` back button, and
|
|
105
|
+
the two-line `managerRef.current?.commitPendingEdit(); onBack()` composition every app's own
|
|
106
|
+
back-button handler used to write out by hand (see `ProfilesManager`'s own `commitPendingEdit` doc
|
|
107
|
+
above for why that flush matters on a router that doesn't genuinely unmount a popped screen). It
|
|
108
|
+
owns that ref itself — there's no `ref` prop on `ProfilesScreen`, since nothing outside it needs
|
|
109
|
+
one any more.
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
Same `profiles`/`defaultColor`/`onCreate`/`onSave`/`onDelete` props as `ProfilesManager` — still
|
|
112
|
+
`ProfileEditPatch`-shaped, still deliberately not widened to your own richer `CreateProfileInput`;
|
|
113
|
+
compose any extra required fields (a key/control scheme, whatever's specific to your game) in your
|
|
114
|
+
own closure at the call site, exactly as you already would calling `ProfilesManager` directly —
|
|
115
|
+
plus `onBack`. `fg`/`fgMuted`/`bg`/`cardBg`/`titleVariant`/`colorPreview` are all independently
|
|
116
|
+
optional and forward straight through to the internal `ProfilesManager` (`bg` is new here —
|
|
117
|
+
`ProfilesManager` itself has no `bg`, since it doesn't own a container `View` of its own).
|
|
118
|
+
|
|
119
|
+
A whole routed screen, wired to your own store, is usually this small:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// app/profiles.tsx
|
|
123
|
+
import { defaultColors } from '@rific/auto-paper'
|
|
124
|
+
import { ProfilesScreen } from '@tastic/profile'
|
|
125
|
+
import { router } from 'expo-router'
|
|
126
|
+
|
|
127
|
+
import { useGameStats } from '@/hooks/useGameStats'
|
|
128
|
+
import { useProfiles } from '@/hooks/useProfiles'
|
|
129
|
+
|
|
130
|
+
export default function Profiles() {
|
|
131
|
+
const { profiles, createProfile, updateProfile, deleteProfile } = useProfiles()
|
|
132
|
+
// Only for wiring onDelete below — clears a deleted profile's own stats record so it doesn't
|
|
133
|
+
// stick around as an orphan once the profile itself is gone.
|
|
134
|
+
const { removeProfileStats } = useGameStats()
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<ProfilesScreen
|
|
138
|
+
profiles={profiles}
|
|
139
|
+
defaultColor={defaultColors[0].value}
|
|
140
|
+
onCreate={(patch) => createProfile(patch)}
|
|
141
|
+
onSave={(id, patch) => updateProfile(id, patch)}
|
|
142
|
+
onDelete={(id) => {
|
|
143
|
+
deleteProfile(id)
|
|
144
|
+
removeProfileStats(id)
|
|
145
|
+
}}
|
|
146
|
+
onBack={() => router.back()}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`useProfiles`/`useGameStats` are your own app's hooks, not exports of this package — `profiles` is
|
|
153
|
+
whatever `Profile[]` (or your own extended shape) you already keep in Redux/context/state, and
|
|
154
|
+
`createProfile`/`updateProfile`/`deleteProfile` are however you already persist it. `onBack` can be
|
|
155
|
+
anything with no arguments — `router.back()`, a `safeBack()`-style helper that no-ops with no back
|
|
156
|
+
stack behind it, `navigation.goBack()` — `ProfilesScreen` only ever calls it after flushing any
|
|
157
|
+
in-progress inline edit first.
|
|
158
|
+
|
|
159
|
+
### Redux helpers for the shared roster
|
|
160
|
+
|
|
161
|
+
The cross-app-roster sketch above is a pattern every `@tastic` game was hand-rolling on its own
|
|
162
|
+
(a roster reducer, a per-profile extension table, the initial-load decision, a foreground-resync
|
|
163
|
+
listener, a per-seat selection reducer). These five pieces are that logic extracted into reusable,
|
|
164
|
+
Redux-shaped building blocks — plain TypeScript, no dependency on `redux`/`react-redux` themselves,
|
|
165
|
+
so a host app plugs the reducer(s) into whatever store it already has:
|
|
166
|
+
|
|
167
|
+
- **`profilesActions` / `profilesReducer` / `createProfileRecord`** — the base roster as a reducer:
|
|
168
|
+
`add`/`update`/`remove`/`setAll` actions, plus `createProfileRecord(input)` to mint a complete
|
|
169
|
+
`Profile` (id + timestamps) ready to dispatch via `profilesActions.add`.
|
|
170
|
+
- **`createProfileExtensionSlice<TExtension>(namespace)`** — a factory for a host app's own
|
|
171
|
+
per-profile extension fields (a control scheme, a key scheme, ...): returns a
|
|
172
|
+
`{ actions: { set, remove }, reducer }` pair over a plain `Record<profileId, TExtension>`,
|
|
173
|
+
namespaced so more than one extension slice in the same app never collides. Not auto-cleared when
|
|
174
|
+
`profilesReducer` removes a profile — an app already dispatches its own cleanup action(s) on
|
|
175
|
+
delete, so this stays a self-contained reducer rather than new cross-slice wiring.
|
|
176
|
+
- **`resolveInitialProfiles(groupId, localFallback)`** — the one-time initial-load decision: shared
|
|
177
|
+
store unavailable → `localFallback` verbatim; shared store empty → seed it from `localFallback`;
|
|
178
|
+
otherwise the shared roster always wins over a possibly-stale local snapshot.
|
|
179
|
+
- **`useSharedProfilesSync({ groupId, onRemoteChange })`** — keeps a Redux-backed roster in sync for
|
|
180
|
+
the rest of the app's lifetime: call the returned `syncToShared(next)` right after a local roster
|
|
181
|
+
change to mirror it out, and `onRemoteChange` fires with a freshly-loaded roster whenever the app
|
|
182
|
+
returns to the foreground (skipped while a `syncToShared` write is still in flight, so a remote
|
|
183
|
+
refresh can't clobber a write that hasn't landed yet).
|
|
184
|
+
- **`createProfileSelectionSlice<TSeat>(namespace, defaultState)`** — a factory for the *other*
|
|
185
|
+
per-seat piece of state: which profile id each seat currently has selected (parallel to
|
|
186
|
+
`createProfileExtensionSlice` above, but keyed by seat instead of by profile id). Returns a
|
|
187
|
+
`{ actions: { select, clearProfile }, reducer }` pair over a plain `Record<TSeat, string | null>`.
|
|
188
|
+
Unlike `createProfileExtensionSlice`, the seat set can't be derived from `TSeat` alone (types
|
|
189
|
+
don't exist at runtime), so callers pass their own `defaultState` — the same literal object (e.g.
|
|
190
|
+
`{ 1: null, 2: null }`) every app already declares today. `select({ seat, profileId })` sets one
|
|
191
|
+
seat's selection directly; `clearProfile(profileId)` walks every seat and nulls out any that
|
|
192
|
+
currently points at that id — call it when a profile is deleted so no seat is left referencing a
|
|
193
|
+
ghost id. Always local, like the guest/CPU/override colors below — never part of the shared
|
|
194
|
+
App Group roster, since who's selected on *this* device is naturally per-app, per-device state.
|
|
98
195
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
196
|
+
## Per-seat color persistence
|
|
197
|
+
|
|
198
|
+
A loadout/lobby screen's color for a given seat is never just "the selected profile's saved
|
|
199
|
+
color" — a seat can be a guest (no profile selected), a CPU opponent, or a profile the player
|
|
200
|
+
manually recolored for a clash-swap without touching the profile itself. Every `@tastic` game
|
|
201
|
+
(AirHockey, BoxHockey, Pong, LightCycles, Snake) ends up needing the same three cooperating pieces
|
|
202
|
+
of state to cover all three cases. This is the established pattern every game should follow — it
|
|
203
|
+
was previously undocumented at the package level, which is exactly why two of the five apps
|
|
204
|
+
(AirHockey, Snake) didn't pick up the third piece below until after they'd already shipped without
|
|
205
|
+
it.
|
|
206
|
+
|
|
207
|
+
- **`lastGuestColor`** — a `Record<Seat, string>`: what a seat's color was the last time it was a
|
|
208
|
+
guest (no profile selected). Written only when a guest seat's color changes; a profile-selected
|
|
209
|
+
seat never reads or writes it.
|
|
210
|
+
- **`lastCpuColor`** — the same idea for the CPU slot, as a single remembered `string` rather than a
|
|
211
|
+
per-seat record (a CPU opponent only ever occupies one fixed seat in every shipped game so far).
|
|
212
|
+
- **`profileOverride`** — a `Record<Seat, string | null>`: a manual recolor or clash-swap landing on
|
|
213
|
+
a seat that currently has a profile selected. `null` means "no override — track the profile's own
|
|
214
|
+
saved color live." Cleared only when that seat's own profile selection genuinely changes, never by
|
|
215
|
+
merely leaving and returning to the screen.
|
|
216
|
+
|
|
217
|
+
None of these three are exports of this package today. `lastGuestColor`/`lastCpuColor` predate it
|
|
218
|
+
entirely, and `profileOverride` is still hand-rolled per app — typically alongside
|
|
219
|
+
`createProfileSelectionSlice`'s own selection state, in the same seat-scoped slice (see e.g.
|
|
220
|
+
BoxHockey's `src/redux/seatColorsSlice.ts`) or folded into the app's own `gameSlice.ts` when there's
|
|
221
|
+
no dedicated slice to put it in (see Snake's `src/redux/gameSlice.ts`). This section exists so a new
|
|
222
|
+
app's author reads the convention here first, rather than hand-rolling something that quietly can't
|
|
223
|
+
interop with the rest of the fleet.
|
|
224
|
+
|
|
225
|
+
The resolution order at a loadout/lobby screen, for one seat:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const color = profile
|
|
229
|
+
? (profileOverride[seat] ?? profile.color) // profile selected: override wins, else the profile's own color
|
|
230
|
+
: isCpu
|
|
231
|
+
? lastCpuColor // CPU slot: its own remembered color
|
|
232
|
+
: lastGuestColor[seat] // guest seat: that seat's own remembered color
|
|
102
233
|
```
|
|
103
234
|
|
|
104
|
-
|
|
105
|
-
|
|
235
|
+
`lastGuestColor`/`lastCpuColor` are only ever consulted when no profile is selected for that seat at
|
|
236
|
+
all — a profile-selected seat's guest/CPU memory is neither read nor overwritten while it's active,
|
|
237
|
+
so switching a seat back to "guest" later still recalls whatever guest color it had before a profile
|
|
238
|
+
was ever selected there.
|
|
239
|
+
|
|
240
|
+
## Install
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npm install @tastic/profile
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Published to the public npm registry via tag-based CI (OIDC trusted publishing — see
|
|
247
|
+
`npm run release:patch`/`:minor`/`:major`).
|
|
106
248
|
|
|
107
249
|
## Peer dependencies
|
|
108
250
|
|
|
109
251
|
`react`, `react-native`, `react-native-paper`, `react-native-safe-area-context`, `@rific/auto-paper`
|
|
110
252
|
(`getContrastColor`, `useAutoPaperTheme`), `@rific/feedback-press` (`TouchableRipple`),
|
|
111
253
|
`@rific/focus-chain` (`useFocusChain`), `@rific/toaster` (`useToast`), `@tastic/hud`
|
|
112
|
-
(`InlineColorPicker`, `PopoverBody`, `PopoverHost`, `useAutoAlign`, `usePopoverHost`)
|
|
113
|
-
are bundled, so use whatever versions your app
|
|
254
|
+
(`InlineColorPicker`, `PopoverBody`, `PopoverHost`, `useAutoAlign`, `usePopoverHost`), `@tastic/core`
|
|
255
|
+
(a peer of `@tastic/hud` itself) — none of these are bundled, so use whatever versions your app
|
|
256
|
+
already has.
|
|
257
|
+
|
|
258
|
+
`expo-modules-core` is an *optional* peer — only resolved by `loadSharedProfiles`/`saveSharedProfiles`
|
|
259
|
+
for the native App Group bridge described above; safe to omit entirely on an Android/web-only
|
|
260
|
+
consumer.
|
package/dist/index.d.mts
CHANGED
|
@@ -35,7 +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
|
+
type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
39
39
|
interface ProfileEditPatch {
|
|
40
40
|
name: string;
|
|
41
41
|
color: string;
|
|
@@ -55,18 +55,139 @@ interface ProfilesManagerProps {
|
|
|
55
55
|
fg?: string;
|
|
56
56
|
fgMuted?: string;
|
|
57
57
|
cardBg?: string;
|
|
58
|
-
titleVariant?: MD3TextVariant;
|
|
58
|
+
titleVariant?: MD3TextVariant$1;
|
|
59
59
|
colorPreview?: (hex: string) => string;
|
|
60
60
|
}
|
|
61
61
|
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant, colorPreview }: ProfilesManagerProps): react.JSX.Element;
|
|
62
62
|
|
|
63
|
+
type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
64
|
+
interface ProfilesScreenProps {
|
|
65
|
+
profiles: Profile[];
|
|
66
|
+
defaultColor: string;
|
|
67
|
+
onCreate: (patch: ProfileEditPatch) => void;
|
|
68
|
+
onSave: (id: string, patch: ProfileEditPatch) => void;
|
|
69
|
+
onDelete: (id: string) => void;
|
|
70
|
+
onBack: () => void;
|
|
71
|
+
fg?: string;
|
|
72
|
+
fgMuted?: string;
|
|
73
|
+
bg?: string;
|
|
74
|
+
cardBg?: string;
|
|
75
|
+
titleVariant?: MD3TextVariant;
|
|
76
|
+
colorPreview?: (hex: string) => string;
|
|
77
|
+
}
|
|
78
|
+
declare function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, cardBg, titleVariant, colorPreview }: ProfilesScreenProps): react.JSX.Element;
|
|
79
|
+
|
|
63
80
|
declare const MAX_PROFILE_NAME_LENGTH = 20;
|
|
64
81
|
declare const MAX_TAG_LENGTH = 3;
|
|
65
82
|
declare function isValidTag(value: unknown): value is string;
|
|
66
83
|
declare function isValidProfile(value: unknown): value is Profile;
|
|
67
84
|
|
|
85
|
+
interface ReduxAction<P> {
|
|
86
|
+
payload: P;
|
|
87
|
+
type: string;
|
|
88
|
+
}
|
|
89
|
+
type ActionCreator<P> = ((payload: P) => ReduxAction<P>) & {
|
|
90
|
+
type: string;
|
|
91
|
+
match: (action: {
|
|
92
|
+
type: string;
|
|
93
|
+
}) => action is ReduxAction<P>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
interface SetProfileExtensionPayload<TExtension> {
|
|
97
|
+
extension: TExtension;
|
|
98
|
+
id: string;
|
|
99
|
+
}
|
|
100
|
+
interface ProfileExtensionActions<TExtension> {
|
|
101
|
+
remove: ActionCreator<string>;
|
|
102
|
+
set: ActionCreator<SetProfileExtensionPayload<TExtension>>;
|
|
103
|
+
}
|
|
104
|
+
type ProfileExtensionState<TExtension> = Record<string, TExtension>;
|
|
105
|
+
interface ProfileExtensionSlice<TExtension> {
|
|
106
|
+
actions: ProfileExtensionActions<TExtension>;
|
|
107
|
+
reducer: (state: ProfileExtensionState<TExtension> | undefined, action: {
|
|
108
|
+
type: string;
|
|
109
|
+
}) => ProfileExtensionState<TExtension>;
|
|
110
|
+
}
|
|
111
|
+
declare function createProfileExtensionSlice<TExtension>(namespace: string): ProfileExtensionSlice<TExtension>;
|
|
112
|
+
|
|
113
|
+
interface SelectProfilePayload<TSeat> {
|
|
114
|
+
profileId: string | null;
|
|
115
|
+
seat: TSeat;
|
|
116
|
+
}
|
|
117
|
+
interface ProfileSelectionActions<TSeat> {
|
|
118
|
+
clearProfile: ActionCreator<string>;
|
|
119
|
+
select: ActionCreator<SelectProfilePayload<TSeat>>;
|
|
120
|
+
}
|
|
121
|
+
type ProfileSelectionState<TSeat extends string | number> = Record<TSeat, string | null>;
|
|
122
|
+
interface ProfileSelectionSlice<TSeat extends string | number> {
|
|
123
|
+
actions: ProfileSelectionActions<TSeat>;
|
|
124
|
+
reducer: (state: ProfileSelectionState<TSeat> | undefined, action: {
|
|
125
|
+
type: string;
|
|
126
|
+
payload?: unknown;
|
|
127
|
+
}) => ProfileSelectionState<TSeat>;
|
|
128
|
+
}
|
|
129
|
+
declare function createProfileSelectionSlice<TSeat extends string | number>(namespace: string, defaultState: ProfileSelectionState<TSeat>, mountKey?: string): ProfileSelectionSlice<TSeat>;
|
|
130
|
+
|
|
131
|
+
interface UpdateProfilePayload {
|
|
132
|
+
id: string;
|
|
133
|
+
patch: Partial<Pick<Profile, 'name' | 'color' | 'tag'>>;
|
|
134
|
+
}
|
|
135
|
+
declare const profilesActions: {
|
|
136
|
+
add: ActionCreator<Profile>;
|
|
137
|
+
remove: ActionCreator<string>;
|
|
138
|
+
setAll: ActionCreator<Profile[]>;
|
|
139
|
+
update: ActionCreator<UpdateProfilePayload>;
|
|
140
|
+
};
|
|
141
|
+
declare function profilesReducer(state: Profile[] | undefined, action: {
|
|
142
|
+
type: string;
|
|
143
|
+
}): Profile[];
|
|
144
|
+
interface CreateProfileInput {
|
|
145
|
+
name: string;
|
|
146
|
+
color: string;
|
|
147
|
+
tag: string;
|
|
148
|
+
}
|
|
149
|
+
declare function createProfileRecord(input: CreateProfileInput): Profile;
|
|
150
|
+
|
|
151
|
+
declare function resolveInitialProfiles(groupId: string, localFallback: Profile[]): Promise<Profile[]>;
|
|
152
|
+
|
|
153
|
+
interface SetLastGuestColorPayload<TSeat> {
|
|
154
|
+
color: string;
|
|
155
|
+
seat: TSeat;
|
|
156
|
+
}
|
|
157
|
+
interface SetProfileOverridePayload<TSeat> {
|
|
158
|
+
color: string | null;
|
|
159
|
+
seat: TSeat;
|
|
160
|
+
}
|
|
161
|
+
interface SeatColorsActions<TSeat> {
|
|
162
|
+
setLastCpuColor: ActionCreator<string>;
|
|
163
|
+
setLastGuestColor: ActionCreator<SetLastGuestColorPayload<TSeat>>;
|
|
164
|
+
setProfileOverride: ActionCreator<SetProfileOverridePayload<TSeat>>;
|
|
165
|
+
}
|
|
166
|
+
type SeatColorsState<TSeat extends string | number> = {
|
|
167
|
+
lastCpuColor: string;
|
|
168
|
+
lastGuestColor: Record<TSeat, string>;
|
|
169
|
+
profileOverride: Record<TSeat, string | null>;
|
|
170
|
+
};
|
|
171
|
+
interface SeatColorsSlice<TSeat extends string | number> {
|
|
172
|
+
actions: SeatColorsActions<TSeat>;
|
|
173
|
+
reducer: (state: SeatColorsState<TSeat> | undefined, action: {
|
|
174
|
+
type: string;
|
|
175
|
+
payload?: unknown;
|
|
176
|
+
}) => SeatColorsState<TSeat>;
|
|
177
|
+
}
|
|
178
|
+
declare function createSeatColorsSlice<TSeat extends string | number>(namespace: string, defaultState: SeatColorsState<TSeat>, mountKey?: string): SeatColorsSlice<TSeat>;
|
|
179
|
+
|
|
180
|
+
interface UseSharedProfilesSyncOptions {
|
|
181
|
+
groupId: string;
|
|
182
|
+
onRemoteChange: (profiles: Profile[]) => void;
|
|
183
|
+
}
|
|
184
|
+
interface UseSharedProfilesSyncResult {
|
|
185
|
+
syncToShared: (next: Profile[]) => void;
|
|
186
|
+
}
|
|
187
|
+
declare function useSharedProfilesSync({ groupId, onRemoteChange }: UseSharedProfilesSyncOptions): UseSharedProfilesSyncResult;
|
|
188
|
+
|
|
68
189
|
declare const isSharedProfileStoreAvailable: boolean;
|
|
69
190
|
declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
70
191
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
71
192
|
|
|
72
|
-
export { MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfilePicker, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, saveSharedProfiles };
|
|
193
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, resolveInitialProfiles, saveSharedProfiles, useSharedProfilesSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +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
|
+
type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
39
39
|
interface ProfileEditPatch {
|
|
40
40
|
name: string;
|
|
41
41
|
color: string;
|
|
@@ -55,18 +55,139 @@ interface ProfilesManagerProps {
|
|
|
55
55
|
fg?: string;
|
|
56
56
|
fgMuted?: string;
|
|
57
57
|
cardBg?: string;
|
|
58
|
-
titleVariant?: MD3TextVariant;
|
|
58
|
+
titleVariant?: MD3TextVariant$1;
|
|
59
59
|
colorPreview?: (hex: string) => string;
|
|
60
60
|
}
|
|
61
61
|
declare function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, cardBg: cardBgOverride, titleVariant, colorPreview }: ProfilesManagerProps): react.JSX.Element;
|
|
62
62
|
|
|
63
|
+
type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
|
|
64
|
+
interface ProfilesScreenProps {
|
|
65
|
+
profiles: Profile[];
|
|
66
|
+
defaultColor: string;
|
|
67
|
+
onCreate: (patch: ProfileEditPatch) => void;
|
|
68
|
+
onSave: (id: string, patch: ProfileEditPatch) => void;
|
|
69
|
+
onDelete: (id: string) => void;
|
|
70
|
+
onBack: () => void;
|
|
71
|
+
fg?: string;
|
|
72
|
+
fgMuted?: string;
|
|
73
|
+
bg?: string;
|
|
74
|
+
cardBg?: string;
|
|
75
|
+
titleVariant?: MD3TextVariant;
|
|
76
|
+
colorPreview?: (hex: string) => string;
|
|
77
|
+
}
|
|
78
|
+
declare function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, cardBg, titleVariant, colorPreview }: ProfilesScreenProps): react.JSX.Element;
|
|
79
|
+
|
|
63
80
|
declare const MAX_PROFILE_NAME_LENGTH = 20;
|
|
64
81
|
declare const MAX_TAG_LENGTH = 3;
|
|
65
82
|
declare function isValidTag(value: unknown): value is string;
|
|
66
83
|
declare function isValidProfile(value: unknown): value is Profile;
|
|
67
84
|
|
|
85
|
+
interface ReduxAction<P> {
|
|
86
|
+
payload: P;
|
|
87
|
+
type: string;
|
|
88
|
+
}
|
|
89
|
+
type ActionCreator<P> = ((payload: P) => ReduxAction<P>) & {
|
|
90
|
+
type: string;
|
|
91
|
+
match: (action: {
|
|
92
|
+
type: string;
|
|
93
|
+
}) => action is ReduxAction<P>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
interface SetProfileExtensionPayload<TExtension> {
|
|
97
|
+
extension: TExtension;
|
|
98
|
+
id: string;
|
|
99
|
+
}
|
|
100
|
+
interface ProfileExtensionActions<TExtension> {
|
|
101
|
+
remove: ActionCreator<string>;
|
|
102
|
+
set: ActionCreator<SetProfileExtensionPayload<TExtension>>;
|
|
103
|
+
}
|
|
104
|
+
type ProfileExtensionState<TExtension> = Record<string, TExtension>;
|
|
105
|
+
interface ProfileExtensionSlice<TExtension> {
|
|
106
|
+
actions: ProfileExtensionActions<TExtension>;
|
|
107
|
+
reducer: (state: ProfileExtensionState<TExtension> | undefined, action: {
|
|
108
|
+
type: string;
|
|
109
|
+
}) => ProfileExtensionState<TExtension>;
|
|
110
|
+
}
|
|
111
|
+
declare function createProfileExtensionSlice<TExtension>(namespace: string): ProfileExtensionSlice<TExtension>;
|
|
112
|
+
|
|
113
|
+
interface SelectProfilePayload<TSeat> {
|
|
114
|
+
profileId: string | null;
|
|
115
|
+
seat: TSeat;
|
|
116
|
+
}
|
|
117
|
+
interface ProfileSelectionActions<TSeat> {
|
|
118
|
+
clearProfile: ActionCreator<string>;
|
|
119
|
+
select: ActionCreator<SelectProfilePayload<TSeat>>;
|
|
120
|
+
}
|
|
121
|
+
type ProfileSelectionState<TSeat extends string | number> = Record<TSeat, string | null>;
|
|
122
|
+
interface ProfileSelectionSlice<TSeat extends string | number> {
|
|
123
|
+
actions: ProfileSelectionActions<TSeat>;
|
|
124
|
+
reducer: (state: ProfileSelectionState<TSeat> | undefined, action: {
|
|
125
|
+
type: string;
|
|
126
|
+
payload?: unknown;
|
|
127
|
+
}) => ProfileSelectionState<TSeat>;
|
|
128
|
+
}
|
|
129
|
+
declare function createProfileSelectionSlice<TSeat extends string | number>(namespace: string, defaultState: ProfileSelectionState<TSeat>, mountKey?: string): ProfileSelectionSlice<TSeat>;
|
|
130
|
+
|
|
131
|
+
interface UpdateProfilePayload {
|
|
132
|
+
id: string;
|
|
133
|
+
patch: Partial<Pick<Profile, 'name' | 'color' | 'tag'>>;
|
|
134
|
+
}
|
|
135
|
+
declare const profilesActions: {
|
|
136
|
+
add: ActionCreator<Profile>;
|
|
137
|
+
remove: ActionCreator<string>;
|
|
138
|
+
setAll: ActionCreator<Profile[]>;
|
|
139
|
+
update: ActionCreator<UpdateProfilePayload>;
|
|
140
|
+
};
|
|
141
|
+
declare function profilesReducer(state: Profile[] | undefined, action: {
|
|
142
|
+
type: string;
|
|
143
|
+
}): Profile[];
|
|
144
|
+
interface CreateProfileInput {
|
|
145
|
+
name: string;
|
|
146
|
+
color: string;
|
|
147
|
+
tag: string;
|
|
148
|
+
}
|
|
149
|
+
declare function createProfileRecord(input: CreateProfileInput): Profile;
|
|
150
|
+
|
|
151
|
+
declare function resolveInitialProfiles(groupId: string, localFallback: Profile[]): Promise<Profile[]>;
|
|
152
|
+
|
|
153
|
+
interface SetLastGuestColorPayload<TSeat> {
|
|
154
|
+
color: string;
|
|
155
|
+
seat: TSeat;
|
|
156
|
+
}
|
|
157
|
+
interface SetProfileOverridePayload<TSeat> {
|
|
158
|
+
color: string | null;
|
|
159
|
+
seat: TSeat;
|
|
160
|
+
}
|
|
161
|
+
interface SeatColorsActions<TSeat> {
|
|
162
|
+
setLastCpuColor: ActionCreator<string>;
|
|
163
|
+
setLastGuestColor: ActionCreator<SetLastGuestColorPayload<TSeat>>;
|
|
164
|
+
setProfileOverride: ActionCreator<SetProfileOverridePayload<TSeat>>;
|
|
165
|
+
}
|
|
166
|
+
type SeatColorsState<TSeat extends string | number> = {
|
|
167
|
+
lastCpuColor: string;
|
|
168
|
+
lastGuestColor: Record<TSeat, string>;
|
|
169
|
+
profileOverride: Record<TSeat, string | null>;
|
|
170
|
+
};
|
|
171
|
+
interface SeatColorsSlice<TSeat extends string | number> {
|
|
172
|
+
actions: SeatColorsActions<TSeat>;
|
|
173
|
+
reducer: (state: SeatColorsState<TSeat> | undefined, action: {
|
|
174
|
+
type: string;
|
|
175
|
+
payload?: unknown;
|
|
176
|
+
}) => SeatColorsState<TSeat>;
|
|
177
|
+
}
|
|
178
|
+
declare function createSeatColorsSlice<TSeat extends string | number>(namespace: string, defaultState: SeatColorsState<TSeat>, mountKey?: string): SeatColorsSlice<TSeat>;
|
|
179
|
+
|
|
180
|
+
interface UseSharedProfilesSyncOptions {
|
|
181
|
+
groupId: string;
|
|
182
|
+
onRemoteChange: (profiles: Profile[]) => void;
|
|
183
|
+
}
|
|
184
|
+
interface UseSharedProfilesSyncResult {
|
|
185
|
+
syncToShared: (next: Profile[]) => void;
|
|
186
|
+
}
|
|
187
|
+
declare function useSharedProfilesSync({ groupId, onRemoteChange }: UseSharedProfilesSyncOptions): UseSharedProfilesSyncResult;
|
|
188
|
+
|
|
68
189
|
declare const isSharedProfileStoreAvailable: boolean;
|
|
69
190
|
declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
70
191
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
71
192
|
|
|
72
|
-
export { MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfilePicker, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, saveSharedProfiles };
|
|
193
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, resolveInitialProfiles, saveSharedProfiles, useSharedProfilesSync };
|