@fibery/ui-kit 1.37.0 → 1.38.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 (194) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/config/jest/testFrameworkSetup.js +1 -1
  3. package/package.json +20 -16
  4. package/src/abbreviate-name.ts +42 -0
  5. package/src/actions-menu/actions-menu-item.tsx +32 -11
  6. package/src/actions-menu/actions-menu-props.tsx +5 -0
  7. package/src/actions-menu/actions-menu-sub-command-menu.tsx +7 -4
  8. package/src/actions-menu/actions-menu-sub-menu.tsx +9 -3
  9. package/src/actions-menu/actions-menu.tsx +28 -9
  10. package/src/actions-menu/context-actions-menu.tsx +22 -0
  11. package/src/actions-panel.tsx +12 -2
  12. package/src/animated-height-container.tsx +24 -5
  13. package/src/antd/ant-upload.tsx +1 -1
  14. package/src/antd/input.tsx +4 -0
  15. package/src/avatar.tsx +1 -27
  16. package/src/axis-header.tsx +136 -76
  17. package/src/box.tsx +1 -1
  18. package/src/breadcrumb.tsx +78 -0
  19. package/src/button/actions-button-compact.tsx +4 -1
  20. package/src/button/base-button.tsx +7 -3
  21. package/src/button/button-group.tsx +1 -1
  22. package/src/button/button.tsx +33 -1
  23. package/src/button/icon-button.tsx +10 -1
  24. package/src/button/select-button.tsx +4 -0
  25. package/src/card-container.tsx +10 -6
  26. package/src/checkbox.tsx +17 -0
  27. package/src/collapsible-section.tsx +3 -1
  28. package/src/color-picker/swatch.tsx +1 -0
  29. package/src/command-menu/index.tsx +3 -3
  30. package/src/context-menu/index.tsx +10 -1
  31. package/src/date-picker/autocomplete.tsx +11 -5
  32. package/src/date-picker/date-range-input.tsx +5 -1
  33. package/src/date-picker/date-range-picker-popup-content.tsx +31 -13
  34. package/src/date-picker/date-range-picker.tsx +6 -3
  35. package/src/date-picker/relative-date-picker.tsx +6 -2
  36. package/src/date-picker/single-date-input.tsx +5 -1
  37. package/src/date-picker/single-date-picker.tsx +8 -5
  38. package/src/date-picker/styles.ts +52 -26
  39. package/src/date-picker/types.ts +3 -1
  40. package/src/day-select/day-select.tsx +21 -75
  41. package/src/day-select/iso-week-day-select.tsx +1 -1
  42. package/src/day-select/mount-day-select.tsx +2 -2
  43. package/src/day-select/week-day-select.tsx +1 -5
  44. package/src/design-system.ts +45 -12
  45. package/src/dropdown-menu/index.tsx +19 -2
  46. package/src/emoji-picker/app-icon-picker.tsx +6 -4
  47. package/src/emoji-picker/emoji-picker-preview-footer.tsx +5 -1
  48. package/src/emoji-picker/emoji-picker.tsx +3 -0
  49. package/src/emoji-picker/emoji.tsx +5 -3
  50. package/src/emoji-picker/icon-emoji-picker.tsx +10 -1
  51. package/src/emoji-picker/primitives/emoji.tsx +49 -51
  52. package/src/emoji-picker/primitives/footer.tsx +5 -0
  53. package/src/emoji-picker/primitives/search.tsx +8 -0
  54. package/src/emoji-picker/primitives/skin-tone.tsx +12 -1
  55. package/src/emoji-picker/use-responsive-emoji-size.ts +11 -0
  56. package/src/emoji-picker/utils/emoji-set.ts +2 -1
  57. package/src/empty-state/empty-state.tsx +6 -0
  58. package/src/fibermoji-placeholder.tsx +1 -27
  59. package/src/field-icon.tsx +4 -1
  60. package/src/file-item-2.tsx +408 -0
  61. package/src/file-item.tsx +215 -47
  62. package/src/header-layout.tsx +11 -0
  63. package/src/html-styles.ts +21 -4
  64. package/src/icons/ast/ActivityFilled.ts +8 -0
  65. package/src/icons/ast/AiAssistantFilled.ts +8 -0
  66. package/src/icons/ast/Back.ts +1 -1
  67. package/src/icons/ast/CollapseAllHorizontal.ts +8 -0
  68. package/src/icons/ast/DateRange.ts +1 -1
  69. package/src/icons/ast/EntityCreateFilled.ts +8 -0
  70. package/src/icons/ast/ExpandAllHorizontal.ts +8 -0
  71. package/src/icons/ast/ExtensionFiles.ts +1 -1
  72. package/src/icons/ast/FileUpload.ts +1 -1
  73. package/src/icons/ast/GlobeUnpublish.ts +8 -0
  74. package/src/icons/ast/HomeFilled.ts +8 -0
  75. package/src/icons/ast/KeyboardHideDown.ts +8 -0
  76. package/src/icons/ast/LayoutMultiple.ts +8 -0
  77. package/src/icons/ast/RicheditorHistoryRedo.ts +1 -1
  78. package/src/icons/ast/RicheditorHistoryUndo.ts +1 -1
  79. package/src/icons/ast/SearchFilled.ts +8 -0
  80. package/src/icons/ast/SettingsFilled.ts +8 -0
  81. package/src/icons/ast/SlideMenuFilled.ts +8 -0
  82. package/src/icons/ast/SwitchToMention.ts +8 -0
  83. package/src/icons/ast/SwitchToText.ts +8 -0
  84. package/src/icons/ast/SwitchToWidget.ts +8 -0
  85. package/src/icons/ast/TypeDate.ts +1 -1
  86. package/src/icons/ast/ViewGallery.ts +8 -0
  87. package/src/icons/ast/index.tsx +16 -0
  88. package/src/icons/react/ActivityFilled.tsx +13 -0
  89. package/src/icons/react/AiAssistantFilled.tsx +13 -0
  90. package/src/icons/react/CollapseAllHorizontal.tsx +13 -0
  91. package/src/icons/react/EntityCreateFilled.tsx +13 -0
  92. package/src/icons/react/ExpandAllHorizontal.tsx +13 -0
  93. package/src/icons/react/GlobeUnpublish.tsx +13 -0
  94. package/src/icons/react/HomeFilled.tsx +13 -0
  95. package/src/icons/react/KeyboardHideDown.tsx +13 -0
  96. package/src/icons/react/LayoutMultiple.tsx +13 -0
  97. package/src/icons/react/SearchFilled.tsx +13 -0
  98. package/src/icons/react/SettingsFilled.tsx +13 -0
  99. package/src/icons/react/SlideMenuFilled.tsx +13 -0
  100. package/src/icons/react/SwitchToMention.tsx +13 -0
  101. package/src/icons/react/SwitchToText.tsx +13 -0
  102. package/src/icons/react/SwitchToWidget.tsx +13 -0
  103. package/src/icons/react/ViewGallery.tsx +13 -0
  104. package/src/icons/react/index.tsx +16 -0
  105. package/src/icons/svg/activity-filled.svg +4 -0
  106. package/src/icons/svg/ai-assistant-filled.svg +3 -0
  107. package/src/icons/svg/back.svg +3 -1
  108. package/src/icons/svg/collapse-all-horizontal.svg +3 -0
  109. package/src/icons/svg/date-range.svg +3 -3
  110. package/src/icons/svg/entity-create-filled.svg +4 -0
  111. package/src/icons/svg/expand-all-horizontal.svg +3 -0
  112. package/src/icons/svg/extension/files.svg +3 -1
  113. package/src/icons/svg/file-upload.svg +3 -6
  114. package/src/icons/svg/globe-unpublish.svg +4 -0
  115. package/src/icons/svg/home-filled.svg +3 -0
  116. package/src/icons/svg/keyboard-hide-down.svg +1 -0
  117. package/src/icons/svg/layout-multiple.svg +3 -0
  118. package/src/icons/svg/richeditor/history/redo.svg +3 -2
  119. package/src/icons/svg/richeditor/history/undo.svg +3 -2
  120. package/src/icons/svg/search-filled.svg +3 -0
  121. package/src/icons/svg/settings-filled.svg +3 -0
  122. package/src/icons/svg/slide-menu-filled.svg +1 -0
  123. package/src/icons/svg/switch-to-mention.svg +3 -0
  124. package/src/icons/svg/switch-to-text.svg +4 -0
  125. package/src/icons/svg/switch-to-widget.svg +4 -0
  126. package/src/icons/svg/type/date.svg +3 -2
  127. package/src/icons/svg/view-gallery.svg +4 -0
  128. package/src/images-gallery/images-gallery-fall-through-provider.tsx +11 -0
  129. package/src/images-gallery/images-gallery.tsx +257 -0
  130. package/src/images-gallery/zoom.tsx +123 -0
  131. package/src/images-gallery-2/images-gallery-2.tsx +442 -0
  132. package/src/images-gallery-2/slide-buttons.tsx +230 -0
  133. package/src/images-gallery-2/zoom.tsx +35 -0
  134. package/src/is-in-popup.ts +1 -1
  135. package/src/kbd.tsx +46 -0
  136. package/src/layout-styles.ts +21 -1
  137. package/src/lists/actions-menu-row-surface.tsx +3 -1
  138. package/src/lists/list-row-surface.tsx +7 -1
  139. package/src/mobile-keyboard-aware-popup.tsx +87 -0
  140. package/src/mobile-styles.ts +8 -0
  141. package/src/modal-menu/contexts/modal-menu-context.tsx +22 -0
  142. package/src/modal-menu/contexts/modal-submenu-context.tsx +16 -0
  143. package/src/modal-menu/index.tsx +14 -0
  144. package/src/modal-menu/modal-menu-content.tsx +68 -0
  145. package/src/modal-menu/modal-menu-group.tsx +10 -0
  146. package/src/modal-menu/modal-menu-item.tsx +45 -0
  147. package/src/modal-menu/modal-menu-label.tsx +24 -0
  148. package/src/modal-menu/modal-menu-root.tsx +57 -0
  149. package/src/modal-menu/modal-menu-separator.tsx +14 -0
  150. package/src/modal-menu/modal-menu-trigger.tsx +54 -0
  151. package/src/modal-menu/modal-submenu-content.tsx +49 -0
  152. package/src/modal-menu/modal-submenu-trigger.tsx +34 -0
  153. package/src/modal-menu/modal-submenu.tsx +17 -0
  154. package/src/notification-dot.tsx +16 -7
  155. package/src/online-users.tsx +3 -3
  156. package/src/palette.ts +1 -1
  157. package/src/platform-symbols.ts +9 -0
  158. package/src/platform.ts +2 -0
  159. package/src/popover/index.tsx +710 -0
  160. package/src/popover/mobile-popup-context.tsx +29 -0
  161. package/src/{popup → popover}/modifiers.tsx +25 -25
  162. package/src/{popup → popover}/popup-modifiers-context.ts +3 -3
  163. package/src/reactions/reaction-button.tsx +342 -135
  164. package/src/reactions/reaction-picker.tsx +51 -44
  165. package/src/reactions/reactions.tsx +11 -1
  166. package/src/reactions/tootltip.tsx +55 -0
  167. package/src/rich-text-embed-error.tsx +2 -1
  168. package/src/select/components/group-heading.tsx +8 -0
  169. package/src/select/components/menu-list-virtualized.tsx +6 -2
  170. package/src/select/components/option.tsx +4 -0
  171. package/src/select/index.tsx +8 -4
  172. package/src/select/select-control-settings-context.tsx +11 -2
  173. package/src/select/select-in-popover.tsx +30 -39
  174. package/src/select/styles.ts +21 -1
  175. package/src/tab-nav/tab-nav.tsx +36 -10
  176. package/src/toast/primitives.tsx +8 -1
  177. package/src/toggle-button/round-toggle-button.tsx +23 -0
  178. package/src/toggle-button/toggle-button-group.tsx +34 -0
  179. package/src/toggle-button/toggle-button.tsx +164 -0
  180. package/src/toggle.tsx +11 -3
  181. package/src/tooltip.tsx +29 -20
  182. package/src/type-badge.tsx +5 -2
  183. package/src/unit/styles.ts +2 -2
  184. package/src/unit/unit-with-tooltip.tsx +1 -1
  185. package/src/use-is-phone.tsx +50 -4
  186. package/src/use-is-support-hover.ts +1 -1
  187. package/src/use-on-screen-keyboard-data.tsx +66 -0
  188. package/src/workflow-progress-icon.tsx +16 -22
  189. package/src/popup/index.tsx +0 -383
  190. package/src/shortcut-badge.tsx +0 -29
  191. /package/src/{popup → popover}/popup-stack-context.tsx +0 -0
  192. /package/src/{popup → popover}/reference.ts +0 -0
  193. /package/src/{popup → popover}/styles.ts +0 -0
  194. /package/src/{popup → popover}/use-click-outside.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @fibery/ui-kit
2
2
 
3
+ ## 1.38.1
4
+
5
+ ### Patch Changes
6
+
7
+ - db89c7a: Add Gallery View
8
+ - 00766c6: switch to pnpm catalogs. Update to latest
9
+ - Updated dependencies [00766c6]
10
+ - @fibery/emoji-data@2.7.1
11
+ - @fibery/helpers@1.3.4
12
+ - @fibery/react@1.4.5
13
+
14
+ ## 1.38.0
15
+
16
+ ### Minor Changes
17
+
18
+ - ab88ef9: add ToggleButtonGroupXComponent
19
+
20
+ ### Patch Changes
21
+
22
+ - ece31af: Move expression-utils to typescript
23
+ - d5b2a71: add RoundTogleButton
24
+ - 0d0530a: add ToggleButton component
25
+ - Updated dependencies [586c255]
26
+ - @fibery/emoji-data@2.7.0
27
+
3
28
  ## 1.37.0
4
29
 
5
30
  ### Minor Changes
@@ -1 +1 @@
1
- import "@testing-library/jest-dom/extend-expect";
1
+ import "@testing-library/jest-dom";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.37.0",
3
+ "version": "1.38.1",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "dependencies": {
@@ -12,6 +12,8 @@
12
12
  "@radix-ui/react-dropdown-menu": "2.1.1",
13
13
  "@radix-ui/react-navigation-menu": "1.2.1",
14
14
  "@radix-ui/react-toast": "1.2.2",
15
+ "@radix-ui/react-toggle": "1.1.10",
16
+ "@radix-ui/react-toggle-group": "1.1.11",
15
17
  "@radix-ui/react-tooltip": "1.1.3",
16
18
  "@types/d3-shape": "^3.1.6",
17
19
  "@types/react-select-country-list": "^2.2.3",
@@ -29,6 +31,7 @@
29
31
  "lodash": "4.17.21",
30
32
  "md5": "2.2.1",
31
33
  "moment": "2.29.4",
34
+ "photoswipe": "5.4.4",
32
35
  "popper-max-size-modifier": "0.2.0",
33
36
  "prop-types": "15.7.2",
34
37
  "rc-input-number": "8.5.0",
@@ -36,6 +39,7 @@
36
39
  "react-color": "2.19.3",
37
40
  "react-day-picker": "8.1.4",
38
41
  "react-intersection-observer": "9.3.5",
42
+ "react-medium-image-zoom": "5.2.10",
39
43
  "react-popper": "2.3.0",
40
44
  "react-select": "5.3.2",
41
45
  "react-select-country-list": "2.2.1",
@@ -43,44 +47,44 @@
43
47
  "screenfull": "6.0.1",
44
48
  "tabbable": "5.2.1",
45
49
  "ua-parser-js": "1.0.39",
46
- "@fibery/emoji-data": "2.6.1",
47
- "@fibery/helpers": "1.3.3",
48
- "@fibery/react": "1.4.4"
50
+ "@fibery/emoji-data": "2.7.1",
51
+ "@fibery/helpers": "1.3.4",
52
+ "@fibery/react": "1.4.5"
49
53
  },
50
54
  "peerDependencies": {
51
- "react": "^18.2.0",
52
- "react-dom": "^18.2.0"
55
+ "react": "18.2.0",
56
+ "react-dom": "18.2.0"
53
57
  },
54
58
  "devDependencies": {
55
- "@babel/core": "7.23.9",
56
- "@babel/runtime": "7.23.9",
57
- "@wyw-in-js/babel-preset": "0.5.5",
59
+ "@babel/core": "7.28.5",
60
+ "@babel/runtime": "7.28.4",
58
61
  "@testing-library/dom": "8.19.1",
59
- "@testing-library/jest-dom": "5.16.5",
62
+ "@testing-library/jest-dom": "6.9.1",
60
63
  "@testing-library/react": "13.4.0",
61
64
  "@testing-library/user-event": "13.5.0",
62
65
  "@types/chroma-js": "2.1.3",
63
66
  "@types/color-hash": "1.0.2",
64
67
  "@types/history": "4.7.11",
65
68
  "@types/invariant": "2.2.34",
66
- "@types/jest": "27.0.1",
69
+ "@types/jest": "30.0.0",
67
70
  "@types/lodash": "4.14.172",
68
71
  "@types/prop-types": "15.7.5",
69
72
  "@types/react": "18.0.26",
70
73
  "@types/react-dom": "18.0.10",
74
+ "@wyw-in-js/babel-preset": "0.5.5",
71
75
  "csstype": "3.0.8",
72
76
  "fs-extra": "10.0.0",
73
77
  "glob": "7.1.7",
74
- "jest": "27.5.1",
75
- "jest-junit": "13.0.0",
78
+ "jest": "30.2.0",
79
+ "jest-junit": "16.0.0",
76
80
  "react": "18.2.0",
77
81
  "react-dom": "18.2.0",
78
82
  "svg-parser": "2.0.4",
79
83
  "svgo": "2.8.0",
80
- "typescript": "5.8.3",
84
+ "typescript": "5.9.3",
81
85
  "unist-util-reduce": "0.2.2",
82
- "@fibery/babel-preset": "7.4.0",
83
- "@fibery/eslint-config": "8.6.1"
86
+ "@fibery/babel-preset": "7.4.1",
87
+ "@fibery/eslint-config": "8.6.2"
84
88
  },
85
89
  "jest": {
86
90
  "testEnvironment": "jsdom",
@@ -0,0 +1,42 @@
1
+ const preservedNameEmojis = ["🤖", "👻", "🧮", "🪄", "🔌", "🔗", "🔒"];
2
+
3
+ export const abbreviateName = (name?: string | null) => {
4
+ if (name === undefined) {
5
+ return "…";
6
+ }
7
+ if (name && preservedNameEmojis.includes(name)) {
8
+ return name;
9
+ }
10
+
11
+ // Check if name looks like "Surname, Name"
12
+ const hasComma = (name || "").includes(",");
13
+
14
+ const cleaned = (name || "")
15
+ .replace(/\p{Emoji_Presentation}|\p{Emoji}\uFE0F?/gu, "") // remove emojis
16
+ .replace(/[^\p{L}\p{M}'\-\s,]+/gu, " ") // keep comma to detect order before removing
17
+ .trim();
18
+
19
+ // Split on whitespace and commas
20
+ let parts = cleaned.split(/[,\s]+/).filter(Boolean);
21
+
22
+ // If there was a comma, assume it's "Surname, Name" and reverse
23
+ if (hasComma && parts.length > 1) {
24
+ parts = parts.reverse();
25
+ }
26
+ let result: string;
27
+ if (parts.length === 1) {
28
+ // Take the first two codepoints (safer for emoji / astral / surrogate pairs)
29
+ result = [...parts[0]].slice(0, 2).join("");
30
+ } else {
31
+ // Take the first codepoint of the first two parts and upper-case
32
+ result = parts
33
+ .slice(0, 2)
34
+ .map((p) => {
35
+ const first = [...p][0] || "";
36
+ return first.toUpperCase();
37
+ })
38
+ .join("");
39
+ }
40
+
41
+ return result || "?";
42
+ };
@@ -2,14 +2,17 @@ import {stopPropagation} from "@fibery/react/src/stop-propagation";
2
2
  import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
3
3
  import {css, cx} from "@linaria/core";
4
4
  import {FunctionComponent, memo, ReactNode, useId} from "react";
5
- import {border, colors, layout, lineHeight, space, textStyles, themeVars} from "../design-system";
5
+ import {border, colors, layout, layoutVars, lineHeight, space, textStyles, themeVars} from "../design-system";
6
6
  import CheckedIcon from "../icons/react/Checked";
7
7
  import {IconBaseProps} from "../icons/types";
8
8
  import {Spinner} from "../loaders";
9
- import {ShortcutBadge} from "../shortcut-badge";
9
+ import {mobileRootSelector} from "../mobile-styles";
10
+ import {Kbd, KbdGroup} from "../kbd";
10
11
  import {useTheme} from "../theme-provider";
12
+ import {useIsPhone} from "../use-is-phone";
11
13
  import {useActionsMenuContext} from "./contexts/actions-menu-context";
12
14
  import {useActiveDangerousRow, useSetActiveDangerousRow} from "./contexts/actions-menu-dangerous-rows";
15
+ import _ from "lodash";
13
16
 
14
17
  const hiddenTextCss = css`
15
18
  visibility: hidden;
@@ -31,8 +34,11 @@ const descriptionCss = css`
31
34
  const rowCss = css`
32
35
  display: flex;
33
36
  align-items: center;
34
- min-height: ${layout.menuItemHeight}px;
37
+ min-height: ${layoutVars.menuItemHeight};
35
38
  gap: ${space.s6}px;
39
+ ${mobileRootSelector} & {
40
+ gap: ${space.s8}px;
41
+ }
36
42
  `;
37
43
 
38
44
  const dangerousItemCss = css`
@@ -85,22 +91,35 @@ const firstLetterToLowerCase = (str: string) => {
85
91
  return str.charAt(0).toLowerCase() + str.slice(1);
86
92
  };
87
93
 
88
- const RightCorner = ({shortcut, loading, checked}: {shortcut?: string; loading?: boolean; checked?: boolean}) => {
94
+ const RightCorner = ({shortcut, loading, checked}: {shortcut?: string[]; loading?: boolean; checked?: boolean}) => {
89
95
  const theme = useTheme();
96
+ const isPhone = useIsPhone();
97
+
98
+ const shortcuts = _.isArray(shortcut) ? shortcut : shortcut ? [shortcut] : null;
90
99
 
91
- return loading || shortcut || checked ? (
100
+ return loading || shortcuts || checked ? (
92
101
  <div className={cornerCss}>
93
102
  {loading ? <Spinner color={theme.primary} iconSize={16} containerSize={16} /> : null}
94
- {shortcut ? <ShortcutBadge shortcut={shortcut} /> : null}
95
- {checked ? <CheckedIcon iconSize={18} color={themeVars.textColor} /> : null}
103
+ {shortcuts ? (
104
+ <KbdGroup>
105
+ {shortcuts.map((item) => (
106
+ <Kbd key={item}>{item}</Kbd>
107
+ ))}
108
+ </KbdGroup>
109
+ ) : null}
110
+ {checked ? <CheckedIcon iconSize={isPhone ? 20 : 18} color={themeVars.textColor} /> : null}
96
111
  </div>
97
112
  ) : null;
98
113
  };
99
114
 
100
- const LeftCorner = ({Icon}: {Icon: FunctionComponent<IconBaseProps> | void}) => {
115
+ const LeftCorner = ({Icon, iconColor}: {Icon: FunctionComponent<IconBaseProps> | void; iconColor?: string}) => {
116
+ const isPhone = useIsPhone();
101
117
  return Icon ? (
102
118
  <div className={cornerCss}>
103
- <Icon color={`var(--actions-menu-item-icon-color, ${themeVars.iconColor})`} iconSize={18} />
119
+ <Icon
120
+ color={`var(--actions-menu-item-icon-color, ${iconColor ?? themeVars.iconColor})`}
121
+ iconSize={isPhone ? 20 : 18}
122
+ />
104
123
  </div>
105
124
  ) : null;
106
125
  };
@@ -115,8 +134,9 @@ export type ActionsMenuItemProps = {
115
134
  description?: string;
116
135
  /** Icon's component */
117
136
  Icon?: FunctionComponent<IconBaseProps>;
137
+ iconColor?: string;
118
138
  /** Hint for keyboard shortcut for this action */
119
- shortcut?: string;
139
+ shortcut?: string[];
120
140
  disabled?: boolean;
121
141
  /** Whether the action requires confirmation */
122
142
  dangerous?: boolean;
@@ -135,6 +155,7 @@ const ActionsMenuItemComponent: React.FC<
135
155
  onSelect,
136
156
  description,
137
157
  Icon,
158
+ iconColor,
138
159
  shortcut,
139
160
  disabled,
140
161
  dangerous,
@@ -180,7 +201,7 @@ const ActionsMenuItemComponent: React.FC<
180
201
  )}
181
202
  disabled={disabled}
182
203
  >
183
- <LeftCorner Icon={Icon} />
204
+ <LeftCorner Icon={Icon} iconColor={iconColor} />
184
205
  <div className={rowContentCss}>
185
206
  {dangerous && (
186
207
  <div className={cx(hiddenTextCss, showWarning && visibleTextCss)}>
@@ -5,6 +5,7 @@ import {ContentProps} from "../dropdown-menu";
5
5
  export type ActionsMenuProps = {
6
6
  /** Clicking on this node will open actions menu. Make sure that trigger forwards ref AND props to html element */
7
7
  trigger: ReactNode;
8
+ triggerEvent?: "click" | "context-menu";
8
9
  children: ReactNode;
9
10
  align?: ContentProps["align"];
10
11
  disabled?: boolean;
@@ -22,6 +23,10 @@ export type ActionsMenuProps = {
22
23
  sideOffset?: number;
23
24
  collisionPadding?: number;
24
25
  autoFocusOnClose?: boolean;
26
+ /** Show menu in a modal on mobile */
27
+ supportsMobile?: boolean;
28
+ mobileTitle?: string;
29
+ height?: string;
25
30
  onEscapeKeyDown?: (event: KeyboardEvent) => void;
26
31
  sticky?: DropdownMenuContentProps["sticky"];
27
32
  };
@@ -16,6 +16,7 @@ import {ActionsMenuSubMenu} from "./actions-menu-sub-menu";
16
16
  import {useActionsMenuContext} from "./contexts/actions-menu-context";
17
17
  // eslint-disable-next-line no-restricted-imports
18
18
  import {IconBaseProps} from "@fibery/ui-kit/src/icons/types";
19
+ import {useIsPhone} from "../use-is-phone";
19
20
 
20
21
  const [ActionsMenuSubCommandMenuProvider, useActionsMenuSubCommandMenuCtx] = createContext<{
21
22
  subMenuOpenRef: RefObject<boolean>;
@@ -113,18 +114,20 @@ export const ActionsMenuSubCommandMenuRoot: React.FC<React.ComponentPropsWithout
113
114
 
114
115
  type ActionsMenuSubCommandMenuInputProps = React.ComponentPropsWithoutRef<typeof CommandMenuInput>;
115
116
  export const ActionsMenuSubCommandMenuInput: React.FC<ActionsMenuSubCommandMenuInputProps> = ({
116
- autoFocus = true,
117
+ autoFocus,
117
118
  onKeyDown,
118
119
  ...otherProps
119
120
  }) => {
121
+ const isPhone = useIsPhone();
120
122
  const inputRef = useRef<HTMLInputElement | null>(null);
123
+ const shouldFocus = autoFocus ?? (isPhone ? false : true);
121
124
 
122
125
  useEffect(() => {
123
126
  // html autofocus doesn't work reliably in Context Menu
124
- if (autoFocus) {
127
+ if (shouldFocus) {
125
128
  setTimeout(() => inputRef.current?.focus(), 0);
126
129
  }
127
- }, [autoFocus]);
130
+ }, [shouldFocus]);
128
131
 
129
132
  return (
130
133
  <CommandMenuInput
@@ -136,7 +139,7 @@ export const ActionsMenuSubCommandMenuInput: React.FC<ActionsMenuSubCommandMenuI
136
139
  }
137
140
  onKeyDown?.(e);
138
141
  }}
139
- autoFocus={autoFocus}
142
+ autoFocus={shouldFocus}
140
143
  {...otherProps}
141
144
  />
142
145
  );
@@ -6,6 +6,7 @@ import ArrowRight from "../icons/react/ArrowRight";
6
6
  import {useActionsMenuContext} from "./contexts/actions-menu-context";
7
7
  // eslint-disable-next-line no-restricted-imports
8
8
  import {IconBaseProps} from "@fibery/ui-kit/src/icons/types";
9
+ import {useIsPhone} from "../use-is-phone";
9
10
 
10
11
  const subTriggerClass = css`
11
12
  display: flex;
@@ -24,6 +25,7 @@ type Props = React.PropsWithChildren<{
24
25
  open?: boolean;
25
26
  onOpenChange?: (v: boolean) => void;
26
27
  disabled?: boolean;
28
+ mobileTitle?: string;
27
29
  contentClassName?: string;
28
30
  container?: HTMLElement | null;
29
31
  Icon?: FunctionComponent<IconBaseProps>;
@@ -37,9 +39,11 @@ const cornerCss = css`
37
39
  `;
38
40
 
39
41
  const LeftCorner = ({Icon}: {Icon: FunctionComponent<IconBaseProps> | void}) => {
42
+ const isPhone = useIsPhone();
43
+
40
44
  return Icon ? (
41
45
  <div className={cornerCss}>
42
- <Icon color={`var(--actions-menu-item-icon-color, ${themeVars.iconColor})`} iconSize={18} />
46
+ <Icon color={`var(--actions-menu-item-icon-color, ${themeVars.iconColor})`} iconSize={isPhone ? 20 : 18} />
43
47
  </div>
44
48
  ) : null;
45
49
  };
@@ -55,14 +59,16 @@ export const ActionsMenuSubMenu: React.FC<Props> = ({
55
59
  triggerRef,
56
60
  children,
57
61
  disabled = false,
62
+ mobileTitle = trigger,
58
63
  contentClassName,
59
64
  Icon,
60
65
  onMouseEnter,
61
66
  }) => {
67
+ const isPhone = useIsPhone();
62
68
  const {MenuPrimitive, container} = useActionsMenuContext();
63
69
 
64
70
  return (
65
- <MenuPrimitive.Sub open={open} onOpenChange={onOpenChange}>
71
+ <MenuPrimitive.Sub title={mobileTitle} open={open} onOpenChange={onOpenChange}>
66
72
  <MenuPrimitive.SubTrigger
67
73
  ref={triggerRef}
68
74
  className={subTriggerClass}
@@ -75,7 +81,7 @@ export const ActionsMenuSubMenu: React.FC<Props> = ({
75
81
  <div className={arrowClass}>
76
82
  <ArrowRight
77
83
  color={disabled ? themeVars.disabledTextColor : themeVars.iconColor}
78
- containerSize={10}
84
+ containerSize={isPhone ? 20 : 10}
79
85
  aria-hidden
80
86
  />
81
87
  </div>
@@ -1,11 +1,14 @@
1
1
  import {useControllableState} from "@fibery/react/src/use-controllable-state";
2
2
  import {css, cx} from "@linaria/core";
3
+ import {useIsPhone} from "../use-is-phone";
3
4
  import * as DropdownMenu from "../dropdown-menu";
5
+ import * as ModalMenu from "../modal-menu";
4
6
  import {ActionsMenuContextProvider} from "./contexts/actions-menu-context";
5
7
  import {ActionsMenuDangerousRowsProvider} from "./contexts/actions-menu-dangerous-rows";
6
8
  import {ActionsMenuProps} from "./actions-menu-props";
7
9
  import {preventDefault} from "@fibery/react/src/prevent-default";
8
10
  import {useState} from "react";
11
+ import {useFeatures} from "../features";
9
12
  import {ActionsMenuConfirmation, ActionsMenuConfirmationProps} from "./actions-menu-confirmation";
10
13
  import {useActionsMenuOpenStateTracker} from "./actions-menu-open-state-tracker";
11
14
 
@@ -20,6 +23,7 @@ const wideCss = css`
20
23
 
21
24
  export const ActionsMenu = ({
22
25
  trigger,
26
+ triggerEvent = "click",
23
27
  wide,
24
28
  align = "start",
25
29
  alignOffset,
@@ -36,8 +40,12 @@ export const ActionsMenu = ({
36
40
  contentStyles,
37
41
  modal,
38
42
  autoFocusOnClose,
43
+ supportsMobile,
44
+ mobileTitle,
39
45
  sticky,
46
+ height,
40
47
  }: ActionsMenuProps): JSX.Element => {
48
+ const features = useFeatures<{enableMobileMenus: boolean}>();
41
49
  const {onMenuOpenChange} = useActionsMenuOpenStateTracker();
42
50
  const [isOpen = false, setOpen] = useControllableState({
43
51
  value: open,
@@ -48,26 +56,37 @@ export const ActionsMenu = ({
48
56
  onMenuOpenChange(value);
49
57
  },
50
58
  });
59
+ const isPhone = useIsPhone();
51
60
  const [confirmation, setConfirmation] = useState<ActionsMenuConfirmationProps | null>(null);
52
61
 
62
+ const MenuPrimitive = isPhone && features.enableMobileMenus && supportsMobile ? ModalMenu : DropdownMenu;
63
+
53
64
  return (
54
65
  <ActionsMenuContextProvider
55
66
  setOpen={setOpen}
56
67
  showConfirmation={setConfirmation}
57
- MenuPrimitive={DropdownMenu}
68
+ MenuPrimitive={MenuPrimitive}
58
69
  container={container}
59
70
  >
60
- <DropdownMenu.Root open={isOpen} onOpenChange={setOpen} modal={modal || false}>
61
- <DropdownMenu.Trigger
71
+ <MenuPrimitive.Root
72
+ open={isOpen}
73
+ height={height}
74
+ onOpenChange={setOpen}
75
+ modal={modal || false}
76
+ title={mobileTitle}
77
+ >
78
+ <MenuPrimitive.Trigger
62
79
  // backward compatibility for dropdown styles
63
- className={isOpen ? "actions-visible" : "actions-hidden"}
64
- onClick={preventDefaultAndStopPropagation}
80
+ className={isOpen ? "actions-visible" : "actions-hidden actions-visible-on-mobile"}
81
+ onClick={triggerEvent === "click" ? preventDefaultAndStopPropagation : undefined}
82
+ onContextMenu={triggerEvent === "context-menu" ? preventDefaultAndStopPropagation : undefined}
83
+ triggerEvent={triggerEvent}
65
84
  disabled={disabled}
66
85
  asChild
67
86
  >
68
87
  {trigger}
69
- </DropdownMenu.Trigger>
70
- <DropdownMenu.Content
88
+ </MenuPrimitive.Trigger>
89
+ <MenuPrimitive.Content
71
90
  align={align}
72
91
  sticky={sticky}
73
92
  alignOffset={alignOffset}
@@ -81,8 +100,8 @@ export const ActionsMenu = ({
81
100
  onCloseAutoFocus={autoFocusOnClose ? undefined : preventDefault}
82
101
  >
83
102
  <ActionsMenuDangerousRowsProvider open={isOpen}>{children}</ActionsMenuDangerousRowsProvider>
84
- </DropdownMenu.Content>
85
- </DropdownMenu.Root>
103
+ </MenuPrimitive.Content>
104
+ </MenuPrimitive.Root>
86
105
 
87
106
  {confirmation ? <ActionsMenuConfirmation {...confirmation} /> : null}
88
107
  </ActionsMenuContextProvider>
@@ -2,6 +2,8 @@ import _ from "lodash";
2
2
  import {useControllableState} from "@fibery/react/src/use-controllable-state";
3
3
  import {ActionsMenuContextProvider} from "./contexts/actions-menu-context";
4
4
  import * as ContextMenu from "../context-menu";
5
+ import {ActionsMenu} from "./actions-menu";
6
+ import {useIsPhone} from "../use-is-phone";
5
7
  import {ActionsMenuDangerousRowsProvider} from "./contexts/actions-menu-dangerous-rows";
6
8
  import {ActionsMenuProps} from "./actions-menu-props";
7
9
  import {preventDefault} from "@fibery/react/src/prevent-default";
@@ -14,11 +16,31 @@ export const ContextActionsMenu = ({
14
16
  trigger,
15
17
  children,
16
18
  disabled,
19
+ mobileTitle,
20
+ supportsMobile,
17
21
  autoFocusOnClose = true,
18
22
  }: ActionsMenuProps): JSX.Element => {
23
+ const isPhone = useIsPhone();
19
24
  const [isOpen = false, setOpen] = useControllableState({value: open, onChange: onOpenChange});
20
25
  const [confirmation, setConfirmation] = useState<ActionsMenuConfirmationProps | null>(null);
21
26
 
27
+ // Special mode - on mobile, open context menu in modal
28
+ if (isPhone && supportsMobile) {
29
+ return (
30
+ <ActionsMenu
31
+ open={open}
32
+ onOpenChange={onOpenChange}
33
+ mobileTitle={mobileTitle}
34
+ supportsMobile
35
+ // Extra div is important here to not loose the original onClick handler
36
+ trigger={<div>{trigger}</div>}
37
+ triggerEvent="context-menu"
38
+ >
39
+ {children}
40
+ </ActionsMenu>
41
+ );
42
+ }
43
+
22
44
  return (
23
45
  <ActionsMenuContextProvider setOpen={setOpen} showConfirmation={setConfirmation} MenuPrimitive={ContextMenu}>
24
46
  <ContextMenu.Root onOpenChange={onOpenChange}>
@@ -1,6 +1,7 @@
1
1
  import {Children, forwardRef, ReactElement, ReactNode} from "react";
2
2
  import {css} from "@linaria/core";
3
3
  import {space} from "./design-system";
4
+ import {useIsPhone} from "./use-is-phone";
4
5
 
5
6
  const gridStyle = css`
6
7
  align-items: center;
@@ -12,10 +13,19 @@ type Props = {
12
13
  inline?: boolean;
13
14
  gap?: number;
14
15
  };
16
+
17
+ const getGap = (gap: number | undefined, isPhone: boolean) => {
18
+ if (gap !== undefined) {
19
+ return gap;
20
+ }
21
+ return isPhone ? 0 : space.s4;
22
+ };
23
+
15
24
  export const ActionsPanel = forwardRef<HTMLDivElement, Props>(function ActionsPanelComponent(
16
- {children, inline, gap = space.s4},
25
+ {children, inline, gap},
17
26
  ref
18
27
  ) {
28
+ const isPhone = useIsPhone();
19
29
  const childrenArray = Children.toArray(children);
20
30
  if (childrenArray.length === 0) {
21
31
  return null;
@@ -27,7 +37,7 @@ export const ActionsPanel = forwardRef<HTMLDivElement, Props>(function ActionsPa
27
37
  className={gridStyle}
28
38
  style={{
29
39
  display: inline ? "inline-grid" : "grid",
30
- gridGap: gap,
40
+ gridGap: getGap(gap, isPhone),
31
41
  gridAutoFlow: "column",
32
42
  }}
33
43
  >
@@ -1,16 +1,20 @@
1
1
  import {useSize} from "@fibery/react/src/use-size";
2
2
  import React, {ComponentProps, forwardRef, useEffect, useRef, useState} from "react";
3
3
  import {duration, transition} from "./design-system";
4
- import cx from "classnames";
5
4
  import _ from "lodash";
6
5
  import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
7
6
 
8
- interface AnimatedHeightContainerProps extends ComponentProps<"div"> {
7
+ type AnimatedHeightContainerProps = ComponentProps<"div"> & {
9
8
  noAnimation?: boolean;
10
- }
9
+ onTransitionStart?: (event: TransitionEvent) => void;
10
+ onTransitionEnd?: (event: TransitionEvent) => void;
11
+ };
11
12
 
12
13
  export const AnimatedHeightContainer = forwardRef<HTMLDivElement, AnimatedHeightContainerProps>(
13
- function AnimatedHeightContainerComponent({className, noAnimation = false, children, ...otherProps}, ref) {
14
+ function AnimatedHeightContainerComponent(
15
+ {className, noAnimation = false, onTransitionStart = _.noop, onTransitionEnd = _.noop, children, ...otherProps},
16
+ ref
17
+ ) {
14
18
  const measuredContainer = useRef<HTMLDivElement | null>(null);
15
19
  const size = useSize(measuredContainer);
16
20
 
@@ -60,6 +64,21 @@ export const AnimatedHeightContainer = forwardRef<HTMLDivElement, AnimatedHeight
60
64
  return cleanup;
61
65
  }, [animating]);
62
66
 
67
+ useEffect(() => {
68
+ const element = containerRef.current;
69
+ if (!element) {
70
+ return () => {};
71
+ }
72
+
73
+ element.addEventListener("transitionstart", onTransitionStart);
74
+ element.addEventListener("transitionend", onTransitionEnd);
75
+
76
+ return () => {
77
+ element.removeEventListener("transitionstart", onTransitionStart);
78
+ element.removeEventListener("transitionend", onTransitionEnd);
79
+ };
80
+ }, [onTransitionStart, onTransitionEnd]);
81
+
63
82
  const style = animating
64
83
  ? {
65
84
  ...otherProps.style,
@@ -74,7 +93,7 @@ export const AnimatedHeightContainer = forwardRef<HTMLDivElement, AnimatedHeight
74
93
  };
75
94
 
76
95
  return (
77
- <div ref={composedRef} className={cx(className)} {...otherProps} style={style}>
96
+ <div ref={composedRef} className={className} {...otherProps} style={style}>
78
97
  <div ref={measuredContainer}>{children}</div>
79
98
  </div>
80
99
  );
@@ -27,7 +27,7 @@ export const antUploadStyle = css`
27
27
  alignItems: "stretch",
28
28
  alignContent: "stretch",
29
29
  },
30
- "& .ant-upload.ant-upload-drag, .ant-upload.ant-upload-select-picture-card": {
30
+ "& .ant-upload:where(.ant-upload-drag), .ant-upload:where(.ant-upload-select-picture-card)": {
31
31
  border: `1px dashed ${themeVars.separator}`,
32
32
  position: "relative",
33
33
  backgroundColor: themeVars.transparent,
@@ -2,6 +2,7 @@ import {getWrapper} from "./get-wrapper";
2
2
  import Input from "antd/lib/input";
3
3
  import "antd/lib/input/style/css";
4
4
  import {css} from "@linaria/core";
5
+ import {mobileRootSelector} from "../mobile-styles";
5
6
  import {opacity, textStyles, themeVars} from "../design-system";
6
7
 
7
8
  import {inputStyles} from "./styles";
@@ -24,6 +25,9 @@ export const transparentInputStyle = css`
24
25
  transition: "none",
25
26
  ...textStyles.regular,
26
27
  ...textStyles.numeric.tabularOff,
28
+ [`${mobileRootSelector} &`]: {
29
+ ...textStyles.big,
30
+ },
27
31
  },
28
32
  "& textarea.ant-input": {
29
33
  fontVariant: "inherit",