@echoform/echo-ui 0.1.0 → 0.2.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.
Files changed (154) hide show
  1. package/README.md +57 -10
  2. package/dist/components/EchoChangeProfileNameModal.d.ts +59 -0
  3. package/dist/components/EchoChangeProfileNameModal.d.ts.map +1 -0
  4. package/dist/components/EchoChangeProfileNameModal.js +317 -0
  5. package/dist/components/EchoChangeProfileNameModal.js.map +1 -0
  6. package/dist/components/EchoColorPicker.d.ts +34 -0
  7. package/dist/components/EchoColorPicker.d.ts.map +1 -0
  8. package/dist/components/EchoColorPicker.js +206 -0
  9. package/dist/components/EchoColorPicker.js.map +1 -0
  10. package/dist/components/EchoConnectedAppsModal.d.ts +40 -0
  11. package/dist/components/EchoConnectedAppsModal.d.ts.map +1 -0
  12. package/dist/components/EchoConnectedAppsModal.js +220 -0
  13. package/dist/components/EchoConnectedAppsModal.js.map +1 -0
  14. package/dist/components/EchoDeleteProfilePanel.d.ts +63 -0
  15. package/dist/components/EchoDeleteProfilePanel.d.ts.map +1 -0
  16. package/dist/components/EchoDeleteProfilePanel.js +286 -0
  17. package/dist/components/EchoDeleteProfilePanel.js.map +1 -0
  18. package/dist/components/EchoDeletionIntercept.d.ts +39 -0
  19. package/dist/components/EchoDeletionIntercept.d.ts.map +1 -0
  20. package/dist/components/EchoDeletionIntercept.js +212 -0
  21. package/dist/components/EchoDeletionIntercept.js.map +1 -0
  22. package/dist/components/EchoDeletionStatusCard.d.ts +35 -0
  23. package/dist/components/EchoDeletionStatusCard.d.ts.map +1 -0
  24. package/dist/components/EchoDeletionStatusCard.js +156 -0
  25. package/dist/components/EchoDeletionStatusCard.js.map +1 -0
  26. package/dist/components/EchoEmblem.d.ts +93 -0
  27. package/dist/components/EchoEmblem.d.ts.map +1 -0
  28. package/dist/components/EchoEmblem.js +308 -0
  29. package/dist/components/EchoEmblem.js.map +1 -0
  30. package/dist/components/EchoEmblemPicker.d.ts +71 -0
  31. package/dist/components/EchoEmblemPicker.d.ts.map +1 -0
  32. package/dist/components/EchoEmblemPicker.js +490 -0
  33. package/dist/components/EchoEmblemPicker.js.map +1 -0
  34. package/dist/components/EchoErrorBanner.d.ts +17 -0
  35. package/dist/components/EchoErrorBanner.d.ts.map +1 -0
  36. package/dist/components/EchoErrorBanner.js +48 -0
  37. package/dist/components/EchoErrorBanner.js.map +1 -0
  38. package/dist/components/EchoFormField.d.ts +27 -0
  39. package/dist/components/EchoFormField.d.ts.map +1 -0
  40. package/dist/components/EchoFormField.js +49 -0
  41. package/dist/components/EchoFormField.js.map +1 -0
  42. package/dist/components/EchoLinkButton.d.ts +16 -0
  43. package/dist/components/EchoLinkButton.d.ts.map +1 -0
  44. package/dist/components/EchoLinkButton.js +37 -0
  45. package/dist/components/EchoLinkButton.js.map +1 -0
  46. package/dist/components/EchoMoreScreen.d.ts +41 -0
  47. package/dist/components/EchoMoreScreen.d.ts.map +1 -0
  48. package/dist/components/EchoMoreScreen.js +94 -0
  49. package/dist/components/EchoMoreScreen.js.map +1 -0
  50. package/dist/components/EchoPasswordField.d.ts +28 -0
  51. package/dist/components/EchoPasswordField.d.ts.map +1 -0
  52. package/dist/components/EchoPasswordField.js +67 -0
  53. package/dist/components/EchoPasswordField.js.map +1 -0
  54. package/dist/components/EchoPrimaryButton.d.ts +23 -0
  55. package/dist/components/EchoPrimaryButton.d.ts.map +1 -0
  56. package/dist/components/EchoPrimaryButton.js +54 -0
  57. package/dist/components/EchoPrimaryButton.js.map +1 -0
  58. package/dist/components/EchoProfileCard.d.ts +35 -0
  59. package/dist/components/EchoProfileCard.d.ts.map +1 -0
  60. package/dist/components/EchoProfileCard.js +206 -0
  61. package/dist/components/EchoProfileCard.js.map +1 -0
  62. package/dist/components/EchoProfileDivider.d.ts +14 -0
  63. package/dist/components/EchoProfileDivider.d.ts.map +1 -0
  64. package/dist/components/EchoProfileDivider.js +31 -0
  65. package/dist/components/EchoProfileDivider.js.map +1 -0
  66. package/dist/components/EchoProfileHero.d.ts +48 -0
  67. package/dist/components/EchoProfileHero.d.ts.map +1 -0
  68. package/dist/components/EchoProfileHero.js +215 -0
  69. package/dist/components/EchoProfileHero.js.map +1 -0
  70. package/dist/components/EchoProfileOverview.d.ts +18 -0
  71. package/dist/components/EchoProfileOverview.d.ts.map +1 -0
  72. package/dist/components/EchoProfileOverview.js +36 -0
  73. package/dist/components/EchoProfileOverview.js.map +1 -0
  74. package/dist/components/EchoProfileRow.d.ts +60 -0
  75. package/dist/components/EchoProfileRow.d.ts.map +1 -0
  76. package/dist/components/EchoProfileRow.js +113 -0
  77. package/dist/components/EchoProfileRow.js.map +1 -0
  78. package/dist/components/EchoProfileSection.d.ts +29 -0
  79. package/dist/components/EchoProfileSection.d.ts.map +1 -0
  80. package/dist/components/EchoProfileSection.js +58 -0
  81. package/dist/components/EchoProfileSection.js.map +1 -0
  82. package/dist/components/EchoRewardNotificationBanner.d.ts +61 -0
  83. package/dist/components/EchoRewardNotificationBanner.d.ts.map +1 -0
  84. package/dist/components/EchoRewardNotificationBanner.js +272 -0
  85. package/dist/components/EchoRewardNotificationBanner.js.map +1 -0
  86. package/dist/components/EchoSettingsRow.d.ts +24 -0
  87. package/dist/components/EchoSettingsRow.d.ts.map +1 -0
  88. package/dist/components/EchoSettingsRow.js +84 -0
  89. package/dist/components/EchoSettingsRow.js.map +1 -0
  90. package/dist/components/EchoSettingsSection.d.ts +16 -0
  91. package/dist/components/EchoSettingsSection.d.ts.map +1 -0
  92. package/dist/components/EchoSettingsSection.js +48 -0
  93. package/dist/components/EchoSettingsSection.js.map +1 -0
  94. package/dist/components/EchoSignOutButton.d.ts +28 -0
  95. package/dist/components/EchoSignOutButton.d.ts.map +1 -0
  96. package/dist/components/EchoSignOutButton.js +67 -0
  97. package/dist/components/EchoSignOutButton.js.map +1 -0
  98. package/dist/components/EchoSuccessBanner.d.ts +17 -0
  99. package/dist/components/EchoSuccessBanner.d.ts.map +1 -0
  100. package/dist/components/EchoSuccessBanner.js +48 -0
  101. package/dist/components/EchoSuccessBanner.js.map +1 -0
  102. package/dist/components/EchoSyncPanel.d.ts +165 -0
  103. package/dist/components/EchoSyncPanel.d.ts.map +1 -0
  104. package/dist/components/EchoSyncPanel.js +913 -0
  105. package/dist/components/EchoSyncPanel.js.map +1 -0
  106. package/dist/components/EchoSyncPromptModal.d.ts +87 -0
  107. package/dist/components/EchoSyncPromptModal.d.ts.map +1 -0
  108. package/dist/components/EchoSyncPromptModal.js +351 -0
  109. package/dist/components/EchoSyncPromptModal.js.map +1 -0
  110. package/dist/components/EchoTitlePicker.d.ts +72 -0
  111. package/dist/components/EchoTitlePicker.d.ts.map +1 -0
  112. package/dist/components/EchoTitlePicker.js +487 -0
  113. package/dist/components/EchoTitlePicker.js.map +1 -0
  114. package/dist/components/EchoVerificationCodeInput.d.ts +40 -0
  115. package/dist/components/EchoVerificationCodeInput.d.ts.map +1 -0
  116. package/dist/components/EchoVerificationCodeInput.js +140 -0
  117. package/dist/components/EchoVerificationCodeInput.js.map +1 -0
  118. package/dist/components/EchoWelcome.d.ts +44 -0
  119. package/dist/components/EchoWelcome.d.ts.map +1 -0
  120. package/dist/components/EchoWelcome.js +289 -0
  121. package/dist/components/EchoWelcome.js.map +1 -0
  122. package/dist/components/EchoWhyBecomeAnEchoModal.d.ts +34 -0
  123. package/dist/components/EchoWhyBecomeAnEchoModal.d.ts.map +1 -0
  124. package/dist/components/EchoWhyBecomeAnEchoModal.js +321 -0
  125. package/dist/components/EchoWhyBecomeAnEchoModal.js.map +1 -0
  126. package/dist/components/EchoformInfoButton.d.ts +27 -0
  127. package/dist/components/EchoformInfoButton.d.ts.map +1 -0
  128. package/dist/components/EchoformInfoButton.js +60 -0
  129. package/dist/components/EchoformInfoButton.js.map +1 -0
  130. package/dist/components/EchoformInfoModal.d.ts +63 -0
  131. package/dist/components/EchoformInfoModal.d.ts.map +1 -0
  132. package/dist/components/EchoformInfoModal.js +347 -0
  133. package/dist/components/EchoformInfoModal.js.map +1 -0
  134. package/dist/echoColors.d.ts +58 -0
  135. package/dist/echoColors.d.ts.map +1 -0
  136. package/dist/echoColors.js +94 -0
  137. package/dist/echoColors.js.map +1 -0
  138. package/dist/index.d.ts +71 -3
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +86 -4
  141. package/dist/index.js.map +1 -1
  142. package/dist/theme.d.ts +48 -0
  143. package/dist/theme.d.ts.map +1 -0
  144. package/dist/theme.js +51 -0
  145. package/dist/theme.js.map +1 -0
  146. package/dist/titleRarity.d.ts +19 -0
  147. package/dist/titleRarity.d.ts.map +1 -0
  148. package/dist/titleRarity.js +46 -0
  149. package/dist/titleRarity.js.map +1 -0
  150. package/package.json +6 -5
  151. package/dist/components/EchoUITest.d.ts +0 -14
  152. package/dist/components/EchoUITest.d.ts.map +0 -1
  153. package/dist/components/EchoUITest.js +0 -53
  154. package/dist/components/EchoUITest.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * EchoDeletionStatusCard
4
+ *
5
+ * Presentation-only deletion-status card shown on the signed-in profile screen
6
+ * when an Echo Profile deletion has already been scheduled or is processing.
7
+ *
8
+ * Extracted from the deletion-card section of the Echo UI Reference account screen.
9
+ *
10
+ * STATUS BEHAVIOR
11
+ * ──────────────
12
+ * pending / failed → warning-styled "Deletion Scheduled" card with restore button
13
+ * processing → error-styled "Deletion In Progress" card, no restore button
14
+ *
15
+ * HOST RESPONSIBILITIES
16
+ * ─────────────────────
17
+ * • Actual cancellation / restoration RPC (onRestore)
18
+ * • Loading state while the restore RPC is in flight (restoreLoading)
19
+ *
20
+ * No Expo Router, no auth, no contexts, no services, no app-specific code.
21
+ * Canonical shared component for @echoform/echo-ui.
22
+ */
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.EchoDeletionStatusCard = EchoDeletionStatusCard;
28
+ const react_1 = __importDefault(require("react"));
29
+ const react_native_1 = require("react-native");
30
+ const vector_icons_1 = require("@expo/vector-icons");
31
+ const theme_1 = require("../theme");
32
+ // ─────────────────────────────────────────────────────────────────────────────
33
+ // Package-local date formatter
34
+ // ─────────────────────────────────────────────────────────────────────────────
35
+ function formatDate(iso) {
36
+ try {
37
+ return new Date(iso).toLocaleDateString(undefined, {
38
+ day: 'numeric',
39
+ month: 'long',
40
+ year: 'numeric',
41
+ });
42
+ }
43
+ catch (_a) {
44
+ return iso;
45
+ }
46
+ }
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Component
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ function EchoDeletionStatusCard({ status, scheduledFor, daysRemaining = 0, restoreLoading = false, onRestore, }) {
51
+ if (status === 'processing') {
52
+ return (<react_native_1.View style={styles.card} accessible accessibilityRole="alert" accessibilityLabel="Echo Profile deletion is currently in progress and cannot be cancelled.">
53
+ <react_native_1.View style={styles.header}>
54
+ <react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.error} importantForAccessibility="no"/>
55
+ <react_native_1.Text style={styles.titleProcessing} importantForAccessibility="no">
56
+ Deletion In Progress
57
+ </react_native_1.Text>
58
+ </react_native_1.View>
59
+ <react_native_1.Text style={styles.body} importantForAccessibility="no">
60
+ Your Echo Profile is being permanently deleted. This process cannot be interrupted.
61
+ </react_native_1.Text>
62
+ </react_native_1.View>);
63
+ }
64
+ // pending or failed
65
+ const a11yLabel = [
66
+ 'Echo Profile deletion scheduled.',
67
+ `${daysRemaining} days remaining.`,
68
+ 'You may restore your profile before then.',
69
+ ].join(' ');
70
+ return (<react_native_1.View style={styles.card} accessible accessibilityRole="alert" accessibilityLabel={a11yLabel}>
71
+ <react_native_1.View style={styles.header}>
72
+ <vector_icons_1.MaterialIcons name="schedule" size={20} color={theme_1.COLORS.warning} importantForAccessibility="no" accessibilityElementsHidden/>
73
+ <react_native_1.Text style={styles.titlePending} importantForAccessibility="no">
74
+ Deletion Scheduled
75
+ </react_native_1.Text>
76
+ </react_native_1.View>
77
+
78
+ <react_native_1.Text style={styles.body} importantForAccessibility="no">
79
+ Your Echo Profile is scheduled for permanent deletion on
80
+ </react_native_1.Text>
81
+
82
+ <react_native_1.Text style={styles.date} importantForAccessibility="no">
83
+ {scheduledFor ? formatDate(scheduledFor) : '—'}
84
+ </react_native_1.Text>
85
+
86
+ <react_native_1.Text style={[styles.body, { marginTop: theme_1.SPACING.xs }]} importantForAccessibility="no">
87
+ {`${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining. Cancel before then to restore your profile.`}
88
+ </react_native_1.Text>
89
+
90
+ {onRestore ? (<react_native_1.Pressable onPress={onRestore} disabled={restoreLoading} style={({ pressed }) => [
91
+ styles.restoreBtn,
92
+ pressed && { opacity: 0.75 },
93
+ restoreLoading && { opacity: 0.5 },
94
+ ]} accessibilityLabel="Cancel deletion and restore Echo Profile" accessibilityRole="button" accessibilityState={{ busy: restoreLoading }}>
95
+ {restoreLoading ? (<react_native_1.ActivityIndicator size="small" color={theme_1.COLORS.neonGreen} importantForAccessibility="no"/>) : (<vector_icons_1.MaterialIcons name="restore" size={18} color={theme_1.COLORS.neonGreen} importantForAccessibility="no" accessibilityElementsHidden/>)}
96
+ <react_native_1.Text style={styles.restoreBtnText}>Restore Echo Profile</react_native_1.Text>
97
+ </react_native_1.Pressable>) : null}
98
+ </react_native_1.View>);
99
+ }
100
+ // ─────────────────────────────────────────────────────────────────────────────
101
+ // Styles
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+ const styles = react_native_1.StyleSheet.create({
104
+ card: {
105
+ backgroundColor: theme_1.COLORS.warning + '0C',
106
+ borderRadius: theme_1.RADIUS.xl,
107
+ borderWidth: 1,
108
+ borderColor: theme_1.COLORS.warning + '44',
109
+ padding: theme_1.SPACING.xl,
110
+ gap: theme_1.SPACING.sm,
111
+ },
112
+ header: {
113
+ flexDirection: 'row',
114
+ alignItems: 'center',
115
+ gap: theme_1.SPACING.sm,
116
+ },
117
+ titlePending: {
118
+ fontSize: theme_1.FONTS.md,
119
+ fontWeight: '800',
120
+ color: theme_1.COLORS.warning,
121
+ },
122
+ titleProcessing: {
123
+ fontSize: theme_1.FONTS.md,
124
+ fontWeight: '800',
125
+ color: theme_1.COLORS.error,
126
+ },
127
+ body: {
128
+ fontSize: theme_1.FONTS.sm,
129
+ color: theme_1.COLORS.textSecondary,
130
+ lineHeight: 20,
131
+ },
132
+ date: {
133
+ fontSize: theme_1.FONTS.lg,
134
+ fontWeight: '700',
135
+ color: theme_1.COLORS.warning,
136
+ },
137
+ restoreBtn: {
138
+ flexDirection: 'row',
139
+ alignItems: 'center',
140
+ justifyContent: 'center',
141
+ gap: theme_1.SPACING.sm,
142
+ backgroundColor: theme_1.COLORS.neonGreen + '15',
143
+ borderRadius: theme_1.RADIUS.lg,
144
+ borderWidth: 1.5,
145
+ borderColor: theme_1.COLORS.neonGreen + 'BB',
146
+ paddingVertical: theme_1.SPACING.md,
147
+ marginTop: theme_1.SPACING.xs,
148
+ minHeight: 52,
149
+ },
150
+ restoreBtnText: {
151
+ fontSize: theme_1.FONTS.md,
152
+ fontWeight: '700',
153
+ color: theme_1.COLORS.neonGreen,
154
+ },
155
+ });
156
+ //# sourceMappingURL=EchoDeletionStatusCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EchoDeletionStatusCard.js","sourceRoot":"","sources":["../../src/components/EchoDeletionStatusCard.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * EchoEmblem — Universal Echo Emblem display component.
3
+ *
4
+ * RENDERING CONTRACT
5
+ * ──────────────────
6
+ * • Reserves a fixed square slot of `size × size` logical pixels.
7
+ * • Images are rendered with contentFit="contain" — no cropping, no stretching.
8
+ * • No overflow:hidden on any container — decorative PNG details outside the
9
+ * circular artwork must never be clipped.
10
+ * • No borderRadius mask applied to the image slot.
11
+ * • Transparent PNG margins are preserved; layout never shifts while the image
12
+ * loads from the network.
13
+ * • The selection ring extends 4pt outside the slot using absolute positioning;
14
+ * it is visible because no parent clips it.
15
+ *
16
+ * STATES
17
+ * ──────
18
+ * Normal | Loading (spinner) | Error (fallback icon) | Selected (ring) | Disabled (opacity)
19
+ *
20
+ * ACCESSIBILITY
21
+ * ─────────────
22
+ * • accessibilityRole="button" when onPress is provided, "image" otherwise.
23
+ * • accessibilityState: selected, disabled, busy (while loading).
24
+ * • Label auto-computes from displayName; overridable via accessibilityLabel.
25
+ * • Inner image and overlays are hidden from screen readers.
26
+ *
27
+ * STUCK-SPINNER FIX
28
+ * ─────────────────
29
+ * The source prop frequently arrives as a new object literal on every render:
30
+ *
31
+ * source={{ uri: imageUrl }} // new object every render
32
+ *
33
+ * If useEffect depended on the object reference, it would reset loading/error
34
+ * states on every render — even when the URI itself has not changed — leaving
35
+ * the spinner permanently visible for already-cached images that never fire
36
+ * onLoadStart again after the first render.
37
+ *
38
+ * Fix: derive a stable string key from the actual URI or require() number,
39
+ * and reset internal state ONLY when that key changes. The wrapper object's
40
+ * identity is intentionally ignored.
41
+ */
42
+ import React from 'react';
43
+ import type { ImageSource } from 'expo-image';
44
+ export type EchoEmblemProps = {
45
+ /**
46
+ * Remote: `{ uri: 'https://...' }`
47
+ * Local: `require('./assets/my-emblem.png')`
48
+ *
49
+ * Uses expo-image ImageSource — covers both forms natively.
50
+ */
51
+ source: ImageSource;
52
+ /**
53
+ * Square slot size in logical pixels.
54
+ * Tested: 32 | 48 | 64 | 96 | 144.
55
+ * Defaults to 96.
56
+ */
57
+ size?: number;
58
+ /**
59
+ * Human-readable emblem name.
60
+ * Drives the auto-computed accessibility label:
61
+ * "Smiley Echo Emblem"
62
+ * "Smiley Echo Emblem, selected"
63
+ * "Smiley Echo Emblem, unavailable"
64
+ */
65
+ displayName?: string;
66
+ /** True while this emblem is the active/chosen selection. */
67
+ selected?: boolean;
68
+ /** True when the emblem is locked or otherwise unavailable to the user. */
69
+ disabled?: boolean;
70
+ /**
71
+ * External loading override.
72
+ * Pass true while fetching emblem metadata before the URL is resolved.
73
+ * The component's internal image-load tracking runs independently.
74
+ */
75
+ loading?: boolean;
76
+ /**
77
+ * When true (default), a neon cyan ring is rendered around the slot
78
+ * when `selected` is true.
79
+ */
80
+ showSelectionIndicator?: boolean;
81
+ /** Overrides the auto-computed VoiceOver / TalkBack label. */
82
+ accessibilityLabel?: string;
83
+ /** Optional hint appended by screen readers after the label. */
84
+ accessibilityHint?: string;
85
+ /**
86
+ * When provided, the component renders as an accessible Pressable button
87
+ * with scale + opacity press feedback.
88
+ * Without onPress it renders as an accessible image view.
89
+ */
90
+ onPress?: () => void;
91
+ };
92
+ export declare function EchoEmblem({ source, size, displayName, selected, disabled, loading: externalLoading, showSelectionIndicator, accessibilityLabel: customLabel, accessibilityHint, onPress, }: EchoEmblemProps): React.JSX.Element;
93
+ //# sourceMappingURL=EchoEmblem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EchoEmblem.d.ts","sourceRoot":"","sources":["../../src/components/EchoEmblem.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AASjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQ9C,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAwCF,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,IAAS,EACT,WAAW,EACX,QAAgB,EAChB,QAAgB,EAChB,OAAO,EAAE,eAAuB,EAChC,sBAA6B,EAC7B,kBAAkB,EAAE,WAAW,EAC/B,iBAAiB,EACjB,OAAO,GACR,EAAE,eAAe,qBAoNjB"}
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ /**
3
+ * EchoEmblem — Universal Echo Emblem display component.
4
+ *
5
+ * RENDERING CONTRACT
6
+ * ──────────────────
7
+ * • Reserves a fixed square slot of `size × size` logical pixels.
8
+ * • Images are rendered with contentFit="contain" — no cropping, no stretching.
9
+ * • No overflow:hidden on any container — decorative PNG details outside the
10
+ * circular artwork must never be clipped.
11
+ * • No borderRadius mask applied to the image slot.
12
+ * • Transparent PNG margins are preserved; layout never shifts while the image
13
+ * loads from the network.
14
+ * • The selection ring extends 4pt outside the slot using absolute positioning;
15
+ * it is visible because no parent clips it.
16
+ *
17
+ * STATES
18
+ * ──────
19
+ * Normal | Loading (spinner) | Error (fallback icon) | Selected (ring) | Disabled (opacity)
20
+ *
21
+ * ACCESSIBILITY
22
+ * ─────────────
23
+ * • accessibilityRole="button" when onPress is provided, "image" otherwise.
24
+ * • accessibilityState: selected, disabled, busy (while loading).
25
+ * • Label auto-computes from displayName; overridable via accessibilityLabel.
26
+ * • Inner image and overlays are hidden from screen readers.
27
+ *
28
+ * STUCK-SPINNER FIX
29
+ * ─────────────────
30
+ * The source prop frequently arrives as a new object literal on every render:
31
+ *
32
+ * source={{ uri: imageUrl }} // new object every render
33
+ *
34
+ * If useEffect depended on the object reference, it would reset loading/error
35
+ * states on every render — even when the URI itself has not changed — leaving
36
+ * the spinner permanently visible for already-cached images that never fire
37
+ * onLoadStart again after the first render.
38
+ *
39
+ * Fix: derive a stable string key from the actual URI or require() number,
40
+ * and reset internal state ONLY when that key changes. The wrapper object's
41
+ * identity is intentionally ignored.
42
+ */
43
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
44
+ if (k2 === undefined) k2 = k;
45
+ var desc = Object.getOwnPropertyDescriptor(m, k);
46
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
47
+ desc = { enumerable: true, get: function() { return m[k]; } };
48
+ }
49
+ Object.defineProperty(o, k2, desc);
50
+ }) : (function(o, m, k, k2) {
51
+ if (k2 === undefined) k2 = k;
52
+ o[k2] = m[k];
53
+ }));
54
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
55
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
56
+ }) : function(o, v) {
57
+ o["default"] = v;
58
+ });
59
+ var __importStar = (this && this.__importStar) || (function () {
60
+ var ownKeys = function(o) {
61
+ ownKeys = Object.getOwnPropertyNames || function (o) {
62
+ var ar = [];
63
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
64
+ return ar;
65
+ };
66
+ return ownKeys(o);
67
+ };
68
+ return function (mod) {
69
+ if (mod && mod.__esModule) return mod;
70
+ var result = {};
71
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
72
+ __setModuleDefault(result, mod);
73
+ return result;
74
+ };
75
+ })();
76
+ Object.defineProperty(exports, "__esModule", { value: true });
77
+ exports.EchoEmblem = EchoEmblem;
78
+ const react_1 = __importStar(require("react"));
79
+ const react_native_1 = require("react-native");
80
+ const expo_image_1 = require("expo-image");
81
+ const vector_icons_1 = require("@expo/vector-icons");
82
+ const theme_1 = require("../theme");
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ // Stable source key
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+ /**
87
+ * Derive a stable primitive key from an ImageSource.
88
+ *
89
+ * WHY: Callers frequently pass `source={{ uri: url }}` which creates a NEW
90
+ * object on every render. Using the object reference in a useEffect dep would
91
+ * reset loading/error on every render — even for already-cached images that
92
+ * will not fire onLoadStart again, causing the spinner to stick forever.
93
+ *
94
+ * By extracting the underlying string (URI) or number (require() asset),
95
+ * the effect only resets state when the actual image content changes.
96
+ */
97
+ function getSourceKey(source) {
98
+ if (typeof source === 'number')
99
+ return source; // local require()
100
+ if (typeof source === 'string')
101
+ return source; // bare URI string
102
+ if (source && typeof source === 'object') {
103
+ // { uri: string } — most common remote form
104
+ if ('uri' in source && typeof source.uri === 'string')
105
+ return source.uri;
106
+ // Array form: use first entry's uri or index
107
+ if (Array.isArray(source)) {
108
+ const first = source[0];
109
+ if (typeof first === 'string')
110
+ return first;
111
+ if (typeof first === 'number')
112
+ return first;
113
+ if (first && typeof first === 'object' && 'uri' in first && typeof first.uri === 'string') {
114
+ return first.uri;
115
+ }
116
+ }
117
+ }
118
+ return '__unknown__';
119
+ }
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+ // Component
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+ function EchoEmblem({ source, size = 96, displayName, selected = false, disabled = false, loading: externalLoading = false, showSelectionIndicator = true, accessibilityLabel: customLabel, accessibilityHint, onPress, }) {
124
+ // Prevent state updates after the component unmounts.
125
+ const mountedRef = (0, react_1.useRef)(true);
126
+ (0, react_1.useEffect)(() => () => { mountedRef.current = false; }, []);
127
+ const [imageLoading, setImageLoading] = (0, react_1.useState)(true);
128
+ const [imageFailed, setImageFailed] = (0, react_1.useState)(false);
129
+ /**
130
+ * Stable primitive key derived from the actual image URI / asset number.
131
+ *
132
+ * OBJECT IDENTITY MUST NOT BE USED HERE.
133
+ * Callers often write: source={{ uri: url }}
134
+ * That creates a new object on every render, even if `url` is identical.
135
+ * Depending on `source` (the object) in useEffect would reset loading/error
136
+ * on every render cycle, permanently showing the spinner for cached images
137
+ * that never re-fire onLoadStart after their first successful load.
138
+ *
139
+ * We derive `sourceKey` — a string or number — so the effect only runs
140
+ * when the actual image content changes.
141
+ */
142
+ const sourceKey = (0, react_1.useMemo)(() => getSourceKey(source), [source]);
143
+ // Reset internal load state only when the actual image source changes.
144
+ // This is the core fix for the stuck-spinner bug.
145
+ (0, react_1.useEffect)(() => {
146
+ setImageLoading(true);
147
+ setImageFailed(false);
148
+ }, [sourceKey]);
149
+ const handleLoadStart = (0, react_1.useCallback)(() => {
150
+ if (!mountedRef.current)
151
+ return;
152
+ setImageLoading(true);
153
+ setImageFailed(false);
154
+ }, []);
155
+ const handleLoad = (0, react_1.useCallback)(() => {
156
+ if (!mountedRef.current)
157
+ return;
158
+ setImageLoading(false);
159
+ }, []);
160
+ // expo-image also fires `onDisplay` when a cached frame is shown without
161
+ // a network round-trip — clear the spinner in that case too so cached
162
+ // images never leave the spinner visible.
163
+ const handleDisplay = (0, react_1.useCallback)(() => {
164
+ if (!mountedRef.current)
165
+ return;
166
+ setImageLoading(false);
167
+ }, []);
168
+ const handleError = (0, react_1.useCallback)(() => {
169
+ if (!mountedRef.current)
170
+ return;
171
+ setImageLoading(false);
172
+ setImageFailed(true);
173
+ }, []);
174
+ // Derived display states
175
+ const showSpinner = externalLoading || imageLoading;
176
+ const showError = imageFailed && !externalLoading;
177
+ // ── Accessibility label ─────────────────────────────────────────────────
178
+ const baseLabel = displayName ? `${displayName} Echo Emblem` : 'Echo Emblem';
179
+ const computedLabel = customLabel !== null && customLabel !== void 0 ? customLabel : (showError
180
+ ? `${baseLabel} image unavailable`
181
+ : disabled
182
+ ? `${baseLabel}, unavailable`
183
+ : selected
184
+ ? `${baseLabel}, selected`
185
+ : baseLabel);
186
+ // hitSlop ensures the minimum 44pt touch target on small sizes.
187
+ const hitSlop = size < 44
188
+ ? {
189
+ top: Math.ceil((44 - size) / 2),
190
+ bottom: Math.ceil((44 - size) / 2),
191
+ left: Math.ceil((44 - size) / 2),
192
+ right: Math.ceil((44 - size) / 2),
193
+ }
194
+ : undefined;
195
+ // Selection ring geometry: 8pt gutter around the slot, no clipping needed.
196
+ const ringSize = size + 8;
197
+ const ringOffset = -4;
198
+ const ringRadius = ringSize / 2;
199
+ // ── Inner visual content (shared between pressable and image-only) ───────
200
+ const innerContent = (<react_native_1.View
201
+ // Fixed square slot. No overflow:hidden. No borderRadius. No mask.
202
+ // aspectRatio stabilises layout if the parent constrains one axis.
203
+ style={[styles.slot, { width: size, height: size }]}
204
+ // Android: hide all inner elements from TalkBack — outer wrapper speaks.
205
+ importantForAccessibility="no-hide-descendants">
206
+ {/*
207
+ * expo-image with contentFit="contain":
208
+ * • Scales the full PNG (transparent margins included) to fit the slot.
209
+ * • Never crops or stretches the artwork.
210
+ * • Always rendered (even during load) so image events fire correctly.
211
+ */}
212
+ <expo_image_1.Image source={source} style={{ width: size, height: size }} contentFit="contain" onLoadStart={handleLoadStart} onLoad={handleLoad} onDisplay={handleDisplay} onError={handleError} accessible={false}/>
213
+
214
+ {/* Loading spinner — floats above the image while it loads */}
215
+ {showSpinner && !showError ? (<react_native_1.View style={[styles.overlay, { width: size, height: size }]} accessible={false} importantForAccessibility="no">
216
+ <react_native_1.ActivityIndicator color={theme_1.COLORS.neonCyan} size={size >= 64 ? 'large' : 'small'}/>
217
+ </react_native_1.View>) : null}
218
+
219
+ {/* Error placeholder — replaces the broken image indicator */}
220
+ {showError ? (<react_native_1.View style={[
221
+ styles.overlay,
222
+ styles.errorPlaceholder,
223
+ { width: size, height: size, borderRadius: Math.round(size * 0.12) },
224
+ ]} accessible={false} importantForAccessibility="no">
225
+ <vector_icons_1.MaterialIcons name="image-not-supported" size={Math.max(14, Math.floor(size * 0.38))} color={theme_1.COLORS.textMuted}/>
226
+ </react_native_1.View>) : null}
227
+
228
+ {/*
229
+ * Selection ring: absolute, extends OUTSIDE the slot via negative insets.
230
+ * Visible because the slot container has no overflow:hidden.
231
+ * Decorative only — hidden from screen readers.
232
+ * iOS: shadow creates neon glow. Android: elevation approximates it.
233
+ */}
234
+ {selected && showSelectionIndicator && !showError ? (<react_native_1.View style={[
235
+ styles.selectionRing,
236
+ {
237
+ width: ringSize,
238
+ height: ringSize,
239
+ borderRadius: ringRadius,
240
+ top: ringOffset,
241
+ left: ringOffset,
242
+ },
243
+ react_native_1.Platform.OS === 'ios'
244
+ ? {
245
+ shadowColor: theme_1.COLORS.neonCyan,
246
+ shadowOffset: { width: 0, height: 0 },
247
+ shadowOpacity: 0.9,
248
+ shadowRadius: 10,
249
+ }
250
+ : { elevation: 5 },
251
+ ]} accessible={false} importantForAccessibility="no"/>) : null}
252
+ </react_native_1.View>);
253
+ // ── Pressable (interactive) ─────────────────────────────────────────────
254
+ if (onPress) {
255
+ return (<react_native_1.Pressable onPress={disabled ? undefined : onPress} disabled={disabled} hitSlop={hitSlop} accessibilityRole="button" accessibilityLabel={computedLabel} accessibilityHint={accessibilityHint} accessibilityState={{ selected, disabled, busy: showSpinner }} style={({ pressed }) => [
256
+ disabled ? styles.disabled : undefined,
257
+ pressed && !disabled ? styles.pressed : undefined,
258
+ ]}>
259
+ {innerContent}
260
+ </react_native_1.Pressable>);
261
+ }
262
+ // ── Image-only (non-interactive) ────────────────────────────────────────
263
+ return (<react_native_1.View accessible accessibilityRole="image" accessibilityLabel={computedLabel} accessibilityState={{ disabled, busy: showSpinner }} style={disabled ? styles.disabled : undefined}>
264
+ {innerContent}
265
+ </react_native_1.View>);
266
+ }
267
+ // ─────────────────────────────────────────────────────────────────────────────
268
+ // Styles
269
+ // ─────────────────────────────────────────────────────────────────────────────
270
+ const styles = react_native_1.StyleSheet.create({
271
+ slot: {
272
+ // Core contract: fixed square, centered content, no clipping.
273
+ aspectRatio: 1,
274
+ alignItems: 'center',
275
+ justifyContent: 'center',
276
+ // overflow:hidden is intentionally absent — decorative artwork details
277
+ // outside the circular border must remain visible.
278
+ // borderRadius is intentionally absent — we never mask the PNG.
279
+ },
280
+ overlay: {
281
+ // Used by both the loading spinner and the error placeholder.
282
+ position: 'absolute',
283
+ top: 0,
284
+ left: 0,
285
+ alignItems: 'center',
286
+ justifyContent: 'center',
287
+ },
288
+ errorPlaceholder: {
289
+ backgroundColor: theme_1.COLORS.surfaceHigh,
290
+ },
291
+ selectionRing: {
292
+ // Ring lives outside the slot bounds (negative top/left).
293
+ // No parent clips it because slot has no overflow:hidden.
294
+ position: 'absolute',
295
+ borderWidth: 2.5,
296
+ borderColor: theme_1.COLORS.neonCyan,
297
+ },
298
+ disabled: {
299
+ // Communicates unavailability without making the artwork unreadable.
300
+ opacity: 0.42,
301
+ },
302
+ pressed: {
303
+ // Provides clear tactile feedback without distorting the image slot.
304
+ opacity: 0.72,
305
+ transform: [{ scale: 0.94 }],
306
+ },
307
+ });
308
+ //# sourceMappingURL=EchoEmblem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EchoEmblem.js","sourceRoot":"","sources":["../../src/components/EchoEmblem.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmHH,gCA+NC;AAhVD,+CAAiF;AACjF,+CAMsB;AACtB,2CAAmC;AAEnC,qDAAmD;AACnD,oCAAkC;AAgElC,gFAAgF;AAChF,oBAAoB;AACpB,gFAAgF;AAEhF;;;;;;;;;;GAUG;AACH,SAAS,YAAY,CAAC,MAAmB;IACvC,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,CAAW,kBAAkB;IAC3E,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,CAAW,kBAAkB;IAC3E,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzC,4CAA4C;QAC5C,IAAI,KAAK,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC;QACzE,6CAA6C;QAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAG,OAAO,KAAK,CAAC;YAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAG,OAAO,KAAK,CAAC;YAC7C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC1F,OAAO,KAAK,CAAC,GAAG,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF,SAAgB,UAAU,CAAC,EACzB,MAAM,EACN,IAAI,GAAG,EAAE,EACT,WAAW,EACX,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,OAAO,EAAE,eAAe,GAAG,KAAK,EAChC,sBAAsB,GAAG,IAAI,EAC7B,kBAAkB,EAAE,WAAW,EAC/B,iBAAiB,EACjB,OAAO,GACS;IAChB,sDAAsD;IACtD,MAAM,UAAU,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC;IAChC,IAAA,iBAAS,EAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE3D,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,IAAA,gBAAQ,EAAC,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,WAAW,EAAG,cAAc,CAAC,GAAI,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAC;IAExD;;;;;;;;;;;;OAYG;IACH,MAAM,SAAS,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEhE,uEAAuE;IACvE,kDAAkD;IAClD,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,cAAc,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,eAAe,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACvC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAO;QAChC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,cAAc,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,UAAU,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QAClC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAO;QAChC,eAAe,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,yEAAyE;IACzE,sEAAsE;IACtE,0CAA0C;IAC1C,MAAM,aAAa,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACrC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAO;QAChC,eAAe,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACnC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,OAAO;QAChC,eAAe,CAAC,KAAK,CAAC,CAAC;QACvB,cAAc,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,yBAAyB;IACzB,MAAM,WAAW,GAAG,eAAe,IAAI,YAAY,CAAC;IACpD,MAAM,SAAS,GAAK,WAAW,IAAI,CAAC,eAAe,CAAC;IAEpD,2EAA2E;IAC3E,MAAM,SAAS,GAAO,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC;IACjF,MAAM,aAAa,GACjB,WAAW,aAAX,WAAW,cAAX,WAAW,GACX,CAAC,SAAS;QACR,CAAC,CAAC,GAAG,SAAS,oBAAoB;QAClC,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,GAAG,SAAS,eAAe;YAC7B,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,GAAG,SAAS,YAAY;gBAC1B,CAAC,CAAC,SAAS,CAAC,CAAC;IAEjB,gEAAgE;IAChE,MAAM,OAAO,GACX,IAAI,GAAG,EAAE;QACP,CAAC,CAAC;YACE,GAAG,EAAK,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,EAAI,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,KAAK,EAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;SACnC;QACH,CAAC,CAAC,SAAS,CAAC;IAEhB,2EAA2E;IAC3E,MAAM,QAAQ,GAAK,IAAI,GAAG,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC;IACtB,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC;IAEhC,4EAA4E;IAC5E,MAAM,YAAY,GAAG,CACnB,CAAC,mBAAI;IACH,mEAAmE;IACnE,mEAAmE;IACnE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,yEAAyE;IACzE,yBAAyB,CAAC,qBAAqB,CAE/C;MAAA,CAAC;;;;;WAKE,CACH;MAAA,CAAC,kBAAK,CACJ,MAAM,CAAC,CAAC,MAAM,CAAC,CACf,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CACrC,UAAU,CAAC,SAAS,CACpB,WAAW,CAAC,CAAC,eAAe,CAAC,CAC7B,MAAM,CAAC,CAAC,UAAU,CAAC,CACnB,SAAS,CAAC,CAAC,aAAa,CAAC,CACzB,OAAO,CAAC,CAAC,WAAW,CAAC,CACrB,UAAU,CAAC,CAAC,KAAK,CAAC,EAGpB;;MAAA,CAAC,6DAA6D,CAC9D;MAAA,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAC3B,CAAC,mBAAI,CACH,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CACvD,UAAU,CAAC,CAAC,KAAK,CAAC,CAClB,yBAAyB,CAAC,IAAI,CAE9B;UAAA,CAAC,gCAAiB,CAChB,KAAK,CAAC,CAAC,cAAM,CAAC,QAAQ,CAAC,CACvB,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAEzC;QAAA,EAAE,mBAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CAER;;MAAA,CAAC,6DAA6D,CAC9D;MAAA,CAAC,SAAS,CAAC,CAAC,CAAC,CACX,CAAC,mBAAI,CACH,KAAK,CAAC,CAAC;gBACL,MAAM,CAAC,OAAO;gBACd,MAAM,CAAC,gBAAgB;gBACvB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE;aACrE,CAAC,CACF,UAAU,CAAC,CAAC,KAAK,CAAC,CAClB,yBAAyB,CAAC,IAAI,CAE9B;UAAA,CAAC,4BAAa,CACZ,IAAI,CAAC,qBAAqB,CAC1B,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAC5C,KAAK,CAAC,CAAC,cAAM,CAAC,SAAS,CAAC,EAE5B;QAAA,EAAE,mBAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CAER;;MAAA,CAAC;;;;;WAKE,CACH;MAAA,CAAC,QAAQ,IAAI,sBAAsB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAClD,CAAC,mBAAI,CACH,KAAK,CAAC,CAAC;gBACL,MAAM,CAAC,aAAa;gBACpB;oBACE,KAAK,EAAS,QAAQ;oBACtB,MAAM,EAAQ,QAAQ;oBACtB,YAAY,EAAE,UAAU;oBACxB,GAAG,EAAW,UAAU;oBACxB,IAAI,EAAU,UAAU;iBACzB;gBACD,uBAAQ,CAAC,EAAE,KAAK,KAAK;oBACnB,CAAC,CAAC;wBACE,WAAW,EAAI,cAAM,CAAC,QAAQ;wBAC9B,YAAY,EAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;wBACtC,aAAa,EAAE,GAAG;wBAClB,YAAY,EAAG,EAAE;qBAClB;oBACH,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;aACrB,CAAC,CACF,UAAU,CAAC,CAAC,KAAK,CAAC,CAClB,yBAAyB,CAAC,IAAI,EAC9B,CACH,CAAC,CAAC,CAAC,IAAI,CACV;IAAA,EAAE,mBAAI,CAAC,CACR,CAAC;IAEF,2EAA2E;IAC3E,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CACL,CAAC,wBAAS,CACR,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CACxC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CACnB,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,iBAAiB,CAAC,QAAQ,CAC1B,kBAAkB,CAAC,CAAC,aAAa,CAAC,CAClC,iBAAiB,CAAC,CAAC,iBAAiB,CAAC,CACrC,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAC9D,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;gBACtB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;gBACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;aAClD,CAAC,CAEF;QAAA,CAAC,YAAY,CACf;MAAA,EAAE,wBAAS,CAAC,CACb,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,OAAO,CACL,CAAC,mBAAI,CACH,UAAU,CACV,iBAAiB,CAAC,OAAO,CACzB,kBAAkB,CAAC,CAAC,aAAa,CAAC,CAClC,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CACpD,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAE9C;MAAA,CAAC,YAAY,CACf;IAAA,EAAE,mBAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAEhF,MAAM,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IAC/B,IAAI,EAAE;QACJ,8DAA8D;QAC9D,WAAW,EAAM,CAAC;QAClB,UAAU,EAAO,QAAQ;QACzB,cAAc,EAAG,QAAQ;QACzB,uEAAuE;QACvE,mDAAmD;QACnD,gEAAgE;KACjE;IAED,OAAO,EAAE;QACP,8DAA8D;QAC9D,QAAQ,EAAS,UAAU;QAC3B,GAAG,EAAc,CAAC;QAClB,IAAI,EAAa,CAAC;QAClB,UAAU,EAAO,QAAQ;QACzB,cAAc,EAAG,QAAQ;KAC1B;IAED,gBAAgB,EAAE;QAChB,eAAe,EAAE,cAAM,CAAC,WAAW;KACpC;IAED,aAAa,EAAE;QACb,0DAA0D;QAC1D,0DAA0D;QAC1D,QAAQ,EAAK,UAAU;QACvB,WAAW,EAAE,GAAG;QAChB,WAAW,EAAE,cAAM,CAAC,QAAQ;KAC7B;IAED,QAAQ,EAAE;QACR,qEAAqE;QACrE,OAAO,EAAE,IAAI;KACd;IAED,OAAO,EAAE;QACP,qEAAqE;QACrE,OAAO,EAAI,IAAI;QACf,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KAC7B;CACF,CAAC,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * EchoEmblemPicker
3
+ *
4
+ * Presentation-only modal for selecting or clearing an Echo Emblem.
5
+ * Extracted from the Echo UI Reference EmblemPicker.
6
+ *
7
+ * The host app owns all data fetching and the savingId state.
8
+ * This component is purely presentational and accessible.
9
+ *
10
+ * DYNAMIC COLLECTION FILTERS
11
+ * ──────────────────────────
12
+ * Filters are generated from the owned Emblem list — no hardcoded app names:
13
+ *
14
+ * "All" → shows every owned Emblem.
15
+ * "Starter" → shows Emblems whose app_key is null (platform-wide collection).
16
+ * <AppName> → one chip per distinct app_key present in the owned list,
17
+ * using metadata.collection_name when available, falling back
18
+ * to a title-cased label derived from app_key itself.
19
+ *
20
+ * ACCESSIBILITY
21
+ * ─────────────
22
+ * • Modal heading announced on open.
23
+ * • Each filter chip is a button with selected state announced.
24
+ * • Each emblem cell is a button with selected/disabled/busy state.
25
+ * • Saving overlay announces busy on the active cell.
26
+ * • Error and empty states are accessible alert/text roles.
27
+ *
28
+ * No Expo Router, no safe-area hooks, no auth, no contexts, no services,
29
+ * no storage, no app-specific code.
30
+ * Canonical shared component for @echoform/echo-ui.
31
+ */
32
+ import React from 'react';
33
+ export type EchoOwnedEmblem = {
34
+ asset_id: string;
35
+ asset_key: string;
36
+ display_name: string;
37
+ description: string | null;
38
+ image_url: string;
39
+ app_key: string | null;
40
+ sort_order: number;
41
+ acquired_at: string;
42
+ acquisition_type: string;
43
+ acquisition_reference: string | null;
44
+ is_currently_active: boolean;
45
+ metadata?: Record<string, unknown> | null;
46
+ };
47
+ export type EchoEmblemPickerProps = {
48
+ visible: boolean;
49
+ emblems: EchoOwnedEmblem[];
50
+ loading: boolean;
51
+ error: string | null;
52
+ onClose: () => void;
53
+ /**
54
+ * Called when the user taps an Emblem or "No Emblem".
55
+ * Receives the asset_id to equip, or null to clear.
56
+ */
57
+ onSelect: (assetId: string | null) => void | Promise<void>;
58
+ /**
59
+ * asset_id currently being saved, 'clear' when clearing, null when idle.
60
+ * Used to show a per-cell saving spinner and disable the grid.
61
+ */
62
+ savingId: string | 'clear' | null;
63
+ /** Called when the user taps "Try Again" after a load error. */
64
+ onRetry: () => void;
65
+ /** Safe-area top inset supplied by the host. Defaults to 0. */
66
+ topInset?: number;
67
+ /** Safe-area bottom inset used for scroll padding. Defaults to 0. */
68
+ bottomInset?: number;
69
+ };
70
+ export declare function EchoEmblemPicker({ visible, emblems, loading, error, onClose, onSelect, savingId, onRetry, topInset, bottomInset, }: EchoEmblemPickerProps): React.JSX.Element;
71
+ //# sourceMappingURL=EchoEmblemPicker.d.ts.map