@hortiview/shared-components 2.11.1 → 2.12.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 +19 -0
- package/README.md +135 -3
- package/dist/components/Allowed/Allowed.d.ts +21 -0
- package/dist/components/Allowed/Allowed.js +13 -0
- package/dist/components/Allowed/Allowed.test.d.ts +1 -0
- package/dist/components/Allowed/Allowed.test.js +49 -0
- package/dist/components/AllowedButton/AllowedButton.d.ts +51 -0
- package/dist/components/AllowedButton/AllowedButton.js +28 -0
- package/dist/components/AllowedButton/AllowedButton.test.d.ts +1 -0
- package/dist/components/AllowedButton/AllowedButton.test.js +126 -0
- package/dist/components/AllowedIconButton/AllowedIconButton.d.ts +43 -0
- package/dist/components/AllowedIconButton/AllowedIconButton.js +23 -0
- package/dist/components/AllowedIconButton/AllowedIconButton.test.d.ts +1 -0
- package/dist/components/AllowedIconButton/AllowedIconButton.test.js +121 -0
- package/dist/components/Filter/Filter.js +7 -4
- package/dist/components/FormComponents/FormDatePicker/FormDatePicker.js +2 -10
- package/dist/components/FormComponents/FormNumber/CustomTextField.js +1 -1
- package/dist/components/GenericTable/GenericTable.js +7 -4
- package/dist/components/ModulePadding/ModulePadding.js +7 -4
- package/dist/components/PermissionChecks/PermissionService.d.ts +35 -0
- package/dist/components/PermissionChecks/PermissionService.js +28 -0
- package/dist/components/PermissionChecks/PermissionService.test.d.ts +1 -0
- package/dist/components/PermissionChecks/PermissionService.test.js +83 -0
- package/dist/components/PermissionChecks/mockedPermissions.d.ts +24 -0
- package/dist/components/PermissionChecks/mockedPermissions.js +299 -0
- package/dist/components/SharedComponentsPermissionProvider/PermissionContext.d.ts +17 -0
- package/dist/components/SharedComponentsPermissionProvider/PermissionContext.js +11 -0
- package/dist/components/SharedComponentsPermissionProvider/SharedComponentsPermissionProvider.d.ts +32 -0
- package/dist/components/SharedComponentsPermissionProvider/SharedComponentsPermissionProvider.js +17 -0
- package/dist/components/Stepper/components/StepperHeader.js +4 -12
- package/dist/index-CuHybtft.js +51 -0
- package/dist/{isString-BNaBRq3S.js → isString-BifemsUQ.js} +1 -1
- package/dist/main.d.ts +8 -0
- package/dist/main.js +74 -58
- package/dist/{omit-uTAyrUTm.js → omit-Tf2F0V8l.js} +183 -183
- package/dist/services/UtilService.js +16 -13
- package/dist/test-utils.d.ts +82 -0
- package/dist/test-utils.js +32 -0
- package/dist/types/Permission.d.ts +153 -0
- package/dist/types/Permission.js +4 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [2.12.0](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/compare/v2.11.2...v2.12.0) (2026-02-02)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add a permission context and provider to be able to access permissions inside of shared components ([7462a97](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/7462a970c02a715eb8edb526d5afc4c50f829de3))
|
|
6
|
+
* add loadingspinner to AllowedButton ([a999ba5](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/a999ba53f1ae6806ec6a39b1443dc419fc0c2587))
|
|
7
|
+
* add permission types and enum from main repo ([f3da7fb](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/f3da7fb003158daf2e0901fe37cb90af5982af1d))
|
|
8
|
+
* add permissionService ([fabef22](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/fabef220f5f85773bc4044b069c8241b42987fad))
|
|
9
|
+
* add storybooks for newly introduced components ([7d79ea3](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/7d79ea3286f3775fe5ee30a432de7abf777c8995))
|
|
10
|
+
* addnew permission components to readMe + export new components from main ([1fbe431](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/1fbe4316e5e424b9c2bbcfc27e3ae03e427b1e31))
|
|
11
|
+
* introduce button components that are permission-gated ([c811d31](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/c811d3198bb72f32007db3a37c8eb822b341f1f9))
|
|
12
|
+
* introduce get permissionFromModulePermissionToken ([523ff1b](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/523ff1b8d9c08af701a0318efb0e260cbf436431))
|
|
13
|
+
|
|
14
|
+
## [2.11.2](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/compare/v2.11.1...v2.11.2) (2026-01-27)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* adjusted stepperHeader invalid prop values ([7542b35](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/commit/7542b358125e8b4460ededdc4c2e08d00499c189)), closes [#13800](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/issues/13800)
|
|
19
|
+
|
|
1
20
|
## [2.11.1](https://dev.azure.com/sdundc/HV%20Platform/_git/HortiView-Frontend-Shared/compare/v2.11.0...v2.11.1) (2026-01-22)
|
|
2
21
|
|
|
3
22
|
### Styles
|
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ Additionally the library provides form components using [react-hook-form](https:
|
|
|
54
54
|
1. [OfflineView](#offlineview)
|
|
55
55
|
1. [OnboardingBanner](#onboardingbanner)
|
|
56
56
|
1. [OverflowTooltip](#overflowtooltip)
|
|
57
|
+
1. [Permissions](#permissions)
|
|
57
58
|
1. [SafeForm](#safeform)
|
|
58
59
|
1. [ScrollBar](#scrollbar)
|
|
59
60
|
1. [SearchBar](#searchbar)
|
|
@@ -932,6 +933,137 @@ const longText =
|
|
|
932
933
|
</OverflowTooltip>;
|
|
933
934
|
```
|
|
934
935
|
|
|
936
|
+
### Permissions
|
|
937
|
+
|
|
938
|
+
This shared library provides different hooks and component that can be used to check wether a user is allowed to proceed with an action, view a component or not.
|
|
939
|
+
|
|
940
|
+
In order for this component to correctly determine whether a user has the appropriate permissions, a basic setup must be implemented. The following section explains how:
|
|
941
|
+
|
|
942
|
+
#### Setup
|
|
943
|
+
|
|
944
|
+
#### 1. Wrap your app with the SharedComponentsPermissionProvider
|
|
945
|
+
|
|
946
|
+
In your main application file (e.g., `App.tsx` or `main.tsx`), wrap your app with the `PermissionProvider`:
|
|
947
|
+
|
|
948
|
+
```tsx
|
|
949
|
+
import { SharedComponentsPermissionProvider } from '@hortiview/shared-components';
|
|
950
|
+
|
|
951
|
+
function App() {
|
|
952
|
+
// Get these values from your store or state management
|
|
953
|
+
// In this example `Zustand` stores are used
|
|
954
|
+
const userPermissions = useClaimsIdentityStore(state => state.userPermissions);
|
|
955
|
+
const activeOrganizationId = useActiveOrganizationStore(state => state.activeOrganization?.id);
|
|
956
|
+
|
|
957
|
+
return (
|
|
958
|
+
<SharedComponentsPermissionProvider
|
|
959
|
+
userPermissions={userPermissions}
|
|
960
|
+
activeOrganizationId={activeOrganizationId}
|
|
961
|
+
>
|
|
962
|
+
<YourAppComponents />
|
|
963
|
+
</SharedComponentsPermissionProvider>
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
#### 2.1 Use the Allowed component anywhere in your app
|
|
969
|
+
|
|
970
|
+
Once the provider is set up, you can use `<Allowed />` anywhere in your component tree without passing props. The wrapped component is conditionally rendered depending on the user permissions:
|
|
971
|
+
|
|
972
|
+
```tsx
|
|
973
|
+
import { Allowed } from '@hortiview/shared-components';
|
|
974
|
+
|
|
975
|
+
function MyComponent() {
|
|
976
|
+
return (
|
|
977
|
+
<Allowed neededPermissions={["organization-read", "organization-edit"]} entityId={organizationId}>
|
|
978
|
+
<OrganizationSettings />
|
|
979
|
+
<Allowed/>
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
#### 2.2 Use the useIsAllowed-hook anywhere in your app
|
|
985
|
+
|
|
986
|
+
The `useIsAllowed`-hook works similar to the `<Allowed />` component. Instead of being used as a wrapper it returns a boolean. That value can be used to conditionally render components or control application logic based on user permissions.
|
|
987
|
+
|
|
988
|
+
```tsx
|
|
989
|
+
import { useIsAllowed } from '@hortiview/shared-components';
|
|
990
|
+
|
|
991
|
+
function MyComponent() {
|
|
992
|
+
const isAllowed = useIsAllowed();
|
|
993
|
+
|
|
994
|
+
// Check if user has permission to edit members for the current organization
|
|
995
|
+
const canEditMembers = isAllowed(['member-edit', 'member-create'], orgId);
|
|
996
|
+
|
|
997
|
+
return <MembersList isDisabled={!canEditMembers} />;
|
|
998
|
+
}
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
#### 2.3 Use the AllowedButton or AllowedIconButton anywhere in your app
|
|
1002
|
+
|
|
1003
|
+
Once the provider is set up, you can use `AllowedButton` and `AllowedIconButton` anywhere in your component tree without passing props. Both buttons expect a permissions array and call the `useIsAllowed` under the hood.
|
|
1004
|
+
|
|
1005
|
+
> **Note:** Both components will automatically hide (return `null`) if the user doesn't have the required permissions. The button will only render when the user is authorized.
|
|
1006
|
+
|
|
1007
|
+
**AllowedButton Example:**
|
|
1008
|
+
|
|
1009
|
+
```jsx
|
|
1010
|
+
import { AllowedButton, Permission } from '@hortiview/shared-components';
|
|
1011
|
+
|
|
1012
|
+
function FarmActions({ farmId }) {
|
|
1013
|
+
return (
|
|
1014
|
+
<div>
|
|
1015
|
+
<AllowedButton
|
|
1016
|
+
data-testid='edit-season-button'
|
|
1017
|
+
label='Edit Season'
|
|
1018
|
+
permissions={['season-edit']}
|
|
1019
|
+
entityId={organizationId}
|
|
1020
|
+
buttonVariant='filled'
|
|
1021
|
+
onClick={() => handleEditSeason(seasonId)}
|
|
1022
|
+
/>
|
|
1023
|
+
|
|
1024
|
+
<AllowedButton
|
|
1025
|
+
data-testid='delete-season-button'
|
|
1026
|
+
label='Delete Season'
|
|
1027
|
+
permissions={['season-delete']}
|
|
1028
|
+
entityId={organizationId}
|
|
1029
|
+
buttonVariant='danger'
|
|
1030
|
+
onClick={() => handleDeleteSeason(seasonId)}
|
|
1031
|
+
/>
|
|
1032
|
+
</div>
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
**AllowedIconButton Example:**
|
|
1038
|
+
|
|
1039
|
+
```jsx
|
|
1040
|
+
import { AllowedIconButton, Permission } from '@hortiview/shared-components';
|
|
1041
|
+
|
|
1042
|
+
function BlockActions({ blockId, farmId }) {
|
|
1043
|
+
return (
|
|
1044
|
+
<div>
|
|
1045
|
+
<AllowedIconButton
|
|
1046
|
+
data-testid='edit-block-icon'
|
|
1047
|
+
icon='edit'
|
|
1048
|
+
permissions={['block-edit', 'block-create']}
|
|
1049
|
+
entityId={farmId}
|
|
1050
|
+
iconButtonVariant='filled-primary'
|
|
1051
|
+
onClick={() => handleEditBlock(blockId)}
|
|
1052
|
+
/>
|
|
1053
|
+
|
|
1054
|
+
<AllowedIconButton
|
|
1055
|
+
data-testid='delete-block-icon'
|
|
1056
|
+
icon='delete'
|
|
1057
|
+
permissions={['block-delete']}
|
|
1058
|
+
entityId={farmId}
|
|
1059
|
+
iconButtonVariant='filled-danger'
|
|
1060
|
+
onClick={() => handleDeleteBlock(blockId)}
|
|
1061
|
+
/>
|
|
1062
|
+
</div>
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
935
1067
|
### SafeForm
|
|
936
1068
|
|
|
937
1069
|
A wrapper around a form that prevents accidental submission when pressing Enter in input fields.
|
|
@@ -1018,10 +1150,10 @@ import { Select } from '@hortiview/shared-components';
|
|
|
1018
1150
|
### Stepper
|
|
1019
1151
|
|
|
1020
1152
|
Provides a simple stepper component, which contains the step indicators, a title text and buttons to navigate forwards and backwards.
|
|
1021
|
-
The component is available in two types:
|
|
1153
|
+
The component is available in two types:
|
|
1022
1154
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1155
|
+
- 'default' (steps as dots and a centered step title below them)
|
|
1156
|
+
- 'description' (n/n numerical progress, description/title right of it)
|
|
1025
1157
|
|
|
1026
1158
|
**Be aware that you need to control the Stepper flow by your own, like:**
|
|
1027
1159
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { HVPermissions } from '../../types/Permission';
|
|
3
|
+
|
|
4
|
+
type AllowedProps = {
|
|
5
|
+
neededPermissions: HVPermissions[];
|
|
6
|
+
entityId: string | undefined;
|
|
7
|
+
disclaimer?: boolean;
|
|
8
|
+
disclaimerText?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A component wrapper which takes permissions and an entity id (as a fallback the activeOrganization id is used) the permission is for.
|
|
12
|
+
* Returns the children if the user has the permission for the entity, nothing otherwise (or an optional disclaimer)
|
|
13
|
+
* @param children - the children to return if allowed to see it
|
|
14
|
+
* @param entityId - the entity id, the permission is for (FarmOrganizationId, VendorOrganizationId or FarmId)
|
|
15
|
+
* @param neededPermissions - the permissions needed to see the children
|
|
16
|
+
* @param disclaimer - if true a disclaimer is shown, otherwise not. (default: false)
|
|
17
|
+
* @param disclaimerText - the text to show in the disclaimer (default: 'You are not allowed to see the content of this section.')
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
export declare const Allowed: ({ children, neededPermissions, entityId, disclaimer, disclaimerText, }: PropsWithChildren<AllowedProps>) => string | number | boolean | import("react/jsx-runtime").JSX.Element | Iterable<import('react').ReactNode> | null | undefined;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { Disclaimer as i } from "../Disclaimer/Disclaimer.js";
|
|
3
|
+
import { useIsAllowed as n } from "../PermissionChecks/PermissionService.js";
|
|
4
|
+
const d = ({
|
|
5
|
+
children: o,
|
|
6
|
+
neededPermissions: e,
|
|
7
|
+
entityId: t,
|
|
8
|
+
disclaimer: r = !1,
|
|
9
|
+
disclaimerText: l = "You are not allowed to see the content of this section."
|
|
10
|
+
}) => n()(e, t) ? o : r ? /* @__PURE__ */ s(i, { text: l }) : null;
|
|
11
|
+
export {
|
|
12
|
+
d as Allowed
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { s as n } from "../../react.esm-BbMrgZCM.js";
|
|
3
|
+
import { renderWithPermissions as s } from "../../test-utils.js";
|
|
4
|
+
import { HVMainPermissions as o } from "../../types/Permission.js";
|
|
5
|
+
import { Allowed as i } from "./Allowed.js";
|
|
6
|
+
import { d as l, t as d, g as t } from "../../vi.CjhMlMwf-CKxPQtd6.js";
|
|
7
|
+
l("Test Allowed", () => {
|
|
8
|
+
d("should return the children if allowed to see them", () => {
|
|
9
|
+
const r = /* @__PURE__ */ e("div", { children: "Allowed to see children" });
|
|
10
|
+
s(
|
|
11
|
+
/* @__PURE__ */ e(i, { neededPermissions: [o.CurrentUserRead], entityId: "General", children: r }),
|
|
12
|
+
{
|
|
13
|
+
userPermissions: {
|
|
14
|
+
General: [o.CurrentUserRead]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
), t(n.getByText("Allowed to see children")).toBeInTheDocument();
|
|
18
|
+
}), d("should return empty wrapper when not allowed to see children", () => {
|
|
19
|
+
const r = /* @__PURE__ */ e("div", { children: "Allowed to see children" });
|
|
20
|
+
s(
|
|
21
|
+
/* @__PURE__ */ e(i, { neededPermissions: [o.CurrentUserRead], entityId: "General", children: r }),
|
|
22
|
+
{
|
|
23
|
+
userPermissions: {
|
|
24
|
+
General: []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
), t(n.queryByText("Allowed to see children")).not.toBeInTheDocument();
|
|
28
|
+
}), d("should return disclaimer when defined & not allowed to see children", () => {
|
|
29
|
+
const r = /* @__PURE__ */ e("div", { children: "Allowed to see children" });
|
|
30
|
+
s(
|
|
31
|
+
/* @__PURE__ */ e(
|
|
32
|
+
i,
|
|
33
|
+
{
|
|
34
|
+
disclaimer: !0,
|
|
35
|
+
neededPermissions: [o.CurrentUserRead],
|
|
36
|
+
entityId: "General",
|
|
37
|
+
children: r
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
{
|
|
41
|
+
userPermissions: {
|
|
42
|
+
General: []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
), t(n.queryByText("Allowed to see children")).not.toBeInTheDocument(), t(
|
|
46
|
+
n.getByText("You are not allowed to see the content of this section.")
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ButtonProps } from '@element-public/react-button';
|
|
2
|
+
import { HVPermissions } from '../../types/Permission';
|
|
3
|
+
|
|
4
|
+
export type AllowedButtonProps = Omit<ButtonProps, 'buttonSize' | 'variant' | 'type' | 'label'> & {
|
|
5
|
+
/**
|
|
6
|
+
* This applies the html button type (button, submit, reset) or the anchor media type when used with the href property.
|
|
7
|
+
* Default: 'button'
|
|
8
|
+
*/
|
|
9
|
+
buttonType?: 'button' | 'submit' | 'reset' | string;
|
|
10
|
+
/**
|
|
11
|
+
* Applies the selected style to the button.
|
|
12
|
+
* Default: 'filled'
|
|
13
|
+
*/
|
|
14
|
+
variant?: 'filled' | 'outlined' | 'danger' | 'text' | 'success';
|
|
15
|
+
/**
|
|
16
|
+
* data-testid used for testing.
|
|
17
|
+
*/
|
|
18
|
+
'data-testid': string;
|
|
19
|
+
/**
|
|
20
|
+
* The entity id, the permission is for (FarmOrganizationId, VendorOrganizationId or FarmLocationId).
|
|
21
|
+
*/
|
|
22
|
+
entityId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Id used for linking the button to a form.
|
|
25
|
+
*/
|
|
26
|
+
formId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* If true, the button shows a loading spinner instead of the label.
|
|
29
|
+
* While true, the button is disabled.
|
|
30
|
+
*/
|
|
31
|
+
isLoading?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Can be a string or JSX.Element for setting a loading spinner or custom component.
|
|
34
|
+
*/
|
|
35
|
+
label: string | JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* The permissions needed to enable the button. If the array is empty, the button is always enabled.
|
|
38
|
+
*/
|
|
39
|
+
permissions: HVPermissions[];
|
|
40
|
+
/**
|
|
41
|
+
* Changes the the size of the button.
|
|
42
|
+
* Default: 'medium'
|
|
43
|
+
*/
|
|
44
|
+
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A permission-gated button component that only renders when the user has the required permissions.
|
|
48
|
+
* @param {AllowedButtonProps} props - Component properties
|
|
49
|
+
* @returns {JSX.Element | null} The button component or null if permissions are not met
|
|
50
|
+
*/
|
|
51
|
+
export declare const AllowedButton: ({ buttonType, variant, "data-testid": testId, label, size, isLoading, ...props }: AllowedButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { B as r } from "../../index.es-B0QNuIUR.js";
|
|
3
|
+
import { Allowed as s } from "../Allowed/Allowed.js";
|
|
4
|
+
import { LoadingSpinner as a } from "../LoadingSpinner/Default/LoadingSpinner.js";
|
|
5
|
+
const c = ({
|
|
6
|
+
buttonType: d = "button",
|
|
7
|
+
variant: n = "filled",
|
|
8
|
+
"data-testid": o,
|
|
9
|
+
label: m,
|
|
10
|
+
size: l = "medium",
|
|
11
|
+
isLoading: i = !1,
|
|
12
|
+
...t
|
|
13
|
+
}) => /* @__PURE__ */ e(s, { neededPermissions: t.permissions, entityId: t.entityId, children: /* @__PURE__ */ e(
|
|
14
|
+
r,
|
|
15
|
+
{
|
|
16
|
+
buttonSize: l,
|
|
17
|
+
"data-testid": `allowed-button-${o}`,
|
|
18
|
+
disabled: t.disabled || i,
|
|
19
|
+
form: t.formId,
|
|
20
|
+
type: d,
|
|
21
|
+
variant: n,
|
|
22
|
+
...t,
|
|
23
|
+
children: i ? /* @__PURE__ */ e(a, { size: "sm", spinnerOnly: !0 }) : m
|
|
24
|
+
}
|
|
25
|
+
) });
|
|
26
|
+
export {
|
|
27
|
+
c as AllowedButton
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { s as t, f as u } from "../../react.esm-BbMrgZCM.js";
|
|
3
|
+
import { renderWithPermissions as n } from "../../test-utils.js";
|
|
4
|
+
import { HVMainPermissions as i } from "../../types/Permission.js";
|
|
5
|
+
import { AllowedButton as d } from "./AllowedButton.js";
|
|
6
|
+
import { u as b } from "../../useBreakpoints-MzTZ0tCT.js";
|
|
7
|
+
import { v as l, d as m, b as c, t as r, g as e } from "../../vi.CjhMlMwf-CKxPQtd6.js";
|
|
8
|
+
l.mock("../../hooks/useBreakpoints", () => ({
|
|
9
|
+
useBreakpoints: l.fn()
|
|
10
|
+
}));
|
|
11
|
+
m("AllowedButton Test", () => {
|
|
12
|
+
c(() => {
|
|
13
|
+
l.mocked(b).mockReturnValue({
|
|
14
|
+
isDesktop: !0,
|
|
15
|
+
isMobile: !1,
|
|
16
|
+
isTablet: !1,
|
|
17
|
+
isDesktopNavbar: !0
|
|
18
|
+
});
|
|
19
|
+
}), r("renders button with no permissions required", () => {
|
|
20
|
+
n(
|
|
21
|
+
/* @__PURE__ */ s(d, { "data-testid": "test", label: "Add", permissions: [], entityId: void 0 })
|
|
22
|
+
), e(t.getByText("Add")).toBeInTheDocument();
|
|
23
|
+
}), r("renders outlined button variant", () => {
|
|
24
|
+
n(
|
|
25
|
+
/* @__PURE__ */ s(
|
|
26
|
+
d,
|
|
27
|
+
{
|
|
28
|
+
"data-testid": "regular-outlined-button",
|
|
29
|
+
label: "Add",
|
|
30
|
+
variant: "outlined",
|
|
31
|
+
permissions: [],
|
|
32
|
+
entityId: void 0
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
const o = t.getAllByRole("button")[0];
|
|
37
|
+
e(o).toHaveClass("mdc-button--outlined"), e(t.getByTestId("allowed-button-regular-outlined-button")).toBeInTheDocument();
|
|
38
|
+
}), r("renders full-width button", () => {
|
|
39
|
+
n(
|
|
40
|
+
/* @__PURE__ */ s(
|
|
41
|
+
d,
|
|
42
|
+
{
|
|
43
|
+
"data-testid": "regular-button",
|
|
44
|
+
label: "Add",
|
|
45
|
+
fullWidth: !0,
|
|
46
|
+
permissions: [],
|
|
47
|
+
entityId: void 0
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
const o = t.getAllByRole("button")[0];
|
|
52
|
+
e(o).toHaveClass("lmnt-button--full-width"), e(t.getByTestId("allowed-button-regular-button")).toBeInTheDocument();
|
|
53
|
+
}), r("calls onClick when clicked", () => {
|
|
54
|
+
const o = l.fn();
|
|
55
|
+
n(
|
|
56
|
+
/* @__PURE__ */ s(
|
|
57
|
+
d,
|
|
58
|
+
{
|
|
59
|
+
"data-testid": "test-onclick-button",
|
|
60
|
+
label: "Add",
|
|
61
|
+
onClick: o,
|
|
62
|
+
permissions: [],
|
|
63
|
+
entityId: void 0
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
const a = t.getAllByRole("button")[0];
|
|
68
|
+
u.click(a), e(o).toHaveBeenCalled(), e(t.getByTestId("allowed-button-test-onclick-button")).toBeInTheDocument();
|
|
69
|
+
}), r("renders disabled button when prop is used", () => {
|
|
70
|
+
n(
|
|
71
|
+
/* @__PURE__ */ s(
|
|
72
|
+
d,
|
|
73
|
+
{
|
|
74
|
+
"data-testid": "disabled-button",
|
|
75
|
+
label: "Add",
|
|
76
|
+
permissions: [i.SuperUser],
|
|
77
|
+
entityId: "farm-123",
|
|
78
|
+
disabled: !0
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
{
|
|
82
|
+
userPermissions: {
|
|
83
|
+
"farm-123": [i.SuperUser]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
), e(t.getByText("Add")).toBeInTheDocument(), e(t.getByTestId("allowed-button-disabled-button")).toBeInTheDocument();
|
|
87
|
+
const o = t.getAllByRole("button")[0];
|
|
88
|
+
e(o).toHaveAttribute("disabled");
|
|
89
|
+
}), r("hides button when permission is missing", () => {
|
|
90
|
+
n(
|
|
91
|
+
/* @__PURE__ */ s(
|
|
92
|
+
d,
|
|
93
|
+
{
|
|
94
|
+
"data-testid": "missing",
|
|
95
|
+
label: "Add",
|
|
96
|
+
permissions: [i.SuperUser],
|
|
97
|
+
entityId: "farm-123"
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
{
|
|
101
|
+
userPermissions: {
|
|
102
|
+
"farm-123": [i.FarmsUpdate, i.FarmsRead]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
), e(t.queryByText("Add")).not.toBeInTheDocument(), e(t.queryByTestId("allowed-button-missing")).not.toBeInTheDocument();
|
|
106
|
+
}), r("renders button when permission exists", () => {
|
|
107
|
+
n(
|
|
108
|
+
/* @__PURE__ */ s(
|
|
109
|
+
d,
|
|
110
|
+
{
|
|
111
|
+
"data-testid": "enabled",
|
|
112
|
+
label: "Add",
|
|
113
|
+
permissions: [i.SuperUser],
|
|
114
|
+
entityId: "farm-123"
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
{
|
|
118
|
+
userPermissions: {
|
|
119
|
+
"farm-123": [i.SuperUser]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
), e(t.getByText("Add")).toBeInTheDocument(), e(t.getByTestId("allowed-button-enabled")).toBeInTheDocument();
|
|
123
|
+
const o = t.getAllByRole("button")[0];
|
|
124
|
+
e(o).not.toHaveAttribute("disabled");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { IconButtonProps } from '@element-public/react-icon-button';
|
|
2
|
+
import { HVPermissions } from '../../types/Permission';
|
|
3
|
+
|
|
4
|
+
export type AllowedIconButtonProps = Omit<IconButtonProps, 'variant' | 'iconType' | 'iconSize'> & {
|
|
5
|
+
/**
|
|
6
|
+
* data-testid used for testing.
|
|
7
|
+
*/
|
|
8
|
+
'data-testid': string;
|
|
9
|
+
/**
|
|
10
|
+
* The entity id, the permission is for (FarmOrganizationId, VendorOrganizationId or FarmLocationId).
|
|
11
|
+
*/
|
|
12
|
+
entityId?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Id used for linking the button to a form.
|
|
15
|
+
*/
|
|
16
|
+
formId?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Variants prefixed with filled- will add a background color to the icon according to the theme.
|
|
19
|
+
* Variants prefixed with color- will change the color of the icon according to the theme.
|
|
20
|
+
* Default: 'filled-primary'
|
|
21
|
+
*/
|
|
22
|
+
variant?: 'filled-danger-alt' | 'filled-danger' | 'filled-primary' | 'filled-secondary' | 'color-primary' | 'color-secondary' | 'color-on-dark' | 'color-on-unknown-black' | 'secondary-on-surface';
|
|
23
|
+
/**
|
|
24
|
+
* The icon style to be used (filled, outlined, two-tone, rounded, sharp).
|
|
25
|
+
* Default: 'filled'
|
|
26
|
+
*/
|
|
27
|
+
iconType?: 'filled' | 'outlined' | 'two-tone' | 'rounded' | 'sharp';
|
|
28
|
+
/**
|
|
29
|
+
* The permissions needed to enable the button. If the array is empty, the button is always enabled.
|
|
30
|
+
*/
|
|
31
|
+
permissions: HVPermissions[];
|
|
32
|
+
/**
|
|
33
|
+
* Changes the the size of the icon button.
|
|
34
|
+
* Default: 'medium'
|
|
35
|
+
*/
|
|
36
|
+
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
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
|
|
42
|
+
*/
|
|
43
|
+
export declare const AllowedIconButton: ({ "data-testid": testId, variant, iconType, size, ...props }: AllowedIconButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
2
|
+
import { I as m } from "../../index.es-C1u7zUBz.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, entityId: t.entityId, 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
|
+
) });
|
|
21
|
+
export {
|
|
22
|
+
f as AllowedIconButton
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { s as t, f as u } from "../../react.esm-BbMrgZCM.js";
|
|
3
|
+
import { renderWithPermissions as i } from "../../test-utils.js";
|
|
4
|
+
import { HVMainPermissions as n } from "../../types/Permission.js";
|
|
5
|
+
import { AllowedIconButton as r } from "./AllowedIconButton.js";
|
|
6
|
+
import { u as c } from "../../useBreakpoints-MzTZ0tCT.js";
|
|
7
|
+
import { v as a, d as m, b, t as d, g as e } from "../../vi.CjhMlMwf-CKxPQtd6.js";
|
|
8
|
+
a.mock("../../hooks/useBreakpoints", () => ({
|
|
9
|
+
useBreakpoints: a.fn()
|
|
10
|
+
}));
|
|
11
|
+
m("AllowedIconButton Test", () => {
|
|
12
|
+
b(() => {
|
|
13
|
+
a.mocked(c).mockReturnValue({
|
|
14
|
+
isDesktop: !0,
|
|
15
|
+
isMobile: !1,
|
|
16
|
+
isTablet: !1,
|
|
17
|
+
isDesktopNavbar: !0
|
|
18
|
+
});
|
|
19
|
+
}), d("renders icon button with no permissions required", () => {
|
|
20
|
+
i(
|
|
21
|
+
/* @__PURE__ */ s(
|
|
22
|
+
r,
|
|
23
|
+
{
|
|
24
|
+
"data-testid": "icon-button",
|
|
25
|
+
icon: "add",
|
|
26
|
+
permissions: [],
|
|
27
|
+
entityId: void 0
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
const o = t.getAllByRole("button")[0];
|
|
32
|
+
e(o).toHaveClass("lmnt-icon-button"), e(t.getByTestId("allowed-icon-button-icon-button")).toBeInTheDocument();
|
|
33
|
+
}), d("renders secondary variant icon button", () => {
|
|
34
|
+
i(
|
|
35
|
+
/* @__PURE__ */ s(
|
|
36
|
+
r,
|
|
37
|
+
{
|
|
38
|
+
"data-testid": "secondary-button",
|
|
39
|
+
icon: "add",
|
|
40
|
+
variant: "filled-secondary",
|
|
41
|
+
permissions: [],
|
|
42
|
+
entityId: void 0
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
const o = t.getAllByRole("button")[0];
|
|
47
|
+
e(o).toHaveClass("lmnt-icon-button--fill-secondary"), e(t.getByTestId("allowed-icon-button-secondary-button")).toBeInTheDocument();
|
|
48
|
+
}), d("calls onClick when clicked", () => {
|
|
49
|
+
const o = a.fn();
|
|
50
|
+
i(
|
|
51
|
+
/* @__PURE__ */ s(
|
|
52
|
+
r,
|
|
53
|
+
{
|
|
54
|
+
"data-testid": "test-onclick-button",
|
|
55
|
+
icon: "add",
|
|
56
|
+
onClick: o,
|
|
57
|
+
permissions: [],
|
|
58
|
+
entityId: void 0
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
const l = t.getAllByRole("button")[0];
|
|
63
|
+
u.click(l), e(o).toHaveBeenCalled(), e(t.getByTestId("allowed-icon-button-test-onclick-button")).toBeInTheDocument();
|
|
64
|
+
}), d("renders disabled icon button when prop is used", () => {
|
|
65
|
+
i(
|
|
66
|
+
/* @__PURE__ */ s(
|
|
67
|
+
r,
|
|
68
|
+
{
|
|
69
|
+
"data-testid": "disabled-button",
|
|
70
|
+
icon: "add",
|
|
71
|
+
permissions: [n.SuperUser],
|
|
72
|
+
entityId: "farm-123",
|
|
73
|
+
disabled: !0
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
{
|
|
77
|
+
userPermissions: {
|
|
78
|
+
"farm-123": [n.SuperUser]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
), e(t.getByTestId("allowed-icon-button-disabled-button")).toBeInTheDocument();
|
|
82
|
+
const o = t.getAllByRole("button")[0];
|
|
83
|
+
e(o).toHaveAttribute("disabled");
|
|
84
|
+
}), d("hides icon button when permission is missing", () => {
|
|
85
|
+
i(
|
|
86
|
+
/* @__PURE__ */ s(
|
|
87
|
+
r,
|
|
88
|
+
{
|
|
89
|
+
"data-testid": "missing",
|
|
90
|
+
icon: "add",
|
|
91
|
+
permissions: [n.SuperUser],
|
|
92
|
+
entityId: "farm-123"
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
{
|
|
96
|
+
userPermissions: {
|
|
97
|
+
"farm-123": [n.FarmsUpdate, n.FarmsRead]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
), e(t.queryByTestId("allowed-icon-button-missing")).not.toBeInTheDocument();
|
|
101
|
+
}), d("renders icon button when permission exists", () => {
|
|
102
|
+
i(
|
|
103
|
+
/* @__PURE__ */ s(
|
|
104
|
+
r,
|
|
105
|
+
{
|
|
106
|
+
"data-testid": "enabled",
|
|
107
|
+
icon: "add",
|
|
108
|
+
permissions: [n.SuperUser],
|
|
109
|
+
entityId: "farm-123"
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
{
|
|
113
|
+
userPermissions: {
|
|
114
|
+
"farm-123": [n.SuperUser]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
), e(t.getByTestId("allowed-icon-button-enabled")).toBeInTheDocument();
|
|
118
|
+
const o = t.getAllByRole("button")[0];
|
|
119
|
+
e(o).not.toHaveAttribute("disabled");
|
|
120
|
+
});
|
|
121
|
+
});
|