@tastic/profile 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -15
- package/dist/index.d.mts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +78 -25
- package/dist/index.mjs +78 -25
- package/package.json +9 -8
- package/src/IdentityChip.tsx +4 -3
- package/src/OptionPicker.tsx +2 -0
- package/src/PickerMenu.tsx +53 -18
- package/src/ProfileColorContext.tsx +18 -9
- package/src/ProfilePicker.tsx +2 -1
- package/src/ProfilesManager.tsx +77 -51
- package/src/SwatchLayer.tsx +12 -6
- package/src/index.ts +1 -1
- package/src/sharedProfileStore.ts +35 -5
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
|
}
|
|
@@ -86,6 +90,12 @@ var MENU_BORDER_WIDTH = 2;
|
|
|
86
90
|
var ROW_INSET = 12;
|
|
87
91
|
var CHIP_SIZE = 32;
|
|
88
92
|
var CHIP_TRIGGER_PADDING = 6;
|
|
93
|
+
var CHIP_TRIGGER_END_PADDING = CHIP_TRIGGER_PADDING + 8;
|
|
94
|
+
var CHIP_TRIGGER_GAP = 8;
|
|
95
|
+
var PLAIN_TRIGGER_GAP = 2;
|
|
96
|
+
var CHEVRON_SIZE = 16;
|
|
97
|
+
var CHIP_TRIGGER_FIXED_WIDTH = CHIP_TRIGGER_PADDING + CHIP_SIZE + 2 * CHIP_TRIGGER_GAP + CHEVRON_SIZE + CHIP_TRIGGER_END_PADDING;
|
|
98
|
+
var PLAIN_TRIGGER_FIXED_WIDTH = PLAIN_TRIGGER_GAP + CHEVRON_SIZE;
|
|
89
99
|
var SWATCH_SIZE = 28;
|
|
90
100
|
var SWATCH_MIN_GAP = 6;
|
|
91
101
|
var SWATCH_RING_WIDTH = 2;
|
|
@@ -130,11 +140,15 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
|
|
|
130
140
|
};
|
|
131
141
|
const triggerLabel = label.toUpperCase();
|
|
132
142
|
const showFooter = !!footer && !showColorPage;
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
const flatTriggerStyle = StyleSheet3.flatten(triggerStyle) ?? {};
|
|
144
|
+
const { flexShrink, ...pillStyle } = flatTriggerStyle;
|
|
145
|
+
const shrinks = typeof flexShrink === "number" && flexShrink > 0;
|
|
146
|
+
const anchorShrink = shrinks ? { flexShrink, minWidth: chipTrigger ? CHIP_TRIGGER_FIXED_WIDTH : PLAIN_TRIGGER_FIXED_WIDTH } : void 0;
|
|
147
|
+
return /* @__PURE__ */ jsxs2(View3, { style: [styles3.anchor, anchorShrink, open && styles3.anchorOpen], onLayout: handleAnchorLayout, children: [
|
|
148
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles3.trigger, chipTrigger && styles3.chipTrigger, shrinks && styles3.triggerShrinks, shrinks ? pillStyle : triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ jsxs2(View3, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles3.chipTriggerInner : styles3.triggerInner, children: [
|
|
135
149
|
chip && /* @__PURE__ */ jsx4(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
|
|
136
150
|
/* @__PURE__ */ jsx4(Text2, { style: [styles3.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
|
|
137
|
-
/* @__PURE__ */ jsx4(Icon2, { source: "menu-down", size:
|
|
151
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "menu-down", size: CHEVRON_SIZE, color: chipTrigger ? fg : color })
|
|
138
152
|
] }) }),
|
|
139
153
|
/* @__PURE__ */ jsx4(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ jsxs2(
|
|
140
154
|
View3,
|
|
@@ -173,9 +187,10 @@ function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
|
173
187
|
const { value, previewValue, label = "Color", caption } = section;
|
|
174
188
|
const renderSwatch = useProfileSwatchRenderer();
|
|
175
189
|
const drawn = previewValue ? previewValue(value) : value;
|
|
190
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, "colorRow");
|
|
176
191
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
177
192
|
/* @__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,
|
|
193
|
+
/* @__PURE__ */ jsx4(View3, { style: [styles3.colorRowSwatch, drawing === null && { backgroundColor: drawn }], children: drawing !== null && /* @__PURE__ */ jsx4(SwatchLayer, { size: COLOR_ROW_SWATCH_SIZE, children: drawing }) }),
|
|
179
194
|
/* @__PURE__ */ jsxs2(View3, { style: styles3.colorRowText, children: [
|
|
180
195
|
/* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
181
196
|
caption ? /* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
@@ -202,9 +217,11 @@ function ColorPage({ section, homeColor, fg, onBack }) {
|
|
|
202
217
|
const swatchDisplay = displayFor(swatch.value);
|
|
203
218
|
const contrast = getContrastColor2(swatchDisplay);
|
|
204
219
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
|
|
220
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE;
|
|
221
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, "colorPage");
|
|
205
222
|
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, {
|
|
223
|
+
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: [
|
|
224
|
+
/* @__PURE__ */ jsx4(SwatchLayer, { size: drawnSize, children: drawing }),
|
|
208
225
|
mark
|
|
209
226
|
] }) : mark }, swatch.value);
|
|
210
227
|
}) })
|
|
@@ -249,10 +266,10 @@ var styles3 = StyleSheet3.create({
|
|
|
249
266
|
chipTriggerInner: {
|
|
250
267
|
alignItems: "center",
|
|
251
268
|
flexDirection: "row",
|
|
252
|
-
gap:
|
|
269
|
+
gap: CHIP_TRIGGER_GAP,
|
|
253
270
|
height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
|
|
254
271
|
paddingLeft: CHIP_TRIGGER_PADDING,
|
|
255
|
-
paddingRight:
|
|
272
|
+
paddingRight: CHIP_TRIGGER_END_PADDING
|
|
256
273
|
},
|
|
257
274
|
colorRowCaption: {
|
|
258
275
|
fontSize: 12,
|
|
@@ -361,13 +378,22 @@ var styles3 = StyleSheet3.create({
|
|
|
361
378
|
triggerInner: {
|
|
362
379
|
alignItems: "center",
|
|
363
380
|
flexDirection: "row",
|
|
364
|
-
gap:
|
|
381
|
+
gap: PLAIN_TRIGGER_GAP,
|
|
365
382
|
height: TRIGGER_HEIGHT
|
|
366
383
|
},
|
|
384
|
+
// Lets the label, alone in its row, give way to a trigger narrower than its name (see `shrinks`).
|
|
385
|
+
// With room it keeps its natural width, so this changes nothing there.
|
|
367
386
|
triggerLabel: {
|
|
387
|
+
flexShrink: 1,
|
|
368
388
|
fontSize: 13,
|
|
369
389
|
fontWeight: "700",
|
|
370
|
-
letterSpacing: 1
|
|
390
|
+
letterSpacing: 1,
|
|
391
|
+
minWidth: 0
|
|
392
|
+
},
|
|
393
|
+
// A shrinking trigger (see `shrinks`): no wider than its anchor. Its row is then laid out at that
|
|
394
|
+
// width, which is what cuts the label off.
|
|
395
|
+
triggerShrinks: {
|
|
396
|
+
maxWidth: "100%"
|
|
371
397
|
}
|
|
372
398
|
});
|
|
373
399
|
|
|
@@ -516,6 +542,7 @@ function isValidProfile(value) {
|
|
|
516
542
|
// src/ProfilesManager.tsx
|
|
517
543
|
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
518
544
|
var CHIP_SIZE2 = 40;
|
|
545
|
+
var textInputRef = (ref) => ref;
|
|
519
546
|
function deriveTag(name) {
|
|
520
547
|
const trimmed = name.trim();
|
|
521
548
|
return trimmed ? trimmed[0].toUpperCase() : "";
|
|
@@ -526,6 +553,8 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
526
553
|
const { dark } = useAutoPaperTheme();
|
|
527
554
|
const insets = useSafeAreaInsets();
|
|
528
555
|
const { error: showErrorToast } = useToast();
|
|
556
|
+
const columnRef = useRef(null);
|
|
557
|
+
const renderSwatch = useProfileSwatchRenderer();
|
|
529
558
|
const host = usePopoverHost();
|
|
530
559
|
const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
|
|
531
560
|
const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
|
|
@@ -596,7 +625,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
596
625
|
headerLeft,
|
|
597
626
|
/* @__PURE__ */ jsx8(Text4, { variant: titleVariant, style: [styles5.title, { color: fg }], children: "Profiles" })
|
|
598
627
|
] }),
|
|
599
|
-
/* @__PURE__ */ jsx8(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ jsx8(FadingScrollView2, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
628
|
+
/* @__PURE__ */ jsx8(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ jsx8(View5, { ref: columnRef, collapsable: false, style: styles5.column, children: /* @__PURE__ */ jsx8(FadingScrollView2, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
600
629
|
if (row.kind === "new") {
|
|
601
630
|
if (editingId !== "new") {
|
|
602
631
|
return /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: () => startEdit("new"), style: styles5.row, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.rowInner, children: [
|
|
@@ -604,27 +633,28 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
604
633
|
/* @__PURE__ */ jsx8(Text4, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
605
634
|
] }) }, "new");
|
|
606
635
|
}
|
|
607
|
-
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
|
|
636
|
+
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign, column: columnRef, renderSwatch }, "new");
|
|
608
637
|
}
|
|
609
638
|
const { profile } = row;
|
|
610
639
|
if (editingId === profile.id) {
|
|
611
|
-
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
|
|
640
|
+
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign, column: columnRef, renderSwatch }, profile.id);
|
|
612
641
|
}
|
|
613
642
|
return /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: () => startEdit(profile), style: styles5.row, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.rowInner, children: [
|
|
614
643
|
/* @__PURE__ */ jsx8(ProfileChip, { profile, size: CHIP_SIZE2 }),
|
|
615
644
|
/* @__PURE__ */ jsx8(Text4, { variant: "bodyLarge", style: [styles5.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
|
|
616
645
|
] }) }, profile.id);
|
|
617
|
-
}) }) }),
|
|
646
|
+
}) }) }) }),
|
|
618
647
|
/* @__PURE__ */ jsx8(ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
|
|
619
648
|
] });
|
|
620
649
|
}
|
|
621
|
-
function AlignedColorPicker({ useAlign, ...props }) {
|
|
650
|
+
function AlignedColorPicker({ useAlign, column, ...props }) {
|
|
622
651
|
const { width: windowWidth } = useWindowDimensions();
|
|
623
652
|
const { width, height } = getInlineColorPickerContentSize(defaultColors2.length, windowWidth);
|
|
624
|
-
const alignOverride = useAlign(props.host.openId === props.id, width, height);
|
|
653
|
+
const alignOverride = useAlign(props.host.openId === props.id, width, height, column);
|
|
625
654
|
return /* @__PURE__ */ jsx8(InlineColorPicker, { ...props, alignOverride });
|
|
626
655
|
}
|
|
627
|
-
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
|
|
656
|
+
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign, column, renderSwatch }) {
|
|
657
|
+
const pickerSwatch = useMemo3(() => renderSwatch ? (color, size, place) => renderSwatch(color, size, place === "trigger" ? "chip" : "colorPage") : void 0, [renderSwatch]);
|
|
628
658
|
const register = useFocusChain();
|
|
629
659
|
const tag = register();
|
|
630
660
|
const name = register();
|
|
@@ -636,13 +666,17 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
636
666
|
if (host.openId === "color") tagFocusRef.current();
|
|
637
667
|
}, [host.openId]);
|
|
638
668
|
return /* @__PURE__ */ jsxs4(View5, { style: styles5.editRow, children: [
|
|
639
|
-
useColorPickerAlign ? /* @__PURE__ */ jsx8(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ jsx8(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
640
|
-
/* @__PURE__ */ jsx8(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
|
|
641
|
-
/* @__PURE__ */ jsx8(TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles5.nameInput }),
|
|
669
|
+
useColorPickerAlign ? /* @__PURE__ */ jsx8(AlignedColorPicker, { useAlign: useColorPickerAlign, column, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark, renderSwatch: pickerSwatch }) : /* @__PURE__ */ jsx8(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark, renderSwatch: pickerSwatch, bounds: column }),
|
|
670
|
+
/* @__PURE__ */ jsx8(TextInput, { ref: textInputRef(tag.ref), ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
|
|
671
|
+
/* @__PURE__ */ jsx8(TextInput, { ref: textInputRef(name.ref), ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles5.nameInput }),
|
|
642
672
|
onDelete && /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: onDelete, style: styles5.iconButton, children: /* @__PURE__ */ jsx8(Icon4, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
643
673
|
] });
|
|
644
674
|
}
|
|
645
675
|
var styles5 = StyleSheet5.create({
|
|
676
|
+
// The roster's column, around the scroll view (see columnRef).
|
|
677
|
+
column: {
|
|
678
|
+
flex: 1
|
|
679
|
+
},
|
|
646
680
|
content: {
|
|
647
681
|
gap: 4,
|
|
648
682
|
paddingHorizontal: 20
|
|
@@ -916,10 +950,29 @@ async function readSharedProfiles(groupId) {
|
|
|
916
950
|
async function loadSharedProfiles(groupId) {
|
|
917
951
|
return await readSharedProfiles(groupId) ?? [];
|
|
918
952
|
}
|
|
953
|
+
var hasId = (entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && entry.id !== "";
|
|
954
|
+
async function unreadableEntries(groupId, profiles) {
|
|
955
|
+
let json;
|
|
956
|
+
try {
|
|
957
|
+
json = web ? web.storage.getItem(webKey(groupId, PROFILES_KEY)) : await native?.getSharedJSON(groupId, PROFILES_KEY);
|
|
958
|
+
} catch {
|
|
959
|
+
return [];
|
|
960
|
+
}
|
|
961
|
+
if (!json) return [];
|
|
962
|
+
try {
|
|
963
|
+
const parsed = JSON.parse(json);
|
|
964
|
+
if (!Array.isArray(parsed)) return [];
|
|
965
|
+
const saved = new Set(profiles.map((profile) => profile.id));
|
|
966
|
+
return parsed.filter((entry) => !isValidProfile(entry) && hasId(entry) && !saved.has(entry.id));
|
|
967
|
+
} catch {
|
|
968
|
+
return [];
|
|
969
|
+
}
|
|
970
|
+
}
|
|
919
971
|
async function saveSharedProfiles(groupId, profiles) {
|
|
920
972
|
if (!native) return;
|
|
921
973
|
try {
|
|
922
|
-
await
|
|
974
|
+
const kept = await unreadableEntries(groupId, profiles);
|
|
975
|
+
await native.setSharedJSON(groupId, PROFILES_KEY, JSON.stringify([...profiles, ...kept]));
|
|
923
976
|
} catch {
|
|
924
977
|
}
|
|
925
978
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
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",
|
|
@@ -62,16 +62,17 @@
|
|
|
62
62
|
},
|
|
63
63
|
"prettier": "@infinitetoken/eslint-config/prettier",
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@infinitetoken/eslint-config": "0.2.
|
|
65
|
+
"@infinitetoken/eslint-config": "0.2.1",
|
|
66
66
|
"@infinitetoken/jest-config": "^0.5.2",
|
|
67
67
|
"@infinitetoken/tsconfig": "^0.6.0",
|
|
68
|
-
"@rific/auto-paper": "^0.13.
|
|
69
|
-
"@rific/core": "^0.
|
|
70
|
-
"@rific/feedback-press": "^0.13.
|
|
68
|
+
"@rific/auto-paper": "^0.13.4",
|
|
69
|
+
"@rific/core": "^0.5.0",
|
|
70
|
+
"@rific/feedback-press": "^0.13.1",
|
|
71
71
|
"@rific/focus-chain": "^0.4.8",
|
|
72
|
-
"@rific/
|
|
72
|
+
"@rific/gestures": "^0.1.3",
|
|
73
|
+
"@rific/toaster": "^0.10.1",
|
|
73
74
|
"@tastic/core": "^0.14.0",
|
|
74
|
-
"@tastic/hud": "^0.
|
|
75
|
+
"@tastic/hud": "^0.25.1",
|
|
75
76
|
"@testing-library/dom": "^10.4.1",
|
|
76
77
|
"@testing-library/react": "^16.3.2",
|
|
77
78
|
"@types/jest": "^30.0.0",
|
|
@@ -97,7 +98,7 @@
|
|
|
97
98
|
"@rific/focus-chain": ">=0.4.0",
|
|
98
99
|
"@rific/toaster": ">=0.4.0",
|
|
99
100
|
"@tastic/core": ">=0.1.0",
|
|
100
|
-
"@tastic/hud": ">=0.
|
|
101
|
+
"@tastic/hud": ">=0.25.1",
|
|
101
102
|
"expo-modules-core": ">=1.0.0",
|
|
102
103
|
"react": ">=19.0.0",
|
|
103
104
|
"react-native": ">=0.76.0",
|
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/OptionPicker.tsx
CHANGED
|
@@ -41,6 +41,8 @@ interface OptionPickerProps<T extends string> {
|
|
|
41
41
|
align?: 'left' | 'right' | 'center'
|
|
42
42
|
alignOverride?: ProfilePickerAlignResult
|
|
43
43
|
rotation?: PopoverRotation
|
|
44
|
+
// The pill's own style (a background), as ProfilePicker's; a `flexShrink` lets it give way in a
|
|
45
|
+
// narrow row, cutting its label off.
|
|
44
46
|
triggerStyle?: StyleProp<ViewStyle>
|
|
45
47
|
}
|
|
46
48
|
|
package/src/PickerMenu.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defaultColors, FadingScrollView, getContrastColor, type SeedColor } from '@rific/auto-paper'
|
|
2
2
|
import { TouchableRipple } from '@rific/feedback-press'
|
|
3
|
-
import type { PopoverHost, PopoverRotation } from '@tastic/hud/chrome'
|
|
3
|
+
import type { PopoverHost, PopoverRotation, ViewInstance } from '@tastic/hud/chrome'
|
|
4
4
|
import { PopoverBody, useAutoAlign } from '@tastic/hud/chrome'
|
|
5
5
|
import { ReactNode, RefObject, useState } from 'react'
|
|
6
6
|
import { type LayoutChangeEvent, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
@@ -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
|
|
@@ -25,6 +25,15 @@ const ROW_INSET = 12
|
|
|
25
25
|
// The chip trigger (see `colorSection`): a CHIP_SIZE circle inside a pill padded by CHIP_TRIGGER_PADDING.
|
|
26
26
|
const CHIP_SIZE = 32
|
|
27
27
|
const CHIP_TRIGGER_PADDING = 6
|
|
28
|
+
const CHIP_TRIGGER_END_PADDING = CHIP_TRIGGER_PADDING + 8
|
|
29
|
+
const CHIP_TRIGGER_GAP = 8
|
|
30
|
+
const PLAIN_TRIGGER_GAP = 2
|
|
31
|
+
const CHEVRON_SIZE = 16
|
|
32
|
+
// Everything in a trigger but its label: the chip, the chevron, the gaps and the padding (84pt for the
|
|
33
|
+
// chip pill, 18 for the plain trigger). A shrinking trigger (see `triggerStyle`) stops here, so its
|
|
34
|
+
// chip and chevron always fit inside it.
|
|
35
|
+
const CHIP_TRIGGER_FIXED_WIDTH = CHIP_TRIGGER_PADDING + CHIP_SIZE + 2 * CHIP_TRIGGER_GAP + CHEVRON_SIZE + CHIP_TRIGGER_END_PADDING
|
|
36
|
+
const PLAIN_TRIGGER_FIXED_WIDTH = PLAIN_TRIGGER_GAP + CHEVRON_SIZE
|
|
28
37
|
|
|
29
38
|
// The color row and page (see `colorSection`). Every height here is fixed (explicit line heights),
|
|
30
39
|
// so pickerContentSize can size them exactly. Swatches are the same 28pt circles as
|
|
@@ -74,16 +83,16 @@ export interface ProfilePickerColorSection {
|
|
|
74
83
|
|
|
75
84
|
// A complete placement decision for the popover: which way it opens, how tall it may grow, whether
|
|
76
85
|
// this open's measurement has landed yet, and the ref attached to the measured trigger.
|
|
77
|
-
// Structurally identical to @tastic/hud's own `AlignResult` (
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
86
|
+
// Structurally identical to @tastic/hud's own `AlignResult` (the same shape `useAutoAlign` itself
|
|
87
|
+
// returns), and a hud `AlignResult` (e.g. from `useZoneClampedAlign`) is assignable to this as-is.
|
|
88
|
+
// The ref holds hud's `ViewInstance`, a View's instance under React Native's legacy types and its
|
|
89
|
+
// strict ones alike (a bare `RefObject<View>` is a ref to the View function under the strict ones).
|
|
81
90
|
export interface ProfilePickerAlignResult {
|
|
82
91
|
align: 'left' | 'right' | 'center'
|
|
83
92
|
verticalAlign: 'below' | 'above'
|
|
84
93
|
maxHeight: number
|
|
85
94
|
measured: boolean
|
|
86
|
-
triggerRef: RefObject<
|
|
95
|
+
triggerRef: RefObject<ViewInstance | null>
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
export interface ProfilePickerContentSize {
|
|
@@ -134,6 +143,8 @@ interface PickerMenuProps {
|
|
|
134
143
|
align?: 'left' | 'right' | 'center'
|
|
135
144
|
alignOverride?: ProfilePickerAlignResult
|
|
136
145
|
rotation: PopoverRotation
|
|
146
|
+
// The pressable trigger's own style (a host's background behind the pill). A `flexShrink` in it lets
|
|
147
|
+
// the trigger give way in the host's row: see `shrinks` below.
|
|
137
148
|
triggerStyle?: StyleProp<ViewStyle>
|
|
138
149
|
// Pinned under the list page's rows, behind a divider (ProfilePicker's Manage row).
|
|
139
150
|
footer?: ReactNode
|
|
@@ -172,9 +183,21 @@ export function PickerMenu({ id, host, color, dark, label, chip, colorSection, h
|
|
|
172
183
|
// Shown on the list page only (see the footer below).
|
|
173
184
|
const showFooter = !!footer && !showColorPage
|
|
174
185
|
|
|
186
|
+
// A host's `flexShrink` is how far the trigger gives way in the host's row, so it goes on the anchor,
|
|
187
|
+
// which is the host's flex item (on the pressable it would only squeeze the pill's height), down to
|
|
188
|
+
// the trigger's fixed parts. The pressable is then capped at the anchor's width, so its label is cut
|
|
189
|
+
// off with an ellipsis and the chip and chevron keep their size. With room, the anchor and the pill
|
|
190
|
+
// are their natural width, as before. The pill's edges, and so the measured trigger, the caret and
|
|
191
|
+
// the popover's alignment, follow the shrunk width. Without a flexShrink nothing here changes.
|
|
192
|
+
const flatTriggerStyle: ViewStyle = StyleSheet.flatten(triggerStyle) ?? {}
|
|
193
|
+
const { flexShrink, ...pillStyle } = flatTriggerStyle
|
|
194
|
+
const shrinks = typeof flexShrink === 'number' && flexShrink > 0
|
|
195
|
+
const anchorShrink = shrinks ? { flexShrink, minWidth: chipTrigger ? CHIP_TRIGGER_FIXED_WIDTH : PLAIN_TRIGGER_FIXED_WIDTH } : undefined
|
|
196
|
+
|
|
175
197
|
return (
|
|
176
|
-
<View style={[styles.anchor, open && styles.anchorOpen]} onLayout={handleAnchorLayout}>
|
|
177
|
-
|
|
198
|
+
<View style={[styles.anchor, anchorShrink, open && styles.anchorOpen]} onLayout={handleAnchorLayout}>
|
|
199
|
+
{/* The cap goes before the host's style, so a host's own maxWidth still wins. */}
|
|
200
|
+
<TouchableRipple onPress={handleTriggerPress} borderless={chipTrigger} style={[styles.trigger, chipTrigger && styles.chipTrigger, shrinks && styles.triggerShrinks, shrinks ? pillStyle : triggerStyle]} accessibilityRole='button' accessibilityLabel={triggerLabel}>
|
|
178
201
|
<View ref={triggerRef} collapsable={false} style={chipTrigger ? styles.chipTriggerInner : styles.triggerInner}>
|
|
179
202
|
{chip && <IdentityChip color={color} tag={chip.tag} icon={chip.icon} size={CHIP_SIZE} />}
|
|
180
203
|
{/* The chip trigger's name and chevron are plain fg: the chip carries the color, and a muted
|
|
@@ -183,7 +206,7 @@ export function PickerMenu({ id, host, color, dark, label, chip, colorSection, h
|
|
|
183
206
|
<Text style={[styles.triggerLabel, { color: chipTrigger ? fg : color }]} numberOfLines={1}>
|
|
184
207
|
{triggerLabel}
|
|
185
208
|
</Text>
|
|
186
|
-
<Icon source='menu-down' size={
|
|
209
|
+
<Icon source='menu-down' size={CHEVRON_SIZE} color={chipTrigger ? fg : color} />
|
|
187
210
|
</View>
|
|
188
211
|
</TouchableRipple>
|
|
189
212
|
|
|
@@ -272,12 +295,13 @@ function ColorRow({ section, fg, dividerColor, onPress }: { section: ProfilePick
|
|
|
272
295
|
const { value, previewValue, label = 'Color', caption } = section
|
|
273
296
|
const renderSwatch = useProfileSwatchRenderer()
|
|
274
297
|
const drawn = previewValue ? previewValue(value) : value
|
|
298
|
+
const drawing = drawSwatch(renderSwatch, drawn, COLOR_ROW_SWATCH_SIZE, 'colorRow')
|
|
275
299
|
return (
|
|
276
300
|
<>
|
|
277
301
|
<TouchableRipple onPress={onPress} style={[pickerMenuStyles.row, { height: colorRowHeight(caption) }]} accessibilityRole='button' accessibilityLabel={caption ? `${label}, ${caption}` : label}>
|
|
278
302
|
<View style={pickerMenuStyles.rowInner}>
|
|
279
303
|
{/* A ProfileColorProvider renderSwatch draws it in place of the flat fill. */}
|
|
280
|
-
<View style={[styles.colorRowSwatch,
|
|
304
|
+
<View style={[styles.colorRowSwatch, drawing === null && { backgroundColor: drawn }]}>{drawing !== null && <SwatchLayer size={COLOR_ROW_SWATCH_SIZE}>{drawing}</SwatchLayer>}</View>
|
|
281
305
|
<View style={styles.colorRowText}>
|
|
282
306
|
<Text style={[styles.colorRowLabel, { color: fg }]} numberOfLines={1}>
|
|
283
307
|
{label}
|
|
@@ -329,15 +353,17 @@ function ColorPage({ section, homeColor, fg, onBack }: { section: ProfilePickerC
|
|
|
329
353
|
const swatchDisplay = displayFor(swatch.value)
|
|
330
354
|
const contrast = getContrastColor(swatchDisplay)
|
|
331
355
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label
|
|
356
|
+
const drawnSize = selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE
|
|
357
|
+
const drawing = drawSwatch(renderSwatch, swatchDisplay, drawnSize, 'colorPage')
|
|
332
358
|
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
359
|
// The same swatch as @tastic/hud's InlineColorPicker: a filled circle, the selected one
|
|
334
360
|
// ringed and checked, a taken one faded with an X. TouchableRipple takes one child, so a
|
|
335
361
|
// drawn swatch and its mark share a face that fills the circle inside the ring.
|
|
336
362
|
return (
|
|
337
|
-
<TouchableRipple key={swatch.value} disabled={taken && !swappable} onPress={() => onChange(swatch.value)} borderless style={[styles.swatch,
|
|
338
|
-
{
|
|
363
|
+
<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 }}>
|
|
364
|
+
{drawing !== null ? (
|
|
339
365
|
<View style={styles.swatchFace}>
|
|
340
|
-
<SwatchLayer
|
|
366
|
+
<SwatchLayer size={drawnSize}>{drawing}</SwatchLayer>
|
|
341
367
|
{mark}
|
|
342
368
|
</View>
|
|
343
369
|
) : (
|
|
@@ -392,10 +418,10 @@ const styles = StyleSheet.create({
|
|
|
392
418
|
chipTriggerInner: {
|
|
393
419
|
alignItems: 'center',
|
|
394
420
|
flexDirection: 'row',
|
|
395
|
-
gap:
|
|
421
|
+
gap: CHIP_TRIGGER_GAP,
|
|
396
422
|
height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
|
|
397
423
|
paddingLeft: CHIP_TRIGGER_PADDING,
|
|
398
|
-
paddingRight:
|
|
424
|
+
paddingRight: CHIP_TRIGGER_END_PADDING
|
|
399
425
|
},
|
|
400
426
|
colorRowCaption: {
|
|
401
427
|
fontSize: 12,
|
|
@@ -504,12 +530,21 @@ const styles = StyleSheet.create({
|
|
|
504
530
|
triggerInner: {
|
|
505
531
|
alignItems: 'center',
|
|
506
532
|
flexDirection: 'row',
|
|
507
|
-
gap:
|
|
533
|
+
gap: PLAIN_TRIGGER_GAP,
|
|
508
534
|
height: TRIGGER_HEIGHT
|
|
509
535
|
},
|
|
536
|
+
// Lets the label, alone in its row, give way to a trigger narrower than its name (see `shrinks`).
|
|
537
|
+
// With room it keeps its natural width, so this changes nothing there.
|
|
510
538
|
triggerLabel: {
|
|
539
|
+
flexShrink: 1,
|
|
511
540
|
fontSize: 13,
|
|
512
541
|
fontWeight: '700',
|
|
513
|
-
letterSpacing: 1
|
|
542
|
+
letterSpacing: 1,
|
|
543
|
+
minWidth: 0
|
|
544
|
+
},
|
|
545
|
+
// A shrinking trigger (see `shrinks`): no wider than its anchor. Its row is then laid out at that
|
|
546
|
+
// width, which is what cuts the label off.
|
|
547
|
+
triggerShrinks: {
|
|
548
|
+
maxWidth: '100%'
|
|
514
549
|
}
|
|
515
550
|
})
|
|
@@ -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,11 +24,13 @@ 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
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
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.
|
|
32
|
+
// ProfilesManager hands it to hud's InlineColorPicker too (its trigger as a 'chip', its grid as the
|
|
33
|
+
// 'colorPage'). Rows that fill with a color (a selected picker row) stay flat.
|
|
25
34
|
renderSwatch?: ProfileSwatchRenderer
|
|
26
35
|
children: ReactNode
|
|
27
36
|
}
|
package/src/ProfilePicker.tsx
CHANGED
|
@@ -100,7 +100,8 @@ interface ProfilePickerProps<P extends Profile> {
|
|
|
100
100
|
// exactly as before.
|
|
101
101
|
colorSection?: ProfilePickerColorSection
|
|
102
102
|
// Extra style for the trigger (its pressable box) - e.g. a background behind the chip trigger's
|
|
103
|
-
// pill. The chip trigger already pads itself and rounds into a pill.
|
|
103
|
+
// pill. The chip trigger already pads itself and rounds into a pill. A `flexShrink` here lets the
|
|
104
|
+
// trigger give way in a row too narrow for it, cutting its label off (see PickerMenu).
|
|
104
105
|
triggerStyle?: StyleProp<ViewStyle>
|
|
105
106
|
}
|
|
106
107
|
|