@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,522 +0,0 @@
1
- /* eslint-disable max-lines */
2
- import PhotoSwipeLightbox, {type PhotoSwipeEventsMap} from "photoswipe/lightbox";
3
- import "photoswipe/style.css";
4
- import {
5
- ImagesGalleryMethodsProvider,
6
- ImagesGalleryRegistryProvider,
7
- ImagesGalleryStateProvider,
8
- } from "../images-gallery/images-gallery";
9
- import {PropsWithChildren, useCallback, useEffect, useMemo, useReducer, useRef, useState} from "react";
10
- import {css} from "@linaria/core";
11
- import {createRoot} from "react-dom/client";
12
- import {space, themeVars} from "../design-system";
13
- import {safeAreaInsetLeftVar, safeAreaInsetRightVar, safeAreaInsetTopVar} from "../mobile-styles";
14
- import {useToast} from "../toast/toast-provider";
15
- import {ToastQueue} from "../toast/toast-queue";
16
- import {useThemeMode} from "../theme-provider";
17
- import {ThemeMode} from "../theme-settings";
18
- import {SlideButtons} from "./slide-buttons";
19
- import {useIsPhone} from "../use-is-phone";
20
-
21
- type Registry = Map<
22
- string,
23
- {
24
- src: string;
25
- type: "image" | "video" | "iframe";
26
- contentType?: string;
27
- actions?: React.ReactNode;
28
- zoomed: boolean;
29
- setZoomed: (zoomed: boolean) => void;
30
- }
31
- >;
32
-
33
- type Methods = {
34
- setVisible: (visible: boolean) => void;
35
- setActivated: (activated: boolean) => void;
36
- update: () => void;
37
- nextImage: () => void;
38
- prevImage: () => void;
39
- };
40
-
41
- const galleryMainClass = css`
42
- z-index: 1000;
43
- & .pswp__button--arrow--prev {
44
- left: calc(${space.s20}px + var(${safeAreaInsetLeftVar}));
45
- height: ${space.s28}px;
46
- width: ${space.s28}px;
47
- margin-top: -${space.s14}px;
48
- }
49
- & .pswp__button--arrow--next {
50
- right: calc(${space.s20}px + var(${safeAreaInsetRightVar}));
51
- height: ${space.s28}px;
52
- width: ${space.s28}px;
53
- margin-top: -${space.s14}px;
54
- }
55
-
56
- & .pswp__button--zoom {
57
- top: ${space.s20}px;
58
- height: ${space.s28}px;
59
- width: ${space.s28}px;
60
- }
61
-
62
- & .pswp__counter {
63
- margin-top: ${space.s20}px;
64
- margin-inline-start: ${space.s24}px;
65
- }
66
-
67
- & .pswp__video-container {
68
- padding: ${space.s60}px;
69
- }
70
-
71
- & .pswp__video-container video {
72
- width: 100%;
73
- height: 100%;
74
- object-fit: contain;
75
- display: block;
76
- background-color: black;
77
- }
78
-
79
- & .pswp__iframe-container {
80
- padding: ${space.s60}px;
81
- display: flex;
82
- justify-content: center;
83
- align-items: center;
84
- }
85
-
86
- & .pswp__iframe-container iframe {
87
- width: 100%;
88
- height: 100%;
89
- object-fit: contain;
90
- display: block;
91
- color: ${themeVars.textColor};
92
- background-color: ${themeVars.colorBgPopup};
93
- border: none;
94
- }
95
-
96
- & .pswp__top-bar {
97
- top: var(${safeAreaInsetTopVar});
98
- left: var(${safeAreaInsetLeftVar});
99
- }
100
- `;
101
-
102
- export function ImagesGalleryProvider2({children}: PropsWithChildren) {
103
- const [imagesRegistry] = useState<Registry>(() => new Map());
104
- const [galleryState, dispatchState] = useReducer(imagesGalleryReducer, [], initialState);
105
-
106
- const lightbox = useRef<PhotoSwipeLightbox | null>(null);
107
- const markAllNotZoomed = useCallback(() => {
108
- for (const node of imagesRegistry.values()) {
109
- if (node.zoomed) {
110
- node.setZoomed(false);
111
- }
112
- }
113
- }, [imagesRegistry]);
114
-
115
- useEffect(() => {
116
- return () => {
117
- lightbox.current?.destroy();
118
- lightbox.current = null;
119
- markAllNotZoomed();
120
- };
121
- }, [markAllNotZoomed]);
122
-
123
- const methods: Methods = useMemo(() => {
124
- return {
125
- setVisible: () => {},
126
- setActivated: (activated: boolean) => {
127
- dispatchState({type: "set-activated", payload: activated});
128
- },
129
- update: () => {
130
- dispatchState({type: "inc"});
131
- },
132
- nextImage: () => {
133
- lightbox.current?.pswp?.next();
134
- },
135
- prevImage: () => {
136
- lightbox.current?.pswp?.prev();
137
- },
138
- };
139
- }, []);
140
-
141
- const toast = useToast();
142
- const themeMode = useThemeMode();
143
- const isPhone = useIsPhone();
144
-
145
- useEffect(() => {
146
- if (galleryState.activated) {
147
- const value = initGallery(imagesRegistry, {toastQueue: toast, isPhone, themeMode});
148
- value.on("close", () => {
149
- dispatchState({type: "set-activated", payload: false});
150
- markAllNotZoomed();
151
- value.destroy();
152
- });
153
-
154
- lightbox.current = value;
155
- }
156
-
157
- return () => {
158
- lightbox.current?.destroy();
159
- lightbox.current = null;
160
- };
161
- }, [galleryState.activated, imagesRegistry, markAllNotZoomed, toast, themeMode, isPhone]);
162
-
163
- return (
164
- <ImagesGalleryRegistryProvider value={imagesRegistry}>
165
- <ImagesGalleryMethodsProvider value={methods}>
166
- <ImagesGalleryStateProvider value={galleryState}>{children}</ImagesGalleryStateProvider>
167
- </ImagesGalleryMethodsProvider>
168
- </ImagesGalleryRegistryProvider>
169
- );
170
- }
171
-
172
- type State = {version: "1" | "2"; visible: boolean; activated: boolean; _inc: number};
173
- type Actions = {type: "set-visible"; payload: boolean} | {type: "set-activated"; payload: boolean} | {type: "inc"};
174
-
175
- function initialState(): State {
176
- return {version: "2", visible: false, activated: false, _inc: 0};
177
- }
178
-
179
- function imagesGalleryReducer(state: State, action: Actions) {
180
- if (action.type === "set-visible") {
181
- return {...state, visible: action.payload};
182
- }
183
- if (action.type === "set-activated") {
184
- return {...state, activated: action.payload};
185
- }
186
- if (action.type === "inc") {
187
- return {...state, _inc: state._inc + 1};
188
- }
189
- return state;
190
- }
191
-
192
- type GalleryEntry = {
193
- type: string;
194
- src: string;
195
- contentType: string | null;
196
- zoomed: boolean;
197
- setZoomed: (value: boolean) => void;
198
- state: "none" | "loading" | "error" | "loaded";
199
- width?: number;
200
- height?: number;
201
- };
202
-
203
- function initGallery(
204
- imagesRegistry: Registry,
205
- {toastQueue, themeMode, isPhone}: {toastQueue: ToastQueue; themeMode: ThemeMode; isPhone: boolean}
206
- ) {
207
- const items = [...imagesRegistry.values()];
208
- const dataSource: GalleryEntry[] = items.map((item) => ({
209
- ...item,
210
- state: "none",
211
- contentType: item.contentType ?? null,
212
- }));
213
-
214
- const zoomedIndex = dataSource.findIndex((item) => item.zoomed);
215
-
216
- const lightbox = new PhotoSwipeLightbox({
217
- dataSource,
218
- mainClass: galleryMainClass,
219
- pswpModule: () => import("photoswipe"),
220
- preloaderDelay: 1,
221
- arrowPrev: false,
222
- arrowNext: false,
223
- close: false,
224
- zoom: false,
225
- loop: false,
226
- });
227
-
228
- lightbox.addFilter("numItems", (_numItems) => {
229
- return dataSource.length;
230
- });
231
-
232
- lightbox.addFilter("itemData", (_itemData, index) => {
233
- const item = dataSource[index];
234
-
235
- if (item.type === "video" || item.type === "iframe") {
236
- return item;
237
- }
238
-
239
- if (item.state === "loaded" || item.state === "error") {
240
- return item;
241
- }
242
-
243
- return {};
244
- });
245
-
246
- lightbox.addFilter("isContentLoading", (_isContentLoading, content) => {
247
- const item = dataSource[content.index];
248
- if (item.type === "video" || item.type === "iframe") {
249
- return false;
250
- }
251
- const complete = item.state === "loaded" || item.state === "error";
252
- return !complete;
253
- });
254
-
255
- lightbox.addFilter("useContentPlaceholder", () => false);
256
-
257
- lightbox.init();
258
-
259
- lightbox.on("contentActivate", (e) => {
260
- dataSource[e.content.index].setZoomed(true);
261
- lightbox.pswp?.element?.focus();
262
- });
263
-
264
- lightbox.on("contentDeactivate", (e) => {
265
- dataSource[e.content.index].setZoomed(false);
266
- e.content.element?.querySelector("video")?.pause();
267
- });
268
-
269
- function refreshSlide(index: number) {
270
- lightbox.pswp?.refreshSlideContent(index);
271
- if (lightbox.pswp?.currIndex === index) {
272
- lightbox.pswp?.updateSize(true);
273
- }
274
- }
275
-
276
- const contentAppendTimeouts = [] as ReturnType<typeof setTimeout>[];
277
- lightbox.on("contentAppend", (e) => {
278
- // sometimes lightbox's slide is loaded with stale (empty) data
279
- if (e.content.data !== dataSource[e.content.index]) {
280
- contentAppendTimeouts.push(
281
- setTimeout(() => {
282
- refreshSlide(e.content.index);
283
- }, 0)
284
- );
285
- }
286
- });
287
-
288
- lightbox.on("contentRemove", (e) => {
289
- const item = dataSource[e.content.index];
290
-
291
- if (item.type === "video" || item.type === "iframe") {
292
- item.state = "none";
293
- }
294
- });
295
-
296
- lightbox.on("contentLoad", (e) => {
297
- const item = dataSource[e.content.index];
298
-
299
- if (item.state === "loading") {
300
- return;
301
- }
302
-
303
- if (item.state === "none") {
304
- e.preventDefault();
305
- const eventArgs = {
306
- e,
307
- item,
308
- refreshSlide,
309
- getPswp: () => lightbox.pswp,
310
- isPhone,
311
- };
312
-
313
- switch (item.type) {
314
- case "image": {
315
- onImageContentLoad(eventArgs);
316
- break;
317
- }
318
- case "video": {
319
- onVideoContentLoad(eventArgs);
320
- break;
321
- }
322
- case "iframe": {
323
- onIframeContentLoad(eventArgs);
324
- break;
325
- }
326
- default: {
327
- throw new Error(`unexpected slide type: ${item.type}`);
328
- }
329
- }
330
- } else if (item.state === "error") {
331
- e.preventDefault();
332
- e.content.onError();
333
- }
334
- });
335
-
336
- const HIDE_UI_TIMEOUT_MS = 3000;
337
- let forceShowUI = false;
338
- let hideTimeout: ReturnType<typeof setTimeout>;
339
- let isHidden = false;
340
-
341
- function hideUI() {
342
- if (forceShowUI) {
343
- return;
344
- }
345
- lightbox.pswp?.element?.classList.remove("pswp--ui-visible");
346
- lightbox.pswp?.element?.classList.add("pswp--hide-cursor");
347
- isHidden = true;
348
- }
349
-
350
- function showUI() {
351
- if (isHidden) {
352
- lightbox.pswp?.element?.classList.add("pswp--ui-visible");
353
- lightbox.pswp?.element?.classList.remove("pswp--hide-cursor");
354
- isHidden = false;
355
- }
356
- clearTimeout(hideTimeout);
357
- hideTimeout = setTimeout(hideUI, HIDE_UI_TIMEOUT_MS);
358
- }
359
-
360
- lightbox.on("afterInit", () => {
361
- const pswp = lightbox.pswp;
362
-
363
- if (!pswp) {
364
- return;
365
- }
366
-
367
- pswp.element?.addEventListener("mousemove", showUI);
368
- pswp.element?.addEventListener("touchstart", showUI);
369
-
370
- hideTimeout = setTimeout(hideUI, HIDE_UI_TIMEOUT_MS);
371
- });
372
-
373
- const uiHandlers = {
374
- showUI,
375
- setForceShowUI: (value: boolean) => {
376
- forceShowUI = value;
377
- // resets timer
378
- if (!forceShowUI) {
379
- showUI();
380
- }
381
- },
382
- };
383
-
384
- lightbox.on("uiRegister", () => {
385
- lightbox.pswp?.ui?.registerElement({
386
- name: "custom-nav",
387
- order: 8,
388
- isButton: false,
389
- appendTo: "wrapper",
390
- onInit: (el, pswp) => {
391
- createRoot(el).render(
392
- <SlideButtons
393
- items={items}
394
- pswp={pswp}
395
- toastQueue={toastQueue}
396
- themeMode={themeMode}
397
- uiHandlers={uiHandlers}
398
- />
399
- );
400
- },
401
- });
402
- });
403
-
404
- lightbox.on("destroy", () => {
405
- contentAppendTimeouts.forEach((timeoutId) => {
406
- clearTimeout(timeoutId);
407
- });
408
- });
409
-
410
- lightbox.loadAndOpen(zoomedIndex === -1 ? 0 : zoomedIndex);
411
-
412
- return lightbox;
413
- }
414
-
415
- type ContentLoadArg = {
416
- e: PhotoSwipeEventsMap["contentLoad"];
417
- item: GalleryEntry;
418
- refreshSlide: (index: number) => void;
419
- getPswp: () => PhotoSwipeLightbox["pswp"];
420
- isPhone: boolean;
421
- };
422
-
423
- function onImageContentLoad({e, item, refreshSlide}: ContentLoadArg) {
424
- item.state = "loading";
425
- // we ok if content type request fails
426
- const contentTypePromise = item.contentType
427
- ? Promise.resolve(item.contentType)
428
- : fetch(item.src, {method: "HEAD"})
429
- .then((response) => response.headers.get("content-type"))
430
- .catch();
431
-
432
- const contentPromise = new Promise<{width: number; height: number}>((resolve, reject) => {
433
- const onLoad = () => {
434
- resolve({
435
- width: img.naturalWidth,
436
- height: img.naturalHeight,
437
- });
438
- };
439
-
440
- const img = new Image();
441
- img.src = item.src;
442
- if (img.complete) {
443
- onLoad();
444
- }
445
- img.onload = () => onLoad();
446
- img.onerror = () => {
447
- reject("error");
448
- };
449
- });
450
-
451
- Promise.allSettled([contentTypePromise, contentPromise]).then(([contentTypeResult, contentResult]) => {
452
- if (contentResult.status === "rejected") {
453
- item.state = "error";
454
- refreshSlide(e.content.index);
455
- e.content.onError();
456
- return;
457
- }
458
-
459
- item.state = "loaded";
460
- item.width = contentResult.value.width;
461
- item.height = contentResult.value.height;
462
-
463
- // normalize
464
- if (contentTypeResult.status === "fulfilled" && contentTypeResult.value?.startsWith("image/svg")) {
465
- const scale = Math.min(3000 / item.width!, 3000 / item.height!);
466
- item.width = Math.ceil(scale * item.width!);
467
- item.height = Math.ceil(scale * item.height!);
468
- }
469
-
470
- refreshSlide(e.content.index);
471
- e.content.onLoaded();
472
- });
473
- }
474
-
475
- function onVideoContentLoad({e, item, refreshSlide, getPswp, isPhone}: ContentLoadArg) {
476
- item.state = "loaded";
477
-
478
- e.content.element = document.createElement("div");
479
- e.content.element.className = "pswp__video-container";
480
- e.content.element.onclick = () => {
481
- getPswp()?.close();
482
- };
483
-
484
- const video = document.createElement("video");
485
- video.controls = true;
486
- video.src = item.src;
487
- video.onerror = () => {
488
- item.state = "error";
489
- refreshSlide(e.content.index);
490
- e.content.onError();
491
- };
492
- video.onclick = (e) => {
493
- e.preventDefault();
494
- e.stopPropagation();
495
- if (isPhone) {
496
- return;
497
- }
498
- if (video.paused) {
499
- video.play().catch();
500
- } else {
501
- video.pause();
502
- }
503
- };
504
-
505
- e.content.element.appendChild(video);
506
- }
507
-
508
- function onIframeContentLoad({e, item, getPswp}: ContentLoadArg) {
509
- item.state = "loaded";
510
-
511
- e.content.element = document.createElement("div");
512
- e.content.element.className = "pswp__iframe-container";
513
-
514
- e.content.element.onclick = () => {
515
- getPswp()?.close();
516
- };
517
-
518
- const iframe = document.createElement("iframe");
519
- iframe.src = item.src;
520
- iframe.setAttribute("allowfullscreen", "");
521
- e.content.element.appendChild(iframe);
522
- }
@@ -1,35 +0,0 @@
1
- import React, {ReactNode, useCallback} from "react";
2
- import "react-medium-image-zoom/dist/styles.css";
3
-
4
- type Props = {
5
- zoomed: boolean;
6
- onZoomedChange: (value: boolean) => void;
7
- children: ReactNode;
8
- className?: string;
9
- openOnClick?: boolean;
10
- zoomImg?: {src: string; alt?: string};
11
- actions?: ReactNode;
12
- };
13
-
14
- export const ZoomV2 = (props: Props) => {
15
- const {zoomed, onZoomedChange, children, className, openOnClick} = props;
16
- const onClick = useCallback(() => {
17
- if (zoomed) {
18
- onZoomedChange(false);
19
- } else if (openOnClick) {
20
- onZoomedChange(true);
21
- }
22
- }, [zoomed, onZoomedChange, openOnClick]);
23
-
24
- const onDoubleClick = useCallback(() => {
25
- if (!zoomed) {
26
- onZoomedChange(true);
27
- }
28
- }, [zoomed, onZoomedChange]);
29
-
30
- return (
31
- <div className={className} onClick={onClick} onDoubleClick={openOnClick ? undefined : onDoubleClick}>
32
- {children}
33
- </div>
34
- );
35
- };