@lotics/ui 18.1.0 → 18.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "18.1.0",
3
+ "version": "18.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./vite": {
package/src/file_grid.tsx CHANGED
@@ -59,6 +59,10 @@ export interface FileGridProps {
59
59
  * e.g. open the gallery at the first hidden file. */
60
60
  onOverflowPress?: (hiddenCount: number) => void;
61
61
  style?: ViewStyle;
62
+ /** Render every tile INERT — no press, no `button` role, no tab stop. Use it
63
+ * when something ABOVE the grid owns the press (a table cell, a popover
64
+ * trigger, a pressable row): a tile that keeps its button nests one button
65
+ * inside another. */
62
66
  disablePress?: boolean;
63
67
  partialRowAlign?: "start" | "end";
64
68
  selectedIds?: ReadonlySet<string>;
@@ -162,6 +166,7 @@ export function FileGrid(props: FileGridProps) {
162
166
  file={item.file}
163
167
  size={size}
164
168
  onPress={disablePress ? undefined : () => handleFilePress(item.file)}
169
+ disablePress={disablePress}
165
170
  onRemove={onDisplayRemove ? () => onDisplayRemove(item.removeId) : undefined}
166
171
  selected={selectedIds?.has(item.file.id)}
167
172
  />
@@ -66,6 +66,12 @@ interface FileThumbnailProps {
66
66
  size?: number;
67
67
  /** Called when thumbnail is pressed */
68
68
  onPress?: () => void;
69
+ /** Render the tile INERT — no press handler, no `button` role, no tab stop.
70
+ * Without this an `onPress`-less tile still falls back to opening the file,
71
+ * so it stays a `<button>`: dropping a strip into a pressable row (a table
72
+ * cell, a `PressableRow`) then nests a button in a button. Set it whenever
73
+ * something ABOVE the tile owns the press. */
74
+ disablePress?: boolean;
69
75
  /** Called when thumbnail is long-pressed */
70
76
  onLongPress?: () => void;
71
77
  /** Called when the completed file is removed */
@@ -87,8 +93,15 @@ interface FileThumbnailProps {
87
93
  * Works with any file source via DisplayFile interface.
88
94
  */
89
95
  export function FileThumbnail(props: FileThumbnailProps) {
90
- const { file, size, onPress, onLongPress, onRemove, selected, uploading, isTemplate } = props;
96
+ const { file, size, onPress, onLongPress, onRemove, selected, uploading, isTemplate, disablePress } = props;
91
97
  const accessibilityLabel = props.accessibilityLabel ?? file.filename;
98
+ // A tile with no `onPress` still OPENS the file — a useful default for a bare
99
+ // thumbnail in a list or a gallery. But a caller that said `disablePress`
100
+ // means it: inventing a handler there renders a <button>, and the one place a
101
+ // compact strip belongs is a table cell, which is itself pressable. Nesting a
102
+ // button in a button is invalid HTML and two tab stops for one action.
103
+ const press = disablePress ? undefined : onPress;
104
+ const pressOrOpen = disablePress ? undefined : (onPress ?? (() => Linking.openURL(file.url)));
92
105
 
93
106
  const rootStyle =
94
107
  size !== undefined ? { width: size, height: size } : { width: "100%" as const, aspectRatio: 1 };
@@ -96,7 +109,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
96
109
  if (isImageMimeType(file.mimeType)) {
97
110
  return (
98
111
  <View style={rootStyle}>
99
- <ImageThumbnail file={file} size={size} accessibilityLabel={accessibilityLabel} onPress={onPress} onLongPress={onLongPress} />
112
+ <ImageThumbnail file={file} size={size} accessibilityLabel={accessibilityLabel} onPress={press} onLongPress={onLongPress} />
100
113
  {uploading && <UploadingOverlay size={size} />}
101
114
  {onRemove && <RemoveButton onPress={onRemove} />}
102
115
  {selected !== undefined && <SelectionOverlay selected={selected} />}
@@ -114,7 +127,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
114
127
  size={size}
115
128
  isTemplate={isTemplate}
116
129
  accessibilityLabel={accessibilityLabel}
117
- onPress={onPress ?? (() => Linking.openURL(file.url))}
130
+ onPress={pressOrOpen}
118
131
  onLongPress={onLongPress}
119
132
  />
120
133
  {uploading && <UploadingOverlay size={size} />}
@@ -132,7 +145,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
132
145
  icon={mediaIcon}
133
146
  size={size}
134
147
  accessibilityLabel={accessibilityLabel}
135
- onPress={onPress ?? (() => Linking.openURL(file.url))}
148
+ onPress={pressOrOpen}
136
149
  onLongPress={onLongPress}
137
150
  />
138
151
  ) : (
@@ -142,7 +155,7 @@ export function FileThumbnail(props: FileThumbnailProps) {
142
155
  size={size}
143
156
  isTemplate={isTemplate}
144
157
  accessibilityLabel={accessibilityLabel}
145
- onPress={onPress ?? (() => Linking.openURL(file.url))}
158
+ onPress={pressOrOpen}
146
159
  onLongPress={onLongPress}
147
160
  />
148
161
  )}
@@ -206,7 +219,8 @@ export function DocumentBadge(props: DocumentBadgeProps) {
206
219
  <Pressable
207
220
  onPress={onPress}
208
221
  onLongPress={onLongPress}
209
- accessibilityRole="button"
222
+ accessibilityRole={onPress ? "button" : undefined}
223
+ tabIndex={onPress ? undefined : -1}
210
224
  accessibilityLabel={accessibilityLabel ?? resolveMime(mimeType).label}
211
225
  {...focusProps}
212
226
  style={focusVisible ? { boxShadow: FOCUS_RING, borderRadius: 6 } : undefined}
@@ -262,7 +276,8 @@ export function DocumentCard(props: DocumentCardProps) {
262
276
  ]}
263
277
  onPress={onPress}
264
278
  onLongPress={onLongPress}
265
- accessibilityRole="button"
279
+ accessibilityRole={onPress ? "button" : undefined}
280
+ tabIndex={onPress ? undefined : -1}
266
281
  accessibilityLabel={a11yLabel}
267
282
  {...focusProps}
268
283
  >
@@ -313,7 +328,8 @@ export function MediaCard(props: MediaCardProps) {
313
328
  style={({ pressed }) => [styles.mediaCardCompact, sizeStyle, { backgroundColor: color }, pressed && styles.pressedDim, focusVisible && { boxShadow: FOCUS_RING }]}
314
329
  onPress={onPress}
315
330
  onLongPress={onLongPress}
316
- accessibilityRole="button"
331
+ accessibilityRole={onPress ? "button" : undefined}
332
+ tabIndex={onPress ? undefined : -1}
317
333
  accessibilityLabel={a11yLabel}
318
334
  {...focusProps}
319
335
  >
@@ -327,7 +343,8 @@ export function MediaCard(props: MediaCardProps) {
327
343
  const backdrop = iconSize + 20;
328
344
 
329
345
  return (
330
- <Pressable style={[styles.documentCard, sizeStyle, focusVisible && { boxShadow: FOCUS_RING }]} onPress={onPress} onLongPress={onLongPress} accessibilityRole="button" accessibilityLabel={a11yLabel} {...focusProps}>
346
+ <Pressable style={[styles.documentCard, sizeStyle, focusVisible && { boxShadow: FOCUS_RING }]} onPress={onPress} onLongPress={onLongPress} accessibilityRole={onPress ? "button" : undefined}
347
+ tabIndex={onPress ? undefined : -1} accessibilityLabel={a11yLabel} {...focusProps}>
331
348
  {(state) => (
332
349
  <>
333
350
  <View style={[styles.mediaCardBody, { backgroundColor: color }]}>
@@ -381,7 +398,7 @@ function ImageThumbnail(props: ImageThumbnailProps) {
381
398
  : { width: "100%" as const, height: "100%" as const };
382
399
 
383
400
  return (
384
- <Pressable onPress={onPress} onLongPress={onLongPress} accessibilityRole={onPress ? "button" : "image"} accessibilityLabel={a11yLabel} {...focusProps} style={[styles.imageThumbnail, sizeStyle, focusVisible && { boxShadow: FOCUS_RING }]}>
401
+ <Pressable onPress={onPress} onLongPress={onLongPress} accessibilityRole={onPress ? "button" : "image"} tabIndex={onPress ? undefined : -1} accessibilityLabel={a11yLabel} {...focusProps} style={[styles.imageThumbnail, sizeStyle, focusVisible && { boxShadow: FOCUS_RING }]}>
385
402
  {(state) => (
386
403
  <>
387
404
  <Image
@@ -188,6 +188,10 @@ export function FileThumbnailGrid(props: FileThumbnailGridProps) {
188
188
  file={file}
189
189
  size={size}
190
190
  onPress={disablePress || !onFilePress ? undefined : () => onFilePress(file)}
191
+ // Withholding `onPress` is NOT enough: a tile with none falls back to
192
+ // opening the file, so it stays a button and a tab stop. The prop has
193
+ // to reach the tile that honors it.
194
+ disablePress={disablePress}
191
195
  onRemove={onRemove ? () => onRemove(file.id) : undefined}
192
196
  selected={selectedIds?.has(file.id)}
193
197
  />