@fibery/ui-kit 1.40.3 → 1.41.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 (174) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/eslint.config.mjs +16 -0
  3. package/package.json +11 -13
  4. package/src/a11y-color.test.ts +162 -0
  5. package/src/a11y-color.ts +13 -20
  6. package/src/actions-menu/actions-menu-item.tsx +9 -12
  7. package/src/actions-menu/context-actions-menu.tsx +8 -3
  8. package/src/ai/model.tsx +0 -1
  9. package/src/ai/temperature.tsx +0 -1
  10. package/src/animated-height-container.tsx +3 -3
  11. package/src/antd/ant-modal.tsx +10 -5
  12. package/src/antd/index.tsx +3 -0
  13. package/src/antd/input.tsx +0 -1
  14. package/src/antd/styles.ts +15 -15
  15. package/src/app-icon-with-fallback.tsx +6 -6
  16. package/src/app-icon-wrapper.tsx +2 -3
  17. package/src/app-icon.tsx +4 -4
  18. package/src/avatar.tsx +3 -3
  19. package/src/button/base-button.tsx +0 -1
  20. package/src/button/button.tsx +1 -0
  21. package/src/button/make-button-colors.ts +5 -13
  22. package/src/checkbox.tsx +2 -2
  23. package/src/collapsible-section.tsx +11 -1
  24. package/src/color-picker/ColorPickerOrLoader.js +2 -2
  25. package/src/color-utils.test.ts +307 -0
  26. package/src/color-utils.ts +215 -0
  27. package/src/command-menu/index.tsx +1 -0
  28. package/src/comment.tsx +3 -2
  29. package/src/context-menu/index.tsx +12 -7
  30. package/src/date-picker/contexts.ts +2 -2
  31. package/src/date-picker/date-range-picker.tsx +3 -4
  32. package/src/date-picker/single-date-picker.tsx +147 -119
  33. package/src/date-picker/types.ts +4 -3
  34. package/src/day-select/iso-week-day-select.tsx +2 -2
  35. package/src/day-select/week-day-select.tsx +2 -2
  36. package/src/delayed.tsx +0 -1
  37. package/src/design-system/alpha.test.ts +59 -0
  38. package/src/design-system/alpha.ts +41 -0
  39. package/src/design-system/animation.ts +10 -0
  40. package/src/design-system/colors-css.test.ts +39 -0
  41. package/src/design-system/colors-css.ts +25 -0
  42. package/src/design-system/colors-js.test.ts +232 -0
  43. package/src/design-system/colors-js.ts +107 -0
  44. package/src/design-system/colors.test.ts +74 -0
  45. package/src/design-system/colors.ts +759 -0
  46. package/src/design-system/date.ts +3 -0
  47. package/src/design-system/layout.ts +106 -0
  48. package/src/design-system/theme.test.ts +94 -0
  49. package/src/design-system/theme.ts +93 -0
  50. package/src/design-system/typography.ts +179 -0
  51. package/src/design-system/vars.test.ts +1679 -0
  52. package/src/design-system/vars.ts +52 -0
  53. package/src/design-system.test.ts +45 -22
  54. package/src/design-system.ts +25 -1339
  55. package/src/dropdown-menu/index.tsx +21 -16
  56. package/src/emoji-picker/app-icon-picker.tsx +5 -5
  57. package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
  58. package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
  59. package/src/favorites-icon.tsx +1 -1
  60. package/src/fibermoji-placeholder.tsx +3 -3
  61. package/src/field-container.tsx +11 -2
  62. package/src/file-item/file-icon.tsx +169 -0
  63. package/src/file-item/file-menu-items.tsx +68 -0
  64. package/src/file-item/file-preview-actions.tsx +38 -0
  65. package/src/file-item/file-title.tsx +48 -0
  66. package/src/file-item/types.ts +27 -0
  67. package/src/file-item/use-register-in-image-gallery.tsx +70 -0
  68. package/src/file-item-2.tsx +32 -348
  69. package/src/file-item.tsx +6 -3
  70. package/src/hue-shift.test.ts +91 -0
  71. package/src/icons/ast/ChatBubble.ts +8 -0
  72. package/src/icons/ast/ChatFloat.ts +8 -0
  73. package/src/icons/ast/ChatSidebar.ts +8 -0
  74. package/src/icons/ast/FileCounter.ts +8 -0
  75. package/src/icons/ast/FileMultiple.ts +8 -0
  76. package/src/icons/ast/FileOther.ts +8 -0
  77. package/src/icons/ast/NoBorder.ts +8 -0
  78. package/src/icons/ast/NoFill.ts +8 -0
  79. package/src/icons/ast/ValueEdit.ts +8 -0
  80. package/src/icons/ast/index.tsx +9 -0
  81. package/src/icons/react/ChatBubble.tsx +13 -0
  82. package/src/icons/react/ChatFloat.tsx +13 -0
  83. package/src/icons/react/ChatSidebar.tsx +13 -0
  84. package/src/icons/react/FileCounter.tsx +13 -0
  85. package/src/icons/react/FileMultiple.tsx +13 -0
  86. package/src/icons/react/FileOther.tsx +13 -0
  87. package/src/icons/react/NoBorder.tsx +13 -0
  88. package/src/icons/react/NoFill.tsx +13 -0
  89. package/src/icons/react/ValueEdit.tsx +13 -0
  90. package/src/icons/react/index.tsx +9 -0
  91. package/src/icons/svg/chat-bubble.svg +4 -0
  92. package/src/icons/svg/chat-float.svg +4 -0
  93. package/src/icons/svg/chat-sidebar.svg +4 -0
  94. package/src/icons/svg/file-counter.svg +3 -0
  95. package/src/icons/svg/file-multiple.svg +3 -0
  96. package/src/icons/svg/file-other.svg +3 -0
  97. package/src/icons/svg/no-border.svg +17 -0
  98. package/src/icons/svg/no-fill.svg +4 -0
  99. package/src/icons/svg/value-edit.svg +3 -0
  100. package/src/images-gallery/images-gallery.tsx +8 -6
  101. package/src/images-gallery/slide-buttons.tsx +4 -11
  102. package/src/is-iOS.ts +0 -1
  103. package/src/is-in-popup.ts +2 -1
  104. package/src/lists/actions-menu-row-surface.tsx +7 -7
  105. package/src/loading-sausage.tsx +2 -2
  106. package/src/media-query-utils.ts +1 -2
  107. package/src/mobile-keyboard-aware-popup.tsx +6 -5
  108. package/src/modal-menu/modal-menu-content.tsx +2 -2
  109. package/src/number-input/decimal.js +9 -7
  110. package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
  111. package/src/number-input/number-input-inline.tsx +118 -0
  112. package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
  113. package/src/number-input/number-input.tsx +63 -121
  114. package/src/number-input/types.ts +19 -0
  115. package/src/number-input/utils.ts +61 -0
  116. package/src/online-users.tsx +2 -2
  117. package/src/palette-generator.test.ts +309 -0
  118. package/src/palette-generator.ts +160 -0
  119. package/src/palettes/_.ts +72 -0
  120. package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
  121. package/src/palettes/slate-arch.ts +188 -0
  122. package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
  123. package/src/palettes/slate-user.ts +187 -0
  124. package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
  125. package/src/palettes/warm-arch.ts +222 -0
  126. package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
  127. package/src/palettes/warm-user.ts +222 -0
  128. package/src/platform.ts +0 -3
  129. package/src/popover/get-element-ref.ts +28 -0
  130. package/src/popover/index.tsx +236 -339
  131. package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
  132. package/src/popover/mobile-popover.tsx +169 -0
  133. package/src/popover/modifiers.tsx +2 -2
  134. package/src/popover/popup-stack-context.tsx +8 -9
  135. package/src/progress.tsx +2 -2
  136. package/src/reactions/reaction-button.tsx +12 -6
  137. package/src/root-theme-provider.test.tsx +411 -0
  138. package/src/scale-generator.ts +356 -0
  139. package/src/select/components/menu-list-virtualized.tsx +12 -25
  140. package/src/select/components/menu.tsx +12 -2
  141. package/src/select/index.tsx +6 -5
  142. package/src/select/select.tsx +38 -39
  143. package/src/select/styles.ts +0 -1
  144. package/src/select/util.ts +1 -1
  145. package/src/static-palettes.ts +356 -0
  146. package/src/thematic-color-picker.tsx +266 -0
  147. package/src/thematic-constants.tsx +27 -0
  148. package/src/thematic-controls.tsx +144 -0
  149. package/src/thematic-scales.tsx +122 -0
  150. package/src/thematic-state.ts +370 -0
  151. package/src/thematic.tsx +386 -0
  152. package/src/theme-provider.test.tsx +820 -0
  153. package/src/theme-provider.tsx +158 -92
  154. package/src/theme-settings.ts +67 -12
  155. package/src/theme-styles.ts +58 -6
  156. package/src/toast/toast-action.tsx +1 -1
  157. package/src/toast/toast.tsx +1 -2
  158. package/src/toggle-on-off.tsx +2 -2
  159. package/src/toggle.tsx +7 -8
  160. package/src/tooltip.tsx +14 -10
  161. package/src/type-badge.tsx +7 -14
  162. package/src/unit/styles.ts +2 -25
  163. package/src/unit/unit-with-tooltip.tsx +3 -2
  164. package/src/use-is-phone.tsx +7 -2
  165. package/src/use-long-press.tsx +2 -2
  166. package/src/use-on-screen-keyboard-data.tsx +2 -2
  167. package/src/with-data.tsx +4 -3
  168. package/src/workflow-progress-icon.tsx +2 -2
  169. package/.eslintignore +0 -3
  170. package/.eslintrc +0 -14
  171. package/src/__mocks__/createInlineTheme.js +0 -3
  172. package/src/create-inline-theme.ts +0 -66
  173. package/src/number-input/index.js +0 -191
  174. package/src/palette.ts +0 -237
@@ -5,6 +5,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
5
  import {forwardRef} from "react";
6
6
  import {border, layout, space, textStyles, themeVars} from "../design-system";
7
7
  import {mobileRootSelector} from "../mobile-styles";
8
+ import {RadixPortalThemeProvider} from "../theme-provider";
8
9
 
9
10
  export const Root = (dropdownMenuProps: DropdownMenuPrimitive.DropdownMenuProps) => (
10
11
  <DropdownMenuPrimitive.Root dir="ltr" modal={false} {...dropdownMenuProps} />
@@ -133,16 +134,18 @@ export const Content = forwardRef<HTMLDivElement, ContentProps>(function Dropdow
133
134
  ) {
134
135
  return (
135
136
  <Wrap if={portalled} with={DropdownMenuPrimitive.Portal} container={container}>
136
- <DropdownMenuPrimitive.Content
137
- onKeyDown={onContentKeyDown}
138
- ref={ref}
139
- className={cx(dropdownMenuContentStyles, className)}
140
- side="bottom"
141
- align="start"
142
- collisionPadding={collisionPadding}
143
- sideOffset={space.s4}
144
- {...rest}
145
- />
137
+ <RadixPortalThemeProvider>
138
+ <DropdownMenuPrimitive.Content
139
+ onKeyDown={onContentKeyDown}
140
+ ref={ref}
141
+ className={cx(dropdownMenuContentStyles, className)}
142
+ side="bottom"
143
+ align="start"
144
+ collisionPadding={collisionPadding}
145
+ sideOffset={space.s4}
146
+ {...rest}
147
+ />
148
+ </RadixPortalThemeProvider>
146
149
  </Wrap>
147
150
  );
148
151
  });
@@ -155,12 +158,14 @@ export const SubContent = forwardRef<
155
158
  >(function DropdownMenuSubContent({className, collisionPadding = space.s12, container, ...rest}, ref) {
156
159
  return (
157
160
  <DropdownMenuPrimitive.Portal container={container}>
158
- <DropdownMenuPrimitive.SubContent
159
- ref={ref}
160
- className={cx(dropdownMenuContentStyles, className)}
161
- collisionPadding={collisionPadding}
162
- {...rest}
163
- />
161
+ <RadixPortalThemeProvider>
162
+ <DropdownMenuPrimitive.SubContent
163
+ ref={ref}
164
+ className={cx(dropdownMenuContentStyles, className)}
165
+ collisionPadding={collisionPadding}
166
+ {...rest}
167
+ />
168
+ </RadixPortalThemeProvider>
164
169
  </DropdownMenuPrimitive.Portal>
165
170
  );
166
171
  });
@@ -1,7 +1,7 @@
1
1
  import {CustomEmoji, EmojiItem} from "@fibery/emoji-data";
2
2
  import {useCallback} from "react";
3
- import {cardTypeColors, getIconColor} from "../design-system";
4
- import {useThemeMode} from "../theme-provider";
3
+ import {cardTypeColors} from "../design-system";
4
+ import {useTheme} from "../theme-provider";
5
5
  import {$TSFixMe} from "../tsfixme";
6
6
  import {EmojiPickerContentWithColor, EmojiPickerContentWithColorProps} from "./emoji-picker-content-with-color";
7
7
  import {EmojiPickerContent} from "./primitives/content";
@@ -30,7 +30,7 @@ export const AppIconPicker: React.FC<AppIconPickerProps> = ({
30
30
  autoFocus = true,
31
31
  emojiSize = 20,
32
32
  }) => {
33
- const themeMode = useThemeMode();
33
+ const theme = useTheme();
34
34
 
35
35
  const renderEmoji = useCallback<NonNullable<EmojiPickerContentWithColorProps["renderEmoji"]>>(
36
36
  ({emoji}) => (
@@ -39,14 +39,14 @@ export const AppIconPicker: React.FC<AppIconPickerProps> = ({
39
39
  display: "block",
40
40
  width: emojiSize,
41
41
  height: emojiSize,
42
- fill: color ? getIconColor(themeMode, color) : undefined,
42
+ fill: color ? theme.fns.getIconColor(color) : undefined,
43
43
  }}
44
44
  viewBox="0 0 24 24"
45
45
  >
46
46
  <use href={(emoji as CustomEmoji).imageUrl} />
47
47
  </svg>
48
48
  ),
49
- [color, themeMode, emojiSize]
49
+ [color, theme, emojiSize]
50
50
  );
51
51
 
52
52
  return (
@@ -2,8 +2,8 @@ import {css} from "@linaria/core";
2
2
  import {useRef, useState} from "react";
3
3
  import {CollapsibleContent, CollapsibleRoot, CollapsibleTrigger} from "../collapsible";
4
4
  import {ColorPicker} from "../color-picker";
5
- import {getIconColor, space, themeVars} from "../design-system";
6
- import {useThemeMode} from "../theme-provider";
5
+ import {space, themeVars} from "../design-system";
6
+ import {useTheme} from "../theme-provider";
7
7
  import {Tooltip} from "../tooltip";
8
8
  import {$TSFixMe} from "../tsfixme";
9
9
  import {EmojiPickerGrid, type EmojiPickerGridHandle, EmojiPickerGridProps} from "./primitives/grid";
@@ -18,8 +18,8 @@ const colorPickerWrapperCss = css`
18
18
  type ColorPickerCollapsibleTrigger = {opened: boolean; color?: string};
19
19
 
20
20
  const ColorPickerCollapsibleTrigger: React.FC<ColorPickerCollapsibleTrigger> = ({opened, color}) => {
21
- const themeMode = useThemeMode();
22
- const iconColor = color ? getIconColor(themeMode, color) : undefined;
21
+ const theme = useTheme();
22
+ const iconColor = color ? theme.fns.getIconColor(color) : undefined;
23
23
 
24
24
  const label = opened ? "Hide color picker" : "Show color picker";
25
25
 
@@ -1,7 +1,7 @@
1
1
  import {css} from "@linaria/core";
2
- import {compact} from "lodash";
2
+ import compact from "lodash/compact";
3
3
  import {Button} from "../button/button";
4
- import {colors, space} from "../design-system";
4
+ import {space, themeVars} from "../design-system";
5
5
  import EmojiDelete from "../icons/react/EmojiDelete";
6
6
  import {$TSFixMe} from "../tsfixme";
7
7
  import {AppIconPicker, AppIconPickerProps} from "./app-icon-picker";
@@ -11,7 +11,7 @@ import {useState} from "react";
11
11
 
12
12
  const tabNavRootStyle = css`
13
13
  padding: ${space.s4}px ${space.s12}px 0 ${space.s12}px;
14
- box-shadow: inset 0 -1px ${colors.shades.opacity5};
14
+ box-shadow: inset 0 -1px ${themeVars.shades.opacity5};
15
15
  `;
16
16
 
17
17
  type TabKey = "icons" | "emojis";
@@ -74,7 +74,7 @@ export const FavoritesIcon = ({
74
74
  onClick={onClick}
75
75
  >
76
76
  <IconButton
77
- style={checked ? makeButtonColors(theme.favoritesIconColor, "ghost") : {}}
77
+ style={checked ? makeButtonColors(theme.favoritesIconColor, "ghost", theme) : {}}
78
78
  size={size || "medium"}
79
79
  onClick={handleChange}
80
80
  color="neutral"
@@ -1,4 +1,4 @@
1
- import _ from "lodash";
1
+ import memoize from "lodash/memoize";
2
2
  import {css} from "@linaria/core";
3
3
  import {FC, useEffect, useRef} from "react";
4
4
  import {textStyles} from "./design-system";
@@ -38,12 +38,12 @@ const mouths = [
38
38
  `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="16" viewBox="0 0 32 16" fill="none"><g clip-path="url(#clip0_31_1134)"><path d="M4.23177 12.4853C2.41601 11.1761 1.06315 9.59547 0.249133 7.87572C-0.702621 5.86498 1.22002 4 3.51791 4L7 4C7.55229 4 8 4.44772 8 5V13.0522C8 13.7579 7.28869 14.2398 6.6548 13.9296C5.79179 13.5074 4.97984 13.0247 4.23177 12.4853Z" fill="currentColor"/><path d="M31.7509 7.87572C30.9369 9.59547 29.584 11.1761 27.7682 12.4853C27.0202 13.0247 26.2082 13.5074 25.3452 13.9296C24.7113 14.2398 24 13.7579 24 13.0522L24 5C24 4.44772 24.4477 4 25 4L28.4821 4C30.78 4 32.7026 5.86498 31.7509 7.87572Z" fill="currentColor"/><path d="M16 16C14.564 16 13.1472 15.8662 11.7816 15.6082C11.3227 15.5215 11 15.1143 11 14.6473V5C11 4.44772 11.4477 4 12 4L20 4C20.5523 4 21 4.44772 21 5V14.6473C21 15.1143 20.6773 15.5215 20.2184 15.6082C18.8528 15.8662 17.436 16 16 16Z" fill="currentColor"/></g><defs><clipPath id="clip0_31_1134"><rect width="32" height="16" fill="white"/></clipPath></defs></svg>`,
39
39
  ];
40
40
 
41
- export const getColors = _.memoize((v: string) => {
41
+ export const getColors = memoize((v: string) => {
42
42
  const idx = Math.floor((cyrb53(v) / Math.pow(2, 53)) * colors.length);
43
43
  return colors[idx];
44
44
  });
45
45
 
46
- const getMouth = _.memoize((v: string) => {
46
+ const getMouth = memoize((v: string) => {
47
47
  const salted = v + "::mouth"; // made mouths independent of colors
48
48
  const idx = Math.floor((cyrb53(salted) / Math.pow(2, 53)) * mouths.length);
49
49
  return mouths[idx];
@@ -20,18 +20,27 @@ type Props = {
20
20
  label?: string;
21
21
  collapsible?: boolean;
22
22
  collapsed?: boolean;
23
+ collapsedDefault?: boolean;
23
24
  onCollapseChange?: (collapsed: boolean) => void;
24
25
  badge?: ReactNode;
25
26
  children: ReactNode;
26
27
  };
27
28
 
28
- export const FieldContainer = ({label, collapsible = false, collapsed, onCollapseChange, badge, children}: Props) => {
29
+ export const FieldContainer = ({
30
+ label,
31
+ collapsible = false,
32
+ collapsed,
33
+ collapsedDefault = true,
34
+ onCollapseChange,
35
+ badge,
36
+ children,
37
+ }: Props) => {
29
38
  return (
30
39
  <CollapsibleSection
31
40
  label={label ? <div className={labelStyle}>{label}</div> : undefined}
32
41
  collapsible={collapsible}
33
42
  collapsed={collapsed}
34
- collapsedDefault={true}
43
+ collapsedDefault={collapsedDefault}
35
44
  onCollapsedChange={onCollapseChange}
36
45
  badge={badge}
37
46
  >
@@ -0,0 +1,169 @@
1
+ import FileImage from "../icons/react/FileImage";
2
+ import FileVideo from "../icons/react/FileVideo";
3
+ import FileTable from "../icons/react/FileTable";
4
+ import FileDocument from "../icons/react/FileDocument";
5
+ import FileArchive from "../icons/react/FileArchive";
6
+ import FilePresentation from "../icons/react/FilePresentation";
7
+ import FileOther from "../icons/react/FileOther";
8
+
9
+ export function getFileIconComponent(contentType?: string) {
10
+ if (!contentType) {
11
+ return FileOther;
12
+ }
13
+ if (contentType.startsWith("image/")) {
14
+ return FileImage;
15
+ }
16
+ if (contentType.startsWith("video/")) {
17
+ return FileVideo;
18
+ }
19
+ if (SheetsContentTypes.includes(contentType)) {
20
+ return FileTable;
21
+ }
22
+ if (ArchiveContentTypes.includes(contentType)) {
23
+ return FileArchive;
24
+ }
25
+ if (PresentationContentTypes.includes(contentType)) {
26
+ return FilePresentation;
27
+ }
28
+ if (DocumentContentTypes.includes(contentType) || contentType.startsWith("text/")) {
29
+ return FileDocument;
30
+ }
31
+
32
+ return FileOther;
33
+ }
34
+
35
+ export function FileIcon({contentType}: {contentType?: string}) {
36
+ const Component = getFileIconComponent(contentType);
37
+ return <Component />;
38
+ }
39
+
40
+ const ArchiveContentTypes = [
41
+ "application/zip",
42
+ "application/zip-compressed",
43
+ "application/x-zip",
44
+ "application/x-zip-compressed",
45
+ "multipart/x-zip",
46
+
47
+ "application/x-rar-compressed",
48
+ "application/vnd.rar",
49
+
50
+ "application/x-7z-compressed",
51
+
52
+ "application/gzip",
53
+ "application/x-gzip",
54
+
55
+ "application/x-tar",
56
+
57
+ "application/x-bzip",
58
+ "application/x-bzip2",
59
+ "application/x-bzip3",
60
+ "application/x-bzip4",
61
+
62
+ "application/x-xz",
63
+
64
+ "application/zstd",
65
+ "application/x-zstd",
66
+
67
+ "application/x-lzma",
68
+ "application/x-lzip",
69
+
70
+ "application/x-lzh",
71
+ "application/x-lha",
72
+
73
+ "application/x-iso9660-image",
74
+ "application/x-cd-image",
75
+
76
+ "application/x-apple-diskimage",
77
+
78
+ "application/x-arj",
79
+
80
+ "application/x-cpio",
81
+
82
+ "application/x-rpm",
83
+
84
+ "application/x-deb",
85
+ "application/vnd.debian.binary-package",
86
+
87
+ "application/x-compress",
88
+ "application/x-archive",
89
+ "application/vnd.android.package-archive",
90
+ ];
91
+
92
+ const PresentationContentTypes = [
93
+ "application/x-iwork-keynote-sffkey",
94
+ "application/vnd.ms-powerpoint",
95
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
96
+ "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
97
+ "application/vnd.openxmlformats-officedocument.presentationml.template",
98
+ "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
99
+ "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
100
+ "application/vnd.ms-powerpoint.template.macroEnabled.12",
101
+ "application/vnd.oasis.opendocument.presentation",
102
+ "application/vnd.oasis.opendocument.presentation-template",
103
+ "application/vnd.apple.keynote",
104
+ "application/vnd.google-apps.presentation",
105
+ "application/pdf",
106
+ "application/vnd.sun.xml.impress",
107
+ "application/vnd.stardivision.impress",
108
+ ];
109
+
110
+ const SheetsContentTypes = [
111
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
112
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
113
+ "application/vnd.ms-excel",
114
+ "application/vnd.ms-excel.sheet.macroEnabled.12",
115
+ "application/vnd.ms-excel.template.macroEnabled.12",
116
+ "application/vnd.ms-excel.addin.macroEnabled.12",
117
+ "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
118
+ "application/vnd.oasis.opendocument.spreadsheet",
119
+ "application/vnd.oasis.opendocument.spreadsheet-template",
120
+ "application/vnd.apple.numbers",
121
+ "application/vnd.google-apps.spreadsheet",
122
+ "text/csv",
123
+ "text/x-comma-separated-values",
124
+ "application/csv",
125
+ "text/tab-separated-values",
126
+ "application/vnd.lotus-1-2-3",
127
+ "application/x-123",
128
+ "application/vnd.stardivision.calc",
129
+ "application/vnd.sun.xml.calc",
130
+ "application/x-dbase",
131
+ "application/x-iwork-numbers-sffnumbers",
132
+ ];
133
+
134
+ const DocumentContentTypes = [
135
+ "application/msword",
136
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
137
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
138
+ "application/vnd.ms-word.document.macroEnabled.12",
139
+ "application/vnd.ms-word.template.macroEnabled.12",
140
+
141
+ "application/vnd.oasis.opendocument.text",
142
+ "application/vnd.oasis.opendocument.text-template",
143
+ "application/vnd.oasis.opendocument.text-master",
144
+ "application/vnd.oasis.opendocument.text-web",
145
+
146
+ "application/vnd.apple.pages",
147
+ "application/x-iwork-pages-sffpages",
148
+
149
+ "application/rtf",
150
+ "text/rtf",
151
+
152
+ "text/plain",
153
+ "text/markdown",
154
+ "text/html",
155
+
156
+ "application/vnd.google-apps.document",
157
+
158
+ "application/vnd.sun.xml.writer",
159
+ "application/vnd.sun.xml.writer.template",
160
+ "application/vnd.sun.xml.writer.global",
161
+
162
+ "application/vnd.stardivision.writer",
163
+ "application/vnd.stardivision.writer-global",
164
+
165
+ "application/x-mswrite",
166
+ "application/x-abiword",
167
+
168
+ "application/pdf",
169
+ ];
@@ -0,0 +1,68 @@
1
+ import {ReactNode} from "react";
2
+ import {useToast} from "../toast/toast-provider";
3
+ import {ActionsMenuItem, ActionsMenuSeparator} from "../actions-menu";
4
+ import Link from "../icons/react/Link";
5
+ import {copyUrlToClipboard} from "../copy-to-clipboard";
6
+ import RicheditorOpenLink from "../icons/react/RicheditorOpenLink";
7
+ import {isPhoneApp} from "../use-is-phone";
8
+ import FileDownload from "../icons/react/FileDownload";
9
+ import Pencil from "../icons/react/Pencil";
10
+ import Delete from "../icons/react/Delete";
11
+ import {UploadFileWithContent} from "./types";
12
+
13
+ export function FileMenuItems({
14
+ file,
15
+ disabled,
16
+ onDownloadClick,
17
+ onRemoveClick,
18
+ onRenameClick,
19
+ }: {
20
+ file: Pick<UploadFileWithContent, "uid" | "name" | "url" | "status">;
21
+ onDownloadClick?: () => void;
22
+ onRemoveClick?: () => void;
23
+ onRenameClick?: (data: {id: string; name: string}) => void;
24
+ disabled?: boolean;
25
+ }): ReactNode {
26
+ const toast = useToast();
27
+
28
+ return (
29
+ <>
30
+ <ActionsMenuItem
31
+ Icon={Link}
32
+ onSelect={() => {
33
+ copyUrlToClipboard({url: `${window.origin}${file.url}`, label: file.name})
34
+ .then(() => toast.success({title: "Link copied to clipboard"}))
35
+ .catch((error) => toast.error({title: "Unable to copy link to clipboard", subTitle: error.message}));
36
+ }}
37
+ >
38
+ Copy file URL
39
+ </ActionsMenuItem>
40
+ <a href={file.url} target="_blank" rel="noopener noreferrer">
41
+ <ActionsMenuItem Icon={RicheditorOpenLink}>
42
+ {isPhoneApp() ? "Open in browser" : "Open in new tab"}
43
+ </ActionsMenuItem>
44
+ </a>
45
+ {isPhoneApp() ? (
46
+ <ActionsMenuItem Icon={FileDownload} onSelect={onDownloadClick}>
47
+ Download
48
+ </ActionsMenuItem>
49
+ ) : (
50
+ <a download={file.name} href={`${file.url}?attachment`}>
51
+ <ActionsMenuItem Icon={FileDownload}>Download</ActionsMenuItem>
52
+ </a>
53
+ )}
54
+ {onRenameClick && file.status === "done" ? (
55
+ <>
56
+ <ActionsMenuSeparator />
57
+ <ActionsMenuItem Icon={Pencil} onSelect={() => onRenameClick?.({id: file.uid, name: file.name})}>
58
+ Rename
59
+ </ActionsMenuItem>
60
+ </>
61
+ ) : null}
62
+ <ActionsMenuSeparator />
63
+ <ActionsMenuItem Icon={Delete} onSelect={onRemoveClick} dangerous disabled={disabled}>
64
+ Delete
65
+ </ActionsMenuItem>
66
+ </>
67
+ );
68
+ }
@@ -0,0 +1,38 @@
1
+ import {isPhoneApp} from "../use-is-phone";
2
+ import {ActionsMenuItem} from "../actions-menu";
3
+ import RicheditorOpenLink from "../icons/react/RicheditorOpenLink";
4
+ import FileDownload from "../icons/react/FileDownload";
5
+
6
+ export function FilePreviewActions({
7
+ url,
8
+ name,
9
+ onDownloadClick,
10
+ }: {
11
+ url: string;
12
+ name: string;
13
+ onDownloadClick?: () => void;
14
+ }) {
15
+ if (isPhoneApp()) {
16
+ return (
17
+ <>
18
+ <a href={url} target="_blank" rel="noopener noreferrer">
19
+ <ActionsMenuItem Icon={RicheditorOpenLink}>Open in browser</ActionsMenuItem>
20
+ </a>
21
+ <ActionsMenuItem Icon={FileDownload} onSelect={onDownloadClick}>
22
+ Download
23
+ </ActionsMenuItem>
24
+ </>
25
+ );
26
+ }
27
+
28
+ return (
29
+ <>
30
+ <a href={url} target="_blank" rel="noopener noreferrer">
31
+ <ActionsMenuItem Icon={RicheditorOpenLink}>Open in new tab</ActionsMenuItem>
32
+ </a>
33
+ <a download={name} href={`${url}?attachment`}>
34
+ <ActionsMenuItem Icon={FileDownload}>Download</ActionsMenuItem>
35
+ </a>
36
+ </>
37
+ );
38
+ }
@@ -0,0 +1,48 @@
1
+ import {css, cx, LinariaClassName} from "@linaria/core";
2
+
3
+ export function FileTitle({
4
+ title,
5
+ containerCss,
6
+ maxWidth,
7
+ }: {
8
+ title: string;
9
+ containerCss?: LinariaClassName;
10
+ maxWidth?: number;
11
+ }) {
12
+ const parts = title.split(".");
13
+ const ext = parts.length > 1 ? "." + parts[parts.length - 1] : "";
14
+ const name = title.substring(0, title.length - ext.length);
15
+
16
+ return (
17
+ <div
18
+ className={cx(
19
+ css`
20
+ display: flex;
21
+ min-width: 0;
22
+ align-items: center;
23
+ `,
24
+ containerCss
25
+ )}
26
+ style={{maxWidth}}
27
+ >
28
+ <span
29
+ className={css`
30
+ white-space: nowrap;
31
+ overflow: hidden;
32
+ text-overflow: ellipsis;
33
+ /* ellipsis shows ONLY if this span overflows */
34
+ min-width: 0;
35
+ `}
36
+ >
37
+ {name}
38
+ </span>
39
+ <span
40
+ className={css`
41
+ flex: 0 0 auto;
42
+ `}
43
+ >
44
+ {ext}
45
+ </span>
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,27 @@
1
+ export type FiberyFile = {
2
+ "fibery/id": string;
3
+ "fibery/name": string;
4
+ "fibery/rank": number;
5
+ "fibery/content-type": string;
6
+ "fibery/secret": string;
7
+ "fibery/content-length": number;
8
+ "fibery/creation-date": string;
9
+ };
10
+
11
+ export type PreviewData = {
12
+ type: "image" | "video" | "iframe";
13
+ previewSrc: string | null;
14
+ originalSrc: string;
15
+ contentType: string;
16
+ };
17
+
18
+ export type UploadFileWithContent = {
19
+ uid: string;
20
+ name: string;
21
+ url: string;
22
+ status?: string;
23
+ contentType: string;
24
+ thumbnailUrl: string | null;
25
+ downloadUrl?: string | null;
26
+ previewData: null | PreviewData;
27
+ };
@@ -0,0 +1,70 @@
1
+ import {useCallback, useEffect, useState} from "react";
2
+ import {useImagesGalleryMethods, useImagesGalleryRegistry} from "../images-gallery/images-gallery";
3
+ import {FilePreviewActions} from "./file-preview-actions";
4
+ import {PreviewData} from "./types";
5
+
6
+ export function useRegisterInImageGallery({
7
+ preview,
8
+ name,
9
+ onDownloadClick,
10
+ }: {
11
+ preview: PreviewData | null;
12
+ name: string;
13
+ onDownloadClick?: () => void;
14
+ }) {
15
+ const [zoomed, setZoomed] = useState(false);
16
+ const imagesRegistry = useImagesGalleryRegistry();
17
+ const galleryMethods = useImagesGalleryMethods();
18
+
19
+ useEffect(() => {
20
+ if (!preview) {
21
+ return;
22
+ }
23
+
24
+ imagesRegistry.set(preview.originalSrc, {
25
+ type: preview.type,
26
+ src: preview.originalSrc,
27
+ label: name,
28
+ zoomed: false,
29
+ setZoomed,
30
+ actions: <FilePreviewActions url={preview.originalSrc} name={name} onDownloadClick={onDownloadClick} />,
31
+ });
32
+ }, [imagesRegistry, name, preview, onDownloadClick]);
33
+
34
+ useEffect(() => {
35
+ if (!preview) {
36
+ return undefined;
37
+ }
38
+
39
+ if (zoomed) {
40
+ galleryMethods.setActivated(true);
41
+ }
42
+ const imageData = imagesRegistry.get(preview.originalSrc);
43
+ if (imageData) {
44
+ imageData.zoomed = zoomed;
45
+ } else {
46
+ imagesRegistry.set(preview.originalSrc, {
47
+ type: preview.type,
48
+ src: preview.originalSrc,
49
+ label: name,
50
+ zoomed,
51
+ setZoomed,
52
+ actions: <FilePreviewActions url={preview.originalSrc} name={name} onDownloadClick={onDownloadClick} />,
53
+ });
54
+ }
55
+
56
+ return () => {
57
+ imagesRegistry.delete(preview.originalSrc);
58
+ };
59
+ }, [galleryMethods, zoomed, preview, imagesRegistry, name, onDownloadClick]);
60
+
61
+ const openGallery = useCallback(() => {
62
+ if (preview) {
63
+ setZoomed(true);
64
+ } else {
65
+ onDownloadClick?.();
66
+ }
67
+ }, [preview, onDownloadClick]);
68
+
69
+ return {openGallery};
70
+ }