@scripso-homepad/ui 0.4.22 → 0.4.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scripso-homepad/ui",
3
- "version": "0.4.22",
3
+ "version": "0.4.23",
4
4
  "type": "module",
5
5
  "description": "Cross-platform UI components for Homepad (React Web + React Native)",
6
6
  "license": "MIT",
@@ -19,12 +19,12 @@ import {
19
19
  } from "../data/countries";
20
20
  import { ChevronDownIcon } from "../icons/ChevronDownIcon";
21
21
  import { colors, fontSize, fontWeight, radii, spacing, fonts } from "../theme/tokens";
22
- import { countryCodeToFlagEmoji } from "../utils/countryFlag";
23
22
  import {
24
23
  COUNTRY_DROPDOWN_SCROLL_CLASS,
25
24
  ensureCountryDropdownScrollStyles,
26
25
  } from "../utils/countryDropdownScrollStyles";
27
26
  import { useApplyWebClassName } from "../utils/useApplyWebClassName";
27
+ import { CountryFlag } from "./CountryFlag";
28
28
 
29
29
  const DROPDOWN_GAP = 10;
30
30
  const DROPDOWN_HEIGHT = 186;
@@ -126,7 +126,7 @@ export function CountryCodeSelector({
126
126
  !isLast && styles.optionSpacing,
127
127
  ]}
128
128
  >
129
- <Text style={styles.flag}>{countryCodeToFlagEmoji(item.code)}</Text>
129
+ <CountryFlag code={item.code} />
130
130
  <Text style={styles.optionDialCode}>{item.dialCode}</Text>
131
131
  </Pressable>
132
132
  );
@@ -159,7 +159,7 @@ export function CountryCodeSelector({
159
159
  Platform.OS === "web" ? styles.triggerWeb : null,
160
160
  ]}
161
161
  >
162
- <Text style={styles.flag}>{countryCodeToFlagEmoji(selected.code)}</Text>
162
+ <CountryFlag code={selected.code} />
163
163
  <Text style={[styles.dialCode, { color: textColor }]}>{selected.dialCode}</Text>
164
164
  <ChevronDownIcon size={CHEVRON_SIZE} color={colors.stormGray100} />
165
165
  </Pressable>
@@ -219,10 +219,6 @@ const styles = StyleSheet.create({
219
219
  triggerWeb: {
220
220
  width: "max-content" as ViewStyle["width"],
221
221
  },
222
- flag: {
223
- fontSize: 18,
224
- lineHeight: 24,
225
- },
226
222
  dialCode: {
227
223
  fontSize: fontSize.md,
228
224
  fontWeight: fontWeight.medium,
@@ -0,0 +1,37 @@
1
+ import { Image, StyleSheet, type StyleProp, type ImageStyle } from "react-native";
2
+
3
+ import { getCountryFlagImageUri } from "../utils/countryFlag";
4
+
5
+ const FLAG_HEIGHT = 18;
6
+ const FLAG_WIDTH = 24;
7
+
8
+ export type CountryFlagProps = {
9
+ code: string;
10
+ style?: StyleProp<ImageStyle>;
11
+ };
12
+
13
+ /** Renders a country flag image (works on Windows; emoji flags do not). */
14
+ export function CountryFlag({ code, style }: CountryFlagProps) {
15
+ const uri = getCountryFlagImageUri(code);
16
+
17
+ if (uri === null) {
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <Image
23
+ source={{ uri }}
24
+ style={[styles.flag, style]}
25
+ accessibilityIgnoresInvertColors
26
+ accessibilityLabel={`${code.trim().toUpperCase()} flag`}
27
+ />
28
+ );
29
+ }
30
+
31
+ const styles = StyleSheet.create({
32
+ flag: {
33
+ width: FLAG_WIDTH,
34
+ height: FLAG_HEIGHT,
35
+ borderRadius: 2,
36
+ },
37
+ });
package/src/index.ts CHANGED
@@ -22,6 +22,14 @@ export type { PhoneInputProps } from "./components/PhoneInput";
22
22
  export { CountryCodeSelector } from "./components/CountryCodeSelector";
23
23
  export type { CountryCodeSelectorProps } from "./components/CountryCodeSelector";
24
24
 
25
+ export { CountryFlag } from "./components/CountryFlag";
26
+ export type { CountryFlagProps } from "./components/CountryFlag";
27
+
28
+ export {
29
+ countryCodeToFlagEmoji,
30
+ getCountryFlagImageUri,
31
+ } from "./utils/countryFlag";
32
+
25
33
  export { Calendar } from "./components/Calendar";
26
34
  export type {
27
35
  CalendarLabels,
@@ -1,4 +1,4 @@
1
- /** ISO 3166-1 alpha-2 country code → flag emoji (cross-platform). */
1
+ /** ISO 3166-1 alpha-2 country code → flag emoji. Prefer CountryFlag images on Windows. */
2
2
  export function countryCodeToFlagEmoji(code: string): string {
3
3
  const upper = code.trim().toUpperCase();
4
4
  if (upper.length !== 2) return "";
@@ -7,3 +7,14 @@ export function countryCodeToFlagEmoji(code: string): string {
7
7
  ...upper.split("").map((char) => 0x1f1e6 - 65 + char.charCodeAt(0)),
8
8
  );
9
9
  }
10
+
11
+ /**
12
+ * PNG flag URL (flagcdn). Windows does not render regional-indicator flag emoji,
13
+ * so UI should use images instead of countryCodeToFlagEmoji.
14
+ */
15
+ export function getCountryFlagImageUri(code: string): string | null {
16
+ const lower = code.trim().toLowerCase();
17
+ if (!/^[a-z]{2}$/.test(lower)) return null;
18
+
19
+ return `https://flagcdn.com/w40/${lower}.png`;
20
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { fn } from '@storybook/test';
3
- import { useState } from 'react';
3
+ import { useState, type ReactNode } from 'react';
4
4
 
5
5
  import { Button } from '../../components/Button';
6
6
  import { WebLayoutCanvas } from '../layout/story-helpers';
@@ -132,8 +132,8 @@ function DrawerStoryShell({
132
132
  }: {
133
133
  open: boolean;
134
134
  onClose: () => void;
135
- footer?: React.ReactNode;
136
- title?: string;
135
+ footer?: ReactNode;
136
+ title?: ReactNode;
137
137
  subtitle?: string;
138
138
  }) {
139
139
  return (
@@ -38,7 +38,7 @@ function unlockBodyScroll() {
38
38
 
39
39
  export type DrawerProps = {
40
40
  open: boolean;
41
- title: string;
41
+ title: ReactNode;
42
42
  subtitle?: string;
43
43
  subtitleClassName?: string;
44
44
  headerBottom?: ReactNode;