@tastic/profile 0.16.0 → 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
@@ -30,6 +30,7 @@ interface OptionPickerOption<T extends string> {
30
30
  value: T;
31
31
  label: string;
32
32
  icon?: string;
33
+ color?: string;
33
34
  }
34
35
  declare function getOptionPickerContentSize(optionCount: number, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
35
36
  interface OptionPickerProps<T extends string> {
@@ -66,7 +67,8 @@ interface Props {
66
67
  }
67
68
  declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
68
69
 
69
- type ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
70
+ type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
71
+ type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
70
72
  interface ProfileColorProviderProps {
71
73
  preview?: (hex: string) => string;
72
74
  renderSwatch?: ProfileSwatchRenderer;
@@ -265,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
265
267
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
266
268
  declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
267
269
 
268
- 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
@@ -30,6 +30,7 @@ interface OptionPickerOption<T extends string> {
30
30
  value: T;
31
31
  label: string;
32
32
  icon?: string;
33
+ color?: string;
33
34
  }
34
35
  declare function getOptionPickerContentSize(optionCount: number, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
35
36
  interface OptionPickerProps<T extends string> {
@@ -66,7 +67,8 @@ interface Props {
66
67
  }
67
68
  declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
68
69
 
69
- type ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
70
+ type ProfileSwatchPlace = 'chip' | 'colorRow' | 'colorPage';
71
+ type ProfileSwatchRenderer = (color: string, size: number, place: ProfileSwatchPlace) => ReactNode;
70
72
  interface ProfileColorProviderProps {
71
73
  preview?: (hex: string) => string;
72
74
  renderSwatch?: ProfileSwatchRenderer;
@@ -265,4 +267,4 @@ declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
265
267
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
266
268
  declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
267
269
 
268
- 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
  }
@@ -210,10 +214,10 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
210
214
  ) })
211
215
  ] });
212
216
  }
213
- function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
217
+ function IconRow({ icon, iconColor, label, selected, color, fg, disabled = false, onPress }) {
214
218
  const textColor = selected ? (0, import_auto_paper2.getContrastColor)(color) : fg;
215
219
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
216
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: icon, size: 13, color: textColor }) }),
220
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: icon, size: 13, color: selected ? textColor : iconColor ?? textColor }) }),
217
221
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
218
222
  ] }) });
219
223
  }
@@ -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
  }) })
@@ -431,7 +438,7 @@ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, r
431
438
  onChange(option.value);
432
439
  host.close();
433
440
  };
434
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
441
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconRow, { icon: option.icon ?? rowIcon, iconColor: option.color, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
435
442
  }
436
443
 
437
444
  // src/ProfileChip.tsx
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
  }
@@ -162,10 +166,10 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
162
166
  ) })
163
167
  ] });
164
168
  }
165
- function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
169
+ function IconRow({ icon, iconColor, label, selected, color, fg, disabled = false, onPress }) {
166
170
  const textColor = selected ? getContrastColor2(color) : fg;
167
171
  return /* @__PURE__ */ jsx4(TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs2(View3, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
168
- /* @__PURE__ */ jsx4(View3, { style: styles3.iconSlot, children: /* @__PURE__ */ jsx4(Icon2, { source: icon, size: 13, color: textColor }) }),
172
+ /* @__PURE__ */ jsx4(View3, { style: styles3.iconSlot, children: /* @__PURE__ */ jsx4(Icon2, { source: icon, size: 13, color: selected ? textColor : iconColor ?? textColor }) }),
169
173
  /* @__PURE__ */ jsx4(Text2, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
170
174
  ] }) });
171
175
  }
@@ -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
  }) })
@@ -383,7 +390,7 @@ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, r
383
390
  onChange(option.value);
384
391
  host.close();
385
392
  };
386
- return /* @__PURE__ */ jsx5(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ jsx5(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
393
+ return /* @__PURE__ */ jsx5(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ jsx5(IconRow, { icon: option.icon ?? rowIcon, iconColor: option.color, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
387
394
  }
388
395
 
389
396
  // src/ProfileChip.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/profile",
3
- "version": "0.16.0",
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",
@@ -66,12 +66,12 @@
66
66
  "@infinitetoken/jest-config": "^0.5.2",
67
67
  "@infinitetoken/tsconfig": "^0.6.0",
68
68
  "@rific/auto-paper": "^0.13.3",
69
- "@rific/core": "^0.3.1",
69
+ "@rific/core": "^0.4.0",
70
70
  "@rific/feedback-press": "^0.13.0",
71
71
  "@rific/focus-chain": "^0.4.8",
72
- "@rific/toaster": "^0.8.2",
72
+ "@rific/toaster": "^0.8.3",
73
73
  "@tastic/core": "^0.14.0",
74
- "@tastic/hud": "^0.23.0",
74
+ "@tastic/hud": "^0.24.0",
75
75
  "@testing-library/dom": "^10.4.1",
76
76
  "@testing-library/react": "^16.3.2",
77
77
  "@types/jest": "^30.0.0",
@@ -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}
@@ -7,6 +7,10 @@ export interface OptionPickerOption<T extends string> {
7
7
  value: T
8
8
  label: string
9
9
  icon?: string
10
+ // The option's own color (e.g. a difficulty's), for its row's icon while the row isn't the selected
11
+ // one; the selected row still fills with the picker's `color`, its icon contrasting as always.
12
+ // Without it the icon takes the row's text color.
13
+ color?: string
10
14
  }
11
15
 
12
16
  // The popover's content footprint: one row per option, plus (with a `colorSection`) the Color row,
@@ -57,7 +61,7 @@ export function OptionPicker<T extends string>({ id, host, options, value, onCha
57
61
  return (
58
62
  <PickerMenu id={id} host={host} color={color} dark={dark} label={selected?.label ?? ''} chip={{ icon }} colorSection={colorSection} contentSize={getOptionPickerContentSize(options.length, colorSection)} align={align} alignOverride={alignOverride} rotation={rotation} triggerStyle={triggerStyle}>
59
63
  {options.map((option) => (
60
- <IconRow key={option.value} icon={option.icon ?? rowIcon} label={option.label} selected={option.value === value} color={color} fg={fg} onPress={() => handleSelect(option)} />
64
+ <IconRow key={option.value} icon={option.icon ?? rowIcon} iconColor={option.color} label={option.label} selected={option.value === value} color={color} fg={fg} onPress={() => handleSelect(option)} />
61
65
  ))}
62
66
  </PickerMenu>
63
67
  )
@@ -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
@@ -231,6 +231,9 @@ export function PickerMenu({ id, host, color, dark, label, chip, colorSection, h
231
231
 
232
232
  interface IconRowProps {
233
233
  icon: string
234
+ // The icon's color while the row isn't selected (an OptionPicker option's own `color`). Defaults to
235
+ // `fg`, like the label.
236
+ iconColor?: string
234
237
  label: string
235
238
  // Fills the row with `color` (its icon and label then contrast against it).
236
239
  selected: boolean
@@ -244,7 +247,7 @@ interface IconRowProps {
244
247
  // A selected row's own background fills entirely with its identity color - matching how a selected
245
248
  // row reads elsewhere (see @tastic/hud's SectionedDropdown) rather than a checkmark bolted onto an
246
249
  // otherwise-plain row.
247
- export function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }: IconRowProps) {
250
+ export function IconRow({ icon, iconColor, label, selected, color, fg, disabled = false, onPress }: IconRowProps) {
248
251
  const textColor = selected ? getContrastColor(color) : fg
249
252
  return (
250
253
  <TouchableRipple disabled={disabled} onPress={onPress} style={[pickerMenuStyles.row, selected && { backgroundColor: color }]}>
@@ -253,7 +256,7 @@ export function IconRow({ icon, label, selected, color, fg, disabled = false, on
253
256
  circle, but still the same fixed-size slot every row's icon/chip sits in (see ProfileChip's
254
257
  own doc), so this row's icon stays lined up with every other row regardless of selection. */}
255
258
  <View style={styles.iconSlot}>
256
- <Icon source={icon} size={13} color={textColor} />
259
+ <Icon source={icon} size={13} color={selected ? textColor : (iconColor ?? textColor)} />
257
260
  </View>
258
261
  <Text style={[pickerMenuStyles.rowLabel, { color: textColor }]} numberOfLines={1}>
259
262
  {label}
@@ -269,12 +272,13 @@ function ColorRow({ section, fg, dividerColor, onPress }: { section: ProfilePick
269
272
  const { value, previewValue, label = 'Color', caption } = section
270
273
  const renderSwatch = useProfileSwatchRenderer()
271
274
  const drawn = previewValue ? previewValue(value) : value
275
+ const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, 'colorRow')
272
276
  return (
273
277
  <>
274
278
  <TouchableRipple onPress={onPress} style={[pickerMenuStyles.row, { height: colorRowHeight(caption) }]} accessibilityRole='button' accessibilityLabel={caption ? `${label}, ${caption}` : label}>
275
279
  <View style={pickerMenuStyles.rowInner}>
276
280
  {/* A ProfileColorProvider renderSwatch draws it in place of the flat fill. */}
277
- <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>
278
282
  <View style={styles.colorRowText}>
279
283
  <Text style={[styles.colorRowLabel, { color: fg }]} numberOfLines={1}>
280
284
  {label}
@@ -326,15 +330,17 @@ function ColorPage({ section, homeColor, fg, onBack }: { section: ProfilePickerC
326
330
  const swatchDisplay = displayFor(swatch.value)
327
331
  const contrast = getContrastColor(swatchDisplay)
328
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')
329
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 />
330
336
  // The same swatch as @tastic/hud's InlineColorPicker: a filled circle, the selected one
331
337
  // ringed and checked, a taken one faded with an X. TouchableRipple takes one child, so a
332
338
  // drawn swatch and its mark share a face that fills the circle inside the ring.
333
339
  return (
334
- <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 }}>
335
- {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 ? (
336
342
  <View style={styles.swatchFace}>
337
- <SwatchLayer render={renderSwatch} color={swatchDisplay} size={selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE} />
343
+ <SwatchLayer size={drawnSize}>{drawing}</SwatchLayer>
338
344
  {mark}
339
345
  </View>
340
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'