@scripso-homepad/ui 0.4.21 → 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/dist/index.cjs +71 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +70 -41
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +1 -1
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CountryCodeSelector.tsx +3 -7
- package/src/components/CountryFlag.tsx +37 -0
- package/src/components/Select.stories.tsx +1 -0
- package/src/components/Select.tsx +9 -2
- package/src/index.ts +8 -0
- package/src/utils/countryFlag.ts +12 -1
- package/src/web/components/Drawer.stories.tsx +3 -3
- package/src/web/components/Drawer.tsx +1 -1
package/package.json
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
+
});
|
|
@@ -214,6 +214,11 @@ interface SelectBaseProps {
|
|
|
214
214
|
hintClassName?: string;
|
|
215
215
|
/** Shorter option rows for compact filter-style dropdowns. */
|
|
216
216
|
compact?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* When true (default), clicking the already-selected option clears the selection
|
|
219
|
+
* in single-select mode. Set to false for required fields that must keep a value.
|
|
220
|
+
*/
|
|
221
|
+
allowDeselect?: boolean;
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
export interface SingleSelectProps extends SelectBaseProps {
|
|
@@ -266,6 +271,7 @@ export function Select({
|
|
|
266
271
|
errorClassName,
|
|
267
272
|
hintClassName,
|
|
268
273
|
compact = false,
|
|
274
|
+
allowDeselect = true,
|
|
269
275
|
}: SelectProps) {
|
|
270
276
|
const wrapperRef = useRef<ComponentRef<typeof View>>(null);
|
|
271
277
|
const triggerRef = useRef<ComponentRef<typeof Pressable>>(null);
|
|
@@ -389,9 +395,10 @@ export function Select({
|
|
|
389
395
|
return;
|
|
390
396
|
}
|
|
391
397
|
|
|
392
|
-
// Keep the current value when the selected option is clicked again.
|
|
393
|
-
// Clearing to "" breaks required selects (e.g. language) and shows the placeholder.
|
|
394
398
|
if (selectedValues.includes(nextValue)) {
|
|
399
|
+
if (allowDeselect) {
|
|
400
|
+
(onValueChange as SingleSelectProps["onValueChange"] | undefined)?.("");
|
|
401
|
+
}
|
|
395
402
|
closeMenu();
|
|
396
403
|
return;
|
|
397
404
|
}
|
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,
|
package/src/utils/countryFlag.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** ISO 3166-1 alpha-2 country code → flag emoji
|
|
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?:
|
|
136
|
-
title?:
|
|
135
|
+
footer?: ReactNode;
|
|
136
|
+
title?: ReactNode;
|
|
137
137
|
subtitle?: string;
|
|
138
138
|
}) {
|
|
139
139
|
return (
|