@medway-ui/native 1.0.4 → 1.1.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.
Files changed (154) hide show
  1. package/README.md +76 -3
  2. package/lib/commonjs/components/Alert.js +6 -3
  3. package/lib/commonjs/components/Alert.js.map +1 -1
  4. package/lib/commonjs/components/AlertDialog.js +38 -15
  5. package/lib/commonjs/components/AlertDialog.js.map +1 -1
  6. package/lib/commonjs/components/Avatar.js +101 -0
  7. package/lib/commonjs/components/Avatar.js.map +1 -0
  8. package/lib/commonjs/components/CardNews.js +319 -0
  9. package/lib/commonjs/components/CardNews.js.map +1 -0
  10. package/lib/commonjs/components/Checkbox.js +36 -13
  11. package/lib/commonjs/components/Checkbox.js.map +1 -1
  12. package/lib/commonjs/components/Dialog.js +15 -10
  13. package/lib/commonjs/components/Dialog.js.map +1 -1
  14. package/lib/commonjs/components/EmptyState.js +42 -0
  15. package/lib/commonjs/components/EmptyState.js.map +1 -0
  16. package/lib/commonjs/components/FallbackImage.js +55 -0
  17. package/lib/commonjs/components/FallbackImage.js.map +1 -0
  18. package/lib/commonjs/components/Input.js +55 -16
  19. package/lib/commonjs/components/Input.js.map +1 -1
  20. package/lib/commonjs/components/Label.js +43 -14
  21. package/lib/commonjs/components/Label.js.map +1 -1
  22. package/lib/commonjs/components/Loading.js +7 -3
  23. package/lib/commonjs/components/Loading.js.map +1 -1
  24. package/lib/commonjs/components/PageHeader.js +39 -0
  25. package/lib/commonjs/components/PageHeader.js.map +1 -0
  26. package/lib/commonjs/components/Select.js +32 -13
  27. package/lib/commonjs/components/Select.js.map +1 -1
  28. package/lib/commonjs/components/Sheet.js +14 -9
  29. package/lib/commonjs/components/Sheet.js.map +1 -1
  30. package/lib/commonjs/components/Skeleton.js +34 -0
  31. package/lib/commonjs/components/Skeleton.js.map +1 -0
  32. package/lib/commonjs/components/Switch.js +43 -14
  33. package/lib/commonjs/components/Switch.js.map +1 -1
  34. package/lib/commonjs/components/ThemedIcon.js +29 -0
  35. package/lib/commonjs/components/ThemedIcon.js.map +1 -0
  36. package/lib/commonjs/components/Toast.js +24 -12
  37. package/lib/commonjs/components/Toast.js.map +1 -1
  38. package/lib/commonjs/components/Tooltip.js +5 -3
  39. package/lib/commonjs/components/Tooltip.js.map +1 -1
  40. package/lib/commonjs/hooks/useThemeColor.js +71 -0
  41. package/lib/commonjs/hooks/useThemeColor.js.map +1 -0
  42. package/lib/commonjs/index.js +152 -0
  43. package/lib/commonjs/index.js.map +1 -1
  44. package/lib/module/components/Alert.js +6 -3
  45. package/lib/module/components/Alert.js.map +1 -1
  46. package/lib/module/components/AlertDialog.js +38 -15
  47. package/lib/module/components/AlertDialog.js.map +1 -1
  48. package/lib/module/components/Avatar.js +94 -0
  49. package/lib/module/components/Avatar.js.map +1 -0
  50. package/lib/module/components/CardNews.js +303 -0
  51. package/lib/module/components/CardNews.js.map +1 -0
  52. package/lib/module/components/Checkbox.js +37 -14
  53. package/lib/module/components/Checkbox.js.map +1 -1
  54. package/lib/module/components/Dialog.js +15 -10
  55. package/lib/module/components/Dialog.js.map +1 -1
  56. package/lib/module/components/EmptyState.js +39 -0
  57. package/lib/module/components/EmptyState.js.map +1 -0
  58. package/lib/module/components/FallbackImage.js +51 -0
  59. package/lib/module/components/FallbackImage.js.map +1 -0
  60. package/lib/module/components/Input.js +55 -16
  61. package/lib/module/components/Input.js.map +1 -1
  62. package/lib/module/components/Label.js +44 -15
  63. package/lib/module/components/Label.js.map +1 -1
  64. package/lib/module/components/Loading.js +7 -3
  65. package/lib/module/components/Loading.js.map +1 -1
  66. package/lib/module/components/PageHeader.js +36 -0
  67. package/lib/module/components/PageHeader.js.map +1 -0
  68. package/lib/module/components/Select.js +32 -13
  69. package/lib/module/components/Select.js.map +1 -1
  70. package/lib/module/components/Sheet.js +14 -9
  71. package/lib/module/components/Sheet.js.map +1 -1
  72. package/lib/module/components/Skeleton.js +30 -0
  73. package/lib/module/components/Skeleton.js.map +1 -0
  74. package/lib/module/components/Switch.js +44 -15
  75. package/lib/module/components/Switch.js.map +1 -1
  76. package/lib/module/components/ThemedIcon.js +26 -0
  77. package/lib/module/components/ThemedIcon.js.map +1 -0
  78. package/lib/module/components/Toast.js +24 -12
  79. package/lib/module/components/Toast.js.map +1 -1
  80. package/lib/module/components/Tooltip.js +6 -4
  81. package/lib/module/components/Tooltip.js.map +1 -1
  82. package/lib/module/hooks/useThemeColor.js +68 -0
  83. package/lib/module/hooks/useThemeColor.js.map +1 -0
  84. package/lib/module/index.js +15 -0
  85. package/lib/module/index.js.map +1 -1
  86. package/lib/typescript/components/Alert.d.ts.map +1 -1
  87. package/lib/typescript/components/AlertDialog.d.ts +2 -2
  88. package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
  89. package/lib/typescript/components/Avatar.d.ts +28 -0
  90. package/lib/typescript/components/Avatar.d.ts.map +1 -0
  91. package/lib/typescript/components/CardNews.d.ts +54 -0
  92. package/lib/typescript/components/CardNews.d.ts.map +1 -0
  93. package/lib/typescript/components/Checkbox.d.ts +5 -5
  94. package/lib/typescript/components/Checkbox.d.ts.map +1 -1
  95. package/lib/typescript/components/Dialog.d.ts.map +1 -1
  96. package/lib/typescript/components/EmptyState.d.ts +13 -0
  97. package/lib/typescript/components/EmptyState.d.ts.map +1 -0
  98. package/lib/typescript/components/FallbackImage.d.ts +9 -0
  99. package/lib/typescript/components/FallbackImage.d.ts.map +1 -0
  100. package/lib/typescript/components/Input.d.ts +4 -5
  101. package/lib/typescript/components/Input.d.ts.map +1 -1
  102. package/lib/typescript/components/Label.d.ts.map +1 -1
  103. package/lib/typescript/components/Loading.d.ts +5 -1
  104. package/lib/typescript/components/Loading.d.ts.map +1 -1
  105. package/lib/typescript/components/PageHeader.d.ts +12 -0
  106. package/lib/typescript/components/PageHeader.d.ts.map +1 -0
  107. package/lib/typescript/components/Select.d.ts +3 -1
  108. package/lib/typescript/components/Select.d.ts.map +1 -1
  109. package/lib/typescript/components/Sheet.d.ts.map +1 -1
  110. package/lib/typescript/components/Skeleton.d.ts +8 -0
  111. package/lib/typescript/components/Skeleton.d.ts.map +1 -0
  112. package/lib/typescript/components/Switch.d.ts +2 -2
  113. package/lib/typescript/components/Switch.d.ts.map +1 -1
  114. package/lib/typescript/components/ThemedIcon.d.ts +24 -0
  115. package/lib/typescript/components/ThemedIcon.d.ts.map +1 -0
  116. package/lib/typescript/components/Toast.d.ts +1 -1
  117. package/lib/typescript/components/Toast.d.ts.map +1 -1
  118. package/lib/typescript/components/Tooltip.d.ts.map +1 -1
  119. package/lib/typescript/hooks/useThemeColor.d.ts +11 -0
  120. package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -0
  121. package/lib/typescript/index.d.ts +16 -0
  122. package/lib/typescript/index.d.ts.map +1 -1
  123. package/package.json +28 -4
  124. package/setup/MedwayUiProvider.tsx +65 -0
  125. package/setup/babel-brownfield.js +50 -0
  126. package/setup/createMedwayUiNavigator.tsx +94 -0
  127. package/setup/global.css +5 -0
  128. package/setup/index.ts +12 -0
  129. package/setup/index.tsx +42 -0
  130. package/setup/lazyScreen.tsx +14 -0
  131. package/setup/metro.js +195 -0
  132. package/setup/tailwind.js +64 -0
  133. package/setup/tokens.native.css +873 -0
  134. package/src/components/Alert.tsx +3 -1
  135. package/src/components/AlertDialog.tsx +63 -19
  136. package/src/components/Avatar.tsx +130 -0
  137. package/src/components/CardNews.tsx +412 -0
  138. package/src/components/Checkbox.tsx +51 -23
  139. package/src/components/Dialog.tsx +13 -7
  140. package/src/components/EmptyState.tsx +57 -0
  141. package/src/components/FallbackImage.tsx +58 -0
  142. package/src/components/Input.tsx +65 -19
  143. package/src/components/Label.tsx +49 -14
  144. package/src/components/Loading.tsx +13 -2
  145. package/src/components/PageHeader.tsx +50 -0
  146. package/src/components/Select.tsx +22 -10
  147. package/src/components/Sheet.tsx +30 -24
  148. package/src/components/Skeleton.tsx +45 -0
  149. package/src/components/Switch.tsx +64 -32
  150. package/src/components/ThemedIcon.tsx +46 -0
  151. package/src/components/Toast.tsx +26 -13
  152. package/src/components/Tooltip.tsx +1 -0
  153. package/src/hooks/useThemeColor.ts +79 -0
  154. package/src/index.ts +60 -0
@@ -7,6 +7,7 @@ import {
7
7
  import { View, Text, Pressable, type ViewProps, type PressableProps } from "react-native";
8
8
  import { cva, type VariantProps } from "class-variance-authority";
9
9
  import { CloseMd } from "@medway-ui/icons/native";
10
+ import { useThemeColor } from "@/hooks/useThemeColor";
10
11
  import { cn } from "@/lib/utils";
11
12
 
12
13
  type AlertVariant = "regular" | "emphasis" | "success" | "warning" | "error";
@@ -80,6 +81,7 @@ function useAlertContext() {
80
81
 
81
82
  function Alert({ className, variant = "regular", onClose, children, ...props }: AlertProps) {
82
83
  const ctx = useMemo(() => ({ variant, onClose }), [variant, onClose]);
84
+ const closeIconColor = useThemeColor("gray-foreground");
83
85
 
84
86
  return (
85
87
  <AlertContext.Provider value={ctx}>
@@ -95,7 +97,7 @@ function Alert({ className, variant = "regular", onClose, children, ...props }:
95
97
  accessibilityLabel="Close alert"
96
98
  className="absolute right-4 top-2"
97
99
  >
98
- <CloseMd size={16} className="text-gray-foreground" />
100
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
99
101
  </Pressable>
100
102
  )}
101
103
  </View>
@@ -1,8 +1,9 @@
1
1
  import { createContext, useContext, useMemo, type ReactNode } from "react";
2
2
  import { View, Text, type ViewProps } from "react-native";
3
3
  import * as AlertDialogPrimitive from "@rn-primitives/alert-dialog";
4
- import { CloseMd } from "@medway-ui/icons/native";
5
4
  import { cva } from "class-variance-authority";
5
+ import { CloseMd } from "@medway-ui/icons/native";
6
+ import { useThemeColor } from "@/hooks/useThemeColor";
6
7
  import { cn } from "@/lib/utils";
7
8
  import { Button, type ButtonProps } from "@/components/Button";
8
9
  import { Tag, type TagProps } from "@/components/Tag";
@@ -136,26 +137,31 @@ function AlertDialogContent({
136
137
  showCloseButton = false,
137
138
  ...props
138
139
  }: AlertDialogContentProps) {
140
+ const closeIconColor = useThemeColor("gray-foreground");
141
+
139
142
  return (
140
143
  <AlertDialogPrimitive.Portal>
141
144
  <AlertDialogPrimitive.Overlay
142
- className={cn("absolute inset-0 items-center justify-center bg-black/50", overlayClassName)}
145
+ className={cn("absolute inset-0 bg-black/50", overlayClassName)}
146
+ />
147
+ <AlertDialogPrimitive.Content
148
+ className="absolute inset-0 items-center justify-center px-4"
149
+ {...props}
143
150
  >
144
- <AlertDialogPrimitive.Content
151
+ <View
145
152
  className={cn(
146
- "relative flex-col w-full max-w-[calc(100%-2rem)] rounded-xl bg-background shadow-lg overflow-hidden",
153
+ "relative flex-col w-full rounded-xl bg-background shadow-lg overflow-hidden",
147
154
  className
148
155
  )}
149
- {...props}
150
156
  >
151
157
  {children}
152
158
  {showCloseButton && (
153
159
  <AlertDialogPrimitive.Cancel className="absolute right-3 top-3 rounded-pill p-1.5 active:bg-gray-background-soft">
154
- <CloseMd size={16} className="text-gray-foreground" />
160
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
155
161
  </AlertDialogPrimitive.Cancel>
156
162
  )}
157
- </AlertDialogPrimitive.Content>
158
- </AlertDialogPrimitive.Overlay>
163
+ </View>
164
+ </AlertDialogPrimitive.Content>
159
165
  </AlertDialogPrimitive.Portal>
160
166
  );
161
167
  }
@@ -225,29 +231,67 @@ function AlertDialogFooter({ className, ...props }: ViewProps) {
225
231
  );
226
232
  }
227
233
 
228
- function AlertDialogAction({ className, variant, size = "lg", children, ...props }: AlertDialogActionProps) {
234
+ function AlertDialogAction({
235
+ className,
236
+ variant,
237
+ size = "lg",
238
+ children,
239
+ disabled,
240
+ onPress,
241
+ ...props
242
+ }: AlertDialogActionProps) {
229
243
  const ctx = useContext(AlertDialogContext);
244
+ const primitiveCtx = AlertDialogPrimitive.useRootContext();
230
245
  const resolvedVariant = variant ?? actionVariantMap[ctx?.variant ?? DEFAULT_VARIANT];
231
246
 
232
247
  return (
233
- <AlertDialogPrimitive.Action
234
- className={cn("flex-1", className)}
235
- {...props}
236
- >
237
- <Button variant={resolvedVariant} size={size} className="w-full">
248
+ <View className={cn("flex-1", className)}>
249
+ <Button
250
+ variant={resolvedVariant}
251
+ size={size}
252
+ className="w-full"
253
+ disabled={disabled}
254
+ onPress={(event) => {
255
+ if (disabled) return;
256
+ primitiveCtx.onOpenChange(false);
257
+ onPress?.(event);
258
+ }}
259
+ {...props}
260
+ >
238
261
  {children as ReactNode}
239
262
  </Button>
240
- </AlertDialogPrimitive.Action>
263
+ </View>
241
264
  );
242
265
  }
243
266
 
244
- function AlertDialogCancel({ className, variant = "regular-soft", size = "lg", children, ...props }: AlertDialogCancelProps) {
267
+ function AlertDialogCancel({
268
+ className,
269
+ variant = "regular-soft",
270
+ size = "lg",
271
+ children,
272
+ disabled,
273
+ onPress,
274
+ ...props
275
+ }: AlertDialogCancelProps) {
276
+ const primitiveCtx = AlertDialogPrimitive.useRootContext();
277
+
245
278
  return (
246
- <AlertDialogPrimitive.Cancel className={cn("flex-1", className)} {...props}>
247
- <Button variant={variant} size={size} className="w-full">
279
+ <View className={cn("flex-1", className)}>
280
+ <Button
281
+ variant={variant}
282
+ size={size}
283
+ className="w-full"
284
+ disabled={disabled}
285
+ onPress={(event) => {
286
+ if (disabled) return;
287
+ primitiveCtx.onOpenChange(false);
288
+ onPress?.(event);
289
+ }}
290
+ {...props}
291
+ >
248
292
  {children as ReactNode}
249
293
  </Button>
250
- </AlertDialogPrimitive.Cancel>
294
+ </View>
251
295
  );
252
296
  }
253
297
 
@@ -0,0 +1,130 @@
1
+ import { type ReactNode } from "react";
2
+ import { View, Text, type ViewProps } from "react-native";
3
+ import * as AvatarPrimitive from "@rn-primitives/avatar";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { User } from "@medway-ui/icons/native";
6
+
7
+ import { Badge as BadgeComponent } from "@/components/Badge";
8
+ import { useThemeColor } from "@/hooks/useThemeColor";
9
+ import { cn } from "@/lib/utils";
10
+
11
+ const badgeSizeMap: Record<
12
+ "sm" | "md" | "lg" | "xl",
13
+ "sm" | "md" | "lg" | "xl"
14
+ > = {
15
+ sm: "sm",
16
+ md: "md",
17
+ lg: "md",
18
+ xl: "lg",
19
+ };
20
+
21
+ const avatarVariants = cva(
22
+ "relative flex shrink-0 overflow-hidden rounded-full",
23
+ {
24
+ variants: {
25
+ size: {
26
+ sm: "size-8",
27
+ md: "size-10",
28
+ lg: "size-12",
29
+ xl: "size-14",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: "md",
34
+ },
35
+ }
36
+ );
37
+
38
+ interface AvatarProps
39
+ extends ViewProps,
40
+ VariantProps<typeof avatarVariants> {
41
+ alt?: string;
42
+ fallback?: ReactNode;
43
+ badge?: number | string;
44
+ children?: ReactNode;
45
+ }
46
+
47
+ interface AvatarImageProps extends Omit<AvatarPrimitive.ImageProps, "source"> {
48
+ src?: string;
49
+ source?: AvatarPrimitive.ImageProps["source"];
50
+ className?: string;
51
+ }
52
+
53
+ interface AvatarFallbackProps extends ViewProps {
54
+ className?: string;
55
+ children?: ReactNode;
56
+ }
57
+
58
+ function Avatar({
59
+ className,
60
+ size,
61
+ badge,
62
+ children,
63
+ alt,
64
+ fallback,
65
+ ...props
66
+ }: AvatarProps) {
67
+ const content =
68
+ children ?? <AvatarFallback>{fallback}</AvatarFallback>;
69
+
70
+ return (
71
+ <View className="relative">
72
+ {badge ? (
73
+ <BadgeComponent
74
+ size={badgeSizeMap[size || "md"]}
75
+ className="absolute -right-2 -top-1 z-10"
76
+ >
77
+ {String(badge)}
78
+ </BadgeComponent>
79
+ ) : null}
80
+ <AvatarPrimitive.Root
81
+ alt={alt ?? ""}
82
+ className={cn(avatarVariants({ size, className }))}
83
+ {...props}
84
+ >
85
+ {content}
86
+ </AvatarPrimitive.Root>
87
+ </View>
88
+ );
89
+ }
90
+
91
+ function AvatarImage({ className, src, source, ...props }: AvatarImageProps) {
92
+ const resolvedSource = source ?? (src ? { uri: src } : undefined);
93
+
94
+ return (
95
+ <AvatarPrimitive.Image
96
+ source={resolvedSource}
97
+ className={cn("size-full object-cover", className)}
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ function AvatarFallback({ className, children, ...props }: AvatarFallbackProps) {
104
+ const iconColor = useThemeColor("primary-foreground");
105
+
106
+ return (
107
+ <AvatarPrimitive.Fallback
108
+ className={cn(
109
+ "flex size-full items-center justify-center rounded-full bg-gray-background-soft",
110
+ className
111
+ )}
112
+ {...props}
113
+ >
114
+ {children ? (
115
+ typeof children === "string" ? (
116
+ <Text className="text-sm font-semibold text-primary-foreground">
117
+ {children}
118
+ </Text>
119
+ ) : (
120
+ children
121
+ )
122
+ ) : iconColor ? (
123
+ <User width={20} height={20} color={iconColor} />
124
+ ) : null}
125
+ </AvatarPrimitive.Fallback>
126
+ );
127
+ }
128
+
129
+ export { Avatar, AvatarImage, AvatarFallback, avatarVariants };
130
+ export type { AvatarProps, AvatarImageProps, AvatarFallbackProps };
@@ -0,0 +1,412 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { Calendar } from "@medway-ui/icons/native";
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ type ReactNode,
8
+ } from "react";
9
+ import {
10
+ Image,
11
+ Text,
12
+ View,
13
+ useWindowDimensions,
14
+ type ImageProps,
15
+ type ViewProps,
16
+ } from "react-native";
17
+
18
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/Avatar";
19
+ import { Button, type ButtonProps } from "@/components/Button";
20
+ import { FallbackImage } from "@/components/FallbackImage";
21
+ import { Skeleton } from "@/components/Skeleton";
22
+ import { useThemeColor } from "@/hooks/useThemeColor";
23
+ import { cn } from "@/lib/utils";
24
+
25
+ interface CardNewsResourcesProps extends ViewProps {
26
+ label: string;
27
+ children?: ReactNode;
28
+ }
29
+
30
+ interface CardNewsContextValue {
31
+ orientation: "vertical" | "horizontal";
32
+ }
33
+
34
+ const CardNewsContext = createContext<CardNewsContextValue>({
35
+ orientation: "vertical",
36
+ });
37
+
38
+ const HORIZONTAL_IMAGE_MIN_WIDTH = 1280;
39
+
40
+ interface CardNewsDateProps extends ViewProps {
41
+ icon?: ReactNode;
42
+ children?: ReactNode;
43
+ }
44
+
45
+ interface CardNewsImageProps extends ViewProps {
46
+ src?: string;
47
+ alt?: string;
48
+ imageProps?: ImageProps;
49
+ children?: ReactNode;
50
+ }
51
+
52
+ interface CardNewsAuthorProps extends Omit<ViewProps, "role"> {
53
+ src?: string;
54
+ alt?: string;
55
+ name?: string;
56
+ role?: string;
57
+ children?: ReactNode;
58
+ }
59
+
60
+ interface CardNewsProps
61
+ extends ViewProps,
62
+ VariantProps<typeof cardNewsVariants> {
63
+ isRead?: boolean;
64
+ children?: ReactNode;
65
+ }
66
+
67
+ const cardNewsVariants = cva("flex border", {
68
+ variants: {
69
+ variant: {
70
+ default:
71
+ "rounded-lg border-secondary-stroke bg-secondary-background-soft-muted p-6",
72
+ read: "rounded-lg border-gray-stroke-muted bg-surface-card p-6",
73
+ compact: "rounded-xl border-gray-stroke-muted bg-surface-card p-4",
74
+ },
75
+ orientation: {
76
+ vertical: "flex-col gap-6",
77
+ horizontal: "flex-row gap-10",
78
+ },
79
+ },
80
+ defaultVariants: {
81
+ variant: "default",
82
+ orientation: "vertical",
83
+ },
84
+ });
85
+
86
+ function CardNews({
87
+ className,
88
+ isRead,
89
+ variant,
90
+ orientation = "vertical",
91
+ ...props
92
+ }: CardNewsProps) {
93
+ const resolvedVariant = isRead ? "read" : (variant ?? "default");
94
+
95
+ return (
96
+ <CardNewsContext.Provider
97
+ value={{ orientation: orientation ?? "vertical" }}
98
+ >
99
+ <View
100
+ className={cn(
101
+ cardNewsVariants({ variant: resolvedVariant, orientation }),
102
+ className
103
+ )}
104
+ {...props}
105
+ />
106
+ </CardNewsContext.Provider>
107
+ );
108
+ }
109
+
110
+ function CardNewsBody({ className, style, ...props }: ViewProps) {
111
+ return (
112
+ <View
113
+ className={cn("flex min-w-0 flex-1 flex-col gap-6", className)}
114
+ style={[{ minWidth: 0 }, style]}
115
+ {...props}
116
+ />
117
+ );
118
+ }
119
+
120
+ function CardNewsHeader({ className, ...props }: ViewProps) {
121
+ return (
122
+ <View
123
+ className={cn(
124
+ "flex w-full flex-row flex-wrap items-start justify-between gap-4",
125
+ className
126
+ )}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ function CardNewsTags({ className, ...props }: ViewProps) {
133
+ return (
134
+ <View
135
+ className={cn("flex min-w-0 flex-1 flex-row flex-wrap items-center gap-x-4 gap-y-3", className)}
136
+ {...props}
137
+ />
138
+ );
139
+ }
140
+
141
+ function CardNewsDate({
142
+ className,
143
+ icon,
144
+ children,
145
+ ...props
146
+ }: CardNewsDateProps) {
147
+ const iconColor = useThemeColor("primary-foreground");
148
+ const resolvedIcon =
149
+ icon ??
150
+ (iconColor ? (
151
+ <Calendar width={16} height={16} color={iconColor} />
152
+ ) : null);
153
+
154
+ return (
155
+ <View
156
+ className={cn("shrink-0 flex-row items-center gap-1.5", className)}
157
+ {...props}
158
+ >
159
+ {resolvedIcon}
160
+ {children ? (
161
+ <Text className="text-xs font-semibold leading-xs text-primary-foreground">
162
+ {children}
163
+ </Text>
164
+ ) : null}
165
+ </View>
166
+ );
167
+ }
168
+
169
+ function CardNewsImage({
170
+ className,
171
+ src,
172
+ alt = "",
173
+ imageProps,
174
+ children,
175
+ ...props
176
+ }: CardNewsImageProps) {
177
+ const { orientation } = useContext(CardNewsContext);
178
+ const { width } = useWindowDimensions();
179
+ const [hasError, setHasError] = useState(false);
180
+ const showImage = !!src && !hasError;
181
+ const showHorizontalImage =
182
+ orientation !== "horizontal" || width >= HORIZONTAL_IMAGE_MIN_WIDTH;
183
+
184
+ if (!showHorizontalImage) {
185
+ return null;
186
+ }
187
+
188
+ return (
189
+ <View
190
+ className={cn(
191
+ "relative overflow-hidden bg-gray-background-soft",
192
+ orientation === "vertical"
193
+ ? "h-[300px] w-full rounded-lg"
194
+ : "w-[482px] rounded-xl",
195
+ className
196
+ )}
197
+ {...props}
198
+ >
199
+ {showImage ? (
200
+ <Image
201
+ source={{ uri: src }}
202
+ accessibilityLabel={alt}
203
+ className="absolute inset-0 size-full"
204
+ resizeMode="cover"
205
+ {...imageProps}
206
+ onError={(event) => {
207
+ imageProps?.onError?.(event);
208
+ setHasError(true);
209
+ }}
210
+ />
211
+ ) : (
212
+ children ?? <FallbackImage className="absolute inset-0" />
213
+ )}
214
+ </View>
215
+ );
216
+ }
217
+
218
+ function CardNewsContent({ className, ...props }: ViewProps) {
219
+ return (
220
+ <View
221
+ className={cn("flex w-full flex-col gap-6", className)}
222
+ {...props}
223
+ />
224
+ );
225
+ }
226
+
227
+ function CardNewsTitle({
228
+ className,
229
+ children,
230
+ ...props
231
+ }: ViewProps & { children?: ReactNode }) {
232
+ return (
233
+ <Text
234
+ numberOfLines={2}
235
+ className={cn(
236
+ "-mb-2 text-title-xs font-semibold leading-title-xs text-primary-foreground",
237
+ className
238
+ )}
239
+ {...props}
240
+ >
241
+ {children}
242
+ </Text>
243
+ );
244
+ }
245
+
246
+ function CardNewsDescription({
247
+ className,
248
+ children,
249
+ ...props
250
+ }: ViewProps & { children?: ReactNode }) {
251
+ return (
252
+ <Text
253
+ numberOfLines={3}
254
+ className={cn("text-md leading-md text-primary-foreground", className)}
255
+ {...props}
256
+ >
257
+ {children}
258
+ </Text>
259
+ );
260
+ }
261
+
262
+ function CardNewsResources({
263
+ className,
264
+ label,
265
+ children,
266
+ ...props
267
+ }: CardNewsResourcesProps) {
268
+ return (
269
+ <View className={cn("flex flex-col gap-2", className)} {...props}>
270
+ <Text className="text-sm font-semibold leading-sm text-primary-foreground-muted">
271
+ {label}
272
+ </Text>
273
+ <View className="flex-row flex-wrap gap-2">{children}</View>
274
+ </View>
275
+ );
276
+ }
277
+
278
+ function CardNewsResourceItem({
279
+ className,
280
+ children,
281
+ iconLeft,
282
+ ...props
283
+ }: ButtonProps) {
284
+ return (
285
+ <Button
286
+ variant="outlined"
287
+ size="sm"
288
+ className={cn("bg-surface-card", className)}
289
+ iconLeft={iconLeft}
290
+ {...props}
291
+ >
292
+ {children}
293
+ </Button>
294
+ );
295
+ }
296
+
297
+ function getInitials(name: string): string {
298
+ return name
299
+ .trim()
300
+ .split(/\s+/)
301
+ .slice(0, 2)
302
+ .map((word) => word[0] ?? "")
303
+ .join("")
304
+ .toUpperCase();
305
+ }
306
+
307
+ function CardNewsAuthor({
308
+ className,
309
+ src,
310
+ alt,
311
+ name,
312
+ role,
313
+ children,
314
+ ...props
315
+ }: CardNewsAuthorProps) {
316
+ return (
317
+ <View
318
+ className={cn("flex-row items-center gap-2", className)}
319
+ {...props}
320
+ >
321
+ {children ?? (
322
+ <>
323
+ <Avatar size="md" alt={alt ?? name ?? ""}>
324
+ {src ? <AvatarImage src={src} /> : null}
325
+ <AvatarFallback>
326
+ {name ? getInitials(name) : undefined}
327
+ </AvatarFallback>
328
+ </Avatar>
329
+ <View className="flex flex-col gap-0.5">
330
+ {role ? (
331
+ <Text className="text-xs font-medium leading-xs text-secondary-foreground">
332
+ {role}
333
+ </Text>
334
+ ) : null}
335
+ {name ? (
336
+ <Text className="text-sm font-semibold leading-sm text-primary-foreground">
337
+ {name}
338
+ </Text>
339
+ ) : null}
340
+ </View>
341
+ </>
342
+ )}
343
+ </View>
344
+ );
345
+ }
346
+
347
+ function CardNewsFooter({ className, ...props }: ViewProps) {
348
+ return (
349
+ <View
350
+ className={cn(
351
+ "flex w-full flex-row flex-wrap items-center gap-6 gap-y-3",
352
+ className
353
+ )}
354
+ {...props}
355
+ />
356
+ );
357
+ }
358
+
359
+ function CardNewsSkeleton({ className, ...props }: ViewProps) {
360
+ return (
361
+ <View
362
+ className={cn(
363
+ "gap-6 rounded-lg border border-gray-stroke-muted bg-surface-card p-6",
364
+ className
365
+ )}
366
+ {...props}
367
+ >
368
+ <View className="flex-row items-start justify-between gap-4">
369
+ <View className="flex-1 flex-row flex-wrap gap-2">
370
+ <Skeleton className="h-7 w-[88px] rounded-full" />
371
+ <Skeleton className="h-7 w-[72px] rounded-full" />
372
+ </View>
373
+ <Skeleton className="h-4 w-[72px]" />
374
+ </View>
375
+ <Skeleton className="h-[150px] w-full rounded-lg" />
376
+ <View className="gap-2">
377
+ <Skeleton className="h-7 w-24 rounded-full" />
378
+ <Skeleton className="h-5 w-full" />
379
+ <Skeleton className="h-4 w-full" />
380
+ <Skeleton className="h-4 w-[85%]" />
381
+ </View>
382
+ <View className="gap-4">
383
+ <Skeleton className="h-12 w-full rounded-lg" />
384
+ <Skeleton className="h-12 w-full rounded-lg" />
385
+ </View>
386
+ </View>
387
+ );
388
+ }
389
+
390
+ export {
391
+ CardNews,
392
+ CardNewsAuthor,
393
+ CardNewsBody,
394
+ CardNewsContent,
395
+ CardNewsDate,
396
+ CardNewsDescription,
397
+ CardNewsFooter,
398
+ CardNewsHeader,
399
+ CardNewsImage,
400
+ CardNewsResourceItem,
401
+ CardNewsResources,
402
+ CardNewsSkeleton,
403
+ CardNewsTags,
404
+ CardNewsTitle,
405
+ };
406
+ export type {
407
+ CardNewsAuthorProps,
408
+ CardNewsDateProps,
409
+ CardNewsImageProps,
410
+ CardNewsProps,
411
+ CardNewsResourcesProps,
412
+ };