@lotics/ui 2.4.0 → 2.4.1
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 +1 -1
- package/src/avatar.web.tsx +102 -0
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ImageContentFit, ImageSource } from "expo-image";
|
|
2
|
+
import { Image, View, StyleSheet, StyleProp, ViewStyle, ImageStyle } from "react-native";
|
|
3
|
+
import { Text } from "./text";
|
|
4
|
+
import { colors } from "./colors";
|
|
5
|
+
|
|
6
|
+
interface AvatarProps {
|
|
7
|
+
size?: number;
|
|
8
|
+
source?: ImageSource;
|
|
9
|
+
name?: string;
|
|
10
|
+
style?: StyleProp<ViewStyle | ImageStyle>;
|
|
11
|
+
contentFit?: ImageContentFit;
|
|
12
|
+
/**
|
|
13
|
+
* When true, the avatar announces its `name` to assistive tech. Default
|
|
14
|
+
* false because avatars almost always appear adjacent to the name text —
|
|
15
|
+
* announcing the image as well would double-read. Pass `announce` when the
|
|
16
|
+
* avatar is standalone (with no visible name nearby).
|
|
17
|
+
*/
|
|
18
|
+
announce?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Web Avatar. The native `avatar.tsx` renders through `expo-image`, a native
|
|
23
|
+
* module that pulls expo's runtime into the graph and fails to load under
|
|
24
|
+
* pure-web bundlers (Vite dev throws on its CJS interop; vitest can't resolve
|
|
25
|
+
* expo's winter runtime). On web, `react-native`'s `Image` (→ react-native-web
|
|
26
|
+
* → `<img>`) renders the same circular avatar with none of that cost. The prop
|
|
27
|
+
* surface is identical — `ImageSource`/`ImageContentFit` are kept as erased
|
|
28
|
+
* type-only imports so no expo-image module is ever loaded.
|
|
29
|
+
*/
|
|
30
|
+
export function Avatar(props: AvatarProps) {
|
|
31
|
+
const { source, size = 32, name = "Unknown", style, contentFit, announce } = props;
|
|
32
|
+
const decorative = !announce;
|
|
33
|
+
|
|
34
|
+
if (!source || !source.uri) {
|
|
35
|
+
return (
|
|
36
|
+
<View
|
|
37
|
+
accessible={!decorative}
|
|
38
|
+
accessibilityLabel={decorative ? undefined : name}
|
|
39
|
+
accessibilityElementsHidden={decorative}
|
|
40
|
+
importantForAccessibility={decorative ? "no-hide-descendants" : undefined}
|
|
41
|
+
aria-hidden={decorative || undefined}
|
|
42
|
+
style={[
|
|
43
|
+
styles.base,
|
|
44
|
+
{ backgroundColor: colors.blue["600"], width: size, height: size },
|
|
45
|
+
style,
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
48
|
+
{/* Initials are a visual shorthand for the name; the accessible name is
|
|
49
|
+
on the container so the SR does not read "HM" in addition. */}
|
|
50
|
+
<Text
|
|
51
|
+
userSelect="none"
|
|
52
|
+
size="xs"
|
|
53
|
+
weight="medium"
|
|
54
|
+
color="inverted"
|
|
55
|
+
accessibilityElementsHidden
|
|
56
|
+
importantForAccessibility="no-hide-descendants"
|
|
57
|
+
aria-hidden
|
|
58
|
+
>
|
|
59
|
+
{getInitials(name, size)}
|
|
60
|
+
</Text>
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Image
|
|
67
|
+
accessibilityLabel={decorative ? undefined : name}
|
|
68
|
+
accessibilityElementsHidden={decorative}
|
|
69
|
+
importantForAccessibility={decorative ? "no-hide-descendants" : undefined}
|
|
70
|
+
style={[styles.base, { width: size, height: size }, style as ImageStyle]}
|
|
71
|
+
source={{ uri: source.uri }}
|
|
72
|
+
resizeMode={contentFit === "contain" ? "contain" : "cover"}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getInitials(name: string, size?: number): string {
|
|
78
|
+
let initials = 2;
|
|
79
|
+
|
|
80
|
+
if (size && size <= 32) {
|
|
81
|
+
initials = 1;
|
|
82
|
+
|
|
83
|
+
if (name.length <= 2) {
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return name
|
|
89
|
+
.split(" ")
|
|
90
|
+
.map((c) => c.charAt(0).toUpperCase())
|
|
91
|
+
.slice(0, initials)
|
|
92
|
+
.join("");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
base: {
|
|
97
|
+
borderRadius: 999,
|
|
98
|
+
justifyContent: "center",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
userSelect: "none",
|
|
101
|
+
},
|
|
102
|
+
});
|