@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 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 drawn
314
- color (after `preview`) and the circle's diameter, and its drawing is clipped to the circle, behind
315
- the tag, icon or check. The tag and check still take their contrast from `color`, so treat it as
316
- the primary color of whatever you draw. Hangman draws a pie of each color's triad:
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 ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
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 ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
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 SwatchLayer({ render, color, size }) {
92
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children: render(color, size) });
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 && !renderSwatch && { backgroundColor: color }], children: [
112
- filled && renderSwatch && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SwatchLayer, { render: renderSwatch, color, size }),
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, !renderSwatch && { backgroundColor: drawn }], children: renderSwatch && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { render: renderSwatch, color: drawn, size: COLOR_ROW_SWATCH_SIZE }) }),
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, !renderSwatch && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: renderSwatch ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.swatchFace, children: [
255
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { render: renderSwatch, color: swatchDisplay, size: selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE }),
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 SwatchLayer({ render, color, size }) {
44
- return /* @__PURE__ */ jsx2(View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children: render(color, size) });
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 && !renderSwatch && { backgroundColor: color }], children: [
64
- filled && renderSwatch && /* @__PURE__ */ jsx3(SwatchLayer, { render: renderSwatch, color, size }),
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, !renderSwatch && { backgroundColor: drawn }], children: renderSwatch && /* @__PURE__ */ jsx4(SwatchLayer, { render: renderSwatch, color: drawn, size: COLOR_ROW_SWATCH_SIZE }) }),
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, !renderSwatch && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: renderSwatch ? /* @__PURE__ */ jsxs2(View3, { style: styles3.swatchFace, children: [
207
- /* @__PURE__ */ jsx4(SwatchLayer, { render: renderSwatch, color: swatchDisplay, size: selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE }),
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.1",
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",
@@ -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 && !renderSwatch && { backgroundColor: color }]}>
43
- {filled && renderSwatch && <SwatchLayer render={renderSwatch} color={color} size={size} />}
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}
@@ -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, !renderSwatch && { backgroundColor: drawn }]}>{renderSwatch && <SwatchLayer render={renderSwatch} color={drawn} size={COLOR_ROW_SWATCH_SIZE} />}</View>
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, !renderSwatch && { backgroundColor: swatchDisplay }, selected && styles.swatchSelected, taken && !swappable && styles.swatchTaken]} accessibilityRole='button' accessibilityLabel={accessibilityLabel} accessibilityState={{ selected, disabled: taken && !swappable }}>
338
- {renderSwatch ? (
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 render={renderSwatch} color={swatchDisplay} size={selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE} />
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
- // Draws one color's swatch fill: a circle `size` across, standing in for the flat fill. `color` is the
4
- // drawn color (after `preview`), and it stays the primary one: the tag, icon, check or dot on top of
5
- // the swatch takes its contrast from it.
6
- export type ProfileSwatchRenderer = (color: string, size: number) => ReactNode
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 every round swatch this package draws: IdentityChip (ProfileChip and the
21
- // pickers' chip trigger), the pickers' Color row swatch and their color page swatches. Hangman draws
22
- // a pie of each color's triad. The renderer is drawn behind the swatch's mark, clipped to the circle.
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
@@ -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({ render, color, size }: Props) {
17
- return <View style={[styles.layer, { borderRadius: size / 2, height: size, width: size }]}>{render(color, size)}</View>
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'