@seed-design/lynx-react 0.7.0 → 0.8.0

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 (245) hide show
  1. package/lib/components/Accordion/Accordion.jsx +2 -2
  2. package/lib/components/ActionButton/ActionButton.jsx +1 -1
  3. package/lib/components/AlertDialog/AlertDialog.d.ts +64 -0
  4. package/lib/components/AlertDialog/AlertDialog.d.ts.map +1 -0
  5. package/lib/components/AlertDialog/AlertDialog.jsx +138 -0
  6. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts +2 -0
  7. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts.map +1 -0
  8. package/lib/components/AlertDialog/AlertDialog.namespace.js +17 -0
  9. package/lib/components/AlertDialog/index.d.ts +3 -0
  10. package/lib/components/AlertDialog/index.d.ts.map +1 -0
  11. package/lib/components/AppBar/AppBar.d.ts +14 -1
  12. package/lib/components/AppBar/AppBar.d.ts.map +1 -1
  13. package/lib/components/AppBar/AppBar.jsx +46 -10
  14. package/lib/components/AppBar/AppBar.namespace.d.ts +1 -1
  15. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -1
  16. package/lib/components/AppBar/index.d.ts +1 -1
  17. package/lib/components/AppBar/index.d.ts.map +1 -1
  18. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts +182 -0
  19. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts.map +1 -0
  20. package/lib/components/AttachmentDisplay/AttachmentDisplay.jsx +444 -0
  21. package/lib/components/AttachmentDisplay/index.d.ts +2 -0
  22. package/lib/components/AttachmentDisplay/index.d.ts.map +1 -0
  23. package/lib/components/AttachmentInput/AttachmentInput.d.ts +179 -0
  24. package/lib/components/AttachmentInput/AttachmentInput.d.ts.map +1 -0
  25. package/lib/components/AttachmentInput/AttachmentInput.jsx +520 -0
  26. package/lib/components/AttachmentInput/index.d.ts +2 -0
  27. package/lib/components/AttachmentInput/index.d.ts.map +1 -0
  28. package/lib/components/BottomSheet/BottomSheet.d.ts +1 -1
  29. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  30. package/lib/components/BottomSheet/BottomSheet.jsx +1 -7
  31. package/lib/components/Callout/Callout.jsx +2 -2
  32. package/lib/components/Checkbox/Checkbox.jsx +2 -2
  33. package/lib/components/Chip/Chip.jsx +2 -2
  34. package/lib/components/ChipTabs/ChipTabs.d.ts +41 -0
  35. package/lib/components/ChipTabs/ChipTabs.d.ts.map +1 -0
  36. package/lib/components/ChipTabs/ChipTabs.jsx +39 -0
  37. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts +2 -0
  38. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts.map +1 -0
  39. package/lib/components/ChipTabs/ChipTabs.namespace.js +13 -0
  40. package/lib/components/ChipTabs/index.d.ts +3 -0
  41. package/lib/components/ChipTabs/index.d.ts.map +1 -0
  42. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts +18 -0
  43. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts.map +1 -0
  44. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.jsx +113 -0
  45. package/lib/components/ContextualFloatingButton/index.d.ts +2 -0
  46. package/lib/components/ContextualFloatingButton/index.d.ts.map +1 -0
  47. package/lib/components/Dialog/Dialog.d.ts +87 -0
  48. package/lib/components/Dialog/Dialog.d.ts.map +1 -0
  49. package/lib/components/Dialog/Dialog.jsx +147 -0
  50. package/lib/components/Dialog/Dialog.namespace.d.ts +2 -0
  51. package/lib/components/Dialog/Dialog.namespace.d.ts.map +1 -0
  52. package/lib/components/Dialog/Dialog.namespace.js +18 -0
  53. package/lib/components/Dialog/index.d.ts +3 -0
  54. package/lib/components/Dialog/index.d.ts.map +1 -0
  55. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts +23 -0
  56. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts.map +1 -0
  57. package/lib/components/FloatingActionButton/FloatingActionButton.jsx +76 -0
  58. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts +2 -0
  59. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts.map +1 -0
  60. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.js +10 -0
  61. package/lib/components/FloatingActionButton/index.d.ts +3 -0
  62. package/lib/components/FloatingActionButton/index.d.ts.map +1 -0
  63. package/lib/components/HelpBubble/HelpBubble.d.ts +67 -0
  64. package/lib/components/HelpBubble/HelpBubble.d.ts.map +1 -0
  65. package/lib/components/HelpBubble/HelpBubble.jsx +600 -0
  66. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts +2 -0
  67. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts.map +1 -0
  68. package/lib/components/HelpBubble/HelpBubble.namespace.js +18 -0
  69. package/lib/components/HelpBubble/index.d.ts +3 -0
  70. package/lib/components/HelpBubble/index.d.ts.map +1 -0
  71. package/lib/components/Icon/Icon.d.ts +1 -0
  72. package/lib/components/Icon/Icon.d.ts.map +1 -1
  73. package/lib/components/Icon/Icon.jsx +3 -2
  74. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts +28 -0
  75. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts.map +1 -0
  76. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.jsx +37 -0
  77. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts +2 -0
  78. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts.map +1 -0
  79. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.js +9 -0
  80. package/lib/components/IdentityPlaceholder/identity-placeholder-business.js +4 -0
  81. package/lib/components/IdentityPlaceholder/identity-placeholder-person.js +4 -0
  82. package/lib/components/IdentityPlaceholder/index.d.ts +3 -0
  83. package/lib/components/IdentityPlaceholder/index.d.ts.map +1 -0
  84. package/lib/components/InputButton/InputButton.d.ts +2 -0
  85. package/lib/components/InputButton/InputButton.d.ts.map +1 -1
  86. package/lib/components/InputButton/InputButton.jsx +40 -8
  87. package/lib/components/List/List.d.ts +1 -1
  88. package/lib/components/List/List.d.ts.map +1 -1
  89. package/lib/components/List/List.jsx +26 -12
  90. package/lib/components/Menu/Menu.d.ts.map +1 -1
  91. package/lib/components/Menu/Menu.jsx +15 -5
  92. package/lib/components/PageBanner/PageBanner.d.ts +3 -0
  93. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  94. package/lib/components/PageBanner/PageBanner.jsx +49 -11
  95. package/lib/components/QuantityPicker/QuantityPicker.d.ts +53 -0
  96. package/lib/components/QuantityPicker/QuantityPicker.d.ts.map +1 -0
  97. package/lib/components/QuantityPicker/QuantityPicker.jsx +282 -0
  98. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts +2 -0
  99. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts.map +1 -0
  100. package/lib/components/QuantityPicker/QuantityPicker.namespace.js +11 -0
  101. package/lib/components/QuantityPicker/index.d.ts +3 -0
  102. package/lib/components/QuantityPicker/index.d.ts.map +1 -0
  103. package/lib/components/RadioGroup/RadioGroup.jsx +2 -2
  104. package/lib/components/ReactionButton/ReactionButton.d.ts +1 -1
  105. package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -1
  106. package/lib/components/ReactionButton/ReactionButton.jsx +22 -13
  107. package/lib/components/ScrollFog/ScrollFog.d.ts +41 -0
  108. package/lib/components/ScrollFog/ScrollFog.d.ts.map +1 -0
  109. package/lib/components/ScrollFog/ScrollFog.jsx +65 -0
  110. package/lib/components/ScrollFog/index.d.ts +2 -0
  111. package/lib/components/ScrollFog/index.d.ts.map +1 -0
  112. package/lib/components/SegmentedControl/SegmentedControl.jsx +2 -2
  113. package/lib/components/Select/Select.d.ts +112 -0
  114. package/lib/components/Select/Select.d.ts.map +1 -0
  115. package/lib/components/Select/Select.jsx +885 -0
  116. package/lib/components/Select/Select.namespace.d.ts +2 -0
  117. package/lib/components/Select/Select.namespace.d.ts.map +1 -0
  118. package/lib/components/Select/Select.namespace.js +23 -0
  119. package/lib/components/Select/index.d.ts +3 -0
  120. package/lib/components/Select/index.d.ts.map +1 -0
  121. package/lib/components/Skeleton/Skeleton.d.ts +15 -0
  122. package/lib/components/Skeleton/Skeleton.d.ts.map +1 -0
  123. package/lib/components/Skeleton/Skeleton.jsx +18 -0
  124. package/lib/components/Skeleton/index.d.ts +2 -0
  125. package/lib/components/Skeleton/index.d.ts.map +1 -0
  126. package/lib/components/Slider/Slider.d.ts +101 -0
  127. package/lib/components/Slider/Slider.d.ts.map +1 -0
  128. package/lib/components/Slider/Slider.jsx +573 -0
  129. package/lib/components/Slider/Slider.namespace.d.ts +2 -0
  130. package/lib/components/Slider/Slider.namespace.d.ts.map +1 -0
  131. package/lib/components/Slider/Slider.namespace.js +19 -0
  132. package/lib/components/Slider/Slider.utils.d.ts +19 -0
  133. package/lib/components/Slider/Slider.utils.d.ts.map +1 -0
  134. package/lib/components/Slider/Slider.utils.js +106 -0
  135. package/lib/components/Slider/index.d.ts +3 -0
  136. package/lib/components/Slider/index.d.ts.map +1 -0
  137. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -1
  138. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +22 -8
  139. package/lib/components/Switch/Switch.d.ts.map +1 -1
  140. package/lib/components/Switch/Switch.jsx +5 -3
  141. package/lib/components/Tabs/Tabs.d.ts +34 -2
  142. package/lib/components/Tabs/Tabs.d.ts.map +1 -1
  143. package/lib/components/Tabs/Tabs.jsx +186 -56
  144. package/lib/components/Tabs/Tabs.utils.d.ts +14 -1
  145. package/lib/components/Tabs/Tabs.utils.d.ts.map +1 -1
  146. package/lib/components/Tabs/Tabs.utils.js +24 -3
  147. package/lib/components/Tabs/index.d.ts +1 -1
  148. package/lib/components/Tabs/index.d.ts.map +1 -1
  149. package/lib/components/index.d.ts +14 -0
  150. package/lib/components/index.d.ts.map +1 -1
  151. package/lib/hooks/useControllableState.d.ts +1 -14
  152. package/lib/hooks/useControllableState.d.ts.map +1 -1
  153. package/lib/hooks/usePressTap.d.ts +1 -27
  154. package/lib/hooks/usePressTap.d.ts.map +1 -1
  155. package/lib/hooks/useScaleFeedback.d.ts +1 -1
  156. package/lib/hooks/useScaleFeedback.js +1 -1
  157. package/lib/index.js +29 -6
  158. package/lib/{hooks/useControllableState.js → packages/lynx-react-headless/use-controllable-state/lib/index.js} +2 -3
  159. package/lib/{hooks/usePressTap.js → packages/lynx-react-headless/use-press-tap/lib/index.js} +13 -10
  160. package/lib/types.d.ts +1 -0
  161. package/lib/types.d.ts.map +1 -1
  162. package/package.json +9 -5
  163. package/src/components/ActionButton/ActionButton.test.tsx +7 -7
  164. package/src/components/AlertDialog/AlertDialog.namespace.ts +22 -0
  165. package/src/components/AlertDialog/AlertDialog.test.tsx +226 -0
  166. package/src/components/AlertDialog/AlertDialog.tsx +374 -0
  167. package/src/components/AlertDialog/index.ts +24 -0
  168. package/src/components/AppBar/AppBar.namespace.ts +1 -0
  169. package/src/components/AppBar/AppBar.test.tsx +257 -0
  170. package/src/components/AppBar/AppBar.tsx +109 -14
  171. package/src/components/AppBar/index.ts +1 -0
  172. package/src/components/AttachmentDisplay/AttachmentDisplay.tsx +886 -0
  173. package/src/components/AttachmentDisplay/index.ts +48 -0
  174. package/src/components/AttachmentInput/AttachmentInput.tsx +1001 -0
  175. package/src/components/AttachmentInput/index.ts +49 -0
  176. package/src/components/BottomSheet/BottomSheet.test.tsx +22 -7
  177. package/src/components/BottomSheet/BottomSheet.tsx +1 -19
  178. package/src/components/ChipTabs/ChipTabs.namespace.ts +14 -0
  179. package/src/components/ChipTabs/ChipTabs.tsx +119 -0
  180. package/src/components/ChipTabs/index.ts +16 -0
  181. package/src/components/ContextualFloatingButton/ContextualFloatingButton.test.tsx +63 -0
  182. package/src/components/ContextualFloatingButton/ContextualFloatingButton.tsx +232 -0
  183. package/src/components/ContextualFloatingButton/index.ts +4 -0
  184. package/src/components/Dialog/Dialog.namespace.ts +24 -0
  185. package/src/components/Dialog/Dialog.test.tsx +172 -0
  186. package/src/components/Dialog/Dialog.tsx +373 -0
  187. package/src/components/Dialog/index.ts +26 -0
  188. package/src/components/FloatingActionButton/FloatingActionButton.namespace.ts +8 -0
  189. package/src/components/FloatingActionButton/FloatingActionButton.test.tsx +59 -0
  190. package/src/components/FloatingActionButton/FloatingActionButton.tsx +207 -0
  191. package/src/components/FloatingActionButton/index.ts +10 -0
  192. package/src/components/HelpBubble/HelpBubble.namespace.ts +24 -0
  193. package/src/components/HelpBubble/HelpBubble.tsx +998 -0
  194. package/src/components/HelpBubble/index.ts +26 -0
  195. package/src/components/Icon/Icon.test.tsx +57 -5
  196. package/src/components/Icon/Icon.tsx +16 -2
  197. package/src/components/IdentityPlaceholder/IdentityPlaceholder.namespace.ts +6 -0
  198. package/src/components/IdentityPlaceholder/IdentityPlaceholder.tsx +103 -0
  199. package/src/components/IdentityPlaceholder/assets.d.ts +4 -0
  200. package/src/components/IdentityPlaceholder/identity-placeholder-business.webp +0 -0
  201. package/src/components/IdentityPlaceholder/identity-placeholder-person.webp +0 -0
  202. package/src/components/IdentityPlaceholder/index.ts +8 -0
  203. package/src/components/InputButton/InputButton.test.tsx +128 -1
  204. package/src/components/InputButton/InputButton.tsx +60 -9
  205. package/src/components/List/List.test.tsx +54 -2
  206. package/src/components/List/List.tsx +43 -10
  207. package/src/components/Menu/Menu.tsx +14 -4
  208. package/src/components/Menu/__tests__/Menu.test.tsx +34 -0
  209. package/src/components/PageBanner/PageBanner.test.tsx +110 -3
  210. package/src/components/PageBanner/PageBanner.tsx +83 -19
  211. package/src/components/QuantityPicker/QuantityPicker.namespace.ts +10 -0
  212. package/src/components/QuantityPicker/QuantityPicker.test.tsx +235 -0
  213. package/src/components/QuantityPicker/QuantityPicker.tsx +605 -0
  214. package/src/components/QuantityPicker/index.ts +14 -0
  215. package/src/components/ReactionButton/ReactionButton.feedback.test.tsx +50 -0
  216. package/src/components/ReactionButton/ReactionButton.test.tsx +6 -4
  217. package/src/components/ReactionButton/ReactionButton.tsx +20 -12
  218. package/src/components/ScrollFog/ScrollFog.test.tsx +222 -0
  219. package/src/components/ScrollFog/ScrollFog.tsx +172 -0
  220. package/src/components/ScrollFog/index.ts +1 -0
  221. package/src/components/Select/Select.namespace.ts +34 -0
  222. package/src/components/Select/Select.test.tsx +297 -0
  223. package/src/components/Select/Select.tsx +1483 -0
  224. package/src/components/Select/index.ts +39 -0
  225. package/src/components/Skeleton/Skeleton.test.tsx +64 -0
  226. package/src/components/Skeleton/Skeleton.tsx +45 -0
  227. package/src/components/Skeleton/index.ts +1 -0
  228. package/src/components/Slider/Slider.namespace.ts +26 -0
  229. package/src/components/Slider/Slider.test.tsx +260 -0
  230. package/src/components/Slider/Slider.tsx +897 -0
  231. package/src/components/Slider/Slider.utils.ts +167 -0
  232. package/src/components/Slider/index.ts +30 -0
  233. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +21 -1
  234. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +30 -7
  235. package/src/components/Switch/Switch.tsx +14 -13
  236. package/src/components/Tabs/Tabs.test.tsx +218 -1
  237. package/src/components/Tabs/Tabs.tsx +346 -75
  238. package/src/components/Tabs/Tabs.utils.ts +51 -1
  239. package/src/components/Tabs/index.ts +17 -1
  240. package/src/components/index.ts +14 -0
  241. package/src/hooks/useControllableState.ts +1 -43
  242. package/src/hooks/usePressTap.test.ts +23 -11
  243. package/src/hooks/usePressTap.ts +1 -60
  244. package/src/hooks/useScaleFeedback.ts +1 -1
  245. package/src/types.ts +1 -0
@@ -0,0 +1,1001 @@
1
+ import * as React from "@lynx-js/react";
2
+ import type { IntrinsicElements, NodesRef } from "@lynx-js/types";
3
+ import {
4
+ attachmentInput,
5
+ type AttachmentInputVariantProps,
6
+ } from "@seed-design/lynx-css/recipes/attachment-input";
7
+ import {
8
+ attachmentInputItem,
9
+ type AttachmentInputItemVariantProps,
10
+ } from "@seed-design/lynx-css/recipes/attachment-input-item";
11
+ import {
12
+ attachmentInputTrigger,
13
+ type AttachmentInputTriggerVariantProps,
14
+ } from "@seed-design/lynx-css/recipes/attachment-input-trigger";
15
+ import type {
16
+ LynxAccessibilityProps,
17
+ LynxIconElementProps,
18
+ LynxPressableProps,
19
+ LynxStyledElementProps,
20
+ LynxTextProps,
21
+ } from "../../types";
22
+ import { useControllableState } from "../../hooks/useControllableState";
23
+ import { usePressTap } from "../../hooks/usePressTap";
24
+ import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
25
+ import { mergeProps } from "../../utils/merge-props";
26
+ import { toArray } from "../../utils/children";
27
+ import clsx from "clsx";
28
+ import { useFieldContext } from "../Field/context";
29
+ import { IconSlotProvider, InternalIcon } from "../Icon/Icon";
30
+
31
+ export interface NativeFile {
32
+ uri: string;
33
+ name: string;
34
+ type: string;
35
+ size: number;
36
+ previewUrl?: string;
37
+ }
38
+
39
+ export type AttachmentFileError =
40
+ | "FILE_TOO_LARGE"
41
+ | "FILE_TOO_SMALL"
42
+ | "TOO_MANY_FILES"
43
+ | "INVALID_TYPE"
44
+ | (string & {});
45
+
46
+ export interface AttachmentFileRejection {
47
+ file: NativeFile;
48
+ errors: AttachmentFileError[];
49
+ }
50
+
51
+ export type AttachmentFileStatusDetails =
52
+ | { status: "pending" }
53
+ | { status: "uploading"; progress?: number }
54
+ | { status: "success" }
55
+ | { status: "error" };
56
+
57
+ export type AttachmentFileEntry = {
58
+ id: string;
59
+ file: NativeFile;
60
+ } & AttachmentFileStatusDetails;
61
+
62
+ export interface AttachmentInputStateProps {
63
+ acceptedFileEntries?: AttachmentFileEntry[];
64
+ defaultAcceptedFileEntries?: AttachmentFileEntry[];
65
+ onAcceptedFileEntriesChange?: (entries: AttachmentFileEntry[]) => void;
66
+ onFileReject?: (rejections: AttachmentFileRejection[]) => void;
67
+ onFileAccept?: (
68
+ entries: AttachmentFileEntry[],
69
+ helpers: { updateFileEntryStatus: (id: string, details: AttachmentFileStatusDetails) => void },
70
+ ) => void;
71
+ }
72
+
73
+ export interface AttachmentInputProps extends AttachmentInputStateProps {
74
+ accept?: string | string[];
75
+ disabled?: boolean;
76
+ required?: boolean;
77
+ invalid?: boolean;
78
+ readOnly?: boolean;
79
+ maxFiles?: number;
80
+ maxFileSize?: number;
81
+ minFileSize?: number;
82
+ validate?: (file: NativeFile) => AttachmentFileError[] | null;
83
+ onSelectFiles?: () => NativeFile[] | Promise<NativeFile[]>;
84
+ onSelectError?: (error: unknown) => void;
85
+ }
86
+
87
+ export type AttachmentInputItemContextValue = AttachmentFileEntry & {
88
+ removeButtonProps: LynxPressableProps;
89
+ imageProps?: { src: string; alt?: string };
90
+ };
91
+
92
+ interface AttachmentInputStateDataProps {
93
+ "data-disabled": boolean;
94
+ "data-readonly": boolean;
95
+ "data-invalid": boolean;
96
+ "data-required": boolean;
97
+ }
98
+
99
+ export interface AttachmentInputContextValue extends AttachmentInputProps {
100
+ acceptedFileEntries: AttachmentFileEntry[];
101
+ currentFileEntryCount: number;
102
+ acceptType?: "image";
103
+ multiple: boolean;
104
+ maxFiles: number;
105
+ disabled: boolean;
106
+ required: boolean;
107
+ invalid: boolean;
108
+ readOnly: boolean;
109
+ triggerDisabled: boolean;
110
+ stateProps: AttachmentInputStateDataProps;
111
+ openFilePicker: () => void;
112
+ setFileEntries: (files: NativeFile[]) => void;
113
+ updateFileEntryStatus: (id: string, details: AttachmentFileStatusDetails) => void;
114
+ removeFileEntry: (id: string) => void;
115
+ clearFileEntries: () => void;
116
+ reorderFileEntry: (fromIndex: number, toIndex: number) => void;
117
+ }
118
+
119
+ interface NativeViewProps extends LynxAccessibilityProps {
120
+ id?: string;
121
+ flatten?: boolean;
122
+ }
123
+ function pickNativeViewProps(
124
+ props: LynxStyledElementProps & NativeViewProps,
125
+ ): LynxStyledElementProps & NativeViewProps {
126
+ return {
127
+ id: props.id,
128
+ flatten: props.flatten,
129
+ style: props.style,
130
+ "accessibility-label": props["accessibility-label"],
131
+ "accessibility-traits": props["accessibility-traits"],
132
+ "accessibility-element": props["accessibility-element"],
133
+ "accessibility-value": props["accessibility-value"],
134
+ "accessibility-role-description": props["accessibility-role-description"],
135
+ "accessibility-elements-hidden": props["accessibility-elements-hidden"],
136
+ "accessibility-heading": props["accessibility-heading"],
137
+ "accessibility-actions": props["accessibility-actions"],
138
+ "accessibility-exclusive-focus": props["accessibility-exclusive-focus"],
139
+ "ios-platform-accessibility-id": props["ios-platform-accessibility-id"],
140
+ };
141
+ }
142
+ type NativeTextElementProps = Omit<LynxTextProps, "children" | "className" | "style">;
143
+ type NativeImageProps = Omit<
144
+ IntrinsicElements["image"],
145
+ "children" | "className" | "style" | "src"
146
+ >;
147
+ type NativeScrollViewProps = Omit<
148
+ IntrinsicElements["scroll-view"],
149
+ "children" | "className" | "style"
150
+ >;
151
+
152
+ const rootRecipe = createSlotRecipeContext(attachmentInput);
153
+ const triggerRecipe = createSlotRecipeContext(attachmentInputTrigger);
154
+ const itemRecipe = createSlotRecipeContext(attachmentInputItem);
155
+
156
+ const AttachmentInputContextObject = React.createContext<AttachmentInputContextValue | null>(null);
157
+ const AttachmentInputItemContextObject =
158
+ React.createContext<AttachmentInputItemContextValue | null>(null);
159
+
160
+ export function useAttachmentInputContext(): AttachmentInputContextValue {
161
+ const context = React.useContext(AttachmentInputContextObject);
162
+ if (!context) {
163
+ throw new Error("AttachmentInput components must be used within AttachmentInput.Root");
164
+ }
165
+ return context;
166
+ }
167
+
168
+ export function useAttachmentInputItemContext(): AttachmentInputItemContextValue {
169
+ const context = React.useContext(AttachmentInputItemContextObject);
170
+ if (!context) {
171
+ throw new Error("AttachmentInput item components must be used within AttachmentInput.Item");
172
+ }
173
+ return context;
174
+ }
175
+
176
+ function isAcceptedType(file: NativeFile, accept?: string | string[]): boolean {
177
+ if (!accept) return true;
178
+
179
+ const patterns = (Array.isArray(accept) ? accept : accept.split(","))
180
+ .map((value) => value.trim().toLowerCase())
181
+ .filter(Boolean);
182
+ const fileName = file.name.toLowerCase();
183
+ const fileType = file.type.toLowerCase();
184
+
185
+ return patterns.some((pattern) => {
186
+ if (pattern.startsWith(".")) return fileName.endsWith(pattern);
187
+ if (pattern.endsWith("/*")) return fileType.startsWith(pattern.slice(0, -1));
188
+ if (pattern.includes("*")) {
189
+ const [type] = pattern.split("/");
190
+ return fileType.startsWith(`${type}/`);
191
+ }
192
+ return fileType === pattern;
193
+ });
194
+ }
195
+
196
+ function isImageAcceptPattern(pattern: string): boolean {
197
+ if (pattern.startsWith("image/")) return true;
198
+ return [".avif", ".bmp", ".gif", ".heic", ".heif", ".jpeg", ".jpg", ".png", ".webp"].some(
199
+ (extension) => pattern === extension,
200
+ );
201
+ }
202
+
203
+ function getAcceptType(accept?: string | string[]): "image" | undefined {
204
+ if (!accept) return undefined;
205
+ const patterns = (Array.isArray(accept) ? accept : accept.split(","))
206
+ .map((value) => value.trim().toLowerCase())
207
+ .filter(Boolean);
208
+ return patterns.length > 0 && patterns.every(isImageAcceptPattern) ? "image" : undefined;
209
+ }
210
+
211
+ function formatFileSize(bytes: number): string {
212
+ if (bytes === 0) return "0 B";
213
+ const units = ["B", "KB", "MB", "GB", "TB"];
214
+ const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
215
+ return `${Number.parseFloat((bytes / 1024 ** index).toFixed(1))} ${units[index]}`;
216
+ }
217
+
218
+ export function useAttachmentInput(props: AttachmentInputProps = {}): AttachmentInputContextValue {
219
+ const {
220
+ acceptedFileEntries: value,
221
+ defaultAcceptedFileEntries = [],
222
+ onAcceptedFileEntriesChange,
223
+ onFileAccept,
224
+ onFileReject,
225
+ accept,
226
+ disabled: disabledProp,
227
+ required: requiredProp,
228
+ invalid: invalidProp,
229
+ readOnly: readOnlyProp,
230
+ maxFiles: maxFilesProp = 1,
231
+ maxFileSize = Number.POSITIVE_INFINITY,
232
+ minFileSize = 0,
233
+ validate,
234
+ onSelectFiles,
235
+ onSelectError,
236
+ } = props;
237
+ const fieldContext = useFieldContext({ strict: false });
238
+ const disabled = disabledProp ?? fieldContext?.disabled ?? false;
239
+ const required = requiredProp ?? fieldContext?.required ?? false;
240
+ const invalid = invalidProp ?? fieldContext?.invalid ?? false;
241
+ const readOnly = readOnlyProp ?? fieldContext?.readOnly ?? false;
242
+ const maxFiles = Math.max(1, maxFilesProp);
243
+ const [acceptedFileEntries, setAcceptedFileEntries] = useControllableState<AttachmentFileEntry[]>(
244
+ {
245
+ value,
246
+ defaultValue: defaultAcceptedFileEntries,
247
+ onChange: onAcceptedFileEntriesChange,
248
+ },
249
+ );
250
+ const entries = acceptedFileEntries ?? [];
251
+ const entriesRef = React.useRef(entries);
252
+ entriesRef.current = entries;
253
+ const optionsRef = React.useRef({
254
+ disabled,
255
+ readOnly,
256
+ accept,
257
+ maxFiles,
258
+ maxFileSize,
259
+ minFileSize,
260
+ validate,
261
+ onSelectFiles,
262
+ onSelectError,
263
+ onFileAccept,
264
+ onFileReject,
265
+ });
266
+ optionsRef.current = {
267
+ disabled,
268
+ readOnly,
269
+ accept,
270
+ maxFiles,
271
+ maxFileSize,
272
+ minFileSize,
273
+ validate,
274
+ onSelectFiles,
275
+ onSelectError,
276
+ onFileAccept,
277
+ onFileReject,
278
+ };
279
+ const idCounterRef = React.useRef(0);
280
+ const pickerRequestRef = React.useRef(0);
281
+ const mountedRef = React.useRef(true);
282
+ React.useEffect(() => {
283
+ return () => {
284
+ mountedRef.current = false;
285
+ pickerRequestRef.current += 1;
286
+ };
287
+ }, []);
288
+
289
+ const multiple = maxFiles > 1;
290
+ const maxFilesReached = entries.length >= maxFiles;
291
+ const triggerDisabled = disabled || readOnly || maxFilesReached;
292
+ const acceptType = getAcceptType(accept);
293
+
294
+ const updateFileEntryStatus = React.useCallback(
295
+ (id: string, details: AttachmentFileStatusDetails) => {
296
+ const next = entriesRef.current.map((entry) =>
297
+ entry.id === id ? { ...entry, ...details } : entry,
298
+ );
299
+ entriesRef.current = next;
300
+ setAcceptedFileEntries(next);
301
+ },
302
+ [setAcceptedFileEntries],
303
+ );
304
+
305
+ const validateFiles = React.useCallback((files: NativeFile[]) => {
306
+ const {
307
+ accept: currentAccept,
308
+ maxFileSize: currentMaxFileSize,
309
+ minFileSize: currentMinFileSize,
310
+ maxFiles: currentMaxFiles,
311
+ validate: currentValidate,
312
+ } = optionsRef.current;
313
+ const accepted: NativeFile[] = [];
314
+ const rejected: AttachmentFileRejection[] = [];
315
+ let addedCount = 0;
316
+
317
+ for (const file of files) {
318
+ const errors: AttachmentFileError[] = [];
319
+ if (entriesRef.current.length + addedCount >= currentMaxFiles) errors.push("TOO_MANY_FILES");
320
+ if (!isAcceptedType(file, currentAccept)) errors.push("INVALID_TYPE");
321
+ if (file.size > currentMaxFileSize) errors.push("FILE_TOO_LARGE");
322
+ if (file.size < currentMinFileSize) errors.push("FILE_TOO_SMALL");
323
+ const customErrors = currentValidate?.(file);
324
+ if (customErrors) errors.push(...customErrors);
325
+
326
+ if (errors.length > 0) {
327
+ rejected.push({ file, errors });
328
+ } else {
329
+ accepted.push(file);
330
+ addedCount += 1;
331
+ }
332
+ }
333
+ return { accepted, rejected };
334
+ }, []);
335
+
336
+ const setFileEntries = React.useCallback(
337
+ (files: NativeFile[]) => {
338
+ const currentOptions = optionsRef.current;
339
+ if (currentOptions.disabled || currentOptions.readOnly) return;
340
+
341
+ const { accepted, rejected } = validateFiles(files);
342
+ const acceptedEntries: AttachmentFileEntry[] = accepted.map((file) => ({
343
+ id: `attachment-${Date.now()}-${++idCounterRef.current}`,
344
+ file,
345
+ status: "pending",
346
+ }));
347
+
348
+ if (acceptedEntries.length > 0) {
349
+ const acceptedForCallback =
350
+ currentOptions.maxFiles > 1 ? acceptedEntries : [acceptedEntries[0]];
351
+ const nextEntries =
352
+ currentOptions.maxFiles > 1
353
+ ? [...entriesRef.current, ...acceptedEntries]
354
+ : acceptedForCallback;
355
+ entriesRef.current = nextEntries;
356
+ setAcceptedFileEntries(nextEntries);
357
+ currentOptions.onFileAccept?.(acceptedForCallback, { updateFileEntryStatus });
358
+ }
359
+ if (rejected.length > 0) currentOptions.onFileReject?.(rejected);
360
+ },
361
+ [setAcceptedFileEntries, updateFileEntryStatus, validateFiles],
362
+ );
363
+
364
+ const openFilePicker = React.useCallback(() => {
365
+ const currentOptions = optionsRef.current;
366
+ if (
367
+ currentOptions.disabled ||
368
+ currentOptions.readOnly ||
369
+ entriesRef.current.length >= currentOptions.maxFiles
370
+ ) {
371
+ return;
372
+ }
373
+ const picker = currentOptions.onSelectFiles;
374
+ if (!picker) return;
375
+
376
+ const requestId = ++pickerRequestRef.current;
377
+ let result: NativeFile[] | Promise<NativeFile[]>;
378
+ try {
379
+ result = picker();
380
+ } catch (error) {
381
+ currentOptions.onSelectError?.(error);
382
+ return;
383
+ }
384
+
385
+ Promise.resolve(result)
386
+ .then((files) => {
387
+ if (!mountedRef.current || requestId !== pickerRequestRef.current) return;
388
+ if (!Array.isArray(files) || files.length === 0) return;
389
+ if (optionsRef.current.disabled || optionsRef.current.readOnly) return;
390
+ setFileEntries(files);
391
+ })
392
+ .catch((error) => {
393
+ if (mountedRef.current && requestId === pickerRequestRef.current) {
394
+ optionsRef.current.onSelectError?.(error);
395
+ }
396
+ });
397
+ }, [setFileEntries]);
398
+
399
+ const removeFileEntry = React.useCallback(
400
+ (id: string) => {
401
+ if (optionsRef.current.readOnly) return;
402
+ const next = entriesRef.current.filter((entry) => entry.id !== id);
403
+ entriesRef.current = next;
404
+ setAcceptedFileEntries(next);
405
+ },
406
+ [setAcceptedFileEntries],
407
+ );
408
+
409
+ const clearFileEntries = React.useCallback(() => {
410
+ if (optionsRef.current.readOnly) return;
411
+ entriesRef.current = [];
412
+ setAcceptedFileEntries([]);
413
+ }, [setAcceptedFileEntries]);
414
+
415
+ const reorderFileEntry = React.useCallback(
416
+ (fromIndex: number, toIndex: number) => {
417
+ if (optionsRef.current.disabled || optionsRef.current.readOnly) return;
418
+ const currentEntries = entriesRef.current;
419
+ if (
420
+ fromIndex < 0 ||
421
+ toIndex < 0 ||
422
+ fromIndex >= currentEntries.length ||
423
+ toIndex >= currentEntries.length ||
424
+ fromIndex === toIndex
425
+ ) {
426
+ return;
427
+ }
428
+ const next = [...currentEntries];
429
+ const [entry] = next.splice(fromIndex, 1);
430
+ next.splice(toIndex, 0, entry);
431
+ entriesRef.current = next;
432
+ setAcceptedFileEntries(next);
433
+ },
434
+ [setAcceptedFileEntries],
435
+ );
436
+
437
+ const stateProps = {
438
+ "data-disabled": triggerDisabled,
439
+ "data-readonly": readOnly,
440
+ "data-invalid": invalid,
441
+ "data-required": required,
442
+ };
443
+
444
+ return {
445
+ ...props,
446
+ acceptedFileEntries: entries,
447
+ currentFileEntryCount: entries.length,
448
+ acceptType,
449
+ multiple,
450
+ maxFiles,
451
+ disabled,
452
+ required,
453
+ invalid,
454
+ readOnly,
455
+ triggerDisabled,
456
+ stateProps,
457
+ openFilePicker,
458
+ setFileEntries,
459
+ updateFileEntryStatus,
460
+ removeFileEntry,
461
+ clearFileEntries,
462
+ reorderFileEntry,
463
+ };
464
+ }
465
+
466
+ export interface AttachmentInputRootProps
467
+ extends AttachmentInputProps,
468
+ AttachmentInputVariantProps,
469
+ LynxStyledElementProps,
470
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
471
+
472
+ export const AttachmentInputRoot = React.forwardRef<NodesRef, AttachmentInputRootProps>(
473
+ (props, forwardedRef) => {
474
+ const [variantProps, otherProps] = attachmentInput.splitVariantProps(props);
475
+ const {
476
+ children,
477
+ className,
478
+ acceptedFileEntries,
479
+ defaultAcceptedFileEntries,
480
+ onAcceptedFileEntriesChange,
481
+ onFileReject,
482
+ onFileAccept,
483
+ accept,
484
+ disabled,
485
+ required,
486
+ invalid,
487
+ readOnly,
488
+ maxFiles,
489
+ maxFileSize,
490
+ minFileSize,
491
+ validate,
492
+ onSelectFiles,
493
+ onSelectError,
494
+ } = otherProps;
495
+ const api = useAttachmentInput({
496
+ acceptedFileEntries,
497
+ defaultAcceptedFileEntries,
498
+ onAcceptedFileEntriesChange,
499
+ onFileReject,
500
+ onFileAccept,
501
+ accept,
502
+ disabled,
503
+ required,
504
+ invalid,
505
+ readOnly,
506
+ maxFiles,
507
+ maxFileSize,
508
+ minFileSize,
509
+ validate,
510
+ onSelectFiles,
511
+ onSelectError,
512
+ });
513
+ const classes = attachmentInput(variantProps);
514
+
515
+ return (
516
+ <AttachmentInputContextObject.Provider value={api}>
517
+ <rootRecipe.ClassNamesProvider value={classes}>
518
+ <view
519
+ {...mergeProps(
520
+ forwardedRef ? { ref: forwardedRef } : {},
521
+ api.stateProps,
522
+ pickNativeViewProps(props),
523
+ )}
524
+ className={clsx(classes.root, className)}
525
+ >
526
+ {children}
527
+ </view>
528
+ </rootRecipe.ClassNamesProvider>
529
+ </AttachmentInputContextObject.Provider>
530
+ );
531
+ },
532
+ );
533
+ AttachmentInputRoot.displayName = "AttachmentInputRoot";
534
+
535
+ export interface AttachmentInputContainerProps
536
+ extends LynxStyledElementProps,
537
+ Omit<NativeScrollViewProps, "scroll-orientation" | "scroll-bar-enable"> {}
538
+
539
+ export const AttachmentInputContainer = React.forwardRef<NodesRef, AttachmentInputContainerProps>(
540
+ ({ children, className, ...nativeProps }, forwardedRef) => {
541
+ const classes = rootRecipe.useClassNames();
542
+ return (
543
+ <scroll-view
544
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
545
+ scroll-orientation="horizontal"
546
+ scroll-bar-enable={false}
547
+ className={clsx(classes.container, className)}
548
+ >
549
+ <view className={classes.containerContent}>{children}</view>
550
+ </scroll-view>
551
+ );
552
+ },
553
+ );
554
+ AttachmentInputContainer.displayName = "AttachmentInputContainer";
555
+
556
+ export interface AttachmentInputItemGroupProps
557
+ extends LynxStyledElementProps,
558
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
559
+
560
+ export const AttachmentInputItemGroup = React.forwardRef<NodesRef, AttachmentInputItemGroupProps>(
561
+ ({ children, className, ...nativeProps }, forwardedRef) => {
562
+ const classes = rootRecipe.useClassNames();
563
+ return (
564
+ <view
565
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
566
+ className={clsx(classes.itemGroup, className)}
567
+ >
568
+ {children}
569
+ </view>
570
+ );
571
+ },
572
+ );
573
+ AttachmentInputItemGroup.displayName = "AttachmentInputItemGroup";
574
+
575
+ export interface AttachmentInputTriggerProps
576
+ extends AttachmentInputTriggerVariantProps,
577
+ LynxStyledElementProps,
578
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap">,
579
+ LynxPressableProps,
580
+ LynxAccessibilityProps {}
581
+
582
+ export const AttachmentInputTrigger = React.forwardRef<NodesRef, AttachmentInputTriggerProps>(
583
+ (props, forwardedRef) => {
584
+ const [variantProps, restProps] = attachmentInputTrigger.splitVariantProps(props);
585
+ const {
586
+ children,
587
+ className,
588
+ bindtap,
589
+ "main-thread:bindtap": mainThreadBindtap,
590
+ "accessibility-element": accessibilityElement = true,
591
+ "accessibility-traits": accessibilityTraits,
592
+ ...nativeProps
593
+ } = restProps;
594
+ const context = useAttachmentInputContext();
595
+ const press = usePressTap({
596
+ disabled: context.triggerDisabled,
597
+ onTap: (event) => {
598
+ context.openFilePicker();
599
+ bindtap?.(event);
600
+ },
601
+ mainThreadOnTap: mainThreadBindtap,
602
+ });
603
+ const classes = attachmentInputTrigger({
604
+ ...variantProps,
605
+ pressed: press.pressed,
606
+ disabled: context.triggerDisabled,
607
+ });
608
+
609
+ return (
610
+ <triggerRecipe.ClassNamesProvider value={classes}>
611
+ <view
612
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, press, nativeProps)}
613
+ accessibility-element={accessibilityElement}
614
+ accessibility-traits={
615
+ accessibilityTraits ?? (context.triggerDisabled ? "disabled" : "button")
616
+ }
617
+ className={clsx(classes.root, className)}
618
+ >
619
+ {children}
620
+ </view>
621
+ </triggerRecipe.ClassNamesProvider>
622
+ );
623
+ },
624
+ );
625
+ AttachmentInputTrigger.displayName = "AttachmentInputTrigger";
626
+
627
+ export interface AttachmentInputTriggerIconProps extends LynxStyledElementProps {
628
+ image?: React.ReactNode;
629
+ general?: React.ReactNode;
630
+ }
631
+
632
+ export const AttachmentInputTriggerIcon = React.forwardRef<
633
+ NodesRef,
634
+ AttachmentInputTriggerIconProps
635
+ >(({ image, general, children, className, ...nativeProps }, forwardedRef) => {
636
+ const context = useAttachmentInputContext();
637
+ const classes = attachmentInputTrigger({ pressed: false, disabled: context.triggerDisabled });
638
+ const icon = context.acceptType === "image" ? (image ?? children) : (general ?? children);
639
+ if (!React.isValidElement<LynxIconElementProps>(icon)) return null;
640
+
641
+ return (
642
+ <triggerRecipe.ClassNamesProvider value={classes}>
643
+ <InternalIcon
644
+ icon={icon}
645
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
646
+ className={clsx(classes.icon, className)}
647
+ />
648
+ </triggerRecipe.ClassNamesProvider>
649
+ );
650
+ });
651
+ AttachmentInputTriggerIcon.displayName = "AttachmentInputTriggerIcon";
652
+
653
+ export interface AttachmentInputTriggerItemCountProps
654
+ extends LynxStyledElementProps,
655
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
656
+
657
+ export const AttachmentInputTriggerItemCount = React.forwardRef<
658
+ NodesRef,
659
+ AttachmentInputTriggerItemCountProps
660
+ >(({ children, className, ...nativeProps }, forwardedRef) => {
661
+ const context = useAttachmentInputContext();
662
+ const classes = attachmentInputTrigger({ pressed: false, disabled: context.triggerDisabled });
663
+ return (
664
+ <triggerRecipe.ClassNamesProvider value={classes}>
665
+ <view
666
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
667
+ className={clsx(classes.itemCountArea, className)}
668
+ >
669
+ <text className={classes.itemCount}>{context.currentFileEntryCount}</text>
670
+ <text className={classes.maxItemCount}>/{context.maxFiles}</text>
671
+ {children}
672
+ </view>
673
+ </triggerRecipe.ClassNamesProvider>
674
+ );
675
+ });
676
+ AttachmentInputTriggerItemCount.displayName = "AttachmentInputTriggerItemCount";
677
+
678
+ export interface AttachmentInputItemProps
679
+ extends AttachmentInputItemVariantProps,
680
+ LynxStyledElementProps,
681
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {
682
+ fileEntry: AttachmentFileEntry;
683
+ }
684
+
685
+ export const AttachmentInputItem = React.forwardRef<NodesRef, AttachmentInputItemProps>(
686
+ ({ fileEntry, children, className, ...props }, forwardedRef) => {
687
+ const root = useAttachmentInputContext();
688
+ const [variantProps] = attachmentInputItem.splitVariantProps(props);
689
+ const type = variantProps.type ?? root.acceptType ?? "general";
690
+ const classes = attachmentInputItem({
691
+ ...variantProps,
692
+ type,
693
+ disabled: root.disabled,
694
+ readOnly: root.readOnly,
695
+ pressed: false,
696
+ dragging: variantProps.dragging ?? false,
697
+ });
698
+ const imageSource = fileEntry.file.previewUrl ?? fileEntry.file.uri;
699
+ const itemContext: AttachmentInputItemContextValue = {
700
+ ...fileEntry,
701
+ imageProps:
702
+ type === "image" && imageSource
703
+ ? { src: imageSource, alt: fileEntry.file.name }
704
+ : undefined,
705
+ removeButtonProps: {
706
+ bindtap: () => {
707
+ if (!root.readOnly) root.removeFileEntry(fileEntry.id);
708
+ },
709
+ },
710
+ };
711
+
712
+ return (
713
+ <AttachmentInputItemContextObject.Provider value={itemContext}>
714
+ <itemRecipe.ClassNamesProvider value={classes}>
715
+ <view
716
+ {...mergeProps(
717
+ forwardedRef ? { ref: forwardedRef } : {},
718
+ root.stateProps,
719
+ pickNativeViewProps(props),
720
+ )}
721
+ className={clsx(classes.root, className)}
722
+ >
723
+ {children}
724
+ </view>
725
+ </itemRecipe.ClassNamesProvider>
726
+ </AttachmentInputItemContextObject.Provider>
727
+ );
728
+ },
729
+ );
730
+ AttachmentInputItem.displayName = "AttachmentInputItem";
731
+
732
+ export interface AttachmentInputItemNameProps
733
+ extends LynxStyledElementProps,
734
+ Omit<NativeTextElementProps, "bindtap" | "main-thread:bindtap"> {}
735
+
736
+ export const AttachmentInputItemName = React.forwardRef<NodesRef, AttachmentInputItemNameProps>(
737
+ ({ children, className, ...nativeProps }, forwardedRef) => {
738
+ const item = useAttachmentInputItemContext();
739
+ const classes = itemRecipe.useClassNames();
740
+ return (
741
+ <text
742
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
743
+ text-maxline="1"
744
+ className={clsx(classes.name, className)}
745
+ >
746
+ {children ?? item.file.name}
747
+ </text>
748
+ );
749
+ },
750
+ );
751
+ AttachmentInputItemName.displayName = "AttachmentInputItemName";
752
+
753
+ export interface AttachmentInputItemSizeProps
754
+ extends LynxStyledElementProps,
755
+ Omit<NativeTextElementProps, "bindtap" | "main-thread:bindtap"> {
756
+ formatBytes?: (bytes: number) => string;
757
+ }
758
+
759
+ export const AttachmentInputItemSize = React.forwardRef<NodesRef, AttachmentInputItemSizeProps>(
760
+ ({ children, className, formatBytes = formatFileSize, ...nativeProps }, forwardedRef) => {
761
+ const item = useAttachmentInputItemContext();
762
+ const classes = itemRecipe.useClassNames();
763
+ return (
764
+ <text
765
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
766
+ className={clsx(classes.size, className)}
767
+ >
768
+ {children ?? formatBytes(item.file.size)}
769
+ </text>
770
+ );
771
+ },
772
+ );
773
+ AttachmentInputItemSize.displayName = "AttachmentInputItemSize";
774
+
775
+ export const AttachmentInputItemSurface = React.forwardRef<
776
+ NodesRef,
777
+ AttachmentInputItemSurfaceProps
778
+ >(({ children, className, ...nativeProps }, forwardedRef) => {
779
+ const classes = itemRecipe.useClassNames();
780
+ return (
781
+ <view
782
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
783
+ className={clsx(classes.surface, className)}
784
+ >
785
+ {children}
786
+ </view>
787
+ );
788
+ });
789
+ export interface AttachmentInputItemSurfaceProps
790
+ extends LynxStyledElementProps,
791
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
792
+ AttachmentInputItemSurface.displayName = "AttachmentInputItemSurface";
793
+
794
+ export interface AttachmentInputItemImageProps extends LynxStyledElementProps, NativeImageProps {}
795
+
796
+ export const AttachmentInputItemImage = React.forwardRef<NodesRef, AttachmentInputItemImageProps>(
797
+ ({ className, "accessibility-label": accessibilityLabel, ...nativeProps }, forwardedRef) => {
798
+ const item = useAttachmentInputItemContext();
799
+ const classes = itemRecipe.useClassNames();
800
+ if (!item.imageProps) return null;
801
+ return (
802
+ <image
803
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
804
+ src={item.imageProps.src}
805
+ accessibility-label={accessibilityLabel ?? item.imageProps.alt}
806
+ className={clsx(classes.image, className)}
807
+ />
808
+ );
809
+ },
810
+ );
811
+ AttachmentInputItemImage.displayName = "AttachmentInputItemImage";
812
+
813
+ export interface AttachmentInputItemThumbnailProps
814
+ extends LynxStyledElementProps,
815
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
816
+
817
+ export const AttachmentInputItemThumbnail = React.forwardRef<
818
+ NodesRef,
819
+ AttachmentInputItemThumbnailProps
820
+ >(({ children, className, ...nativeProps }, forwardedRef) => {
821
+ const classes = itemRecipe.useClassNames();
822
+ return (
823
+ <view
824
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
825
+ className={clsx(classes.thumbnail, className)}
826
+ >
827
+ <IconSlotProvider value={{ classNames: { icon: classes.thumbnailIcon } }}>
828
+ {children}
829
+ </IconSlotProvider>
830
+ </view>
831
+ );
832
+ });
833
+ AttachmentInputItemThumbnail.displayName = "AttachmentInputItemThumbnail";
834
+
835
+ export interface AttachmentInputItemMetadataProps
836
+ extends LynxStyledElementProps,
837
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
838
+
839
+ export const AttachmentInputItemMetadata = React.forwardRef<
840
+ NodesRef,
841
+ AttachmentInputItemMetadataProps
842
+ >(({ children, className, ...nativeProps }, forwardedRef) => {
843
+ const classes = itemRecipe.useClassNames();
844
+ return (
845
+ <view
846
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
847
+ className={clsx(classes.metadata, className)}
848
+ >
849
+ {children}
850
+ </view>
851
+ );
852
+ });
853
+ AttachmentInputItemMetadata.displayName = "AttachmentInputItemMetadata";
854
+
855
+ export interface AttachmentInputItemBadgeProps
856
+ extends LynxStyledElementProps,
857
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap"> {}
858
+
859
+ export const AttachmentInputItemBadge = React.forwardRef<NodesRef, AttachmentInputItemBadgeProps>(
860
+ ({ children, className, ...nativeProps }, forwardedRef) => {
861
+ const classes = itemRecipe.useClassNames();
862
+ return (
863
+ <view
864
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
865
+ className={clsx(classes.badge, className)}
866
+ >
867
+ <text className={classes.badgeLabel}>{children}</text>
868
+ </view>
869
+ );
870
+ },
871
+ );
872
+ AttachmentInputItemBadge.displayName = "AttachmentInputItemBadge";
873
+
874
+ export interface AttachmentInputItemActionButtonProps
875
+ extends LynxStyledElementProps,
876
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap">,
877
+ LynxPressableProps {}
878
+
879
+ export const AttachmentInputItemActionButton = React.forwardRef<
880
+ NodesRef,
881
+ AttachmentInputItemActionButtonProps
882
+ >(({ children, className, bindtap, ...nativeProps }, forwardedRef) => {
883
+ const classes = itemRecipe.useClassNames();
884
+ const actionChildren = toArray(children).map((child: React.ReactNode) => {
885
+ if (typeof child === "string" || typeof child === "number") {
886
+ return <text className={classes.actionLabel}>{child}</text>;
887
+ }
888
+ return child;
889
+ });
890
+
891
+ return (
892
+ <view
893
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps, { bindtap })}
894
+ className={clsx(classes.actionButton, className)}
895
+ >
896
+ <IconSlotProvider value={{ classNames: { icon: classes.actionIcon } }}>
897
+ {actionChildren}
898
+ </IconSlotProvider>
899
+ </view>
900
+ );
901
+ });
902
+ AttachmentInputItemActionButton.displayName = "AttachmentInputItemActionButton";
903
+
904
+ export interface AttachmentInputItemBackdropProps
905
+ extends Omit<LynxStyledElementProps, "children">,
906
+ Omit<NativeViewProps, "children" | "bindtap" | "main-thread:bindtap"> {
907
+ status: AttachmentFileEntry["status"];
908
+ children?: React.ReactNode | ((entry: AttachmentFileEntry) => React.ReactNode);
909
+ }
910
+
911
+ export const AttachmentInputItemBackdrop = React.forwardRef<
912
+ NodesRef,
913
+ AttachmentInputItemBackdropProps
914
+ >(({ status, children, className, ...nativeProps }, forwardedRef) => {
915
+ const entry = useAttachmentInputItemContext();
916
+ const classes = itemRecipe.useClassNames();
917
+ if (entry.status !== status) return null;
918
+ return (
919
+ <view
920
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, nativeProps)}
921
+ className={clsx(classes.backdrop, className)}
922
+ >
923
+ {typeof children === "function" ? children(entry) : children}
924
+ </view>
925
+ );
926
+ });
927
+ AttachmentInputItemBackdrop.displayName = "AttachmentInputItemBackdrop";
928
+
929
+ export interface AttachmentInputItemRemoveButtonProps
930
+ extends LynxStyledElementProps,
931
+ Omit<NativeViewProps, "bindtap" | "main-thread:bindtap">,
932
+ LynxPressableProps,
933
+ LynxAccessibilityProps {}
934
+
935
+ export const AttachmentInputItemRemoveButton = React.forwardRef<
936
+ NodesRef,
937
+ AttachmentInputItemRemoveButtonProps
938
+ >((props, forwardedRef) => {
939
+ const {
940
+ children,
941
+ className,
942
+ bindtap,
943
+ "main-thread:bindtap": mainThreadBindtap,
944
+ "accessibility-element": accessibilityElement = true,
945
+ "accessibility-traits": accessibilityTraits,
946
+ ...nativeProps
947
+ } = props;
948
+ const item = useAttachmentInputItemContext();
949
+ const context = useAttachmentInputContext();
950
+ const press = usePressTap({
951
+ disabled: context.readOnly,
952
+ onTap: (event) => {
953
+ context.removeFileEntry(item.id);
954
+ bindtap?.(event);
955
+ },
956
+ mainThreadOnTap: mainThreadBindtap,
957
+ });
958
+ const classes = itemRecipe.useClassNames();
959
+
960
+ return (
961
+ <view
962
+ {...mergeProps(forwardedRef ? { ref: forwardedRef } : {}, press, nativeProps)}
963
+ accessibility-element={accessibilityElement}
964
+ accessibility-traits={accessibilityTraits ?? (context.readOnly ? "disabled" : "button")}
965
+ className={clsx(classes.removeButton, className)}
966
+ >
967
+ <IconSlotProvider value={{ classNames: { icon: classes.removeIcon } }}>
968
+ {children}
969
+ </IconSlotProvider>
970
+ </view>
971
+ );
972
+ });
973
+ AttachmentInputItemRemoveButton.displayName = "AttachmentInputItemRemoveButton";
974
+
975
+ export interface AttachmentInputContextProps {
976
+ children: (context: AttachmentInputContextValue) => React.ReactNode;
977
+ }
978
+
979
+ export const AttachmentInputContext = ({ children }: AttachmentInputContextProps) =>
980
+ children(useAttachmentInputContext());
981
+
982
+ export const AttachmentInput = {
983
+ Root: AttachmentInputRoot,
984
+ Container: AttachmentInputContainer,
985
+ ItemGroup: AttachmentInputItemGroup,
986
+ Trigger: AttachmentInputTrigger,
987
+ TriggerIcon: AttachmentInputTriggerIcon,
988
+ TriggerItemCount: AttachmentInputTriggerItemCount,
989
+ Item: AttachmentInputItem,
990
+ ItemName: AttachmentInputItemName,
991
+ ItemSize: AttachmentInputItemSize,
992
+ ItemImage: AttachmentInputItemImage,
993
+ ItemThumbnail: AttachmentInputItemThumbnail,
994
+ ItemMetadata: AttachmentInputItemMetadata,
995
+ ItemBadge: AttachmentInputItemBadge,
996
+ ItemActionButton: AttachmentInputItemActionButton,
997
+ ItemBackdrop: AttachmentInputItemBackdrop,
998
+ ItemRemoveButton: AttachmentInputItemRemoveButton,
999
+ ItemSurface: AttachmentInputItemSurface,
1000
+ Context: AttachmentInputContext,
1001
+ };