@hortiview/shared-components 2.23.5 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [2.24.0](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/compare/v2.23.5...v2.24.0) (2026-04-30)
2
+
3
+ ### Features
4
+
5
+ * extend contextMenu and allowedIconButton for FAB display on mobile and tablet devices ([f194914](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/f1949142376af9f4ce12324b8bf7c074ef892ff3))
6
+
1
7
  ## [2.23.5](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/compare/v2.23.4...v2.23.5) (2026-04-27)
2
8
 
3
9
  ### Code Refactoring
package/README.md CHANGED
@@ -35,13 +35,13 @@ Additionally the library provides form components using [react-hook-form](https:
35
35
  1. [Filter](#filter)
36
36
  1. [FormattedNumberDisplay](#formattednumberdisplay)
37
37
  1. [FormComponents](#formcomponents)
38
- 1. [FormCheckBox](#formcheckbox)
39
- 1. [FormDatePicker](#formdatepicker)
40
- 1. [FormNumber](#formnumber)
41
- 1. [FormRadio](#formradio)
42
- 1. [FormSelect](#formselect)
43
- 1. [FormSlider](#formslider)
44
- 1. [FormText](#formtext)
38
+ 1. [FormCheckBox](#formcheckbox)
39
+ 1. [FormDatePicker](#formdatepicker)
40
+ 1. [FormNumber](#formnumber)
41
+ 1. [FormRadio](#formradio)
42
+ 1. [FormSelect](#formselect)
43
+ 1. [FormSlider](#formslider)
44
+ 1. [FormText](#formtext)
45
45
  1. [GenericTable](#generictable)
46
46
  1. [HashTabView](#hashtabview)
47
47
  1. [HeaderFilter](#headerfilter)
@@ -202,6 +202,32 @@ const actions = [
202
202
  <ContextMenu actions={actions} />;
203
203
  ```
204
204
 
205
+ #### Render ContextMenu as a Floating Action Button (FAB)
206
+
207
+ On mobile and tablet devices, you can display the context menu as a floating action button (FAB) by setting
208
+ `isFloatingActionButtonOnMobile` to true. This removes the context menu from the DOM and positions it
209
+ statically in the bottom right corner of the screen.
210
+
211
+ You can control the offset from the screen edges using `floatingButtonScreenOffsetType`, which includes
212
+ presets for full-screen implementations and screens with a BottomAppBar.
213
+
214
+ To prevent the FAB from blocking any elements in the rendered view, provide a DOM element selector to
215
+ `floatingButtonPaddingInsertTarget`. A spacer element will be attached to this target, increasing its
216
+ height by the appropriate amount.
217
+
218
+ ```jsx
219
+ import { ContextMenu } from '@hortiview/shared-components';
220
+
221
+ const actions = [...];
222
+
223
+ <ContextMenu
224
+ actions={actions}
225
+ isFloatingActionButtonOnMobile={true}
226
+ floatingButtonScreenOffsetType={'buttonAppBar'}
227
+ floatingButtonPaddingInsertTarget={'#layout-container'}
228
+ />;
229
+ ```
230
+
205
231
  ### DeleteModal
206
232
 
207
233
  A modal to confirm a deletion.
@@ -1098,6 +1124,12 @@ function BlockActions({ blockId, farmId }) {
1098
1124
  }
1099
1125
  ```
1100
1126
 
1127
+ #### Render AllowedIconButton as Floating Action Button (FAB)
1128
+
1129
+ On mobile and tablet devices, you can display the AllowedIconButton as a floating action button (FAB).
1130
+ For more information, see the [Context Menu documentation](#render-contextmenu-as-a-floating-action-button-fab),
1131
+ as both implementations are identical.
1132
+
1101
1133
  ### SafeForm
1102
1134
 
1103
1135
  A wrapper around a form that prevents accidental submission when pressing Enter in input fields.
@@ -1 +1 @@
1
- ._modal_lk11w_1 div[class*=lmnt-modal__surface]{max-height:100svh!important;max-width:100svw!important}._modal_lk11w_1 div:not([class*=mdc-dialog--fullscreen]) div[class*=lmnt-modal__surface]{border-radius:1rem!important}._modal_lk11w_1 footer{margin-bottom:0!important}._gap_lk11w_14{gap:4px!important;padding-left:.5rem!important}._title_lk11w_19{header{display:flex;justify-content:start;padding-inline:0!important;>div{width:100%;margin-left:0!important}}}._titleWithoutCloseIcon_lk11w_32{margin-left:1rem!important}._closeButton_lk11w_36{color:var(--lmnt-theme-on-surface-inactive)}._closeButton_lk11w_36:before{display:none!important}._closeButton_lk11w_36:hover{background-color:var(--lmnt-theme-surface-variant)!important}._errorBanner_lk11w_48{width:100%}._headerActions_lk11w_52{margin-left:.5rem}
1
+ ._modal_1l955_1{z-index:75}._modal_1l955_1 div[class*=lmnt-modal__surface]{max-height:100svh!important;max-width:100svw!important}._modal_1l955_1 div:not([class*=mdc-dialog--fullscreen]) div[class*=lmnt-modal__surface]{border-radius:1rem!important}._modal_1l955_1 footer{margin-bottom:0!important}._gap_1l955_18{gap:4px!important;padding-left:.5rem!important}._title_1l955_23{header{display:flex;justify-content:start;padding-inline:0!important;>div{width:100%;margin-left:0!important}}}._titleWithoutCloseIcon_1l955_36{margin-left:1rem!important}._closeButton_1l955_40{color:var(--lmnt-theme-on-surface-inactive)}._closeButton_1l955_40:before{display:none!important}._closeButton_1l955_40:hover{background-color:var(--lmnt-theme-surface-variant)!important}._errorBanner_1l955_52{width:100%}._headerActions_1l955_56{margin-left:.5rem}
@@ -0,0 +1 @@
1
+ ._menu_1bdpq_1{width:15.875rem;border-radius:.5rem!important}._icon_1bdpq_6{color:var(--lmnt-theme-on-secondary-inactive)}._listItem_1bdpq_10 [class*=mdc-list-item__start]{color:var(--lmnt-theme-on-secondary-inactive);align-self:center!important;margin:0 1rem!important}._listItem_1bdpq_10 [class*=mdc-list-item__end]{color:var(--lmnt-theme-on-secondary-inactive)}._offlineViewMargin_1bdpq_20{margin:.5rem}._fabContainer_1bdpq_24{position:fixed;z-index:51}._fabContainer_1bdpq_24._buttonAppBar_1bdpq_29{bottom:5.4rem;right:1rem}._fabContainer_1bdpq_24._fullScreen_1bdpq_34{bottom:1.5rem;right:1rem}._fabIcon_1bdpq_39{box-shadow:0 4px 10px #0000004d}._fabMenu_1bdpq_43{margin-bottom:.5rem}._scrim_1bdpq_47{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:#0000004d;z-index:50;opacity:0;pointer-events:none;transition:opacity .15s ease-in}._scrimVisible_1bdpq_60{opacity:1;pointer-events:auto}._fabSpacer_1bdpq_65{height:5.2rem}
@@ -1,5 +1,7 @@
1
1
  import { IconButtonProps } from '@element-public/react-icon-button';
2
+ import { FloatingActionButtonProps } from '../ContextMenu/ContextMenu';
2
3
  import { IsAllowedProps } from '../PermissionChecks/PermissionService';
4
+ import { default as React } from 'react';
3
5
 
4
6
  export type AllowedIconButtonProps = Omit<IconButtonProps & React.HTMLProps<HTMLButtonElement>, 'variant' | 'iconType' | 'iconSize' | 'size'> & {
5
7
  /**
@@ -34,10 +36,21 @@ export type AllowedIconButtonProps = Omit<IconButtonProps & React.HTMLProps<HTML
34
36
  * Default: 'medium'
35
37
  */
36
38
  size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
37
- };
39
+ } & FloatingActionButtonProps;
38
40
  /**
39
- * A permission-gated icon button component that only renders when the user has the required permissions.
40
- * @param {AllowedIconButtonProps} props - Component properties
41
- * @returns {JSX.Element | null} The icon button component or null if permissions are not met
41
+ * Renders an icon button only when the required permissions are granted.
42
+ *
43
+ * Supports optional mobile floating action button behavior through
44
+ * `FloatingActionButtonProps` -> {@link FloatingActionButtonProps}
45
+ *
46
+ * @param props Component props.
47
+ * @param props.data-testid Test id suffix used to generate the rendered button test id.
48
+ * @param props.entityIds Entity id or ids used for permission evaluation.
49
+ * @param props.formId Optional form id used to associate the button with a form.
50
+ * @param props.variant Visual variant of the icon button.
51
+ * @param props.iconType Icon style variant.
52
+ * @param props.permissions Permissions required to render the button.
53
+ * @param props.size Size of the icon button.
54
+ * @returns The rendered icon button, or `null` when access is not allowed.
42
55
  */
43
- export declare const AllowedIconButton: ({ "data-testid": testId, variant, iconType, size, ...props }: AllowedIconButtonProps) => import("react/jsx-runtime").JSX.Element;
56
+ export declare const AllowedIconButton: (props: AllowedIconButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,23 +1,58 @@
1
- import { jsx as i } from "react/jsx-runtime";
2
- import { I as m } from "../../index.es-CkB4776y.js";
3
- import { Allowed as l } from "../Allowed/Allowed.js";
4
- const f = ({
5
- "data-testid": o,
6
- variant: e = "filled-primary",
7
- iconType: d = "filled",
8
- size: n = "medium",
9
- ...t
10
- }) => /* @__PURE__ */ i(l, { neededPermissions: t.permissions, entityIds: t.entityIds, children: /* @__PURE__ */ i(
11
- m,
12
- {
13
- "data-testid": `allowed-icon-button-${o}`,
14
- form: t.formId,
15
- iconSize: n,
16
- iconType: d,
17
- variant: e,
18
- ...t
19
- }
20
- ) });
1
+ import { jsx as e, jsxs as A, Fragment as S } from "react/jsx-runtime";
2
+ import { I as x } from "../../index.es-CkB4776y.js";
3
+ import { c as i } from "../../index-Df2FYN-K.js";
4
+ import { createPortal as d } from "react-dom";
5
+ import { useBreakpoints as P } from "../../hooks/useBreakpoints.js";
6
+ import { Allowed as T } from "../Allowed/Allowed.js";
7
+ import { s as t } from "../../contextMenu.module-RWPDK7Ds.js";
8
+ import { useMemo as h } from "react";
9
+ const O = (c) => {
10
+ const {
11
+ "data-testid": l,
12
+ variant: m = "filled-primary",
13
+ iconType: f = "filled",
14
+ size: p = "medium",
15
+ permissions: u,
16
+ entityIds: b,
17
+ formId: B,
18
+ isFloatingActionButtonOnMobile: g = !1,
19
+ floatingButtonPaddingInsertTarget: o,
20
+ floatingButtonScreenOffsetType: n = "buttonAppBar",
21
+ ...I
22
+ } = c, { isDesktop: y } = P(), r = g && !y, a = /* @__PURE__ */ e(
23
+ x,
24
+ {
25
+ className: i(r && t.fabIcon),
26
+ "data-testid": `allowed-icon-button-${l}`,
27
+ form: B,
28
+ iconSize: r ? "xlarge" : p,
29
+ iconType: f,
30
+ variant: m,
31
+ ...I
32
+ }
33
+ ), s = h(() => o ? document.querySelector(o) : null, [o]);
34
+ return /* @__PURE__ */ e(T, { neededPermissions: u, entityIds: b, children: r ? /* @__PURE__ */ A(S, { children: [
35
+ d(
36
+ /* @__PURE__ */ e(
37
+ "div",
38
+ {
39
+ className: i(
40
+ t.fabContainer,
41
+ n === "buttonAppBar" && t.buttonAppBar,
42
+ n === "fullScreen" && t.fullScreen
43
+ ),
44
+ "data-testid": "fab-container",
45
+ children: a
46
+ }
47
+ ),
48
+ document.body
49
+ ),
50
+ s && d(
51
+ /* @__PURE__ */ e("div", { className: t.fabSpacer, "data-testid": "fab-spacer" }),
52
+ s
53
+ )
54
+ ] }) : a });
55
+ };
21
56
  export {
22
- f as AllowedIconButton
57
+ O as AllowedIconButton
23
58
  };
@@ -1,9 +1,9 @@
1
1
  import { jsx as e, Fragment as d, jsxs as c } from "react/jsx-runtime";
2
2
  import { D as V } from "../../index.es-C27R5Xae.js";
3
- import { G as l } from "../../index.es-Z0aF-7Cn.js";
3
+ import { G as s } from "../../index.es-Z0aF-7Cn.js";
4
4
  import { P as $ } from "../../index.es-DgncYOqO.js";
5
5
  import { c as w } from "../../index-Df2FYN-K.js";
6
- import { useMemo as n } from "react";
6
+ import { useMemo as l } from "react";
7
7
  import { BasicHeading as v } from "../BasicHeading/BasicHeading.js";
8
8
  import { EmptyView as F } from "../EmptyView/EmptyView.js";
9
9
  import { ListArea as M } from "../ListArea/ListArea.js";
@@ -32,7 +32,7 @@ import '../../assets/BaseView.css';const z = "_fullHeight_1yui7_1", J = "_fullWi
32
32
  listMaxHeight: H = "calc(100vh - 220px)",
33
33
  titleLevel: N = 5,
34
34
  detailTitleLevel: W,
35
- pathname: s,
35
+ pathname: r,
36
36
  basicHeadingIcon: B,
37
37
  isOnline: p = !0,
38
38
  offlineViewProps: _,
@@ -41,19 +41,19 @@ import '../../assets/BaseView.css';const z = "_fullHeight_1yui7_1", J = "_fullWi
41
41
  searchPlaceholder: R,
42
42
  isGrouped: A = !1
43
43
  }) => {
44
- const { isDesktopNavbar: o, isDesktop: C } = P(), r = n(() => f.filter((a) => a.isAllowed !== !1), [f]), t = n(() => r.length === 1 ? r?.at(0) : r.find((a) => a.route === s), [s, r]), I = n(() => p ? t?.component ? t.component : o ? /* @__PURE__ */ e($, { className: i.fullWidth, children: /* @__PURE__ */ e(F, { title: h }) }) : /* @__PURE__ */ e(d, {}) : /* @__PURE__ */ e(T, { ..._, fullWidth: !0 }), [t?.component, h, o, p, _]), { showList: S, showDetail: E } = n(() => o ? { showList: !0, showDetail: !0 } : { showList: !t, showDetail: !!t }, [t, o]), { ref: j, hasScrolled: G } = q([
45
- s,
44
+ const { isDesktopNavbar: o, isDesktop: C } = P(), n = l(() => f.filter((a) => a.isAllowed !== !1), [f]), t = l(() => n.find((a) => a.route === r), [r, n]), I = l(() => p ? t?.component ? t.component : o ? /* @__PURE__ */ e($, { className: i.fullWidth, children: /* @__PURE__ */ e(F, { title: h }) }) : /* @__PURE__ */ e(d, {}) : /* @__PURE__ */ e(T, { ..._, fullWidth: !0 }), [t?.component, h, o, p, _]), { showList: S, showDetail: E } = l(() => o ? { showList: !0, showDetail: !0 } : { showList: !t, showDetail: !!t }, [t, o]), { ref: j, hasScrolled: G } = q([
45
+ r,
46
46
  o
47
47
  ]);
48
48
  return /* @__PURE__ */ c(
49
- l,
49
+ s,
50
50
  {
51
51
  "data-testid": "base-view-container",
52
52
  className: `${C ? i.fullHeight : ""} ${L ?? ""}`,
53
53
  fullWidth: !0,
54
54
  children: [
55
55
  S && /* @__PURE__ */ c(
56
- l,
56
+ s,
57
57
  {
58
58
  "data-testid": "show-list-container",
59
59
  gap: "none",
@@ -81,11 +81,11 @@ import '../../assets/BaseView.css';const z = "_fullHeight_1yui7_1", J = "_fullWi
81
81
  M,
82
82
  {
83
83
  hasLastItemNoRoundedEdges: g,
84
- elements: r,
84
+ elements: n,
85
85
  hasSearch: k,
86
86
  maxHeight: H,
87
87
  isSorted: y,
88
- pathname: s,
88
+ pathname: r,
89
89
  routerLinkElement: x,
90
90
  searchPlaceholder: R,
91
91
  isGrouped: A
@@ -95,7 +95,7 @@ import '../../assets/BaseView.css';const z = "_fullHeight_1yui7_1", J = "_fullWi
95
95
  }
96
96
  ),
97
97
  E && /* @__PURE__ */ c(
98
- l,
98
+ s,
99
99
  {
100
100
  "data-testid": "show-details-container",
101
101
  gap: "none",
@@ -118,7 +118,7 @@ import '../../assets/BaseView.css';const z = "_fullHeight_1yui7_1", J = "_fullWi
118
118
  ),
119
119
  G && /* @__PURE__ */ e(V, {}),
120
120
  /* @__PURE__ */ e(
121
- l,
121
+ s,
122
122
  {
123
123
  "data-testid": "base-view-show-details-scroll-component-group",
124
124
  fullWidth: !0,
@@ -28,6 +28,40 @@ type ContextMenuProps = {
28
28
  * Props forwarded to <OfflineView /> when offline.
29
29
  */
30
30
  offlineViewProps?: Partial<OfflineViewProps>;
31
+ /**
32
+ * If only one action is provided/allowed, trigger that action without opening the context menu
33
+ * @remarks Basically turns the component into an icon button
34
+ * @default false
35
+ */
36
+ isSingleActionTriggeredOnClick?: boolean;
37
+ /**
38
+ * If only one action is provided/allowed, show the icon of that action, instead of the default menu one
39
+ *
40
+ * @default true
41
+ */
42
+ hasActionIconForSingleAction?: boolean;
43
+ } & FloatingActionButtonProps;
44
+ export type FloatingActionButtonProps = {
45
+ /**
46
+ * Determines if the button should be rendered as a floating action button (FAB) on mobile devices.
47
+ * @default false
48
+ */
49
+ isFloatingActionButtonOnMobile?: boolean;
50
+ /**
51
+ * Specify a target element (query selector) to insert an extra padding element below.
52
+ * The padding element is used to make sure the FAB doesn't block any elements at the end of a view.
53
+ * @example '#layout-container'
54
+ * @remarks This padding attribute is not related to floatingButtonScreenOffsetType. The inserted padding is always the same size, slightly taller than the button height
55
+ */
56
+ floatingButtonPaddingInsertTarget?: string;
57
+ /**
58
+ * Determines the distance/offset of the floating action button from the bottom and right edge of the screen
59
+ * - 'buttonAppBar' adds offset for when the element ButtonAppBar is used, so the button stays slightly above the bar
60
+ * - 'fullScreen' adds offset for a full-screen view, with the button sitting slightly above the screen edges
61
+ * @default 'buttonAppBar'
62
+ * @remarks This padding attribute is not related to floatingButtonPaddingInsertTarget. The paddingType just controls the offset of the button to the screen edges.
63
+ */
64
+ floatingButtonScreenOffsetType?: 'buttonAppBar' | 'fullScreen';
31
65
  };
32
66
  export type ActionProps = ListItemProps & {
33
67
  closeOnClick?: boolean;
@@ -40,19 +74,31 @@ export type ActionProps = ListItemProps & {
40
74
  isAllowed?: boolean;
41
75
  };
42
76
  /**
77
+ * Renders action menu with optional permission-aware filtering, offline fallback,
78
+ * and mobile FAB presentation.
43
79
  *
44
- * @param {boolean} triggerOpen indicates if the menu should be open
45
- * @param {ListItemProps[]} actions list of actions to display in the context menu as ListItems
46
- * @recommended `primaryText`, `onClick`, `leadingBlock`
47
- * @requires `ListItemProps` from `@element-public/react-components`
80
+ * On mobile, the button can be rendered as a floating action button by enabling
81
+ * `isFloatingActionButtonOnMobile`. If exactly one allowed action is available,
82
+ * trigger can optionally execute the action directly and reuse the action icon.
83
+ *
84
+ * @param triggerOpen External open-state control. Leave blank for uncontrolled mode.
85
+ * @param actions Menu actions to render.
48
86
  * @example const actions = [
49
87
  * { primaryText: 'Open', onClick: () => openSomeModal(), leadingBlock: 'add' },
50
88
  * { primaryText: 'Delete', onClick: () => DeleteSomeThing(), leadingBlock: 'delete_outline'},
51
89
  * { primaryText: 'Edit', onClick: () => EditSomeThing(), leadingBlock: 'edit'},
52
90
  * ];
53
- * @param {boolean} isOnline Whether the user is online; shows OfflineView when false.
54
- * @param {OfflineViewProps} offlineViewProps Props forwarded to OfflineView when offline.
55
- * @returns a context menu with the given actions as ListItems
91
+ *
92
+ * @param iconOrientation Orientation of default menu icon.
93
+ * @param isOnline Whether menu should render actions or offline placeholder.
94
+ * @param offlineViewProps Props forwarded to `OfflineView`.
95
+ *
96
+ * @param isSingleActionTriggeredOnClick Whether single allowed action should execute immediately.
97
+ * @param hasActionIconForSingleAction Whether single allowed action icon should replace default button icon.
98
+ * @param isFloatingActionButtonOnMobile Whether to render the button as a FAB on mobile.
99
+ * @param floatingButtonPaddingInsertTarget Selector for optional spacer insertion target.
100
+ * @param floatingButtonScreenOffsetType FAB offset preset for bottom spacing.
101
+ * @returns Context menu trigger and menu content, or `null` when no allowed actions exist.
56
102
  */
57
- export declare const ContextMenu: ({ triggerOpen, actions, iconOrientation, "data-testid": dataTestId, isOnline, offlineViewProps, }: ContextMenuProps) => import("react/jsx-runtime").JSX.Element | null;
103
+ export declare const ContextMenu: ({ triggerOpen, actions, iconOrientation, "data-testid": dataTestId, isOnline, offlineViewProps, hasActionIconForSingleAction, isSingleActionTriggeredOnClick, isFloatingActionButtonOnMobile, floatingButtonPaddingInsertTarget, floatingButtonScreenOffsetType, }: ContextMenuProps) => import("react/jsx-runtime").JSX.Element | null;
58
104
  export {};
@@ -1,59 +1,74 @@
1
- import { jsx as t, jsxs as v } from "react/jsx-runtime";
2
- import { I } from "../../index.es-CkB4776y.js";
3
- import { M as p, L as w, a as m, b as C } from "../../index.es-Cg57snrN.js";
4
- import { useState as g, useCallback as f, useEffect as h, useMemo as k, Fragment as y } from "react";
5
- import { u as x } from "../../uniqueId-CJo-XRQb.js";
6
- import { OfflineView as L } from "../OfflineView/OfflineView.js";
7
- import '../../assets/ContextMenu.css';const V = "_menu_148i2_1", B = "_icon_148i2_6", N = "_listItem_148i2_10", b = "_offlineViewMargin_148i2_20", l = {
8
- menu: V,
9
- icon: B,
10
- listItem: N,
11
- offlineViewMargin: b
12
- }, q = ({
13
- triggerOpen: s = null,
14
- actions: o,
15
- iconOrientation: c = "vertical",
16
- "data-testid": u,
17
- isOnline: d = !0,
18
- offlineViewProps: _
1
+ import { jsx as i, jsxs as g, Fragment as _ } from "react/jsx-runtime";
2
+ import { I as z } from "../../index.es-CkB4776y.js";
3
+ import { M as F, L as V, a as h, b as $ } from "../../index.es-Cg57snrN.js";
4
+ import { c as o } from "../../index-Df2FYN-K.js";
5
+ import { useState as j, useCallback as x, useEffect as D, useMemo as d, Fragment as q } from "react";
6
+ import { createPortal as f } from "react-dom";
7
+ import { useBreakpoints as E } from "../../hooks/useBreakpoints.js";
8
+ import { OfflineView as T } from "../OfflineView/OfflineView.js";
9
+ import { s as t } from "../../contextMenu.module-RWPDK7Ds.js";
10
+ const X = ({
11
+ triggerOpen: c = null,
12
+ actions: u,
13
+ iconOrientation: p = "vertical",
14
+ "data-testid": M,
15
+ isOnline: B = !0,
16
+ offlineViewProps: N,
17
+ hasActionIconForSingleAction: b = !0,
18
+ isSingleActionTriggeredOnClick: L = !0,
19
+ isFloatingActionButtonOnMobile: S = !1,
20
+ floatingButtonPaddingInsertTarget: m,
21
+ floatingButtonScreenOffsetType: v = "buttonAppBar"
19
22
  }) => {
20
- const [i, n] = g(!1), M = f(() => {
21
- n(!i);
22
- }, [i]), a = f(() => {
23
+ const [r, n] = j(!1), a = x(() => {
23
24
  n(!1);
24
- }, []);
25
- h(() => {
26
- n(s !== null ? s : !1);
27
- }, [s]);
28
- const r = k(
29
- () => o.filter((e) => e.isAllowed !== !1),
30
- [o]
31
- );
32
- return r.length === 0 ? null : /* @__PURE__ */ t(
33
- p,
25
+ }, []), w = x(() => {
26
+ r ? a() : n(!0);
27
+ }, [r, a]);
28
+ D(() => {
29
+ n(c !== null ? !!c : !1);
30
+ }, [c]);
31
+ const s = d(
32
+ () => u.filter((e) => e.isAllowed !== !1),
33
+ [u]
34
+ ), { isDesktop: A } = E(), l = S && !A, k = d(() => {
35
+ if (s.length === 1 && b) {
36
+ const e = s[0];
37
+ if (typeof e.leadingBlock == "string")
38
+ return e.leadingBlock;
39
+ }
40
+ return p === "vertical" ? "more_vert" : "more_horiz";
41
+ }, [s, p, b]), C = d(() => m ? document.querySelector(m) : null, [m]);
42
+ if (s.length === 0) return null;
43
+ const y = /* @__PURE__ */ i(
44
+ F,
34
45
  {
35
- className: l.menu,
36
- "data-testid": u ?? "selection-menu",
37
- open: i,
46
+ className: o(t.menu, l && t.fabMenu),
47
+ "data-testid": M ?? "selection-menu",
48
+ open: r,
38
49
  surfaceOnly: !0,
39
- hoistToBody: !0,
50
+ hoistToBody: !l,
40
51
  onClose: a,
41
- trigger: /* @__PURE__ */ t(
42
- I,
52
+ trigger: /* @__PURE__ */ i(
53
+ z,
43
54
  {
44
- className: l.icon,
45
- variant: i ? "filled-primary" : void 0,
55
+ className: o(t.icon, l && t.fabIcon),
56
+ variant: r || l ? "filled-primary" : void 0,
57
+ iconSize: l ? "xlarge" : void 0,
46
58
  "data-testid": "open-button",
47
- icon: c === "vertical" ? "more_vert" : "more_horiz",
48
- onClick: M
59
+ icon: k,
60
+ onClick: () => {
61
+ s.length === 1 && L ? (s[0].onClick?.(), a()) : w();
62
+ },
63
+ "data-icon": k
49
64
  }
50
65
  ),
51
- children: d ? /* @__PURE__ */ t(w, { "data-testid": "selection-list", children: r.map((e) => /* @__PURE__ */ v(y, { children: [
52
- e.dividerBefore && /* @__PURE__ */ t(m, {}),
53
- /* @__PURE__ */ t(
54
- C,
66
+ children: B ? /* @__PURE__ */ i(V, { "data-testid": "selection-list", children: s.map((e, I) => /* @__PURE__ */ g(q, { children: [
67
+ e.dividerBefore && /* @__PURE__ */ i(h, {}),
68
+ /* @__PURE__ */ i(
69
+ $,
55
70
  {
56
- className: l.listItem,
71
+ className: t.listItem,
57
72
  ...e,
58
73
  leadingBlockType: "icon",
59
74
  "data-testid": e["data-testid"],
@@ -62,20 +77,53 @@ import '../../assets/ContextMenu.css';const V = "_menu_148i2_1", B = "_icon_148i
62
77
  }
63
78
  }
64
79
  ),
65
- e.dividerAfter && /* @__PURE__ */ t(m, {})
66
- ] }, x(`LI_${e.primaryText?.toString()}_`))) }) : /* @__PURE__ */ t(
67
- L,
80
+ e.dividerAfter && /* @__PURE__ */ i(h, {})
81
+ ] }, `LI_${e.primaryText?.toString()}_${I}`)) }) : /* @__PURE__ */ i(
82
+ T,
68
83
  {
69
84
  size: "small",
70
85
  variant: "filled",
71
- className: l.offlineViewMargin,
72
- ..._
86
+ className: t.offlineViewMargin,
87
+ ...N
73
88
  }
74
89
  )
75
90
  },
76
91
  "selectionmenu"
77
92
  );
93
+ if (l) {
94
+ const e = /* @__PURE__ */ i(
95
+ "div",
96
+ {
97
+ className: o(
98
+ t.fabContainer,
99
+ v === "buttonAppBar" && t.buttonAppBar,
100
+ v === "fullScreen" && t.fullScreen
101
+ ),
102
+ "data-testid": "fab-container",
103
+ children: y
104
+ }
105
+ );
106
+ return /* @__PURE__ */ g(_, { children: [
107
+ f(e, document.body),
108
+ f(
109
+ /* @__PURE__ */ i(
110
+ "div",
111
+ {
112
+ className: o(t.scrim, r && t.scrimVisible),
113
+ onClick: a,
114
+ "data-testid": "context-menu-scrim"
115
+ }
116
+ ),
117
+ document.body
118
+ ),
119
+ C && f(
120
+ /* @__PURE__ */ i("div", { className: t.fabSpacer, "data-testid": "fab-spacer" }),
121
+ C
122
+ )
123
+ ] });
124
+ }
125
+ return y;
78
126
  };
79
127
  export {
80
- q as ContextMenu
128
+ X as ContextMenu
81
129
  };
@@ -13,6 +13,7 @@ import "../../orderBy-Ce85KqD6.js";
13
13
  import "../../index-CuHybtft.js";
14
14
  import "../SharedComponentsPermissionProvider/PermissionContext.js";
15
15
  import { Modal as B } from "../Modal/Modal.js";
16
+ import "react-dom";
16
17
  import { LoadingSpinner as M } from "../LoadingSpinner/Default/LoadingSpinner.js";
17
18
  import "react-hook-form";
18
19
  import "../../get-CBFiuc3f.js";
@@ -26,7 +27,7 @@ import '../../assets/DeleteModal.css';const j = "_bulletPoint_bd412_1", A = "_mo
26
27
  modal: A,
27
28
  colorDanger: G,
28
29
  crossedOut: q
29
- }, co = ({
30
+ }, mo = ({
30
31
  title: r,
31
32
  confirmButtonLabel: t,
32
33
  cancelButtonLabel: n,
@@ -123,5 +124,5 @@ import '../../assets/DeleteModal.css';const j = "_bulletPoint_bd412_1", A = "_mo
123
124
  impossibleDeleteHeader: r
124
125
  }) => /* @__PURE__ */ o(l, { level: 1, themeColor: "text-primary-on-background", children: r });
125
126
  export {
126
- co as DeleteModal
127
+ mo as DeleteModal
127
128
  };
@@ -8,6 +8,7 @@ import "../../react-tooltip.min-Dkz5ltCC.js";
8
8
  import "../../orderBy-Ce85KqD6.js";
9
9
  import "../../index-CuHybtft.js";
10
10
  import "../SharedComponentsPermissionProvider/PermissionContext.js";
11
+ import "react-dom";
11
12
  import "../../uniqueId-CJo-XRQb.js";
12
13
  import { LoadingSpinner as w } from "../LoadingSpinner/Default/LoadingSpinner.js";
13
14
  import "react-hook-form";
@@ -23,7 +24,7 @@ import '../../assets/EmptyView.css';const C = "_emptyViewContainer_19inl_1", V =
23
24
  medium: x,
24
25
  small: N,
25
26
  title: g
26
- }, H = ({
27
+ }, J = ({
27
28
  title: e,
28
29
  subtitle: r,
29
30
  height: m = "medium",
@@ -57,5 +58,5 @@ import '../../assets/EmptyView.css';const C = "_emptyViewContainer_19inl_1", V =
57
58
  }
58
59
  );
59
60
  export {
60
- H as EmptyView
61
+ J as EmptyView
61
62
  };
@@ -14,6 +14,7 @@ import { SearchBar as I } from "../SearchBar/SearchBar.js";
14
14
  import "../../index-CuHybtft.js";
15
15
  import "../SharedComponentsPermissionProvider/PermissionContext.js";
16
16
  import { Modal as K } from "../Modal/Modal.js";
17
+ import "react-dom";
17
18
  import "../../uniqueId-CJo-XRQb.js";
18
19
  import { Select as M } from "../Select/Select.js";
19
20
  import "react-hook-form";
@@ -31,7 +32,7 @@ import '../../assets/Filter.css';const T = "_filterButton_1qtzn_1", V = "_relati
31
32
  dense: Q,
32
33
  filterModal: U,
33
34
  borderRadius: X
34
- }, xt = ({
35
+ }, yt = ({
35
36
  clearFilterText: u,
36
37
  closeCallback: d,
37
38
  currentFilter: i,
@@ -203,5 +204,5 @@ import '../../assets/Filter.css';const T = "_filterButton_1qtzn_1", V = "_relati
203
204
  );
204
205
  };
205
206
  export {
206
- xt as Filter
207
+ yt as Filter
207
208
  };
@@ -806,7 +806,7 @@ var f = Gt.strings, ye = (
806
806
  });
807
807
  ot.displayName = "Modal";
808
808
  ot.propTypes = Oe;
809
- const be = "_modal_lk11w_1", Ie = "_gap_lk11w_14", Ne = "_title_lk11w_19", Be = "_titleWithoutCloseIcon_lk11w_32", De = "_closeButton_lk11w_36", Fe = "_errorBanner_lk11w_48", y = {
809
+ const be = "_modal_1l955_1", Ie = "_gap_1l955_18", Ne = "_title_1l955_23", Be = "_titleWithoutCloseIcon_1l955_36", De = "_closeButton_1l955_40", Fe = "_errorBanner_1l955_52", y = {
810
810
  modal: be,
811
811
  gap: Ie,
812
812
  title: Ne,
@@ -7,6 +7,7 @@ import "../../react-tooltip.min-Dkz5ltCC.js";
7
7
  import "../../orderBy-Ce85KqD6.js";
8
8
  import "../../index-CuHybtft.js";
9
9
  import "../SharedComponentsPermissionProvider/PermissionContext.js";
10
+ import "react-dom";
10
11
  import "../../uniqueId-CJo-XRQb.js";
11
12
  import "react-hook-form";
12
13
  import "../../get-CBFiuc3f.js";
@@ -15,7 +16,7 @@ import "../../isString-BNdV0Jpg.js";
15
16
  import "../../omit-BWQrFyQ-.js";
16
17
  import "../../types/Time.js";
17
18
  import "../../react.esm-DF7i80eN.js";
18
- const D = ({ children: o }) => {
19
+ const M = ({ children: o }) => {
19
20
  const { isDesktop: t } = m();
20
21
  return /* @__PURE__ */ i(
21
22
  r,
@@ -28,5 +29,5 @@ const D = ({ children: o }) => {
28
29
  );
29
30
  };
30
31
  export {
31
- D as ModulePadding
32
+ M as ModulePadding
32
33
  };
@@ -0,0 +1,17 @@
1
+ import './assets/contextMenu.css';const n = "_menu_1bdpq_1", _ = "_icon_1bdpq_6", e = "_listItem_1bdpq_10", c = "_offlineViewMargin_1bdpq_20", b = "_fabContainer_1bdpq_24", i = "_buttonAppBar_1bdpq_29", t = "_fullScreen_1bdpq_34", o = "_fabIcon_1bdpq_39", s = "_fabMenu_1bdpq_43", a = "_scrim_1bdpq_47", p = "_scrimVisible_1bdpq_60", r = "_fabSpacer_1bdpq_65", f = {
2
+ menu: n,
3
+ icon: _,
4
+ listItem: e,
5
+ offlineViewMargin: c,
6
+ fabContainer: b,
7
+ buttonAppBar: i,
8
+ fullScreen: t,
9
+ fabIcon: o,
10
+ fabMenu: s,
11
+ scrim: a,
12
+ scrimVisible: p,
13
+ fabSpacer: r
14
+ };
15
+ export {
16
+ f as s
17
+ };
@@ -8,25 +8,26 @@ import "../react-tooltip.min-Dkz5ltCC.js";
8
8
  import "../orderBy-Ce85KqD6.js";
9
9
  import "../index-CuHybtft.js";
10
10
  import "../components/SharedComponentsPermissionProvider/PermissionContext.js";
11
+ import "react-dom";
11
12
  import "../uniqueId-CJo-XRQb.js";
12
13
  import "react-hook-form";
13
14
  import "../get-CBFiuc3f.js";
14
15
  import "../omit-BWQrFyQ-.js";
15
16
  import { DATE_FORMAT as c } from "../types/Time.js";
16
17
  import "../react.esm-DF7i80eN.js";
17
- const d = (t) => t.split(" ").map((r) => r.charAt(0).toUpperCase() + r.slice(1).toLowerCase()).join(" "), f = (t) => Object.fromEntries(
18
+ const P = (t) => t.split(" ").map((r) => r.charAt(0).toUpperCase() + r.slice(1).toLowerCase()).join(" "), f = (t) => Object.fromEntries(
18
19
  Object.entries(t).map(([r, i]) => [r, o(i)])
19
- ), o = (t) => n(t) ? t.map(o) : p(t) ? f(t) : s(t) ? t.trim() : t, P = (t, r, i = 2) => {
20
+ ), o = (t) => n(t) ? t.map(o) : p(t) ? f(t) : s(t) ? t.trim() : t, U = (t, r, i = 2) => {
20
21
  if (!r) return "0";
21
22
  const m = e[t];
22
23
  return r.toLocaleString(m, { maximumFractionDigits: i });
23
- }, U = (t, r = "en-US", i = c) => a.includes(r) ? new Intl.DateTimeFormat(
24
+ }, y = (t, r = "en-US", i = c) => a.includes(r) ? new Intl.DateTimeFormat(
24
25
  e[r],
25
26
  i
26
27
  ).format(new Date(t)) : new Intl.DateTimeFormat(r, i).format(new Date(t));
27
28
  export {
28
- d as capitalizeFirstLetters,
29
- U as getFormattedDateTime,
30
- P as getNumberAsLocaleString,
29
+ P as capitalizeFirstLetters,
30
+ y as getFormattedDateTime,
31
+ U as getNumberAsLocaleString,
31
32
  f as trimLeadingAndTrailingSpaces
32
33
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hortiview/shared-components",
3
3
  "description": "This is a shared component library. It should used in the HortiView platform and its modules.",
4
- "version": "2.23.5",
4
+ "version": "2.24.0",
5
5
  "type": "module",
6
6
  "repository": "https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared",
7
7
  "author": "Falk Menge <falk.menge.ext@bayer.com>",
@@ -1 +0,0 @@
1
- ._menu_148i2_1{width:15.875rem;border-radius:.5rem!important}._icon_148i2_6{color:var(--lmnt-theme-on-secondary-inactive)}._listItem_148i2_10 [class*=mdc-list-item__start]{color:var(--lmnt-theme-on-secondary-inactive);align-self:center!important;margin:0 1rem!important}._listItem_148i2_10 [class*=mdc-list-item__end]{color:var(--lmnt-theme-on-secondary-inactive)}._offlineViewMargin_148i2_20{margin:.5rem}