@fibery/ui-kit 1.39.1 → 1.40.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 (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +7 -7
  3. package/src/appIcons.json +10 -0
  4. package/src/box.tsx +2 -2
  5. package/src/button/base-button.tsx +18 -3
  6. package/src/button/button.tsx +14 -6
  7. package/src/button/icon-button.tsx +4 -1
  8. package/src/button/select-button.tsx +11 -3
  9. package/src/collapsible/index.tsx +2 -2
  10. package/src/command-menu/index.tsx +4 -2
  11. package/src/copy-to-clipboard.ts +10 -0
  12. package/src/dot.tsx +36 -0
  13. package/src/file-item-2.tsx +223 -133
  14. package/src/file-item.tsx +2 -0
  15. package/src/guide-link.tsx +1 -1
  16. package/src/html-styles.ts +13 -27
  17. package/src/icons/ast/FileImage.ts +1 -1
  18. package/src/icons/ast/Mermaid.ts +8 -0
  19. package/src/icons/ast/QuickFilter.ts +8 -0
  20. package/src/icons/ast/QuickFilterPersonal.ts +8 -0
  21. package/src/icons/ast/TextNoWrap.ts +8 -0
  22. package/src/icons/ast/TextWrap.ts +8 -0
  23. package/src/icons/ast/TypeId.ts +8 -0
  24. package/src/icons/ast/index.tsx +6 -0
  25. package/src/icons/react/Mermaid.tsx +13 -0
  26. package/src/icons/react/QuickFilter.tsx +13 -0
  27. package/src/icons/react/QuickFilterPersonal.tsx +13 -0
  28. package/src/icons/react/TextNoWrap.tsx +13 -0
  29. package/src/icons/react/TextWrap.tsx +13 -0
  30. package/src/icons/react/TypeId.tsx +13 -0
  31. package/src/icons/react/index.tsx +6 -0
  32. package/src/icons/svg/file-image.svg +2 -2
  33. package/src/icons/svg/mermaid.svg +3 -0
  34. package/src/icons/svg/quick-filter-personal.svg +3 -0
  35. package/src/icons/svg/quick-filter.svg +3 -0
  36. package/src/icons/svg/text-no-wrap.svg +3 -0
  37. package/src/icons/svg/text-wrap.svg +3 -0
  38. package/src/icons/svg/type/id.svg +3 -0
  39. package/src/images-gallery/get-gallery-src.ts +71 -0
  40. package/src/images-gallery/images-gallery-fall-through-provider.tsx +2 -1
  41. package/src/images-gallery/images-gallery.tsx +524 -185
  42. package/src/{images-gallery-2 → images-gallery}/slide-buttons.tsx +25 -13
  43. package/src/images-gallery/thumbnails-mime-types-white-list.ts +17 -0
  44. package/src/images-gallery/zoom.tsx +5 -94
  45. package/src/notification-dot.tsx +11 -21
  46. package/src/popover/index.tsx +8 -4
  47. package/src/select/components/menu-list-virtualized.tsx +123 -84
  48. package/src/select/components/menu.tsx +6 -3
  49. package/src/select/index.tsx +95 -259
  50. package/src/select/select-in-popover.tsx +11 -3
  51. package/src/select/select.tsx +288 -0
  52. package/src/table.tsx +13 -10
  53. package/src/toggle-button/toggle-button.tsx +21 -4
  54. package/src/toggle-on-off.tsx +10 -1
  55. package/src/images-gallery-2/images-gallery-2.tsx +0 -522
  56. package/src/images-gallery-2/zoom.tsx +0 -35
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable max-lines */
2
2
  import {css, cx} from "@linaria/core";
3
- import {useCallback, useEffect, useState} from "react";
3
+ import {CSSProperties, useCallback, useEffect, useState} from "react";
4
4
  import {ActionsMenu, ActionsMenuItem, ActionsMenuSeparator} from "./actions-menu";
5
5
  import {ActionsButtonCompact} from "./button/actions-button-compact";
6
6
  import {border, colors, space, themeVars, fontSize, fontWeight, lineHeight} from "./design-system";
@@ -17,11 +17,12 @@ import Link from "./icons/react/Link";
17
17
  import RicheditorOpenLink from "./icons/react/RicheditorOpenLink";
18
18
  import Spinner from "./icons/react/Spinner";
19
19
  import AppWiki from "./icons/react/AppWiki";
20
- import {copyToClipboard} from "./copy-to-clipboard";
20
+ import {copyUrlToClipboard} from "./copy-to-clipboard";
21
21
  import {useToast} from "./toast/toast-provider";
22
22
  import {Icon} from "./icons/Icon";
23
23
  import {useImagesGalleryMethods, useImagesGalleryRegistry} from "./images-gallery/images-gallery";
24
24
  import {isPhoneApp} from "./use-is-phone";
25
+ import Pencil from "./icons/react/Pencil";
25
26
 
26
27
  const fileItemStyle = css`
27
28
  display: block;
@@ -30,13 +31,20 @@ const fileItemStyle = css`
30
31
  background-size: cover;
31
32
  text-decoration: none;
32
33
  border-radius: ${space.s8}px;
33
- border: 1px solid ${themeVars.separatorColor};
34
34
  background-clip: padding-box;
35
35
  &:focus {
36
36
  text-decoration: none;
37
37
  }
38
38
  `;
39
39
 
40
+ const thumbnailStyle = css`
41
+ border: 1px solid ${themeVars.separatorColor};
42
+ `;
43
+
44
+ const iconOnlyStyle = css`
45
+ border: 1px solid ${themeVars.colorBorderButtonOutlineNeutralDefault};
46
+ `;
47
+
40
48
  const overlayStyle = css`
41
49
  width: 100%;
42
50
  height: 100%;
@@ -64,7 +72,15 @@ const fileOverlayStyle = css`
64
72
  background: transparent;
65
73
  `;
66
74
 
75
+ const actionsButtonStyle = css`
76
+ position: absolute;
77
+ top: ${space.s8}px;
78
+ right: ${space.s8}px;
79
+ opacity: 0;
80
+ `;
81
+
67
82
  export type FileItemType = {
83
+ uid: string;
68
84
  name: string;
69
85
  url: string;
70
86
  downloadUrl?: string | null;
@@ -82,14 +98,18 @@ export type FileItemProps = {
82
98
  data: FileItemType;
83
99
  onDownloadClick?: () => void;
84
100
  onRemoveClick?: (x: FileItemType) => void;
101
+ onRenameClick?: (x: {id: string; name: string}) => void;
85
102
  disabled?: boolean;
86
103
  };
87
104
 
105
+ export const minFileTileWidth = 120;
106
+
88
107
  export function FileItem(props: FileItemProps) {
89
108
  const {onDownloadClick, onRemoveClick} = props;
90
109
  const {name, thumbnailUrl, status, previewData: preview, contentType} = props.data;
91
110
  const isPending = status === "uploading" || status === "deleting";
92
- const isBackgroundReady = Boolean(thumbnailUrl && !isPending);
111
+ const [isThumbnailLoadFailed, setIsThumbnailLoadFailed] = useState(false);
112
+ const isBackgroundReady = !isThumbnailLoadFailed && Boolean(thumbnailUrl && !isPending);
93
113
  const [isActionsOpened, setIsActionsOpened] = useState(false);
94
114
 
95
115
  const showFileIcon = !isPending && !isBackgroundReady;
@@ -106,6 +126,7 @@ export function FileItem(props: FileItemProps) {
106
126
  imagesRegistry.set(preview.originalSrc, {
107
127
  type: preview.type,
108
128
  src: preview.originalSrc,
129
+ label: name,
109
130
  zoomed: false,
110
131
  setZoomed,
111
132
  actions: <FilePreviewActions url={preview.originalSrc} name={name} onDownloadClick={onDownloadClick} />,
@@ -114,7 +135,7 @@ export function FileItem(props: FileItemProps) {
114
135
 
115
136
  useEffect(() => {
116
137
  if (!preview) {
117
- return;
138
+ return undefined;
118
139
  }
119
140
 
120
141
  if (zoomed) {
@@ -127,11 +148,16 @@ export function FileItem(props: FileItemProps) {
127
148
  imagesRegistry.set(preview.originalSrc, {
128
149
  type: preview.type,
129
150
  src: preview.originalSrc,
151
+ label: name,
130
152
  zoomed,
131
153
  setZoomed,
132
154
  actions: <FilePreviewActions url={preview.originalSrc} name={name} onDownloadClick={onDownloadClick} />,
133
155
  });
134
156
  }
157
+
158
+ return () => {
159
+ imagesRegistry.delete(preview.originalSrc);
160
+ };
135
161
  }, [galleryMethods, zoomed, preview, imagesRegistry, name, onDownloadClick]);
136
162
 
137
163
  const onContainerClick = useCallback(() => {
@@ -143,63 +169,70 @@ export function FileItem(props: FileItemProps) {
143
169
  }, [preview, onDownloadClick]);
144
170
 
145
171
  return (
146
- <div
147
- className={cx(
148
- css`
149
- flex: 1 0 0;
150
- height: 100%;
151
- `,
152
- isBackgroundReady &&
153
- css`
154
- &:hover .${overlayStyle} {
155
- opacity: 1;
156
- background-position: 0 0;
157
- color: ${themeVars.whiteColor};
158
- background-color: rgba(0, 0, 0, 0.6);
159
- }
160
- `,
161
- isBackgroundReady &&
162
- isActionsOpened &&
163
- css`
164
- .${overlayStyle} {
165
- opacity: 1;
166
- background-position: 0 0;
167
- color: ${themeVars.whiteColor};
168
- background-color: rgba(0, 0, 0, 0.6);
169
- }
170
- `
171
- )}
172
- >
172
+ <>
173
173
  <div
174
- style={isBackgroundReady ? {backgroundImage: `url(${thumbnailUrl})`} : {}}
175
- onClick={onContainerClick}
176
174
  className={cx(
177
175
  css`
178
- flex-shrink: 0;
176
+ flex: 1 0 0;
177
+ height: 100%;
178
+ min-width: ${minFileTileWidth}px;
179
179
  `,
180
- fileItemStyle,
181
- isPending &&
180
+ isBackgroundReady &&
181
+ css`
182
+ &:hover .${overlayStyle} {
183
+ opacity: 1;
184
+ background-position: 0 0;
185
+ color: ${themeVars.whiteColor};
186
+ background-color: rgba(0, 0, 0, 0.6);
187
+ }
188
+ `,
189
+ isBackgroundReady &&
190
+ isActionsOpened &&
182
191
  css`
183
- ${{backgroundColor: themeVars.entityCardSelectedColor}}
192
+ .${overlayStyle} {
193
+ opacity: 1;
194
+ background-position: 0 0;
195
+ color: ${themeVars.whiteColor};
196
+ background-color: rgba(0, 0, 0, 0.6);
197
+ }
198
+ `,
199
+ !isBackgroundReady &&
200
+ css`
201
+ &:hover .${overlayStyle} {
202
+ background-color: ${themeVars.colorBgMenuItemHover};
203
+ }
204
+ &:hover .${actionsButtonStyle} {
205
+ opacity: 1;
206
+ }
207
+ `,
208
+ !isBackgroundReady &&
209
+ isActionsOpened &&
210
+ css`
211
+ .${overlayStyle} {
212
+ background-color: ${themeVars.colorBgMenuItemHover};
213
+ }
214
+ .${actionsButtonStyle} {
215
+ opacity: 1;
216
+ }
184
217
  `
185
218
  )}
186
219
  >
187
- {isPending && (
188
- <div
189
- className={css`
190
- display: flex;
191
- flex-direction: column;
192
- justify-content: center; /* centers vertically */
193
- align-items: center; /* centers horizontally */
194
- height: 100%;
195
- width: 100%;
196
- `}
197
- >
198
- <Spinner />
199
- </div>
200
- )}
201
- {showFileIcon && (
202
- <div className={cx(overlayStyle, fileOverlayStyle)}>
220
+ <div
221
+ style={isBackgroundReady ? {backgroundImage: `url(${thumbnailUrl})`} : {}}
222
+ onClick={onContainerClick}
223
+ className={cx(
224
+ css`
225
+ flex-shrink: 0;
226
+ `,
227
+ fileItemStyle,
228
+ showFileIcon ? iconOnlyStyle : thumbnailStyle,
229
+ isPending &&
230
+ css`
231
+ ${{backgroundColor: themeVars.entityCardSelectedColor}}
232
+ `
233
+ )}
234
+ >
235
+ {isPending && (
203
236
  <div
204
237
  className={css`
205
238
  display: flex;
@@ -208,101 +241,119 @@ export function FileItem(props: FileItemProps) {
208
241
  align-items: center; /* centers horizontally */
209
242
  height: 100%;
210
243
  width: 100%;
211
- padding-top: ${space.s1}px;
212
- gap: ${space.s2}px;
213
244
  `}
214
245
  >
215
- <Icon containerSize={40} iconSize={40}>
216
- <FileIcon contentType={contentType} />
217
- </Icon>
246
+ <Spinner />
247
+ </div>
248
+ )}
249
+ {showFileIcon && (
250
+ <div className={cx(overlayStyle, fileOverlayStyle)}>
218
251
  <div
219
252
  className={css`
220
- align-content: center;
221
-
222
253
  display: flex;
223
- align-items: center;
224
- gap: 10px;
225
- flex-shrink: 0;
226
- color: ${themeVars.colorTextMenuItem};
227
- text-overflow: ellipsis;
228
- overflow: hidden;
254
+ flex-direction: column;
255
+ justify-content: center; /* centers vertically */
256
+ align-items: center; /* centers horizontally */
257
+ height: 100%;
258
+ width: 100%;
259
+ padding-top: ${space.s1}px;
260
+ gap: ${space.s2}px;
229
261
  `}
230
262
  >
231
- <FileTitle
232
- title={name}
263
+ <Icon containerSize={40} iconSize={40}>
264
+ <FileIcon contentType={contentType} />
265
+ </Icon>
266
+ <div
233
267
  className={css`
234
- -webkit-line-clamp: 1;
235
- -webkit-box-orient: vertical;
236
- overflow: hidden;
268
+ display: flex;
269
+ width: 100%;
270
+ justify-content: center;
271
+ gap: 10px;
272
+ flex-shrink: 0;
273
+ color: ${themeVars.colorTextMenuItem};
237
274
  text-overflow: ellipsis;
275
+ overflow: hidden;
238
276
  `}
239
- />
277
+ >
278
+ <FileTitle
279
+ title={name}
280
+ className={css`
281
+ overflow: hidden;
282
+ text-overflow: ellipsis;
283
+ `}
284
+ />
285
+ </div>
240
286
  </div>
241
- </div>
242
- <div
243
- className={css`
244
- position: absolute;
245
- top: ${space.s8}px;
246
- right: ${space.s8}px;
247
- `}
248
- >
249
- <FileActionsMenu
250
- setIsActionsOpened={setIsActionsOpened}
251
- data={props.data}
252
- disabled={props.disabled}
253
- isBackgroundReady={isBackgroundReady}
254
- onDownloadClick={onDownloadClick}
255
- onRemoveClick={onRemoveClick}
256
- />
257
- </div>
258
- </div>
259
- )}
260
- {!isPending && !showFileIcon && (
261
- <div className={cx(overlayStyle, isBackgroundReady ? imageOverlayStyle : fileOverlayStyle)}>
262
- <div
263
- className={css`
264
- position: relative;
265
- display: flex;
266
- flex-direction: column;
267
- justify-content: space-between; /* Top + bottom placement */
268
- width: 100%;
269
- height: 100%;
270
- overflow: hidden;
271
- box-sizing: border-box;
272
- gap: ${space.s8}px;
273
- `}
274
- >
275
- <div
276
- className={css`
277
- align-self: flex-end;
278
- `}
279
- >
287
+ <div className={actionsButtonStyle}>
280
288
  <FileActionsMenu
281
289
  setIsActionsOpened={setIsActionsOpened}
282
290
  data={props.data}
283
291
  disabled={props.disabled}
284
- isBackgroundReady={isBackgroundReady}
285
292
  onDownloadClick={onDownloadClick}
286
- onRemoveClick={props.onRemoveClick}
293
+ onRemoveClick={onRemoveClick}
294
+ onRenameClick={props.onRenameClick}
287
295
  />
288
296
  </div>
297
+ </div>
298
+ )}
299
+ {!isPending && !showFileIcon && (
300
+ <div className={cx(overlayStyle, isBackgroundReady ? imageOverlayStyle : fileOverlayStyle)}>
289
301
  <div
290
302
  className={css`
303
+ position: relative;
291
304
  display: flex;
292
- align-self: flex-start;
293
- align-items: flex-end;
294
- text-overflow: ellipsis;
295
- //overflow: hidden;
305
+ flex-direction: column;
306
+ justify-content: space-between; /* Top + bottom placement */
307
+ width: 100%;
308
+ height: 100%;
309
+ overflow: hidden;
296
310
  box-sizing: border-box;
311
+ gap: ${space.s8}px;
297
312
  `}
298
313
  >
299
- <FileTitle title={name} />
314
+ <div
315
+ className={css`
316
+ align-self: flex-end;
317
+ `}
318
+ >
319
+ <FileActionsMenu
320
+ setIsActionsOpened={setIsActionsOpened}
321
+ data={props.data}
322
+ disabled={props.disabled}
323
+ actionsButtonStyle={
324
+ isBackgroundReady ? makeButtonColors(colors.inversedTextColor, "ghost") : undefined
325
+ }
326
+ onDownloadClick={onDownloadClick}
327
+ onRemoveClick={props.onRemoveClick}
328
+ onRenameClick={props.onRenameClick}
329
+ />
330
+ </div>
331
+ <div
332
+ className={css`
333
+ display: flex;
334
+ width: 100%;
335
+ align-self: flex-start;
336
+ align-items: flex-end;
337
+ text-overflow: ellipsis;
338
+ box-sizing: border-box;
339
+ `}
340
+ >
341
+ <FileTitle title={name} />
342
+ </div>
300
343
  </div>
301
344
  </div>
302
- </div>
303
- )}
345
+ )}
346
+ </div>
347
+ {thumbnailUrl ? (
348
+ <img
349
+ style={{display: "none"}}
350
+ onError={() => setIsThumbnailLoadFailed(true)}
351
+ src={thumbnailUrl}
352
+ alt="invisible"
353
+ />
354
+ ) : null}
304
355
  </div>
305
- </div>
356
+ </>
306
357
  );
307
358
  }
308
359
 
@@ -312,14 +363,16 @@ function FileActionsMenu({
312
363
  setIsActionsOpened,
313
364
  onDownloadClick,
314
365
  onRemoveClick,
315
- isBackgroundReady,
366
+ onRenameClick,
367
+ actionsButtonStyle,
316
368
  }: {
317
369
  data: FileItemType;
318
370
  onDownloadClick?: () => void;
319
371
  onRemoveClick?: (data: FileItemType) => void;
372
+ onRenameClick?: (data: {id: string; name: string}) => void;
320
373
  disabled?: boolean;
321
374
  setIsActionsOpened: (val: boolean) => void;
322
- isBackgroundReady: boolean;
375
+ actionsButtonStyle?: CSSProperties;
323
376
  }) {
324
377
  const toast = useToast();
325
378
 
@@ -331,14 +384,14 @@ function FileActionsMenu({
331
384
  className={css`
332
385
  z-index: 1;
333
386
  `}
334
- style={isBackgroundReady ? makeButtonColors(colors.inversedTextColor, "ghost") : undefined}
387
+ style={actionsButtonStyle}
335
388
  />
336
389
  }
337
390
  >
338
391
  <ActionsMenuItem
339
392
  Icon={Link}
340
393
  onSelect={() => {
341
- copyToClipboard(`${window.origin}${data.url}`)
394
+ copyUrlToClipboard({url: `${window.origin}${data.url}`, label: data.name})
342
395
  .then(() => toast.success({title: "Link copied to clipboard"}))
343
396
  .catch((error) => toast.error({title: "Unable to copy link to clipboard", subTitle: error.message}));
344
397
  }}
@@ -359,6 +412,14 @@ function FileActionsMenu({
359
412
  <ActionsMenuItem Icon={FileDownload}>Download</ActionsMenuItem>
360
413
  </a>
361
414
  )}
415
+ {onRenameClick && data.status === "done" ? (
416
+ <>
417
+ <ActionsMenuSeparator />
418
+ <ActionsMenuItem Icon={Pencil} onSelect={() => onRenameClick?.({id: data.uid, name: data.name})}>
419
+ Rename
420
+ </ActionsMenuItem>
421
+ </>
422
+ ) : null}
362
423
  <ActionsMenuSeparator />
363
424
  <ActionsMenuItem Icon={Delete} onSelect={() => onRemoveClick?.(data)} dangerous disabled={disabled}>
364
425
  Delete
@@ -368,6 +429,10 @@ function FileActionsMenu({
368
429
  }
369
430
 
370
431
  function FileTitle({title, className}: {title: string; className?: string}) {
432
+ const parts = title.split(".");
433
+ const ext = parts.length > 1 ? "." + parts[parts.length - 1] : "";
434
+ const name = title.substring(0, title.length - ext.length);
435
+
371
436
  return (
372
437
  <div
373
438
  className={cx(
@@ -379,16 +444,12 @@ function FileTitle({title, className}: {title: string; className?: string}) {
379
444
  vertical-align: top;
380
445
  text-align: center;
381
446
  justify-content: center;
382
- word-break: break-word;
383
447
  overflow: hidden;
384
448
  font-size: ${fontSize.mini}px;
385
449
  font-weight: ${fontWeight.medium};
386
- line-height: ${lineHeight.narrow};
450
+ line-height: ${lineHeight.heading};
387
451
  text-transform: none;
388
452
  text-overflow: ellipsis;
389
- -webkit-line-clamp: 1;
390
- -webkit-box-orient: vertical;
391
-
392
453
  &:hover {
393
454
  text-decoration: none;
394
455
  color: inherit;
@@ -397,7 +458,33 @@ function FileTitle({title, className}: {title: string; className?: string}) {
397
458
  className
398
459
  )}
399
460
  >
400
- {title}
461
+ <div
462
+ className={css`
463
+ display: flex;
464
+ min-width: 0;
465
+ align-items: center;
466
+ max-width: 180px;
467
+ `}
468
+ >
469
+ <span
470
+ className={css`
471
+ white-space: nowrap;
472
+ overflow: hidden;
473
+ text-overflow: ellipsis;
474
+ /* ellipsis shows ONLY if this span overflows */
475
+ min-width: 0;
476
+ `}
477
+ >
478
+ {name}
479
+ </span>
480
+ <span
481
+ className={css`
482
+ flex: 0 0 auto;
483
+ `}
484
+ >
485
+ {ext}
486
+ </span>
487
+ </div>
401
488
  </div>
402
489
  );
403
490
  }
@@ -428,7 +515,10 @@ function FilePreviewActions({url, name, onDownloadClick}: {url: string; name: st
428
515
  );
429
516
  }
430
517
 
431
- function FileIcon({contentType}: {contentType: string}) {
518
+ function FileIcon({contentType}: {contentType?: string}) {
519
+ if (!contentType) {
520
+ return <AppWiki />;
521
+ }
432
522
  if (contentType.startsWith("image/")) {
433
523
  return <FileImage />;
434
524
  }
package/src/file-item.tsx CHANGED
@@ -111,6 +111,7 @@ export function FileItem(props: FileItemProps) {
111
111
  imagesRegistry.set(preview.originalSrc, {
112
112
  type: preview.type,
113
113
  src: preview.originalSrc,
114
+ label: name,
114
115
  contentType: preview.contentType,
115
116
  zoomed: false,
116
117
  setZoomed,
@@ -136,6 +137,7 @@ export function FileItem(props: FileItemProps) {
136
137
  imagesRegistry.set(preview.originalSrc, {
137
138
  type: preview.type,
138
139
  src: preview.originalSrc,
140
+ label: name,
139
141
  contentType: preview.contentType,
140
142
  zoomed,
141
143
  setZoomed,
@@ -12,7 +12,7 @@ export const GuideLink: React.FC<GuideLinkProps> = ({href, children, size = "sma
12
12
  return (
13
13
  <a href={href} rel="noreferrer" target={"_blank"}>
14
14
  {children ? (
15
- <Button iconStart={<HintIcon />} variant={variant} color="neutral" size={size}>
15
+ <Button iconStart={<HintIcon />} variant={variant} color="neutral" size={size} supportMobile>
16
16
  {children}
17
17
  </Button>
18
18
  ) : (