@lotics/ui 5.0.0 → 5.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.
package/AGENTS.md CHANGED
@@ -201,7 +201,12 @@ callbacks: `onFilePress` → set a `number|null` index that drives `<FileGallery
201
201
  activeIndex onIndexChange>` (built-in prev/next/ESC/rotate); `onRemove` → drop the file from state
202
202
  (the grid renders a ✕ on each thumbnail automatically). Keep label+grid+dropzone on a `gap` (a bare
203
203
  `CardBody` has none). `FileThumbnail` shows the right surface per MIME (image thumbnail · doc card ·
204
- media card). Never hand-build a drop well, a file row grid, or a preview modal.
204
+ media card), and takes `uploading` to dim it with a spinner while the bytes are still going up.
205
+ Never hand-build a drop well, a file row grid, or a preview modal. In a custom-code APP, don't
206
+ hand-roll the upload+preview state either — `@lotics/app-sdk` `useAttachments()` gives the chat's
207
+ instant-preview lifecycle (local object-URL now, `file_id` on complete); map each `AttachedFile` to a
208
+ `DisplayFile` (snake → camel: `preview_url`→`url`, `mime_type`→`mimeType`) for
209
+ `<FileThumbnail file={…} uploading={f.status === "uploading"} />`.
205
210
 
206
211
  ### Quick capture — the SPEED surface
207
212
  For fast repeat entry (logging activity, expenses), one compact capture row (a `SegmentedControl`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "5.0.0",
3
+ "version": "5.1.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -3,6 +3,7 @@ import { fontFamilySemiBold } from "./text_utils";
3
3
  import { Text } from "./text";
4
4
  import { Icon, type IconName } from "./icon";
5
5
  import { Checkbox } from "./checkbox";
6
+ import { ActivityIndicator } from "./activity_indicator";
6
7
  import { colors } from "./colors";
7
8
  import { useCallback, useState } from "react";
8
9
  import {
@@ -66,6 +67,9 @@ interface FileThumbnailProps {
66
67
  onRemove?: () => void;
67
68
  /** When defined, renders a selection overlay with a checkbox. */
68
69
  selected?: boolean;
70
+ /** While true, dims the thumbnail with a spinner — the file is still uploading
71
+ * (the local preview shows immediately; this marks it as in-flight). */
72
+ uploading?: boolean;
69
73
  }
70
74
 
71
75
  /**
@@ -73,7 +77,7 @@ interface FileThumbnailProps {
73
77
  * Works with any file source via DisplayFile interface.
74
78
  */
75
79
  export function FileThumbnail(props: FileThumbnailProps) {
76
- const { file, size, onPress, onLongPress, onRemove, selected } = props;
80
+ const { file, size, onPress, onLongPress, onRemove, selected, uploading } = props;
77
81
 
78
82
  const rootStyle =
79
83
  size !== undefined ? { width: size, height: size } : { width: "100%" as const, aspectRatio: 1 };
@@ -82,6 +86,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
82
86
  return (
83
87
  <View style={rootStyle}>
84
88
  <ImageThumbnail file={file} size={size} onPress={onPress} onLongPress={onLongPress} />
89
+ {uploading && <UploadingOverlay size={size} />}
85
90
  {onRemove && <RemoveButton onPress={onRemove} />}
86
91
  {selected !== undefined && <SelectionOverlay selected={selected} />}
87
92
  </View>
@@ -99,6 +104,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
99
104
  onPress={onPress ?? (() => Linking.openURL(file.url))}
100
105
  onLongPress={onLongPress}
101
106
  />
107
+ {uploading && <UploadingOverlay size={size} />}
102
108
  {onRemove && <RemoveButton onPress={onRemove} />}
103
109
  </View>
104
110
  );
@@ -124,12 +130,37 @@ export function FileThumbnail(props: FileThumbnailProps) {
124
130
  onLongPress={onLongPress}
125
131
  />
126
132
  )}
133
+ {uploading && <UploadingOverlay size={size} />}
127
134
  {onRemove && <RemoveButton onPress={onRemove} />}
128
135
  {selected !== undefined && <SelectionOverlay selected={selected} />}
129
136
  </View>
130
137
  );
131
138
  }
132
139
 
140
+ /** A dim + spinner overlay shown while a file is still uploading. `pointerEvents`
141
+ * none so the RemoveButton beneath it stays clickable (cancel an in-flight upload). */
142
+ function UploadingOverlay({ size }: { size?: number }) {
143
+ return (
144
+ <View style={uploadingOverlayStyles.overlay} pointerEvents="none">
145
+ <ActivityIndicator size={size !== undefined && size <= COMPACT_THUMBNAIL_SIZE ? 12 : 18} color={colors.white} />
146
+ </View>
147
+ );
148
+ }
149
+
150
+ const uploadingOverlayStyles = StyleSheet.create({
151
+ overlay: {
152
+ position: "absolute",
153
+ top: 0,
154
+ left: 0,
155
+ right: 0,
156
+ bottom: 0,
157
+ alignItems: "center",
158
+ justifyContent: "center",
159
+ backgroundColor: "rgba(24,24,27,0.42)",
160
+ borderRadius: 8,
161
+ },
162
+ });
163
+
133
164
  // =============================================================================
134
165
  // Base Components (exported for direct use when needed)
135
166
  // =============================================================================