@ankhorage/zora 2.8.3 → 2.8.4

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 (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -2
  3. package/dist/components/avatar/Avatar.d.ts.map +1 -1
  4. package/dist/components/avatar/Avatar.js +16 -8
  5. package/dist/components/avatar/Avatar.js.map +1 -1
  6. package/dist/components/date-picker/DatePicker.d.ts.map +1 -1
  7. package/dist/components/date-picker/DatePicker.js +16 -7
  8. package/dist/components/date-picker/DatePicker.js.map +1 -1
  9. package/dist/components/form/useFormController.d.ts.map +1 -1
  10. package/dist/components/form/useFormController.js +4 -4
  11. package/dist/components/form/useFormController.js.map +1 -1
  12. package/dist/components/heading/resolveHeadingRecipe.d.ts.map +1 -1
  13. package/dist/components/heading/resolveHeadingRecipe.js +18 -2
  14. package/dist/components/heading/resolveHeadingRecipe.js.map +1 -1
  15. package/dist/components/text/resolveTextRecipe.d.ts.map +1 -1
  16. package/dist/components/text/resolveTextRecipe.js +18 -2
  17. package/dist/components/text/resolveTextRecipe.js.map +1 -1
  18. package/dist/patterns/auth/oauthProviders.d.ts.map +1 -1
  19. package/dist/patterns/auth/oauthProviders.js +4 -4
  20. package/dist/patterns/auth/oauthProviders.js.map +1 -1
  21. package/dist/patterns/auth/utils.d.ts.map +1 -1
  22. package/dist/patterns/auth/utils.js +13 -7
  23. package/dist/patterns/auth/utils.js.map +1 -1
  24. package/dist/patterns/image-upload-field/uploadFlow.js +1 -1
  25. package/dist/patterns/image-upload-field/uploadFlow.js.map +1 -1
  26. package/dist/patterns/selection/resolveSelectionNextIds.js +1 -1
  27. package/dist/patterns/selection/resolveSelectionNextIds.js.map +1 -1
  28. package/dist/patterns/theme-composer/ThemeComposer.d.ts +1 -1
  29. package/dist/patterns/theme-composer/ThemeComposer.d.ts.map +1 -1
  30. package/dist/patterns/theme-composer/ThemeComposer.js +37 -40
  31. package/dist/patterns/theme-composer/ThemeComposer.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/components/avatar/Avatar.tsx +16 -8
  34. package/src/components/date-picker/DatePicker.tsx +18 -7
  35. package/src/components/form/useFormController.ts +6 -4
  36. package/src/components/heading/resolveHeadingRecipe.ts +20 -2
  37. package/src/components/text/resolveTextRecipe.test.ts +20 -2
  38. package/src/components/text/resolveTextRecipe.ts +20 -2
  39. package/src/metadata/componentMeta.test.ts +5 -3
  40. package/src/nativePeerContract.test.ts +11 -1
  41. package/src/patterns/auth/oauthProviders.ts +6 -7
  42. package/src/patterns/auth/utils.ts +13 -7
  43. package/src/patterns/image-upload-field/uploadFlow.ts +1 -1
  44. package/src/patterns/selection/resolveSelectionNextIds.ts +1 -1
  45. package/src/patterns/theme-composer/ThemeComposer.tsx +48 -41
@@ -44,19 +44,18 @@ const DEFAULT_OAUTH_PROVIDER_LABELS = {
44
44
  zoom: 'Zoom',
45
45
  } as const satisfies Record<string, string>;
46
46
 
47
- const oauthProviderLabels: Partial<Record<string, string>> = DEFAULT_OAUTH_PROVIDER_LABELS;
47
+ const oauthProviderIcons = new Map<string, OAuthProviderIconSpec>(
48
+ Object.entries(DEFAULT_OAUTH_PROVIDER_ICONS),
49
+ );
50
+ const oauthProviderLabels = new Map<string, string>(Object.entries(DEFAULT_OAUTH_PROVIDER_LABELS));
48
51
 
49
52
  export function resolveOAuthProviderIcon(providerId: string): OAuthProviderIconSpec | undefined {
50
- const normalizedProviderId = normalizeOAuthProviderId(providerId);
51
-
52
- return DEFAULT_OAUTH_PROVIDER_ICONS[
53
- normalizedProviderId as keyof typeof DEFAULT_OAUTH_PROVIDER_ICONS
54
- ];
53
+ return oauthProviderIcons.get(normalizeOAuthProviderId(providerId));
55
54
  }
56
55
 
57
56
  export function resolveOAuthProviderLabel(providerId: string): string {
58
57
  const normalizedProviderId = normalizeOAuthProviderId(providerId);
59
- const knownLabel = oauthProviderLabels[normalizedProviderId];
58
+ const knownLabel = oauthProviderLabels.get(normalizedProviderId);
60
59
 
61
60
  return knownLabel ?? titleCaseProviderId(normalizedProviderId);
62
61
  }
@@ -3,11 +3,17 @@ import type { AuthIdentifierKind } from './types';
3
3
 
4
4
  export const defaultIdentifiers = ['email'] as const satisfies readonly AuthIdentifierKind[];
5
5
 
6
- const identifierLabels: Record<AuthIdentifierKind, string> = {
7
- email: 'Email',
8
- phone: 'Phone',
9
- username: 'Username',
10
- };
6
+ function resolveIdentifierKindLabel(identifier: AuthIdentifierKind): string {
7
+ switch (identifier) {
8
+ case 'phone':
9
+ return 'Phone';
10
+ case 'username':
11
+ return 'Username';
12
+ case 'email':
13
+ default:
14
+ return 'Email';
15
+ }
16
+ }
11
17
 
12
18
  function includesIdentifier(
13
19
  identifiers: readonly AuthIdentifierKind[],
@@ -18,10 +24,10 @@ function includesIdentifier(
18
24
 
19
25
  export function resolveIdentifierLabel(identifiers: readonly AuthIdentifierKind[]): string {
20
26
  if (identifiers.length === 1) {
21
- return identifierLabels[identifiers[0] ?? 'email'];
27
+ return resolveIdentifierKindLabel(identifiers[0] ?? 'email');
22
28
  }
23
29
 
24
- return identifiers.map((identifier) => identifierLabels[identifier]).join(', or ');
30
+ return identifiers.map(resolveIdentifierKindLabel).join(', or ');
25
31
  }
26
32
 
27
33
  export function resolveIdentifierType(
@@ -78,7 +78,7 @@ function formatBytes(value: number): string {
78
78
  unitIndex += 1;
79
79
  }
80
80
 
81
- const unit = units[unitIndex] ?? 'B';
81
+ const unit = units.at(unitIndex) ?? 'B';
82
82
  const rounded = unitIndex === 0 ? Math.round(size) : Math.round(size * 10) / 10;
83
83
  return `${rounded} ${unit}`;
84
84
  }
@@ -20,7 +20,7 @@ export function normalizeIds(
20
20
  export function areIdsEqual(a: readonly string[], b: readonly string[]): boolean {
21
21
  if (a.length !== b.length) return false;
22
22
  for (let index = 0; index < a.length; index += 1) {
23
- if (a[index] !== b[index]) return false;
23
+ if (a.at(index) !== b.at(index)) return false;
24
24
  }
25
25
 
26
26
  return true;
@@ -21,6 +21,15 @@ const HEX_ERROR_MESSAGE = 'Enter a valid 6-digit hex color.';
21
21
  const HEX_INPUT_PLACEHOLDER = 'Enter hex color';
22
22
  const NAME_ERROR_MESSAGE = 'Theme name cannot be empty.';
23
23
 
24
+ interface InputDraft {
25
+ inputValue: string;
26
+ error: string | undefined;
27
+ }
28
+
29
+ function createInputDraft(sourceValue: string): InputDraft {
30
+ return { inputValue: sourceValue, error: undefined };
31
+ }
32
+
24
33
  function isValidHex(value: string): boolean {
25
34
  try {
26
35
  parseHexColorOrThrow(value);
@@ -56,56 +65,52 @@ function ThemeComposerInner({
56
65
  }: ThemeComposerProps) {
57
66
  const { theme } = useZoraTheme();
58
67
 
59
- const [hexInput, setHexInput] = React.useState<string>(value.primaryColor);
60
- const [hexError, setHexError] = React.useState<string | undefined>(undefined);
61
-
62
- const [nameInput, setNameInput] = React.useState<string>(value.name);
63
- const [nameError, setNameError] = React.useState<string | undefined>(undefined);
68
+ const [hexDraft, setHexDraft] = React.useState<InputDraft>(() =>
69
+ createInputDraft(value.primaryColor),
70
+ );
71
+ const [nameDraft, setNameDraft] = React.useState<InputDraft>(() => createInputDraft(value.name));
72
+ const [previousPrimaryColor, setPreviousPrimaryColor] = React.useState(value.primaryColor);
73
+ const [previousName, setPreviousName] = React.useState(value.name);
64
74
 
65
- // Keep local inputs in sync when value changes externally
66
- React.useEffect(() => {
67
- setHexInput(value.primaryColor);
68
- setHexError(undefined);
69
- }, [value.primaryColor]);
75
+ if (previousPrimaryColor !== value.primaryColor) {
76
+ setPreviousPrimaryColor(value.primaryColor);
77
+ setHexDraft(createInputDraft(value.primaryColor));
78
+ }
70
79
 
71
- React.useEffect(() => {
72
- setNameInput(value.name);
73
- setNameError(undefined);
74
- }, [value.name]);
80
+ if (previousName !== value.name) {
81
+ setPreviousName(value.name);
82
+ setNameDraft(createInputDraft(value.name));
83
+ }
75
84
 
76
85
  function handleNameChange(text: string) {
77
- setNameInput(text);
78
- if (text.trim().length === 0) {
79
- setNameError(NAME_ERROR_MESSAGE);
80
- } else {
81
- setNameError(undefined);
86
+ const error = text.trim().length === 0 ? NAME_ERROR_MESSAGE : undefined;
87
+ setNameDraft({ inputValue: text, error });
88
+
89
+ if (!error) {
82
90
  onChange({ ...value, name: text });
83
91
  }
84
92
  }
85
93
 
86
94
  function handleHexChange(text: string) {
87
- // Ensure leading hash
88
95
  const normalized = text.startsWith('#') ? text : `#${text}`;
89
- setHexInput(normalized);
96
+ const error = isValidHex(normalized) ? undefined : HEX_ERROR_MESSAGE;
97
+ setHexDraft({ inputValue: normalized, error });
90
98
 
91
- if (isValidHex(normalized)) {
92
- setHexError(undefined);
99
+ if (!error) {
93
100
  onChange({ ...value, primaryColor: normalized });
94
- } else {
95
- setHexError(HEX_ERROR_MESSAGE);
96
101
  }
97
102
  }
98
103
 
99
104
  function handleSubmit() {
100
- const hasValidName = nameInput.trim().length > 0;
101
- const hasValidHex = isValidHex(hexInput);
105
+ const hasValidName = nameDraft.inputValue.trim().length > 0;
106
+ const hasValidHex = isValidHex(hexDraft.inputValue);
102
107
 
103
108
  if (!hasValidName) {
104
- setNameError(NAME_ERROR_MESSAGE);
109
+ setNameDraft((current) => ({ ...current, error: NAME_ERROR_MESSAGE }));
105
110
  }
106
111
 
107
112
  if (!hasValidHex) {
108
- setHexError(HEX_ERROR_MESSAGE);
113
+ setHexDraft((current) => ({ ...current, error: HEX_ERROR_MESSAGE }));
109
114
  }
110
115
 
111
116
  if (!hasValidName || !hasValidHex) {
@@ -114,8 +119,8 @@ function ThemeComposerInner({
114
119
 
115
120
  onSubmit?.({
116
121
  ...value,
117
- name: nameInput,
118
- primaryColor: hexInput,
122
+ name: nameDraft.inputValue,
123
+ primaryColor: hexDraft.inputValue,
119
124
  });
120
125
  }
121
126
 
@@ -136,16 +141,16 @@ function ThemeComposerInner({
136
141
  <Stack gap="xs">
137
142
  <Text variant="label">Name</Text>
138
143
  <Input
139
- value={nameInput}
144
+ value={nameDraft.inputValue}
140
145
  onChangeText={handleNameChange}
141
146
  placeholder="My theme"
142
147
  autoCorrect={false}
143
- invalid={nameError !== undefined}
148
+ invalid={nameDraft.error !== undefined}
144
149
  testID={testID ? `${testID}-name-input` : undefined}
145
150
  />
146
- {nameError ? (
151
+ {nameDraft.error ? (
147
152
  <Text color="danger" variant="bodySmall">
148
- {nameError}
153
+ {nameDraft.error}
149
154
  </Text>
150
155
  ) : null}
151
156
  </Stack>
@@ -178,13 +183,13 @@ function ThemeComposerInner({
178
183
  <Stack direction="row" gap="m" align="center">
179
184
  <Box flex={1}>
180
185
  <Input
181
- value={hexInput}
186
+ value={hexDraft.inputValue}
182
187
  onChangeText={handleHexChange}
183
188
  placeholder={HEX_INPUT_PLACEHOLDER}
184
189
  autoCapitalize="none"
185
190
  autoCorrect={false}
186
191
  maxLength={7}
187
- invalid={hexError !== undefined}
192
+ invalid={hexDraft.error !== undefined}
188
193
  testID={testID ? `${testID}-hex-input` : undefined}
189
194
  />
190
195
  </Box>
@@ -194,15 +199,17 @@ function ThemeComposerInner({
194
199
  height={36}
195
200
  radius="m"
196
201
  style={{
197
- backgroundColor: isValidHex(hexInput) ? hexInput : theme.colors.border,
202
+ backgroundColor: isValidHex(hexDraft.inputValue)
203
+ ? hexDraft.inputValue
204
+ : theme.colors.border,
198
205
  borderWidth: 1,
199
206
  borderColor: theme.colors.border,
200
207
  }}
201
208
  />
202
209
  </Stack>
203
- {hexError ? (
210
+ {hexDraft.error ? (
204
211
  <Text color="danger" variant="bodySmall">
205
- {hexError}
212
+ {hexDraft.error}
206
213
  </Text>
207
214
  ) : null}
208
215
  </Stack>
@@ -310,6 +317,6 @@ function ThemeComposerInner({
310
317
  /***
311
318
  * UI for composing and applying a theme via structured controls.
312
319
  *
313
-
320
+ *
314
321
  */
315
322
  export const ThemeComposer = withZoraThemeScope(ThemeComposerInner);