@tastic/profile 0.16.1 → 0.16.2
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 +8 -5
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +14 -7
- package/dist/index.mjs +14 -7
- package/package.json +1 -1
- package/src/IdentityChip.tsx +4 -3
- package/src/PickerMenu.tsx +8 -5
- package/src/ProfileColorContext.tsx +16 -7
- package/src/SwatchLayer.tsx +12 -6
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -310,16 +310,19 @@ untouched. `useProfileColorPreview()` returns the provider's function (or `undef
|
|
|
310
310
|
|
|
311
311
|
`renderSwatch` (also optional, with or without `preview`) draws the fill of every round swatch this
|
|
312
312
|
package draws, in place of the flat color: `ProfileChip`, the pickers' chip trigger, the Color row's
|
|
313
|
-
swatch and the color page's swatches. It's called as `renderSwatch(color, size)` with the
|
|
314
|
-
color (after `preview`)
|
|
315
|
-
|
|
316
|
-
the
|
|
313
|
+
swatch and the color page's swatches. It's called as `renderSwatch(color, size, place)` with the
|
|
314
|
+
drawn color (after `preview`), the circle's diameter and where the swatch is (`ProfileSwatchPlace`:
|
|
315
|
+
`'chip'`, `'colorRow'` or `'colorPage'`, 0.16.2+), and its drawing is clipped to the circle, behind
|
|
316
|
+
the tag, icon or check. Return `null` (or `undefined`) to keep a swatch's flat fill there. The tag and
|
|
317
|
+
check still take their contrast from `color`, so treat it as the primary color of whatever you draw.
|
|
318
|
+
Hangman draws a pie of each color's triad where colors are chosen, and keeps its chips flat so their
|
|
319
|
+
tags stay legible:
|
|
317
320
|
|
|
318
321
|
```tsx
|
|
319
322
|
import { getTriadicPalette } from '@rific/auto-paper'
|
|
320
323
|
import { ProfileColorProvider, type ProfileSwatchRenderer } from '@tastic/profile'
|
|
321
324
|
|
|
322
|
-
const renderSwatch: ProfileSwatchRenderer = (color, size) => <TriadPie colors={getTriadicPalette(color)} size={size} />
|
|
325
|
+
const renderSwatch: ProfileSwatchRenderer = (color, size, place) => (place === 'chip' ? null : <TriadPie colors={getTriadicPalette(color)} size={size} />)
|
|
323
326
|
<ProfileColorProvider renderSwatch={renderSwatch}>{children}</ProfileColorProvider>
|
|
324
327
|
```
|
|
325
328
|
|
package/dist/index.d.mts
CHANGED
|
@@ -67,7 +67,8 @@ interface Props {
|
|
|
67
67
|
}
|
|
68
68
|
declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
|
|
69
69
|
|
|
70
|
-
type
|
|
70
|
+
type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
|
|
71
|
+
type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
|
|
71
72
|
interface ProfileColorProviderProps {
|
|
72
73
|
preview?: (hex: string) => string;
|
|
73
74
|
renderSwatch?: ProfileSwatchRenderer;
|
|
@@ -266,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
|
266
267
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
267
268
|
declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
|
|
268
269
|
|
|
269
|
-
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
|
270
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchPlace, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -67,7 +67,8 @@ interface Props {
|
|
|
67
67
|
}
|
|
68
68
|
declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
|
|
69
69
|
|
|
70
|
-
type
|
|
70
|
+
type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
|
|
71
|
+
type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
|
|
71
72
|
interface ProfileColorProviderProps {
|
|
72
73
|
preview?: (hex: string) => string;
|
|
73
74
|
renderSwatch?: ProfileSwatchRenderer;
|
|
@@ -266,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
|
|
|
266
267
|
declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
|
|
267
268
|
declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
|
|
268
269
|
|
|
269
|
-
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
|
270
|
+
export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchPlace, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
|
package/dist/index.js
CHANGED
|
@@ -88,8 +88,11 @@ function useProfileDisplayColor(hex) {
|
|
|
88
88
|
// src/SwatchLayer.tsx
|
|
89
89
|
var import_react_native = require("react-native");
|
|
90
90
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
91
|
-
function
|
|
92
|
-
return
|
|
91
|
+
function drawSwatch(render, color, size, place) {
|
|
92
|
+
return render ? render(color, size, place) ?? null : null;
|
|
93
|
+
}
|
|
94
|
+
function SwatchLayer({ size, children }) {
|
|
95
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children });
|
|
93
96
|
}
|
|
94
97
|
var styles = import_react_native.StyleSheet.create({
|
|
95
98
|
layer: {
|
|
@@ -106,10 +109,11 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
106
109
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
107
110
|
function IdentityChip({ color, tag, icon, size, filled = true }) {
|
|
108
111
|
const renderSwatch = useProfileSwatchRenderer();
|
|
112
|
+
const drawing = filled ? drawSwatch(renderSwatch, color, size, "chip") : null;
|
|
109
113
|
const contrastColor = (0, import_auto_paper.getContrastColor)(color);
|
|
110
114
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
|
|
111
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled &&
|
|
112
|
-
|
|
115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled && drawing === null && { backgroundColor: color }], children: [
|
|
116
|
+
drawing !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SwatchLayer, { size, children: drawing }),
|
|
113
117
|
tag ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Text, { style: [styles2.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor })
|
|
114
118
|
] });
|
|
115
119
|
}
|
|
@@ -221,9 +225,10 @@ function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
|
221
225
|
const { value, previewValue, label = "Color", caption } = section;
|
|
222
226
|
const renderSwatch = useProfileSwatchRenderer();
|
|
223
227
|
const drawn = previewValue ? previewValue(value) : value;
|
|
228
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, "colorRow");
|
|
224
229
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
225
230
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: pickerMenuStyles.rowInner, children: [
|
|
226
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.colorRowSwatch,
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.colorRowSwatch, drawing === null && { backgroundColor: drawn }], children: drawing !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { size: COLOR_ROW_SWATCH_SIZE, children: drawing }) }),
|
|
227
232
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.colorRowText, children: [
|
|
228
233
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
229
234
|
caption ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
@@ -250,9 +255,11 @@ function ColorPage({ section, homeColor, fg, onBack }) {
|
|
|
250
255
|
const swatchDisplay = displayFor(swatch.value);
|
|
251
256
|
const contrast = (0, import_auto_paper2.getContrastColor)(swatchDisplay);
|
|
252
257
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
|
|
258
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE;
|
|
259
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, "colorPage");
|
|
253
260
|
const mark = selected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, {});
|
|
254
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch,
|
|
255
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, {
|
|
261
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch, drawing === null && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: drawing !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.swatchFace, children: [
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { size: drawnSize, children: drawing }),
|
|
256
263
|
mark
|
|
257
264
|
] }) : mark }, swatch.value);
|
|
258
265
|
}) })
|
package/dist/index.mjs
CHANGED
|
@@ -40,8 +40,11 @@ function useProfileDisplayColor(hex) {
|
|
|
40
40
|
// src/SwatchLayer.tsx
|
|
41
41
|
import { StyleSheet, View } from "react-native";
|
|
42
42
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
43
|
-
function
|
|
44
|
-
return
|
|
43
|
+
function drawSwatch(render, color, size, place) {
|
|
44
|
+
return render ? render(color, size, place) ?? null : null;
|
|
45
|
+
}
|
|
46
|
+
function SwatchLayer({ size, children }) {
|
|
47
|
+
return /* @__PURE__ */ jsx2(View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children });
|
|
45
48
|
}
|
|
46
49
|
var styles = StyleSheet.create({
|
|
47
50
|
layer: {
|
|
@@ -58,10 +61,11 @@ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
|
58
61
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
59
62
|
function IdentityChip({ color, tag, icon, size, filled = true }) {
|
|
60
63
|
const renderSwatch = useProfileSwatchRenderer();
|
|
64
|
+
const drawing = filled ? drawSwatch(renderSwatch, color, size, "chip") : null;
|
|
61
65
|
const contrastColor = getContrastColor(color);
|
|
62
66
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
|
|
63
|
-
return /* @__PURE__ */ jsxs(View2, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled &&
|
|
64
|
-
|
|
67
|
+
return /* @__PURE__ */ jsxs(View2, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled && drawing === null && { backgroundColor: color }], children: [
|
|
68
|
+
drawing !== null && /* @__PURE__ */ jsx3(SwatchLayer, { size, children: drawing }),
|
|
65
69
|
tag ? /* @__PURE__ */ jsx3(Text, { style: [styles2.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx3(Icon, { source: icon, size: size * 0.6, color: contrastColor })
|
|
66
70
|
] });
|
|
67
71
|
}
|
|
@@ -173,9 +177,10 @@ function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
|
173
177
|
const { value, previewValue, label = "Color", caption } = section;
|
|
174
178
|
const renderSwatch = useProfileSwatchRenderer();
|
|
175
179
|
const drawn = previewValue ? previewValue(value) : value;
|
|
180
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, "colorRow");
|
|
176
181
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
177
182
|
/* @__PURE__ */ jsx4(TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ jsxs2(View3, { style: pickerMenuStyles.rowInner, children: [
|
|
178
|
-
/* @__PURE__ */ jsx4(View3, { style: [styles3.colorRowSwatch,
|
|
183
|
+
/* @__PURE__ */ jsx4(View3, { style: [styles3.colorRowSwatch, drawing === null && { backgroundColor: drawn }], children: drawing !== null && /* @__PURE__ */ jsx4(SwatchLayer, { size: COLOR_ROW_SWATCH_SIZE, children: drawing }) }),
|
|
179
184
|
/* @__PURE__ */ jsxs2(View3, { style: styles3.colorRowText, children: [
|
|
180
185
|
/* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
181
186
|
caption ? /* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
@@ -202,9 +207,11 @@ function ColorPage({ section, homeColor, fg, onBack }) {
|
|
|
202
207
|
const swatchDisplay = displayFor(swatch.value);
|
|
203
208
|
const contrast = getContrastColor2(swatchDisplay);
|
|
204
209
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
|
|
210
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE;
|
|
211
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, "colorPage");
|
|
205
212
|
const mark = selected ? /* @__PURE__ */ jsx4(Icon2, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ jsx4(Icon2, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ jsx4(Icon2, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ jsx4(View3, { style: [styles3.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ jsx4(View3, {});
|
|
206
|
-
return /* @__PURE__ */ jsx4(TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch,
|
|
207
|
-
/* @__PURE__ */ jsx4(SwatchLayer, {
|
|
213
|
+
return /* @__PURE__ */ jsx4(TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch, drawing === null && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: drawing !== null ? /* @__PURE__ */ jsxs2(View3, { style: styles3.swatchFace, children: [
|
|
214
|
+
/* @__PURE__ */ jsx4(SwatchLayer, { size: drawnSize, children: drawing }),
|
|
208
215
|
mark
|
|
209
216
|
] }) : mark }, swatch.value);
|
|
210
217
|
}) })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
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",
|
package/src/IdentityChip.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native'
|
|
|
3
3
|
import { Icon, Text } from 'react-native-paper'
|
|
4
4
|
|
|
5
5
|
import { useProfileSwatchRenderer } from './ProfileColorContext'
|
|
6
|
-
import { SwatchLayer } from './SwatchLayer'
|
|
6
|
+
import { drawSwatch, SwatchLayer } from './SwatchLayer'
|
|
7
7
|
|
|
8
8
|
// Presence check, not isValidTag's own full-string emoji validation (profilesValidation.ts) - this
|
|
9
9
|
// only decides a font-size ratio, so it doesn't need that check's ZWJ/variation-selector precision,
|
|
@@ -28,6 +28,7 @@ interface Props {
|
|
|
28
28
|
// place of the flat color (a filled chip only); the tag's contrast still comes from `color`.
|
|
29
29
|
export function IdentityChip({ color, tag, icon, size, filled = true }: Props) {
|
|
30
30
|
const renderSwatch = useProfileSwatchRenderer()
|
|
31
|
+
const drawing = filled ? drawSwatch(renderSwatch, color, size, 'chip') : null
|
|
31
32
|
const contrastColor = getContrastColor(color)
|
|
32
33
|
// An emoji glyph reads visually smaller than a bold letter at the same fontSize (the system emoji
|
|
33
34
|
// font leaves more of its own em-box empty), so a plain-text ratio that looks right for "J" still
|
|
@@ -39,8 +40,8 @@ export function IdentityChip({ color, tag, icon, size, filled = true }: Props) {
|
|
|
39
40
|
// them off there ("D..."); 0.32 fits them, with adjustsFontSizeToFit still the backstop on native.
|
|
40
41
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4)
|
|
41
42
|
return (
|
|
42
|
-
<View style={[styles.chip, { borderRadius: size / 2, height: size, width: size }, filled &&
|
|
43
|
-
{
|
|
43
|
+
<View style={[styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && drawing === null && { backgroundColor: color }]}>
|
|
44
|
+
{drawing !== null && <SwatchLayer size={size}>{drawing}</SwatchLayer>}
|
|
44
45
|
{tag ? (
|
|
45
46
|
<Text style={[styles.tag, { color: contrastColor, fontSize: tagFontSize }]} numberOfLines={1} adjustsFontSizeToFit>
|
|
46
47
|
{tag}
|
package/src/PickerMenu.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { Icon, Text } from 'react-native-paper'
|
|
|
8
8
|
|
|
9
9
|
import { IdentityChip } from './IdentityChip'
|
|
10
10
|
import { useProfileSwatchRenderer } from './ProfileColorContext'
|
|
11
|
-
import { SwatchLayer } from './SwatchLayer'
|
|
11
|
+
import { drawSwatch, SwatchLayer } from './SwatchLayer'
|
|
12
12
|
|
|
13
13
|
// The trigger and popover ProfilePicker and OptionPicker share: a name trigger (or the chip pill),
|
|
14
14
|
// a bordered menu of rows, and with a `colorSection` a Color row that swaps the menu for a page of
|
|
@@ -272,12 +272,13 @@ function ColorRow({ section, fg, dividerColor, onPress }: { section: ProfilePick
|
|
|
272
272
|
const { value, previewValue, label = 'Color', caption } = section
|
|
273
273
|
const renderSwatch = useProfileSwatchRenderer()
|
|
274
274
|
const drawn = previewValue ? previewValue(value) : value
|
|
275
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, 'colorRow')
|
|
275
276
|
return (
|
|
276
277
|
<>
|
|
277
278
|
<TouchableRipple onPress={onPress} style={[pickerMenuStyles.row, { height: colorRowHeight(caption) }]} accessibilityRole='button' accessibilityLabel={caption ? `${label}, ${caption}` : label}>
|
|
278
279
|
<View style={pickerMenuStyles.rowInner}>
|
|
279
280
|
{/* A ProfileColorProvider renderSwatch draws it in place of the flat fill. */}
|
|
280
|
-
<View style={[styles.colorRowSwatch,
|
|
281
|
+
<View style={[styles.colorRowSwatch, drawing === null && { backgroundColor: drawn }]}>{drawing !== null && <SwatchLayer size={COLOR_ROW_SWATCH_SIZE}>{drawing}</SwatchLayer>}</View>
|
|
281
282
|
<View style={styles.colorRowText}>
|
|
282
283
|
<Text style={[styles.colorRowLabel, { color: fg }]} numberOfLines={1}>
|
|
283
284
|
{label}
|
|
@@ -329,15 +330,17 @@ function ColorPage({ section, homeColor, fg, onBack }: { section: ProfilePickerC
|
|
|
329
330
|
const swatchDisplay = displayFor(swatch.value)
|
|
330
331
|
const contrast = getContrastColor(swatchDisplay)
|
|
331
332
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label
|
|
333
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE
|
|
334
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, 'colorPage')
|
|
332
335
|
const mark = selected ? <Icon source='check' size={16} color={contrast} /> : swappable ? <Icon source='swap-horizontal' size={16} color={contrast} /> : taken ? <Icon source='close' size={16} color={contrast} /> : isHomeColor ? <View style={[styles.homeDot, { backgroundColor: contrast }]} /> : <View />
|
|
333
336
|
// The same swatch as @tastic/hud's InlineColorPicker: a filled circle, the selected one
|
|
334
337
|
// ringed and checked, a taken one faded with an X. TouchableRipple takes one child, so a
|
|
335
338
|
// drawn swatch and its mark share a face that fills the circle inside the ring.
|
|
336
339
|
return (
|
|
337
|
-
<TouchableRipple key={swatch.value} disabled={taken && !swappable} onPress={() => onChange(swatch.value)} borderless style={[styles.swatch,
|
|
338
|
-
{
|
|
340
|
+
<TouchableRipple key={swatch.value} disabled={taken && !swappable} onPress={() => onChange(swatch.value)} borderless style={[styles.swatch, drawing === null && { backgroundColor: swatchDisplay }, selected && styles.swatchSelected, taken && !swappable && styles.swatchTaken]} accessibilityRole='button' accessibilityLabel={accessibilityLabel} accessibilityState={{ selected, disabled: taken && !swappable }}>
|
|
341
|
+
{drawing !== null ? (
|
|
339
342
|
<View style={styles.swatchFace}>
|
|
340
|
-
<SwatchLayer
|
|
343
|
+
<SwatchLayer size={drawnSize}>{drawing}</SwatchLayer>
|
|
341
344
|
{mark}
|
|
342
345
|
</View>
|
|
343
346
|
) : (
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { createContext, type ReactNode, useContext, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// the
|
|
6
|
-
|
|
3
|
+
// Where a swatch is drawn: an IdentityChip (ProfileChip, the pickers' chip trigger), a picker's Color
|
|
4
|
+
// row, or a swatch on its color page. An app can tell an identity (a chip, which wants a legible mark
|
|
5
|
+
// on one color) from a choice of color (the Color row and page, where previewing more of what the
|
|
6
|
+
// color gives is the point): Hangman draws its triad pie only on the latter.
|
|
7
|
+
export type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage'
|
|
8
|
+
|
|
9
|
+
// Draws one color's swatch fill: a circle `size` across, standing in for the flat fill, at `place`.
|
|
10
|
+
// `color` is the drawn color (after `preview`), and it stays the primary one: the tag, icon, check or
|
|
11
|
+
// dot on top of the swatch takes its contrast from it. Returning null or undefined keeps that swatch's
|
|
12
|
+
// flat fill.
|
|
13
|
+
export type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode
|
|
7
14
|
|
|
8
15
|
interface ProfileColorContextValue {
|
|
9
16
|
preview?: (hex: string) => string
|
|
@@ -17,9 +24,11 @@ interface ProfileColorProviderProps {
|
|
|
17
24
|
// toward its card-back palette so they read muted, not as the raw swatch; a context rather than a
|
|
18
25
|
// module-level setting because that blend depends on light/dark mode.
|
|
19
26
|
preview?: (hex: string) => string
|
|
20
|
-
// Replaces the flat fill of
|
|
21
|
-
// pickers' chip trigger), the pickers' Color row swatch and their color page swatches
|
|
22
|
-
//
|
|
27
|
+
// Replaces the flat fill of the round swatches this package draws: IdentityChip (ProfileChip and the
|
|
28
|
+
// pickers' chip trigger), the pickers' Color row swatch and their color page swatches, each told
|
|
29
|
+
// where it is (ProfileSwatchPlace) and kept flat where it returns null. Hangman draws a pie of each
|
|
30
|
+
// color's triad on the Color row and page only. The renderer is drawn behind the swatch's mark,
|
|
31
|
+
// clipped to the circle.
|
|
23
32
|
// Rows that fill with a color (a selected picker row) stay flat, and so does hud's InlineColorPicker
|
|
24
33
|
// (ProfilesManager's color trigger and swatches), which doesn't read this context.
|
|
25
34
|
renderSwatch?: ProfileSwatchRenderer
|
package/src/SwatchLayer.tsx
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
1
2
|
import { StyleSheet, View } from 'react-native'
|
|
2
3
|
|
|
3
|
-
import { type ProfileSwatchRenderer } from './ProfileColorContext'
|
|
4
|
+
import { type ProfileSwatchPlace, type ProfileSwatchRenderer } from './ProfileColorContext'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
|
-
render: ProfileSwatchRenderer
|
|
7
|
-
// The drawn color, handed to `render` as is.
|
|
8
|
-
color: string
|
|
9
7
|
// The circle's diameter: the swatch's own size, less any ring drawn inside it.
|
|
10
8
|
size: number
|
|
9
|
+
// What the renderer drew (see drawSwatch).
|
|
10
|
+
children: ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// What a ProfileColorProvider `renderSwatch` draws for one swatch, or null for the flat fill: there's
|
|
14
|
+
// no renderer, or it returned null or undefined for this place.
|
|
15
|
+
export function drawSwatch(render: ProfileSwatchRenderer | undefined, color: string, size: number, place: ProfileSwatchPlace): ReactNode {
|
|
16
|
+
return render ? (render(color, size, place) ?? null) : null
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
// A ProfileColorProvider `renderSwatch` drawing in place of a swatch's flat fill: absolutely placed at
|
|
14
20
|
// the swatch's top left (inside any border), behind the mark drawn after it, and clipped to the circle
|
|
15
21
|
// so a square drawing still reads as the round swatch. Not exported from index.ts.
|
|
16
|
-
export function SwatchLayer({
|
|
17
|
-
return <View style={[styles.layer, { borderRadius: size / 2, height: size, width: size }]}>{
|
|
22
|
+
export function SwatchLayer({ size, children }: Props) {
|
|
23
|
+
return <View style={[styles.layer, { borderRadius: size / 2, height: size, width: size }]}>{children}</View>
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
const styles = StyleSheet.create({
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { getOptionPickerContentSize, OptionPicker, type OptionPickerOption } from './OptionPicker'
|
|
2
2
|
export { ProfileChip } from './ProfileChip'
|
|
3
|
-
export { ProfileColorProvider, type ProfileSwatchRenderer, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer } from './ProfileColorContext'
|
|
3
|
+
export { ProfileColorProvider, type ProfileSwatchPlace, type ProfileSwatchRenderer, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer } from './ProfileColorContext'
|
|
4
4
|
export { getProfilePickerContentSize, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize } from './ProfilePicker'
|
|
5
5
|
export { type ProfileEditPatch, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps } from './ProfilesManager'
|
|
6
6
|
export { ProfilesScreen, type ProfilesScreenProps } from './ProfilesScreen'
|